← Tous les cours / Symfony Config / Extension Cours
00 — LE PROBLÈME

Le problème de la configuration

Comment Symfony transforme tes fichiers YAML en une configuration validée, fusionnée et typée.

Imagine que tu crées un bundle Symfony qui a besoin d'un host, d'un port et d'un timeout. L'utilisateur va coller ça dans config/packages/mon_bundle.yaml. Mais comment vas-tu le valider, lui donner des valeurs par défaut, et le fusionner s'il y a plusieurs fichiers d'environnement ?

La réponse de Symfony tient en trois objets : TreeBuilder, Processor et Extension. Ils forment un pipeline — de tes fichiers YAML jusqu'aux paramètres du conteneur de services.

Le pipeline de configuration
1. TreeBuilder décrit le schéma (types, défauts, contraintes).
2. Processor fusionne tous les fragments et valide contre le schéma.
3. Extension reçoit la config validée et enregistre des services.

Lance le pipeline pour voir comment tes fichiers YAML deviennent une config typée.

FICHIERS YAML
config/packages/mon_bundle.yaml config/packages/dev/mon_bundle.yaml
PROCESSOR en attente…
EXTENSION en attente…

Ce pipeline s'exécute à chaque compilation du conteneur — c'est-à-dire au premier démarrage après avoir vidé le cache. En production, le résultat est mis en cache et aucun traitement YAML n'a lieu à chaque requête.

💡 Performance : la compilation est coûteuse mais ne se produit qu'une fois. En production, le conteneur est sérialisé en PHP pur — zéro parsing YAML au runtime.
01 — TREEBUILDER

TreeBuilder — définir le schéma

TreeBuilder est le langage de description du schéma de configuration — types, valeurs par défaut, validations.

Le TreeBuilder est le DSL (Domain Specific Language) qui décrit à quoi ressemble ta configuration. Tu l'utilises pour déclarer chaque clé attendue, son type, ses contraintes et sa valeur par défaut.

TreeBuilder
Objet racine du schéma. Il expose un nœud racine de type array par défaut. Le schéma se construit par appels chaînés (fluent interface).
use Symfony\Component\Config\Definition\Builder\TreeBuilder;

$treeBuilder = new TreeBuilder('mon_bundle');
$root = $treeBuilder->getRootNode(); // retourne ArrayNodeDefinition

$root
    ->children()
        ->scalarNode('host')
            ->isRequired()
            ->cannotBeEmpty()
            ->info('Hôte du serveur distant')
        ->end()
        ->integerNode('port')
            ->defaultValue(443)
            ->min(1)
            ->max(65535)
        ->end()
        ->booleanNode('verify_ssl')
            ->defaultTrue()
        ->end()
    ->end()
;

Les types de nœuds

Méthode Type PHP Usage typique
scalarNode() string, int, float, bool, null Valeur simple polyvalente
stringNode() string Texte strict (pas de coercition)
booleanNode() bool Interrupteur on/off
integerNode() int Entiers avec min/max
floatNode() float Nombres décimaux
enumNode() string Liste de valeurs autorisées
arrayNode() array Structure imbriquée ou liste
variableNode() mixed Structure arbitraire (dernier recours)

enumNode — valeurs contraintes

$root
    ->children()
        ->enumNode('driver')
            ->values(['mysql', 'pgsql', 'sqlite'])
            ->defaultValue('pgsql')
        ->end()
    ->end()
;
🔑 ->end() remonte au parent. Chaque ->end() dans la chaîne fluente retourne au nœud parent. Oublier un ->end() est l'erreur la plus fréquente avec TreeBuilder — le schéma compile mais les enfants ne sont pas attachés au bon nœud.

Nœuds imbriqués

Un arrayNode peut lui-même avoir des enfants :

$root
    ->children()
        ->arrayNode('connection')
            ->addDefaultsIfNotSet()
            ->children()
                ->scalarNode('host')->defaultValue('localhost')->end()
                ->integerNode('port')->defaultValue(5432)->end()
            ->end()
        ->end()
    ->end()
;

addDefaultsIfNotSet() garantit que si l'utilisateur omet complètement la clé connection, elle sera quand même présente dans la config finale avec ses valeurs par défaut — sans lancer d'exception.

