Как реализовать такую архитектуру на Eloquent laravel?
Недавно я задавал похожий вопрос, но общего характера и больше про RBAC. С тем вопросом относительно разобрался, но другой остался, его и задаю.
Напоминаю вводную.
Есть проект, в нем такие роли - админ, врач, клиент. У клиента своя анкета, у врача своя. Главный вопрос - если мы врачей и клиентов храним в одной таблице, как лучше хранить их анкеты?
Ведь у врача есть обязательное поле - специализация, например. У клиента его нет, зато есть свое специальное поле, к примеру, это хронические болезни.
Какие варианты вижу я:
1. Все затолкать в одну таблицу (users), нужное подтягивать в зависимости от ситуации. Минус в том, что это бардак в чистом виде - одна таблица двоих еще вместит, а что если там 3-4-5 ролей? Там уже будет хлам.
2. Сделать поле анкета json-ом. Тут тебе и компактность, и нет смешения, но json достаточно недавно в sql для меня, не знаю точно, где его юзать. Да и поиск становится чуть замороченнее.
3. Просто сделать users как базовая модель, а от нее наследовать другие модели - clients и doctors. Тут надо разобраться - как такое сделать и как решать вопрос с авторизацией и как внутри связи устроены? Если этот вариант годный, буду его копать.
4. Увести профили в отдельные таблицы, например, client_profiles и doctor_profiles. Но как тогда корректно прокинуть связь так, чтобы у докторов не было client_profiles (пусть и null'евого), а у клиентов doctor_profiles?..
5. Вышеописанное еще можно сделать так - сделать таблицу profile_attributes, и построчно выставлять соответствие между атрибутом профайла и значением для каждого конкретного юзера - клиента или доктора, не важно.
Короче, понимаю, что варианты есть разные, есть мною не описанные, но хотелось бы понять, какие в этом вопросе best practics?
1. null'и ненужные поля, они не занимают много места. Будет хлам в таблице, но если в коде разбить одну таблицу на несколько моделек - будет уже лучше и спокойней.
2. json - не очень вариант, только если как-то это абстрагируешь и будешь все из json'а пихать назад в аттрибуты, что бы с этим было удобно работать в коде. Но это тоже так себе вариант.
3. если использовать uuid и морф связи без foreign ключей - да, как вариант
4. не создавать профили кому не нужно и все)
5. нет, хрень. Даже хуже чем второй вариант.
В этом конкретном случае я бы выбирал между вариантами 1 и 3, ибо привязывать примитивку "специализация" к какому-то отдельному энтити - такое себе удовольствие. 1-ый не такой плохой как кажется, а с третим теоретически сложнее работать, хотя это неплохая идея.
Я за 4. Связь hasOne и поле user_id в каждой таблице. При регистрации юзер выбирает, что он врач и ты создаешь запись в doctor_profile. Подтягивать через with. Удобно тем что просто и наглядно)
4 вариант из предложенных больше всех напрашивается.
Но есть ещё 6 вариант более интересный, но более сложный, создать 2 таблицы
Таблица attributes
id
name
role_id
Таблица attribute_values
attribute_id
user_id
value
5 вариант ни в коем случае не брать в разработку. Есть рабочий проект с такой структурой хранения данных. Да, удобно. Но во первых, мы существенно теряем в производительности, поскольку для каждого такого юзера dbal должен вытаскивать несколько строк из profile_attributes, вместо одной. Во вторых, для хранения данных в таком виде требуется раза в 1.5-2 больше физического места.
Нет, это решение НЕ работает. Да, оно однозначно сработает для загрузки профиля одного человека - тут никакой магии, но если вы попытаетесь загрузить with'ом/load'ом несколько юзеров с профилями сразу - все пойдет по пи*де, потому что ларка вызывает метод релейшена ровно единожды, так еще и на фейковой модельке.
Alex Wells, это самое простое решение, которое работает для SPA, где бэк реализован как REST-api. С правильной бизнес-логикой, с использованием App\Http\Resources, без говнокода. Ежу понятно, что with, load и тому подобное работать не будет.
class User extends Eloquent
{
/**
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function profile()
{
return $this->morphTo();
}
}
# создание/сохранение моделей
$doctor = App\Doctor::create();
$user = new App\User();
$user->profile()->associate($doctor);
$user->save();
Но вообще ты прав, с точки зрения реализации предпочтителен вариант с полиморфными связями. Просто уверен, что еще не все перешли даже на 5.8, поэтому могут возникнуть сложности когда захочется сделать выборку с фильтром по атрибутам зависимых моделек.
Antonio Solo, если у вас будет пять ролей, то по вашему разумению должны быть пять полей user.role1_profile_id ... user.role5_profile_id плюс сомнительное удобство с запросами.
Если вы используете полиморфные связи то при любом кол-ве ролей у вас будет два поля: id и тип связанной модели + все волшебство, которое Лара добавляет к модели (события и т.п.)
Вам надо не только анкеты вынести в отдельные таблицы, но и типы юзеров.
Итого таблицы
users
doctors
clients
admins
Таблицы докторов и клиентов ссылаются на users. В users общая инфа, в doctors ид из юзера и все необходимые поля для анкеты. С клиентами также. Админы могут быть не завязаны на юзеров, это как требования организованы.
Работаю с 3 вариантом, очень удобно, каждая модель-класс удобно хранит необходимые методы для роли пользователя. Никаких лишних таблиц, только users, user_data, валидация в formrequest - можно разделить в зависимости от роли, а также набор принимаемых полей. По-моему - идеальный вариант.)