Используем аннотации в 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/{id<\d+>}",
 *   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


Модели


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


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


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


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.

Поделиться публикацией

Комментарии 57

    +2
    Всё бы ничего, но где репозиторий и сервис? Писать в контроллер обновление модели — это то, чего делать не стоит. Давайте не лениться и давать людям скелеты, которые подходят для реальных приложений, а не CRUD на 5 страничек.
      0

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

      +1
      Как-то я из статьи не понял, кто и на каком этапе должен прожевать эти аннотации.
        +1

        Если говорить о том, что "прожевывает" аннотации, или точнее сказать "разбирает" их, то это doctrine/annotation. Если говорить о зонах ответственности, то за инъекции отвечает PHP-DI, за маршрутизацию Sunrise Router, за валидацию свойств моделей Symfony Validator, а за работу с БД и моделей в целом Doctrine ORM.

          +1
          После разбора что получается и куда оно идёт дальше?
        0
        Давно ищу информацию по чистым аннотациям как можно меньше всего завязанных на конкретный фрэймворк.
        Вы пишите:
        Аннотация @­Route может содержать следующие свойства:

        А где можно почитать подробную документацию по этой аннотации по всем свойствам которые она поддерживает. А так же какие еще есть аннотации.

        Когда я попытался разобраться, это оказалось не так просто. Файлы аннотаций встроены в пакеты Symfony Components и слабо документированы. Не могли бы вы дать пару ссылок на подробную документацию по аннотациям?
          0

          Конкретно работа с Doctrine Annotations описана достаточно хорошо в офф доке — https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html

            0
            А что насчет @­Route?

            И даже по вашей ссылке — нет там списка всех параметров и их форматов.
            То что я вижу на странице по вашей ссылке очень далеко от «достаточно хорошо».
          0

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

          +8

          Кода у вас меньше не стало, он просто уехал в phpdoc. Минусы аннотаций — программирование конфигов, не всегда есть поддержка IDE из-коробки, повышение связанности слоёв.


          Модели почему что-то знают о персистировании себя? Это — задача ОРМа из слоя инфраструктуры. Аннотации могут замарать слой бизнес-логики.


          Хотя с другой стороны конфиг, который используется вот только для этого экшена контроллера крайне удобен рядом с экшеном. Ок, допустим. Но тогда конфиг ли это? Я хочу разеюзать ваш код в другом проекте и для того, чтобы поменять что-то в конфиге мне надо лезть в… код! Как-то не реюзабельно получается. М.б. это имеет право на жизнь только в малых проектах, которые точно не реюзабельны?


          ps: У меня лично на аннотации аллергия ))

            +2
            Я вижу проблему несколько под другим углом. Аннотации прекрасно работают внутри фрэймворков. Зачастую фрэймфорки могут сами генерировать код PHP, читая какие-то конфиги, насколько я понимаю.

            Для меня основная проблема это использование этой фичи в самописных фрэймворках и проектах. Что-то у меня пока не получается прикрутить аннотации без того чтобы мне было всасывать тот же Doctrine в проект. Вот хочу я юзать Route, но я не вижу документации, я не понимаю, где я могу прочесть весь список того что эта аннотация поддерживает и кто ее поддерживает. Все примеры построены на том что все зависимости уже втянуты и состыкованы между собой магическим образом.
              0

              См. конструктор класса Route — там всё есть.


              https://github.com/sunrise-php/http-router/blob/6c537b045908e9f37c6ca9deeab41d51902d8cd7/src/Route.php


              Правда не понятно, почему нету use Sunrise\Http\Router\Route. Это уже магия нехорошая.

                0
                Посмотрел:
                /**
                	 * Constructor of the class
                	 *
                	 * @param string $id
                	 * @param string $path
                	 * @param string[] $methods
                	 */
                	public function __construct(string $id, string $path, array $methods)
                	{
                		$this->setId($id);
                		$this->setPath($path);
                		foreach ($methods as $method)
                		{
                			$this->addMethod($method);
                		}
                	}


                И ничего не увидел. Я спрашивал:
                но я не вижу документации, я не понимаю, где я могу прочесть весь список того что эта аннотация поддерживает и кто ее поддерживает


                Не вижу как это конструктор отвечает на мой вопрос.
                0

                Отсутствие use это не магия, это недостаток текущей версии Doctrine Annotations, который обещают исправить, если я сейчас не ошибаюсь, в 3 версии. Вся "магия", как вы выражаетесь, заключается в том, что при сборке ридера, я ему скармливаю автозагрузчик классов, таким образом, ридер знает как связать аннотацию с классом. Таким образом, нам нет необходимости писать лишнее, и включать в каждом контроллере аннотацию вручную.

                  +1

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


                  Это к тому, что если можно перебить класс аннотации, то конечному читателю кода эта магия опять же неочевидна будет.

                0

                Другой вариант, что аннотации дёргают setter'ы, а не конструктор. Опять магия, что есть зло.

                0
                Сами по себе аннотации ничего не делают, по факту это лишь типизированный и структурированный кусок текста, у которого есть возможность задавать поля и некоторая их валидация. Список полей и валидация — все это можно узнать посмотрев в код конкретной аннотации, это простой PHP объект, сам по себе он не несет никакой логики, это по сути своей VO.

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

                Например есть роутер симфони, который в качестве одного из источника роутов может считывать свои аннотации с класса и методов контроллера. Все что он делает — это с помощью одного из загрузчиков считывает аннотации, преобразовывает во внутренний формат и конфигурирует роутер. Аналогично с мэппингами доктрины, валидатором симфони и других компонент. Есть аннотация которая задает структуру и есть код, который эту структуру загружает во внутреннюю конфигурацию компонента с помощью специального загручика. Чаще всего он может это делать и из других форматов.

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

                У Zend Expressive есть "трюк", как использовать экшены к контроллере, это легко повторяется в Sunrise Router, так как специфика контроллера ± схожа.

                +9
                /**
                 * @Route(
                 *   id="resource.update",
                 *   path="/resource/{id<\d+>}",
                 *   methods={"PATCH"},
                 *   before={
                 *     "App\Http\Middleware\FooMiddleware",
                 *     "App\Http\Middleware\BarMiddleware"
                 *   },
                 *   after={
                 *     "App\Http\Middleware\BazMiddleware",
                 *     "App\Http\Middleware\QuxMiddleware"
                 *   }
                 * )
                 */
                


                Сорри, но это жесть. Половина кода в комментах…
                  –1

                  Аннотации можно как любить, так и наоборот. Кто-то строит маршрутизацию на массивах, кто-то напрямую через API (Sunrise Router имеет API), а можно по средствам аннотаций, тут как говорится, на вкус и цвет.

                    +1
                    К аннотациям я отношусь достаточно спокойно. И использую их, как-раз для роутинга. Вот, например, из предыдущего проекта:
                    /**
                    	 * @route /backend/users/add --method=get --name=backend/users/add
                    	 * @route /backend/users/edit/{id:\d+} --method[]=get --method[]=post --name=backend/users/edit
                    	 * @throws NotFound
                    	 */
                    


                    Но в вашем случае, в аннотации уже логика.
                  0

                  Аннтоации в PHP-DI удобны, в symfony/di неудобство начинается, когда необходимо внедрить скалярные параметры, либо когда необходимо добавить теги, на мой взглядь не помешало бы аннотации для них.
                  (да, можно самому сделать в компайл тайм, но хотелось бы из коробки, + поддержку IDE)




                  Жаль что rfc по аннотациям не приняли, пока неудобно писать свои аннотации, т.к. необходимо предусмотреть кеш, и поддержка IDE не будет.




                  Кстати, IDE поддерживает аннотации в PHP-DI?

                    0

                    На сколько мне известно, поддержки аннотаций в IDE у PHP-DI нету. Есть поддержка контейнера, но это другая история. Я задал вопрос Мэтью в гиттере, появится ли такая возможность, самому стало интересно.

                    +5

                    Я, конечно, извиняюсь, но внедрение зависимостей через @Inject не клева, как минимум, это нетестируемо и нарушает инкупсуляцию. В тестах придется постоянно делать обертки над классом, а самим классах использовать модификатор доступа не ниже, чем protected. Можно, конечно, и private, чтобы большую часть времени тратить на простыни с рефлексией… И я не говорю уже о IDE, которая подсветит это свойство, как неопределенное, и чтобы разрешить эти предупреждения нам придется искать какой-то плагин… З а ч е м? Мое мнение, внедрять зависимости очевидным способом через конструктор — здорово. А лучше вообще хранить конфиги отдельно от кода и начать дружить с принципом единственной ответственности.

                      0
                      Соглашусь, при таком подходе, искать по всему проекту, особенно если он не ваш, какой контроллер отвечает за конкретный адрес довольно не удобно. А Inject выглядить более-менее, если зависимость только 1, что неверно для большей части кода, в любом случае отсутсвие конструктора, выглядить всегда странно, иди потом объясни джуну что за магии тут твориться.
                        0
                        Тоже поддержу. Даже отойдя от темы аннотаций, видеть все зависимости класса явно — одно из условий «хорошего» кода. Причем это опять же «универсально» (если уж мы стремимся к поддержке psr), так как код с конструкторами получается фреймворк агностик.

                        Зачем писать middleware тоже не понял. Очень удобно в каждом контроллере это копипастить и еще удобнее рефакторить (НЕТ!).
                          +1
                          Аспектно-ориентированное внедрение зависимостей тем и хорошо, что позволяет сократить визуальный шум.
                            0
                            Это было бы так, если бы аннотации были частью языка. Если бы эти условные inject подразумевали неявно объявленный конструктор. В PHP, к сожалению, это не так.
                              0
                              В PHP аннотации суть комментарии, в языке же аннотаций, атрибутов и т.п. нет.
                              Отчасти причиной является его динамическая природа, которая даже файлы загружает динамически. И рефлексия у него ограничивается по этой же причине достаточно примитивными вещами.
                              0
                              Вы конструкторы визуальным шумом называете или что?
                                0
                                Нет, сквозную функциональность: логирование, проверка доступа, конфигурация, локализация, обращение к источникам данных и т.п.
                          0

                          Почему используется именно sunrise/http-router?


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

                            0
                            Поддерживаю автора в пользу аннотаций, только примеры в статье не особо подходящие. И сеттинг данных из запроса можно делать аннотациями в DTO, Entity… Мое виденье.
                              0
                              Если и использовать аннотации по-максимуму, то нужно как минимум не забыть про AOP, а именно Transactional аннотацию. Опыт использования в Spring исключительно положительный.
                                +3
                                Под минимализмом я подразумеваю простую истину: код должен быть максимально «прозрачным», его должно быть минимум

                                Поэтому давайте добавим в него магии! Вжух! Вот вам мешок как-бы аннотаций (которые, на самом деле, просто распарсенные комментарии)!.. Ну ладно, я пошел, а как оно работает — сами разбирайтесь.

                                  +1
                                  Хотелось бы сказать спасибо автору. Узнал много нового.
                                    0

                                    изыди!
                                    публично пороть надо за использование и тем более пропаганду таких "трюков" как аннотации

                                      +4

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

                                        +1
                                        Ну как же! Главное преимущество — написать правильный Hello World. Теперь с аннотациями.
                                        Ещё можно добавить в резюме: Уверенный пользователь «PHP Comments».
                                          0
                                          Я могу понять ваш сарказм. Но если девелопер сможет написать «Hello World» на PHP c нуля написав MVC свои классы, без внешних зависимостей, да еще и через аннотации за 2 часа на интервью — я его сразу порекомендую на синьора или даже лида в любой проект на PHP.
                                            0

                                            Вспоминается знаменитый троллейбус.


                                            написав MVC свои классы, без внешних зависимостей, да еще и через аннотации за 2 часа на интервью

                                            Но зачем? По-моему, если предлагают такой хренью заниматься на собеседовании — лучше срау уходить.

                                              +1
                                              Смотря на какую позицию и смотря как это происходит.
                                              0
                                              А что тут сложного?
                                                +2
                                                Если вам не сложно, потратьте 2 часа своего времени и выполните. Не в службу, а в дружбу. Положите на гитхаб. Я буду очень, очень, очень признателен. Мне реально очень помогло бы для моей работы.

                                                Возможно кто-то тут тоже поддержит мой запрос.

                                                Ключевое:
                                                1. Ноль внешних зависимостей.
                                                2. MVC
                                                3. Модели определяют свои поля через аннотации
                                                4. Контроллеры определяюит свои routes, action и их параметры через аннотации


                                                Это что-то, что я ожидал увидеть здесь, согласно заголовку. Но оказалось, к сожалению, что снова слишком много composer required чтобы разобраться в механизмах магии.

                                                Так что если для вас это «запросто, за 2 часа», то я был бы очень признателен.
                                                  0

                                                  С вас нормальное ТЗ, тогда можно будет начать обсуждать сроки (вы же понимаете, что за два часа это будет именно "Hello world"?) и оплату (да, мне жалко тратить время на не нужную мне вещь и реализовывать порочные, с моей точки зрения, практики, чтобы кому-то что-то доказать).
                                                  На вскидку:
                                                  Пункты 1, 3, 4 реализуются через наследование, token_get_all и, в зависимости от хотелок, рефлексию.
                                                  По поводу пункта 2 только на хабре было множество статей, объясняющих "что же это такое на самом деле". И каждая по своему. Вам какой вариант больше нравится?

                                                    0
                                                     Спасибо, за деньги мне не надо. Направлять куда смотреть, тоже. Спасибо я умею пользоваться гуглом и читать. Я вас именно на слове пытался поймать и может быть закрыть некие пробелы в своем образовании.

                                                    ТЗ на мой взгляд достаточное. Свобода у разработчика данного задания на собеседовании полная. Меня бы устроил любой вариант реализации.

                                                    вы же понимаете, что за два часа это будет именно «Hello world»?


                                                    Да я понимаю. И именно это я и имел в виду. И, извините, я подозреваю даже для вас, если вы и понимаете как это сделать архитектурно и в коде, 2 часа точно мало, а возможно и 4 мало.

                                                    Так что моя просьба о помощи, скорее была в поддержку того, что задачка далеко не тривиальная. И ответ Вам: «а чего тут сложного». Сложно то что никто кроме Вас пока даже не заявил что это просто. А вы затребовали деньги и ТЗ.

                                                    Скажу за себя, я бы смог наверное сделать это все на коленке но без аннотаций. И у меня было бы ноль магии.
                                                      +1
                                                      Я вас именно на слове пытался поймать

                                                      Ну вот не получилось :)

                                                      При полной свободе интерпретации ТЗ на написание уровня «фигак-фигак, здравствуй мир» пары часов должно хватить, плюс/минус. Задача, сама по себе, не подразумевает каких-то уникальных знаний и умений, но она, скажем так, объемная и мало что может показать в плане собеседования именно потому, что за 2 часа это будет именно «фигак-фигак».

                                                      PS Не скажу за весь остальной мир, но то, каким образом реализуют аннотации в PHP — это костыль в велосипедном колесе, бессмысленный и беспощадный.
                                                        –4
                                                        Ну вот не получилось :)

                                                        Не согласен.
                                                        Пусть народ голосует:

                                                        если согласны со мной, что получилось и задача не тривиальная — голос за этот коммент

                                                        Если согласны с Вами, и что я ничего не показал, а задачка «не сложная» — голос за ваш предыдущий коммент

                                                        Спорить тут, согласен, особо не о чем. Все остались при своих мнениях.
                                          +6
                                          ИМХО!
                                          аннотации != «код которого нет»
                                          аннотации == «код который не понимает ни один редактор» (это все равно что взять блокнот и писать код в нем)

                                          как итог ни нормальной подсветки синтаксиса, ни подсказок и когда упадет будешь очень долго и нудно ползать по коментариям и разгребать
                                            +2
                                            справедливости ради шторм их прекрасно понимание (с помощью плагина, но тем не менее) и с тем же автокомплитом проблем никаких нет.
                                            +4
                                            «А здесь мы прикрутим аннотации, чтобы ты мог писать код, даже когда ты пишешь комментарии»

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

                                            Самое читаемое