How create polymorphic agnostic models? - php

Currently I use the storage agnostic pattern in my project. This pattern is pretty simple: I've my Model\Model and my specific storage layer that extends of Model\Model (like Entity\Model and Document\Model).
The problem I'm facing is with polymorphic objects. If I don't extend an entity Doctrine does not recognize the inheritance mapping. In that way I need to replicate my code for every storage layer:
class Model\Option {}
class Model\OptionNumber extends Option {}
class Entity\Model extends Model\Option {}
I would like to have:
class Entity\OptionNumber extends Model\OptionNumber{}
But it's not possible. I've to code in Entity\OptionNumber once PHP have no support for multiple inheritance.
Is there any pattern that solve this problem?
Just for reference, I'm using Doctrine 2.2 and Symfony 2.3.
Sure!
Model:
abstract class Option implements OptionInterface
{
/**
* #var int
*/
protected $id;
/**
* #var string
*/
protected $name;
/**
* #var string
*/
protected $presentation;
/**
* #var Collection of OptionChoiceInterface
*/
protected $choices;
}
Entity:
class Option extends BaseOption
{
}
Document:
class Option extends BaseOption
{
}

Related

Doctrine multi-level inheritance

I have some problem using Doctrine and inheritance in Symfony 5.
First, I have a base entity with only 2 dates (createdAt and updatedAt).
/**
* #ORM\MappedSuperclass()
*/
class BaseEntity
{
/**
* #ORM\Column(type="datetime")
*/
protected $createdAt;
/**
* #ORM\Column(type="datetime")
*/
protected $updatedAt;
}
Next, I have another class extending the BaseEntity (for example "MyClass1").
/**
* #ORM\Entity(repositoryClass=MyClass1Repository::class)
* #ORM\MappedSuperclass()
*/
class MyClass1 extends BaseEntity
{
/* Some properties */
}
Finally, I have my class extending MyClass1 (for example "MyClass2").
/**
* #ORM\Entity(repositoryClass=MyClass2Repository::class)
*/
class MyClass2 extends BaseEntity
{
/* Some properties */
}
When I migrate my database (make:migration), Doctrine creates 2 tables :
myclass1
myclass2
Can you tell me where I am wrong ? I'd have only 1 table : myclass2.
The mistake is in the MyClass1 definition. From the Doctrine documentation about inheritance mapping.
A mapped superclass is an abstract or concrete class that provides persistent entity state and mapping information for its subclasses, but which is not itself an entity. Typically, the purpose of such a mapped superclass is to define state and mapping information that is common to multiple entity classes.
A Mapped superclass itself is not an entity, by adding the #Entity next to the #MappedSuperclass you're effectively making it an entity. The mapped superclass should not be an entity and is not query-able.

Doctrine2: persisting parent entity given child class

