Laravel по-русски

Русское сообщество разработки на PHP-фреймворке Laravel.

Ты не вошёл. Вход тут.

#1 22.02.2013 17:23:03

Виктор

Работа с ORM

Здравствуйте

Перехожу с 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=...,...

Спасибо)

#2 22.02.2013 20:31:13

Re: Работа с ORM

Не совсем понял сути проблемы, поэтому могу предложить только несколько уточнений:

  • ты ошибочно называешь «Fluent»-ом Eloquent, так как Fluent — это просто контейнер-построитель запросов, у него нет find() и других методов ORM
  • JOIN можно делать в помощью PHP...->join('table2', function ($join) { $join->where('a''=''b') ... })
  • аналогично делаются вложенные условия, поэтому сложность запроса не ограничена: PHP...->where(function ($subAnd) { ... }) — или or_where() для OR

Не в сети

#3 24.02.2013 15:38:40

Виктор

Re: Работа с ORM

Спасибо за отклик! ☺

Ну вот например у меня есть

PHP
class User extends Eloquent() {
    
pulic function company() {
        return 
$this->belongs_to('company');
    }
}

также есть

PHP
class Company extends Eloquent {
    public function 
user() {
        return 
$this->has_many('user');
    }
    public function 
content() {
        return 
$this->has_many('content');
    }
}

и еще

PHP
class Content extends Eloquent {
    public function 
company() {
        return 
$this->belongs_to('company');
    }
}

(Мог с синтаксисом напутать, ну суть в том что есть сущность «компания». В одной компании может быть несколько пользователей. И есть сущность «контент», который принадлежит компании)

Как мне с помощью только Eloquent (ну т.е. чтобы не приходилось писать статические методы в классе Content, работающие с Fluent-ом, а делать все прям в контроллере) решить 2 следующие актуальные для меня задачи:

1. Есть action по которому пользователь может изменить информацию о контенте. Задача — не разрешать ему изменять информацию о контенте, который принадлежит чужой компании. Допустим мы проделали всю валидацию, и в итоге получили набор полей и их значений, которые нужно изменить в сущности «контент». Код выглядит примерно так (опять таки, пишу на скорую руку, могу где-то ошибиться) :

PHP
// В контроллере
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 запроса:

PHP
function update() {
    
$content Content::find(Input::get('id'));
    
$id $content->company->get_id(); // здесь не уверен в синтаксисе
    
if ($id !== Session::get('companyId')) die('die hacker');
    ...
}

Во, только что сам до этого додумался ☺ Но только мне не нравится 2 запроса, ведь с помощью Fluent это пишется с одним запросом:

PHP
// Это уже модель 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 не очень, особенно второй вопрос…

#4 24.02.2013 17:12:15

Re: Работа с ORM

PHP
    $affected DB::table('content')
        ->
where('id''='1)->where('content.company_id''='$company_id)
        ->
update(array('content.name'$name));

Этот же запрос можно переписать для Eloquent:

PHP
    $affected Content
        
::where('id''='1)->where('content.company_id''='$company_id)
        ->
update(array('content.name'$name));

Разница только в первой строчке. Eloquent — тот же Fluent, если речь не идёт о выборке моделей (SELECT).

Но вообще проверять права в модели — плохая идея, модель должна только давать интерфейс к данным, а проверкой прав должен заниматься уровень выше — контроллер, например. Скажем, если у тебя есть администраторы, то они могут менять любые компании. В итоге придётся изменять метод модели и добавлять ещё один, который не будет проверять company_id. Модель раздуется.

Не в сети

#5 26.02.2013 13:53:43

Виктор

Re: Работа с ORM

  1. Разница только в первой строчке. Eloquent — тот же Fluent, если речь не идёт о выборке моделей (SELECT).

Ну вот например у меня такой код (уже реальный):

PHP
    $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’);
Так и хочется написать что-то вроде

PHP
$user User::find(Input::get('id'))->AND('user.company_id = ..'); :)

Как я понимаю, нужно в любом случае выполнить два запроса. Первый делает проверку прав, второй уже изменяет сущность? Типа как я писал:

PHP
function update() {
    
$content Content::find(Input::get('id'));
    
$id $content->company->get_id(); // здесь не уверен в синтаксисе
    
if ($id !== Session::get('companyId')) die('die hacker');
    ...
}

