Тип PHPDoc намекает на массив объектов? - PullRequest
388 голосов
/ 22 апреля 2009

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

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

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

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

Ответы [ 13 ]

878 голосов
/ 19 ноября 2009

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

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

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

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

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

299 голосов
/ 22 апреля 2009

Использование:

/* @var $objs Test[] */
foreach ($objs as $obj) {
    // Typehinting will occur after typing $obj->
}

при вводе строковых переменных и

class A {
    /** @var Test[] */
    private $items;
}

для свойств класса.

Предыдущий ответ от '09, когда PHPDoc (и такие IDE, как Zend Studio и Netbeans) не имели такой опции:

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

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

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

57 голосов
/ 02 мая 2015

Netbeans намеки:

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

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

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

29 голосов
/ 01 января 2013

Чтобы указать переменную, это массив объектов:

$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 не обязательно.

19 голосов
/ 09 сентября 2016

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

Синтаксис

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.

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

12 голосов
/ 17 октября 2012

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

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

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

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

/**
 * @return SomeObj[]
 */

за это.

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

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

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

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

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()

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

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

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

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

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

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

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

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

5 голосов
/ 30 октября 2014

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

Boom! Нет необходимости в /** @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 -collection, и я не знаю, какие сущности в конечном итоге будут храниться в коллекции.

Итак, вот хитрость: не указывайте тип возвращаемого значения в абстрактном классе, вместо этого используйте описание 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 голосов
/ 06 февраля 2013

В 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!
}

Он не идеален, но лучше просто оставить его просто «смешанным», что не имеет значения.

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

4 голосов
/ 15 апреля 2014

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

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

3 голосов
/ 23 декабря 2016

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

Лучший способ заключается в расширении класса ArrayIterator, а не в использовании собственных типов массивов. Это позволяет вам вводить подсказки на уровне класса, а не на уровне экземпляра, что означает, что вам нужно только один раз PHPDoc, а не весь код (который не только грязный и нарушает DRY, но также может быть проблематичным, когда речь идет о рефакторинг - 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 не введет Typed Arrays, если они вообще когда-либо появятся), так как мы объявляем тип итератора в итерируемом классе, а не в экземплярах класса, разбросанных по всему код.

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

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