Подтвердить что ты не робот

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

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

class Foo {}

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

function getFoo(Foo $f) {}

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

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

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

function getFoo(Foo $f[]) {}
4b9b3361

Ответ 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);