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

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

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

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

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

Ответ 1

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

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

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

Ответ 2

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

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

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

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

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

Ответ 3

Подсказки Netbeans:

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

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

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

Ответ 4

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

$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 не требуется.

Ответ 5

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 нотацию коллекций в стиле Generic в стиле PHPDoc.

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

Ответ 6

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

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

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

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

/**
 * @return SomeObj[]
 */

для этого.

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

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

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

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

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 не вынужден вводить эти докблоки, чтобы иметь завершение кода. Для того, чтобы @возвращаться только в одном месте, уменьшает избыточность (@var) как можно больше. Посыпать "docBlocks с помощью @var" сделает ваш код хуже читаемым.

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

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

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

Ответ 7

В 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 - вам разрешено проецировать массив, поскольку объект Text Object будет вызывать ошибки.

Ответ 8

Как упоминала DanielaWaranie в своем ответе - есть способ указать тип элемента $, когда вы итерируете более $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 делает. Попробуйте, опубликуйте в комментариях результаты!

Ответ 9

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

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

Ответ 10

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

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

Ответ 11

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

Ответ 12

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

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

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

Ответ 13

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

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