How to resolve "Circular reference detected for service" issue? - php

I'm trying to inject my repository service into EventListener but that leads me to following exception, which, with my basic knowledge of Symfony2, I have no idea how to resolve. Exception is:
ServiceCircularReferenceException in bootstrap.php.cache line 2129:
Circular reference detected for service "doctrine.orm.default_entity_manager", path: "doctrine.orm.default_entity_manager -> doctrine.dbal.default_connection -> person.connect.listener -> tag.repository.service".
And here is how I've declared repository and listener:
tag.repository.service:
class: Application\Bundle\PersonBundle\Entity\TagRepository
factory: ["#doctrine", getRepository]
arguments: [ Application\Bundle\PersonBundle\Entity\Tag ]
person.connect.listener:
class: Application\Bundle\PersonBundle\EventListener\ConnectListener
arguments:
tokenStorage: "#security.token_storage"
tagRepo: "#tag.repository.service"
tags:
- { name: doctrine.event_listener, event: postPersist, connection: default }
Most answers, that I've able to find, suggest injecting service container, but I really don't want do that. Is there any way to resolve this properly?
UPD: Here is the code of the listener. Everything worked fine until I've tried to inject TagRepository
class ConnectListener
{
/**
* #var TokenStorage
*/
private $tokenStorage;
/**
* #var TagRepository
*/
private $tagRepo;
/**
* #param TokenStorage $tokenStorage
* #param TagRepository $tagRepo
*/
public function __construct(TokenStorage $tokenStorage, TagRepository $tagRepo)
{
$this->tokenStorage = $tokenStorage;
}
/**
* #param LifecycleEventArgs $args
* #return void
*/
public function postPersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
if ($entity instanceof Person) {
$user = $this->tokenStorage->getToken()->getUser();
$visibility = new PersonVisibility($entity, $user);
$visibility->setVisibilityType(PersonVisibility::VT_CREATED);
$entityManager->persist($visibility);
$entityManager->flush();
}
}
}

As far as TagRepository is descendant of EntityRepository try obtaining its instance in postPersist event. Like this:
// using full classname:
$tagRepo = $entityManager->getRepository("Application\Bundle\PersonBundle\Entity\TagRepository");
// alternatively:
$tagRepo = $entityManager->getRepository("ApplicationPersonBundle:Tag");

Yo can also change your declaration of your repository, don't use the factory and use one of these 2 methods.
This will avoid the circular reference and will be cleaner than use the EntityManager class.

Related

Symfony throwing ServiceCircularReferenceException

