BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles PHP 8 - Classes and Enums

PHP 8 - Classes and Enums

Bookmarks

Key Takeaways

  • PHP 8.1 adds support for read-only properties that make class properties invariant and unmodifiable.
  • PHP 8.0 has a new feature that automatically promotes class constructor parameters to corresponding class properties with the same name if the constructor parameters are declared with a visibility modifier and are not of type callable.
  • PHP 8.1 adds support for final class and interface constants, and for interface constants that can be overridden. 
  • As of PHP 8.0, the special ::class constant can be used on objects, and as of PHP 8.1 objects can be used in define().
  • PHP 8.1 adds support for enumerations, or enums for short, to declare an enumerated set of values that are similar to, though not the same as, class objects.
     

This article is part of the article series "PHP 8.x". You can subscribe to receive notifications about new articles in this series via RSS.

PHP continues to be one of the most widely used scripting languages on  the web with 77.3% of all the websites whose server-side programming language is known using it according to w3tech. PHP 8 brings many new features and other improvements, which we shall explore in this article series.

 

In this article, we will review new PHP 8 features related to classes, including:

  • Enums,  a layer over classes to specify an enumerated list of possible values for a type 
  • The new readonly modifier for a class property, which makes the property unmodifiable after its initialization 
  • Constructor parameter promotion, useful to assign a constructor parameter value to an object property automatically.

Read-Only Class Properties

Developers have been scrounging for ways to make class properties immutable for use cases such as value objects. Often, properties must be initialized once, usually in a constructor, and are not meant to be ever modified. One alternative that has been used is to make a property private and declare only a public getter method for it. This reduces the scope for modification but does not preclude modification. To make a class property invariant, PHP 8.1 adds support for readonly properties with the condition that the property must be typed. A typed property can indeed be declared readonly with the new readonly keyword. The following script declares a readonly property of type int called $a. The property’s value is set only once in the constructor. The script outputs the value 1 when run. 

<?php
class A {
   public readonly int $a;
   public function __construct(int $a) {     
       $this->a = $a;
   }
}
$a = new A(1);
echo $a->a;

To demonstrate the effect of making the property readonly, modify its value with the following assignment.

 $a->a = 2;

This will generate a error message:

Fatal error: Uncaught Error: Cannot modify readonly property A::$a 

To demonstrate that the condition that the readonly property must be typed holds, try making an untyped property readonly as in the following script:

<?php
class A {
   public readonly  $a;
   public function __construct(int $a) {        
       $this->a = $a;
   }
}
$a = new A(1);

The script generates an error message:  

Fatal error: Readonly property A::$a must have type

If you don’t want a  readonly property to have a specific type, you can declare it as mixed, e.g.:

public readonly mixed $a;

In addition to the type requirement, other limitations apply to readonly properties. A readonly property cannot be declared static. Run the following script to demonstrate it:

<?php
class A {
   public static readonly int $a;
   public function __construct(int $a) {        
       $this->a = $a;
   }
}
$a = new A(1);

The script generates an error message:

Fatal error: Static property A::$a cannot be readonly

A readonly property can only be initialized only from the scope in which it is declared. The following script initializes a readonly property, but not in the scope in which it is declared:  

<?php
class A {
   public  readonly int $a;
}
$a = new A();
$a->a=1; 

The script generates an error message when run:

Fatal error: Uncaught Error: Cannot initialize readonly property A::$a from global scope 

You may consider declaring a readonly property with a default value at the time of initialization, but this wouldn’t be particularly useful as you could use a class constant instead. Therefore, setting a default value for a readonly property has been disallowed. The following script declares a default value for a readonly property.

<?php
class A {
   public  readonly int $a=1;
   public function __construct(int $a) {        
       $this->a = $a;
   }
}

The script generates an error message when run:

Fatal error: Readonly property A::$a cannot have default value

The objective of the readonly property feature is to make a class property immutable. Therefore, a readonly property cannot be unset with unset() after initialization. The following script calls unset() on a readonly property after it has been initialized.

<?php
class A {
   public  readonly int $a;   
   public function __construct(int $a) {        
       $this->a = $a;
       unset($this->a);
   }
}
$a = new A(1);

