Doctrine 2 inheritance mapping: can NULL be used as a discriminator? - php

I have the following hierarchy of classes:
/**
* #Entity
* #InheritanceType("SINGLE_TABLE")
* #DiscriminatorColumn(name="discr", type="string")
* #DiscriminatorMap({"root" = "RootCategory", "sub" = "SubCategory"})
*/
class Category
{
/**
* #Id #Column(type="integer") #GeneratedValue
*/
protected $id;
}
/**
* #Entity
*/
class RootCategory extends Category
{
}
/**
* #Entity
*/
class SubCategory extends Category
{
/**
* #ManyToOne(targetEntity="RootCategory")
* #JoinColumn(name="parentId", referencedColumnName="id")
*/
protected $parent;
}
In this domain model, we have only two possible levels in the hierarchy, hence the distinction as RootCategory or SubCategory.
I find it redundant to have to specify an explicit discriminator column, where we could just use a simple rule as the discriminator:
If parentId is null, then this is a RootCategory
If parentId is not null, then this is a SubCategory
Is it possible with Doctrine 2, to use the NULL status of a column as the discriminator?

No, it's impossible.
If you look at the source code of Doctrine 2 you will see that he transforms DiscriminatorMap into array, that can't have NULL key, only string or integer.

Can you use the following in DQL
$qb->where('{...} IS NULL');

Related

How to map type when map keys are not in the entity table with Doctrine?

Can the discriminator mapping strings of a Doctrine entity be fetched from a database foreign table field? If not, how to handle inheritance type mapping in such a situation?
Considering a Doctrine 2.7.2 abstract entity Person with concrete entities A and B like:
/**
* #MappedSuperclass
* #Table(name="PERSON")
*/
abstract class Person {
/**
* #Id
* #GeneratedValue
*/
protected $id;
/**
* #Column(name="name",type="string")
*/
protected $name;
/**
* #OneToOne(targetEntity="PersonType")
* #JoinColumn(name="type", referencedColumnName="id")
*/
protected $type;
}
/**
* #Entity
* #Table(name="PERSON")
*/
class A extends Person {}
/**
* #Entity
* #Table(name="PERSON")
*/
class B extends Person {}
A Person has a type bound to a PersonType entity like:
/**
* #Entity
* #Table(name="PERSON_TYPE")
*/
class PersonType {
/**
* #Id
* #GeneratedValue
*/
protected $id;
/**
* #Column(name="code",type="string")
*/
protected $code;
/**
* #Column(name="name",type="string")
*/
protected $name;
}
How to get the right class instantiated by repository query methods like find(), findBy(), findById(), etc?
Database set:
SELECT * FROM PERSON:
id name type
1 John 1
2 Tom 2
SELECT * FROM PERSON_TYPE:
id code name
1 A Type A
2 B Type B
Expected results:
$entityManager->getRepository("A")->findOneById(2); // null
$entityManager->getRepository("B")->findOneById(2); // B object (Tom)
$entityManager->getRepository("A")->findOneById(1); // A object (John)
I can't find how to specify this using a discriminator.
Alternatively I have a working solution implementing a SQLFilter with the inconvenience to have to enable or disable filter.
An alternative solution may be to populate $discr?
(I can of course use the Person::findByType() method but it would be nice to have this feature directly managed by the respective repositories).

Symfony2 Get entities of an entity in another entity

