Подсказка типа - укажите массив объектов

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

class Foo {}

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

function getFoo(Foo $f) {}

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

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

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

function getFoo(Foo $f[]) {}

person Yoav Kadosh    schedule 24.12.2013    source источник
comment
Это либо массив, либо объект. PHP не волнует, что находится в массиве. Вы не можете указать, я хочу, чтобы эта функция принимала массивы, но я хочу, чтобы этот массив содержал только объекты типа Foo.   -  person Royal Bg    schedule 24.12.2013
comment
Хммм ... @RoyalBg Ты уверен? Очень жаль ...   -  person Yoav Kadosh    schedule 24.12.2013
comment
@RoyalBg прав. Я ломал голову над этим. Если вы передаете массив объектов, все, что вам нужно сделать для функции, - это установить function getFoo($f) { ... }. Поэтому, если вы хотите иметь функцию, которая обрабатывает исключительно массивы, просто создайте новую функцию function getFooArray($f) { ... }   -  person sjagr    schedule 24.12.2013
comment
Да, я думаю, что Royal Bg прав. У вас может быть function getFoos(Array $f), который анализирует массив и передает каждый $f[$i] в function getFoo(Foo $g)   -  person Frank Conry    schedule 24.12.2013
comment
Вам нужно будет написать класс коллекции, который принимает только объекты Foo. См. Ответ @ bishop для получения общей идеи. Некоторые библиотеки PHP имеют общие объекты коллекции, на которые вы можете ссылаться, например, объект Doctrine Collection.   -  person Jeremy Kendall    schedule 24.12.2013
comment
просто интересно, что мы думаем о решении с оператором splat function foo(Abc ...$args) { } foo(...$arr);, найденном здесь   -  person Shahid    schedule 10.06.2016


Ответы (5)


Если вы хотите убедиться, что работаете с «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);

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


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

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

(Полное раскрытие информации, я автор Haldayne.)


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

person bishop    schedule 24.12.2013

Старый пост, но вариативные функции и распаковка массивов могут использоваться (с некоторыми ограничениями) для выполнения хинтинга типизированных массивов, по крайней мере, с 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. Функции с переменным числом аргументов могут иметь только один параметр с переменным числом аргументов, и он должен быть последним параметром (иначе как компилятор мог бы определить, где заканчивается параметр с переменным числом аргументов и начинается следующий), что означает, что вы не можете объявлять функции по строкам

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

    or

    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
person Evan    schedule 28.08.2016

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

function bar(Foo[] $myFoos)

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

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

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

С другой стороны, оболочки МОГУТ иметь смысл, если вы можете сконцентрировать в них хорошую бизнес-логику.

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

person Rolf    schedule 30.04.2014
comment
Показанный код неверен! Читатель может принять это за решение, которое сбивает с толку. - person Sliq; 05.09.2019
comment
Ладно. Я добавил примечание об этом. - person Rolf; 09.09.2019

function getFoo()

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

function addFoo( Foo $f )

Итак, получатель вернет массив Foo, а метод add может гарантировать, что у вас есть только Foo в массиве.

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, но он помогает проиллюстрировать, что происходит в классе.

person Hayden    schedule 24.12.2013
comment
Красивое и простое решение - person Yoav Kadosh; 24.12.2013

person    schedule
comment
Чтобы уточнить, подсказка типа phpdoc @param Foo[] ... не является стандартом JS. Однако это весьма полезно; он используется IDE, такими как phpstorm, для улучшения своего интеллекта. - person ToolmakerSteve; 29.08.2020