Главная » Хабрахабр » [Из песочницы] Используем аннотации в PHP по максимуму

[Из песочницы] Используем аннотации в PHP по максимуму

При разработке, чего бы то ни было, я придерживаюсь одного простого принципа – минимализм. Будучи back-end разработчиком, я всеми фибрами своей души люблю микросервисные архитектуры, но еще больше, люблю разрабатывать микросервисы. Под минимализмом я подразумеваю простую истину: код должен быть максимально «прозрачным», его должно быть минимум (идеальный код – код которого нет), а посему, я делаю ставку в пользу аннотаций.

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

Такой скелет будет основан на следующих пакетах:

Также, такой скелет, будет основан на пакетах придерживающихся следующих PSR-рекомендаций:

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

Лень читать, что там?

composer create-project sunrise/awesome-skeleton app

wheel bike

Контроллеры

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

Маршрутизация в контроллерах

Рассмотрим пример ниже:

declare(strict_types=1);
namespace App\Http\Controller; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface; /** * @Route( * id="resource.update", * path="/resource/", * methods={"PATCH"}, * before={ * "App\Http\Middleware\FooMiddleware", * "App\Http\Middleware\BarMiddleware" * }, * after={ * "App\Http\Middleware\BazMiddleware", * "App\Http\Middleware\QuxMiddleware" * } * ) */
class ResourceUpdateController implements MiddlewareInterface
{ /** * {@inheritDoc} */ public function process( ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface { $response = $handler->handle($request); // some code return $response; }
}

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

Аннотация @­Route может содержать следующие свойства:

  • id – ID маршрута
  • path – правило пути маршрута
  • methods – допустимые HTTP методы маршрута
  • before – промежуточные ПО которые запустятся перед запуском контроллера
  • after – промежуточные ПО которые запустятся после запуска контроллера

Путь маршрута содержит привычную для всех конструкцию {id}, однако для валидации атрибута необходимо задать регулярное выражение {id<\d+>}, а если необходимо сделать часть пути необязательной, ее достаточно взять в скобки /resource/{action}(/{id}).

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

Инъекция зависимостей в контроллерах

Рассмотрим пример ниже:

declare(strict_types=1);
namespace App\Http\Controller; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface; /** * @Route( * id="resource.update", * path="/resource/{id<\d+>}", * methods={"PATCH"} * ) */
class ResourceUpdateController implements MiddlewareInterface
{ /** * @Inject * @var LoggerInterface */ protected $logger; /** * {@inheritDoc} */ public function process( ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface { $this->logger->debug('foo bar'); $response = $handler->handle($request); // some code return $response; }
}

Нет ничего проще, чем получить из контейнера ту или иную зависимость...

Регистрация контроллера в приложении

Главное, что от вас требуется из коробки, создавать контроллеры в директории src/Http/Controller. Вам достаточно просто создать контроллер, остальное приложение сделает за вас, обнаружит такой контроллер, передаст его маршрутизатору, который запустит его при необходимости… Что может быть проще?

wheel bike

Модели

Сразу необходимо обозначить, что модели должны создаваться в директории src/Entity и наследовать App\Entity\AbstractEntity. Если вы работали с Doctrine ORM и Symfony Validator, для вас ничего интересного, за исключением того, что все настроено из коробки, для остальных представлю некоторые примеры.

Простой пример модели

Рассмотрим пример ниже:

declare(strict_types=1);
namespace App\Entity; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert; /** * @ORM\Entity * @ORM\HasLifecycleCallbacks * @ORM\Table(name="resource") */
class Resource extends AbstractEntity
{ /** * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") * @ORM\Column(type="integer") * * @var null|int */ protected $id; /** * @ORM\Column( * type="string", * length=128, * nullable=false * ) * * @Assert\NotBlank * @Assert\Type("string") * @Assert\Length(max=128) * * @var null|string */ protected $title; /** * @ORM\Column( * type="text", * nullable=false * ) * * @Assert\NotBlank * @Assert\Type("string") * * @var null|string */ protected $content; // setters and getters
}

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

composer db:update

Настройки подключения к базе данных находятся в файле: src/config/environment.php

Простой пример использования модели в контроллере

declare(strict_types=1);
namespace App\Http\Controller; use App\Entity\Resource;
use Doctrine\ORM\EntityManager;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface; /** * @Route( * id="resource.create", * path="/resource", * methods={"POST"} * ) */
class ResourceCreateController implements MiddlewareInterface
{ /** * @Inject * * @var EntityManager */ protected $entityManager; /** * {@inheritDoc} */ public function process( ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface { $data = (array) $request->getParsedBody(); $response = $handler->handle($request); $resource = new Resource(); $resource->setTitle($data['title'] ?? null); $resource->setContent($data['content'] ?? null); $violations = $resource->validate(); if ($violations->count() > 0) { return $response->withStatus(400); } $this->entityManager->persist($resource); $this->entityManager->flush(); return $response->withStatus(201); }
}

Аннотация @­Assert отвечает за валидацию, сама логика валидации описана в наследуемом моделью классе AbstractEntity.

Реализация несет демонстрационный характер, целью автора является сократить кол-во строк кода...

wheel bike

Настройки приложения

Файл

Описание

config/cli-config.php

Doctrine CLI

config/container.php

PHP-DI

config/definitions.php

Зависимости приложения

config/environment.php

Конфигурация окружения приложения

Свои зависимости

Для добавления новой зависимости в приложение, достаточно открыть файл config/definitions.php, и добавить новую зависимость по аналогии с уже существующими, после чего она станет доступна через инъекции, как в примерах настоящей статьи.

Рекомендации

После установки скелета, рекомендуется добавить файл config/environment.php в .gitignore, а сам файл как пример продублировать с новым именем:

cp config/environment.php config/environment.php.example

Можно пойти другим путем, заполнив настройки окружения из .env задействовав пакет symfony/dotenv.

wheel bike

В игру «зачем писать это, когда есть это» можно играть бесконечно, но как бы там ни было, open-source все стерпит...

Для установки скелета воспользуйтесь следующей командой:

composer create-project sunrise/awesome-skeleton app

Для изучения исходного кода скелета воспользуйтесь ссылкой: sunrise/awesome-skeleton.

Отдельное спасибо хочется выразить Оскару Отеро за его вклад в open-source, в особенности за прекрасную подборку Awesome PSR-15 Middleware, часть из которой интегрирована в sunrise/awesome-skeleton.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

WebAssembly в продакшне и «минное поле» Smart TV: интервью с Андреем Нагих

Разработка приложений под Smart TV — тоже «нетипичный JavaScript», когда все слышали о чём-то, но немногие лично пробовали. Интерес к WebAssembly велик, но пока что нечасто встретишь людей, использующих эту технологию в рабочем проекте. TV, а в последние месяцы так ...

[Перевод] Ethereum планирует стать на 99% экономичней

Криптовалюта скоро сядет на энергетическую диету, чтобы конкурировать с более эффективными блокчейнами На фоне ажиотажа вокруг Биткоина его «младший брат» Ethereum отошел в тень. Но проект с рыночной капитализацией около 10 млрд долларов вряд ли можно считать незаметным. И объемы ...