Тип hinting - указать массив объектов
Как я могу указать тип аргумента как массив?
Скажем, у меня есть класс с именем "Foo":
class Foo {}
а затем у меня есть функция, которая принимает этот тип класса в качестве аргумента:
function getFoo(Foo $f) {}
Когда я передаю массив из Foo, я получаю сообщение об ошибке:
Допустимая фатальная ошибка. Аргумент 1 передан getFoo() должен быть экземпляром Foo, заданного массивом
Есть ли способ преодолеть эту проблему? возможно что-то вроде
function getFoo(Foo $f[]) {}
Ответы
Ответ 1
Если вы хотите убедиться, что вы работаете с "Массивом Foo", и хотите, чтобы методы получали "Массив 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", а затем передаете этот "тип", используя защиту хинтинга типов.
Библиотека Haldayne обрабатывает шаблон для проверки требований к членству, если вы не хотите создавать свои собственные:
class ArrayOfFoo extends \Haldayne\Boost\MapOfObjects {
protected function allowed($value) { return $value instanceof Foo; }
}
(полное раскрытие, я автор Haldayne.)
Историческая справка: Array Of RFC предложил эту функцию еще в 2014 году. RFC был отклонен с 4 и 16 годами. Недавно концепция вновь появилась в списке внутренних устройств, но жалобы были практически такими же, как и в отношении оригинального RFC: добавление этой проверки значительно повлияет на производительность.
Ответ 2
Старый пост, но вариативные функции и распаковка массива могут использоваться (с некоторыми ограничениями) для выполнения типизированного массива, по крайней мере с 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
Ответ 3
Извините, PHP не работает таким образом. Это было сделано для быстрого и легкого программирования, и поэтому вас не беспокоит строгая типизация, которая оставляет вас в аду динамического типа без какой-либо помощи (например, компилятор вывода типа). Интерпретатор PHP совершенно ничего не знает о том, что вы поместили в свой массив, поэтому он должен перебирать все записи массива, если он хочет проверить что-то вроде следующего (что, конечно, не работает в PHP):
function bar(Foo[] $myFoos)
Это существенно влияет на производительность, когда массив становится большим. Я думаю, что причина, почему PHP не предлагает подсказки типизированного массива.
Другие ответы здесь предполагают, что вы создаете свою строго типизированную оболочку массива. Обертки хороши, когда у вас есть компилятор с универсальной типизацией, такой как Java или С#, но я не согласен с PHP. Здесь эти обертки представляют собой утомительный шаблонный код, и вам нужно создать его для каждого типизированного массива. Если вы хотите использовать функции массива из библиотеки, вам нужно расширить свои обертки с помощью функций делегирования с проверкой типов и раздуть свой код. Это может быть сделано в академической выборке, но в производственной системе с множеством классов и коллекций, где время на разработку обходится дорого, а MIPS в веб-кластере мало? Я думаю, что нет.
Таким образом, просто для проверки правильности типа PHP в сигнатуре функции я бы воздержался от строго типизированных оболочек массивов. Я не верю, что это даст вам достаточно ROI. Поддержка PHPDoc хороших PHP IDE поможет вам больше, и в тегах PHPDoc работает нотация Foo [].
С другой стороны, обертки МОГУТ иметь смысл, если вы можете сконцентрировать в них хорошую бизнес-логику.
Возможно, наступит день, когда PHP расширится строго типизированными массивами (или, точнее, строго типизированными словарями). Я хотел бы этого. И с их помощью они могут предоставить подписи подсказки, которые не наказывают вас.
Ответ 4
function getFoo()
Как правило, у вас будет метод add, который будет набирать текст Foo
function addFoo( Foo $f )
Итак, getter вернет массив Foo, и метод add может гарантировать, что вы только Foo для массива.
РЕДАКТИРОВАТЬ Удален аргумент от получателя. Я не знаю, что я думал, вам не нужен аргумент в getter.
ИЗМЕНИТЬ, чтобы отобразить пример полного класса:
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
, но он помогает проиллюстрировать, что происходит в классе.
Ответ 5
class Foo {
/**
* @param Foo[] $f
*/
function getFoo($f) {
}
}
Ответ 6
Хотя это глупая вещь, я думаю, что это примерно так же близко, как вы могли бы получить. Вы можете ввести подсказку в класс 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);