Русское сообщество разработки на PHP-фреймворке Laravel.
Ты не вошёл. Вход тут.
Страницы 1
Здравствуйте
Перехожу с CI на Laravel. До этого никогда не использовал ORM.
Задача следующая: есть url, по которому нужно выдать информацию о пользователе по его айди. Вроде все просто, User::find(Input::get('id'))->to_array(); и в json его (не использую специальный метод для отображения модели в json-е, потому что помимо этого есть еще передаваемые поля).
Но дело в том, что, нужно отобразить еще поля компании, к которой принадлежит пользователь. И дать возможность выбрать поля, которые нужно отображать.
Например,
/admin/user/get/?fields=name,company_name
Отобразить имя пользователя, и имя компании к которой он принадлежит.
Собственно, с помощью fluent я решаю такую задачу (т.к. это простой вариант, а есть запросы где нужно сделать кучу join-ов, where, raw условий и т.д.).
Но как нормально написать это используя Eloquent?
Я могу получить отдельно пользователя: User::with('company')->where_id(Input::get('id'))->first()->to_array(), я могу получить компанию $company = $user->company->to_array(), потом мне нужно будет думать насчет полей (name например есть и у пользователя и у компании). когда я работаю с fluent я делаю select вроде "user.name as name, company.name as company_name", а дальше все достаточно легко получается.
А тут как не пробую - не понимаю)
С fluent-ом (или в CI аналогично ActiveRecord) я, например для массового селекта (т.е. получить всех юзеров и + доп. инфу к ним) делаю большой селект, джойны, дальше у меня есть массив условий который связан с тем что приходит в $_GET, и в общем в модели в цикле проставляю where условия, дальше обхожу результат выборки и удаляю ненужные поля, если задано fields=...,...
Спасибо)
Не совсем понял сути проблемы, поэтому могу предложить только несколько уточнений:
PHP...->join('table2', function ($join) { $join->where('a', '=', 'b') ... })
PHP...->where(function ($subAnd) { ... })
— или or_where() для ORНе в сети
class User extends Eloquent() {
pulic function company() {
return $this->belongs_to('company');
}
}
class Company extends Eloquent {
public function user() {
return $this->has_many('user');
}
public function content() {
return $this->has_many('content');
}
}
class Content extends Eloquent {
public function company() {
return $this->belongs_to('company');
}
}
(Мог с синтаксисом напутать, ну суть в том что есть сущность «компания». В одной компании может быть несколько пользователей. И есть сущность «контент», который принадлежит компании)
Как мне с помощью только Eloquent (ну т.е. чтобы не приходилось писать статические методы в классе Content, работающие с Fluent-ом, а делать все прям в контроллере) решить 2 следующие актуальные для меня задачи:
1. Есть action по которому пользователь может изменить информацию о контенте. Задача — не разрешать ему изменять информацию о контенте, который принадлежит чужой компании. Допустим мы проделали всю валидацию, и в итоге получили набор полей и их значений, которые нужно изменить в сущности «контент». Код выглядит примерно так (опять таки, пишу на скорую руку, могу где-то ошибиться) :
// В контроллере
function update() {
// ...Валидация...
$content = Content::find(Input::get('id'));
$content->fill(array('name' => Input::get('name')));
$content->save();
}
Т.е. вызов выглядит как: /user/content/update/?id=5&name=newName
Но так пользователь сможет изменить имя контенту, который не принадлежит его компании. Можно написать используя 2 запроса:
function update() {
$content = Content::find(Input::get('id'));
$id = $content->company->get_id(); // здесь не уверен в синтаксисе
if ($id !== Session::get('companyId')) die('die hacker');
...
}
Во, только что сам до этого додумался Но только мне не нравится 2 запроса, ведь с помощью Fluent это пишется с одним запросом:
// Это уже модель Content
public static function update($id, $company_id, $name) {
$affected = DB::table('content')
->where('id', '=', 1)->where('content.company_id', '=', $company_id)
->update(array('content.name', $name));
return $affected;
}
И уже в контроллере смотреть сколько записей изменилось. Впрочем, 0 измененных записей не всегда говорит о том, что пользователь меняет информацию чужой компании — может, просто, имя контента одно и тоже. Тогда чтобы различать эти ситуации придется делать 2 запроса
Но это не такая острая проблема, как следующая:
2. В простом случае мне нужно вытащить пользователя, у компании которого есть контент с именем LIKE %abc%, загруженный в такой то день например. Причем мне нужно вытащить только user_name и user_email из полей пользователя. Вот как это сделать не прибегая к написанию статических методов в модели, используя fluent?
Я к сожалению не за рабочим компьютером, и привел бы код с Fluent-ом, где у меня один запрос с кучей join-ов, для которого расставляются условия WHERE исходя из параметров get запроса…
Ох, что-то я понаписал много
В общем, с fluent-ом мне все ясно, т.к. это аналогично CI-шному подходу. А вот с Eloquent не очень, особенно второй вопрос…
PHP$affected = DB::table('content')
->where('id', '=', 1)->where('content.company_id', '=', $company_id)
->update(array('content.name', $name));
Этот же запрос можно переписать для Eloquent:
$affected = Content
::where('id', '=', 1)->where('content.company_id', '=', $company_id)
->update(array('content.name', $name));
Разница только в первой строчке. Eloquent — тот же Fluent, если речь не идёт о выборке моделей (SELECT).
Но вообще проверять права в модели — плохая идея, модель должна только давать интерфейс к данным, а проверкой прав должен заниматься уровень выше — контроллер, например. Скажем, если у тебя есть администраторы, то они могут менять любые компании. В итоге придётся изменять метод модели и добавлять ещё один, который не будет проверять company_id. Модель раздуется.
Не в сети
- Разница только в первой строчке. Eloquent — тот же Fluent, если речь не идёт о выборке моделей (SELECT).
Ну вот например у меня такой код (уже реальный):
$user = User::find(Input::get('id'));
if (is_null($user)) {
return Response::json(array('success' => false, 'error' => (string)__('messages.entity_not_found')));
}
$user->fill(array(
'name' => Input::get('name', $user->name),
'email' => Input::get('email', $user->email),
'phone' => Input::get('phone', $user->phone),
'is_admin' => Input::get('is_admin', $user->is_admin),
'is_deleted' => Input::get('is_deleted', $user->is_deleted)
));
$user->save();
Это экшен обновления данных пользователя. Код нравится, все красиво, за исключением проверки того что юзер меняет информацию о юзерах своей компании. Кароче говоря user.company_id должно быть равно session::get(’company_id’);
Так и хочется написать что-то вроде
$user = User::find(Input::get('id'))->AND('user.company_id = ..'); :)
Как я понимаю, нужно в любом случае выполнить два запроса. Первый делает проверку прав, второй уже изменяет сущность? Типа как я писал:
function update() {
$content = Content::find(Input::get('id'));
$id = $content->company->get_id(); // здесь не уверен в синтаксисе
if ($id !== Session::get('companyId')) die('die hacker');
...
}
Насчет проверки прав в модели — я в модель часто передаю массив типа $conditions, в котором содержаться условия. Вот например у меня есть функция которая возвращает много много данных из БД. Но админам нужно показывать чуть больше, юзерам чуть меньше. Соотственно в массив $conditions я передаю список полей, которая должна вернуть модель. В контроллере смотрю обычный ли это юзер или админ, и в зависимости от этого контролирую возвращаемые поля…
PHP$user = User::find(Input::get('id'))->AND('user.company_id = ..'); :)
Так, конечно, написать нельзя, но ведь можно так:
$user = User::where('id', '=', Input::get('id'))->where('company_id = ..')->first();
- Вот например у меня есть функция которая возвращает много много данных из БД. Но админам нужно показывать чуть больше, юзерам чуть меньше.
В идеале модель не имеет никакого понятия о правах — её дело добавлять и изменять записи. Вне зависимости от прав она возвращает одинаковый результат.
В случае с полями подхода два: если это просто разный набор полей из одной таблицы (например, админам нужно поле ip, другим нет), то от модели получаются все поля, но контроллер решает, какие показать; либо если речь идёт о большой работе (например, админам нужно получить данные из нескольких таблиц) — тогда в модели делается два разных метода, один простой, другой сложный. Какой вызывать опять же решает контроллер.
Не в сети
Насчет eloquent-а понятно, спасибо) Единственное меня смущает что кажется что эти User::where должны быть как будто ф-ия модели. Хоть User::where..->join(’company’) например не занимает много строчек, но в CI я писал такие же условия уже в модели…
Насчет модели — ну я же напрямую права не проверяю в ней. У меня логика приложения такая: почти у каждой сущности по урлу вида /entity/getlist можно получить список полей. Причем эти поля — данные из разных таблиц (не всегда, но часто). Только для админа я показываю чуть больше, для юзера — чуть меньше. Так же урл getlist принимает параметры limit, offset, order_by, список полей, фильтрация по разным условиям where/like/in, и т.п. В контроллере я собираю массив этих условий, которые передаю в модель. Для админа список получаемых полей расширен, для юзера сужен. Ведь в любом случае только такой вариант возможен, когда я в модель передаю скажем массив условий WHERE. Не буду же я из модели делать просто select *, а в контроллере проходится по возвращаемым моделью массивам и фильтровать вручную…
Единственный минус, это то, что иногда модель делает лишние join-ы, когда пользователю не нужна дополнительная инфа. Можно конечно разбить на 2 метода в этом случае, пока для удобства один.
Я все к тому что у меня именно контроллер решает, какие поля доступны. Т.е., буквально:
$responseFields = array('id', 'name', 'type', 'moderated');
$like = array(
'name' => 'content.name'
);
$where = array(
'id' => array('type' => 'id', 'value' => 'content.id'),
'type' => array('type' => 'text', 'value' => 'content.type'),
'moderated' => array('type' => 'bool', 'value' => 'content.moderated')
);
...
$screens = Screen::getList($params); // $params массив с условиями
В модель нигде не передается переменные сессии, права и т.д. просто я к тому что по другому я не понимаю как можно общаться с моделью, без передачи условий. Модель предоставляет интерфейс к данным, но ей же можно указывать какие данные мы хотим получить (в зависимости конечно от прав, которые проверяются на уровне контроллера)
- Не буду же я из модели делать просто select *, а в контроллере проходится по возвращаемым моделью массивам и фильтровать вручную…
А почему нет? У тебя десятки полей с длинной в сотню байт и больше, чтобы ограничивать выборку вручную?
- Я все к тому что у меня именно контроллер решает, какие поля доступны.
Раз он решает, то модель тем более не должна ничего делать. Если вопрос о разных формах JOIN — тогда на каждую свой метод, или флаги для одного метода. Но не проверка прав или ещё что-то.
- просто я к тому что по другому я не понимаю как можно общаться с моделью, без передачи условий.
Общение-то правильное, но не понятно зачем модель делает ещё какие-то проверки (на права?), если для неё уже всё передано и подготовлено. Возможно я просто не понял, что твоя модель на самом деле делает, и мы говорим об одном и том же.
- А так еще раз спасибо за ваши ответы!
Не в сети
Страницы 1