Documenting abstract factory method return types in PHP with docblocks

This has been asked again and again, but the replies are a bit old and I’m somewhat desperately hoping something changed since “can’t be done” replies.

Context:

    class AbstractBuildObject {}
    class Hammer extends AbstractBuildObject{}
    class Nail extends AbstractBuildObject{}

    class AbstractFactory{    
        /**
          * return $type
          */
        public function build1(string $type): AbstractBuiltObject {
            return new $type();
        }

        /**
          * return any(AbstractBuiltObject)
          */
        public function build2(string $someArg): AbstractBuiltObject {
            $type = $this->decideTypeBasedOnArgsAndState($someArg);
            return new $type();
        }
    }

I tried to represent what I need with the annotations above the builders.

return $type (or ideally return $type of AbstractBuiltObject should hint that the return type is specified in the input parameter.

In the second case, any(AbstractBuiltObject) signifies that any derived concretion of the abstract class might be returned.

So I need some kind of annotation to achieve the effects I described. These annotations obviously don’t work, I just used them for illustrating the concept.

I know one might be tempted to use pipe type joins like return Hammer|Nail, but in my case, the factory class should hold be modified every time a new concrete implementation is added to the project, it’s also not specific enough in the build1 case, where I know precisely what the return type should be.

So, in short, I need this to work at least in PhpStorm:

    (new AbstractFactory())->build1(Hammer::class)-> // I should have Hammer autocomplete here
    (new AbstractFactory())->build2('foo')-> // I should have autocomplete of any concretion of the abstract here

Answer

The solution we adopted is this:

<?php
class AbstractBuiltClass {
    /**
      * @return static
      */
    public static function type(self $instance)
    // change return type to :static when #PHP72 support is dropped and remove explicit typecheck
    :self
    {
        if(!($instance instanceof static)){
            throw new Exception();
        }
        return $instance;
    }
}

class Hammer extends AbstractBuiltClass{
    function hammerMethod(){}
}

Hammer::type($factory->get(Hammer::class))->hammerMethod();

Contenders for a viable solution:

  1. Psalm template annotations: https://psalm.dev/docs/annotating_code/templated_annotations/ very promising but not widely supported yet
  2. Variable docblock (see Alex Howansky’s answer)

Leave a Reply

Your email address will not be published. Required fields are marked *