Doctrine 2 how to index collections - indexing

Can you explain me please, how to index Doctrine entity collections? I have an entity Article with collection of related Categories defined as #ManyToMany relationship. In this annotation I have parameter indexBy="id" which I hope creates an array of Categories indexed by its IDs. But the Categories collection is indexed like an php array from 0.
The code looks like:
/**
* #ORM\ManyToMany(targetEntity="Category", inversedBy="articles", indexBy="id", cascade={"persist", "remove"})
* #ORM\JoinTable(name="articles_categories")
*/
private $categories;
Can you tell me what am I doing wrong? THX.

Related

Using Doctrine and Symfony to create Polymorphic like associations

I'm attempting to have an Fileable trait that will give provide an Entity with methods to CRUD Files based on the File Entity mentioned below.
After reading the documentation on Doctrine and searching the internet, the best I could find is Inheritance Mapping but these all require the subclass to extend the superclass which is not ideal as the current Entities already extend other classes. I could have FileFoo entity and a FileBar entity but this gets too messy and requires an extra join (super -> sub -> entity).
Alternatively, I could have a File Entity which has many columns for Entities (so foo_id for the Foo object, bar_id for the bar object and so on) but this gets messy and would require a new column for every entity that I'd want to add the Fileable trait too.
So to the questions:
Am I thinking about how I want to hold data incorrectly?
Is there some features/functions in Doctrine/Symfony that I've missed?
Do you think I feature like this would be added if I were to fork Doctrine to add this feature, also where should I look?
<?php
/**
* File
*
* #ORM\Table()
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks()
*/
class File
{
/**
* #var integer
*
* #ORM\Column(type="integer")
* #ORM\Id()
* #ORM\GeneratedValue()
*/
protected $id;
/**
* #var string
*
* #ORM\Column(type="string")
*/
protected $entityName;
/**
* #var string
*
* #ORM\Column(type="string")
*/
protected $entityId;
...
I accomplished a similar thing using Inheritance defined in traits, which alongside interfaces, basically gave me what a multiple extend would give.
Take a look at embeddables or you could use traits.

Symfony2 - Entity and Relationship Annotation

This is classic method for the entity relationship;
/**
* #ORM\OneToMany(targetEntity="Product", mappedBy="category")
*/
protected $products;
and other entity;
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
protected $category;
But, when I use this method, I can't use limit function. All results are taken. But, I should use sql limit function. So, I use a repository class. I wrote all sql code again. I used join function again. Is the relationsip annotation necessary in this situation? Do I still need to add this annotation?
Unfortunately, there is no way of doing what are you asking. You are gonna have to write a custom DQL, preferably inside a custom repository in order to fetch exactly what you want. Of course you still need the annotation itself, as is required by Doctrine to do many things under the hood and, in general, to keep a good relationship model between your entities.
The most close annotation you have to control the way in which relationships are joined when using automatic method is the OrderBy one:
/*
* #OneToMany(...)
* #OrderBy({"name" = "ASC"})
*/
protected $relation;

Doctrine 2 - single table inheritance - access property of child entity

I have simple table inheritance on my entities, say something like:
/**
* #InheritanceType("SINGLE_TABLE")
* #DiscriminatorColumn(name="type", type="string")
* #DiscriminatorMap({"base"="BaseArticle", "extended"="ExtendedArticle"})
*/
class BaseArticle extends \Models\BaseModel{
...
}
class ExtendedArticle extends BaseArticle{
/**
* #column(type="string")
*/
protected $extendedProperty;
}
I need to do a query across all article types, but in some types restrict the query by some property, that is in extended ExtendedArticle, ie:
SELECT a FROM BaseArticle a WHERE (a INSTANCE OF BaseAricle) OR (a INSTANCE OF ExtendedArticle AND a.extendedProperty = "xy")
Which gives me following exception:
[Semantical Error] line 0, col 406 near 'extendedProperty="xy"))': Error: Class Models\Articles\BaseArticle has no field or association named location
So question is, how to access child's properties in query to parent class?
You can't. Here's a workaround:
SELECT a
FROM BaseArticle a
WHERE
a INSTANCE OF BaseAricle
OR a.id IN (
SELECT ea.id
FROM ExtendedArticle ea
WHERE ea.extendedProperty = "xy"
)
I have solved this by adding $extendedProperty into BaseArticle and using getters/setters to effectively hide this property in my BaseArticle entity and lifecycle callbacks to handle NULLABLE. But this is really bad solution as the inheritance is almost pointless.

How to manage deserialized entities with entity manager?

I am using JMSSerializerBundle to serialize my entities to json and deserialize json into entities, but I think this question applies for any deserialization techniques.
For example, this schema:
class Order
{
private $id;
/**
* #Serializer\Type("ArrayCollection<MyBundle\Entity\Order\Item>")
* #ORM\OneToMany(targetEntity="\MyBundle\Entity\Order\Item", mappedBy="order", cascade={"persist"})
*/
private $items;
}
class Item
{
private $id;
/**
* #ORM\ManyToOne(targetEntity="\MyBundle\Entity\Order", inversedBy="items")
*/
private $order;
/**
* #var integer $amount
* #Serializer\Type("integer")
* #ORM\Column(name="amount", type="integer")
*/
private $amount;
}
Maps to this json: {"id":1,"items":[{"id":1,"amount":100}, {"id":2,"amount":200}]} and the same json is properly deserialized into an object of type MyBundle:Order that has a colletion of two MyBundle:Order/Item objects.
The problem is that when I try to persist this object, new entries are created in the database, rather than updating existing, ignoring the ids. How do I tell entity manager that theses objects should be updated, rather that created?
Update. Generally EntityManager::merge solution (as suggested by DaveM) is fine. But you must only merge existing objects. For example, if you have a json that represents a new Order entity that is connected to existing Order\Item entities
{"id":null,"items":[{"id":1,"amount":100}, {"id":2,"amount":200}]}
In this case you cannot just merge an Order object like this:
$em->merge($order), because order is a new entity and entity manager will attempt to find an Order object with id = null and you will end up with a new Order and empty items array. So the solution is to loop the Order::$items array and merge each item individually. Then a new order will be created and connected with existing items.
You need to use the merge() method on the EntityManager as merging entities refers to the merging of entities into the context of an EntityManager so that they can become managed again. In order to merge the state of an entity into an EntityManager use the EntityManager#merge($entity) method. The state of the passed entity will be merged into a managed copy of this entity and this copy will subsequently be returned.
$detachedEntity = unserialize($serializedEntity);
$entity = $em->merge($detachedEntity);
Also be sure to note when you want to serialize/unserialize entities you have to make all entity properties protected, never private. The reason for this is, if you serialize a class that was a proxy instance before, the private variables won’t be serialized and a PHP Notice is thrown.
More information can be found in the doctrine documentation here:
http://doctrine-orm.readthedocs.org/en/2.0.x/reference/working-with-objects.html#merging-entities
I know this question is three years old, but it mislead me to think the only answer was using the merge operation. I'd like to add my two cents:
The JMSSerializerBundle includes an object constructor for Doctrine entities. When you enable this constructor, the deserialized entities are managed entities that can be persisted(with $em->persist($entity)).
Please check this comment to understand other benefits from this.
And here is how you can enable it.

setfirstresult & setmaxresult in child collection

I have and entity lets call it Entity, and a Child collection Children.
I have a screen where the user has the Entity information, and a list with the Children collection, but that collection can be get very big, so i was thinking about using paging: get the first 20 elements, and lazy load the next only if the user explicitly presses the next button.
So i created in the Entity Repository a function with this signature:
IEnumerable<Child> GetChildren(Entity entity, int actualPage, int numberOfRecordsPerPage)
I need to use the setfirstresult and setmaxresult, not in the Agregate root Entity, but in the child collection. But when i use those two configurations, they allways refer to the entity type of the HQL/Criteria query.
Other alternative would be to create a HQL/Criteria query for the Child type, set the max and first result, then filter the ones who are in the Entity Children collection (by using subquery).
But i wasn't able to do this filter. If it was a bidirectional association (Child refering the parent Entity) it would be easier.
Any suggestions?
Any
One approach would be to create a query that returns results from both tables by doing a group by. This approach would allow you to apply paging on data that will come from the children collection and have a common factor (the entity's ID in each row) while you keep your starting point (the Entity object). What I mean is something like that:
public IList<object> GetData(int entityID, int actualPage, int numberOfRecordsPerPage)
{
ICriteria criteria = _repository.Session.CreateCriteria<FlowWhatIfProfile>("entity")
.CreateCriteria("Children", "children", NHibernate.SqlCommand.JoinType.InnerJoin)
.Add(Restrictions.Eq("children.EntityID", entityID));
ProjectionList pl = Projections.ProjectionList();
pl.Add(Projections.GroupProperty("children.Id"));
pl.Add(Projections.GroupProperty("children.Property1"));
pl.Add(Projections.GroupProperty("children.Property2"));
pl.Add(Projections.GroupProperty("children.Property2"));
pl.Add(Projections.GroupProperty("entity.Id"));
return criteria.SetProjection(pl)
.SetFirstResult(actualPage * numberOfRecordsPerPage)
.SetFetchSize(numberOfRecordsPerPage)
.List<object>();
}
The drawback would be that your returned data are a list of arrays (you will have to cast object to object[]) but you can overcome that by using the AliasToBean functionality that lest NHibernate project these arrays to strongly typed objects that you define.
it's simple with CreateFilter
session.CreateFilter(entity.children, "")
.SetFirstResult(0)
.SetMaxResults(20)
.List();
http://knol.google.com/k/fabio-maulo/nhibernate-chapter-16/1nr4enxv3dpeq/19#