Symfony join entities in one-to-many relationship - symfony

I am working on a Symfony project recording sales as relating to stock.
My reasoning:
one sale item is associated to one stock item
one stock item can be associated to multiple sale items
As a result, I setup a one-to-many sale-to-stock relationship as show in the following code snippets:
class Sale
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var float
*
* #ORM\Column(name="cost", type="float")
*/
private $cost;
/**
* #var \DateTime
*
* #ORM\Column(name="date", type="datetime")
*/
private $date;
/**
* #ORM\ManyToOne(targetEntity="iCerge\Salesdeck\StockBundle\Entity\Stock", inversedBy="sales")
* #ORM\JoinColumn(name="sid", referencedColumnName="id")
*/
protected $stock;
...
... and ...
class Stock
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #var \DateTime
*
* #ORM\Column(name="created", type="datetime")
*/
private $created;
/**
* #var \DateTime
*
* #ORM\Column(name="updated", type="datetime")
*/
private $updated;
/**
* #ORM\OneToMany(targetEntity="iCerge\Salesdeck\SalesBundle\Entity\Sale", mappedBy="stock")
*/
protected $sales;
...
NOW, if my code for implementing a one-to-many relationship is correct, I am trying to load a sale object with it's associated stock data in one query using the following code:
public function fetchSale($sid){
$query = $this->createQueryBuilder('s')
->leftjoin('s.stock', 't')
->where('s.id = :sid')
->setParameter('sid', $sid)
->getQuery();
return $query->getSingleResult();
}
The fetchSale function is from my projects SaleRepository.php class.
The leftjoin part of the query is what I hoped would successfully fetch the related stock information but I just get no output ([stock:protected]) as is shown below:
myprog\SalesProj\SalesBundle\Entity\Sale Object
(
[id:myprog\SalesProj\SalesBundle\Entity\Sale:private] => 50
[cost:myprog\SalesProj\SalesBundle\Entity\Sale:private] => 4.99
[date:myprog\SalesProj\SalesBundle\Entity\Sale:private] => DateTime Object
(
[date] => 2015-04-18 17:12:00
[timezone_type] => 3
[timezone] => UTC
)
[stock:protected] =>
[count:myprog\SalesProj\SalesBundle\Entity\Sale:private] => 5
)
How I can successfully fetch a sales' related stock data in the same query?

Doctrine is lazy-loading by default, so it's possible that $stock hasn't been initialized and the dump is not showing it. Try dumping $sale->getStock(). That tells Doctrine to go fetch it.
You can also force the loading of the Stock by selecting it:
public function fetchSale($sid){
$query = $this->createQueryBuilder('s')
->leftjoin('s.stock', 't')
->where('s.id = :sid')
->setParameter('sid', $sid)
->select('s', 't')
->getQuery();
return $query->getSingleResult();
}
By the way, fetchSale($sid) as it is now is the same as calling:
$entityManager->getRepository('SalesBundle:Sale')->find($sid);

