Symfony 3 Inject service into entity repository - php

I tried inject memcached service into entity repository, but my variant not work.
services:
work.repository.company:
class: WorkBundle\Repository\CompanyRepository
factory: ['#doctrine.orm.entity_manager', getRepository]
arguments:
- 'WorkBundle:Company'
calls:
- [setCacheService, ['#memcache.default']]
CompanyRepository have setter setCacheService, but it's not called.
class CompanyExtension extends \Twig_Extension
{
/**
* #var EntityManager
*/
private $em;
public function setEntityManager(EntityManager $entityManager)
{
$this->em = $entityManager;
}
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('getCompaniesCount', array($this, 'getCompaniesCount'))
);
}
/**
* #return integer
*/
public function getCompaniesCount()
{
return $this->em->getRepository('WorkBundle:Company')->getActiveCompaniesCount();
}
public function getName()
{
return 'work_company_extension';
}
}
Why this code not works?

Have you registered repositoryClass in your WorkBundle:Company entity? Your entity should contain something like: #ORM\Entity(repositoryClass="Work\Company") or yaml equivalent.
You should let Symfony create the repository by injecting work.repository.company into your Twig extension.

Related

Cannot load translation from database in symfony 4.3.3

i'm trying to load translations from database in Symfony 4. The Translator instance doesn't call the custom loader i wrote using this tutorial (https://medium.com/#andrew72ru/store-translation-messages-in-database-in-symfony-3f12e579df74).
I created dummy files in the /translation folder (messages.it.db) to trigger the loader but it doesn't get called.
services.yaml
parameters:
locales: ['it','en']
db_i18n.entity: App\Entity\Translation
services:
translation.loader.db:
class: App\Loader\DbLoader
arguments:
- '#service_container'
- '#doctrine.orm.entity_manager'
tags:
- { name: translation.loader, alias: db}
DbLoader.php
namespace App\Loader;
use Creative\DbI18nBundle\Interfaces\EntityInterface;
use Creative\DbI18nBundle\Interfaces\TranslationRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Translation\Loader\LoaderInterface;
use Symfony\Component\Translation\MessageCatalogue;
class DbLoader implements LoaderInterface
{
/**
* #var EntityManagerInterface
*/
private $doctrine;
/**
* #var string
*/
private $entityClass;
public function __construct(ContainerInterface $container, EntityManagerInterface $doctrine)
{
$this->doctrine = $doctrine;
$this->entityClass = $container->getParameter('db_i18n.entity');
}
public function load($resource, $locale, $domain = 'messages')
{
$messages = $this->getRepository()->findByDomainAndLocale($domain, $locale);
$values = array_map(static function (EntityInterface $entity) {
return $entity->getTranslation();
}, $messages);
$catalogue = new MessageCatalogue($locale, [
$domain => $values
]);
return $catalogue;
}
public function getRepository(): TranslationRepositoryInterface
{
return $this->doctrine->getRepository($this->entityClass);
}
}
Here's my translation table
Here is the test code i'm using to call the Translator
TestController.php
class TestController extends AbstractController
{
/**
* #Route("/test", name="test")
*/
public function index(TranslatorInterface $translator)
{
$translator->trans('prova', [], 'messages', 'it');
return new Response();
}
}
The result is supposed to be "prova it" but I get "prova" instead, which is the key of the translation. I tried to put a dd() on the DbLoader constructor and it's never been called.
I also have in my project Api Platform, but i don't think it's causing this problem.
I resolved my issue.
By using dd() on my Translator instance i discovered that Symfony wasn't loading my translation files correctly. Looking through the properties i noticed the path of my translation files were not correct.
I placed them in src/Resources/translations instead and then it worked!

symfony4, Dependency injection, no default argument

I have setup service to controller function like this
App\Controller\Controller:
calls:
- [new, ['#request_stack','#doctrine.orm.default_entity_manager']]
I needed Entity Manager inside controller action and my function looks like this
public function new(RequestStack $request, EntityManager $em): Response
{
$currentRequest = $request->getCurrentRequest();
$data = json_decode($currentRequest->getContent(), true);
....
return new ApiResponse(['message' => $message['message'], 'body' => 'success']);
}
and when executing comes to line return new ApiResponse it gives error
Controller "Controller::new()" requires that you provide a value for the "$request" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.
How to get entity manager in controller action or how to resolve this problem?
As the Symfony 4 Doc on Doctrine says :
// you can fetch the EntityManager via $this->getDoctrine()
// or you can add an argument to your action: index(EntityManagerInterface $entityManager)
$entityManager = $this->getDoctrine()->getManager();
So you can just get the entity manager this way in your controller
However, you can also register the Entity Manager as a service to use it.
Be sure to set the autowire to true :
# config/services.yaml
services:
_defaults:
autowire: true
and register it as a service :
# config/services.yaml
services:
#....
controller_em:
class: App\Controller\Controller
arguments: [ '#doctrine.orm.default_entity_manager' ]
public: true
So that you can use it like so in your controller :
private $objectManager;
public function __construct(ObjectManager $objectManager)
{
$this->objectManager = $objectManager;
}
You can also use this way to use the Entity Manager in Voter or Manager.
well. you need to inject your stuff into controller's object constructor - that is called DI in Symfony-way (or via set-methods):
services.yml - if everything ok with your autowire
App\Controller\Controller:
calls:
- [new]
if not add it manually:
App\Controller\Controller:
arguments:
- '#doctrine.orm.default_entity_manager'
calls:
- [new]
Controller.php
/** #var EntityManager */
private $em;
public __construct(EntityManager $em)
{
$this->em = $em;
}
and then just use it in your method:
public function new(RequestStack $request): Response
{
$this->em ...
}
For your information you can create your own AbsractController to inject the EntityManager in all controller extending it like this.
<?php
namespace App\Controller;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController as BaseController;
abstract class AbstractController extends BaseController
{
/**
* #var EntityManagerInterface
*/
protected $em;
/**
* #required
*
* #param EntityManagerInterface $em
*/
public function setEntityManager(EntityManagerInterface $em)
{
$this->em = $em;
}
}
If a controller extends this AbstractController, you could access $this->em everywhere in it.
The "required" annotation here is the key to enable what you tried to do without the need of adding configuration as you did. It's like adding a calls line in your configuration!
You could do something like this for every services you need in all your controllers.

Symfony 4.2 - How to decorate the UrlGenerator

I want to decorate the Symfony UrlGenerator class.
Symfony\Component\Routing\Generator\UrlGenerator: ~
my.url_generator:
class: AppBundle\Service\UrlGenerator
decorates: Symfony\Component\Routing\Generator\UrlGenerator
arguments: ['#my.url_generator.inner']
public: false
I've added this to the services.yml but my AppBundle\Service\UrlGenerator class is ignored:
I tried the following configuration again.
config/services.yaml
parameters:
locale: 'en'
router.options.generator_class: AppBundle\Service\UrlGenerator
router.options.generator_base_class: AppBundle\Service\UrlGenerator
Still it doesn't work
How to decorate the UrlGenerator in Symfony 4.2?
The right answer is : you shouldn't decorate UrlGeneratorInterface.
You have to decorate 'router' service. Check here : https://github.com/symfony/symfony/issues/28663
** services.yml :
services:
App\Services\MyRouter:
decorates: 'router'
arguments: ['#App\Services\MyRouter.inner']
** MyRouter.php :
<?php
namespace App\Services;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouterInterface;
class MyRouter implements RouterInterface
{
/**
* #var RouterInterface
*/
private $router;
/**
* MyRouter constructor.
* #param RouterInterface $router
*/
public function __construct(RouterInterface $router)
{
$this->router = $router;
}
/**
* #inheritdoc
*/
public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH)
{
// Your code here
return $this->router->generate($name, $parameters, $referenceType);
}
/**
* #inheritdoc
*/
public function setContext(RequestContext $context)
{
$this->router->setContext($context);
}
/**
* #inheritdoc
*/
public function getContext()
{
return $this->router->getContext();
}
/**
* #inheritdoc
*/
public function getRouteCollection()
{
return $this->router->getRouteCollection();
}
/**
* #inheritdoc
*/
public function match($pathinfo)
{
return $this->router->match($pathinfo);
}
}
I believe the issue is that UrlGenerator service name is Symfony\Component\Routing\Generator\UrlGeneratorInterface, and not Symfony\Component\Routing\Generator\UrlGenerator (cf. this code).
Secondly, when you decorate a service, the decorator will take the service name. So you should not need to modify router.options.generator_class.
Try with this configuration:
my.url_generator:
class: AppBundle\Service\UrlGenerator
decorates: Symfony\Component\Routing\Generator\UrlGeneratorInterface
arguments: ['#my.url_generator.inner']
Setting public to false is likely not needed, as on Symfony4/Flex it should be the default value.
Update for comments:
decorated service may look like this:
class MyUrlGenerator implements UrlGeneratorInterface
{
private $originalUrlGenerator;
public function __construct(UrlGeneratorInterface $innerUrlGenerator)
{
$this->originalUrlGenerator = $innerUrlGenerator;
}
public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH)
{
// Maybe add your custom logic here...
// or completely override base method
return $this->originalUrlGenerator->generate($name, $parameters, $referenceType);
}
}
I believe you have to decorate the Symfony\Component\Routing\Generator\UrlGeneratorInterface, because the services should depend on the interface and not on a specific implementation (class).

Symfony 2 EntityManager injection in service

I've created my own service and I need to inject doctrine EntityManager, but I don't see that __construct() is called on my service, and injection doesn't work.
Here is the code and configs:
<?php
namespace Test\CommonBundle\Services;
use Doctrine\ORM\EntityManager;
class UserService {
/**
*
* #var EntityManager
*/
protected $em;
public function __constructor(EntityManager $entityManager)
{
var_dump($entityManager);
exit(); // I've never saw it happen, looks like constructor never called
$this->em = $entityManager;
}
public function getUser($userId){
var_dump($this->em ); // outputs null
}
}
Here is services.yml in my bundle
services:
test.common.userservice:
class: Test\CommonBundle\Services\UserService
arguments:
entityManager: "#doctrine.orm.entity_manager"
I've imported that .yml in config.yml in my app like that
imports:
# a few lines skipped, not relevant here, i think
- { resource: "#TestCommonBundle/Resources/config/services.yml" }
And when I call service in controller
$userservice = $this->get('test.common.userservice');
$userservice->getUser(123);
I get an object (not null), but $this->em in UserService is null, and as I already mentioned, constructor on UserService has never been called
One more thing, Controller and UserService are in different bundles (I really need that to keep project organized), but still: everyting else works fine, I can even call
$this->get('doctrine.orm.entity_manager')
in same controller that I use to get UserService and get valid (not null) EntityManager object.
Look like that I'm missing piece of configuration or some link between UserService and Doctrine config.
Your class's constructor method should be called __construct(), not __constructor():
public function __construct(EntityManager $entityManager)
{
$this->em = $entityManager;
}
For modern reference, in Symfony 2.4+, you cannot name the arguments for the Constructor Injection method anymore. According to the documentation You would pass in:
services:
test.common.userservice:
class: Test\CommonBundle\Services\UserService
arguments: [ "#doctrine.orm.entity_manager" ]
And then they would be available in the order they were listed via the arguments (if there are more than 1).
public function __construct(EntityManager $entityManager) {
$this->em = $entityManager;
}
Note as of Symfony 3.3 EntityManager is depreciated. Use EntityManagerInterface instead.
namespace AppBundle\Service;
use Doctrine\ORM\EntityManagerInterface;
class Someclass {
protected $em;
public function __construct(EntityManagerInterface $entityManager)
{
$this->em = $entityManager;
}
public function somefunction() {
$em = $this->em;
...
}
}
Since 2017 and Symfony 3.3 you can register Repository as service, with all its advantages it has.
Check my post How to use Repository with Doctrine as Service in Symfony for more general description.
To your specific case, original code with tuning would look like this:
1. Use in your services or Controller
<?php
namespace Test\CommonBundle\Services;
use Doctrine\ORM\EntityManagerInterface;
class UserService
{
private $userRepository;
// use custom repository over direct use of EntityManager
// see step 2
public function __constructor(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
public function getUser($userId)
{
return $this->userRepository->find($userId);
}
}
2. Create new custom repository
<?php
namespace Test\CommonBundle\Repository;
use Doctrine\ORM\EntityManagerInterface;
class UserRepository
{
private $repository;
public function __construct(EntityManagerInterface $entityManager)
{
$this->repository = $entityManager->getRepository(UserEntity::class);
}
public function find($userId)
{
return $this->repository->find($userId);
}
}
3. Register services
# app/config/services.yml
services:
_defaults:
autowire: true
Test\CommonBundle\:
resource: ../../Test/CommonBundle

Injecting SecurityContext into a Listener prePersist or preUpdate in Symfony2 to get User in a createdBy or updatedBy Causes Circular Reference Error

I setup a listener class where i'll set the ownerid column on any doctrine prePersist. My services.yml file looks like this ...
services:
my.listener:
class: App\SharedBundle\Listener\EntityListener
arguments: ["#security.context"]
tags:
- { name: doctrine.event_listener, event: prePersist }
and my class looks like this ...
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\SecurityContextInterface;
class EntityListener
{
protected $securityContext;
public function __construct(SecurityContextInterface $securityContext)
{
$this->securityContext = $securityContext;
}
/**
*
* #param LifecycleEventArgs $args
*/
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
$entity->setCreatedby();
}
}
The result of this is the following error.
ServiceCircularReferenceException: Circular reference detected for service "doctrine.orm.default_entity_manager", path: "doctrine.orm.default_entity_manager -> doctrine.dbal.default_connection -> my.listener -> security.context -> security.authentication.manager -> fos_user.user_manager".
My assumption is that the security context has already been injected somewhere in the chain but I don't know how to access it. Any ideas?
I had similar problems and the only workaround was to pass the whole container in the constructor (arguments: ['#service_container']).
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\DependencyInjection\ContainerInterface;
class MyListener
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
// ...
public function prePersist(LifeCycleEventArgs $args)
{
$securityContext = $this->container->get('security.context');
// ...
}
}
As of Symfony 2.6 this issue should be fixed. A pull request has just been accepted into the master. Your problem is described in here.
https://github.com/symfony/symfony/pull/11690
As of Symfony 2.6, you can inject the security.token_storage into your listener. This service will contain the token as used by the SecurityContext in <=2.5. In 3.0 this service will replace the SecurityContext::getToken() altogether. You can see a basic change list here: http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements#deprecated-the-security-context-service
Example usage in 2.6:
Your configuration:
services:
my.entityListener:
class: App\SharedBundle\Listener\EntityListener
arguments:
- "#security.token_storage"
tags:
- { name: doctrine.event_listener, event: prePersist }
Your Listener
namespace App\SharedBundle\Listener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class EntityListener
{
private $token_storage;
public function __construct(TokenStorageInterface $token_storage)
{
$this->token_storage = $token_storage;
}
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entity->setCreatedBy($this->token_storage->getToken()->getUsername());
}
}
For a nice created_by example, you can use https://github.com/hostnet/entity-blamable-component/blob/master/src/Listener/BlamableListener.php for inspiration. It uses the hostnet/entity-tracker-component which provides a special event that is fired when an entity is changed during your request. There's also a bundle to configure this in Symfony2
https://github.com/hostnet/entity-tracker-component
https://github.com/hostnet/entity-tracker-bundle
There's a great answer already in this thread but everything changes. Now there're entity listeners classes in Doctrine:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#entity-listeners-class
So you can add an annotation to your entity like:
/**
* #ORM\EntityListeners({"App\Entity\Listener\PhotoListener"})
* #ORM\Entity(repositoryClass="App\Repository\PhotoRepository")
*/
class Photo
{
// Entity code here...
}
And create a class like this:
class PhotoListener
{
private $container;
function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/** #ORM\PreRemove() */
public function preRemoveHandler(Photo $photo, LifecycleEventArgs $event): void
{
// Some code here...
}
}
Also you should define this listener in services.yml like that:
photo_listener:
class: App\Entity\Listener\PhotoListener
public: false
autowire: true
tags:
- {name: doctrine.orm.entity_listener}
I use the doctrine config files to set preUpdate or prePersist methods:
Project\MainBundle\Entity\YourEntity:
type: entity
table: yourentities
repositoryClass: Project\MainBundle\Repository\YourEntitytRepository
fields:
id:
type: integer
id: true
generator:
strategy: AUTO
lifecycleCallbacks:
prePersist: [methodNameHere]
preUpdate: [anotherMethodHere]
And the methods are declared in the entity, this way you don't need a listener and if you need a more general method you can make a BaseEntity to keep that method and extend the other entites from that. Hope it helps!
Symfony 6.2.4
Add this in your Entity :
#[ORM\EntityListeners(["App\Doctrine\MyListener"])]
Add this in your services.yaml:
App\Doctrine\MyListener:
tags: [doctrine.orm.entity_listener]
Then you can do this :
<?php
namespace App\Doctrine;
use App\Entity\MyEntity;
use Symfony\Component\Security\Core\Security;
class MyListener
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function prePersist(MyEntity $myEntity)
{
//Your stuff
}
}
Hope it helps.

Categories