Proper phpdoc comment for iteratable object? - php

I'm having a bit of a problem trying to get a correct autocompletion for the following code example. I'm using PHPStorm 7 on a Win7 machine.
First just a simple class.
/**
* Class myObject
*/
class myObject
{
/**
* some method
*/
public function myMethod()
{
// do something
}
}
This one is the collection class which can contain multiple instances of the prior class and implements the IteratorAggregate interface.
/**
* Class myCollection
*/
class myCollection implements IteratorAggregate
{
/**
* #var myObject[]
*/
protected $_objects = array();
/**
* #param myObject $object
* #return myCollection
*/
public function add(myObject $object)
{
$this->_objects[] = $object;
return $this;
}
/**
* #return ArrayIterator
*/
public function getIterator()
{
return new ArrayIterator($this->_objects);
}
}
And here is the code example.
$collection = new myCollection;
$collection->add(new myObject);
$collection->add(new myObject);
foreach ($collection as $object) {
$object->myMethod(); // gets no autocompletion
}
As you may have guessed (and read in the example) the myMethod() call gets not autocompleted and is beeing listed in the code analysis. The only way i found is adding a comment block for $object, which i find, to be honest, extremely annoying.
/** #var $object myObject */
foreach ($collection as $object) {
$object->myMethod(); // gets autocompletion now, but sucks
}
So, any ideas or fundamented knowledge on how to solve this?

/**
* #return ArrayIterator|myObject[]
*/
public function getIterator()
{
return new ArrayIterator($this->_objects);
}
For extended classes (the base class is above):
/**
* #method myObject[] getIterator()
*/
class ExtendedClass extends BaseCollection
{
}
or
/**
* #method iterable<myObject> getIterator()
*/
class ExtendedClass extends BaseCollection
{
}
I think this will be best way to handle such case. at least it works with PHPStorm

Your
/** #var $object myObject */
block is indeed the correct way to accomplish this. The syntax you are expecting to do the work,
/**
* #var myObject[]
*/
is not standard phpdoc notation, although it is in informal use and has some effort ongoing to standardize. Until such standardization does happen, IDEs recognizing it will probably be hit-or-miss. IDE coverage of your $object local var block is also hit-or-miss, actually.

In your myCollection class, override current() as follows:
/** #return myObject */
public function current() {
return parent::current();
}

Possible workaround (also ugly) is to create static "constructor", that will return myObject. At least it works in eclipse. If you want to see collection methods too, then just add myCollection to return as "#return myObject[]|myCollection"
class myCollection implements \IteratorAggregate
{
/**
* #return myObject[]
*/
public function create()
{
return new static();
}
}

Related

Proper type hinting for IteratorAggregate satisfying Psalm and PhpStorm

I have collections using IteratorAggregate interface. And I can't find a way how to type hint it properly that both Psalm and PhpStorm are satisfied.
Here is a simplified example. It has an AbstractCollection and one Collection, but there are actually multiple collections extending the AbstractCollection.
This example works fine for PhpStorm but Psalm complains about it.
https://psalm.dev/r/9a3fc1db43
I tried several things, but nothing really works. Any ideas how I can get proper type hinting in PhpStorm when iterating the collection, but at the same time have Psalm not complaining?
This should work: https://psalm.dev/r/24c1344df3
<?php
/**
* #template TValue
* #template-implements IteratorAggregate<string, TValue>
*/
abstract class AbstractCollection implements IteratorAggregate
{
/**
* #var array<string, TValue>
*/
protected array $items = [];
/** #return ArrayIterator<string, TValue> */
public function getIterator(): ArrayIterator
{
return new ArrayIterator($this->items);
}
}
/**
* #template-extends AbstractCollection<string>
*/
class Collection extends AbstractCollection
{
public function __construct() {
$this->items = ['foo' => 'bar'];
}
}
foreach (new Collection() as $item) {
echo $item; // PHPStorm should know the type here
}
You missed the docblock on AbstractCollection::getIterator().

PhpStorm metadata file for repository classes

In our application, we use repositories for models that are fetched from the database. So, we have an abstract repository that knows about the database, has a loadById method to load a database record and an abstract getEntity method that creates an object for that specific repository. Example code:
abstract class EntityRepository {
/**
* #param int $id
* #return AbstractEntity
*/
public function loadById($id) {
$record = $this->db->loadById($id);
$entity = $this->getEntity();
return $this->inflate($record, $entity);
}
/**
* #return AbstractEntity
*/
protected abstract function getEntity();
}
class PeopleRepository extends EntityRepository {
protected function getEntity() {
return new PeopleEntity();
}
}
abstract class AbstractEntity {
private $id;
/**
* #return int
*/
public function getId() {
return $this->id;
}
/**
* #param int $id;
*/
public function setId($id) {
$this->id = $id;
}
}
class PeopleEntity extends AbstractEntity {
private $name;
/**
* #return string
*/
public function getName() {
return $this->name;
}
/**
* #param string $name;
*/
public function setName($name) {
$this->name= $name;
}
}
When using an instance of PeopleRepository and fetching a model through loadById, PhpStorm is not able to resolve the returned model to a concrete type, but provides only code completion for the functions of AbstractEntity. Is there any simple way to make it work?
In https://confluence.jetbrains.com/display/PhpStorm/PhpStorm+Advanced+Metadata, I've only found ways to make it work for concrete classes and their functions. So, enumerating all repository classes and all their ways of creating an entity might work. But I'd love to see an abstract way of defining like "All instances of EntityRepository will return an entity of that type defined in getEntity() when loading an entity"
I doubt there's a blanket way of doing this. Even using PHPStorm meta you have to be explicit for each case. Perhaps the way of doing this is by doing something like adding a repository facade e.g.
class RepositoryFacade {
public static function __callStatic($method, $args) {
if ($args[0] == People::class) {
array_shift($args);
return new PeopleRepository()->{$method}(...$args);
}
}
}
Then you might be able to typehint this using:
override(RepositoryFacade::loadById(0), type(0));
Of course the facade is not really the best pattern to be using in general so I can see how this might not be ideal.

PHPDoc: document parent class method that always yields itself or descendant class?

Consider this code:
class ParentClass {
public static function generate($className = __CLASS__){
if(!$className) return new self();
else return new $className();
}
}
class ChildClass extends ParentClass {
/**
* #param string $className
* #return ChildClass
*/
public static function generate($className = __CLASS__) {
return parent::generate($className);
}
}
var_dump($ChildClass::generate()); // object(ChildClass)[1]
ChildClass::generate() returns an instance of ChildClass wherever I use it because I never provide a $className argument. Problem is that my IDE gives me a warning about the parent::generate() call not matching the documented return type:
I would like to make this warning go away by adding documentation to the parent method. I could do:
#return ParentClass | ChildClass
Adding this to the parent method works but that's not practical because there are many dozen children classes, and there could be many more in the future. I have tried both of the following:
#return static
#return $className
but that hasn't made the warning go away. Is there a PHPDoc approved way to indicate that the calling child class will always be returned? Or -- more accurately -- that a class of type $className will be returned? If not, is there a way that works even with just my IDE? (PhpStorm 2017.2)
Update
#gogaz comment below got me to think: it would be enough if a PHPDoc #return could indicate something like self | descendants
You can document parent class method and then just inherit that on a child class method.
class ParentClass
{
/**
* #param string $className
* #return ChildClass
*/
public static function generate($className = __CLASS__){
if(!$className) return new self();
else return new $className();
}
}
class ChildClass extends ParentClass
{
/**
* #inheritdoc
*/
public static function generate($className = __CLASS__) {
return parent::generate($className);
}
}
I hope that this will solve your problem.
UPDATE:
Another way to do this is by using interface that all classes will implement, so you could expect that your generate method will return some class that implements that interface.
/**
* ...
* #return MyCustomInterface
*/
If this doesn't solve your issue... Then, you can set return type as "mixed", this will suppress warning and it won't lie :) because your generate method can return any class you specify via argument...
/**
* ...
* #return mixed
*/
I've run across something similar (PHPStorm is more anal than PHP is on the subject). Unless there's some reason you HAVE to have it be specific to the child class, I would do this
class ParentClass {
/**
* #param string $className
* #return ParentClass
*/
public static function generate($className = __CLASS__){
if(!$className) return new self();
else return new $className();
}
}
Remember, your ChildClass is still an instance of ParentClass

Declare an object as a specific class in php

Is there an opportunity, that will allow it to declare a variable in php as a specific object, so that IDEs like Netbeans can detect that Object and suggest me the possible methods and variable in that class?
What I know, is that it works with function like this example:
public static function add(\Event $element) {
//The $element variable is now declared, that it have to be an Event object
}
In the theory, my question looks like this:
\Event $events = EventContainer::getAll();
But unfortunately that wouldn't work.
In eclipse I usually use this pattern:
class Foo {
/**
* #var $barArray \Baz\Bar[]
*/
protected $barArray;
/**
* #var $bar \Baz\Bar
*/
protected $bar
/**
* #return \Baz\Bar
*/
public function getBar()
{
return $this->bar;
}
/**
* #return \Baz\Bar[]
*/
public function getAllBar()
{
return $this->barArray;
}
}
That way eclipse knows by the javadoc what to use in autocomplete. It might work the same in other editors.

Getting the child-type of an object from a method inherited from the father

my problem is getting the right type of object from a method, which is returning a "mixed" type due to inhreitance.
I've got a generic list class
class List
{
/**
* #var Item[]
*/
protected $items;
public function __construct()
{
$this->items = array();
}
/**
* #return Item[]
*/
public function getAll()
{
return $this->items;
}
/**
* #return Item
*/
public function getOne($index)
{
if (isset($this->items[$index])) {
return $this->items[$index];
}
return null;
}
}
containing element of type Item, which is a generic class either
class Item
{
/**
* #var string
*/
protected $name;
public function __construct($name)
{
$this->name = $name;
}
}
Such generic classes are extended by N different lists. Just an example
class UserList extends List
{
/* user-specific implementation */
}
class User extends Item
{
/* user-specific implementation */
}
In the client code
$user_list = new UserList();
foreach ($user_list->getAll() as $user) {
echo $user->getEmailAddr();
}
Inside the foreach I don't have code completion, because my getAll method (inherited from the father) is returning Item[], or mixed[], not a User[]. Same problem with getOne method.
I wouldn't like to have to override such methods.
Is there a more clever and elegant solution?
Thank you
I don't think there's any way for the IDE to infer the type automatically. Use a phpdoc type annotation:
foreach ($user_list->getAll() as $user) {
/** #var User $user */
echo $user->getEmailAddr();
}
See the related question PHPDoc type hinting for array of objects?

Categories