• Время чтения ~1 мин
  • 14.03.2023

Ответ из вашего приложения Laravel — это то, что я бы назвал жизненно важным, особенно когда вы создаете API. Давайте посмотрим, как мы можем усилить наши ответы.

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

return response()->json(
    data: [],
    status: 200,
);

Это немного преувеличенный пример; вы обычно отправляете данные и пропускаете код состояния. Однако для меня привычки умирают с трудом!

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

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

return new JsonResponse(
    data: [],
    status: 200,
);

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

return new JsonResponse(
    data: [],
    status: JsonResponse::HTTP_OK,
);

Класс JsonResponse расширяет класс Symfony Response через несколько слоев абстракции, чтобы вы могли вызвать это напрямую - однако ваш статический анализатор может пожаловаться на это. Я собрал пакет под названием juststeveking/http-status-code, PHP Enum, который будет возвращать что-то подобное, и его единственная работа заключается в возврате кодов состояния. Я предпочитаю этот более легкий подход к таким вещам, так как вы точно знаете, что происходит и что может сделать этот класс или пакет. Проблема иногда заключается в том, что класс, который вы используете, делает так много, что вам нужно загрузить эту огромную вещь в память, чтобы иметь возможность вернуть целое значение. Это не имеет большого смысла, поэтому я рекомендую использовать выделенный пакет или класс, чтобы управлять этим самостоятельно. Давайте посмотрим, как это выглядит, когда вы это сделаете:

return new JsonResponse(
    data: [],
    status: Http::OK->value,
);

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

Ответ довольно прост - Классы ответа. В Laravel есть контракт, который мы можем использовать под названием Responsable, который говорит нам, что наш класс должен иметь toResponse метод. Мы можем вернуть это непосредственно из наших контроллеров, так как Laravel будет анализировать и понимать эти классы без проблем. Давайте рассмотрим краткий базовый пример того, как выглядят эти классы:

class MyJsonResponse implements Responsable
{
    public function __construct(
        public readonly array $data,
        public readonly Http $status = Http::OK,
   ) {}
    public function toResponse($request): Response
    {
        return new JsonResponse(
            data: $this->data,
            status: $this->status->value,
        );
    }
}

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

class CollectionResponse implements Responsable
{
    public function __construct(
        public readonly JsonResourceCollection $data,
        public readonly Http $status = Http::OK,
    ) {}
    public function toResponse($request): Response
    {
        return new JsonResponse(
            data: $this->data,
            status: $this->status->value,
        );
    }
}

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

return new CollectionResponse(
    data: UserResource::collection(
        resource: User::query()->get(),
    ),
);

он чище, имеет меньше дублирования кода и легко переопределяет статус по умолчанию, если нам это нужно. Это дает нам преимущество, которое дали нам вспомогательные методы и класс Json Response, но позволяет нам больше контекста и предсказуемости.

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

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

trait SendsResponse
{
    public function toResponse($request): Response
    {
        return new JsonResponse(
            data: $this->data,
            status: $this->status->value,
        );
    }
}

Самая большая проблема с этим подходом заключается в том, что статический анализ будет жаловаться на этот код, потому что признак должен иметь или знать о свойствах класса. Тем не менее, это что-то вроде простого решения.

/**
 * @property-read mixed $data
 * @property-read Http $status
 */

Мы можем добавить этот блок doc в атрибут, чтобы он знал о свойствах, к которым у него есть доступ.

Теперь наши классы Response будут намного проще в использовании и сборке, с меньшим количеством повторений в нашем коде.

class MessageResponse implements Responsable
{
    use SendsResponse;

    public function __construct(
        public readonly array $data,
        public readonly Http $status = Http::OK,
    ) {}
}

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

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Об авторе CrazyBoy49z
WORK EXPERIENCE
Контакты
Ukraine, Lutsk
+380979856297