You can use Doctrine's eager loading feature.
1. Always load associated object:
If you always want to fetch the stock object when loading the sale object, you can update your entity definition (see docs for #ManyToOne) by adding a fetch="EAGER" to the #ManyToOne definition:
/**
* #ORM\ManyToOne(targetEntity="iCerge\Salesdeck\StockBundle\Entity\Stock", inversedBy="sales", fetch="EAGER")
* #ORM\JoinColumn(name="sid", referencedColumnName="id")
*/
protected $stock;
Doctrine will then take care of loading all required objects in as few queries as possible.
2. Sometimes load associated object:
If you want to load the associated object only in some queries and not by default, according to the manual you can also tell Doctrine to use eager loading on a specific query. In your case, this may look like:
public function fetchSale($sid){
$query = $this->createQueryBuilder('s')
->where('s.id = :sid')
->setParameter('sid', $sid)
->getQuery();
$query->setFetchMode("FQCN\Sale", "stock", \Doctrine\ORM\Mapping\ClassMetadata::FETCH_EAGER);
return $query->getSingleResult();
}

Related

With ManyToOne relation entities, how may i access a foreign key id?

I'm trying to access to a foreign key stored in an entity using doctrine and querybuilder.
I got an entity named User which is linked to another entity called Client with a ManyToOne relationship.
I wanted to build a querybuilder that get me the field client_id in user table, that match the id of a client.
My User Entity :
/**
* AppBundle\EntityAppBundle\Entity\User
*
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
*/
class User extends FOSUser
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(name="nom", type="string", length=255)
*
*
*/
private $nom;
/**
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Etablissement", inversedBy="users")
*
*/
private $etablissements;
/**
*
* #ORM\ManyToOne(targetEntity="Client", inversedBy="users")
*
*/
private $client;
My Client Entity :
/**
* AppBundle\EntityAppBundle\Entity\Client
*
* #ORM\Table(name="client")
* #ORM\Entity()
*/
class Client{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="nom", type="string", length=255)
*
*
*/
private $nom;
/**
* #var string
*
* #ORM\Column(name="adresse", type="string", length=255)
*
*
*/
/**
*
* #ORM\OneToMany(targetEntity="AppBundle\Entity\User", mappedBy="client",
cascade={"persist"}, orphanRemoval=true)
*
*/
private $users;
In my database, my entity user has the client_id in foreign key column.
So in my queryBuilder in UserRepository, I do :
public function findClientIdViaUserId($myUserId, $myClientID)
{
return $this->createQueryBuilder('e')
->from('AppBundle:User', 'i')
->join('AppBundle:Client', 'c')
->where('c.id = :myClientID')
->andWhere('e.id = :myUserId')
->setParameter('myuserId', $myUserId)
->setParameter('myClientId', $myClientID)
->getQuery()
->getOneOrNullResult();
}
I expect to get the id of the client_id for a user_id.
Let's say that i wanted to get one client_id with the user_id called 1.
With my queryBuilder i got an error like :
[Syntax Error] line 0, col 67: Error: Expected Doctrine\ORM\Query\Lexer::T_WITH, got ','
How may I process to get the client_id from the user_id ?
Thank you for your replies !
Why don't you use EntityManager ?
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('AppBundle:User')->find(YOURUSERID);
$client = $user->getClient();
You made a mistake in chaining of Doctrine methods. You've already set an alias for User entity by calling createQueryBuilder('e'). Repository knows about entity it linked to.
When you call from('AppBundle:User', 'i') - an alias for User entity is i now. That's why Doctrine is throwing an error about wrong syntax in resulting DQL.
So, try this piece of code:
return $this
->createQueryBuilder('e')
->join('e.client', 'c')
->where('c.id = :myClientID')
->andWhere('e.id = :myUserId')
->setParameter('myUserId', $myUserId)
->setParameter('myClientId', $myClientID)
->getQuery()
->getOneOrNullResult();

Notice: Trying to get property of non-object in vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php line 481

I have a really strange case related to doctrine, loggable (DoctrineExtension) and listeners.
I will explain the situation I am having here and below is all the code I think is related to the problem.
I have two entities (Agreement and Template) where an agreement is based on a specific Template version. Template entity has the DoctrineExtension Loggable annotation. So I can revert an Agreement template to the specific version using the LogEntryRepository->revert() method. (I am using a postLoad listener to do that, so each time an agreement is retrieved, the right Template version is loaded for that Agreement).
If I get a controller action where an agreement is retrieved using a ParamConververter annotation, everything works ok and my agreement is retrieved with the right Template.
If I try to retrieve the very same agreement in the first line of the controller action using a query builder, I get the following exception
Notice: Trying to get property of non-object in /home/administrator{dir}/vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php line 481
Any help would be appreciated.
Thanks.
Just copying the parts that are related to the problem:
Entities
/**
* Agreement
*
* #ORM\Table(name="agreement")
* #ORM\Entity
* #Gedmo\Loggable
*/
class Agreement
{
/**
* #var integer
* #ORM\Column(name="id", type="bigint", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var integer
* #ORM\Column(name="template_version", type="bigint", nullable=false)
* #Gedmo\Versioned
*/
private $templateVersion;
/**
* #var \Template
* #ORM\ManyToOne(targetEntity="Template")
* #ORM\JoinColumn(name="template_id", referencedColumnName="id")
*/
private $template;
}
/*
* Template
*
* #ORM\Table(name="template")
* #ORM\Entity
* #ORM\ChangeTrackingPolicy("DEFERRED_EXPLICIT")
* #Gedmo\Loggable
*/
class Template
{
/**
* #var integer
* #ORM\Column(name="id", type="bigint", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
* #ORM\Column(name="name", type="string", length=255, nullable=false)
* #Gedmo\Versioned
*/
private $name;
}
Doctrine Subscriber
*(services.yml)*
services:
ourdeal.listener.loggable:
class: App\Bundle\Listener\LoggableSubscriber
tags:
- { name: doctrine.event_subscriber }
class LoggableSubscriber implements EventSubscriber
{
public function getSubscribedEvents()
{
return array(
'prePersist',
'postLoad',
);
}
public function prePersist(LifecycleEventArgs $args)
*...Code omitted...*
public function postLoad(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
if ($entity instanceof Agreement)
{
$agreement = $entity;
$repo = $entityManager->getRepository('Gedmo\Loggable\Entity\LogEntry');
$repo->revert($agreement->getTemplate(), $agreement->getTemplateVersion());
}
}
}
Actions
With this action, I get the desired agreement without problems.
/**
* #Route("/agreement/send/{id}", name="agreement/send")
* #ParamConverter("agreement", class="Bundle:Agreement")
* #Template()
*/
public function sendAction(Request $request, Agreement $agreement) {
*...Code omitted...*
}
Using this code, I get the exception (the hardcoded id and this code is just for test)
/**
* #Route("/agreement/send", name="agreement/send")
* #Template()
*/
public function sendAction(Request $request) {
$em = $this->get('doctrine')->getManager();
$qb = $em->createQueryBuilder()->select('a')->from('AppBundle:Agreement', 'a')->where('a.id=1378');
$agreements = $qb->getQuery()->getResult();
}
use setParameter()
$em->createQueryBuilder()
->select('a')
->from('AppBundle:Agreement', 'a')
->where('a.id = :id')
->setParameter('id', $request->get('id'));
There is a known bug #52083 that affects PHP versions before 5.3.4, which fails randomly with "Notice: Trying to get property of non-object".
If that is your case, try upgrading PHP will solve your issue. Hope that helps

Doctrine2 not update DateTime [duplicate]

This question already has answers here:
Doctrine2 ORM does not save changes to a DateTime field
(3 answers)
Closed 8 years ago.
I think I have found a bug which I can not find solution ..
I try to update the datetime field, but do not update it, don't gives me an error.
Move all other fields modifies them correctly, but the datetime field no.
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('MyOwnBundle:Events')->find($id);
$In = $entity->getDateIn();
$In->modify('+1 day');
$entity->setDateIn($In);
$em->flush();
I also tried to insert a DateTime() object directly but does not update at all!
$entity->setDateIn(new \DateTime());
Is there a solution to this problem?
I installed symfony 2.1 and doctrine 2.3.3
EDIT
Event entity:
/**
* Events
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="My\OwnBundle\Entity\EventsRepository")
*/
class Events
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=100)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="description", type="text")
*/
private $description;
/**
* #var \DateTime
*
* #ORM\Column(name="dateIn", type="datetime")
*/
private $dateIn;
/**
* #var \DateTime
*
* #ORM\Column(name="dateOut", type="datetime")
*/
private $dateOut;
....
/**
* Set dateIn
*
* #param \DateTime $dateIn
* #return Events
*/
public function setDateIn($dateIn)
{
$this->dateIn = $dateIn;
return $this;
}
/**
* Get dateIn
*
* #return \DateTime
*/
public function getDateIn()
{
return $this->dateIn;
}
/**
* Set dateOut
*
* #param \DateTime $dateOut
* #return Events
*/
public function setDateOut($dateOut)
{
$this->dateOut = $dateOut;
return $this;
}
/**
* Get dateOut
*
* #return \DateTime
*/
public function getDateOut()
{
return $this->dateOut;
}
....
The modify() method will not update the entity since Doctrine tracks DateTime objects by reference. You need to clone your existing DateTime object, giving it a new reference. Modify the new one and then set is as a new timestamp.
For more information, see the article in the Doctrine Documentation.
the entity is right, but you need to persist your entity with $em->persist($entity) and you don't need to set again the date because the datetime is passed by reference
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('MyOwnBundle:Events')->find($id);
$entity->getDateIn()->modify('+1 day');
$em->persist($entity);
$em->flush();

Symfony2 Doctrine2 column being set to null

I have a system that took form information detailing a project, added it to a project table and is meant to add an entry into an assigned projects table to associate user with project (point of this is allowing multiple users for each project). Anyway I got this working without foreign keys, struggled to add them but eventually got them.
Unfortunately this additional has caused this error 'SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'projectId' cannot be null' whenever something is added to the assignedProjects table.
So my question is, have I missed something in my codes?
The code to add a new row to assignedProjects:
$assignedProject = new AssignedProjects();
$assignedProject->setProjectId($project->getId());
$assignedProject->setUserId($user[0]['id']);
$em = $this->getDoctrine()->getEntityManager();
$em->persist($assignedProject);
$em->flush();
The code for the assignProjects entity:
class AssignedProjects
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer $projectId
*
* #ORM\Column(name="projectId", type="integer")
*/
private $projectId;
/**
* #ORM\ManyToOne(targetEntity="Projects", inversedBy="assignment")
* #ORM\JoinColumn(name="projectId", referencedColumnName="id")
*/
private $project;
/**
* #var integer $UserId
*
* #ORM\Column(name="userId", type="integer")
*/
private $userId;
/**
* #ORM\ManyToOne(targetEntity="Dev\UserBundle\Entity\User", inversedBy="assignment")
* #ORM\JoinColumn(name="userId", referencedColumnName="id")
*/
private $user;
(followed by the usual getters and setters)
and the project tables entity is:
class Projects
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $projectName
*
* #ORM\Column(name="projectName", type="string", length=255)
*/
private $projectName;
/**
* #ORM\OneToMany(targetEntity="AssignedProjects", mappedBy="project")
*/
protected $assignment;
Any help would be much appreciated!
Either you use the ProjectId and UserId columns and manage the relationship manually (not recommended) or you use the doctrine relationships(recommended), but don´t do both things. If you go for the second option, don´t include the projectId and userId columns, they are automatically created for you by doctrine. So, your AssignedProjects class should be:
class AssignedProjects {
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id * #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Projects", inversedBy="assignment")
* #ORM\JoinColumn(name="projectId", referencedColumnName="id")
*/
private $project;
/**
* #ORM\ManyToOne(targetEntity="Dev\UserBundle\Entity\User", inversedBy="assignment")
* #ORM\JoinColumn(name="userId", referencedColumnName="id")
*/
private $user;
and in your controller you would do:
$assignedProject = new AssignedProjects();
$assignedProject->setProject($project);
$assignedProject->setUser($user[0]);
$em = $this->getDoctrine()->getEntityManager();
$em->persist($assignedProject);
$em->flush();
Note that I am setting the Project and User fields, not the ids
By the way, unless you need to save extra data about this project assignement (things like the date or similar), you can declare a direct ManyToMany relationship between User and Project and do away with this class, Doctrine would generate the needed table by itself
With Doctrine2, you don't have to declare the foreign key (projectId) but only the association (project). So you can delete $projectId property, as well as setProjectId ans getProjectId methods. Same fix for $user...
Instead, you will use setProject like that :
$assignedProject = new AssignedProjects();
$assignedProject->setProject($project);
$assignedProject->setUser($user[0]);
$em = $this->getDoctrine()->getEntityManager();
$em->persist($assignedProject);
$em->flush();
Have a look to Doctrine2 documentation, it will help you, for sure !
http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/association-mapping.html

Stop related entities to be queried in doctrine2 while using QueryBuilder

I am having two entities User and Profile with one to one relationship.
$qb = $this->getDoctrine()->getEntityManager()->createQueryBuilder();
$qb->add('select', 'u')
->add('from', '\Acme\TestBundle\Entity\User u')
->add('orderBy', 'u.id DESC');
$query = $qb->getQuery();
$customer = $query->execute();
When i Check the number of queries in Symfony profiler I could see n number for queries triggered on Profile table for n users in User table. Is there any way where I can stop the querying of the Profile table.
Please let me know if there is better way of implementing it.
Thanks in advance
Added Entity Classes
class User
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $email
*
* #ORM\Column(name="email", type="string", length=255)
*/
private $email;
/**
* #var Acme\TestBundle\Entity\Profile
*
* #ORM\OneToOne(targetEntity="Acme\TestBundle\Entity\Profile", mappedBy="user")
*/
private $profile;
}
class Profile
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer $user_id
*
* #ORM\Column(name="user_id", type="integer")
*/
private $user_id;
/**
* #var string $user_name
*
* #ORM\Column(name="user_name", type="string", length=100)
*/
private $user_name;
/**
* #var Acme\TestBundle\Entity\User
*
* #ORM\OneToOne(targetEntity="Acme\TestBundle\Entity\User", inversedBy="profile")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
}
Response from the mysql log
120110 15:14:29 89 Connect root#localhost on test
89 Query SET NAMES UTF8
89 Query SELECT c0_.id AS id0, c0_.email AS email1, c0_.password AS password2, c0_.is_demo_user AS is_demo_user3, c0_.status AS status4, c0_.current_service AS current_service5, c0_.registration_mode AS registration_mode6, c0_.verification_code AS verification_code7, c0_.account_type AS account_type8, c0_.activated_date AS activated_date9, c0_.status_updated_at AS status_updated_at10, c0_.created_at AS created_at11, c0_.updated_at AS updated_at12 FROM user c0_ WHERE c0_.id = 1 ORDER BY c0_.email ASC
89 Query SELECT t0.id AS id1, t0.user_id AS user_id2, t0.user_name AS user_name3, t0.age AS age4, t0.created_at AS created_at5, t0.updated_at AS updated_at6, t0.user_id AS user_id7 FROM profile t0 WHERE t0.user_id = '1'
89 Quit
Your answer is here!!
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/extra-lazy-associations.html
"In many cases associations between entities can get pretty large. Even in a simple scenario like a blog. where posts can be commented, you always have to assume that a post draws hundrets of comments. In Doctrine 2.0 if you accessed an association it would always get loaded completly into memory. This can lead to pretty serious performance problems, if your associations contain several hundrets or thousands of entities.
With Doctrine 2.1 a feature called Extra Lazy is introduced for associations. Associations are marked as Lazy by default, which means the whole collection object for an association is populated the first time its accessed. If you mark an association as extra lazy the following methods on collections can be called without triggering a full load of the collection:"
<?php
namespace Doctrine\Tests\Models\CMS;
/**
* #Entity
*/
class CmsGroup
{
/**
* #ManyToMany(targetEntity="CmsUser", mappedBy="groups", fetch="EXTRA_LAZY")
*/
public $users;
}
It's a bit late, but it might help others out there!

Resources