The script generates an error message when run:

Fatal error: Uncaught Error: Cannot unset readonly property A::$a 

You could always call unset() on a readonly property before initialization as in the following script:

<?php
class A {
   public  readonly int $a;
     
   public function __construct(int $a) {
        unset($this->a);
       $this->a = $a;        
   }
} 
$a = new A(1);
echo $a->a;

The script runs with no errors and outputs the value of 1

A readonly property cannot be modified by simple reassignment or by any other operator manipulation. The following script does not use a reassignment statement for a readonly property, but uses an increment operator on it. 

<?php
class A {
   public  readonly int $a;     
   public function __construct(int $a) {         
       $this->a = $a;       
   }
} 
$a = new A(1);
$a->a++;

The effect is the same, and so is the error message:

Fatal error: Uncaught Error: Cannot modify readonly property A::$a

Specifically, it is just the readonly property that is immutable, not any objects or resources stored in it. You may modify any objects, and non-readonly properties stored in a readonly property. The following script sets the value of class property $a that is not readonly through a readonly property $obj of type object

<?php
class A {
   public int $a;
    public function __construct(public readonly object $obj) {}
}
$a = new A(new stdClass); 
$a->obj->a=1; 
echo $a->obj->a;

The script runs with no errors and outputs the value of 1. 

PHP 8.2 adds readonly classes as an extension  of the readonly class properties feature. If a class is declared with the readonly modifier, all class properties are implicitly readonly. The class properties in a readonly class must be typed and non-static, for example:

readonly class A
{
    public int $a;
    public string $b;
    public array $c;
    
    public function __construct() {
        $this->a = 1;
        $this->b = "hello";
        $this->c = [
                    "1" => "one",
                    "2" => "two",
                   ];
    }
}

Readonly classes do have some limitations in that dynamic properties cannot be defined, and only a readonly class can extend another readonly class. 

Constructor property promotion

The objective of constructor property promotion, a feature introduced in PHP 8.0, is to make class property declarations and initializations unnecessary. To elaborate, consider the following script in which class properties $pt1, $pt2, $pt3, and $pt4 are declared in the Rectangle class and initialized in the class constructor. 

<?php

class Point {
    public function __construct(
        public float $pt = 0.0,
    ) {}
}

class Rectangle {

  public Point $pt1;
  public Point $pt2;
  public Point $pt3; 
  public Point $pt4;

  public function __construct(
        Point $pt1,
        Point $pt2,
        Point $pt3,
        Point $pt4,
    ) {
        $this->pt1 = $pt1;
        $this->pt2 = $pt2;
        $this->pt3 = $pt3;
        $this->pt4 = $pt4;
        
  }
}

With the new constructor property promotion feature the script is reduced to the following:

<?php
 
class Point {
    public function __construct(
        public float $pt = 0.0,
    ) {}
}

class Rectangle {
 
  public function __construct(
       public Point $pt1,
       public Point $pt2,
       public Point $pt3,
       public Point $pt4,
    ) {
         
  }
}

The constructor body may be empty or may contain other statements, which are run after the promotion of constructor arguments to the corresponding class properties. The only requirement for a constructor argument to be promoted to a class property is that it include a visibility modifier. The constructor’s argument value is automatically assigned to a class property with the same name. 

The following script snippet demonstrates the automatic promotion and initialization of public constructor arguments to class properties using the example of the Rectangle class with a call to the class constructor as follows: 

<?php
 …
 …
 $pt1=new Point();
 $pt2=new Point();
 $pt3=new Point();
 $pt4=new Point();

 $a = new Rectangle($pt1,$pt2,$pt3,$pt4);

// Output the value of the class properties:
var_dump($a->pt1);
var_dump($a->pt2);
var_dump($a->pt3);
var_dump($a->pt4);

You’ll find that the class properties do get added and initialized implicitly, giving the following output.

object(Point)#1 (1) { ["pt"]=> float(0) } 
object(Point)#2 (1) { ["pt"]=> float(0) } 
object(Point)#3 (1) { ["pt"]=> float(0) } 
object(Point)#4 (1) { ["pt"]=> float(0) }

