Sylius plugins: How to get to the starting blocks
”A good developer is lazy”
When we want to add new features to Sylius it is rarely a unique set of needs we want to address. Sure, the particular project will have its unique set of demands and we will address them with different solutions, but these solutions will have been used in other settings before. This is why we have reusable libraries and frameworks such as Symfony and Sylius.
When we face a particular problem in Symfony, re-usable code can be built as ”bundles”. Allowing us to reuse our solution within the context of other Symfony projects. When we face a particular problem in Sylius we write a variant of a Symfony bundle called a ”Sylius plugin”.
Being able to reuse or make public our solution will benefit many developers. Our future selves won’t have to build a solution from scratch anymore, which is how lazily good it is to build a Sylius plugin.
Building a Sylius plugin is not as easy as plug-and-play
It’s generally a good idea to start off with the official documentation on building a plugin when you want to extend Sylius. This article will highlight a few pain points to consider when we build a Sylius plugin, as this will complement the official documentation. These pain points are most readily addressed by following these recommendations:
- Follow the documentation.
- Symfony assumptions are being made, take heed.
- Gettings started is the hardest part.
A problem - a solution - a plugin
Recently I found myself in need of adding a payment method to Sylius. In my particular case, it was a Klarna Checkout payment method that I needed. No existing solution addressed my need. As Klarna is a popular payment provider in Sweden (and Nordic countries), it does not have as a wide spread customer base as Stripe or PayPal, but it serves millions of customers each year. Building a solution (a payment method for Klarna Checkout) would probably be re-used by third parties or in other projects where I will build an e-commerce solution for the Nordics. This is a pretty clear-cut case of where a Sylius plugin would be a suitable recourse.
We will set up the skeleton of a Klarna Checkout plugin! Note that this will not be an officially supported plugin or show up in the Sylius plugin store - these require Behat tests which we won’t be delving into in this article.
Getting to the starting block
We will start by creating a new project with the help of Composer and sylius/plugin-skeleton:
composer create-project sylius/plugin-skeleton VendorNameSyliusKlarnaGatewayPlugin
Note that it’s important to follow the naming convention. Symfony requires a "Bundle" postfix to load associated services. Sylius stipulates a vendor-name prefix and "Plugin" postfix. In my case, I would name it
AndersBjorklandSyliusKlarnaGatewayPlugin
.
This will make use of a skeleton project, packed with a full Sylius project to test your plugin against. It has an example plugin from the get-go which we will have to clear out if we do not want to browse it first. As Sylius requires BDD-tests for plugins to appear in the Sylius Plugin Store we will also see that it comes packed with a bunch of Behat tests to go along with the example plugin. So remember, clearing out the example plugin you should also clear out the tests too.
The skeleton plugin has some boilerplate to support its example plugin. You will want to make changes in composer.json and a bunch of files in both your project and in test/Application. The whole list of required changes to setup you up is listed at Sylius Plugins - Naming changes.
Set up name and namespaces
In composer.json:
"name": ”anders-bjorkland/sylius-klarna-gateway-plugin"
…
"autoload": {
"psr-4": {
"AndersBjorkland\\SyliusKlarnaGatewayPlugin\\": "src/",
"Tests\\AndersBjorkland\\SyliusKlarnaGatewayPlugin\\": "tests/"
}
},
Rename files
src/AcmeSyliusExamplePlugin.php -> src/AndersBjorklandSyliusKlarnaGatewayPlugin.php
src/DependencyInjection/AcmeSyliusExampleExtension.php -> src/DependencyInjection/AndersBjorklandSyliusKlarnaGatewayExtension.php
Adjust namespaces
The current namespaces will be Acme\SyliusExamplePlugin\…
, you can search and replace or manually edit these. For me, this will become AndersBjorkland\SyliusKlarnaGatewayPlugin\…
and I would change this in the files below.
- src/AndersBjorklandSyliusKlarnaGatewayPlugin.php
- src/DependencyInjection/AndersBjorklandSyliusKlarnaGatewayExtension.php
- src/DependencyInjection/Configuration.php
Similarly will be done for the Sylius application residing under the tests
folder:
tests/Application/Kernel.php has the namespace Tests\Acme\SyliusExamplePlugin\Application
, it would become Tests\AndersBjorkland\SyliusKlarnaGatewayPlugin\Application
.
Do not adjust the namespace for src/Kernel.php. It is set to work under the
App
namespace and is not required to be configured to work with the plugin. The same goes forpublic/index.php
.
Adjust use statements
When we change namespaces we will also have to update how the classes are loaded in different files. So update the following files with the correct use statements.
- tests/Application/public/index.php:
Tests\Acme\SyliusExamplePlugin\Application\Kernel
->Tests\AndersBjorkland\SyliusKlarnaGatewayPlugin\Application\Kernel
Add a plugin to load with the test-application
The test application will load bundles and Sylius plugins via the file tests/Application/config/bundles.php. The example plugin is present here, so change its name accordingly:
Acme\SyliusExamplePlugin\AcmeSyliusExamplePlugin::class
-> AndersBjorkland\SyliusKlarnaGatewayPlugin\AndersBjorklandSyliusKlarnaGatewayPlugin::class
Modify services configuration
If you are someone that prefers YAML over XML you will want to modify the Extension-file that is tasked with loading service configuration. So instead of using XmlFileLoader which is the current default in the example-plugin, make use of YamlFileLoader
instead. Where you want to store the services.yaml file is up to you, I prefer to have it at src/Resources/config/services.yaml. We will tell the loader to load the correct file:
src/DependencyInjection/AndersBjorklandSyliusKlarnaGatewayExtension.php:
public function load(array $configs, ContainerBuilder $container): void
{
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('services.yaml');
}
And more configuration!
Almost done with setting up a starting point for building a new plugin. We need to add a configuration node for the plugin. We will do that by modifying src/DependencyInjection/Configuration.php:
Change acme_sylius_example_plugin
to something akin to anders_bjorkland_sylius_klarna_gateway_plugin
.
Update composer autoloading
Update Composer, so it will autoload all the files correctly:
composer dump-autoload
Install assets and get some services running
The plugin skeleton addresses how to get started. There is a walk-through for a docker setup as well as a non-docker setup. I usually run a database in a Docker container and the application off of a native system. Minding this, here’s how we can set up such a system.
- Disable all services but the database in docker-compose.yaml.
Adjust tests/Application/.env:DATABASE_URL=mysql://root:mysql@127.0.0.1:3306/sylius_%kernel.environment%?charset=utf8mb4
- Start the Docker container from the project root with
docker-compose up -d
. - If dependencies have not been installed, from project-root, run
composer install
. - Change directory to tests/Application.
- Install assets and UI component. In tests/Application:
Runyarn install
Runyarn build
Runbin/console assets:install public
- Set up database and load fixtures. In tests/Application:
Runbin/console doctrine:database:create
Runbin/console doctrine:migrations:migrate
Runbin/console sylius:fixtures:load
- Start the web server. I prefer Symfony’s local web server. In tests/Application run
symfony serve -d
, alternativelyphp -S localhost:8000 public/index.php
- You can now view the Sylius test application at
localhost:8000/en_US
andlocalhost:8000/admin
Login credentials loaded with the fixtures:User: sylius
Password: sylius
It’s a long way to set up a development environment for Sylius plugins, and it is important to follow the instructions carefully. But doing it this way means we will have reusable components that we can use repeatedly, and more people can benefit from our work.
I hope your development goes well and I’m looking forward to seeing your work in the Sylius eco-system.
Hero image by Hanna Auramenka.