phpdocblocks

Is it possible to annotate a generic container class in a docblock


Say I have a class Book which has a property on it called $chapters. In the implementation I'm working with, this property is represented by a generic helper class called Collection, which in turn contains an array of Chapter classes.

Example:


class Book {
  /** @var Collection */
  public $chapters;
}

class Collection {

  private $items;

  public function getItems(): array {
    return $this->items;
  }
}

class Chapter {}

Is it possible to annotate the $chapters property in Book so that my IDE knows that it is a Collection object, but that a call to that collection's getItems() method will return an array of Chapter instances?

Can I do this without creating a child class of Collection and annotating that?

EDIT: I don't think I was clear in my goal. I'm looking to type hint a class which is outside of the Book class and give guidance on what it's $items property would be — something like this (which I'm sure is invalid):

class Book {
  /**
   * @var Collection { 
   *   @property Chapter[] $items
   * }
   */
  public $chapters;
}

Solution

  • For anyone who is still looking how to solve this issue, here is the solution based on Psalm's article "Templating".

    Generics (templating)

    If you you are working with variable data type inside a class (or even function), you can use what is called generic data types (in PHP this might be called templating). You can use generic data types for situations, when you need to write a piece of code, which doesn't really care of the type it uses, but it might be important for the client/user of the code. In other words: you want let the user of your code (Collection) specify the type inside your structure without modifying the structure directly. The best example (which is conveniently the anwswer itself) are collections.

    Collections don't really need to know about what type of data they hold. But for user such as yourself this information is important. For this reason we can create generic data type inside your Collection and everytime someone wants to use the class they can specify through the generic type the type they want the Collection to hold.

    /**
     * @template T
     */
    class Collection
    {
        /**
         * @var T[]
         */
        private $items;
    
    
        /**
         * @return T[]
         */
        public function getItems(): array
        {
            return $this->items;
        }
    }
    

    Here we defined our generic type called T, which can be specified by the user of Collection such as follows:

    class Book
    {
        /**
         * @var Collection<Chapter>
         */
        public $chapters;
    }
    

    This way our IDEs (or static analyzers) will recognize the generic data type inside Collection as Chapter (i.e. the T type becomes Book).

    Disclaimer

    Although this is the way to write generic types in PHP (at least for now), in my experience many IDEs struggle to resolve the generic type. But luckily analyzers such as PHPStan knows how to work with generics.