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

php arrays type-hinting

17417 просмотра

5 ответа

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

class Foo {}

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

function getFoo(Foo $f) {}

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

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

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

function getFoo(Foo $f[]) {}
Автор: Yoav Kadosh Источник Размещён: 12.11.2019 09:39

Ответы (5)


25 плюса

Решение

Если вы хотите убедиться, что работаете с «Массивом 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: добавление этой проверки значительно повлияло бы на производительность .

Автор: bishop Размещён: 24.12.2013 04:29

13 плюса

Старые функции post, но variadic и распаковка массива могут использоваться (с некоторыми ограничениями) для выполнения подсказок по типизированному массиву, по крайней мере с 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 могут иметь только один параметр variadic, и он должен быть последним параметром (иначе как компилятор может определить, где заканчивается параметр 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
Автор: Evan Размещён: 28.08.2016 10:38

7 плюса

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

function bar(Foo[] $myFoos)

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

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

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

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

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

Автор: Rolf Размещён: 30.04.2014 09:39

3 плюса

function getFoo()

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

function addFoo( Foo $f )

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

РЕДАКТИРОВАТЬ Удалил аргумент из геттера. Я не знаю, о чем я думал, тебе не нужен спор в добытчике.

РЕДАКТИРОВАТЬ только для отображения полного примера класса:

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

Автор: Hayden Размещён: 24.12.2013 04:17

2 плюса

class Foo {
    /**
     * @param Foo[] $f
     */
    function getFoo($f) {

    }
}
Автор: Shami Размещён: 10.01.2018 02:16
Вопросы из категории :
32x32