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!

Drupal8.x and Symfony To the Aid Experiences: Part I Series

Screenshot 2014-05-21 00.45.27

Ok, so let’s do this. I decided to take a stab go into one of the tweets from jmolivas and picked a link and bam it was this module on drupal8x.

So i had already cloned drupal into a folder and had ran “`php -S localhost:8000“` and had gone through the installation on the web, they call it installation script which makes no sense :).

Then I had my drupal blue screen. The i was kindly oriented by yesCT and others in the IRC to plug some more modules, these are drag and drop. I know it sucks but once you download one and move a folder under modules you start to think is not too bad. We don’t do that in Symfony, is almost sinful, but bearing up with drupal legacy we just do this on this part of planet earth. So i moved the folder for the webprofile, that was neat and discovered they had a similar WDT like in Symfony. The same happen with a dependency and how they enable modules. Rather than running composer install or update package, you basically move the folder under “`modules“` and enable it in under “`Extend“` drop down on the navigation bar.

So this is the link of the repo i found in jmolivas tweet https://github.com/dmouse/vimeo_field.
What i found to my surprise was a test which was mocking Drupal core classes. The first thing that it striked me was, why would you mock all classes that you extend from your actual class under test. Well it turns out that the tests did not have a proper autoloader. I had two options, load as a require-dev drupal/drupal, or sourcing the autoload from drupal root directory and merge it with the current autoloader in a boostrap.php file. I opted for the first so that travis can work of course. At first i was looked as a rare bird by some drupalers but I went my own way knowing my theory.

    protected function setUp()
    {
        $this->formmaterItemListInterface = $this->getMockBuilder('Drupal\Core\Field\FieldItemListInterface')
            ->disableOriginalConstructor()
            ->getMock();
        $this->formmaterBase = $this->getMockBuilder('Drupal\Core\Field\FormatterBase')
            ->setMethods([])
            ->disableOriginalConstructor()
            ->getMock();
 
        $this->formatter = $this->getMockBuilder('Drupal\vimeo_field\Plugin\Field\FieldFormatter\VimeoDefaultFormatter')
            ->disableOriginalConstructor()
            ->setMethods(['t', 'getSetting'])
            ->getMock();
 
        $this->formatter->expects($this->any())
            ->method('t')
            ->with($this->isType('string'))
            ->will($this->returnCallback(function () {
                $args = func_get_args();
                return $args[0];
            }));

Notice the first mocks which have nothing to do in this test. The class variables were not even defined. But if you removed those lines you would get an error because they were there because phpunit would not know who the heck were those classes because the autoloader was not finding them. This is obviously a bad practice. Picture building a module very complex that depends on half of the classes on the core, poor you.

     "require-dev": {
 -        "phpunit/phpunit": "~4.1"
 +        "phpunit/phpunit": "~3.7",
 +        "drupal/drupal": "8.x-dev@dev"
      },

I have noticed also the test needs more integration tests, but for that more thinking is needed. For now is enough to include drupal/drupal as a dependency and make the testing of the module standalone.

After following the instructions and sending some PRs and comments I got the desired result 🙂

I also wanted to require-dev drush just in case but i rather enabled the bundle ehem module according to the instructions in the readme.

Drupal community needs our help as good PHP developers, especially if you know some Symfony it would help us a lot! now i can say i belong to two communities and is so cool!

Doing this work out of pure joy and very thankful for such undeserved favor, please enjoy too!

your friend @cordoval

The Symfony Exam Drill (Unofficial)

I have prepared people to take the Symfony Certification Exam. These are a battery of questions I would like to share for free. They will help you just prepare for any exam, not just Symfony Certification Exam but for your own benefit so that you know that you know something and is a warm up exercise good for sharpening your skills. And may give you a good idea of what it is to take a Symfony Exam for work or for other. Feel free to use it as you wish, especially if you want to ensure you know enough and basically value educating yourself and complete some gaps.

Reading this I do want you to consider if this benefits you to support me by donating your will amount so that I can keep blogging and spending time on sharing secrets with you. I do enjoy it a lot, and I hope you learn too cause I learn from the community.

This work is not to be intended to be compared in any way to anything, it is just a drill exercise, none of the information here can be used for liability, all was invented, everything in it are just personal notes that I want to share and that should not be utilized in any manner but to just help you prepare your own drills and nothing more. All opinions should be double checked and I take no responsibility or liability for how you use them. Basically you use these notes at your own risk.

In favor of those willing just to learn here we come, I advise you to not look up the answers but to just try it first then read the dialog that unfolded between me and the person I was training:

Note: I have left it the most raw I could so that you could feel the dealing with the questions and get a better hint of the drill. If you find personal information please let me know to remove. If you find improvements please format it better and send me a gist link and I can update this drill. Thank you!

1. In twig, what do you use if you want to optimize the size of the generated HTML content?
    a. spaceless
    b. gzip
    c. operators
 
the spaceless tag
answer is b
really ?
twig supports gzip ?
don't look up
let's go to the next one
alright great
This tag is not meant to "optimize" the size of the generated HTML content but merely to avoid extra whitespace between HTML tags to avoid browser rendering quirks under some circumstances.
Ah cool.
i thought gzip was on the response level
okay
 
2. In Twig, which other Twig function works as the old raw tag, for when you want to display code and not be parsed?
    a. spaceless
    b. raw2
    c. escape with some special parameters
    d. noparse
    e. verbatim
    f. raw tag
 
hmm
e ?
you mean a function that works the same as the old raw tag right ?
but verbatim is a tag, not a function...
yeah a tag
ok correct
next
okay
 
3. In Twig, the batch function:
    a. is used to fill strings with random characters in a batch
    b. renders a vector out of an element value given
    c. is used to parse a string and output the string characters in an for loop
    d. returns a list of lists with a given number of items out of the set
 
d
:)
 
