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
}
There's another problem with data integrity. Say you have a method which requires an array of
Posts.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
Post a Comment