Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

One way to achieve this is by implementing the Dependency Inversion Principle (DIP) and using Dependency Injection (DI) to inject the required repository implementation into the service layer via constructor injection or setter injection.

First, define a generic interface or trait for the repository level methods, such as:

interface UserRepositoryInterface {
    public function findById(int $id): ?User;
    public function findAll(): array;
    public function save(User $user): void;
}

Then, create a concrete implementation of this interface, such as:

class UserRepository implements UserRepositoryInterface {
    public function findById(int $id): ?User {
        // ...
    }

    public function findAll(): array {
        // ...
    }

    public function save(User $user): void {
        // ...
    }
}

Next, create a service layer class that uses the trait and requires the UserRepositoryInterface via DI:

class UserService {
    use UserRepositoryTrait;

    public function __construct(UserRepositoryInterface $userRepository) {
        $this->userRepository = $userRepository;
    }

    public function getUser(int $id): ?User {
        return $this->userRepository->findById($id);
    }

    // ...
}

Finally, configure the DI container to inject the UserRepository instance when creating a new UserService instance:

$container = new DI\Container();
$container->set(UserRepositoryInterface::class, DI\autowire(UserRepository::class));
$container->set(UserService::class, DI\autowire(UserService::class));

This way, the service layer is not dependent on the concrete repository implementation, but still uses the methods provided by the UserRepositoryInterface via the UserRepositoryTrait. This makes it easy to switch out the actual repository implementation with a different one if needed, without affecting the service layer.