ok 4
is it all about twig ?
twig is a big component
on the test
okay
you should be thankful i am stressing twig now
(Y)
 
4. In Twig, when compiling a node tree how can you change the default compiler on your $twig object
 
 
free answer
i don't think that answer
i don't know*
$twig->setCompiler($compiler)
:)
Ah so easy
never used twig as an independant component
always used it as  a service
so far
alright
 
5. In Twig which loader can you use to load templates from a database and the filesystem simultaneously?
    a. I can implement Twig_LoaderInterface, Twig_ExistsLoaderInterface into a combined loader and load everything at once
    b. I can implement DatabaseTwigLoader extending Twig_LoaderInterface and Twig_ExistsLoaderInterface
    and load templates using TwigMainLoader
    c. I can do as b but use the Twig_Loader_Chain to chain loaders
    and set it to the Context
    d. I can do as c but rather than setting the loaders into Context I initialize Twig_Environment with the chain loader
    e. is not currently possible but there is a PR for that
 
d?
:)
correct
awesome
i didn't know this one
but assumed it would work like the chain router
from cmf
 
6. in Twig, name which one is not a method of the Twig_Filter class:
    a. needsLoader()
    b. needsEnvironment()
    c. needsContext()
    d. getSafe() and getPreEscape()
    e. b and d
 
a
correct
a filter only needs the string and context
doesn't need to know where the template came from
or was loaded from
:)
 
7. In Twig, a visitor priority varies between:
    a. -255 to 255
    b. -70 to 70
    c. 0 - 40
    d. -10 to 10
    e. it is fixed
 
d
 
8. In Twig, all these classes Escaper, Optimizer, SafeAnalysis, Sandbox share common methods :
    a. setArguments, getArguments
    b. isValid()
    c. setPriority(), getPriority()
    d. a combination of a and c
    e. none of the above
 
d?
not sure
incorrect
e.
those classes are Visitors
hmm okay, so no common methods like that
they all implement enterNode and leaveNode
and getPriority
because they are visitors
i see
even safeanalysis
is a visitor ?
yeah
oh okay
 
9. In Twig this statement is valid:
    a. first and last functions work in the same way as the random filter
    b. first and last functions are great to extract the first and the last element of an array
    c. random filter when used without arguments is like mt_rand
    d. random function when passed a string splits the string and outputs a single character
    e. when random function receives a number it treats it as the top value of the random series it outputs
 
a is invalid
all others are valid
no
ah wait wait
hah
c
ci si valid
because all others say function
while they are filters
not functions
stop, you had your chance
wait
ah no wait
a. first and last functions work in the same way as the random filter
first and last are filters
yes you're right
random() is a function
they work similarly to random however random is a function and it outputs random values out of an array or such
b. first and last functions are great to extract the first and the last element of an array
but how do they work similarly ?
they are not functions
c. random filter when used without arguments is like mt_rand
random is a function, but it does work like mt_rand
d. random function when passed a string splits the string and outputs a single character
correct
e. when random function receives a number it treats it as the top value of the random series it outputs
correct
d and e
so this was a trick question
must pay attention to all choices before making up my mind
correct
this test is very similar to the real one
(Y)
 
 
10. in Twig, suppose you have an object person
    a. If person has a property address-main, I can access it with person.getAddressMain()
    b. If person has a property address_main, I better access it with person.['address_main']
    c. a and b are possible
    d. I have to create a special getter for accessing the property above
    e. no, the [] operand works in any situation
    f. get_attribute(person, 'address-main') does it
    g. none of the above, Twig comes with a special function for this
 
i think its a
but it could be c
but i think its a
When the attribute contains special characters (like - that would be interpreted as the minus operator), use the attribute function instead to access the variable attribute.
{# equivalent to the non-working foo.data-foo #}
{{ attribute(foo, 'data-foo') }}
oh
because it's - instead of _ in a
i see now
so you can't do person.address-main
cause that's like person.address - main
?
correct
so is it g
none of the above ?
right
 
11. In Twig, what is the correct order:
    For convenience sake foo.bar does the following things on the PHP layer:
    https://gist.github.com/cordoval/e8a98fe50481dcdace25
    a
    b
    c
    or d?
 
 
c
?
or b
hmm
correct
c
okay so my reasoning is correct
 
12. In Twig:
    a. you can chain filters with filters and functions with functions but not mix them
    b. you can chain anything
    c. you can access global vars only provided by Symfony on TwigBundle
    d. there is only three global vars that come by default _self, _superglobals, _charset
 
d is incorrect
and b is incorrect
hmmm
so a or c
e. if you want to use the c twig extension you have to change only a thing in your composer.json
f. Twig is not anymore on PEAR
ah
should i choose 1 answer ?
or many ?
g. Twig version 1.12.2 onwards are secure
h. Twig 1.13 has a buggy C extension
a. you can chain filters with filters and functions with functions but not mix them
you can so it is false
you can chain filters with functions ?
how do you chain functions? i never saw that
only filters
have an example ?
date()|date
yes but that's not chaining functions
that's chaining a filter after a function
but you can't do
date()|something()|else()
can you ?
date(random())
okay so that's considered chaining
alright
b. you can chain anything seems to be correct
c. you can access global vars only provided by Symfony on TwigBundle
is incorrect
d. there is only three global vars that come by default _self, _superglobals, _charset
it is not _superglobals but _context
e. if you want to use the c twig extension you have to change only a thing in your composer.json
is a php extension so it is not installable with composer.json
f. Twig is not anymore on PEAR
is true
g. Twig version 1.12.2 onwards are secure
is false
1.12.3 have serious security issues
http://pear.twig-project.org/
h. Twig 1.13 has a buggy C extension
is correct
it was very buggy
crashed the C extension
a lot
let me remind you you can't access the internet
yes i know, i just checked that now
cause i was sure i saw it the other day
on pear
well it is discontinued
it will not happen anymore
alright
enough with Twig
that was intense
that's exactly the type of question at the exam
right ?
you shall see it yourself when you take it
 
13. In Symfony:
    1. Symfony\Bundle\SecurityBundle\Security\FirewallContext services are publicly created that correspond to each context you create under your security.yml, a context is called an authenticator
    2. Symfony\Bundle\SecurityBundle\Security\FirewallContext services are privately created that correspond to each context you create under your security.yml, a context is also known as per each firewall
    3. Symfony\Bundle\SecurityBundle\Security\FirewallContext services are synthetically created that correspond to each context you create under your security.yml, each context is liked to an authenticator
    4. Symfony\Bundle\SecurityBundle\Security\FirewallContext services are publicly created that correspond to each context you create under your security.yml, a context is related to each FirewallContext directly but is overall handled by a generic Symfony\Bundle\SecurityBundle\Security\Firewall object
 
4
:)
;)
 
14. Metadata\Driver\DriverChain is a class:
    a. that comes within FrameworkBundle
    b. that comes with the FrameworkExtraBundle
    c. It is not found in the Symfony Standard Edition
    d. It is a class with license issues
    e. It is deprecated but it is in Symfony core
 
not a and not b
i don't think e
so c or d
d is correct
c is not correct
it is the meta-data library from johaness
license problems because of generic naming ?
no because it is * http://www.apache.org/licenses/LICENSE-2.0
not compatible with symfony's license
ok
 
