I've a Symfony 4 project with User entity and SoldeConges Entity.
An user has a SoldeConges collection.
But when I dump the $user->getSoldeConges(), the collection is empty.
My User entity :
/**
* #ORM\OneToMany(targetEntity="App\Entity\SoldeConges", mappedBy="user", orphanRemoval=true)
*/
private $soldeConges;
/**
* #return Collection|SoldeConges[]
*/
public function getSoldeConges(): Collection
{
return $this->soldeConges;
}
And my user has 3 soldeConges :
PhpMyAdmin SoldeConge table :
And when I make a dump in my controller for my User (which is the user number 1) :
$soldeConges = $this->getUser()->getSoldeConges();
dump($soldeConges);
I've :
So, why can not access to my User SoldeConges collection ?
1)To get your soldeConges (this is symfony 3 code, adapt it to 4 ;-) ):
$em = $this->getDoctrine()->getManager();
$soldeCongesRepository= $em->getRepository('AppSoldeConges:SoldeConges');
$soldeConges = $soldeCongeRepository->findBy(['userId'=>$this->getUser()->getId()]);
2)It may be due to Doctrine lazy loading.
Try fetch="EAGER" (it's LAZY by default):
* #ORM\OneToMany(targetEntity="App\Entity\SoldeConges", mappedBy="user", orphanRemoval=true, fetch="EAGER")
Doctrine loads the whole collection in once IF you try to access it. A dump is the memory model at the moment where you place your dump() statement.
If you should render the collection first (or even only if you use the count() method on the collection) and then use the dump() statement you will see that your collection has been loaded. This is the system called lazy loading. It will execute a second query when needed. But as you may know if two queries could get one query then it should be better and faster.
On the other hand if you have entities with large collections this could get a serious problem. In that case you could use "extra lazy loading". (See the docs)
Anyway if you want to get your collections loaded immediately with your entities then you could use your own DQL query that have one or more JOINS. Below an example of your Repository with a new function called findAllWithJoin. Call that function from your controller instead of findAll().
namespace App\Repository;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;
class UserRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
public function findAllWithJoin()
{
$entityManager = $this->getEntityManager();
$query = $entityManager->createQuery('SELECT u, sc FROM User u JOIN u.soldeConges sc');
return $query->execute();
}
}
Related
I've just started working with Doctrine and built a simple blog project. One of my requirements is that a blog post should not be visible to anybody (for simpleness, skip an editor's interface) until the publish date is reached.
As far as I see, it's obvious to do so using a custom repository. Let's extend the find method the following way:
public function find($id, $lockMode = null, $lockVersion = null)
{
/** #var Post $post */
$post = parent::find($id, $lockMode, $lockVersion);
if($post->getCreatedAt() > new \DateTime()) {
return null;
}
return $post;
}
This restricts the access for a page showing a single Post entity. For an overview page, the same can be done using a custom method:
public function findForOverview()
{
$query = $this->createQueryBuilder('p')
->where('p.createdAt < CURRENT_TIMESTAMP()')
->orderBy('p.createdAt', 'DESC')
->getQuery();
return $query->getResult();
}
So, even for this simple requirement, I've already written two custom methods. If I continue to work on my project, other restriction limitations might occur and additional ways to load that entity might arise. And as far as I see, for each case I have to implement the logic for all access guards.
Is there no simpler way to do that? I'm thinking of something like an annotation or an "entity load listener" that makes it simple to write one single entry point for all such checks - making it impossible to forget such checks...
Such restrictions are usually implemented by using mechanism of SQL filters in Doctrine. Implementation of this filter works on lower level then DQL and allows you to apply modifications for SQL query being constructed. In your case it may look like this:
namespace App\ORM\Filter;
use App\Entity\Post;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Filter\SQLFilter;
class PostVisibilityFilter extends SQLFilter
{
/**
* Gets the SQL query part to add to a query.
*
* #param ClassMetadata $targetEntity
* #param string $targetTableAlias
* #return string The constraint SQL if there is available, empty string otherwise
*/
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
{
if ($targetEntity->name !== Post::class) {
return '';
}
return sprintf('%s.%s >= now()', $targetTableAlias, $targetEntity->getColumnName('createdAt'));
}
}
I am creating a small app using Symfony 4 & Doctrine. There are users (User entities) and they are owning some kind of content called radio tables (RadioTable entity). Radio tables are containing radio stations (RadioStation entity). RadioStation.radioTableId is related to RadioTable (many to one) and RadioTable.ownerId is related to User (many to one).
Maybe I should notice that this is my first project with SF.
Entities are configured using annotations, this way:
<?php
namespace App\Entity;
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
*/
class User implements UserInterface, \Serializable, EncoderAwareInterface
{
/**
* #ORM\OneToMany(targetEntity="App\Entity\RadioTable", mappedBy="owner", orphanRemoval=true)
*/
private $radioTables;
/**
* #ORM\Column(type="date")
*/
private $lastActivityDate;
}
// -----------------
namespace App\Entity;
/**
* #ORM\Entity(repositoryClass="App\Repository\RadioTableRepository")
* #ORM\EntityListeners({"App\EventListener\RadioTableListener"})
*/
class RadioTable
{
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="radioTables")
* #ORM\JoinColumn(nullable=false, onDelete="cascade")
*/
private $owner;
/**
* #ORM\Column(type="datetime")
*/
private $lastUpdateTime;
}
// -----------------
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\RadioStationRepository")
* #ORM\EntityListeners({"App\EventListener\RadioStationListener"})
*/
class RadioStation
{
/**
* #ORM\ManyToOne(targetEntity="App\Entity\RadioTable")
* #ORM\JoinColumn(nullable=false, onDelete="cascade")
*/
private $radioTable;
}
I need to update $lastUpdateTime in a proper RadioTable entity when radio stations are added, removed or modified. Also, I need to update $lastActivityDate of the radio table owner (User class), when radio table is created, removed or updated. I am trying to achieve this by using entity listeners:
<?php
namespace App\EventListener;
class RadioStationListener
{
/**
* #PreFlush
* #PreRemove
*/
public function refreshLastUpdateTimeOfRadioTable(RadioStation $radioStation)
{
$radioStation->getRadioTable()->refreshLastUpdateTime();
}
}
// -----------------------------
namespace App\EventListener;
class RadioTableListener
{
/**
* #PreFlush
* #PreRemove
*/
public function refreshLastActivityDateOfUser(RadioTable $radioTable, PreFlushEventArgs $args)
{
$radioTable->getOwner()->refreshLastActivityDate();
/* hack */
$args->getEntityManager()->flush($radioTable->getOwner());
/* hack */
}
}
(In refresh*() methods I am just creating a new instance of \DateTime for proper entity field.)
I encountered the problem. When I tried to update/remove/create radio stations, RadioStation listener worked properly and related RadioTable class was successfully updated. But when I tried to update radio table, User class was updated but was not persisted to the database by Doctrine.
I was confused because the structure of the code in these entity listeners is very similar.
Partially I found the cause of the problem. It's obvious that only owner can modify its own radio tables and the user has to be logged in to modify them. I am using Security component from Symfony to support login-in mechanism.
When I temporarily hacked controller code to disable Security and tried to update the radio table as anonymous, RadioTable entity listener worked properly and User entity was successfully modified and persisted to database.
To fix the problem I need to manually talk with Doctrine's entity manager and call flush() with User entity as an argument (without argument I am doing endless loop). This line is marked by /* hack */ comment.
After this looong story, I want to ask the question: WHY I have to do it? WHY I have to manually call flush() for User object but only if Security component is used and the user is logged in?
I solved the problem.
Doctrine processes entities in a specified order. First, newly created entities (scheduled for INSERT) have precedence. Next, persisted entities (scheduled for UPDATE) are processed in the same order as they were fetched from the database. From inside entity listener, I am not able to predict or enforce the preferred order.
When I'm trying to update User's last activity date inside RadioTable's entity listener, changes made in User entity are not persisted. It's because in very early stage Security component loads my User object from DB and then Symfony prepares RadioTable object for the controller (by param converter for example).
To fix the issue I need to tell Doctrine to recalculate User entity changeset. Here is what I did.
I created small trait for my entity listeners:
<?php
namespace App\EventListener\EntityListener;
use Doctrine\Common\EventArgs;
trait EntityListenerTrait
{
// There is need to manually enforce update of associated entities,
// for example when User entity is modified inside RadioTable entity event.
// It's because associations are not tracked consistently inside Doctrine's events.
private function forceEntityUpdate(object $entity, EventArgs $args): void
{
$entityManager = $args->getEntityManager();
$entityManager->getUnitOfWork()->recomputeSingleEntityChangeSet(
$entityManager->getClassMetadata(get_class($entity)),
$entity
);
}
}
Inside entity listeners I am doing this:
<?php
namespace App\EventListener\EntityListener;
use App\Entity\RadioTable;
use Doctrine\Common\EventArgs;
use Doctrine\ORM\Mapping\PreFlush;
use Doctrine\ORM\Mapping\PreRemove;
class RadioTableListener
{
use EntityListenerTrait;
/**
* #PreFlush
* #PreRemove
*/
public function refreshLastActivityDateOfUser(RadioTable $radioTable, EventArgs $args): void
{
$user = $radioTable->getOwner();
$user->refreshLastActivityDate();
$this->forceEntityUpdate($user, $args);
}
}
There is another solution. It's possible to call $entityManager->flush($user) but it works properly only for UPDATEs, generates endless loop for INSERTs. To avoid endless loop it's possible to check $unitOfWork->isScheduledForInsert($radioTable).
This solution is worse because it generates additional transaction and SQL queries.
I would like to retrieve a record from another entity (or record from the DB) within a entity.
They there are no relationship between the two entities.
I am using #ORM\HasLifecycleCallbacks() and #ORM\PrePersist so when the main entity is created it will also create another entity (save a record to another table)
The above is working fine, there are no issues with this.
What I am having an issue with is I would like to link that entity with another table but I need to retrieve the object based on the value of the first entity.
Usually I would write a function in the entity repository but I am not calling the entity manager within the entity.
An Entity in Doctrine is an object representation of a concept, with attributes and methods. It is meant to be lightweight, a POPO (plain old php object). It must not know anything about its persistence. Therefore if you see reference to the EntityManager in a model, it probably stinks.
Solutions? You could use an entity listener called on entity creation and then use a service dedicated only to properly compose your object(s), maybe something like a Factory. In this way, your entity stays lightweight, the lifecycle management is satisfied and the entity composing is responsibility only of your service.
Entity manager is accessible in an entity repository. You can legally use it to fetch data from other entities and to compose your business logic. This is what entity repositories are made for: Doctrine Custom Repositories, Symfony Custom Repository Classes.
/**
* #ORM\Entity
*/
class Beta {}
/**
* #ORM\Entity
*/
class Alpha {}
class AlphaRepository extends EntityRepository
{
public function getDataFromAnotherEntity($something)
{
$query = 'select * from MyBundle\Entity\Alpha alpha where alpha.id = :something';
return $this->getEntityManager()
->createQuery($query)
->setParameter('something', $something)
->getResult();
}
}
In Symfony 3.1 you can use the entityManager to set a reference. This is still lightweight as it does not instance a complete Doctrine Record.
Example: I have an entity Status which has some states, and it's referenced in another entity. On create i use this method inside EventSubscriber:
public function preAction(LifecycleEventArgs $args)
{
$entity = $args->getObject();
$entityManager = $args->getObjectManager();
if (method_exists($entity, 'setStatus')) {
if ($entity->getStatus() === null) {
$entity->setStatus($entityManager->getReference('AppBundle\Entity\Status', Status::STATUS_REGULAR));
}
}
}
I am creating some Fixtures in Symfony2 using Doctrine. I get the following error:
Integrity constraint violation: 1062 Duplicate entry '206-411' for key 'PRIMARY'
when I try to persist a many-to-many unidirectional association.
I understand the error, but I'm confused: isn't obvious that some IDs are duplicate in a many-to-many relationship?
If I'm wrong please correct me. I put my code below, any clarification is welcome.
Fixture file:
namespace sociaLecomps\SuperBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\Query;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class LoadAssociationsData extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface
{
private $container;
public function setContainer(ContainerInterface $container = null){
$this->container = $container;
}
public function load(ObjectManager $manager)
{
$em = $this->container->get('doctrine')->getManager('default');
/*
* COURSE - STUDENT ASSOCIATION
*/
$courses = $em->createQuery('SELECT c FROM sociaLecompsSuperBundle:Course c')->getResult();
$students = $em->createQuery('SELECT s FROM sociaLecompsSuperBundle:Student s')->getResult();
$i=0;
for($j=0; $j<count($courses); $j++){
$course = $courses[$j];
//here I'm adding two different students to the same course
$s = array($students[$i], $students[$i++]);
$course->setSubscribedStudents($s);
$em->persist($course);
$i++;
}
$manager->flush();
}
}
Relationship declaration in Course class:
/**
* #ORM\ManyToMany(targetEntity="Student")
* #ORM\JoinTable(name="relation_course_student",
* joinColumns={#ORM\JoinColumn(name="course_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="student_id", referencedColumnName="id")}
* )
**/
private $subscribed_students;
public function __construct() {
$this->subscribed_students = new ArrayCollection();
}
Entities Student and Course are created, also with Fixtures, before attempting to create the association.
If I try to insert only one student per course it all works smoothly.
I see that your courses entities already exist because you're fetching them directly from the database ($courses = $em->createQuery('SELECT c FROM sociaLecompsSuperBundle:Course c')->getResult();). So you shouldn't be trying to persist the entity a second time. I suggest you use merge() this way:
$em->merge($course);
Note 1:
I see you're using Doctrine fixtures here and that students and courses have already been created. If they have been created through a Doctrine fixture as well consider using the addReference and getReference methods. Example here: https://github.com/doctrine/data-fixtures/blob/master/README.md#sharing-objects-between-fixtures
Note 2: Also you don't have a cascade option set into your subscribed_students association. Since the students already exist should not be an issue. Otherwise you can either set the cascade option or run a merge|persist on the student entity as well.
That was the stupidest thing.
I replaced:
$s = array($students[$i], $students[$i++]);
with
$s = array($students[$i], $students[++$i]);
Since it was a post-increment, the second insertion tried to put the same student into the database, resulting with an exact row duplicate.
Hope this helps someone.
I have 2 entities in a one-to-one association. The first, Person, is stored in a MySQL database and handled by Doctrine. The second, AdUserRecord, describes an ActiveDirectory user record. It is read-only. It does not need to know about Person. Also, AdUserRecord properties should never be stored in the MySQL db for privacy reasons.
An AdUserRecord is retrieved using a service, AdSearcher, which can search by samaccountname or objectGUID. Whenever a search is successful, the service checks to see if there is a corresponding Person record and creates one if there is not. That works fine.
My problem occurs when I start with a Person object. Mostly, I don't need to access a Person's AdUserRecord so I'd prefer not to query Active Directory unless it's required. That means, I think, that Person::getAdrecord() needs to have access to the AdSearcher service. Something like this:
public function getAdrecord(){
if($this->adrecord) return $this->adrecord;
$searcher = ???; //get AdSearcher service somehow
$record = $search->getRecordByUserGuid($this->ad_guid);
if(!$record) throw new \Exception('this person no longer exists');
$this->adrecord = $record;
return $this->adrecord;
}
I've been reading the Symfony docs pretty assiduously, but I'm still stumped.
Questions
how do I get a service into an entity? Should it be injected via the constructor, or just where it's needed, in the getter? If it only occurs in the getter, do I have to inject it or is there a way to import it?
is adding a service to an entity the canonical way of handling these types of situations? Would it be preferable to build an entity manager for AdUserRecords?
what interfaces do I need to implement if I have to build an entity manager?
Person class
namespace ACRD\DefaultBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use ACRD\DefaultBundle\Entity\AdUserRecord;
/**
* #ORM\Entity
* #Orm\Table(name="person")
*
*/
class Person {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="AD_guid", type="string", length=36, unique=true)
*/
protected $ad_guid;
/**
* #var AdUserRecord
*/
protected $adrecord;
//usual getters and setters
}
It looks like Doctrine's postLoad event is the best solution.
// src/Acme/DemoBundle/EventListener/ActiveDirectorySubscriber.php
namespace Acme\DemoBundle\EventListener;
use Acme\DemoBundle\Model\AdAwareInterface;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
// for doctrine 2.4: Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Symfony\Component\DependencyInjection\ContainerAware
class ActiveDirectorySubscriber extends ContainerAware implements EventSubscriber
{
public function getSubscribedEvents()
{
return array(
'postLoad',
);
}
public function postLoad(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if (!($entity instanceof AdAwareInterface)) {
return:
}
$adSearcher = $this->getContainer()->get('acme_demo.ad_searcher');
if ($adPerson = $adSearcher->find($entity->getAdGuid())) {
$entity->setAdPerson($adPerson);
}
}
}
You also mentioned that most of the time you don't need to use the active directory stuff. Before optimizing I highly suggest you actually measure how much of a performance impact there is. If, however, you do notice a performance problem, consider using a proxy object to mitigate the AdPerson searching right to the point where you actually need something from it.
public function postLoad(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if (!($entity instanceof AdAwareInterface)) {
return:
}
$adSearcher = $this->getContainer()->get('acme_demo.ad_searcher');
$entity->setAdPerson(new AdPersonProxy($adSearcher));
}
The AdPersonProxy would basically extend from your AdPerson class, wrap each and every public method with a call to load the actual AdPerson object and then act as a facade between the two. Consider the following implications before you start coding though:
it adds complexity to your codebase (the more code, the more there is to maintain);
it will be a pain to debug - for example you might get an exception inside your
template that will leave you scratching your head for a long time (been there,
done that);
The bottom line is that in theory services should (mostly) not be injected inside entities.
Regarding your third question:
EntityManagers implement Doctrine/Common/Persistence/ObjectManager - have a look at the interface on github.
Further:
a somewhat clean implementation would be similar to the Document<->Entity mapping (called references) provided by gedmo/doctrine-extensions.
Take a glimpse at the documentation to see how it works here and here.
If that's what you want start diving into the code of the ReferenceListener :)