Тип PHPDoc, намекающий на массив объектов?


Таким образом, в PHPDoc можно указать @var над объявлением переменной-члена, чтобы указать ее тип. Затем IDE, например. PhpED, будет знать, с каким типом объекта он работает, и сможет предоставить представление о коде для этой переменной.

<?php
  class Test
  {
    /** @var SomeObj */
    private $someObjInstance;
  }
?>

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

Итак, есть ли способ объявить тег PHPDoc, чтобы указать, что участник переменная представляет собой массив SomeObjs? @var массива недостаточно, и @var array(SomeObj), например, не кажется допустимым.

Author: Artem Russakovskii, 2009-04-22

13 answers

Лучшее, что вы можете сделать, это сказать:

foreach ($Objs as $Obj)
{
    /* @var $Obj Test */
    // You should be able to get hinting after the preceding line if you type $Obj->
}

Я часто делаю это в студии Zend. Не знаю, как другие редакторы, но это должно сработать.

 273
Author: Zahymaka, 2016-01-28 11:40:19

В интегрированной среде разработки PhpStorm от JetBrains вы можете использовать /** @var SomeObj[] */, например:

/**
 * @return SomeObj[]
 */
function getSomeObjects() {...}

Документация phpdoc рекомендует этот метод:

Указано, что содержит один тип, определение типа информирует читателя о типе каждого элемента массива. Тогда в качестве элемента для данного массива ожидается только один тип.

Пример: @return int[]

 863
Author: Nishi, 2014-01-24 06:53:35

Подсказки Netbeans:

Вы получаете завершение кода для $users[0]-> и для $this-> для массива пользовательских классов.

/**
 * @var User[]
 */
var $users = array();

Вы также можете увидеть тип массива в списке членов класса, когда выполняете завершение $this->...

 55
Author: user1491819, 2016-06-02 11:47:23

Для указания переменной используется массив объектов:

$needles = getAllNeedles();
/* @var $needles Needle[] */
$needles[1]->...                        //codehinting works

Это работает в Netbeans 7.2 (я использую его)

Работает также с:

$needles = getAllNeedles();
/* @var $needles Needle[] */
foreach ($needles as $needle) {
    $needle->...                        //codehinting works
}

Поэтому использование объявления внутри foreach не является необходимым.

 29
Author: Highmastdon, 2013-01-01 13:16:13

PSR-5: PHPDoc предлагает форму нотации в стиле дженериков.

Синтаксис

Type[]
Type<Type>
Type<Type[, Type]...>
Type<Type[|Type]...>

Значения в коллекции МОГУТ быть даже другим массивом и даже другой коллекцией.

Type<Type<Type>>
Type<Type<Type[, Type]...>>
Type<Type<Type[|Type]...>>

Примеры

<?php

$x = [new Name()];
/* @var $x Name[] */

$y = new Collection([new Name()]);
/* @var $y Collection<Name> */

$a = new Collection(); 
$a[] = new Model_User(); 
$a->resetChanges(); 
$a[0]->name = "George"; 
$a->echoChanges();
/* @var $a Collection<Model_User> */

Примечание: Если вы ожидаете, что IDE будет выполнять помощь в написании кода, то это еще один вопрос о том, поддерживает ли IDE нотацию коллекций в универсальном стиле PHPDoc.

Из моего ответа на этот вопрос.

 19
Author: Gerard Roche, 2017-05-23 11:55:07

Я предпочитаю читать и писать чистый код - как описано в "Чистом коде" Роберта К. Мартина. Следуя его кредо, вы не должны требовать, чтобы разработчик (как пользователь вашего API) знал (внутреннюю) структуру вашего массива.

Пользователь API может спросить: Это массив только с одним измерением? Распределены ли объекты по всем уровням многомерного массива? Сколько вложенных циклов (для каждого и т.д.) мне нужно для доступа ко всем объектам? Какой тип объектов "хранится" в этом массив?

Как вы описали, вы хотите использовать этот массив (который содержит объекты) в качестве одномерного массива.

Как описано Ниши, вы можете использовать:

/**
 * @return SomeObj[]
 */

Для этого.

Но опять же: имейте в виду - это не стандартная нотация docblock. Эта нотация была введена некоторыми производителями IDE.

Хорошо, хорошо, как разработчик вы знаете, что "[]" привязан к массиву в PHP. Но что означает "что-то[]" в обычном контексте PHP? "[]" означает: создать новый элемент внутри "что-то". Новым элементом может быть все. Но то, что вы хотите выразить, это: массив объектов с одинаковым типом и его точным типом. Как вы можете видеть, производитель IDE вводит новый контекст. Новый контекст, который вы должны были изучить. Новый контекст, который должны были изучить другие разработчики PHP (чтобы понять ваши docblocks). Плохой стиль (!).

