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

Laravel

Настройка Xdebug3 для Laravel-приложения в Docker

30.01.2021 18:12:04 | Автор: admin

Начнём пожалуй, со структуры, в которой всё будет:

docker/ docker-compose.yml .env .env.example .gitignore services     database      dump      .gitignore     nginx      site.conf     php         Dockerfile         php.ini

А теперь обо всём по порядку:

Файл .gitingore содержит только одну строчку /.env

Файл .env.example в начале проекта такой же, как и .env

В файле docker-compose.yml содержится информация про все наши сервисы:

version: "3.7"services:  php:    build:      args:        uname: ${PHP_UNAME}        uid: ${PHP_UID}        gid: ${PHP_GID}      context: ./services/php    container_name: ${PROJECT_NAME}_php    image: ${PROJECT_NAME}_php    restart: unless-stopped    working_dir: /var/www/    volumes:      - ./services/php/php.ini:/usr/local/etc/php/php.ini      - ../:/var/www    environment:      COMPOSER_MEMORY_LIMIT: 2G      XDEBUG_CONFIG: client_host=${XDEBUG_REMOTE_HOST} client_port=${XDEBUG_STORM_PORT} remote_enable=1      PHP_IDE_CONFIG: serverName=${XDEBUG_STORM_SERVER_NAME}    networks:      - main_network    depends_on:      - db  db:    image: mysql:5.6    restart: unless-stopped    container_name: ${PROJECT_NAME}_db    command: --default-authentication-plugin=mysql_native_password    environment:      MYSQL_DATABASE: ${DB_DATABASE}      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}      MYSQL_PASSWORD: ${DB_PASSWORD}      MYSQL_USER: ${DB_USERNAME}    ports:      - ${DB_LOCAL_PORT}:3306    volumes:      - ./services/database/dump:/var/lib/mysql    networks:      - main_network  nginx:    image: nginx:1.17-alpine    restart: unless-stopped    container_name: ${PROJECT_NAME}_nginx    ports:      - ${NGINX_LOCAL_PORT}:80    volumes:      - ../:/var/www      - ./services/nginx:/etc/nginx/conf.d    networks:      - main_network    depends_on:      - phpnetworks:  main_network:    driver: bridge    name: ${PROJECT_NAME}_main_network    ipam:      driver: default      config:        - subnet: ${SUBNET_IP}/${SUBNET_MASK}

Из важного здесь стоит отметить ./services/php/php.ini:/usr/local/etc/php/php.ini - наш локальный файл php.ini (там некоторые конфиги дебаггера) будет намаплен на тот что внутри контейнера. XDEBUG_CONFIG - будет задана переменная окружения внутри контейнера php, которую потом будет испльзовать xdebug вместо значений по-умолчанию. Здесь мы задаем client_host - хост, к которому xdebug будет пытаться подключиться при инициации отладочного соединения. Этот адрес должен быть адресом машины, на которой ваш PhpStorm прослушивает входящие отладочные соединения. Получается так, что наша локальная машина находится в одной подсети с запущеными контейнерами, а её адресс будет первым в этой подсети. Таким образом, мы всегда можем знать каким будет адресс нашей машины, и позже зададим это значение в переменную XDEBUG_REMOTE_HOST . В CLIENT_PORT нужно будет задать порт, установленный на прослушивание в IDE (9003). XDEBUG_STORM_SERVER_NAME - имя сервера, который мы создадим в IDE позже. Этот параметр нужен, чтобы сообщить PhpStorm, как сопоставлять пути при подключении с докера (ведь у вас же открыты локальные файлы в редакторе, а код работает на удалённых; хотя при испльзовании volumes это не совсем так).

Вот, как выглядит файл окружения .env :

PROJECT_NAME=my_projectDB_DATABASE=my_project_dbDB_USERNAME=my_projectDB_PASSWORD=p@$$w0rdDB_ROOT_PASSWORD=toorPHP_UNAME=devPHP_UID=1000PHP_GID=1000DB_LOCAL_PORT=3377NGINX_LOCAL_PORT=8077XDEBUG_STORM_SERVER_NAME=DockerXDEBUG_REMOTE_HOST=192.168.227.1XDEBUG_STORM_PORT=9003SUBNET_IP=192.168.227.0SUBNET_MASK=28

На счёт подсети для проекта, то здесь мы задали 192.168.227.0 с маской 28, то-есть для всех устройств остаётся 32 - 28 = 4 бита, что равносильно 2 ** 4 - 1 = 15 контейнеров. Не 16 потому что в подсеть входит также наша локальная машина, которая, кстати, будет иметь адресс 192.168.227.1. Именно это значение мы задали в переменную XDEBUG_REMOTE_HOST.

Настройки веб-сервера site.conf:

server {    listen 80;    server_name 127.0.0.1 localhost;    client_max_body_size 5m;    error_log  /var/log/nginx/error.log;    access_log /var/log/nginx/access.log;    root /var/www/public;    index index.php;    location / {        try_files $uri $uri/ /index.php?$query_string;        gzip_static on;    }    location ~ \.php$ {        try_files $uri =404;        fastcgi_split_path_info ^(.+\.php)(/.+)$;        fastcgi_pass php:9000;        fastcgi_index index.php;        include fastcgi_params;        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;        fastcgi_param PATH_INFO $fastcgi_path_info;    }}

В нашем Dockerfile (тот что для php) нужно не забыть установить и включить xdebug. Для этого добавляем 2 строчки:

RUN pecl install xdebugRUN docker-php-ext-enable xdebug

Стоит отметить, что будет испльзована последняя (то-есть 3) версия (есть различия в конфигурации по сравнению с 2).

Полный Dockerfile:

FROM php:7.4-fpm# Arguments defined in docker-compose.ymlARG unameARG gidARG uid# Install system dependenciesRUN apt-get update \    && apt-get install -y \        git \        curl \        dpkg-dev \        libpng-dev \        libjpeg-dev \        libonig-dev \        libxml2-dev \        libpq-dev \        libzip-dev \        zip \        unzip \        cronRUN pecl install xdebugRUN docker-php-ext-enable xdebugRUN docker-php-ext-configure gd \  --enable-gd \  --with-jpegADD ./php.ini /usr/local/etc/php/php.ini# Clear cacheRUN apt-get clean && rm -rf /var/lib/apt/lists/*# Install PHP extensionsRUN docker-php-ext-install pdo pdo_mysql pdo_pgsql pgsql mbstring exif pcntl bcmath gd sockets zip# Get latest ComposerCOPY --from=composer:latest /usr/bin/composer /usr/bin/composer# Create system user to run Composer and Artisan CommandsRUN groupadd --gid $gid $unameRUN useradd -G www-data,root -s /bin/bash --uid $uid --gid $gid $unameRUN mkdir -p /home/$uname/.composer && \    chown -R $uname:$uname /home/$uname# Set working directoryWORKDIR /var/wwwUSER $uname# Expose port 9000 and start php-fpm serverEXPOSE 9000CMD ["php-fpm"]