15. In Symfony:
    a. SecureRandom class at the heart of security component has only one public nextBytes method
    b. RoleHierarchy also at the heart of security component implements RoleHierarchyInterface which obliges this class to implement buildRoleMap
    c. There are only two classes named ResponseListener in the whole of Symofony and they implement EventSubscriberInterface
    d. There is a ResponseListener under RememberMe feature and this acts by setting a cookie on every response the app sends back to the user
    e. About the other ResponseListener found under HttpKernel complaint with RFC 2616 means most importantly that it ensures certain dimensions on header length are kept
    f. in Symfony Symfony\Component\Security\Core\Encoder\EncoderFactory getEncoder does create an encoder everytime for security reasons
 
should i comment on each
or choose one correct ?
say which ones are true
say you don't know if you don't
or skip
so i can explain
okay
1)don't know
b)true
c)false
d)false
e)true
f)true
a is true
b is false
that gerRole is private , the interface method is getReachableRoles
c is true
d is false because it does that but not on every response
only when it detects the remember attribute on the request
yes casues its a cookie
it can last
e is false
it ensures it contains certain headers
so more than just checking lengths which i think is a made up
yes RFC2616 is much more than that
oh wow already 2 hours
no wonder the exam is 3 or 4
 
16. in Symfony:
    a. GetSetMethodNormalizer is broken by design
    b. one can load normalizers and/or encoders by tagging them as serializer.normalizer and serializer.encoder respectiv    ely true makes the serializer available in the container and loads 3 encoders and 1 default normalizer
    d. JsonEncoder and XmlEncoder implement both EncoderInterface and DecoderInterface
    e. NormalizerInterface has methods normalize and supportsNormalization
    f. DenormalizerInterface has methods denormalize and supportsDenormalization but their methods have less arguments       than the normalizer
 
a)don't know
b)true)
c)don't know
d)true
e)false
f)don't know
g. going from object to array is called normalizing, and from an array to JSON is called decoding
g)false
a is true
The GetSetMethodNormalizer is broken by design. As soon as you have a circular object graph, an infinite loop is created when calling the getters. You're encouraged to add your own normalizers that fit your use-case.
oops that's true
b is true
c is false, it does but it is only 2 encoders and no normalizers
d is true
e is true
f is false, is the other way, the denormalizer method needs more arguments
g is false
think it as this
Object is high in hierarchy
then comes an array
then a json string
so going up means industry
we normalize from array to object
and we encode from json string to array
(Y)
we serialize because channels don't support objects
so that is lower rank
deserialization goes up in the chain until object
is reached
then we move them back up to objects
right
with normalization
yes
from array to object
yes
once json is decoded to array
yes
think enygma
json is the papers with the letters
object is the meaning
array is in the process of decrypting
we decode the letters
and normalize the arrays to get the idea
(Y)
we denormalize an idea into an array and then encode it into a secret paper with letters
alright
next
 
 
17. in Symfony:
    a. you can inject a container parameter into a ContainerAware Command
    b. Descriptors are objects to render documentation on Symfony Console Apps
    c. Symfony Console component needs the PHP globals internally to work
    d. Symfony natively supports multiple file uploading by default
    e. you can only import strings with the IniFileLoader from a .ini file
 
a)false. you can't but you can retrieve it from $this->container
b)true
c)false
d)true
e)true
d is false
is not by default
oh i see
how do you enable it ?
'multiple' => true
on your form type field options
yes but it supports it by default
you just need to enable it
d. Symfony natively supports multiple file uploading by default
multiple is false by default
 
18. In Symfony:
    a. There is a lesser known templating component with which you can build your own templating system not php and not     twig
    b. This component is heavily used by Twig
    c. Symfony has 27 components to this date
    d. Syfmony Translator component follows the same architecture for loaders than Twig does
 
 
a)false
b)false
c)true)
d)false
a is true
b is true in Symfony
c is true
d is true $translator->addLoader('array', new ArrayLoader());
oh
what's that templating component
?
/Users/cordoval/Sites/project/vendor/symfony/symfony/src/Symfony/Component/Templating
ahh i didn't understand the question
of course you can build your own with the component
i thought there was already another templating engine
other than twig and php implemented
alright
 
