Yandex Message Queue в Symfony Messanger

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

В рамках облаков часто используется Amazon SQS. В России же есть Yandex, который в рамках Yandex Cloud предоставляет сервис Message Queue, который является реализацией протокола Amazon SQS. Таким образом, инструменты разработчика от SQS подходят и для YMQ.

На данный момент в YMQ используется протокол на основе XML, который уже не используется в официальном SDK, там уже JSON, поэтому из коробки ничего не работает. Разработчики обещают обеспечить поддержку в будущем, но без указания сроков.

Для того что бы использовать Yandex Message Queue в Symfony Messanger требуется установить библиотеку для работы с SQS:

composer require symfony/amazon-sqs-messenger

Далее в .env:

MESSENGER_TRANSPORT_DSN=sqs://message-queue.api.cloud.yandex.net/<id>/<id>/<queueName>?access_key=<key>&secret_key=<secret>

Этот адрес можно взять в консоли управления Yandex Cloud.

Здесь:

id/id — это часть URL из консоли управления, key и secret — это статические ключи доступа для YMQ. Требуются права ymq.writer, ymq.reader

Если сейчас запустить вычитку очереди через php bin/console messanger:consume <название очереди>, то получим ошибку: 400 Bad request. Так происходит из-за того, что для обмена данными используется формат xml, а библиотека по умолчанию пытается использовать JSON.

Symfony для связи с AWS использует библиотеку AsyncAWS. Для SQS используется ее часть — async-aws/sqs. В версии 2.0 используется JSON, а вот в 1.9 — XML.

Для того, что бы Messanger использовал XML для коммуникации с SQS-совместимым сервисом нужно добавить явную зависимость на эту библиотеку:

composer require async-aws/sqs ^1.9

После этого YMQ через Symfony Messanger работает нормально

Установка AMQP на MacOS для PHP 8.2

Что бы установить потребуется brew

brew search librabbitmq

brew install rabbitmq-c

pecl install amqp

Важно, что при установке amqp установщик спрашивает путь и предлагает autodetect. Нужно явно указывать пусть, который показан при установке rabbitmq-c, например: /opt/homebrew/Cellar/rabbitmq-c/0.11.0

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

Миграция Doctrine с аннотаций на атрибуты

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

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

Сделать это можно при помощи rector. Для этого нужно:

  • Добавить rector как dev-зависимость: composer require rector/rector --dev
  • Создать конфигурационный файл в корне проекта с именем rector.php
<?php

declare(strict_types=1);

use Rector\Doctrine\Set\DoctrineSetList;
use Rector\Symfony\Set\SymfonySetList;
use Rector\Symfony\Set\SensiolabsSetList;
use Rector\Config\RectorConfig;

return function (RectorConfig $rectorConfig): void {
    $rectorConfig->sets([
        DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES,
        SymfonySetList::ANNOTATIONS_TO_ATTRIBUTES,
        SensiolabsSetList::FRAMEWORK_EXTRA_61,
    ]);
};
  • Запустить вызвав в консоли: vendor/bin/rector process src где src — папка, для которой нужно запустить обработку. Можно также ограничить только директорией с сущностями — тогда src/Entity
  • Поправить конфигурацию doctrine в файле config/doctrine.yaml в маппинге нужно удалить строку: type: annotation

Управляемая загрузка файлов на PHP и Nginx с использованием Accel-Redirect

Предположим, вам нужно ограничить возможность загрузки файлов и у вас PHP приложение. Файлы же могут находиться на удаленном или локальном по отношению к сайту хосте.
Есть пара вариантов как это сделать:

  1. Загружать файлы средствами PHP в TMP, проверять может ли пользователь их скачать или нет и отдавать или не отдавать файл
  2. Воспользоваться заголовком x-accel-redirect доступном в Nginx.
Continue reading

MySQL — Медленная операция COUNT(*) / COUNT(id)

Жил я себе спокойно, разрабатывал приложения, углубился в базы данных, почитал про highload, и умные люди на конференции в прошлом году заявляли, что в PgSQL операция count(id) весьма медленная. Интересно, никогда об этом не думал, что там — посчитать по индексу… а, нет, не по индексу, полный скан, значит не так уж и быстро…

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

SELECT count(id) FROM <table>

запрос на статистику таблицы:

show table status like '<table name>';

Я тестировал на таблицы в ~20 тыс строк, скорость работы отличается значительно

  • 0,63 ms для обычного точного count, при этом реальное число 21902
  • 0,47 ms для статуса таблицы, а тут число 22008

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

 

Как задавать права доступа в Symfony используя базу данных (Voters used database)

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

Использование Symfony Voters

Symfony использует Voters что бы определить доступ к URL и другим ресурсам. Множество voters которые входят в комплект поставки вместе с Symfony security  которые используют конфигурацию в security.access_control что бы определить нужно ли давать права на запрос или нет.

Continue reading

Как тестировать приватные сервисы в Symfony.

2 версии Symfony подвержены рассогласованностью между сервисами и тестами. Вы используете Symfony 3.4 или 4.0? Вы хотите тестировать сервисы, но не знаете как получить их правильно? Сегодня мы посмотрим на возможные решения.

Continue reading

Деревья в PHP + Twig 2.0

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

Под рукой PHP, а как view — Twig.

Пусть структура данных на backend будет такой:

$tree = [

            [
                'name' => 'foo',
                'children' => [
                    [
                        'name' => 'bar',
                        'children' => [
                            [
                                'name' => 'baz',
                                'children' => [

                                ],
                            ],

                            [
                                'name' => 'baz',
                                'children' => [

                                ],
                            ],


                        ]
                    ]
                ]
            ]

        ];

Тогда простроить дерево можно так:

{% macro makeTree(node) %}
    {% import _self as self %}
    
  • {{ node.name }} {% if node.children|length %}
      {% for child in node.children %} {{ self.makeTree(child) }} {% endfor %}
    {% endif %}
  • {% endmacro %} {% from _self import makeTree %} {% if tree %}
      {% for node in tree %} {{ makeTree(node) }} {% endfor %}
    {% endif %}

    Получится вложенный список из ul и li.

    Как определить квартал из даты в PHP

    Функции для работы с датой и временем, которые поставляются с PHP прекрасны для форматирования дат, однако отсутствует простой способ определить квартал для timestamp или объекта DateTime.

    Вот тут маленькая функция, которая умеет возвращать квартал из DateTime:

    /**
     * Return the quarter for a timestamp.
     * @returns integer
     */
    function quarter(\DateTime $dateTime){
       return (int) ceil($dateTime->format('n') / 3);
    }
    

    Как выбрать только необходимые поля в реляции (связи) Laravel

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

    $user = User::with(['department' => function ($q) {
                    $q->select(['id','name']);
                } ])->get();