PHP Data Mapper с рекурсивными отношениями MTM между доменами - PullRequest
0 голосов
/ 23 мая 2018

Я много читаю на уровне модели в PHP MVC и разрабатываю Data Mapper (да, я заново изобретаю колесо, оно учится).

Могу сказать, что понимаю связь между даннымиMappers и доменные объекты.

Я пытаюсь согласованно реализовать способ извлечения вложенных отношений в Data Mapper, не делая Mapper зависимым от других Mappers.

ВотПример некоторых связанных доменов:

  1. Пользователь
  2. Роль
  3. Разрешения

Таким образом, Роль имеет много разрешений, а пользователь имеетмного ролей:

User -> Role(s) -> Permission(s)

При звонке UserMapper -> fetch($id) я должен получить домен пользователя.Ради (абсурдной) простоты домен пользователя имеет одно свойство, roles, набор доменов ролей.

Пока все хорошо.В UserMapper я могу присоединить таблицу roles к users, сопоставить первичный ключ, получить все роли, связанные с каждым пользователем, и создать домен ролей для каждого из них.Коллекция ролей добавлена ​​пользователю.

Проблема

Эта коллекция ролей не завершена.Опять же, ради абсурдной простоты, у каждой роли есть одно свойство, permissions, набор доменов разрешений.

Все, что мы до сих пор делали, - это заполняем пользовательский домен коллекцией ролей, но у нас нетне получили разрешения, связанные с каждой ролью в коллекции.Коллекция ролей не завершена.

Мой вопрос: как заполнить коллекцию разрешений для каждой роли у каждого пользователя?

Идея 1

Это тривиально простая идея (с точки зрения реализации PHP).UserMapper начинает выглядеть так:

class UserMapper implements Mapper {
    public function __construct(RoleMapper $mapper)
}

А в RoleMapper:

class RoleMapper implements Mapper {
    public function __construct(PermissionMapper $mapper)
}

В UserMapper мы выбираем подходящих пользователей.Затем мы повторим коллекцию User и вызовем RoleMapper -> fetch_by_user($user -> id).Role Mapper получает соответствующие роли, выполняет итерацию полученной коллекции ролей и вызывает PermissionMapper -> fetch_by_role($role -> id).

Это легко реализовать.Слепо очевидно, что происходит.Но это кажется неправильным и, вероятно, будет ужасно неэффективным в использовании (по крайней мере, для чего-либо с более сложными отношениями, чем этот пример).

Мне не нравится, что преобразователи данных зависят от других преобразователей данных - каждыйData Mapper больше не является автономным и не всегда выполняет свое собственное отображение.UserMapper не знает, что именно будет в возвращаемой коллекции - мы предполагаем, что можем доверять RoleMapper, но концептуально мы нарушили инкапсуляцию.

Идея 2

С чего я начал.UserMapper использует объединение для получения ролей, связанных с каждым пользователем.

Изначально это выглядит нормально.Это эффективно - мы просто выполняем одну инструкцию SQL.Data Mappers не нужно ограничивать одной таблицей - мне это удобно.

Но ... что происходит, когда нам нужно получить отношения рекурсивно?

Что я меньше - это идея о том, что UserMapper должен не только знать, что пользователи имеют роли (это нормально), но и что роли имеют разрешения .Эта деталь, по-видимому, находится вне сферы интересов UserMapper.

Что, если в дальнейшем у Ролей также будет много "Шоколадных Foobars?"Я не хочу обновлять UserMapper для правильного получения всех этих Chocolate Foobars.

Возможно, мы также добавим новый домен "Pet", и у Pets тоже могут быть роли.Так что теперь я должен обновить мой UserMapper и мой PetMapper для поддержки этих сумасшедших «шоколадных батончиков», связанных с ролями.Идея 1 полностью устранена, но у нее есть свои проблемы, описанные выше.

Заключение

Я начал писать этот пост в надежде, чтовсе просто дало бы мне решение (не могу сказать, сколько раз я набирал в Stack Overflow и в итоге не нажимал enter).Пока я не нашел.

Ни один из этих подходов не кажется "правильным".С одной стороны, идея 1 поддерживает согласованность отдельных картографических данных и, по-видимому, обеспечивает более высокий уровень обслуживания.Но это также приведет к очень большому количеству запросов (даже если они являются высокооптимизированными запросами на основе индексов) и потребует, чтобы у преобразователей данных были другие сопоставители данных в качестве зависимостей, что кажется неправильным.

Но идея 2 - это малолучше.Объединение в устройстве отображения данных верхнего уровня работает нормально ... если только домен, к которому вы присоединяетесь, не требует присоединений.

Мне было бы интересно услышать подходы к решению этой проблемы.Как вы управляете вложенными отношениями между доменами в сценарии Data Mapper?

В конечном итоге, вызывая UserMapper -> fetch(), мы получаем домены пользователей, заполненные коллекциями ролей, которые сами заполняются коллекциями разрешений.Пользователи - MTM с ролями, роли - MTM с разрешениями.

Это всего лишь пример сценария.Другие включают Blog post -> Image(s) -> Category(ies), или Workstation -> Phone(s) -> Camera(s) -> Component(s).

Если взять последний пример, WorkstationMapper-> fetch() должен вернуть WorkstationCollection, где мы можем сделать: Workstation -> get_phone("TA-1033") -> get_camera("front") -> has_component("1080p video capture component").

1 Ответ

0 голосов
/ 23 мая 2018

Почему вы пытаетесь построить полную ORM?Этот путь приведет к безумию.

Итак, у вашего пользователя несколько ролей, и у каждой роли есть несколько разрешений, и вы хотите загрузить их все.Так почему бы вам не сделать это следующим образом:

$roles = new Entity\RoleCollection;
$roles->forUser(new Entity\User($id));
// note: you really do not need to fetch user :)

$roleMapper = new Mapper\RoleCollection($pdo);
$roleMmapper->fetch($roles);
// populates the roles; if user is set, populates by user 

$permissionMapper = Mapper\RoleCollectionPermissions($pdo);
$permissionMapper->fetch($roles);
// populates the permission collections inside each of role collection entry 

По сути, если вы хорошо разбираетесь в SQL, для этого потребуется всего два запроса.И в этом случае нет никакой магии, пока вы придерживаетесь (хотя бы частично) четкой схемы именования для ваших картографов.Вы получаете только те части, которые вам нужны.

И, если вам действительно нужны разрешения, а роли являются просто промежуточным звеном для этих целей, просто упростите его:

$permissions = new PermissionCollection;
$permissions->forUser(new Entity\User($id))

$permissionMapper = Mapper\PermissionCollection($pdo);
$permissionMapper->fetch($permissions);

Включите соединение с двумя глубинами в маппере, есликоллекция разрешений имеет пользовательский класс, назначенный в качестве условия.

Если вы начали с ORM, то кажется естественным создавать рекурсивные загрузчики, которые заполняют все.Но это не так, как вы пишете картографы данных.Это то, как вы создаете универсальные ORM.
Придерживайтесь принципа YAGNI .

TL; DR

Пишите маппер, который обрабатывает ваши существующие сценарии вместо каких-то общих "загрузите все"задачи.

...