Также, зададим некоторые параметры исплнения в php.ini :

max_execution_time=1000max_input_time=1000xdebug.mode=debugxdebug.log="/var/www/xdebug.log"xdebug.remote_enable=1

Запустим наше приложение:

docker-compose up -d

И дальше пойдём в PhpStorm для настройки:

Создадим сервер с названием Docker и cделаем маппинг локального корня проекта (/var/www/quizzy.loc) на путь, по которому он лежит в докере (/var/www):

Дальше, нам нужно будет настроить использование интерпретатора php из докера:

Настраиваем php interpreterНастраиваем php interpreterВыбираем сервис, в котором находится php и указываем путь к интерпретаторуВыбираем сервис, в котором находится php и указываем путь к интерпретаторуЗадаём параметры запуска (через exec)Задаём параметры запуска (через exec)

Теперь можем перейти в подпункт "Debug" и настроить порт на котором запускать:

Настраиваем дебаггерНастраиваем дебаггер

Проверим, работает ли Xdebug:

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

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

Перезагружаем страницу, и в PhpStrom должен поймать подключение:

Подробнее..

LaravelДайджест (28 декабря 2020 10 января 2021)

10.01.2021 18:10:27 | Автор: admin

Подборка свежих уроков, видео и пакетов по фреймворку Laravel.


Laravel Дайджест


Релизы


  • Laravel 8.21
  • Livewire 2.3.6
  • Потенциальная уязвимость в Fortify 1.7.3
    Рекомендуется обновление до Fortify 1.7.4, включая приложения на базе Jetstream.
  • Jetstream 2
    Добавлено: приглашения в команду, шаблоны аутентификации для Inertia, поддержка Tailwind 2.
  • PEST 1.0
    После семи месяцев разработки выпущена первая стабильная версия
  • Laravel IDE Helper 2.9
    Прекращена поддержка Laravel 6 и 7, а также поддержка PHP 7.2. Добавлена поддержка Doctrine 3.
  • Laravel DebugBar Companion
    Десктопное приложение для дебага. Доступно для macOS, Windows и Linux.
  • Eloquent Attribute Value Prediction
    Пакет для прогнозирования значений eloquent-моделей, используя возможности машинного обучения.
  • Larax
    Пакет для отслеживания ошибок в проекте в режиме реального времени.
  • Laravel Github Actions Workflow Configurator
    Генерация yaml-файла для Github Actions
  • Socialstream
    Пакет подменяющий аутентификационные каркасы Jetstream на поддерживающие Laravel Socialite
  • Envault 2
    Управление локальными .env-файлами всех проектов
  • Онлайн-Генератор отношений
    Постоянно забываете порядок полей в сложных отношениях? Попробуйте это
  • Ray
    Десктопное приложение для дебага от Spatie.

Уроки



Видео



Памятка Какой каркас аутентификации взять для нового проекта?


Дополненная и русифицированная версия
Какой каркас аутентификации взять для нового проекта?


Телеграм на русском


Подробнее..

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
Подробнее..

LaravelДайджест (1017 января 2021)

18.01.2021 08:22:02 | Автор: admin

Подборка свежих уроков, видео и пакетов по фреймворку Laravel.


Laravel Дайджест


Безопасность



Релизы


  • Laravel 8.22
  • Turbo Laravel
    Пакет реализующий Hotwire в Laravel. Аналог Turbo Rails. Набор методов для ускорения изменений страниц. Позволяет достигнуть скорости SPA без написания яваскрипта. Внимание: пакет не протестирован в боевых условиях. Фидбэк и помощь приветствуются.
  • Laravel Actions 2
    Пакет для организации логики на основе действий (actions), которые предоставляет ваше приложение. Вместо создания контроллеров, заданий, слушателей и т.д, он позволяет вам создать PHP-класс, который обрабатывает конкретную задачу и запускать этот класс как угодно.
  • Venture 2.0
    Пакет для создания и управления сложными асинхронными процессами. Добавлена поддержка вложенных процессов.
  • Square 2
    Библиотека статических Eloquent-моделей для фиксированных данных, например, валюты, страны, аэропорты.

Уроки



Видео



Видео по Jetstream



Видео-курс Laravel для начинающих



Телеграм на русском


Подробнее..

LaravelДайджест (1824 января 2021)

24.01.2021 20:20:26 | Автор: admin

Подборка свежих уроков, видео и пакетов по фреймворку Laravel.


Laravel Дайджест


Тейлор о версионировании фреймворка


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


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


Я по-прежнему считаю, что предыдущее наше версионирование было лучше ( {paradigm/era}.{major}.{patch} ). Лучше именно для фреймворка, а не для пакетов, таких как Cashier, Dusk и т.д. Пусть это станет уроком для других опенсорсных разработчиков: доверяйте интуиции и не поддавайтесь давлению.


На русском языке


  • 18 советов по оптимизации запросов к базе данных
    Если ваше приложение медленно работает или делает много запросов к БД, то используйте наши советы, чтобы сократить время его загрузки. Мы исследуем методы оптимизации MySQL-, eloquent- и просто сырых запросов к базе данных.
  • Непрерывная интеграция для Laravel с помощью Github Actions
    Github предлагает сервис Непрерывной Интеграции (CI Continuous Integration), который называется Github Actions. Процессы CI-сборки называются воркфлоу (workflows рабочие процессы). Они запускаются, когда в вашем github-репозитории происходят определенные события: коммиты, пул-реквесты. Если вы работаете в команде разработчиков, то Github Actions поможет автоматически проверить пул-реквесты, запустив для них необходимые тесты. После этого можно с уверенностью принимать его и вливать в проект.
  • Laravel Lang Publisher Менеджер локализаций
    При использовании мультиязычного приложения перед разработчиком встаёт проблема перевода стандартных фраз и поддержка перевода в актуальном состоянии. В попытках автоматизации многие находят пакет Laravel-Lang/lang. Но у него есть один недостаток он предоставляет только файлы перевода, которые необходимо вручную скопировать в своё приложение, при этом не потеряв другие свои переводы. И здесь мы сталкиваемся с проблемой, которую решает пакет Laravel Lang Publisher.

Релизы



Уроки



Видео



Телеграм на русском


Подробнее..

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
Подробнее..

LaravelДайджест (25 января 7 февраля 2021)

07.02.2021 20:15:43 | Автор: admin

Подборка свежих уроков, видео и пакетов по фреймворку Laravel.


Laravel Дайджест


Новости


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


На русском языке


  • Полнотекстовый поиск с помощью MeiliSearch и Laravel Scout
    MeiliSearch простое автономное решение для полнотекстового поиска, которое легко интегрировать в Laravel-приложение, с помощью драйвера для Laravel Scout.
  • Мощный Illuminate Request
    Когда я впервые познакомился с Symfony, то меня поразил компонент symfony/http-foundation. Думаю, это один из самых важных пакетов современного PHP-приложения. Он заполняет лакуны основных PHP-функций, предоставляя дружественный объектно-ориентированный интерфейс для запросов и ответов.
  • Как подружить ltree и Laravel
    У нас много древовидных справочников и вложенность их не ограничена, кол-во в некоторых составляет несколько тысяч, а работать с ними надо максимально эффективно и удобно, как с точки зрения кода, так с точки зрения производительности.

