Drupal8.x and Symfony Deeper into Routing: Part II Series

Screenshot 2014-05-23 00.40.22

So today i went on reading the Drupal core element Access. I believe Access and the other elements, I call them like that, are the equivalent of components in Symfony. The difference is that they are coupled from mildly to badly. Let me explain what i mean. Drupal has also components but i prefer not to call them components because they are really all very similar in that they are `utils`. The component libraries are quaint and resolve one problem and even then they are a bit tiny coupled, however they are not the major components or players in Drupal. Drupal core is build of elements. Symfony is build on components with a layer of glue between bridges and framework bundles that do the coupling. In the case of Drupal, they are the core libraries or elements from now on.

So looking at the first element in Drupal I found an interesting element called Access. Access solves the problem of determining access to a route, the first class resource citizen in Drupal, naturally of a content management system. The agents that intervene on this problem are mainly and namely: An account, the request, the route name or route object itself. In practical terms is all determined by a given request and the state of the system.

Let me explain further. A request comes in, we pull the account from the session, that is the current user or the user that is being actuated as, we also match the request to a route and thereby have a route to do further processing.

The processing consist of extracting (even overriding) parameters from the route and setting into the route itself an array of checkers labels. These checkers bear the name of checks, but in reality they are checker labels, corresponding to checker services, similar to voters in Symfony lingo. Notice we don’t put back the stuff into the request but rather into the route. Since the route represents a resource in Drupal, then the resource is passed around to hold this important information. The voters or checkers are services relatively flexible. They only need to implement some Access related interface. The idea is that these checker services can be passed parameters and method information to invoke so that they can determine whether the access is granted or denied.

The problem is simple, given certain checkers and the route which will come with parameters on it, let’s resolve whether the account associated have access to this route.

This is how you can access from the admin dashboard of Drupal to the entities to roles and what not.

One thing to notice is the voter system is made flexible via checkers/voters that can vote for or against, but that also can override all other checkers. This is kind of like vital checkers, you must have this checker pass else you will suffer a lock down. The voter system Access also have capabilities for checking that all checkers comply or just any of them.

The AccessManager is the king in that component/element. It is a service that gets the important pieces injected via its constructor, that is: the url generator, a custom parameter converter, an argument resolver, and the router provider. It does its job checking the route name for a given account and route and request.

The AccessArgumentResolver is the obscure component there, and it is still buggy. The fetching of the parameters from the request and resolving which parameters should be passed from the request to do the check and the structure and the use cases make this class the worst organized from the element. It definitely needs some refactoring and testing.

In comparison to Symfony voters I guess is a nice exercise from Drupal to invent their own voter system. The system is a bit stiff but is a good example of what you can do with the Route, Request, and voters.

The AccessManager would be easy to test were it not because it was injected the ContainerAwareTrait. The first time I saw this I was bracing myself for the worst. Thankfully it only injects it for lazy loading the services that are functioning as checkers. That thing is required. The class seems to be divided almost in two, the lazy loading of services and then the more clear logic about the voting system.

The Route class from Symfony is very powerful and was used as key component here. Unfortunately Drupal has multiplied certain interfaces unnecessarily into other element/core components. I think that it should keep each core component with strong interfaces and then just use them across to avoid coupling it all over the place. Thankfully there is no Route wrapper and this is the way it should be, Symfony does not even have an interface for Route and we bet for a good reason.

These are the methods that were used from the Route in Drupal Access core component:

$route->getRequirements()
$route->getDefaults()
$route->getOption('_access_mode')
$route->setOption('_access_checks')

You can see that the Route uses its requirements, defaults, and options. The options is the strongest feature that gets used and this stores the access checker labels, checks that will be made out of the checker services and also the type of voting aka access mode “all” or “any”.

Let’s take a closer look on the tests for Route and see the behavior of the class Route:

  public function testOptions()
    {
        $route = new Route('/{foo}');
        $route->setOptions(array('foo' => 'bar'));
        $this->assertEquals(array_merge(array(
        'compiler_class'     => 'Symfony\\Component\\Routing\\RouteCompiler',
        ), array('foo' => 'bar')), $route->getOptions(), '->setOptions() sets the options');
        $this->assertEquals($route, $route->setOptions(array()), '->setOptions() implements a fluent interface');
 
        $route->setOptions(array('foo' => 'foo'));
        $route->addOptions(array('bar' => 'bar'));
        $this->assertEquals($route, $route->addOptions(array()), '->addOptions() implements a fluent interface');
        $this->assertEquals(array('foo' => 'foo', 'bar' => 'bar', 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler'), $route->getOptions(), '->addDefaults() keep previous defaults');
    }
 
    public function testOption()
    {
        $route = new Route('/{foo}');
        $this->assertFalse($route->hasOption('foo'), '->hasOption() return false if option is not set');
        $this->assertEquals($route, $route->setOption('foo', 'bar'), '->setOption() implements a fluent interface');
        $this->assertEquals('bar', $route->getOption('foo'), '->setOption() sets the option');
        $this->assertTrue($route->hasOption('foo'), '->hasOption() return true if option is set');
    }
 
    public function testDefaults()
    {
        $route = new Route('/{foo}');
        $route->setDefaults(array('foo' => 'bar'));
        $this->assertEquals(array('foo' => 'bar'), $route->getDefaults(), '->setDefaults() sets the defaults');
        $this->assertEquals($route, $route->setDefaults(array()), '->setDefaults() implements a fluent interface');
 
        $route->setDefault('foo', 'bar');
        $this->assertEquals('bar', $route->getDefault('foo'), '->setDefault() sets a default value');
 
        $route->setDefault('foo2', 'bar2');
        $this->assertEquals('bar2', $route->getDefault('foo2'), '->getDefault() return the default value');
        $this->assertNull($route->getDefault('not_defined'), '->getDefault() return null if default value is not set');
 
        $route->setDefault('_controller', $closure = function () { return 'Hello'; });
        $this->assertEquals($closure, $route->getDefault('_controller'), '->setDefault() sets a default value');
 
        $route->setDefaults(array('foo' => 'foo'));
        $route->addDefaults(array('bar' => 'bar'));
        $this->assertEquals($route, $route->addDefaults(array()), '->addDefaults() implements a fluent interface');
        $this->assertEquals(array('foo' => 'foo', 'bar' => 'bar'), $route->getDefaults(), '->addDefaults() keep previous defaults');
    }

Symfony tests for Route are well maintained and therefore read nicely. A quick read explain us the simplicity and minimum constraints required to make the Route a versatile class. Now the route is not alone, and that is where everything else will come to play, however we are just treating this for simplicity’s sake.

So far as it is concerned, our Access core component in Drupal only has that responsibility of deciding on access for account or profiles associated to a user interacting via requests with the system.

The Access core component also comes with an example, that is an implementation of a common checker. If you want to create your own checkers for your use cases, you can imitate how the CsrfAccessCheck implements the interface AccessInterface and you can go from there. I believe in Drupal you should be even able to replace the whole AccessManager implementation or any of its lower creatures, however, I may be going to far or too advanced for the generic use case.

I don’t know what awaits me in the other core components. If i have made many mistakes explaining this I ask for comprehension, this is what I read and deduce from the code which is the better documentation 🙂 and my Symfonic intuition.

Until then your friend @cordoval with undeserved joy doing this drupliconic publication.

If you like this series, I got accepted to DrupalCon2014 in Austin, I need to cover for the expenses of my air ticket. Please i kindly ask you for your support as a friend and I hope I will keep working on these series and provide more secrets and details. Please paypal me to my email cordoval at gmail dot com to say thanks. With your effort I will be able to spread it more! Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *