A picky developer's take on PHP static site generators: Sculpin
I was walking around the park a few days ago. It was a bright day with a clear sky. I could see kids playing and their parents chatting a few steps away. "This is nice," I thought, "but what about static site generators for PHP?" Well, that's obviously a made-up story. I wasn't at the park. It was actually raining. But that’s really what I was thinking.
Sign up for a free Contentful account and start building in moments.
There are trends that come and go, but one particular language (and its ecosystem) has stayed: PHP. I've been a PHP developer for more than 10 years. Even though PHP is the punchline of all serious developers, it really has become a terrific platform in recent years. PHP 7 brought fantastic performance improvements and additions, such as scalar type declarations and error handling through the Throwable
interface. The ecosystem blossomed thanks to Composer (arguably one of the best package managers out there), shared standards in the form of PSRs, and high quality frameworks and components (Symfony, Laravel, Zend Framework, Slim...).
Static sites have not seemed to leave a mark in the PHP world. Now they're cool once again, so I guess you either die a hero, or you live long enough to see yourself become the villain, but then be reborn a hero once again? Who knows.
What I do know, however, is that us PHP developers should have access to the quality static site generators (SSG) that others have. I'm thinking about Jekyll, Middleman, Hugo, Metalsmith, Gatsby, and Wyam. So on that sunny day at the park rainy day in my home office, I set out to discover more about the status of static site generators in PHP land.
More than meets the eye
Even though the community around static sites in PHP might not be noisy, it is vibrant and active. There are a lot of options to choose from! I boiled the list down to the four SSGs with the highest star count on GitHub: Sculpin, Jigsaw, Couscous, and Spress. I chose a hands-on approach. I tried downloading them and getting them to work, while exploring how they're made and how they make you work. For reference, I'm running PHP 7.1 on macOS, with Composer 1.6 installed globally. This week, we're exploring Sculpin! (Next week, Jigsaw!)
Sculpin (about 1200 ★)
You've probably heard of Sculpin, as it's the most popular in the list. It's based on the Symfony framework, uses Twig templates, and supports Markdown files. As it's my first time trying it, I'll use their official documentation, and particularly their Getting started guide. The first thing they ask, besides having Composer installed, is to download a skeleton project and install the dependencies:
I got a whole bunch of warning when running composer install
. Apparently, the signature of some Composer plugin provided by Sculpin is not what it should be, so it's triggering multiple deprecation notices. Not a hard fail, though. Let's keep going.
I'm mainly interested in understanding how everything works under the hood. So I do what I always do when I find a new, interesting PHP project: I look at the composer.json
file. A default Sculpin project requires (of course) Sculpin itself, dflydev/embedded-composer
, Assetic, and then the trio Bootstrap, jQuery, and HighlightJS from the components
organization. I usually prefer to keep frontend dependencies separated from my PHP ones, but I can let it slide here.
Next step is taking a look at Sculpin's own composer.json
, which is located in vendor/sculpin/sculpin/composer.json
. The selection of installed packages is interesting, but there's a huge problem here: all Symfony components are locked at version 2.3, which was indeed an LTS but has reached end of life for about a year. Taking a look at the develop
branch on the Sculpin GitHub repo, I see that it's using version 3.2 of the components, which is better but still less than ideal. It should be at least on 3.4, which is another LTS. I also notice that the latest Sculpin stable version has been published about a year ago which prompts me to look for info about its status. I quickly find a blog post where the old maintainer announces the transition to a new one, but that's also from a while back, so I'm not really sure what's going on. If anyone has more recent updates, please let me know!
Let's run the server:
And we're live! Let's open the URL. Behold the spectacle that Sculpin presents to us.
Visuals are a little underwhelming, but there's everything we need out of the box. We're viewing a version of the website which will be rebuilt on every change we make to the source files.
The getting started guide ends with a brief explanation of how we should add blog posts. I realize that I need to take an actual look at the documentation, so give me a few minutes and I'll be right back.
The directory structure
Sculpin stores almost everything in the /source
directory — with the exception of some configuration in /app
— and will build your website in /output_{env}
. This "all-in-one" setup is perhaps convenient, but I'd rather have one place for storing data, one place for storing templates, and optionally one place for storing code or configuration that connects the two.
The contents of /source
will actually be what the final build will look like, so any file in there serves a purpose. Your sitemap, your robots file, your favicon, they all should be saved in here. This means that the directory structure you use here will be the same that will be applied to the final build. Want to have a /project/something/something-else.html
in your website? Well, create that very file in the /source
directory. It's certainly intuitive! You need to pay attention to certain directories, whose names start with an underscore. Here I have _layouts
, _views
, and _posts
.
The directories named _layouts
and _views
are, as you can probably guess, related to formatting certain parts of your HTML pages. If you know something about Twig and how its inheritance mechanism works, think of it like this: _layouts
will include the templates that you define using {% extends 'layout.html.twig' %}
. They are the most outer shells of the pages you're going to generate. _Views
provides content-type-specific templates, which are defined only for the Post content type in our example site (so we only have _views/post.html
). If we were to create another content type called Project (more on that later), we'd create a specific template in /_views/project.html
.
Whereas the connection between views and templates is rather explicit (views have an actual extends
statement), the way posts are managed is a bit more implicit. When they are rendered, Sculpin will automagically store the contents of your blog posts in a special page.blocks.content
Twig variable, which you can then use (remember to use the |raw
filter, otherwise you will escape regular HTML tags, too).
Now, let's take a look at the /source/_posts
directory. Here we find multiple files. Most of them are Markdown, one is written using Textile. Choice is great, but I can stick to Markdown without regrets! These files present a front matter section (a block of YAML at the beginning of a file), where you can define extra parameters that will be available in the page
variable in the post view. For instance, this section:
will yield page.title
and page.categories
. After the front matter, everything will be treated as the page body, and will end up in the aforementioned page.blocks.content
variable. Doesn't seem too complex. If I wanted to add a new blog post, I'd just have to create a new file here, fill it with the contents I want, and Sculpin would pick it up automatically. Nice!
Custom content types
Blog posts are great, but they're not the end-all-be-all in life. What if I wanted to have a "Project" content type that I could use to display my portfolio? Project pages would show something like a title, a URL, possibly some tags to explain the tech I've used, a screenshot, and a description. Sculpin allows us to do just that. In order to create a custom content type, let's add this to the sculpin_content_types
section in /app/config/sculpin_kernel.yml
:
Sculpin's documentation covers well-enough what each key means, so I won't discuss that. Instead, I'd rather focus on something else. We're not defining any special property or field that the projects will have. In fact, content types in Sculpin do not really define how data is structured, because conceptually a content entry equals text plus some optional metadata. This definition is probably okay for simple projects, but the lack of structure might be a problem for more complex websites.
Is Sculpin Contentful-ready?
Sculpin provides ways to be extended, which mostly revolve around creating bundles and integrating them within your project. I must admit that the documentation here is a bit lacking, as it cuts short a bit too often, without providing too many examples. The thing I found especially weird is that there is no clear, suggested way of adding your own scripts without resorting to creating a full bundle.
The thought of having to create an actual Symfony bundle, defining the autoloader in composer.json
, registering it in app/SculpinKernel.php
(which doesn't exist in the skeleton project), then having to look at some third-party code to understand how to hook up the event system. This can be arguably a lot to do, especially considering how Symfony itself is moving away from bundles, where they are not meant to be reused in multiple projects.
So what if I were to configure a Sculpin website to import Contentful entries? Well, the easiest thing would probably be to work around it, rather than with it. I would run composer require contentful/contentful
to download our PHP SDK, and then possibly create a contentful.php
file where I manually fetch entries and build the files I need, which I would later dump in the _source
directory.
The ideal solution, though, would be to create a bundle that sets up an event listener early in the build process, then to create all files built from Contentful entries. A problem I can see with this approach is that Sculpin would fetch all entries from Contentful on every run, which means a lot of overhead for situations where you're simply tweaking a template. A better separation between content and presentation would help mitigate this problem.
Verdict on Sculpin php
Rather than a static site-generator, Sculpin is a blog generator which can be extended to do something else. It provides logical out-of-the-box defaults for creating your own blog (there are also a couple nifty scripts for publishing it to an S3 bucket), and it's easy enough to pick up and go, especially if you have some knowledge of how Twig works. It is also based on Symfony components, which means that Symfony bundles are supported (though I must admit I haven't tried using them).
There are a few downsides that are worth mentioning, alas. The most striking one is the obsolescence of the underlying infrastructure. Of course, with a SSG you're not really publishing that code, but it's still something that doesn’t fully put me at ease. Furthermore, content and presentation might be a little more separate. Right now everything is shoved into one huge directory, which goes against a clean project architecture (and even separation of concerns). Finally, content types are structured very loosely, with data entries being seen as a block of content with possibly some metadata attached.
In the next article, we're going to take a look at Jigsaw, so stay tuned! Ready to jump in now? Sign up for a free account with Contentful and start building in moments.