Symfony2 - extending entities, abstract entity - php

I have 2 entities with the same fields - parent, children, order. Actually it is a mechanism and this 3 fields not applicable to content of this entity - like name, title, category etc.
I want to set this fields to one place, one class and I'm considering where should I put it. Should it be an abstract class? Or should I make a trait?
I also can use ORM\Discriminator mechanism, but I think this is for something else, not for that what I want to do.

i would make an abstract class with doctrines #MappedSuperClass annotation and shared fields and the entities extend them
here is an example with a shared created_at field
namespace Your\CoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\MappedSuperclass;
/**
* Abstract base class to be extended by my entity classes with same fields
*
* #MappedSuperclass
*/
abstract class AbstractEntity {
/**
* #var integer
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="created_at", type="datetime")
*/
private $createdAt;
/**
* Get id
* #return integer
*/
public function getId() {
return $this->id;
}
/**
* Get createdAt
* #return \DateTime
*/
public function getCreatedAt() {
return $this->createdAt;
}
/**
* Set createdAt
*
* #param \DateTime $createdAt
*
* #return AbstractEntity
*/
public function setCreatedAt($createdAt)
{
$this->createdAt = $createdAt;
return $this;
}
}
your entities both extend this class like:
class YourEntityClass extends AbstractEntity
{
in YourEntityClass the $id property must be "protected"

I had that issue some time ago. I also wanted to have nice abstraction for entities (or value objects, whatever) just to be a little lazy. But It is not the good way IMHO.
What if name from one entity has to have other length than the other? What if you want to add new field, add it to one entity, and some time later you wanted it in the other entity, but you forget to move it to abstraction?
I think entity is such autonomic thing that it is pointless and confusing to move some part of it to the abstraction.
Remember about KISS principle.

Related

Single Table Inheritance (STI) with a ManyToOne relationship

I'm pretty new to Doctrine, so any general advice is welcome. I'm trying to achieve the following:
A page can have both videos and photos. Both are media and share properties, so I thought Single Table Inheritance makes sense. Here are the relevant parts of my setup:
Page
/**
* #ORM\Entity
*/
class Page {
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="Media", mappedBy="page", cascade={"persist"})
* #var ArrayCollection|Media[]
*/
protected $media;
/**
* #return Media[]|ArrayCollection
*/
public function getMedia()
{
return $this->media;
}
}
Media
/**
* #ORM\Entity
* #ORM\Table(name="media")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="media_type", type="string")
* #ORM\DiscriminatorMap({"video" = "Video", "photo" = "Photo"})
*/
abstract class Media {
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #ORM\Column(type="string")
*/
protected $url;
/**
* #ORM\ManyToOne(targetEntity="Page", inversedBy="media")
*/
protected $page;
}
Photo
/**
* #ORM\Entity
*/
class Photo extends Media {
/**
* #ORM\Column(type="string")
*/
protected $exif;
}
Video
/**
* #ORM\Entity
*/
class Video extends Media {
/**
* #ORM\Column(type="integer")
*/
protected $length;
}
This works perfectly fine, but -and this is my question- how do I fetch all Photos or Videos of a page. I've tried adding things like
/**
* #ORM\OneToMany(targetEntity="Photo", mappedBy="page", cascade={"persist"})
* #var ArrayCollection|Photo[]
*/
protected $photos;
to Page, but this results in a schema error, since it doesn't have an inverse defined on Photo. Any help is greatly appreciated!
You can apply filter to the media collection
class Page {
.....
public function getPhotos()
{
return $this->getMedia()->filter(
function($media) {
return $media instanceof Photo;
}
);
}
public function getVideos()
{
return $this->getMedia()->filter(
function($media) {
return $media instanceof Video;
}
);
}
}
Bear in mind, it will fetch all medias in a single select query, instantiate both Photos and Videos, and then filter the result. It may affect performance, but you may benefit from doctrine cache, depending on your scenario.
If performance is a key, you may need to implement a custom methods in PageRepository to actually fetch only subset of medias using instance of dql as described in Query the Type.
I think you can solve this by using a join on the specific subclass:
$queryBuilder->select('m')
->from('Media', 'm')
->join('Photo', 'p', 'WITH', 'm.id = p.id')
->where('m.page = :page_id')
->setParameter('page_id', $pageId);
You won't be able to have a single $media property on your Page class which maps to a superclass. You will have to have a property per class. See the note in the Doctrine docs:
A mapped superclass cannot be an entity, it is not query-able and
persistent relationships defined by a mapped superclass must be
unidirectional (with an owning side only). This means that One-To-Many
associations are not possible on a mapped superclass at all.
Furthermore Many-To-Many associations are only possible if the mapped
superclass is only used in exactly one entity at the moment. For
further support of inheritance, the single or joined table inheritance
features have to be used.

JMSSerializer and inheritance Class

