-
-
Save Antnee/fb2c41b0e3cf65eadf0c to your computer and use it in GitHub Desktop.
<?php | |
class Item { | |
private $id; | |
public function __construct(int $id) | |
{ | |
$this->id = $id; | |
} | |
public function id() : int | |
{ | |
return $this->id; | |
} | |
} | |
class Collection { | |
private $items = []; | |
public function __construct(Item ...$items) | |
{ | |
$this->items = $items; | |
} | |
public function item() : Generator | |
{ | |
yield from $this->items; | |
} | |
} | |
$items = []; | |
for ($i=0; $i<10; $i++) { | |
$items[] = new Item($i); | |
} | |
$collection = new Collection(...$items); | |
$generator = $collection->item(); | |
printf("\nThe Collection::item() method returns a type of %s, as expected\n", get_class($generator)); | |
printf("However, it yields the following:\n\n"); | |
foreach ($generator as $item) { | |
printf("\tClass: %s. Calling id() method returns %d (%s)\n", get_class($item), $item->id(), gettype($item->id())); | |
} | |
printf("\n\nIt doesn't appear to be possible to hint the yielded type\n\n"); |
I can't see nothing wrong here, yield keyword is presumably the only way to create Generator object so $collection->item()
returns new Generator instance.
Generator::current()
returns yielded value, so (assuming you can extend final Generator class) declare type on Generator's current method instead. Generator::current() : Item
In above example just use Iterator instead of Generator: https://3v4l.org/VSf6k
@gallna The point was that in PHP 7 it's not possible to type hint the value that is yielded from a generator. This is just an example :)
However...
What I'm looking for is a guarantee that I will definitely get the correct type back.
In your solution there's no guarantee that I even constructed my array with \Item
objects.
My example would stop runtime before it even instantiated the \Collection
object, which is more desirable than instantiating it with duff data.
In yours I could put the odd \stdClass
object in there, or maybe a \DateTime
object, and the collection would be instantiated and potentially passed around the system assuming that everything is OK. At some point I would try to iterate over it and I'd get an error when I tried to call a method on the return value. In that case I may need to roll back a whole load of stuff that I've already done.
This would be resolved by not trusting the \ArrayIterator
constructor and overloading it, mind you
If it's going to fail, it should fail fast!
As an aside, although in this case I constructed my collection with an array of objects, I may choose to construct it with a closure that fetches each item from a DB ONLY when required. It could be done with an \ArrayIterator
, but it's a lot more hassle than doing it with a generator, which implements the \Iterator
interface anyway, therefore is also \Traversable
Generators usually have a significantly smaller memory footprint than iterators.
The point still remains though that it's not possible to type hint when you will receive from a generator when it yields, and that's disappointing. I'd be OK with this syntax, which is similar to other languages which implemented generics:
class Collection {
public function item() : Generator<Item>
{
yield from $this->items;
}
}
Sorry, I see the point now. I think we'll wait long to see implementation you expect.
Probably. I do know that some people are pushing for generics. That should also bring the ability to create an "array of types" as well. Hopefully something like $foo = []bar;
to create an array of bar
objects
Here is working solution, where $items = []Item
: https://3v4l.org/OH18b
Or better one: https://3v4l.org/YMQer
@gallna - It's not the prettiest or most obvious piece of code I've ever seen, but I like that last solution. It does what I need it to :) It's a shame that it's such a PITA to get it to do what we're trying to do, but it certainly does it here. Nice one!
hey @gallna - do you have another link for https://3v4l.org/YMQer by chance? That one produces a 500 and I am interested in your solution =) Thank you!
It works for me @rhift
<?php
class Item {
private $id;
public function __construct(int $id)
{
$this->id = $id;
}
public function id() : int
{
return $this->id;
}
}
class Collection extends IteratorIterator
{
public function __construct(Item ...$items)
{
parent::__construct(
(function() use ($items) {
yield from $items;
})()
);
}
public function current() : Item
{
return parent::current();
}
}
$items = [];
for ($i=0; $i<10; $i++) {
$items[] = new Item($i);
}
$collection = new Collection(...$items);
foreach ($collection as $item) {
printf("\tClass: %s. Calling id() method returns %d (%s)\n", get_class($item), $item->id(), gettype($item->id()));
}
oh weird, thanks for pasting it @Antnee!
https://3v4l.org/YcKua
PHP 7 allows us to define a return type. In this case,
Collection::item()
doesn't appear to be returning anything specific (ie there's no return statement), but rather it's yielding anItem
type. So I tried to set the return type inCollection::item()
toItem
, but this will error saying:So, I changed it to a
Generator
and the code runs fine, but I wanted to hint that I would be yieldingItem
objects, not returning aGenerator
. The function DOES return aGenerator
, so it's not wrong, but in every loop I get anItem
object to work with. I can't help but feel that this isn't the best way to use return type hinting with generators