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


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

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

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

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

13 372

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. Не знаю, как другие редакторы, но это должно работать.

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

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

The документация 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 будет делать code assist, то это еще один вопрос о том, поддерживает ли IDE нотацию коллекций общего стиля PHPDoc.

от моего ответа до этот вопрос.

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

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

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

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

/**
 * @return SomeObj[]
 */

для этого.

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

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

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

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

class orderCollection implements ArrayIterator

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

class orderCollection implements RecursiveArrayIterator

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

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

/**
 * [...]
 * @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-интерфейс пользователя не заставляют представить, что для установки, код завершения. Чтобы иметь @return только в одном месте, уменьшите избыточность (@var)как можно больше. Посыпать "docBlocks with @var" сделает ваш код хуже читаемым.

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

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

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

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

бум! не нужно /** @var SomeObj[] $collectionObj */ over 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();
}