Ответы пользователя по тегу PHP
  • Зачем в обычном проекте может понадобиться использование __invoke?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    __invoke() метод позволяет превратить объект в функцию, которую мы можем передавать/получать/резовлить через псевдо-тип callable.
    Этакий элемент функционального программирования.

    Хорошо подходит там, где мы выстраиваем pipe или reduce, для описания которых ФП подход является наиболее выразительным и лаконичным. Не знаком с архитектурой Ларавел, но могу предположить, что для авторизации прорисовывается некоторый пайплайн, где можно выполнять разные пайпы или фильтры для првоерки/валидации или чего-либо именно с таким подходом и это более лаконично...

    Пример:
    public function add(callable $pipe) {
        //..
    }
    
    ...
    
    $pipeline = (new Pipeline)
         ->add(fn() => true)
         ->add(fn(bool $enabled) =>'Maks enabled')
         ->add(new MaksHandler()) // тут класс с методом __invoke и работает как анонимка
    ;
    Ответ написан
    33 комментария
  • Как можно ускорить работу Exception?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    на расходы конкретно на исключения для 15 000 вызовов (на каждую строку по 1 исключению) будут около 0.01 секунды (сотая секунды крч) для моего локального компа (много, но и данных много)
    Пруф

    618e769ee759d433078044.png


    $error = $this->handle($json);
    if ($error === '') {
    // логируем что все ок
    } else {
    // логируем ошибку
    }

    Тут заменено не только это, а заменено что-то внутри в handle(), тк не летит исключение

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

    в 15 тыс новых объектов проблемы может не быть. Такие задержки по времени выглядят как проблема на уровне логирования исключений, тк I/O операции над файлом долгие, а сбор трейса исключений и его запись — долгие потенциально.
    Уйдя от исключений вы возможно ушли и от логирования исключений.
    Ответ написан
  • Как задать composer'у proxy в контейнере docker?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Вот корректный образ

    FROM php:8-fpm
    RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer


    Все собирается
    Ответ написан
    Комментировать
  • Как получить даты всех дочерних объектов бинарного дерева?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    У вас корректный код:
    sandbox.onlinephpfunctions.com/code/00ed97cf62c568...

    <?php
    
    class Node{
      private $arr = [];
      
      public function getChildren($node)
        {
    
            if (!empty($node->data)){
                array_push($this->arr, $node->data);
            }
    
            if (!empty($node->left)){
                $this->getChildren($node->left);
            }
    
            if (!empty($node->right)){
                $this->getChildren($node->right);
            }
    
            return $this->arr;
        }  
    }
    
    $node =  createNode(
        6 . '_base', 
        createNode(7 . '_right'),
        createNode(8 . '_left',
            createNode(81 . '_left'),
            createNode(82 . '_right',
               createNode(821 . '_right', 
                    null,  
                    createNode(824 . '_left') 
               )
              
            )
        )
    );
    
    function createNode($data, $right = null, $left = null) {
        $obj = new stdClass();
        $obj->data  = $data;
        $obj->right  = $right;
        $obj->left  = $left;
        return $obj;
    }
        
    var_dump((new Node)->getChildren($node));
    
    // [
    //  "6_base"
    //  "8_left"
    //  "82_right"
    //  "821_right"
    //  "824_left"
    //  "81_left"
    //  "7_right"
    // ]
    Ответ написан
  • Отделение бизнес логики от фреймворка Symfony?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Генератор — просто инструмент для помощи, по итогу сущности чисты, не считая аннотаций/атрибутов для маппинга в ORM, но это просто мета-информации и завязка не существенна (не считая маленького компромисса с ArrayCollection). То есть если вы выберите др ORM, то эти аннотации вам не помешают никак, просто лишние заюзанные классы аннотаций

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

    Чтобы отделить репозиторий от домена — просто в домене делайте интерфейс, а вот реализация этого репозитория будет в Infrastrucure Layer, но это избыточно... риск минимальный, если сделаете не совсем по канону, а именно риск стоит как основной аргумент такового отделения (не просто же вы словам следуете, а причину понимаете?)
    Разработка строится на компромисах, если смените доктрину на др ORM — так и так писать новые репозиторий, вероятность низкая и многие например не делают такие интерфейсы — слишком усложнит код...
    Вам надо будет просто репозиторий в маппинге ORM\Repository заменить в таком случае

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

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

    617588c41bdde421641847.png
    Ответ написан
    8 комментариев
  • Как инициализировать маршрутизатор Symfony в проекте?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Вам надо поставить еще пару пакетов:
    symfony/config
    symfony/cache
    doctrine/annotations


    по сути с конфигом вы почти симфони заведете в стандартной установке, не считая Request/Response из Symfony Foundation... уже тогда симфу и ставьте

    можете взять пакеты попроще, типа Aura/Slim, там при регистрации приложения просто добавляются новые роуты
    Ответ написан
  • Можно ли использовать сервисы в rich моделях?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    а? Если нет и нужно все равно писать абсолютно всю логику в обьекте модели, то тогда он станет просто god обьектом на 100500 строчек

    Потому что вы все делаете наоборот!

    Пишите по бизнес-процессу ваши UseCase, это некоторый хэндлер к примеру
    Пример
    final class HandleCheckOutShoppingCart
    {
        public function __construct(Carts $carts, PaymentGateway $gateway)
        {
            $this->carts   = $carts;
            $this->gateway = $gateway;
        }
    
        public function __invoke(CheckOutShoppingCart $command) : void
        {
            $shoppingCart = $this->carts->get($command->shoppingCart());
    
            $payment = $this->gateway->captureCharge($command->charge());
    
            $shoppingCart->checkOut($payment);
        }
    }


    Потом у вас вырисовываются границы сущности, и в сущностях уже инварианты, которые контролирует эта сущность. Никаких god object
    Ответ написан
    5 комментариев
  • Есть ли особенности работы Doctrine в долгоживущих приложениях?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Как такое может быть?

    Если вы достаете сущность по id, то она загружается один раз и потом не обращается к БД, следя за изменениями сущности из приложения
    Предлагаю очищать EM после каждой обработки задачи через $em->clear()

    Sometimes you want to clear the identity map of an EntityManager to start over. We use this regularly in our unit-tests to enforce loading objects from the database again instead of serving them from the identity map. You can call EntityManager#clear() to achieve this result.
    https://www.doctrine-project.org/projects/doctrine...


    Чтение можно в обход делать, изменение сущностей лучше не делать в обход EM

    Консьюмеру не хватате параметра какого - то?

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

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    у даты есть метод format()

    конкретно для вашего случая будет
    $date->format('c');

    https://www.php.net/manual/ru/datetime.format.php

    615eeda7241e0904013833.png
    Ответ написан
    Комментировать
  • Скажите, а шаблон проектирования (для PHP), где фичи для приложения выносятся в отдельные модули - имеет какое-то название?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    где фичи для приложения выносятся в отдельные модули

    В вопросе уже овтет :):):)

    Package By Feature

    Вот можете посмотреть на примере PHP-проекта от Удальцова Валентина, хорошая попытка (с привязкой по времени):
    https://www.youtube.com/watch?v=2iPNz3p5Xiw&t=3633s

    как разбить приложение на переиспользуемые модули-фичи

    А вот тут сомнения по моему ответу, тк цель обратная — понизить связанность, тогда как переиспользуемость — повышение ее в некотором роде
    Может быть вы сейчас про DRY? Don't Repeat Yourself?
    Ответ написан
    1 комментарий
  • Как добавить синтаксический сахар, связанный с оператором в php?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Проблема 0: В PHP нет перегрузки операторов и магических методов как в Пайтоне под такое дело

    Вариант 1: Условно функциональный
    Можно например так (если конечно вам подойдет)
    $a() - $b()

    Тогда надо просто __invoke() реализовать в каждом из классов

    Плюс такого, что в $a или $b может быть не только класс, но callable
    <?php
    
    class N {
        private int $n;
        public function __construct(int $n = 0) {
            $this->n = $n;
        }
    
        public function __invoke(): int {
            return $this->n;
        }
    }
    
    $a = new N(5);
    $b = fn() => 100;
    
    var_dump($a() - $b()); // -95


    И даже на лету передавать аргументы, например мультипликатор:
    <?php
    
    class N {
        private int $n;
        public function __construct(int $n = 0) {
            $this->n = $n;
        }
    
        public function __invoke(int $multiplier = 1): int {
            return $multiplier * $this->n;
        }
    }
    
    $a = new N(5);
    $b = fn(int $multiplier = 1) => $multiplier * 100;
    
    var_dump($a(100) - $b()); // 400
    sandbox.onlinephpfunctions.com/code/331eef7f9ad530...
    А можно передавать еще callable :) Крч вот вариант функциональный очень гибкий
    Или наоборот в конструкторе зашить мультипликатор, а сами числа передавать на лету, по-разному можно сделать

    Вариант 2: Value Object
    Можно посомтреть как сделан интерфейс например у объекта DateTime и увидеть, как работает diff() (разница времени) и можно сделать похожий интерфейс

    <?php
    
    interface NumericInterface {
        public function add(Numeric $second): NumericInterface;
        public function toInt(): int;
    }
    
    class Numeric implements NumericInterface {
        private $n;
        
        public function __construct($n = 0) {
            $this->n = $n;
        }
    
        public function add(Numeric $second): self {
            return new self($second->n +  $this->n);
        }
        
        
        public function toInt(): int {
            return $this->n;
        }
    }
    
    $num1 = new Numeric(10);
    $num2 = new Numeric(18);
    
    $result = $num1->add($num2)->toInt();
    
    var_dump($result); // 28
    Ответ написан
    1 комментарий
  • Как заставить работать Guzzle асинхронно?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Во время работы с Async Guzzle вам нужен вызов $curl->tick(), чтобы он мог продолжить работу. wait() под капотом делает именно это

    Вот даже есть пример, как интегрировать курл с реакт-пхп и там есть этот тик, который делает движение
    https://gist.github.com/psampaz/7f2aad5d1d54eeeec8...

    use GuzzleHttp\Client;
    use GuzzleHttp\Handler\CurlMultiHandler;
    use GuzzleHttp\HandlerStack;
    use Psr\Http\Message\ResponseInterface;
    
    $curl = new CurlMultiHandler;
    $handler = HandlerStack::create($curl);
    $client = new Client(['handler' => $handler]);
    
    $p = $client
        ->getAsync('http://google.com')
        ->then(
            function (ResponseInterface $res) {
                echo 'response: ' . $res->getStatusCode() . PHP_EOL;
            },
            function (\Exception $e) {
                echo $e->getMessage() . PHP_EOL;
            }
        );
    
    while ($p->getState() === 'pending') {
        $curl->tick();
        // sleep(1);
    }
    echo 'bottom' . PHP_EOL;


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

    Опишите изначальную задачу, которую преследуете

    UPD после выяснения задачи:

    Задача: рассылать почту, не задерживая клиента
    Типовое решение: Выполнить fastcgi_finish_request и сделать после него отправку почты. То есть после выполнения данной функции идет разрыв с сервером, то есть браузер прогрузится до конца! Но сам скрипт еще поработает до того, как все сделает (и браузер об этом не узнает).

    Можете сообщение записать в БД, а после fastcgi_finish_request всю очередь вычесать и отправить
    Ответ написан
    2 комментария
  • Как обратиться к свойству объекта заведомо не зная этого свойства?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Семантика у задачи плохая очень (каталог в каталоге, у каталога товар? один? :)
    Но в целом вот на коленке как достать значения вложенной глубины

    <?php
    
    $catalog = new stdClass();
    $catalog->product = new stdClass();
    $catalog->product->item = 'Short';
    $catalog->product->model = 'Adidas';
    
    $catalogPaths = ['product.item', 'product.model'];
    
    
    function extractValuesByPath(object $obj, array $paths) {
        return array_reduce($paths, function(array $values, string $pattern) use($obj) {
            $values[] = extractPathValue($obj, $pattern);
            return $values;
        }, []);
    }
    
    function extractPathValue($obj, $pattern) {
        $paths = explode('.', $pattern);
        
        foreach($paths as $path) {
            if (!property_exists($obj, $path)) {
                // Можно нулл вернуть или к пример `undefined $path`
                throw new InvalidArgumentException('Нет такого свойства во входящих данных!');
            }
        
            $obj = $obj->$path;
        }
        
        return $obj;
    }
    
    $res = extractValuesByPath($catalog, $catalogPaths);
    var_dump($res);
    // [
    //       'Short',
    //       'Adidas',
    // ]
    Ответ написан
  • Как спроектировать фреймворк?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Фреймворк от начала и до конца (с пайплайнами, мидлварями и контейнером):
    https://youtube.com/playlist?list=PLE20id3DjfFnio1...
    Ответ написан
    5 комментариев
  • Как сохранить результат проверки assertFileEquals в codeception в отдельный файл?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Ассерты ничего не возвращают
    Ответ написан
    Комментировать
  • Чем array_walk лучше foreach?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Если сама функция array_walk может быть не интересной, то ее рекурсивная версия куда вкуснее, тк работает с вложенными массивами
    Ответ написан
    Комментировать
  • Можно ли в DTO указывать методы isName, isLimit?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Геттеры в ДТО только для типизации, так это просто структура
    Вообще с 7.4 можно юзать паблик поля
    Ответ написан
  • Как после array_unique пересчитать ключи массива?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    array_values(array_unique($arr));
    Ответ написан
    1 комментарий
  • Не корректная работа json_decode?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    A value can be a string in double quotes, or a number, or true or false or null, or an object or an array. These structures can be nested.

    A string is a sequence of zero or more Unicode characters, wrapped in double quotes, using backslash escapes. A character is represented as a single character string. A string is very much like a C or Java string.

    A number is very much like a C or Java number, except that the octal and hexadecimal formats are not used.

    https://www.json.org/json-en.html

    Ну это не считая привычных всем массивов и объектов

    Если предположить, что скаляры были бы не доступны, то нельзя было бы делать массив скаляров:
    [
       "aaa",
       "bbb",
       "1234"
    ]
    Ответ написан
    Комментировать