Doctrine ODM association not loaded - php

I've started working with Doctrine MongoDB ODM. I have a Document called Document which has an association originalFile to another document called File.
class Document extends AbstractDocument
{
/**
* #var integer $id
*
* #MongoDB\Id
*/
protected $id;
/**
* #var ImportSource
*
* #MongoDB\ReferenceOne(targetDocument="ImportSource", cascade="all", simple=true)
*/
protected $importSource;
/**
* #var DocumentState
*
* #MongoDB\ReferenceOne(targetDocument="DocumentState", cascade="all", simple=true)
*/
protected $state;
/**
* #var \DateTime $created
*
* #MongoDB\Date
*/
protected $created;
/**
* #var \DateTime $modified
*
* #MongoDB\Date
*/
protected $modified;
/**
* #var File $formattedFile
*
* #MongoDB\ReferenceOne(targetDocument="File", cascade="all")
*/
protected $formattedFile;
/**
* #var File $originalFilename
*
* #MongoDB\ReferenceOne(targetDocument="File", cascade="all")
*/
protected $originalFile;
//getters, setters etc..
}
File:
class File
{
/**
* #var string
*
* #MongoDB\Id
*/
protected $id;
/**
* #var FileType
*
* #MongoDB\ReferenceOne(targetDocument="FileType", cascade="all", simple=true)
*/
protected $type;
/**
* #var string
*
* #MongoDB\String
*/
protected $basename;
/**
* #var \DateTime
*
* #MongoDB\Date
*/
protected $created;
/**
* #var \DateTime
*
* #MongoDB\Date
*/
protected $deleted;
/**
* #var \MongoGridFSFile
*
* #MongoDB\File
*/
protected $file;
Storing the Document works without any problems. The Document and the File-Document are stored in MongoDB. When I'm loading the Document the $originalFile property is null. I have no clue what's missing. Is the mapping wrong or is this simply a bug in Doctrine?
Edit:
This is how I'm storing the documents:
//create if not exists
if ($documentObj === null) {
$documentObj = new Document();
$documentObj->setCreated(new \DateTime());
//create file
$file = new File();
} else {
$documentObj->setModified(new \DateTime());
//get file
$file = $documentObj->getOriginalFile();
}
$file->setFile($pathToFile);
//set file
$documentObj->setOriginalFile($file);
//set import source
$documentObj->setImportSource($source);
//store document
$documents[] = $this->storeDocument($documentObj, $documentFields);
//... method storeDocument fills other properties of the document and finally persits the document:
$this->manager->persist($documentObj);
$this->manager->flush();

I have no clue why, but it's working now... I was occupied with other projects for a few days, returned to this projects, ran my unit tests again and everything worked. Only thing I've done was a composer update. Maybe a bug correlating with my setup.

I got such problem, it is related to metadata cache, just run
bin/console cache:clear
bin/console doctrine:cache:clear-metadata
Also good to know these commands, if you are using symfony built-in cache
bin/console doctrine:cache:clear-query
bin/console doctrine:cache:clear-result

Related

PersistentCollection not initialized but data in database with Symfony and Doctrine

I have a problem with a PersistentCollection.
I have an Object User (herited of FOSUserbundle user class) who have an EmdebedDocument Seance. The Seance have an Array of Event.
My Seance Class:
/**
* #MongoDB\EmbeddedDocument
*/
class Seance
{
/**
* #MongoDB\Id
*/
protected $id;
/**
* #MongoDB\EmbedMany(targetDocument="Event")
*/
protected $dispos ;
/**
* #MongoDB\Field(type="string")
*/
protected $rayonDeplacement;
/**
* #MongoDB\Field(type="string")
*/
protected $lieu;
/**
* #MongoDB\Field(type="string")
*/
protected $prix;
/**
* #MongoDB\Field(type="string")
*/
protected $nbPersonne;
And my class Event
/**
* #MongoDB\EmbeddedDocument
*/
class Event extends BaseEvent
{
/**
* #var integer
* #MongoDB\Id
*/
protected $id;
/**
* #var \DateTime
* #MongoDB\Field(type="date")
* #Assert\NotNull()
*/
protected $startDate;
/**
* #var \DateTime
* #MongoDB\Field(type="date")
* #Assert\NotNull()
*/
protected $endDate;
I give the event from user with:
$user->getSeance()->getDispos()
This function returns a empty PersistentCollection while they are events in database.
When dump the return of getDispos() method I have:
I dont't understant why I have mongoData field with my data but arrayCollection empty.
Thank you for yout help.
PersistentCollection is initialized lazily - for performance reasons, the data from database is held in mongoData. The collection is initialized during the first time you need some data from it (or try to modify it) - then the data held in mongoData is hydrated into your embedded documents and that is added to decorated coll. All this is happening transparently to you, just try using your collection instead of dumping it.

Doctrine ManyToMany Association Entity: why does removeXXX() not delete underlying database record?

I have a situation where I need to add columns to a many-to-many join table, so I'm trying to follow the recommended practice of having the join table represented by an entity with ManyToOne relationships with each of the other two entities.
In this case, we have a court interpreter management system where there's an entity called Event, another called Interpreter. The InterpreterAssignment entity is one-to-many with both of these, but it also needs two metadata columns: a created datetime, and the Application\Entity\User who created it (I leave out the latter for simplicity's sake).
So, this works just fine:
$interpreter = $entityManager->getRepository('Application\Entity\Interpreter')
->findOneBy(['lastname'=>'Mintz']);
$assignment = new Entity\InterpreterAssignment();
$assignment->setInterpreter($interpreter)->setEvent($event);
$event->addInterpretersAssigned($assignment);
$em->flush();
...and I don't even need to say persist() because of the cascade={"persist","remove"}) on Event#interpretersAssigned.
However, when I try to do the reverse, that is,
use the removeInterpretersAssigned() method that Doctrine wrote for me:
$event = $entityManager->find('Application\Entity\Event',103510);
$assignment = $event->getInterpretersAssigned()[0];
$event->removeInterpretersAssigned($assignment);
$em->flush();
the database is untouched; Doctrine does not delete the row in the join table.
I can work around by saying $entityManager->remove($assignment). But I can't help but think that $event->removeInterpretersAssigned($assignment) is supposed to work.
So, I must be missing something but I can't see what. The Doctrine cli tool says my mappings are OK. Here are the entities, in relevant part:
/* namespace declarations and use statements omitted */
class Event
{
/* other fields and methods omitted */
/**
* #ORM\OneToMany(targetEntity="InterpreterAssignment",mappedBy="event",cascade={"persist","remove"})
* #var InterpreterAssignment[]
*/
protected $interpretersAssigned;
/* the following created by the Doctrine cli tool */
/**
* Remove interpretersAssigned
*
* #param \Application\Entity\InterpreterAssignment $interpretersAssigned
*/
public function removeInterpretersAssigned(\Application\Entity\InterpreterAssignment $interpretersAssigned)
{
$this->interpretersAssigned->removeElement($interpretersAssigned);
}
/**
* Get interpretersAssigned
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getInterpretersAssigned()
{
return $this->interpretersAssigned;
}
}
class Interpreter
{
/**
* #ORM\OneToMany(targetEntity="InterpreterAssignment",mappedBy="interpreter")
* #var InterpreterAssignment[]
*/
protected $assignments;
/**
* Remove assignment
*
* #param \Application\Entity\InterpreterAssignment $assignment
*/
public function removeAssignment(\Application\Entity\InterpreterAssignment $assignment)
{
$this->assignments->removeElement($assignment);
}
/**
* Get assignments
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getAssignments()
{
return $this->assignments;
}
}
and here is the InterpreterAssignment
/**
* #ORM\Entity
* #ORM\Table(name="interp_events", uniqueConstraints={#ORM\UniqueConstraint(name="unique_deft_event",columns={"interp_id","event_id"})})
* #ORM\HasLifeCycleCallbacks
*/
class InterpreterAssignment
{
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Interpreter",inversedBy="assignments")
* #ORM\JoinColumn(name="interp_id", referencedColumnName="interp_id")
* #var Interpreter
*/
protected $interpreter;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Event",inversedBy="interpretersAssigned")
* #ORM\JoinColumn(name="event_id", referencedColumnName="event_id")
* #var Event
*/
protected $event;
/**
* #ORM\Column(type="datetime",nullable=false)
* #var \DateTime
*/
protected $created;
/**
* #ORM\PrePersist
*/
public function onPrePersist()
{
$this->created = new \DateTime();
}
/**
* Set interpreter
*
* #param \Application\Entity\Interpreter $interpreter
*
* #return InterpreterAssignment
*/
public function setInterpreter(\Application\Entity\Interpreter $interpreter)
{
$this->interpreter = $interpreter;
return $this;
}
/**
* Get interpreter
*
* #return \Application\Entity\Interpreter
*/
public function getInterpreter()
{
return $this->interpreter;
}
/**
* Set event
*
* #param \Application\Entity\Event $event
*
* #return InterpreterAssignment
*/
public function setEvent(\Application\Entity\Event $event)
{
$this->event = $event;
return $this;
}
/**
* Get event
*
* #return \Application\Entity\Event
*/
public function getEvent()
{
return $this->event;
}
/* other stuff ommitted */
}
Many thanks.
I think you need to do 2 things:
(optional) You need to call $assignment->setEvent(null) after calling $event->removeInterpretersAssigned($assignment);
Also you may want to use Orphan Removal to remove the entity from the many to many table. and so the entity code should changed to (notice the addition of , orphanRemoval=true to the mapping code):
/**
* #ORM\OneToMany(targetEntity="InterpreterAssignment",mappedBy="event",cascade={"persist","remove"}, orphanRemoval=true)
* #var InterpreterAssignment[]
*/
protected $interpretersAssigned;

Doctrine 2 multiple mappedBy?

I've got a problem setting up the Doctrine mapping correctly.
I have a CashRegister Entity which has a bin location and a return bin location. Both locations are from the same Type (BinLocation Entity).
Outgoing from CashRegister, CashRegister->getBinLocations() and CashRegister->getReturnBinLocations() are working fine, but how can I achieve that BinLocation->getCashRegisters() returns all CashRegister Entities that are referenced (binLocation + returnBinLocation)?
/**
* CashRegister
*
* #ORM\Table(name="cash_registers")
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class CashRegister
{
...
/**
* #var BinLocation
*
* #ORM\ManyToOne(targetEntity="BinLocation", inversedBy="cashRegisters")
* #ORM\JoinColumn(name="bin_location_id", referencedColumnName="id")
*/
private $binLocation;
/**
* #var BinLocation
*
* #ORM\ManyToOne(targetEntity="BinLocation", inversedBy="cashRegisters")
* #ORM\JoinColumn(name="return_bin_location_id", referencedColumnName="id")
*/
private $returnBinLocation;
/**
* #return BinLocation
*/
public function getBinLocation()
{
return $this->binLocation;
}
/**
* #return BinLocation
*/
public function getReturnBinLocation()
{
return $this->returnBinLocation;
}
...
}
/**
* BinLocation
*
* #ORM\Table(name="bin_locations")
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class BinLocation
{
...
/**
* #var CashRegister[]
*
* #ORM\OneToMany(targetEntity="CashRegister", mappedBy="binLocation") <= Here is the problem, in this case mappedBy need to be an array [binLocation, returnBinLocation]
*/
private $cashRegisters;
/**
* #return CashRegister[]
*/
public function getCashRegisters()
{
return $this->cashRegisters;
}
...
}
The simple answer is that you cannot. mappedBy accepts only one argument.
The solution to achieve what you want is however simple. Create a second property in BinLocation called: cashRegisters2 as follows:
/**
* #var CashRegister[]
*
* #ORM\OneToMany(targetEntity="CashRegister", mappedBy="binLocation")
*/
private $cashRegisters;
/**
* #var CashRegister[]
*
* #ORM\OneToMany(targetEntity="CashRegister", mappedBy="binLocation")
*/
private $cashRegisters2;
Then merge the Collections in your getCashRegisters method.
/**
* #return CashRegister[]
*/
public function getCashRegisters()
{
return new ArrayCollection(
array_merge($cashRegisters->toArray(), $cashRegisters2->toArray())
);
}
Also change your CashRegister mappings accordingly:
/**
* #var BinLocation
*
* #ORM\ManyToOne(targetEntity="BinLocation", inversedBy="cashRegisters")
* #ORM\JoinColumn(name="bin_location_id", referencedColumnName="id")
*/
private $binLocation;
/**
* #var BinLocation
*
* #ORM\ManyToOne(targetEntity="BinLocation", inversedBy="cashRegisters2")
* #ORM\JoinColumn(name="return_bin_location_id", referencedColumnName="id")
*/
private $returnBinLocation;
Note: I did not test the code. This example is to server guide only.
Note2: The ArrayCollection merge was inspired from here: https://stackoverflow.com/a/16871539/2853903
I have also searched for solutions and made a patch for Doctrine so you can have custom_attributes linked to a variety of entity types.
Doctrine 2.6: https://github.com/danielbeeke/doctrine2/commit/2d8530176b872cb490c5c88b8c8e17d8d0091388
Doctrine 2.7: https://github.com/danielbeeke/doctrine2/commit/5bde696848ea9fe7035fadc4d46baa4c0d51f3a2
/**
* #Entity
* #Table(name="product")
* #HasLifecycleCallbacks
**/
class Product {
/**
* One Product has Many Attributes.
*
* #OneToMany(
* targetEntity="CustomAttribute",
* mappedBy="EntityId",
* mappedByType={
* "field": "EntityType",
* "value": "product"
* }
* )
*
* #var $CustomAttributes ArrayCollection
*/
protected $CustomAttributes;
}
/**
* #Entity
* #Table(name="custom_attribute")
* #HasLifecycleCallbacks
**/
class CustomAttribute_entity {
/** #Id #Column(type="integer") #GeneratedValue */
protected $Id;
/**
* Many Attributes have One Entity of the EntityType provided.
* #ManyToOne(targetEntity="Product", inversedBy="CustomAttributes")
* #JoinColumn(name="EntityId", referencedColumnName="Id")
*/
protected $EntityId;
/** #Column(type="string") */
protected $EntityType;
/** #Column(type="string") */
protected $Type;
/** #Column(type="string") */
protected $Value;
}

Doctrine Entities and traits. Right way

I have a Comment entity (for user comments) and I want to add a new feature (Commentable) in my old entities.
I created a trait Commentable:
trait Commentable
{
/**
* List of comments
*
* #var Comment[]|ArrayCollection
*
* #ORM\OneToMany(targetEntity="Comment")
*/
protected $comments;
/**
* Constructor
*/
public function __construct()
{
$this->comments = new ArrayCollection();
}
/**
* Get Comments
*
* #return Comment[]|ArrayCollection
*/
public function getComments()
{
return $this->comments;
}
/**
* Add comment to the entity
*
* #param Comment $comment
*/
public function addComment(Comment $comment)
{
$this->comments->add($comment);
}
}
and in the old entities I do something like this:
class Image
{
use Commentable {
Commentable::__construct as private __commentableConstruct;
}
/** some stuff **/
}
The Comment class looks like:
class Comment
{
/**
* Identifier
*
* #var int
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* Comment owner
*
* #var User
*
* #ORM\ManyToOne(targetEntity="User", inversedBy="comments")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
/**
* Comment content
*
* #var string
*
* #ORM\Column(type="text")
*/
protected $content;
/**
* #var Image
*
* #ORM\ManyToOne(targetEntity="Image", inversedBy="comments")
*/
protected $image;
/** all the classes using Commentable **/
/** some stuff */
}
I think the idea is not bad. I can create new behaviours and easily add it to entities.
But I don't like the idea on the Comment entity. Adding all the classes using the commentable trait is not 'usefull'.
I'm receiving this error... but I don't know how I can fix that with traits:
OneToMany mapping on field 'comments' requires the 'mappedBy' attribute.
I fixed the problem using
trait Commentable
{
/**
* List of comments
*
* #var Comment[]|ArrayCollection
*
* #ORM\ManyToMany(targetEntity="XXXX\Entity\Comment")
* #ORM\OrderBy({"createdAt" = "DESC"})
*/
protected $comments;
/**
* Constructor
*/
public function __construct()
{
$this->comments = new ArrayCollection();
}
/**
* Get Comments
*
* #return Comment[]|ArrayCollection
*/
public function getComments()
{
return $this->comments;
}
/**
* Add comment to the entity
*
* #param Comment $comment
*/
public function addComment(Comment $comment)
{
$this->comments->add($comment);
}
}
It's not a trait matter, it's a mapping / doctrine related problem.
Your annotation "#OneToMany" misses a configuration according to the documentation
I guess that in your Image class, you should overwrite the property that you use for mapping.

Not able to call functions on extracted entities with Doctrine 2

I'm new to PHP and also with Doctrine. (Worked before with Hibernate ORM implementation).
My problem is that after I fetch a record from my database by the entityManager, I can't access the object methods at all. Below are some code snippets:
Entity manager creation:
$classLoader = new \Doctrine\Common\ClassLoader('entities');
$classLoader->register();
$config = new Configuration();
$cache = new ArrayCache();
$config->setMetadataCacheImpl($cache);
$driverImpl = $config->newDefaultAnnotationDriver('entities');
$driverImpl->getAllClassNames();
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCacheImpl($cache);
$config->setProxyDir('proxies');
$config->setProxyNamespace('proxies\namespaces');
$config->setAutoGenerateProxyClasses(true);
$em = EntityManager::create(getConnOptions(), $config);
it works fine!
Here is my Entity class :
namespace entities\positions;
/**
* Positions
*
* #Table(name="positions")
* #Entity
*/
class Positions
{
/**
* #var bigint $id
*
* #Column(name="id", type="bigint", nullable=false)
* #Id
* #GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string $notes
*
* #Column(name="notes", type="string", length=255, nullable=true)
*/
private $notes;
/**
* #var integer $number
*
* #Column(name="number", type="integer", nullable=true)
*/
public $number;
/**
* #var Volumes
*
* #ManyToOne(targetEntity="Volumes")
* #JoinColumns({
* #JoinColumn(name="volume_id", referencedColumnName="id")
* })
*/
private $volume;
public function getNumber() {
return $this->number;
}
}
and here is the code that generates error:
$found = $this->em->find('Positions', 1);
echo $found->getNumber();
the error that I get is the following:
Fatal error: Call to undefined method Positions::getNumber() in /var/www/php-test/business/Test.php on line 30
Can you suggest me how to fix it?
Thanks.
N.B. It gives me the same error if I try to call : $found->number, that I have made public for this reason.
The problem is due to the fact that I was declared the namespace in entities. This was the reason for what I got this error. If you have entities under entities/ directory scattered in it's own directory, you need to put this paths in the driver creation array configuration :
$driverImpl = $config->newDefaultAnnotationDriver(array("entities", "entities/dir1", "entities/dir2"));
That does the trick.

Categories