Ответы пользователя по тегу PHP
  • Как правильно отправлять почту через PHP?

    @Flying
    Очень странно, что письма, высланные через Mailgun имеют плохую доставляемость. Подобные сервисы как раз, в частности, и используют из-за того, что они нормально реализуют все стандартные средства идентификации отправителей, а также обладают хорошей репутацией с точки зрения spam list'ов и прочих сервисов мониторинга.

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

    Также не очень понятно само описание проблемы. "Из-за отсутствия шифрования" чего? Возможно вы просто что-то неправильно используете или не настроили?

    Возвращаясь к вопросу о библиотеках - я бы склонялся в сторону Symfony Mailer. Это "наследник" SWIFTMailer, который в свою очередь, думаю, был самым популярным решением для этой задачи на PHP, всё-таки больше 270 миллионов скачиваний, у PHPMailer в 10 раз меньше.

    Понятно, что Symfony Mailer хорошо интегрирован в сам Symfony, но он легко (хотя и менее удобно) используется и отдельно. Из несомненных плюсов данного подхода - возможность подключения различных транспортов (в том числе и того же Mailgun), что позволяет использовать разные методы отсылки писем без необходимости менять код. Также Symfony Mailer интегрируется с Symfony Messenger, который в свою очередь предоставляет mailer'у возможность асинхронной рассылки писем с retry стратегиями, отслеживанием ошибок отсылки и т.п.
    Ответ написан
    Комментировать
  • Как быстро проверить какие пакеты имеют уязвимости в composer.json?

    @Flying
    Локально можно делать проверку так, как описано здесь.

    Но поскольку проверка уязвимостей должна быть постоянным процессом - лучше всё-таки использовать либо упомянутый вами roave/security-advisories либо, если ваш проект на GitHub - то через GitHub action.

    В конечном итоге все эти варианты опираются на один и тот же источник информации.
    Ответ написан
    Комментировать
  • Тернарный оператор, ?? вместо ?:, зачем?

    @Flying
    Это совсем разные вещи.

    ?? - это null coalescing operator
    ?: - это ternary operator с первым выражением, эквивалентным выражению в левой части оператора.
    Ответ написан
    Комментировать
  • В каких случаях следует делать кастомные Exceptions?

    @Flying
    Собственные исключения необходимы в случаях, когда вы хотите обрабатывать (или, в случае если вы автор библиотеки - предоставить возможности обработки) какой-либо исключительный сценарий специальным образом. К примеру посмотрите, как организована работа с exceptions в Doctrine.

    Также собственные исключения могут пригодится в случае, если вы хотите передавать с этим исключением какую-то дополнительную информацию. К примеру посмотрите на exceptions для ответов разных HTTP статусов в Symfony.
    Ответ написан
    4 комментария
  • Как избежать PHP Notice: Undefined offset?

    @Flying
    Например так:
    if (($var[0] ?? null) !== null) {
      return $var[0];
    }
    Ответ написан
  • Есть ли конвертер аннотаций в атрибуты?

    @Flying
    Посмотрите на Rector, он специально предназначен для подобных манипуляций. Есть отдельные пакеты с правилами для Symfony и Doctrine, но вероятно придётся дописать какие-то правила. Ну и статья в тему.

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

    Да, и Symfony 5.2 уже вышла из поддержки, нужно целиться в 5.4
    Ответ написан
    Комментировать
  • Как импортировать большой JSON (18гб) в MySQL?

    @Flying
    Вам не нужно загружать файл в память, для этого существует потоковая обработка:

    <?php
    $fp = fopen('big-file.json', 'r');
    if (!is_resource($fp)) {
        throw new \RuntimeException('Failed to open file for reading');
    }
    while (!feof($fp)) {
        $line = fgets($fp, 32768);  // Укажите лимит на одну запись
        if (empty($line)) {
            // Пропускаем пустые строки
            continue;
        }
        $data = json_decode($line, true, 512, JSON_THROW_ON_ERROR);
        // ... записываем в базу
    }
    fclose($fp);
    Ответ написан
    4 комментария
  • Извлечь имя из текста на php возможно?

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

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

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

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

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

    Если смотреть на зависимые от них пакеты - можно найти ещё много всего интересного по этой теме.
    Ответ написан
    2 комментария
  • Что может вызывать ошибку 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, а в виде фоновых процессов.
    Ответ написан
    4 комментария
  • Как сделать уникальным массив массив ассоциированных массивов?

    @Flying
    Например вот так:
    count($array) === count(array_unique(array_column($array, 'NUMBER')));
    Ответ написан
    1 комментарий
  • Можно ли как-нибудь проверить свои знания laravel, symfony или любой другой технологии?

    @Flying
    По Symfony можно попробовать сдать сертификационный экзамен, правда это не сказать чтобы дёшево.
    Ответ написан
    Комментировать
  • Попинайте 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, а в коде в этот аргумент передаётся выражение что недопустимо.

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

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

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

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

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

    @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);
            });
        }
    }
    Ответ написан
    Комментировать