Русский
Русский
English
Статистика
Реклама

Symfony

Перевод Новое в Symfony инициатива UX новая экосистема JavaScript для Symfony

19.01.2021 14:16:27 | Автор: admin

С момента своего создания JavaScript всегда был ориентирован на создание инновационного пользовательского опыта (UX - User Experience). Он позволяет разработчикам создавать максимально интуитивно понятный и удобный UX под конкретную задачу.

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

Эта проблема не нова: она очень похожа на состояние Symfony в PHP до появления Symfony Flex. Нам нужен эквивалент Symfony Flex для JavaScript - инструмент способный создавать прекрасный пользовательский опыт так же быстро, как теперь мы можем настроить HTTP-клиент, почтовую программу или панель администрирования.

Представляем вам Symfony UX.

Symfony UX: создание высокоинтерактивных приложений с с использованием китов JavaScript

Symfony UX - это набор инструментов, который возводит мост между Symfony и экосистемой JavaScript. Он стоит на плечах таких гигантов JavaScript, как npm, Webpack Encore и Stimulus.

Symfony UX не привязан к какой-либо конкретной структуре JavaScript: вы все равно можете использовать React, Vue или Angular, если они вам по душе. Это opinionated software, которое берет за основу стандартный HTML и может постепенно внедряться в существующих приложениях.

Symfony UX состоит из трех основных компонентов:

  1. Интеграции Symfony для Stimulus, обеспечивающая новую организацию JavaScript-кода в приложениях;

  2. Обновления Symfony Flex и Symfony Webpack Encore для автоматической настройки JavaScript-кода, поставляемого PHP-пакетами;

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

Пример: отображение диаграмм в PHP с использованием Symfony UX Chart.js

Хорошим примером возможностей Symfony UX является новый компонент Symfony UX Chart.js. Этот компонент внутренне полагается на библиотеку Chart.js.

Чтобы использовать Symfony UX вам сначала нужно обновить свои зависимости Symfony Flex и Webpack Encore:

composer update symfony/flexyarn upgrade "@symfony/webpack-encore@^0.32.0"

И синхронизировать свои рецепты с последней версией:

composer recipes:install symfony/webpack-encore-bundle --force -v

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

После обновления Symfony Flex будет реагировать на каждый установленный вами PHP-пакет, содержащий JavaScript-код. Например, теперь вы можете установить компонент Chart.js:

$ composer require symfony/ux-chartjs
Using version ^1.0 for symfony/ux-chartjs./composer.json has been updatedSynchronizing package.json with PHP packagesDon't forget to run npm or yarn to refresh your Javascript dependencies!...

(Текст сообщения: Синхронизация package.json с PHP-пакетами

Не забудьте запустить npm или yarn, чтобы обновить Javascript-зависимости!)

Symfony Flex только что синхронизировал файл package.json вашего проекта с установленным вами PHP-пакетом Chart.js. Теперь вы можете найти в этом файле новый JavaScript-модуль:

// package.json{    "devDependencies": {        ...        "@symfony/ux-chartjs": "file:vendor/symfony/ux-chartjs/Resources/assets"    }}

Symfony Flex также обновил новый файл assets/controllers.json в вашем проекте. Этот файл ссылается на все контроллеры Stimulus, предоставляемые установленными пакетами Symfony UX, чтобы Webpack Encore мог добавлять их в ваши встроенные JavaScript-файлы:

// assets/controllers.json{    "controllers": {        "@symfony/ux-chartjs": {            "chart": {                "enabled": true,                "webpackMode": "eager"            }        }    },    "entrypoints": []}

Из-за этих изменений теперь вам следует установить новые JavaScript-зависимости и скомпилировать новые файлы:

yarn install --forceyarn encore dev

И вот и все! Благодаря Symfony Flex, Symfony UX Chart.js был установлен и настроен одновременно как пакет Symfony в PHP и как библиотека JavaScript, скомпилированная во встроенные файлы вашего приложения.

Это означает, что теперь с помощью этого пакет вы можете построить диаграмму:

// ...use Symfony\UX\Chartjs\Builder\ChartBuilderInterface;use Symfony\UX\Chartjs\Model\Chart;class HomeController extends AbstractController{    /**     * @Route("/", name="homepage")     */    public function index(ChartBuilderInterface $chartBuilder): Response    {        $chart = $chartBuilder->createChart(Chart::TYPE_LINE);        $chart->setData([            'labels' => ['January', 'February', 'March', 'April', 'May', 'June', 'July'],            'datasets' => [                [                    'label' => 'My First dataset',                    'backgroundColor' => 'rgb(255, 99, 132)',                    'borderColor' => 'rgb(255, 99, 132)',                    'data' => [0, 10, 5, 2, 20, 30, 45],                ],            ],        ]);        $chart->setOptions([/* ... */]);        return $this->render('home/index.html.twig', [            'chart' => $chart,        ]);    }}

После создания в PHP диаграмму можно отобразить с помощью Twig:

{{ render_chart(chart) }}{# You can pass HTML attributes as a second argument to add them on the <canvas> tag #}{{ render_chart(chart, {'class': 'my-chart'}) }}

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

Откройте для себя все пакеты Symfony UX на github.com/symfony/ux!

Вы также можете ознакомиться со слайдами с презентации Фабьена и доклада Тетуана, чтобы получить более полное представление о инициативе!

Давайте строить замечательную экосистему вместе

Symfony UX - это инициатива: ее цель - построить экосистему. Для этого нам нужна ваша помощь: какие еще пакеты мы могли бы создать в Symfony UX? А как насчет библиотеки, которая автоматически добавляет маску ввода в текстовые поля ваших форм Symfony? Или возможность сделать EntityType рендеринг с автозаполнением AJAX? Все, что вы делаете в JavaScript, можно организовать как UX-пакет.

У нас есть еще несколько идей, и в ближайшее время мы выпустим больше пакетов. Остальное на вас: давайте вместе создавать замечательную экосистему!


А прямо сейчас приглашаем всех желающих записаться на бесплатный демо-урок курса Symfony Framework по теме: "Микрофреймворки: сравнение производительности Symfony и Symlex".


Подробнее..

Перевод Блеск и нищета open source платформы RawCMS. Причины провала и выводы

23.04.2021 18:05:42 | Автор: admin

Я люблю открытое ПО. Я начал разрабатывать сторонний проект с открытым исходным кодом в 2006 году, и это был секрет развития моей карьеры. Благодаря моим экспериментам того времени, надеюсь, я вырос как разработчик и возвращаю что-то сообществу Open Source. По-моему мнению, открытый исходный код это драйвер роста компаний и разработчиков. И сегодня я хочу рассказать о своём опыте начавшейся в 2018 году работы над платформой low-code с открытым исходным кодом под названием RawCMS.


Провал

RawCMS начиналась как сторонний проект по улучшению будущего Asp.net core 3.1 и изучению возможности работы с неструктурированными данными для ускорения процесса разработки.

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

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

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

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

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

Вся эта сложность пугала разработчиков на подходе.

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

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

Окончательный вывод: для наших нужд мы выбрали худшую технологию.

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

Как я смог за два дня сделать то же, что за два года?

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

Подходящий инструмент

Я не опоздал с .Net. Работаю с платформой с 2005 года, реализовал множество крупных корпоративных проектов, и меня приглашали на множество конференций, чтобы я поделился своим опытом.

Но я просто подобрал не тот инструмент для стороннего проекта. Я по-прежнему считаю, что .Net вероятно, лучший фреймворк для корпоративных приложений, а язык C# не имеет себе равных, с точки зрения производительности и впечатлений от разработки.

Проблема заключалась вот в чём: я выбрал .NET только потому, что это был фреймворк, в котором я чувствую себя более уверенно. Для меня выбор .Net был возможностью успеха. И я ошибался.

С тех пор как я начал этот проект, мне пришлось справляться с некоторыми функциями, которые .Net не поддерживает нативно; каждая отдельная проблема была преодолением.Я говорю об управлении нетипизированными данными (в частности маппинг и GraphQL), имея плагинную архитектуру и встраивая в неё модульный SPA.

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

Упорствовать вошибке от лукавого

Ошибки от человека, но упорство в ошибке от лукавого. Пытаясь заставить всё работать, я понял это на собственном опыте. Для меня это был важный урок.Сдаться перед огромной проблемой не всегда проявление слабости, а иногда, может быть, доказательство того, что вы умны. Я не остановился перед трудностями и победил зверя, но что потом?

Цена проделанной работы, как и её трудность, была огромной, я имею в виду, что много времени и усилий потрачено впустую, за исключением замечательного опыта и компетенций, которые позволили мне (виртуально) путешествовать по миру и рассказывать свою историю.

Что я понял? Вместе нам никогда не приходилось сдаваться перед проблемой, но столкнувшись со сложностью один на один...

Спросите себя, в чём проблема в самой проблеме или в вас?

Ещё до кодинга расскажите, как видите продукт

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

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

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

Этого бы не произошло, если бы система была проще и была основана на популярном фреймворке, таком как Symphony, с сообществом, где разработчики смогли бы найти ответы на свои вопросы.

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

Выводы

Мне по-прежнему нравится вносить свой вклад в мир открытого исходного кода. Думая о потраченном на RawCMS времени, на самом деле я не чувствую, что оно вообще потеряно.

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

И вот какую пользу я извлёк из этого опыта:

  • Многое узнал о современных технологиях, что умножает мою ценность как профессионала.

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

  • Помог начинающим разработчикам сделать первый шаг.

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

  • Вернул что-то сообществу открытого ПО.

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

Но ценный опыт можно получить и проще, например прокачав себя в разработке на направлении C#-разработчик. На нём можно не только повысить свою квалификацию, но и пообщаться с опытными менторами, которые разъяснят непонятные моменты.

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Перевод Symfony Messenger объединение сообщений в пакеты

01.06.2021 18:16:36 | Автор: admin

Иногда требуется сделать так, чтобы сообщения в Symfony Messenger отправлялись потребителю пакетами, а не поодиночке. Недавно нам потребовалось отправлять через Messenger обновленные строки текста из наших программ поставщику переводческих услуг.

Но из-за жесткого ограничения на интенсивность передачи данных со стороны переводческой фирмы мы не можем отправлять сообщения по одному. Следовательно, необходимо реализовать следующий алгоритм отправки: сперва сохранять все полученные сообщения, предназначенные данному потребителю, а затем отправлять все сообщения, если время ожидания новых сообщений превысило десять секунд или если сохранено более 100 сообщений.

Покажем, как мы это сделали:

// Symfony Messenger Message:class TranslationUpdate{    public function __construct(        public string $locale,        public string $key,        public string $value,    ) {    }}
class TranslationUpdateHandler implements MessageHandlerInterface{    private const BUFFER_TIMER = 10; // in seconds    private const BUFFER_LIMIT = 100;    private array $buffer = [];    public function __construct(        private MessageBusInterface $messageBus,    ) {        pcntl_async_signals(true);        pcntl_signal(SIGALRM, \Closure::fromCallable([$this, 'batchBuffer']));    }    public function __invoke(TranslationUpdate $message): void    {        $this->buffer[] = $message;        if (\count($this->buffer) >= self::BUFFER_LIMIT) {            $this->batchBuffer();        } else {            pcntl_alarm(self::BUFFER_TIMER);        }    }    private function batchBuffer(): void    {        if (0 === \count($this->buffer)) {            return;        }        $translationBatch = new TranslationBatch($this->buffer);        $this->messageBus->dispatch($translationBatch);        $this->buffer = [];    }}

Здесь мы имеем дело с сообщением Messenger, которое отправляется каждый раз, когда у нас появляется обновленный текст на перевод (тот же принцип можно применить и к любым другим сообщениям).

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

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

Для обработки системных сигналов в нашем PHP-коде мы используем функции PCNTL (прочитать о них подробнее можно в документации PHP, а также в нашем блоге, если владеете французским). Мы установили таймер, который будет посылать процессу сигнал SIGALRM через заданное количество секунд. Затем, когда сигнал будет принят процессом, запустится функция обратного вызова, которую мы указали в качестве второго аргумента pcntl_signal. Обратный вызов установлен для всего приложения, поэтому мы можем использовать этот трюк с объединением сообщений в пакеты только один раз.

Затем в методе batchBuffer мы используем новую передачу в Messenger (см. вызов dispatch), чтобы отслеживать сообщения на случай возникновения проблем, а поскольку метод реализован через PCNTL, компонент Messenger не будет повторять попытку обработки при исключении.

class TranslationBatch{    /**     * @param TranslationUpdate[] $notifications     */    public function __construct(        private array $notifications,    ) {    }}
class TranslationBatchHandler implements MessageHandlerInterface{    public function __invoke(TranslationBatch $message): void    {      // handle all our messages    }}

Итак, теперь у нас есть обработчик пакетов, который всегда будет получать список сообщений для отправки. С его помощью мы можем легко объединять наши сообщения Messenger в пакеты, не прибегая к использованию cron.

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


Перевод материала подготовлен в рамках курса "Symfony Framework". Всех желающих приглашаем на двухдневный онлайн-интенсив Создание системы статистики для онлайн-магазина. На интенсиве мы:
начнем знакомство с Symfony и ClickHouse (если точнее, то построим систему сбора статистики в ClickHouse). На базе подобной системы в будущем вы сможете строить и развивать решения Business Intelligence-систем и операционной статистики,
затем развернем API,
и с его помощью посмотрим на инструменты самой статистики.
Регистрация на первый день здесь.

Подробнее..

Перевод Развертывание приложения Symfony в AWS Lambda

11.06.2021 18:10:18 | Автор: admin

Сначала давайте разберемся, что такое бессерверная архитектура и когда она нужна.

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

AWS Lambda обеспечивает высокую доступность, причем плата взимается только за фактически затрачиваемое время вычислений. Этот сервис может быть весьма полезен для таких задач, как запуск cron-заданий, отправка уведомлений в режиме реального времени, предоставление доступа к API, обработка каких-нибудь событий при выполнении различных операций и т.д. В сети можно найти массу примеров использования сервиса.

Наш сценарий использования

Предоставить доступ к API, созданному с помощью Symfony, который публикует сообщения в LinkedIn. Процесс разработки будет включать этапы от написания до развертывания кода.

Пишем код

В Symfony 5-й версии появился новый компонент под названием Notifier, который дает возможность отправлять уведомления через разные сервисы (Slack, Twitter, Twilio и др.).

В Symfony нет встроенной поддержки LinkedIn, поэтому несколько месяцев назад я создал поверх Notifier шлюз для публикации контента в этой социальной сети. Исходный код шлюза можно посмотреть по ссылке, его же мы будем использовать и в этой демонстрации.

Приступаем

$ symfony new --full aws-lambda-linkedin-notifier$ cd aws-lambda-linkedin-notifier$ composer require eniams/linkedin-notifier

Включаем шлюз (см. документацию)

<?php// config/bundles.phpreturn [// others bundles,Eniams\Notifier\LinkedIn\LinkedInNotifierBundle::class => ['all' => true]];// .envLINKEDIN_DSN=

Логика публикации контента

<?phpclass PostContentController{    /**     * @Route("/contents", name="post_content", methods="POST")     */    public function __invoke(NotifierInterface $notifier, Request $request)    {        if(null !== $message = (\json_decode($request->getContent(), true)['message'] ?? null)) {            $notifier->send(new Notification($message, ['chat/linkedin']));            return new JsonResponse('message posted with success', 201);        }        throw new BadRequestException('Missing "message" in body');    }}

Логика проста: мы предоставляем доступ к API через маршрут /contents, который принимает запрос POST с сообщением message в его теле.

В 11-й строке мы отправляем публикуемое сообщение в LinkedIn благодаря Symfony и шлюзу это делается очень просто!

Будучи профессиональными разработчиками, покроем этот код автотестами:

<?phpclass PostContentControllerTest extends WebTestCase    /**     * @dataProvider methodProvider     */    public function testANoPostRequestShouldReturnA405(string $method)    {        $client = static::createClient();        $client->request($method, '/contents');        self::assertEquals(405, $client->getResponse()->getStatusCode());    }    public function testAPostRequestWithoutAMessageInBodyShouldReturnA400()    {        $client = static::createClient();        $client->request('POST', '/contents');        self::assertEquals(400, $client->getResponse()->getStatusCode());    }    public function testAPostRequestWithAMessageInBodyShouldReturnA201()    {        $request = new Request([],[],[],[],[],[], json_encode(['message' => 'Hello World']));        $notifier = new class implements NotifierInterface {            public function send(Notification $notification, Recipient ...$recipients): void            {            }        };        $controller = new PostContentController();        $response = $controller->__invoke($notifier, $request);        self::assertEquals(201, $response->getStatusCode());    }    public function methodProvider()    {        return [            ['GET'],            ['PUT'],            ['DELETE'],        ];    }view rawTestPostContentController.php hosted with  by GitHub

Код готов! Пришло время его развернуть

Стоп! Что?! AWS Lambda не поддерживает PHP!

Именно так: AWS Lambda поддерживает не все языки программирования, а только некоторые, в том числе Go, Java, Python, Ruby, NodeJS и .NET.

Теперь надо учить новый язык и переписывать код? Надеюсь, нет!

Ничего переписывать не придется благодаря Матье Напполи (Matthieu Nappoli), создателю Bref.sh, и замечательной команде, помогающей ему поддерживать этот проект с открытым исходным кодом.

Bref позволяет развертывать PHP-приложения в AWS и запускать их на AWS Lambda.

Конфигурация развертывания

Добавим в kernel.php обработку логов:

<?php    // Kernel.php    public function getLogDir(): string    {        if (getenv('LAMBDA_TASK_ROOT') !== false) {            return '/tmp/log/';        }        return parent::getLogDir();    }    public function getCacheDir()    {        if (getenv('LAMBDA_TASK_ROOT') !== false) {            return '/tmp/cache/'.$this->environment;        }        return parent::getCacheDir();    }

Подготовим фреймворк Serverless (см. документацию):

$ npm install -g serverless$ serverless config credentials --provider aws --key  --secret$ composer require bref/bref

Зададим конфигурацию в файле serverless.yaml:

service: notifier-linkedin-apiprovider:    name: aws    region: eu-west-3    runtime: provided    environment: # env vars        APP_ENV: prod        LINKEDIN_DSN: YOUR_DSNplugins:    - ./vendor/bref/breffunctions:    website:        handler: public/index.php # bootstrap         layers:            - ${bref:layer.php-73-fpm} # https://bref.sh/docs/runtimes/index.html#usage         timeout: 28 # Timeout to stop the compute time        events:            - http: 'POST /contents' # Only POST to /contents are allowedpackage:    exclude:        - 'tests/**'view rawserverless.yaml hosted with  by GitHub

Развертываем!

$ serverless deployServerless: Packaging service...Serverless: Excluding development dependencies...Serverless: Uploading CloudFormation file to S3...Serverless: Uploading artifacts...Serverless: Uploading service notifier-linkedin-api.zip file to S3 (10.05 MB)...Serverless: Validating template...Serverless: Updating Stack...Serverless: Checking Stack update progress.......................Serverless: Stack update finished...Service Informationservice: notifier-linkedin-apistage: devregion: eu-west-3stack: notifier-linkedin-api-devresources: 15api keys:  Noneendpoints:  POST - https://xxx.execute-api.eu-west-3.amazonaws.com/dev/contentsfunctions:  website: notifier-linkedin-api-dev-websitelayers:  NoneServerless: Removing old service artifacts from S3...Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.

Теперь наш код развернут в AWS Lambda, а API доступен по адресу https://xxx.execute-api.eu-west-3.amazonaws.com/dev/contents.

Попробуем:


Перевод материала подготовлен в рамках курса "Symfony Framework". Если интересно узнать о курсе больше, приглашаем на день открытых дверей онлайн, где преподаватель расскажет о формате и программе обучения.

Подробнее..

У Вас проблемы с legacy значит, Вам повезло! Распил монолита на PHP

06.01.2021 06:14:44 | Автор: admin

Вступление

Меня часто просят рассказать о работе с legacy-монолитами. Про микросервисную архитектуру и переход на нее говорят много, но редко упоминают о том, что проекты приходят ней после многих лет роста с монолитным приложением. Учебники по решению проблем не пишут. Чтобы поменять архитектуру живого решения, надо пройти через несколько этапов. Автор работал с разными проектами - и с полноценным multitenancy service-oriented REST architecture в Oracle, и с огромным монолитом, в репозитории которого были коммиты за десять лет. Эта статья - о темной стороне, о legacy-коде, и практических решениях проблем с монолитными приложениями на PHP.

Причины появления legacy

Есть две основные причины появления legacy-кода.

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

Вторая - технический долг, который создается специально. Руководство сокращает срок разработки ПО за счет отказа от проектирования, автоматического тестирования или code review, одобряет сторонние библиотеки, которые не поддерживаются, а разработчики не документируют сложную логику. Это встречается повсеместно и не зависит от количества денег на счету компании. Не стоит ругать плохих начальников. У них есть весомые причины поступать именно так.

У продуктов есть жизненный цикл, период большого спроса на популярные товары длится три-четыре месяца. Все лучшее конкуренты скопируют и сделают еще лучше, поэтому компании вынуждены регулярно выпускать новинки. Чтобы поддерживать объем выручки, новые продукты и новые версии выпускают каждые несколько месяцев, так продажи нового цикла компенсируют снижение продаж по товарам в конце цикла. По три-четыре крупных релиза в год делают и Apple, и Marvel, и в Oracle на рынке enterprise SAAS тоже квартальный релизный цикл. При этом, рецепта успеха не существует. 97% стартапов выкидывают наработки по своему продукту, и пробуют делать что-то новое, прежде чем найдут такой продукт, который у них покупают. Поэтому затраты на разработку MVP в стартапах максимально сокращают.

У вас проблемы с легаси - значит, вам повезло!

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

Проблемы?

Не всегда плохой код создает проблемы. Например, знаменитый пакет Wordpress написан очень плохим кодом, но на его основе работает 38% интернет-сайтов. Стандартные работы выполняют специалисты на аутсорсинге по прайс-листу, а обновления устанавливаются по нажатию кнопки. Проблемы с Wordpress начинаются, когда в него добавляют нестандартный код, и автоматическое обновление становится невозможно.

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

Что делать тем, кому повезло?

Начинать надо с тестирования

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

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

Обновление версии языка

Через несколько лет после написания код становится несовместимым с актуальной версией языка, и это приводит к вороху проблем.

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

Составить список проблемы совместимости с новой версией PHP помогут утилиты статического анализа.

Rector поможет решить простые случаи несовместимости с новой версией, автоматически обновив часть кода.

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

Phan показывает использование в коде лексических конструкций, которые убраны из новых версий PHP.

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

Обновление версии платформы или языка в таком случае выполняется достаточно быстро. Автор был инициатором обновления PHP с 5-ой версии на 7-ую для приложения с очень большим объемом кода, и эта задача была успешно выполнена командой за три недели.

Переход от монолита к сервисной архитектуре

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

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

К счастью, слона можно съесть по кусочкам - отделять от монолита модули, не переписывая код заново, зафиксировать API, а затем превращать их в сервисы. Сначала части кода приложения надо выделить в отдельные пакеты, а затем из пакетов можно будет создавать сервисы.

Перенос кода в пакеты открывает ряд возможностей:

  • можно сократить размер репозитория приложения,

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

  • можно описать зависимости между своими модулями и использовать composer для управления зависимостями и версиями своих пакетов,

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

  • можно выпускать разные версии пакетов, и согласовать изменения API.

Главное - это относительно небольшая по объему работы задача. Вынести часть кода в пакет без переписывания можно за несколько дней. У автора был опыт переноса в пакеты по тысяче строк кода в день с инверсией внешних зависимостей. А после фиксации API модулей будет проще заниматься масштабным рефакторингом.

Разделение приложения на пакеты

Допустим, есть приложение на PHP, которое предоставляет клиентский API. Начинать любые изменения надо с процедур тестирования и релиза, которые включают план отката. Эти процедуры называют release, control, validation и DevOps. В активно развивающихся проектах тестирование и выкладка отработаны. В этом случае надо начинать разделять приложение с определения таких ограниченных контекстов, которые логично выделить в отдельные модули и сервисы.

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

Создание отдельного модуля - это цикл из пяти подзадач:

1. Выбрать небольшой функционал для переноса в модуль - например, изменение размера изображений;

2. Определить API модуля - написать интерфейс, доступный приложению;

3. Написать или проверить приемочные тесты - например, на загрузку и валидацию изображения;

4. Скопировать в модуль старый код и инвертировать в коде модуля зависимости через границу модуля, без рефакторинга или переписывания всего кода;

5. Заменить в коде приложения прямые обращения к старому коду на вызовы сервиса из нового модуля; Для решения этой задачи используется две технологии: IoC-контейнер и менеджер зависимостей.

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

Начать создавать пакеты можно в локальном каталоге, а для полноценной сборки и развертывания стоит создать собственный репозиторий пакетов, такой, как Packeton, и перенести код модулей в собственные git-репозитории. Так же, можно использовать платный репозиторий Private Packagist.

Как создать composer-пакет в приложении и зарегистрировать его как сервис в IoC-контейнере, можно посмотреть здесь: до изменений, после изменений, diff.

В примерах используется composer для управления зависимостями пакетов и Symfony Dependency Injection как IoC-контейнер для сервисов. У Вас может быть другой контейнер. Если в приложении нет IoC-контейнера, придется делать рефакторинг и реализовать внедрение зависимостей. Простейший пример добавления IoC-контейнера в приложение.

Решение проблем со связанностью кода

Есть два типа связанности:

а) код будущего модуля содержит вызовы структур, которые описаны в других частях приложения

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

Рассмотрим случаи связанности кода, и варианты выделения модулей в пакеты без трудоемкого рефакторинга.

1. Расширение классов, реализация интерфейсов, использование трейтов, когда декларация структур используется через границу будущего модуля.

Приведу пример устранения связанности при наследовании, когда родительский и дочерний классы вызывают методы друг друга: результат, diff.

Основные алгоритмы расцепления связанности:

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

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

  • Наследование от внешних классов с зависимостями надо превратить в композицию с помощью адаптеров, которые внедряются как сервисы.

  • Для защищенных свойств, которые используются в дочернем классе, надо сделать getter-методы, а для защищенных методов надо создать прокси-методы.

  • Наследование классов приложения от классов модуля стоит инвертировать в композицию с сервисом, который предоставляется новым пакетом.

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

2. Статические вызовы.

Синтаксис PHP допускает вызов статических методов у объектов как методов класса (пример). Если Вы выносите в пакет или обычную функцию или класс, у которого есть статический метод, эти функции/методы нужно добавить в публичное API пакета (пример, diff).

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

Ссылки: пример прямого статического вызова, пример инверсии зависимости статического вызова через внедрение сервиса, diff коммита.

Если несколько методов из разных классов используются вместе, для них можно создать сервис-фасад.

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

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

5. Использование глобальных констант и констант классов.

Возьмем пример: в приложении есть класс, который нарушает Single Responsibility Principle, и содержит обращения к константе другого класса. Наша задача - вынести первый класс в пакет без рефакторинга второго класса, потому что рефакторинг потребует изменения всего остального кода, в котором используется константа. Надо избавиться от прямого обращения к константе.

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

Ссылки: до изменений, после изменений, diff, декларация инъекции константы в контейнере.

6. Динамическое разрешение имен через строковые операции.

Пример: $model = new ($modelName . Class);

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

Эту конструкцию можно попробовать переписать в switch-структуру со статическим списком классов. К счастью, в приложениях подобный код встречается редко.

Оптимизация

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

Есть несколько способов решения этой задачи:

  1. Сервисы, которые передаются в пакет, можно объявить как lazy.

  2. Объект API пакета можно объявить как Service Subscriber.

  3. Разделить API пакета на несколько сервисов.

Самый гибкий способ - это реализация Service Subscriber. Когда сервис объявляется подписчиком, можно реализовать в пакете вызов внешних сервисов по мере обращения к ним в пакете. Примеры: код до изменений, где используется один из нескольких классов, и код после переноса в пакет c инверсией зависимостей, где нужный сервис создается по требованию. Diff.

Service-Oriented Architecture

Хорошо, разделили код на пакеты, но при выкладке все собирается в одно приложение, и работает в одном процессе, как монолит. А где же сервис-ориентированная архитектура? До нее еще долгий путь.

У каждого пакета зафиксирован публичный API. На основе этого API можно создать сервис с restful-протоколом. Код нового сервиса - это код пакета, вокруг которого написан достаточно стандартный роутинг, запись логов, и прочий инфраструктурный код. А в старом коде вместо кода пакета появляется адаптер для http-вызовов через curl.

При создании отдельных внутренних приложений-сервисов надо решить две задачи:

  1. Детальное протоколирование вызовов всех сервисов. Каждому клиентскому запросу надо присваивать уникальный ID вызова, который передается во все сервисы при вызовах внутренних API, и каждый вызов сервиса надо протоколировать. Надо иметь возможность отследить вызовы сервисов по цепочке.

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

Заключение

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

Подробнее..

Отложенные задачи в рамках микро-сервисной архитектуры

13.02.2021 20:21:59 | Автор: admin

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

Для решения такой задачи было создано очередное решение под названием Trigger Hook. Принципиальная схема работы показана на рисунке 1. На схеме показано, что происходит с заданиями в течения всего их жизненного цикла. Смена цвета означает смену статуса задачи.

Рисунок 1 - Принципиальная схема работы Trigger HookРисунок 1 - Принципиальная схема работы Trigger Hook

задача, время запуска которой наступит не скоро

задача, время запуска которой скоро наступит

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

обработанное задание

неподтвержденный статус задания в базе данных

команда на удаление

Жизненный цикл задачи:

  • При создании задачи она попадает в базу данных (квадратный блок) (красные и желтые).

  • В оперативную память подгружаются задачи (треугольный блок), если их время запуска скоро наступит (переход красный->желтый). Данная структура реализована в виде приоритезированной очереди (кучи).

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

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

Дальше постараюсь подробнее описать некоторые особенности и привести аргументы в пользу выбора данного решения.

Простота API

Id принимается в формате UUIDv4. Если не передать, то будет сгенерирован самостоятельно. Возможность передачи id задачи со стороны внешнего сервиса будет полезна при использовании асинхронного канала. Время запуска указывается в формате UNIX.

Создание:

task := &domain.Task{Id:     id,ExecTime: time,}triggerHook.Create(task)

Удаление:

triggerHook.Delete(task.Id)

Получение событий наступления времени запуска:

for {result := triggerHook.Consume()if err != send(result.Task()) {result.Rollback()}result.Confirm()}

Стойкость к сбоям

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

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

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

Точность и производительность

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

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

Были измерены основные показатели скорости обработки задач.

Сервер приложения:

  • AWS EC2 Ubuntu 20

  • t2.micro

  • 1 vCPUs 2.5 GHz

  • 1 GiB RAM

Сервер базы данных:

  • AWS RDS MySQL 8.0

  • db.t3.micro

  • 2 vCPUs

  • 1 GiB RAM

  • Network: 2085 Mbps

Тест

Длительность теста

Средняя скорость (задач/сек)

Количество задач

Создание задач

1 минута 11 сек

1396

100000

Удаление задач

52 сек

1920

100000

Отправка задач (состояние задачи от красной до голубой)

498 милисекунд

200668

100000

Подтверждение задач (состояние задачи от голубой до удаления)

2 сек

49905

100000

Мониторинг

Для быстрой проверки корректной работы Trigger Hook предоставляет возможность подключить time-series базу данных. На этапе инициализации есть возможность определить периодичность измерений и выбрать интересующие метрики. Полный список доступных метрик есть тут.

Также есть возможность подключить систему логирования через адаптер. Доступны:

  • фатальные ошибки - приводящие к полной остановке приложения

  • ошибки на которые стоит обратить внимание, но которые не приводят к остановке

  • дебаг сообщения

Далее в примере Вы можете увидеть пример подключения к InfluxDB+Grafana

Trigger Hook в составе микро-сервисной архитектуры

Асинхронное взаимодействие

При использовании микро-сервисной архитектуры, обычно, предпочтение отдают асинхронному взаимодействию. Trigger Hook хорошо вписывается в микро-сервисную, событийно ориентированную архитектуру. Но в любом случае, входящие (создание, удаление) и исходящее (событие наступления времени запуска задачи) каналы могут быть как асинхронными, так и синхронными в зависимости от требований.

Ниже, на рисунке 2 приведен один из возможных вариантов схемы коммуникации через асинхронный канал. В качестве брокера сообщений может выступать какая-нибудь очередь, например, RabbitMQ. Эта схема исключает блокировку вызываемого микро-сервиса вызывающим, как при синхронном запросе посредством, например HTTP. Брокер принимает неограниченное количество задач (условно неограниченное), а обработчик этих задач берется за них по мере освобождения. Как только команда на создание будет обработана, отправляется событие об успешном создании задачи. Так же через брокер, клиентский сервис получает это событие и реагирует на него соответствующим образом - меняет статус сущности, использующей отложенное задание. В качестве сущности может выступать, например Push уведомление на мобильные устройства с рекламой.

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

Рисунок 2 - Схема коммуникации через асинхронный каналРисунок 2 - Схема коммуникации через асинхронный канал

На рисунке 3 показаны процессы создания сущности имеющий отложенное выполнение и на рисунке4 выполнение при наступлении времени.

Рисунок 3 - Процесс создания сущности с отложенным выполнениемРисунок 3 - Процесс создания сущности с отложенным выполнениемРисунок 4 - Выполнение задания сущностиРисунок 4 - Выполнение задания сущности

Совместный доступ

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

Верхний слой будет обладать доменным знанием. Другими словами, менеджер задач будет иметь определенный набор типов задач, определенный набор событий, относящихся к тем или иным типам задач. Например, обращение к интерфейсу будет звучать как создай отложенную задачу на отправку email сообщения или создай отложенную задачу на списание платы по подписке на YouTube, а уже сам менеджер задач будет обращаться к Trigger Hook с запросом создай отложенную задачу. Когда придет время запустить задачу, Trigger Hook создаст событие время выполнения задания наступило. Это событие перехватит менеджер задач, обработает его, выдав, например, событие время списания платы по подписке наступило. На рисунках 5 и 6 показан этот процесс.

Рисунок 5 - Создание задания с использованием промежуточного слояРисунок 5 - Создание задания с использованием промежуточного слояРисунок 6 - Обработка события с использованием промежуточного слояРисунок 6 - Обработка события с использованием промежуточного слоя

Связь между компонентами приложения должна быть очень слабой. Это касается и микро-сервисов в целом. На практике, одной из причин усиления связи, является перенос части ответственности одного сервиса в другой. Поэтому, одной из самых сложных задач, является поиск границы раздела (монолитного, например) приложения на микро-сервисы. Что бы это сделать удачно, нужно учитывать специфику доменной области знаний и текущей реализации приложения. Теперь вопрос - в какой микро-сервис поместить слой менеджер задач?

Рисунок 7 - Менеджер задач в одном м/с с Trigger HookРисунок 7 - Менеджер задач в одном м/с с Trigger Hook

На рисунке 7 показана схема, где менеджер задач является отдельным, микро-сервисом, содержащий доменное знание о типах задач, событиях относящихся к этим задачам. Как видно из схемы, предполагается совместное использование одного микро-сервиса менеджера заданий для разных клиентских микро-сервисов. У каждого микро-сервиса свой канал для получения событий. В RabbitMq такой канал событий легко реализовать в виде схемы direct.

Рисунок 8 - Менеджер задач как часть клиентского м/сРисунок 8 - Менеджер задач как часть клиентского м/с

На рисунке 8 показана иная схема, где менеджер задач является частью клиентского микро-сервиса и используется только для своих внутренних нужд. Такая схема подойдет если нет других микро-сервисов использующих отложенные задания или же каждый микро-сервис имеет свой менеджер задач с Trigger Hook микро-сервисом.

Масштабирование

Некоторые приложения сложнее масштабировать, чем другие. Все намного проще, если состояние приложения хранится только во внешнем хранилище с поддержкой конкурентного доступа, например, классическая связка PHP + MySQL. В этом случае несколько экземпляров приложения PHP разворачиваются на разных серверах, а Nginx балансирует нагрузку между ними, при этом, MySQL ресурс остается один на все экземпляры PHP приложений. Если MySQL не справляется, то уже независимо от PHP приложения, могут быть добавлены реплики.

Все несколько сложнее, когда приложение хранит собственное состояние. Его сложнее масштабировать горизонтально. Trigger Hook хранит свое состояние в оперативной памяти. Оно подгружает задачи, время запуска которых скоро наступит. Допустим, Вы создали задачу, время выполнения которой наступит примерно через 5 секунд. Это означает, что Trigger Hook уже погрузил ее для выполнения. Но Вы захотели отменить эту задачу. Для этого нужно вызвать метод API delete. Важно вызвать этот метод у того экземпляра приложения, который взял задачу на обработку. Это первая сложность.

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

На рисунке 9 показан пример масштабирования нагрузки. У каждого экземпляра Trigger Hook своя БД, на разных серверах (иначе особого смысла нет). Перед экземплярами Trigger Hook имеется балансировщик нагрузки. Кроме балансировки, он пишет в какую-нибудь hash map базу данных, например, Redis, пару ключ-значение:

task_id:instance_host
Рисунок 9 - Схема горизонтального масштабированияРисунок 9 - Схема горизонтального масштабирования

Это нужно для обеспечения функции удаления задачи. Если в Вашем приложении не предусмотрено удаление, то достаточно балансера без базы данных. События, генерируемые экземплярами Trigger Hook можно пересылать по одному каналу через брокер. Генерирование id будет происходить на стороне клиентского сервиса (при асинхронном взаимодействии) или на стороне trigger hook (при асинхронном или синхронном взаимодействии). Для клиентских сервисов интерфейс не изменится.

Приложение для демонстрации Trigger Hook

Приложение состоит из пяти микро-сервисов. Каждый использует Docker контейнер. Все работает на Kubernetes. Приложение легко можно развернуть в minikube. Тут описана подробная инструкция.

Рисунок 10 - Упрощенная схема взаимодействия микро-сервисовРисунок 10 - Упрощенная схема взаимодействия микро-сервисов

Message service - сервис (рисунок 11), который предоставляет API для создания email сообщений и назначения отправки на определенное время или отмены. Также позволяет просмотреть полный список сообщений и их статусы.

Некоторые особенности:

  • Находится на уровне домена.

  • Состоит из менеджера сообщений и менеджера заданий.

  • Написан на PHP, фреймворк Symfony 5.

  • Работает в двух экземплярах. Первый обслуживает API запросы при помощи Nginx. Второй - запускает демон через supervisor для прослушивания события из очереди RabbitMQ. Имеет вспомогательные экземпляры для запуска миграций.

  • Использует схему с рисунка 8 для управления заданиями.

Рисунок 11 - Message serviceРисунок 11 - Message service

Message Dashboard - интерфейс для Message service (рисунок 12).

Рисунок 12 - Интерфейс демо-приложенияРисунок 12 - Интерфейс демо-приложения

Сервис Mailer находится на уровне инфраструктуры. Должен непосредственно делать рассылку. Не реализован, так как не важен в рамках демо.

Trigger service - сервис уровня инфраструктуры. Использует GRPC канал для получения команд на создание и удаление заданий, AMQP для рассылки события наступления времени выполнения задания (триггер).

Рисунок 13 - Trigger serviceРисунок 13 - Trigger service

Monitoring - также находится на инфраструктурном уровне, так как показывает технические метрики без привязки к бизнес событиям. На рисунке 14 показано как выглядит панель. Используется Grafana и InfluxDB. Полное описание метрик есть тут.

Рисунок 14 - Технические метрики Trigger HookРисунок 14 - Технические метрики Trigger Hook

Надеюсь, приложение и статья будут Вам полезны! Следите за моим github, следите за проектом, ставьте звездочки). Спасибо!

Подробнее..

PHP Дайджест 196 (1 11 января 2021)

11.01.2021 14:11:20 | Автор: admin

Свежая подборка со ссылками на новости и материалы. В выпуске: релиз PHP 8.0.1, MySQL движок на PHP от Vimeo и другие релизы, обновленный Enum и свежие предложения для PHP 8.1, уязвимость в Laminas, инструменты, статьи, видео, PHP Дайджест Live в 20:00 МСК.

Приятного чтения!



Новости и релизы



PHP Internals


  • [RFC] Enumerations, Round 2 Предложение по Enum для PHP было сильно доработано. В частности: кейсы (значения) не могут иметь методы или константы, а сам Enum может; поддерживаются трейты без свойств; в скалярных енамах вместо метода value() теперь просто свойство. Обзор предложения был в выпуске 194 и на стриме.
  • [RFC] Bundling ext/simdjson into core Автор предлагает забандлить в ядро PHP библиотеку simdjson. Оно в разы быстрее чем текущее ext/json и позволяет парсить гигабайтные json за секунды.

    В обсуждении указали на то, что библиотека молодая и не доступна во многих инсталяциях. Поэтому пока лучше предоставлять ее в виде PECL расширения, а забандлить позже.
  • [RFC] Array unpacking with string keys В PHP 5.6 была добавлена распаковка массива в аргументах:
    variadic_function(...['apple', 'banana', 'lemon']);
    

    А в PHP 7.4 то же самое в массивах:
    $parts = ['apple', 'pear'];$fruits = ['banana', 'orange', ...$parts, 'watermelon'];// ['banana', 'orange', 'apple', 'pear', 'watermelon'];
    

    Но оба случая не поддерживали массивы со строковыми ключами. И поскольку с добавлением именованных аргументов в PHP 8.0 уже решен первый случай, то теперь нет причин сохранять ограничение для второго.

    В PHP 8.1 предлагается разрешить распаковку массивов со строковыми ключами:
    $array1 = ['a' => 'apple', 'p' => 'pear'];$array2 = ['b' => 'banana', 'o' => 'orange'];$array = [...$array1, ...$array2];// Приблизительно то же самое что:$array = array_merge($array1, $array2);
    
  • [PR] Use 'ENT_QUOTES|ENT_SUBSTITUTE' for HTML encoding and decoding functions Автор пул-реквеста заметил, что функция htmlspecialchars() почти всегда используется с флагами ENT_QUOTES и ENT_SUBSTITUTE:

    ENT_QUOTES WordPress
    ENT_QUOTES Blade (Laravel)
    ENT_QUOTES | ENT_SUBSTITUTE Twig (Symfony or Slim)
    ENT_QUOTES | ENT_SUBSTITUTE CodeIgniter
    ENT_QUOTES | ENT_SUBSTITUTE CakePHP
    ENT_QUOTES | ENT_SUBSTITUTE Yii
    Предлагается сделать эти флаги включенными по умолчанию.
  • check[RFC] Restrict $GLOBALS usage Принято единогласно. Использование $GLOBALS начиная с PHP 8.1
    будет ограничено
    Продолжат работать чтение, запись, isset и unset:
    $GLOBALS['x'] = 1;echo $GLOBALS['x']isset($GLOBALS['x']);unset($GLOBALS['x']);
    

    А вот попытка изменить саму переменную $GLOBALS вызовет ошибку:
    $GLOBALS = [];$GLOBALS =& $x;$x =& $GLOBALS;unset($GLOBALS);
    

    Также ошибка будет, если передать $GLOBALS по ссылке в функцию:
    asort($GLOBALS);// > Compile-time error
    

    Все это упрощает внутренности PHP и улучшает производительность операций с массивами в PHP.

  • [RFC] Concepts to improve mysqli extension Рекомендованным механизмом для доступа к БД в PHP является PDO. Тем не менее во многих приложениях используется mysqli. У последнего есть ряд старых проблем, которые автор и предлагает решить.
  • [RFC] Add array_is_list(array $array): bool Стартовало голосование по добавлению функции, которая вернет true, если передать в нее массив с последовательными целочисленными ключами 0, 1, 2 ... count($value)-1. Функция переименована из is_list() в array_is_list(). О причинах было подробнее на стриме.

    В Symfony уже успели сделать полифил для PHP 8.1 с этой функцией.
  • В PHP 8.1 добавлены супербыстрые алгоритмы хеширования: xxHash и MurmurHash3.

Инструменты


  • dollarDump Debugging Evolved Ray Ребятки из Spatie представили свое приложение для отладки Ray. Добавляете вызовы ray($anything) в своем коде, и при запуске PHP-скрипта оно красиво отображается в отдельном десктопном приложении.

    Если вы осилили Xdebug, то вряд ли это имеет смысл. А если отлаживаете в стиле var_dump(...)/die(), то может быть интересно.

    Смотрите подробный videoвидеообзор на английском или на русском в ближайшем PHP Дайджест Live.
  • AdamGaskins/barcoder Пакет с лаконичным интерфейсом для генерации SVG-картинок штрихкодов (QR, Datamatrix, и т.п.).
  • vimeo/php-mysql-engine MySQL движок на чистом PHP. Пригодится, если при тестировании вы обращаетесь к базе и хотите ускорить запуск тестов, эмулируя MySQL в памяти. Библиотека расширяет класс PDO и позволяет вызывать обычные методы PDO MySQL. Аккуратно: есть ограничения.
  • jvoisin/snuffleupagus PHP-расширение блокирует запуск потенциально небезопасного кода в рантайме и избавляет от многих потенциальных уязвимоcтей. Изначально разработан для хостеров, которые, естественно, не могут редактировать код своих клиентов, но хотят сделать его безопаснее.
  • mbunge/php-attributes Пакет для автоматического резолва/инициализации атрибутов PHP 8. Можно просто подключить автозагрузчик или использовать резолвер вручную.
  • mlocati/docker-php-extension-installer Инструмент упрощает установку PHP-расширений в Docker.
  • php-opencv/php-opencv Расширение для компьютерного зрения (распознавание лиц, объектов, и т. п.) и машинного обучения теперь с поддержкой PHP 8. Примеры использования.

Symfony



Laravel



Yii



Zend / Laminas


  • Итоги 2020 для Laminas Project
  • В Zend Framework / Laminas зарепортили уязвимость Суть уязвимости можно понять из этого примера:
    class MyClassWithToString {    public $name;    public function __construct($name) {        $this->name = $name;    }    public function __toString() {        return (string) $this->name;    }}$input = unserialize('O:19:"MyClassWithToString":1:{s:4:"name";s:15:"/tmp/etc/passwd";}');if ($input instanceof MyClassWithToString) {    unlink($input);}
    

    Во фреймворк запушили исправление с проверкой на is_string() перед тем как делать unlink(). Но если посмотреть внимательнее, то уязвимость касается десериализации данных от пользователя. А на php.net красным написано, что не стоит использовать unserializie() в подобных случаях.

    Более того, с 2017 года ошибки десериализации больше не считаются ошибками безопасности, просто потому что unserialize() никогда не будет безопасным (не только в PHP).

    Вот еще свежий пост об эксплуатации подобных багов на примере Yii.

Async PHP


  • Swoole PHP 4.6.0 В свежем релизе асинхронного движка добавлен нативный асинхронный сURL.
  • amphp/mysql-dbal Концепт асинхронного драйвера для Doctrine DBAL/ORM на базе Amphp v3.

Статьи



Аудио/Видео



Сообщество







Сегодня будет третий стрим по мотивам PHP Дайджеста. Разбор новостей и ссылок из выпуска с подробностями и деталями, обзор присланного, интересное но не вошедшее в выпуск, результаты розыгрыша и новый конкурс со слониками.
Начало в 20:00 Москва, Минск / 19:00 Киев.



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

Больше новостей и комментариев в Telegram-канале PHP Digest.

Прислать ссылку
Поиск ссылок по всем дайджестам
Предыдущий выпуск: PHP Дайджест 195
Подробнее..

PHP Дайджест 197 (11 25 января 2021)

25.01.2021 12:19:59 | Автор: admin

Свежая подборка со ссылками на новости и материалы. В выпуске: объекты в качестве ключей массивов и другие RFC предложения для PHP 8.1, запуск WebAssembly в PHP, о коллизиях в массивах, порция полезных инструментов, статьи, видео, PHP Дайджест Live.

Приятного чтения!



Новости и релизы



PHP Internals


  • [RFC] Object keys in arrays
    Никита предлагает сделать возможным использование объектов в качестве ключей обычных массивов.

    $obj1 = new stdClass;$obj2 = new stdClass;$array = [];$array[$obj1] = 1;$array[$obj2] = 2;var_dump($array[$obj1]); // int(1)var_dump($array[$obj2]); // int(2)
    

    Сейчас для этого можно использовать SplObjectStorage или WeakMap, и они будут вести себя как массивы. Но все равно останутся ограничения. Например, их нельзя использовать в функциях array_* и многих других стандартных.

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

    Поводом для предложения послужил тот факт, что в RFC Enumerations предлагается сделать значения енамов объектами. И соответственно тогда их нельзя будет использовать в качестве ключей массивов. А это существенный минус.
  • [RFC] Object scoped RNG Implementations
    Функции для генерации псевдослучайных чисел rand() или mt_rand() будут генерировать одну и ту же последовательность для одинакового посевного (seed) значения srand(). Но из-за использования глобального состояния невозможно создать несколько генераторов с разными посевными значениями и использовать их одновременно.

    Автор предлагает добавить объектный API для работы с генераторами псевдослучайных последовательностей, чтоб решить проблему глобального состояния.
    $seed = 1234;$rng = new RNG\MT19937($seed);$array = [1, 2, 3, 4, 5];shuffle($array, $rng); // Результат всегда стабильный
    
    Если нужны криптографически стойкие случайные числа, то есть, которые устойчивы к атакам, то следует использовать: random_bytes() или random_int().
  • [RFC] var_representation(): readable alternative to var_export()
    Функция var_export(), которая выводит набор выражений в строку, давно была предметом жалоб. Как минимум был RFC с предложением сменить синтаксис массива с array( ) на [ ].

    Теперь же предлагается просто ввести новую функцию var_representation($value, int $flags=0) :string, которая исправит все недостатки var_export().

    В качестве альтернативы пока можно использовать brick/varexporter.
  • [RFC] Change Default mysqli Error Mode
    В рамках инциативы по улучшению расширения mysqli (подробнее было в PHPLive#3 ) предлагается первый шаг: сделать режим бросания исключений в случае ошибки дефолтным. То есть это как если сейчас в приложении добавить вызов: mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

    Из крупного опенсорса mysqli используют только CodeIgniter и WordPress.
  • [RFC] Dump results of expressions in `php -a` На голосовании предложение по улучшению интерактивного шелла php -a.
  • PHP 8.1: What's New and Changed На php.watch можно следить за тем, как будет выглядеть PHP 8.1. На сайте собраны все принятые RFC и важные пул-реквесты с описаниями.

Инструменты


  • fabpot/local-php-security-checker Проверяет composer.json на предмет наличия зависимостей с известными уязвимостями. В качестве базы уязвимостей используется FriendsOfPHP/security-advisories.

    Можно использовать готовый GitHub action или Docker-образы от oxcom.
  • funivan/PhpClean Плагин для PhpStorm, который добавляет пачку интересных инспекций, например, чтоб везде были объявлены типы, не было лишних комментариев, и использовалась композиция вместо наследования. Отличный пост в поддержку.
  • wasmerio/wasmer-php WebAssembly рантайм для PHP. Расширение позволяет запустить и использовать любой wasm-бинарник из PHP. То есть можно взять библиотеку на Rust, скомпилировать в wasm и использовать на любой платформе из PHP. При этом с очень высокой производительностью. Подробнее в посте автора.
  • temporalio/sdk-php Антон Титов и Кирилл Несмеянов готовят PHP-SDK для temporal.io распределенный, масштабируемый, отказоустойчивым, высокодоступный движок, для выполнения процессов бизнес-логики.

    Пример реализации накопительной транзакции т.е. перевести деньги продавцу от нескольких покупателей в течении какого-то периода времени:
    Скрытый текст
    #[Workflow\WorkflowInterface]class LoopWorkflow{    private array $values = [];    private array $result = [];    private $simple;    public function __construct()    {        $this->simple = Workflow::newActivityStub(            SimpleActivity::class,            ActivityOptions::new()->withStartToCloseTimeout(5)        );    }    #[SignalMethod]    public function addValue(        string $value    ) {        $this->values[] = $value;    }    #[WorkflowMethod(name: 'LoopWorkflow')]    public function run(        int $count    ) {        while (true) {            yield Workflow::await(fn() => $this->values !== []);            $value = array_shift($this->values);            $this->result[] = yield $this->simple->echo($value);            if (count($this->result) === $count) {                break;            }        }        return $this->result;    }}
    

    Под капотом RoadRunner, reactphp/promise, атрибуты PHP 8. Подробнее на стриме расскажет сам автор, Антон Титов.

Symfony



Laravel



Yii



Async PHP


  • walkor/Workerman Асинхронный движок с простым API, поддержкой HTTP, WebSocket, SSL. Может работать в связке с libevent.

    Самый быстрый фреймворк на PHP в бенчмарках the-benchmarker/web-frameworks, в частности, потому что умеет из коробки стартовать пачку воркеров.

    Также на его базе есть реализация socket.io сервера walkor/phpsocket.io, адаптер PSR-7,15,17 chubbyphp/chubbyphp-workerman-request-handler, и фреймворк gotzmann/comet.

Статьи



Аудио/Видео



Сообщество


  • habrЭто не легаси-код, это PHP.
  • Буря в стакане по поводу PHP 8 В декабре был пост от @jrf_nl, в котором автор жаловалась, что в PHP 8 слишком много обратно несовместимых изменений и обновиться очень сложно.
    Эту идею подхватил и Зеев Сураски, который играл одну из ключевых ролей в развитии PHP в период 1997-2017.


    В ответ Brent Roose написал свой пост и разложил, почему проблема преувеличена, а обновляться не страшно и лучше делать это регулярно.

    Хотя доля правды в словах Зеева и других есть. Потому что иногда миграция может быть неочевидной. Например, вот такой код никогда не вызывал ни ошибок ни депрекейшн ноутисов, а в PHP 8.0 отвалится с TypeError:

    var_dump(round("foo"));
    
    3v4l.org/pU0LD

    Или вот еще неочевидный, хоть и задокументированный, пример из слайдов:
    $sub = substr('abcdef', 4, -4);if ($sub === false) {    echo 'fail';} else {    echo 'do something with $sub';}// PHP 5-7 > 'fail'// PHP 8   > 'do something with $sub'
    
    3v4l.org/Ln9g3

    В тему хороший ресурс по обновлению и поддержке легаси кода: understandlegacycode.com.
  • Как выглядел бы PHP, если бы это зависело от меня Подборка желанных фич от Brent Roose: final и void по умолчанию, никакого mixed, параметры и свойства обязательно типизированные, дженерики, куда ж без них, енамы, объекты для скаляров.





Сегодня будет четвертый стрим по мотивам PHP Дайджеста. Разбор новостей и ссылок из выпуска с подробностями и интересными деталями, не вошедшими в текстовый выпуск. В гостях Антон Титов с рассказом про новый инструмент. А также результаты розыгрыша и новый конкурс со слониками.
Начало в 20:00 Москва, Минск / 19:00 Киев.



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

Больше новостей и комментариев в Telegram-канале PHP Digest.

Прислать ссылку
Поиск ссылок по всем дайджестам
Предыдущий выпуск: PHP-Дайджест 196
Подробнее..

PHP Дайджест 198 (25 января 8 февраля 2021)

08.02.2021 04:16:37 | Автор: admin
Фото: Иван Ганцев.

Обновление стандартов PSR-6 и PSR-13, кеширование наследования в опкеш, аксессоры свойств и другие новости из PHP Internals, диалект Lisp компилируемый в PHP, а также инструменты, видео, подкасты и PHP Дайджест Live.

Приятного чтения!



Новости и релизы



PHP Internals


  • [RFC] Warning for implicit float to int conversions
    PHP динамический язык, что значит он может менять тип переменной на лету. У такого подхода есть как плюсы, так и минусы.

    Например, при преобразовании вещественных чисел (float) в целые (int) тихо теряется дробная часть.
    var_dump(3.1415, (int) 3.1415);> float(3.1415)> int(3)
    
    3v4l.org/fP1aC

    В данном RFC предлагается бросать предупреждение, если делается такое преобразование и дробная часть у float ненулевая.
  • Inheritance Cache
    Дмитрий Стогов представил PR, в котором реализовал кеширование наследования.

    Кеш на 8% улучшает производительность Hello World приложения на Symfony. И чтоб получить этот прирост, ничего делать не надо будет. Просто обновить PHP и удостовериться, что включен опкеш. Браво, Дмитрий!
    Скрытый текст
    Классы PHP компилируются и кешируются в opcache, но их связывание происходит во время выполнения при каждом запросе. Этот процесс может потребовать проведения ряда проверок на совместимость и заимствования методов/свойств/констант из родительских классов или трейтов. Все это требует много времени, хотя результат один и тот же в каждом запросе.

    Кэш наследования выполняет связывание набор всех зависимых классов (родительских, интерфейсов, трейтов, тип свойств, и т.п.) один раз и сохраняет в опкеше.

    Кроме того, в рамках этого патча Дмитрий удалил ограничения для неизменяемых классов. И теперь все классы, хранящиеся в опкеше иммутабельны.
  • [RFC] Property Accessors ! ранний черновик !
    Никита создал черновик предложения по аксессорам, то есть возможности объявлять геттеры/сеттеры для каждого свойства отдельно.

    Во-первых, RFC предполагает возможность объявлять асимметричные модификаторы доступа:
    class User {    public string $name { get; private set; }    // или вот так    public string $prop { public get; private set; }}
    

    Также рид-онли свойства:
    class Test {    // Read-write property.    public $prop { get; set; } // равносильно `public $prop;`    // Read-only property.    public $prop { get; }}
    

    Во-вторых, добавлять валидацию с помощью ключевого слова guard.
    class User {    public string $name {        guard {            if (strlen($value) === 0) {                throw new ValueError("Name must be non-empty");            }        }    }}
    

    В-третьих, ленивую инициализацию с помощью ключевого слова lazy:
    class Test {    public string $somethingExpensive {        lazy {            return computeSomethingExpensive();        }    }}
    

    В 2013 году подобное предложение уже обсуждалось для PHP 5.5, но провалилось на голосовании.

    Пока это супер ранний черновик, который даже не обсуждался в Internals. На первый взгляд, предложение в текущем виде получается слишком сложным и, возможно, не стоит того. Но черновик просочился даже до публикации, так что посмотрим как он еще изменится.
  • [RFC] Fibers Продолжается активное обсуждение файберов. Из интересного: к дискуссии подключился один из мейнтейнеров Swoole:
    Once PHP has a stack coroutine like Fiber, we can do more than what we can do now. Since we can interrupt from PHP internal functions, then we can replace all the implementation of PHP blocking functions, such as sleep(), and we can also replace php_stream so that we can change the implementation of PDO, mysqli, and phpredis into a coroutine way, and we can also make curl become a coroutine version through libcurl's support for multiplexing.
  • [RFC] Enumerations Стартовало голосование по енамам. Подробнее о предложении можно прочитать в дайджесте 194 или посмотреть в видео дайджест-лайва.
  • [RFC] var_representation(): readable alternative to var_export() Стартовало голосование по добавлению новой функции, которая исправляет проблемы старой var_export().
  • cross[RFC] Dump results of expressions in `php -a` Отклонено.
  • Что нового в PHP 8.1 Пополняющийся пост от Brent Roose. Если хочется прям все-все в подробностях, то лучше смотреть на php.watch.

    Следить за новыми RFC и ходом голосований также можно на PHP RFC Watch

Инструменты


  • vimeo/php-mysql-engine Симулятор MySQL-запросов (движок) на чистом PHP. В посте про инструмент Matt Brown, автор Psalm, рассказывает, как внедрение этого движка ускорило запуск тестов в Vimeo в два раза.

    На стриме возник вопрос: чем это лучше использования SQLite?

    Простой бенчмарк от Валентина Удальцова (канал Пых) показывает, что инструмент Vimeo заметно медленнее, чем PDO('sqlite::memory:'):
    sqlite:           4.00 MiB  - 66 msphp-mysql-engine: 10.00 MiB - 330 ms
    

    Поэтому, если для приложения достаточно подмножества SQLite, то можно остановиться на нем.
  • cweagans/composer-patches Плагин для Cоmposer, который позволяет применять патчи к зависимостям. Удобно, если ваши изменения специфичные и не имеют смысла в виде полноценного PR для пакета/фреймворка, и на целый форк не тянут.
  • OndraM/ci-detector Позволяет определить используемый CI-сервер и получить данные о билде.
  • rakibtg/SleekDB NoSQL база данных на PHP. Данные хранятся в JSON-документах и есть язык запросов
  • Orangesoft-Development/throttler Балансировщик нод. Пример использования для выбора прокси для Guzzle. Прислал Александр Денисюк.
  • sunrise-php/awesome-skeleton Микрофрейморк на компонентах для разработки микросервисов и запуске на RoadRunner или Swoole. Прислал fenric.

Symfony



Laravel



Yii



Статьи



Аудио/Видео



Занимательное


  • Phel Функциональный язык программирования, который компилируется в PHP. Является диалектом Lisp и вдохновлен Clojure. Пример кода:
    Скрытый текст
    # Define a namespace(ns my\example)# Define a variable with name "my-name" and value "world"(def my-name "world")# Define a function with name "print-name" and one argument "your-name"(defn print-name [your-name]  (print "hello" your-name))# Call the function(print-name my-name)
    



Уже пятый выпуск стрима по мотивам PHP Дайджеста будет сегодня на YouTube-канале PHP Point. Разбор новостей и ссылок из выпуска с подробностями и деталями. Новый ведущий, гость в выпуске, и по традиции конкурс со слониками.
Начало в 20:00 Москва, Минск / 19:00 Киев.



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

Больше новостей и комментариев в Telegram-канале PHP Digest.

Прислать ссылку
Поиск ссылок по всем дайджестам
Предыдущий выпуск: PHP-Дайджест 197
Подробнее..

PHP Дайджест 199 (8 22 февраля 2021)

22.02.2021 14:21:33 | Автор: admin

В PHP 8.1 будет enum, и еще два принятых, два отклоненных и три новых RFC предложения для PHP 8.1. WordPress используется на 40% сайтов. Почему нужно убрать strict_types, почему не стоит использовать empty(), а также инструменты, видео, статьи, подкасты, и PHP Дайджест Live в 20:00 МСК.

Приятного чтения!



Новости и релизы



PHP Internals


  • check[RFC] Enumerations
    С результатом 44 против 7 голосование завершено. В PHP 8.1 будет enum.
    enum RfcStatus {    case Draft;    case UnderDiscussion;    case Accepted;}function setRfcStatus(RfcStatus $status) :void {    // ...}setRFCStatus(RfcStatus::Accepted); // ОкsetRFCStatus('Draft');             // TypeError
    

    Подробнее про инамы можно прочитать в пересказах RFC в статье Брента и еще подробнее на php.watch.

    В Symfony уже открыли тикеты для добавления поддержки инамов.
  • check[RFC] Deprecate passing null to non-nullable arguments of internal functions
    В текущих версиях PHP стандартные функции без ошибок принимают null в качестве аргумента, когда параметр не nullable.

    А начиная с PHP 8.1 встроенные функции тоже будут бросать TypeError. Например, str_contains("", null). 3v4l.org/OVoa0A.

    Интересный факт: предложение принято единогласно, притом что это довольно крупная поломка обратной совместимости в PHP.
  • check[RFC] Array unpacking with string keys
    Предложение принято и в PHP 8.1 будет работать распаковка любых массивов, в том числе со строковыми ключами:
    $array1 = ['a' => 'apple', 'p' => 'pear'];$array2 = ['b' => 'banana', 'o' => 'orange'];$array = [...$array1, ...$array2];// Приблизительно то же самое что:$array = array_merge($array1, $array2);
    
  • [RFC] Fibers
    Из предложения по файберам был убран планировщик, потому что он сильно усложнял реализацию и вероятность принятия предложения.

    Теперь Fiber API предоставляет самый минимум и похож на аналогичные возможности в Ruby.

    Пример использования с ReactPHP trowski/react-fiber:
    Скрытый текст
    $loop = new FiberLoop(Factory::create());$browser = new Browser($loop);$request = function (string $method, string $url) use ($browser, $loop): void {    /** @var Response $response */    $response = $loop->await($browser->requestStreaming($method, $url));    /** @var ReadableStreamInterface $stream */    $stream = $response->getBody();    $body = $loop->await(Stream\buffer($stream));    var_dump(\sprintf(        '%s %s; Status: %d; Body length: %d',        $method,        $url,        $response->getStatusCode(),        \strlen($body)    ));};$requests = [];$requests[] = $loop->async($request, 'GET', 'https://reactphp.org');$requests[] = $loop->async($request, 'GET', 'https://google.com');$requests[] = $loop->async($request, 'GET', 'https://www.php.net');$loop->await(Promise\all($requests));
    
  • [RFC] CachedIterable (rewindable, allows any key&repeating keys)
    Tyson Andre предлагает добавить кеширующий итератор. Он сохраняет состояние любого итератора и внутри себя содержит иммутабельные копии его ключей и значений.
  • Proposal: namespace the SPL
    Обсуждается предложение создать неймспейс Spl и создать в нем алиасы для существующих классов: Spl\FixedArray -> SplFixedArray. А все новые классы, такие как CachedIterable и ReverseIterator уже вносит сразу в новый неймспейс.

    А пока в качестве альтернативы есть отличный инструмент azjezz/psl.
  • [RFC] mysqli bind in execute
    Kamil Tekiela продолжает инициативу по улучшению mysqli. В этом RFC предлагает добавить новый необязательный параметр в mysqli_stmt::execute(). Он будет принимать массив значений, которые автоматически биндятся, вместо отдельного вызова mysqli_stmt::bind_param(). В последний сейчас принимает только переменные по ссылке.
  • cross[RFC] PHP\iterable\any() and all() on iterables Предложение добавить функции any() и all() для итераторов не прошло голосование.
  • cross[RFC] var_representation(): readable alternative to var_export() Идея добавить альтернативу для var_export не нашла поддержки, поэтому пока используем юзерленд альтернативу brick/varexporter.
  • [Draft] Unify PHP's typing modes В PHP по сути есть два режима типизации. Один слишком слабый, а другой, strict_types=1 слишком строгий. Этот документ описывает причины существования этих двух режимов, их недостатки и что нужно сделать, чтобы объединять оба режима.

    Документ написан George Peter Banyard, и пока он не планирует его выдвигать в качестве официального RFC.

    Разберем его положения на стриме.
  • Об Observer API в PHP 8 Статья о внутреннем API для отслеживания входа и выхода из функции. Он существенно упростил разработку расширений типа Xdebug, профайлеров и APM-решений New Relic, Tideways, и т.п.

Инструменты


  • renoki-co/php-k8s Позволяет управлять ресурсами кубернетиса из PHP.
  • marcocesarato/php-conventional-changelog Генерирует с changelog из сообщений коммитов.
  • andrey-helldar/package-wizard CLI-инструмент для создания начальной структуры пакетов.
  • rryqszq4/ngx_php7 Встраиваемый в nginx интерпретатор PHP. Позволяет создавать обработчики запросов на PHP, модифицировать запрос/ответ, фильтровать тело ответа и заголовки, и прочее.

Symfony



Laravel



Yii



Async PHP


  • swow/swow Расширение для PHP, которое предоставляет асинхронные возможности на базе libuv, включая асинхронный стрим, то есть из коробки работающие PDO, file_get_сontents() и т.п. (когда они обернуты в корутину). По сути, является минималистичным подмножеством Swoole.

phpstorm PhpStorm



Статьи



Аудио/Видео



Занимательное


  • mario-deluna/php-render 3D рендерер на чистом PHP, даже безШейде Шейдеры, парсер .obj файлов и прочее.
    Код примера:





Уже традиционный стрим по мотивам PHP Дайджеста. Будет разбор новостей и ссылок из выпуска с подробностями и дополнительными деталями.
Начало в 20:00 Москва, Минск / 19:00 Киев.



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

Больше новостей и комментариев в Telegram-канале PHP Digest.

Прислать ссылку
Поиск ссылок по всем дайджестам
Предыдущий выпуск: PHP-Дайджест 198
Подробнее..

PHP Дайджест 200 (22 февраля 15 марта 2021)

15.03.2021 18:04:43 | Автор: admin
Фото: Grgoire Gaonach

Свежая подборка со ссылками на новости и материалы. В выпуске: Объекты в инициализаторах, неймспейсы для расширений, и другие RFC предложения для PHP 8.1. Обновлен PSR-11, предложен PSR ClockInterface. Порция полезных инструментов, видео, подкасты, статьи, и PHP Дайджест Live в 20:00 МСК.

Приятного чтения!



Новости и релизы



PHP Internals


  • [RFC] New in initializers
    В текущих версиях PHP можно использовать только константные значения в инициализаторах, то есть в дефолтных значениях свойств, параметров, констант. Если нужно не константное значение, то свойства инициализируют в конструкторе, а аргументы в теле методов. С константами таких вариантов сейчас вообще нет.

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

    static $x = new Foo();const C = new Foo();#[AnAttribute(new Foo())]class Test {    public const C = new Foo();    public static $prop = new Foo();    public $prop = new Foo();    public function __construct(        private Logger $logger = new NullLogger()    ) {}}function test($param = new Foo()) {}
    

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

    Документ содержит много подробностей и нюансов. Например, как работает рефлексия, трейты, анонимные классы, использование в атрибутах для решения проблемы вложенности, и прочее. Рассмотрим подробнее на стриме PHP Дайджест Live.
  • [RFC] Namespaces in bundled PHP extensions
    Классы и функции, предоставляемые в PHP, в настоящее время находятся в глобальном пространстве имен. Идея почистить и распределить все по неймспейсам обсуждалась давно.

    В данном RFC предлагается отказаться от префиксов вендоров, в том числе PHP. А неймспейсами должны стать имена расширений. То есть класс OpenSSLCertificate станет OpenSSL\Certificate.

    Пока правда, это касается только новых символов, а миграция существующих в рамках этого RFC не затрагивается. Но в примерах приведены возможные трансформации:
    str_contains() -> String\contains()
    in_array() -> Array\contains().
    Звучит как идея для PHP 9.
  • [RFC] Static variables in inherited methods
    Допустим, есть метод, в котором используется статическая переменная. Если отнаследоваться от класса с этим методом, то для наследника эта статическая переменная будет новой.

    RFC предлагает сделать единственным набор статических переменных для метода, независимо от того наследуется он или нет.
    Скрытый текст
    class A {    public static function counter() {        static $i = 0;        return ++$i;    }}class B extends A {}var_dump(A::counter()); // int(1)var_dump(A::counter()); // int(2)var_dump(B::counter()); // int(3)var_dump(B::counter()); // int(4)
    

  • [RFC] Fibers
    Стартовало голосование по файберам. Подробнее о том, что это было на канале. Если коротко: это небольшое, но важное улучшение генераторов, которое позволит писать асинхронный код на PHP проще. Например, вот так:

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

    Также против голосовал Joe Watkins, так как по его мнению файберы не обеспечивают использование по полной всем тем, что сегодня может предложить хард/софт. В то время как расширение krakjoe/parallel вполне могло бы.

    Здравое зерно в этих рассуждениях есть, и тем не менее, файберы реальный шаг в сторону асинхронных возможностей, который не противоречит ни Swoole ни parallel.
  • [RFC] noreturn type
    Авторы Psalm и PHPStan предлагают добавить новый тип в PHP noreturn.

    Такой тип указывает на то, что функция либо всегда бросает исключение либо завершает выполнение, то есть вызывает exit(), die(), trigger_error().

    function redirect(string $uri): noreturn {    header('Location: ' . $uri);    exit();}function redirectToLoginPage(): noreturn {    redirect('/login');}
    

    Подобный тип есть в Hack, в Python, уже давно используется в самих Psalm, PHPStan и в PhpStorm в виде атрибута #[NoReturn] или через exitpoint в .phpstormmeta.php.
  • [RFC] debug_backtrace_depth(int $limit=0): int Предлагается новая функция debug_backtrace_depth(int $limit=0), которая возвращает текущий уровень глубины стека вызовов. Может быть полезно для отладки рекурсивных функций, например.

    Сейчас можно получить такое же поведение с помощью полифила: count(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $limit=0)).
  • [RFC] println(string $data = ''): int Предлагается добавить функцию println, которая выведет строку в stdout и завершит ее символом новой строки. Не str_contains(), конечно, но тоже занятно.

Инструменты



Symfony



Laravel



Yii



Статьи



Видео



audio Подкасты



Сообщество





После небольшого перерыва возвращаемся со стримом и ведущим Валентином Удальцовым!

Будет разбор новостей и ссылок из выпуска с подробностями и деталями, мнение Валентина по RFC и статьям. Интересное но не вошедшее в выпуск, результаты розыгрыша и новый конкурс со слониками.

Начало в 20:00 Москва, Минск / 19:00 Киев.


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

Больше новостей и комментариев в Telegram-канале PHP Digest.

Прислать ссылку
Поиск ссылок по всем дайджестам
Предыдущий выпуск: PHP-Дайджест 199
Подробнее..

PHP Дайджест 201 (15 29 марта 2021)

29.03.2021 12:16:08 | Автор: admin

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

Приятного чтения!



Новости



Async PHP


  • [RFC] Fibers Файберы будут в PHP 8.1

    После долгих обсуждений и споров с мейнтейнерами Swoole предложение по файберам наконец-то принято.

    Файбер это блок кода, у которого есть свой стек (переменные и состояние). Он может быть запущен, приостановлен или прерван полностью в любой момент из основного кода или другого файбера. Их можно применить для реализации зеленых тредов в PHP.

    Скрытый текст
    $fiber = new Fiber(function (): void {    $value = Fiber::suspend('suspend');    echo "Value used to resume fiber: ", $value, "\n";});$value = $fiber->start();echo "Value from fiber suspending: ", $value, "\n";$fiber->resume('resume');> Вывод этого кода:Value from fiber suspending: suspendValue used to resume fiber: resume
    

    Значит ли это, что в PHP 8.1 будет асинхронность из коробки?
    Нет. Для асинхронных штук все еще надо будет использовать ReactPHP, Amp или подобные решения. Но использовать асинхронный код, и особенно интегрировать асинхронные блоки в традиционный код, будет намного легче.

    Подробнее про файберы можно почитать по ссылкам:
    Fibers PHP 8.1 Краткий пересказ RFC о PHP.Watch.
    Файберы PHP: Новый шанс для асинхронного PHP? Хороший разбор от мейнтейнера ReactPHP про суть файберов и типичные заблуждения.

    В PHP 8.1 файберы будут доступны из коробки в виде забандленного расширения ext-fiber. Но также автор говорит, что расширение будет обратно совместимым с PHP 7.2.

  • Мифы об асинхронном PHP Сергей Жук рассказывает основы: чем отличается конкурентность от параллельности, потоки и процессы, что такое неблокирующий ввод/вывод. И развенчивает популярный миф от том, что асинхронный PHP не является по-настоящему асинхронным.
  • Asynchronous PHP Multiprocessing, Multithreading & Coroutines Еще один пост об основах асинхронности в PHP от участника core team Laravel.

PHP Internals


  • [RFC] Auto-capturing multi-statement closures
    Сразу два предложения от Larry Garfield и Nuno Moduro по улучшению лямбд.

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

    То есть сейчас можно вот так:
    $y = 1;$fn1 = fn($x) => $x + $y;
    

    или вот так:
    $fn2 = function ($x) use ($y): int {   // ...   return $x + $y;};
    

    А предлагается вот так:
    $c = 1;$foo = fn($a, $b):int {  // ...  $val = $a * $b;  return $val * $c;};
    

  • [RFC] Short Functions
    В этом RFC предлагается разрешить использовать стрелочный синтаксис в методах и обычных именованных функциях.

    // Былоfunction add(int $a, int $b): int{    return $a + $b;}// Сталоfunction add(int $a, int $b): int => $a + $b;
    

    Пример с короткими геттерами:
    Скрытый текст
    class Person{    public function __construct(        private string $firstName,        private string $lastName,    ) {}    public function getFirstName(): string => $this->firstName;    public function getLastName(): string => $this->lastName;    public function getFullName(): string => $this->firstName . ' ' . $this->lastName;}
    

    Если предложения будут приняты, то синтаксис функций в PHP будет полностью консистентен и описывается вот такими
    правилами
    Символ => указывает выполнить выражение справа во всех случаях (функции, лямбды, массивы, match).
    Скобки { ... } обозначает блок кода, в котором может быть return.
    Ключевое слово function означет что автозахвата переменных нет.
    Ключевое слово fn обозначает, что выполнится автозахват переменных по значению.
    Функция с именем объявляется глобально на этапе компиляции, Функция без имени объявляется локально как замыкание в рантайме.

  • [RFC] Deprecations for PHP 8.1
    Уже по традиции в каждом релизе часть неконсистентных функций и поведений объявляется устаревшими. В версии PHP 8.1 они будут бросать dreprecation notice, а уже PHP 9 будут удалены полностью.

    Пока ничего сверхпримечательного: mysqli::init(), функции key(), current(), next(), prev(), and reset() на объектах, и много еще подобного.
  • [RFC] Pure intersection types
    В PHP 8.0 были добавлены объединенные типы, а в данном RFC предлагается добавить пересечения типов.

    Синтаксис вот такой TypeA&TypeB и означает, что переменная должна одновременно быть instanceof TypeA и instanceof TypeB.

    class A {    private Traversable&Countable $countableIterator;    public function setIterator(Traversable&Countable $countableIterator): void {        $this->countableIterator = $countableIterator;    }    public function getIterator(): Traversable&Countable {        return $this->countableIterator;    }}
    

    Предложение называется pure intersection types, потому что комбинации с union типами не поддерживаются и оставлены на рассмотрение в будущем.
  • [Draft] Add FPM early bootstrapping mode
    В этом черновик Benjamin Eberlei (автор атрибутов в PHP 8) предлагает добавить опцию fpm.bootstrap_file в которой будет путь к скрипту. Этот скрипт будет выполнен перед тем, как FPM процесс начнет слушать входящие соединения. При этом память и состояния из этого скрипта шарится со сркиптом, который отрабатывает на FPM-соединение, то есть запрос.

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


    То есть это что-то типа улучшенного auto_prepend_file.
  • PHP JIT/arm64 port Ребятки из ARM подключились и делают поддержку PHP JIT на ARM-процессорах.
  • [RFC] mysqli bind in execute Принят.

Инструменты


  • PeachPie 1.0.0 Вот уже 5 лет в рамках проекта PeachPie развивается компилятор PHP под .NET. Предствален стабильный релиз и теперь PeachPie позволяет рассматривать PHP как нативный язык .NET платформы. Применение: миграция приложений, кроссплатформенная разработка, другие экзотические кейсы.
  • sj-i/php-fuse FFI биндинги для libfuse можно сделать свою виртуальную файловую систему из чего угодно. В примерах есть ФС из PHP массива.
  • parsica-php/parsica Построитель парсеров с необычным синтаксисом: $parser = between(char('{'), char('}'), atLeastOne(alphaChar()));
  • spatie/period Библиотека позволяет делать сложные сравнения дат, например, найти пересечения периодов, разницу, пробелы, крайние границы и прочее.
  • pemistahl/grex Инструмент написан на Rust, но штука мегаполезная. Генерирует регулярные выражения по входным данным. То есть вводите одну или несколько строк, которые должны проходить регулярку, а на выходе получаете паттерн, котороый нужно слегка подправить.
  • i18n Ally JetBrains plugin Плагин PhpStorm для извлечения захардкоженных строк из Twig и PHP в YAML, JSON и XLIFF файлы. videoВидеодемо. Один из контрибьюторов плагина Edmund Beinarovic придет на стрим рассказать про плагин подробнее.

Symfony



Laravel



Yii


  • yiisoft/html Еще один пакет из семейства Yii 3. На этот раз для генерирования тегов HTML.

Статьи



Аудио/Видео



Сообщество






Стрим по мотивам PHP Дайджеста вместе со мной сегодня проведет Петр Мязин, автор подкаста Пятиминутка PHP.

Разберем новости и ссылки из выпуска с подробностями и деталями, и пообщаемся с гостем про плагин для PhpStorm i18n Ally.

Начало в 19:00 Москва, Минск, Киев.





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

Больше новостей и комментариев в Telegram-канале PHP Digest.

Прислать ссылку
Поиск ссылок по всем дайджестам
Предыдущий выпуск: PHP-Дайджест 200
Подробнее..

PHP Дайджест 204 (17 31 мая 2021)

31.05.2021 14:10:41 | Автор: admin
Фото: Christian Mnch.

В эти две недели core команда PHP активно обсуждала предложение по Partial function Application и в качестве альтернативы Никита Попов предложил более простой синтаксис для получения ссылки на любые функции. Также в уже принятые в PHP 8.1 енумы предлагается добавить статические свойства.

Symfony 6 будет требовать PHP 8.0, а вышедшая Doctrine 2.9 поддерживает указание метаданных в атрибутах вместо PHPDoc.

Еще в выпуске классная статья про регекспы и инструкция по использованию Deployer, а также другие полезные статьи, пачка инструментов, немного видео и подкастов.

Приятного чтения!


PHP Internals


  • [RFC] First-class callable syntax


    В качестве альтернативы довольно сложному [RFC] Partial Function Application Никита предлагает более простое решение проблемы получения ссылки на любую функцию или метод.

    // Сейчас вот так$fn = Closure::fromCallable('strlen');$fn = Closure::fromCallable([$this, 'method']);$fn = Closure::fromCallable([Foo::class, 'method']);// Предлагается вот такое$fn = strlen(...);$fn = $this->method(...);$fn = Foo::method(...);
    


    И соответственно, такой синтаксис можно будет применять везде, где ожидается Callable. Например, вот так:
    array_map(Something::toString(?), [1, 2, 3]);array_map(strval(...), [1, 2, 3]);// вместоarray_map([Something::class, 'toString'], [1, 2, 3])array_map('strval', [1, 2, 3]);
    

  • [RFC] Disable autovivification on false


    Сейчас PHP позволяет инициализировать массив из переменной со значением null или false. Предлагается для false все-таки бросать Fatal error:
    $a = true;$a[] = 'value'; // Fatal error: Uncaught Error: Cannot use a scalar value as an array$a = null;$a[] = 'value'; // Ok$a = false;$a[] = 'value'; // Сейчас это работает, но предлагается задепрекейтить
    
    3v4l.org/UucOC

  • [RFC] Allow static properties in enums


    В PHP 8.1 будут енумы. Подробный разбор был videoна стриме PHP-дайджеста и в тексте на php.watch.

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

    Пример использования
    enum Environment {    case DEV;    case STAGE;    case PROD;    private static Environment $currentEnvironment;    /**     * Read the current environment from a file on disk, once.     * This will affect various parts of the application.     */    public static function current(): Environment {        if (!isset(self::$currentEnvironment)) {            $info = json_decode(file_get_contents(__DIR__ . '/../../config.json'), true);            self::$currentEnvironment = match($info['env']) {                'dev' => self::DEV,                'stage' => self::STAGE,                'prod' => self::PROD,            };        }        return self::$currentEnvironment;    }    // Other methods can also access self::$currentEnvironment}printf("Current environment is %s\n", Environment::current()->name);
    

    Предложение спорное. Пишите в комментариях, что думаете по этому поводу.

    Кстати, в релизе PhpStorm 2021.2 уже будет поддержка enum, а пощупать можно будет на этой неделе в выпуске 2021.2 EAP.

  • [PR] Поддержка HTTP Early Hint support


    По умолчанию, PHP поддерживает отправку только одного набора заголовков. Но статус коды HTTP 1xx могут потребовать отправки нескольких наборов хедеров. В частности, для использования 103, нужно сначала отправить заголовки Link, и затем, когда весь ответ будет готов, отправить обычные 200 OK.

    Сейчас такое можно сделать, но немного криво: заголовки 103 отправить, как обычно, через header(), а следующую порцию заголовков вручную прям через echo.

    Никита предлагает добавить функцию для того, чтоб можно было отправлять несколько наборов заголовков. В пул-реквесте обсуждение API с участием команды Symfony и одним из авторов спецификаций HTTP.

  • check[RFC] Add IntlDatePatternGenerator


    Предложение принято. В PHP 8.1 будет класс IntlDatePatternGenerator для быстрого создания дат в локализированном формате. Подробнее в PHP Internals News #85 с автором RFC.

  • [RFC] Final class constants


    На голосовании.

  • В Internals обсуждается идея задепрекейтить багтрекер bugs.php.net


    Вместо него предлагается использовать issues на GitHub. У идеи есть как плюсы, так и минусы. Но как первый шаг, все баги документации теперь будут Гитхабе. Так что если вы нашли ошибку в мануале PHP, то можно просто создать issue в репозитории php/doc-en или php/doc-ru. Вот пример.


Инструменты


  • Doctrine ORM 2.9 Большое обновление популярной ORM. Под капотом поддержка атрибутов PHP 8, типизированные свойства, и другое.
  • Flarum 1.0.0 Релиз популярного движка для форума на PHP.
  • moneyphp/money 4.0 Пакет для правильной работы с денежными значениями.
  • phpast.com Просмотр дерева абстрактного синтаксиса PHP. Полезно при отладке инструментов на базе nikic/PHP-Parser. Код на гитхабе: ryangjchandler/phpast.com.
  • JBZoo/CI-Report-Converter Всеядный конвертер отчетов для CI. Основное призвание утилиты совместить самый разный результат линтеров с самыми разными CI (TeamCity, GitHub Actions, etc). Прислал smetdenis.
  • veewee/xml Все для удобной работы с XML в одном пакете.


Symfony




Laravel




Статьи




Аудио/Видео




community Сообщество





Подписывайтесь на Telegram-канал PHP Digest.

Если вам понравился дайджест, поставьте, пожалуйста, ему плюс это очень мотивирует продолжать делать.

Заметили ошибку или опечатку? Сообщите в личку хабра или телеграм.

Прислать ссылку можно через форму или просто написав мне в телеграм.
Поиск ссылок по всем дайджестам
Предыдущий выпуск: PHP-Дайджест 203

Подробнее..

Зачем нужен static при объявлении анонимных функций?

07.06.2021 22:20:16 | Автор: admin

Буквально на днях пришел вопрос от одного из подписчиков касательно одного из постов моего telegram канала. Его смутил вот такой кусок кода

<?phpusort($firstArray, static function($first, $second) {    return $first <=> $second;});

Звучал он так:

Зачем делать callbackи в функции сортировки (usort), статическими?

И я подумал, что это действительно хороший вопрос, на который стоит обратить внимание.

В чем проблема?

Начнем с определения из документации, чтобы засинхронизироваться:

Анонимные функции, также известные как замыкания (closures), позволяют создавать функции, не имеющие определённых имён. Они наиболее полезны в качестве значенийcallable-параметров, но также могут иметь и множество других применений.

Анонимные функции реализуются с использованием классаClosure.

Там-же, но это почти никто не читает :

При объявлении в контексте класса, текущий класс будет автоматически связан с ним, делая $this доступным внутри функций класса. Если вы не хотите автоматического связывания с текущим классом, используйте статические анонимные функции.

Выходит, что когда Сlosure объявляется в контексте класса, то класс автоматически привязывается к замыканию. Это означает, что $this доступен внутри области анонимной функции:

Код, чтобы протестить самостоятельно
<?phpclass ExampleTest extends TestCase{     public function testBasicTest(): void    {        $array = [2, 1];        usort($array, function ($first, $second) {            var_dump($this);            return $first <=> $second;        });       self::assertTrue(true);    }}

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

Замыкание, содержащее ссылку на $this, может быть не обработано сборщиком мусора, что, в свою очередь, может существенно повлиять на производительность.

Вот пример без использования static:

<?php class LargeObject {    protected $array;    public function __construct() {        $this->array = array_fill(0, 2000, 15);    }    public function getItemProcessor(): Closure {        return function () { // Внутри функции любые вычисления            $a = 1;            $b = 2;            return $a + $b;        };    }}function getPeakMemory(): string{    return sprintf('%.2F MiB', memory_get_peak_usage() / 1024 / 1024);}$start = microtime(true);$processors = [];for ($i = 0; $i < 2000; $i++) {    $lo = new LargeObject();    $processors[] = $lo->getItemProcessor();}var_dump(getPeakMemory());

Как результат, мы получим string(10) "134.10 MiB"

Но в случае, если мы добавим static в 11 строке, то потребление памяти составит string(8) "1.19 MiB"

Всё потому, что в processors[] мы продолжаем накапливать массив, внутри которого находятся Сlosures которые связаны с классом, а значит, содержат все те данные, которые в нём хранятся.

Выводы

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

P.S.

Часто для полноценного поста на Хабре мало короткой заметки. Такие выдержки я публикую в своем телеграм-канале https://t.me/beerphp. Подписывайся и сможешь получить больше интересного материала ;)

Подробнее..

PHP Дайджест 205 (1 15 июня 2021)

15.06.2021 00:15:33 | Автор: admin


Подборка свежих новостей и материалов из мира PHP. В выпуске: первая альфа PHP 8.1.0, Composer 2.1, Symfony 5.3 и другие релизы. Обзор новых предложений для PHP 8.1: Partial Function Application, pipe оператор, readonly свойства. А также порция полезных инструментов, статьи, видео и подкасты.

Приятного чтения!

Новости


  • PHP 8.1.0 alpha 1


    Вышел первая альфа и тем самым стартовал рели-процесс PHP 8.1. Обновления будут выходить каждые две недели по расписанию. Финальный релиз запланирован на 25 ноября.

    Заморозка фич ожидается 20 июля, а значит еще могут быть изменения. Из наиболее заметных новых фич на данный момент есть:

    • Enum они же перечисления RFC;
    • Новый тип never для возвращаемых значений RFC;
    • Файберы RFC;
    • Финальные константы в классах RFC;
    • Оператор распаковки поддерживает массивы со строковыми ключами RFC;
    • Объявлено устаревшим преобразование float в int, где теряется дробная часть RFC;
    • Интерфейс Serializable объявлен устаревшим RFC;
    • Запись восьмеричных чисел с префиксом 0o RFC;
    • Ограничено использование $GLOBALS RFC;

    Полный список изменений можно посмотреть на php.watch/versions/8.1.

  • PHP 8.0.7, PHP 7.4.20


    Багфикс релизы актуальных веток.

  • Стартовала программа раннего доступа PhpStorm 2021.2


    Каждую неделю публикуем новые билды, которые можно использовать бесплатно. А также анонсируем то, над чем идет работа в релизе.
    Уже доступны: поддержка енамов PHP 8.1, переработанный и улучшенный рефакторинг Extract Method, исправлены ошибки форматирования.

  • Composer 2.1.0


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

  • У каждого пакета на packagist.org теперь есть статистика по PHP-версиям


    Один из авторов Composer, Jordi Boggiano, каждые полгода публиковал в блоге пост со статистикой используемых версий PHP.

    Теперь вместо блога, эта общая статистика всегда доступна на packagist.org/php-statistics.

    Кроме того, у каждого пакета есть своя подобная страница, например, symfony/console/php-stats.

  • PHP Russia 2021


    Конференция состоится уже 28 июня. Программа сформирована habrничего лишнего, только хардкор, только технологии.

    Для читателей дайджеста есть промокод со скидкой: php_digest.


PHP Internals


  • [RFC] Partial Function Application


    Предложение было существенно переработано и объединено с более узким RFC от Никиты First-class callable syntax.

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

    Итого предлагается три способа получить ссылку на функцию:
    1. $func = some_func(...) так можно получить ссылку на любую функцию. Собственно, предложение Никиты.
    2. $func = some_func(1, 2, ?, 5) так можно получить ссылку с одним аргументом, что может быть полезно для различных колбэков.
    3. $func = any_func($all, $params, ...) так можно передать все аргументы в функцию, но при этом не вызывать ее. Ссылку позже можно вызвать, не передавая никаких параметров.

  • [RFC] Pipe Operator v2


    Если предложение выше пройдет голосование, то пайп-оператор станет его логичным продолжением.

    Вместо вложенных вызовов типа:

    array_filter(array_map('strtoupper', str_split(htmlentities("Hello World"))), fn($v) => $v != 'O');
    

    можно будет писать более понятные цепочки вида:

    $result = "Hello World"    |> htmlentities(?)    |> str_split(?)    |> array_map(strtoupper(?), ?)    |> array_filter(?, fn($v) => $v != 'O');
    

  • [RFC] Pure intersection types


    Предложение добавить пересечения типов находится на голосовании и похоже, что преодолеет необходимый порог. Тем временем можно послушать подкаст audioPHP Internals News #88 с George Peter Banyard, автором RFC.

  • [RFC] Readonly properties 2.0


    В качестве альтернативы довольно сложному и громоздкому предложению по акссессорам свойств сам же Никита выдвинул на рассмотрение RFC по readonly свойствам.

    Предлагается добавить модификатор readonly для свойств. Такие свойства нельзя будет изменить после инициализации.

    Скрытый текст
    class Test {    public readonly string $prop;    public function __construct(string $prop) {        // Legal initialization.        $this->prop = $prop;    }}$test = new Test("foobar");// Legal read.var_dump($test->prop); // string(6) "foobar"// Illegal reassignment. It does not matter that the assigned value is the same.$test->prop = "foobar";// Error: Cannot modify readonly property Test::$prop
    


    А в комбинации с constructor property promotion из PHP 8.0, можно будет сократить вообще до вот такого:

    class User {    public function __construct(        public readonly string $name    ) {}}$user = new User('Roman');echo $user->name; // Ok$user->name = 'Nikita'; // Error
    

  • [RFC] Make reflection setAccessible() no-op


    Сейчас чтобы получить доступ к свойству или методу через рефлексию, надо обязательно предварительно вызвать ->setAccessible(true).

    Marco Ocramius Pivetta предлагает убрать этот вызов, то есть ReflectionProperty и ReflectionMethod будут вести себя так, как если бы уже был вызван setAccessible(true).

    class Foo { private $bar = 'a'; }(new ReflectionProperty(Foo::class, 'bar'))->getValue();
    



Инструменты


  • nunomaduro/php-interminal Инструмент для чтения PHP Internals обсуждений в терминале. Пока умеет выводить только последние сообщения, но выглядит красиво.
  • joonlabs/php-graphql PHP-реализация спецификаций GraphQL. Автор утверждает, что быстрее чем другие реализации.
  • spiral/attributes Позволяет читать атрибуты из PHP 8 на PHP 7.2+ и дополнительно может работать с аннотациями доктрины. Фреймворк-агностик и для работы требует лишь nikic/php-parser. Прислал SerafimArts.
  • spiral/storage Компонент для работы с распределёнными файловыми хранилищами. Работает поверх thephpleague/flysystem и предоставляет более удобный API. Прислал SerafimArts.
  • kalessil/production-dependencies-guar Предотвращает добавление дев-пакетов в секцию require в composer.json.

    В тему у Валентина Удальцова на канале Пых была заметка с идеями проверок на CI.


Symfony




Laravel




Yii




Статьи




Аудио/Видео





Подписывайтесь на Telegram-канал PHP Digest.

Если вам понравился дайджест, поставьте, пожалуйста, ему плюс это очень мотивирует продолжать делать.

Заметили ошибку или опечатку? Сообщите в личку хабра или телеграм.

Прислать ссылку можно через форму или просто написав мне в телеграм.
Поиск ссылок по всем дайджестам
Предыдущий выпуск: PHP-Дайджест 204

Подробнее..

Пара слов о спецификациях

30.01.2021 20:16:42 | Автор: admin

Всем доброго времени суток! Удивительно, но упоминание о шаблоне "Спецификация" в контексте php встречается крайне редко. А ведь с его помощью можно не только избежать комбинаторного взрыва методов репозитория, но и улучшить переиспользование кода. Я же в свою очередь хотел бы остановиться на еще одной возможности, предоставляемой данным паттерном. С ее помощью можно решить проблему, которая возникает почти в каждом веб-приложении. И лично мне очень не хватало этого знания еще пару лет назад.


Что будем делать


Предположим, что мы разрабатываем task tracker. На главной странице будет выводиться список задач. Также нам понадобится просмотр отдельной задачи.


TaskController.php
<?phpdeclare(strict_types=1);namespace App\Controller;use App\Entity\Task;use App\Repository\TaskRepository;use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\Routing\Annotation\Route;#[Route('/task')]final class TaskController extends AbstractController{    #[Route('/', name: 'task_index', methods: ['GET'])]    public function index(TaskRepository $taskRepository): Response    {        return $this->render('task/index.html.twig', [            'tasks' => $taskRepository->findAll(),        ]);    }    #[Route('/{id}', name: 'task_show', methods: ['GET'])]    public function show(Task $task): Response    {        return $this->render('task/show.html.twig', [            'task' => $task,        ]);    }}

Далее предположим, что у нас есть 3 типа пользователей:


  • Admin может работать со всеми задачами.
  • Manager может работать только с задачами своего проекта.
  • Developer может работать только с назначенными ему задачами.

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


TaskController.php
namespace App\Controller; use App\Entity\Task;+use App\Entity\User; use App\Repository\TaskRepository;+use App\Security\CurrentUserProvider;+use Doctrine\ORM\QueryBuilder; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response;+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\Routing\Annotation\Route;+use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; #[Route('/task')] final class TaskController extends AbstractController {+    public function __construct(private AuthorizationCheckerInterface $authorizationChecker, private CurrentUserProvider $currentUserProvider)+    {+    }+     #[Route('/', name: 'task_index', methods: ['GET'])]     public function index(TaskRepository $taskRepository): Response     {+        $queryBuilder = $taskRepository->createQueryBuilder('t');+        $this->filter($queryBuilder);+         return $this->render('task/index.html.twig', [-            'tasks' => $taskRepository->findAll(),+            'tasks' => $queryBuilder->getQuery()+                ->getResult(),         ]);     }+    private function filter(QueryBuilder $queryBuilder): void+    {+        if ($this->authorizationChecker->isGranted(User::ROLE_ADMIN)) {+            return;+        }++        $user = $this->currentUserProvider->getUser();++        if ($this->authorizationChecker->isGranted(User::ROLE_MANAGER)) {+            $queryBuilder->andWhere('t.project in(:projects)')+                ->setParameter('projects', $user->getProjects());++            return;+        }++        $queryBuilder->andWhere('t.performedBy = :performedBy')+            ->setParameter('performedBy', $user);+    }+     #[Route('/{id}', name: 'task_show', methods: ['GET'])]     public function show(Task $task): Response     {+        if (!$this->isViewable($task)) {+            throw new AccessDeniedHttpException();+        }+         return $this->render('task/show.html.twig', [             'task' => $task,         ]);     }++    private function isViewable(Task $task): bool+    {+        if ($this->authorizationChecker->isGranted(User::ROLE_ADMIN)) {+            return true;+        }++        $user = $this->currentUserProvider->getUser();++        if ($this->authorizationChecker->isGranted(User::ROLE_MANAGER)) {+            return $user->getProjects()+                ->contains($task->getProject());+        }++        return $task->getPerformedBy() === $user;+    } }

Конечно, писать много кода в контроллере это не очень хорошо. Можно так или иначе раскидать его по сервисам, задействовать стандартные symfony voters. Но основная проблема этого кода в том, что наши бизнес-правила полностью повторяются и в методе filter, и в методе isViewable. И исправление этого факта уже не выглядит столь очевидно. Что можно с этим сделать? Нам нужна абстракция бизнес-правила, работающая как для списка элементов, так и для отдельной сущности. Именно это и предоставляет шаблон "Спецификация".


Пишем Спецификацию


В настоящий момент я нашел 2 проекта, реализующих данный паттерн для php. Happyr/Doctrine-Specification и K-Phoen/rulerz. При этом первый не поддерживает работу с отдельными объектами, а второй фактически заброшен и на symfony 5 уже не устанавливается. Да и формирование правил в строке, признаться, мне не слишком нравится.


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


Specification.php
<?phpdeclare(strict_types=1);namespace App\Specification;use Doctrine\ORM\QueryBuilder;use Symfony\Component\PropertyAccess\PropertyAccess;abstract class Specification{    abstract public function isSatisfiedBy(object $entity): bool;    abstract public function generateDql(string $alias): ?string;    abstract public function getParameters(): array;    public function modifyQuery(QueryBuilder $queryBuilder): void    {    }    public function filter(QueryBuilder $queryBuilder): void    {        $this->modifyQuery($queryBuilder);        $alias = $queryBuilder->getRootAliases()[0];        $dql = $this->generateDql($alias);        if (null === $dql) {            return;        }        $queryBuilder->where($dql);        foreach ($this->getParameters() as $field => $value) {            $queryBuilder->setParameter($field, $value);        }    }    protected function getFieldValue(object $entity, string $field): mixed    {        return PropertyAccess::createPropertyAccessorBuilder()            ->enableExceptionOnInvalidIndex()            ->getPropertyAccessor()            ->getValue($entity, $field);    }}

Помимо базовых в спецификации присутствуют вспомогательные методы. Метод filter упрощает ее применение к объекту query builder. Метод getFieldValue
пригодится нам при создании операций.


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


CompositeSpecification.php
<?phpdeclare(strict_types=1);namespace App\Specification;use Doctrine\ORM\QueryBuilder;abstract class CompositeSpecification extends Specification{    abstract public function getSpecification(): Specification;    public function isSatisfiedBy(object $entity): bool    {        return $this->getSpecification()            ->isSatisfiedBy($entity);    }    public function generateDql(string $alias): ?string    {        return $this->getSpecification()            ->generateDql($alias);    }    public function getParameters(): array    {        return $this->getSpecification()            ->getParameters();    }    public function modifyQuery(QueryBuilder $queryBuilder): void    {        $this->getSpecification()            ->modifyQuery($queryBuilder);    }}

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


AlwaysSpecified.php
<?phpdeclare(strict_types=1);namespace App\Specification;final class AlwaysSpecified extends Specification{    public function isSatisfiedBy(object $entity): bool    {        return true;    }    public function generateDql(string $alias): ?string    {        return null;    }    public function getParameters(): array    {        return [];    }}

Equals.php
<?phpdeclare(strict_types=1);namespace App\Specification;final class Equals extends Specification{    public function __construct(private string $field, private mixed $value)    {    }    public function isSatisfiedBy(object $entity): bool    {        return $this->value === $this->getFieldValue($entity, $this->field);    }    public function generateDql(string $alias): ?string    {        return sprintf('%s.%s = :%2$s', $alias, $this->field);    }    public function getParameters(): array    {        return [            $this->field => $this->value,        ];    }}

MemberOf.php
<?phpdeclare(strict_types=1);namespace App\Specification;final class MemberOf extends Specification{    public function __construct(private string $field, private object $value)    {    }    public function isSatisfiedBy(object $entity): bool    {        return $this->getFieldValue($entity, $this->field)            ->contains($this->value);    }    public function generateDql(string $alias): ?string    {        return sprintf(':%2$s member of %1$s.%2$s', $alias, $this->field);    }    public function getParameters(): array    {        return [            $this->field => $this->value,        ];    }}

Not.php
<?phpdeclare(strict_types=1);namespace App\Specification;final class Not extends Specification{    public function __construct(private Specification $specification)    {    }    public function isSatisfiedBy(object $entity): bool    {        return !$this->specification            ->isSatisfiedBy($entity);    }    public function generateDql(string $alias): ?string    {        return sprintf(            'not (%s)',            $this->specification->generateDql($alias)        );    }    public function getParameters(): array    {        return $this->specification            ->getParameters();    }}

Добавлять их можно по мере необходимости. Чуть хитрее обстоит дело с объединением таблиц. Я попробовал несколько вариантов и в итоге остановился на этом.


Join.php
<?phpdeclare(strict_types=1);namespace App\Specification;use Doctrine\ORM\QueryBuilder;final class Join extends Specification{    public function __construct(private string $rootAlias, private string $field, private Specification $specification)    {    }    public function isSatisfiedBy(object $entity): bool    {        return $this->specification            ->isSatisfiedBy($this->getFieldValue($entity, $this->field));    }    public function generateDql(string $alias): ?string    {        return $this->specification            ->generateDql($this->field);    }    public function getParameters(): array    {        return $this->specification            ->getParameters();    }    public function modifyQuery(QueryBuilder $queryBuilder): void    {        $queryBuilder->join(sprintf('%s.%s', $this->rootAlias, $this->field), $this->field);        $this->specification            ->modifyQuery($queryBuilder);    }}

Переходим на бизнес-правила


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


IsViewable.php
<?phpdeclare(strict_types=1);namespace App\Specification\Task;use App\Entity\User;use App\Security\CurrentUserProvider;use App\Specification\AlwaysSpecified;use App\Specification\CompositeSpecification;use App\Specification\Equals;use App\Specification\Join;use App\Specification\MemberOf;use App\Specification\Specification;use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;final class IsViewable extends CompositeSpecification{    public function __construct(private AuthorizationCheckerInterface $authorizationChecker, private CurrentUserProvider $currentUserProvider)    {    }    public function getSpecification(): Specification    {        if ($this->authorizationChecker->isGranted(User::ROLE_ADMIN)) {            return new AlwaysSpecified();        }        $user = $this->currentUserProvider->getUser();        if ($this->authorizationChecker->isGranted(User::ROLE_MANAGER)) {            $isProjectMember = new MemberOf('members', $user);            return new Join('task', 'project', $isProjectMember);        }        return new Equals('performedBy', $user);    }}

А вот в контроллере кода поубавится.


TaskController.php
namespace App\Controller; use App\Entity\Task;-use App\Entity\User; use App\Repository\TaskRepository;-use App\Security\CurrentUserProvider;-use Doctrine\ORM\QueryBuilder;+use App\Specification\Task\IsViewable; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\Routing\Annotation\Route;-use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; #[Route('/task')] final class TaskController extends AbstractController {-    public function __construct(private AuthorizationCheckerInterface $authorizationChecker, private CurrentUserProvider $currentUserProvider)+    public function __construct(private IsViewable $isViewable)     {     }@@ -26,7 +23,7 @@ final class TaskController extends AbstractController     public function index(TaskRepository $taskRepository): Response     {         $queryBuilder = $taskRepository->createQueryBuilder('t');-        $this->filter($queryBuilder);+        $this->isViewable->filter($queryBuilder);         return $this->render('task/index.html.twig', [             'tasks' => $queryBuilder->getQuery()@@ -34,29 +31,10 @@ final class TaskController extends AbstractController         ]);     }-    private function filter(QueryBuilder $queryBuilder): void-    {-        if ($this->authorizationChecker->isGranted(User::ROLE_ADMIN)) {-            return;-        }--        $user = $this->currentUserProvider->getUser();--        if ($this->authorizationChecker->isGranted(User::ROLE_MANAGER)) {-            $queryBuilder->andWhere('t.project in(:projects)')-                ->setParameter('projects', $user->getProjects());--            return;-        }--        $queryBuilder->andWhere('t.performedBy = :performedBy')-            ->setParameter('performedBy', $user);-    }-     #[Route('/{id}', name: 'task_show', methods: ['GET'])]     public function show(Task $task): Response     {-        if (!$this->isViewable($task)) {+        if (!$this->isViewable->isSatisfiedBy($task)) {             throw new AccessDeniedHttpException();         }@@ -64,20 +42,4 @@ final class TaskController extends AbstractController             'task' => $task,         ]);     }--    private function isViewable(Task $task): bool-    {-        if ($this->authorizationChecker->isGranted(User::ROLE_ADMIN)) {-            return true;-        }--        $user = $this->currentUserProvider->getUser();--        if ($this->authorizationChecker->isGranted(User::ROLE_MANAGER)) {-            return $user->getProjects()-                ->contains($task->getProject());-        }--        return $task->getPerformedBy() === $user;-    } }

Отлично! Повторения кода больше нет. Но что если мы усложним условия?
Представим, что в списке у менеджера и разработчика должны выводиться только задачи, статус проекта которых не равен "archived".


IsViewable.php
use App\Entity\User; use App\Security\CurrentUserProvider; use App\Specification\AlwaysSpecified;+use App\Specification\AndX; use App\Specification\CompositeSpecification; use App\Specification\Equals; use App\Specification\Join; use App\Specification\MemberOf;+use App\Specification\Not;+use App\Specification\Project\IsArchived; use App\Specification\Specification; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;@@ -26,14 +29,23 @@ final class IsViewable extends CompositeSpecification             return new AlwaysSpecified();         }+         $isNotArchived = new Not(new IsArchived());          $user = $this->currentUserProvider->getUser();         if ($this->authorizationChecker->isGranted(User::ROLE_MANAGER)) {             $isProjectMember = new MemberOf('members', $user);-            return new Join('task', 'project', $isProjectMember);+            return $this->getProjectSpecification(new AndX($isNotArchived, $isProjectMember));         }-        return new Equals('performedBy', $user);+        return new AndX(+            new Equals('performedBy', $user),+            $this->getProjectSpecification($isNotArchived)+        );+    }++    private function getProjectSpecification(Specification $specification): Join+    {+        return new Join('task', 'project', $specification);     } }

Выводы


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


Да и вообще, что вы думаете о данном паттерне? Почему он так мало представлен в php? И можно ли ожидать, что он станет стандартом на уровне фреймворков?


С полным примером из статьи можно ознакомиться на github.

Подробнее..

Итоги года в PHP мире

04.01.2021 10:13:21 | Автор: admin

К концу 2019-го встречи разных PHP-тусовок проходили чуть ли не каждую неделю, появились новые подкасты и каналы, а статьи и доклады лились рекой. Весна 2020-го не задалась - могло показаться, что все заглохнет, но сообщество быстро перестроилось. Стримы заменили митапы, люди не бросили готовить контент и, конечно, важные релизы.

Спасибо Алисе Мартыновой за картинкуСпасибо Алисе Мартыновой за картинку

Мы с @pronskiy из PHP-дайджеста решили спросить вас - а что из этого всего запомнилось? И запустили небольшой анонимный опрос. Здесь и сейчас собираем ваши мнения, которые агрегируем и опубликуем в феврале.

Пройти опрос

Методология исследования. Сварщики мы ненастоящие. Идея родилась в небольшом чате, где организаторы митапов и ведущие каналов о PHP делятся задумками новых активностей. Никто из нас - не профессиональный социолог. Зато мы честные) Созвонились, набросали вопросы (часть отсеяли), прикинули каналы распространения - чаты о PHP, Хабр, таргет в соцсетях и так далее. И пошли делать.

Но небольшой бэкграунд в исследованиях у нас есть

Нам помогает @alyssashch, которая второй год исследует ИТ-ландшафт Ростова-на-Дону - посмотрите, как круто у нее получается.

Что и зачем исследуем

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

Среди открытых вопросов:

  • Человек года в сообществе - если вы хотели сказать спасибо конкретным активистам, создателям контента и контрибьюторам, не стесняйтесь. А мы придумаем, как их отметить.

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

  • Статья, которая запомнилась в этом году - по аналогии с докладами.

  • Также есть открытый вопрос про ИТ-компании с хорошей экспертизой в PHP - это и подсказки для поиска докладчиков на будущие митапы и стримы, и возможность посмотреть вхождение их докладов и статей в опросник.

Среди вопросов с готовыми ответами:

  • Каналы в телеграме и на ютубе (брали самые активные из этого списка), а также подкасты, регулярно выпускавшие контент - запомнились ли и чем.

  • Яркие события, такие как релизы и популярные стримы - что зацепило и стоило ли ожиданий.

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

Что будет с данными

Мы собираем ответы до середины января. Затем нормализуем и сведем в красивые графики, возможно, даже кликабельные - и опубликуем на phpcommunity.ru, Хабре и телеграм-канале PHP-дайджеста. Вероятнее всего, сырые данные (за исключением email-адресов) будут также доступны в виде архива - можно будет забрать его и строить свои срезы.

Опрос анонимен, но мы собираем email - его можно отдать по желанию, чтобы выиграть официального PHP-слона. Тут все просто. Слон плюшевый, физический, вручить его надо кому-то физически. Чтобы как-то связаться с человеком, нужен его контакт.

Итоговый слоник может отличаться цветом.Итоговый слоник может отличаться цветом.

Обладателя выберем при подведении итогов опроса. Возможно, призов будет больше.

Подробнее..

Я буду долго гнать велосипед! История создания своего фреймворка

23.02.2021 20:21:55 | Автор: admin

Где-то около 8 лет назад мне потребовалось определиться с PHP фреймворком для реализации одного проекта. Из фреймворков я знал только понаслышке zend, и ModX Revo с Bitrix. Последние-то и фреймворком трудно было назвать - это были полноценные CMS, которых на тот момент было огромное множество, и они были на пике популярности. В то время не искали разработчиков Laravel или Symfony, тогда нужны были администраторы/модераторы/разработчики Bitrix, Drupal и т.д.

И я принял тогда решение писать свой фреймворк с "0". Задача стояла простая - нужна была работа с БД и RESTful API интерфейс.

Итак - начинаем собирать двухколесный.

"Ленивому и в будни праздник"

БД

Началось, что я немного ленив и писать SQL запросы напрямую к БД мне не хотелось совсем, тем более что я "наглотался" этого в предыдущих проектах.

Это привело меня к поиску чего-то готового, и я наткнулся на тогда еще молодой фреймворк Medoo.in, и это оказалось открытием 1. Немного "доработав" фреймворк, проблема с БД была решена.

Приступаем к RESTful API интерфейсу

Что это такое? - Это просто запросы GET, POST, PUT, DELETE от клиента к серверу. Как бы не пытались вложить в это слово "REST" огромный смысл.

А как же SOAP?!

SOAP - это тот же REST интерфейс в котором передается XML по двум каналам GET и POST. И поверьте смотреть на этот формат через такую призму будет куда понятнее :)

С REST'ом были тоже различные мелкие фреймворки которые пытались мне помочь, но меня всё не устраивало. Какие-то роуты, какие-то адресации в общем - трудно, и в другой бы ситуации может быть и помогло но не в этой.

Благо у меня был опыт работы с MODX и тогда мне очень заинтересовал их подход к реализации friendly(дружественных) наименований страниц, отвечал за него небольшой файлик (.htaccess) для Apache. Коротко, со следующим содержанием:

RewriteCond %{REQUEST_FILENAME} !-fRewriteCond %{REQUEST_FILENAME} !-dRewriteRule ^(.*)$ /index.php?q=$1 [L,QSA]

Коротко о том, что здесь написано:
Если вы обращаетесь к серверу и на сервере нет такого файла или папки, запрос переадресуется в файл index.php и уже там сами разбирайтесь, что от вас хотел пользователь, при этом никаких 4хх ошибок не будет, ну если вы сами не захотите их сделать.

И запрос http://мойдомен.ру/я/хочу/что-то/необычное будет автоматически отредиректен сюда http://мойдомен.ру/index.php?q=я/хочу/что-то/необычное

И это полностью решало вопрос редиректа! Но не решало вопроса Роутинга, приступим.

Роутим!!!

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

В общем - роутинг банален, первое значение роута - это всегда класс для обработки, 2-е значение - это public функция. И не к чему какие-то сложности. Для исключения я придумал функцию Init в которую передаются все параметры о роуте.

Как к вам обращаются через GET, POST, и т.д. можно всегда узнать из $SERVER["METHOD"], если не унаследовались от нужного класса.

Так обращение к http://domen.com/users/list/10/30 переадресует пользователя в класс users.controller.php функция public function list($params) {}

По-моему, ничего прозрачнее придумать уже невозможно.

Собственно на этом можно было бы и остановиться, и так и было. Всё было сделано по канонам MVC - модель/выдача/контроллер - построена, но без View.

В 2-х словах о MVC кому интересно

MVC - это что-то вроде стандарта правильной разработки "программ".
Контроллер - это первое с чем "сталкивается" пользователь. Там система начинает понимать, что он неё хотят, "открыть страницу со списком пользователей", или "самоуничножиться".

Контроллер - сам не обращается к БД, для этого есть модель. И контроллер ничего не выводит на экран, для этого есть View, контроллер только подготавливает(упаковывает) данные. Если у вас в контроллере появились не дай бог прямые запросы к БД и формирование тегов html, то выйдите на улицу подышите, можете закурить, но запах клея и краски надо выветреть. Так быть не должно.

Модель - это область обращения к БД. Почему надо её выводить отдельно? Вам может пригодится позже. К примеру при смене БД или при смене колонок в таблице, вам не надо искать по всему коду все места где есть обращение к таблице, а изменить запрос или выдачу в одном месте - Модели данной таблицы.

Выдача - это часть отвечает за формирование результата работы Контроллера. На основании данных из контроллера формируется HTML, или JSON, или XML, или межгалактический стандарт! Всё зависит от того что захотите.

5 лет, система работала как часы, 250 тыс. REST запросов в сутки, независимо от версии php, от нахождения сервера, сервер не нагружался в пики и до 15% на самом слабом железе - всё работало!

Вот ссылка

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

А для этого я его не проектировал...

"Всякому овощу свое время"

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

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

В общем не вдаваясь в подробности существующий тогда TWIG - разбил все мои начинания в пыль! Я реализовал почти аналогию TWIGа и когда увидел его, мне потребовалось не мало мужества принять, то, что данный инструмент - в 100 раз "гибче", "удобнее", и т.д, моего.

С точки зрения ядра мне оставалось всего лишь определять что возвращает мне класс, и если он возвращал мне объект(object), то я не задумываясь, и по всем принципам SOLID (сейчас слюна у отличников потекла) отправлял его рендерить и выводить на экран!

Вот ссылка с твигом

Юхуууу! Где мой пина-колада, я отдыхать!
...
Почти
...
Как подкралось не пойми от куда, и кто его придумал - ТЕСТИРОВАНИЕ!!!

"Бага с возу - релизу легче"

Тут я даже не стал долго думать. Есть отличные инструменты по типу CODECEPTION.
Добавил через Composer - поднастроил - пользуйся! Фреймворк позволяет подключать вендорные(vendors) модули.

Если кто-то поможет настроить SILENIUM на CODECEPTION - буду рад, у меня пока не получилось.

И ссылка с Codeception

Теперь точно пошел пить свой "Куба-Либре"!

В следующий раз расскажу и сравню одно и тоже задание на 4-х популярных фреймворках - это Laravel, Symfony, Yii2 и Phalcon, попробую рассказать о их "+" и "-" на простом примере с какими проблемами я сам столкнулся и как их решил.

Спасибо, не унывайте!

Подробнее..
Категории: Php , Symfony , Фреймфорк

Автоматическая проверка кода за 5 минут

22.03.2021 16:16:43 | Автор: admin
Данная инструкция показывает как автоматизировать проверку на code style в вашем php проекте.

Давайте посмотрим как будет выглядеть настройка в новом проекте.

Шаг 1 Делаем инициализацию composer (у кого он уже настроен, пропускаем)


Для этого в корне вашего проекта запускаем команду. Если у вас не установлен composer, то можете обратиться к официальной документации getcomposer.org

composer init

Шаг 2 Добавляем .gitignore


###> phpstorm ###.idea###< phpstorm ###/vendor/###> friendsofphp/php-cs-fixer ###/.php_cs/.php_cs.cache###< friendsofphp/php-cs-fixer ###

Шаг 3 Добавляем нужные библиотеки


composer require --dev friendsofphp/php-cs-fixer symfony/process symfony/console  squizlabs/php_codesniffer

Шаг 4 Добавляем обработчик хука


Сам обработчик можно написать на чем угодно, но так как статься про php то будем писать код на нем.

Создаем файлик в папочке hooks/pre-commit.php
#!/usr/bin/php<?phpdefine('VENDOR_DIR', __DIR__.'/../../vendor');require VENDOR_DIR.'/autoload.php';use Symfony\Component\Console\Input\InputInterface;use Symfony\Component\Console\Output\OutputInterface;use Symfony\Component\Console\Application;use Symfony\Component\Process\Process;class CodeQualityTool extends Application{    /**     * @var OutputInterface     */    private $output;    /**     * @var InputInterface     */    private $input;    const PHP_FILES_IN_SRC = '/^src\/(.*)(\.php)$/';    public function __construct()    {        parent::__construct('Ecombo Quality Tool', '1.0.0');    }    /**     * @param InputInterface $input     * @param OutputInterface $output     *     * @return void     * @throws \Exception     */    public function doRun(InputInterface $input, OutputInterface $output)    {        $this->input = $input;        $this->output = $output;        $output->writeln('<fg=white;options=bold;bg=red>Code Quality Tool</fg=white;options=bold;bg=red>');        $output->writeln('<info>Fetching files</info>');        $files = $this->extractCommitedFiles();        $output->writeln('<info>Running PHPLint</info>');        if (! $this->phpLint($files)) {            throw new \Exception('There are some PHP syntax errors!');        }        $output->writeln('<info>Checking code style with PHPCS</info>');        if (! $this->codeStylePsr($files)) {            throw new \Exception(sprintf('There are PHPCS coding standards violations!'));        }        $output->writeln('<info>Well done!</info>');    }    /**     * @return array     */    private function extractCommitedFiles()    {        $output = array();        $against = 'HEAD';        exec("git diff-index --cached --name-status $against | egrep '^(A|M)' | awk '{print $2;}'", $output);        return $output;    }    /**     * @param array $files     *     * @return bool     *     * @throws \Exception     */    private function phpLint($files)    {        $needle = '/(\.php)|(\.inc)$/';        $succeed = true;        foreach ($files as $file) {            if (! preg_match($needle, $file)) {                continue;            }            $process = new Process(['php', '-l', $file]);            $process->run();            if (! $process->isSuccessful()) {                $this->output->writeln($file);                $this->output->writeln(sprintf('<error>%s</error>', trim($process->getErrorOutput())));                if ($succeed) {                    $succeed = false;                }            }        }        return $succeed;    }    /**     * @param array $files     *     * @return bool     */    private function codeStylePsr(array $files)    {        $succeed = true;        $needle = self::PHP_FILES_IN_SRC;        $standard = 'PSR2';        foreach ($files as $file) {            if (! preg_match($needle, $file)) {                continue;            }            $phpCsFixer = new Process([                'php',                VENDOR_DIR.'/bin/phpcs',                '-n',                '--standard='.$standard,                $file,            ]);            $phpCsFixer->setWorkingDirectory(__DIR__.'/../../');            $phpCsFixer->run();            if (! $phpCsFixer->isSuccessful()) {                $this->output->writeln(sprintf('<error>%s</error>', trim($phpCsFixer->getOutput())));                if ($succeed) {                    $succeed = false;                }            }        }        return $succeed;    }}$console = new CodeQualityTool();$console->run();



В данном примере код будет проходить 3 проверки:
проверка на синтаксические ошибки
проверка на PSR2 через code sniffer

PSR2 можно заменить на любой другой который поддерживает code sniffer. Список поддерживаемых стандартов можно увидеть введя команду

 vendor/bin/phpcs -i


Шаг 5 Конфигурируем composer для реализации автозапуска проверки на pre-commit


Для того чтобы код проверки запускался на pre commit хук нам необходимо положить файлик с кодом, который сделали в 3 пункте положить в папку .git/hooks/pre-commit. Это можно сделать вручную но куда удобнее это дело автоматизировать. Для этого нам нужно написать обработчик, который будет копировать этот файлик и повешать его на событие которые вызывается после composer install. Для этого делаем следующее.

5.1 Создаем сам обработчик который будет копировать файлик pre-commit.php в папку хуков гита


Создаем файлик src/Composer/ScriptHandler.php
<?phpnamespace App\Composer;use Composer\Script\Event;class ScriptHandler{    /**     * @param Event $event     *     * @return bool     */    public static function preHooks(Event $event)    {        $io = $event->getIO();        $gitHook = '.git/hooks/pre-commit';        if (file_exists($gitHook)) {            unlink($gitHook);            $io->write('<info>Pre-commit hook removed!</info>');        }        return true;    }    /**     * @param Event $event     *     * @return bool     *     * @throws \Exception     */    public static function postHooks(Event $event)    {        /** @var array $extras */        $extras = $event->getComposer()->getPackage()->getExtra();        if (! array_key_exists('hooks', $extras)) {            throw new \InvalidArgumentException('The parameter handler needs to be configured through the extra.hooks setting.');        }        $configs = $extras['hooks'];        if (! array_key_exists('pre-commit', $configs)) {            throw new \InvalidArgumentException('The parameter handler needs to be configured through the extra.hooks.pre-commit setting.');        }        if (file_exists('.git/hooks')) {            /** @var \Composer\IO\IOInterface $io */            $io = $event->getIO();            $gitHook = '.git/hooks/pre-commit';            $docHook = $configs['pre-commit'];            copy($docHook, $gitHook);            chmod($gitHook, 0777);            $io->write('<info>Pre-commit hook created!</info>');        }        return true;    }}

5.2 Настраиваем composer чтобы запускался обработчик
в composer.json добавляем следующую секцию

    "scripts": {        "post-install-cmd": [            "App\\Composer\\ScriptHandler::postHooks"        ],        "post-update-cmd": [            "App\\Composer\\ScriptHandler::postHooks"        ],        "pre-update-cmd": "App\\Composer\\ScriptHandler::preHooks",        "pre-install-cmd": "App\\Composer\\ScriptHandler::preHooks"    },    "extra": {        "hooks": {            "pre-commit": "hooks/pre-commit.php"        }    }



pre-update-cmd, pre-install-cmd перед install и update удаляется старый обработчик

post-install-cmd, post-update-cmd после install и update будет устанавливаться обработчик на pre commit

В итоге файлкик composer.json примет следующий вид

composer.json
{    "name": "admin/test",    "authors": [        {            "name": "vitaly.gorbunov",            "email": "cezar62882@gmail.com"        }    ],    "minimum-stability": "stable",    "require": {},    "autoload": {        "psr-4": {            "App\\": "src/"        }    },    "scripts": {        "post-install-cmd": [            "App\\Composer\\ScriptHandler::postHooks"        ],        "post-update-cmd": [            "App\\Composer\\ScriptHandler::postHooks"        ],        "pre-update-cmd": "App\\Composer\\ScriptHandler::preHooks",        "pre-install-cmd": "App\\Composer\\ScriptHandler::preHooks"    },    "require-dev": {        "friendsofphp/php-cs-fixer": "^2.16",        "symfony/process": "^5.0",        "symfony/console": "^5.0",        "squizlabs/php_codesniffer": "^3.5"    },    "extra": {        "hooks": {            "pre-commit": "hooks/pre-commit.php"        }    }}



Запускаем еще раз composer install чтобы файлик скопировался куда надо.

Все готово, теперь если вы попытаетесь закомитить код с кривым code style то git console вам об этом скажет.

В качестве примере давайте создадим в папке src файлик MyClass.php по следующим содержаением.

<?phpnamespace App;class MyClass{    private $var1; private $var2;    public function __construct() {    }    public function test() {    }}

Пытаемся закомитить и получаем ошибки проверки кода.

MBP-Admin:test admin$ git commit -am 'test'Code Quality ToolFetching filesRunning PHPLintChecking code style with PHPCSFILE: /Users/admin/projects/test/src/MyClass.php----------------------------------------------------------------------FOUND 5 ERRORS AFFECTING 5 LINES----------------------------------------------------------------------  8 | ERROR | [x] Each PHP statement must be on a line by itself 10 | ERROR | [x] Opening brace should be on a new line 13 | ERROR | [x] Opening brace should be on a new line 15 | ERROR | [x] Function closing brace must go on the next line    |       |     following the body; found 1 blank lines before    |       |     brace 16 | ERROR | [x] Expected 1 newline at end of file; 0 found----------------------------------------------------------------------PHPCBF CAN FIX THE 5 MARKED SNIFF VIOLATIONS AUTOMATICALLY----------------------------------------------------------------------Time: 49ms; Memory: 6MBIn pre-commit line 53:                                                  There are PHPCS coding standards violations!                      

Ура, всё работает.
Подробнее..

3 года программирования вслепую. Часть 2

04.04.2021 20:17:04 | Автор: admin

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


Поиск и подготовка


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


К тому моменту я успел хорошо освоить symfony framework. html и js также не вызывали затруднений. А вот с css был знаком только в теории.
При наличие пары глаз поблизости рано или поздно, безусловно, можно выполнить любую задачу. К тому же для скринридеров существуют плагины, помогающие в позиционировании и не только. Но трудозатраты, в любом случае, были бы неоправданно высоки. Так что я сосредоточился на бекенде самой доступной части веб-разработки. Где незрячий может выполнять все задачи самостоятельно и наиболее полно себя проявить.


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


Еще на первых уроках видеокурсов по php преподаватель перечислил множество редакторов кода, имеющих форматер, подсветку синтаксиса, автодополнение и многое другое. Но они все оказались недоступны. Я ставил и сносил sublime, brackets, visual studio code ни одна из них не работала со скринридером. Скачивал и PHPStorm, потыкался в неозвучиваемый экран установки, после чего снес и его. Notepad++ вроде бы был доступен, хоть и очень относительно. Еще была visual studio, но она вообще не помогала в работе с php. Так что после долгих поисков я остановился на akel pad. В нем не было ничего. Но он был быстрым. Что ж, этого хватило для обучения, но дальше надо было искать что-то другое.


Я снова прошелся по списку редакторов и обнаружил, что за прошедшее время visual studio code "обрел голос". Ранее фокус скринридера просто упирался в молчащее окно программы, сейчас же интерфейс озвучивался как обычная веб-страница с привычными горячими клавишами навигации.
К тому моменту отступы по всему проекту уже были успешно расставлены с помощью php-cs-fixer.


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


Первая работа


Компания занималась разработкой веб-проектов под заказ. Ей требовался middle symfony разработчик. Миддлом на тот момент я еще, разумеется, не был. Но ключевые слова, перечисленные в вакансии, были знакомы. Да и наниматель готов был пойти навстречу. Трудности, однако, стали возникать сразу...


Первой из них был докер. О нем до того момента я только слышал. Сейчас же наш проект требовалось с его помощью поднять локально. Много ли усилий нужно, чтобы выполнить в консоли docker-compose up? В моем случае это заняло неделю.


Проблема заключалась в том, что у меня стояла windows 7. windows самая дружественная для незрячих ОС. Она давно и успешно обжита, для нее есть хорошо развитые скринридеры, множество синтезаторов речи и прочей инфраструктуры. Но вот докер на ней работал медленно, глючно и через виртуальную машину. Остальные члены команды, работавшие на маке и линуксе, помочь могли с большим трудом. Но в итоге после долгих задушевных переписок в чате проект все же удалось поднять. По ходу этого процесса я узнал о git autocrlf и yarn --no-bin-links.


В перерывах между сеансами укрощения докера я изучал код проекта. Он был построен на api platform. Сильно отличавшейся от всего, что я видел до этого. Контроллеров, например, в проекте не было совсем. Есть у api platform и свои подводные камни. Разумеется, успешно найденные в ходе первых же задач.


Кроме выше указанного на проекте присутствовало много того, что я видел впервые: функциональное тестирование, git flow, code review, ci/cd. Работали мы по скраму, спринтами по 2 недели с ежедневными митингами, управление задачами и учет времени шло в jira, переписка осуществлялась в slack. Освоить это и многое другое нужно было еще вчера.


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


По прошествии первого из трех месяца испытательного срока были подведены итоги. Ментор обозначил все положительные и отрицательные стороны моей работы, HR сказал, что через два-три дня они все проанализируют и примут решение о формате дальнейшего сотрудничества. Я воспринял это как полезную в плане обратной связи формальность. Таких было много в компании. К бюрократии мне тоже нужно было привыкать. Например, отпроситься на два часа в течение рабочего дня с гибким графиком у ПМ было нельзя. Для этого требовалось заполнить специальную форму на корпоративном портале, после чего один из менеджеров должен был заявку утвердить. Но через пару дней HR связался со мной и уведомил, что компания больше сотрудничать не планирует. Работодатель ожидал от меня большего, к тому же в тот момент у меня возникли проблемы со здоровьем. Вообще я чувствовал, что после первоначальных трудностей начинаю выходить на нормальную продуктивность. Но риски для компании перевесили потенциальные преимущества. А значит, у меня оставался всего месяц на то, чтобы впитать в себя как можно больше знаний из этой среды.


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


Работа над ошибками


Опыт первого трудоустройства давал обильную пищу для размышлений. Я приобрел большое количество знаний. Но были и проблемы. В частности, я был не доволен visual studio code. Кроме автодополнения переменных оно практически ничем не помогало при повседневной работе. Большинство разработчиков на предыдущем проекте использовало PHPStorm. И мне сильно не хватало его возможностей. Но в очередной раз установив его, я снова уткнулся в неозвучиваемый экран. После чего написал гневное письмо в поддержку о том, что неплохо бы обеспечить хоть какую-нибудь доступность их продуктов. Однако в ответном письме указывалось, что они вполне доступны уже сейчас.
В итоге я нашел на сайте jetbrains неочевидно расположенную страницу, посвященную accessibility, по инструкциям с которой установил java access bridge и IDE, наконец, заговорила.


Что ж, это и впрямь был качественный шаг вперед по сравнению с visual studio code. Вероятно с помощью плагинов и можно довести функциональность редактора кода до уровня IDE, но для php у меня и близко это не получилось.
Быстрая навигация по всей кодовой базе, очень мощное автодополнение, множество встроенных рефакторингов. Все это давало ощущение полета от процесса разработки. Вот разве что подсветку синтаксиса скринридер у меня так и не озвучил.
Следующие несколько месяцев я снова посвятил своему домашнему проекту, просто наслаждаясь тем, как быстро и легко пишется код.


Свободное плавание


Очередная сессия в ВУЗе была позади, а значит пришла пора искать новую работу. Я решил, что на последнем курсе, пусть и заочного обучения, мне понадобится больше времени на учебу. Поэтому я рассматривал вакансии только с частичной занятостью. И в течение нескольких недель нашел, что искал.


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


О том, что у меня проблемы со зрением, разным работодателям я говорил по-разному. Прямое заявление во время интервью, что я слепой, выбивает из колеи большинство интервьюиров. Некоторые люди реагируют в стиле: "Вот это да! Я что-то об этом слышал, можешь объяснить как ты делаешь то и это?" У кого-то возникают опасения по поводу моей работоспособности. Я объясняю, как работаю с компьютером, кидаю ссылки на статьи, где все подробно расписано. Однако на данный момент я стараюсь уведомлять о своих проблемах со зрение в момент, когда мы договариваемся об интервью. Это позволяет всем лучше к нему подготовиться. Еще интервьюиров часто интересует, есть ли какие-то особенности моего взаимодействия с другими членами команды. На что я указываю, что не люблю скриншоты и другую графическую информацию. И обычно прошу передавать ее текстом, хотя с развитием машинного распознавания картинок эта проблема стала менее острой.


Проект, на который я устроился, нужно было реализовывать с нуля. Мы вместе с присоединившимся чуть позже разработчиком создавали систему по ТЗ. Высокая интенсивность работы от нас не требовалась. В целом наш менеджер не был склонен держать процесс разработки под жестким контролем, да и имел постоянную занятость в другом месте. Так что вскоре все технические решения я принимал самостоятельно. Я старался максимально следовать образу разработки, увиденному на предыдущем проекте. CI, code review, функциональное тестирование. Это позволяло нам довольно уверенно двигаться вперед и при этом быть спокойными за тылы.


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


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


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


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


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


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


В последнее время


Так окончились первые 3 года моей карьеры. Но я так долго собирался с мыслями для этих двух статей, что с тех портоже прошло немало времени. Прошлой весной я снова стал изучать рынок вакансий. Он показался мне довольно бедным. Быть может все замерли, наблюдая первую ковидную волну, а может весна не самое активное время для найма. Так или иначе, где-то через месяц я нашел место, где работаю и сейчас.
За этот год я научился в юнит тесты. Подружился с докером на WSL. Стараюсь больше читать умных книг, пробовать новые инструменты, подтягивать английский, да и в целом двигаться вперед!

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru