Last active
September 21, 2015 20:46
-
-
Save spiechu/2b11484ca25d37829aa1 to your computer and use it in GitHub Desktop.
Part of blog entry http://spiechu.pl/2015/09/21/symfony2-compiler-pass-with-tags-and-custom-attributes/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace MyProject\MyBundle; | |
class AsciiConverter implements StringProcessorInterface | |
{ | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getProccessedString($string) { | |
return iconv(mb_detect_encoding($string), 'ASCII//TRANSLIT', $string); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace MyProject\MyBundle\Controller; | |
use MyProject\MyBundle\ProcessorService; | |
use Symfony\Bundle\FrameworkBundle\Controller\Controller; | |
use Symfony\Component\HttpFoundation\Response; | |
class HomepageController extends Controller | |
{ | |
/** | |
* @return Response | |
*/ | |
public function homepageAction() | |
{ | |
/* @var $processorService ProcessorService */ | |
$processorService = $this->get('my_domain.processor.service'); | |
$string = ' Chrząszcz brzmi w trzcinie, ąę '; | |
$processedString = $processorService->processString($string); | |
// $processed string contains | |
// CHRZASZCZ BRZMI W TRZCINIE, AE | |
// all three processors were run | |
return $this->render('MyProjectMyBundle:Homepage:homepage.html.twig'); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace MyProject\MyBundle; | |
use MyProject\MyBundle\DependencyInjection\Compiler\ProcessorCompilerPass; | |
use Symfony\Component\DependencyInjection\Compiler\PassConfig; | |
use Symfony\Component\DependencyInjection\ContainerBuilder; | |
use Symfony\Component\HttpKernel\Bundle\Bundle; | |
class MyProjectMyBundle extends Bundle | |
{ | |
/** | |
* {@inheritdoc} | |
*/ | |
public function build(ContainerBuilder $container) | |
{ | |
parent::build($container); | |
$container->addCompilerPass( | |
new ProcessorCompilerPass(), | |
// this way every services' parameters are resolved | |
// and we have proper class names in compiler | |
PassConfig::TYPE_OPTIMIZE | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace MyProject\MyBundle\DependencyInjection\Compiler; | |
use MyProject\MyBundle\StringProcessorInterface; | |
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; | |
use Symfony\Component\DependencyInjection\ContainerBuilder; | |
use Symfony\Component\DependencyInjection\Definition; | |
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; | |
class ProcessorCompilerPass implements CompilerPassInterface | |
{ | |
const COLLECTION_ID = 'my_domain.processor.collection'; | |
const PROCESSOR_TAG_NAME = 'my_project.processor.provider'; | |
const TAG_ARG_PRIORITY = 'priority'; | |
/** | |
* {@inheritdoc} | |
* | |
* @throws InvalidArgumentException When tagged class doesn't | |
* implement processor interface | |
*/ | |
public function process(ContainerBuilder $container) | |
{ | |
if (!$container->hasDefinition(self::COLLECTION_ID)) { | |
return; | |
} | |
$collectionDefinition = $container->getDefinition(self::COLLECTION_ID); | |
$taggedServiceConfigs = $container->findTaggedServiceIds(self::PROCESSOR_TAG_NAME); | |
$serviceDefinitions = new \SplPriorityQueue(); | |
foreach ($taggedServiceConfigs as $serviceId => $tags) { | |
$definition = $container->getDefinition($serviceId); | |
// compilation will fail | |
// we'll have knowledge what service is messed up even before application runs | |
$this->assertStringProcessor($definition); | |
// we could have multiple processor tags, | |
// for example one processor call with a high priority | |
// and the same processor call at the end with low priority | |
foreach ($tags as $tag) { | |
$priority = isset($tag[self::TAG_ARG_PRIORITY]) | |
? (int) $tag[self::TAG_ARG_PRIORITY] | |
: 10; | |
$serviceDefinitions->insert($definition, $priority); | |
} | |
} | |
$collectionDefinition->setArguments([ | |
// this way we have sorted definitions by priorities | |
iterator_to_array($serviceDefinitions), | |
]); | |
} | |
/** | |
* @param Definition $definition | |
* | |
* @throws InvalidArgumentException When tagged class doesn't | |
* implement processor interface | |
*/ | |
private function assertStringProcessor(Definition $definition) { | |
if (!is_a($definition->getClass(), StringProcessorInterface::class, true)) { | |
throw new InvalidArgumentException(sprintf( | |
'Class "%s" does not implement "%s" interface', | |
$definition->getClass(), | |
StringProcessorInterface::class | |
)); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace MyProject\MyBundle; | |
class ProcessorService | |
{ | |
/** | |
* @var StringProcessorInterface[] | |
*/ | |
private $processors = []; | |
/** | |
* @param array|\Traversable $processors | |
* | |
* @throws InvalidArgumentException When processor is not an array | |
* or object implementing \Traversable interface | |
*/ | |
public function __construct($processors) | |
{ | |
// we cannot have clear typehint to traversable | |
// since array doesn't implement \Traversable ;-) | |
if (!is_array($processors) && !$processors instanceof \Traversable) { | |
throw new InvalidArgumentException( | |
'Processors must be array or implement \Traversable interface' | |
); | |
} | |
// super defensive programming in action | |
foreach ($processors as $processor) { | |
$this->addProcessor($processor); | |
} | |
} | |
/** | |
* @param StringProcessorInterface $processor | |
*/ | |
private function addProcessor(StringProcessorInterface $processor) { | |
$this->processors[] = $processor; | |
} | |
/** | |
* @param string $string | |
* | |
* @return string | |
*/ | |
public function processString($string) { | |
foreach ($this->processors as $processor) { | |
// maybe catch exceptions, | |
// log them and do not kill whole processing? | |
$string = $processor->getProccessedString($string); | |
} | |
return $string; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
parameters: | |
# we'll check if class name in parameter will be properly resolved by compiler pass | |
other_bundle.processor.upcase.class: 'MyProject\MyBundle\Upcase' | |
services: | |
# main processing service | |
my_domain.processor.service: | |
class: MyProject\MyBundle\ProcessorService | |
arguments: | |
# we're only interested in whole processors collection | |
- @my_domain.processor.collection | |
# collection filled by compiler pass | |
my_domain.processor.collection: | |
# dirty trick to have an undercover array ;-) | |
class: ArrayObject | |
# cannot be accessed by $container->get() | |
public: false | |
# tagged string processor discovered with compiler pass and added into collection | |
my_domain.processor.upcase: | |
# first stage of compilation will contain unresolved parameters | |
# that's why we have compiler pass on PassConfig::TYPE_OPTIMIZE | |
class: %other_bundle.processor.upcase.class% | |
public: false | |
tags: | |
# no priority given, defaults to 10 | |
- { name: my_project.processor.provider } | |
my_domain.processor.ascii: | |
class: MyProject\MyBundle\AsciiConverter | |
public: false | |
tags: | |
# low priority, processed as last | |
- { name: my_project.processor.provider, priority: 1 } | |
# --- suppose this is other bundle with other services.yml definition | |
# other bundle defines processor with higher priority | |
other_bundle.processor.trim: | |
class: MyProject\MyBundle\Trim | |
public: false | |
tags: | |
- { name: my_project.processor.provider, priority: 15 } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace MyProject\MyBundle; | |
interface StringProcessorInterface | |
{ | |
/** | |
* @param string $string | |
* | |
* @return string | |
*/ | |
public function getProccessedString($string); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace MyProject\MyBundle; | |
class Trim implements StringProcessorInterface | |
{ | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getProccessedString($string) { | |
return trim($string); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace MyProject\MyBundle; | |
class Upcase implements StringProcessorInterface | |
{ | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getProccessedString($string) { | |
return mb_strtoupper($string); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment