Slow Doctrine Find - php

I am trying to figure out why one of my doctrine finds is running so slow. I don't really know where to start, so please bear with me.
I do a pretty basic find to fetch a user object. This find is taking ~160ms. When I run the query via phpmyadmin, it takes .7ms.
$this->em->find('Entities\User', $userId)
I have already tried adding skip-name-resolve to mysql's my.cnf. The id field in the user table is indexed. I really don't know what else to try. Let me know if there is additional information I can provide.
Below is the entity file:
namespace Entities;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityRepository;
/** #Entity(repositoryClass = "Entities\UserRepository")
* #Table(name="user")
*/
class User extends \Company_Resource_AbstractEntity
{
/** #Id #Column(type="integer") #GeneratedValue */
protected $id;
/** #Column(type="string") */
protected $name;
/** #Column(type="string") */
protected $password;
/** #Column(type="string") */
protected $email;
/** #Column(type="string") */
protected $first_name;
/** #Column(type="string") */
protected $last_name;
/** #Column(type="integer") */
protected $password_reset;
/** #Column(type="string") */
protected $salt;
/** #Column(type="integer") */
protected $active;
/** #Column(type="string") */
protected $cookie_hash;
/**
* #ManyToOne(targetEntity="Company" , inversedBy="user")
*/
protected $company;
/**
* #ManyToOne(targetEntity="Privilege" , inversedBy="user")
*/
protected $privilege;
/**
* #OneToMany(targetEntity="CompanySubscription" , mappedBy="user")
*/
protected $subscription;
/**
* #OneToMany(targetEntity="EquipmentEvent" , mappedBy="check_in_user")
*/
protected $check_in;
/**
* #OneToMany(targetEntity="EquipmentEvent" , mappedBy="check_out_user")
*/
protected $check_out;
/**
* #OneToMany(targetEntity="GroupEvent" , mappedBy="check_in_user")
*/
protected $check_in_group;
/**
* #OneToMany(targetEntity="GroupEvent" , mappedBy="check_out_user")
*/
protected $check_out_group;
/**
* #OneToMany(targetEntity="Maintenance" , mappedBy="submit_user")
*/
protected $maintenance_submit;
/**
* #OneToMany(targetEntity="Maintenance" , mappedBy="completed_user")
*/
protected $maintenance_complete;
/**
* #OneToMany(targetEntity="UserLogin" , mappedBy="user")
*/
protected $login;
}
Abstract entity:
use \Doctrine\Common\Collections\ArrayCollection;
abstract class Company_Resource_AbstractEntity implements ArrayAccess
{
public function offsetExists($offset)
{
return property_exists($this, $offset);
}
// The get/set functions should check to see if an appropriately named function exists before just returning the
// property. This way classes can control how data is returned from the object more completely.
public function offsetGet($offset)
{
$property = new Zend_Filter_Word_UnderscoreToCamelCase();
$method = 'get'. $property->filter($offset);
return $this->{$method}();
}
public function offsetSet($offset, $value)
{
$property = new Zend_Filter_Word_UnderscoreToCamelCase();
$method = 'set'. $property->filter($offset);
return $this->{$method}($value);
}
public function offsetUnset($offset)
{
// can't do this
}
/*==-====-====-====-====-====-====-====-====-====-====-==*/
/*
* Provides magic method access for getFieldName() and setFieldName()
* where field_name is a simple field and not a relation
* A special getData implementation returns all of the current object vars
*/
public function __call($method, $arguments)
{
preg_match('#^([a-z]+)(.*)#', $method, $matches);
$action = $matches[1];
$property = $matches[2];
$underscore = new Zend_Filter_Word_CamelCaseToUnderscore();
$offset = strtolower($underscore->filter($property));
if ($action == 'get')
{
if ($property == 'Data')
return get_object_vars($this);
if ($this->offsetExists($offset))
return $this->{$offset};
else
throw new Zend_Exception(sprintf("'%s' does not have property '%s'", get_class($this), $offset));
}
else if ($action == 'set')
{
if ($this->offsetExists($offset))
return $this->{$offset} = $arguments[0];
else
throw new Zend_Exception(sprintf("'%s' does not have property '%s'", get_class($this), $offset));
}
else
throw new Zend_Exception(sprintf("'%s' does not have method '%s'", get_class($this), $method));
}
}
The SQL that the find produces:
SELECT t0.id AS id1,
t0.name AS name2,
t0.password AS password3,
t0.email AS email4,
t0.first_name AS first_name5,
t0.last_name AS last_name6,
t0.password_reset AS password_reset7,
t0.salt AS salt8,
t0.active AS active9,
t0.cookie_hash AS cookie_hash10,
t0.company_id AS company_id11,
t0.privilege_id AS privilege_id12
FROM user t0 WHERE t0.id = ?
Anyone see anything wrong or know where to go further with this?
Using Doctrine 2.2.2.
The explain I get when I run that query with phpmyadmin: http://i.imgur.com/wWeGO.png
The table schema: http://i.imgur.com/BQsRX.jpg

