I think a better approach would be to use a repository or service class. You can still let the user define what model class to use (similar to Cashier), but then your repository/service class does the actual fetching:
class FooController extends Controller
{
protected $foos;
public function __construct(FooRepository $foos)
{
$this->foos = $foos;
}
public function show(string $id)
{
$foo = $this->foos->retrieve($id);
}
public function store(StoreFooRequest $request)
{
$foo = $this->foos->create($request->validated());
}
public function update(UpdateFooRequest $request, string $id)
{
$foo = $this->foos->retrieve($id);
$this->foos->update($foo, $request->validated());
}
public function destroy(string $id)
{
$foo = $this->foos->find($id);
$this->foos->delete($foo);
}
}
class FooRepository
{
public function retrieve(string $id): FooInterface
{
try {
return (YourPackage::$fooModel)->findOrFail($id);
} catch (ModelNotFoundException $e) {
throw new FooNotFoundException($e->getMessage(), $e);
}
}
public function create(array $data): FooInterface
{
return (YourPackage::$fooModel)->create($data);
}
public function update(FooInterface $foo, array $data): FooInterface
{
$foo->update($data);
return $foo->fresh();
}
public function delete(FooInterface $foo): bool
{
return $foo->delete();
}
}
Then the package user can define what model they want to use in place of FooInterface