Symfony throwing ServiceCircularReferenceException - php

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.

Related

TYPO3 v9.5.11 Extbase: Inject ServiceObject generated by a ContainerClass into Repository

I am trying to inject an service object into my Repository. I have created different Service Classes under the directory Classes/Services. There is also one class that I created called ContainerService, which creates and instantiate one ServiceObject for each Service Class.
ContainerService Class:
namespace VendorName\MyExt\Service;
use VendorName\MyExt\Service\RestClientService;
class ContainerService {
private $restClient;
private $otherService;
/**
* #return RestClientService
*/
public function getRestClient() {
$objectManager = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
if ($this->restClient === null) {
$this->restClient = $objectManager->get(RestClientService::class);
}
return $this->restClient;
}
...
As I said, I create my ServiceObjects in the ContainerService Class.
Now I want to inject the ContainerService into my Repository and use it.
MyRepository Class:
namespace VendorName\MyExt\Domain\Repository;
use VendorName\MyExt\Service\ContainerService;
class MyRepository extends Repository
{
/**
* #var ContainerService
*/
public $containerService;
/**
* inject the ContainerService
*
* #param ContainerService $containerService
* #return void
*/
public function injectContainerService(ContainerService $containerService) {
$this->containerService = $containerService;
}
// Use Objects from The ContainerService
public function findAddress($addressId) {
$url = 'Person/getAddressbyId/'
$someData = $this->containerService->getRestClient()->sendRequest($url)
return $someData;
}
In MyController I recieve the $someData from my findAddress function and do some work with it.
But when I call my Page, I get following ErrorMessage:
(1/2) #1278450972 TYPO3\CMS\Extbase\Reflection\Exception\UnknownClassException
Class ContainerService does not exist. Reflection failed.
Already tried to reload all Caches and dumping the Autoload didn't help either.
Didn't install TYPO3 with composer.
I appreciate any advice or help! Thanks!
Actually found the Issue.
In MyRepository Class there was a Problem with the Annotations and the TypeHint:
namespace VendorName\MyExt\Domain\Repository;
use VendorName\MyExt\Service\ContainerService;
class MyRepository extends Repository
{
/**
*** #var \VendorName\MyExt\Service\ContainerService**
*/
public $containerService;
/**
* inject the ContainerService
*
* #param \VendorName\MyExt\Service\ContainerService $containerService
* #return void
*/
public function injectContainerService(\VendorName\MyExt\Service\ContainerService $containerService) {
$this->containerService = $containerService;
}
// Use Objects from The ContainerService
public function findAddress($addressId) {
$url = 'Person/getAddressbyId/'
$someData = $this->containerService->getRestClient()->sendRequest($url)
return $someData;
}
Now it works.

Logging from inside a repository in Symfony

I'm tracing a weird error in a Symfony 2 app and I'd like to know if there's a way to print log messages from a Repository PHP file. For example:
class OrderEntityRepository extends EntityRepository
{
/**
*
* #param mixed $filter
* #return type
*/
public function findByCriteria($filter) {
[...]
/* I'D LIKE TO LOG SOME VARIABLES FROM HERE */
}
}
I've tried using error_log() but nothing happens.
Is this possible? Thanks in advance,
It's possible but it's usually not a good practice. The good thing to do is to send back the Repository result to your Controller or Service and you log from them an error or something else.
But if you still want to do it, Repository are like services (when you implements ServiceEntityRepository see this slide for more information). If you want to log something specific inside you have to inject the LoggerInterface into your Repository configuration (like you do with service).
In your service.yml (or xml) if you don't use autowire:
Your\Repository:
arguments: ['#logger']
In your repository class:
/**
* #var LoggerInterface
*/
protected $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
On symfony 3.8 I have
class BlahRepository extends ServiceEntityRepository
{
/* #var ContainerInterface $container */
private $container;
/* #var LoggerInterface $logger */
private $logger;
public function __construct(RegistryInterface $registry, ContainerInterface $container, LoggerInterface $logger)
{
parent::__construct($registry, Blah::class);
$this->container = $container;
$this->logger = $logger;
}
}
and I am able to use $this->logger->info("text")
I think the trick may be extending ServiceEntityRepository
In order to use dependency injection for Doctrine entity repositories, you can create a custom RepositoryFactory.
Tested on Symfony 3.4.
<?php
namespace AppBundle\Doctrine;
use Doctrine\Common\Persistence\ObjectRepository;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Repository\DefaultRepositoryFactory;
use Doctrine\ORM\Repository\RepositoryFactory as RepositoryFactoryInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
class RepositoryFactory implements RepositoryFactoryInterface, LoggerAwareInterface
{
/** #var DefaultRepositoryFactory */
protected $defaultRepositoryFactory;
/** #var LoggerInterface */
private $logger;
/**
* #required (for autowiring)
* #param LoggerInterface $logger (Monolog will be the default one)
*/
public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}
/**
* #see Configuration::getRepositoryFactory()
*/
public function __construct()
{
$this->defaultRepositoryFactory = new DefaultRepositoryFactory();
}
/**
* Gets the repository for an entity class.
*
* #param EntityManagerInterface $entityManager
* #param string $entityName The name of the entity.
* #return ObjectRepository
*/
public function getRepository(EntityManagerInterface $entityManager, $entityName): ObjectRepository
{
$repository = $this->defaultRepositoryFactory->getRepository($entityManager, $entityName);
if ($repository instanceof LoggerAwareInterface && $this->logger !== null) {
$repository->setLogger($this->logger);
}
return $repository;
}
}
Declare it in Doctrine configuration.
# app/config.yml
doctrine:
# ...
orm:
repository_factory: AppBundle\Doctrine\RepositoryFactory
And finally, make your repository class implement LoggerAwareInterface.
class OrderEntityRepository extends EntityRepository implements LoggerAwareInterface
{
/** #var LoggerInterface */
private $logger;
/**
* #param LoggerInterface $logger
*/
public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}
/**
* #param mixed $filter
* #return type
*/
public function findByCriteria($filter) {
//[...]
$this->logger->alert('message');
}
}
You can also make a LoggerAwareTrait trait to spare yourself some code duplication.

Add Twig global variable after kernel.request event

I am currently working on developing a project in SAAS, each client can access his platform by a personalized url (site1.com, site2.com, etc.).
For each domain name a set of template customization data is defined in the back office and I must be able to access it from my Twig files. So I defined a listener on the kernerl.request event that adds a global variable to Twig based on the current domain name. Everything works fine in most cases, except when a page is first displayed, Twig must be run upstream and I get the following error:
Unable to add global "site" as the runtime or the extensions have
already been initialized.
Listener class
class SiteListener
{
public function __construct(
SiteHelper $siteHelper,
\Twig_Environment $twig
) {
$this->siteHelper = $siteHelper;
$this->twig = $twig;
}
/**
* Add current contexts to twig global.
*/
public function addContextsToTwigGlobal(GetResponseEvent $event)
{
$this->twig->addGlobal('site', $this->siteHelper);
}
}
Listener service declaration
multisite.listener.site:
class: MultisiteBundle\Listener\SiteListener
arguments:
- "#multisite.helper.site"
- "#twig"
tags:
- { name: kernel.event_listener, event: kernel.request, method: addContextsToTwigGlobal }
SiteHelper service
class SiteHelper
{
/**
* #var RequestStack
*/
protected $requestStack;
/**
* #var ContextConfigManager;
*/
protected $contextConfigManager;
/**
* #var ContextConfig
*/
protected $contextConfig;
public function __construct(
RequestStack $requestStack,
ContextConfigManager $contextConfigManager
) {
$this->requestStack = $requestStack;
$this->contextConfigManager = $contextConfigManager;
$this->contextConfig = $this->contextConfigManager
->findByHostOrStandard($this->getHost());
}
/**
* Get host from current request.
*
* #return string|null
*/
public function getHost()
{
$request = $this->requestStack->getCurrentRequest();
return ($request) ? $request->getHost() : null;
}
/**
* Get current context config
*
* #return ContextConfig
*/
public function getContextConfig()
{
return $this->contextConfig;
}
}
Any idea ?
I decided to write a Twig function to avoid this kind of problem. This seem to be a good solution.
class SiteExtension extends \Twig_Extension
{
/**
* #var SiteHelper
*/
private $siteHelper;
/**
* Constructor.
*
* #param SiteHelper $siteHelper
*/
public function __construct(SiteHelper $siteHelper)
{
$this->siteHelper = $siteHelper;
}
/**
* {#inheritdoc}
*/
public function getFunctions()
{
return array(
new \Twig_Function('site', array($this->siteHelper, 'getContext')),
);
}
}

Symfony2 Custom Validator with EntityManager not working

I am trying to build a validator that will check against the database for some values. For this I need to inject inside a service the entityManager and give an alias to my Validation method as documented in Symfony official documentation.
The problem is that after doing everything by the book I am still getting an error saying that the entityManager is null:
Catchable Fatal Error: Argument 1 passed to XXX\CommonBundle\Validator\Constraints\IsSingleEntryValidator::__construct() must be an instance of Doctrine\ORM\EntityManager, none given, called in /var/www/XXX/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Validator/ConstraintValidatorFactory.php on line 71 and defined
My service:
XXX.validators.is_single_entry:
class: XXX\CommonBundle\Validator\Constraints\IsSingleEntryValidator
arguments:
- "#doctrine.orm.default_entity_manager"
tags:
- { name: validator.constraint_validator, alias: single_entry_validation }
And the validator class:
class IsSingleEntryValidator extends ConstraintValidator
{
/**
* #var EntityManager
*/
protected $em;
/**
* Constructor
*
* #param EntityManager $em
*/
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function validate($value, Constraint $constraint)
{
...
}
public function validateBy()
{
return 'single_entry_validation';
}
}
And the use of validator:
/**
* #ORM\Column(name="is_primary", type="boolean", nullable=true)
* #SiteAssert\IsSingleEntry(message="validator.single.entry")
*/
protected $isPrimary;
#Ragdata - doctrine.orm.default_entity_manager Doctrine\ORM\EntityManager
There are actually 2 mistakes in my code.
Calling of validatedBy() function
This function should be called inside the IsSingleEntry class and not IsSingleEntryValidator
Method name should be diferent
I call the method validateBy() but the correct function name should be validatedBy()
So the code should be looking like this now:
IsSingleEntry
class IsSingleEntry extends Constraint
{
public $message = "The value already exists in the database";
/**
* #return string
*/
public function validatedBy()
{
return 'single_entry_validation';
}
}
IsSingleEntryValidator
class IsSingleEntryValidator extends ConstraintValidator
{
/**
* #var EntityManager
*/
protected $em;
/**
* Construct
*
* #param EntityManager $em
*/
public function __construct(EntityManager $em)
{
$this->em = $em;
}
/**
* Validate
*
* #param mixed $value
* #param Constraint $constraint
*/
public function validate($value, Constraint $constraint)
{
$oActiveExists = $this->em->getRepository('DatabaseBundle:Languages')->findOneByIsPrimary(true);
if ($oActiveExists) {
$this->context->buildViolation($constraint->message)
->addViolation();
}
}
}

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

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.

Categories