php - Repository pattern: how do I accurate the signature (return type) of a function?

I'm using the repository pattern to fetch my data from the database.

Here is my base class:

class BaseRepository implements EloquentRepositoryInterface
{
 
  protected $model;

  public function __construct(Model $model)
  {
    $this->model = $model;
  }

  
  public function findOrFail($id): Model
  {
    try {
      return $this->model->findOrFail($id);
    } catch (\Exception $e) {
      throw new \Exception($e);
    }
  }

}

which implements this interface:

interface EloquentRepositoryInterface
{
  public function findOrFail($id): Model;
}

Then I have one entity called Item, which extends Model:

class Item extends Model
{
  // ... 
}

And what I want to do is create an ItemRepositoryInterface:

interface ItemRepositoryInterface extends EloquentRepositoryInterface
{
  public function findOrFail($id): Item;
}

But I can't change the interface signature... PhpStorm is telling me that is incompatible so I had to remove the public function findOrFail($id): Item from the interface. So that my $itemRepository->findOrFail() respects the signature of EloquentRepositoryInterface.

Here my ItemRepository :

class ItemRepository extends BaseRepository implements ItemRepositoryInterface
{
   // ...
}

The problem

is that when I use $itemRepository->findOrFail() the specs tells me it's returning a Model

What I want

is that when I call $itemRepository->findOrFail() the specs should tell me that it's returning an Item

Is there a way to have this behaviour ? Like keeping the signature of findOrFail() inside EloquentRepositoryInterface and 'overwrite' the return type of it, without having to rewrite the whole function ?

Answer

Solution:

Why do you want to use the BaseRepository at all? The solution for your problem would be generics but as far as i know php doesn't ship with support for generics. You have 3 options: you could either remove type information from method contract for the return type or skip that BaseRepository. Or you don't use repositories at all with eloquent, since eloquent uses the Active Record Pattern, your persistence will always be bound to your models. So having an additional Repository layer which delegates everything to the model looks a bit overhead, except you want to abstract some complex queries. If you still wanna use Repositories, then go for the second option.

interface ItemRepository {
    public function findOrFail($id) : Item;
}

class EloquentItemRepository implements ItemRepository
{
    public function findOrFail($id) : Item {
        return Item::findOrFail($id);
    }
}

Answer

Solution:

I don't believe you can "override"/rewrite the return type of an interface method on PHP. The php documentation on return types states the "return type during inheritance is invariant":

The enforcement of the declared return type during inheritance is invariant; this means that when a sub-type overrides a parent method then the return type of the child must exactly match the parent and may not be omitted. If the parent does not declare a return type then the child is allowed to declare one.

One possible solution will be to create a second method with a different name on the ItemRepositoryInterface that would call and covert the result of findOrFail:

interface ItemRepositoryInterface extends EloquentRepositoryInterface
{
    public function findOrFailItem($id): Item;
}

Answer

Solution:

EloquentRepositoryInterface:

interface EloquentRepositoryInterface
{
  public function findOrFail($id): ?Model;
}

The syntax ?Model will return model if it exists. Use the same for the item interface

Source