Building a Symfony bundle and Extending Sculpin

11th January, 2019

I've recently started using Sculpin as a site generator. However I wanted to add Gulp as a build tool. As a test of Sculpins extensibility I wanted to try and create a Symfony bundle that would run the gulp command during running the sculpin generate command.

I was using Gulp to compile and concatenate my SASS files, copy some JS files, and to generate some JSON files too.

Building a Symfony Bundle

From the Symfony website

A bundle is simply a structured set of files within a directory that implement a single feature. Each directory contains everything related to that feature, including PHP files, templates, stylesheets, JavaScript files, tests and anything else. Every aspect of a feature exists in a bundle and every feature lives in a bundle.

There's a lot of confusing conventions that may trip up a developer not too used to Symfony bundles and extensions.

The documentation seems to deal well with developing a package inside of a Symfony application, but I wanted to make this as a separate packagable bundle. Firstly, I created an empty composer project on my local machine, installed phpspec and sculpin as dev dependencies.

Creating a Composer package and using it locally

Using composer init creates a composer.json file in your source directory and interactively walks through the steps needed to make a package, like requiring vendor/package name.

To install my dependencies I ran composer require --dev phpspec/phpspec sculpin/sculpin.

I also set a PSR-4 namespace in my composer.json file so my files could be autoloaded.

{
    "autoload": {
        "psr-4": {
            "PeteMc\\Sculpin\\SculpinGulpBundle\\": "src/PeteMc/Sculpin/SculpinGulpBundle/"
        }
    }
}

I used phpspec to spec and generate my source code.

phpspec d PeteMc/Sculpin/SculpinGulpBundle/SculpinGulpBundle

running phpspec r offered to create the class file for me, I said yes or y.

The only thing I needed to do with my new bundle file is check that it is a version of the Symfony Kernel Bundle.

I added the following spec to make my spec file look like this:

# /spec/PeteMc/Sculpin/SculpinGulpBundle/SculpinGulpBundleSpec.php
<?php

namespace spec\PeteMc\Sculpin\SculpinGulpBundle;

use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Symfony\Component\HttpKernel\Bundle\Bundle;

class SculpinGulpBundleSpec extends ObjectBehavior
{
    function it_is_initializable()
    {
        $this->shouldHaveType('PeteMc\Sculpin\SculpinGulpBundle\SculpinGulpBundle');
    }

    function it_is_a_symfony_kernel_bundle()
    {
        $this->shouldImplement(Bundle::class);
    }
}

This failed my test, so then I could update my bundle source file to look like this:

# /src/PeteMc/Sculpin/SculpinGulpBundle/SculpinGulpBundle.php
<?php

namespace PeteMc\Sculpin\SculpinGulpBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class SculpinGulpBundle extends Bundle
{
}

That'll do!

Next I wanted to include this in my Sculpin site, but in this state it wasn't ready to host on Packagist. Fortunately with Composer you can have dependencies on local git projects, perfect for development. Popping back to that project, I edited my sculpin.json file to look a bit like this:

    "require": {
        ...,
        "petemc/sculpin-gulp-bundle": "@dev"
    },
    "repositories": [
        {
            "type":"vcs",
            "url":"/local/path/to/bundle/sculpin-gulp-bundle"
        }
    ]

And created a file called SculpinKernel.php, in the app dir, as instructed in the Sculpin docs.

# app/SculpinKernel.php
<?php

class SculpinKernel extends \Sculpin\Bundle\SculpinBundle\HttpKernel\AbstractKernel
{
    protected function getAdditionalSculpinBundles()
    {
        return [
            'PeteMc\Sculpin\SculpinGulpBundle\SculpinGulpBundle'
        ];
    }
}

Remember to run sculpin update every time you change and commit your bundle, to get the latest changes.

Now my bundle was included, but not yet doing anything. Back to the bundle project.

Writing the GulpGenerator

