Doctrine 2 - Access level problems when using Class Table Inheritance - php

I'm trying to implent the Class Table Inheritance Doctrine 2 offers in my Symfony 2 project.
Let's say a have a Pizza class, Burito class and a MacAndCheese class which all inherit from a Food class.
The Food class has the following settings:
<?php
namespace Kitchen;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="food")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="dish", type="string")
* #ORM\DiscriminatorMap({"pizza" = "Pizza", "burito" = "Burito", "mac" => "MacAndCheese"})
*/
class Food {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
And the inherited classes have these settings (Pizza for example):
<?php
namespace Kitchen;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="food_pizza")
*/
class Pizza extends Food {
When running doctrine:schema:update --force from the Symfony 2 app/console I get an error about the access level of $id in the children of Food (Pizza for example), stating it must be protected or weaker. I haven't declared $id anywhere in the Pizza, since I reckoned it would be inherited from Food.
So I tried to declare $id, but that gives me an error, cause I can't redeclare $id.
I figure I need some kind of reference to $id from Food in Pizza, but the Doctrine 2 documentation didn't really give me a clear answer on what this would look like.
Hopefully you understand what I mean and can help me.

Apparently I should have investigated the code generated by doctrine:generate:entities a bit more. When I started my IDE this morning and seeing the code again, I noticed that it had 'copied' all of the inherited fields (like $id in Food, in the example above) to the children (Pizza, in the example above).
For some reason it decided to make these fields private. I manually changed the access level to protected in all of the classes and I tried to run doctrine:schema:update --force again: it worked!
So, as in many cases, the solution was a good night's rest! ;)
If someone comes up with a better solution and / or explanation for this problem, please do post it. I'd be more than happy to change the accepted answer.

Something to keep in mind:
Every Entity must have an identifier/primary key. You cannot generate
entities in an inheritance hierachy currently (beta) As a workaround
while generating methods for new entities, I moved away from project
inheritated entities and after generating I moved them back.
source

May be you should define the #ORM\DiscriminatorMap in a such way:
/**
*
..
* #ORM\DiscriminatorMap({"food" = "Food", "pizza" = "Pizza", "burito" = "Burito", "mac" => "MacAndCheese"})
*/
If you compare your code with the example from Doctrine site, you will see that they added parent entity to the DiscriminatorMap.

Related

Symfony 4 Custom annotation problem #ORM\Entity does not exist

as part of the development of my CMS that I publish in a while .. I am facing a problem.
error :
[Semantical Error] The annotation "#Doctrine\ORM\Mapping\Entity" in class ScyLabs\GiftCodeBundle\Entity\GiftCode does not exist, or could not be auto-loaded.
I explain to you ,
Basically, in the project, everything is Overridable, it is already the case, with configurations in the file services.yaml.
For obvious reasons of simplicity, and an immediate need, to allow me to create a second bundle inheriting from it. I told myself that doing my "Override" or saying to the project: "Hello here I am, I am a class uses me" is very convenient with annotations (and much clearer).
So, I create a custom annotation (So far so good ..) That you find here ..
<?php
/**
* Created by PhpStorm.
* User: alex
* Date: 04/11/2019
* Time: 14:25
*/
namespace ScyLabs\NeptuneBundle\Annotation\ScyLabsNeptune;
/**
* #Annotation
* #Target({"CLASS"})
* #Attributes({
* #Attribute("key",type="string"),
* #Attribute("classNameSpace",type="string"),
* })
*/
class Override
{
/**
* #var string
*/
public $key;
/**
* #var string
*/
public $classNameSpace;
public function __construct(array $opts) {
$this->key = $opts['value'];
$this->classNameSpace = $opts['class'];
}
}
Well, my annotation was in place, I will now put it in an entity, .. As here
<?php
/**
* Created by PhpStorm.
* User: alex
* Date: 05/11/2019
* Time: 10:20
*/
namespace ScyLabs\GiftCodeBundle\Entity;
use ScyLabs\NeptuneBundle\Annotation\ScyLabsNeptune;
use Doctrine\ORM\Mapping as ORM;
/**
* #ScyLabsNeptune\Override("gift",class="ScyLabs\GiftCodeBundle\Entity\GiftCode")
* #ORM\Entity()
*/
class GiftCode
{
}
Why do that ? And in fact, everything is automated in the neptune, except special case, it will automatically generate all the URLs necessary for the proper functioning of an entity (ADD / EDIT / DELETE / LIST) ... And for this, it must indicate to the project that the entity exists, and that it must be part of this system.
So, until now I use a very complete configuration in services.yaml, in which I fill a table keys => value, corresponding to "key" => "Namespace"
In my case: "gift" => "ScyLabs \ GiftCodeBundle \ Entity \ GiftCode"
In short, suddenly, for override, I do a treatment in a compilation step
<?php
/**
* Created by PhpStorm.
* User: alex
* Date: 01/08/2018
* Time: 09:46
*/
namespace ScyLabs\NeptuneBundle;
class ScyLabsNeptuneBundle extends Bundle
{
public function getContainerExtension()
{
// Compilation de l'extension
return new ScyLabsNeptuneExtension();
}
}
And in this extension, I have this piece of code that makes everything
$bundles = require $projectDir.'/config/bundles.php';
foreach ($bundles as $bundle => $env){
if($bundle === ScyLabsNeptuneBundle::class)
continue;
if(method_exists(new $bundle,'getParent') && (new $bundle)->getParent() === ScyLabsNeptuneBundle::class){
$reader = new AnnotationReader();
$reflClass = new \ReflectionClass(GiftCode::class);
$classAnotations = $reader->getClassAnnotation($reflClass,"Override");
foreach ($classAnotations as $classAnotation){
if($classAnotation instanceof Override && class_exists($classAnotation->classNameSpace)){
$config['override'][$classAnotation->key] = $classAnotation->classNameSpace; }
}
}
}
From what I suspect after a lot of research, at the compilation stage of my extension, #ORM \ Entity, and or / / Autowire, it seems not compiled yet.
The problem is that suddenly, when I get my personal annotation (Override), I can not recover #ORM \ Entity, and I can not necessarily remove it because it would not work anymore as an entity.
Why do that here? Because behind I have another step of comoilation (A CompilationPass)
$container->addCompilerPass(new ResolveDoctrineTargetEntitiesPass(),PassConfig::TYPE_BEFORE_OPTIMIZATION,1000);
Who, redefined the Entities that doctrine will call in relation to the painting that I send to him (you know, the one I defined just before).
With this I give the possibility of override entities with an identical name.
What to do ?? .. I confess that I can not do more ...
Thanks in advance friends;)
By default, the annotation reader does not use the same autoloader as classes.
You need to tell him how to load the annotation class like that :
AnnotationRegistry::registerUniqueLoader('class_exists');
For more explanation, you can look at the doc https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/annotations.html#registering-annotations
thanks for you response.
But it don't work and this fonction is deprecated and removed to Annotations 2.0.
BUT , when i try i found a resolution.
When i follow your link and try the code in the page , i try this function
AnnotationRegistry#registerFile($file)
For get #ORM\Entity file path , i use
new \ReflectionClass(ORM\Entity::class);
And , this work.
I deleted AnnotationRegistry#registerFile($file) function , and this work.
Thanks you for help ;)
You'r the best

