Підказки типу PHPDoc для масиву об’єктів?


417

Так, у PHPDoc можна вказати @varвище декларації змінної члена, щоб натякнути на її тип. Тоді IDE, напр. PHPEd, буде знати, з яким типом об'єкта працює, і зможе надати код для цієї змінної.

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

Це чудово працює, поки мені не потрібно зробити те ж саме для масиву об’єктів, щоб мати можливість отримати належну підказку, коли згодом я повторюю ці об’єкти.

Отже, чи є спосіб оголосити тег PHPDoc, щоб вказати, що змінна члена є масивом SomeObjs? @varмасиву недостатньо і @var array(SomeObj), здається, недійсний, наприклад.


2
У цьому блозі Netbeans 6.8 Dev є деяка посилання, що 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 (це не означає, що це повинно бути відповіддю для ОП, оскільки він, наприклад, просить PHPEd)
Erenor Paz

Відповіді:


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. Не знаєте про інших редакторів, але це повинно працювати.


3
Це має сенс, але це не спрацювало для PHPEd 5.2. Єдине, що мені вдалося придумати, що працював - це foreach ($ Objs as / ** @var Test * / $ Obj), що жахливо потворно. :(
Артем Русаковський

14
Примітка в Netbeans 7, здається, важлива, у вас є лише одна зірочка - /** @var $Obj Test */не працює.
contrebis

3
@contrebis: "@var" - дійсний тег docblock. Тож навіть якщо ваш IDE не підтримує його в межах docblock "/ ** ... /" і підтримує "@var" лише в "/ ... * /" - будь ласка, не змінюйте правильний docblock. Подайте проблему в трекер помилок IDE, щоб IDE відповідав стандартам. Уявіть, що ваша команда розробників / зовнішні розробники / спільноти використовують різні ІДЕ. Зберігайте його таким, яким він є, і будьте готові до майбутнього.
DanielaWaranie

181
Переконайтеся, що ви дивитесь нижче! Я майже не прокрутився вниз - був би ГОЛОВНИЙ ПОМИЛКУ !!! Багато IDE будуть підтримувати кращий синтаксис! (натяк: @var Object[] $objectsговорить, що «$ об’єкти» - це масив екземплярів Object.)
Thom Porter

10
/** @var TYPE $variable_name */- правильний синтаксис; не змінюйте порядок назви типу та змінної (як це було запропоновано раніше у коментарях), оскільки це не працює у всіх випадках.
srcspider

893

У PhpStorm IDE від 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, що реалізує інтерфейс
Ронан

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

також працює в Eclipse 4.6.3 (idk, яка версія версії була введена, але її робота та те, що я зараз використовую)
hanshenrik

Це, на жаль, array_pop()чомусь не працює після використання чи подібних функцій. Здається, що Netbeans не усвідомлює, що ці функції повертають один елемент вхідного масиву.
William 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
На мою думку, це рішення є чистішим, ніж прийнята відповідь, оскільки ви можете використовувати передбачення кілька разів, і натяк на тип буде працювати з новою /* @var $Obj Test */приміткою кожен раз.
Генрі

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

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

1. Введення тексту працює з phpdoc ... якщо ви не використовуєте docblock, ваш IDE не намагатиметься відгадати те, що ви написали в якомусь випадковому коментарі. 2. Правильний формат, як сказано в деяких інших відповідях - це те, що я вказав вище; тип даних перед назвою змінної . 3. Я не написав іншої відповіді, оскільки на це питання не потрібна інша, і я краще не просто редагувати ваш код.
Крістіан

24

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 допоможе кодом, то це ще один питання про те, чи підтримує IDE нотацію колекцій у загальному стилі PHPDoc.

З моєї відповіді на це питання .


Загальне позначення було видалено з PSR-5
зоровано

11

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

Користувач API може запитати: це масив лише одного виміру? Чи об'єкти розміщені навколо всіх рівнів багатовимірного масиву? Скільки вкладених циклів (foreach тощо) мені потрібно для доступу до всіх об’єктів? Який тип об’єктів "зберігається" в цьому масиві?

Як ви окреслили, ви хочете використовувати цей масив (який містить об'єкти) як одномірний масив.

Як зазначає Ніші, ви можете використовувати:

/**
 * @return SomeObj[]
 */

для того.

Але ще раз: будьте в курсі - це не стандартне позначення docblock. Це позначення було введено деякими виробниками IDE.

Гаразд, добре, як розробник ви знаєте, що "[]" прив'язаний до масиву в PHP. Але що означає "щось []" у звичайному контексті PHP? "[]" означає: створити новий елемент у межах "чогось". Новим елементом може бути все. Але те, що ви хочете висловити, це: масив об'єктів одного типу і його точний тип. Як бачите, виробник IDE вводить новий контекст. Новий контекст, який ти повинен був вивчити. Новий контекст, який повинні були вивчити інші розробники PHP (щоб зрозуміти ваші docblocks). Поганий стиль (!).

Оскільки ваш масив має один вимір, ви, можливо, хочете назвати цей "масив об'єктів" "списком". Майте на увазі, що "список" має дуже особливе значення в інших мовах програмування. Краще було б, наприклад, назвати це "колекцією".

Пам'ятайте: ви використовуєте мову програмування, яка дозволяє вам використовувати всі варіанти OOP. Використовуйте клас замість масиву та зробіть його клас прохідним, як масив. Наприклад:

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 з @var" зробить ваш код найгіршим для читання.

Зрештою, ви все зробили. Виглядає важко за добу? Схоже, взяти кувалду, щоб зламати горіх? Не справді, оскільки ви знайомі з цим інтерфейсом та чистим кодом. Пам'ятайте: ваш вихідний код пишеться один раз / читають багато.

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

Дякую Крістіану Вайсу (з Німеччини) за те, що він був моїм тренером і за те, що навчав мене такої чудової речі. PS: Знайомтесь зі мною та ним на XING.


це виглядає як "правильний" шлях, але я не можу змусити його працювати з Netbeans. Зробив невеликий приклад: imgur.com/fJ9Qsro
fehrlich

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

@Wirone, схоже, phpDocumentor додає це до свого посібника як реакцію на виробників ідеї. Навіть якщо у вас є широка підтримка інструментів, це не означає, що це найкраща практика. Вона починає отримувати SomeObj [], розповсюджуючись у більшій кількості проектів, подібних до вимог, Requir_once, включати і включати_once робили багато років тому. При автоматичному завантаженні поява цих тверджень падає нижче 5%. Сподіваємось, SomeObj [] знижується до тієї ж швидкості протягом наступних 2 років на користь вищезазначеного підходу.
DanielaWaranie

1
Я не розумію, чому? Це дуже просте і чітке позначення. Коли ви бачите, SomeObj[]ви знаєте, що це двовимірний масив SomeObjекземплярів, і тоді ви знаєте, що з цим робити. Я не думаю, що це не дотримується кредо "чистого коду".
Wirone

Це має бути відповіддю. Хоча не всі підходи @return <className>щодо підтримки IDE для current()та для всіх хлопців. PhpStorm підтримує, тому він мені дуже допоміг. Дякую, друже!
Павло

5

Використання array[type]в Zend Studio.

У Zend Studio, array[MyClass]або array[int]чи навіть array[array[MyClass]]відмінно працюють.


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 масив | Тест деякого опису." що викликає таку ж поведінку, але є трохи більш пояснювальною.
Боб Фангер

1
Це рішення , а не рішення. Що ви тут говорите, "ця функція може повернути об'єкт типу" Тест "АБО масив". Однак технічно це нічого не говорить вам про те, що може бути в масиві.
Byson

5

Як ДаніелаВарані згадувала у своїй відповіді - є спосіб вказати тип елемента $ при ітерації над $ елементами у $ collectionObject: Додайте @return MyEntitiesClassNameдо current()та решту Iteratorта ArrayAccess-методи, які повертають значення.

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

Отже, ось хитрість: не вказуйте тип повернення в абстрактному класі, а використовуйте вклад 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 .... yuck '
Себас,

Дякую. Як можна ввести підказку статичного методу?
Євген Афанасьєв

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 не запровадить введені масиви, якщо вони коли-небудь є), оскільки ми оголошуємо тип ітератора в ітерабельному класі, а не на екземплярах класу, розпорошеного по коду.

Я не показав тут повного рішення для розширення ArrayIterator, тому якщо ви використовуєте цю техніку, ви також можете:

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

Дуже приємне і чисте рішення! :)
Marko Šutija

2

Проблема полягає в тому, що @varможна просто позначити один тип - Не містити складної формули. Якщо у вас був синтаксис "масив Foo", чому зупинятись на цьому і не додавати синтаксис "масив масиву, який містить 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 підтримують це?
філфрео

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-файл, запит на функцію в трекері випуску IDE.
DanielaWaranie

1
Якщо це викид, якщо тип невірний, він може бути корисним для перевірки типу виконання. Якщо ...
lilbyrdie
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.