Релизы


  • Laravel 8.26
  • Environment Synchronization
    Пакет для актуализации файла .env.example
  • Laravel Package Tools
    Пакет для упрощения создания своих пакетов. Регистрация конфига, миграции и многое другое.
  • Laravel FastLogin
    Пакет для добавления логина через FaceID/TouchID
  • Puny
    Библиотека минималистичного модульного тестирования
  • Перезапуск сайта Laravel Pastebin
    Обновлён дизайн. Добавлен тёмный режим. Сделана кнопка копирования.

Уроки



Тестирование



Видео



Телеграм на русском


Подробнее..

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
Подробнее..

LaravelДайджест (821 февраля 2021)

21.02.2021 20:17:01 | Автор: admin

Подборка свежих уроков, видео и пакетов по фреймворку Laravel.


Laravel Дайджест


Релизы


  • Laravel 8.28
  • Laravel Analytics
    Аналитика просмотров страниц с панелью управления
  • Laravel Breeze 1.1
    Добавлена поддержка Inertia.js
  • Laravel Spark Next
    Стартовый комплект для SaaS-решений с подписками.
  • Package Wizard
    Консольная библиотека для создания пакетов
  • Sprucewire
    Адаптер для Spruce и Livewire. Позволяет использовать глобальные состояния Spruce для обмена данными между компонентами Livewire и синхронизировать их состояния.
  • Lumen Microservice
    Стартовый каркас проекта в Докере
  • Плагина Laravel Tinker 2.0 для PhpStorm
    Быстрый запуск кода, как через laravel artisan tinker, но со всеми удобствами IDE
  • Laravel Centrifugo
    Драйвер Centrifugo для Laravel
  • Laravel PHP K8s 2.0
    Пакет для использования Kubernetes Cluster API в Laravel
  • Larafirebase
    Пакет для отправления уведомлений через Firebase в Laravel
  • Laravel Blade Sortable
    Blade-компонент для добавления сортируемых/перетаскиваемых html-элементов

Уроки



Тестирование



Видео



Код-ревью от Povilas Korop



Телеграм на русском


Подробнее..

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
Подробнее..

LaravelДайджест (22 февраля 7 марта 2021)

07.03.2021 20:08:29 | Автор: admin

Подборка свежих уроков, видео и пакетов по фреймворку Laravel.


Laravel Дайджест


Релизы


  • Laravel 8.30
  • Filament
    Админка для Laravel-проектов на TALL-стеке
  • Livewire Devtools
    Браузерное расширения для дебагга Livewire-приложений. Выложено пока только для Хрома, но скоро будет и под Firefox.
  • Laravel Kit 2.0
    Десктопная админка для Laravel-проектов. (Windows, macOS, Linux)
  • Laravel Prefixed IDs
    Пакет от Spatie для создания префиксов идентификаторов моделей
  • Laravel Masked DB Dump
    Пакет для создания дампов базы данных с возможностью замены полей на лету для защиты конфиденциальной информации
  • Airdrop for Laravel
    Пакет для ускорения развертывания за счет пропуска уже скомпилированных ресурсов.
  • Laravel Vite
    Интеграция Vite (очень быстрый сборщик) для замены Laravel Mix.

Уроки



Livewire



API



Видео



Код-ревью от Povilas Korop



Телеграм на русском


Подробнее..

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

Подробнее..

Длинная история про то, как мы веб-разработчика на фрилансерских сайтах искали, но так и не нашли

06.06.2021 20:07:08 | Автор: admin

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

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

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

Постепенно у нас накопилось приличное количество текстового материала - статьи, аналитика, разные тестовые задачки, и тому подобное. Параллельно YouTube канал набрал несколько сотен тысяч подписчиков. А еще мы стали замечать, что доля поискового и внешнего трафика на канале приближается к 50% (это много). Причем поисковые фразы хорошо коррелируют с "нужными" высокочастотными запросами.

В общем, мы дозрели до своего сайта.

Пишем техзадание

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

Сделать совершенно типичный сайт для публикации статей. Функциональные требования не выделяются чем-то уникальным. Дизайн - примитивный. Нагрузка - средняя. Pagespeed insights >= 90%.

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

Полнотекстовый [морфологический] поиск а-ля ElasticSearch или Sphinx.
Фильтрация списков статей по множеству тегов.
Сортировка списков статей по времени и/или категориям.
Кастомное меню с навигацией по ключевым статьям на главной странице.
И еще одна секретная фича на фронтенде.

Я еще расскажу об этих "специальных" требованиях ниже. Впрочем, даже с ними функциональная часть нашего ТЗ не выглядела как Rocket Surgery.

Осталось разобраться с нефункциональными требованиями.

Гугл не скрывает, что использует скорость загрузки страниц в качестве одного из критериев своих алгоритмов ранжирования. Поэтому мы включили в ТЗ оптимизацию сайта до зеленой зоны PageSpeed Insights. Кроме вполне очевидной пользы, это требование позволяло нам отсечь неопытных исполнителей (спойлер: это сработало, но не совсем так, как мы предполагали).

За отправную точку оценки нагрузки взяли наш YouTube трафик. В хороший день это 7-8 тысяч уникальных посетителей со средним количеством просмотренных видео 2,4. Берем полуторакратный запас и получаем порядка 30 тысяч хитов. Это примерно 12 тысяч посетителей в день.

В общем, у нас получилось вполне приличное техзадание на 15 страниц.

Публикуем заказ.

Рассуждая в том ключе, что "хорошие" исполнители ТЗ читают, а "плохие" нам не нужны, мы добавили в техзадание "капчу": просьбу начинать отклик со слов "Hello there!".

Сам текст публикации был максимально коротким. Примерно таким:

Cделать контентный сайт на базе вашей любимой CMS/фреймворка согласно ТЗ

Внимательно прочитать требования к проекту (!).
Выбрать оптимальную CMS/фреймворк, удовлетворяющие требованиям.
Оценить общую трудоемкость и стоимость проекта.
Согласовать приблизительный поэтапный план разработки.
Собственно, сделать сайт согласно плану.

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

площадка

стоимость публикации

стоимость "выделения"

ИТОГО:

freelance.habr.com

0

800

800

fl.ru

350

1250

1600

freelancehunt.com

0

840

840

freelance.ru

350

0

350

weblancer.net

0

0

0

На все про все было потрачено примерно 3500 рублей. Бюджетненько!

Разбираем отклики

Всего на заказ отозвались ровно 100 исполнителей:

площадка

всего откликов

прошли "капчу"

прошли "капчу" %%

freelance.habr.com

33

8

24%

fl.ru

30

4

13%

freelancehunt.com

20

1

5%

freelance.ru

15

1

7%

weblancer.net

2

0

0%

ИТОГО:

100

14

14%

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

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

