PHP Generics and why we need them

PHP Generics and why we need them


$posts = $blogModel->find();
foreach ($posts as $post) {
    $id = $post->getId();

    // Do something
}

Data integrity

$posts = [
    'foo',
    null,
    self::BAR,
    new Post('Lorem'),
];
PHP Fatal error:  Uncaught Error: Call to a member function getId() on string
foreach ($posts as $post) {
    if (!$post instanceof Post) {
        continue;
    }

    $id = $post->getId();

    // Do something
}
function handlePosts(array $posts) {
    foreach ($posts as $post) {
        // ...
    }
}
function handlePosts(Post ...$posts) {
    foreach ($posts as $post) {
        // ...
    }
}
handlePosts(...$posts);

Performance

Code completion

# BlogModel

public function find() : array {
    // return ...
}
/**
 * @return Post[]
 */
public function find() : array {
    // return ...
}
/** @var Blog[] $posts */
$posts = $blogModel->find();

class Collection implements Iterator, ArrayAccess
{
    private $position;

    private $array = [];

    public function __construct() {
        $this->position = 0;
    }

    public function current() {
        return $this->array[$this->position];
    }

    public function next() {
        ++$this->position;
    }

    public function key() {
        return $this->position;
    }

    public function valid() {
        return isset($this->array[$this->position]);
    }

    public function rewind() {
        $this->position = 0;
    }

    public function offsetExists($offset) {
        return isset($this->array[$offset]);
    }

    public function offsetGet($offset) {
        return isset($this->array[$offset]) ? $this->array[$offset] : null;
    }

    public function offsetSet($offset, $value) {
        if (is_null($offset)) {
            $this->array[] = $value;
        } else {
            $this->array[$offset] = $value;
        }
    }

    public function offsetUnset($offset) {
        unset($this->array[$offset]);
    }
}
$collection = new Collection();
$collection[] = new Post(1);

foreach ($collection as $item) {
    echo "{$item->getId()}\n";
}
$collection[] = 'abc';

foreach ($collection as $item) {
    // This fails
    echo "{$item->getId()}\n";
}
class PostCollection extends Collection
{
    public function current() : ?Post {
        return parent::current();
    }

    public function offsetGet($offset) : ?Post {
        return parent::offsetGet($offset);
    }

    public function offsetSet($offset, $value) {
        if (!$value instanceof Post) {
            throw new InvalidArgumentException("value must be instance of Post.");
        }

        parent::offsetSet($offset, $value);
    }
}
$collection = new PostCollection();
$collection[] = new Post(1);

// This would throw the InvalidArgumentException.
$collection[] = 'abc';

foreach ($collection as $item) {
    echo "{$item->getId()}\n";
}

Glorious generics

class GenericCollection<T> implements Iterator, ArrayAccess
{
    public function current() : ?T {
        return $this->array[$this->position];
    }

    public function offsetGet($offset) : ?T {
        return isset($this->array[$offset]) ? $this->array[$offset] : null;
    }

    public function offsetSet($offset, $value) {
        if (!$value instanceof T) {
            throw new InvalidArgumentException("value must be instance of {T}.");
        }

        if (is_null($offset)) {
            $this->array[] = $value;
        } else {
            $this->array[$offset] = $value;
        }
    }

    // public function __construct() ...
    // public function next() ...
    // public function key() ...
    // public function valid() ...
    // public function rewind() ...
    // public function offsetExists($offset) ...
}
$collection = new GenericCollection<Post>();
$collection[] = new Post(1);

// This would throw the InvalidArgumentException.
$collection[] = 'abc';

foreach ($collection as $item) {
    echo "{$item->getId()}\n";
}

Comments

Popular posts from this blog

A Tailwind CSS Preset for Laravel 5.5

Short and safe array iteration

PHPStorm's performance