¿Sugerencias de tipo PHPDoc para una matriz de objetos?

417

Entonces, en PHPDoc se puede especificar @vararriba de la declaración de la variable miembro para insinuar su tipo. Luego un IDE, por ej. PHPEd, sabrá con qué tipo de objeto está trabajando y podrá proporcionar una idea del código para esa variable.

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

Esto funciona muy bien hasta que necesito hacer lo mismo con una matriz de objetos para poder obtener una pista adecuada cuando repita esos objetos más adelante.

Entonces, ¿hay alguna manera de declarar una etiqueta PHPDoc para especificar que la variable miembro es una matriz de SomeObjs? @varLa matriz no es suficiente, y @var array(SomeObj)no parece ser válida, por ejemplo.

Artem Russakovskii
fuente
2
Hay alguna referencia en este blog de desarrollo de Netbeans 6.8 que el IDE ahora es lo suficientemente inteligente como para deducir el tipo de miembros de la matriz: blogs.sun.com/netbeansphp/entry/php_templates_improved
John Carter
3
@therefromhere: su enlace está roto. Creo que la nueva URL es: blogs.oracle.com/netbeansphp/entry/php_templates_improved
DanielaWaranie
Para personas como yo, que pasan y buscan una respuesta: si usas PHPStorm, mira la respuesta más votada: ¡tiene una pista específica! stackoverflow.com/a/1763425/1356098 (esto no significa que debería ser la respuesta para el OP, ya que está pidiendo PHPEd, por ejemplo)
Erenor Paz

Respuestas:

337

Utilizar:

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

al teclear las variables en línea, y

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

para propiedades de clase.

Respuesta anterior de '09 cuando PHPDoc (e IDEs como Zend Studio y Netbeans) no tenían esa opción:

Lo mejor que puedes hacer es decir:

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

Lo hago mucho en Zend Studio. No sé sobre otros editores, pero debería funcionar.

Zahymaka
fuente
3
Esto tiene sentido, pero no funcionó para PHPEd 5.2. Lo único que se me ocurrió que funcionó es foreach ($ Objs como / ** @var Test * / $ Obj), que es horriblemente feo. :(
Artem Russakovskii
14
Nota en Netbeans 7, parece ser importante que solo tenga un asterisco, /** @var $Obj Test */no funciona.
contrebis
3
@contrebis: El "@var" es una etiqueta docblock válida. Por lo tanto, incluso si su IDE no lo admite dentro de un docblock "/ ** ... /" y admite "@var" solo en "/ ... * /", por favor, no cambie su docblock correcto. Presente un problema al rastreador de errores de su IDE para que su IDE cumpla con los estándares. Imagine que su equipo de desarrollo / desarrolladores externos / comunidad utiliza diferentes IDE. Mantenlo como está y prepárate para el futuro.
DanielaWaranie
181
¡Asegúrate de mirar abajo! Casi no me desplazo hacia abajo, ¡habría sido un GRAN ERROR! ¡Muchos IDEs admitirán una mejor sintaxis! (pista: @var Object[] $objectsdice que "$ objects" es una serie de instancias de Object.)
Thom Porter
10
/** @var TYPE $variable_name */es la sintaxis correcta; no invierta el orden del tipo y el nombre de la variable (como se sugirió anteriormente en los comentarios) ya que eso no funcionará en todos los casos.
srcspider
893

En PhpStorm IDE de JetBrains, puede usar /** @var SomeObj[] */, por ejemplo:

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

La documentación de phpdoc recomienda este método:

especificado que contiene un solo tipo, la definición de Tipo informa al lector del tipo de cada elemento de la matriz. Solo se espera un tipo como elemento para una matriz dada.

Ejemplo: @return int[]

Nishi
fuente
10
Acabo de descargar y he estado usando phpstorm durante la semana pasada. Le gana a Aptana (que es genial para ser gratis). Esto es exactamente lo que estaba buscando. De hecho, es lo mismo que lo harías para JavaScript, debería haber adivinado
Juan Mendes
3
¡Gracias hombre! Esto es exactamente lo que estaba buscando. PHPStorm es fantástico.
Erik Schierboom
55
Esto no funciona en Netbeans, estoy decepcionado. Jetbrains hacen algunas herramientas muy buenas.
Keyo
10
@fishbone @Keyo, esto funciona en Netbeans ahora (al menos en la versión 7.1, quizás antes), aunque parece que necesitas usar una variable temporal (¿un error?). Sugerencia para foreach(getSomeObjects() as $obj)no funciona, pero lo hace para$objs = getSomeObjects(); foreach($objs as $obj)
John Carter
2
Sería bueno tenerlo @var Obj[string]para matrices asociativas.
donquixote
59

Consejos Netbeans:

Obtiene la finalización del código en $users[0]->y $this->para una variedad de clases de usuario.

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

También puede ver el tipo de matriz en una lista de miembros de la clase cuando completa $this->...

usuario1491819
fuente
44
también funciona en PhpStorm 9 EAP: / ** * @var UserInterface [] * / var $ users = []; // Matriz de Objs implementando una interfaz
Ronan
Lo he intentado en NetBeans IDE 8.0.2, pero recibo sugerencias de la clase en la que estoy actualmente.
Wojciech Jasiński
también funciona en Eclipse 4.6.3 (idk qué versión de soporte se introdujo, pero está funcionando y es lo que estoy usando ahora)
hanshenrik
Desafortunadamente, esto no funciona después de usar array_pop()o funciones similares por alguna razón. Parece que Netbeans no se da cuenta de que esas funciones devuelven un solo elemento de la matriz de entrada.
William W
29

Para especificar una variable es una matriz de objetos:

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

Esto funciona en Netbeans 7.2 (lo estoy usando)

Funciona también con:

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

Por lo tanto, foreachno es necesario el uso de declaraciones dentro de .

Highmastdon
fuente
2
En mi opinión, esta solución es más limpia que la respuesta aceptada, porque puede usar foreach varias veces y la sugerencia de tipo seguirá funcionando sin una nueva /* @var $Obj Test */anotación cada vez.
Henry
Veo dos problemas aquí: 1. El phpdoc adecuado comienza con /** 2. El formato correcto es@var <data-type> <variable-name>
Christian
@Christian 1: la pregunta principal no es phpdoc sino tipografía 2: el formato correcto no es como usted dice, incluso de acuerdo con otras respuestas. De hecho, veo 2 problemas con su comentario, y me pregunto por qué no hace su propia respuesta con el formato correcto
Highmastdon
1. Typehinting funciona con phpdoc ... si no usa el docblock, su IDE no intentará adivinar lo que escribió en algún comentario aleatorio. 2. El formato correcto, como algunas otras respuestas también dijeron, es lo que especifiqué anteriormente; tipo de datos antes del nombre de la variable . 3. No escribí otra respuesta porque la pregunta no necesita otra y prefiero no solo editar su código.
Christian
24

PSR-5: PHPDoc propone una forma de notación de estilo genérico.

Sintaxis

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

Los valores en una Colección PUEDEN ser incluso otra matriz e incluso otra Colección.

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

Ejemplos

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

Nota: Si espera que un IDE haga asistencia de código, entonces es otra cuestión sobre si el IDE admite la notación de colecciones de estilo genérico PHPDoc.

De mi respuesta a esta pregunta .

Gerard Roche
fuente
La notación genérica se eliminó de PSR-5
Zored
11

Prefiero leer y escribir código limpio, como se describe en "Código limpio" de Robert C. Martin. Al seguir su credo, no debe exigir al desarrollador (como usuario de su API) que conozca la estructura (interna) de su matriz.

El usuario de la API puede preguntar: ¿Es eso una matriz con una sola dimensión? ¿Se extienden los objetos en todos los niveles de una matriz multidimensional? ¿Cuántos bucles anidados (foreach, etc.) necesito para acceder a todos los objetos? ¿Qué tipo de objetos están "almacenados" en esa matriz?

Como describió, desea usar esa matriz (que contiene objetos) como una matriz unidimensional.

Como lo describe Nishi, puede usar:

/**
 * @return SomeObj[]
 */

para eso.

Pero de nuevo: tenga en cuenta que esta no es una notación estándar de docblock. Esta notación fue introducida por algunos productores de IDE.

Ok, ok, como desarrollador sabes que "[]" está vinculado a una matriz en PHP. Pero, ¿qué significa "algo []" en el contexto normal de PHP? "[]" significa: crear un nuevo elemento dentro de "algo". El nuevo elemento podría ser todo. Pero lo que quiere expresar es: una matriz de objetos con el mismo tipo y su tipo exacto. Como puede ver, el productor de IDE presenta un nuevo contexto. Un nuevo contexto que tenías que aprender. Un nuevo contexto que otros desarrolladores de PHP tuvieron que aprender (para comprender sus bloques de documentos). Mal estilo (!).

Debido a que su matriz tiene una dimensión, tal vez quiera llamar a esa "matriz de objetos" una "lista". Tenga en cuenta que "lista" tiene un significado muy especial en otros lenguajes de programación. Sería mejor llamarlo "colección", por ejemplo.

Recuerde: utiliza un lenguaje de programación que le permite todas las opciones de OOP. Use una clase en lugar de una matriz y haga que su clase sea transitable como una matriz. P.ej:

class orderCollection implements ArrayIterator

O si desea almacenar los objetos internos en diferentes niveles dentro de una estructura de objeto / matriz multidimensional:

class orderCollection implements RecursiveArrayIterator

Esta solución reemplaza su matriz por un objeto de tipo "orderCollection", pero hasta ahora no habilita la finalización de código dentro de su IDE. Bueno. Próximo paso:

Implemente los métodos introducidos por la interfaz con docblocks, en particular:

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

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

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

No olvide usar sugerencias de tipo para:

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

Esta solución deja de introducir muchos:

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

en todos sus archivos de código (por ejemplo, dentro de bucles), como lo confirmó Zahymaka con su respuesta. Su usuario de API no está obligado a introducir ese bloque de documentos para completar el código. Tener @return en un solo lugar reduce la redundancia (@var) lo más posible. Espolvoree "docBlocks con @var" haría que su código sea peor legible.

Finalmente has terminado. ¿Parece difícil de lograr? Parece que tomar un mazo para romper una nuez? No realmente, ya que está familiarizado con esas interfaces y con el código limpio. Recuerde: su código fuente se escribe una vez / lee muchos.

Si la finalización del código de su IDE no funciona con este enfoque, cambie a uno mejor (por ejemplo, IntelliJ IDEA, PhpStorm, Netbeans) o presente una solicitud de función en el rastreador de problemas de su productor de IDE.

Gracias a Christian Weiss (de Alemania) por ser mi entrenador y por enseñarme cosas tan maravillosas. PD: Encuéntrate conmigo y con él en XING.

DanielaWaranie
fuente
esto parece la forma "correcta", pero no puedo hacer que funcione con Netbeans. Hice
fehrlich
2
Quizás en 2012 esto "no era un estándar", pero ahora se describe como una funcionalidad integrada de phpDoc.
Wirone
@Wirone parece que phpDocumentor agrega esto a su manual como reacción a los productores de ide. Incluso si tiene un amplio soporte de herramientas, no significa que sea la mejor práctica. Comienza a difundir SomeObj [] en más y más proyectos, similar a require, require_once, include e include_once que hace años. Con la carga automática, la aparición de esas declaraciones cae por debajo del 5%. Esperemos que SomeObj [] caiga a la misma tasa en los próximos 2 años a favor del enfoque anterior.
DanielaWaranie
1
No entiendo porque Esta es una notación muy simple y clara. Cuando vea SomeObj[]que sabe que es una matriz bidimensional de SomeObjinstancias y luego sabrá qué hacer con ella. No creo que no siga el credo de "código limpio".
Wirone
Esta debería ser la respuesta. Sin embargo, no todo el enfoque de soporte IDE es @return <className>para current()y para todos los chicos. PhpStorm es compatible, así que me ayudó mucho. ¡Gracias amigo!
Pavel
5

Uso array[type]en Zend Studio.

En Zend Studio, array[MyClass]o array[int]incluso array[array[MyClass]]un gran trabajo.

Erick Robertson
fuente
5

En NetBeans 7.0 (también puede ser inferior), podría declarar el tipo de retorno "matriz con objetos de texto" tal como @return Textlo haría y la sugerencia de código funcionará:

Editar: actualizado el ejemplo con la sugerencia de @Bob Fanger

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

y solo úsalo:

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

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

No es perfecto, pero es mejor dejarlo simplemente "mezclado", lo que no aporta ningún valor.

CONTRAS es que puedes pisar la matriz como objeto de texto que arrojará errores.

d.raev
fuente
1
Yo uso "@return array | Prueba alguna descripción". lo que desencadena el mismo comportamiento pero es un poco más explicativo.
Bob Fanger
1
Esta es una solución , no una solución. Lo que está diciendo aquí es "esta función puede devolver un objeto de tipo 'Prueba', O una matriz". Sin embargo, técnicamente no le dice nada sobre lo que podría estar en la matriz.
Byson
5

Como DanielaWaranie menciona en su respuesta - hay una forma de especificar el tipo de $ item cuando iterar sobre los elementos en $ $ collectionObject: Añadir @return MyEntitiesClassNamea current()y el resto de la Iteratory ArrayAccess-Métodos que los valores de retorno.

¡Auge! No hay necesidad de /** @var SomeObj[] $collectionObj */más foreach, y trabaja bien con objeto de colección, no hay necesidad de recogida de retorno con un método específico descrito como @return SomeObj[].

Sospecho que no todos los IDE lo admiten, pero funciona perfectamente bien en PhpStorm, lo que me hace más feliz.

Ejemplo:

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
}

