Pieceã®äžã®Symfony #4: Configã³ã³ããŒãã³ã
Symfony Advent Calendar JP 2012 - Day 6
ä»åã¯Stagehand_TestRunnerãPiece_FlowçãPiece Frameworkã®ããã€ãã®ãããã¯ãã§äœ¿ãããŠããConfigã³ã³ããŒãã³ãã«ã€ããŠè§£èª¬ããŸãã
Symfony Configã³ã³ããŒãã³ã
Configã³ã³ããŒãã³ãã¯ãœãããŠã§ã¢ã®å¯å€éšåã衚çŸããèšèªãå®çŸ©ããåŠçããããã®ãã¬ãŒã ã¯ãŒã¯ã§ããSymfonyãã¬ãŒã ã¯ãŒã¯ã«ãããŠã¯ãã³ãã«ããã³ãã®èåŸã«ããã³ã³ããŒãã³ãã®å¯å€éšåããŠãŒã¶ãŒãæ§æããããã«äœ¿ãããŠããŸãã
Configã³ã³ããŒãã³ããåãæ±ãã®ã¯äžè¬çã«èšå®ãæ§æãšåŒã°ãããã®ã§ãã
ãã¡ã€ã³åºæèšèª (DSL) ã¯ç¹å®çšéåãã®èšèªã§ãããã¡ã€ã³åºæèšèªã¯ãã·ã¹ãã ãã¡ããªã®å
·äœçãªã¡ã³ãããçºæ³šãããã®ã«äœ¿ããããã«ãžã§ãã¬ãŒãã£ãããã°ã©ãã³ã°ã«ãããŠéèŠãªåœ¹å²ãæãããŸãã
â ãžã§ãã¬ãŒãã£ãããã°ã©ãã³ã° (IT ArchitectsâArchive CLASSIC MODER)
èšå®ã¯ãœãããŠã§ã¢ã®å¯å€éšåïŒå¯å€ç¹ïŒã«å¯Ÿããå
·äœçãªå€ã§ãããæçµçã«ãªããžã§ã¯ãã®ãããªå®è£
ã³ã³ããŒãã³ãã®æ§æã«å€æãããŸããå¥ã®èšãæ¹ããããšãèšå®ã«ãã£ãŠå
·äœçãªãœãããŠã§ã¢ãæ§æãããŸãã
èšå®ã¯ãã¡ã€ã³åºæã®èšèªã®äœè£ãæã£ãŠããããããã¡ã€ã³ç¹åèšèªïŒDSL: Domain Specific LanguageïŒã§ãããšãããŸãã
ããŒãµãŒãžã§ãã¬ãŒã¿ãŒïŒ
èšå®èšèªãååšãã以äžãã®ããŒãµãŒã¯å¿
é ãšãªããŸãããPHPã®äžçã§ã¯ãããŸã§ã¯ææžãã®ãã®ã§æžãŸããŠããäºäŸã倧éšåãå ããã®ã§ã¯ãªãã§ãããããæè»œã«äœ¿ããããŒãµãŒãžã§ãã¬ãŒã¿ãŒãååšããªãããšã倧ããªçç±ãããããŸããããããã ãã§ããããïŒ
Symfonyãã¬ãŒã ã¯ãŒã¯ã§ã¯èšå®ã®èšè¿°ã«INIãPHPãXMLãYAMLããã®ä»ä»»æã®ãã©ãŒãããã䜿ãããšãã§ããŸããéåžžã®ããŒãµãŒãžã§ãã¬ãŒã¿ãŒãåäžã®ããã¹ããã©ãŒãããã«å¯Ÿããææ³ãå®çŸ©ããããšã§ããŒãµãŒãçæãããã®ã ãšãããããã©ãŒãããæ¯ã«ææ³å®çŸ©ãå¿
èŠãšãªãçŸå®çã§ã¯ãããŸããã
æœè±¡æ§æãå®çŸ©ãã
ãŸããæœè±¡æ§æãå®çŸ©ããŸããããã¯æœè±¡åœ¢ã®ãã¹ããŒããã§ãã
次ã«ããšãã£ã¿ããå®çŸ©ããæœè±¡åœ¢ãæåœ±ãéããŠæäœå¯èœã«ããŸãã
ããããããžã§ãã¬ãŒã¿ããå®çŸ©ããŸããããã¯æœè±¡åœ¢ãã©ã®ããã«å®è¡å¯èœåœ¢ã«å€æããããå®çŸ©ããŠããŸããå®éã«ã¯ããžã§ãã¬ãŒã¿ã¯DSLã®ã»ãã³ãã£ã¯ã¹ãå®çŸ©ããŸãã
â Martin Fowler's Bliki in Japanese - LanguageWorkbench æ°ããDSLã®å®çŸ©
Configã³ã³ããŒãã³ãã§ã¯ç¹å®ã®ãã©ãŒãããã«ã€ããŠã§ã¯ãªããèšå®ã®æœè±¡åœ¢ïŒæœè±¡æ§ææšïŒã«ã€ããŠææ³ãå®çŸ©ããŸããããã«ãã£ãŠåäžã®ææ³ã§è€æ°ã®ãã©ãŒãããããµããŒãããããšãå¯èœã«ãªããŸãã
DSLã®èгç¹ããèŠãã°ãConfigã³ã³ããŒãã³ãã¯ã°ã©ããŒèšèªã«ããèšå®èšèªã®ææ³å®çŸ©ãæ žãšããDSLæ§ç¯ãã¬ãŒã ã¯ãŒã¯ãšãããã§ãããã
Configã³ã³ããŒãã³ããåäœã§å©çšãã
Configã³ã³ããŒãã³ããåäœã§å©çšããã®ã¯ããã»ã©é£ããã¯ãããŸããããŸãã¯èšå®èšèªå®çŸ©ãšèšå®ã®åŠçã®é¢ä¿ãå³ã§ã¿ãŠã¿ãŸãããã
ã°ã©ããŒèšèªã«ããèšå®èšèªã®å®çŸ©
Configã³ã³ããŒãã³ããå©çšããå Žåã®äž»ãªæŽ»åã¯ã°ã©ããŒèšèªã«ãã£ãŠèšå®ã®æœè±¡æ§æãå®çŸ©ããããšã§ãã以äžã¯çŸåšéçºäžã®Piece_Flow v2ã«ãããããŒãžãããŒå®çŸ©ã®ææ³ãèšè¿°ãããã®ã§ãã
Piece\Flow\PageFlow\Definition17Configuration:
<?php ... namespace Piece\Flow\PageFlow; ... use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; ... class Definition17Configuration implements ConfigurationInterface { public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); $treeBuilder->root('definition17') ->children() ->scalarNode('firstState')->isRequired()->cannotBeEmpty()->end() ->arrayNode('viewState') ->isRequired() ->requiresAtLeastOneElement() ->prototype('array') ->children() ->scalarNode('name')->isRequired()->cannotBeEmpty()->end() ->scalarNode('view')->isRequired()->cannotBeEmpty()->end() ->arrayNode('transition') ->prototype('array') ->children() ->scalarNode('event')->isRequired()->cannotBeEmpty()->end() ->scalarNode('nextState')->isRequired()->cannotBeEmpty()->end() ->arrayNode('action') ->children() ->scalarNode('class')->defaultNull()->cannotBeEmpty()->end() ->scalarNode('method')->isRequired()->cannotBeEmpty()->end() ->end() ->end() ->arrayNode('guard') ->children() ->scalarNode('class')->defaultNull()->cannotBeEmpty()->end() ->scalarNode('method')->isRequired()->cannotBeEmpty()->end() ->end() ->end() ->end() ->end() ->end() ->arrayNode('entry') ->children() ->scalarNode('class')->defaultNull()->cannotBeEmpty()->end() ->scalarNode('method')->isRequired()->cannotBeEmpty()->end() ->end() ->end() ->arrayNode('exit') ->children() ->scalarNode('class')->defaultNull()->cannotBeEmpty()->end() ->scalarNode('method')->isRequired()->cannotBeEmpty()->end() ->end() ->end() ->arrayNode('activity') ->children() ->scalarNode('class')->defaultNull()->cannotBeEmpty()->end() ->scalarNode('method')->isRequired()->cannotBeEmpty()->end() ->end() ->end() ->end() ->end() ->end() ->arrayNode('actionState') ->prototype('array') ->children() ->scalarNode('name')->isRequired()->cannotBeEmpty()->end() ->arrayNode('transition') ->isRequired() ->requiresAtLeastOneElement() ->prototype('array') ->children() ->scalarNode('event')->isRequired()->cannotBeEmpty()->end() ->scalarNode('nextState')->isRequired()->cannotBeEmpty()->end() ->arrayNode('action') ->children() ->scalarNode('class')->defaultNull()->cannotBeEmpty()->end() ->scalarNode('method')->isRequired()->cannotBeEmpty()->end() ->end() ->end() ->arrayNode('guard') ->children() ->scalarNode('class')->defaultNull()->cannotBeEmpty()->end() ->scalarNode('method')->isRequired()->cannotBeEmpty()->end() ->end() ->end() ->end() ->end() ->end() ->arrayNode('entry') ->children() ->scalarNode('class')->defaultNull()->cannotBeEmpty()->end() ->scalarNode('method')->isRequired()->cannotBeEmpty()->end() ->end() ->end() ->arrayNode('exit') ->children() ->scalarNode('class')->defaultNull()->cannotBeEmpty()->end() ->scalarNode('method')->isRequired()->cannotBeEmpty()->end() ->end() ->end() ->arrayNode('activity') ->children() ->scalarNode('class')->defaultNull()->cannotBeEmpty()->end() ->scalarNode('method')->isRequired()->cannotBeEmpty()->end() ->end() ->end() ->end() ->end() ->end() ->arrayNode('lastState') ->addDefaultsIfNotSet() ->children() ->scalarNode('name')->isRequired()->cannotBeEmpty()->end() ->scalarNode('view')->isRequired()->cannotBeEmpty()->end() ->arrayNode('entry') ->children() ->scalarNode('class')->defaultNull()->cannotBeEmpty()->end() ->scalarNode('method')->isRequired()->cannotBeEmpty()->end() ->end() ->end() ->arrayNode('exit') ->children() ->scalarNode('class')->defaultNull()->cannotBeEmpty()->end() ->scalarNode('method')->isRequired()->cannotBeEmpty()->end() ->end() ->end() ->arrayNode('activity') ->children() ->scalarNode('class')->defaultNull()->cannotBeEmpty()->end() ->scalarNode('method')->isRequired()->cannotBeEmpty()->end() ->end() ->end() ->end() ->end() ->arrayNode('initial') ->children() ->scalarNode('class')->defaultNull()->cannotBeEmpty()->end() ->scalarNode('method')->isRequired()->cannotBeEmpty()->end() ->end() ->end() ->arrayNode('final') ->children() ->scalarNode('class')->defaultNull()->cannotBeEmpty()->end() ->scalarNode('method')->isRequired()->cannotBeEmpty()->end() ->end() ->end() ->end() ; return $treeBuilder; } ...
ãã®èšå®èšèªã«å¯Ÿå¿ããèšå®äŸã¯ä»¥äžã®ããã«ãªããŸãã
Warning ãã®äŸã«ã¯ã«ãŒãããŒãdefinition17ã®èšè¿°ããããŸããããéåžžã¯ã«ãŒãããŒããèšè¿°ããå¿
èŠããããŸãã
firstState: Input lastState: name: Finish view: Finish activity: method: onFinish viewState: - name: Input view: Input activity: method: onInput transition: - event: next nextState: Validation - name: Confirmation view: Confirmation activity: method: onConfirmation transition: - event: next nextState: Registration - event: prev nextState: Input actionState: - name: Validation activity: method: onValidation transition: - event: invalid nextState: Input - event: valid nextState: Confirmation - name: Registration activity: method: onRegistration transition: - event: done nextState: Finish
DSLã¯ãã®ãœãããŠã§ã¢ã®ãŠãŒã¶ãŒã®ããã®é«ã¬ãã«ãªAPIã§ããããã¡ã€ã³ã®èšèªã䜿ã£ãŠãŠãŒã¶ãŒã®èŠæ±ã«é©åããæœè±¡åºŠã§æžããããšãæãŸããŸãã
èšå®ã®èªã¿èŸŒã¿ãçµåãããªããŒã·ã§ã³ãããã©ã«ãå€å
¥å
èšå®ã®èªã¿èŸŒã¿ã¯ä»»æã®æ¹æ³ã§è¡ããŸããåºæ¬çã«ã¯Yamlã³ã³ããŒãã³ãïŒYAMLïŒãDependency Injectionã³ã³ããŒãã³ãïŒINIãPHPãXMLãYAMLïŒãæäŸãããã®ã䜿ããšè¯ãã§ããããåè¿°ã®ConfigurationInterfaceãªããžã§ã¯ããšèªã¿èŸŒãŸããèšå®ãSymfony\Component\Config\Definition\Processor::processConfiguration()ã¡ãœããã«æž¡ãããšã§ãè€æ°ã®èšå®ã®çµåãããªããŒã·ã§ã³ãããã©ã«ãå€å
¥åãè¡ãããæçµçãªèšå®ãè¿ãããŸãã
Piece\Flow\PageFlow\PageFlowGenerator:
<?php ... namespace Piece\Flow\PageFlow; ... use Symfony\Component\Config\Definition\Processor; use Symfony\Component\Yaml\Yaml; ... class PageFlowGenerator { ... protected function readDefinition() { $processor = new Processor(); return $processor->processConfiguration( new Definition17Configuration(), array('definition17' => Yaml::parse($this->pageFlowRegistry->getFileName($this->pageFlow->getID()))) ); } ...
æçµçãªèšå®ã¯æœè±¡æ§ææšã衚çŸããé
åã«ãªã£ãŠãããããç°¡åã«å©çšããããšãã§ããŸãã以äžã®ã³ãŒãã§ã¯ãåè¿°ã®readDefinition()ã¡ãœããã®åŒã³åºãã«ãã£ãŠåŸãããèšå®ã«ã¢ã¯ã»ã¹ãããã®å€ã䜿ã£ãŠPiece\Flow\PageFlow\PageFlowãªããžã§ã¯ããæ§æããŠããŸãã
Piece\Flow\PageFlow\PageFlowGenerator:
<?php ... namespace Piece\Flow\PageFlow; ... class PageFlowGenerator { ... public function generate() { $definition = $this->readDefinition(); if (State::isProtectedState($definition['firstState'])) { throw new ProtectedStateException("The state [ {$definition['firstState']} ] cannot be used in flow definitions."); } $this->fsmBuilder->setStartState($definition['firstState']); if (!empty($definition['lastState'])) { if (State::isProtectedState($definition['lastState']['name'])) { throw new ProtectedStateException("The state [ {$definition['lastState']['name']} ] cannot be used in flow definitions."); } $this->fsmBuilder->addTransition($definition['lastState']['name'], Event::EVENT_END, State::STATE_FINAL); $this->configureViewState($definition['lastState']); $this->pageFlow->addEndState($definition['lastState']['name']); $this->pageFlow->addView($definition['lastState']['name'], $definition['lastState']['view']); } $this->configureViewStates($definition['viewState']); $this->configureActionStates($definition['actionState']); if (!empty($definition['initial'])) { $this->fsmBuilder->setExitAction(State::STATE_INITIAL, $this->wrapAction($definition['initial'])); } if (!empty($definition['final'])) { $this->fsmBuilder->setEntryAction(State::STATE_FINAL, $this->wrapAction($definition['final'])); } $this->pageFlow->setFSM($this->fsmBuilder->getFSM()); return $this->pageFlow; } ...
Configã³ã³ããŒãã³ããSymfonyãã³ãã«ã§å©çšãã
Configã³ã³ããŒãã³ããSymfonyãã³ãã«ã§å©çšããã®ã¯ãšãŠãç°¡åã§ããFooBarBundle\DependencyInjection\Configurationã¯ã©ã¹ã§èšå®èšèªãå®çŸ©ããFooBarBundle\DependencyInjection\FooBarExtensionã¯ã©ã¹ã§èšå®ã®çµåãããªããŒã·ã§ã³ãããã©ã«ãå€èªåå
¥åãè¡ããæçµçãªèšå®ãDIã³ã³ããã®ãã©ã¡ãŒã¿ãŒããµãŒãã¹å®çŸ©ã«å€æããŸãã以äžã®ã³ãŒãã¯Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtensionã¯ã©ã¹ã®äŸã§ãã
Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension:
<?php ... namespace Symfony\Bundle\FrameworkBundle\DependencyInjection; ... class FrameworkExtension extends Extension { ... public function load(array $configs, ContainerBuilder $container) { $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('web.xml'); $loader->load('services.xml'); ... $configuration = $this->getConfiguration($configs, $container); $config = $this->processConfiguration($configuration, $configs); $container->setParameter('kernel.secret', $config['secret']); ...
ãžã§ãã¬ãŒãã£ãããã°ã©ãã³ã°ãæšæŠããç§ã«ãšã£ãŠãConfigã³ã³ããŒãã³ãã¯Symfonyã³ã³ããŒãã³ãã®äžã§ããšãããéèŠãªäœçœ®ä»ãã«ãããŸããä»åŸãæ§ã
ãªå Žé¢ã§ç©æ¥µçã«äœ¿ãããšã«ãªãã§ãããã
Config (current) - Symfony
Martin Fowler's Bliki in Japanese - LanguageWorkbench