I have been using the SymfonyCast on Symfony 4 to try and help me to connect the pieces. I have learned a lot about doctrine Symfony and twig and the environment that they are supposed to work together in. However, that is not what I have to build on. I need to build inside an existing project OpenEMR.
I posted my project code here:
https://github.com/juggernautsei/symfony_twig_doctrine_component/blob/master/library/financialreports/src/FinancialSummaryByInsuranceController.php
The controller works to load the twig template. Now, I am trying to populate the controller with data from the database. I have built the entity class and the repository class. I just can't figure out what to put in this line.
public function getpaidata($insurerid)
{
$payments = $this->repository
}
To access the class in the repository. The IDE suggested the code in the repository class.
public function getInsurerPaid(ArSession $insurerId)
{
/*$insurerPaid = $this->_em->getRepository($this->_entityName)->findBy([
"payer_id" => $insurerId
]);*/
$insurerPaid = $this->findBy([
'payer_id' => $insurerId
]);
return $insurerPaid;
}
But as I am typing in out the code in the controller, the IDE PHPStorm is not suggesting anything. So, I am stuck. I have tried the suggested code here
https://symfony.com/doc/2.0/book/doctrine.html#creating-an-entity-class
https://symfonycasts.com/screencast/symfony-doctrine/repository
but nothing tells me how to access the method that is in the repository class.
UPDATE:
The getpaiddata() method is now changed to
/**
* #return Fully hydrated object.
*/
public function getpaidata($insurerid)
{
$row = $this->repository->findBy([
"payer_id" => $insurerid
]);
return $row;
}
The problem is likely how you get $this->repository. If you fetch it via the entity manager, via $this->_em->getRepository($entityName) like in the commented snippet, the return value has a type hint which tells the IDE that it is just a generic EntityRepository instead of your custom repository class.
You can install the Symfony-plugin to PhpStorm, which will give you better autocompletion if your entity has the right annotation #Entity(repositoryClass="...").
In a typical Symfony 4 application you could also just inject the correct repository, instead of the EntityManager, e.g. in your constructor:
public function __construct(PaidDataRepository $repository)
{
$this->repository = $repository;
}
From what it looks like, OpenEMR has it's own way of creating the EntityManager using Connector::instance(). So this will probably not work for you easily, unfortunately.
Another way around this would be to just place a type hint above assigning your variable:
/** #var App\Repository\PaidDataRepository $repository */
$repository = $this->_em->getRepository(PaidData::class)
or, since you have a class variable you can put a similar annotation on there.
I'm trying to implement an instance counter on a php/symfony3 project, but I don't find how.
I have an "Advert" entity, and I want to show how many instances exist on a Twig view.
So, in the entity class, I created a static attribute private static $nbAdverts = 0; with a getter/setter, and two static methods:
/**
* #ORM\PrePersist
*/
public static function increaseAdverts()
{
self::$nbAdverts++;
}
and
/**
* #ORM\PreRemove
*/
public static function decreaseAdverts()
{
self::$nbAdverts--;
}
I'm calling these methods using the Doctrine events: PrePersist and PreRemove.
These adverts are created in the controller using the Advert repository before being persisted and flushed. Also called using this repository before being removed.
The counter remains stuck at 0 when I add or remove an advert.
I think I'm doing something wrong: is it possible to do this without rewriting the repository add method? If it is, any idea about how?
The behavior you are getting is absolutely normal. You are running a web application, any variable not stored in the session, is set to it default value if exists, at every request. So to achieve what you want to do you can save your static variable in session and update it in the increaseAdverts() and decreaseAdverts() Methods.
Is there a way to force a doctrine event ( like preUpdate ) on a parent associated entity ?
So for example: I have a order entity with one-to-many orderItem entities.
Now, I want to do a bunch of checkup's and possible changes to the order entity or even one of it's orderItem entities ( where I need to access many other services) whenever any of the orderItems change. But the doctrine events do not fire on the order entity when one of its orderItem entities changes.
Note: this post entirely focuses on the particular case of the preUpdate event. It is possible to dispatch an event manually by using the event manager. The problem lies in the fact that simply triggering the preUpdate event of an entity is not enough to have its new state persisted to the database if the preUpdate method modified something.
There are multiple ways to do this but none of them are really straightforward. Considering only the case of the preUpdate event, I had quite a lot of trouble to find how to do this in a clean way as association updates are simply not built in a way to handle such cases as discussed in the Doctrine documentation.
Either way, if you want to do this, among the solutions I found, there were many that suggested to directly mess up with the UnitOfWork of Doctrine. This can be quite powerful but then you have to be careful about what you use and when you use it as Doctrine might not be able to actually dispatch the event you want in some cases discussed below.
Anyway, I ended up implementing something that makes use of a change of tracking policy for the parent entity. By doing so, the parent entity preUpdate event can be triggered if one of its properties is modified or if one of its "children" was modified.
Main concerns with the UnitOfWork
If you wish to use the UnitOfWork (that you can retrieve by using $args->getEntityManager()->getUnitOfWork() with any type of arguments of lifecycle events), you can use the public method scheduleForUpdate(object $entity). However, if you wish to use this method, you will need to call it before the commit order is computed inside of the unit of work. Moreover, if you have a preUpdate event associated to the entity you scheduled for update, it will raise an error if your entity has an empty change set (which is exactly the case we are dealing with when the main entity is not modified but one of its related entities is).
Thus calling $unitOfWork->scheduleForUpdate($myParentEntity), in a preUpdate of a child entity is not an option as explained in the documentation (performing calls to the UnitOfWork API is strongly discouraged as it does not work as it would outside of the flush operation).
It should be noted that $unitOfWork->scheduleExtraUpdate($parentEntity, array $changeset) can be used in that specific context but this method is marked as "INTERNAL". The following solutions avoid using it but it might be a good approach if you know what you are getting into.
Possible solutions
Note: I did not test the implementation of the wanted behaviour with the onFlush event but it was often presented as the most powerful approach. For the other two possibilities listed here, I tried them successfully with a OneToMany association.
In the following section, when I'm talking about a parent entity, I refer to the entity that has the OneToMany association while children entities are refering to the entities that have the ManyToOne association (thus, the children entities are the owning side of the association).
1. Using onFlush event
You can try to work your way out of this by using the onFlush event however, in that case you have to deal with the UnitOfWork internals as suggested in the documentation. In that case, you can't do it within an Entity listener (introduced in 2.4) as the onFlush event is not among the possible callbacks. Some examples based on what's given by the official doc can be found on the web. Here is a possible implementation: Update associated entities in doctrine.
The main drawback here is that you don't really trigger the preUpdate event of your entity, you just handle the behaviour you wanted somewhere else. It seemed a bit too heavy handed for me, so I searched for other solutions.
2. Using the UnitOfWork in preFlush event of the child entities
One way to actually trigger the preUpdate event of the parent entity, is to add another entity listener to the child entity and to use the UnitOfWork. As explained before, you can't simply do this in the preUpdate event of the child entity.
In order for the commit order to be properly computed, we need to call scheduleForUpdate and propertyChanged in the preFlush event of the child entity listener as shown below:
class ChildListener
{
public function preFlush(Child $child, PreFlushEventArgs $args)
{
$uow = $args->getEntityManager()->getUnitOfWork();
// Add an entry to the change set of the parent so that the PreUpdateEventArgs can be constructed without errors
$uow->propertyChanged($child->getParent(), 'children', 0, 1);
// Schedule for update the parent entity so that the preUpdate event can be triggered
$uow->scheduleForUpdate($child->getParent());
}
}
As you can see, we need to notify the UnitOfWork that a property has changed so that everything works properly. It looks a bit sloppy but it gets the work done.
The important part is that we mark the children property (the OneToMany association of the parent) as changed so that the change set of the parent entity is not empty. A few important notes about the internals at stake with this propertyChanged call:
The method expects a persistent field name (non-persistent ones will be ignored), any mapped field will do, even associations, that is why using children works here.
The change set that is modified consecutively to this call does not have any side effects here as it will be recomputed after the preUpdate event.
The main problem of this approach is that the parent entity is scheduled for update even if it is not needed. As there is no direct way to tell if the child entity has changed in its preFlush event (you could use the UnitOfWork but it would become a bit redundant with its internals), you will trigger the preUpdate event of the parent at every flush where a child entity is being managed.
Moreover, with this solution, Doctrine will begin a transaction and commit even if there are no queries performed (e.g. if nothing was modified at all, you will still find in the Symfony Profiler, two consecutives entries "START TRANSACTION" and "COMMIT" in the Doctrine logs).
3. Change the tracking policy of the parent and handle the behaviour explicitly
Since I've been messing with the internals of the UnitOfWork quite a bit, I stumbled upon the propertyChanged method (that was used in the previous solution) and noticed that it was part of the interface PropertyChangedListener. It happens that this is linked to a documented topic: the tracking policy. By default, you can just let Doctrine detect the changes but you can also change this policy and manage everything manually as explained here, in the documentation.
After reading about this, I eventually came up with the following solution that cleanly handles the wanted behaviour, the cost being that you have to do some extra work in your entities.
Thus, to have exactly what I desired, my parent entity follows the NOTIFY tracking policy and children notify the parent when one of their properties is modified. As described in the official documentation, you have to implement the NotifyPropertyChanged interface and then notify the listeners of properties changes (the UnitOfWork automatically adds itself to the listeners if it detects that one of the managed entities implements the interface). After that, if the annotation #ChangeTrackingPolicy is added, at commit times, Doctrine will rely on the change set that was built via propertyChanged calls and not on an automatic detection.
Here is how you would do it for a basic Parent entity:
namespace AppBundle\Entity;
use Doctrine\Common\NotifyPropertyChanged;
use Doctrine\Common\PropertyChangedListener;
/**
* ... other annotations ...
* #ORM\EntityListeners({"AppBundle\Listener\ParentListener"})
* #ORM\ChangeTrackingPolicy("NOTIFY")
*/
class Parent implements NotifyPropertyChanged
{
// Add the implementation satisfying the NotifyPropertyChanged interface
use \AppBundle\Doctrine\Traits\NotifyPropertyChangedTrait;
/* ... other properties ... */
/**
* #ORM\Column(name="basic_property", type="string")
*/
private $basicProperty;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Child", mappedBy="parent", cascade={"persist", "remove"})
*/
private $children;
/**
* #ORM\Column(name="other_field", type="string")
*/
private $otherField;
public function __construct()
{
$this->children = new \Doctrine\Common\Collections\ArrayCollection();
}
public function notifyChildChanged()
{
$this->onPropertyChanged('children', 0, 1);
}
public function setBasicProperty($value)
{
if($this->basicProperty != $value)
{
$this->onPropertyChanged('basicProperty', $this->basicProperty, $value);
$this->basicProperty = $value;
}
}
public function addChild(Child $child)
{
$this->notifyChildChanged();
$this->children[] = $child;
$child->setParent($this);
return $this;
}
public function removeChild(Child $child)
{
$this->notifyChildChanged();
$this->children->removeElement($child);
}
/* ... other methods ... */
}
with the trait taken from the code given in the documentation:
namespace AppBundle\Doctrine\Traits;
use Doctrine\Common\PropertyChangedListener;
trait NotifyPropertyChangedTrait
{
private $listeners = [];
public function addPropertyChangedListener(PropertyChangedListener $listener)
{
$this->listeners[] = $listener;
}
/** Notifies listeners of a change. */
private function onPropertyChanged($propName, $oldValue, $newValue)
{
if ($this->listeners)
{
foreach ($this->listeners as $listener)
{
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
}
}
}
}
and the following Child entity with the owning side of the association:
namespace AppBundle\Entity;
class Child
{
/* .. other properties .. */
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Parent", inversedBy="children")
*/
private $parentEntity;
/**
* #ORM\Column(name="attribute", type="string")
*/
private $attribute;
public function setAttribute($attribute)
{
// Check if the parentEntity is not null to handle the case where the child entity is created before being attached to its parent
if($this->attribute != $attribute && $this->parentEntity)
{
$this->parentEntity->notifyChildChanged();
$this->attribute = $attribute;
}
}
/* ... other methods ... */
}
And there it is, you have everything fully working. If, your child entity is modified, you explicitly call notifyChildChanged that will then notify the UnitOfWork that children field has changed for the parent entity thus cleanly triggering the update process and the preUpdate event if one is specified.
Unlike the solution #2, the event will be triggered only if something has changed and you can control with precision why it should be marked as changed. For example, you could mark the children as changed if only a certain set of attributes is changed and ignore other changes as you have full control other what is eventually notified to the UnitOfWork.
Note:
With the NOTIFY tracking policy, apparently, preFlush events won't be triggered in the Parent entity listener (preFlush event being triggered in computeChangeSet which is simply not called for entities using this policy).
It is necessary to track every "normal" property to trigger updates if normal properties are changed. One solution to do this without having to modify all your setters is to use magic calls as shown below.
It is safe to set a children entry in the change set as it will be simply ignored when the update query is created since the parent entity is NOT the owning side of the association. (i.e. it does not have any foreign keys)
Use of magic calls to handle notifications easily
In my application, I added the following trait
namespace AppBundle\Utils\Traits;
trait MagicSettersTrait
{
/** Returns an array with the names of properties for which magic setters can be used */
abstract protected function getMagicSetters();
/** Override if needed in the class using this trait to perform actions before set operations */
private function _preSetCallback($property, $newValue) {}
/** Override if needed in the class using this trait to perform actions after set operations */
private function _postSetCallback($property, $newValue) {}
/** Returns true if the method name starts by "set" */
private function isSetterMethodCall($name)
{
return substr($name, 0, 3) == 'set';
}
/** Can be overriden by the class using this trait to allow other magic calls */
public function __call($name, array $args)
{
$this->handleSetterMethodCall($name, $args);
}
/**
* #param string $name Name of the method being called
* #param array $args Arguments passed to the method
* #throws BadMethodCallException if the setter is not handled or if the number of arguments is not 1
*/
private function handleSetterMethodCall($name, array $args)
{
$property = lcfirst(substr($name, 3));
if(!$this->isSetterMethodCall($name) || !in_array($property, $this->getMagicSetters()))
{
throw new \BadMethodCallException('Undefined method ' . $name . ' for class ' . get_class($this));
}
if(count($args) != 1)
{
throw new \BadMethodCallException('Method ' . $name . ' expects 1 argument (' . count($args) . ' given)');;
}
$this->_preSetCallback($property, $args[0]);
$this->$property = $args[0];
$this->_postSetCallback($property, $args[0]);
}
}
which I could then use in my entities. Here is an example of my Tag entity whose preUpdate event needed to be called when one of its aliases was modified:
/**
* #ORM\Table(name="tag")
* #ORM\EntityListeners({"AppBundle\Listener\Tag\TagListener"})
* #ORM\ChangeTrackingPolicy("NOTIFY")
*/
class Tag implements NotifyPropertyChanged
{
use \AppBundle\Doctrine\Traits\NotifyPropertyChangedTrait;
use \AppBundle\Utils\Traits\MagicSettersTrait;
/* ... attributes ... */
protected function getMagicSetters() { return ['slug', 'reviewed', 'translations']; }
/** Called before the actuel set operation in the magic setters */
public function _preSetCallback($property, $newValue)
{
if($this->$property != $newValue)
{
$this->onPropertyChanged($property, $this->$property, $newValue);
}
}
public function notifyAliasChanged()
{
$this->onPropertyChanged('aliases', 0, 1);
}
/* ... methods ... */
public function addAlias(\AppBundle\Entity\Tag\TagAlias $alias)
{
$this->notifyAliasChanged();
$this->aliases[] = $alias;
$alias->setTag($this);
return $this;
}
public function removeAlias(\AppBundle\Entity\Tag\TagAlias $alias)
{
$this->notifyAliasChanged();
$this->aliases->removeElement($alias);
}
}
I can then reuse the same trait in my "child" entity named TagAlias:
class TagAlias
{
use \AppBundle\Utils\Traits\MagicSettersTrait;
/* ... attributes ... */
public function getMagicSetters() { return ['alias', 'main', 'locale']; }
/** Called before the actuel set operation in the magic setters */
protected function _preSetCallback($property, $newValue)
{
if($this->$property != $newValue && $this->tag)
{
$this->tag->notifyAliasChanged();
}
}
/* ... methods ... */
}
Note: If you chose to do this, you might encounter errors when Forms are trying to hydrate your entities as magic calls are disabled by default. Simply add the following to your services.yml to enable magic calls. (taken from this discussion)
property_accessor:
class: %property_accessor.class%
arguments: [true]
A more pragmatic approach is to version your parent entity. A simple example of this would be a timestamp (e.g. updated_at) that is updated when the collection of child entities is modified. This assumes you update all the child entities through its parent.
My user has countTasks property, with corresponding setter and getter:
class User implements UserInterface, \Serializable
{
/**
* #var integer
*/
private $countTasks;
}
I want this property to be always shown in the application's navigation bar (the "14" number in red):
Obviously, this property should be set for every controller. (Actually, only for every that deals with rendering the navigation bar, but that's not the case here). So the application should count tasks for the currently logged-in user for every controller.
I found a relevant topic in the Symfony cookbook: How to Setup before and after Filters, and I managed to implement it:
Acme\TestBundle\EventListener\UserListener.php:
namespace Acme\TestBundle\EventListener;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class UserListener
{
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if ( ! is_array($controller)) {
return;
}
$securityContext = $controller[0]->get('security.context');
// now count tasks, but only if a user logged-in
if ($securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED') or $securityContext->isGranted('IS_AUTHENTICATED_FULLY'))
{
$user = $securityContext->getToken()->getUser();
// ...
// countig tasks and setting $countTasks var
// ...
$user->setCountTasks($countTasks);
}
}
}
services.yml:
services:
acme.user.before_controller:
class: Acme\TestBundle\EventListener\UserListener
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
It works as expected and I'm able to pull the property in a Twig template like this:
{{ app.user.countTasks }}
It works as expected in prod env.
In dev however, profiler throws UndefinedMethodException:
UndefinedMethodException: Attempted to call method "get" on class "Symfony\Bundle\WebProfilerBundle\Controller\ProfilerController" in ...\src\Acme\TestBundle\EventListener\UserListener.php line 18.
where line 18 is this one:
$securityContext = $controller[0]->get('security.context');
As a quick patch I added additional check (before line 18) to prevent profiler from executing the further logic:
if (is_a($controller[0], '\Symfony\Bundle\WebProfilerBundle\Controller\ProfilerController'))
{
return;
}
$securityContext = $controller[0]->get('security.context');
and it has made the trick. But I'm afraid it's not the right way. I'm also afraid that I'm loosing some part of debug information in profiler.
Am I right with my concerns? Can you point me to a better way to prevent profiler from executing this listener? In config somehow?
Even in Symfony's documentation How to Setup before and after Filters, an instanceof condition is being evaluated in line 29.
I'd go about saying that if the doc's doing it, you're pretty safe doing it yourself (unless stated otherwise, which is not the case).
In the beginning I was trying to fix the issue conversely than I should. During testing it turned out that I have to exclude some other core controllers too. Of course, rather than block unwanted controllers I should have allowed the required ones only.
I ended up creating an empty interface UserTasksAwareController:
namespace Acme\TestBundle\Controller;
interface UserTasksAwareController
{}
fixing that validity check in UserListener.php:
if ( ! $controllers[0] instanceof UserTasksAwareController) {
return;
}
and implementing it in every other controller which deals with displaying countTasks property, like this one:
class UserController extends Controller implements UserTasksAwareController
So, the problem I had was just another one when you forget to program to an interface, not an implementation.
I'm making a website where users can create albums.
I would like to create a default album per user.
I would like to create an Album entity and to persist it in the constructor of my User class.
Is it possible ?
I just know that the entityManager is not accessible from an Entity... That's why it's a problem for me.
Even though this technically IS possible I would strongly recommend you not to do this.
To answer your question, it is possible and it would be done like this:
class User extends FOSUser
{
/**
* #ORM\OneToMany(targetEntity="Album", cascade={"persist"})
*/
private $albums;
public function __construct()
{
$this->albums = new ArrayCollection();
$this->addAlbum(new Album());
}
public function addAlbum(Album $album)
{
$this->albums[] = $album;
}
public function getAlbums()
{
return $this->albums:
}
}
With setup like this whenever you create a new user and save it, a related album will be created together with it. I have to repeat, even though it's possible, don't do it like this.
Good solutions
There are few strategies that can be used to achieve what you want.
FOSUserBundle master
If you're not using 1.3.x version of FOSUserBundle but master, you can see that RegistrationController fires a few events. The one you're interested in is FOSUserEvents::REGISTRATION_INITIALIZE. You should create an event listener and add album to user in your listener.
FOSUserBundle 1.3.x
If you're using one of older versions, these events don't exist unfortunately and you can do it two ways.
Extend FOSUserBundle UserManager and override createUser method. You can add your album adding logic there. I would prefer this approach.
Override FOSUserBundle RegistrationController::registerAction. It can be viable option sometimes but in your case I think option 1 is better.