Makina Blog
Utiliser le DIC de Symfony dans Drupal 7
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
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. 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;
}
Actualités en lien
DbToolsBundle : sortie de la version 1.2
Découvrez les nouveautés de cette nouvelle version ainsi que les fonctionnalités à venir de la prochaine version majeure.
Access Control, bibliothèque PHP pour gérer des droits d’accès
Suite à un projet de gestion métier opérationnel dont la durée de vie et la maintenance sont à long termes, nous avons expérimenté un passage de celui-ci sur l’architecture hexagonale et la clean architecture.
Migration d'un site Drupal 7 en Drupal 11
Trucs, astuces et "bouts" de code pour migrer votre site web de Drupal 7 à Drupal 11. Compte-rendu d'une conférence donnée au Drupalcamp Rennes 2024.