Подписывайтесь на наш Telegram канал и будьте в курсе всех событий.
Inn_100_gramm

Если вы видите это, значит, я еще не придумал, что написать.

Маршрутизация моделей в Laravel: автоматическое извлечение моделей по идентификатору

Одной из мощных возможностей Laravel является функционал под названием “Маршрутизация моделей” (Route Model Binding). Этот функционал позволяет автоматически извлекать модель из базы данных по ее первичному ключу, переданному в маршруте. Это упрощает код и улучшает читаемость и поддерживаемость приложения.

Пример использования

Рассмотрим пример, в котором мы хотим создать маршрут для отображения информации о курсе. Мы будем использовать контроллер для обработки запроса и извлечения данных модели Course.

Маршрут

Для начала определим маршрут в файле маршрутов:

Route::get('{course}', [CourseController::class, 'show'])->name('show');

Контроллер

Теперь создадим метод show в контроллере CourseController, который будет обрабатывать запросы к этому маршруту:

namespace App\Http\Controllers;

use App\Models\Course;
use App\Http\Resources\ShowResource;
use App\Handlers\ShowHandler;
use Illuminate\Http\JsonResponse;

class CourseController extends Controller
{
    public function show(Course $course, ShowHandler $handler): JsonResponse
    {
        $data = $handler->handle($course);

        return $this->response(new ShowResource($data));
    }
}

Как это работает

Когда маршрут вызывается с идентификатором модели, например /course/1, Laravel автоматически выполняет запрос к базе данных, чтобы найти модель Course с id, равным 1. Найденная модель будет передана в метод контроллера как параметр $course.

Внутри метода show мы можем использовать объект модели Course, так как он уже автоматически загружен Laravel. Далее, мы обрабатываем данные с помощью ShowHandler и возвращаем результат в виде JSON-ответа, используя ресурс ShowResource.

Обработка отсутствующих моделей

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

Пользовательские ключи

В некоторых случаях может понадобиться использовать другой ключ вместо id. Например, если вы хотите извлекать модель по полю slug, вы можете настроить это в модели Course.

Пример с пользовательским ключом

// В модели Course
public function getRouteKeyName()
{
    return 'slug';
}

После добавления этого метода, Laravel будет использовать поле slug для поиска модели вместо id. Теперь маршрут /course/some-slug будет искать курс с slug, равным some-slug.

Преимущества маршрутизации моделей

  1. Упрощение кода: Нет необходимости вручную извлекать модель из базы данных, это делает код контроллера чище и понятнее.
  2. Повышение безопасности: Автоматическая обработка отсутствующих моделей и возвращение ошибки 404 предотвращает возникновение ошибок.
  3. Гибкость: Возможность использования пользовательских ключей для извлечения моделей.

Заключение

Маршрутизация моделей в Laravel — это мощный инструмент, который упрощает работу с моделями в приложении. Автоматическое извлечение моделей по идентификатору, улучшенная читаемость кода и встроенная обработка ошибок делают этот функционал незаменимым для разработчиков.

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

0
Inn_100_gramm

Если вы видите это, значит, я еще не придумал, что написать.

Обработка зависших запросов в PostgreSQL

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

Проверка активных процессов и блокировок Первым шагом для диагностики проблем в базе данных является проверка активных процессов и блокировок. Для этого можно использовать следующий SQL-запрос:

SELECT pid, age(clock_timestamp(), query_start) AS duration, usename, query 
FROM pg_stat_activity 
WHERE state != 'idle' 
  AND pid != pg_backend_pid();

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

Принудительное завершение зависшей транзакции Если вы обнаружили процесс, который блокирует другие операции, его можно завершить принудительно. Для этого используйте следующий SQL-запрос:

SELECT pg_terminate_backend(pid);

Замените pid на идентификатор процесса, который необходимо завершить. Этот запрос завершит выбранный процесс и освободит ресурсы, которые он занимал.

Завершение всех зависших процессов Если необходимо завершить все зависшие процессы, кроме текущей сессии, можно использовать следующий запрос. Он завершит все процессы, которые находятся в состоянии, отличном от idle, и выполняются более 10 минут:

SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state != 'idle'
  AND pid != pg_backend_pid()
  AND query_start < NOW() - INTERVAL '10 minutes';

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

Заключение Управление зависшими запросами в PostgreSQL — важная задача для обеспечения стабильной и эффективной работы базы данных. С помощью приведенных выше SQL-запросов можно диагностировать и устранять проблемы, связанные с зависшими процессами, что поможет поддерживать высокую производительность и надежность системы.

0
Dmitry

https://pepperfm.ru

Стандартизация ответов API без трейтов

Проблема

Заметил я, что большая часть библиотек, созданных для апи респонса, реализованы через трейты, остальная часть — огромные библиотеки. В этих трейтах реализованы методы под всё, что только можно (response, accepted, created, forbidden…)

Таким образом, если в моём контроллере 1–2 метода, то, подключая такой трейт, я имею в классе кучу ненужного мусора. В паре больших библиотек на 700+ звёзд я вижу overengineering на уровне UX (для себя, как для пользователя библиотеки)

Что делать?