Вид первый, "автобот":

Здравствуйте, это как раз мой профиль, готов помочь!
Портфолио > https://***.**/portfolio/
Телефон/Viber/WhatsApp: ***

(здесь и далее приведены совершенно реальные отклики, орфография и пунктуация авторов сохранены)

Характерные признаки "автобота": присылает отклик практически сразу после публикации заказа. Где-то в интервале от минуты до пары часов. Как правило, в портфолио бота половина ссылок не работает. Причем это лучшая половина. Чаще всего боты водятся на freelancehunt.com и freelance.ru. Что бы вы ни делали, вступать с вами в переписку бот не будет.

Вид второй, "разработчик продающих сайтов":

--не бот--

***, добрый день! Меня зовут ***, я веб-разработчик с опытом более 10 лет. Самое главное - это продающая структура, а уж потом дизайн и верстка, я делаю именно "продающие" сайты, которые реально работают.

CMS на которых делаю сайты: 1. 1С Битрикс 2. Тильда 3. Wordpress

Характерные признаки: старательно дистанцируется от ботов. Вне зависимости от задачи, использует такие словосочетания как "повышение продаж", "продающая структура", "конверсия", "SEO", и другие. Средний опыт работы - более 10 лет. В списке регалий имеет Битрикс и Тильду.

Наконец, вид третий. Самый забавный. "эффективный project manager":

Добрый день, ***!

Бюджет вашего проекта 10 000$. Если вас устраивает такая стоимость, то давайте назначим звонок на конкретное время. Длительность звонка: 1 час. Наш CTO к тому времени ознакомится с вашим документом и подготовит ответы на ваши вопросы.

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

В итоге, лучше всего себя показал freelance.habr.com (ура!), хуже всего - freelancehunt.com и freelance.ru. Как еще существует weblancer.net - непонятно.

Далее, из сотни откликнувшихся лишь 14 человек полностью прочитали наше задание. Еще шестеро капчу не прошли, но попали в short-list, потому что написали развернутый отклик.
Итого - 20.

Слава роботам!

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

Life is what happens to you while you're busy making other plans
John Lennon.

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

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

Почти половина отвалилась сразу, даже не попрощавшись. Причем, что интересно, отвалились как раз те, чьи отклики производили наилучшее впечатление.

Несколько человек все же планы прислали, чем нас еще немного повеселили. Ниже - наш топ-3.

"Стандартный" план:

этапы разработки у нас стандартные ))
1. Разработка прототипа проекта (UX проектирование)
2. Разработка дизайна
3. Верстка
4. Посадка верстка и доработки серверной части
5. Тестирование

Еще вариант, со сроками:

1. Прототипирование и создание дизайна сайта 10 дней
2. Верстка сайта 7 дней
3. Программирование структуры сайта 30 дней
4. Создание уникальных модулей 10 дней

И, наконец, мой любимый, самый короткий:

Разработка фронтенда, админ-панель, бэкенд
По срокам примерно 2-3 недели, цену предлагайте сами.

В результате, этап планирования прошли шестеро. Трое - с Wordpress, и по одному с Laravel, October CMS, и Drupal.

Впереди нас ждет еще очень много всего интересного!

Wordpress - три попытки, от $2000 до $4000

Когда-то в начале двухтысячных Wordpress задумывался как простая система для self-hosting блога: удобная админка, таксономия, теги, антиспам фильтр, неплохой по меркам тех времен редактор, и так далее. Где-то начиная с 2011, стали появляться разнообразные плагины: e-commerce, booking, формы, галереи, и так далее.

Наверное, это был правильный вектор развития с точки зрения бизнеса. В конце концов, миллионы владельцев "лэндингов" и интернет-магазинов не могут ошибаться. Вот только как система для публикации контента Wordpress так и остался где-то в 2010-м. И даже, по-моему, немного откатился назад.

Камнем преткновения для Wordpress разработчиков стали наши требования по скорости загрузки страниц. Напомню, что нам очень хотелось получить сайт в зеленой зоне PageSpeed Insights. Так вот, по оценке наших кандидатов, оптимизация Wordpress съедала 40-50%% бюджета. Буквально каждый кандидат категорически настаивал на разработке кастомизированной темы "с нуля".

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

Мы просто подсчитали, что только оптимизация обходилась нам где-то от $800 до $2000. Но это еще не самое страшное.

Другая проблема Wordpress, как ни странно, - это очень скромные "встроенные" средства управления контентом. Приведу такой пример: на Хабре можно посмотреть список статей в хабе фриланс, либо в хабе разработка веб-сайтов, но нельзя получить список статей, относящихся к обоим хабам одновременно.

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

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

В общем, у нас были три Wordpress-кандидата. Все трое - хорошие специалисты с неплохими портфолио и впечатляющим опытом. Кстати, говоря о бюджете, даже с учетом Wordpress-оверхеда, он был вполне приемлемым: от $2000 до $4000.

Мы всячески пытались довести хотя бы одного из кандидатов до контракта. Но не сложилось: первый сошел с дистанции в процессе согласования деталей. Второй изначально особой активности не проявлял, как бы намекая, что не сильно заинтересован. Третий немного подумал, и сам предложил отказаться от Wordpress в пользу Laravel. За +20% к цене.

А ведь все так хорошо начиналось!

Laravel - $2000

Параллельно у нас появился еще один многообещающий кандидат:

Hello there! Я не могу предложить готовую CMS, но зато могу предложить потрясающий фреймворк Laravel. На нём могу реализовать весь необходимый вам функционал.

Вечер перестает быть томным, - подумали мы.

Буквально за выходные кандидат-ларавел развёл кипучую деятельность: написал весьма подробное техническое обоснование, включая примерный расчет VPS сервера под требуемую нагрузку, список необходимых сторонних библиотек, краткий план работ, mind map, и даже прислал несколько референсных тем. Несмотря на разницу во времени, кандидат отвечал на вопросы практически мгновенно, да и в целом производил самое что ни на есть благоприятное впечатление.

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

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

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

А жаль, нормально же вроде общались.

Drupal - $8000

В любом pet-проекте есть несколько интересных идей "на вырост". У нас они тоже есть. Что гораздо важнее, мы знаем, чего мы делать не будем: не будем открывать интернет-магазин, запускать корпоративный портал, хостить форумы, и тому подобное.

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

Поэтому мы немного удивились, когда на наш заказ откликнулась целая web-студия с предложением сделать сайт на Drupal 9. Они оценили бюджета проекта в $8000.

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

Как были обоснованы $8000? А никак! Все, что было хоть сколько-нибудь детально расписано, относилось к дополнительным опциям. Ну, вы понимаете, все эти , "структура сайта", "user story", "подготовка ТЗ" и "фиксация деталей по функциям". Где-то даже промелькнула фраза "наполнение контентом".

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

В общем, от этого предложения мы отказались сами: писать user story нам не хотелось, да и в целом выбор Drupal показался необоснованным.

А еще мы пожалели $8000.

October CMS - $2000

