I have 3 models that I have setup thus far in a simple application I am working on:
So far I have these models:
UserAccountEntity - Top level Table (Has a One-Many Relationship to UserAccountEntityStrings)
UserAccountEntityStrings - Child Table (Has a Many-One relation ship to UserAccountEntity and EavAttributes
EavAttributes - Lookup Table
When I query data from my top level table, I get the schema,association information for the child table. But I do not get any of the persisted data from the child table.
What I expected the results to be were, the data from the top level model and the data from the associated child model. Any help with this is greatly appreciated.
A note that may be helpful, I am using Zend 1.11.10 and Doctrine 2
This is what my query looks like:
$users = $em->createQuery('select u from Fiobox\Entity\UserModule\UserAccountEntity u')->execute();
Zend_Debug::dump($users[0]);
This is the association in my top level model:
/**
*
* #param \Doctrine\Common\Collections\Collection $property
* #OneToMany(targetEntity="UserAccountEntityStrings",mappedBy="UserAccountEntity", cascade={"persist","remove"})
*/
private $strings;
These are the associations in my child model:
/**
*
* #var UserAccountEntity
* #ManyToOne(targetEntity="UserAccountEntity")
* #JoinColumns({
* #JoinColumn(name="entity_id", referencedColumnName="entity_id")
* })
*/
private $user;
/**
* #var EavAttribute
* #ManyToOne(targetEntity="Fiobox\Entity\EavModule\EavAttributes")
* #JoinColumn(name="attribute_id", referencedColumnName="attribute_id")
*/
private $attributes;
Have you actually tried anything?
Doctrine will lazy load stuff for you. Your var_dump probably shows persistent collections of proxy objects for your child objects. But if you access them, they'll be loaded automatically:
<?php
$users = $em->createQuery('select u from Fiobox\Entity\UserModule\UserAccountEntity u')->fetchAll();
foreach($users as $u){
foreach($u->strings as $s){
var_dump($s);
}
}
If you know that you're going to need all that child data, you might as well force a fetch-join in your DQL:
<?php
$users = $em->createQuery('select u, s from Fiobox\Entity\UserModule\UserAccountEntity u JOIN u.strings s')->fetchAll();
Related
in a extbase extension i us sys_category. In list action there is no problem, all categories work as expected. But i want to write category entries with a custom database finisher from tx_form.
In the model all seems correct:
/**
* Sets the categories
*
* #param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $categories
* #return void
*/
public function setCategories($categories)
{
$this->categories = $categories;
}
in my finisher:
$newAddress->setCategories($newCat);
$this->addressRepository->add($newAddress);
The form gives me only the uid of the category but for "setCategories" i need an \TYPO3\CMS\Extbase\Persistence\ObjectStorage.
How do i get a \TYPO3\CMS\Extbase\Persistence\ObjectStorage from the uid of the category?
Thanks!
You need a setter method for the categories like this:
public function addCategory($category)
{
$this->categories->attach($category);
}
This method will add one model to your object storage.
As you need the category model to be added, you need to get the corresponding model of the uid with $categoryRepository->findByUid($uid);
I have a OneToMany Relationship between a ChartPage and a BaseChart:
1 ChartPage holds 1 BaseChart and 1 BaseChart holds many ChartPages
Charts are managed in a different bundle of my application, so they can be removed individually. What I like to have is, that Doctrine automatically removes the ChartPage.Chart reference when the Chart is deleted, but nothing else (not remove the ChartPage).
The other way around should leave everything as is: When I remove the ChartPage with a referenced BaseChart - nothing should happen (not delete the BaseChart)
I tried every combination with one of these: cascade="{detach,merge,refresh,remove,persist}" that I could think of, but I can't figure it out..
This is my mapping:
<?php
/**
* Class ChartPage
* #package VBCMS\Bundle\AdminBundle\Document\Page
* #Serializer\AccessType("public_method")
* #MongoDB\Document()
*/
class ChartPage extends BasePage {
/**
* #var BaseChart
* #Serializer\Type("VBCMS\Bundle\StatisticBundle\Document\BaseChart")
* #Serializer\Accessor(setter="setChartDeserialize")
* #MongoDB\ReferenceOne(
* targetDocument="VBCMS\Bundle\StatisticBundle\Document\BaseChart",
* mappedBy="pages",
* cascade={"persist,detach,merge"}
* )
*/
protected $chart;
}
/
/**
* Class BaseChart
* #package VBCMS\Bundle\StatisticBundle\Document
* #Serializer\AccessType("public_method")
* #MongoDB\Document(
* collection="Chart",
* repositoryClass="VBCMS\Bundle\StatisticBundle\Repository\ChartRepository"
* )
*/
class BaseChart {
/**
* #var BasePage[]|Collection
* #Serializer\Exclude()
* #MongoDB\ReferenceMany(
* targetDocument="VBCMS\Bundle\AdminBundle\Document\Page\ChartPage",
* inversedBy="chart",
* cascade={"persist,detach,merge"}
* )
*/
protected $pages;
}
The only idea I have left is to build a custom preRemove EventListener that sets the references back to NULL before a BasePage ist removed, but I hoped I could avoid this manual mess.
Doctrine MongoDB ODM's cascade functionality only operates in one direction. If you perform some lifecycle event on object A, which has a reference to B, we could cascade the persist/remove/etc over to B. There is a concept of orphan removal in ODM, which allows for automated removal of objects embedded or referenced in a one-to-one or one-to-many relationship. I don't believe it's documented in the ODM manual, but it's very similar to what is described in the ORM documentation for the feature.
In your case, you don't want any cascading functionality on removal of A; you want B to remain as-is.
On the flip side, you would like all references to B among A objects to be cleaned up when you manually remove a B object. Using a pre or postRemove listener is your best option for this, and provided you have indexed the reference on A, it should be a very trivial multi-update query to set the references to null where they once referred to the instance of B that is removed.
I have a php object mapping to a mongodb document(called Node) with a structure
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
class Node{
/**
* #MongoDB\Id
*/
protected $id;
/**
* #MongoDB\String
*/
protected $domain;
/**
* #MongoDB\ReferenceMany(targetDocument="NodeItem",cascade=
* {"persist"},simple="true")
*/
protected $items = array();
//getter and setters below
}
And a referenced document called, NodeItem,
class NodeItem {
/**
* #MongoDB\Id
*/
protected $id;
/**
* #MongoDB\String
*/
protected $name;
/**
* #MongoDB\ReferenceOne(targetDocument="Node", cascade={"persist"},
* simple="true")
*/
protected Node;
//setter and getters
}
As reflected by the annotations above 'Node' references MANY 'NodeItems' stored in a $items array and 'NodeItems' references ONE 'Node'. So those are bi-directional referenced collections.
My Question is how to effectively delete a few 'NodeItem' documents from its collection (based on the array of available ids), so that the deleted NodeItem documents are also deleted from the $items array references in 'Node' (cascaded delete I think is what I am asking for?).
I wrote a function that has code like this :
$qb = $this->dm->createQueryBuilder('SomeBundleBundle:NodeItem');
/*
* deletes from NodeItem collection
*/
foreach($NodeItemsArray as $itemId){
$qb->remove()->field('id')->equals($itemId)->getQuery()->execute();
}
But the above function only deletes the documents from NodeItem collection, but the associated items in the $items array of 'Node' are not deleted. Also, the {cascade:persist} in the annotations doesn't seem to help. The code is implemented in Symfony 2 framework
Some help is appreciated !
The only way to achieve that is with a listener on the onRemove event.
But has mentioned by #jmikola, you'll have to use the $dm->remove() method, and not the QueryBuilder (since it doesn't support events yet).
so, to delete the Item do:
//Get the NodeItem you want in the items array and delete it:
$items = $node->getItems();
$dm->remove($items->get(2)); //Remove the third item as an example
And register the event:
class CascadeNodeDeleterListener {
public function preRemove(LifecycleEventArgs $eventArgs) {
$odm = $eventArgs->getDocumentManager(); /* #var $odm DocumentManager */
$object = $eventArgs->getDocument();
if($object instanceOf NodeItem) {
$node = $object->getNode();
$items = $node->getItems();
$items->removeElement($object);
$class = $dm->getClassMetadata(get_class($node));
$dm->getUnitOfWork()->recomputeSingleDocumentChangeSet($class, $node);
}
}
}
In services.yml:
<service id="listener" class="CascadeNodeDeleterListener">
<tag name="doctrine.common.event_listener" event="onRemove" />
</service>
See Doctrine ODM Events from more info.
Cascade behavior in ODM is only respected by UnitOfWork operations. MongoDB does not natively support cascades and triggers (yet, anyway). In your case, query builder will construct and execute a query like the following:
db.node_items.remove({"_id": ObjectId("...")})
UnitOfWork is not involved at all (there are no staged operations or flushing) and no cascade is triggered.
On the other hand, say you had a managed $nodeItem object. Passing it to DocumentManager::remove() would invoke UnitOfWork and cause any referenced documents mapped with cascade=REMOVE or cascade=ALL to also be removed. Of course, you'd have to call flush() to execute the operations in MongoDB.
Based on your current code, the only operation that will be cascaded is DocumentManager::persist(). In practice, I assume you'd create a Node, construct and add a few NodeItems to it, and persist the Node (allowing its items to be persisted automatically).
If NodeItems only ever belong to a single Node, you may want to avoid cascade=REMOVE and simply do $nodeItem->getNode()->getItems()->removeElement($nodeItem) after you remove the $nodeItem itself, but before flush().
Also, I noticed you're initializing your collection field to an array. Later on, ODM is going to hydrate this field as a Doctrine\ODM\MongoDB\PersistentCollection instance, which could lead to ambiguity. As a best practice, you should initialize such fields as Doctrine\Common\Collections\ArrayCollection in your constructor. That way, you can always expect them to be Collection instances.
I have 2 entities: User and Avatar. Each user can choice one avatar from a list, so I think this is a One2One unidirectional relationship. The problem is that the field avatar_id is always NULL in the db when I save the form.
Let's see the code:
class User implements UserInterface
{
/**
* #var int $avatarId
*
* #ORM\Column(name="avatar_id", type="integer", nullable=true)
*/
private $avatarId;
/**
* #var Avatar
*
* #ORM\OneToOne(targetEntity="Avatar", cascade={"persist", "remove"})
*/
private $avatar;
}
When I var_dump the user object before saving, the field avatarId contains the Avatar object but the id is not saved. What I'm doing wrong?
["avatarId":"Test\UserBundle\Entity\User":private]=>
object(Test\UserBundle\Entity\Avatar)#419 (5) {
["id":"Test\User\Bundle\Entity\Avatar":private]=>
int(3)
["imageName":"Test\UserBundle\Entity\Avatar":private]=>
string(14) "death-dark.jpg"
}
You don't need the avatarId field, since the avatar field will take care of it automatically. You can use the #JoinColumn annotation to set the referencing column name explicitly.
A few things:
ID columns are typically generated with the following annotations:
/**
* #ORM\Id #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
This tells the ORM to automatically generate a value when you create a new object, thus when you dump the object it will have a value before persisting.
And a note on your OneToOne relationship, if Avatars can be used by multiple people from that list, that would be a ManyToOne relationship (Many users to one avatar, one avatar to many users).
Is there a way to add a default scope to a Zend_Db_Table_Abstract based model.
I want to be able to query a model with some conditions taken as default.
e.g.
deleted = false
order name asc
You can override the Zend_Db_Table_Abstract:: _fetch() method and modify the generated Zend_Db_Table_Select in there before retrieving the rows from the database adapter. As far as I know all fetch*-methods and find() in Zend_Db_Table_Abstract boil down to this generic row-retrieval-method (besides Zend_Db_Table_Abstract::fetchNew() naturally), so your modified code will be called everytime rows are retrieved from the database.
/**
* Support method for fetching rows.
*
* #param Zend_Db_Table_Select $select query options.
* #return array An array containing the row results in FETCH_ASSOC mode.
*/
protected function _fetch(Zend_Db_Table_Select $select)
{
$select->where('deleted = false')->order('name asc');
return parent:: _fetch($select);
}