An extremely picky developer's take on PHP static site generators: Part 1 - Sculpin

I was walking around the park a few days ago. It was a bright day, 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 and that day it was actually raining, but that’s really what I was thinking.

When working in an industry like tech, you start noticing trends that come and go, but there's one language (and its ecosystem) which historically has not been too concerned with those trends, as it was doing its own thing: PHP. I've been a PHP developer for more than 10 years, and even though it is true that for a long while PHP has been the punchline of all serious developers, it is also true that in recent years it has become a terrific platform: PHP 7 brought fantastic performance improvements (not to mention additions such as scalar type declarations, or error handling through the Throwable interface) and 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…)

But one trend which has not seemed to leave a mark in the PHP world, yet, is static sites. 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, Wyam, and more. 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, and tell you all about it.

More than meets the eye

The first thing I realized was that even though the community around static sites in PHP might not make so much noise, it is very vibrant and active. There are actually a lot of options to choose from! For my own sanity, I boiled the list down to the four SSGs with the highest star count on GitHub: Sculpin, Jigsaw, Couscous, and Spress. To make a somewhat fair comparison, I chose a hands-on approach and 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!

Sculpin (about 1200 ★)

https://sculpin.io

You've probably heard of this, 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 installing the dependencies:

1
2
3
git clone https://github.com/sculpin/sculpin-blog-skeleton.git sculpin
cd sculpin
composer install

So first off, 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, so let's keep going.

As you may imagine, 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. If I'm being honest, 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 probably 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!

I decide that today I don't care about that: Sculpin might be already awesome and not in need of further development, for what I know, so let's get back to my project. Let's run the server:

1
vendor/bin/sculpin generate --watch --server

And we're live! Let's open the URL and behold the spectacle that Sculpin presents to us.

Running on localhost

Visuals are a little underwhelming, but there's everything we need out of the box. We're currently viewing a version of the website which will be rebuilt on every change we make to the source files.

Cool

The getting started guide pretty much ends with a brief explanation of how we should add blog posts. Here 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.

Sculpin directory structure

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. There is some Sculpin magic going on here, so let's figure out what these are.

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 instead content type-specific templates, which in our example site are defined only for the Post content type (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 personally I can stick to Markdown and not have regrets! Opening these files, you'll see that they 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:

1
2
3
title: My awesome blog post
categories:
    - misc

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. Seems like nothing too weird or 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, which I can 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. Well, thankfully Sculpin allows us to do just that so in order to create a custom content type, let's add this to the sculpin_content_types section in /app/config/sculpin_kernel.yml:

1
2
3
4
5
6
7
projects:
    type: path
    path: _projects
    singular_name: project
    layout: project
    enabled: true
    taxonomies: [tags]

Sculpin's documentation covers well-enough what each key means, so I won't discuss that. Instead, I'd rather focus on something else: as you may see, 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… Well, it can be arguably a lot to do, especially considering how Symfony itself it 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 probably be creating a bundle which sets up an event listener very early in the build process, then creating all files built from Contentful entries. A problem I can see with this approach is that on every run, Sculpin would fetch all entries from Contentful, which means a lot of overhead for situations where maybe you're simply tweaking a template. A better separation between content and presentation would probably help mitigate this problem.

Verdict

Rather than a static site-generator, Sculpin is a blog generator which can also be extended to do something else. It provides sane 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 perhaps 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 possibly 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!

Blog posts in your inbox

Subscribe to receive most important updates. We send emails once a month.