У меня есть код, который выглядит следующим образом, который я хотел бы улучшить:
// example type
class Stuff
{
public function __construct($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
}
// generator function
function searchStuff()
{
yield new Stuff('Fou');
yield new Stuff('Barre');
yield new Stuff('Bazze');
}
// code that iterates over the results of the generator
$stuffIterator = searchStuff();
assert($stuffIterator instanceof Iterator);
foreach ($stuffIterator as $stuff) {
/** @var Stuff $stuff */
echo $stuff->getName() . PHP_EOL;
}
Вещь, которую я хотел бы улучшить, — это аннотация в цикле (третья последняя строка), которую я хотел бы полностью удалить. Причины
Мой наивный подход состоял в том, чтобы объявить интерфейс итератора, который добавляет правильную аннотацию типа к универсальному Iterator
интерфейс:
interface StuffIterator extends Iterator
{
public function current(): Stuff;
}
Это имеет тот недостаток, что я не могу установить это как «жесткую» аннотацию для функции, только как аннотацию строки документации, потому что "Generators may only declare a return type of Generator, Iterator, Traversable, or iterable"
, что плохо, потому что тогда оно не соблюдается. Кроме того, моя среда IDE не выбирает тип, но это другая проблема.
Другой подход состоял в том, чтобы написать реальный класс итератора, который обернет Generator
вернулся из функции. Проблема в том, что этот класс тоже должен быть создан, поэтому я должен был бы вызвать $stuffGenerator = new StuffIterator(searchStuff());
или написать другую функцию-обертку, чтобы сделать это, ни одна из которых не должна быть необходимой. Тем не менее, глупая IDE не принимает подсказку типа (grrrr …!).
Итак, вот мой вопрос: какие существуют альтернативы этому подходу? Я мог бы представить что-то вроде дженериков C ++ или Java, но, увы, я не могу просто переписать рассматриваемое приложение.
Дальнейшие заметки:
очень хороший вопрос Я думаю, что ответ на ваш вопрос не получится, как вы могли ожидать. Решение, возможно, не хорошее, но работает. Прежде всего, вы не можете определить тип возврата доходности, кроме Generator
и так далее. Вы дали ответ сами. Но …
Просто представьте следующую отправную точку.
class Stuff
{
protected $name;
public function getName() : ?string
{
return $this->name;
}
public function setName(string $name) : Stuff
{
$this->name = $name;
return $this;
}
}
class StuffCollection extends \IteratorIterator
{
public function __construct(Stuff ...$items)
{
parent::__construct(
(function() use ($items) {
yield from $items;
})()
);
}
public function current() : Stuff
{
return parent::current();
}
}
Что я здесь сделал? Мы знаем Stuff
класс уже. Ничего нового Новая вещь это StuffCollection
учебный класс. Из-за расширения его от IteratorIterator
класс, который мы можем переопределить IteratorIterator::current()
метод и дать ему подсказку типа.
$collection = new StuffCollection(
(new Stuff())->setName('One'),
(new Stuff())->setName('Two'),
(new Stuff())->setName('Three')
);
foreach ($collection as $item) {
var_dump(assert($item instance of Stuff));
echo sprintf(
'Class: %s. Calling getName method returns "%s" (%s)',
get_class($item),
$item->getName(),
gettype($item->getName())
) . "<br>";
}
Выход из этого должен быть …
bool(true) Class: Stuff. Calling getName method returns "One" (string)
bool(true) Class: Stuff. Calling getName method returns "Two" (string)
bool(true) Class: Stuff. Calling getName method returns "Three" (string)
Что это значит? Вы действительно не можете определить тип возвращаемого значения непосредственно в вызове yield. Доход всегда будет возвращать Generator
пример. Одним из возможных решений может быть использование IteratorIterator
учебный класс.
Даже ваша IDE должна работать с этим решением.
Других решений пока нет …