Doctrine entity inheritance - php

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();
}
};

Related

Doctrine criteria on subclasses of abstract entity

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.

doctrine 2: inheritance mapping join with parent table

I have a base table like this:
class BaseProduct
{
/**
* #ORM\ManyToOne(targetEntity="ProductBundle\Entity\Category", inversedBy="baseProducts")
* #ORM\JoinColumn(name="menu_category_id", referencedColumnName="id", nullable=true)
**/
protected $category;
// ...
and another entity that inherit from BaseProduct
class ChildProduct extends BaseProduct
{
/**
* #ORM\ManyToOne(targetEntity="ProductBundle\Entity\Category", inversedBy="childProducts")
* #ORM\JoinColumn(name="menu_category_id", referencedColumnName="id", nullable=true)
**/
protected $category;
and Category entity:
class Category
{
/**
* #ORM\OneToMany(targetEntity="ProductBundle\Entity\BaseProduct", mappedBy="category")
* #ORM\OrderBy({"position"= "ASC"})
*/
private $baseProducts;
/**
* #ORM\OneToMany(targetEntity="ProductBundle\Entity\ChildProduct", mappedBy="category")
* #ORM\OrderBy({"position"= "ASC"})
*/
private $childProducts;
My ChildProduct table has one column named id and referenced to BaseProduct id's. Now I want to join Category with ChildProduct with this query:
$qb->select('mc', 'cp')
->from('ProductBundle:Category', 'mc')
->leftJoin('mc.childProducts', 'cp')
// .....
when I execute this query it gives this error:
ContextErrorException in SqlWalker.php line 922:
Notice: Undefined index: childProducts
While I have childProducts in Category.
Now I have two questions:
am I able to query on a parent field that does not exists in child table.
what's wrong with my query
Check doctrine chapter Inheritance Mapping: Inheritance Mapping
Following the documentation try this (I didn't test the code)
/** #ORM\MappedSuperclass */
class BaseProduct
{
/**
* #ORM\ManyToOne(targetEntity="ProductBundle\Entity\Category", inversedBy="baseProducts")
* #ORM\JoinColumn(name="menu_category_id", referencedColumnName="id", nullable=true)
**/
protected $category;
Now Doctrine knows that your class is a base class for others.
All you have to do it extend base class and add annotation #ORM\Entity
/**
* #ORM\Entity()
**/
class ChildProduct extends BaseProduct
{
/**
* #ORM\ManyToOne(targetEntity="ProductBundle\Entity\Category", inversedBy="childProducts")
* #ORM\JoinColumn(name="menu_category_id", referencedColumnName="id", nullable=true)
**/
protected $category;
I think you can remove from ChildProduct $category field. It should work also.

Symfony - how to make Doctrine search in abstract mapped superclass?

I am building an online shop for plastic model makers (hence the 'model').
I have a Product mapped superclass because I want all products (models, tools, paints etc. to have name, price and description):
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\MappedSuperclass;
use Gedmo\Mapping\Annotation as Gedmo; // gedmo annotations
/** #MappedSuperclass */
abstract class Product
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
* #ORM\Column(type="string", length=100, nullable=false)
*/
protected $name;
/**
* #var int
* #ORM\Column(type="decimal", scale=2, nullable=false)
*/
protected $price;
/**
* #var string
* #ORM\Column(type="text", nullable=true)
*/
protected $description;
/**
* #Gedmo\Timestampable(on="create")
* #ORM\Column(type="datetime")
*/
private $createdAt;
/**
* #ORM\Column(type="datetime")
* #Gedmo\Timestampable(on="update")
*/
private $updatedAt;
//getters and setters...
}
Then, there's a Model class, for plastic models, this extends the Product class but also has a Category for being able to search from cars, aircraft, ships etc:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="model")
*/
class Model extends Product
{
/**
* #var Category
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Category", inversedBy="models")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id", nullable=true)
*/
private $category;
/**
* #return Category
*/
public function getCategory()
{
return $this->category;
}
/**
* #param Category $category
*/
public function setCategory($category)
{
$this->category = $category;
}
}
There will of course be more classes extending Product mapped superclass. Now I want to select the 10 most recently added products, regardless of their kind (models, tools, paints).
How can I tell Doctrine to give me the last 10 added products? Of course I tried something like:
$this->getDoctrine()->getManager()->getRepository("AppBundle:Product");
but of course it throws an exception saying that
SQLSTATE[42S02]: Base table or view not found: 1146 Table
'modeller_app.product' doesn't exist
which obviously is true, because Product is an abstract class and not actually an entity. How can I solve this problem?
You need to use class table inheritance.
Here is a example:
/**
* #ORM\Entity
* #ORM\Table(name="product")
* #InheritanceType("JOINED")
* #DiscriminatorColumn(name="discr", type="string")
* #DiscriminatorMap({"product" = "Product", "model" = "Model"})
*/
class Product
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
* #ORM\Column(type="string", length=100, nullable=false)
*/
protected $name;
...
}
/**
* #ORM\Entity
* #ORM\Table(name="product")
*/
class Model extends Product
{
// Custom properties
...
}
Now you can query the Product entity and it will return the result from all entities that inherits it.

Doctrine doesn't generate inherited-class's properties

I have one abstract MappedSuperClass and another class called "User" which is a child of this MappedSuperClass. The problem is that doctrine doesn't generate the User class's properties. Only the MappedSuperClass's properties. Why?
<?php
namespace IsCoconut\Models;
use DateTime;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\MappedSuperclass;
/** #MappedSuperclass */
abstract class BaseEntity
{
/**
* #var int
* #Id
* #Column(type="integer")
* #GeneratedValue(strategy="AUTO")
*/
protected $id;
/** #Column(type="boolean")
* #var boolean
*/
protected $deleted = false;
/** #Column(name="creation_date_time", type="datetime")
* #var DateTime
*/
protected $creationDateTime;
protected function __construct()
{
$this->creationDateTime = new DateTime();
}
}
And this is my Entity which should be generated in database by Doctrine
<?php
namespace IsCoconut\Models;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
/**
* #Entity
* #Table(name="users")
*/
class User extends BaseEntity
{
/**
* #var string
* #ORM\Column(type="string", length=512)
*/
private $forename;
/**
* #var string
* #ORM\Column(type="string", length=512)
*/
private $surname;
/**
* #var string
* #ORM\Column(type="string", length=512, unique=true, nullable=false)
*/
private $email;
/**
* #var string
* #ORM\Column(type="string", length=512, unique=true, nullable=false)
*/
private $passwordHash;
}
This is output of doctrine orm:schema-tool:create --dump-sql
CREATE TABLE users (id INT NOT NULL, deleted BOOLEAN NOT NULL, creation_date_time TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id));
CREATE SEQUENCE users_id_seq INCREMENT BY 1 MINVALUE 1 START 1;
The User's class entity properties are missing! Why?
Try this
* #ORM\Entity
* #ORM\Table(name="User")
Actually, this might be the solution:
change:
abstract class BaseEntity
to:
class BaseEntity
See if that works. It might not, please try it.
When I removed all #ORM\ prefixes, it works now
I had the same problem, and found it was due to properties set as private.
/**
* #ORM\MappedSuperclass
*/
class BaseClass
{
/**
* #var string
* #ORM\Column(type="string", length=250, nullable=false)
*/
protected $channel;
//private $channel;
}
The mapping on your abstract class should be * #ORM\MappedSuperclass not /** #MappedSuperclass */. The mapping is done by Doctrine so the ORM annotation is important here.
You also need to ensure your properties in your abstract class are protected (which you already said they were)

Extending an entity class in doctrine

I'm trying to extend a class used as a doctrine entity, but for some reason I keep getting the error:
There is no column with name 'location_id' on table 'admin_subdivisions'
When I say extend, I mean at the php level NOT the database level. I simply want to create another table, with an extra column. I have several entities which extend the following abstract class
abstract class LocationProxy
{
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="Location", cascade={"ALL"}, fetch="LAZY")
* #ORM\JoinColumn(name="location_id", referencedColumnName="location_id", nullable=false)
*
* #var Location
*/
protected $location;
}
None of these second level classes give me any problems. Now, I want to extend this second level class
/**
* #ORM\Entity()
* #ORM\Table(name="admin_divisions")
*/
class AdminDivision extends LocationProxy
{
}
with this
/**
* #ORM\Entity()
* #ORM\Table(name="admin_subdivisions")
*/
class AdminSubDivision extends AdminDivision
{
}
but, it produces the error. Can anybody point out what I am doing wrong?
here is the Location class definition
/**
* #ORM\Entity()
* #ORM\Table(name="locations")
*/
class Location
{
/**
* #ORM\Id
* #ORM\Column(name="location_id", type="integer", options={"unsigned"=true})
*
* #var int
*/
private $id;
}
You must specify the inheritence type so that doctrine knows how to build the tables for the subclasses: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#mapped-superclasses
In you case you will need to add the following Annotations to your abstract LocationProxy Class:
#ORM\Entity
#ORM\InheritanceType("JOINED")
#ORM\DiscriminatorColumn(name="discr", type="string")
Or choose a different inheritance type
So the whole class will look like this:
/**
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="discr", type="string")
*/
abstract class LocationProxy {
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="De\Gregblog\Receipts\Location", cascade={"ALL"}, fetch="LAZY")
* #ORM\JoinColumn(name="location_id", referencedColumnName="location_id", nullable=false)
*
* #var Location
*/
protected $location;
}

Categories