Dica do tipo PHPDoc para array de objetos?

417

Portanto, no PHPDoc, pode-se especificar @varacima da declaração da variável membro para sugerir seu tipo. Então um IDE, por exemplo. O PHPEd saberá com que tipo de objeto está trabalhando e poderá fornecer uma visão do código para essa variável.

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

Isso funciona muito bem até que eu precise fazer o mesmo com uma matriz de objetos para poder obter uma dica adequada quando eu percorrer esses objetos posteriormente.

Então, existe uma maneira de declarar uma tag PHPDoc para especificar que a variável membro é uma matriz de SomeObjs? @vararray não é suficiente e @var array(SomeObj)não parece ser válido, por exemplo.

Artem Russakovskii
fonte
2
Há alguma referência neste Netbeans blogue 6,8 dev que o IDE é agora bastante inteligente para deduzir o tipo de membros da matriz: blogs.sun.com/netbeansphp/entry/php_templates_improved
John Carter
3
@therefromhere: seu link está quebrado. Eu acho que o novo URL é: blogs.oracle.com/netbeansphp/entry/php_templates_improved
DanielaWaranie
Para pessoas como eu, passando e procurando uma resposta: se você usa o PHPStorm, veja a resposta mais votada: ela tem uma dica específica! stackoverflow.com/a/1763425/1356098 (isso não significa que ele deve ser a resposta para o OP, já que ele está pedindo PHPEd, no exemplo)
Erenor Paz

Respostas:

337

Usar:

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

ao digitar variáveis ​​em linha e

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

para propriedades de classe.

Resposta anterior de '09 quando PHPDoc (e IDEs como Zend Studio e Netbeans) não tinham essa opção:

O melhor que você pode fazer é dizer:

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

Eu faço muito isso no Zend Studio. Não conheço outros editores, mas deve funcionar.

