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


417

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

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

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

Итак, есть ли способ объявить тег PHPDoc, чтобы указать, что переменная-член является массивом SomeObjs? Например, @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
DanielaWaranie

Для таких людей, как я, проходящих мимо и ищущих ответ: если вы используете PHPStorm, посмотрите на получивший наибольшее количество голосов ответ: у него есть особая подсказка! stackoverflow.com/a/1763425/1356098 (это не значит, что он должен быть ответом для OP, так как он запрашивает PHPEd, например)
Эренор Паз

Ответы:


337

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

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


3
Это имеет смысл, но это не сработало для PHPEd 5.2. Единственное, что мне удалось придумать, это сработало - foreach ($ Objs as / ** @var Test * / $ Obj), что ужасно уродливо. :(
Артем Руссаковский

14
Обратите внимание, что в Netbeans 7 кажется важным, что у вас есть только одна звездочка - /** @var $Obj Test */не работает.
contrebis

3
@contrebis: «@var» является допустимым тегом docblock. Поэтому, даже если ваша IDE не поддерживает его в докблоке "/ ** ... /" и поддерживает только "@var" только в "/ ... * /" - пожалуйста, не меняйте свой правильный докблок. Отправьте сообщение об ошибке в систему отслеживания ошибок вашей IDE, чтобы сделать ее совместимой со стандартами. Представьте, что ваша команда разработчиков / внешние разработчики / сообщество используют разные IDE. Оставь все как есть и будь готов к будущему.
DanielaWaranie

181
Убедитесь, что вы смотрите ниже! Я почти не прокручивал вниз - это была бы БОЛЬШАЯ ОШИБКА !!! Многие IDE будут поддерживать лучший синтаксис! (подсказка: @var Object[] $objectsговорит, что «$ objects» - это массив экземпляров Object.)
Том Портер

10
/** @var TYPE $variable_name */правильный синтаксис; не меняйте порядок типа и имени переменной (как предложено ранее в комментариях), так как это не будет работать во всех случаях.
srcspider

893

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

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

phpdoc рекомендует этот метод:

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

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


10
Я только что скачал и использовал phpstorm на прошлой неделе. Черт побери из Аптаны (что здорово для того, чтобы быть свободным). Это именно то, что я искал. На самом деле, это то же самое, что вы сделали бы это для JavaScript, я должен был догадаться
Хуан Мендес,

3
Спасибо чувак! Это именно то, что я искал. PHPStorm это фантастика.
Эрик Ширбум

5
Это не работает в Netbeans, я разочарован. Jetbrains делают некоторые очень хорошие инструменты.
Кейо

10
@fishbone @Keyo теперь это работает в Netbeans (по крайней мере, в ночной сборке 7.1, может быть раньше), хотя кажется, что вам нужно использовать временную переменную (ошибка?). Хинтинг для foreach(getSomeObjects() as $obj)не работает, но для$objs = getSomeObjects(); foreach($objs as $obj)
Джон Картер

2
Было бы неплохо иметь @var Obj[string]для ассоциативных массивов.
Donquixote

59

Netbeans намеки:

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

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

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


4
также работает в PhpStorm 9 EAP: / ** * @var UserInterface [] * / var $ users = []; // Массив Objs, реализующих интерфейс
Ронан

Я пробовал это в среде IDE NetBeans 8.0.2, но я получаю предложения от класса, в котором я сейчас нахожусь.
Войцех Ясиньски

также работает в Eclipse 4.6.3 (IDK, какая поддержка версий была введена, но она работает, и что я использую сейчас)
hanshenrik

К сожалению, array_pop()по какой-то причине это не работает после использования или подобных функций. Кажется, что Netbeans не понимает, что эти функции возвращают единственный элемент входного массива.
Уильям W

29

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

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


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

Я вижу здесь две проблемы: 1. Правильный phpdoc начинается с /** 2. Правильный формат@var <data-type> <variable-name>
Christian

@Christian 1: главный вопрос не в phpdoc, а в наборе текста 2: правильный формат не такой, как вы говорите, даже в соответствии с другими ответами. На самом деле, я вижу 2 вопроса с вашим комментарием, и мне интересно, почему вы не сделали свой собственный ответ в правильном формате
Highmastdon

1. Набор текста работает с phpdoc ... если вы не используете docblock, ваша IDE не будет пытаться угадать, что вы написали в каком-то случайном комментарии. 2. Правильный формат, как и в некоторых других ответах, - это то, что я указал выше; тип данных перед именем переменной . 3. Я не написал другой ответ, потому что вопрос не нуждается в другом, и я бы предпочел не просто редактировать ваш код.
Кристиан

24

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 Generic.

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


Общее обозначение было удалено из PSR-5
zored

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 не обязан вводить эти docblocks для завершения кода. Наличие @return только в одном месте уменьшает избыточность (@var) настолько, насколько это возможно. Прибавьте «docBlocks with @var», чтобы ваш код стал хуже читаемым.

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

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

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


это выглядит как "правильный" способ, но я не могу заставить его работать с Netbeans. Сделал небольшой пример: imgur.com/fJ9Qsro
fehrlich

2
Возможно, в 2012 году это было «не стандартно», но теперь это описывается как встроенная функциональность phpDoc.
Wirone

@ Wirone, похоже, phpDocumentor добавляет это к своему руководству как реакцию на продюсеров ide. Даже если у вас широкая поддержка инструментов, это не значит, что это лучшая практика. Это начинает распространять SomeObj [] во все большем количестве проектов, аналогично тому, как много лет назад сделали require, require_once, include и include_once. С автозагрузкой видимость этих заявлений падает ниже 5%. Надеемся, что SomeObj [] упадет до того же уровня в течение следующих 2 лет в пользу подхода, описанного выше.
DanielaWaranie

1
Я не понимаю почему? Это очень простая и понятная запись. Когда вы видите, SomeObj[]что знаете, что это двумерный массив SomeObjэкземпляров, а затем вы знаете, что с ним делать. Я не думаю, что это не следует кредо "чистого кода".
Wirone

Это должно быть ответом. Однако не все IDE поддерживают подход @return <className>for current()и всех парней. PhpStorm поддерживает, поэтому он мне очень помог. Спасибо друг!
Павел


5

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


1
Я использую "@return array | Test Some description." который вызывает такое же поведение, но немного более объяснительный.
Боб Фангер

1
Это обходной путь , а не решение. Вы говорите, что «эта функция может возвращать объект типа« Test »ИЛИ массив». Однако технически он ничего не говорит вам о том, что может быть в массиве.
Бизон

5

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

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


Ваучер для проталкивания его , что далеко, но , к сожалению , я до сих пор могу решить сам специализироваться коллекцию , чтобы заменить старый добрый Java универсальных типов .... фу»
Sebas

Спасибо. Как вы можете напечатать статический метод?
Евгений Афанасьев

3

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

Очень хорошее и чистое решение! :)
Марко Шутия

2

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

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


5
Одна из вещей, которые мне нравятся в C / C ++, это то, что он фактически отслеживает типы до этого уровня. Это был бы очень приятный спуск.
Brilliand

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

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

5
какие IDE поддерживают это?
Philfreo

21
Это очень некрасиво. Попрощайтесь с чистым кодом, когда вы начнете программировать вот так.
halfpastfour.am

Скорее посмотрите на мой ответ с определением содержимого массива: stackoverflow.com/a/14110784/431967
Highmastdon

-5

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

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
Игорь Надж

3
Переключитесь на интегрированную среду разработки, которая дает завершение кода на основе докблоков или проверок. Если вы не хотите переключать свой IDE-файл, запросите функцию на трекере проблем вашей IDE.
DanielaWaranie

1
Если это вызывает исключение, если тип неправильный, это может быть полезно для проверки типа во время выполнения. Если ...
Lilbyrdie
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.