I have this function in Entity class but the getDoctrine do not fond...
public function getObject()
{
$em = $this->getDoctrine()->getEntityManager();
switch($this->objectType)
{
case 'video':
return $em->getRepository('fdj2012AdminBundle:Video')->find($this->objectId);
break;
case 'default':
return false;
break;
}
}
How to use entityManager inside my Entity ?
Actually Entity shouldn't know about EM. I use Event Listeners if I need advance logic in my Entity. When you register Listeners like services you can pass args there, like a EM or Container and get them inside Listener class.
Symfony Doc
But I know not really good way to get EM inside Entity class. By taking global variable Kernel in Entity methods.
global $kernel;
if ( 'AppCache' == get_class($kernel) )
{
$kernel = $kernel->getKernel();
}
$em = $kernel->getContainer()->get( 'doctrine.orm.entity_manager' );
Shame on me :(
In services.yml add this
access_manager:
class: AppBundle\Services\EntityManager
arguments: [ #service_container ]
In Manager-
private $_container;
public function __construct(ContainerInterface $container)
{
$this->_container = $container;
}
To access manager-
$entity2Manager = $this->_container->get('entity2_manager');
Related
I have one question I don't seem to find an answer to.
I have my User entity with a "Status" field.
What I want to do is store in another table "StatusEvent" a new line each time the status of a user is changed to keep track of the history of statuses of my users.
I tried to work with the PreUpdate method but it doesn't allow the creation of new Entities in this step.
I was maybe thinking that it might be possible with other events (onFlush maybe?) but these do not have the methods of the LifecycleEventArgs from PreUpdate (which allows to know if a field has been changed).
Anyone has already came across a same pattern or has an idea on how I could implement it?
Thanks by advance,
This is a nice case to use a custom event and listener.
Create a class UserEvents to hold a constant with the event name like
class UserEvents
{
const STATUS_CHANGED = 'user.status.changed';
}
Create a UserStatusChangedEvent that extends Event and takes the user as a parameter.
class UserChangedEvent extends Event
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function getUser(): User
{
return $this->user;
}
}
And then create and register a listener to capture/handle that event and create the entry that you need using the data from the user object that was passed in the event when it was dispatched.
class UserListener
{
public function onStatusChanged(UserChangedEvent $event)
{
$user = $event->getUser();
//TODO: Create your new status change entry. If you need the entity manager, just inject it in the constructor, like with any other service
}
}
You then need to register you listener as a service and tag it
AppBundle\Event\Listener\UserListener:
tags:
- { name: kernel.event_listener, event: user.status.changed, method: onStatusChanged }
And now all you have to do is dispatch a new instance of the event every time the status changes, passing it the user that you just persisted.
$eventDispatcher->dispatch(
UserEvents::STATUS_CHANGED,
$user
);
Edit: To defend the manual dispatching of the custom event VS the automated dispatch of onFlush, the custom event code is far easier to read even from a newbie that has no knowledge of how/when doctrine lifecycle events are triggered or how the entity manager works internally. The cherry at the top is that the dispatching works as a nice reminder that you have a listener there, which will be useful when you revisit your code in a few months.
The solution by #Dimitris would work, but requires you to dispatch the event manually.
I would use the onFlush method like you mentioned. (If you are writing a library, you are better off with the custom event)
You can use UnitOfWork to get the change sets.
public function onFlush(OnFlushEventArgs $event)
{
$em = $event->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityInsertions() as $entity) {
$this->newEntities[] = $entity;
if ($entity instanceof User) {
$changeSet = $uow->getEntityChangeSet($entity);
// if the $changeSet contains the status, log the change
$log = new Log();
$em->persist($log);
$uow->computeChangeSet($em->getClassMetadata(Log::class), $log);
}
}
foreach ($uow->getScheduledEntityUpdates() as $entity) {
// same here, create a private method to avoid duplication
}
}
The trade off of this listener is it will only log things on flush.
If your entity changes state multiple times before flush, only the last state will be logged. Eg state1 -> 2 -> 3 will only be logged as state1 -> 3
If you plan on creating a complex status field with many states and transitions have a look at the workflow component and use the listeners from there. It is a bit more work, but well worth it.
So what I did following the advice of both Dimitris and Padam67.
Define a DoctrineListener that listens on the onFlush event and register it
Dispatch a custom event in the DoctrineListener
Define an EventSubscriber listening on my custom event
Define a handler to manage the logic
Call the handler from the EventSubscriber
I know it makes a lot of files, but I like to separate everything as much as possible for a cleaner and simpler code to read :)
Define a DoctrineListener that listens on the onFlush event:
config/services.yaml
App\EventListener\Doctrine\DoctrineListener:
tags:
- { name: doctrine.event_listener, event: onFlush }
App\EventListener\Doctrine\DoctrineListener.php
<?php
namespace App\EventListener\Doctrine;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\UnitOfWork;
use App\Event\TalentStatusChangedEvent;
use App\Entity\Talent;
use App\Event\Constants\TalentEvents;
class DoctrineListener
{
private $logger;
private $dispatcher;
public function __construct(
LoggerInterface $logger,
EventDispatcherInterface $dispatcher
) {
$this->logger = $logger;
$this->dispatcher = $dispatcher;
}
public function onFlush(OnFlushEventArgs $event)
{
$entityManager = $event->getEntityManager();
$uow = $entityManager->getUnitOfWork();
foreach ($uow->getScheduledEntityInsertions() as $entity) {
if ($entity instanceof Talent) {
$this->createTalentStatusChangedEvent($entity, $uow);
}
}
foreach ($uow->getScheduledEntityUpdates() as $entity) {
if ($entity instanceof Talent) {
$this->createTalentStatusChangedEvent($entity, $uow);
}
}
}
private function createTalentStatusChangedEvent(Talent $entity, UnitOfWork $uow)
{
$this->logger->info(self::class . ' - talentStatusChanged: ' . $entity . ' - start');
$changeSet = $uow->getEntityChangeSet($entity);
if (array_key_exists('status', $changeSet)) {
$talentStatusChangedEvent = new TalentStatusChangedEvent($entity, new \DateTime());
$this->dispatcher->dispatch(TalentEvents::STATUS_CHANGED, $talentStatusChangedEvent);
$this->logger->info(self::class . ' - talentStatusChanged: ' . $entity . ' - success');
} else {
$this->logger->info(self::class . ' - talentStatusChanged: ' . $entity . ' - fail');
}
}
}
Define a TalentStatusChangedEvent
App\Event\TalentStatusChangedEvent.php
<?php
namespace App\Event;
use App\Entity\Talent;
use Symfony\Component\EventDispatcher\Event;
class TalentStatusChangedEvent extends Event
{
private $talent;
private $statusChangedDate;
public function __construct(Talent $talent, \DateTime $date)
{
$this->talent = $talent;
$this->statusChangedDate = $date;
}
public function getTalent()
{
return $this->talent;
}
public function getStatus()
{
return $this->talent->getStatus();
}
public function getStatusChangedDate()
{
return $this->statusChangedDate;
}
}
Define an EventSubscriber for my event (defined a separate file containing all my events per type)
App\EventListener\Admin\User\TalentSubscriber.php
<?php
namespace App\EventListener\Admin\User;
use App\Domain\User\StatusChanged\StatusChangedHandler;
use App\Entity\Talent;
use App\Event\Constants\TalentEvents;
use App\Event\TalentStatusChangedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class TalentSubscriber implements EventSubscriberInterface
{
private $statusChangedHandler;
public function __construct(
StatusChangedHandler $statusChangedHandler
) {
$this->statusChangedHandler = $statusChangedHandler;
}
public static function getSubscribedEvents()
{
return array(
TalentEvents::STATUS_CHANGED => 'statusChanged',
);
}
public function statusChanged(TalentStatusChangedEvent $event) {
$this->statusChangedHandler->handle($event);
}
}
Define a handler to actually manage the creation of the linked entity
App\Domain\User\StatusChanged.php
<?php
namespace App\Domain\User\StatusChanged;
use App\Entity\Talent;
use App\Entity\TalentStatusEvent;
use App\Event\TalentStatusChangedEvent;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
class StatusChangedHandler
{
private $entityManager;
private $logger;
public function __construct(
EntityManagerInterface $entityManager,
LoggerInterface $logger
) {
$this->entityManager = $entityManager;
$this->logger = $logger;
}
public function handle(TalentStatusChangedEvent $event)
{
$this->logger->info(self::class . ' - Talent ' . $event->getTalent() . ' - start');
$talentStatusEvent = new TalentStatusEvent();
$talentStatusEvent->setTalent($event->getTalent());
$talentStatusEvent->setStatus($event->getStatus());
$this->entityManager->persist($talentStatusEvent);
// Calling ComputeChangeSet and not flush because we are during the onFlush cycle
$this->entityManager->getUnitOfWork()->computeChangeSet(
$this->entityManager->getClassMetadata(TalentStatusEvent::class),
$talentStatusEvent
);
$this->logger->info(self::class . ' - Talent ' . $event->getTalent() . ' - success');
}
}
From the examples I'm finding in the Symfony docs, it looks like the typical thing to do when needing to save data is something like in the controller class:
public function createAction(){
$product = new Product();
$product->setName('Amy Keyboard');
$product->setPrice(24.99);
$product->setDescription('Ergonomic and stylish!');
$em = $this->getDoctrine()->getManager();
$em->persist($product);
$em->flush();
return $this->render('index.html.twig');
}
It would be really great to not have to type those 3 $em lines in every single controller method! And it would be even sweeter to move all of this logic to a class somewhere else and then just call $product->saveProduct($data)! What is the best option here?
I usually create a manager class e.g. ProductManager and register it as service. I inject the EntityManager via setter injection and implement all the methods I need.
In your case this would look similar to this:
AppBundle/Product/ProductManager
namespace AppBundle\Product;
use Doctrine\ORM\EntityManager;
class ProductManager {
/** #var EntityManager */
private $entityManager;
public function setEntityManager (EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function getAll()
{
return $this->entityManager->createQuery('SELECT p FROM '.Product::class.' p')
->getResult();
}
public function add(Product $product, $flush = true)
{
$this->entityManager->persist($product);
if ( $flush ) {
$this->entityManager->flush($product);
}
}
public function byId($id)
{
// Fetch a product by id (note: No need to use DQL or the EntityRepository here either!)
return $this->entityManager->find(Product::class, $id);
}
}
app/config/services.yml
services:
app.product_manager:
class: AppBundle\Product\ProductManager
calls:
- [setEntityManager, ['#doctrine.orm.entity_manager']]
Controller
public function createAction(){
$product = new Product();
$product->setName('Amy Keyboard');
$product->setPrice(24.99);
$product->setDescription('Ergonomic and stylish!');
// add the product
$this->get('app.product_manager')->add($product);
return $this->render('index.html.twig');
}
Take a look at the Propel project if you want something like $product->save() but it is a totally different approach. This is the official bundle https://github.com/propelorm/PropelBundle/blob/3.0/README.markdown
For clarity I continue here the discussion started here.
Inside a Doctrine Entity Listener, in the preUpdate method (where I have access to both the old and new value of any field of the entity) I'm trying to persist an entity unrelated to the focal one.
Basically I have entity A, and when I change a value in one of the fields I want to write, in the project_notification table, the fields oldValue, newValue plus others.
If I don't flush inside the preUpdate method, the new notification entity does not get stored in DB. If I flush it I enter into a infinite loop.
This is the preUpdate method:
public function preUpdate(ProjectTolerances $tolerances, PreUpdateEventArgs $event)
{
if ($event->hasChangedField('riskToleranceFlag')) {
$project = $tolerances->getProject();
$em = $event->getEntityManager();
$notification = new ProjectNotification();
$notification->setValueFrom($event->getOldValue('riskToleranceFlag'));
$notification->setValueTo($event->getNewValue('riskToleranceFlag'));
$notification->setEntity('Entity'); //TODO substitute with the real one
$notification->setField('riskToleranceFlag');
$notification->setProject($project);
$em->persist($notification);
// $em->flush(); // gives infinite loop
}
}
Googling a bit I discovered that you cannot call the flush inside the listeners, and here it's suggested to store the stuff to be persisted in an array, to flush it later in the onFlush. Nonetheless it does not work (and probably it should not work, as the instance of the listener class gets destroyed after you call the preUpdate, so whatever you store in as protected attribute at the level of the class gets lost when you later call the onFlush, or am I missing something?).
Here is the updated version of the listener:
class ProjectTolerancesListener
{
protected $toBePersisted = [];
public function preUpdate(ProjectTolerances $tolerances, PreUpdateEventArgs $event)
{
$uow = $event->getEntityManager()->getUnitOfWork();
// $hasChanged = false;
if ($event->hasChangedField('riskToleranceFlag')) {
$project = $tolerances->getProject();
$notification = new ProjectNotification();
$notification->setValueFrom($event->getOldValue('riskToleranceFlag'));
$notification->setValueTo($event->getNewValue('riskToleranceFlag'));
$notification->setEntity('Entity'); //TODO substitute with the real one
$notification->setField('riskToleranceFlag');
$notification->setProject($project);
if(!empty($this->toBePersisted))
{
array_push($toBePersisted, $notification);
}
else
{
$toBePersisted[0] = $notification;
}
}
}
public function postFlush(LifecycleEventArgs $event)
{
if(!empty($this->toBePersisted)) {
$em = $event->getEntityManager();
foreach ($this->toBePersisted as $element) {
$em->persist($element);
}
$this->toBePersisted = [];
$em->flush();
}
}
}
Maybe I can solve this by firing an event from inside the listener with all the needed info to perform my logging operations after the flush...but:
1) I don't know if I can do it
2) It seems a bit an overkill
Thank you!
I give all the credits to Richard for pointing me into the right direction, so I'm accepting his answer. Nevertheless I also publish my answer with the complete code for future visitors.
class ProjectEntitySubscriber implements EventSubscriber
{
public function getSubscribedEvents()
{
return array(
'onFlush',
);
}
public function onFlush(OnFlushEventArgs $args)
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityUpdates() as $keyEntity => $entity) {
if ($entity instanceof ProjectTolerances) {
foreach ($uow->getEntityChangeSet($entity) as $keyField => $field) {
$notification = new ProjectNotification();
// place here all the setters
$em->persist($notification);
$classMetadata = $em->getClassMetadata('AppBundle\Entity\ProjectNotification');
$uow->computeChangeSet($classMetadata, $notification);
}
}
}
}
}
Don't use preUpdate, use onFlush - this allows you to access the UnitOfWork API & you can then persist entities.
E.g. (this is how I do it in 2.3, might be changed in newer versions)
$this->getEntityManager()->persist($entity);
$metaData = $this->getEntityManager()->getClassMetadata($className);
$this->getUnitOfWork()->computeChangeSet($metaData, $entity);
As David Baucum stated, the initial question referred to Doctrine Entity Listeners, but as a solution, the op ended up using an Event Listener.
I am sure many more will stumble upon this topic, because of the infinite loop problem.
For those that adopt the accepted answer, TAKE NOTE that the onFlush event (when using an Event Listener like above) is executed with ALL the entities that might be in queue for an update, whereas an Entity Listener is used only when doing something with the entity it was "assigned" to.
I setup a custom auditing system with symfony 4.4 and API Platform, and i managed to achieve the desired result with just an Entity Listener.
NOTE: Tested and working however, the namespaces and functions have been modified, and this is purely to demonstrate how to manipulate another entity inside a Doctrine Entity Listener.
// this goes into the main entity
/**
* #ORM\EntityListeners({"App\Doctrine\MyEntityListener"})
*/
<?
// App\Doctrine\MyEntityListener.php
namespace App\Doctrine;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\Security;
// whenever an Employee record is inserted/updated
// log changes to EmployeeAudit
use App\Entity\Employee;
use App\Entity\EmployeeAudit;
private $security;
private $currentUser;
private $em;
private $audit;
public function __construct(Security $security, EntityManagerInterface $em) {
$this->security = $security;
$this->currentUser = $security->getUser();
$this->em = $em;
}
// HANDLING NEW RECORDS
/**
* since prePersist is called only when inserting a new record, the only purpose of this method
* is to mark our object as a new entry
* this method might not be necessary, but for some reason, if we set something like
* $this->isNewEntry = true, the postPersist handler will not pick up on that
* might be just me doing something wrong
*
* #param Employee $obj
* #ORM\PrePersist()
*/
public function prePersist(Employee $obj){
if(!($obj instanceof Employee)){
return;
}
$isNewEntry = !$obj->getId();
$obj->markAsNewEntry($isNewEntry);// custom Employee method (just sets an internal var to true or false, which can later be retrieved)
}
/**
* #param Employee $obj
* #ORM\PostPersist()
*/
public function postPersist(Employee $obj){
// in this case, we can flush our EmployeeAudit object safely
$this->prepareAuditEntry($obj);
}
// END OF NEW RECORDS HANDLING
// HANDLING UPDATES
/**
* #see {https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/events.html}
* #param Employee $obj
* #param PreUpdateEventArgs $args
* #ORM\PreUpdate()
*/
public function preUpdate(Employee $obj, PreUpdateEventArgs $args){
$entity = $args->getEntity();
$changeset = $args->getEntityChangeSet();
// we just prepare our EmployeeAudit obj but don't flush anything
$this->audit = $this->prepareAuditEntry($obj, $changeset, $flush = false);
}
/**
* #ORM\PostUpdate()
*/
public function postUpdate(){
// if the preUpdate handler was called, $this->audit should exist
// NOTE: the preUpdate handler DOES NOT get called, if nothing changed
if($this->audit){
$this->em->persist($this->audit);
$this->em->flush();
}
// don't forget to unset this
$this->audit = null;
}
// END OF HANDLING UPDATES
// AUDITOR
private function prepareAuditEntry(Employee $obj, $changeset = [], $flush = true){
if(!($obj instanceof Employee) || !$obj->getId()){
// at this point, we need a DB id
return;
}
$audit = new EmployeeAudit();
// this part was cut out, since it is custom
// here you would set things to your EmployeeAudit object
// either get them from $obj, compare with the changeset, etc...
// setting some custom fields
// in case it is a new insert, the changedAt datetime will be identical to the createdAt datetime
$changedAt = $obj->isNewInsert() ? $obj->getCreatedAt() : new \DateTime('#'.strtotime('now'));
$changedFields = array_keys($changeset);
$changedCount = count($changedFields);
$changedBy = $this->currentUser->getId();
$entryId = $obj->getId();
$audit->setEntryId($entryId);
$audit->setChangedFields($changedFields);
$audit->setChangedCount($changedCount);
$audit->setChangedBy($changedBy);
$audit->setChangedAt($changedAt);
if(!$flush){
return $audit;
}
else{
$this->em->persist($audit);
$this->em->flush();
}
}
The idea is to NOT persist/flush anything inside preUpdate (except prepare your data, because you have access to the changeset and stuff), and do it postUpdate in case of updates, or postPersist in case of new inserts.
Theres a little hack I came across today. Maybe it helps to future generations.
So basicly, in onFlush Listener I cant store anything (because of deadlock or something similar If I call flush in another repository) and in postFlush i dont have access to change sets.
So I registered it as Subscriber with both events (onFlush, postFlush) implemented and just have class variable private array $entityUpdateBuffer = []; where I temp store Entities scheduled to update from onFlush event.
class MyEntityEventSubscriber implements EventSubscriber
{
private array $entityUpdateBuffer = [];
public function __construct(private MyBusiness $myBusiness)
{
}
public function getSubscribedEvents(): array
{
return [
Events::onFlush,
Events::postFlush,
];
}
public function onFlush(OnFlushEventArgs $args): void
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
$this->entityUpdateBuffer = $uow->getScheduledEntityUpdates();
}
public function postFlush(PostFlushEventArgs $args): void
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($this->entityUpdateBuffer as $entity) {
if (!$entity instanceof MyEntity) {
continue;
}
$changeSet = $uow->getEntityChangeSet($entity);
// Call whatever that uses $entity->getId() as reference
$this->myBusiness->createChangeRecordWithEntityId(
$entity->getId(),
$changeSet,
)
}
}
}
Using an Lifecycle Listener instead of an EntityListener might be better suited in this case (I find that the symfony docs provide a better overview over the different options). This is due to onFlush, a very powerful event, not being available for EntityListeners. This event is invoked after all changesets are computed and before the database actions are executed.
In this answer I explore the options using an Entity Listener.
Using preUpdate: This event provides a PreUpdateEventArgs which makes it easy to find all values that are going to be changed. However this event is triggered within UnitOfWork#commit, after the inserts have been processed. Hence there is now no possibility to add a new entity to be persisted within current transaction.
Using preFlush: This event occurs at the beginning of a flush operation. Changesets might not yet be available, but we can compare the original values with the current ones. This approach might not be suitable when there are many changes that are needed. Here is an example implementation:
public function preFlush(Order $order, PreFlushEventArgs $eventArgs)
{
// Create a log entry when the state was changed
$entityManager = $eventArgs->getEntityManager();
$unitOfWork = $entityManager->getUnitOfWork();
$originalEntityData = $unitOfWork->getOriginalEntityData($order);
$newState = $order->getState();
if (empty($originalEntityData)) {
// We're dealing with a new order
$oldState = "";
} else {
$stateProperty = 'state';
$oldState = $originalEntityData[$stateProperty];
// Same behavior as in \Doctrine\ORM\UnitOfWork:720: Existing
// changeset is ignored when the property was changed
$entityChangeSet = $unitOfWork->getEntityChangeSet($order);
$stateChanges = $entityChangeSet[$stateProperty] ?? [];
if ($oldState == $newState && $stateChanges) {
$oldState = $stateChanges[0] ?? "";
$newState = $stateChanges[1] ?? "";
}
}
if ($oldState != $newState) {
$statusLog = $this->createOrderStatusLog($order, $oldState, $newState);
$unitOfWork->scheduleForInsert($statusLog);
$unitOfWork->computeChangeSet($entityManager->getClassMetadata('App\Entity\OrderStatusLog'), $statusLog);
}
}
Using postFlush/postUpdate: Using these events would lead to a second database transaction, which is undesirable.
I'm using Symfony for make a web site and I installed sonata admin Bundle.
I have an entity with a boolean variable (enable).
I would like when this variable change state to True the other one for the same table go to False. In fact I would like only one variable (enable) for the same table is at True.
So I thought to change setEnable directly in my Entity but I can't get the repository from my Entity class.
How can I get my repository from my Entity Class ?
You should use lifecycleCallbacks to do this (with preUpdate), check doc for a good configuration.
And do something like this :
public function preUpload()
{
if ($this->getVariable1())
$this->setVariable2(false);
}
I think you might change prospective.
If this behavior is a logic of your application it's better to do a doctrine subscriber. So when you persist or update your entity it's will be checked.
Take a look at http://symfony.com/doc/current/cookbook/doctrine/event_listeners_subscribers.html
In your specific case this will be done with this simple code:
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
class ObjectSubscriber implements EventSubscriber
{
public function __construct($objectManager)
{
}
public function getSubscribedEvents()
{
return array(
'postPersist',
'postUpdate',
);
}
public function postUpdate(LifecycleEventArgs $args)
{
$this->manage($args);
}
public function postPersist(LifecycleEventArgs $args)
{
$this->manage($args);
}
public function manage(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof YourEntity) {
if ($entity->getEnable()) {
$em = $args->getEntityManager();
$entities = $em->getRepository('YourEntityRepository')->findByEnable(true);
foreach ($entities as $e) {
if ($e->getId() == $entity->getId()) {
continue;
}
$e->setEnable(false);
}
$em->flush();
}
}
}
}
I'm running the equivalent of this code in lots and lots of controller actions, basically it grabs the user's username, and if that username is attached to a blog entity it will allow the user to see the blog entity(s):
$em = $this->getDoctrine()->getManager();
$user = $this->get('security.context')->getToken()->getUser();
$entities = $em->getRepository('MySiteBundle:Blog')->findBy(array('user' => $user));
return $this->render('MySiteBundle:Blog:index.html.twig', array(
'entities' => $entities,
I want to move it into a service so I can cut down on code repetition. I want to avoid doing as much logic in my controllers as possible.
That being said, I'm not sure how I can access the user session and doctrine in a service.
Here's my services.yml:
mysite.user.blog:
class: MySite\SiteBundle\Services\BlogUser
And here's how I was attempting to call it in the controller:
public function testAction() {
$response = $this->get('mysite.user.blog');
return new Response($response);
}
I did try using an event subscriber/listener tag, but that doesn't seem to accomplish the task I want.
Here is my completely horrible attempt at a service. I couldn't get any response from it without using a constructor.
namespace MySite\SiteBundle\Services;
use MySite\SiteBundle\Entity\Blog;
class BlogUser {
protected $entities;
public function __construct(){
$em = $this->getDoctrine()->getManager();
$user = $this->get('security.context')->getToken()->getUser();
$this->entities = $em->getRepository('MySiteBundle:Blog')->findBy(array('user' => $user));
}
}
Am I going about this the completely wrong way? Is there a better way that I'm missing?
EDIT/ANSWER:
modified my naming convention a little:
//services.yml
mysite.user.blog.entities:
class: Mysite\SiteBundle\Services\BlogUser
arguments: ["#doctrine.orm.entity_manager", "#security.context"]
In the controller action:
$userEntities = $this->get('mysite.user.blog.entities');
$entities = $userEntities->getEntities();
In the service itself:
class BlogUser {
protected $entities;
public function __construct($em, $securityContext){
$user = $securityContext->getToken()->getUser();
$this->entities = $em->getRepository('MySiteBundle:Blog')->findBy(array('user' => $user));
}
public function getEntities(){
return $this->entities;
}
}
Still needs two lines to get the $entities variable in the controller, but this is way better than defining the same thing over and over.
"Security.context" has been deprecated since Symfony 2.6
After some community discussions, it was decided that SecurityContext gives too many dependencies to retrieve a simple Token/User object. That's why, starting with Symfony 2.6, thesecurity.context service has been deprecated and split into two new services:security.authorization_checker and security.token_storage.
Source
Thus, the new way to do it would be, first configure your service as:
mysite.user.blog:
class: MySite\SiteBundle\Services\BlogUser
arguments: ["#doctrine.orm.entity_manager", "#security.token_storage"]
Then in the service class constructor:
class BlogUser
{
protected $user;
protected $entities;
public function __construct(EntityManager $em, TokenStorage $tokenStorage)
{
$this->user = $tokenStorage->getToken()->getUser();
$this->entities = $em->getRepository('MySiteBundle:Blog')->findBy(array('user' => $user));
}
}
Yes, you are doing it in wrong way. Let's look at your code:
# call to undefined object method getDoctrine()
$em = $this->getDoctrine()->getManager();
# call to undefined object method get()
$user = $this->get('security.context')->getToken()->getUser();
You cannot call getting entitymanager and security.context in your service in the same way like in your controller. Instead, you have to inject entitymanager and security.context services. Example:
# services.yml
mysite.user.blog:
class: MySite\SiteBundle\Services\BlogUser
calls:
- [ setUserFromSecurityContext, [ #security.context ]]
- [ setEntityManager, [ #doctrine.orm.entity_manager ]]
And improved service:
namespace Catablog\SiteBundle\Services;
use MySite\SiteBundle\Entity\Blog;
class BlogUser {
private $entityManager;
private $user;
public function setEntityManager(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function setUserFromSecurityContext(SecurityContext $securityContext)
{
# notice, there are a cases when `getToken()` returns null, so improve this
$this->user = $securityContext->getToken()->getUser();
}
public function getEntities(){
# your code here
}
}
More info about Dependency injection
You are looking on how to 'inject' other services into your custom service. Take a look at Service Container documentation.
In your case, you can inject doctrine.orm.entity_manager and security.context services into your BlogUser class via constructor injection. For example:
class BlogUser {
public function __construct($em, $securityContext) {
$user = $securityContext->getToken()->getUser();
$this->entities = $em->getRepository('MySiteBundle:Blog')->findBy(array('user' => $user));
}
}
And configure your service as the following:
mysite.user.blog:
class: MySite\SiteBundle\Services\BlogUser
arguments: ["#doctrine.orm.entity_manager", "#security.context"]