19. in Symfony:
    a. There is a component that is only 6 classes, has no folder structure and is very simple. That is the Yaml    component.
    b. There is a component that is only 10 classes, has no folder structure and is very simple. That is the Yaml component
    c. The Dispatcher component is only 8 classes and the latest version passes also the dispatcher and the name of the event to the listener
    d. In Symfony, all the instances of the event_dispatcher are container aware by default unless you register a new event_dispatcher that does inject your services
    e. src/Symfony/Bundle/FrameworkBundle/Resources/config shows xml configuration for each service how the FrameworkBundle hooks special services of Symfony, it is also in charge to set the environment services, for instance test.client
 
a)true
b)false
3)true
d)false
a is false
b is true
e)true
c is true
d is true
e is true
event dispatcher is container aware ?
:)
Symfony is not the best framework sometimes between us :)
heh
it still works
alright
for some our use cases yeah
so yaml component has 10 classes ?
that's too big
6 plus 4 exception classes
20.
this part is the practical one
alright
should be better
paste here a gist with a subscriber implementation of what you remember of a subscriber doing the following
a. taking the response from the event and setting a cookie
b. write a twig filter and use a global twig parameter into that filter
c. use an annotation in an action with the ParameterConverter correctly
all those 3 are snippets
all those inside 1 subscriber ?
no
those are separate snippets
you cannot look up anything
send me the gists please
you have 5 minutes only
like this
...
?
yeah
send it quick 3 minutes
when we write a twig filter
we must implement it as part of a class
a twig extension
you want me to write the extension
?
you want me to implement a custom twig filter or simply use an existing one within a template ?
yeah, the extension
 
ah sorry
 
should be it
ok that is half a point
ok
we are done
okay
why half a point ?
because it was ok but not perfect
what are mistakes
i see
extending classes
oh twig extension
and also tags, namespaces, and also configuration directives
forgot to extend
and usage

I hope it had benefit you, I hope you had the same joy I have on learning and use these things for good.

Encouragements in all good,

your friend @cordoval

Deconstructing Symfony: Part II – Clean up your project

I have some controversial practices with Symfony projects. One of them is to remove the bundles that are really for newcomers and that actually I would have wished never deal with. But all has pros and cons. The bad practices that were introduced in some standard distributions are meant to make accessible it to more, however, in the long run these practices affect greatly the path of learning. But once you are over them you can look back and understand it like in the explanation that follows.

So many people would like to generate code, and I understand the point, however code generation is never perfect and causes in the long run a world of pain because is not code you wrote and is not code you can call maintainable code. So if you hear me saying “remove all your generator bundles” please do!

Let’s start with a sample of the dreadful “`php app/console controller-generate or bundle-generate here“`, even doctrine have some similar commands, avoid them please. Ok now close your pristine clean code eyes because what you are about to see has no forgiveness:

<?php
 
namespace Vendor\TrashBundle\Controller;
 
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Vendor\TrashBundle\Entity\Category;
use Vendor\TrashBundle\Form\CategoryType;
 
class CategoryController extends Controller
{
    public function indexAction()
    {
        $em = $this->getDoctrine()->getManager();
 
        $entities = $em->getRepository('VendorTrashBundle:Category')->findAll();
 
        return $this->render('VendorTrashBundle:Category:index.html.twig', array(
            'entities' => $entities,
        ));
    }
 
    public function showAction($id)
    {
        $em = $this->getDoctrine()->getManager();
 
        $entity = $em->getRepository('VendorTrashBundle:Category')->find($id);
 
        if (!$entity) {
            throw $this->createNotFoundException('Unable to find Category entity.');
        }
 
        $deleteForm = $this->createDeleteForm($id);
 
        return $this->render('VendorTrashBundle:Category:show.html.twig', array(
            'entity'      => $entity,
            'delete_form' => $deleteForm->createView(),
        ));
    }
 
    public function newAction()
    {
        $entity = new Category();
        $form   = $this->createForm(new CategoryType(), $entity);
 
        return $this->render('VendorTrashBundle:Category:new.html.twig', array(
            'entity' => $entity,
            'form'   => $form->createView(),
        ));
    }
 
    public function createAction()
    {
        $entity  = new Category();
        $request = $this->getRequest();
        $form    = $this->createForm(new CategoryType(), $entity);
        $form->bind($request);
 
        if ($form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            $em->persist($entity);
            $em->flush();
 
            return $this->redirect($this->generateUrl('category_show', array('id' => $entity->getId())));
        }
 
        return $this->render('VendorTrashBundle:Category:new.html.twig', array(
            'entity' => $entity,
            'form'   => $form->createView(),
        ));
    }
 