Zahymaka
fonte
3
Isso faz sentido, mas não funcionou para o PHPEd 5.2. A única coisa que consegui dar certo foi o foreach ($ Objs como / ** @var Test * / $ Obj), o que é terrivelmente feio. :(
Artem Russakovskii
14
Nota no Netbeans 7, parece ser importante que você tenha apenas um asterisco - /** @var $Obj Test */não funciona.
contrebis 13/06
3
@contrebis: o "@var" é uma tag docblock válida. Portanto, mesmo que seu IDE não o suporte dentro de um bloco de documentos "/ ** ... /" e suporte "@var" apenas em "/ ... * /" - por favor, não altere seu bloco de documentos correto. Arquive um problema no rastreador de erros do seu IDE para torná-lo compatível com os padrões. Imagine que sua equipe de desenvolvimento / desenvolvedores externos / comunidade use IDEs diferentes. Mantenha-o como está e esteja preparado para o futuro.
DanielaWaranie
181
Certifique-se de olhar abaixo! Eu quase não rolei para baixo - teria sido um GRANDE ERRO !!! Muitos IDEs suportam melhor sintaxe! (dica: @var Object[] $objectsdiz que "$ objetos" é uma matriz de instâncias de Object.)
Thom Porter
10
/** @var TYPE $variable_name */é a sintaxe correta; não inverta a ordem do tipo e nome da variável (conforme sugerido anteriormente nos comentários), pois isso não funcionará em todos os casos.
Srcpider
893

No PhpStorm IDE do JetBrains, você pode usar /** @var SomeObj[] */, por exemplo:

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

A documentação do phpdoc recomenda este método:

especificada contendo um único tipo, a definição de Tipo informa o leitor sobre o tipo de cada elemento da matriz. Somente um tipo é esperado como elemento para uma determinada matriz.

Exemplo: @return int[]

Nishi
fonte
10
Acabei de baixar e tenho usado o phpstorm na semana passada. Supera o Parreira do Aptana (o que é ótimo por ser gratuito). Era exatamente isso que eu estava procurando. Na verdade, é a mesma maneira que você faria para JavaScript, eu devia ter adivinhado
Juan Mendes
3
Valeu cara! Era exatamente isso que eu estava procurando. PHPStorm é fantástico.
Erik Schierboom
5
Isso não funciona no Netbeans, estou desapontado. Jetbrains fazem algumas ferramentas muito legais.
Keyo 30/08/11
10
@fishbone @Keyo isso funciona no Netbeans agora (no 7.1 todas as noites, pelo menos, talvez mais cedo), embora pareça que você precise usar uma variável temporária (um bug?). Sugestões para foreach(getSomeObjects() as $obj)não funcionam, mas funcionam para:$objs = getSomeObjects(); foreach($objs as $obj)
John Carter
2
Seria bom ter @var Obj[string]para matrizes associativas.
Donquixote
59

Dicas do Netbeans:

Você obtém a conclusão do código para $users[0]->e $this->para uma matriz de classes de usuário.

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

Você também pode ver o tipo da matriz em uma lista de membros da classe ao concluir a $this->...

user1491819
fonte
4
também funciona no PhpStorm 9 EAP: / ** * @var UserInterface [] * / var $ users = []; // Matriz de Objs implementando uma interface
Ronan
Eu tentei no NetBeans IDE 8.0.2, mas recebo sugestões da classe em que estou atualmente.
Wojciech Jasiński
também trabalha no Eclipse 4.6.3 (idk o suporte versão foi introduzido, mas o seu trabalho, e seu o que eu estou usando agora)
hanshenrik
Infelizmente, isso não funciona após o uso de array_pop()funções similares por algum motivo. Parece que o Netbeans não realiza essas funções retornam um único elemento da matriz de entrada.
William W
29

Para especificar uma variável é uma matriz de objetos:

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

Isso funciona no Netbeans 7.2 (estou usando)

Trabalha também com:

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

Portanto, o uso da declaração dentro do foreachnão é necessário.

Highmastdon
fonte
2
Esta solução é mais limpa do que a resposta aceita, na minha opinião, porque você pode usá-lo várias vezes e a dica de tipo continuará a trabalhar com uma nova /* @var $Obj Test */anotação a cada vez.
Henry
Eu vejo dois problemas aqui: 1. O phpdoc adequado começa com /** 2. O formato correto é@var <data-type> <variable-name>
Christian
@ Cristão 1: a questão principal não é phpdoc, mas digitando 2: o formato correto não é o que você diz, mesmo de acordo com outras respostas. De fato, vejo dois problemas com o seu comentário e estou me perguntando por que você faz sua própria resposta com o formato correto
Highmastdon
1. Typehinting funciona com phpdoc ... se você não usar o docblock, seu IDE não tentará adivinhar o que você escreveu em algum comentário aleatório. 2. O formato correto, como algumas outras respostas também disseram, é o que eu especifiquei acima; tipo de dados antes do nome da variável . 3. Não escrevi outra resposta, porque a pergunta não precisa de outra e prefiro não apenas editar seu código.
Christian
24

PSR-5: O PHPDoc propõe uma forma de notação no estilo genérico.

Sintaxe

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

Valores em uma coleção podem até ser outra matriz e até outra coleção.

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

Exemplos

<?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: Se você espera que um IDE faça assistência ao código, é outra pergunta sobre se o IDE suporta a notação de coleções no estilo genérico PHPDoc.

Da minha resposta a esta pergunta .

Gerard Roche
fonte
A notação genérica foi removida do PSR-5
zored
11

Prefiro ler e escrever código limpo - conforme descrito em "Código Limpo", de Robert C. Martin. Ao seguir o seu credo, você não deve exigir que o desenvolvedor (como usuário da sua API) conheça a estrutura (interna) da sua matriz.

O usuário da API pode perguntar: Isso é uma matriz com apenas uma dimensão? Os objetos estão espalhados em todos os níveis de uma matriz multidimensional? Quantos loops aninhados (foreach, etc.) eu preciso acessar todos os objetos? Que tipo de objetos são "armazenados" nessa matriz?

Como você descreveu, deseja usar essa matriz (que contém objetos) como uma matriz unidimensional.

Conforme descrito por Nishi, você pode usar:

/**
 * @return SomeObj[]
 */

por isso.

Mais uma vez: esteja ciente - essa não é uma notação padrão de bloqueio de documentos. Essa notação foi introduzida por alguns produtores de IDE.

Ok, ok, como desenvolvedor, você sabe que "[]" está vinculado a uma matriz em PHP. Mas o que um "algo []" significa no contexto normal do PHP? "[]" significa: crie um novo elemento dentro de "algo". O novo elemento pode ser tudo. Mas o que você deseja expressar é: matriz de objetos do mesmo tipo e do tipo exato. Como você pode ver, o produtor do IDE introduz um novo contexto. Um novo contexto que você teve que aprender. Um novo contexto que outros desenvolvedores de PHP tiveram que aprender (para entender seus docblocks). Estilo ruim (!).

Como sua matriz possui uma dimensão, talvez você queira chamar essa "matriz de objetos" de "lista". Esteja ciente de que "lista" tem um significado muito especial em outras linguagens de programação. Seria melhor chamar isso de "coleção", por exemplo.

Lembre-se: você usa uma linguagem de programação que permite todas as opções de OOP. Use uma classe em vez de uma matriz e torne sua classe percorrível como uma matriz. Por exemplo:

class orderCollection implements ArrayIterator

Ou se você deseja armazenar os objetos internos em diferentes níveis em uma estrutura de matriz / objeto multidimensional:

class orderCollection implements RecursiveArrayIterator

Esta solução substitui sua matriz por um objeto do tipo "orderCollection", mas não habilita a conclusão de código no seu IDE até o momento. OK. Próxima Etapa:

Implemente os métodos introduzidos pela interface com docblocks - em particular:

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

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

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

Não se esqueça de usar dicas de tipo para:

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

Esta solução para de introduzir muitos:

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

em todos os seus arquivos de código (por exemplo, dentro de loops), como Zahymaka confirmou com sua resposta. O usuário da API não é obrigado a introduzir os docblocks, a ter o código completo. Ter @return em apenas um local reduz a redundância (@var) o mais mutch possível. Polvilhe "docBlocks com @var" para tornar seu código mais legível.

Finalmente você está pronto. Parece difícil de conseguir? Parece tomar uma marreta para quebrar uma noz? Não muito, desde que você esteja familiarizado com essas interfaces e com o código limpo. Lembre-se: seu código-fonte é escrito uma vez / é lido muitos.

Se a conclusão do código do seu IDE não funcionar com essa abordagem, mude para uma melhor (por exemplo, IntelliJ IDEA, PhpStorm, Netbeans) ou envie uma solicitação de recurso no rastreador de problemas do seu produtor de IDE.

Agradeço a Christian Weiss (da Alemanha) por ser meu treinador e por me ensinar coisas excelentes. PS: Encontre eu e ele no XING.

DanielaWaranie
fonte
isso parece o caminho "certo", mas não consigo fazê-lo funcionar com o Netbeans. Criamos
fehrlich 11/11
2
Talvez em 2012 isso "não fosse um padrão", mas agora é descrito como funcionalidade interna do phpDoc.
Wirone
@Wirone parece que o phpDocumentor adiciona isso ao seu manual como uma reação aos produtores de ide. Mesmo se você tiver um amplo suporte de ferramentas, isso não significa que seja uma prática recomendada. Começa a espalhar SomeObj [] em mais e mais projetos, semelhante a exigir, require_once, include e include_once, anos atrás. Com o carregamento automático, a aparência dessas instruções cai abaixo de 5%. Esperamos que SomeObj [] caia para a mesma taxa nos próximos 2 anos em favor da abordagem acima.
DanielaWaranie
1
Eu não entendo porque? Esta é uma notação muito simples e clara. Quando você vê SomeObj[]que é uma matriz bidimensional de SomeObjinstâncias e sabe o que fazer com ela. Eu não acho que ele não segue o credo "código limpo".
Wirone 29/07
Essa deve ser a resposta. Porém, nem todos os IDE oferecem suporte a @return <className>for current()e all guys. O PhpStorm suporta, por isso me ajudou muito. Obrigado companheiro!
Pavel
5

Use array[type]no Zend Studio.

Em Zend Studio, array[MyClass]ou array[int]ou mesmo array[array[MyClass]]um grande trabalho.

Erick Robertson
fonte
5

No NetBeans 7.0 (também pode ser menor), você pode declarar o tipo de retorno "matriz com objetos de texto", assim como @return Texta dica de código funcionará:

Edit: atualizou o exemplo com a sugestão do @Bob Fanger

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

e apenas use-o:

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

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

Não é perfeito, mas é melhor apenas deixá-lo "misturado", o que não traz valor.

CONS é que você pode pisar a matriz como objeto de texto, o que gerará erros.

d.raev
fonte
1
Eu uso "@return array | Testar alguma descrição." que desencadeia o mesmo comportamento, mas é um pouco mais explicativo.
precisa
1
Esta é uma solução alternativa , não uma solução. O que você está dizendo aqui é "essa função pode retornar um objeto do tipo 'Teste', OU uma matriz". No entanto, tecnicamente, não informa nada sobre o que pode estar na matriz.
Byson
5

Como DanielaWaranie mencionou em sua resposta - existe uma maneira de especificar o tipo de $ item ao iterar mais de $ itens em $ collectionObject: adicionar e @return MyEntitiesClassNameao current()restante de IteratoreArrayAccess -Métodos que valores de retorno.

Estrondo! Não há necessidade de /** @var SomeObj[] $collectionObj */terminar foreache funciona corretamente com o objeto de coleção, não há necessidade de retornar a coleção com o método específico descrito como @return SomeObj[].

Eu suspeito que nem todos os IDE o suportam, mas ele funciona perfeitamente no PhpStorm, o que me deixa mais feliz.

Exemplo:

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
}