Qué útil iba a agregar publicando esta respuesta

En mi caso current()y el resto de los interfacemétodos se implementan en la Abstractclase de colección y no sé qué tipo de entidades se almacenarán eventualmente en la colección.

Así que aquí está el truco: no especifique el tipo de retorno en la clase abstracta, en su lugar use la instrucción PhpDoc @methoden la descripción de la clase de colección específica.

Ejemplo:

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

}

Ahora, uso de clases:

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

Una vez más: sospecho que no todos los IDE lo admiten, pero PhpStorm sí. ¡Prueba el tuyo, publica en comentarios los resultados!

Pavel
fuente
Vale por llevarlo tan lejos, pero desafortunadamente todavía puedo resolverme para especializar una colección para reemplazar los buenos y viejos tipos genéricos de Java ... ¡qué asco!
Sebas
Gracias. ¿Cómo puedes escribir un método estático?
Yevgeniy Afanasyev
3

Sé que llego tarde a la fiesta, pero he estado trabajando en este problema recientemente. Espero que alguien vea esto porque la respuesta aceptada, aunque correcta, no es la mejor manera de hacerlo. No en PHPStorm al menos, sin embargo, no he probado NetBeans.

La mejor manera implica extender la clase ArrayIterator en lugar de usar tipos de matriz nativos. Esto le permite escribir una sugerencia a nivel de clase en lugar de a nivel de instancia, lo que significa que solo tiene que usar PHPDoc una vez, no en todo el código (que no solo es desordenado y viola DRY, sino que también puede ser problemático cuando se trata de refactorización: PHPStorm tiene la costumbre de perder PHPDoc cuando se refactoriza)