Написать свою библиотеку!

Easy api responder template using via DI

Я решил создать такую логику обработки данных, чтобы на пользовательском уровне требовалось:

  • минимум действий
  • имелась простота использования
  • читаемость

То есть для получение стандартизированного респонса, всё, что нам нужно, это вернуть респонс через объект библиотеки

composer require pepperfm/api-responder-for-laravel

Итого, базовый минимум, который у нас есть сразу после установки библиотеки, это:

Успешный ответ:

{
  response: {
    data: {
      entities,
      meta: []|{},
      message: 'Success'
    }
  }
}

Ответ с ошибкой:

{
  response: {
    data: {
      errors: null,
      message: 'Error'
    }
  }
}
public function __construct(public ResponseContract $json)
{
}

public function index(Request $request)
{
    $users = User::query()->get();

    return $this->json->response($users);
}

public function store(UserService $service)
{
    try {
        app('db')->beginTransaction();
        
        $service->update(request()->input());
        
        app('db')->commit();
    } catch (\Exception $e) {
        app('db')->rollback();
        logger()->debug($e->getMessage());

        return $this->json->error(
            message: $e->getMessage(),
            httpStatusCode: $e->getCode()
        );
    }


    return $this->json->response($users);
}

На выходе при успешном ответе имеем распаковку формата в виде: response.data.entities По-умолчанию формат актуален в контексте REST, то есть для методов show() и update() ответ будет формата: response.data.entity


Глубокое погружение

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

Возможности

Любимый нами сахар

Обёртка над response() метдом для пагинации:

/*
 * Generate response.data.meta.pagination from first argument of paginated() method  
 */
public function index(Request $request)
{
    $users = User::query()->paginate();

    return $this->json->paginated($users);
}

Метод paginated() принимает два основных параметра:

array|\Illuminate\Pagination\LengthAwarePaginator $data,
array|\Illuminate\Pagination\LengthAwarePaginator $meta = [],

В своей логике резолвит их и добавляет в ответ по ключу meta — ключ pagination

Интерфейсы ответа в соответствии с форматом, возвращаемым Laravel:

export interface IPaginatedResponse<T> {
    current_page: number
    per_page: number
    last_page: number
    data: T[]
    from: number
    to: number
    total: number
    prev_page_url?: any
    next_page_url: string
    links: IPaginatedResponseLinks[]
}
export interface IPaginatedResponseLinks {
    url?: any
    label: string
    active: boolean
}

В итоге ответ получается формата:

{
  response: {
    data: {
      entities,
      meta: {
        pagination: ...
      },
      message: 'Success'
    }
  }
}

Обёртка над response() метдом для кодов ответа:

public function store(UserService $service)
{
    ...
    // message: 'Stored', httpStatusCode: JsonResponse::HTTP_CREATED
    return $this->json->stored();
}

public function destroy()
{
    ...
    // message: 'Deleted', httpStatusCode: JsonResponse::HTTP_NO_CONTENT
    return $this->json->deleted();
}

Работа с разными типами параметра

Первый аргумент метода response() может быть типов array|Arrayable, поэтому можно маппить данные перед передачей в метод в рамках этих типов. К примеру:

public function index()
{
    $users = User::query()->paginate();
    $dtoCollection = $users->getCollection()->mapInto(UserDto::class);

    return resolve(ResponseContract::class)->paginated(
        data: $dtoCollection,
        meta: $users
    );
}
public function index()
{
    $users = SpatieUserData::collect(User::query()->get());

    return \ApiBaseResponder::response($users);
}

Кастомизация через конфиг

Сам конфиг:

return [
    'plural_data_key' => 'entities',

    'singular_data_key' => 'entity',

    'using_for_rest' => true,

    'methods_for_singular_key' => ['show', 'update'],

    'force_json_response_header' => true,
];
  • отключение using_for_rest оставляет возвращаемый формат всегда response.data.entities (мн. ч.) не зависимо от метода, из которого происходит вызов
  • с помощью methods_for_singular_key можно пополнить список методов, в которых будет возвращаться ключ в ед. ч.
  • methods_for_singular_key, собственно, добавляет заголовок в запросы по классике: $request->headers->set('Accept', 'application/json');

Кастомизация через атрибуты

Блокировка значений using_for_rest и methods_for_singular_key в конфиге для установки ключа ответа в соответствии с singular_data_key

#[ResponseDataKey]
public function attributeWithoutParam(): JsonResponse
{
    // response.data.entity
    return BaseResponse::response($this->user);
}

По аналогии можно передать своё назавние ключа

#[ResponseDataKey('random_key')]
public function attributeWithParam(): JsonResponse
{
    // response.data.random_key
    return BaseResponse::response($this->user);
}

В итоге

Осноанвя потребность закрыта: я хотел иметь возможность просто поставить библиотеку, и просто иметь из коробки лаконичный базис для стандартизации формата ответа. Без лишних движений.

И, конечно, как возможностей для лишних движений (кастомизация), так и ненасыпанного сахара — ещё очень много, так что в доработке библиотеки всё впереди) но основной посыл я точно сохраню! Ибо, как гласят великие мемы истории:

Красота в простате

0