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

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

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

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

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

13 ответов


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

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

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


в PhpStorm IDE от JetBrains вы можете использовать /** @var SomeObj[] */, например:

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

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

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

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


подсказки Netbeans:

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

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

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


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

$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 не надо.


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 сделает код assist, то это еще один вопрос о том, поддерживает ли IDE нотацию коллекций PHPDoc Generic-style.

из моего ответа к этот вопрос.


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

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

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

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

/**
 * @return SomeObj[]
 */

для этого.

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

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

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

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

class orderCollection implements ArrayIterator

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

class orderCollection implements RecursiveArrayIterator

это решение заменяет массив объектом типа "orderCollection", но пока не включает завершение кода в вашей среде IDE. Окей. Следующий шаг:

реализуйте методы, которые вводятся интерфейсом с docblocks-в частности:

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

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

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

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

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

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

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

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

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

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

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


Как упоминалось в ответе DanielaWaranie - есть способ указать тип $item при итерации по $ items в $collectionObject: Add @return MyEntitiesClassName to 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 instuction @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 делает. Попробуйте ваш, напишите в комментарии результаты!


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

Edit: обновлен пример с предложением @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!
}

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

минусы вы можете ступать массив, как текстовый объект ведьма будет ошибки броска.


использовать array[type] в студии Zend.

В Студии Zend,array[MyClass] или array[int] или даже array[array[MyClass]] работа отличная.


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

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


<?php foreach($this->models as /** @var Model_Object_WheelModel */ $model): ?>
    <?php
    // Type hinting now works:
    $model->getImage();
    ?>
<?php endforeach; ?>

Я знаю, что я опоздал на вечеринку, но в последнее время я работаю над этой проблемой. Я надеюсь, что кто-то видит это, потому что принятый ответ, хотя и правильный,не лучший способ сделать это. По крайней мере, не в 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)

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

private $userList = array();
$userList = User::fetchAll(); // now $userList is an array of User objects
foreach ($userList as $user) {
   $user instanceof User;
   echo $user->getName();
}