02 — NŒUDS AVANCÉS

Nœuds avancés — prototypes, canBeDisabled et validations

Les patterns avancés du TreeBuilder : listes prototypées, conditions d'activation, règles de validation croisée.

Au-delà des nœuds scalaires, TreeBuilder propose des patterns pour les listes homogènes, les blocs optionnels et les validations croisées entre plusieurs clés.

Prototypes — listes homogènes

Un prototype définit le schéma d'un élément répété. C'est l'équivalent d'un type de tableau : tu décris une entrée, Symfony l'applique à chaque élément.

$root
    ->children()
        ->arrayNode('connections')
            ->arrayPrototype()          // chaque entrée est un tableau
                ->children()
                    ->scalarNode('host')->isRequired()->end()
                    ->integerNode('port')->defaultValue(3306)->end()
                ->end()
            ->end()
        ->end()
    ->end()
;

YAML correspondant :

connections:
    primary:
        host: db1.example.com
    replica:
        host: db2.example.com
        port: 3307
💡 scalarPrototype vs arrayPrototype : utilise scalarPrototype() pour une liste de chaînes simples (['tag1', 'tag2']), arrayPrototype() pour une liste de structures.

canBeDisabled / canBeEnabled

Ces deux méthodes ajoutent automatiquement une clé enabled au nœud. Elles sont très utilisées dans les bundles Symfony (cache, mailer, security…).

canBeDisabled()
enabled: true par défaut.
L'utilisateur peut désactiver le bloc avec enabled: false.
->arrayNode('cache')
    ->canBeDisabled()
    ->children()
        ->scalarNode('driver')
            ->defaultValue('redis')
        ->end()
    ->end()
->end()
canBeEnabled()
enabled: false par défaut.
L'utilisateur active le bloc avec enabled: true.
->arrayNode('profiler')
    ->canBeEnabled()
    ->children()
        ->booleanNode('collect')
            ->defaultTrue()
        ->end()
    ->end()
->end()

Validations croisées avec validate()

validate() retourne un ExprBuilder qui permet d'écrire une règle de validation arbitraire sur la valeur finale d'un nœud.

$root
    ->children()
        ->arrayNode('cache')
            ->addDefaultsIfNotSet()
            ->children()
                ->enumNode('driver')
                    ->values(['redis', 'memcached', 'array'])
                    ->defaultValue('array')
                ->end()
                ->scalarNode('dsn')->defaultNull()->end()
            ->end()
            ->validate()
                ->ifTrue(fn($v) => $v['driver'] !== 'array' && null === $v['dsn'])
                ->thenInvalid('Le DSN est obligatoire pour le driver "%s"')
            ->end()
        ->end()
    ->end()
;

beforeNormalization — normaliser avant validation

beforeNormalization() s'exécute avant la fusion et la validation. C'est utile pour accepter un raccourci syntaxique et le transformer en structure complète.

$root
    ->children()
        ->arrayNode('logger')
            ->beforeNormalization()
                ->ifString()                   // si l'utilisateur écrit juste une chaîne
                ->then(fn($v) => ['channel' => $v])  // on la transforme en tableau
            ->end()
            ->children()
                ->scalarNode('channel')->defaultValue('app')->end()
            ->end()
        ->end()
    ->end()
;
⚠️ Ordre d'exécution : beforeNormalization → fusion des configs → validate(). Ne pas utiliser validate() pour faire ce que beforeNormalization peut faire — la normalisation est là pour la rétrocompatibilité, la validation pour les contraintes métier.
03 — PROCESSOR

Processor — fusionner et valider

Le Processor est le moteur qui fusionne plusieurs configurations et les valide contre le schéma.

Le Processor est le moteur qui prend plusieurs fragments de configuration (un par environnement, un par bundle…) et les fusionne en une seule structure validée. Pense à lui comme un compilateur pour ta config.

Processor::processConfiguration()
Accepte une instance de ConfigurationInterface (ton schéma) et un tableau de configurations brutes (chaque élément = un fichier YAML parsé). Retourne un tableau PHP propre, fusionné et typé.
use Symfony\Component\Config\Definition\Processor;

