Can I access discriminator field from php in doctrine2? - php

I have an entity which defines inheritance like this:
* #DiscriminatorColumn(name="type", type="string")
* #DiscriminatorMap({"text" = "TextAttribute", "boolean" = "BooleanAttribute", "numeric" = "NumericAttribute", "date" = "DateAttribute"})
I am wondering is it possible to have getter for field 'type'? I know I can use instanceof (and in most cases this is what I'm doing) but there are few scenarios where $item->getType() would make my life so much easier.

Extending what beberlei said, you could declare some constants in the Attribute class, and an abstract getType() function. Then, overload it in every derived attribute class.
Something like:
abstract class Attribute {
const TYPE_BOOL = 0;
const TYPE_INT = 1;
...
abstract public function getType();
}
class BooleanAttribute extends Attribute {
public function getType() {
return parent::TYPE_BOOL;
}
}

Here is how I'd do.
First, you made an AttributeInterface, to be sure that all future new Attribute types will implement the need method :
interface AttributeInterface
{
/**
* Return the attribute type
*/
public function getType();
}
Then you create the Attribute abstract class implementing the AttributeInterface interface.
Use the constants in the #DiscrimatorMap call for some consistency
/**
* Attribute
* ...
* #DiscriminatorColumn(name="type", type="string")
* #DiscriminatorMap({Attribute::TYPE_TEXT = "TextAttribute", Attribute::TYPE_BOOLEAN = "BooleanAttribute", Attribute::TYPE_NUMERIC = "NumericAttribute", Attribute::TYPE_DATE = "DateAttribute"})
*/
abstract class Attribute implements AttributeInterface
{
const TYPE_TEXT = 'text';
const TYPE_BOOLEAN = 'boolean';
const TYPE_NUMERIC = 'numeric';
const TYPE_DATE = 'date';
}
Finally, you create all needed classes, extending Attribute class and implementing the getType() method
/**
* TextAttribute
*
* #ORM\Entity
*/
class TextAttribute extends Attribute
{
public function getType()
{
return $this::TYPE_TEXT;
}
}
/**
* BooleanAttribute
*
* #ORM\Entity
*/
class BooleanAttribute extends Attribute
{
public function getType()
{
return $this::TYPE_BOOLEAN;
}
}
/**
* NumericAttribute
*
* #ORM\Entity
*/
class NumericAttribute extends Attribute
{
public function getType()
{
return $this::TYPE_NUMERIC;
}
}
/**
* DateAttribute
*
* #ORM\Entity
*/
class DateAttribute extends Attribute
{
public function getType()
{
return $this::TYPE_DATE;
}
}
// And so on...

It's possible either with the EntityManager or using the DocumentManager.
$documentManager->getClassMetadata(get_class($entity))->discriminatorValue;

My approach is to simply access it's value through the meta data doctrine generates
$cmf = $em->getMetadataFactory();
$meta = $cmf->getMetadataFor($class);
$meta->discriminatorValue
will give you the value, so as a method
public static function get_type()
{
//...get the $em instance
$cmf = $em->getMetadataFactory();
$meta = $cmf->getMetadataFor(__CLASS__);
return $meta->discriminatorValue;
}
I cache the metadata in a static variable for each class that extends my base entity, there is a lot of other useful information in there as well ...

No that is not possible, but you can do something like: get_class($object) == TYPE_CONST

There's a slicker way to do it in PHP 5.3:
abstract Parent
{
const TYPE = 'Parent';
public static function get_type()
{
$c = get_called_class();
return $c::TYPE;
}
}
class Child_1 extends Parent
{
const TYPE = 'Child Type #1';
//..whatever
}
class Child_2 extends Parent
{
const TYPE = 'Child Type #2';
//...whatever
}

Use something like this if you want, like me, avoid use of const :
public function getType()
{
$type = explode('\\', get_class($this));
return end($type);
}

Another slicker and faster way than to overload the method in every child or define a constant in every child is to use reflection class to retrieve the name of the class without the namespace :
public function getType() {
return (new \ReflectionClass($this))->getShortName();
}
It also works in any php version since php 5
It might not return exactly the discriminator name depending on your discriminator map declaration but it will return the child entity name (the class name) which is a great way to name and distinguish the different subentities
Without a need to define anything in the subclasses.

Related

Abstract factory class returning an interface type, method not found?

I am attempting to use the abstract factory pattern. I've created a class, FactoryProducer, that creates a class-specific factory based on a string passed into one of the two class methods.
The issue I'm having is that I've extended one of the concrete factory classes, but the FactoryProducer returns an interface type that doesn't include that method. VS Code is saying that the method doesn't exist. Here's the relevant code
Factory Producer Class
/**
* Creates database or model factory.
*/
class FactoryProducer {
/**
* Creates a factory for the Model classes based on the given function argument.
*
* #param string $type The model class (e.g. 'asset', 'employee')
* #return ModelFactoryInterface The given model's factory.
*/
public static function getModelFactory(string $type) {
switch($type) {
case 'asset':
return new \Inc\Models\AssetModelFactory;
break;
case 'application':
//code here
break;
}
}
}
Concrete Factory Class AssetModelFactory
/**
* The factory for the Asset class.
*/
class AssetModelFactory implements ModelFactoryInterface {
/**
* Create an empty Asset class object.
*
* #return Asset
*/
function create(): Asset {
return new Asset();
}
/**
* Creates an Asset object instantiated with the given properties.
*
* #param array $props The properties for the class.
* #return void
*/
function createWithProps(array $props): Asset {
$asset = new Asset();
$keysToCheck = ['name', 'companyName', 'type', 'label', 'location', 'employees', 'key'];
if(\Inc\Base\Helpers::array_keys_exists($keysToCheck, $props)) {
$asset->setProperties($props['name'], $props['companyName'], $props['type'], $props['label'], $props['location'], $props['employees'], $props['key']);
return $asset;
}
else {
return new \WP_Error('incorrect_props', 'You did not include all of the necessary properties.');
}
}
}
The issue I'm having is with the second method, createWithProps(array $props), because the interface doesn't include this method:
/**
* The interface for model classes.
*/
interface ModelFactoryInterface {
/**
* Creates an object that extends AbstractModel
*
* #return AbstractModel
*/
public function create(): AbstractModel;
}
As you can see, the concrete class objects extend an abstract class. Here is the code that is giving the error:
$assetFactory = \Inc\Base\FactoryProducer::getModelFactory('asset');
$asset = $assetFactory->createWithProps($request);
I'm wondering if I've implemented the abstract factory class incorrectly, or if this is expected behavior from VS Code given that the returned concrete class from FactoryProducer is dynamic based on the parameter (e.g. I've passed 'asset' into the FactoryProducer::getModelFactory method which will, ultimately, return an instance of AssetModelFactory, but the official return type is ModelFactoryInterface).
Thank you in advance for any advice you can provide.
I was able to figure out what I did. I am used to programming languages like C# wherein I can strongly-type the variable prior to declaring it. I ended up refactoring the code so that the factories have methods that return a specific concrete object instead of using a switch statement:
class DBFactory implements DBFactoryInterface {
public static function createAsset(): AbstractDB {
return new \Inc\DB\AssetDB;
}
public static function createApplication(): AbstractDB {
return new \Inc\DB\ApplicationDB;
}
public static function createCompany(): AbstractDB {
return new \Inc\DB\CompanyDB;
}
}

PHPDoc: document parent class method that always yields itself or descendant class?

Consider this code:
class ParentClass {
public static function generate($className = __CLASS__){
if(!$className) return new self();
else return new $className();
}
}
class ChildClass extends ParentClass {
/**
* #param string $className
* #return ChildClass
*/
public static function generate($className = __CLASS__) {
return parent::generate($className);
}
}
var_dump($ChildClass::generate()); // object(ChildClass)[1]
ChildClass::generate() returns an instance of ChildClass wherever I use it because I never provide a $className argument. Problem is that my IDE gives me a warning about the parent::generate() call not matching the documented return type:
I would like to make this warning go away by adding documentation to the parent method. I could do:
#return ParentClass | ChildClass
Adding this to the parent method works but that's not practical because there are many dozen children classes, and there could be many more in the future. I have tried both of the following:
#return static
#return $className
but that hasn't made the warning go away. Is there a PHPDoc approved way to indicate that the calling child class will always be returned? Or -- more accurately -- that a class of type $className will be returned? If not, is there a way that works even with just my IDE? (PhpStorm 2017.2)
Update
#gogaz comment below got me to think: it would be enough if a PHPDoc #return could indicate something like self | descendants
You can document parent class method and then just inherit that on a child class method.
class ParentClass
{
/**
* #param string $className
* #return ChildClass
*/
public static function generate($className = __CLASS__){
if(!$className) return new self();
else return new $className();
}
}
class ChildClass extends ParentClass
{
/**
* #inheritdoc
*/
public static function generate($className = __CLASS__) {
return parent::generate($className);
}
}
I hope that this will solve your problem.
UPDATE:
Another way to do this is by using interface that all classes will implement, so you could expect that your generate method will return some class that implements that interface.
/**
* ...
* #return MyCustomInterface
*/
If this doesn't solve your issue... Then, you can set return type as "mixed", this will suppress warning and it won't lie :) because your generate method can return any class you specify via argument...
/**
* ...
* #return mixed
*/
I've run across something similar (PHPStorm is more anal than PHP is on the subject). Unless there's some reason you HAVE to have it be specific to the child class, I would do this
class ParentClass {
/**
* #param string $className
* #return ParentClass
*/
public static function generate($className = __CLASS__){
if(!$className) return new self();
else return new $className();
}
}
Remember, your ChildClass is still an instance of ParentClass

Type Hinting abstract class singleton

How would one Type Hint a static singleton-returning method on an abstract class, which returns instances of the extending called classes?
For example, let's look at the following code:
<?php
abstract class Foo {
/** #return Foo */
public function init() {
static $instance;
if ( is_null($instance) ) {
$class = get_called_class();
$instance = new $class();
}
return $instance;
}
}
class Bar extends Foo {
public $name = "Bar name";
}
class Baz extends Foo {
public $age = 42;
}
My intention is for tools such as PhpStorm to understand that Bar::init() returns an object of type Bar and that Baz::init() returns an object of type Baz. Thus, for example, objects created from the Baz::init() method would auto-complete the name property but not the age property.
Obviously the current type hint #return Foo is wrong as the method will never return an object instance of an abstract class.
So #return static will work in this case for PHPStorm. It is the easiest option and will provide what you are looking for.
Optionally you can use the #method annotation against the class although this is very manual and needs to be done for each class. There is another strange thing with this method in PHPStorm where if you navigate to the init() method (ctrl+click or w/e) it will navigate to this annotation first. However this is how that looks:
/**
* #method Bar init()
*/
class Baz extends Foo
{
}
Where optionally as a final resort--and I really don't think you will need it but its here for completeness. Extend the method and add your return annotation as you would a normal method.
/**
* #return Baz
*/
public function init()
{
return parent::init();
}
You could try this:
abstract class Foo {
/** #return static */
public function init() {
static $instance;
if ( is_null($instance) ) {
$class = get_called_class();
$instance = new $class();
}
return $instance;
}
}
This will probably require PhpStorm to be set up with PHP 5.3+ type hints to work.

PHPDoc for "casted" return type

I have some kind of factory where a method returns one of multiple possible classes but they all inherit the same parent class. The factory method returns always the same class when it gets the same parameter. So when I receive a class from the factory method I know the subclass beside the common parent class.
The problem is that PhpStorm shows me a warning when I try to set the #return type to the child class.
Here an example:
abstract class Base {}
class A extends Base {}
class B extends Base {}
class T {
/**
* #param string $class
* #return Base
*/
public function returnBase($class)
{
switch ($class) {
case 'A':
return new A();
break;
// ... more cases ...
default:
return new B();
}
}
/**
* #return A
*/
public function test()
{
return $this->returnBase('A'); // Warning: "Return value is expected to be 'A', 'Base' returned"
}
}
I know in this example I could set the return type of returnBase() to A|B, but in my actual code I have much more classes. I don't want to set the return type of the test() method to "Base" because the subclass might have unique methods/properties.
You can set a varibale type for each returnBase() method like this :
/**
* #return A
*/
public function test()
{
/** #var A $a */
$a = $this->returnBase('A');
return $a;
}
Accroding to this article about PHP’s Garbage Collection, a redundant variable doesn't have any impact on memory consumtion.

Object slicing in PHP

Is it possible to get the base object from derived object in php
Something like this
class base {}
class derived extends base{
public function getBase()
{
return (base)$this;
}
The above code, throws out a error
You can use parent:: to resolve to a parent method, property or constant.
If you're trying to get the name of the base class, here's a way to do that:
class base {
public function getClassName() {
return "base";
}
}
class derived extends base{
public function getBaseClassName() {
return parent::getClassName();
}
public function getClassName() {
return "derived";
}
}
$d = new derived();
echo $d->getBaseClassName();
Edit: When you use inheritance to extend a class (eg: derived extends base), you are saying that derived is a kind of base, and all instances of derived are also instances of base. In most OO languages, the two instances, base and derived are not separate entities, and they can not be treated separately. (C++ is an exception to the rule in this regard).
If you need to treat the instances separately, then inheritance is the wrong tool for the job. Use extension by containment, rather than inheritance. This will look something like the following:
class base {
public someBaseFunction() {
// ...
}
}
class derived {
/**
* each instance of `derived` *contains* an in instance of `base`
* that instance will be stored in this protected property
*/
protected $base;
/**
* constructor
*/
function __construct() {
// remember to call base's constructor
// passing along whatever parameters (if any) are needed
$this->base = new base();
// ... now do your constructor logic, if any ...
}
/**
* Here's the method that fetches the contained
* instance of `base`
*/
public function getBase() {
return $this->base;
}
/**
* If needed, `derived` can implement public elements
* from `base`'s interface. The logic can either delegate
* directly to the contained instance of base, or it can
* do something specific to `derived`, thus "overriding"
* `base`'s implementation.
*/
public function someBaseFunction() {
return $this->base->someBaseFunction();
}
/**
* of course, `derived` can implement its own public
* interface as well...
*/
public function someOtherFunction() {
}
}

Categories