PHP Enum? Cherchez la femme...
http://www.edesign.nl/2013/06/09/the-enumerated-type-for-php/

PHP Enum? Cherchez la femme...

DISCLAIMER: The following are absolutely personal, not exhaustive, considerations/information/dissemination. There may be errors and inaccuracies. The code fragments reported they are only by way of example.


The evolutions of the PHP language are leading to an object model increasingly complete and the introduction of stronger types system, aimed at making the language healthier and effective.

Typing of properties,functions and function parameters has been introduced with version 7.4.x.

In examining these characteristics I wanted to attempt an exercise either to learn the language and to squeeze the available object model of PHP.

What better occasion than to create Enumerative Types? The target that have been given to me have become the following:

  • strongly typed elements
  • simplicity of definition
  • printable on console
  • with a name and an ordinal (declaration order)
  • each enumerative must be able to be used with === operator and with switch statement
  • there must be a string-enumerative conversion utility
  • an element of an enumerative can have its own data in consideration that each enumerative is a singleton
  • an element being singleton cannot be cloned
  • an enumerative must be able to implement interfaces

Logically I was inspired by the Java world but there is no direct equivalence of constructs, I had to cheat a little. Other authors started from the concept of constants (const construct) or from the definition static functions ( Ex. CardType :: HEART () ) which expose private variables. Constants currently, however, they cannot be objects but only primitive types, as well as static functions require invocation.

Instead, I preferred to refer to the concept of static properties, to have the possibility of declarations like the following:

final class CardType
{
     public static CardType $HEART;

     public static CardType $DIAMON;

     public static CardType $CLUB;

     public static CardType $SPADE;  
}

If we try to use this type, we immediately run into an error because the properties must be initialized before using them (or they need to be nullable with "elvis operator"). There's another problem, the initialisations must take place in a constructor but if this is defined public it became possible to create other objects, losing the nature of singleton that I want from "this kind" of enumerations.

So, I started to decompose the initialization from the declaration of the enumeratives and to do this I defined a base class (abstract) from which all possible enumerative will derive. This class has interesting features:

  • a private but heritable costructor (protected)
  • a special public activation function
  • a series of convenience functions for string-enumerative conversions
  • another heritable method, that we will see, playing the role of costructor for private properties of instances of specific enumeration
  • the cloning method is overwritten to avoid creating duplicates
abstract class  Enum
{
  ...
 
    /** @psalm-suppress PossiblyUnusedMethod  */
    protected final function __construct(String $name, int $ordinal)
    {
      ...
    }
    
    /**
     *   the substitute constructor function...
     */
    protected function initializer():void
    {
    }
    
    /**
     *  the commodity function to represent every enumeration
     */
    public function __toString():string
    {
        return "Enum[".static::class." : ".$this->name."]";
    }
    
    
    public final function __clone()
    {
        throw new \LogicException("No clone allowed for Enum: ".static::class);
    }
    
   
    
    /**
     *
     * @param class-string<Enum> $fqcn
     * @throws \InvalidArgumentException
     *
     **/
    public static final function from($fqcn):void
    {
     ...
    }
  ...
}   

If now we wanted to make our enumeratives functioning and ready to use, we will just do:

//
//  Type Definition...
//
final class CardType extends Enum
{
     public static CardType $HEART;

     public static CardType $DIAMON;

     public static CardType $CLUB;

     public static CardType $SPADE;  
}

//
//  Type Activation...
//
Enum::from(CardType::class);

//
//  Type Usage...
//
printf("card type: %s with name: %s and ordinal: %s".PHP_EOL,
                        (string)CardType::$HEART,
                                CardType::$HEART->name(),
                                CardType::$HEART->ordinal());
                             

The main activity is done by the activation functionality (Enum::from). This function performs a series of checks:

  • has the enumeration already been declared elsewhere?
  • is a new enumeration class declared final
  • is a direct extention class of the base class (Enum)
  • the properties are all enumerative ( same declared type)

After execution of the Enum::from function, all singleton are initialized (by reflection mechanics offered by PHP) including the definition of the name e the ordinal that represents the declaration position of the enumerative.

At this point we get that the definition of an enumerative force the developer to declare the type as final and to extend the base class (Enum) only; also, only after initialization, properties are usable and cannot be neither cloned nor overwritten despite being public property (at least not in a simple way...)

The work that followed was to provide the same capabilities as the Java enumeration and a similar developer experience. In particular, to make it possible for the developer to introduce specific properties of a given enumerative type and initialize them. I resorted to the presence of a "protected" function that can be overwritten to allows you to define private properties as if you were operating in a costructor; this function (initializer) is actually invoked in the moment of initialization of the enumeratives (Enum::from) simulating a static initializer (here it should be open a specific paragraph since another powerful costruct of modern PHP are Closure that, between other usages, can be the starting point of lazy inizializer and more tricks about a clean and effective use of PHP visibilities e functions but... this is... for a next story...).

Let's see an example of definition with private properties:

final class CardType extends Enum
 {
     public static CardType $HEART;

     public static CardType $DIAMOND;

     public static CardType $CLUB;

     public static CardType $SPADE;  
     
     /** @psalm-suppress PropertyNotSetInConstructor  */
     private string $description;


     public final function descript():string
     {
      return $this-> description;
     }

     protected final function initializer():void{
         switch($this->name()){
             case 'HEART': $this->description = $this->heartDesc();break;
             case 'DIAMON': $this->description = $this->diamonDesc();break;
             case 'CLUB':   $this->description = $this->clubDesc(); break;
             case 'SPADE':  $this->description = $this->spadeDesc();break;
             default: break; // ...maybe throws exception?...
         }         
     }

     private function heartDesc():string
     {
      return "My heart goes boom boom!";
     }

     private function diamonDesc():string
     {
      return "I'm the precious one!";
     }

     private function clubDesc():string
     {
      return "Flowers all around the world!";
     }

     private function spadeDesc():string
     {
       return "Lemmy Docet!";
     }

 }

Completed the fundamental mechanics, I was able to insert the series of elements of commodity that allow you to carry out useful activities:

  • valueOf (string): Enum
  • valueOrDefault (string, Enum): Enum
  • values (): array <Enum>

However, characteristics are still missing, such as for example how to deal with serialization: for now there is a policy by default: throws exceptions... but it would probably be more correct to adopt another strategy. Another aspect could be that of introducing a cleaning method in favor of Garbage Collection of enumeratives ( ...since an array of array is the Flyweight Pattern to preserve all Singletons defined during the program execution).

In the meantime I leave you with the code, validated with PHPStan and PSALM


Alternative Resources:


Good job and Bye ;-)

To view or add a comment, sign in

Insights from the community

Others also viewed

Explore topics