Насчет проверки прав в модели — я в модель часто передаю массив типа $conditions, в котором содержаться условия. Вот например у меня есть функция которая возвращает много много данных из БД. Но админам нужно показывать чуть больше, юзерам чуть меньше. Соотственно в массив $conditions я передаю список полей, которая должна вернуть модель. В контроллере смотрю обычный ли это юзер или админ, и в зависимости от этого контролирую возвращаемые поля…

#6 01.03.2013 12:35:52

Re: Работа с ORM

  1. PHP$user User::find(Input::get('id'))->AND('user.company_id = ..'); :)

Так, конечно, написать нельзя, но ведь можно так:

PHP
$user User::where('id''='Input::get('id'))->where('company_id = ..')->first();
  1. Вот например у меня есть функция которая возвращает много много данных из БД. Но админам нужно показывать чуть больше, юзерам чуть меньше.

В идеале модель не имеет никакого понятия о правах — её дело добавлять и изменять записи. Вне зависимости от прав она возвращает одинаковый результат.

В случае с полями подхода два: если это просто разный набор полей из одной таблицы (например, админам нужно поле ip, другим нет), то от модели получаются все поля, но контроллер решает, какие показать; либо если речь идёт о большой работе (например, админам нужно получить данные из нескольких таблиц) — тогда в модели делается два разных метода, один простой, другой сложный. Какой вызывать опять же решает контроллер.

Не в сети

#7 01.03.2013 13:28:47

Виктор

Re: Работа с ORM

Насчет eloquent-а понятно, спасибо) Единственное меня смущает что кажется что эти User::where должны быть как будто ф-ия модели. Хоть User::where..->join(’company’) например не занимает много строчек, но в CI я писал такие же условия уже в модели…

Насчет модели — ну я же напрямую права не проверяю в ней. У меня логика приложения такая: почти у каждой сущности по урлу вида /entity/getlist можно получить список полей. Причем эти поля — данные из разных таблиц (не всегда, но часто). Только для админа я показываю чуть больше, для юзера — чуть меньше. Так же урл getlist принимает параметры limit, offset, order_by, список полей, фильтрация по разным условиям where/like/in, и т.п. В контроллере я собираю массив этих условий, которые передаю в модель. Для админа список получаемых полей расширен, для юзера сужен. Ведь в любом случае только такой вариант возможен, когда я в модель передаю скажем массив условий WHERE. Не буду же я из модели делать просто select *, а в контроллере проходится по возвращаемым моделью массивам и фильтровать вручную…
Единственный минус, это то, что иногда модель делает лишние join-ы, когда пользователю не нужна дополнительная инфа. Можно конечно разбить на 2 метода в этом случае, пока для удобства один.

Я все к тому что у меня именно контроллер решает, какие поля доступны. Т.е., буквально:

PHP
    $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 массив с условиями

В модель нигде не передается переменные сессии, права и т.д. просто я к тому что по другому я не понимаю как можно общаться с моделью, без передачи условий. Модель предоставляет интерфейс к данным, но ей же можно указывать какие данные мы хотим получить (в зависимости конечно от прав, которые проверяются на уровне контроллера)

А так еще раз спасибо за ваши ответы!

#8 01.03.2013 13:44:03

Re: Работа с ORM

  1. Не буду же я из модели делать просто select *, а в контроллере проходится по возвращаемым моделью массивам и фильтровать вручную…

А почему нет? У тебя десятки полей с длинной в сотню байт и больше, чтобы ограничивать выборку вручную?

  1. Я все к тому что у меня именно контроллер решает, какие поля доступны.

Раз он решает, то модель тем более не должна ничего делать. Если вопрос о разных формах JOIN — тогда на каждую свой метод, или флаги для одного метода. Но не проверка прав или ещё что-то.

  1. просто я к тому что по другому я не понимаю как можно общаться с моделью, без передачи условий.

Общение-то правильное, но не понятно зачем модель делает ещё какие-то проверки (на права?), если для неё уже всё передано и подготовлено. Возможно я просто не понял, что твоя модель на самом деле делает, и мы говорим об одном и том же.

  1. А так еще раз спасибо за ваши ответы!

Всегда рад.

Не в сети

Подвал раздела