Нам изначально нравилась философия минимализма в October. К примеру, мы не планировали регистрацию внешних пользователей. Для простого контентного сайта в этом просто нет необходимости: контент будем добавлять только мы, а для комментариев все равно удобнее использовать что-то вроде commento.io. Зачем нам лишний код?

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

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

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

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

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

В общем, мы заключили контракт и закинули деньги за первые два этапа на escrow.

Первый этап был закончен точно в срок, мы даже удивились. Не то, чтобы это был сложный этап, - изначальная установка CMS, базовых плагинов и тем. Но к этому моменту мы уже так устали от всего происходящего, что радовались как маленькие дети. У нас, наконец, что-то заработало! Мы даже можем что-то опубликовать!

Увы, наша радость была недолгой. Второй этап был отложен на неделю, потом еще на неделю. Судя по коммитам на Github, второй этап даже не был начат. А потом исполнитель просто пропал.

Запил, наверное.

Бонус - профессиональные студии, до $18 000

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

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

Если коротко, то там еще хуже.

Нам присылали "коммерческие предложения", скопированные с других проектов, не имеющих ничего общего с нашим. Нам предлагали сделать магазин, - "вам же когда-нибудь понадобится магазин?" Нам предлагали "зашифровать чувствительные данные". Нам предлагали увеличить трафик в два раза (серьезно?!?), а также "переделать сайт под SEO продвижение", и "протестить нишу". Да, еще нам предложили "написать адекватное ТЗ". Я уже рассказывал про use cases и user story?

Нам долго и обстоятельно рассказывали о преимуществах EditorJS над Tailwind, и TinyMCE над Bootstrap (и наоборот). Нам даже объяснили, почему монолитная архитектура нам подходит лучше, чем микросервисы. Ах да, ну и конечно, правильный PHP - это PHP 7, правильный Laravel - это 8, ну а правильный HTML - это 5! И никак иначе!

Зачастую все это сопровождалось огромным количеством грамматических ошибок и перемежалось фразами "я вас услышал", "наиболее оптимальный", и тому подобными. Не поймите меня неправильно, лично я спокойно отношусь к грамматическим ошибкам. Но когда в твоей команде большинство - лингвисты, им сложно объяснить, почему project-manager подрядчика пишет как третьеклассник, а разговаривает как пятиклассник.

И все это за каких нибудь $18 000 (восемнадцать тысяч долларов США). Ну хорошо, если мы "урежем" часть требований - $12 000. Но тогда работа аналитика оплачивается отдельно.

Вместо эпилога

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

Подробнее..

Зачем нужен 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

Подробнее..

Расширяем возможности миграций Laravel за счет Postgres

15.01.2021 02:22:41 | Автор: admin

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

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

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

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

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

Миграции

Вот как выглядит стандартная миграция в Laravel:

Пример обычной миграции
<?phpdeclare(strict_types=1);use Illuminate\Database\Migrations\Migration;use Illuminate\Database\Schema\Blueprint;use Illuminate\Support\Facades\Schema;class CreateDocuments extends Migration{    private const TABLE = 'documents';    public function up()    {        Schema::create(static::TABLE, function (Blueprint $table) {            $table->bigIncrements('id');            $table->timestamps();            $table->softDeletes();            $table->string('number');            $table->date('issued_date')->nullable();            $table->date('expiry_date')->nullable();            $table->string('file');            $table->bigInteger('author_id');            $table->bigInteger('type_id');            $table->foreign('author_id')->references('id')->on('users');            $table->foreign('type_id')->references('id')->on('document_types');        });    }    public function down()    {        Schema::dropIfExists(static::TABLE);    }}

Но, тут вы прочитали статью на хабре про новые типы в Postgres, например, tsrange и захотели добавить в миграцию что-то вроде этого...

$table->addColumn('tsrange', 'period')->nullable();

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

$table->tsRange('period')->nullable();

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

Пример как пропатчить Laravel миграции

Патчим Blueprint

<?phpBlueprint::macro('tsRange', function (string $columnName) {  return $this->addColumn('tsrange', $columnName);});

Патчим PostgresGrammar

<?phpPostgresGrammar::macro('typeTsrange', function () {  rerurn 'tsrange';});

Далее создаем какой-нить провайдер, типа ExtendDatabaseProvider:

<?phpuse Illuminate\Support\ServiceProvider;class DatabaseServiceProvider extends ServiceProvider{    public function register()    {        Blueprint::macro('tsRange', function (string $columnName) {            return $this->addColumn('tsrange', $columnName);        });        PostgresGrammar::macro('typeTsrange', function () {            return 'tsrange';        });    }}

Вроде бы все, запускаем миграцию, все работает..

И не важно, переопределили ли вы половину компонентов Laravel для работы с БД или воспользовались макросами и миксинами из MacroableTrait, круги ада еще не закончились.

Круги ада (часть 1)

И вот вы локально все это крутите, все работает как часы, написали +100500 строк кода, и решили выкатить готовую таску в гитлаб. Мы же идем в ногу со временем и там у нас Докер, CI, тесты и тд...

И вот мы замечаем, что наши миграции в "не локальном" окружении не работают из-за ошибки:

Doctrine\DBAL\Driver\PDOException: SQLSTATE[08006] [7]FATAL:  sorry, too many clients already

И вот ты сидишь и думаешь, что ты не так делаешь, начинаешь подпихивать в локальный .env окружения из CI вашего GitLab, переписывать код, вместо макросов переопределять разные классы, распихивать везде и всюду дебаги, все идеально, локально ошибки нет. А в пайплайнах CI не так-то просто дебажить, начинаешь пихать везде и всюду логирование, и это не помогает, ведь ошибка где-то внутри, в vendor. Но ты об этом еще пока не знаешь.

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

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

Важен контекст

Ошибка sorry, too many clients already может быть совершенно по любой причине.

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

Doctrine\DBAL\DBALException: Unknown database type tsrange requested,Doctrine\DBAL\Platforms\PostgreSQL100Platform may not support it.

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

Барабанная дробь

Any Doctrine type that you use has to be registered with \Doctrine\DBAL\Types\Type::addType().

You can get a list of all the known types with \Doctrine\DBAL\Types\Type::getTypesMap().

If this error occurs during database introspection then you might have forgotten to register all database types for a Doctrine Type.

Use AbstractPlatform#registerDoctrineTypeMapping() or have your custom types implement Type#getMappedDatabaseTypes().

If the type name is empty you might have a problem with the cache or forgot some mapping information.

