It's the first time I run into this problem. I want to create a doctrine object and pass it along without having to flush it.
Right after it's creation, I can display some value in the object, but I can't access nested object:
$em->persist($filter);
print_r($filter->getDescription() . "\n");
print_r(count($filter->getAssetClasses()));
die;
I get:
filter description -- 0
(I should have 19 assetClass)
If I flush $filter, i still have the same issue (why oh why !)
The solution is to refresh it:
$em->persist($filter);
$em->flush();
$em->refresh($filter);
print_r($filter->getDescription() . " -- ");
print_r(count($filter->getAssetClasses()));
die;
I get:
filter description -- 19
unfortunately, you can't refresh without flushing.
On my entities, I've got the following:
in class Filter:
public function __construct()
{
$this->filterAssetClasses = new ArrayCollection();
$this->assetClasses = new ArrayCollection();
}
/**
* #var Collection
*
* #ORM\OneToMany(targetEntity="FilterAssetClass", mappedBy="filterAssetClasses", cascade={"persist"})
*/
private $filterAssetClasses;
public function addFilterAssetClass(\App\CoreBundle\Entity\FilterAssetClass $filterAssetClass)
{
$this->filterAssetClasses[] = $filterAssetClass;
$filterAssetClass->setFilter($this);
return $this;
}
in class FilterAssetClass:
/**
* #var Filter
*
* #ORM\ManyToOne(targetEntity="App\CoreBundle\Entity\Filter", inversedBy="filterAssetClasses")
*/
private $filter;
/**
* #var Filter
*
* #ORM\ManyToOne(targetEntity="AssetClass")
*/
private $assetClass;
public function setFilter(\App\CoreBundle\Entity\Filter $filter)
{
$this->filter = $filter;
return $this;
}
Someone else did write the code for the entities, and i'm a bit lost. I'm not a Doctrine expert, so if someone could point me in the good direction, that would be awesome.
Julien
but I can't access nested object
Did you set those assetClasses in the first place?
When you work with objects in memory (before persist), you can add and set all nested objects, and use those while still in memory.
My guess is that you believe that you need to store objects to database in order for them to get their IDs assigned.
IMHO, that is a bad practice and often causes problems. You can use ramsey/uuid library instead, and set IDs in Entity constructor:
public function __construct() {
$this->id = Uuid::uuid4();
}
A database should be used only as a means for storing data. No business logic should be there.
I would recommend this video on Doctrine good practices, and about the above mentioned stuff.
Your problem is not related to doctrine nor the persist/flush/refresh sequence; the problem you describe is only a symptom of bad code. As others have suggested, you should not be relying on the database to get at your data model. You should be able to get what you are after entirely without using the database; the database only stores the data when you are done with it.
Your Filter class should include some code that manages this:
// Filter
public function __contsruct()
{
$this->filterAssetClasses = new ArrayCollection();
}
/**
* #ORM\OneToMany(targetEntity="FilterAssetClass", mappedBy="filterAssetClasses", cascade={"persist"})
*/
private $filterAssetClasses;
public function addFilterAssetClass(FilterAssetClass $class)
{
// assuming you don't want duplicates...
if ($this->filterAssetClasses->contains($class) {
return;
}
$this->filterAssetClasses[] = $class;
// you also need to set the owning side of this relationship
// for later persistence in the db
// Of course you'll need to create the referenced function in your
// FilterAssetClass entity
$class->addFilter($this);
}
You may have all of this already, but you didn't show enough of your code to know. Note that you should probably not have a function setFilterAssetClass() in your Filter entity.
Related
I searched a while, but until now, I wasn't able to find a suitable answer.
In my "Offer" class, I have the following:
/**
* #ORM\OneToMany(mappedBy="offer")
* #var Collection<OfferItem>
*/
protected $offerItems;
/**
* #return Collection
*/
public function getOfferItems()
{
return $this->offerItems;
}
/**
* #param Collection $offerItems
*/
public function setOfferItems($offerItems)
{
$this->offerItems = $offerItems;
}
Now, I create a new Offer and would like to add some OfferItems as well:
$offer = new Offer();
$offerItem = new OfferItem();
$offer->getOfferItems()->add($offerItem);
But then, the error comes: "Fatal error: Call to a member function add() on null". Okay, in some points, it makes sense - the collection is empty until know - and perhaps "null".
I'm not such an PHP / Flow3 / Doctrine expert, to have the overview, how to handle such an sitation?
I think, I have to set an empty (but not null-) collection to the offer. But
$collection = new \Doctrine\Common\Collections\Collection()
Is not working, because "Collection" is an interface.
Any hint, idea or something like that, to understand my problem would be nice.
Thank you very much in advance for your help!
I have a simple document that has its ID:
/**
* #MongoDB\Id(strategy="increment")
* #Serializer\Since("2.0")
*/
protected $id;
and a property code
/**
* #var string
*
*/
protected $code;
I want that the code be generated based in the ID. So I am planning to define it in the constructor
public function __construct()
{
$this->code = (string)$this->id.(string)rand(0,1000);
}
My question is, as both are defined in the same php class, there would be any risk to define one based in another?
Any risk of the code ask for the id before it was defined? Or there is any better way of doing such thing?
Your ID will be null when you create the object. To create code property you should set it after the persist. Something like this:
$dm = $this->get('doctrine.odm.mongodb.document_manager');
$item = new Item();
$item->setSomeValue($someValue);
$dm->persist($item);
$dm->flush();
$item->setCode($item->getId());
$dm->persist($item);
$dm->flush();
As you can imagine, this is not a good practice and you should avoid it. Generate values from database ID it is not a good idea too. I recommend you to use functions like uniqid to do the workaround. Uniqid is safer, faster and cleaner.
if you just want the serialized obj to be like:
{
id:123,
code:123
}
u can just add a getter function and it will be included in serialized result
/**
* #var string
*
*/
protected $code;
public function getCode(){
return $this->getId();
}
if this does not work automagicaly (should) you can use annotations for serializer:
/**
* #JMS\Expose
* #JMS\Accessor(getter="getCode")
*/
private $code;
if you want to persist the code property you can do it like Vangoso, but it does not realy make sense storing the same information twice
i have the following document:
class Purchase
{
/**
* #MongoDB\Id(strategy="INCREMENT")
*/
protected $id;
...
I'm using this document in a Symfony 2.8.4 project.
In this case, the ID for the first document that i persist is '1', the next one will be '2' and so on.
I'd like to start the counter from 1000, but i can't figure how i can do it inside the "Model part".
Thanks
Unfortunately there is now way to set counter in "Model part", but as the current counters are stored in the database you may alter their values there. For more details how this work you can inspect how IncrementGenerator::generate works.
It could be a perfect use of the SequenceGenerator, but not supported by the platform.
One work-around (out of the model, unfortunately) consists in setting the first ID manually.
In your controller:
$object = new Purchase();
$em = $this->getDoctrine()->getManager():
$metadata = $em->getClassMetadata(get_class($object));
$metadata->setIdGeneratorType($metadata::GENERATOR_TYPE_NONE);
$object->setId(1000);
$em->persist($object);
$em->flush();
print $object->getId(); // 1000
Don't forget to add the setter function in your Purchase document:
/**
* #param int $id
*
* #return Purchase
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
Then, remove this logic (or wrap it in a check to verify that the object is the first being persisted).
I have one specific issue. I have two entities:
class MyPlaylist {
...
/**
* #var Array
* #ORM\OneToMany(targetEntity="MyPlaylistContent", mappedBy="myPlaylist", orphanRemoval=true)
* #ORM\OrderBy({"position" = "DESC"})
*/
private $myPlaylistItems;
and
class MyPlaylistContent {
....
/**
* #ORM\ManyToOne(targetEntity="MyPlaylist", inversedBy="myPlaylistItems")
*/
private $myPlaylist;
Now I have this in my service
....
$myPlaylist = new MyPlaylist();
$myPlaylist->setUser($user);
$myPlaylist->setActive(true);
// add tracks
foreach ($playlist->getMyPlaylistItems() as $item) {
$entity = new MyPlaylistContent();
$entity->setTrack($item->getTrack());
$entity->setMyPlaylist($myPlaylist);
$this->em->persist($entity);
}
$this->em->persist($myPlaylist);
$this->em->flush();
\Doctrine\Common\Util\Debug::dump($myPlaylist);
return $myPlaylist;
so, I return a new playlist. If I look at the database, all works fine. I have both entities and in MyPlaylistContent - 3 tracks. But
\Doctrine\Common\Util\Debug::dump($myPlaylist); shows next
["active"]=> bool(true) ["myPlaylistItems"]=> array(0) { }
On the page, the app shows the empty playlist (no tracks). If I refresh the page, I can see all tracks.
The point is, if you open the page, the controller will call the service, build the content and return the list as a response.
It looks as the same example, but it does not work for me
http://symfony.com/doc/current/book/doctrine.html#saving-related-entities
What is wrong here? Why don't I get tracks for the current entity?
You forget to add MyPlaylistContent to MyPlaylist.
Use this snippet into foreach
$myPlaylist->addMyPlaylistContent($myPlaylistContent);
Of course change name or implement method accordingly
First note: this is because objects are "normal" php objects, they have nothing to do with doctrine so, relationships are only a doctrine concept. EntityManager in doctrine will handle this kind of processes, not php itself. If you take a look to your classes methods you will probably notice that no "connection" (assignments) are made between those objects. If you would like, you can modify MyPlaylistContent to add itself to MyPlaylist once assigned.
Something like
class MyPlaylistContent
{
[...]
public function setMyPlaylist(MyPlaylist $mp)
{
$this->myPlaylist = $myPlaylist;
$mp->addMyPlaylistContent($this);
return $this;
}
Second note: hope your names are more consistents of these ones :)
I am having annoying problems with persisting an entity with one or more OneToMany-Childs.
I have a "Buchung" entity which can have multiple "Einsatztage" (could be translated to an event with many days)
In the "Buchung entity I have
/**
* #param \Doctrine\Common\Collections\Collection $property
* #ORM\OneToMany(targetEntity="Einsatztag", mappedBy="buchung", cascade={"all"})
*/
private $einsatztage;
$einsatztage is set to an ArrayCollection() in the __constructor().
Then there is the "Einsatztag" Entity which has a $Buchung_id variable to reference the "Buchung"
/**
* #ORM\ManyToOne(targetEntity="Buchung", inversedBy="einsatztage", cascade={"all"})
* #ORM\JoinColumn(name="buchung_id", referencedColumnName="id")
*/
private $Buchung_id;
Now If I try to persist an object to the database the foreign key of the "Einsatztag" Table is always left empty.
$buchung = new Buchung();
$buchung->setEvent( $r->request->get("event_basis"));
$buchung->setStartDate(new \DateTime($r->request->get("date_from")));
$buchung->setEndDate(new \DateTime($r->request->get("date_to")));
$von = $r->request->get("einsatz_von");
$bis = $r->request->get("einsatz_bis");
$i = 0;
foreach($von as $tag){
$einsatztag = new Einsatztag();
$einsatztag->setNum($i);
$einsatztag->setVon($von[$i]);
$einsatztag->setBis($bis[$i]);
$buchung->addEinsatztage($einsatztag);
$i++;
}
$em = $this->getDoctrine()->getManager();
$em->persist($buchung);
foreach($buchung->getEinsatztage() as $e){
$em->persist($e);
}
$em->flush();
Firstly, you have to understand that Doctrine and Symfony does not work with id's within your entities.In Einsatztag entity, your property should not be called $Buchung_id since it's an instance of buchung and not an id you will find out there.
Moreover, in your loop, you add the Einsatztag to Buchung. But do you process the reverse set ?
I do it this way to always reverse the set/add of entities.
Einsatztag
public function setBuchung(Buchung $pBuchung, $recurs = true){
$this->buchung = $pBuchung;
if($recurs){
$buchung->addEinsatztag($this, false);
}
}
Buchung
public function addEinsatztag(Einsatztag $pEinsatztag, $recurs = true){
$this->einsatztages[] = $pEinsatztag;
if($recurs){
$pEinsatztag->setBuchung($this, false);
}
}
Then, when you will call
$buchung->addEinsatztag($einsatztag);
Or
$einsatztag->set($buchung);
The relation will be set on both side making your FK to be set. Take care of this, you'll have some behavior like double entries if you do not use them properly.
SImplier , you can use default getter/setters and call them on both sides of your relation, using what you already have, like following:
$einsatztag->set($buchung);
$buchung->addEinsatztag($einsatztag);
Hope it helped ;)
First of all, don't use _id properties in your code. Let it be $buchung. If you want it in the database, do it in the annotation. And this also the reason, why it's not working. Your are mapping to buchung, but your property is $Buchung_id
<?php
/** #ORM\Entity **/
class Buchung
{
// ...
/**
* #ORM\OneToMany(targetEntity="Einsatztag", mappedBy="buchung")
**/
private $einsatztage;
// ...
}
/** #ORM\Entity **/
class Einsatztag
{
// ...
/**
* #ORM\ManyToOne(targetEntity="Product", inversedBy="einsatztage")
* #JoinColumn(name="buchung_id", referencedColumnName="id")
**/
private $buchung;
// ...
}
You don't have to write the #JoinColumn, because <propertyname>_id would the default column name.
I'm going to ignore the naming issue and add a fix to the actual problem.
You need to have in the adder method a call to set the owner.
//Buchung entity
public function addEinsatztage($einsatztag)
{
$this->einsatztags->add($einsatztag);
$ein->setBuchung($this);
}
And to have this adder called when the form is submitted you need to add to the form collection field the by_reference property set to false.
Here is the documentation:
Similarly, if you're using the CollectionType field where your underlying collection data is an object (like with Doctrine's ArrayCollection), then by_reference must be set to false if you need the adder and remover (e.g. addAuthor() and removeAuthor()) to be called.
http://symfony.com/doc/current/reference/forms/types/collection.html#by-reference