Skip to content

Files

Latest commit

589ecc7 · Feb 4, 2019

History

History
128 lines (99 loc) · 4.09 KB

types.md

File metadata and controls

128 lines (99 loc) · 4.09 KB

Types

PHP (as of 5.6) has three kinds of types; classes, interfaces and traits. The usual way of using types is by creating and accessing instance objects. However, there are a lot of situations where it's useful to refer to the types themselves.

The usual way to refer to types in PHP is to use the fully-qualified class name (FQCN), which is a string that uniquely identifies a type. Unfortunately, this string then has to be verified anytime it's used because a string can contain any value, and it's easy to make a typo. The ::class magic property which resolves to the type of a classe at compile time mitigates this somewhat, but if you define a function that takes a FQCN as a parameter, it's still possible to pass any string to it, so your function will need to use some validation such as class_exists.

The SolidPhp\ValueObjects\Type namespace contains type value objects that make it easier to deal with type parameters. You can now write

use SolidPhp\ValueObjects\Type\Type;
use SolidPhp\ValueObjects\Type\ClassType;
use SolidPhp\ValueObjects\Type\InterfaceType;

class MyClass {}

// Type objects can be constructed from the FQCN
$myClassType = Type::of(MyClass::class);

// You'll get back the correct kind of specific type object
$myClassType instanceof Type; // true
$myClassType instanceof ClassType; // true
$myClassType instanceof InterfaceType; // false

doSomethingWithAType($myClassType);

// Type objects can also be constructed from an instance
$myInstance = new MyClass();
$myInstanceType = Type::of($myInstance);

$myInstanceType === $myClassType; // true

// constructing a Type only works if the underlying class/interface/trait
// actually exists
$myNonExistingClassType = Type::of('NonExistingClass'); // InvalidArgumentException

// Type objects contain some useful methods for analyzing types and
// their relations
function doSomethingWithAType(Type $myType) {
    // $myType must refer to a class, interface or trait that actually
    // exists.
    echo $myType->getKind(); // this is the 'kind' of type (class, interface or trait)
    echo $myType->getFullyQualifiedName(); // this is the FQCN string
    
    if ($myType->isSubtypeOf(Type::of(MyClass::class))) {
        echo sprintf('Type %s extends, implements or uses %s', $myType, Type::of(MyClass::class));
    }
}
 

Kind is an enum with the following values:

  • Kind::CLASS() - for regular classes (whether concrete or abstract)
  • Kind::INTERFACE() - for interfaces
  • Kind::TRAIT() - for traits

See the manual page on enums for instructions on how to work with enums.

use SolidPhp\ValueObjects\Type\InterfaceType;

function doSomethingWithInterface(InterfaceType $myInterface) {
    // $myInterface will refer to an interface
}

Type objects contain methods that are useful for analyzing the relation between types, and the ClassType in particular has utility methods for getting the ReflectionClass and calling static methods.

use SolidPhp\ValueObjects\Type\Type;

function compareTypes(Type $A, Type $B) {
    if ($A->isSubtypeOf($B)) {
        echo "$A extends, implements or uses $B!";
    }
    if ($A->isSupertypeOf($B)) {
        echo "$B extends, implements or uses $A!";
    }
}

class TypedCollection {
    /** @var Type */
    private $elementType;
    
    private $elements = [];
    
    public function __construct(Type $elementType, array $initialElements = []) {
        $this->elementType = $elementType;
        $this->addElements($initialElements);
    }
    
    public function addElements(array $elements): void {
        foreach ($elements as $element) {
            $this->addElement($element);
        }
    }
    
    public function addElement($element): void {
        if (!$this->elementType->isInstance($element)) {
            throw new InvalidArgumentException(sprintf('This collection can only contain elements of %s', $this->elementType));
        }
        
        $this->elements[] = $element;
    }
    
    public function getElements(): array {
        return $this->elements;
    }
    
    public function getElementType(): Type {
        return $this->elementType;
    }
}