Que utilidade eu adicionaria ao postar esta resposta

No meu caso, o current()restante dos interfacemétodos é implementado na Abstractclasse -collection e não sei que tipo de entidades serão armazenadas na coleção.

Então, aqui está o truque: não especifique o tipo de retorno na classe abstrata, em vez disso, use a instrução PhpDoc @methodna descrição de uma classe de coleção específica.

Exemplo:

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

}

Agora, uso de 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();
}

Mais uma vez: suspeito que nem todos os IDE suportam, mas o PhpStorm suporta. Experimente o seu, postar nos comentários os resultados!

Pavel
fonte
Voucher para empurrá-lo tão longe, mas infelizmente eu ainda pode resolver-me a especializar-se uma coleção para substituir boa Old Java tipos genéricos .... eca'
Sebas
Obrigado. Como você pode digitar um método estático?
Yevgeniy Afanasyev
3

Sei que estou atrasado para a festa, mas venho trabalhando neste problema recentemente. Espero que alguém veja isso porque a resposta aceita, embora correta, não é a melhor maneira de fazer isso. Não no PHPStorm, pelo menos, ainda não testei o NetBeans.

A melhor maneira envolve estender a classe ArrayIterator em vez de usar tipos de matriz nativos. Isso permite que você digite dica no nível da classe e não no nível da instância, o que significa que você precisa apenas do PHPDoc uma vez, não em todo o seu código (que não é apenas confuso e viola o DRY, mas também pode ser problemático quando se trata de refatoração - PHPStorm tem o hábito de perder o PHPDoc ao refatorar)

