Doctrine criteria on subclasses of abstract entity - php

I have an abstract entity which is inherited by two subclasses. I'm using it in association with other entity and now I want to create criteria to filter out objects with the property set to some value.
My superclass:
/**
* #ORM\MappedSuperclass
* #ORM\Entity(repositoryClass="Foo\Bar\AddressRepository")
* #ORM\InheritanceType(value="SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="discriminator_type", type="string")
* #ORM\DiscriminatorMap({"homeaddress" = "HomeAddress", "companyaddress" = "CompanyAddress"})
*/
abstract class AbstractAddress
{
/**
* #var bool
* #ORM\Column(type="boolean", options={"default": 0})
*/
protected $active;
}
Subclasses:
/**
* #ORM\Entity(repositoryClass="Foo\Bar\AddressRepository")
*/
class HomeAddress extends AbstractAddress
{
}
/**
* #ORM\Entity(repositoryClass="Foo\Bar\AddressRepository")
*/
class CompanyAddress extends AbstractAddress
{
}
Entity with association:
/**
* #ORM\Entity(repositoryClass="Foo\Bar\CustomerRepository")
*/
class Customer
{
/**
* #var ArrayCollection
* #ORM\ManyToMany(targetEntity="Foo\Bar\AbstractAddress", cascade={"all"}, orphanRemoval=true)
*/
private $addresses;
/**
* #return Collection|null
*/
public function getAddresses(): ?Collection
{
return $this->addresses->matching(
Criteria::create()->where(Criteria::expr()->eq('active', true))
);
}
}
This setup produces exception:
ResultSetMapping builder does not currently support your inheritance scheme.
The problem is that it tries to instantiate AbstractAddress instead of one of the subclasses respectively.
Is there any way to achieve this filtering using Criteria API?
I know that I can use filter() instead, but this function doesn't impact SQL query, so I would rather have this solved used criteria.

Related

Symfony doctrine many to many unique

I have to entity with manyToMany relation, that i have transform in two OneToMany. So I have create an other Entity called CollanaCollezionista into that i have some attribute.
I want that the couple collana/collezionista is unique, how can i do this on doctrine and symfony?
/**
* Collana
*/
class Collana
{
private $id;
private $titolo;
/**
* #MaxDepth(1)
* #ORM\OneToMany(targetEntity="CollezionistaCollana", mappedBy="collana")
*/
private $collezionisti;
}
/**
* Collezionista
*/
class Collezionista
{
private $id;
private $user;
/**
* #ORM\OneToMany(targetEntity="CollezionistaCollana", mappedBy="collezionista")
*/
private $collane;
}
So I have an other entity, called CollezionistaCollana. How can i set that the couple collezionista-collana is unique?
Answer:
#UniqueConstraint do the stuff.
use Doctrine\ORM\Mapping\UniqueConstraint as UniqueConstraint;
/**
* CollezionistaCollana
*
* #ORM\Table(name="collezionista_collana",uniqueConstraints={#‌UniqueConstraint(nam‌​e="unique_hold", columns={"idCollezionista", "idCollana"})})
* #ORM\Entity
*/
class CollezionistaCollana
{
// Class details
}

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.

Doctrine entity inheritance

I have an entity which I want to use as a base class for other entities (unknown at this time) and I need to store relationships in the base entity:
/**
* #ORM\Entity
* #ORM\Table(name="CMS_content")
*/
class BaseContent {
/**
* #ORM\ManyToOne(targetEntity="BaseContent")
* #ORM\JoinColumn(name="parent", referencedColumnName="id", unique=false)
*/
protected $parent;
/**
* #ORM\ManyToOne(targetEntity="ContentType")
* #ORM\JoinColumn(name="content_type", referencedColumnName="id", unique=false)
*/
protected $contentType;
...
};
/**
* #ORM\Entity
* #ORM\Table(name="CMS_whateverSpecializedContent")
*/
class WhateverSpecializedContent extends BaseContent {};
I cannot use#ORM\InheritanceType("JOINED") because I want to be able to create arbitrary number of subclasses later without touching the base class. I also need to have the base class in a separate database table so the relationship would make sense.
What other options do I have to manage these kind of structure?
Instead of using entity inheritance I ended up using the delegate design pattern. Both Content and BaseContent implements a common interface and the BaseContent delegate the functionality to a joined Content entity.
Now every subclass of this BaseContent will have a joined Content entity and can be used where an IContent is expected.
interface IContent {...}
/**
* #ORM\Entity
* #ORM\Table(name="CMS_content")
*/
class Content implements IContent {
/**
* #ORM\ManyToOne(targetEntity="BaseContent")
* #ORM\JoinColumn(name="parent", referencedColumnName="id", unique=false)
*/
protected $parent;
/**
* #ORM\ManyToOne(targetEntity="ContentType")
* #ORM\JoinColumn(name="content_type", referencedColumnName="id", unique=false)
*/
protected $contentType;
...
};
/**
* #ORM\Entity
* #ORM\Table(name="CMS_whateverSpecializedContent")
*/
class WhateverSpecializedContent extends BaseContent {};
/**
* #ORM\MappedSuperclass
*/
abstract class BaseContent implements IContent {
/**
* #ORM\OneToOne(targetEntity="Content", cascade={"persist", "merge", "remove"})
* #ORM\JoinColumn(name="content", referencedColumnName="id", unique=false)
*/
private $content;
public function implementedMethod() {
$this->content->implementedMethod();
}
};

Categories