Php – Repository query conditions, dependencies and DRY

domain-driven-designdryPHPseparation-of-concerns

To keep it simple, let's suppose an application which has Accounts and Users. Each account may have any number of users. There's also 3 consumers of UserRepository:

  • An admin interface which may list all users
  • Public front-end which may list all users
  • An account authenticated API which should only list it's own users

Assuming UserRepository is something like this:

class UsersRepository extends DatabaseAbstraction {
    private function query() {
        return $this->database()->select('users.*');
    }
    public function getAll() {
        return $this->query()->exec();
    }
    // IMPORTANT:
    // Tons of other methods for searching, filtering,
    // joining of other tables, ordering and such...
}

Keeping in mind the comment above, and the necessity to abstract user querying conditions, How should I handle querying of users filtering by account_id? I can picture three possible roads:

1. Should I create an AccountUsersRepository?

class AccountUsersRepository extends UserRepository {
    public function __construct(Account $account) {
        $this->account = $account;
    }
    private function query() {
        return parent::query()
            ->where('account_id', '=', $this->account->id);
    }
}

This has the advantage of reducing the duplication of UsersRepository methods, but doesn't quite fit into anything I've read about DDD so far (I'm rookie by the way)

2. Should I put it as a method on AccountsRepository?

class AccountsRepository extends DatabaseAbstraction {
    public function getAccountUsers(Account $account) {
        return $this->database()
            ->select('users.*')
            ->where('account_id', '=', $account->id)
            ->exec();
    }
}

This requires the duplication of all UserRepository methods and may need another UserQuery layer, that implements those querying logic on chainable way.

3. Should I query UserRepository from within my account entity?

class Account extends Entity {
    public function getUsers() {
        return UserRepository::findByAccountId($this->id);
    }
}

This feels more like an aggregate root for me, but introduces dependency of UserRepository on Account entity, which may violate a few principles.

4. Or am I missing the point completely?

Maybe there's an even better solution?

Footnotes: Besides permissions being a Service concern, in my understanding, they shouldn't implement SQL query but leave that to repositories since those may not even be SQL driven.

Best Answer

I would keep all User related data-access in the UserRepository. It is not a good idea to make repositories depend on each other but it's perfectly ok to have a service depend on multiple repositories. So in this case you could have an AccountService which uses both UserRepository and AccountRepository.

The third option you have considered is something I've also used and it has the benefit that now the user list is loaded lazily (although this can be accomplished by other means as well). The huge drawback is that it will be very hard to serialize that object to send it over wire for example and it also couples the data-access layer with your object model.