Как указать тип аргумента в виде массива? Скажем, у меня есть класс под названием «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 («…» в вызове функции), чтобы преобразовать ваш массив в список параметров, каждый из которых должен быть типа, намеченного в вариационном объявлении 2 (в котором также используется многоточие).
«…» в вызове функции абсолютно необходимо (что неудивительно, учитывая выше). Попытка позвонить
test($arrayOfFoo)
в контексте вышеприведенного примера дается ошибка типа, поскольку компилятор ожидает параметр (ы) foo, а не массив. См. Ниже, хотя и хакерское решение для передачи в массиве данного типа напрямую, сохраняя при этом некоторый тип-намек.
Функции 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);