I've got a User Entity defined (mapping in yml)
namespace My\CoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
class User
{
...
And I created a child class that inherits from that entity, so that I can add some custom validation methods and a few fields that I need but do not need to be persisted (e.g. ConfirmPassword, ConfirmEmail fields)
namespace My\SecondBundle\EditModels;
use My\CoreBundle\Entity\User;
class UserModel extends User
{
When the user submit a registration form, I map the request to a UserModel entity, and if it is valid I try to persist the user.
The following code throws an exception
$entityManager->persist($userModel);
//=>The class 'My\SecondBundle\EditModels\UserModel' was not found in the chain configured namespaces My\CoreBundle\Entity
Question: How can I persist $userModel (instance of UserModel) as a User entity class? Possible options:
Do not use an inherited class and add custom fields and validation method to the User entity itself
Copy the fields from the UserModel to the User entity and persist the user entity
I don't think I should use Doctrine inheritance mechanism as I do not want to save the extra fields.
Thank you
I think your problem here, is that you've just configured My\CoreBundle\Entity namespace in Doctrine2, but the entity you actually want to persist is located in My\SecondBundle\EditModels.
Usually when inheriting classes marked as #ORM\Entity() the class you are extending from must have the class annotation #ORM\MappedSuperclass(). But normally you use this for single table inhertiance e.g., not for your usecase.
In my opinion the approach to split database related attributes from the others, is not affordable. I would keep validation related stuff in the model itself - you need it in your create/update action.
I'm not familiar with XML configuration, but when using annotations you need to mark each property to be mapped with database (using #ORM\Column()). So Doctrine will ignore all the other attributes and methods entirely.
So here I share my recently developed AbstractModel for you, to see how I've implemented validation (with respect/validation):
<?php
namespace Vendor\Package\Model;
use Doctrine\ORM\Mapping as ORM;
/**
* Abstract Model
*
* #ORM\MappedSuperclass()
*/
abstract class AbstractModel
{
/**
* #var \Respect\Validation\Validator
*/
protected $validator;
/**
* AbstractModel constructor
*/
public function __construct()
{
$this->validator = static::validation();
}
/**
* Defines validation for this model
*
* #return \Respect\Validation\Validator
*/
public static function validation() : \Respect\Validation\Validator
{
return \Respect\Validation\Validator::create();
}
/**
* Executes validations, defined in validation method.
*
* #return bool
*/
public function isValid() : bool
{
if (is_null($this->validator)) {
$this->validator = new \Respect\Validation\Validator();
$this->validation();
}
return $this->validator->validate($this);
}
}
A model which extends from the AbstractModel needs to implement a static validate method, to define class validation:
<?php
namespace Vendor\Package\Model;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity()
* #ORM\Table(name="my_model")
*/
class MyModel extends AbstractModel
{
/**
* #var string
* #ORM\Column(type="string")
*/
private $name;
/**
* Defines validation for this model
*
* #return \Respect\Validation\Validator
*/
public static function validation() : \Respect\Validation\Validator
{
return \Respect\Validation\Validator::create()
->attribute('name', \Respect\Validation\Validator::notEmpty()->stringType()->length(null, 32))
;
}
// getter, setter, ...
}
Each entity, persisted to database, will have the $validator property and all these methods, but because I left annotations here (and pretty sure this also works with xml/yaml) Doctrine ignores it.
And this way you also keep validation related stuff out of the model class itself, which is good for readability. The validation itself should be defined in the model itself, imho. But this respect/validation framework is neat way to achive this. Hope this helps :)

Symfony2: Entity extends MappedSuperClass extends FOSUserBundle User

I created a BaseBundle with a SuperUser MappedSuperClass who extends FOSUserBundle User
use FOS\UserBundle\Model\User as FOSUser;
/*
* #ORM\MappedSuperclass
*/
class SuperUser extends FOSUser
{
/**
* #var string
* #ORM\Column(name="locale", type="string", nullable=true)
*/
protected $locale;
}
In my Project I installed my BaseBundle. I created a User entity who extends my SuperUser class.
/*
* User class
*/
class User extends SuperUser
{
/**
* #var string
*/
private $fullName;
}
The problem is that when I create the table, Doctrine see only FOSUser properties and User properties but not my SuperUser properties. I can see only fullName and FOSUser properties.
My SuperUser class is bypassed..
I noticed that User class is with YAML notation, SuperUser is with PHP Annotation while FOSUser Class is with XML notation. I don't know if this create problems.
From the documentation:
A bundle can accept only one metadata definition format. For example, it's not possible to mix YAML metadata definitions with annotated PHP entity class definitions.
Try to add the YML mapping of your MappedSuperClass

PhpStorm warns about "Expected class1, got class2" when class2 extends class1

I'm using Phalcon Framework and it has class for models: Phalcon\Mvc\Model (from now only as P\M\Model). I've defined base domain, which extends that class and then every other domain extends my base domain, thus Phalcons Model class:
Domain.php:
class Domain extends \Phalcon\Mvc\Model
{
...
}
DomainA.php:
class DomainA extends Domain
{
...
}
then I'm using repository manager to get repository for DomainA models. All repositories have same parent, similarly as Domain, which has defined method find()
Repository.php:
class Repository
{
/**
* Will always return object of classes
* which extends Phalcon\Mvc\Model
*
* #return Phalcon\Mvc\Model
*/
public function find()
{
...
$domain::find();
}
}
RepositoryA.php:
class RepositoryA extends Repository
{
...
}
So, the RepositoryA now has method find() from its parent and because parent does not know exactly what he is going to return, but knows parent of what all the returns are so it is type-hinting it via #return.
Then I have some other class, which has method expecting only DomainA object, which is also parent of P\M\Model and I try to push there object of that type it works OK, because returned object from repository actualy IS an DomainA object, but Repository annotates it as P\M\Model so PhpStorm highlights it with message "Expected DomainA, got Phalcon\Mvc\Model..."
public function pleaseGiveDomainA(DomainA $obj)
{
...
}
// Works OK but is higlighted in IDE
$this->pleaseGiveDomainA($repositoryA->find());
How should I annotate this kind of stuff? Hinting in #return all cases of domains like #return DomainA|DomainB|DomainC... is no good as we have hundreds of Domains, also expecting in function the parent P\M\Model is no good, because we want to be sure its only DomainA.
Thanks.
try using an interface rather than a base model. In my experience sometimes PHPStorm can get confused with this sort of complex class hierarchy. In my programs I define an interface and type hint against that. Which allows PHPStorm to properly detect the class
If you redefine find() in class RepositoryA then just annotate its implementation with #return DomainA. Otherwise, declare find() as #method in the docblock of class RepositoryA with DomainA as its return type.
Both approaches are displayed below:
/**
* #method DomainA find() <-- use this when the method is inherited
* but not redefined in this class
*/
class RepositoryA extends Repository
{
/**
* #return DomainA <-- preferably use this
*/
public function find()
{
}
}
A similar trick can be used for inherited properties that in the children class store objects of classes that extend the class used in annotation in the base class:
/**
* #property DomainA $member <-- it was declared as #var Domain $member
* in the base class
*/
PSR-5 defines a #method tag.
Syntax
#method [return type] [name]([type] [parameter], [...]) [description]
Examples
/**
* #method string getString()
* #method void setInteger(int $integer)
* #method setString(int $integer)
*/
class Child extends Parent
{
<...>
}
abstract class EntitySerializer {
/**
* #return Entity
*/
abstract public function getEntity();
}
/**
* #method RealEntity getEntity()
*/
abstract class RealEntitySerializer extends EntitySerializer {}
/**
* #method PseudoEntity getEntity()
*/
abstract class PseudoEntitySerializer extends EntitySerializer {}
Re: taken from my SO answer here.

How to set fixed entity relation in Doctrine 2

I have abstract entity (App - base core):
namespace App\Bundles\AppBundle\Entity;
abstract class App extends ContainerAware implements AppInterface
{
// .....
}
and self entity:
namespace AppRus\Bundles\AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM,
App\Bundles\AppBundle\Entity\App as BaseApp;
/**
* App
*
* #ORM\Table(name="apps")
* #ORM\Entity(repositoryClass="AppRus\Bundles\AppBundle\Entity\AppRepository")
*/
class App extends BaseApp
{
// ....
}
and entity for control revisions:
namespace App\Bundles\AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* AppHistory
*
* #ORM\Table(name="apps_history")
* #ORM\Entity(repositoryClass="App\Bundles\AppBundle\Entity\AppHistoryRepository")
*/
class AppHistory
{
/**
* #var integer
*
* #ORM\ManyToOne(targetEntity="App\Bundles\AppBundle\Entity\App")
* #ORM\JoinColumn(name="app_apple_id", referencedColumnName="apple_id")
*/
private $app;
}
I can't create relation AppHistory#app to App#apple_id
When I set entity to abstract class App, then doctrine create a new table "App"
When I set MappedSuperClass to abstract class App, I have error: http://docs.doctrine-project.org/en/latest/reference/inheritance-mapping.html#mapped-superclasses
How can I create relation from AppHistory to abstract core App?
My understanding is that that's not possible. I think you're doing things the wrong way, at least for Doctrine2.
First of all, from the Doctrine2 docs you mention:
A mapped superclass is an abstract or concrete class that provides persistent entity state and mapping information for its subclasses, but which is not itself an entity.
You can't create a relation to something that's not an entity!
To solve your immediate problem (~version control~ in doctrine), check out EntityAudit on Github instead.

Categories