This question is about Symfony2 table relationships using ORM. I have three tables/entities that are related to each other. The relationship is very similar to Wordpress Posts, Categories and Categories relationship tables.
Table 1 contains posts.
Table 2 contains categories
Table 3 contains relationships between the categories and posts.
I want to be able to have the categories property in the posts table and a posts property in the categories table. So that when I call.
Categories->posts : I should get posts in that category.
Posts->categories : I should get the categories the post belongs to.
I want to have unique categories per table and I want all posts to point to a category without having to create a new entry for the category that already exists which is what ManyToOne or OneToMany is offering this is why the third table I think is necessary.
For example here is the relationships
class Category_relationship
{
/**
* #var integer
*
* #ORM\Column(name="object_id", type="bigint")
*
* #ORM\ManyToOne(targetEntity="Worksheet", inversedBy="category_relationships")
* #ORM\JoinColumn(name="worksheet_id", referencedColumnName="id", nullable=FALSE)
*/
private $objectId;
/**
* #var integer
*
* #ORM\Column(name="category_id", type="bigint")
*
* #ORM\ManyToOne(targetEntity="Category", inversedBy="categories")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id", nullable=FALSE)
*/
private $categoryId;
}
Here is the Category class:
class Category
{
/**
* #ORM\OneToMany(targetEntity="Category_relationship", mappedBy="categoryId", cascade={"persist", "remove"}, orphanRemoval=TRUE)
*/
protected $posts;
}
Here is the Category class:
class Posts
{ /**
* #ORM\OneToMany(targetEntity="Category_relationship", mappedBy="objectId", cascade={"persist", "remove"}, orphanRemoval=TRUE)
*/
protected $categories;
}
I want to create a system where I can assign posts to a category but the category table can only contain 1 entry about the category. I also want to be able to use expressions link;
Post->categories
Category->posts
or
Post->AddCategory()
Category->AddPost()
Thanks for your help.
It seems that you want a simple many-to-many relationship.
Every post can have multiple categories, and every category have list of related posts. Many to many handles pivot table by itself.
So, in Post entity you have to declare relationship that way:
/**
* #ORM\ManyToMany(targetEntity="Category", inversedBy="posts")
* #ORM\JoinTable(name="PostsCategories",
* joinColumns={#ORM\JoinColumn(name="post_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="category_id", referencedColumnName="id")}
* )
**/
protected $categories;
Remember about using Doctrine\ORM\Mapping with ORM alias (you don't have to import all subclasses separately):
use Doctrine\ORM\Mapping as ORM;
After that, you need to create a new ArrayCollection in class constructor:
public function __construct()
{
$this->categories = new ArrayCollection();
}
And add proper methods, like addCategory:
public function addCategory(Category $category)
{
$this->categories[] = $category;
return $this;
}
You can also add them automatically with:
php app/console doctrine:generate:entities BundleName:EntityName
Same thing in Category entity, but with a little different definiton:
/**
* #ORM\ManyToMany(targetEntity="Post", mappedBy="categories")
**/
protected $posts;
You can find all of these information in Doctrine docs

Doctrine - Entity loaded without existing associations

I have two entity types: \Model\News and \Model\News\Category.
\Model\News: (without couple of fields)
namespace Model;
/**
* #Entity
* #Table(name="news")
*/
class News extends \Framework\Model {
/**
* #Id
* #GeneratedValue
* #Column(type="integer")
*/
protected $id;
/**
* #ManyToOne(targetEntity="\Model\User", inversedBy="news")
*/
protected $author;
/**
* #ManyToOne(targetEntity="\Model\News\Category", inversedBy="news")
*/
protected $category;
}
\Model\News\Category:
namespace Model\News;
/**
* #Entity
* #Table(name="news_category")
*/
class Category extends \Framework\Model {
/**
* #Id
* #GeneratedValue
* #Column(type="integer")
*/
protected $id;
/**
* #Column(type="string", length=50, unique=TRUE)
*/
protected $name;
/**
* #OneToMany(targetEntity="\Model\News", mappedBy="category")
*/
protected $news;
/**
* Constructor
*/
public function __construct() {
parent::__construct();
$this->news = new \Doctrine\Common\Collections\ArrayCollection;
}
}
Table data from \Model\News:
id | category_id | author_id
------------------------------- ...
4 | 1 | NULL
Table data from \Model\News\Category:
id | name
---------------
1 | General
---------------
2 | Other
While I'm loading News type Entity with this particular code and doing dump with \Kint class:
$sId = '4';
$sModel = 'Model\News';
$oObject = $entity_manager->find($sModel, $sId);
d($oObject);
It returns me this:
My question is, why category property from $oObject variable has NULL values despite the fact that the category with id = 1 exists in database?
UPDATE:
After above code, I want to load this category (with ID=1) separately. Same thing. But... when I'm loading a category with other ID (for example, 2) it's loading with no problems:
$oObject2 = $entity_manager->('\Model\News\Category', '2');
I have no idea what to do now...
If you know that the category entity will be accessed almost each time you load news, you might want to eager load it (force doctrine to load it when News is loaded instead of when a Category property is called).
In order to do so, just add the fetch="EAGER" annotation on your association
/**
* #ManyToOne(targetEntity="\Model\News\Category", inversedBy="news", fetch="EAGER")
*/
protected $category;
So... I finally came to solve the problem thanks to #asm89 from #doctrine IRC chat in freenode server.
Doctrine is creating a proxy class which uses "lazy loading". Data is loaded only when one of getters is used.
So, after using $oObject->getCategory()->getName(), my category object available from $oObject->getCategory() was filled up with proper data.

Weird issue with related entity in Doctrine

I'm using Doctrine2 inside Symfony and I have the following setup:
An Item class:
/**
* Class Item
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="OneShortly\CommonBundle\Entity\ItemRepository")
*/
class Item
{
/**
* #ORM\ManyToOne(targetEntity="Category")
* #ORM\JoinColumn(name="primaryCategory", referencedColumnName="foreignId")
*/
private $primaryCategory;
}
And a Category class:
/**
* Category
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="OneShortly\CommonBundle\Entity\CategoryRepository")
*/
class Category
{
/**
* #var integer
*
* #ORM\Column(name="foreignId", type="integer", unique=true)
*/
private $foreignId;
}
Now when I do this:
$item = new Item();
$item->setPrimaryCategory($category);
$this->em->persist($item);
$this->em->flush();
I get this error:
[Symfony\Component\Debug\Exception\ContextErrorException] Notice:
Undefined index: foreignId in
home/www/project/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
line 692
Now, I've been looking at this from all angles and still cannot see what is wrong with this code. Can you help?
After some more digging I figured out myself by using doctrine:schema:validate:
[Mapping] FAIL - The entity-class
'Acme\CommonBundle\Entity\Item' mapping is invalid:
* The referenced column name 'foreignId' has to be a primary key column on the target entity class
'Acme\CommonBundle\Entity\Category'.
[Database] FAIL - The database schema is not in sync with the current
mapping file.
So, I changed the foreign key from foreignId to id (which happens to be the primary key) and it works. I could, of course, just use foreignId as a primary key, but I realized actually I don't need that.
Take a look at http://symfony.com/doc/current/book/doctrine.html#relationship-mapping-metadata.
You should rather have:
/**
* Class Item
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="OneShortly\CommonBundle\Entity\ItemRepository")
*/
class Item
{
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="items")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
private $primaryCategory;
}
and:
/**
* Category
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="OneShortly\CommonBundle\Entity\CategoryRepository")
*/
class Category
{
/**
* #ORM\OneToMany(targetEntity="Item", mappedBy="primaryCategory")
*/
private $items;
}
Forget ID in ORM.