I have got a problem with JMS Serializer and inheritance class. When I serialize my entities it doesn't take care of JMSSerializer Annotation because of inheritance class... So How could I set JMSSerializer Annocation with class inheritance ?
Example :
/**
* Class Category
*
* #Serializer\ExclusionPolicy("all")
*/
class Category extends BaseCategory
{
/**
* #var integer $id
*
* #Serializer\Expose
*/
protected $id;
}
/**
* Class BaseCategory
*/
class BaseCategory
{
/**
* #var Dish
*/
protected $dishs;
/**
* #var string
*/
protected $name;
}
When I serialize Category, the json returned looks like :[{"dishs":[{"name":"Salade","id":5}],"name":"...","id":1}]. The ExclusionPolicy annotation is not applied.
Do you have any ideas why ? If I set all fields in the same entity, it works good, but it breaks all my template ...
Thanks
You inherit from BaseCategory - you cannot expect this to work.
Set your ExclusionPolicy in your base class - in this case your extended class will inherit from BaseCategory.

Doctrine 2 many-to-many with MappedSuperclass in Zend framework 2

I am new to Doctrine2 and trying to create entities for the following DB structure:
I want to have all machine parts as an array in one attribute of the machine class. I tried this:
class Machine {
....
/**
* #var array
* #ORM\OneToMany(targetEntity="MachineHasPart", mappedBy="machine", cascade={"persist", "remove"}, orphanRemoval=TRUE)
*/
private $parts;
....
public function getParts () {
return array_map(
function ($machineHasPart) {
return $machineHasPart->getPart();
},
$this->parts->toArray()
);
}
}
Where MachineHasPart is a #MappedSuperclass for the intermediate entities/tables (like machineHasCylinder etc), but it failed with:
An exception occurred while executing 'SELECT FROM machineHasPart t0'.
Should I restructure my database to use ORM here? Or there is a solution for my case?
You cannot query a #MappedSuperClass. This is also mentioned in the Doctrine2 documentation in chapter 6.1. Mapped Superclasses:
A mapped superclass cannot be an entity, it is not query-able and persistent
This means you have to either change the target entity to something queryable or you have to make MachineHasPart to a entity and change to single table inheritance.
When I look at your database structure I would suggest changing your Machine entity to have three independent relationships for the parts. One for Belt, one for Cylinder and one for Gear.
Then instead of a generic getParts you will have three methods getBelts, getCylinders and getGears.
If that is really not what you want then you can leave a comment.
UPDATE
You can solve it also with class inheritance. First make a base class Part that is also an entity and use it in the other classes Belt, Cylinder and Gear:
Part:
<?php
namespace Machine\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Part
*
* #ORM\Entity
* #ORM\Table("part")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="discriminator", type="string")
* #ORM\DiscriminatorMap({
* "part" = "Part",
* "gear" = "Gear",
* "cylinder" = "Cylinder",
* "belt" = "Belt",
* })
* #property int $id
*/
class Part
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var Machine
* #ORM\ManyToOne(targetEntity="Machine\Entity\Machine", inversedBy="parts")
* #ORM\JoinColumn(name="machine_id", referencedColumnName="id", nullable=true)
*/
protected $machine;
/**
* Get id.
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set id.
*
* #param int $id
* #return self
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
//... add setters and getters for machine as normal ...
}
Extend this class in your other parts:
Belt:
<?php
namespace Machine\Entity;
/**
* Belt
*
* #ORM\Entity
*/
class Belt extends Part
{
}
Cylinder:
<?php
namespace Machine\Entity;
/**
* Cylinder
*
* #ORM\Entity
*/
class Cylinder extends Part
{
}
Gear:
<?php
namespace Machine\Entity;
/**
* Gear
*
* #ORM\Entity
*/
class Gear extends Part
{
}
Now in your machine relate to the parts like as follows.
Machine:
<?php
namespace Machine\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Machine
*
* #ORM\Entity
* #ORM\Table("machine")
* #property int $id
*/
class Machine
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* Get id.
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set id.
*
* #param int $id
* #return self
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
/**
* #var Collection
* #ORM\OneToMany(targetEntity="Machine\Entity\Part", mappedBy="machine")
*/
protected $parts;
public function __constuct()
{
$parts = new ArrayCollection();
}
/**
*
* #return Collection
*/
public function getParts()
{
return $this->parts;
}
//... add setters and getters for parts as normal ...
}
Extend this class in your other parts:
Reading further in the Doctrine2 documentation in chapter 6.1. Mapped Superclasses (referred to by #Wilt):
... Furthermore Many-To-Many associations are only possible if the mapped superclass is only used in exactly one entity at the moment...
This means in this case the ORM mapping doesn't help. I cannot gather the data of all three entities MachineHasCylinder, MachineHasBelt and MachineHasGear through a MappedSupperclass at the same time.
I think using DQL or Native SQL is the only solution for this problem.

How to add extra fields and related getters/setters from an external file to one or many Symfony/Doctrine Entities, without inheritance?

I need to add some extra fields to a bunch of entities/classes and i would like to get those fields included in each entity/class.
I could write all the fields in each class and it will work but i'm looking for a way to centralize the definitions of the fields and related methods, just to make it more maintainable and let me distribute modification, just editing a single file.
I got it working using inheritance but in that manner, there will be a parent class/table, which will keep track of the children classes/tables either with *#InheritanceType("SINGLE_TABLE")* or #InheritanceType("JOINED") and this could dramatically affect the performances, as the number of records will increase.
I do not have any advantage in using inheritance here, as well.
A simple example to clarify the problem:
Code for fields i want to include in some class
filename: myfields.php
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="created_at",type="datetime")
*/
protected $createdAt;
/**
* #ORM\Column(name="modified_at", type="datetime")
*/
protected $modifiedAt;
public function getId()
{
return $this->id;
}
public function setCreatedAt($createdAt)
{
$this->createdAt = $createdAt;
return $this;
}
public function getCreatedAt()
{
return $this->createdAt;
}
public function setModifiedAt($modifiedAt)
{
$this->modifiedAt = $modifiedAt;
return $this;
}
public function getModifiedAt()
{
return $this->modifiedAt;
}
filename: Car.php
#ORM\Entity()
#ORM\Table(name="car")
class Car
{
/**
* #ORM\Column(name="model", type="string")
*/
protected model;
/**
* #ORM\Column(name="price", type="decimal")
*/
protected price;
// related setter and getters methods
//Include myfields.php
}
filename: Animal.php
#ORM\Entity()
#ORM\Table(name="animal")
class Animal
{
/**
* #ORM\Column(name="species", type="string")
*/
protected species;
// related setter and getters methods
//Include myfields.php
}
I don't want to make something like class Animal extends MyFields but I would like just to import and let store the common informations in each entity table.
If i will need to add some extra-field or just to modify some method, i will modify just "myfields.php" and it will be propagated to all the Entities that include it.
For sure in symfony i have to launch: php app/console doctrine:schema:update --force
and the structure will be modified fo all the classes that include it.
You can solve this problem using traits (when you are using PHP 5.4+). This will allow for horizontal reuse of your properties and accessors, also called multiple inheritance. You can simply define all you common fields in the Trait and import the Trait in your consuming class.
Trait CommonFieldsTrait
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="created_at",type="datetime")
*/
protected $createdAt;
/**
* #ORM\Column(name="modified_at", type="datetime")
*/
protected $modifiedAt;
public function getId()
{
return $this->id;
}
...
}
#ORM\Entity()
#ORM\Table(name="animal")
class Animal
{
use CommonFieldsTrait;
/**
* #ORM\Column(name="species", type="string")
*/
protected species;
// related setter and getters methods
}

How to relate entities in Symfony 2 and Doctrine?

How can i create a relationship between entities with Symfony 2 and Doctrine? I'm only able to create standalone entities. Maybe someone can help me figure this out using the entity generator? I want to:
Create two entities: Post and Category. A Post is part of a Category.
Create a Tag entity: A Post can have many Tags.
A practical example is covered in Symfony2 docs here:
http://symfony.com/doc/current/book/doctrine.html#entity-relationships-associations
To elaborate, taking the first example, you need to create a OneToMany relationship between your Category object and your Post object:
Category.php:
<?php
namespace Your\CustomBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Table(name="category")
* #ORM\Entity()
*/
class Category
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Post", mappedBy="category")
*/
public $posts;
/**
* Constructor
*/
public function __construct()
{
$this->posts = new ArrayCollection();
}
/**
* #return integer
*/
public function getId()
{
return $this->id;
}
}
Post.php
<?php
namespace Your\CustomBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="post")
* #ORM\Entity()
*/
class Post
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="posts")
*/
public $category;
/**
* #return integer
*/
public function getId()
{
return $this->id;
}
}
This should get you started. I've just written this so there might be errors :s
I'm making properties $posts and $category public here for brevity; however you'd probably be advised to make these private and add setters/getters to your classes.
Also note that $posts is an array-like Doctrine ArrayObject class especially for arrgregating entities, with methods like $category->posts->add($post) etc.
For more detail look into association mapping in the Doctrine documentation. You'll probably need to set up a ManyToMany relationship between Posts and Tags.
Hope this helps :)
You don't create the relationships with the entity generator itself.
Once the entity classes themselves exist (created either with the entity generator or written by hand), you then edit them to add the relationships.
For example, with your Post having many Tags example
namespace Your\Bundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Your\Bundle\Entity\Post
*
* #ORM\Table(name="post")
* #ORM\Entity
*/
class Post
{
/**
* #var \Doctrine\ORM\PersistentCollection
*
* #ORM\OneToMany(targetEntity="Tag", mappedBy="post", cascade={"persist"})
*/
private $tags;
}
See Doctrine's Documentation for more information about specifying relationships.

Categories