Как я могу взять большое количество параметров GET и отфильтровать запрос в зависимости от них?

У меня есть этот контроллер для RESTful API, который я создаю в Laravel Lumen, который принимает относительно большое количество параметров и анализирует их, где запросы и данные извлекаются в зависимости от того, были ли они предоставлены. Например,

GET /nodes?region=California
GET /nodes?ip=127.0.0.1

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

Теперь, когда дело доходит до фильтрации значений каждого в массиве, я делаю это с помощью массива foreach. Это самый чистый способ, который я мог придумать, без лишнего кода (я не хочу делать свои контроллеры слишком толстыми). Есть ли другой способ сделать это чисто, может быть, с разделением функций/классов?

Вот мой код конструктора:

/**
 * Get some values before using functions.
 * 
 * @param Request $request Instance of request.
 */
public function __construct(Request $request)
{
    $this->offset = (int) $request->input('offset', 0);

    // TODO: I'm not sure how to implement this, code in question
    $this->filters = [
        'region' => $request->input('region', null),
        'name' => $request->input('name', null),
        'ip' => $request->input('ip', null)
    ];

    $this->filters = array_filter($this->filters, function ($v) {
        return !is_null($v);
    });

    // Set a sane SQL limit.
    $this->limit = 5;
    $this->request = $request;
}

И код контроллера:

/**
 * List all nodes.
 * 
 * @return [string] [JSON containing list of nodes, if sorted.]
 */
public function all()
{
    try {
        // use filters provided
        $data =  Nodes::limit($this->limit)->offset($this->offset);

        foreach ($this->filters as $filter => $value) {
            $data->where($filter, $value);
        }

        $data = $data->get();
        $response = $this->respond($data);
    } catch (\Exception $e) {
        $response = $this->respondServerError('Could not retrieve data from database.');
    }

    return $response;    
}

person Sergio E. Diaz    schedule 22.03.2016    source источник


Ответы (1)


Поэтому каждый раз, когда мне нужно выполнить фильтрацию списка ресурсов в API, вот как я это делаю.

Однако, прежде чем я начну, небольшой совет относительно получения объекта Request, когда вы находитесь в методе вашего контроллера: если вы добавите Request $request в качестве параметра для вашей функции all(), у вас будет доступ к переменной $request там, так же, как ваш конструктор. Таким образом, полная подпись будет public function all(Request $request). Методы контроллера имеют ту же магическую инъекцию зависимостей, что и другие конструкторы классов в Laravel/Lumen. Кроме того, в своей функции вы всегда можете попросить функцию app() предоставить вам объект определенного класса. Поскольку объект запроса привязан в контейнере только к «запросу», вы можете запросить полное имя класса или просто «запрос»: $request = app('request');

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

Вот пример функции для иллюстрации некоторых идей:

public function getIndex(Request $request)
{
    //Create a User object to append WHERE clauses onto
    $user = app('App\Models\User');

    //Run through our simple text fields
    foreach(['first_name', 'last_name', 'region', 'ip'] as $field) {
        if ($request->has($field)) {
            $user->where($field, $request->input($field));
        }
    }

    //This field uses a LIKE match, handle it separately
    if ($request->has('email')) {
        $user->where('email', LIKE, '%' . $request->input('email') . '%');
    }

    //This field is a list of IDs
    if ($request->has('id')) {
        $ids = explode(',', $request->input('id'));
        $user->whereIn('id', $ids);
    }

    //Use pagination
    $users = $user->paginate(25);

    /**
     * Continue with the rest of response formatting below here
     */
}

Вы заметите, что я использовал функцию разбиения на страницы, чтобы ограничить результаты. При создании конечной точки API со списком ресурсов вы захотите поместить свои заголовки (мое предпочтение) или информацию тела ответа о том, как получить первую, предыдущую, следующую и последнюю страницу результатов. Функция разбивки на страницы в Laravel упрощает эту задачу, так как она может создавать большинство ссылок с использованием метода links().

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

Итак, вот более полный пример записи параметров фильтра, чтобы их можно было добавить к ссылкам на страницы:

public function getIndex(Request $request)
{
    //Create a User object to append WHERE clauses onto
    $user = app('App\Models\User');

    //List of filters we found to append to links later
    $appends = [];

    //Run through our simple text fields
    foreach(['first_name', 'last_name', 'region', 'ip'] as $field) {
        if ($request->has($field)) {
            $appends[$field] = $request->input($field);
            $user->where($field, $request->input($field));
        }
    }

    //This field uses a LIKE match, handle it separately
    if ($request->has('email')) {
        $appends['email'] = $request->input('email');
        $user->where('email', LIKE, '%' . $request->input('email') . '%');
    }

    //This field is a list of IDs
    if ($request->has('id')) {
        $appends['id'] = $request->input('id');
        $ids = explode(',', $request->input('id'));
        $user->whereIn('id', $ids);
    }

    //Use pagination
    $users = $user->paginate(25);

    //Make sure we append our filter parameters onto the pagination object
    $users->appends($appends);

    //Now calling $users->links() will return the correct links with the right filter info

    /**
     * Continue with the rest of response formatting below here
     */
}

Документацию по разбивке на страницы можно найти здесь: https://laravel.com/docs/5.2/pagination.

В качестве примера того, как можно замечательно сделать ссылки на страницы, ознакомьтесь с документацией API Github: https://developer.github.com/v3/#pagination


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

Надеюсь, это поможет!

person stratedge    schedule 23.03.2016
comment
Спасибо друг! Действительно поставил меня на правильный путь. Отметить как отвеченный. - person Sergio E. Diaz; 23.03.2016