$processor = new Processor();
$config = $processor->processConfiguration(
    new MonBundleConfiguration(),  // implémente ConfigurationInterface
    [
        // config de base (config/packages/mon_bundle.yaml)
        ['host' => 'db.example.com', 'port' => 5432],
        // surcharge dev (config/packages/dev/mon_bundle.yaml)
        ['host' => 'localhost'],
    ]
);
// $config = ['host' => 'localhost', 'port' => 5432, 'verify_ssl' => true]

Règles de fusion

La fusion suit des règles précises :

Type Comportement
Scalaire La dernière valeur écrase les précédentes
Tableau associatif Fusion récursive clé par clé
Tableau numérique (liste) Concaténation (les entrées s'additionnent)
Nœud avec performDeepMerging(false) Remplacement total du tableau
🔑 Cas concret : si tu as tags: ['a', 'b'] en base et tags: ['c'] en dev, le résultat sera tags: ['a', 'b', 'c']. Si c'est un associatif (mapping: {x: 1} + mapping: {y: 2}), tu obtiens mapping: {x: 1, y: 2}.

ConfigurationInterface

Toute classe de configuration implémente cette interface avec une seule méthode :

use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;

final class Configuration implements ConfigurationInterface
{
    public function getConfigTreeBuilder(): TreeBuilder
    {
        $treeBuilder = new TreeBuilder('mon_bundle');

        $treeBuilder->getRootNode()
            ->children()
                ->scalarNode('host')->isRequired()->end()
                ->integerNode('port')->defaultValue(443)->end()
            ->end()
        ;

        return $treeBuilder;
    }
}
💡 Valider sans Extension : tu peux utiliser Processor + ConfigurationInterface en dehors d'un bundle — par exemple pour valider un fichier de config dans une application console. Ce n'est pas réservé aux bundles.
04 — ABSTRACTEXTENSION

AbstractExtension — configure, loadExtension

AbstractExtension est la classe de base moderne d'une extension Symfony 8.1 — configure() définit le schéma, loadExtension() charge les services.

AbstractExtension est la classe de base recommandée depuis Symfony 6.1 pour créer une extension de bundle. Elle sépare proprement trois responsabilités : définir le schéma, préparer d'autres bundles, et charger les services.

AbstractExtension
Classe abstraite qui implémente ConfigurableExtensionInterface et PrependExtensionInterface. Elle gère automatiquement la normalisation et la fusion — tu n'as pas à instancier Processor manuellement.
use Symfony\Component\DependencyInjection\Extension\AbstractExtension;
use Symfony\Component\DependencyInjection\Extension\DefinitionConfigurator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

final class MonBundleExtension extends AbstractExtension
{
    // 1) Déclarer le schéma de configuration
    public function configure(DefinitionConfigurator $definition): void
    {
        $definition->rootNode()
            ->children()
                ->scalarNode('host')->isRequired()->end()
                ->integerNode('port')->defaultValue(443)->end()
            ->end()
        ;
    }

    // 2) Charger les services avec la config validée
    public function loadExtension(
        array $config,
        ContainerConfigurator $configurator,
        ContainerBuilder $container
    ): void {
        // $config est déjà normalisé et validé
        $configurator->services()
            ->set(MonService::class)
            ->arg('$host', $config['host'])
            ->arg('$port', $config['port'])
        ;
    }
}

configure() avec import de fichier

Pour les gros schémas, tu peux déléguer à un fichier YAML ou PHP externe :

public function configure(DefinitionConfigurator $definition): void
{
    $definition->import('../config/definition.php');
    // ou '../config/definition.yaml'
}

loadExtension() — enregistrer des services

Dans loadExtension(), tu utilises ContainerConfigurator comme dans config/services.yaml, mais en PHP.

public function loadExtension(array $config, ContainerConfigurator $cc, ContainerBuilder $cb): void
{
    // Charger un fichier de services
    $cc->import('../config/services.php');

    // Définir un paramètre
    $cb->setParameter('mon_bundle.host', $config['host']);

    // Activer/désactiver des services selon la config
    if ($config['cache']['enabled']) {
        $cc->services()
            ->set(CacheLayer::class)
            ->arg('$driver', $config['cache']['driver']);
    }
}
AbstractExtension (recommandé)
  • configure() pour le schéma
  • loadExtension() pour les services
  • Processor géré automatiquement
  • ContainerConfigurator fluent
Extension classique
  • Hérite de Extension
  • load() fait tout
  • Processor appelé manuellement
  • XmlFileLoader / YamlFileLoader
05 — PREPENDEXTENSIONINTERFACE

PrependExtensionInterface — reconfigurer les autres

prepend() permet de modifier la configuration d'autres bundles avant qu'ils ne se chargent — c'est la clé de l'interopérabilité.

PrependExtensionInterface donne à ton bundle le pouvoir de modifier la configuration d'autres bundles avant qu'ils ne se chargent. C'est le mécanisme derrière l'intégration automatique de bundles comme DoctrineBundle avec SecurityBundle.

prepend()
Méthode appelée avant tous les load(). Elle reçoit le ContainerBuilder complet et peut injecter de la configuration dans n'importe quelle extension déjà enregistrée.

Avec AbstractExtension, il suffit d'implémenter prependExtension() (la version fluente) :

use Symfony\Component\DependencyInjection\Extension\AbstractExtension;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\DependencyInjection\ContainerBuilder;

final class MonBundleExtension extends AbstractExtension
{
    public function prependExtension(
        ContainerConfigurator $configurator,
        ContainerBuilder $container
    ): void {
        // Ajouter de la config à Doctrine avant que DoctrineBundle se charge
        $container->prependExtensionConfig('doctrine', [
            'dbal' => [
                'types' => [
                    'mon_type' => MonType::class,
                ],
            ],
        ]);

        // Lire la config existante d'un bundle
        $doctrineConfigs = $container->getExtensionConfig('doctrine');
    }
}

Cas d'usage typiques

Situation Ce que prepend() fait
Bundle d'authentification Ajoute un user provider à SecurityBundle
Bundle de cache Configure les pools dans FrameworkBundle
Bundle ORM custom Enregistre des types Doctrine personnalisés
Bundle de monitoring Active le profiler Symfony en dev
⚠️ Ordre non garanti entre bundles : plusieurs bundles peuvent faire prepend() sur le même bundle. Leurs contributions sont toutes collectées, puis fusionnées dans l'ordre d'enregistrement des bundles dans config/bundles.php. Ne pas supposer un ordre précis entre extensions concurrentes.

PrependExtensionInterface classique

Si tu utilises l'Extension classique (sans AbstractExtension), tu implémentes l'interface directement :

use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

final class MonBundleExtension extends Extension implements PrependExtensionInterface
{
    public function prepend(ContainerBuilder $container): void
    {
        $container->prependExtensionConfig('framework', [
            'cache' => ['pools' => ['mon_pool' => ['adapter' => 'cache.adapter.redis']]],
        ]);
    }

    public function load(array $configs, ContainerBuilder $container): void
    {
        // traitement normal...
    }
}
06 — RESOURCES

Resources — cache invalidation

FileResource, DirectoryResource et GlobResource permettent à Symfony de savoir quand invalider le cache du conteneur.

Symfony met en cache le conteneur de services compilé. Pour savoir quand ce cache est périmé, il suit des ressources : fichiers, répertoires, patterns glob. Quand une ressource change, le cache est invalidé et le conteneur recompilé.

SelfCheckingResourceInterface
Interface commune à toutes les ressources. La méthode isFresh(int $timestamp): bool répond à la question : "cette ressource a-t-elle changé depuis $timestamp ?"

Les trois types de ressources

FileResource
Suit le mtime d'un fichier unique.
use Symfony\Component\Config\Resource\FileResource;

$container->addResource(
    new FileResource(__DIR__.'/../config/services.yaml')
);
DirectoryResource
Suit récursivement un répertoire (optionnellement filtré).
use Symfony\Component\Config\Resource\DirectoryResource;

$container->addResource(
    new DirectoryResource(
        __DIR__.'/../Entity',
        '/\.php$/'   // filtre regex
    )
);

GlobResource

use Symfony\Component\Config\Resource\GlobResource;

$container->addResource(
    new GlobResource(
        __DIR__.'/../config',  // répertoire de base
        '*.yaml',              // pattern glob
        true                   // récursif
    )
);

GlobResource est plus efficace que DirectoryResource quand tu n'as besoin de suivre qu'un type de fichier spécifique dans une arborescence.

ConfigCacheInterface — mettre en cache le résultat

Symfony fournit aussi un mécanisme pour mettre en cache le résultat d'un traitement coûteux (par exemple, la lecture de toutes tes annotations de mapping Doctrine) :

use Symfony\Component\Config\ConfigCacheFactory;
use Symfony\Component\Config\ResourceCheckerConfigCacheFactory;

$cachePath = $cacheDir.'/mon_bundle.php';
$cache = new ResourceCheckerConfigCacheFactory()->getCache($cachePath);

if (!$cache->isFresh()) {
    // recalcule le résultat coûteux
    $data = $this->computeExpensiveData();
    $resources = [new FileResource($sourceFile)];

    $cache->write('<?php return '.var_export($data, true).';', $resources);
}

$result = require $cache->getPath();
🔑 Quand ajouter des ressources dans load() : chaque fois que tu lis un fichier dans ton extension (YAML de mapping, fichier de routage, template…), ajoute une FileResource correspondante. Sans ça, Symfony ne sait pas que le cache doit être invalidé quand ce fichier change.
07 — BUNDLE COMPLET

Architecture complète d'un bundle

Configuration + Extension + Bundle — les trois fichiers qui rendent un bundle configurable.

Un bundle configurable se compose toujours des mêmes trois fichiers. Voyons-les ensemble, puis teste la validation du schéma avec le simulateur.

MonBundle.php
MonBundleExtension.php
Configuration.php

1. MonBundle.php

namespace Acme\MonBundle;

use Symfony\Component\HttpKernel\Bundle\AbstractBundle;

final class MonBundle extends AbstractBundle
{
    // AbstractBundle pointe automatiquement vers DependencyInjection/MonBundleExtension
    // Si tu utilises AbstractExtension inline, rien d'autre à faire ici.
}
💡 AbstractBundle vs Bundle : depuis Symfony 6.1, AbstractBundle est la classe de base recommandée. Elle intègre nativement le support d'AbstractExtension et du chargement automatique de config/services.php.

2. MonBundleExtension.php

namespace Acme\MonBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\Extension\AbstractExtension;
use Symfony\Component\DependencyInjection\Extension\DefinitionConfigurator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

final class MonBundleExtension extends AbstractExtension
{
    public function configure(DefinitionConfigurator $definition): void
    {
        $definition->rootNode()
            ->children()
                ->scalarNode('host')->isRequired()->end()
                ->integerNode('port')->defaultValue(5432)->min(1)->max(65535)->end()
                ->enumNode('driver')->values(['pgsql', 'mysql'])->defaultValue('pgsql')->end()
            ->end()
        ;
    }

    public function loadExtension(array $config, ContainerConfigurator $cc, ContainerBuilder $cb): void
    {
        $cc->import('../config/services.php');
        $cb->setParameter('mon_bundle.dsn', $config['driver'].'://'.$config['host'].':'.$config['port']);
    }
}

3. Configuration.php (optionnel avec AbstractExtension)

namespace Acme\MonBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

final class Configuration implements ConfigurationInterface
{
    public function getConfigTreeBuilder(): TreeBuilder
    {
        $treeBuilder = new TreeBuilder('mon_bundle');
        $treeBuilder->getRootNode()
            ->children()
                ->scalarNode('host')->isRequired()->end()
            ->end()
        ;
        return $treeBuilder;
    }
}
⚠️ configure() ou Configuration.php ? Avec AbstractExtension, tu n'as pas besoin de Configuration.php séparé — configure() fait le même travail. Garde Configuration.php uniquement si tu veux pouvoir utiliser Processor en dehors de l'extension (tests unitaires, scripts console).

Modifie la configuration YAML et clique sur "Valider" pour simuler la validation du schéma.

08 — DÉBOGUER LA CONFIG

Déboguer la configuration — config:dump

config:dump-reference, debug:config et debug:container — les trois commandes pour inspecter la configuration.

Symfony fournit trois commandes complémentaires pour inspecter ta configuration. Savoir laquelle utiliser selon la situation t'évitera beaucoup de tâtonnements.

config:dump-reference
Affiche le schéma de configuration d'un bundle — ce que tu peux mettre dans ton fichier YAML, avec toutes les valeurs par défaut.
# Affiche le schéma de configuration de FrameworkBundle
php bin/console config:dump-reference framework

# Affiche uniquement la section cache
php bin/console config:dump-reference framework cache

# Format YAML (défaut) ou XML
php bin/console config:dump-reference framework --format=xml
debug:config
Affiche la configuration effective d'un bundle dans l'application courante — la config fusionnée et validée telle que Symfony l'a reçue.
# Quelle config doctrine a-t-elle effectivement reçue ?
php bin/console debug:config doctrine

# Avec l'environnement de production
APP_ENV=prod php bin/console debug:config doctrine
debug:container
Inspecte le conteneur de services compilé — les paramètres, les services, les tags.
# Lister tous les paramètres
php bin/console debug:container --parameters

# Trouver un paramètre précis
php bin/console debug:container --parameter=kernel.project_dir

# Inspecter un service
php bin/console debug:container App\Service\MonService

# Services tagués
php bin/console debug:container --tag=twig.extension

Workflow de débogage

Ma config YAML est-elle bien lue ?
debug:config
Le schéma accepte-t-il cette clé ?
config:dump-reference
Mon service a-t-il le bon paramètre ?
debug:container
Commande Répond à Quand l'utiliser
config:dump-reference Que puis-je configurer ? Découverte du bundle, documentation
debug:config Qu'est-ce qui est chargé ? Vérifier la fusion multi-env
debug:container Qu'est-ce qui est dans le conteneur ? Vérifier les services et paramètres
🔑 Astuce prod : debug:config et debug:container fonctionnent aussi avec APP_ENV=prod. C'est très utile pour diagnostiquer pourquoi une config de prod diffère du dev sans avoir à déployer des logs.
09 — SYNTHÈSE

Synthèse — guide de choix

AbstractExtension vs Extension, configure() vs Configuration class, quand utiliser prepend.

Tu connais maintenant tous les maillons du pipeline de configuration Symfony. Cette synthèse te donne des règles de décision claires pour choisir la bonne approche.

AbstractExtension vs Extension classique

Critère AbstractExtension Extension classique
Symfony requis ≥ 6.1 Toutes versions
Schéma de config configure() Configuration.php séparé
Processor Géré automatiquement Appelé manuellement
Chargement services loadExtension() load()
prepend prependExtension() PrependExtensionInterface::prepend()
Recommandé pour Nouveaux bundles Bundles legacy, compatibilité

configure() inline vs Configuration.php séparé

configure() inline
Recommandé
  • Tout en un seul fichier
  • Moins de boilerplate
  • Parfait pour la majorité des bundles
Configuration.php séparé
Selon besoin
  • Testable unitairement
  • Réutilisable hors extension
  • Schémas très complexes (>50 nœuds)

Quand utiliser prepend ?

💡 Règle simple : utilise prepend() quand ton bundle intègre un autre bundle — il sait qu'il coexiste avec lui et veut lui fournir une configuration de départ cohérente. Ne l'utilise pas pour corriger la config de l'utilisateur ou pour contourner une mauvaise architecture.

Ordre d'exécution — récapitulatif

1. Tous les
prepend()
2. Tous les
load()
3. Compiler
Passes
4. Cache
compilé

Checklist bundle configurable

🔑 Avant de livrer ton bundle :
  • ✓ TreeBuilder couvre toutes les clés documentées
  • ✓ Valeurs par défaut raisonnables sur tous les nœuds optionnels
  • isRequired() sur les clés sans défaut sensé
  • enumNode pour toute clé à valeurs finies
  • info() sur chaque nœud non trivial (alimente config:dump-reference)
  • FileResource ajoutée pour chaque fichier lu dans load()
  • config:dump-reference testé — l'output YAML est lisible