Symfony doctrine lazy load properties

I have an entity that stores large files as blobs to the DB.
I would now like to get Symfony to never ever load these blobs unless I specifically request them via the appropriate getter.
In essence I want the same idea as lazy-loading relationships but for a string property.
What I have tried so far is to put all my other properties that hold the file meta data into a trait and then apply that trait to two entities.
namespace App\Entity\Traits;
use Doctrine\ORM\Mapping as ORM;
trait DocumentMetaData
{
/**
* #var int|null
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var \DateTime|null
*
* #ORM\Column(type="datetime")
*/
private $date_uploaded;
}
One entity has nothing to it but the trait...
namespace App\Entity;
use App\Entity\Traits\DocumentMetaData;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="documents")
* #ORM\Entity()
*/
class Document
{
use DocumentMetaData;
}
...the other has the added blob property:
namespace App\Entity;
use App\Entity\Traits\DocumentMetaData;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="documents")
* #ORM\Entity()
*/
class DocumentFile
{
use DocumentMetaData;
/**
* #var string|null
*
* #ORM\Column(type="blob")
*/
private $blob;
}
Now, if I don't want the blob to be loaded, for example for a listing of files, I simply use the entity that doesn't have the blob.
This approach sort of works but causes issues as I need to point both entities at the same table (see the class level ORM annotations).
Specifically, it makes doctrine freak out when running migrations:
The table with name 'myapp.documents' already exists.
That makes perfect sense and really I'm hoping that someone can point me to a nicer solution.
How can I tell doctrine not to load the blob unless its explicitly asked for?
So as per the comments on my question - the way to do this so that migrations do not break is to leverage doctrine's ability to lazy load relationships between tables.
Basically I had to go and create a new entity that only holds my giant blobs and then establish a one to one relationship between the original entity and the blob entity.
I then set that relationship to load EXTRA_LAZY and as a result I can now control when precisely the blobs of giant data should be loaded.
I don't think this is ideal in terms of normalising the DB design but it works a lot better than anything else so happy with that.

Wrong value coming from Doctrine 2 'getClassMetadata()->getIdentifier()', how or why?

I have the following Entity as part of a Symfony 3.2.6 project in Doctrine 2:
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Timestampable\Traits\TimestampableEntity;
use QuoteBundle\Model\SourceTrait;
/**
* #ORM\Entity
* #ORM\Table(name="quote")
*/
class Quote
{
use SourceTrait;
use TimestampableEntity;
/**
* #ORM\Id
* #ORM\Column(type="integer",unique=true,nullable=false)
* #ORM\GeneratedValue
*/
private $quoteId;
/**
* #return int
*/
public function getQuoteId(): ?int
{
return $this->quoteId;
}
}
In another class dynamically I need to get the identifier of the entities (I wrote just an example of the entities - the one above - but this applies to all of the entities I have all over the application) so the best way I found to achieve this was using getClassMetaData() method from Doctrine.
This is the dynamic code into the class:
$modelMetaData = $em->getClassMetadata($bundleName.':'.$modelName);
Which gets translated for example in:
$modelMetaData = $em->getClassMetadata('QuoteBundle:Quote');
From there I should have all the data related to the entity.
One of the problem is we're two developers and we're having different results regarding getClassMetaData().
I am running a stack in Docker using Docker Compose which is exactly the same and this mean: same PHP version (7.1.2), same Apache version, same MySQL version (this one is in a common server outside the stack) and same Symfony libraries. I have checked, double checked and triple checked and can do it again if it's necessary.
For the examples from now on I'll call myself Dev1 and the second developer will be Dev2.
Dev1 code:
$id = $em->getClassMetadata('QuoteBundle:Quote')->getIdentifier()[0];
dump($id);
quote_id
Dev2 code:
$id = $em->getClassMetadata('QuoteBundle:Quote')->getIdentifier()[0];
dump($id);
quoteId
As you can see the ID obtained is different which is making the code to fails on Dev2 environment and I don't know what else to look since everything seems to be fine for me.
The question is: should getIdentifier() return quote_id (the column name) or quoteId (the mapped name)??
As an addition here is an example of dump($modelMetaData) for Dev1 and here the same example for Dev2.

Symfony/Doctrine class table inheritance and foreign key as primary key