    public function editAction($id)
    {
        $em = $this->getDoctrine()->getManager();
 
        $entity = $em->getRepository('VendorTrashBundle:Category')->find($id);
 
        if (!$entity) {
            throw $this->createNotFoundException('Unable to find Category entity.');
        }
 
        $editForm = $this->createForm(new CategoryType(), $entity);
        $deleteForm = $this->createDeleteForm($id);
 
        return $this->render('VendorTrashBundle:Category:edit.html.twig', array(
            'entity'      => $entity,
            'edit_form'   => $editForm->createView(),
            'delete_form' => $deleteForm->createView(),
        ));
    }
 
    public function updateAction($id)
    {
        $em = $this->getDoctrine()->getManager();
 
        $entity = $em->getRepository('VendorTrashBundle:Category')->find($id);
 
        if (!$entity) {
            throw $this->createNotFoundException('Unable to find Category entity.');
        }
 
        $editForm   = $this->createForm(new CategoryType(), $entity);
        $deleteForm = $this->createDeleteForm($id);
 
        $request = $this->getRequest();
 
        $editForm->bind($request);
 
        if ($editForm->isValid()) {
            $em->persist($entity);
            $em->flush();
 
            return $this->redirect($this->generateUrl('category_edit', array('id' => $id)));
        }
 
        return $this->render('VendorTrashBundle:Category:edit.html.twig', array(
            'entity'      => $entity,
            'edit_form'   => $editForm->createView(),
            'delete_form' => $deleteForm->createView(),
        ));
    }
 
    public function deleteAction($id)
    {
        $form = $this->createDeleteForm($id);
        $request = $this->getRequest();
 
        $form->bind($request);
 
        if ($form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            $entity = $em->getRepository('VendorTrashBundle:Category')->find($id);
 
            if (!$entity) {
                throw $this->createNotFoundException('Unable to find Category entity.');
            }
 
            $em->remove($entity);
            $em->flush();
        }
 
        return $this->redirect($this->generateUrl('settings_category'));
    }
 
    private function createDeleteForm($id)
    {
        return $this->createFormBuilder(array('id' => $id))
            ->add('id', 'hidden')
            ->getForm()
        ;
    }
}

Now you can open your clean code eyes. Ok let’s just first describe the monster. Besides using old php5.3 ugly array syntax, picture if you generated CRUDs for just 4 entities of your bundle. It will be 4 times the code your poor eyes just saw, and code that does the same thing, in the same way, it comes with typos and it comes with errors in formatting as well (yes you have to fix them if you want to maintain them). Some persons in this world, that do this generating of these CRUDs for a bundle, call themselves developers. Of course we all are bad and good ones but just saying this so we take a bit of shame and react.

Let’s go from 153 lines of madness to this:

<?php
 
namespace Vendor\ImprovedBundle\Controller;
 
class CategoryController extends BaseController
{
    protected $class = 'Vendor\ImprovedBundle\Entity\Category';
    protected $handle = 'VendorImprovedBundle:Category';
    protected $basePath = 'category';
    protected $type = 'Vendor\ImprovedBundle\Form\CategoryType';
}

This is after we have refactored some repetitive code and moved it into a base controller, made each method independent of the class the CRUD is handling, taking away the names for paths and form types and classes to make them class variables, and after we have used some helper methods from the repository.

Let’s see the base controller:

<?php
 
namespace Vendor\ImprovedBundle\Controller;
 
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
 
class BaseController extends Controller
{
    protected $handle = 'handle_here';
    protected $class = 'class_here';
    protected $basePath = 'base_path_here';
    protected $type = 'type_here';
 
    public function indexAction()
    {
        return $this->render('index', ['entities' => $this->findAll()]);
    }
 
    public function showAction($id)
    {
        return $this->render(
            'show',
            [
                'entity' => $this->find($id),
                'delete_form' => $this->createDeleteForm($id)->createView(),
            ]
        );
    }
 
    public function newAction()
    {
        $entity = $this->createEntity();
 
        return $this->render(
            'new',
            [
                'entity' => $entity,
                'form' => $this->getForm($entity)->createView(),
            ]
        );
    }
 