I wanted to spec a simple class that will run the gulp command every time the sculpin generate command is run. I used phpspec to generate my spec and source files with the following command

phpspec d PeteMc/Sculpin/SculpinGulpBundle/GulpGenerator

And I added a couple of specs, one to test it implements the Symfony event subscriber interface, and two to check it runs the gulp command if there are any changed files.

# spec/PeteMc/Sculpin/SculpinGulpBundle/GulpGeneratorSpec.php
<?php

namespace spec\PeteMc\Sculpin\SculpinGulpBundle;

use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sculpin\Core\Event\SourceSetEvent;
use Sculpin\Core\Source\SourceInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Process\Process;

class GulpGeneratorSpec extends ObjectBehavior
{
    function it_implements_symfony_event_subscriber_interface()
    {
        $this->shouldImplement(EventSubscriberInterface::class);
    }

    function it_runs_the_gulp_command_if_there_are_changed_files(SourceSetEvent $sourceSetEvent, SourceInterface $source)
    {
        $sourceSetEvent->updatedSources()->willReturn([$source]);
        $this->getProcess($sourceSetEvent)->shouldBeLike(new Process('gulp sculpin'));
    }

    function it_does_not_run_the_gulp_command_if_there_are_no_changed_files(SourceSetEvent $sourceSetEvent)
    {
        $sourceSetEvent->updatedSources()->willReturn([]);
        $this->getProcess($sourceSetEvent)->shouldReturn(null);
    }
}

I added the following simple implementation

# src/PeteMc/Sculpin/SculpinGulpBundle/GulpGenerator.php
<?php

namespace PeteMc\Sculpin\SculpinGulpBundle;

use Sculpin\Core\Event\SourceSetEvent;
use Sculpin\Core\Sculpin;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Process\Process;

class GulpGenerator implements EventSubscriberInterface
{
    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents()
    {
        return [
            Sculpin::EVENT_AFTER_RUN => 'runGulp'
        ];
    }

    /**
     * @param  SourceSetEvent $sourceSetEvent
     */
    public function runGulp(SourceSetEvent $sourceSetEvent)
    {
        if ($process = $this->getProcess($sourceSetEvent)) {
            echo 'Running gulp.' . PHP_EOL;
            $process->run();
            echo $process->getOutput() . PHP_EOL;
        }
    }

    /**
     * @param  SourceSetEvent $sourceSetEvent
     *
     * @return null|Process
     */
    public function getProcess(SourceSetEvent $sourceSetEvent)
    {
        if ($sourceSetEvent->updatedSources()) {
            return new Process('gulp sculpin');
        }
        return null;
    }
}

I used the Symfony Process component to wrap an executable sub-process.

Wiring it together

Now to access the Symfony Dependency Injection Container you have to create a PHP class which takes the name of your bundle, so SculpinGulpBundle but then replaces the word "Bundle" with "Extension", so in my case becomes SculpinGulpExtension. Oh, and that has to go in the DependencyInjection directory. This is just convention but it will load this file magically if you do as above, so I didn't ask any questions.

Here's what mine looked like:

# src/PeteMc/Sculpin/SculpinGulpBundle/DependencyInjection/SculpinGulpExtension.php
<?php

namespace PeteMc\Sculpin\SculpinGulpBundle\DependencyInjection;

use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;

class SculpinGulpExtension extends Extension
{
    /**
     * {@inheritdoc}
     */
    public function load(array $configs, ContainerBuilder $container)
    {
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
        $loader->load('services.xml');
    }
}

In this load method, I created an XmlFileLoader, specified a directory called Resources/config, and loaded a file called services.xml, as I see is the convention.

Here's the interesting part, in services.xml you can wire up your application, add tags for the event listener, set parameters and all kinds of runtime magic!

<!-- src/PeteMc/Sculpin/SculpinGulpBundle/Resources/config/services.xml -->
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
    <parameters>
        <parameter key="sculpin_gulp.generator.class">PeteMc\Sculpin\SculpinGulpBundle\GulpGenerator</parameter>
    </parameters>

    <services>
        <service id="sculpin_gulp.generator" class="%sculpin_gulp.generator.class%">
            <tag name="kernel.event_subscriber" />
        </service>
    </services>

</container>

Here we're defining a service, which will be available in the container through the unique name sculpin_gulp.generator. The class attribute tells Symfony which class to use. There is a convention to parameterize this, so I did above. I don't think this is necessary though and looks to me to just add more lines of beautiful XML. The magic tag on my service tells Symfony to register my class as a listener or subscriber to events coming from the kernel. This is how we will sync with Sculpin, as Sculpin fires 6 different events at different stages of the generate lifecycle.

Save and commit your hard work, and run a sculpin update back in the Sculpin web project to get the latest package changes, then run sculpin generate

➜  petemcfarlane git:(master) ✗ sculpin generate
Detected new or updated files
Generating: 100% (80 sources / 0.00 seconds)
Converting: 100% (125 sources / 0.13 seconds)
Formatting: 100% (125 sources / 0.03 seconds)
Running gulp.
[10:12:32] Using gulpfile ~/Sites/petemcfarlane/gulpfile.js
[10:12:32] Task 'sculpin' is not in your gulpfile
[10:12:32] Please check the documentation for proper gulpfile formatting

Processing completed in 1.05 seconds

Success, well, kind of. We can see from the above output that we did run gulp, but that Task 'sculpin' is not in your gulpfile. Simple, in your gulpfile.js I just added a task and referenced a call to my default task like so:

# gulpfile.js
...
gulp.task('sculpin', ['default']);

Run it again and it works!

➜  petemcfarlane git:(master) ✗ sculpin generate
Detected new or updated files
Generating: 100% (80 sources / 0.00 seconds)
Converting: 100% (125 sources / 0.16 seconds)
Formatting: 100% (125 sources / 0.04 seconds)
Running gulp.
[10:15:27] Using gulpfile ~/Sites/petemcfarlane/gulpfile.js
[10:15:27] Starting 'sass'...
[10:15:27] Starting 'bootstrap-js'...
[10:15:27] Finished 'bootstrap-js' after 38 ms
[10:15:28] Finished 'sass' after 712 ms
[10:15:28] Starting 'default'...
[10:15:28] Finished 'default' after 3.39 μs
[10:15:28] Starting 'sculpin'...
[10:15:28] Finished 'sculpin' after 2.19 μs

Processing completed in 1.61 seconds

Publishing to Packagist

This was pretty simple, I created a github repo, pushed my changes, and went through the Packagist submission process. Then in my website I could change my dependency to the public package, and remove my VCS repository.

composer require petemc/sculpin-gulp-bundle

Full source for package: https://github.com/petemcfarlane/sculpin-gulp-bundle

Tagged: php symfony sculpin composer packagist phpspec


Sculpin - the static site generator

11th January, 2019

Sculpin

I've started playing with Sculpin the static site generator written in PHP and Symfony. The idea is pretty cool - instead of having a CMS like Wordpress, needing a server running PHP, a database, needing to maintain plugins and security patches etc, Sculpin will convert markdown files to a static HTML website (old-school!). Then you just upload the generated HTML files to a webserver of your choice and serve these static pages. An advantage is this is far more efficient than running a full web application, and it requires a lot less of the maintenance and managements. You don't even need a webserver, you could just serve the static files from a web host like Amazon S3 or use GitHub pages for free!

It's not a new idea, there is a similar implementation in Ruby called Jekyll, but I decided to give Sculpin a go as its written in PHP.

It's ideal because I don't have very complex needs, I basically want to write text, with some code snippets and images here and there.

Installation

Installing Sculpin should have been quite simple, just download the phar and moving to my bin directory. Unfortunately there are some issues running Sculpin on PHP 7 with the latest downloadable phar so I cloned the latest source version from GitHub, and symlinked the binary executable from there to my bin dir instead. Sorted!

Using

I have found it quite easy to do most tasks following the documentation, but there a few things I didn't immediately understand from the docs that might be worth explaining.

sculpin.json and sculpin.lock are basically just composer.json and composer.lock but under a special Sculpin name. If you need to add a dependency, put it in the sculpin.json file and execute the sculpin update command.

The .sculpin directory is much like the vendor directory in a standard composer project, so you normally don't want to go touching files in there as it is managed by the Sculpin (composer) executables.

All of your content (posts and pages markdown files, images and other assets) go in the source/ directory. I find this a bit confusing because, as well as your content, it also mixes in your views, templates, and other files you may not expect to see in there. I would like to have these in separate locations.

The directories named starting output_* are generated by the sculpin application. This is the location where your final HTML, XML, CSS, JS, images etc. will be placed ready to be served. You will notice the directory suffixes map directly to the environments dev and prod. There are some differences here, for example posts tagged as draft only show in the dev environment. You probably don't want to manually edit any files in here, as any changes you make will likely be written over or lost when you next generate your site.

You can preview your site using the sculpin generate --watch --server command. The --watch option basically means generate will be called again every time a change is detected to your source files. The --server option is a shortcut to running the sculpin server command, it uses ReactPHP to create a local server, useful for testing, on http://localhost:8000 by default.

There is a handy bash file, publish.sh which contains a few sample steps you might use in order to deploy your site live, for example generating the production files, and rsyncing these to your web server. Simple, but effective! Because they are just static files you can probably get away with a relatively small, not-so-powerful webserver.

Issues

The only other problems I've run into is trying to pass variables into templates. There seems to be an index.html and blog.html template page in the source, which relate to the HTML files that are created, and I can edit the markdown at the top of the page to use variables in the scope. I just haven't figured out how to edit the template for a single post view.

And I'm not sure where I am supposed to put content that I don't want to be distributed into my web server, for example sass files, and raw markdown files. Maybe there is a way and I just can't find it in the documentation.

I wanted to update the theme slightly. I noticed it is using Bootstrap v2 so I decided to update that. I ended up creating a bundle to integrate Gulp, and using this to manage Bootstrap and my own custom SASS. Here's a post explaining that process

Tagged: php symfony sculpin


Value Objects, Entities and Services

11th January, 2019

Value Objects

Value objects are little jigsaw pieces that make up a large part of your domain code. They model real life values or ideas into objects. They are typically quite small, not because they are unimportant but because they encapsulate one single idea well. Examples might include distance, money, date, time, location, region, temperature, email address. They are typically formed from primitive values (ints, strings, arrays or other objects) but they carry much more information than their primitive parts. They are a great way to encapsulate validation rules and other behavior, all in one place. They can be considered equal based on their value, for example, one instance of a Length object with a value of 5km should be considered equivalent to another instance of a Length object with a value of 5km. If you give me a £5 note, it is considered the same as any other £5 note, which is not the same as a $5 bill! There is one key aspect that separates them from entities in that they don't have an identity in your domain.

Entities

Entities are similar to value objects in that they can have properties and methods, but they also have a unique identity which doesn't change over time. Take a User object, for example: this is typically uniquely identified with an ID. The user may change their address, password, name, even gender but they will remain the same, unique person. There may be two users in your system, both called "John Smith" but that doesn't mean they are the same person. To identify each different John Smith we assign them an identity or ID.

The difference can be quite subtle to pick up on at first, but it is very useful to distinguish for writing clean, declarative, decoupled code.

Services

A service, to be pretty vague, typically doesn't have a value itself (unlike a value object) and doesn't have an identity (unlike a entity would). A service can be some utility object that manages persistence, or it could be something that transforms your entities or applies an algorithm to your other domain objects, or perhaps a http web client that fetches data from an API. You may have services inside your domain, and also third party services dotted around your domain that plug into your application (like adapters in Hexagonal Architecture).

Tagged: oop php design