I am currently designing a web application with Symfony 2.5 (and Doctrine 2.4.2) that has to be flexible to easily plug in new modules/bundles.
So I have an entity (let say A) that has two one-to-one associations with abstract classes (B and C). The future modules will implement one of the two abstract classes. Since the associations are one-to-one, I made them the ID of the abstract classes to ease the access to them when we know the ID of an instance of A
So here is what the code looks like:
class A:
<?php
namespace Me\TestBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table()
* #ORM\Entity
*/
class A
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToOne(targetEntity="B", cascade={"all"}, mappedBy="a")
* #ORM\JoinColumn(name="id", referencedColumnName="a_id")
*/
private $b;
/**
* #ORM\OneToOne(targetEntity="C", cascade={"all"}, mappedBy="a")
* #ORM\JoinColumn(name="id", referencedColumnName="a_id")
*/
private $c;
}
class B:
<?php
namespace Me\TestBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table()
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="discr", type="string")
*/
abstract class B
{
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="A", inversedBy="b")
* #ORM\JoinColumn(name="a_id", referencedColumnName="id")
*/
private $a;
}
I will not post the code of the class C since it is the same as class B.
In my point of view, it seems all good. Even for the mapping verification command.
Indeed, when I execute php app/console doctrine:schema:validate, it tells me my schema is valid. However, this command then try to compare my schema to the schema in the database and it just fails at that point. php app/console doctrine:schema:update --dump-sql fails in the exact same way. So it is pretty embarrassing since it tells me my schema is valid but it cannot use it properly.
The error:
[ErrorException]
Warning: array_keys() expects parameter 1 to be array, null given in /vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Index.php line 95
The error appears as soon as I add the InheritanceType and DiscriminatorColumn annotation to the classes B and C. Thing is that it tells me my schema is valid.
So does anyone have any clue if I am doing something wrong? Or is it definitely a bug in Doctrine? Do you have any other idea that would bring at least as much flexibility as my current solution?
Elioty
EDIT: I changed the owning side to be the abstract classes B and C since, accordingly to the doc, the owning side is the one with the foreign key and must use inversedBy attribute. Even with these changes, my schema is still valid and the same error still occurs.
EDIT2: If I create another field in B (and C) to hold the identity of the entity instead of the one-to-one association, the error disappears but it is no more like my valid schema.
EDIT3: I had a chat with a member of Doctrine's development team and (s)he told me it definitely looks like a bug. Bug report here.
First of all, the command php app/console doctrine:schema:validate checks the validity of the current schema. The current schema is the one generated by your last called to the command php app/console doctrine:schema:update --force
On the other hand the command php app/console doctrine:schema:update --dump-sql does not execute an actual update to the schema, though it may find some errors some others will not be come up...
I dont understand class B. The identity of this (abstract) class is a OneToOne relationship? Anyway, the thing is that Doctine will ignore the #ORD\Id if you don't add inheritanceType definitions. When you add this inheritanceType definition Doctrine will make attribute 'a' the primary key of B. Also it will create another attribute named 'a_id' which will serve as a foreign key for future subclasses (extends of B). But...relationships are not inherited.
Hope this helps...

Class inheritance is not found

I am building an application using Zend Framework and Doctrine 2.
My code looks like this:
namespace Entities;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #Entity (repositoryClass="Repositories\Person")
* #Table(name="persons")
* #InheritanceType("SINGLE_TABLE")
* #DiscriminatorColumn(name="type", type="string")
* #DiscriminatorMap({"2"="User"})
*/
class Person
{
/**
* #Id #Column(type="integer")
* #GeneratedValue(strategy="AUTO")
*/
private $id;
}
And my class User
namespace Entities;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #Entity (repositoryClass="Repositories\User")
*/
class User extends Person
{
}
Now, I get this error:
Fatal error: Class 'Entities\Person' not found in C:\xamp\htdocs\Persons\application\m
odels\Entities\User.php on line 13
I have no idea why I get that error. I have tried calling the "Person" class in many different ways but the its not working. Any idea? Thanks!
When running in Zend Framework, you have an autoloader setup that handles the loading of classes for you dynamically.
When you run just the Doctrine tool from the command line, you don't have an autoloader at your disposal. Doctrine is trying to load the User class, which requires Person, and yet it doesn't (apparently) know how to load Person.
I think the simple solution would be to have require_once('Person.php'); at the top of your User entity. This is probably unnecessary for ZF, but will be helpful for Doctrine command line tools.

Categories