ManyToOne association mapping to a Class Table Inheritance entity in Doctrine 2

I have an Author entity, which is a Class Table Inheritance containing an AuthorUser and an AuthorGroup.
/**
* Author
*
* #ORM\Table
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap({"user" = "AuthorUser", "group" = "AuthorGroup"})
*/
class Author {
// ...
}
AuthorUser relates to my User entity and AuthorGroup to my Group entity.
class AuthorUser extends Author
{
/**
* #var User
*
* #ORM\ManyToOne(targetEntity="User", inversedBy="?????")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
}
class AuthorGroup extends Author
{
/**
* #var Group
*
* #ORM\ManyToOne(targetEntity="Group", inversedBy="?????")
* #ORM\JoinColumn(name="group_id", referencedColumnName="id")
*/
protected $user;
}
I have no idea how to inverse this. Anyway, the problem is that i have to add this CTI to my Article entity field. How can i relate using ManyToOne to this Article entity field?
class Article
{
/**
* #var Author
*
* #ORM\ManyToOne(targetEntity="Author", inversedBy="?????????")
* #ORM\JoinColumn(name="author_id", referencedColumnName="id")
*/
protected $author;
}
I'm not sure how to make this as transparent as possible. When i create a new Article, i need to provide either an User or Group object to the author field. I followed this behavior, but it doesn't seem to help. It gets even more complicated.
One solution could be to always have AuthorGroups, even when there's only one Author.
Otherwise, take a look at https://github.com/FabienPennequin/DoctrineExtensions-Rateable
You might be able to use that code to provide a similar Authored interface that can discriminate between the AuthorUser and AuthorGroup.

Categories