dotancohen
dotancohen

Reputation: 31531

Type hinting methods called from builder method

Consider an Active Record implementation which uses a static builder to return instances of a model. Here is a very reduced version of the ModelBuilder and Model classes to demonstrate, with all code irrelevant to the question removed:

class ModelBuilder {
    public function __construct($class) {
        $this->class = $class
    }

    private function _execute() {
        $result = $pdo->query("SELECT * FROM {$this->class}");
        return $result->fetchAll(PDO::FETCH_CLASS, $this->class);
    }

    public function all() {
        return $this->_execute();
    }

    public function one() {
        return $this->_execute()[0];
    }
}

class Model {
    public static function builder() {
        return new ModelBuilder(get_called_class());
    }
}

class FooModel extends Model {

}

To get a Foo item we call the FooModel class like this:

$foo = FooModel::builder()->one();

In this case intelligent IDEs (in my case PhpStorm) do not know what type of object $foo is. I could type-hint the variable /** @var $foo FooModel */ each time I create an object but I would prefer proper type-hinting on the all() and one() methods.

What is the proper type hinting to add to the all() and one() methods? Note that static does not work, I believe because ModelBuilder is not an ancestor of the initial called class and it is being called explicitly in the builder() method.

This particular project is using PHP 5.6, but answers that are PHP 7 specific are welcome as well.

Upvotes: 1

Views: 119

Answers (1)

cn0047
cn0047

Reputation: 17091

If your models have parent class like BaseModel - you can use BaseModel in return block inside doc-block for all and one methods, like:

/**
* @return BaseModel Model instance.
*/
public function one() {
    return $this->_execute()[0];
}

But in this case - it is just BaseModel it is not FooModel nor BooModel...
You will have access only to BaseModel methods and properties, but not to FooModel specific...

With purpose to have certain model - you have override needed method in child model with proper doc-block, like:

class FooModel extends BaseModel
{
    /**
    * @return FooModel Foo model instance.
    */
    public function one() {
        return parent::one();
    }
}

Or have doc-block for class, like this:

/**
 * @method FooModel one Foo model instance.
 */
class FooModel extends BaseModel
{
}

Or use doc-block /** @var $foo FooModel */ like you've already mentioned.

Upvotes: 1

Related Questions