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.
Related
I am creating a simple CMS Bundle for my headless symfony backend and I'm trying to map Page to Page with parent and child relation(Many children to one parent) and I have this class mapped superclass to create reusable code, this is a minified sample on what I'm trying to archive:
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\MappedSuperclass()
*/
class Test
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function getId()
{
return $this->id;
}
/**
* #ORM\ManyToOne(targetEntity="Ziebura\CMSBundle\Entity\Test")
*/
protected $parent;
public function getParent()
{
return $this->parent;
}
public function setParent($parent)
{
$this->parent = $parent;
}
}
Then I'm extending this class as a normal entity to create DB table
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Ziebura\CMSBundle\Entity\Test as BaseTest;
/**
* #ORM\Table(name="test")
* #ORM\Entity(repositoryClass="App\Repository\TestRepository")
*/
class Test extends BaseTest
{
}
The issue is that I'm getting this doctrine exception
Column name `id` referenced for relation from App\Entity\Test towards Ziebura\CMSBundle\Entity\Test does not exist.
I don't quite understand why it produces this error or is the thing that I'm trying to archive impossible, I already did relations on mapped superclasses but it was 2 or more tables and not just a single on. I already tried creating $children field but it didnt worked and still produced above error. Did anyone try to create something simmilar? I couldn't find anything about this in doctrine docs, only found how to map 2 different superclasses. I suppose the easy way out would be to specify the relation in App namespace not in the Bundle but that pretty much destroys the purpose of reusable code if I'd have to declare that in every project I use the bundle. I believe in stack let's figure this out. Thanks!
Lets read Doctrine docs about this: https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/inheritance-mapping.html#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 cannot be an entity, it is not query-able and persistent relationships defined by a mapped superclass must be unidirectional (with an owning side only). This means that One-To-Many associations are not possible on a mapped superclass at all.
According to this:
MappedSuperclass cannot be Entity
Cannot have One-To-Many relationship - so if you are defining ManyToOne to same class then it creates also OneToMany on same class - which, as you read above, is forbidden.
For some reason only changing the full entity path in BaseTest resolved app throwing the exception and it works, if anyone would face same issue try changing
/**
* #ORM\ManyToOne(targetEntity="Ziebura\CMSBundle\Entity\Test")
*/
protected $parent;
public function getParent()
{
return $this->parent;
}
public function setParent($parent)
{
$this->parent = $parent;
}
To
/**
* #ORM\ManyToOne(targetEntity="Test")
*/
protected $parent;
public function getParent()
{
return $this->parent;
}
public function setParent($parent)
{
$this->parent = $parent;
}
If someone knows why it has to be like this I'd much appreciate a comment to my answer.
I've noted that for creating a facade class, laravel provides only name "db"
framework/src/Illuminate/Support/Facades/DB.php
class DB extends Facade
{
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor()
{
return 'db';
}
}
I looked deeper and figured out that this method uses the provided name
framework/src/Illuminate/Support/Facades/Facade.php
protected static function resolveFacadeInstance($name)
{
if (is_object($name)) {
return $name;
}
if (isset(static::$resolvedInstance[$name])) {
return static::$resolvedInstance[$name];
}
return static::$resolvedInstance[$name] = static::$app[$name];
}
I understand first and second If statements.
But I have problems with understanding this:
return static::$resolvedInstance[$name] = static::$app[$name]
As I understood that $app is a protected property of Facade class which contains an instance of \Illuminate\Contracts\Foundation\Application class.
/**
* The application instance being facaded.
*
* #var \Illuminate\Contracts\Foundation\Application
*/
protected static $app;
My two questions:
How is it possible to use an object as an array(static::$app[$name]) if Application class doesn't extends ArrayObject class?
How laravel understands which class to call with providing only a short name 'db'?
Clicking through the Laravel source, I found this. As you can see, ApplicationContract (the private static $app from your question) is implemented by Application. This is in turn derived from Container, which implements the PHP core ArrayAccess interface. Carefully implementing this whole chain eventually makes Applicatin accessible like an array.
Turns out it boils down to good ole' object oriented programming :)
// Illuminate/Foundation/Application.php
class Application extends Container implements ApplicationContract, HttpKernelInterface
^^^^^^^^^ ^-> the private static $app in your question.
// Illuminate/Container/Container.php
class Container implements ArrayAccess, ContainerContract
^^^^^^^^^^^
// PHP core ArrayAccess documentation
/**
* Interface to provide accessing objects as arrays.
* #link http://php.net/manual/en/class.arrayaccess.php
*/
interface ArrayAccess {
You can look this, php manual and use ArrayAccess interface:
http://php.net/manual/en/class.arrayaccess.php
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 :)
Assume we have the following inheritance chain in PHP
abstract class Entity {}
abstract class RealEntity extends Entity {}
abstract class PseudoEntity extends Entity {}
and a bunch of other classes that mirror the same inheritance chain
abstract class EntitySerializer {
/**
* #return Entity
*/
abstract public function getEntity();
}
abstract class RealEntitySerializer extends EntitySerializer {
/**
* #return RealEntity
*/
abstract public function getEntity();
}
abstract class PseudoEntitySerializer extends EntitySerializer {
/**
* #return PseudoEntity
*/
abstract public function getEntity();
}
PHP complains that the abstract method getEntity must either be properly implemented (and loose the abstract keyword) or must not be re-declared. I can understand why PHP complains, because despite the PHPdoc comments the method signature is identical to the signature of the parent method in EntitySerializer.
However, I want somehow to make clear that child classes that extend RealEntitySerializer or PseudoEntitySerializer must not return an instance of an arbitrary Entity but narrow the return type to RealEntity or PseudoEntity resp.
Especially, If I omit re-definition of the method and its corresponding PHPdoc from the intermediate classes in order to make PHP happy, my IDE correctly assumes that RealEntitySerializer::getEntity and PseudoEntitySerializer::getEntity is allowed to return an arbitrary instance of Entity. In consequence, my IDE complains that I call undefined methods if I call methods that are particular to one of the intermediate classes on an object that was returned by RealEntitySerializer::getEntity or PseudoEntitySerializer::getEntity resp.
How, do I achieve both goals? Code that is (a) interpreted by PHP without an error and and that is (b) properly documented.
You want the PSR-5: PHPDoc #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 {}
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
{
}