    public function createAction(Request $request)
    {
        $form = $this->getForm($entity = $this->createEntity());
 
        if ($form->handleRequest($request)->isValid()) {
            $this->save($entity);
 
            return $this->redirectTo($this->basePath.'_show', ['id' => $entity->getId()]);
        }
 
        return $this->render(
            'new',
            [
                'entity' => $entity,
                'form' => $form->createView(),
            ]
        );
    }
 
    public function editAction($id)
    {
        $editForm = $this->getForm($entity = $this->find($id));
        $deleteForm = $this->createDeleteForm($id);
 
        return $this->render(
            'edit',
            [
                'entity' => $entity,
                'edit_form' => $editForm->createView(),
                'delete_form' => $deleteForm->createView(),
            ]
        );
    }
 
    public function updateAction(Request $request, $id)
    {
        $editForm = $this->getForm($entity = $this->find($id));
        $deleteForm = $this->createDeleteForm($id);
 
        if ($editForm->handleRequest($request)->isValid()) {
            $this->save($entity);
 
            return $this->redirectTo($this->basePath.'_edit', ['id' => $id]);
        }
 
        return $this->render(
            'edit',
            [
                'entity' => $entity,
                'edit_form' => $editForm->createView(),
                'delete_form' => $deleteForm->createView(),
            ]
        );
    }
 
    public function deleteAction(Request $request, $id)
    {
        $form = $this->createDeleteForm($id);
 
        if ($form->handleRequest($request)->isValid()) {
            $this->remove($this->find($id));
        }
 
        return $this->redirectTo($this->basePath);
    }
 
    public function render($view, array $parameters = [], Response $response = null)
    {
        return parent::render($this->handle.':'.$view.'.html.twig', $parameters);
    }
 
    public function redirectTo($path, array $params = [])
    {
        return $this->redirect($this->generateUrl($path, $params));
    }
 
    public function getForm($data = null, array $options = [])
    {
        return parent::createForm(new $this->type, $data, $options);
    }
 
    protected function createDeleteForm($id)
    {
        return $this
            ->createFormBuilder(['id' => $id])
            ->add('id', 'hidden')
            ->getForm()
        ;
    }
 
    protected function find($id)
    {
        return $this->getRepo()->find($id);
    }
 
    protected function findAll()
    {
        return $this->getRepo()->findAll();
    }
 
    protected function save($object)
    {
        $this->getRepo()->save($object);
    }
 
    protected function remove($object)
    {
        $this->getRepo()->remove($object);
    }
 
    protected function getRepo()
    {
        return $this->get('doctrine')->getRepository($this->handle);
    }
 
    protected function createEntity()
    {
        return new $this->class;
    }
}

After doing this refactoring our controller methods and actions are very readable and maintainable should there be any change or new requirement. Then we can override actions if need be and play with customizations further. So far it is enough and our tests still pass, of course if we had them, did they also get generated? of course not! Notice we have not even mentioned some other bundles because we don’t need them or want them for a legacy system and we want to have first sight of what we are working with and learn or relearn better practices. The truth is all code written one hour ago is already legacy, so it can be improved. When improving code you want to stay close to the domain, else a world of hurt awaits by entering into your project other people’s domains and codes.

Let’s take a look a the new entity repository:

<?php
 
namespace Vendor\ImprovedBundle\Entity;
 
use Doctrine\ORM\EntityRepository;
 
class CategoryRepository extends EntityRepository
{
    public function save($object, $flush = true)
    {
        $this->getEntityManager()->persist($object);
        if ($flush) {
            $this->getEntityManager()->flush();
        }
    }
 
    public function remove($object, $flush = true)
    {
        $this->getEntityManager()->remove($object);
        if ($flush) {
            $this->getEntityManager()->flush();
        }
    }
}

Many people are all over the map as to where to do the flushes so you can pass the handle to do it in or outside. In CRUDs in particular this should be no problems since you are not manipulating but one entity and its related ones anyway, but just in case you want to reuse the methods we provide those accessors options.

Go now and start removing many lines of code (LOC) at work, refactor legacy and share your findings. My advise at the beginning of a project from scratch, do what I did in the first blog post of this series, and remove the bundles and plug the commands by hand. That way nobody on your team will have even an option to think on generating code.

If you like this and want to support me raise funds for presenting at DrupalCon and to print more Gush stickers please paypal me at cordoval@gmail.com.

Thanks for your support!

your friend, @cordoval