Reputation: 284
I implement the strategy pattern using Symfony 4.2 and the issue is the addMethodCall in the following isn't executed.
class ConverterPass implements CompilerPassInterface
{
const CONVERSION_SERVICE_ID = 'crv.conversion';
const SERVICE_ID = 'crv.converter';
public function process(ContainerBuilder $container)
{
// check if the conversion service is even defined
// and if not, exit early
if ( ! $container->has(self::CONVERSION_SERVICE_ID)) {
return false;
}
$definition = $container->findDefinition(self::CONVERSION_SERVICE_ID);
// find all the services that are tagged as converters
$taggedServices = $container->findTaggedServiceIds(self::SERVICE_ID);
foreach ($taggedServices as $id => $tag) {
// add the service to the Service\Conversion::$converters array
$definition->addMethodCall(
'addConverter',
[
new Reference($id)
]
);
}
}
}
In kernel.php I got
protected function build(ContainerBuilder $container)
{
$container->addCompilerPass(
new ConverterPass()
);
}
My ConverterInterface.php
namespace App\Converter;
interface ConverterInterface
{
public function convert(array $data);
public function supports(string $format);
}
then ConvertToSela.php and similarly another Convert
namespace App\Converter;
class ConvertToSela implements ConverterInterface
{
public function supports(string $format)
{
return $format === 'sela';
}
public function convert(array $data)
{
return 'sela';
}
}
In Conversion.php which I execute I get empty array which means the addConverter
isn't called.
class Conversion
{
private $converters;
public function __construct()
{
$this->converters = [];
}
public function addConverter(ConverterInterface $converter)
{
dd($converter);
$this->converters[] = $converter;
return $this->converters;
}
public function convert(array $data, $format)
{
foreach ($this->converters as $converter) {
if ($converter->supports($format)) {
return $converter->convert($data);
}
}
throw new \RuntimeException('No supported Converters found in chain.');
}
}
Upvotes: 1
Views: 2418
Reputation: 284
My problem was in class ConverterPass when instead of the service id from services.yaml I have the class name it works fine.
Upvotes: 0
Reputation: 75
Working example on Symfony 4.2.4 with tagged services and compiler pass:
\App\Service\Strategy\FileContext:
declare(strict_types=1);
namespace App\Service\Strategy;
class FileContext
{
/**
* @var array
*/
private $strategies;
/**
* @param FileStrategyInterface $strategy
*/
public function addStrategy(FileStrategyInterface $strategy): void
{
$this->strategies[] = $strategy;
}
/**
* @param string $type
*
* @return string
*/
public function read(string $type): string
{
/** @var FileStrategyInterface $strategy */
foreach ($this->strategies as $strategy) {
if ($strategy->isReadable($type)) {
return $strategy->read();
}
}
throw new \InvalidArgumentException('File type not found');
}
}
\App\Service\Strategy\FileStrategyInterface:
declare(strict_types=1);
namespace App\Service\Strategy;
interface FileStrategyInterface
{
/**
* @param string $type
*
* @return bool
*/
public function isReadable(string $type): bool;
/**
* @return string
*/
public function read(): string;
}
\App\Service\Strategy\CsvStrategy:
declare(strict_types=1);
namespace App\Service\Strategy;
class CsvStrategy implements FileStrategyInterface
{
private const TYPE = 'csv';
/**
* {@inheritdoc}
*/
public function isReadable(string $type): bool
{
return self::TYPE === $type;
}
/**
* {@inheritdoc}
*/
public function read(): string
{
return 'Read CSV file';
}
}
\App\Service\Strategy\TxtStrategy:
class TxtStrategy implements FileStrategyInterface
{
private const TYPE = 'txt';
/**
* {@inheritdoc}
*/
public function isReadable(string $type): bool
{
return self::TYPE === $type;
}
/**
* {@inheritdoc}
*/
public function read(): string
{
return 'Read TXT file';
}
}
\App\DependencyInjection\Compiler\FileContextCompilerPass:
declare(strict_types=1);
namespace App\DependencyInjection\Compiler;
use App\Service\Strategy\FileContext;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class FileContextCompilerPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$service = $container->findDefinition(FileContext::class);
$strategyServiceIds = array_keys($container->findTaggedServiceIds('strategy_file'));
foreach ($strategyServiceIds as $strategyServiceId) {
$service->addMethodCall('addStrategy', [new Reference($strategyServiceId)]);
}
}
}
\App\Kernel:
namespace App;
use App\DependencyInjection\Compiler\FileContextCompilerPass;
...
class Kernel extends BaseKernel
{
use MicroKernelTrait;
...
/**
* {@inheritdoc}
*/
protected function build(ContainerBuilder $container)
{
$container->addCompilerPass(new FileContextCompilerPass());
}
}
Make sure you've added valid taged services definitions.
config/services.yaml:
App\Service\Strategy\TxtStrategy:
tags:
- { name: strategy_file }
App\Service\Strategy\CsvStrategy:
tags:
- { name: strategy_file }
Command for testing. \App\Command\StrategyCommand:
declare(strict_types=1);
namespace App\Command;
use App\Service\Strategy\FileContext;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class StrategyCommand extends Command
{
/**
* {@inheritdoc}
*/
protected static $defaultName = 'strategy:run';
/**
* @var FileContext
*/
private $fileContext;
/**
* @param FileContext $fileContext
*/
public function __construct(FileContext $fileContext)
{
$this->fileContext = $fileContext;
parent::__construct();
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDescription('Run strategy example')
->addOption('file', null, InputOption::VALUE_REQUIRED);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if (!$file = trim($input->getOption('file'))) {
return;
}
echo $this->fileContext->read($file);
}
}
Result:
$ bin/console strategy:run --file=txt
Read TXT file%
$ bin/console strategy:run --file=csv
Read CSV file%
Upvotes: 4