最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

php - PhpStorm intellisense not working properly with method chaining + multiple classes - Stack Overflow

programmeradmin3浏览0评论

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:

  1. The intellisense (while typing) only works for the functions ->setSubscription and ->setPaymentMethodId, it doesn't work for ->setDueDate.
  2. Pressing F12 on ->setDueDate doesn't work, it returns the error "Cannot find declaration to go to".
  3. The function 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:

  1. Using chaining method: It does not show the ->setDueDate function.

  1. Not using chaining method: It shows the ->setDueDate function.

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:

  1. The intellisense (while typing) only works for the functions ->setSubscription and ->setPaymentMethodId, it doesn't work for ->setDueDate.
  2. Pressing F12 on ->setDueDate doesn't work, it returns the error "Cannot find declaration to go to".
  3. The function 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:

  1. Using chaining method: It does not show the ->setDueDate function.

  1. Not using chaining method: It shows the ->setDueDate function.

Share Improve this question edited Feb 15 at 19:00 LazyOne 165k47 gold badges414 silver badges415 bronze badges asked Feb 15 at 15:53 LinesofcodeLinesofcode 5,90314 gold badges70 silver badges131 bronze badges 5
  • Transaction's public function setPaymentMethodId is missing the return $this statement. PhpStorm resolves return statements. If you would run that code in a test it should also throw as : self return type is violated on void return. – hakre Commented Feb 15 at 16:52
  • @hakre thanks, but that was just a silly mistake while creating an example to post in stackoverflow. It's not related. – Linesofcode Commented Feb 15 at 17:15
  • Okay, I also wondered because you should have seen a fatal. Now replace : self with : static on that functions return value, otherwise it remains true that Transaction has no method named setDueDate. It may also for the trait, but I'm not sure just quick. I'll also test in Phpstorm if you like. – hakre Commented Feb 15 at 17:34
  • @hakre take a look at my edited post. Meanwhile, I tried with static and it indeed works, why? – Linesofcode Commented Feb 15 at 18:11
  • Could be related (after a quick search): youtrack.jetbrains/issue/WI-67397 . Seems to be related to how self is resolved when used in a trait (your screenshot show that: ContextTrait|Transaction -- no Invoice mentioned there). – LazyOne Commented Feb 15 at 19:07
Add a comment  | 

1 Answer 1

Reset to default 1

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).

发布评论

评论列表(0)

  1. 暂无评论