I am using Symfony 2.7 and i am writing all logs to data based on below tutorial
https://nehalist.io/logging-events-to-database-in-symfony/
In service i have
monolog.db_handler:
class: AppBundle\Util\MonologDBHandler
arguments: ['#doctrine.orm.entity_manager']
in monlog db handler i have following
class MonologDBHandler extends AbstractProcessingHandler
{
/**
* #var EntityManagerInterface
*/
protected $em;
/**
* MonologDBHandler constructor.
* #param EntityManagerInterface $em
*/
public function __construct(EntityManagerInterface $em)
{
parent::__construct();
$this->em = $em;
}
/**
* Called when writing to our database
* #param array $record
*/
protected function write(array $record)
{
$logEntry = new Log();
$logEntry->setMessage($record['message']);
$logEntry->setLevel($record['level']);
$logEntry->setLevelName($record['level_name']);
$logEntry->setExtra($record['extra']);
$logEntry->setContext($record['context']);
$this->em->persist($logEntry);
$this->em->flush();
}
}
if i enable dev mode i am getting following error
Fatal error: Uncaught
Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException:
Circular reference detected for service
"doctrine.orm.default_entity_manager", path:
"doctrine.orm.default_entity_manager ->
doctrine.dbal.default_connection -> monolog.logger.doctrine ->
monolog.monolog.db_handler".
I know this error due to service doctrine injection .how i can sole this issue.Thank you
This approach is a really bad idea and should be avoided but you can get rid of the circular reference by accessing the entity manager via the global $kernel variable.
class MonologDBHandler extends AbstractProcessingHandler
{
public function __construct()
{
parent::__construct();
}
protected function write(array $record)
{
global $kernel;
$em = $kernel->getContainer()-get('doctrine.orm.entity_manager');
And then remove the arguments section from your service definition.

symfony 3 Doctrine LIstener service inject token_storage doesnt work

I have a doctrine listener which needs the get the current logged in user.
class DoctrineListener
{
/**
* #var null|TokenInterface
*/
private $token;
/**
* DoctrineListener constructor.
*
* #param TokenStorageInterface $tokenStorage
*/
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->token = $tokenStorage->getToken();
var_dump($this->token);
}
and in my service.yml:
doctrine.listener:
class: AppBundle\EventListener\DoctrineListener
arguments:
- '#security.token_storage'
public: false
tags:
- { name: doctrine.event_listener, event: preFlush, method: preFlush }
The dump always returns me null when I try to use it in this listener.
I inject the token_storage_service in other services and it works well.
I'm under symfony 3.1, with a rest API.
And i send my authorizations header with Postman.
Can someone tell me what's wrong with my code ?
Thanks in advance.
Try to call $tokenStorage->getToken() in you preFlush method not in the constructor.

Calling a service inside a lifecycle event

I have a lifecycle event. As soon as an order is created the prePersist lifecycle event add a few more details to the order before it is persisted to the database.
This is my prePersist event class;
<?php
namespace Qi\Bss\BaseBundle\Lib\PurchaseModule;
use Qi\Bss\BaseBundle\Entity\Business\PmodOrder;
use Doctrine\ORM\Event\LifecycleEventArgs;
/**
* Listener class
* Handles events related to list prices
*/
class OrderUserListener
{
/**
* Service container
* #var type
*/
private $serviceContainer;
/**
* Performs tasks before destruction
* #ORM\PrePersist
*/
public function prePersist(LifecycleEventArgs $args)
{
$order = $args->getEntity();
if ($order instanceof PmodOrder) {
$user = $this->serviceContainer->get('security.token_storage')->getToken()->getUser();
if ($user) {
$order->setCreatedBy($user);
$order->setCreatedAt(new \DateTime(date('Y-m-d H:i:s')));
$order->setDepartment($user->getDepartment());
$order->setStatus(PmodOrder::STATUS_AWAITING_APPROVAL);
$this->serviceContainer->get('bss.pmod.order_logger')->log($order, 'Order Created');
}
}
}
/**
* Sets the sales order exporter object
* #param type $serviceContainer
*/
public function setServiceContainer($serviceContainer)
{
$this->serviceContainer = $serviceContainer;
}
}
It works perfectly but this part $this->serviceContainer->get('bss.pmod.order_logger')->log($order, 'Order Created'); doesn't want to work. I try to call a service inside it. I know the service works perfectly inside my controllers, but here I get an error;
A new entity was found through the relationship
'Qi\Bss\BaseBundle\Entity\Business\PmodLog#order' that was not
configured to cascade persist operations for entity: Nuwe Test vir
logger. To solve this issue: Either explicitly call
EntityManager#persist() on this unknown entity or configure cascade
persist this association in the mapping for example
#ManyToOne(..,cascade={"persist"}).
This is how my OrderLogger service class looks like;
<?php
namespace Qi\Bss\BaseBundle\Lib\PurchaseModule;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Doctrine\ORM\EntityManager;
use Qi\Bss\BaseBundle\Entity\Business\PmodLog;
/**
* Class AppLogger. Purchase Module logger.
* #package FcConnectBundle\Lib
*/
class OrderLogger {
private $em;
private $tokenStorage;
/**
* Constructor.
*
* #param EntityManager $em
* #param TokenStorage $securityTokenStorage
*/
public function __construct(EntityManager $em, TokenStorage $securityTokenStorage)
{
$this->em = $em;
$this->tokenStorage = $securityTokenStorage;
}
/**
* Log an order action.
*
* #param string $text
*/
public function log($order, $action)
{
$logRecord = new PmodLog();
if (is_object($this->tokenStorage->getToken())) {
$user = $this->tokenStorage->getToken()->getUser();
if (is_object($user)) {
$logRecord->setUser($user);
}
}
$logRecord->setOrder($order);
$logRecord->setAction($action);
$logRecord->setTime(new \DateTime());
$this->em->persist($logRecord);
$this->em->flush();
}
}
I have already tried changing the persist in my log to merge, but that also doesn't work. Can somebody please help and explain what I do wrong?
This is not the best architecture, but it will work:
On prePersist add all messages to some kind of private variable (like $logMessages), and add another event
/**
* #param PostFlushEventArgs $args
*/
public function postFlush(PostFlushEventArgs $args)
{
$logMessages = $this->logMessages;
$this->logMessages = array(); //clean to avoid double logging
if (!empty($logMessages)) {
foreach ($logMessages as $message) {
$this->serviceContainer->get('bss.pmod.order_logger')->log($message);
}
}
}
I fixed the problem by adding a postPersist and call the logger in there instead of inside my prePersist;
/**
* Performs tasks before destruction
* #ORM\PostPersist
*/
public function postPersist(LifecycleEventArgs $args)
{
$order = $args->getEntity();
if ($order instanceof PmodOrder) {
$this->serviceContainer->get('bss.pmod.order_logger')->log($order, 'Order Created');
}
}
Because what I think is happening is that the logger tries to be executed but the order in the logger doesn't yet exists as it is not yet persisted. This way makes more sense to me, and I think this is the easiest fix. I could be wrong though, any comments and other opinions on my answer are welcome.

Symfony 2 Doctrine MongoDB entity listener

I'm trying to get Entity Listeners work with ODM in Symfony 2.7 but to no avail.
a51.document.listener.store:
class: A51\FilesystemBundle\EventListener\StoreEntityListener
tags:
- { name: doctrine.odm.mongodb.document_manager, event: postLoad, method: onPostLoad }
arguments: [#a51.repo.file]
and:
<?php
namespace A51\FilesystemBundle\EventListener;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use A51\FilesystemBundle\Document\Store;
use A51\FilesystemBundle\Repository\FileRepositoryInterface;
class StoreEntityListener
{
/**
* #var FileRepositoryInterface
*/
private $fileRepository;
public function __construct(FileRepositoryInterface $fileRepository)
{
$this->fileRepository = $fileRepository;
}
public function onPostLoad(LifecycleEventArgs $args)
{
$this->index($args);
}
public function index(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
if ($entity instanceof Store)
{
$entity->setTotalByteSize($this->fileRepository->findSumFilesSizeByStore($entity));
}
}
}
I've tried pretty much everything I could find in docs but for some reason onPostLoad method does not get called.
Store document gets loaded with ParamConverter:
* #ParamConverter("store", class="A51FilesystemBundle:Store")
Any help would be welcome.
I have a MongoDB listener in my project, but my code it's so different of yours. There's a simpler way, all you must to do it's import DocumentManager and then you can call it from construct to use it on all you listener. I'm gonna show you my code and tell me if this help you ;)
namespace AppBundle\OdmListener;
use Doctrine\ORM\Event\PostPersistEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ODM\MongoDB\DocumentManager;
class RedundancyListener
{
/**
* #var DocumentManager
*/
private $dm;
/**
* #param DocumentManager $odm
*/
function __construct(DocumentManager $dm)
{
$this->dm = $dm;
}
Then inside you can do any queries or updates as you do it in you controller. Also you can use ORM or ODM CycleEvents if you import them, like I do in the example.
/**
* #param LifecycleEventArgs $eventArgs
*/
public function preUpdate(LifecycleEventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
if ($entity instanceof \AppBundle\Entity\Example) {
$subscriptionHash = $this->getSubscription($entity);
$this->dm->createQueryBuilder('AppBundle\Document\Example')
->update()
//->multiple(true)
->field('name')->set($entity->getName())
->field('last_name')->set($entity->getLastName())
->field('mail')->set($entity->getMail())
->getQuery()
->execute();
}
}
}

Get service container from entity in symfony 2.1 (Doctrine)

How to use entity as service in doctrine (Using Symfony 2.1).
Example usage:
<?php
namespace MyNamespace;
class MyEntity
{
protected $container = NULL;
public function __construct($container)
{
$this->container = $container;
}
/**
* #ORM\PrePersist
*/
public function()
{
// Must call to container and get any parameters
// for defaults sets entity parameters
$this->container->get('service.name');
}
}
As a result, I need to get access to the entire container.
EDIT: THIS IS NOT THE PREFERRED WAY, it's the only way to get service container inside an entity, it's not a good practice, it should be avoided, but this just answers the question.
In case you still want the container and/or repository you can extend a base abastractEntity like this:
<?php
namespace Acme\CoreBundle\Entity;
/**
* Abstract Entity
*/
abstract class AbstractEntity
{
/**
* Return the actual entity repository
*
* #return entity repository or null
*/
protected function getRepository()
{
global $kernel;
if ('AppCache' == get_class($kernel)) {
$kernel = $kernel->getKernel();
}
$annotationReader = $kernel->getContainer()->get('annotation_reader');
$object = new \ReflectionObject($this);
if ($configuration = $annotationReader->getClassAnnotation($object, 'Doctrine\ORM\Mapping\Entity')) {
if (!is_null($configuration->repositoryClass)) {
$repository = $kernel->getContainer()->get('doctrine.orm.entity_manager')->getRepository(get_class($this));
return $repository;
}
}
return null;
}
}
An entity is a data model and should only hold data (and not have any dependencies on services). If you want to modify your model in case of a certain event (PrePersist in your case) you should look into making a Doctrine listener for that. You can inject the container when defining the listener:
services:
my.listener:
class: Acme\SearchBundle\Listener\YourListener
arguments: [#your_service_dependency_or_the_container_here]
tags:
- { name: doctrine.event_listener, event: prePersist }

Categories