Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

Updated for Magento 2! No Frills Magento Layout is the only Magento
front end book you'll ever need. Get your copy
today!

So far in this series, we’ve been putting our route configuration in the config/routes.yaml files. When you’re working on a local Symfony application, this is the right thing to do.

However — if you’re distributing a symfony bundle, or an entire application like Sylius, putting your routes into the config/routes.yaml file isn’t a great option. You could do this — keep a list of routes in your README.md and then tell users to manually copy and paste them into their own config/routes.yaml file, (in fact, this is what was going on in the early days of Symfony 2), but that’s putting a lot of burden on the user, and making your code or system extra hard to use.

Fortunately, in 2019, Symfony offers code-distributing developers a number of systems for loading routes that will fit right into their regular workflows. Today we’re going to explore how Symfony loads routes, and take a look at how Sylius has organized their routes.

This article assumes you’ve installed Sylius using the standard edition package. Specifics may vary slightly if you’ve installed Sylius in a different way, but the concepts should stay the same.

Symfony Kernel

At the core of every Symfony application is an object called the Kernel. The Kernel object is responsible for bootstrapping Symfony’s container and a few key services, and then running the application. Two of those services are the http_kernel and router services — both of which are responsible for request and URL routing.

Although this code is in your src/ folder, this is all from the Symfony project (i.e. not Sylius). This is the boilerplate Kernel code generated when you create a new Symfony application.

The import method on the RouteCollectionBuilder object is responsible for loading the route configuration files from disk. The glob argument above tells the RouteCollectionBuilder that the paths we’re passing in are glob patterns. These patterns are mostly standard glob patterns, with a few custom Symfony enhancements. If we swap those variables out for their values

Sylius Routing Files

These are Sylius’s route files. The Sylius team has placed these files here so that the Symfony Kernel will load them. If you’re debugging a page or service endpoint in a Sylius application, these files are the best place to start.

However: There’s more files in play than just these three. If we open up the sylius_shop.yaml, we’ll see the following

That’s only three routes. However, thanks to our lasttwo articles, we know that the sylius_shop and sylius_shop_payum routes are resource routes, which means they’re loading route files from different bundles.

Debugging Routes

Manually following a trail of routing files can be a tedious affair. Fortunately, Symfony has mature tooling. If you’re running Sylius in developer mode, the bottom of every page should feature Symfony’s debugging toolbar.

If you open up the debugging bar’s routes section, you’ll see a list of every path rule Symfony tried to match before finding the current route

This isn’t every route in the system — but when you’re debugging routes it’s a great place to start.

Above and Beyond Core Symfony

So that’s the basics — next we need to talk about places the Sylius team went a little above and beyond standard Symfony routing.

We haven’t talked about it yet, but one of Symfony’s configuration based features allows system and application developers to create a custom route loader. Once written and configured, a custom rout loader will look for routes of a certain type (i.e. their type:... configuration field), and then use the value in the resource:... configuration field to create a route.

Sylius uses a custom route loader as part of its resource configuration system. We’ll be talking about this resource configuration system in full at a later date — today we’re just going to focus on this custom route loader as a stand alone feature.

You can identify a routing configuration that takes advantage of the custom loader by looking at its type

For example, in the above route configuration the type is sylius.resource_api. This type is one supported by Sylius’s custom route loader, the other type being a sylius.resource. When Symfony is parsing the route files and encounters either of these types, it will pass the resource value to Sylius’s custom route loader for further processing.

The resource value itself is a little strange. Let’s take a closer look.

That | character is a YAML construct. It tells the YAML parser that the next bit of indented text should be treated as a string. Symfony passes this resource value to Sylius’s custom route loader as a raw string, and then the loader parses it as YAML in order to get a PHP data structure.

Not the most elegant of solutions, but it does allow Sylius to send a large amount of structured data to the custom route loader.

So what does the resource loader do? It automatically creates up to six routes at once for common CRUD-ish actions — specifically show, index, create, update, delete, and bulkDelete. The embedded yaml-string can be thought of a mini-domain-specific-language whose features include creating routes.

As you can see from the debug:router output above, this is definitely a Sylius route. However, you will not find this route’s configuration anywhere in a standard routing configuration file. That’s because it’s created by the custom route loader.

You’ll be wise to keep these auto-generated routes in mind when you’re debugging your system. In addition to the debug:router command, Sylius also offers us a sylius:debug:resource command, which will let us provide a resource’s alias (sylius.order)

Most of this information is NOT related to routing — as we said we’ll cover resources in full at a later date. However, we can see that this resource will always create routes that use the Sylius\Bundle\CoreBundle\Controller\OrderController controller.

Sylius Custom Defaults

Another bit of non-standard-Symfony-but-things-symfony’s-flexibility-allows is the _sylius default.

In our earlierarticles we mentioned that Symfony’s routing configuration files are very strict with their top level keys. If you try to create an invalid configuration

route_id:
my_custom_key: ...

Symfony will reject the configuration because it contains an unknown key (my_custom_key).

You may recall from last time that defaults is a bit of a grab bag of legacy features and things that don’t fit in anywhere else. Also, unlike the top level configuration — a sub-key of defaults can have any name.

defaults:
_sylius:
//...

Sylius uses a default named _sylius to “smuggle” extra values into the route, which also means they’ll be available from the request/controller.

Covering what each of these fields do is beyond the scope of this article, but keep these in mind as you explore the Sylius source and try to reason about Sylius code.

Wrap Up

Sylius’s implementation of routes show both the power, and the danger, of a configuration based MVC system. The danger is confusion — both the custom route loader and the special _sylius variable are non-obvious. Even an experienced Symfony developer might trip over them the first time they’re encountered.

The power of this system is the Sylius team was able to build exactly the abstraction they wanted, and one that (presumably) helped their team accomplish something they wouldn’t have been able to otherwise.

Having only looked at the routing system, it’s hard to say whether this abstraction, the Sylius Resource System, will be as useful for third party developers as it’s been for the Sylius core team. In the coming articles we’ll be taking a deeper look at this system.