If a constructor argument does not include a visibility modifier, it is not promoted to the corresponding class property.  Not all constructor arguments have to be promoted. The following script does not promote constructor argument $pt4 as it is not declared with a visibility modifier.

<?php
 
class Point {
    public function __construct(
        public float $pt = 0.0,
    ) {}
}

class Rectangle {
 
  public function __construct(
       public Point $pt1,
       public Point $pt2,
       public Point $pt3,
       Point $pt4,
    ) {
       
  }
}

Call the Rectangle class constructor and output class property values, as before. In this case, the result is different because $pt4 does not include a visibility modifier and therefore it is never promoted to a corresponding class property. A warning message is output:

Warning: Undefined property: Rectangle::$pt4

You would need to declare and initialize the $pt4 class property explicitly as in the modified script:

<?php 
class Point {
    public function __construct(
        public float $pt = 0.0,
    ) {}
}

class Rectangle {
 
  public Point $pt4;

  public function __construct(
       public Point $pt1,
       public Point $pt2,
       public Point $pt3,
       Point $pt4,
    ) {
       $this->pt4=$pt4;
  }
}

Now, you can call the constructor and output class properties as before with the same output.  

Another requirement for a class constructor argument to be promoted to a corresponding class property is that it is not of type callable.  The following script declares constructor arguments of type callable.

<?php

class Rectangle { 
  
  public function __construct(
       public callable $pt1,
       public callable $pt2,
       public callable $pt3,
       public callable $pt4,
    ) {
        
  }
}

When run the script generates an error message:

Fatal error: Property Rectangle::$pt1 cannot have type callable

In the first article in the PHP 8 series, we explained how to use the new operator in initializers, including for initializing default values for function parameters. The new operator may also be used to set constructor parameter default values, along with constructor property promotion, as in the following script.

 

<?php

class Point {
    public function __construct(
        public float $pt = 0.0,
    ) {}
}

class Rectangle {
 
  public function __construct(
       public Point $pt1=new Point(),
       public Point $pt2=new Point(),
       public Point $pt3=new Point(),
       public Point $pt4=new Point(),
    ) {
         
  }
}

The Rectangle class constructor may be called without any constructor arguments, and the promoted property values be output:

$a = new Rectangle();
var_dump($a->pt1);
var_dump($a->pt2);
var_dump($a->pt3);
var_dump($a->pt4);

The output is:

object(Point)#2 (1) { ["pt"]=> float(0) } 
object(Point)#3 (1) { ["pt"]=> float(0) } 
object(Point)#4 (1) { ["pt"]=> float(0) } 
object(Point)#5 (1) { ["pt"]=> float(0) }

Objects use in define()

The built-in define() function is used to define named constants. With PHP 8.1 objects can be passed to define() as in the following example script.  

<?php

class Point {
    public function __construct(
        public float $pt = 0.0,
    ) {}
} 

define("PT1", new Point(1.0));
var_dump(PT1); 

The output from the script is:

object(Point)#1 (1) { ["pt"]=> float(1) }

Class constants may be declared final

PHP 8.1 allows you to declare class constants using the final keyword.  Additionally, if a class constant is declared final in a class, any class extending it cannot override, or redefine, the constant’s value.   In the following script, a class constant c, which is declared to be final in class A, is redefined in a class B that extends it.

<?php
class A
{
    final public const c = 1;
}
class B extends A
{
    public const c = 2;
}

When the script is run, the following error message is generated:

Fatal error: B::c cannot override final constant A::c 

The special ::class constant can be used on objects

The special ::class constant, which allows for fully qualified class name resolution at compile time, can also be used with class objects as of PHP 8.0. One difference is that class name resolution happens at runtime with objects, unlike compile-time resolution for classes. Using ::class on an object is equivalent to calling  get_class() on the object.  The following script uses ::class on an object of class A, which output “A”. 

<?php 
class A {
} 
$a = new A();
print $a::class;
?>

Interfaces constants can be overridden

As of PHP 8.1, interface constants can be overridden by a class, or interface, that inherits them. In the following script, interface constant c is overridden by a class constant with the same name. The value of the overridden constant may be the same or  different. 

<?php
interface A
{
    public const c = 1;
}

class B implements A
{
    public const c = 2;

   public function __construct() { 
        
   }
}

Both constant values may be output:

echo A::c;
echo B::c;

The output is:

1
2

As it holds for class constants that are declared final, interface constants declared final cannot be overridden. The following script overrides an interface constant declared final.

<?php
interface A
{
    final public const c = 1;
}

class B implements A
{
    public const c = 2;
}

An error message is output when script is run:

Fatal error: B::c cannot override final constant A::c

Autoload function __autoload() is removed

The __autoload() function that was deprecated in PHP 7.2.0 has been removed in PHP 8.0. If the __autoload() function is called, the following error message results:

Fatal error: Uncaught Error: Call to undefined function __autoload()

 

Enums

An enumeration, or enum for short, is a new feature to declare a custom type with an explicit set of possible values. The new language construct enum is used to declare an enumeration with the simplest enumeration being an empty one.

enum FirstEnum {
}

An enum may declare possible values using the case keyword, for example:

enum SortType {
  case Asc;
  case Desc;
  case Shuffle;
}

The discussion on enumerations is bundled with classes because of their similarity.

How enums are similar to classes

  • An enum is a class. The example enum SortType is a class, and its possible values are object instances of the class.
  • Enums share the same namespace as classes, interfaces, and traits.
  • Enums are autoloadable as classes are.
  • Each enum value, such as the Asc, Desc and Shuffle values for the SortType enum is an object instance. An enum value would pass an object type check.
  • An enum’s values, or the case names, are internally represented as class constants, and are therefore case-sensitive.

Enums are useful for several use cases, such as:

  • A structured alternative to a set of constants 
  • Custom type definitions
  • Data modeling
  • Monad-style programming
  • Defining a domain model
  • Validation by making unsupported values unrepresentable, resulting in reduced requirement for code testing 

We shall discuss enums with some examples. Because an enum’s values are objects they may be used where an object can be used, including as function parameter type, and function return type. In the following script, enum SortType is used as a function parameter type, and function return type.

<?php

enum SortType  
{
 
  case Asc;
  case Desc;
  case Shuffle;
 
}
class A {

  public function sortType():SortType{
    return SortType::Asc;
  }
 
}
$a=new A();
var_dump($a->sortType());

The output from the script is :

enum(SortType::Asc)

Next, we shall use the same example of sorting an array that  we used in the first article in this series. The parameter for the sortArray function is of type SortType, which is an enum. 

function sortArray(SortType $sortType) { 

  $arrayToSort=array("B", "A", "f", "C");
  …
  …
}

The enum object value is compared using the == operator.

if ($sortType == SortType::Asc){...}

The same example as used with enums is as follows:

<?php
enum SortType {
  case Asc;
  case Desc;
  case Shuffle;
   
}
 
