Тип hinting – указать массив объектов

Как указать тип аргумента в виде массива? Скажем, у меня есть класс под названием «Foo»:

class Foo {} 

и тогда у меня есть функция, которая принимает этот тип класса в качестве аргумента:

 function getFoo(Foo $f) {} 

Когда я передаю массив «Foo», я получаю сообщение об ошибке:

Допустимая фатальная ошибка : аргумент 1, переданный getFoo (), должен быть экземпляром Foo, заданного массива

Есть ли способ преодолеть эту проблему? может быть что-то вроде

 function getFoo(Foo $f[]) {} 

Если вы хотите, чтобы вы работали с «Array of Foo», и хотите, чтобы методы получали «Array of Foo», вы можете:

 class ArrayOfFoo extends \ArrayObject { public function offsetSet($key, $val) { if ($val instanceof Foo) { return parent::offsetSet($key, $val); } throw new \InvalidArgumentException('Value must be a Foo'); } } 

тогда:

 function workWithFoo(ArrayOfFoo $foos) { foreach ($foos as $foo) { // etc. } } $foos = new ArrayOfFoos(); $foos[] = new Foo(); workWithFoo($foos); 

Секретный соус заключается в том, что вы определяете новый «тип» «массива foo», а затем передаете этот тип «type», используя защиту типа hinting.


Библиотека Haldayne обрабатывает шаблонный шаблон для проверки требований к членству, если вы не хотите откатывать свои собственные:

 class ArrayOfFoo extends \Haldayne\Boost\MapOfObjects { protected function allowed($value) { return $value instanceof Foo; } } 

(Полное раскрытие, я автор Халдейн.)


Историческое примечание: Массив RFC предложил эту функцию еще в 2014 году. RFC был отклонен с 4 yay и 16 нет. Концепция недавно появилась в списке внутренних дел , но жалобы были почти такими же, как и против первоначального RFC: добавление этой проверки значительно повлияло бы на производительность .

Старые должности, но вариативные функции и распаковка массива могут использоваться (с некоторыми ограничениями) для выполнения типизированного массива, по крайней мере с PHP7. (Я не тестировал более ранние версии).

Пример:

 class Foo { public function test(){ echo "foo"; } }; class Bar extends Foo { //override parent method public function test(){ echo "bar"; } } function test(Foo ...$params){ foreach($params as $param){ $param->test(); } } $f = new Foo(); $b = new Bar(); $arrayOfFoo = [$f,$b]; test(...$arrayOfFoo); //will output "foobar" 

Ограничения:

  1. Это не технически решение, так как вы не передаете типизированный массив. Вместо этого вы используете оператор распаковки массива 1 («…» в вызове функции), чтобы преобразовать ваш массив в список параметров, каждый из которых должен быть типа, намеченного в вариационном объявлении 2 (в котором также используется многоточие).

  2. «…» в вызове функции абсолютно необходимо (что неудивительно, учитывая выше). Попытка позвонить

     test($arrayOfFoo) 

    в контексте вышеприведенного примера дается ошибка типа, поскольку компилятор ожидает параметр (ы) foo, а не массив. См. Ниже, хотя и хакерское решение для передачи в массиве данного типа напрямую, сохраняя при этом некоторый тип-намек.

  3. Функции Variadic могут иметь только один вариационный параметр, и он должен быть последним параметром (поскольку в противном случае компилятор может определить, где заканчивается вариационный параметр и начинается следующее), что означает, что вы не можете объявлять функции по линиям

     function test(Foo ...$foos, Bar ...$bars){ //... } 

    или

     function test(Foo ...$foos, Bar $bar){ //... } 

Альтернатива «Только-чуть-чуть-чуть-просто-проверяя-каждый-элемент»:

Следующая процедура лучше, чем просто проверка типа каждого элемента, поскольку (1) он гарантирует, что параметры, используемые в функциональном теле, имеют правильный тип, не загромождая функцию с проверками типов, и (2) он выдает исключения обычного типа ,

Рассматривать:

 function alt(Array $foos){ return (function(Foo ...$fooParams){ //treat as regular function body foreach($fooParams as $foo){ $foo->test(); } })(...$foos); } 

Идея определяет и возвращает результат сразу же вызванного закрытия, который заботится обо всех вариациях / распаковках для вас. (Можно продолжить дальше принцип, определяя функцию более высокого порядка, которая генерирует функции этой структуры, уменьшая шаблон). В приведенном выше примере:

 alt($arrayOfFoo) // also outputs "foobar" 

Проблемы с этим подходом включают:

(1) Особенно для неопытных разработчиков, это может быть неясно.

(2) Это может привести к некоторым накладным расходам.

(3) Он, как и внутренняя проверка элементов массива, рассматривает проверку типа как деталь реализации, поскольку нужно проверять объявление функции (или пользоваться исключениями типа), чтобы понять, что только конкретный типизированный массив является допустимым параметром. В интерфейсе или абстрактной функции подсказка полного типа не может быть закодирована; все, что можно было сделать, это комментарий, что ожидается реализация вышеупомянутого вида (или чего-то подобного).

Заметки

[1]. Вкратце: распаковка массивов делает эквивалентную

 example_function($a,$b,$c); 

а также

 example_function(...[$a,$b,$c]); 

[2]. В двух словах: вариационные функции вида

 function example_function(Foo ...$bar){ //... } 

может быть справедливо вызвана одним из следующих способов:

 example_function(); example_function(new Foo()); example_function(new Foo(), new Foo()); example_function(new Foo(), new Foo(), new Foo()); //and so on 

К сожалению, PHP не работает таким образом. Это было сделано для быстрого программирования, и поэтому вам не надоедает строгая типизация, которая оставляет вас в динамическом типе ада без какой-либо помощи (например, компилятор вывода типов). Интерпретатор PHP совершенно незнакомо о том, что вы положили в свой массив, поэтому он должен перебирать все записи массива, если он хочет проверить что-то вроде

 function bar(Foo[] $myFoos) 

Это существенно влияет на производительность, когда массив становится большим. Я думаю, именно поэтому PHP не предлагает типизированные подсказки массива.

Другие ответы здесь предлагают создать вашу строго типизированную оболочку массива. Wrappers прекрасны, когда у вас есть компилятор с типичной типизацией, например Java или C #, но для PHP я не согласен. Здесь эти обертки представляют собой утомительный код шаблона, и вам нужно создать его для каждого типизированного массива. Если вы хотите использовать функции массива из библиотеки, вам необходимо расширить свои обертки с функциями делегирования проверки типов и раздуть ваш код. Это можно сделать в академической выборке, но в производственной системе со множеством классов и коллекций, где время разработчика дорогостоящее, а MIPS в веб-кластере не хватает? Думаю, нет.

Поэтому, чтобы иметь возможность проверки типа PHP в сигнатуре функции, я бы воздержался от строго типизированных оболочек массивов. Я не считаю, что дать вам достаточно ROI. Поддержка PHPDoc хороших PHP IDE помогает вам больше, а в тегах PHPDoc используется нотация Foo [].

С другой стороны, обертки могут иметь смысл, если вы можете сконцентрироваться на них.

Возможно, наступит день, когда PHP будет расширен с помощью строго типизированных массивов (или более точных: строго типизированных словарей). Я бы хотел этого. И с ними они могут предоставить сигнатурные подсказки, которые не наказывают вас.

 function getFoo() 

Как правило, у вас тогда будет метод add, который будет набирать текст для Foo

 function addFoo( Foo $f ) 

Таким образом, getter вернет массив Foo, и метод add может гарантировать, что у вас есть только Foo для массива.

EDIT Удален аргумент от получателя. Я не знаю, о чем я думал, вам не нужен аргумент в геттере.

EDIT просто для отображения примера полного класса:

 class FooBar { /** * @return array */ private $foo; public function getFoo() { return $foo; } public function setFoo( array $f ) { $this->foo = $f; return $this; } public function addFoo( Foo $f ) { $this->foo[] = $f; return $this; } } 

Обычно вы, и, вероятно, не должны иметь метод setter, поскольку у вас есть метод add, чтобы гарантировать, что $foo – это массив Foo , но он помогает проиллюстрировать, что происходит в классе.

Хотя это глупая вещь, я думаю, что это примерно так близко, как вы могли бы получить. Вы можете ввести подсказку в класс Foos и получить доступ к массиву с помощью get() . Это, вероятно, не полезно ни для чего, но похоже на ваш пост.

 class Foo { private $foo = 1; } class Foos { private $foos = array(); public function add(Foo $foo) { array_push($this->foos, $foo); } public function get() { return $this->foos; } } class Bar { public function __construct(Foos $Foos) { var_dump($Foos->get()); } } $Foos = new Foos(); for ($i=0; $i<5; ++$i) { $Foos->add(new Foo()); } $Bar = new Bar($Foos);