Accueil / Blog / Métier / 2016 / Utiliser le DIC de Symfony dans Drupal 7

Utiliser le DIC de Symfony dans Drupal 7

Par Sébastien Corbin publié 14/12/2016
Afin de préparer la compatibilité vers Drupal 8, il est possible de créer des composants avec le Dependency Injection Container de Symfony dans Drupal 7
Utiliser le DIC de Symfony dans Drupal 7

Lors du développement de Drupal 8, la communauté est enfin sortie de sa bulle : des expressions comme "Getting off the island", "Not invented here" ou "Proudly found elswhere" sont apparues et représentent toutes cet objectif de ne pas réinventer la roue et d'incorporer des composants robustes et testés déjà développés par la communauté PHP.

L'utilisation de Composer a donc permis cette intégration de différents composants tels que Guzzle, PsrLog, Twig, ZendFeed, Behat, password_compat, EasyRDF, EmailValidator et quelques uns de Symfony et Doctrine.

Ces composants sont utilisés et peuvent être surchargés par le principe de services <http://symfony.com/doc/current/service_container.html> et d'injection de dépendances. Le principe est de déclarer des services sous forme de fichier Yaml, en leur attribuant un nom système.

Dependency Injection Container

Compatibilité avec Drupal 7

Imaginons maintenant que vous ou votre client a dans l'optique de migrer vers Drupal 8 dans les prochaines années, vous souhaitez donc développer des modules en mode composant, qui soient réutilisables dans Drupal 8. Il va falloir donc respecter les interfaces de celui-ci, et pour cela il nous faut utiliser la même méthode, l'injection de dépendance.

Voici par exemple un extrait de fichier poney.services.yml qui déclare le service poney.manager, ce service s'occupera de gérer nos poneys :

services:
  poney.manager:
    class: Poney\Manager
    arguments: ["@database"]

Pour gérer les objets de poney, il doit d'interfacer avec la base de données, que l'on passe donc en paramètre.

Grâce au module Symfony DIC il est possible de déclarer ceci dans Drupal 7, ce qui fonctionnera également dans Drupal 8.

Liste des services de D8 portés en D7

La liste de services inclus dans Drupal 8 est assez suffisante et les pull-requests bienvenues :

  • service_container
  • kernel
  • http_kernel
  • request_stack
  • current_user
  • database
  • form_builder
  • module_handler
  • cache.bootstrap
  • cache.default
  • cache.entity
  • cache.menu
  • cache.render
  • cache.data
  • entity.manager
  • path.alias_manager
  • path.alias_storage
  • path.current
  • event_dispatcher
  • logger.factory
  • session

Utilisation de la CompilerPass

Dès que l'on a le DIC de Symfony, on peut utiliser la CompilerPass pour injecter au runtime des services par des modules activés ou non. Il suffit de créer un fichier poney.container.php :

<?php

namespace Drupal\Module\poney;

use Drupal\Core\DependencyInjection\ServiceProviderInterface;
use MakinaCorpus\Poney\DependencyInjection\Compiler\CompilerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class ServiceProvider implements ServiceProviderInterface
{
    /**
     * {@inheritdoc}
     */
    public function register(ContainerBuilder $container)
    {
        $container->addCompilerPass(new CompilerPass());
    }
}
<?php

namespace MakinaCorpus\Poney\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

class CompilerPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        if ($container->hasDefinition('etable')) {
            $taggedServices = $container->findTaggedServiceIds('etable');
            foreach ($taggedServices as $id => $attributes) {
                $formatter = $container->getDefinition($id);
                $formatter->addMethodCall(
                    'setEtable',
                    [new Reference('etable')]
                );
            }
        }
    }
}

Puis lorsqu'un module tiers sera activé et fournira un service etable, la méthode setEtable des services ayant le tag etable sera automatiquement appelée.

Surcharge de services

Lorsqu'on veut surcharger un service pour modifier son fonctionnement, il est possible de le décorer, par exemple :

monautremodule.etalon:
  class: MakinaCorpus\MonAutreModule\Etalon
  public: false
  decorates: poney

Créer des Contrôleurs et des Formulaires Drupal 8

Création d'un contrôleur simple

Les controleurs dans Drupal 8 sont l'équivalent en Drupal 7 des pages callback, à ça près qu'ils ont accès au container, et permettent d'utiliser la POO au mieux.

<?php

namespace Poney\Controller;

use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;

use MakinaCorpus\Drupal\Sf\Controller;
use Drupal\node\NodeInterface;

class PoneyController extends Controller
{
    use StringTranslationTrait;

    /**
     * @return AccountInterface
     */
    private function getCurrentUser()
    {
        return $this->get('current_user');
    }

    /**
     * List of poneys.
     */
    public function listPoneysAction()
    {
        $list = [];
        $poneys = $this->get('database')
                       ->select('node', 'n')
                       ->fields('n', ['nid', 'title'])
                       ->condition('type', 'poney')
                       ->exectute()
                       ->fetchAllKeyed();
        foreach($poneys as $nid => $name) {
            $list[] = $this->t("Poney #@nid: @name", ['@nid' => $nid, '@name' => $name]));
        }

        $render_array['list'] = [
          '#theme' => 'item_list',
          '#items' => $list,
          '#title' => $this->t('Poneys'),
        ];
        return $render_array;
    }

    /**
     * Screen that displays poney details.
     */
    public function viewPoneyAction(NodeInterface $node)
    {
        if ($node->getType() !== 'poney') {
            throw $this->createNotFoundException(); // Same as throwing Symfony NotFoundHttpException
        }
        if ($node->uid !== $this->getCurrentUser()->uid) {
            throw $this->createAccessDeniedException(); // Same as throwing Symfony AccessDeniedHttpException
        }

        return [
            '#theme'     => 'node',
            '#node'      => $node,
            '#view_mode' => 'etable',
        ];
    }
}

La totalité des services n'ayant pas été backportés, il est préféréable du'iliser des requêtes simples. il nous reste à déclarer nos routes, toujours dans le hook_menu() pour le moment :

<?php

use Poney\Controller\PoneyController;

/**
 * Implements hook_menu().
 */
function poney_menu() {
  $items = [];

  $items['poneys'] = [
    'title'             => 'List of poneys',
    'page callback'     => 'sf_dic_page',
    'page arguments'    => [PoneyController::class . '::listPoneys'],
    'type'              => MENU_CALLBACK,
  ];
  $items['poney/%node'] = [
    'title'             => 'Poney info',
    'page callback'     => 'sf_dic_page',
    'page arguments'    => [PoneyController::class . '::viewPoney', 1],
    'type'              => MENU_CALLBACK,
  ];

  return $items;
}

On remarquera que l'on passe la méthode en argument et non en callback, ce afin que sf_dic puisse travailler.

Création d'un formulaire simple

<?php

namespace Poney\Form;

use Drupal\node\NodeInterface;
use Drupal\Core\Entity\EntityManager;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;

use Symfony\Component\DependencyInjection\ContainerInterface;

class PoneyEditForm extends FormBase
{
    static public function create(ContainerInterface $container)
    {
        // You can get the services you need as arguments here
        return new self(
            $container->get('entity.manager')
        );
    }

    private $entityManager;

    public function getFormId()
    {
        return 'poney_edit_form';
    }

    public function __construct(EntityManager $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    public function buildForm(array $form, FormStateInterface $formState, NodeInterface $poney = null)
    {
        if (!$poney) {
            return $form;
        }

        $formState->setTemporaryValue('poney', $poney);

        $form['name'] = [
            '#title'            => t("Poney name"),
            '#type'             => 'textfield',
            '#attributes'       => ['placeholder' => $this->t('Caramel')],
            '#default_value'    => $poney->title,
            '#required'         => true,
        ];

        $form['actions']['#type'] = 'actions';
        $form['actions']['submit'] = [
            '#type'   => 'submit',
            '#value'  => $this->t("Submit"),
        ];

        return $form;
    }

    public function submitForm(array &$form, FormStateInterface $formState)
    {
        /* @var $poney NodeInterface */
        $poney = $formState->getTemporaryValue('poney');
        $poney->title = $formState->getValue('title');
        $this->entityManager->getStorage()->save($poney);
    }
}

et la route associée :

<?php

/**
 * Implements hook_menu().
 */
function poney_menu() {
  $items['poney/%node/edit'] = [
    'title'            => "Poney management",
    'page callback'    => 'sf_dic_page_form',
    'page arguments'   => ['Poney\Form\PoneyEditForm', 1],
    'access callback'  => TRUE,
    'type'             => MENU_CALLBACK,
  ];
  return $items;
}
ABONNEZ-VOUS À LA NEWSLETTER !
Voir aussi
Faut-il mettre Drupal ou Symfony dans son cahier des charges ? Aucun ! Décrivez nous votre besoin ! 20/02/2017

Cela fait des années que nous développons des sites web sur la base d'un CMS ou d'un framework, ...

10 modules Drupal que vous n'utilisez (peut-être) pas assez 10 modules Drupal que vous n'utilisez (peut-être) pas assez 10/12/2014

Dans l'équipe Drupal de Makina Corpus, nous avons l'opportunité de travailler sur de nombreux ...

Gérer ses dépendances Drupal 7 avec Composer Gérer ses dépendances Drupal 7 avec Composer 08/12/2016

Drupal.org distribue maintenant les modules et thèmes dans son propre dépôt Packagist, voyons ...

Le framework Symfony, un choix réfléchi 05/10/2016

Alliant souplesse, performance et efficacité, Symfony devient un incontournable dans nos ...

Utiliser Migrate en Drupal 8 Utiliser Migrate en Drupal 8 20/12/2016

Trucs, astuces et points d'attention pour l'import de données avec Migrate en Drupal 8.