Ответы пользователя по тегу PHP
  • Извлечь имя из текста на php возможно?

    @Flying
    Сильно зависит от реальной задачи, которая в вопросе не указана.

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

    Если же речь идёт о произвольном тексте - то здесь всё сильно интереснее. Ведь имён очень много, они могут иметь разные формы (полное / краткое имя), падежи (Саша, Саше, Сашу, Сашей и т.п.), могут быть написаны с ошибками, опечатками, неоднозначностями написания (Артем / Артём), транслитом и т.п.

    Здесь я бы рекомендовал в первую очередь обращаться к специализированным сервисам. Если речь идёт о русском языке - в голову первым делом приходит, конечно же, dadata.ru и их API по стандартизации имён. Да, это стоит каких-то денег, но работает очень хорошо, могу сказать по своему опыту.
    Ответ написан
  • Трюк с тернарным оператором PHP?

    @Flying
    В целом то, что вам нужно скорее ближе к новой функциональности в PHP 8: throw expression. В этом случае ваш код мог бы выглядеть, к примеру, вот так:
    Auth::check() ?? throw new AuthenticationRequiredException("Вам необходимо сначала авторизоваться");

    Однако, если вы реально хотите именно такой конструкции как ваша - то здесь, конечно, тоже есть варианты, ведь начиная с PHP 7 нам доступен uniform function call syntax и, следовательно, возможны конструкции вида:
    Auth::check() ?? (function(){echo "Вам необходимо сначала авторизоваться";})();

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

    Существенно лучшим вариантом в этом случае на самом деле будет просто создание отдельной функции, которая будет брать на себя реакцию на такие ситуации:
    function failure(string $error): void 
    {
      // Просто для того чтобы быть ближе к вашему примеру, 
      // в реальности здесь должна быть нормальная логика обработки, 
      // к примеру тот же throw new RuntimeExcepton($error);
      echo $error;  
    }

    в этом случае ваш пример сводится к:
    Auth::check() ?? failure("Вам необходимо сначала авторизоваться");

    Помимо этого обратите внимание на то, что использование null coalescing operator ?? подразумевает, что тип возвращаемого значения функции Auth::check() - это mixed|null что выглядит странно, поскольку от результата проверки ожидается тип boolean.

    В реальности здесь лучше подходит сокращённая версия тернарного оператора, т.н. elvis operator. В этом случае ваш код может выглядеть вот так:
    class Auth {
        public static function check(): bool 
        {
            return false;
        }
    }
    
    function failure(string $error): void 
    {
        // В реальности, как указано выше, лучше использовать 
        // throw new RuntimeException($error);   
        // echo используется для примера
        echo $error;
    }
    
    Auth::check() ?: failure('Вам необходимо сначала авторизоваться');

    Проверить можно здесь.
    Ответ написан
  • Какую библиотеку или методику лучше использовать для работы с ценами в интернет-магазине?

    @Flying
    Для работы с денежными значениями есть минимум две библиотеки:
    1. moneyphp/money
    2. brick/money

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

    Если смотреть на зависимые от них пакеты - можно найти ещё много всего интересного по этой теме.
    Ответ написан
  • Что может вызывать ошибку 502?

    @Flying
    HTTP 502 - это Bad Gateway. Как видно из описания - это означает что proxy (в вашем случае, предположу, nginx) не может получить ответа от upstream'а (php-fpm в вашем случае). Приведённый вами текст ошибки это прямо подтверждает.

    Из этого можно сделать первый вывод: куда-то пропадают процессы php-fpm. Поскольку рабочие процессы php-fpm даже при падении перезапускает основной процесс php-fpm - навскидку можно сделать несколько первичных предположений:
    1. У вас просто не хватает рабочих процессов php-fpm для обслуживания имеющихся запросов. Проверяйте логи php-fpm и подстраивайте параметры pm, pm.max_children, pm.min_spare_servers
    2. Что-то роняет сами процессы php-fpm. Смотрите логи самого PHP и php-fpm на предмет записей об ошибках, устраняйте их

    Также просто понаблюдайте что происходит с процессами php-fpm на вашем сервере, возможно это даст подсказки.

    Конечно есть шанс что проблема в другом, например, действительно, в базе данных. Но здесь для принятия решения явно не хватает имеющейся информации. Однако надеюсь что все тяжёлые процессы вы запускаете не через php-fpm, а в виде фоновых процессов.
    Ответ написан
  • Попинайте 2. Удалось ли исправить устаревший код, который забраковал работодатель?

    @Flying
    Согласен с FanatPHP по поводу того что изменения, по сравнению с предыдущим вариантом, просто колоссальные, поздравляю!

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

    1. Вам стоит лучше изучить возможности языка. PHP развивается очень динамично и в 7-й версии было добавлено огромное количество вкусных возможностей, про них стоит знать и ими стоит пользоваться
    2. Необходимо лучше изучить имеющуюся на данный момент экосистему готовых пакетов, это позволит вам использовать готовые, проверенные решения вместо изобретения своих велосипедов. Подключение Twig - первый шаг, но далеко не последний
    3. Вам обязательно стоит посвятить время изучению кода популярных проектов. Это многое может рассказать вам о подходах к решениям, паттернах и т.п. Конечно, параллельно нужно будет выяснять значения массы используемых терминов чтобы лучше понимать происходящее, это тоже пойдёт вам на пользу.
    4. Смотрите как развиваются другие технологии, которые вы используете, таблицами сейчас не верстают :)
    5. Рекомендую рассмотреть вариант использования для написания кода нормальной IDE, к примеру PHPStorm, это существенно улучшит вашу производительность и убережёт от массы ошибок


    Теперь более предметно:

    Оформление кода:

    Вы утверждаете что код соответствует PSR-2 (кстати, актуальный - PSR-12), однако есть мелкие огрехи (раздел 2.2).

    Оформление composer.json:

    Лучше указывать требования к платформе, в вашем случае - как минимум версию PHP т.к. platform requirements проверяются Composer'ом при установке.

    Поскольку ваш проект - не библиотека, то composer.lock тоже должен быть частью репозитория.

    Про настройки autoload'а вам уже сказали, обращу только внимание на то что сейчас структура репозитория отражает PSR-0, а не актуальный PSR-4, при использовании PSR-4 ваш код проекта лежал бы в src.

    Организация репозитория

    Код проекта должен лежать за пределами web root, поэтому вам явно не хватает папки public с единственным index.php

    Конфигурация

    Конфигурирование через константы - весьма устаревший подход. К примеру представьте что вы разворачиваете этот код на production сервере и вам нужно сменить данные для подключения к базе данных не создавая локальных изменений в рабочей копии. Сейчас у вас это не получится. Выход: читаем про
    12 factor app и, в частности, раздел config, а затем подключаем в проект, к примеру, vlucas/phpdotenv или symfony/dotenv

    Сервисы

    Использование bootstrap.php - хорошо, но для организации работы с сервисами сейчас как правило используются dependency injection контейнеры, даже PSR-11 для них есть. В текущем виде ваш подход слабо расширяем, а передача сервисов через глобальные переменные - так себе идея по многим причинам.

    Понятно что вворачивать DI container для примера на три файлика - overkill, но с самим подходом вам следует ознакомиться. Из реализаций самая популярная сейчас - symfony/dependency-injection, но есть и альтернативы.

    Автолоадер

    Про autoload.php вам уже сказали, вместо него стоит корректно конфигурировать автозагрузку в composer.json и полагаться на то что Composer вам сгенерирует. Он ведь умеет всё это и оптимизировать при необходимости.

    Внешние пакеты

    Для новых проектов рекомендуется выбирать актуальные и поддерживаемые версии пакетов. Хотя Twig 1.x ещё поддерживается, тем не менее актуальная версия - 3.x.

    Обработка ошибок

    Попробовать сделать это самостоятельно, безусловно, полезно чтобы лучше разобраться как это работает, но для реальной работы лучше полагаться на проверенные решения, к примеру filp/whoops или symfony/error-handler

    Типы данных

    Важно помнить что PHP с версии 7.0 поддерживает указание скалярных типов для аргументов и
    возвращаемых значений, с 7.1 - nullable типы и class constant visibility, а с 7.4 - типизированные свойства. Всё это гораздо надёжнее чем описание типов через аннотации и этим стоит пользоваться.

    Также повсеместно в коде используется нестрогое сравнение, тогда как очень желательно всегда использовать строгое (документация).

    Data Objects

    Сейчас Place и PlaceFilter по сути являются data объектами т.е. они не содержат самостоятельной логики, а просто переносят некие данные. При этом оба этих класса имеют пустые конструкторы (которые, кстати, лучше убирать), а загрузка данных организована через setter методы. Это позволяет изменить данные в них в произвольные моменты времени что вряд ли является желаемым поведением. Вместо этого подобные объекты лучше организовывать в виде immutable объектов (статья с примерами) чтобы не позволять ненужного нарушения целостности данных. Альтернативно, к примеру, для PlaceFilter может лучше подходить использование паттерна
    Builder
    , хотя в википедии не самый лучший пример.

    Именование

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

    Оптимизации

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

    References

    PDOStatement::bindParam() принимает аргумент $variable как reference, а в коде в этот аргумент передаётся выражение что недопустимо.

    Английский язык

    Я специально почти везде по тексту давал ссылки на информацию на английском языке т.к. он - основной язык в нашей индустрии и его важно знать и использовать.
    Ответ написан
  • Отличие создание объекта через new и dependency injection?

    @Flying
    При создании экземпляра объёкта через new вы просто создаёте экземпляр объекта т.к. это конструкция самого языка. Если этот объект нужно создавать как-то специфически, к примеру если надо передавать что-то в конструктор или как-то дополнительно конфигурировать и т.п. - всё это ваша ответственность. Если вам понадобится этот же объект где-то в другом месте проекта - то передача его туда - тоже ваша ответственность.

    Dependency injection container, в отличие от этого обеспечивает следующие задачи:
    1. Централизованное хранилище объектов и интерфейс для их получения
    2. Автоматическое корректное создание сконфигурированных экземпляров объектов

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

    @Flying
    Если делать корректно - то вам стоит использовать libphonenumber, есть её порт на PHP.

    Однако если речь идёт о частном случае - то всё можно сделать намного проще:
    function formatPhone($phone)
    {
        $number = preg_replace('/\D+/', '', $phone);
        if (strlen($number) === 10) {
            $number = '7' . $number;
        }
        return [
            'display' => sprintf('+%s (%s) %s-%s-%s', substr($number, 0, -10), substr($number, -10, 3), substr($number, -7, 3), substr($number, -4, 2),
                substr($number, -2, 2)),
            'link'    => sprintf('tel:+%s', $number),
        ];
    }
    
    $phone = '(495) 123-45-00';
    $formatted = formatPhone($phone);
    
    echo "${formatted['display']}\n{$formatted['link']}\n";

    Посмотреть вживую можно здесь.
    Ответ написан
  • Как перейти к конкретной реализации при использовании принципа Dependency inversion?

    @Flying
    Поскольку вопрос не о выполнении кода, а об IDE - то конкретный ответ будет сильно зависеть от того какая IDE используется и даже от того какой framework используется.

    По сути вопрос сводится к умению IDE (самостоятельно или через плагины) доставать информацию относящуюся к runtime'у приложения. А это в свою очередь зависит от того умеет ли конкретное приложение (или framework на котором оно построено) предоставлять эту информацию в виде, который умеет понимать IDE. Если да - будет вам счастье перехода на реально используемые реализации, нет - будете переходить на абстрактные интерфейсы и искать реализацию самостоятельно. В целом здесь начинает хорошо помогать привычка прописывать везде типы через аннотации (в вашем примере этого нет), но, конечно, у этого подхода есть свои минусы т.к. необходимо самому следить чтобы указанные типы совпадали с реальными. Здесь в свою очередь помогают ещё несколько хороших привычек связанных с процессом написания кода, но так можно глубоко забраться в offtopic...

    В качестве примера приведу пожалуй PHPStorm в связке с плагином Symfony Support в сценарии работы с Symfony. Поскольку в Symfony DI container является компилируемым и, помимо этого, он генерирует машинно-читаемое представление контейнера - через плагин IDE получает кучу информации о том что там в реальности используется в runtime'е и навигация по коду (а также масса других вещей) становится сильно проще.

    Но не всем наборам IDE + framework так везёт, к примеру поддержка приложения на ZF2, которым тоже приходится заниматься, такого не предоставляет, приходится искать всё руками. PHPStorm и здесь, конечно, очень выручает, но со сценарием Symfony конечно не сравнить.
    Ответ написан
  • Как сделать производительнее?

    @Flying
    Не буду присваивать себе ответ, просто поделюсь ссылкой на Stack Overflow. Думаю что этот вариант будет самым производительным если не учитывать варианты предварительной обработки.
    Ответ написан
  • Почему Microsoft Edge не определяет кодировку?

    @Flying
    Так происходит потому что вы просматриваете ответ сервера в браузере вместо того чтобы просматривать его в оригинале. Конечно кажется что браузер показывает то что ему прислали, на уровне обывательского опыта это так и есть, но как разработчик вы обязаны помнить что браузер - очень сложная система и его задача - рендер присланных html страниц. Вы же пытаетесь заставить браузер интерпретировать ваш, по сути, вывод в отладочный лог не побеспокоясь о том чтобы объяснить браузеру что же вы ему прислали. Да, это удобно, но тем самым вы неосознанно полагаетесь на эвристики браузера - именно это и приводит к описанному вами эффекту.

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

    Навскидку вы могли:
    1. Сообщить что вы посылаете текстовые данные, а не html, через header('Content-Type: text/plain; charset=utf-8');
    2. Сообщить что вы посылаете данные в кодировке utf-8 через header('Content-Type: text/html; charset=utf-8');
    3. Смотреть на исходный текст, а не на рендер страницы (Ctrl+U вроде бы)
    4. Просто вывести данные в текстовый лог через error_log($data,3,__DIR__.'/debug.log');
    Ответ написан
  • Как реализовать асинхронность с ReactPHP в Windows?

    @Flying
    Нет, это не из-за Windows, проблема в вашем коде, хотя она и не так очевидна как кажется, пришлось повозиться чтобы найти ответ.

    Если вкратце то основная проблема - в использовании sleep() для эмуляции задержки. Ведь в отличие от того же setTimeout() в JavaScript который нативно использует event loop, а следовательно асинхронен, sleep() в PHP - это просто задержка т.е. блокирующая операция. Ваш код не мог продолжаться дальше пока не отработает sleep(), отсюда и последовательность выполнения которая по факту практически синхронная.

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

    Немного изменённый вариант вашего кода, с форматированием и выводом задержки давал:
    [3] 3 sec
    [2] 5 sec
    [1] 6 sec

    причём и на Windows и на Linux.

    Если же использовать вариант приведённый ниже - то результат становится ожидаемым, и на Windows и на Linux:
    [1] 1 sec
    [2] 2 sec
    [3] 3 sec


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

    server.php
    <?php
    
    use React\EventLoop\Factory;
    use React\Http\Response;
    use React\Http\Server;
    
    require_once __DIR__ . '/vendor/autoload.php';
    require_once __DIR__ . '/async.php';
    
    $loop = Factory::create();
    $socket = new \React\Socket\Server($argv[1] ?? '0.0.0.0:0', $loop);
    
    $server = new Server(static function () use ($loop) {
        $test = new Async($loop);
        return $test->run()->then(static function () use ($test) {
            return new Response(
                200,
                ['Content-Type' => 'text/plain'],
                (string)$test
            );
        });
    });
    $server->listen($socket);
    
    echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL;
    $loop->run();

    async.php
    <?php
    
    use React\EventLoop\LoopInterface;
    use React\Promise\PromiseInterface;
    
    class Async
    {
        protected $_stdout;
        /**
         * @var LoopInterface
         */
        private $loop;
    
        /**
         * @param LoopInterface $loop
         */
        public function __construct(LoopInterface $loop)
        {
            $this->loop = $loop;
        }
    
        public function __toString()
        {
            return $this->_stdout;
        }
    
        public function run(): PromiseInterface
        {
            $start = time();
            $handler = function ($x) use ($start) {
                $this->_stdout .= sprintf("[%d] %d sec\n", $x, time() - $start);
            };
            return React\Promise\all([
                $this->asyncAction(3)->then($handler),
                $this->asyncAction(2)->then($handler),
                $this->asyncAction(1)->then($handler),
            ]);
        }
    
        protected function asyncAction(int $sleep = 0): PromiseInterface
        {
            return new React\Promise\Promise(function ($resolve, $reject) use ($sleep) {
                $this->action($sleep, static function ($e, $result) use ($resolve, $reject) {
                    if ($e) {
                        $reject($e);
                    } else {
                        $resolve($result);
                    }
                });
            });
        }
    
        protected function action(int $sleep, callable $callback): void
        {
            $this->loop->addTimer($sleep, static function () use ($sleep, $callback) {
                $callback(null, $sleep);
            });
        }
    }
    Ответ написан
  • Каким способом можно сделать PARSER в Symfony?

    @Flying
    Парсинг больших файлов осуществляется в потоковом режиме. Для XML существует стандарт потокового парсинга - SAX. В PHP есть стандартный модуль, реализующий SAX через libxml2.

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

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

    Также хочу заметить что парсинг 200мб XML - не та задача которая требует отображения прогресса т.к. сам процесс парсинга будет занимать максимум единицы секунд, а скорее ещё быстрее. Больше времени может уйти на загрузку этого файла, или сохранение / обработку данных, полученных из XML, возможно вы это имели в виду?
    Ответ написан
  • Почему советуют не выбирать yii2 для разработки?

    @Flying
    Даже если не учитывать массу очень дельных аргументов в других ответах - начинать новые проекты на Yii2 не стоит уже по той причине что 2-я версия уже почти два года официально находится в состоянии feature freeze т.е. по сути не развивается. Два года - огромный период в IT, к примеру поддержки только вышедшего PHP 7.4 и, тем более, 8-й версии там можно уже не ждать.

    Т.е. поддерживать уже имеющиеся проекты там ещё можно, но начинать новое лучше на framework'е у которого есть активная разработка и понятный план развития. В этом плане наиболее предсказуема Symfony.
    Ответ написан
  • Как сравнить строки в PHP?

    @Flying
    PHP поддерживает сравнение объектов DateTimeInterface напрямую, в данном случае стоит пользоваться именно этим:
    $format = 'd.m.Y H:i';
    $tz = new DateTimeZone('UTC');
    
    $d1 = DateTimeImmutable::createFromFormat($format, '01.01.2019 15:24', $tz);
    $d2 = DateTimeImmutable::createFromFormat($format, '02.01.2019 13:56', $tz);
    
    $newer = $d1 >= $d2 ? $d1 : $d2;
    echo $newer->format(DATE_ATOM);
    Ответ написан
  • Какие принципы SOLID здесь нарушены?

    @Flying
    С моей точки зрения здесь в первую очередь нарушается S (принцип единственной ответственности). Box в целом не должен уметь что-то кроме хранения фигур. Как правильно заметил vgbege - из задачи не следует что в дальнейшем вариантов подсчёта каких-то метрик по фигурам с Box'е не станет больше, так что в первую очередь я бы выносил отдельно калькулятор. Также у вас нет обобщающего интерфейса для IArea и IShape из-за чего и возникает путаница в addShape.

    В целом у меня получилось нечто подобное:
    /**
     * Основной интерфейс для всех фигур которые можно хранить в Box'е
     */
    interface IFigure
    {
    
    }
    
    /**
     * Интерфейс для фигур которые могут обладать площадью
     */
    interface IArea extends IFigure
    {
        public function getArea(): float;
    }
    
    /**
     * Интерфейс для фигур которые могут обладать цветом заливки
     */
    interface IFillColor
    {
        public function setFillColor(string $color): self;
    
        public function getFillColor(): string;
    }
    
    /**
     * Обобщённый интерфейс для фигур, обладающих и цветом и площадью
     * По факту, думаю, он не имеет смысла, но нужен по условию задачи
     */
    interface IShape extends IArea, IFillColor
    {
    
    }
    
    /**
     * Коробка для хранения фигур
     */
    class Box
    {
        /**
         * @var IFigure[]
         */
        private $figures = [];
    
        /**
         * @param IFigure[] $figures
         */
        public function __construct(array $figures = [])
        {
            // Добавляем полученные через конструктор фигуры в объект
            // Используем addFigure() чтобы проконтролировать корректность типов
            array_walk($figures, [$this, 'addFigure']);
        }
    
        /**
         * @param IFigure $figure
         * @return $this
         */
        public function addFigure(IFigure $figure): self
        {
            $this->figures[] = $figure;
            return $this;
        }
    
        /**
         * @param IFigure $figure
         * @return bool
         */
        public function hasFigure(IFigure $figure): bool
        {
            return in_array($figure, $this->figures, true);
        }
    
        /**
         * @param IFigure $figure
         * @return $this
         */
        public function removeFigure(IFigure $figure): self
        {
            $this->figures = array_filter($this->figures, static function (IFigure $f) use ($figure) {
                return $f !== $figure;
            });
            return $this;
        }
    
        /**
         * @return IFigure[]
         */
        public function getFigures(): array
        {
            return $this->figures;
        }
    }
    
    /**
     * Интерфейс для калькуляторов фигур в Box'ах
     */
    interface IFigureCalculator
    {
        /**
         * @param Box $box
         * @param mixed ...$args
         * @return mixed
         */
        public function calculate(Box $box, ...$args);
    }
    
    /**
     * Пример калькулятора
     */
    class AreaCalculator implements IFigureCalculator
    {
        public function calculate(Box $box, ...$args): float
        {
            // Получаем список фигур которые обладают площадью
            $figures = array_filter($box->getFigures(), static function (IFigure $figure) {
                return $figure instanceof IArea;
            });
            // Получаем цвет из дополнительных аргументов калькулятора
            $color = array_shift($args);
            if ($color) {
                // У нас задан цвет, фильтруем фигуры по цвету
                $figures = array_filter($figures, static function (IFigure $figure) use ($color) {
                    return $figure instanceof IFillColor && $figure->getFillColor() === $color;
                });
            }
            // Подсчитываем суммарную площадь
            return array_reduce($figures, static function (float $area, IArea $figure) {
                return $area + $figure->getArea();
            }, 0);
        }
    }


    В качестве альтернативы я рассматривал также вариант дать Box'у возможность самому считать метрики через создание у него метода:
    public function calculate(IFigureCalculator $calculator, ...$args)

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

    @Flying
    Подобная задача решается через т.н. брокеры сообщений (Message broker). Есть несколько возможных вариантов, к примеру выделенные сервера, реализующие протокол AMQP (например RabbitMQ, ActiveMQ и другие), стриминговые сервера (к примеру Kafka), сервисы очередей (Amazon SQS и подобные), а также другие реализации паттерна PubSub, к примеру в Redis.

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

    В вашем сценарии скорее всего будет подразумеваться две очереди:
    1. Очередь задач на scraping. а которую подписываются множество worker'ов, осуществляющих непосредственный сбор данных
    2. Очередь с результатами сбора данных, куда worker'ы будут скидывать данные и на которую подписываются обработчики, решающие что же делать с данными дальше

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

    Для PHP существуют немало binding'ов к серверам, реализующим message brokering. Не знаю что именно на сервер вы выберете, поэтому навскидку приведу php-amqplib/php-amqplib для работы с AMQP (в первую очередь с RabbitMQ), enqueue/amqp-tools (также для AMQP) и superbalist/php-pubsub для реализации PubSub паттерна, к примеру через Redis.

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

    @Flying
    Я в проекте для этой цели использовал пакет hidehalo/emoji, работает весьма неплохо.
    Ответ написан