skip to Main Content

I’m currently working on a PHP project where I’ve implemented dependency injection in my code. The code is functioning correctly, but I’m encountering an issue with PHPStorm (version 2023.1) misinterpreting the code, which is causing typehinting to break. I’m reaching out to the community for assistance in resolving this problem and helping PHPStorm correctly interpret my code to restore seamless typehinting functionality.

To provide some context, here’s a simplified example of the code:

class B {

    public static function SayHello(){
        echo 'Hello World';
    }

}


class A {

    /**
     * @param B::class $B
     */
    public function __construct(
        private readonly string $B
    ){}

    public function start(){
        $this->B::SayHello(); // <---- I Would like that SayHallo() was available via typehinting
    }

}

class AFactory extends A {

    public function __construct(){
        parent::__construct(B::class);
    }
}


$class = new AFactory();
$class->start();

This behavior is puzzling because I have clearly indicated in the PHPDoc that $B should be of type B::class, referring to the class rather than just a generic string.

When attempting to address the issue, I made several attempts to inform PHPStorm that $this->B should be recognized as an instance of the B class, rather than just a string. One approach I tried was specifying in the PHPDoc that $B should be considered an instance of the B class. However, this approach introduced other problems.

PHPStorm interpreted the PHPDoc declaration as indicating that $B had already been initialized, which was not the case. As a result, it introduced further confusion and did not resolve the issue as expected.

I would greatly appreciate any help or suggestions from the community to resolve this issue or find alternative ways to implement Dependency Injection effectively.

3

Answers


  1. Chosen as BEST ANSWER

    Thanks to the post of Marcin Olowsky, I've found a workaround to the problem. As he suggested, I had to pollute my code a bit. By writing the start() method as follow:

    public function start(){
        /**
         * @var class-string<B> $c
         */
        $c = $this->B;
        $c::SayHello();
    
    }
    

    By doing it this way, PHPStorm knows the possibilities of $c and gives me the correct type hints.


  2. Unfortunately you cannot do this w/o polluting your code as you are unable to typehint $this->B. The workaround would be to reassign it to another variable and typehint that variable instead, like this:

        public function start(){
            /** @var B $cls */
            $cls = $this->B;
            $cls::SayHello();
        }
    
    Login or Signup to reply.
  3. If your goal is to implement DI, then you don’t want to pass a string containing the name of the class, you want to pass an object containing an instance of the class.

    This behavior is puzzling because I have clearly indicated in the PHPDoc that $B should be of type B::class, referring to the class rather than just a generic string.

    No. B::class is a string, which contains the name of the class, which is "B". You want an instance of B, so just use B:

    // this means that $obj must be an instance of B
    public function __construct(private readonly B $obj)
    {
    }
    
    public function start()
    {
        $this->obj::<start typing> // now your IDE will autocomplete B's methods
    }
    

    So, then you’d do:

    $b = new B();
    $a = new A($b); // This is where the DI happens.
    

    In your example, you’re saying that the constructor’s argument can only ever be the exact string "B" — which makes no sense, as if it can never change, why would you even bother taking it as a parameter.

    Also note, if you’re explicitly typehinting, then you generally don’t need docblocks, as your IDE will be able to deduce type just from the typehints, and providing both gives you the possibility of having them conflict. Unless you’re adding descriptions to the parameters, I’d leave them out. Otherwise, make sure they match:

    /**
     * @param B $obj An instance of B.
     */
    public function __construct(private readonly B $obj)
    {
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search