I believe the problem with my setup was the actual number of lines in the file. Doctrine was reading through those every time. I enabled APC for the meta-cache and load time decreased dramatically after the first load. Without query or result cache, that query ACTUALLY only takes about 6 MS which is what I was aiming for all along. Wish I would have tried that sooner.

Related

Building relationship entity with Neo4J PHP OGM EntityManager

I am trying to build an entity object for my relationship in Neo4j database with GraphAware Neo4j PHP OGM library using this simple method:
public function getRelationshipEntity($entityId) {
$repo = $this->entityManager->getRepository( Entity\Relationship\Fired::class );
return $repo->findOneById($entityId);
}
Here we have the entity classes, relationship first:
namespace Entity\Relationship;
use GraphAware\Neo4j\OGM\Annotations as OGM;
use Entity\Issue;
use Entity\Event;
/**
* #OGM\RelationshipEntity(type="FIRED")
*/
class Fired {
/**
* #OGM\GraphId()
*/
protected $id;
/**
* #OGM\StartNode(targetEntity="Entity\Event")
*/
protected $event;
/**
* #OGM\EndNode(targetEntity="Entity\Issue")
*/
protected $issue;
/**
* #var string
*
* #OGM\Property(type="string")
*/
protected $time;
/**
* #var string
*
* #OGM\Property(type="string")
*/
protected $eventName;
}
Then, start node:
namespace Entity;
use GraphAware\Neo4j\OGM\Annotations as OGM;
/**
* #OGM\Node(label="Event")
*/
class Event {
/**
* #OGM\GraphId()
*/
protected $id;
/**
* #var string
*
* #OGM\Property(type="string")
*/
protected $name;
}
..and end node:
namespace Entity;
use Doctrine\Common\Collections\ArrayCollection;
use GraphAware\Neo4j\OGM\Annotations as OGM;
/**
* #OGM\Node(label="Issue")
*/
class Issue {
/**
* #OGM\GraphId()
*/
protected $id;
/**
* #OGM\Property(type="string")
*/
protected $key;
/**
* #OGM\Property(type="string")
*/
protected $created;
/**
* #OGM\Property(type="string")
*/
protected $updated;
/**
* #OGM\Relationship(type="FIRED", direction="INCOMING", relationshipEntity="Entity\Relationship\Fired", collection=true)
* #var ArrayCollection
*/
protected $eventFires;
public function __construct($key) {
$this->key = $key;
$this->eventFires = new ArrayCollection();
}
public function __wakeup() {
$this->__construct($this->key);
}
/**
* #return ArrayCollection
*/
public function getEventFires() {
return $this->eventFires;
}
public function addEventFire(Entity\Relationship\Fired $eventFired) {
$this->eventFires->add($eventFired);
}
public function removeEventFire(Entity\Relationship\Fired $eventFired) {
$this->eventFires->removeElement($eventFired);
}
}
Apparently, what works really well for node entites, triggers the following error for relationships:
Fatal error: Call to undefined method GraphAware\Neo4j\OGM\Metadata\RelationshipEntityMetadata::hasCustomRepository() in /vendor/graphaware/neo4j-php-ogm/src/EntityManager.php
Any suggestion how I could workaround this? I even tried using EntityManager::createQuery() the following way:
public function getRelationships($eventName) {
$query = $this->entityManager->createQuery('MATCH (e:Event)-[f:FIRED{eventName: {eventName}}]->() RETURN e,f ORDER BY f.time');
$query->addEntityMapping('e', 'Entity\Event' );
$query->addEntityMapping('f', 'Entity\Relationship\Fired' );
$query->setParameter( 'eventName', $eventName);
return $query->execute();
}
But, apparently, addEntityMapping() doesn't work for relationship entities either! (It might be a feature though, not a bug):
Catchable fatal error: Argument 1 passed to GraphAware\Neo4j\OGM\Hydrator\EntityHydrator::hydrateNode() must implement interface GraphAware\Common\Type\Node, instance of GraphAware\Bolt\Result\Type\Relationship given, called in /vendor/graphaware/neo4j-php-ogm/src/Query.php on line 145 and defined in /vendor/graphaware/neo4j-php-ogm/src/Hydrator/EntityHydrator.php on line 232
So, I ended up that I can easily define and store relationship entities in Neo4J with this library but not sure how I could retrieve it easily with EntityManager, in the similar way I can do so with nodes.
Any help would be much appreciated!
As requested in comment below, these are GraphAware packages that I am using:
graphaware/neo4j-bolt 1.9.1 Neo4j Bolt Binary Protocol PHP Driver
graphaware/neo4j-common 3.4.0 Common Utilities library for Neo4j
graphaware/neo4j-php-client 4.8.0 Neo4j-PHP-Client is the most advanced PHP Client for Neo4j
graphaware/neo4j-php-ogm 1.0.0-RC6 PHP Object Graph Mapper for Neo4j

Variable undefined when using Doctrine QueryBuilder

I want to make alternative way for making stored procedures by using Doctrine but I am stuck, could any one help me?
Example stored procedure to be formed:
CREATE PROCEDURE catalog_get_department_details(IN DepartmentName)
BEGIN
SELECT name, description
FROM
department
WHERE name = name;
Departments Entity:
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\departmentsRepository")
* #ORM\Table(name="departments")
*/
class departments
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $department_id;
/**
* #ORM\Column(type="string")
*/
private $name;
/**
* #ORM\Column(type="string", nullable=true)
*/
private $description;
/**
* #ORM\OneToMany(targetEntity="categories",mappedBy="departments")
*/
private $categories;
function __construct()
{
$this->categories = new ArrayCollection();
}
public function getDepartmentId()
{
return $this->department_id;
}
public function setDepartmentId($department_id)
{
$this->department_id = $department_id;
}
/**
* #return mixed
*/
public function getName()
{
return $this->name;
}
/**
* #param mixed $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* #return mixed
*/
public function getDescription()
{
return $this->description;
}
/**
* #param mixed $description
*/
public function setDescription($description)
{
$this->description = $description;
}
The scenario is when the route is /index/departmentname/Regional ;
my DefaultController will capture Regional as parameter
DefaultController:
class DefaultController extends Controller
{
/**
* #Route ("/index/department/{department_name}")
*/
function departmentAction($department_name)
{
// accessing departmentsRepository
$categoriesRepository = $this->getDoctrine()->getManager()
->getRepository('AppBundle:departments');
$categoriesRepository->getDepartmentDetails($department_name);
}
departmentsRepository:
class departmentsRepository extends \Doctrine\ORM\EntityRepository
{
function getDepartmentDetails($departmentName)
{
$em=$this->getEntityManager()->getRepository('AppBundle:departments');
$qb=$em->createQueryBuilder('dep');
$qb->select('dep.name','dep.description');
$qb->where("dep.name=$departmentName");
When I call var_dump($qb->getDQL());die; it shows me exactly what I want:
SELECT dep.name, dep.description FROM AppBundle\Entity\departments dep WHERE dep.name=Regional
I then execute it by calling
$qb->getQuery()->execute();
But I receive the following error:
[Semantical Error] line 0, col 86 near 'Regional': Error: 'Regional'
is not defined.
Any idea what I'm doing wrong?
Your dep.name value isn't being escaped. You would expect the query to look like this instead:
WHERE dep.name='Regional'
But what you should be doing, and what is safer, is binding that to a parameter, like so:
$em = $this->getEntityManager()->getRepository('AppBundle:departments');
$qb = $em->createQueryBuilder('dep');
$qb->select('dep.name', 'dep.description');
$qb->where("dep.name = :departmentName");
$qb->setParameter('departmentName', $departmentName);
Doctrine will handle the escaping for you, and safely. This also allows you to avoid SQL injection attacks. Also since you are already in your departments repository you should be able to use the _em value as a shortcut, and also not have to re-specify the departments entity, like so:
$qb = $this->_em->createQueryBuilder('dep');
$qb->select('dep.name', 'dep.description');
$qb->where("dep.name = :departmentName");
$qb->setParameter('departmentName', $departmentName);
Side not, in your controller action you are calling the repository function but not actually saving the results to any variable.

Doctrine2 - Trigger event on property change (PropertyChangeListener)

I am not writing "what did I try" or "what is not working" since I can think of many ways to implement something like this. But I cannot believe that no one did something similar before and that is why I would like to ask the question to see what kind of Doctrine2 best practices show up.
What I want is to trigger an event on a property change. So let's say I have an entity with an $active property and I want a EntityBecameActive event to fire for each entity when the property changes from false to true.
Other libraries often have a PropertyChanged event but there is no such thing available in Doctrine2.
So I have some entity like this:
<?php
namespace Application\Entity;
class Entity
{
/**
* #var int
* #ORM\Id
* #ORM\Column(type="integer");
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var boolean
* #ORM\Column(type="boolean", nullable=false)
*/
protected $active = false;
/**
* Get active.
*
* #return string
*/
public function getActive()
{
return $this->active;
}
/**
* Is active.
*
* #return string
*/
public function isActive()
{
return $this->active;
}
/**
* Set active.
*
* #param bool $active
* #return self
*/
public function setActive($active)
{
$this->active = $active;
return $this;
}
}
Maybe ChangeTracking Policy is what you want, maybe it is not!
The NOTIFY policy is based on the assumption that the entities notify
interested listeners of changes to their properties. For that purpose,
a class that wants to use this policy needs to implement the
NotifyPropertyChanged interface from the Doctrine\Common namespace.
Check full example in link above.
class MyEntity extends DomainObject
{
private $data;
// ... other fields as usual
public function setData($data) {
if ($data != $this->data) { // check: is it actually modified?
$this->onPropertyChanged('data', $this->data, $data);
$this->data = $data;
}
}
}
UPDATE
This is a full example but silly one so you can work on it as you wish. It just demonstrates how you do it, so don't take it too serious!
entity
namespace Football\TeamBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="country")
*/
class Country extends DomainObject
{
/**
* #var int
*
* #ORM\Id
* #ORM\Column(type="smallint")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(type="string", length=2, unique=true)
*/
protected $code;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set code
*
* #param string $code
* #return Country
*/
public function setCode($code)
{
if ($code != $this->code) {
$this->onPropertyChanged('code', $this->code, $code);
$this->code = $code;
}
return $this;
}
/**
* Get code
*
* #return string
*/
public function getCode()
{
return $this->code;
}
}
domainobject
namespace Football\TeamBundle\Entity;
use Doctrine\Common\NotifyPropertyChanged;
use Doctrine\Common\PropertyChangedListener;
abstract class DomainObject implements NotifyPropertyChanged
{
private $listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener)
{
$this->listeners[] = $listener;
}
protected function onPropertyChanged($propName, $oldValue, $newValue)
{
$filename = '../src/Football/TeamBundle/Entity/log.txt';
$content = file_get_contents($filename);
if ($this->listeners) {
foreach ($this->listeners as $listener) {
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
file_put_contents($filename, $content . "\n" . time());
}
}
}
}
controller
namespace Football\TeamBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Football\TeamBundle\Entity\Country;
class DefaultController extends Controller
{
public function indexAction()
{
// First run this to create or just manually punt in DB
$this->createAction('AB');
// Run this to update it
$this->updateAction('AB');
return $this->render('FootballTeamBundle:Default:index.html.twig', array('name' => 'inanzzz'));
}
public function createAction($code)
{
$em = $this->getDoctrine()->getManager();
$country = new Country();
$country->setCode($code);
$em->persist($country);
$em->flush();
}
public function updateAction($code)
{
$repo = $this->getDoctrine()->getRepository('FootballTeamBundle:Country');
$country = $repo->findOneBy(array('code' => $code));
$country->setCode('BB');
$em = $this->getDoctrine()->getManager();
$em->flush();
}
}
And have this file with 777 permissions (again, this is test) to it: src/Football/TeamBundle/Entity/log.txt
When you run the code, your log file will have timestamp stored in it, just for demonstration purposes.

Traversing Doctrine2 ODM/MongoDB Object Graph in Symfony2 fails to populate properties in 2nd level object

I am converting my otherwise working Symfony2 application to use MongoDB through Doctrine-ODM. I have the vast majority of the system working, but I can't get the user roles portion working. I can login, but then there are no roles attached to the user.
The relevant document classes are here with everything stripped out except what is relevant.
User
<?php
namespace XXXXX\UserBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
use Doctrine\Common\Collections\ArrayCollection;
use XXXXX\UserBundle\Interfaces\UserInterface;
/**
*
* #MongoDB\Document( collection="user")
*
*/
class User implements UserInterface {
/**
* #MongoDB\Id
*/
protected $id;
/**
* #MongoDB\ReferenceMany(targetDocument="Group")
*/
protected $groups;
/**
* Constructor
*/
public function __construct() {
$this->groups = new ArrayCollection();
$this->salt = base_convert(sha1(uniqid(mt_rand(), true)), 16, 36);
}
public function getRoles() {
$array = array();
//parse the roles down to an array
foreach ($this->getGroups() as $group) {
/* #var $group Group */
foreach ($group->getRoles() as $role) {
/* #var $role Role */
if(!$role->getName())
throw new \Exception('Role must exist in group: '.$group->getName().' with ID: '.$group->getId().'.');
$array[$role->getName()] = $role->getName();
}
}
sort($array);
return $array;
}
/**
* Get groups
*
* #return Doctrine\Common\Collections\Collection
*/
public function getGroups() {
return $this->groups;
}
}
Group
<?php
namespace XXXXX\UserBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
use XXXXX\UserBundle\Interfaces\UserInterface;
use XXXXX\UserBundle\Interfaces\RoleInterface;
use XXXXX\UserBundle\Interfaces\GroupInterface;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #MongoDB\Document( collection="user_group" )
*/
class Group implements GroupInterface {
/**
* #MongoDB\Id
*/
protected $id;
/**
* #MongoDB\String
* #var string
*/
protected $name;
/**
* #MongoDB\ReferenceMany(targetDocument="User")
*/
protected $users;
/**
* #MongoDB\ReferenceMany(targetDocument="Role", inversedBy="groups")
*/
protected $roles;
/**
* Constructor
*/
public function __construct() {
$this->users = new ArrayCollection();
$this->roles = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Get roles
*
* #return Doctrine\Common\Collections\Collection
*/
public function getRoles()
{
return $this->roles;
}
}
Role
<?php
namespace XXXXX\UserBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
use XXXXX\UserBundle\Interfaces\UserInterface;
use XXXXX\UserBundle\Interfaces\GroupInterface;
use XXXXX\UserBundle\Interfaces\RoleInterface;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #MongoDB\Document( collection="user_role")
*/
class Role implements RoleInterface {
/**
* #MongoDB\Id
*/
protected $id;
/**
* #MongoDB\String
* #var string
*/
protected $name;
/**
* #MongoDB\String
* #var string
*/
protected $description;
/**
* #MongoDB\ReferenceMany(targetDocument="Group", mappedBy="roles")
*/
protected $groups;
/**
* Set name
*
* #param string $name
* #return RoleInterface
*/
public function setName($name) {
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName() {
return $this->name;
}
public function getId() {
return $this->id;
}
public function getDescription() {
return $this->description;
}
public function setDescription($description) {
$this->description = $description;
}
}
I use fixtures to load the data into the database, and the data in MongoDB is as follows. ( I stripped the additional data elements.)
User.
{ "_id" : ObjectId("5091a7241311fae01f00000d"), "groups" : [ DBRef("user_group", ObjectId("5091a7241311fae01f00000b")), DBRef("user_group", ObjectId("5091a7241311fae01f00000c")) ] }
Groups that are referenced by the User. (This is from the query that is run by Symfony2)
db.user_group.find({ "_id": { "$in": { "5091a7241311fae01f00000b":ObjectId("5091a7241311fae01f00000b"), "5091a7241311fae01f00000c": ObjectId("5091a7241311fae01f00000c") } } }).sort([ ]);
{ "_id" : ObjectId("5091a7241311fae01f00000b"), "name" : "Base.Users", "roles" : [ DBRef("user_role", ObjectId("5091a7241311fae01f000009")) ] }
{ "_id" : ObjectId("5091a7241311fae01f00000c"), "name" : "AdminPortal.Base", "roles" : [ DBRef("user_role", ObjectId("5091a7241311fae01f000009")), DBRef("user_role", ObjectId("5091a7241311fae01f00000a")) ] }
And finally, the roles referenced by the groups. (Also taken from the exact query being run by Symfony2)
db.user_role.find({ "_id": { "$in": { "5091a7241311fae01f000009": ObjectId("5091a7241311fae01f000009") } } }).sort([ ]);
{ "_id" : ObjectId("5091a7241311fae01f000009"), "name" : "ROLE_USER", "description" : "Role required for all system users." }
Further, the exception in the getRoles() function for the user is called and the following text is returned.
Role must exist in group: Base.Users with ID:
5091a7241311fae01f00000b.
The problem is that the roles are being queried from the database, but are not then being populated into the role object. I can verify that they are being loaded, as when I comment the exception, it will run and attempt to add the correct number of roles per group. The problem is that the name property of the role is set to NULL. The role object itself is a persisted and loaded object as when I do a print_r($role);exit; directly before the if statement, I will get the hugely recursive output that doctrine objects exhibit. The only thing that doesn't happen is that the "name" (and other) properties are not loaded from the database.
Any insight into how I can solve this would be greatly appreciated. Thanks.
I was able to determine a work-around. Basically, using the convientent functions like find, findBy, findOneBy, etc do not seem to be setting the objects up for traversing. I was able to get the correct result by modifying the loading function to use a querybuilder instead of the convenient function "findOneBy".
My modified query is below. Hopefully this helps somebody in the future.
/**
*
* #param string $username
* #return User|Null
*/
public function findUserByUserName($username) {
$qb = $this->createQueryBuilder();
$qb->find($this->getClassName());
$qb->field('username');
$qb->equals($username);
$query = $qb->getQuery();
return $query->getSingleResult();
}
I suppose it could be more concise, but I had to break it apart to debug it, and am moving on with my life. :)

Doctrine2, pass Id or Object?

I do not understad why with some Entity objects I can set the Id and for others objects I get an error and says me that the Id can't be null and I have to pass an object instead.
e.g.:
$log = new Log();
$log->setTypeId(1);
$log->setUserId(1);
$entityManager->persist($log);
$entityManager->flush();
If I try the code above I get error that says: Integrity constraint violation: 1048 Column 'user_id' cannot be null. And I have to first create the Type Object and de User object and the pass them:
$log->setType($TypeObject)
$log->setUser($UserObject)
But for other entity objects I have no problem assigning the value directly, why is that?
This is my Entity Log:
<?php
/**
* #Entity
* #Table(name="log")
* #HasLifecycleCallbacks
*/
class Log
{
/**
* #var type
* #Id
* #Column(type="integer")
* #GeneratedValue
*/
protected $id;
/**
*
* #var type
* #Column(type="integer")
*/
protected $user_id;
/**
*
* #var type
* #Column(type="integer")
*/
protected $type_id;
/**
*
* #var type
* #Column(type="datetime")
*/
protected $created;
/**
*
* #var type
* #ManyToOne(targetEntity="User", inversedBy="logs")
*/
protected $user;
/**
*
* #ManyToOne(targetEntity="Type", inversedBy="logs")
*/
protected $type;
public function getId()
{
return $this->id;
}
public function getUserId()
{
return $this->user_id;
}
public function getTypeId()
{
return $this->type_id;
}
public function getCreated()
{
return $this->created;
}
public function setUserId($userId)
{
$this->user_id = $userId;
}
public function setTypeId($typeId)
{
$this->type_id = $typeId;
}
public function setCreated($created)
{
$this->created = $created;
}
public function setUser($user)
{
$this->user = $user;
}
public function setType($type)
{
$this->type = $type;
}
/**
* #PrePersist
*/
public function prePersist()
{
$this->setCreated(new DateTime());
}
}
?>
The existing answer never did sit well with me. There are many valid scenarios where loading an object just to define the relationship while already having the FK handy just does not make any sense at all.
A better solution is to use Doctrine's EntityManager's getRefrence method.
Reference Proxies...
The method EntityManager#getReference($entityName, $identifier) lets
you obtain a reference to an entity for which the identifier is known,
without loading that entity from the database. This is useful, for
example, as a performance enhancement, when you want to establish an
association to an entity for which you have the identifier. You could
simply do this:
<?php
// $em instanceof EntityManager, $cart instanceof MyProject\Model\Cart
// $itemId comes from somewhere, probably a request parameter
$item = $em->getReference(\MyProject\Model\Item::class, $itemId);
$cart->addItem($item);
Maybe this was not available when this question was first posted - I don't know.
EDIT
I found this statement on the website of Doctrine2. It's a best practice that you might want to follow when coding your models.
Doctrine2 Best Practices
25.9. Don’t map foreign keys to fields in an entity
Foreign keys have no meaning whatsoever in an object model. Foreign keys are how a relational database establishes relationships. Your object model establishes relationships through object references. Thus mapping foreign keys to object fields heavily leaks details of the relational model into the object model, something you really should not do
EDIT
Doctrine does the mapping from your objects to their respective Ids.
What you've done here is a bit redundant.
You've essentially told doctrine the same thing twice.
You've told it that it has a 'user_id' column AND that it also has a User object, which are the same thing. But doctrine can already guess that this relationship will have a user_id column based on the fact that the log class has a user object inside.
You should simply do the following instead
<?php
/**
* #Entity
* #Table(name="log")
* #HasLifecycleCallbacks
*/
class Log
{
/**
* #var type
* #Id
* #Column(type="integer")
* #GeneratedValue
*/
protected $id;
/**
*
* #var type
* #Column(type="datetime")
*/
protected $created;
/**
*
* #var type
* #ManyToOne(targetEntity="User", inversedBy="logs")
*/
protected $user;
/**
*
* #ManyToOne(targetEntity="Type", inversedBy="logs")
*/
protected $type;
public function getId()
{
return $this->id;
}
public function getCreated()
{
return $this->created;
}
public function setCreated($created)
{
$this->created = $created;
}
public function setUser($user)
{
$this->user = $user;
}
public function setType($type)
{
$this->type = $type;
}
/**
* #PrePersist
*/
public function prePersist()
{
$this->setCreated(new DateTime());
}
}
Doctrine will worry about the user_id and type_id on it's own. You don't have to worry about it. This way you get to work with full fledged objects, making it easier to program, instead of having to worry about id's. Doctrine will handle that.
If ALL you have is an id, because that's what you're using on the front end, then just fetch the object associated with that id using the Entitymanager.
$user = $em->getEntity( 'User', $idFromWeb );
$log = new Log();
$log->setUser( $user );

Categories