Veja o código abaixo:

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

A chave aqui é o PHPDoc @method MyObj current()substituindo o tipo de retorno herdado de ArrayIterator (que é mixed). A inclusão desse PHPDoc significa que, quando iteramos sobre as propriedades da classe usandoforeach($this as $myObj) , obtemos a conclusão do código quando nos referimos à variável$myObj->...

Para mim, esta é a maneira mais clara de conseguir isso (pelo menos até o PHP apresentar as matrizes digitadas, se é que o fazem), como declaramos o tipo de iterador na classe iterável, não nas instâncias da classe espalhadas pelo código.

Não mostrei aqui a solução completa para estender o ArrayIterator; portanto, se você usar esta técnica, também poderá:

  • Inclua outro PHPDoc em nível de classe, conforme necessário, para métodos como offsetGet($index)enext()
  • Mover a verificação de sanidade is_a($object, MyObj::class) do construtor para um método privado
  • Chame isso de verificação de integridade (agora privada) a partir de substituições de métodos como offsetSet($index, $newval)eappend($value)
e_i_pi
fonte
Solução muito agradável e limpa! :)
Marko Šutija
2

O problema é que @varpode apenas indicar um único tipo - Não contém uma fórmula complexa. Se você tinha uma sintaxe para "array of Foo", por que parar por aí e não adicionar uma sintaxe para "array of array, que contém 2 Foo's e três Bar's"? Entendo que uma lista de elementos talvez seja mais genérica que isso, mas é uma ladeira escorregadia.

Pessoalmente, usei algumas vezes @var Foo[]para significar "uma matriz de Foo's", mas não é suportada pelos IDE.

Troelskn
fonte
5
Uma das coisas que eu amo no C / C ++ é que ele realmente rastreia os tipos até esse nível. Seria uma inclinação muito agradável para descer.
Brilliand
2
É suportado pelo Netbeans 7.2 (pelo menos é o uso versão I), mas com um pouco ajustment a saber: /* @var $foo Foo[] */. Acabei de escrever uma resposta abaixo sobre isso. Isso também pode ser usado dentro de foreach(){}laços
Highmastdon
1
<?php foreach($this->models as /** @var Model_Object_WheelModel */ $model): ?>
    <?php
    // Type hinting now works:
    $model->getImage();
    ?>
<?php endforeach; ?>
Scott Hovestadt
fonte
5
quais IDEs apóiam isso?
philfreo 27/06
21
Isso é muito feio. Diga adeus ao código limpo quando começar a programar dessa maneira.
Halfpastfour.am
Sim olhar para a minha resposta com a definição do conteúdo do array: stackoverflow.com/a/14110784/431967
Highmastdon
-5

Eu encontrei algo que está funcionando, ele pode 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
fonte
11
O único problema é que introduz código adicional a ser executado, que é puramente usado apenas pelo seu IDE. É muito melhor definir dicas de tipo nos comentários.
Ben Rowe
1
Uau, isso funciona muito bem. Você terminaria com código adicional, mas parece inofensivo. Vou começar a fazer: $ x instanceof Y; // typehint
Igor Nadj
3
Alterne para um IDE que fornece a conclusão do código com base em blocos de documentos ou inspeções. Se você não deseja alternar seu arquivo IDE, solicite um recurso no rastreador de problemas do seu IDE.
DanielaWaranie
1
Se isso gerar uma exceção se o tipo não estiver correto, poderá ser útil para a verificação do tipo de tempo de execução. Se ...
lilbyrdie