function sortArray(SortType $sortType) { 

$arrayToSort=array("B", "A", "f", "C");

    if ($sortType == SortType::Asc) {
             sort($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  
        } elseif ($sortType == SortType::Desc) {
             rsort($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  
        } elseif ($sortType == SortType::Shuffle){
              
             shuffle($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  
     }
}
$val = SortType::Asc;
sortArray($val);
sortArray(SortType::Desc);
sortArray(SortType::Shuffle); 

The output of the script  for the array sorting example is:

0 = A 1 = B 2 = C 3 = f 
0 = f 1 = C 2 = B 3 = A 
0 = f 1 = A 2 = B 3 = C

Because an enum case, or possible value, is an object instance, the instanceof operator may be used with an enum value, for example:

if ($sortType instanceof SortType) {...}

An enum’s value is not converted to a string and cannot be used as an equivalent string. For example, if you call the sortArray() function with a string argument:

sortArray('Shuffle');

An error would result:

Fatal error: Uncaught TypeError: sortArray(): Argument #1 ($sortType) must be of type SortType, string given

All enum values, or cases, have a read-only property called name that has its value as the case-sensitive name of the case. The name property could be used for debugging. For example, the following print statement would output “Asc”. 

print SortType::Asc->name;  

An enum’s values must be unique, case-sensitive values. The following script has unique values:

<?php

enum SortType  
{
 
  case Asc;
  case Desc;
  case ASC;
 
}

But the following script doesn’t declare unique values:

<?php

enum SortType  
{
  case Asc;
  case Desc;
  case Asc;
}

The script generates the following error message:

 Fatal error: Cannot redefine class constant SortType::Asc

The enumerations we discussed are basic enumerations, or pure enums. A pure enum only defines pure cases with no related data. Next, we discuss another type of enums called backed enums.

Backed enums

A backed enum defines scalar equivalents of type string or int for the enum cases, for example: 

enum SortType:int {
  case Asc=1;
  case Desc=2;
  case Shuffle=3;
   
}

The scalar equivalent can be of type int or string, but not a union of int|string, and all cases of a backed enum must declare a scalar value. To demonstrate use the following Backed Enum:

<?php
enum SortType:int {
  case Asc=1;
  case Desc=2;
  case Shuffle;
}

It would produce an error message:

Fatal error: Case Shuffle of backed enum SortType must have a value

The scalar equivalents for backed enum cases must be unique. To demonstrate, use the following script that declares the same scalar equivalent for two enum cases:

<?php
enum SortType:int {
  case Asc=1;
  case Desc=2;
  case Shuffle=2;
}

The script would result in an error message:

Fatal error: Duplicate value in enum SortType for cases Desc and Shuffle

The scalar equivalents may be literal expressions in addition to being literal values, as example:

<?php

enum SortType:int {
  case Asc=1;
  case Desc=2+1;
  case Shuffle=3-3;
}

All backed enum values, or backed cases, have an additional read-only property called value that has its value as the scalar value of the backed case. For example, the following print statement would output the scalar equivalent value for the Desc case:

print SortType::Desc->value;

Here, value is a read-only property and unmodifiable. The following snippet assigns a variable as a reference to the value property of a backed case:

$sortType = SortType::Desc;
$ref = &$sortType->value;

The variable assignment would generate an error message:

Fatal error: Uncaught Error: Cannot modify readonly property SortType::$value 

Backed enums implement an internal interface BackedEnum that declares two methods:

  • from(int|string): self – Takes a scalar enum value for a backed case and returns the corresponding enum case. Returns a ValueError if the scalar value is not found.
  • tryFrom(int|string): ?self – Takes a scalar enum value for a backed case and returns the corresponding enum case. Returns null if the scalar value is not found.

The following script demonstrates the use of these methods.

<?php

 enum SortType:int {
  case Asc=1;
  case Desc=2;
  case Shuffle=3;
}

$sortType =  SortType::from(1);
 
print $sortType->value; 
echo “<br/>”;
$sortType = SortType::tryFrom(4) ?? SortType::Desc;
print $sortType->value;  
echo “<br/>”;

$sortType =  SortType::from("4");

The output is:

1
2
Fatal error: Uncaught ValueError: 4 is not a valid backing value for enum "SortType"

The from() and tryFrom() methods use strict/weak typing modes, the default being weak typing, which implies some implicit conversion. Float and string values for integers get converted to integer values as demonstrated by the following script:

<?php

enum SortType:int {
  case Asc=1;
  case Desc=2;
  case Shuffle=3;
}

$sortType =  SortType::from(1.0);
 
print $sortType->value; 
echo "<br/>";
$sortType = SortType::tryFrom("4") ?? SortType::Desc;
print $sortType->value;  
echo "<br/>";
$sortType =  SortType::from("2.0");
print $sortType->value; 

The output is:

1
2
2

A string that cannot get converted to an integer must not be passed when an int is expected, as in:

$sortType = SortType::from("A");

The preceding would result in a  error message:

Fatal error: Uncaught TypeError: SortType::from(): Argument #1 ($value) must be of type int, string given

In strict typing mode, the type conversion is not applied, and error messages such as the preceding, or the following are generated:

Fatal error: Uncaught TypeError: SortType::from(): Argument #1 ($value) must be of type int, float given

Both pure and backed enums implement an internal interface called UniEnum that provides a static method called cases() that outputs the possible values for the enum, i.e., the enum cases. The following script demonstrates the cases() method.

<?php  
 enum SortType  {
  case Asc;
  case Desc;
  case Shuffle;
   
}
 
enum BackedSortType:int {
  case Asc=1;
  case Desc=2;
  case Shuffle=3;
   
}

var_dump(SortType::cases());

var_dump(BackedSortType::cases());

The output is:

array(3) { [0]=> enum(SortType::Asc) [1]=> enum(SortType::Desc) [2]=> enum(SortType::Shuffle) } 

array(3) { [0]=> enum(BackedSortType::Asc) [1]=> enum(BackedSortType::Desc) [2]=> enum(BackedSortType::Shuffle) }

Enums may include methods and implement an interface

Enums, both pure and backed, may declare methods, similar to class instance methods. Enums may also implement an interface. The enum must implement the interface functions in addition to any other functions. The following script is a variation of the same array sorting example that includes an enum that implements an interface. The enum implements a function  from the interface in addition to a function not belonging to the interface. 

<?php

interface SortType
{
    public function sortType(): string;
}

enum SortTypeEnum implements SortType   {
  case Asc;
  case Desc;
  case Shuffle;
  
 public function sortType(): string
    {
        return match($this) {
            SortTypeEnum::Asc => 'Asc',
            SortTypeEnum::Desc => 'Desc',
            SortTypeEnum::Shuffle => 'Shuffle',
        };
    }

   public function notFromInterface(): string
    {
        return "Function Not From Interface";
    }
}
 
function sortArray(SortType $sortType) { 

$arrayToSort=array("B", "A", "f", "C");
if ($sortType->sortType() == "Asc") {
             sort($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  echo "<br/>";
        } elseif ($sortType->sortType() == "Desc") {
             rsort($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  echo "<br/>";
        } elseif ($sortType->sortType() == "Shuffle"){
              
             shuffle($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  echo "<br/>";
          }
         elseif  ($sortType instanceof SortType){
              
             sort($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  
 }
}
$val = SortTypeEnum::Asc;
 
sortArray(SortTypeEnum::Asc);
sortArray(SortTypeEnum::Desc);
sortArray(SortTypeEnum::Shuffle);
print SortTypeEnum::Asc->notFromInterface(); 

The output from the script is as follows:

0 = A 1 = B 2 = C 3 = f
0 = f 1 = C 2 = B 3 = A
0 = C 1 = f 2 = B 3 = A
Function Not From Interface 

A backed enum may also implement an interface and provide additional methods as in the following script:

<?php

interface SortType
{
    public function sortType(): string;
}

enum SortTypeEnum: string implements SortType
{
  case Asc = 'A';
  case Desc = 'D';
  case Shuffle = 'S';
   
 public function sortType(): string
    {
        return match($this->value) {
            'A' => 'Asc',
            'D' => 'Desc',
            'S' => 'Shuffle',
        };
    }

   public function notFromInterface(): string
    {
        return "Function Not From Interface";
    }
}
 
function sortArray(SortType $sortType) { 

$arrayToSort=array("B", "A", "f", "C");
    if ($sortType->sortType() == "Asc") {
             sort($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  echo "<br/>";
        } elseif ($sortType->sortType() == "Desc") {
             rsort($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  echo "<br/>";
        } elseif ($sortType->sortType() == "Shuffle"){
              
             shuffle($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  echo "<br/>";
          }
         elseif  ($sortType instanceof SortType){
              
             sort($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  
    }
} 
 
sortArray(SortTypeEnum::Asc);
sortArray(SortTypeEnum::Desc);
sortArray(SortTypeEnum::Shuffle);
print SortTypeEnum::Asc->notFromInterface(); 

The output is as follows:

0 = A 1 = B 2 = C 3 = f
0 = f 1 = C 2 = B 3 = A
0 = C 1 = f 2 = B 3 = A
Function Not From Interface 

Enums may declare static methods 

An enum may declare static methods. In a variation of the array sorting example, a static method chooseSortType() is used to choose the sort type based on the length of the array to be sorted:

<?php
enum SortType 
{
 
  case Asc;
  case Desc;
  case Shuffle;

   public static function chooseSortType(int $arraySize): static
    {
        return match(true) {
            $arraySize < 10 => static::Asc,
            $arraySize < 20 => static::Desc,
            default => static::Shuffle,
        };
    }
}
 
function sortArray(array $arrayToSort) { 
 
    if (SortType::chooseSortType(count($arrayToSort)) == SortType::Asc) {
             sort($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  echo "<br/>";
        } elseif (SortType::chooseSortType(count($arrayToSort)) == SortType::Desc) {
             rsort($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  echo "<br/>";
        } elseif (SortType::chooseSortType(count($arrayToSort)) == SortType::Shuffle){
              
             shuffle($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  echo "<br/>";
        }
          
} 
 

$arrayToSort=array("B", "A", "f", "C");
sortArray($arrayToSort); 

$arrayToSort=array("B", "A", "f", "C","B", "A", "f", "C","B", "A", "f", "C");

sortArray($arrayToSort);

$arrayToSort=array("B", "A", "f", "C","B", "A", "f", "C","B", "A", "f", "C","B", "A", "f", "C","B", "A", "f", "C","B", "A", "f", "C");

sortArray($arrayToSort);

The output is as follows:

0 = A 1 = B 2 = C 3 = f
0 = f 1 = f 2 = f 3 = C 4 = C 5 = C 6 = B 7 = B 8 = B 9 = A 10 = A 11 = A
0 = A 1 = B 2 = B 3 = C 4 = B 5 = C 6 = f 7 = A 8 = f 9 = C 10 = B 11 = f 12 = f 13 = A 14 = A 15 = B 16 = C 17 = f 18 = A 19 = B 20 = C 21 = f 22 = C 23 = A 

 

Enums may declare constants

An enum may declare constants. The following script declares a constant called A. 

<?php

enum SortType  
{
 
  case Asc;
  case Desc;
  case Shuffle;
  
  public  const A = 1;
 
}

The constants may refer to enum’s own cases, which are also constants. The following script for sorting an array demonstrates the use of constants that refer to the enum in which they are declared.

<?php
enum SortType 
{
 
  case Asc;
  case Desc;
  case Shuffle;

   public const ASCENDING = self::Asc;
   public const DESCENDING = self::Desc;
   public const SHUFFLE = self::Shuffle;
}
 
function sortArray(SortType $sortType) { 
    $arrayToSort=array("B", "A", "f", "C");
    if ($sortType == SortType::Asc) {
             sort($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  echo "<br/>";
        } elseif ($sortType == SortType::Desc) {
             rsort($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  echo "<br/>";
        } elseif ($sortType == SortType::Shuffle){
              
             shuffle($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  
    }
}

sortArray(SortType::ASCENDING); 
sortArray(SortType::DESCENDING); 
sortArray(SortType::SHUFFLE); 

The output is as follows:

0 = A 1 = B 2 = C 3 = f
0 = f 1 = C 2 = B 3 = A
0 = C 1 = B 2 = f 3 = A

Because an enum’s values are constants themselves, an explicit constant may not redefine an enum’s value. We demonstrate this in the following script:

<?php

enum SortType:int {

  public const Asc = "Asc";
  case Asc=1+1;
  case Desc=2;
  case Shuffle=3; 
}

The script generates the following error message:

Fatal error: Cannot redefine class constant SortType::Asc 

A enum’s case value must be compile-time evaluable, as  the following script declaring an enum case as a constant demonstrates.

<?php
 
enum SortType:int {

  const CONSTANT=4;

  case Asc=1;
  case Desc=2;
  case Shuffle=CONSTANT;
}

An error message is generated:

Fatal error: Enum case value must be compile-time evaluatable

Enums with traits

Enums may use traits. The following script for sorting an array declares a trait called ChooseSortType and uses the trait in an enum.

<?php

trait ChooseSortType {

     public function chooseSortType(int $arraySize): SortType
    {
        return match(true) {
            $arraySize < 10 => SortType::Asc,
            $arraySize < 20 => SortType::Desc,
            default => SortType::Shuffle,
        };
    }

}

enum SortType {
 
  use ChooseSortType;

  case Asc;
  case Desc;
  case Shuffle;
}
 
function sortArray(SortType $sortType, array $arrayToSort) { 
 
    if ($sortType->chooseSortType(count($arrayToSort)) == SortType::Asc) {
             sort($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  echo "<br/>";
        } elseif ($sortType->chooseSortType(count($arrayToSort)) == SortType::Desc) {
             rsort($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  echo "<br/>";
        } elseif ($sortType->chooseSortType(count($arrayToSort)) == SortType::Shuffle){
              
             shuffle($arrayToSort);
             foreach ($arrayToSort as $key => $val) {
                echo "$key = $val ";
             }  echo "<br/>";
        }
          
} 
 

$arrayToSort=array("B", "A", "f", "C");

sortArray(SortType::Desc,$arrayToSort); 

$arrayToSort=array("B", "A", "f", "C","B", "A", "f", "C","B", "A", "f", "C");

sortArray(SortType::Asc,$arrayToSort);

$arrayToSort=array("B", "A", "f", "C","B", "A", "f", "C","B", "A", "f", "C","B", "A", "f", "C","B", "A", "f", "C","B", "A", "f", "C");

sortArray(SortType::Desc,$arrayToSort);

The output is as follows:

0 = A 1 = B 2 = C 3 = f
0 = f 1 = f 2 = f 3 = C 4 = C 5 = C 6 = B 7 = B 8 = B 9 = A 10 = A 11 = A
0 = B 1 = A 2 = C 3 = f 4 = B 5 = A 6 = B 7 = A 8 = B 9 = A 10 = f 11 = A 12 = C 13 = B 14 = f 15 = f 16 = C 17 = f 18 = C 19 = B 20 = C 21 = C 22 = A 23 = f 

How are enums different from classes

While we mentioned that enums are similar to classes, they are different in many regards:

  • Enums are serialized differently from objects
  • Enums don’t have a state, which a class object does
  • Enums don’t declare constructors as no object initialization is needed
  • Enums can’t extend other enums; that is, no inheritance
  • Object and static properties are not supported
  • Enums can’t be instantiated with the new operator
  • The print_r output is different as compared to class objects

To demonstrate one of these differences, consider the following script in which an enum declares a class property:

<?php

enum SortType  
{
  case Asc;
  case Desc;
  case Shuffle;
  
  public $var = 1;
} 

The script generates error message:

Fatal error: Enums may not include properties

To demonstrate another of these differences, consider the following script in which an enum is instantiated:

<?php

enum SortType  
{
  case Asc;
  case Desc;
  case Shuffle;
}

$sortType=new SortType(); 

The script generates an error message:

Fatal error: Uncaught Error: Cannot instantiate enum SortType

To demonstrate another difference, the print_r output for a pure enum, and a backed enum is listed by script:

<?php  
 enum SortType {
  case Asc;
  case Desc;
  case Shuffle;
}
enum BackedSortType: int {
  case Asc = 1;
  case Desc = 2;
  case Shuffle = 3;
}

print_r(SortType::Asc);
print_r(BackedSortType::Desc);

The output is :

SortType Enum ( [name] => Asc ) 
BackedSortType Enum:int ( [name] => Desc [value] => 2 )

Enums may nor declare a __toString method. To demonstrate use the following script in which an enum implements the Stringable interface and provides implementation for the __toString method. 

<?php

enum SortType implements Stringable 
{
  case Asc;
  case Desc;
  case Shuffle;
   
  const ASCENDING = SortType::Asc; 
     
  public function __toString(): string {
        return "";
  }
   
}
   
echo SortType::ASCENDING;

The script generates an error message:

Fatal error: Enum may not include __toString

In this article we discussed most of the class-related features in PHP 8, including enums, the new readonly modifier for class properties, and constructor parameter promotion.

In the next article in the series we will explore functions and methods related new features. …
 

This article is part of the article series "PHP 8.x". You can subscribe to receive notifications about new articles in this series via RSS.

PHP continues to be one of the most widely used scripting languages on  the web with 77.3% of all the websites whose server-side programming language is known using it according to w3tech. PHP 8 brings many new features and other improvements, which we shall explore in this article series.

About the Author

Rate this Article

Adoption
Style

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Community comments

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

BT