Skip to content

Instantly share code, notes, and snippets.

@mvriel
Created October 2, 2012 20:14
Show Gist options
  • Save mvriel/3823010 to your computer and use it in GitHub Desktop.
Save mvriel/3823010 to your computer and use it in GitHub Desktop.
Generic notation in PHPDoc vs. plain array notation
<?php
/**
* @template <T> The type of the individual elements
*/
class ArrayCollection implements IteratorAggregate
{
private $elements;
/**
* @param array<T> $elements
*/
public function __construct(array $elements)
{
$this->elements = $elements;
}
/**
* @return Iterator<T>
*/
public function getIterator()
{
return new ArrayIterator($this->elements);
}
}
// usage
/** @type ArrayCollection<Foo> $col */
$col = new ArrayCollection();
foreach ($col as $elem) {
// $elem is instance of Foo here
}
?>
<?php
class ArrayCollection implements IteratorAggregate
{
private $elements;
/**
* @param mixed[] $elements
*/
public function __construct(array $elements)
{
$this->elements = $elements;
}
/**
* @return ArrayIterator
*/
public function getIterator()
{
return new ArrayIterator($this->elements);
}
}
// usage
/** @type ArrayCollection|Foo[] $col */
$col = new ArrayCollection();
foreach ($col as $elem) {
// $elem is instance of Foo here
}
?>
@schmittjoh
Copy link

There are two reasons I can think of why I'd prefer the first alternative:

  1. Having a precise type would help IDEs/analysis tools to provide better assistance, Foo[] suggests that $col is an array; typically, you will have more methods on the ArrayCollection class like first() for example. Calling first on an array could be flagged by such tools.
  2. Using the ArrayCollection<Foo> style you only need to declare the type once. This will become more obvious if you consider that the ArrayCollection likely has more than one method which depends on the type that it contains.
/*
 * with @template, the type only has to be specified once
 */
/** @type ArrayCollection<Foo> */
$col = new ArrayCollection();

foreach ($col as $elem) { /** $elem is Foo */ }
$col->first(); // returns Foo|null
$col->slice(0, 5); // returns array of Foo

/*
 * without @template, a @type annotation is required on each use
 */
$col = new ArrayCollection();

foreach ($col as $elem) {
    /** @type Foo $elem */
}

/** @type Foo|null $a */ 
$a = $col->first();

/** @type Foo[] $subSet */
$subSet = $col->slice(0, 5);

Obviously, the @template annotation is not supported by IDEs yet. After all, it does not exist yet, so this is not suprising :). However, since most IDEs are written in Java (eclipse, netbeans, PHPStorm) and have type inference engines for Java which support this feature, it should be fairly easy for them to add support for such types in PHP as well. Additionally, I guess if we write a good standard that is used by most major libraries, they will be so happy that they do not have to support so many variations anymore, that they will implement this in no time :)

@moufmouf
Copy link

moufmouf commented Oct 9, 2012

Here is a sample that might make more sense regarding Johannes proposal:

Let's have a look at the SplObjectStorage class, that provides a map where keys can be objects. (http://www.php.net/manual/en/class.splobjectstorage.php)

/** @type SplObjectStorage<MyKeyObject, MyValueObject> */
$splObjectStorage = new SplObjectStorage();

// No need to cast $current into a MyKeyObject
$current = $splObjectStorage->current();

// No need to cast $object into a MyValueObject
$object = $splObjectStorage->offsetGet($key);

I like Johannes proposal because I find it very powerful.
However, let's admit that 90% of the time, we are dealing with arrays, not ArrayCollection or SplObjectStorage, so we should be sure that the use of the @type annotation with the array is rock solid.
For this matter, one thing we do not capture is the difference between an indexed array and a hashmap. PHP does not make any difference between those 2 constructs, and yet, they are completely different and the documentation should tell the user whether the array is numerically indexed, or is a map.

A proposal would be to use 2 keys. For instance:

/** @type array<int, Object> A numerically indexed array of Objects */
$arr1 = array();

/** @type array<string, Object> A map of Objects */
$arr2 = array();

Now, this seems to be a bit of an overkill compared to the more simple proposal from Mike...
In an ideal world, we should also be able to write:

/** @type array<Object> An array of Objects (we don't know about the key) */

But that would not suit well with the @template annotation....
So I'm a bit at a loss here... Any idea on how to make this both powerful and simple to use?

@schmittjoh
Copy link

I see no problem with supporting:

@type array<object> a list of objects, we don't care about the type
@type array<string,object> a map of strings to objects

The @template proposals is not directly related to arrays and more about allowing to write generic classes in userland while still getting the best autocompletion/static analysis that is possible, and btw, you could have more than one @template annotation on your class if your class needs to fill in more holes (like key, and value in a map as in your \SplObjectStorage example).

@mvriel
Copy link
Author

mvriel commented Oct 11, 2012

I wanted to post here that I have not abandoned the discussion but I have been at PHPNW12 the past weekend and am still catching up. A proper response follows somewhere in the upcoming week

@ashnazg
Copy link

ashnazg commented Apr 22, 2013

Could these ideas be successfully condensed into just this one syntax:

@type (class name or primitive type 'array')<(optional key type, ) (required member type) (description)

@mvriel
Copy link
Author

mvriel commented Apr 22, 2013

I have been thinking quite a bit about this proposal in the past time and to be honest, it is starting to grow on me. Though I am not so sure about the @template tag since that would only serve a purpose for a Collection object that only intends to be used with a specific set of values (that and I don't think the name @template is intuitive).

Some concerns that I still have:

  • How to deal with generics where the value may be of several types? Use the or operator (|) as has been done so far?
  • We should also support multi-dimensional notations

@Deltachaos
Copy link

I think we sould keep the or operator for working with several types. But what do you mean by "multi-dimensional notations"?

@cmrafifar
Copy link

Coming from a Java perspective, I would love some kind of Generics support in PHP and/or PHPDOC. 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment