PHPDoc container objects to act like arrays - php

Is it possible to indicate that an object behaves like a container, implements \ArrayAccess when writing a doc, specifically, a #return one.
For example if I have a class Collection implements \ArrayAccess, \Countable, \IteratorAggregate object filled with Entity objects from database.
What I'd like to have is PHPStorm (specifically) to have hints available for Collection objects as well as to understand what values they have.
Currently we have this notation for arrays of objects
/**
* #return \Entity[]
*/
This means the method returns an array of Entities. How may I do the same for a different class? I'm thinking about something like this:
/**
* #return \Entity[\Collection]
*/
I have created a pastebin (note that it has links to two more pastebins for Entity and Collection classes).
PASTEBIN example

Based on your pastebin examples single typehint in right place seems to be enough (tested in PhpStorm v9.5 EAP build):
/** #var \Collection|\Entity[] $collection */
$collection = new Collection($entities);
For $collection it offers methods from \Collection class:
For $entity (inside foreach loop) it offers \Entity methods:
If anything you can always type hint $entity variable individually:

What you seem to want cannot really be done (at the moment) as far as I am aware. I've tried to get the same functionality myself some time ago.
However, you can do something like this:
<?php
/**
* #return MyCollectionType
*/
function getEntityCollectionFromSomewhere()
{
return ...
}
$myEntityCollection = getEntityCollectionFromSomewhere();
foreach($myEntityCollection as $entity)
{
if ($entity instanceof MyEntityType)
{
// code completion is active here for MyEntityType...
}
}
This is the best I've found so far; you get Collection completion where you're working with a collection, and entity functionality where you're working with a single element from that collection.

Related

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

What is the return value of getSomethings in a Doctrine / Symfony One-To-Many Relationship?

I like to either type-hint or starting in PHP7 actually show the return value of a getter function. But with One-To-Many relationships in Doctrine / Symfony, I'm still stuck and am not sure what to add to the #var tag.
[...]
/**
* #var string
* #ORM\Column(name="name", type="string")
*/
private $features;
/**
* What goes into var here?
*
* One Product has Many Features.
* #ORM\OneToMany(targetEntity="Feature", mappedBy="product")
*/
private $features;
public function __construct()
{
$this->features = new ArrayCollection();
$this->name = 'New Product Name';
}
/**
* #return Collection
*/
public function getFeatures(): Collection
{
return $this->features;
}
[...]
Currently I’m using #var Collection and can then use the Collection functions. But what would be the »proper« thing to return? Is it indeed Collection? Or is it ArrayCollection? I’m tempted to use Features[] in order to use the functions of Feature, if I need to (instead of typehinting), but it doesn’t feel right.
What would be the »cleanest« / stable way to do this?
If you want to keep the docblock I would use the union type | to both specify the Collection and the list of values it contains like:
/**
* #var Collection|Feature[]
*/
With this your IDE should both find the methods from Collection as well as the Feature-type hints when you get a single object from the collection, e.g. in a foreach.
As to the question of ArrayCollection vs. Collection, it is usually recommended to type hint for the interface (Collection in this case). ArrayCollection offers a few more methods, but unless you really need them I would not bother with the type hint just to get them.
What I tend to do in projects is keep the Collection inside the entity and only pass out an array in the getter like this:
public function getFeatures(): array
{
return $this->features->toArray();
}
public function setFeatures(array $features): void
{
$this->features = new ArrayCollection($features);
}
Be careful, the void return type is not supported in PHP 7.0 yet. The benefit of returning an array is that in your code you don't have to worry about what kind of Collection Doctrine uses. That class is mainly used to maintain reference between objects inside Doctrine's Unit Of Work, so it should not really be part of your concern.

Override PHP docblock/annotation of method without overloading (PhpStorm autocompletion)

This is a question about the autocompletion behavior in PhpStorm (and possibly other IDEs) in conjunction with PHP docblocks.
I have to groups of classes in my application. First there are individual classes for various products (CarProduct, FoodProduct etc.), all inheriting from BaseProduct, and the counterpart for individual contracts (CarContract, FoodContract etc.), all inheriting from BaseContract.
<?php
class BaseContract
{
/** #var BaseProduct */
private $product;
/**
* #return BaseProduct
*/
public function getProduct()
{
return $this->product;
}
}
Now I have an instance of CarContract, and I wanna get some CarProduct specific information:
<?php
/* PhpStorm thinks, this is BaseProduct */
$product = $carContract->getProduct();
/* hence, getSpeed() is not available for PhpStorm */
$product->getSpeed();
The autocompletion is not working as I like. There are two workarounds for this, but both are not nice:
Overload getProduct() in the subclass, just with updated #return docblocks
Add /** #var CarProduct $product */ everywhere, where I access the product of a CarContract
Is there a "usual" way to solve something like this, or are my workarounds the only solutions?
PhpStorm does not really allow/does not support doing something like: have the same named class defined elsewhere and just use it as a reference for overriding definitions of real class. You can do that .. but IDE will warn with "multiple definitions of the same class" and it may introduce some weird behaviour/unexpected warnings...
Here is a ticket that ask for such feature: https://youtrack.jetbrains.com/issue/WI-851 -- watch it (star/vote/comment) to get notified on any progress.
Your options are: you can provide correct type hint locally (to local variable) using #var -- you already know it and that's first that you would think of:
<?php
/** #var \CarProduct $product */
$product = $carContract->getProduct();
$product->getSpeed();
Another possible way: instead of overriding actual method .. you can try doing the same but with #method PHPDoc -- will work with your code:
<?php
/**
* My Car Product class
*
* #method \CarProduct getProduct() Bla-bla optional description
*/
class CarContract extends BaseContract ...

PHP dynamic return type hinting

Suppose I have the following PHP function:
/**
* #param string $className
* #param array $parameters
* #return mixed
*/
function getFirstObject($className, $parameters) {
// This uses a Doctrine DQl builder, but it could easily replaced
// by something else. The point is, that this function can return
// instances of many different classes, that do not necessarily
// have common signatures.
$builder = createQueryBuilder()
->select('obj')
->from($className, 'obj');
addParamClausesToBuilder($builder, $parameters, 'obj');
$objects = $builder
->getQuery()
->getResult();
return empty($objects) ? null : array_pop($objects);
}
Basically, the function always returns either an instance of the class specified with the $className parameter or null, if something went wrong. The only catch is, that I do not know the full list of classes this function can return. (at compile time)
Is it possible to get type hinting for the return type of this kind of function?
In Java, I would simply use generics to imply the return type:
static <T> T getOneObject(Class<? extends T> clazz, ParameterStorage parameters) {
...
}
I am aware of the manual type hinting, like
/** #var Foo $foo */
$foo = getOneObject('Foo', $params);
but I would like to have a solution that does not require this boilerplate line.
To elaborate: I am trying to write a wrapper around Doctrine, so that I can easily get the model entities that I want, while encapsulating all the specific usage of the ORM system. I am using PhpStorm.
** edited function to reflect my intended usage. I originally wanted to keep it clean of any specific use case to not bloat the question. Also note, that the actual wrapper is more complex, since I also incorporate model-specific implicit object relations and joins ect.
I use phpdoc #method for this purpose. For example, I create AbstractRepository class which is extend by other Repository classes. Suppose we have AbstractRepository::process(array $results) method whose return type changes according to the class that extends it.
So in sub class:
/**
* #method Car[] process(array $results)
*/
class CarRepo extends AbstractRepository {
//implementation of process() is in the parent class
}
Update 1:
You could also use phpstan/phpstan library. Which is used for static code analyses and you can use it to define generic return types:
/**
* #template T
* #param class-string<T> $className
* #param int $id
* #return T|null
*/
function findEntity(string $className, int $id)
{
// ...
}
This can now be achieved with the IntellJ (IDEA/phpStorm/webStorm) plugin DynamicReturnTypePlugin:
https://github.com/pbyrne84/DynamicReturnTypePlugin
If you use PHPStorm or VSCode (with the extension PHP Intelephense by Ben Mewburn) there is an implementation named metadata where you could specify your own type-hinting based on your code doing the magic inside. So the following should work (as it did on VSCode 1.71.2)
<?php
namespace PHPSTORM_META {
override(\getFirstObject(0), map(['' => '$0']));
}

Doctrine classes, can I add custom functions?

I'm having a difficult time finding if I can add custom functions to doctrine classes.
Lets say I have
use Doctrine\ORM\Mapping as ORM;
/**
* Map
*/
class Map
{
/**
* #var integer
*/
private $id;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
In my classes I would like some custom functions that return values that do not per se need to be stored in databse but merely provide a checking of certain values functionality.
For examply I would like to add a function isAboveTen();
function isAboveTen()
{
return this->id > 10;
}
Can I just go ahead and do this or do I need to define them as a special field in the xml file or annotations?
You can safely add functions working on simple member types, Doctrine will ignore them if you do not add any annotations.
The question whether you should avoid doing this depends on your overall architecture and coding guidelines. As mentioned in the comments both flavors with logic possibly inside vs outside the entities exist.
However, you should keep in mind that:
All persistent properties/field of any entity class should always be private or protected, otherwise lazy-loading might not work as expected. In case you serialize entities (for example Session) properties should be protected (See Serialize section below).
Which is described in the documentation. Since you are accessing these members inside your class, magic methods like __get() will not be called.

Categories