Dans cet article, nous allons essayer de traiter un composant appelé Config , qui aide à charger et traiter diverses données quelle que soit la source.
Ce qui suit est une traduction de l' aperçu des composants Symfony2: Config . L'original a été publié en 2014 et indique la deuxième version de Symfony, mais les informations sont pertinentes pour la dernière version, actuellement, la quatrième version.
Imaginons que nous voulons créer un générateur de blog qui prendra plusieurs paramètres tels qu'un titre ( titre ), une description ( description ), le nombre de publications sur la page principale ( posts_main_page ), les icônes de réseaux sociaux ( social ) et la présence ou l'absence de RSS
RSS ( rss ) . À ces fins, nous décrirons le fichier de configuration au format YAML
:
blog: title: description: ... rss: true posts_main_page: 2 social: twitter: url: http://twitter.com/raulfraile icon: twitter.png sensiolabs_connect: url: https://connect.sensiolabs.com/profile/raulfraile icon: sensiolabs_connect.png
Nous allons maintenant essayer d'analyser notre fichier, vérifier la disponibilité des champs obligatoires et définir des valeurs par défaut si nécessaire. Nous vérifierons la conformité de toutes les données obtenues avec les règles établies, par exemple, rss ne peut contenir qu'une valeur booléenne et posts_main_page doit contenir une valeur entière comprise entre 1 et 10. Nous devrons répéter ces procédures à chaque fois que le fichier est accédé, à moins bien sûr que le système de mise en cache soit utilisé . De plus, un tel mécanisme complique l'utilisation de fichiers d'autres formats tels que INI
, XML
ou JSON
.
Pour simplifier les actions ci-dessus, nous utiliserons le composant Config . Le composant est simple, bien testé et suffisamment flexible pour être utilisé dans différents projets.
L'architecture
Nous diviserons le projet en deux parties principales:
- Définition de la structure hiérarchique des paramètres.
Le composant vous permet de déterminer le format de la source de configuration, qui peut aller d'un simple fichier INI
à quelque chose de plus exotique. La classe TreeBuilder vous aidera à déterminer les types de paramètres, à les rendre obligatoires / facultatifs et à définir la valeur par défaut. - Détection, chargement et traitement.
Une fois le format source spécifié, il doit être trouvé, chargé et traité. Enfin, le composant renverra un tableau simple avec des valeurs vérifiées ou lèvera une exception en cas d'erreur.
Exemple
Revenons à notre exemple. Et donc, nous voulons créer un système de génération de blog flexible. Tout d'abord, nous définissons une structure hiérarchique (arbre), et une instance de la classe TreeBuilder nous aidera avec cela, qui fournit une interface de type syntaxe DSL
.
<?php namespace RaulFraile\Config; use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\Definition\Builder\TreeBuilder; class Configuration implements ConfigurationInterface { public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('blog'); $rootNode ->children() ->scalarNode('title') ->isRequired() ->end() ->scalarNode('description') ->defaultValue('') ->end() ->booleanNode('rss') ->defaultValue(false) ->end() ->integerNode('posts_main_page') ->min(1) ->max(10) ->defaultValue(5) ->end() ->arrayNode('social') ->arrayPrototype() ->children() ->scalarNode('url')->end() ->scalarNode('icon')->end() ->end() ->end() ->end() ->end() ; return $treeBuilder; } }
Ne vous inquiétez pas si vous voyez une structure similaire de code PHP pour la première fois, la syntaxe DSL
paraît toujours un peu étrange. Dans l'exemple ci-dessus, nous avons déterminé le nœud racine du blog et construit à partir de celui-ci la structure de l'arborescence de configuration, dont les branches sont les paramètres dont nous avons besoin et les règles pour leurs valeurs. Par exemple, le titre est indiqué comme paramètre obligatoire d'un type scalaire, la description comme paramètre facultatif, qui est vide par défaut, dans rss nous attendons une valeur booléenne qui est false
par défaut, et posts_main_page devrait contenir une valeur entière dans la plage de 1 à 10, 5 étant la valeur par défaut .
Eh bien, nous avons défini la structure, passons maintenant au chargement et au traitement. Par condition, la source peut être quelconque, pour commencer, nous devons la convertir en un tableau régulier afin de vérifier et de traiter toutes les valeurs en utilisant notre structure de configuration. Pour chaque format source, nous avons besoin d'une classe distincte, donc si nous voulons utiliser les formats de fichier YAML
et XML
, nous devons créer deux classes. Dans l'exemple ci-dessous, par souci de simplicité, seule la classe de format YAML
est présentée:
<?php namespace RaulFraile\Config; use Symfony\Component\Config\Loader\FileLoader; use Symfony\Component\Yaml\Yaml; class YamlConfigLoader extends FileLoader { public function load($resource, $type = null) { $configValues = Yaml::parse(file_get_contents($resource)); return $configValues; } public function supports($resource, $type = null) { return is_string($resource) && 'yml' === pathinfo( $resource, PATHINFO_EXTENSION ); } }
Comme vous pouvez le voir, tout est très simple. La méthode YamlConfigLoader::supports
est utilisée par la classe LoaderResolver
pour vérifier la source de configuration. La méthode YamlConfigLoader::load
convertit un fichier YAML
en un tableau de données à l'aide d'un autre composant Symfony YAML .
En conclusion, nous combinons la structure de configuration et le chargeur lors du traitement de la source pour obtenir les valeurs nécessaires:
<?php use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Loader\LoaderResolver; use Symfony\Component\Config\Loader\DelegatingLoader; use Symfony\Component\Config\Definition\Processor; use RaulFraile\Config\YamlConfigLoader; use RaulFraile\Config\Configuration; include_once __DIR__. '/vendor/autoload.php';
Analysons ce code. Tout d'abord, nous définissons un tableau de répertoires où les fichiers de configuration peuvent être localisés, et le plaçons comme paramètre dans l'objet FileLocator
qui recherche le fichier config.yml
dans le répertoire spécifié. Ensuite, créez un objet YamlConfigLoader
qui retourne un tableau avec des valeurs, et il est déjà traité par notre structure de configuration.
En conséquence, nous obtenons le tableau suivant:
array(5) { 'title' => string(7) "My blog" 'description' => string(24) "This is just a test blog" 'rss' => bool(true) 'posts_main_page' => int(2) 'social' => array(2) { 'twitter' => array(2) { 'url' => string(29) "http://twitter.com/raulfraile" 'icon' => string(11) "twitter.png" } 'sensiolabs_connect' => array(2) { 'url' => string(49) "https://connect.sensiolabs.com/profile/raulfraile" 'icon' => string(22) "sensiolabs_connect.png" } } }
Si nous essayons de changer config.yml
supprimant les champs rss et post_main_page , nous obtiendrons les valeurs par défaut:
array(5) { ... 'rss' => bool(false) 'posts_main_page' => int(5)
Mise en cache
Le traitement de fichiers de configuration volumineux peut être une tâche longue. Le composant décrit possède un mécanisme de mise en cache simple basé sur la vérification de la date de modification du fichier de configuration.
Pour activer le cache, quelques lignes de code suffisent:
<?php use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\ConfigCache; use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Config\Definition\Processor; use RaulFraile\Config\YamlConfigLoader; use RaulFraile\Config\Configuration; include_once __DIR__. '/vendor/autoload.php'; $cachePath = __DIR__.'/cache/config.php'; $configFile = 'config.yml';
Une instance de la classe ConfigCache
vérifie l'existence d'un cache de fichiers et, si disponible, compare la date de modification du fichier de configuration. Lorsque nous créons un cache de fichiers, nous enregistrons également une liste d'objets utilisés pour une comparaison ultérieure.
Chargement multiple
Pour ajouter un autre format de configuration, il suffit de définir une classe qui sera responsable d'un format spécifique. Dans l'exemple ci-dessous, nous avons ajouté la prise en charge de la configuration XML
et un gestionnaire correspondant. La classe LoaderResolver
nous aidera à combiner différents formats dans un pool commun, et la classe DelegatingLoader
chargera le fichier requis sur demande.
<?php namespace RaulFraile\Config; use Symfony\Component\Config\Loader\FileLoader; class XmlConfigLoader extends FileLoader { public function load($resource, $type = null) {
$loaderResolver = new LoaderResolver(array( new YamlConfigLoader($locator), new XmlConfigLoader($locator) )); $delegatingLoader = new DelegatingLoader($loaderResolver); $configValues = $delegatingLoader->load($locator->locate('config.xml'));
Entre autres choses, le composant a la fonctionnalité de générer des informations de référence pour votre documentation.
<?php ... use Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper; $dumper = new YamlReferenceDumper(); echo $dumper->dump($configuration);
Il produira:
blog: title: ~ # Required description: '' rss: false posts_main_page: 5 social: url: ~ icon: ~
Résumé
Peut-être trouverez-vous que tout cela est trop compliqué et déroutant, et vous pouvez le faire avec quelques fonctions. Peut-être, mais tel est le «prix» d'une bonne structure de POO. En revanche, ce composant offre plusieurs avantages:
- ~ 80% de couverture du composant avec des tests et un support actif.
- L'ajout de nouveaux formats de configuration est vraiment facile. Il suffit de définir un gestionnaire qui convertit les données source en un tableau régulier. Une structure similaire sera utilisée pour d'autres formats. L'extension de n'importe quelle partie du composant est implémentée en ajoutant l'interface nécessaire.
- La mise en cache fonctionne dès le départ avec des paramètres flexibles pour les environnements de développement et de
prod
. - Vérification intégrée des paramètres et de leurs valeurs.
- Génération d'informations de base.