doctrine 2: inheritance mapping join with parent table - php

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.

Related

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.

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

Doctrine 2 OneToOne from PFK of another OneToOne association

I have one question. I'm using Doctrine 2.0 and i want to know if is possible to made OneToOne association by using PFK created by another OneToOne assotiation as you can see on the picture below.
My code looks like this:
User class:
/**
* #ORM\Entity()
*/
class User extends \Kdyby\Doctrine\Entities\IdentifiedEntity
{
/**
* #ORM\OneToOne(targetEntity="AllianceMember", mappedBy="user")
* var AllianceMember
*/
protected $allianceMembership;
}
AllianceMember class:
/**
* #ORM\Entity()
*/
class AllianceMember extends \Kdyby\Doctrine\Entities\BaseEntity
{
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="User", inversedBy="allianceMembership")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
* #var User
*/
protected $user;
/**
* #ORM\OneToOne(targetEntity="AllianceRole", mappedBy="allianceMember")
* var AllianceRole
*/
protected $role;
AllianceRole class:
/**
*
* #ORM\Entity()
*/
class AllianceRole extends \Kdyby\Doctrine\Entities\BaseEntity
{
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="AllianceMember", inversedBy="role")
* #ORM\JoinColumn(name="role_id", referencedColumnName="user_id", nullable=false)
* #var AllianceMember
*/
protected $allianceMember;
}
I'm getting this error, when I'm trying to get instance of User entity:
The column user_id must be mapped to a field in class App\Entity\AllianceMember since it is referenced by a join column of another class.
Is it even possible?
Thanks.
Yes, using mapped entities for your #Id has been possible since doctrine 2.1 see Primary key and foreign key at the same time with doctrine 2
I think you simply need to remove some of your JoinColumn statements. Since #user is already designated as your #Id, it isn't necessary to specify user_id as the JoinColumn:
/**
* #ORM\Entity()
*/
class AllianceMember extends \Kdyby\Doctrine\Entities\BaseEntity
{
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="User", inversedBy="allianceMembership")
* #var User
*/
protected $user;
/**
* #ORM\OneToOne(targetEntity="AllianceRole", mappedBy="allianceMember")
* var AllianceRole
*/
protected $role;
and
/**
*
* #ORM\Entity()
*/
class AllianceRole extends \Kdyby\Doctrine\Entities\BaseEntity
{
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="AllianceMember", inversedBy="role")
* #var AllianceMember
*/
protected $allianceMember;
}
The id of the AllianceMember is the user_id, and the id of the AllianceRole is the id of the AllianceMember, which is user_id.

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

Doctrine and inheritance mapping: association on a child entity

I'm trying to query an entity and joined an other entity which is a child in an inheritance mapping structure.
The situation is:
I have a parent entity (eg. Animal)
I have a child entity (eg. Dog)
I have an entity with a 1:1 association to the child (Dog House)
I want to query all the dog houses and doctrine to get me their dog owners as well.
The simple DQL query I wrote is:
SELECT o FROM MyBundle:DogHouse h JOIN h.dog d
The error is:
Notice: Undefined index: id in
../vendor/doctrine/lib/Doctrine/ORM/Query/SqlWalker.php
line 779, class: ErrorException
Looking at the stack trace it seems that doctrine is trying to join with the id on the child entity instead of the parent
Here is the classes illustrating this case
/**
* Animal
*
* #ORM\Entity
* #ORM\Table(name="animal")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="integer")
* #ORM\DiscriminatorMap({"1"="cat", "2"="dog"})
*/
class Animal
{
/**
* #ORM\Column(name="animal_id", type="integer", nullable=false)
* #ORM\Id
*/
protected $id;
/**
* #ORM\Column(name="name", type="string", length=200, nullable=false)
*/
protected $name;
/* getters, setters... */
}
/**
* Dog
*
* #ORM\Entity
* #ORM\Table(name="dog")
*/
class Dog extends Animal
{
/**
* #ORM\OneToOne(targetEntity="DogHouse", inversedBy="dog")
*/
private $dogHouse;
/* getters, setters... */
}
/**
* Dog House
*
* #ORM\Table(name="dog_house")
* #ORM\Entity
*/
class DogHouse
{
/**
* #ORM\Column(name="dog_house_id", type="integer", nullable=false)
* #ORM\Id
*/
private $id;
/**
* #ORM\OneToOne(targetEntity="Dog", mappedBy="doghouse")
*/
private $dog;
/* getters, setters... */
}
Note:
This post relates to the exact same problem and wasn't answered
Your mappings are set up incorrectly...
The one To one relation between Dog / Doghouse should be as follows..
class DogHouse....
/**
* #ORM\OneToOne(targetEntity="Dog", mappedBy="doghouse")
*/
private $dog;
Make sure your mappedBy="doghouse"

Categories