Иными словами, нужно успеть зарегистрировать тип в Doctrine\Dbal прежде, чем до вашего Database Connection дойдет информация, что вы используете кастомные типы (под кастомными я подразумеваю те, которые есть в Postgres, но отсутствуют в заветном getTypesMap в недрах Doctrine.

Круги ада (часть 2)

Вы лезете в исходники, куда-то очень глубоко в vendor в недры doctrine\dbal...

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

О боже, часть из них приватные!

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

Руки опускаются окончательно..

Спасительный круг

Не буду ходить вокруг, да около.

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

Подумаем о будущем

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

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

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

Основные компоненты

Эти объекты нам надо модифицировать, но сделать это в стиле ООП, сбоку, по типу как трейты иньектятся в классы:

  • Blueprint - объект, использующийся в миграциях, по сути билдер

  • Builder - он же фасад Schema

  • PostgresGrammar - объект для компиляции Blueprint-а в SQL-выражения

  • Types - наши типы

Давайте, придумаем объект, который будет подмешивать объекты расширений во внутренние объекты Laravel таким образом, чтобы и овцы были целы и волки сыты, имею ввиду, чтобы IDE был счастлив, все работало, а наш код был понятным.

Пример класса, описывающего такое расширение
<?phpnamespace Umbrellio\Postgres\Extensions;use Illuminate\Support\Traits\Macroable;use Umbrellio\Postgres\Extensions\Exceptions\MacroableMissedException;use Umbrellio\Postgres\Extensions\Exceptions\MixinInvalidException;abstract class AbstractExtension extends AbstractComponent{    abstract public static function getMixins(): array;    abstract public static function getName(): string;    public static function getTypes(): array    {        return [];    }    final public static function register(): void    {        collect(static::getMixins())->each(static function ($extension, $mixin) {            if (!is_subclass_of($mixin, AbstractComponent::class)) {                throw new MixinInvalidException(sprintf(                    'Mixed class %s is not descendant of %s.',                    $mixin,                    AbstractComponent::class                ));            }            if (!method_exists($extension, 'mixin')) {                throw new MacroableMissedException(sprintf('Class %s doesnt use Macroable Trait.', $extension));            }            /** @var Macroable $extension */            $extension::mixin(new $mixin());        });    }}

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

Патчим PostgresConnection
<?phpnamespace Umbrellio\Postgres;use DateTimeInterface;use Doctrine\DBAL\Connection;use Doctrine\DBAL\Events;use Illuminate\Database\PostgresConnection as BasePostgresConnection;use Illuminate\Support\Traits\Macroable;use PDO;use Umbrellio\Postgres\Extensions\AbstractExtension;use Umbrellio\Postgres\Extensions\Exceptions\ExtensionInvalidException;use Umbrellio\Postgres\Schema\Builder;use Umbrellio\Postgres\Schema\Grammars\PostgresGrammar;use Umbrellio\Postgres\Schema\Subscribers\SchemaAlterTableChangeColumnSubscriber;class PostgresConnection extends BasePostgresConnection{    use Macroable;    private static $extensions = [];    final public static function registerExtension(string $extension): void    {        if (!is_subclass_of($extension, AbstractExtension::class)) {            throw new ExtensionInvalidException(sprintf(                'Class %s must be implemented from %s',                $extension,                AbstractExtension::class            ));        }        self::$extensions[$extension::getName()] = $extension;    }    public function getSchemaBuilder()    {        if ($this->schemaGrammar === null) {            $this->useDefaultSchemaGrammar();        }        return new Builder($this);    }    public function useDefaultPostProcessor(): void    {        parent::useDefaultPostProcessor();        $this->registerExtensions();    }    protected function getDefaultSchemaGrammar()    {        return $this->withTablePrefix(new PostgresGrammar());    }    private function registerExtensions(): void    {        collect(self::$extensions)->each(function ($extension) {            /** @var AbstractExtension $extension */            $extension::register();            foreach ($extension::getTypes() as $type => $typeClass) {                $this                    ->getSchemaBuilder()                    ->registerCustomDoctrineType($typeClass, $type, $type);            }        });    }}

А также необходимо переопределить провайдер и фабрику для работы с БД:

Патчим DatabaseProvider
<?phpnamespace Umbrellio\Postgres;use Illuminate\Database\DatabaseManager;use Illuminate\Database\DatabaseServiceProvider;use Umbrellio\Postgres\Connectors\ConnectionFactory;class UmbrellioPostgresProvider extends DatabaseServiceProvider{    protected function registerConnectionServices(): void    {        $this->app->singleton('db.factory', function ($app) {            return new ConnectionFactory($app);        });        $this->app->singleton('db', function ($app) {            return new DatabaseManager($app, $app['db.factory']);        });        $this->app->bind('db.connection', function ($app) {            return $app['db']->connection();        });    }}
Патчим ConnectionFactory
<?phpnamespace Umbrellio\Postgres\Connectors;use Illuminate\Database\Connection;use Illuminate\Database\Connectors\ConnectionFactory as ConnectionFactoryBase;use Umbrellio\Postgres\PostgresConnection;class ConnectionFactory extends ConnectionFactoryBase{    protected function createConnection($driver, $connection, $database, $prefix = '', array $config = [])    {        if ($resolver = Connection::getResolver($driver)) {            return $resolver($connection, $database, $prefix, $config);        }        if ($driver === 'pgsql') {            return new PostgresConnection($connection, $database, $prefix, $config);        }        return parent::createConnection($driver, $connection, $database, $prefix, $config);    }}

Начало работы

Представим что нам надо добавить поддержку нового типа tsrange в наши миграции. Как будет выглядеть наше расширение в нашем проекте теперь.

TsRangeExtension.php
<?phpnamespace App\Extensions\TsRange;use App\Extensions\TsRange\Schema\Grammars\TsRangeSchemaGrammar;use App\Extensions\TsRange\Schema\TsRangeBlueprint;use App\Extensions\TsRange\Types\TsRangeType;use Umbrellio\Postgres\Extensions\AbstractExtension;use Umbrellio\Postgres\Schema\Blueprint;use Umbrellio\Postgres\Schema\Grammars\PostgresGrammar;class TsRangeExtension extends AbstractExtension{    public const NAME = TsRangeType::TYPE_NAME;    public static function getMixins(): array    {        return [            TsRangeBlueprint::class => Blueprint::class,            TsRangeSchemaGrammar::class => PostgresGrammar::class,            // ... список миксинов может включать в себя почти любой внутренний компонент Laravel        ];    }    public static function getName(): string    {        return static::NAME;    }    public static function getTypes(): array    {        return [            static::NAME => TsRangeType::class,        ];    }}
TsRangeBlueprint.php
<?phpnamespace App\Extensions\TsRange\Schema;use Illuminate\Support\Fluent;use App\Extensions\TsRange\Types\TsRangeType;use Umbrellio\Postgres\Extensions\Schema\AbstractBlueprint;class TsRangeBlueprint extends AbstractBlueprint{    public function tsrange()    {        return function (string $column): Fluent {            return $this->addColumn(TsRangeType::TYPE_NAME, $column);        };    }}
TsRangeSchemaGrammar.php
<?phpnamespace App\Extensions\TsRange\Schema\Grammars;use App\Extensions\TsRange\Types\TsRangeType;use Umbrellio\Postgres\Extensions\Schema\Grammar\AbstractGrammar;class TsRangeSchemaGrammar extends AbstractGrammar{    protected function typeTsrange()    {        return function (): string {            return TsRangeType::TYPE_NAME;        };    }}
TsRangeType.php
<?phpnamespace App\Extensions\TsRange\Types;use Doctrine\DBAL\Platforms\AbstractPlatform;use Doctrine\DBAL\Types\Type;class TsRangeType extends Type{    public const TYPE_NAME = 'tsrange';        public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string    {        return static::TYPE_NAME;    }    public function convertToPHPValue($value, AbstractPlatform $platform): ?array    {        //...        return $value;      }    public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string    {        //...              return $value;    }    public function getName(): string    {        return self::TYPE_NAME;    }}

Теперь необходимо зарегистрировать наше расширение TsRangeExtension в нашем провайдере для работы с БД:

<?phpnamespace App\TsRange\Providers;use Illuminate\Support\ServiceProvider;use App\Extensions\TsRange\TsRangeExtension;use Umbrellio\Postgres\PostgresConnection;class TsRangeExtensionProvider extends ServiceProvider{    public function register(): void    {        PostgresConnection::registerExtension(TsRangeExtension::class);    }}

Итог

Вы можете писать свои расширения для Postgres имплементируя AbstractExtension, на мой взгляд, очень быстро и просто, не вникая в тонкости работы Laravel и Doctrine.

Это мой первый опыт, сделать что-то полезное для PHP сообщества, для тех кто использует в своих проектах Laravel / Postgres, не судите строго, пожалуйста.

Но я буду рад обратной связи, в любом ее проявлении, в Issues / Pull-реквестах, или в комментах относительно не только моей публикации, но и пакета в целом приму любую критику.

Пощупать данный пакет можно на GitHub: laravel-pg-extensions.

Спасибо за внимание.

Подробнее..
Категории: Postgresql , Open source , Laravel , Php , Github , Postgres

Recovery mode Code style для миграций Laravel

19.02.2021 02:10:05 | Автор: admin

Всем привет.

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

Сейчас я пару месяцев работаю над проектом где 20+ разработчиков, работа одновременно ведётся в примерно 30-ти ветках, имеется пять сред для отработки кода (драфт, дев, тестинг, хотфикс, прод), у каждой среды своя БД (перед выкаткой камита на стенд/среду, происходит проверочная выкатка с использованием отдельной БД, то есть на пять сред мы имеем 10 отдельных баз данных).

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

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

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

Disclaimer

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

Описание проблемы

Есть у нас ветка драфт, что бы пушить в драфт не надо делать МР, эта ветка нужна для того что бы программист мог по быстрому показать свои наработки бизнес аналитику, что бы фронт-энд мог интегрироваться с изменениями на бэк-энде. Драфт регулярно ресетиться.

То есть ты что то сделал с функционалом, решил показать другим разработчикам - выкатываешь всё на драфт, на БД драфта раскатывается твоя миграция, ты показал, понял что был не прав и переделываешь миграцию, в это время кто то ресетит драфт вместе с твоей миграцией, и что мы имеем ? мы имеем миграцию которая были применена и которую ни кто не откатил, как думаете получиться ещё раз её применить ?

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

То есть порядок применения миграций иногда бывает хаотичным.

Правило первое: "накатывать можно бесконечно"

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

Миграцию можно как бесконечное количество раз накатить на саму себя, так и откатить с самой себя.

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

# накатить конкретную миграциюphp artisan migrate --path="services/best-team-servise/database/migrations/2021_02_04_240000_alter_data_model_table_add_unique_index.php" --pretend# ключ --pretend позволит нам посмотреть SQL до того как будет применена миграция, иногда полезно# откатить ровно одну миграциюphp artisan migrate:rollback --step=1# можно откатить и десять, при случае раберётесь

После того как миграция применена, можно сгенерировать описание модели

php artisan ide-helper:models "Project\Models\DataModel"

и сделать посев данных:

php artisan db:seed --class=DataModelSeeder

Как сделать так что бы миграцию можно было бесконечно применять ? в методах up() и down() миграции, мы должны делать проверку того, что действие со схемой БД можно выполнить.

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

Получаем Builder :

        $conn = (new DataModel())->connection;        $builder = Schema::connection($conn);

проверяем что миграция не была применена (проверяем что миграция может быть выполнена):

        $isExists = $builder->hasColumn(            'data_model',            'deleted_at'        );

Если миграция может быть выполнена, то выполняем:

        if (!$isExists) {            $builder->table(                'data_model',                function (Blueprint $table) {                    $table->softDeletesTz();                }            );        }

Аналогично с таблицами - проверяем что таблица не существует, с индексами - проверяем что индекс не существует, с индексами посложней, но можно, поможет такой код:

        $alias = (new DataModel())->connection;        $builder = Schema            ::connection($alias)            ->getConnection()            ->getDoctrineSchemaManager();$existingIndexes = $builder->listTableIndexes('data_model');

С индексами в Laravel есть заморочка, если мы создали индекс как:

Blueprint::unique('index_name');

То и удалять надо как:

Blueprint::dropUnique('index_name');

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

Накатываем чистым SQL, пишем так:

DROP TRIGGER IF EXISTS trigger_name    ON public.data_model;CREATE TRIGGER trigger_name    BEFORE INSERT    ON public.data_model    FOR EACH ROW    EXECUTE PROCEDURE public.function_name();

Когда откатываем, пишем так же:

DROP TRIGGER IF EXISTS trigger_name    ON public.data_model;

Правило второе: "шаблонные имена"

Миграций в проекте сотни, за полгода было написано 1000+ миграций. Конечно вся 1000 не лежит в одной директории, они раскиданы по директориям модулей.

Но тем нем нее, когда открываешь директорию в которой 50+ миграций, тебе сложно ориентироваться в них без "хороших" имён.

Соглашения об именах

Если создаём таблицу, то имя миграции начинается с create, если меняем таблицу то alter, если удаляем таблицу, то drop.

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

alter_data_model_add_property_columnalter_data_model_alter_property_column_to_textalter_data_model_alter_property_column_set_default_valuealter_data_model_create_index_on_code_type_columnsalter_data_model_create_unique_index_on_code_column

Дропать таблицы, конечно, можно только на этапе MVP.

Команды для создания миграций:

# делаем миграцию для создания таблицыphp artisan make:migration create_profile_table --create=profile# делаем миграцию для изменения таблицыphp artisan make:migration add_confirmed_to_profile --table=profile

Файл миграции будет помещён в директорию database/migrations собственно приложения, у нас каждый сервис это отдельный пакет и после создания файла миграции его надо переложить в директорию своего пакета.

Правило третье: таблицы и колонки дропать нельзя, все колонки nullable()

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

Откатывать миграции вместе с репозиторием, эта так себе занятие.

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

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

            $columns = Schema            ::connection((new DataModel())->connection)            ->getConnection()            ->getDoctrineSchemaManager()            ->listTableColumns($(new DataModel())->getTable());            $data = [];            foreach ($columns as $column) {                $name = $column->getName();/* @var array[] $record строка данных для посева*/                $exists = key_exists($name, $record);                if ($exists) {                    $data[$name] = $record[$name];                }            }            $isSuccess = DataModel                ::withTrashed()                ->updateOrCreate(                    ['uniqe_index_column' => $data['uniqe_index_column'],],                    $data                )->exists;

Правило четвёртое: значения по умолчанию, там где null недопустим

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

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

Заключение

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

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

Подробнее..
Категории: Sql , Laravel , Migrations

Еще немного про сервисный слой в PHP

17.03.2021 14:12:14 | Автор: admin

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

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

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

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

Основы

Если обратиться к теории, то сервисный слой можно описать таким определением:

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

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

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

Email уведомления

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

namespace App\Http\Controllers;use App\Http\Requests\CreateOrderRequest;use Illuminate\Support\Facades\Mail;class OrderController{    public function createOrder(CreateOrderRequest $request)    {        // Логика создания заказа...        Mail::send('mail.order_created', [            'order' => $order        ], function ($message) use ($order) {            $message->to($order->email)                ->subject(trans('mail/order_created.mail_title'));        });    }}

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

public function editOrder(EditOrderRequest $request){    // Логика обновления данных заказа...    Mail::send('mail.order_updated', [        'order' => $order    ], function ($message) use ($order) {        $message->to($order->email)            ->subject(trans('mail/order_updated.mail_title'));    });}

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

public function registerCustomer(RegisterCustomerRequest $request){    // Логика регистрации пользователя...    Mail::send('mail.customer_register', [        'customer' => $customer    ], function ($message) use ($customer) {        $message->to($customer->email)            ->subject(trans('mail/customer_register.mail_title'));    });}

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

Набор оборотов

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

Выше, мы реализовали три отправки email сообщений в трех разных частях системы, а поскольку мы рассматриваем близкую к реальности ситуацию, то по мере развития интернет-магазина будет реализовано еще очень много таких отправок. А теперь представьте, что нам нужно пройтись по всем частям системы и заменить старый код с фасадом Mail на новую логику отправки с помощью сервиса рассылок. Сколько времени необходимо на это потратить и сколько тестов нужно переписать (если код конечно покрывался тестами)? И чем больше кода разработчику необходимо изменить, тем больше вероятность допущения ошибки по причине человеческого фактора. Хорошо еще, если разработчик вынесет логику обращения к сервису рассылок в отдельный класс, а не будет дублировать код по всем частям системы. Чтобы не попадать в такие ситуации, перепроектируем систему с применением сервисного слоя.

Сервисный слой

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

namespace App\Services;use Illuminate\Support\Facades\Mail;use App\Mail\Events\MailEventInterface;use App\Mail\Events\OrderCreatedEvent;use App\Mail\Events\OrderUpdatedEvent;use App\Mail\Events\CustomerRegisterEvent;class NotificationService{    public function notify(string $event, array $data)    {        $event = $this->makeNotificationEvent($event, $data);        Mail::send($event->getView(), $event->getData(), function ($message) use ($event) {            $message->to($event->getEmail())                ->subject($event->getMailSubject());        });    }    private function makeNotificationEvent(string $event, array $data) : MailEventInterface    {        switch ($event) {            case 'order_created':                return new OrderCreatedEvent($data);            case 'order_updated':                return new OrderUpdatedEvent($data);            case 'customer_register':                return new CustomerRegisterEvent($data);            default:                throw new \InvalidArgumentException("Undefined event $event");        }    }}

Далее, создадим интерфейсMailEventInterface.

namespace App\Mail\Events;interface MailEventInterface{    public function getView() : string;    public function getData() : array;    public function getEmail() : string;    public function getMailSubject() : string;}

А также, в качестве примера, напишем новый классOrderCreatedEvent (оповещение клиента об успешном оформлении заказа).

namespace App\Mail\Events;class OrderCreatedEvent implements MailEventInterface{    private $order;    public function __construct(array $data)    {        // Логика валидации (на любителя)        $this->order = $data['order'];    }    public function getView(): string    {        return 'mail.order_created';    }    public function getData(): array    {        return [            'order' => $this->order        ];    }    public function getEmail(): string    {        return $this->order->email;    }    public function getMailSubject(): string    {        return trans('mail/order_created.mail_title');    }}

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

namespace App\Http\Controllers;use App\Http\Requests\CreateOrderRequest;use App\Services\NotificationService;class OrderController{    private $notificationService;        public function __construct(NotificationService $notificationService)    {        $this->notificationService = $notificationService;    }    public function createOrder(CreateOrderRequest $request)    {        // Логика создания заказа...                $this->notificationService->notify('order_created', [            'order' => $order        ]);    }}

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

Нужно ли объявлять интерфейс для сервисного слоя?

И да и нет. Ответ тут зависит от ситуации. Взгляните на пример выше. Этот код прекрасно проявит себя в деле, если поступит задача отправлять все письма через сервис рассылок. Но что если нам понадобитьсяперевести лишь часть событий?В таком случае, гораздо эффективнее было бы объявить общий интерфейс NotificationServiceInterface и в зависимости от контроллера, пробрасывать соответствующую реализацию в нашем сервис-провайдере. Что-то по типу этого.

$this->app->when(OrderController::class)    ->needs(NotificationServiceInterface::class)    ->give(function () {        return new ESputnikNotificationService();    });$this->app->when(OrderUpdateController::class)    ->needs(NotificationServiceInterface::class)    ->give(function () {        return new MailNotificationService();    });

К слову, в 95% случаях, интерфейсы для сервисного слоя все-таки не нужны.

Можно ли использовать сервисы внутри сервисов?

Я бы однозначно не рекомендовал такую практику, так как этим вы нарушаете single responsibility принцип, делая ваш код, к тому же, достаточно запутанным.

Работу с несколькими сервисами можно организовать такими способами.

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

class OrderController{    public function saveOrder(        SaveOrderRequest $request,         OrderService $orderService,         NotificationService $notificationService    ) {        try {            $order = $orderService->createOrderFromRequest($request);            $notificationService->notify('order_created', [                'order' => $order            ]);            return response()->json([                'success' => true,                'data' => [                    'order' => $order                ]            ]);        }        catch (OrderServiceException|NotificationServiceException $e) {            return response()->json([                'success' => false,                'exception' => $e->getMessage()            ]);        }    }}

2. Выделение класса, которому можно делегировать работу с цепочкой сервисов. Например, я обычно использую класс с суффиксом Operation (CreateOrderOperation). Ошибки можно все также отлавливать с помощью try/catch, но гораздо практичнее будет ввести сущность OperationResult, которую будет возвращать каждая операция в не зависимости от результата выполнения. Это способ мне нравится больше.

class OrderController{    public function saveOrder(        SaveOrderRequest $request,        CreateOrderOperation $createOrderOperation    ) {        // Внутри операции выполняются все обращения к сервисам и т.д.        $result = $createOrderOperation->createOrderFromRequest($request);        // Для более чистого экшена, сущность OperationResult        // может имплементировать JsonSerializable        return response()->json($result);    }}

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

Всем спасибо за внимание!

Подробнее..

Категории

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

  • Имя: Макс
    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