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.
Related
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.
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;
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.
I'm using Doctrine ORM in a project that I am working on. Although the idea of providing an object interface to the db is nice, I have a question about the implementation of the entity classes.
Let's consider this example of a User entity:
<?php
/**
* #Entity #Table(name="users")
**/
class User
{
/**
* #Id #GeneratedValue #Column(type="integer")
* #var int
**/
protected $id;
/**
* #Column(type="string")
* #var string
**/
protected $name;
/**
* #OneToMany(targetEntity="Bug", mappedBy="reporter")
* #var Bug[]
**/
protected $reportedBugs = null;
/**
* #OneToMany(targetEntity="Bug", mappedBy="engineer")
* #var Bug[]
**/
protected $assignedBugs = null;
// .. (other code)
}
Now, its all fine and dandy, but I was wondering what would happen if I make a spelling mistake in one of the comments e.g. I write:
#Table(name="users)
instead of
#Table(name="users")
the IDE will not complain since its a comment, and I'll only get an error when I run the "generate entities" command (that does the magic in the background of generating code, creating tables, columns and relationships).
So my question is: Aren't entity definitions for ORM error prone? since there really is no check on the syntax if its valid, and errors are generated only at 'generate' time. Is there a way to automate / check for mistakes earlier on while development?
Thanks!
IMO, The issue that you highlighted can be compared to using wrong variables in PHP ($var1 instead of $var2). For that matter, in all dynamic languages.
But you can avoid the problem highlighted by you, if you write unit tests.
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.