Поскольку ваш массив имеет одно измерение, вы, возможно, захотите назвать этот "массив объектов" "списком". Имейте в виду, что "список" имеет совершенно особое значение в других языки программирования. Было бы гораздо лучше назвать это, например, "коллекцией".

Помните: вы используете язык программирования, который позволяет вам использовать все возможности ООП. Используйте класс вместо массива и сделайте свой класс проходимым, как массив. Например:

class orderCollection implements ArrayIterator

Или если вы хотите хранить внутренние объекты на разных уровнях в многомерном массиве/структуре объектов:

class orderCollection implements RecursiveArrayIterator

Это решение заменяет ваш массив объектом типа "Коллекция заказов", но не пока что включите завершение кода в вашей интегрированной среде разработки. Хорошо. Следующий шаг:

Реализуйте методы, которые вводятся интерфейсом с помощью docblocks, в частности:

/**
 * [...]
 * @return Order
 */
orderCollection::current()

/**
 * [...]
 * @return integer E.g. database identifier of the order
 */
orderCollection::key()

/**
 * [...]
 * @return Order
 */
orderCollection::offsetGet()

Не забудьте использовать подсказку типа для:

orderCollection::append(Order $order)
orderCollection::offsetSet(Order $order)

Это решение перестает вводить много:

/** @var $key ... */
/** @var $value ... */

По всем вашим файлам кода (например, в циклах), как подтвердила Захимака своим ответом. Ваш пользователь API не обязан вводить эти блокировки документов, чтобы завершить код. Чтобы @возвращался только на один место уменьшает избыточность (@var) настолько, насколько это возможно. Добавление "Docblocks с @var" сделает ваш код хуже читаемым.

Наконец-то вы закончили. Выглядит труднодостижимым? Похоже на то, чтобы взять кувалду, чтобы расколоть орех? Не совсем, так как вы знакомы с этими интерфейсами и с чистым кодом. Помните: ваш исходный код написан один раз /прочитан многими.

Если завершение кода вашей IDE не работает с этим подходом, переключитесь на лучший (например, IntelliJ IDEA, PhpStorm, Netbeans) или отправьте запрос на функцию в службу отслеживания проблем вашего производителя IDE.

Спасибо Кристиану Вайсу (из Германии) за то, что он был моим тренером и научил меня таким замечательным вещам. PS: Встретимся со мной и с ним на XING.

 12
Author: DanielaWaranie, 2012-11-01 20:27:26

Как упомянула Даниэла Варани в своем ответе - есть способ указать тип $item при повторении $items в $collectionobject: Добавьте @return MyEntitiesClassName в current() и остальные Iterator и ArrayAccess - методы, которые возвращают значения.

Бум! Нет необходимости в /** @var SomeObj[] $collectionObj */ по foreach, и работает правильно с объектом коллекции, нет необходимости возвращать коллекцию с помощью определенного метода, описанного как @return SomeObj[].

Я подозреваю, что не все IDE поддерживают его, но он отлично работает в PhpStorm, который делает меня счастливее.

Пример:

Class MyCollection implements Countable, Iterator, ArrayAccess {

    /**
     * @return User
     */
    public function current() {
        return $this->items[$this->cursor];
    }

    //... implement rest of the required `interface` methods and your custom
}

Что полезного я собирался добавить, опубликовав этот ответ

В моем случае current() и остальные interface-методы реализованы в Abstract-классе коллекции, и я не знаю, какие сущности в конечном итоге будут храниться в коллекции.

Итак, вот в чем хитрость: не указывайте тип возвращаемого значения в абстрактном классе, вместо этого используйте инструкцию PHPDoc @method в описании конкретной коллекции класс.

Пример:

Class User {

    function printLogin() {
        echo $this->login;
    }

}

Abstract Class MyCollection implements Countable, Iterator, ArrayAccess {

    protected $items = [];

    public function current() {
        return $this->items[$this->cursor];
    }

    //... implement rest of the required `interface` methods and your custom
    //... abstract methods which will be shared among child-classes
}

/**
 * @method User current()
 * ...rest of methods (for ArrayAccess) if needed
 */
Class UserCollection extends MyCollection {

    function add(User $user) {
        $this->items[] = $user;
    }

    // User collection specific methods...

}

Теперь, использование классов:

$collection = new UserCollection();
$collection->add(new User(1));
$collection->add(new User(2));
$collection->add(new User(3));

foreach ($collection as $user) {
    // IDE should `recognize` method `printLogin()` here!
    $user->printLogin();
}

Еще раз: я подозреваю, что не все IDE поддерживают это, но PhpStorm поддерживает. Попробуйте свои, опубликуйте в комментариях результаты!

 5
Author: Pavel, 2014-10-30 04:28:22

В NetBeans 7.0 (может быть и ниже) вы можете объявить тип возвращаемого значения "массив с текстовыми объектами" так же, как @return Text, и намек на код будет работать:

Редактировать: обновил пример предложением @Bob Fanger

/**
 * get all Tests
 *
 * @return Test|Array $tests
 */
public function getAllTexts(){
    return array(new Test(), new Test());
}

И просто используйте его:

$tests =  $controller->getAllTests();
//$tests->         //codehinting works!
//$tests[0]->      //codehinting works!

foreach($tests as $text){
    //$test->      //codehinting works!
}

Это не идеально, но лучше тогда просто оставить это просто "смешанным", ведьма не приносит никакой пользы.

МИНУСЫ в том, что вам разрешено перемещаться по массиву, так как ведьма текстовых объектов будет выдавать ошибки.

 4
Author: d.raev, 2013-09-20 07:12:01

Используйте array[type] в Zend Studio.

В Zend Studio array[MyClass] или array[int] или даже array[array[MyClass]] отлично работают.

 4
Author: Erick Robertson, 2014-04-14 23:18:31

Проблема в том, что @var может просто обозначать один тип - не содержать сложной формулы. Если у вас был синтаксис для "массива Foo", почему бы не остановиться на этом и не добавить синтаксис для "массива массива, который содержит 2 Foo и три бара"? Я понимаю, что список элементов, возможно, более общий, чем этот, но это скользкий путь.

Лично я несколько раз использовал @var Foo[] для обозначения "массива Foo", но он не поддерживается IDE.

 2
Author: troelskn, 2009-04-22 21:04:10
<?php foreach($this->models as /** @var Model_Object_WheelModel */ $model): ?>
    <?php
    // Type hinting now works:
    $model->getImage();
    ?>
<?php endforeach; ?>
 1
Author: Scott Hovestadt, 2010-06-27 21:29:11

Я знаю, что опаздываю на вечеринку, но в последнее время я работаю над этой проблемой. Я надеюсь, что кто-то увидит это, потому что принятый ответ, хотя и правильный, не лучший способ, которым вы можете это сделать. По крайней мере, не в PhpStorm, хотя я не тестировал NetBeans.

Лучший способ заключается в расширении класса ArrayIterator вместо использования собственных типов массивов. Это позволяет вводить подсказку на уровне класса, а не на уровне экземпляра, что означает, что вам нужно только PHPDoc один раз, не во всем вашем коде (что не только беспорядочно и нарушает СУХОЙ, но также может быть проблематичным, когда дело доходит до рефакторинга - PhpStorm имеет привычку пропускать PHPDoc при рефакторинге)

Смотрите код ниже:

class MyObj
{
    private $val;
    public function __construct($val) { $this->val = $val; }
    public function getter() { return $this->val; }
}

/**
 * @method MyObj current()
 */
class MyObjCollection extends ArrayIterator
{
    public function __construct(Array $array = [])
    {
        foreach($array as $object)
        {
            if(!is_a($object, MyObj::class))
            {
                throw new Exception('Invalid object passed to ' . __METHOD__ . ', expected type ' . MyObj::class);
            }
        }
        parent::__construct($array);
    }

    public function echoContents()
    {
        foreach($this as $key => $myObj)
        {
            echo $key . ': ' . $myObj->getter() . '<br>';
        }
    }
}

$myObjCollection = new MyObjCollection([
    new MyObj(1),
    new MyObj('foo'),
    new MyObj('blah'),
    new MyObj(23),
    new MyObj(array())
]);

$myObjCollection->echoContents();

Ключом здесь является PHPDoc @method MyObj current(), переопределяющий тип возвращаемого значения, унаследованный от ArrayIterator (который является mixed). Включение этого PHPDoc означает, что когда мы перебираем свойства класса с помощью foreach($this as $myObj), мы затем получаем завершение кода при обращении к переменная $myObj->...

Для меня это самый аккуратный способ добиться этого (по крайней мере, до тех пор, пока PHP не представит типизированные массивы, если они когда-либо появятся), поскольку мы объявляем тип итератора в классе iterable, а не в экземплярах класса, разбросанных по всему коду.

Я не показал здесь полное решение для расширения ArrayIterator, поэтому, если вы используете этот метод, вы также можете захотеть:

  • При необходимости включите другой PHPDoc уровня класса для таких методов, как offsetGet($index) и next()
  • Переместите проверку на вменяемость is_a($object, MyObj::class) из конструктора в частный метод
  • Вызовите эту (теперь закрытую) проверку на вменяемость из переопределений методов, таких как offsetSet($index, $newval) и append($value)
 1
Author: e_i_pi, 2016-12-23 00:21:18

Я нашел то, что работает, это может спасти жизни!

private $userList = array();
$userList = User::fetchAll(); // now $userList is an array of User objects
foreach ($userList as $user) {
   $user instanceof User;
   echo $user->getName();
}
 -5
Author: eupho, 2010-01-13 10:43:15