Ver código a continuación:

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 clave aquí es el PHPDoc que @method MyObj current()anula el tipo de retorno heredado de ArrayIterator (que es mixed). La inclusión de este PHPDoc significa que cuando iteramos sobre las propiedades de la clase usando foreach($this as $myObj), obtenemos la finalización del código al referirnos a la variable$myObj->...

Para mí, esta es la mejor manera de lograr esto (al menos hasta que PHP introduzca las matrices con tipo, si alguna vez lo hacen), ya que estamos declarando el tipo de iterador en la clase iterable, no en instancias de la clase dispersa en todo el código.

No he mostrado aquí la solución completa para extender ArrayIterator, por lo que si usa esta técnica, es posible que también desee:

  • Incluya otros PHPDoc a nivel de clase según sea necesario, para métodos como offsetGet($index)ynext()
  • Mueva la verificación de cordura is_a($object, MyObj::class)del constructor a un método privado
  • Llame a esta verificación de cordura (ahora privada) desde las anulaciones de métodos como offsetSet($index, $newval)yappend($value)
e_i_pi
fuente
Muy buena y limpia solución! :)
Marko Šutija
2

El problema es que @varpuede denotar un solo tipo: no contiene una fórmula compleja. Si tenía una sintaxis para "matriz de Foo", ¿por qué detenerse allí y no agregar una sintaxis para "matriz de matriz, que contiene 2 Foo y tres barras"? Entiendo que una lista de elementos es quizás más genérica que eso, pero es una pendiente resbaladiza.

Personalmente, algunas veces he usado @var Foo[]para significar "una variedad de Foo", pero no es compatible con IDE.

troelskn
fuente
55
Una de las cosas que me encantan de C / C ++ es que en realidad realiza un seguimiento de los tipos hasta este nivel. Esa sería una pendiente muy agradable para deslizarse.
Brilliand
2
Con el apoyo de Netbeans 7.2 (al menos esa es la que use la versión), pero con un poco ajustment a saber: /* @var $foo Foo[] */. Acabo de escribir una respuesta a continuación al respecto. Esto también se puede usar dentro de los foreach(){}bucles
Highmastdon el
1
<?php foreach($this->models as /** @var Model_Object_WheelModel */ $model): ?>
    <?php
    // Type hinting now works:
    $model->getImage();
    ?>
<?php endforeach; ?>
Scott Hovestadt
fuente
55
¿Qué IDEs respaldan esto?
philfreo
21
Esto es muy feo. Diga adiós al código limpio cuando comience a programar de esta manera.
halfpastfour.am
En cambio, mire mi respuesta con la definición del contenido de la matriz: stackoverflow.com/a/14110784/431967
Highmastdon
-5

¡He encontrado algo que funciona, puede salvar vidas!

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
fuente
11
El único problema es que introduce código adicional para ser ejecutado, que es utilizado únicamente por su IDE. En su lugar, es mucho mejor definir sugerencias de tipo dentro de los comentarios.
Ben Rowe
1
Wow, esto funciona muy bien. Terminarías con un código adicional, pero parece ser inofensivo. Voy a comenzar a hacer: $ x instanceof Y; // tipografía
Igor Nadj
3
Cambie a un IDE que le proporcione la finalización del código en función de bloques de documentos o inspecciones. Si no desea cambiar su archivo IDE, solicite una función en el rastreador de problemas de su IDE.
DanielaWaranie
1
Si esto arroja una excepción si el tipo no es correcto, puede ser útil para la verificación de tipos en tiempo de ejecución. Si ...
lilbyrdie