Extending SensioGeneratorBundle for our Admin Areas

Symfony2 lacks from an official Admin Generator. Yes, there are quite a bunch of projects, being SonataAdminBundle the most advanced one, but most of the time when you download one of those bundles you have to spend quite a lot of time reading the documentation (if there is any), configuring lots of stuff and actually sometimes having to extend them to fit your purpose.

And you can start thinking, is there any way to extend Sensio Generation tool (which is provided in Symfony2 Standard Edition) if I just want to extend the layouts and some small stuff?

Well, the answer is yes, and if you keep reading you will learn how to do this and evaluate if it is better to invest your time in configuring Sonata or you feel confident to try to extend Sensio Generator.

For the record, this post is not against Sonata or the others, it just tries to give some light in how to start creating your own Admin Generation tools which is quite a hot topic in Symfony2 developers discussions and events.

Explanation of what we are trying to achieve: Extend Crud Generator

You can find this CLI utility executing

php app/console generate:doctrine:crud

This Command is coded in file vendor/bundles/Sensio/Bundle/GeneratorBundle/Command/GenerateDoctrineCommand.php and if you look at it, it relies on some skeleton files located inside Resources folder.

So our strategy is simple, extend the Command and modify the skeleton!

Please note that this does not cover all cases and might not be enough but my experience is that just modifying the skeletons works for most everyday problems.

Creating a new command inheriting GenerateDoctrineCommand

Let the code talk itself:

<?php
 
namespace Emagister\BannersBundle\Command;
 
use Sensio\Bundle\GeneratorBundle\Command\GenerateDoctrineCrudCommand;
 
class CrudTuneadoCommand extends GenerateDoctrineCrudCommand
{
    protected $generator;
 
    protected function configure()
    {
        parent::configure();
 
        $this->setName('emagister:generate:crud');
        $this->setDescription('Our admin generator rocks!');
    }
}

And that’s it! This does not do much, but we have a brand new command

Creating a layout for our Crud Generator

I don’t understand why SensioGeneratorBundle does not provide a layout or just extends app/Resources/views/base.html.twig. This leads to some misunderstanding to Symfony2 newcomers, mostly because without a proper HTML markup, Symfony2 profiler disappears and people don’t know what they have done wrong!

So, let’s create a layout in our bundle:

{# Resources/views/layout.html.twig #}
<html>
<head>
<title>{% block title %}Banners Admin{% endblock %}</title>
</head>
<body>
{% block page %}{% endblock %}
</body>
</html>

How our CRUD views must be

Sensio Generator creates CRUD index like this:

<h1>#EntityName# list</h1>

<table class="records_list">
(...)
</table>
 
<ul>
(...)
</ul>

And to use our layout, it should be:

{% extends 'EmagisterBannersBundle::layout.html.twig' %}
 
{% block title %}
   {{ parent() }} - #EntityName# List
{% endblock %}
 
{% block page %}
<h1>#EntityName# list</h1>

<table class="records_list">
(...)
</table>
 
<ul>
(...)
</ul>
{% endblock %}

Modifying the skeleton and using it

To achieve our purpose, we must follow these 3 steps:

1. We copy Resources/skeleton full directory from SensioGeneratorBundle to our bundle
2. Modify skeleton/crud/views/index.html.twig like this

{{ "{% extends 'EmagisterBannersBundle::layout.html.twig' %}" }}
 
{{ "{% block title %}" }}
{{ "{{ parent() }} - " ~ entity ~ " List" }}
{{ "{% endblock %}" }}
 
{{ "{% block page %}" }}
 
<h1>{{ entity }} list</h1>
 
<table class="records_list">
    <thead>
        <tr>
        {%- for field, metadata in fields %}
 
            <th>{{ field|capitalize }}</th>
 
        {%- endfor %}
 
            <th>Actions</th>
        </tr>
    </thead>
    <tbody>
    {{ '{% for entity in entities %}' }}
        <tr>
 
    {%- for field, metadata in fields %}
        {%- if loop.first and ('show' in actions) %}
 
            <td><a href="{{ "{{ path('"~ route_name_prefix ~"_show', { 'id': entity.id }) }}" }}">{{ '{{ entity.'~ field|replace({'_': ''}) ~' }}' }}</a></td>
 
        {%- elseif metadata.type in ['date', 'datetime'] %}
 
            <td>{{ '{% if entity.'~ field|replace({'_': ''}) ~' %}{{ entity.'~ field|replace({'_': ''}) ~'|date(\'Y-m-d H:i:s\') }}{% endif%}' }}</td>
 
        {%- else %}
 
            <td>{{ '{{ entity.'~ field|replace({'_': ''}) ~' }}' }}</td>
 
        {%- endif %}
 
        {%- if loop.last %}
 
            <td>
                {%- include "views/others/actions.html.twig" %}
            </td>
 
        {%- endif %}
    {%- endfor %}
 
        </tr>
    {{ '{% endfor %}' }}
    </tbody>
</table>
 
{% if 'new' in actions %}
<ul>
    <li>
        <a href="{{ "{{ path('"~ route_name_prefix ~"_new') }}" }}">
            Create a new entry
        </a>
    </li>
</ul>
{% endif %}
 
{{ "{% endblock %}" }}

3. Make our command use the new skeleton

<?php
namespace Emagister\BannersBundle\Command;
 
use Sensio\Bundle\GeneratorBundle\Command\GenerateDoctrineCrudCommand;
use Sensio\Bundle\GeneratorBundle\Generator\DoctrineCrudGenerator;
 
class CrudTuneadoCommand extends GenerateDoctrineCrudCommand
{
    protected $generator;
 
    protected function configure()
    {
        parent::configure();
 
        $this->setName('emagister:generate:crud');
        $this->setDescription('Our admin generator rocks!');
    }
 
    protected function getGenerator()
    {
        if (null === $this->generator) {
            $this->generator = new DoctrineCrudGenerator($this->getContainer()->get('filesystem'), __DIR__.'/../Resources/skeleton/crud');
        }
 
        return $this->generator;
    }
}

And that’s it! Our new Crud index files will use the layout and of course we can extend these steps as much as we like to obtain a totally customized generator tool. Note that you can also modify how Controllers, Entities, Repositories are even how the WebTestCase files are generated!

Hope you got the whole point of this post! Stay tuned!

You may also like...

5 Responses

  1. cordoval says:

    nicey, command, extending bundle, yes thinking on the user, get things done, good job

    “I don’t understand why SensioGeneratorBundle does not provide a layout or just extends app/Resources/views/base.html.twig.”

    maybe a PR?

  2. Ricard Clau says:

    Actually, there is a pull request on that subject that I +1d at https://github.com/sensio/SensioGeneratorBundle/pull/37

    I don’t know why it wasn’t accepted while base.html.twig is provided in the main folder

  3. Peter Ang says:

    nice..just what i am looking for..
    Thanks much

  4. I created a bundle to fix this behavior, along with many others I don’t like in SensioGeneratorBundle. Also added some sensible default and options. See https://github.com/PUGX/PUGXGeneratorBundle

  5. Luis Hdez says:

    There is a pending pull request for that

    https://github.com/sensio/SensioGeneratorBundle/pull/177

    But it looks like this bundle it’s kinda abandoned…