I'm having some trouble with PhpStorm intellisense when working with several classes. This is the structure I'm dealing with:
trait ContextTrait
{
public Subscriptions $subscription;
public function setSubscription(Subscriptions $value): self
{
$this->subscription = $value;
return $this;
}
}
abstract class Transaction
{
use ContextTrait;
protected ?int $paymentMethodId = null;
public function setPaymentMethodId(?int $value): self
{
$this->paymentMethodId = $value;
return $this;
}
}
class Invoice extends Transaction
{
protected ?string $dueDate = null;
public function setDueDate(?string $value): self
{
$this->dueDate = $value;
return $this;
}
}
When initializing the class like this:
$invoice = (new Invoice)
->setSubscription(...)
->setPaymentMethodId(...)
->setDueDate(...);
I'm having the following issues:
->setSubscription
and ->setPaymentMethodId
, it doesn't work for ->setDueDate
.->setDueDate
doesn't work, it returns the error "Cannot find declaration to go to".setDueDate
is grayed out in the class, stating that the function is never used anywhere.If I use a different instantiation approach (without chaining):
$invoice = new Invoice;
$invoice->setSubscription(...);
$invoice->setPaymentMethodId(...);
$invoice->setDueDate(...);
Everything works perfectly. Is there any way to fix this?
Edit:
An example of what is shows to me in PhpStorm:
->setDueDate
function.->setDueDate
function.Meanwhile, I tried with
static
[instead of: self
] and it indeed works, why?
This boils down in the PHP language (PhpStorm's IntelliSense, better Code completion tries to mimic it):
self
is the class itself, that is always concrete:
abstract class Transaction
{
...
public function setPaymentMethodId(?int $value): self
{
...
return $this;
}
}
When chaining, the code completion resolves self
to Transaction
:
$invoice = (new Invoice)
->setSubscription(...)
->setPaymentMethodId(...) # resolves to Transaction
->setDueDate(...); # Transaction has no such method
Now with static
this turns into what is called Late Static Bindings.
Late means late (when it happens), and binding means to associate the concrete class. As an abstract class cannot be instantiated, the class actually never is purely self
but one that extends from it and that is not abstract:
class Invoice extends Transaction
{
...
public function setDueDate(?string $value): self
{
...
}
}
Again with the chaining and : static
on setPaymentMethodId(), the concrete class Invoice is being resolved for code completion:
$invoice = (new Invoice)
->setSubscription(...)
->setPaymentMethodId(...) # resolves to Invoice
->setDueDate(...); # Invoice has such a method
This to this point is just about static
vs. self
for the late static binding in PHP.
Now what the IDE needs to do during chaining (as compared to individual $object->method() calls), is to resolve it one after the other in the chain. This adds complexity and my educated guess is, that the analyzer has a certain depth for which it resolves and if it sees self
it is reducing the overhead for a best quick result.
There are also other guess-mechanisms on special classes as they work like decorators (e.g. the traversable IteratorIterator) and if you hint towards generics (or as pointed out in the comment for traits).
In general you can also benefit from using interfaces as they are easy to look-up both for the IDE and for PHP. You also go away from diverse abstract or non-abstract implementations (by making them pure abstract classes, known as interfaces in PHP).