Type de PHPDoc indiquant un tableau d'objets?

417

Donc, dans PHPDoc, on peut spécifier @varau-dessus de la déclaration de variable membre pour faire allusion à son type. Ensuite, un IDE, par exemple. PHPEd, saura avec quel type d'objet il travaille et sera en mesure de fournir un aperçu du code pour cette variable.

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

Cela fonctionne très bien jusqu'à ce que je doive faire de même avec un tableau d'objets pour pouvoir obtenir un indice approprié lorsque j'itérerai ces objets plus tard.

Alors, existe-t-il un moyen de déclarer une balise PHPDoc pour spécifier que la variable membre est un tableau de SomeObjs? @vartableau ne suffit pas, et @var array(SomeObj)ne semble pas être valide, par exemple.

Artem Russakovskii
la source
2
Il y a une référence dans ce blog de développement Netbeans 6.8 que l'EDI est maintenant suffisamment intelligent pour déduire le type de membres du tableau: blogs.sun.com/netbeansphp/entry/php_templates_improved
John Carter
3
@therefromhere: votre lien est rompu. Je pense que la nouvelle URL est: blogs.oracle.com/netbeansphp/entry/php_templates_improved
DanielaWaranie
Pour les gens comme moi, en passant et en cherchant une réponse: si vous utilisez PHPStorm, regardez la réponse la plus votée: elle a un indice spécifique! stackoverflow.com/a/1763425/1356098 (cela ne signifie pas que cela devrait être la réponse pour l'OP, car il demande PHPEd, par exemple)
Erenor Paz

Réponses:

337

Utilisation:

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

lors de la saisie de types de variables en ligne, et

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

pour les propriétés de classe.

Réponse précédente de '09 lorsque PHPDoc (et les IDE comme Zend Studio et Netbeans) n'avaient pas cette option:

Le mieux que vous puissiez faire est de dire:

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

Je fais beaucoup ça dans Zend Studio. Je ne connais pas les autres éditeurs, mais cela devrait fonctionner.

Zahymaka
la source
3
Cela a du sens mais cela n'a pas fonctionné pour PHPEd 5.2. La seule chose que j'ai pu trouver qui a fonctionné est foreach ($ Objs as / ** @var Test * / $ Obj), qui est horriblement moche. :(
Artem Russakovskii
14
Remarque dans Netbeans 7, il semble important que vous n'ayez qu'un seul astérisque - /** @var $Obj Test */ne fonctionne pas.
contrebis
3
@contrebis: "@var" est une balise docblock valide. Ainsi, même si votre IDE ne le prend pas en charge dans un docblock "/ ** ... /" et ne prend en charge "@var" que dans "/ ... * /" - veuillez ne pas modifier votre docblock correct. Signalez un problème au programme de suivi des bogues de votre IDE pour le rendre conforme aux normes. Imaginez que votre équipe de développement / développeurs externes / communauté utilise différents IDE. Gardez-le tel quel et soyez prêt pour l'avenir.
DanielaWaranie
181
Assurez-vous de regarder ci-dessous! Je n'ai presque pas fait défiler vers le bas - aurait été une grosse erreur !!! De nombreux IDE prendront en charge une meilleure syntaxe! (indice: @var Object[] $objectsdit que "$ objects" est un tableau d'instances d'Object.)
Thom Porter
10
/** @var TYPE $variable_name */est la syntaxe correcte; n'inversez pas l'ordre du type et du nom de variable (comme suggéré plus haut dans les commentaires) car cela ne fonctionnera pas dans tous les cas.
srcspider
893

Dans l'IDE PhpStorm de JetBrains, vous pouvez utiliser /** @var SomeObj[] */, par exemple:

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

La documentation de phpdoc recommande cette méthode:

spécifiée contenant un seul type, la définition Type informe le lecteur du type de chaque élément du tableau. Un seul type est alors attendu comme élément pour un tableau donné.

Exemple: @return int[]

Nishi
la source
10
Je viens de télécharger et j'utilise phpstorm depuis la semaine dernière. Bat le diable d'Aptana (ce qui est idéal pour être libre). Ceci est exactement ce que je cherchais. En fait, c'est la même façon que vous le feriez pour JavaScript, j'aurais dû deviner
Juan Mendes
3
Merci mec! Ceci est exactement ce que je cherchais. PHPStorm est fantastique.
Erik Schierboom
5
Cela ne fonctionne pas dans Netbeans, je suis déçu. Jetbrains fait de très bons outils.
Keyo
10
@fishbone @Keyo cela fonctionne maintenant dans Netbeans (au moins dans la version 7.1 de nuit, peut-être plus tôt), bien qu'il semble que vous ayez besoin d'utiliser une variable temporaire (un bug?). Faire allusion foreach(getSomeObjects() as $obj)ne fonctionne pas, mais cela fonctionne pour$objs = getSomeObjects(); foreach($objs as $obj)
John Carter
2
Ce serait bien d'avoir @var Obj[string]pour les tableaux associatifs.
donquixote
59

Astuces Netbeans:

Vous obtenez l'achèvement du code sur $users[0]->et $this->pour un tableau de classes d'utilisateurs.

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

Vous pouvez également voir le type du tableau dans une liste de membres de classe lorsque vous effectuez $this->...

user1491819
la source
4
fonctionne également dans PhpStorm 9 EAP: / ** * @var UserInterface [] * / var $ users = []; // Tableau d'Objs implémentant une interface
Ronan
Je l'ai essayé dans NetBeans IDE 8.0.2, mais je reçois des suggestions de la classe dans laquelle je suis actuellement.
Wojciech Jasiński
fonctionne également dans Eclipse 4.6.3 (idk quelle version de support a été introduite, mais son fonctionnement, et ce que
j'utilise
Cela ne fonctionne malheureusement pas après avoir utilisé array_pop()ou des fonctions similaires pour une raison quelconque. Il semble que Netbeans ne réalise pas que ces fonctions renvoient un seul élément du tableau d'entrée.
William W
29

Pour spécifier une variable est un tableau d'objets:

$needles = getAllNeedles();
/* @var $needles Needle[] */
$needles[1]->...                        //codehinting works

Cela fonctionne dans Netbeans 7.2 (je l'utilise)

Fonctionne également avec:

$needles = getAllNeedles();
/* @var $needles Needle[] */
foreach ($needles as $needle) {
    $needle->...                        //codehinting works
}

Par conséquent, l'utilisation d'une déclaration à l'intérieur du foreachn'est pas nécessaire.

Highmastdon
la source
2
Cette solution est plus propre que la réponse acceptée à mon avis, car vous pouvez utiliser foreach plusieurs fois et l'indication de type continuera à fonctionner avec une nouvelle /* @var $Obj Test */annotation à chaque fois.
Henry
Je vois deux problèmes ici: 1. le bon phpdoc commence par /** 2. Le format correct est@var <data-type> <variable-name>
Christian
@Christian 1: la question principale n'est pas phpdoc mais typhinting 2: le format correct n'est pas comme vous le dites, même selon d'autres réponses. En fait, je vois 2 problèmes avec votre commentaire, et je me demande pourquoi vous ne faites pas votre propre réponse avec le format correct
Highmastdon
1. Typhinting fonctionne avec phpdoc ... si vous n'utilisez pas le docblock, votre IDE n'essaiera pas de deviner ce que vous avez écrit dans un commentaire aléatoire. 2. Le format correct, comme d'autres réponses l'ont également indiqué, est ce que j'ai spécifié ci-dessus; type de données avant le nom de la variable . 3. Je n'ai pas écrit une autre réponse parce que la question n'a pas besoin d'une autre et je préfère ne pas simplement modifier votre code.
Christian
24

PSR-5: PHPDoc propose une forme de notation de style générique.

Syntaxe

Type[]
Type<Type>
Type<Type[, Type]...>
Type<Type[|Type]...>

Les valeurs d'une collection PEUVENT même être un autre tableau et même une autre collection.

Type<Type<Type>>
Type<Type<Type[, Type]...>>
Type<Type<Type[|Type]...>>

Exemples

<?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> */

Remarque: Si vous vous attendez à ce qu'un IDE aide le code, alors c'est une autre question de savoir si l'IDE prend en charge la notation des collections de style générique PHPDoc.

De ma réponse à cette question .

Gerard Roche
la source
La notation générique a été supprimée du PSR-5
zored
11

Je préfère lire et écrire du code propre - comme indiqué dans "Clean Code" de Robert C. Martin. Lorsque vous suivez son credo, vous ne devez pas obliger le développeur (en tant qu'utilisateur de votre API) à connaître la structure (interne) de votre tableau.

L'utilisateur de l'API peut demander: est-ce un tableau avec une seule dimension? Les objets sont-ils répartis à tous les niveaux d'un réseau multidimensionnel? De combien de boucles imbriquées (foreach, etc.) ai-je besoin pour accéder à tous les objets? Quels types d'objets sont "stockés" dans ce tableau?

Comme vous l'avez indiqué, vous souhaitez utiliser ce tableau (qui contient des objets) comme un tableau unidimensionnel.

Comme indiqué par Nishi, vous pouvez utiliser:

/**
 * @return SomeObj[]
 */

pour ça.

Mais encore une fois: soyez conscient - ce n'est pas une notation standard de docblock. Cette notation a été introduite par certains producteurs IDE.

D'accord, d'accord, en tant que développeur, vous savez que "[]" est lié à un tableau en PHP. Mais que signifie "quelque chose []" dans un contexte PHP normal? "[]" signifie: créer un nouvel élément dans "quelque chose". Le nouvel élément pourrait être tout. Mais ce que vous voulez exprimer, c'est: tableau d'objets avec le même type et c'est le type exact. Comme vous pouvez le voir, le producteur IDE introduit un nouveau contexte. Un nouveau contexte qu'il fallait apprendre. Un nouveau contexte que d'autres développeurs PHP ont dû apprendre (pour comprendre vos docblocks). Mauvais style (!).

Parce que votre tableau a une dimension, vous voudrez peut-être appeler ce "tableau d'objets" une "liste". Sachez que "liste" a une signification très spéciale dans d'autres langages de programmation. Il serait préférable de l'appeler "collection" par exemple.

N'oubliez pas: vous utilisez un langage de programmation qui vous permet toutes les options de POO. Utilisez une classe au lieu d'un tableau et rendez votre classe traversable comme un tableau. Par exemple:

class orderCollection implements ArrayIterator

Ou si vous souhaitez stocker les objets internes à différents niveaux dans une structure de tableau / objet multidimensionnelle:

class orderCollection implements RecursiveArrayIterator

Cette solution remplace votre tableau par un objet de type "orderCollection", mais n'activez pas la complétion de code dans votre IDE jusqu'à présent. D'accord. L'étape suivante:

Implémentez les méthodes introduites par l'interface avec docblocks - en particulier:

/**
 * [...]
 * @return Order
 */
orderCollection::current()

/**
 * [...]
 * @return integer E.g. database identifier of the order
 */
orderCollection::key()

/**
 * [...]
 * @return Order
 */
orderCollection::offsetGet()

N'oubliez pas d'utiliser l'indication de type pour:

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

Cette solution arrête d'introduire beaucoup de:

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

partout dans vos fichiers de code (par exemple dans les boucles), comme Zahymaka l'a confirmé avec sa réponse. Votre utilisateur d'API n'est pas obligé d'introduire ces docblocks pour que le code soit complété. Avoir @return sur un seul endroit réduit la redondance (@var) le plus possible. Saupoudrer "docBlocks avec @var" rendrait votre code le moins lisible.

Finalement, vous avez terminé. Semble difficile à atteindre? On dirait de prendre un marteau pour casser une noix? Pas vraiment, puisque vous êtes familier avec ces interfaces et avec du code propre. Rappelez-vous: votre code source est écrit une fois / lu plusieurs.

Si l'achèvement du code de votre IDE ne fonctionne pas avec cette approche, passez à une meilleure (par exemple IntelliJ IDEA, PhpStorm, Netbeans) ou déposez une demande de fonctionnalité sur le suivi des problèmes de votre producteur IDE.

Merci à Christian Weiss (d'Allemagne) d'avoir été mon entraîneur et de m'avoir enseigné un truc tellement génial. PS: Rencontrez-moi et lui sur XING.

DanielaWaranie
la source
cela ressemble à la "bonne" façon, mais je ne peux pas le faire fonctionner avec Netbeans. Fait un petit exemple: imgur.com/fJ9Qsro
fehrlich
2
Peut-être qu'en 2012 ce n'était "pas un standard", mais maintenant il est décrit comme une fonctionnalité intégrée de phpDoc.
Wirone
@Wirone on dirait que phpDocumentor l'ajoute à son manuel en réaction aux producteurs d'idées. Même si vous disposez d'un large éventail d'outils, cela ne signifie pas qu'il s'agit de la meilleure pratique. Cela commence à répandre SomeObj [] dans de plus en plus de projets, similaires à require, require_once, include et include_once il y a des années. Avec le chargement automatique, l'apparence de ces déclarations tombe en dessous de 5%. Espérons que SomeObj [] chute au même rythme au cours des 2 prochaines années en faveur de l'approche ci-dessus.
DanielaWaranie
1
Je ne comprends pas pourquoi? Il s'agit d'une notation très simple et claire. Lorsque vous voyez que SomeObj[]vous savez qu'il s'agit d'un tableau d' SomeObjinstances bidimensionnel, vous savez quoi en faire. Je ne pense pas qu'il ne respecte pas le credo "code propre".
Wirone
Cela devrait être la réponse. Cependant, toutes les approches de support IDE ne sont pas avec @return <className>for current()et all guys. PhpStorm prend en charge, donc cela m'a beaucoup aidé. Merci mon pote!
Pavel
5

À utiliser array[type]dans Zend Studio.

Dans Zend Studio, array[MyClass]ou array[int]ou même array[array[MyClass]]fonctionne très bien.

Erick Robertson
la source
5

Dans NetBeans 7.0 (peut-être aussi plus bas), vous pouvez déclarer le type de retour "tableau avec des objets Text" de la même manière @return Textet l'indication de code fonctionnera:

Edit: mise à jour de l'exemple avec la suggestion @Bob Fanger

/**
 * get all Tests
 *
 * @return Test|Array $tests
 */
public function getAllTexts(){
    return array(new Test(), new Test());
}

et il suffit de l'utiliser:

$tests =  $controller->getAllTests();
//$tests->         //codehinting works!
//$tests[0]->      //codehinting works!

foreach($tests as $text){
    //$test->      //codehinting works!
}

Ce n'est pas parfait mais il vaut mieux alors juste le laisser juste "mélangé", ce qui n'apporte aucune valeur.

CONS est que vous êtes autorisé à fouler le tableau en tant qu'objet texte, ce qui générera des erreurs.

d.raev
la source
1
J'utilise "@return array | Test Some description." qui déclenche le même comportement mais est un peu plus explicatif.
Bob Fanger
1
Il s'agit d'une solution de contournement , pas d'une solution. Ce que vous dites ici est "cette fonction peut renvoyer un objet de type 'Test', OU un tableau". Cependant, il ne vous dit techniquement rien sur ce qui pourrait être dans la baie.
Byson
5

Comme DanielaWaranie mentionné dans sa réponse - il y a un moyen de spécifier le type de $ article lorsque vous itérer articles $ en $ collectionObject: Ajouter @return MyEntitiesClassNameau current()et le reste du Iteratoret ArrayAccess-méthodes les valeurs de retour.

Boom! Pas besoin de /** @var SomeObj[] $collectionObj */plus foreach, et fonctionne correctement avec l'objet collection, pas besoin de retourner la collection avec la méthode spécifique décrite comme @return SomeObj[].

Je soupçonne que tous les IDE ne le supportent pas, mais cela fonctionne parfaitement dans PhpStorm, ce qui me rend plus heureux.

Exemple:

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
}

Quelle utilité j'allais ajouter en postant cette réponse

Dans mon cas current()et le reste des interfaceméthodes sont implémentées dans la Abstractclasse -collection et je ne sais pas quel type d'entités sera finalement stocké dans la collection.

Voici donc l'astuce: ne spécifiez pas le type de retour dans la classe abstraite, utilisez plutôt l'instuction PhpDoc @methoddans la description de la classe de collection spécifique.

Exemple:

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

}

Maintenant, l'utilisation des classes:

$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();
}

Encore une fois: je soupçonne que tous les IDE ne le supportent pas, mais PhpStorm le fait. Essayez le vôtre, postez en commentaire les résultats!

Pavel
la source
Bon pour l'avoir poussé jusque-là, mais malheureusement je peux encore me résoudre à spécialiser une collection pour remplacer les bons vieux types génériques java .... beurk '
Sebas
Je vous remercie. Comment pouvez-vous saisir une méthode statique?
Yevgeniy Afanasyev
3

Je sais que je suis en retard à la fête, mais j'ai récemment travaillé sur ce problème. J'espère que quelqu'un voit cela parce que la réponse acceptée, bien que correcte, n'est pas la meilleure façon de le faire. Pas dans PHPStorm au moins, je n'ai pas testé NetBeans cependant.

La meilleure façon consiste à étendre la classe ArrayIterator plutôt qu'à utiliser des types de tableaux natifs. Cela vous permet de taper hint au niveau de la classe plutôt qu'au niveau de l'instance, ce qui signifie que vous n'avez à PHPDoc qu'une seule fois, pas dans tout votre code (ce qui est non seulement désordonné et viole DRY, mais peut également être problématique en ce qui concerne refactoring - PHPStorm a l'habitude de manquer PHPDoc lors du refactoring)

Voir le code ci-dessous:

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

La clé ici est le PHPDoc remplaçant @method MyObj current()le type de retour hérité d'ArrayIterator (qui est mixed). L'inclusion de ce PHPDoc signifie que lorsque nous parcourons les propriétés de la classe à l'aide foreach($this as $myObj), nous obtenons ensuite l'achèvement du code lorsque nous nous référons à la variable$myObj->...

Pour moi, c'est la meilleure façon d'y parvenir (au moins jusqu'à ce que PHP introduise des tableaux typés, s'ils le font), car nous déclarons le type d'itérateur dans la classe itérable, pas sur les instances de la classe dispersées dans le code.

Je n'ai pas montré ici la solution complète pour étendre ArrayIterator, donc si vous utilisez cette technique, vous pouvez également vouloir:

  • Inclure d'autres PHPDoc au niveau de la classe selon les besoins, pour des méthodes telles que offsetGet($index)etnext()
  • Déplacer le test de validité is_a($object, MyObj::class)du constructeur dans une méthode privée
  • Appelez ce contrôle d'intégrité (désormais privé) à partir des remplacements de méthode tels que offsetSet($index, $newval)etappend($value)
e_i_pi
la source
Solution très agréable et propre! :)
Marko Šutija
2

Le problème est qu'il @varne peut désigner qu'un seul type - Ne pas contenir de formule complexe. Si vous aviez une syntaxe pour "tableau de Foo", pourquoi vous arrêter là et ne pas ajouter une syntaxe pour "tableau de tableau, qui contient 2 Foo et trois barres"? Je comprends qu'une liste d'éléments est peut-être plus générique que cela, mais c'est une pente glissante.

Personnellement, j'ai parfois utilisé @var Foo[]pour signifier "un tableau de Foo", mais ce n'est pas pris en charge par les IDE.

troelskn
la source
5
Une des choses que j'aime à propos de C / C ++ est qu'il garde en fait la trace des types jusqu'à ce niveau. Ce serait une pente très agréable à glisser.
Brilliand
2
Est pris en charge par Netbeans 7.2 (au moins qui est la version que je utilise), mais avec un peu ajustment à savoir: /* @var $foo Foo[] */. Je viens d'écrire une réponse ci-dessous à ce sujet. Cela peut également être utilisé à l'intérieur des foreach(){}boucles
Highmastdon
1
<?php foreach($this->models as /** @var Model_Object_WheelModel */ $model): ?>
    <?php
    // Type hinting now works:
    $model->getImage();
    ?>
<?php endforeach; ?>
Scott Hovestadt
la source
5
quels IDE soutiennent cela?
philfreo
21
C'est très moche. Dites adieu au code propre lorsque vous commencez à programmer comme ça.
halfpastfour.am
Regardez plutôt ma réponse en définissant le contenu du tableau: stackoverflow.com/a/14110784/431967
Highmastdon
-5

J'ai trouvé quelque chose qui fonctionne, cela peut sauver des vies!

private $userList = array();
$userList = User::fetchAll(); // now $userList is an array of User objects
foreach ($userList as $user) {
   $user instanceof User;
   echo $user->getName();
}
eupho
la source
11
le seul problème est qu'il introduit du code supplémentaire à exécuter, qui est purement utilisé par votre IDE uniquement. Il est préférable de définir plutôt une indication de type dans les commentaires.
Ben Rowe
1
Wow cela fonctionne très bien. Vous vous retrouveriez avec du code supplémentaire, mais il semble inoffensif. Je vais commencer à faire: $ x instanceof Y; // typehint
Igor Nadj
3
Basculez vers un IDE qui vous donne un code complet basé sur des docblocks ou des inspections. Si vous ne voulez pas changer votre fichier IDE, une demande de fonctionnalité sur le tracker de problème de votre IDE.
DanielaWaranie
1
Si cela lève une exception si le type n'est pas correct, cela peut être utile pour la vérification du type d'exécution. Si ...
lilbyrdie