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

317

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

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

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

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

  • 2
    В этом блоге разработчика Netbeans 6.8 есть упоминание о том, что среда IDE теперь достаточно умна, чтобы определить тип членов массива: blogs.sun.com/netbeansphp/entry/php_templates_improved
  • 3
    @therefromhere: ваша ссылка не работает. Я думаю, что новый URL-адрес: blogs.oracle.com/netbeansphp/entry/php_templates_improved
Показать ещё 1 комментарий
Теги:
ide
phpdoc
var

13 ответов

273
Лучший ответ

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

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

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

  • 3
    Это имеет смысл, но не работает для PHPEd 5.2. Единственное, что мне удалось придумать, это сработало foreach ($ Objs as / ** @var Test * / $ Obj), что ужасно уродливо. :(
  • 2
    Это работает в среде NetBeans 6.7 (я думаю , что это прослушивается, так как вы получаете? Для типа , когда вы нажмете Ctrl-Space, но она способна автозаполнения объекта в члены / методы).
Показать ещё 17 комментариев
787

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

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

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

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

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

  • 10
    Я только что скачал и использовал phpstorm на прошлой неделе. Черт побери из Аптаны (что здорово для того, чтобы быть свободным). Это именно то, что я искал. На самом деле, так же, как вы сделали бы это для JavaScript, я должен был догадаться
  • 3
    Спасибо чувак! Это именно то, что я искал. PHPStorm это фантастика.
Показать ещё 22 комментария
44

Подсказки Netbeans:

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

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

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

  • 4
    также работает в PhpStorm 9 EAP: / ** * @var UserInterface [] * / var $ users = []; // Массив объектов, реализующих интерфейс
  • 0
    Я пробовал это в IDE NetBeans 8.0.2, но я получаю предложения от класса, в котором я сейчас нахожусь.
Показать ещё 1 комментарий
28

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

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

  • 1
    Спасибо Highmastdon, это также работает в phpStorm
  • 2
    На мой взгляд, это решение чище, чем принятый ответ, потому что вы можете использовать foreach несколько раз, и /* @var $Obj Test */ типа будет продолжать работать без новой аннотации /* @var $Obj Test */ каждый раз.
Показать ещё 4 комментария
12

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.

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

11

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

  • 0
    это выглядит как "правильный" способ, но я не могу заставить его работать с Netbeans. Сделал небольшой пример: imgur.com/fJ9Qsro
  • 2
    Возможно, в 2012 году это было «не стандартно», но сейчас это описывается как встроенная функциональность phpDoc.
Показать ещё 3 комментария
4

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

  • 0
    Ваучер за то, что он продвинулся так далеко, но, к сожалению, я все еще могу решить специализироваться на коллекции, чтобы заменить старые добрые универсальные типы java .... yuck '
  • 0
    Спасибо. Как вы можете напечатать статический метод?
4

В 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 будет вызывать ошибки.

  • 1
    Я использую "@return array | Test Some description." который вызывает такое же поведение, но немного более объяснительный.
  • 0
    Это обходной путь , а не решение. Вы говорите, что «эта функция может возвращать объект типа« Test »ИЛИ массив». Однако технически он ничего не говорит вам о том, что может быть в массиве.
3

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

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

  • 0
    Это также работает в PHPStorm.
1
<?php foreach($this->models as /** @var Model_Object_WheelModel */ $model): ?>
    <?php
    // Type hinting now works:
    $model->getImage();
    ?>
<?php endforeach; ?>
  • 5
    какие IDE поддерживают это?
  • 20
    Это очень некрасиво. Попрощайтесь с чистым кодом, когда вы начнете программировать, как это.
Показать ещё 1 комментарий
1

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

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

  • 5
    Одна из вещей, которые мне нравятся в C / C ++, это то, что он фактически отслеживает типы до этого уровня. Это был бы очень приятный спуск.
  • 2
    Поддерживается Netbeans 7.2 (по крайней мере, это версия, которую я использую), но с небольшой поправкой, а именно: /* @var $foo Foo[] */ . Просто написал ответ ниже об этом. Это также можно использовать внутри циклов foreach(){}
0

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

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

private $userList = array();
$userList = User::fetchAll(); // now $userList is an array of User objects
foreach ($userList as $user) {
   $user instanceof User;
   echo $user->getName();
}
  • 11
    Единственная проблема заключается в том, что вводится дополнительный код, который должен использоваться только вашей IDE. Гораздо лучше определить хинтинг типа в комментариях.
  • 1
    Вау, это прекрасно работает. В итоге вы получите дополнительный код, но он кажется безвредным. Я собираюсь начать делать: $ x instanceof Y; // typehint
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню