[php] PHPDoc type hinting for array of objects?

So, in PHPDoc one can specify @var above the member variable declaration to hint at its type. Then an IDE, for ex. PHPEd, will know what type of object it's working with and will be able to provide a code insight for that variable.

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

This works great until I need to do the same to an array of objects to be able to get a proper hint when I iterate through those objects later on.

So, is there a way to declare a PHPDoc tag to specify that the member variable is an array of SomeObjs? @var array is not enough, and @var array(SomeObj) doesn't seem to be valid, for example.

This question is related to php ide phpdoc var hint

The answer is


In the PhpStorm IDE from JetBrains, you can use /** @var SomeObj[] */, e.g.:

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

The phpdoc documentation recommends this method:

specified containing a single type, the Type definition informs the reader of the type of each array element. Only one Type is then expected as element for a given array.

Example: @return int[]


As DanielaWaranie mentioned in her answer - there is a way to specify the type of $item when you iterating over $items in $collectionObject: Add @return MyEntitiesClassName to current() and rest of the Iterator and ArrayAccess-methods which return values.

Boom! No need in /** @var SomeObj[] $collectionObj */ over foreach, and works right with collection object, no need to return collection with specific method described as @return SomeObj[].

I suspect not all IDE support it but it works perfectly fine in PhpStorm, which makes me happier.

Example:

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
}

What useful i was going to add posting this answer

In my case current() and rest of interface-methods are implemented in Abstract-collection class and I do not know what kind of entities will eventually be stored in collection.

So here is the trick: Do not specify return type in abstract class, instead use PhpDoc instuction @method in description of specific collection class.

Example:

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

}

Now, usage of 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();
}

Once again: I suspect not all IDE support it, but PhpStorm does. Try yours, post in comment the results!


I've found something which is working, it can save lives !

private $userList = array();
$userList = User::fetchAll(); // now $userList is an array of User objects
foreach ($userList as $user) {
   $user instanceof User;
   echo $user->getName();
}

Use array[type] in Zend Studio.

In Zend Studio, array[MyClass] or array[int] or even array[array[MyClass]] work great.


I prefer to read and write clean code - as outlined in "Clean Code" by Robert C. Martin. When following his credo you should not require the developer (as user of your API) to know the (internal) structure of your array.

The API user may ask: Is that an array with one dimension only? Are the objects spread around on all levels of a multi dimensional array? How many nested loops (foreach, etc.) do i need to access all objects? What type of objects are "stored" in that array?

As you outlined you want to use that array (which contains objects) as a one dimensional array.

As outlined by Nishi you can use:

/**
 * @return SomeObj[]
 */

for that.

But again: be aware - this is not a standard docblock notation. This notation was introduced by some IDE producers.

Okay, okay, as a developer you know that "[]" is tied to an array in PHP. But what do a "something[]" mean in normal PHP context? "[]" means: create new element within "something". The new element could be everything. But what you want to express is: array of objects with the same type and it´s exact type. As you can see, the IDE producer introduces a new context. A new context you had to learn. A new context other PHP developers had to learn (to understand your docblocks). Bad style (!).

Because your array do have one dimension you maybe want to call that "array of objects" a "list". Be aware that "list" has a very special meaning in other programming languages. It would be mutch better to call it "collection" for example.

Remember: you use a programming language that enables you all options of OOP. Use a class instead of an array and make your class traversable like an array. E.g.:

class orderCollection implements ArrayIterator

Or if you want to store the internal objects on different levels within an multi dimensional array/object structure:

class orderCollection implements RecursiveArrayIterator

This solution replaces your array by an object of type "orderCollection", but do not enable code completion within your IDE so far. Okay. Next step:

Implement the methods that are introduced by the interface with docblocks - particular:

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

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

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

Do not forget to use type hinting for:

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

This solution stops introducing a lot of:

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

all over your code files (e.g. within loops), as Zahymaka confirmed with her/his answer. Your API user is not forced to introduce that docblocks, to have code completion. To have @return on only one place reduces the redundancy (@var) as mutch as possible. Sprinkle "docBlocks with @var" would make your code worst readable.

Finaly you are done. Looks hard to achive? Looks like taking a sledgehammer to crack a nut? Not realy, since you are familiar with that interfaces and with clean code. Remember: your source code is written once / read many.

If code completion of your IDE do not work with this approach, switch to a better one (e.g. IntelliJ IDEA, PhpStorm, Netbeans) or file a feature request on the issue tracker of your IDE producer.

Thanks to Christian Weiss (from Germany) for being my trainer and for teaching me such a great stuff. PS: Meet me and him on XING.


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

To specify a variable is an array of objects:

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

This works in Netbeans 7.2 (I'm using it)

Works also with:

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

Therefore use of declaration inside the foreach is not necessary.


I know I'm late to the party, but I've been working on this problem recently. I hope someone sees this because the accepted answer, although correct, is not the best way you can do this. Not in PHPStorm at least, I haven't tested NetBeans though.

The best way involves extending the ArrayIterator class rather than using native array types. This allows you to type hint at a class-level rather than at an instance-level, meaning you only have to PHPDoc once, not throughout your code (which is not only messy and violates DRY, but can also be problematic when it comes to refactoring - PHPStorm has a habit of missing PHPDoc when refactoring)

See code below:

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

The key here is the PHPDoc @method MyObj current() overriding the return type inherited from ArrayIterator (which is mixed). The inclusion of this PHPDoc means that when we iterate over the class properties using foreach($this as $myObj), we then get code completion when referring to the variable $myObj->...

To me, this is the neatest way to achieve this (at least until PHP introduces Typed Arrays, if they ever do), as we're declaring the iterator type in the iterable class, not on instances of the class scattered throughout the code.

I haven't shown here the complete solution for extending ArrayIterator, so if you use this technique, you may also want to:

  • Include other class-level PHPDoc as required, for methods such as offsetGet($index) and next()
  • Move the sanity check is_a($object, MyObj::class) from the constructor into a private method
  • Call this (now private) sanity check from method overrides such as offsetSet($index, $newval) and append($value)

The problem is that @var can just denote a single type - Not contain a complex formula. If you had a syntax for "array of Foo", why stop there and not add a syntax for "array of array, that contains 2 Foo's and three Bar's"? I understand that a list of elements is perhaps more generic than that, but it's a slippery slope.

Personally, I have some times used @var Foo[] to signify "an array of Foo's", but it's not supported by IDE's.


Netbeans hints:

You get code completion on $users[0]-> and for $this-> for an array of User classes.

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

You also can see the type of the array in a list of class members when you do completion of $this->...


PSR-5: PHPDoc proposes a form of Generics-style notation.

Syntax

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

Values in a Collection MAY even be another array and even another Collection.

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

Examples

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

Note: If you are expecting an IDE to do code assist then it's another question about if the IDE supports PHPDoc Generic-style collections notation.

From my answer to this question.


In NetBeans 7.0 (may be lower too) you could declare the the return type "array with Text objects " just as @return Text and the code hinting will work:

Edit: updated the example with @Bob Fanger suggestion

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

and just use it:

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

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

It is not perfect but it is better then just to leave it just "mixed", which brings no value.

CONS is you are allowed to tread the array as Text Object which will throw errors.


Examples related to php

I am receiving warning in Facebook Application using PHP SDK Pass PDO prepared statement to variables Parse error: syntax error, unexpected [ Preg_match backtrack error Removing "http://" from a string How do I hide the PHP explode delimiter from submitted form results? Problems with installation of Google App Engine SDK for php in OS X Laravel 4 with Sentry 2 add user to a group on Registration php & mysql query not echoing in html with tags? How do I show a message in the foreach loop?

Examples related to ide

How can I view the Git history in Visual Studio Code? How to ignore a particular directory or file for tslint? How do I completely rename an Xcode project (i.e. inclusive of folders)? Where is the visual studio HTML Designer? How to disable gradle 'offline mode' in android studio? Android studio Error "Unsupported Modules Detected: Compilation is not supported for following modules" Android Studio was unable to find a valid Jvm (Related to MAC OS) QtCreator: No valid kits found Difference between WebStorm and PHPStorm package android.support.v4.app does not exist ; in Android studio 0.8

Examples related to phpdoc

PHP Function Comments PHPDoc type hinting for array of objects?

Examples related to var

how to display a javascript var in html body ReferenceError: variable is not defined Initialize value of 'var' in C# to null What is /var/www/html? How can I write these variables into one line of code in C#? Why should I use var instead of a type? What is the equivalent of the C# 'var' keyword in Java? PHPDoc type hinting for array of objects? What's the difference between using "let" and "var"? What is the scope of variables in JavaScript?

Examples related to hint

Flutter: how to make a TextField with HintText but no Underline? Android EditText view Floating Hint in Material Design OPTION (RECOMPILE) is Always Faster; Why? How do I put hint in a asp:textbox HTML input field hint Android EditText for password with android:hint Java JTextField with input hint Watermark / hint text / placeholder TextBox PHPDoc type hinting for array of objects?