I have an Exception somewhere in my service/ folder, and Symfony is trying to autowire it :
Cannot autowire service
"App\Service\Order\Exception\StripeRequiresActionException": argument
"$secretKey" of method "__construct()" is type-hinted "string", you
should configure its value explicitly.
This is my class :
class StripeRequiresActionException extends \Exception
{
/**
* #var string
*/
protected $secretKey;
public function __construct(string $secretKey)
{
parent::__construct();
$this->secretKey = $secretKey;
}
/**
* #return string
*/
public function getSecretKey(): string
{
return $this->secretKey;
}
}
I don't want it to be autowired. Is there an easy way to prevent this class to be loaded by the DI, with an annotation for example? I know I can exclude this class in my yaml configuration, but I don't want to do that because I find this ugly and harder to maintain.
Maybe you could exclude all exceptions, no matter where they are.
If all your exceptions follow the pattern you show in your question, you could do something similar to:
App\:
resource: '../src/*'
exclude: ['../src/{Infrastructure/Symfony,Domain,Tests}', '../src/**/*Exception.php']
This comes directly from a project I have open right here. The default exclude for Symfony looks somewhat different. But the important bit would be to add the pattern *Exception.php to the excluded files.
This is simpler to maintain than an annotation, even if an annotation were possible (which I believe it's not). Keeps the configuration all in a same place, you can create new exceptions without having to change configuration or add unnecessary code.
Even if I agree that in your particular case the cleanest way is to do what yivi suggested, I think I have a more generic solution that could suit more cases.
In my case I have a PagesScanner service that returns PageResult objects, both are several level deep into an autowired directory.
Excluding the class like suggested is a pain and will make the yaml unreadable quickly as the number of exceptions increases.
So I created a new compiler pass that searches for an #IgnoreAutowire annotation on each class under the App/ folder :
<?php
namespace App\DependencyInjection\Compiler;
use App\Annotation\IgnoreAutowire;
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
final class RemoveUnwantedAutoWiredServicesPass implements CompilerPassInterface
{
/**
* {#inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$annotationReader = new AnnotationReader();
$definitions = $container->getDefinitions();
foreach ($definitions as $fqcn => $definition) {
if (substr($fqcn, 0, 4) === 'App\\') {
try {
$refl = new \ReflectionClass($fqcn);
$result = $annotationReader->getClassAnnotation($refl, IgnoreAutowire::class);
if ($result !== null) {
$container->removeDefinition($fqcn);
}
} catch (\Exception $e) {
// Ignore
}
}
}
}
}
This way all I have to do is to add the annotation to classes I don't want to be autowired:
<?php
namespace App\Utils\Cms\PagesFinder;
use App\Annotation\IgnoreAutowire;
/**
* #IgnoreAutowire()
*/
class PageResult
{
[...]
}
Another good thing about this approch is you can even have parameters in the class constructor without any error because the actual autowiring thing is done after the compiler pass.
BTW code for php8 attributes:
CompilerPass
<?php
namespace App\DependencyInjection\Compiler;
use App\Annotation\IgnoreAutowire;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
final class RemoveUnwantedAutoWiredServicesPass implements CompilerPassInterface
{
/**
* {#inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$definitions = $container->getDefinitions();
foreach ($definitions as $fqcn => $definition) {
if (str_starts_with($fqcn, 'App\\')) {
try {
$refl = new \ReflectionClass($fqcn);
$attribute = $refl->getAttributes(IgnoreAutowire::class)[0] ?? null;
if ($attribute !== null) {
$container->removeDefinition($fqcn);
}
} catch (\Exception $e) {
// Ignore
}
}
}
}
}
Attribute
<?php
namespace App\Annotation;
use Attribute;
/**
* Annotation class for #IgnoreAutowire().
*
* #Annotation
* #Target({"CLASS"})
*/
#[Attribute(Attribute::TARGET_CLASS)]
class IgnoreAutowire
{
}
You can also disable autoconfigure for only this class in config/services.yaml:
App\Service\Order\Exception\StripeRequiresActionException:
autoconfigure: false
Symfony won't add this class to the DI.
Related
I have a Sumfony 4.3 command that processes some data and loops through a number of "processors" to do the processing. The code uses a factory (autowired) which then instantiates the command.
use App\Entity\ImportedFile;
use App\Service\Processor\Processor;
class Factory implements FactoryInterface
{
/** #var array */
private $processors;
/** #var TestClausesInterface */
private $testClauses;
private $em;
private $dataSetProvider;
private $ndviFromNasaService;
private $archivalHashService;
private $mailer;
private $projectDir;
public function __construct(
TestClausesInterface $testClauses,
ValidProcessorList $processors,
EntityManagerInterface $em,
DataSetProvider $dataSetProvider,
NDVIFromNasaService $ndviFromNasaService,
ArchivalHashService $archivalHashService,
\Swift_Mailer $mailer,
$projectDir)
{
$this->processors = $processors;
$this->testClauses = $testClauses;
$this->em = $em;
$this->dataSetProvider = $dataSetProvider;
$this->ndviFromNasaService = $ndviFromNasaService;
$this->archivalHashService = $archivalHashService;
$this->mailer = $mailer;
$this->projectDir = $projectDir;
}
public function findProcessorForFile(ImportedFile $file)
{
...
if ($found){
$candidates = $this->recursive_scan( $this->projectDir.'/src/Processor');
foreach ($candidates as $candidate){
if (substr($candidate,0,strlen('Helper')) === 'Helper'){
continue;
}
try {
$candidate = str_replace($this->projectDir.'/src/Processor/', '', $candidate);
$candidate = str_replace('/','\\', $candidate);
$testClassName = '\\App\\Processor\\'.substr( $candidate, 0, -4 );
/* #var Processor $test */
if (!strstr($candidate, 'Helper')) {
$test = new $testClassName($this->testClauses, $this->em, $this->dataSetProvider, $this->ndviFromNasaService, $this->archivalHashService, $this->mailer, $this->projectDir);
}
However I still have to:
autowire all arguments both in the Factory and Processor top class
pass all arguments in correct order to the Processor
I have around 70 subclasses of Processor. All of them use EntityInterface, but only a couple use SwiftMailer and the other dependencies.
As I am adding services to be used only by a few Processors, I am looking for a way to autowire these arguments only at the Processor level. Ideally, also without adding service definitions to services.yml
In summary, I would like to be able to add a dependency to any subclass of Processor, even if it is a parent class of other subclasses and have the dependency automatically injected.
There is much it is not immediately obvious in your code, but the typical way to resolve this is by using a "service locator". Docs.
Let's imagine you have several services implementing the interface Processor:
The interface:
interface Processor {
public function process($file): void;
}
Couple implementation:
class Foo implements Processor
{
public function __construct(DataSetProvider $dataSet, ArchivalHashService $archivalHash, \Swift_Mailer $swift) {
// initialize properties
}
public function process($file) {
// process implementation
}
public static function getDefaultIndexName(): string
{
return 'candidateFileOne';
}
}
Couple implementations:
class Bar implements Processor
{
public function __construct(\Swift_Mailer $swift, EntityManagerInterface $em) {
// initialize properties
}
public function process($file) {
// process implementation
}
public static function getDefaultIndexName(): string
{
return 'candidateFileTwo';
}
}
Note that each of the processors have completely different dependencies, and can be auto-wired directly, and that each of them has a getDefaultIndexName() method.
Now we'll "tag" all services implementing the Processor interface:
# services.yaml
services:
# somewhere below the _defaults and the part where you make all classes in `src` available as services
_instanceof:
App\Processor:
tags:
- { name: "processor_services", default_index_method: 'getDefaultIndexName' }
Attention here: The documentation says that if you define a public static function getDefaultIndexName() it will be picked by default. But I've found this not to be working at the moment. But if you define the default_index_method you can wire it to a method of your choice. I'm keeping the getDefaultIndexName for the time being, but you can pick something of your own choice.
Now, if you need this processes in a console command, for example:
use Symfony\Component\DependencyInjection\ServiceLocator;
class MyConsoleCommand
{
private ServiceLocator $locator;
public function __construct(ServiceLocator $locator)
{
$this->locator = $locator;
}
}
To inject the service locator you would do:
#services.yaml
services:
App\HandlerCollection:
arguments: [!tagged_locator { tag: 'processor_services' } ]
And to fetch any of the processors from the service locator you would do:
$fooProcessor = $this->locator->get('candidateFileOne');
$barProcessor = $this->locator->get('candidateFileTwo');
Summping up, basically what you need is:
Define a shared interface for the processors
Use that interface to tag all the processor services
Define a getDefaultIndexName() for each processor, which helps you match files to processors.
Inject a tagged service locator in the class that need to consume this services
And you can leave all services auto-wired.
Note: You could use an abstract class instead of an interface, and it would work the same way. I prefer using an interface, but that's up to you.
For completion sake, here is a repo with the above working for Symfony 4.3.
In Synfony 3.3, the new best practice for DI
is to use normal constructor dependency injection (or "action"
injection in controllers) instead of fetching public services via
$this->get() (though that does still work)
as seen in offical documentation
So no need to specify services as we can type hint them in class controllers :
class InvoiceMailer
{
private $generator;
public function __construct(InvoiceGenerator $generator)
{
$this->generator = $generator
}
// ...
}
This seems to work well, but what if I extends a class and add more parameters in my constructors ???
use Symfony\Component\HttpKernel\Exception\HttpException;
class MyClass extends HttpException
{
private $generator;
public function __construct(InvoiceGenerator $generator, \Exception $previous = null, array $headers = [], $code = 0)
{
$this->generator = $generator;
$statusCode = $generator->getStatusCode();
$message = $generator->getTitle();
parent::__construct($statusCode, $message, $previous, $headers, $code);
}
// ...
}
Now I get a circular reference error :
[Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException]
Circular reference detected for service "AppBundle\Service\MyClass", path: "AppBundle\Service\MyClass -> AppBundle\Service\MyClass".
So, what is the best practice in this case ??
Thanks.
For that case you might have to define the service explicitly as \Exception $previous argument in fact is parent class of MyClass (through HttpException) so the autowiring method try to inject/create an instance of MyClass again on this argument, result: "Circular Reference".
This is an abstraction of what happens to you:
namespace App\Foo;
class MyClass extends \Exception
{
public function __construct(\Exception $previous = null)
{
}
}
Same error, so you can to solve it passing a null value to this argument:
# service.yml
services:
# ...
App\Foo\MyClass:
$previous: ~
or changing its definition manually in a compiler pass or DI extension.
Alright so I'm converting a small laravel project to symfony (will get bigger, and the bundling architecture symfony uses will be ideal)
I'm apparently spoiled with laravels facades and eloquent working with existing databases almost right out of the box.
I can't find the most appropriate way to have a wrapper or "helper" class get access to an entities repository.
first let me give a few examples then I will explain what I have attempted. (I'm willing to bounty some points for a good answer but unfortunately the time constraints on the project can't exactly wait)
So in laravel I had all my model classes. Then I created some wrapper / helper classes that would essentially turn the data into something a little more usable (i.e. multiple queries and objects containing more versatile information to work with). And with the magic of facades I could call upon each model and query them without and dependencies injected into these "Helper" classes. keeping them very lean. In symfony it appears the ideal solution is to put all of your reusable database logic in repositories, ok.
In symfony I'm surrounded by Inversion of Control (IoC); which is fine but design pattern is failing to be intuitive for me to fully figure this scenario out. I have tried to create services out every single repository, which works great if being called from a controller or other Dependency Injected (DI) service. But in a standard php class, it appears my hands are tied without passing entity manager to each helper class's constructor. *shivers*
The first limitation is I have zero ability to change the schema of the existing tables (which obviously doesn't change the problem, just don't want anyone to suggest altering the entities).
So how does one accomplish this.
EDIT:
so thanks to #mojo's comment I've pulled off what I wanted to do. Still looking for a better alternative if it exists. (see edit 2 below)
currently I have:
config.yml docterine.orm.entity_managers:
entity_managers:
default:
auto_mapping: true
connection: default
asterisk:
connection: asterisk
mappings:
AsteriskDbBundle: ~
asteriskcdr:
connection: asteriskcdr
mappings:
AsteriskCdrDbBundle:
service.yml
services:
app.services.doctrine.entitymanager.provider:
class: AppBundle\Services\EntityManagerProvider
arguments: [#doctrine]
tags:
- {name: kernel.event_listener, event: kernel.request, method: onKernelRequest}
EntityManagerProvider
namespace AppBundle\Services;
use Doctrine\Bundle\DoctrineBundle\Registry as DoctrineRegistry;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Config\Definition\Exception\Exception;
class EntityManagerProvider
{
/** #var DoctrineRegistry */
private static $doctrine;
public function __construct(DoctrineRegistry $doctrine)
{
static::$doctrine = $doctrine;
}
/**
* #param $class
* #return EntityManager
*/
public static function getEntityManager($class)
{
if(($em = static::$doctrine->getManagerForClass($class)) instanceof EntityManager == false)
throw new Exception(get_class($em) . ' is not an instance of ' . EntityManager::class);
return $em;
}
// oh man does this feel dirty
public function onKernelRequest($event)
{
return;
}
}
Example Controller
$extension = Extension::createFromDevice(DeviceRepository::findById(92681));
ExtendedEntityRepository
namespace AppBundle\Entity;
use AppBundle\Services\EntityManagerProvider;
use AppBundle\Utils\DateTimeRange;
use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\ClassMetadata;
use Symfony\Component\Config\Definition\Exception\Exception;
class ExtendedEntityRepository extends \Doctrine\ORM\EntityRepository
{
/** #var ExtendedEntityRepository */
protected static $instance;
public function __construct(EntityManager $entityManager, ClassMetadata $class)
{
parent::__construct($entityManager, $class);
if(static::$instance instanceof static == false)
static::$instance = $this;
}
// some horribly dirty magic to get the entity that belongs to this repo... which requires the repos to have the same name and exist one directory down in a 'Repositories' folder
public static function getInstance()
{
if(static::$instance instanceof static == false) {
preg_match('/^(.*?)Repositories\\\([A-Za-z_]*?)Repository$/', static::class, $match);
$class = $match[1] . $match[2];
$em = EntityManagerProvider::getEntityManager($class);
static::$instance = new static($em, $em->getClassMetadata($class));
}
return static::$instance;
}
public static function findById($id)
{
return static::getInstance()->find($id);
}
public static function getQueryBuilder()
{
return static::getInstance()->getEntityManager()->createQueryBuilder();
}
public static function getPreBuiltQueryBuilder()
{
return static::getQueryBuilder()->select('o')->from(static::getInstance()->getClassName(), 'o');
}
public static function findByColumn($column, $value)
{
//if($this->getClassMetadata()->hasField($column) == false)
// throw new Exception($this->getEntityName() . " does not contain a field named `{$column}`");
return static::getPreBuiltQueryBuilder()->where("{$column} = ?1")->setParameter(1, $value)->getQuery()->execute();
}
public static function filterByDateTimeRange($column, DateTimeRange $dateTimeRange, QueryBuilder $queryBuilder = null)
{
if($queryBuilder == null)
$queryBuilder = static::getPreBuiltQueryBuilder();
if($dateTimeRange != null && $dateTimeRange->start instanceof \DateTime && $dateTimeRange->end instanceof \DateTime) {
return $queryBuilder->andWhere(
$queryBuilder->expr()->between($column, ':dateTimeFrom', ':dateTimeTo')
)->setParameters(['dateTimeFrom' => $dateTimeRange->start, 'dateTimeTo' => $dateTimeRange->end]);
}
return $queryBuilder;
}
}
DeviceRepository
namespace Asterisk\DbBundle\Entity\Repositories;
use AppBundle\Entity\ExtendedEntityRepository;
/**
* DeviceRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class DeviceRepository extends ExtendedEntityRepository
{
//empty as it only needs to extend the ExtendedEntityRepository class
}
Extension
namespace AppBundle\Wrappers;
use Asterisk\DbBundle\Entity\Device;
class Extension
{
public $displayName;
public $number;
public function __construct($number, $displayName = "")
{
$this->number = $number;
$this->displayName = $displayName;
}
public static function createFromDevice(Device $device)
{
return new Extension($device->getUser(), $device->getDescription());
}
}
Agent (This is an example of why having repositories access statically is helpful)
namespace AppBundle\Wrappers;
use AppBundle\Utils\DateTimeRange;
use Asterisk\CdrDbBundle\Entity\Cdr;
use Asterisk\CdrDbBundle\Entity\Repositories\CdrRepository;
use Asterisk\DbBundle\Entity\Device;
use Asterisk\DbBundle\Entity\Repositories\FeatureCodeRepository;
use Asterisk\DbBundle\Entity\Repositories\QueueDetailRepository;
use Asterisk\DbBundle\Enums\QueueDetailKeyword;
class Agent
{
public $name;
public $extension;
/** #var Call[] */
public $calls = [];
/** #var array|Queue[] */
public $queues = [];
/** #var AgentStats */
public $stats;
private $_extension;
public function __construct(Device $extension, DateTimeRange $dateTimeRange = null)
{
$this->_extension = $extension;
$this->extension = Extension::createFromDevice($extension);
$this->name = $this->extension->displayName;
$this->calls = $this->getCalls($dateTimeRange);
$this->stats = new AgentStats($this, $dateTimeRange);
}
public function getCalls(DateTimeRange $dateTimeRange = null)
{
/** #var CdrRepository $cdrRepo */
$cdrRepo = CdrRepository::getPreBuiltQueryBuilder();
$query = $cdrRepo->excludeNoAnswer($cdrRepo->filterByDateTimeRange($dateTimeRange));
$cdrs = $query->andWhere(
$query->expr()->orX(
$query->expr()->eq('src', $this->extension->number),
$query->expr()->eq('dst', $this->extension->number)
)
)->andWhere(
$query->expr()->notLike('dst', '*%')
)
->getQuery()->execute();
foreach($cdrs as $cdr) {
$this->calls[] = new Call($cdr);
}
return $this->calls;
}
public function getBusyRange(DateTimeRange $dateTimeRange = null)
{
$on = FeatureCodeRepository::getDndActivate();
$off = FeatureCodeRepository::getDndDeactivate();
$toggle = FeatureCodeRepository::getDndToggle();
$query = CdrRepository::filterByDateTimeRange($dateTimeRange);
/** #var Cdr[] $dndCdrs */
$dndCdrs = $query->where(
$query->expr()->in('dst', [$on, $off, $toggle])
)
->where(
$query->expr()->eq('src', $this->extension->number)
)->getQuery()->execute();
$totalTimeBusy = 0;
/** #var \DateTime $lastMarkedBusy */
$lastMarkedBusy = null;
foreach($dndCdrs as $cdr) {
switch($cdr->getDst())
{
case $on:
$lastMarkedBusy = $cdr->getDateTime();
break;
case $off:
if($lastMarkedBusy != null)
$totalTimeBusy += $lastMarkedBusy->diff($cdr->getDateTime());
$lastMarkedBusy = null;
break;
case $toggle:
if($lastMarkedBusy == null) {
$lastMarkedBusy = $cdr->getDateTime();
}
else
{
$totalTimeBusy += $lastMarkedBusy->diff($cdr->getDateTime());
$lastMarkedBusy = null;
}
break;
}
}
return $totalTimeBusy;
}
public function getQueues()
{
$query = QueueDetailRepository::getPreBuiltQueryBuilder();
$queues = $query->where(
$query->expr()->eq('keyword', QueueDetailKeyword::Member)
)->where(
$query->expr()->like('data', 'Local/'.$this->extension->number.'%')
)->getQuery()->execute();
foreach($queues as $queue)
$this->queues[] = Queue::createFromQueueConfig(QueueDetailRepository::findByColumn('extension', $queue->id), $queue);
return $this->queues;
}
}
EDIT 2:
Actually I forgot I declared each repository as a service, so I could omit the black magic voodoo in the getInstance() method. But loading the service on kernel event seems like a bad idea...
parameters:
entity.device: Asterisk\DbBundle\Entity\Device
services:
asterisk.repository.device:
class: Asterisk\DbBundle\Entity\Repositories\DeviceRepository
factory: ["#doctrine.orm.asterisk_entity_manager", getRepository]
arguments:
- %entity.device%
tags:
- {name: kernel.event_listener, event: kernel.request, method: onKernelRequest}
Edit 3
Cerad gave me an answer on my other related question That suggested using a single kernel event listener service and injecting each repository as a dependency. Thus allowing me to access the repositories statically. My only concern is the overhead required to load each repository on every request. My ideal method would be lazy load the repositories, but I'm unaware of a method at this time. proxy-manager-bridge looked promising but with my singleton pattern I don't think it will work.
I'm curious about the best way of speccing classes that handle file operations.
Assuming I have a fictional class with a method duplicate whose job is to duplicate the contents of a file.
<?php
class FileOperator
{
public function duplicate($filename)
{
$content = file_get_contents($filename);
file_put_contents($filename, $content.$content);
}
}
I know that I can use something like vfsStream to assert the change without touching the actual filesystem (at least with assertions in PHPUnit).
How could I assert that in a spec? Or would it be approached differently?
Also, I get that I might want to extract that functionality into another class and use a Spy to assert that the FileOperator calls its dependency correctly, but then I'd still have to spec that adapter class, and my question remains.
Thanks.
This is more likely a functional test rather than an unit test, so it's hard to use phpspec in this case.
If you insist, I see two options.
If you happen to need a method to fetch the file contents too, you could write your spec this way:
use org\bovigo\vfs\vfsStream;
use org\bovigo\vfs\vfsStreamDirectory;
use PhpSpec\ObjectBehavior;
class FileOperatorSpec extends ObjectBehavior
{
/**
* #var vfsStreamDirectory
*/
private $workDir;
function let()
{
$this->workDir = vfsStream::setup('workDir');
}
function it_duplicates_a_content_in_a_file()
{
$this->createFile('foo', 'bar');
$this->duplicate('vfs://workDir/foo');
$this->read('vfs://workDir/foo')->shouldReturn('barbar');
}
private function createFile($path, $content)
{
$file = vfsStream::newFile($path);
$file->setContent($content);
$this->workDir->addChild($file);
}
}
Alternatively, you could use the expect helper:
use org\bovigo\vfs\vfsStream;
use org\bovigo\vfs\vfsStreamDirectory;
use PhpSpec\ObjectBehavior;
class FileOperatorSpec extends ObjectBehavior
{
/**
* #var vfsStreamDirectory
*/
private $workDir;
function let()
{
$this->workDir = vfsStream::setup('workDir');
}
function it_duplicates_a_content_in_a_file()
{
$this->createFile('foo', 'bar');
$this->duplicate('vfs://workDir/foo');
expect(file_get_contents('vfs://workDir/foo'))->toBe('barbar');
}
private function createFile($path, $content)
{
$file = vfsStream::newFile($path);
$file->setContent($content);
$this->workDir->addChild($file);
}
}
I'm trying to run JMSSerializer. My simple code
use JMS\Serializer\Annotation\Type;
class Comment
{
private $msg;
public function __construct($msg)
{
$this->msg = $msg;
}
}
class Person
{
/**
* #Type("array<Comment>")
*/
private $commentList;
public function addComment(Comment $comment)
{
$this->commentList[] = $comment;
}
}
$type = new Type;
$serializer = JMS\Serializer\SerializerBuilder::create()->build();
$data = new Person();
$data->addComment(new Comment('hey'));
var_dump($serializer->serialize($data, 'json'));
fails with
PHP Fatal error: Uncaught exception 'Doctrine\Common\Annotations\AnnotationException' with message '[Semantical Error] The annotation "#JMS\Serializer\Annotation\Type" in property Person::$commentList does not exist, or could not be auto-loaded.' in xxx.php:52
OK, but if I add line
$type = new Type;
to trigger autoloader manually, it works:
string(32) "{"comment_list":[{"msg":"hey"}]}"
As I see AnnotationRegistry doesn't use autoloader, it tries to use some own autoloader. It looks ugly, what do I have to do to fix it?
OK, I answer my question myself. I have to register annotations somewhere in autoloader file:
\Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespace(
'JMS\Serializer\Annotation', __DIR__.'/vendor/jms/serializer/src'
);
Other ways: http://docs.doctrine-project.org/projects/doctrine-common/en/latest/reference/annotations.html#registering-annotations
A full configuration sample for the standalone JMS serializer library could be:
<?php
namespace iMSCP\Service;
use JMS\Serializer\Serializer;
use JMS\Serializer\SerializerBuilder;
use Doctrine\Common\Annotations\AnnotationRegistry;
use iMSCP_Registry as Registry;
/**
* Class SerializerServiceFactory
* #package iMSCP\Service
*/
class SerializerServiceFactory
{
/**
* #var Serializer
*/
static $serialiszer;
public static function create()
{
if (static::$serialiszer === null) {
$config = Registry::get('config');
AnnotationRegistry::registerAutoloadNamespace(
'JMS\Serializer\Annotation', $config['CACHE_DATA_DIR'] . '/packages/vendor/jms/serializer/src'
);
static::$serialiszer = SerializerBuilder::create()
->setCacheDir(CACHE_PATH . '/serializer')
->setDebug($config['DEVMODE'])
->build();
}
return static::$serialiszer;
}
}
Here, I register the JMS\Serializer\Annotation namespace using the Annotation registry as provided by Doctrine. Once done, all is working as expected.