32

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.

Greg
  • 2,413
  • 5
  • 22
  • 23
  • Possible duplicate of [Map a discriminator column to a field with Doctrine 2](https://stackoverflow.com/questions/21284964/map-a-discriminator-column-to-a-field-with-doctrine-2) – sroes Jul 05 '17 at 14:54

8 Answers8

25

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;
    }
}
xPheRe
  • 2,333
  • 24
  • 33
18

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...
Byscripts
  • 2,578
  • 2
  • 18
  • 25
  • 6
    This doesn't actually access the discriminator field, it just pretends to. Consider a discriminator map which has multiple values for the same class, but you need the actual value from the database and not a guess as to which value it should be. – Steve Buzonas Jan 08 '15 at 20:03
13

It's possible either with the EntityManager or using the DocumentManager.

$documentManager->getClassMetadata(get_class($entity))->discriminatorValue;
Lakatos Gyula
  • 3,949
  • 7
  • 35
  • 56
8

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 ...

user2272605
  • 161
  • 2
  • 2
5

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

beberlei
  • 4,297
  • 1
  • 23
  • 25
  • 1
    What about cases when multiple discriminator values are mapped to same class? I'm migrating some project to Symfony and there are 5 types (class constants) mapped to 2 classes (hydrated by one database field, like discriminator). Other parts of code rely on `MappedObject::getType()` vs class constant comparison, so I need discriminator value or I have to refactor codebase with unique classes for each discriminator's value... – Wirone Feb 07 '17 at 13:07
4

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
}
Yaron
  • 1,867
  • 20
  • 16
  • Note that in php5.3 the `get_called_class` call is not necessary you can make use of the `static` keyword eg: `return static::TYPE` – Tofandel Mar 03 '20 at 17:21
3

Use something like this if you want, like me, avoid use of const :

public function getType()
{
    $type = explode('\\', get_class($this));

    return end($type);
}
dizda
  • 79
  • 1
  • 7
3

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.

Tofandel
  • 3,006
  • 1
  • 29
  • 48
  • For my case this is the best method. I don't have a map set. And the type will be the lowercased class name. So i can out my images (files) into seperate folders like that: public function getFullPath(): ?string { return $this->getType() . "/" . $this->getFolder() . $this->getFilename(); } public function getType(): string { return strtolower((new \ReflectionClass($this))->getShortName()); } – Slowwie Mar 09 '21 at 18:32