Ответы пользователя по тегу PHP
  • Как сделать юнит тест для метода отправки сообщений на Email?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Юнит-тест:
    Юнит-тест невозможен в данном случае, тк нет этого самого юнита в виде чистой функции (без побочных эффектов). Тут сплошной сайд-эффект: отправка на SMTP, прослушивание HTTP, то есть особо не потестируешь юнитами.
    Объектом тестирования юнит-тестом служит или функция или объект. А у вас и нет ни функции/процедуры даже хотя бы.

    Это будет функциональный (если будете из кода вызывать) или приемочный (если свое приложение через HTTP дергать) в любом случае.

    Как по итогу все же протестировать:
    Такой функционал обычно тестируют так:
    • для начала настройки отправки почты переносят в конфиг
    • в тестовом окружении в конфиг подсовывают настройки своего SMTP сервера
    • поднимают фейковый почтовый сервер, например Mailhog на Go (очень легко ставится)
    • выполняют работу скрипта, чтобы почта ушла, почта летит на ваш подмененный сервер, и у него через АПИ проверяют, что письмо пришло


    Если возьмете Codeception, установите Mailhog на машину, где будут гоняться тесты, или докер-контейнер (вообще парой строк, например если в Gitlab CI гоняете), и поставите модуль Codeception + Mailhog. То легко все проделаете.
    Ответ написан
    2 комментария
  • Почему IDE не видит методы такого объекта?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    IDE не могут знать, какие элементы в массиве, коллекция = массив так или иначе, если коллекция объект, то элементы все равно хранятся в массиве класса коллекции. А массивы не типизированные в PHP по части конкретного элемента.

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

    Можно так обозначить:
    /** @return MemberEntity[] */
    public function getMembers() {}

    Тогда не нужно будет во всех вызывающих местах проставлять тип для каждой переменной в цикле
    Ответ написан
    3 комментария
  • Как конвертировать массив в php?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    <?php
    
    $array = ['name' => 'Albert1991'];
    
    // json
    var_dump(json_encode($array)); //{"name":"Albert1991"}
    
    // НЕ json, а сериализованный формат php
    var_dump(serialize($array)); // a:1:{s:4:"name";s:10:"Albert1991";}
    Ответ написан
    1 комментарий
  • Должны ли Value Object быть Final?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Если статус не является одним из них - выбрасываем исключение. Так же было бы удобно в самом статусе создавать объект через методы фабрики: Status::active(), Status::publish().

    Такой тип с перечислением необходимых значений называетсяEnum, Enum можно считать VO
    Абстрактный функционал вашего псевдотипа, понятное дело, не будет финальным, но конкретные VO конечно делайте final и будет все хорошо.

    Единственное сложные VO трудновато будет тестировать с final модификатором, вам поможет например uopz расширение.

    Для статусов/ролей/id делать VO — круто и хорошо, делать final — тоже

    В догонку статья от Ocramius: When to declare classes final
    Ответ написан
  • Как оставить в ассоциативно массиве только определенные ключи?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    $data = [
        'вес'               => '',
        'дата'              => '',
        'телефон'           => '',
        'город'             => '',
        'товар'             => '',
        'суммазаказа'       => '',
        'остаток'           => '',
        'статусзаказа'      => '',
        'ок'                => '',
        'предоплата'        => '',
        'доставка'          => '24',
        'поставщик'         => '',
        'статусоплаты'      => '',
        'номерзаказа'       => '6666',
        'оплата'            => '',
        'ценазакупки'       => '',
        'прибыль'           => '',
        'артикул'           => '',
        'коментарии'        => '',
        'коментарийклиента' => '',
        'датазаказатовара'  => '',
        'статусдоставки'    => '',
        'магазин'           => '',
    ];
    $filterKeys = ['доставка', 'номерзаказа'];
    
    $result = array_filter($filterKeys, function($key) use ($data) {
        return isset($data[$key]);
    });
    
    var_dump($result);

    sandbox.onlinephpfunctions.com/code/7093a31de82bcb...
    Ответ написан
  • Какой паттерн использовать для задачи получение заказов, отправка статусов заказов из нескольких разных внешних источников?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Начинаем с границ
    Очерчиваем границы нашей системы, как-будто у нас есть этот "идеальный заказ".
    Это будут интерфейсы некоторых абстрактных реквестов и респонсов.
    Реквест на обновление, добавление и респонс на получение и т.д.... И несколько ДТО их реализующие или которые и будут этим интерфейсом сами по себе.

    Далее вы строите клиент OrderClientInterface, который выше созданные реквесты отправляет, респонсы возвращает. И на его интерфейс вы завязываетесь. Строите поверх него фабрику, которая построит вам нужный клиент под нужную систему :)

    Адаптеры (к слову это и паттерн одноименный)
    Клиенты-адаптеры уже будут связывать АПИ внешних систем с интерфейсом вашего абстрактного (имеется в виду интерфейс) клиента. Через guzzle, или через некий свой sdk, уже для вашего домена не важно, дело техники. С авторизацией или без нее... Это детали клиента.

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

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

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    function myFunc(...$args) {
        // вернем все аргументы, что пришли в функцию
        return $args;
    }
    
    $arr = [1, 'Maks', new \DateTime(), null];
    
    var_dump(myFunc(...$arr));

    sandbox.onlinephpfunctions.com/code/c5ba5d829c63a1...
    Ответ написан
    2 комментария
  • Скажите как вам мой код?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    require_once 'classes.php';
    Перейти на Композер и неймспейсы

    class smartPhoneProduct
    Оставить Product, а тип товара вынести в свойство товара type или в category

    Все, что касается модификаций — вынести в характеристики, например может подойтиEAV, то есть некая сущность Характеристика с product_id, name, value и таких быть может много у одного товара. Или иной способ работы, главное чтобы при добавлении сотен характеристик вам не пришлось бы сотнями файлов классы придумывать и всегда был один Product, ну или были др причины создавать детей, например простой товар и цифровой...
    Альтернативы EAV обсуждались например тут: Альтернатива EAV, структура базы?

    getProduct()
    После исправления предыдущего шага данная штука product->getProduct() устранится, а сериализация должна быть в другом слое, товар не должен знать ничего про то, как отображать его, тк отображений может быть много (в каталоге, в письме, в отчетах, в корзине)

    echo "\nСкидка: " .$discountOnProduct. "P";
    echo — вывод, бизнес-объект не должен ничего знать про вывод, ответ сервера и прочее, должен быть просто return $this->description()
    Тем более цена не должна идти в описании, а быть неким getPrice(), причем как правило это делают не у товара, а у вариантов/предложений товара, которые с товаром связаны 1 к 1

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

    это основное... к код-стайлу пока претензии не имеют смысла
    Ответ написан
    6 комментариев
  • Есть ли готовые компоненты для Symfony (но можно и без привязки к нему), способные делать то, что делает Doctrine через рефлексию?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Прямой ответ на вопрос:

    1. Symfony Normalizer в составе Symfony Serializer
    https://symfony.com/doc/current/serializer/normali...
    А именно PropertyNormalizer:
    Symfony includes the following normalizers:
    ...
    PropertyNormalizer to normalize PHP object using PHP reflection.


    2. Zend Hydrator
    Дока: https://docs.zendframework.com/zend-hydrator/
    Код: https://github.com/zendframework/zend-hydrator

    Именно как Доктрина через рекфлексию, то это ReflectionHydrator
    Но в пакете есть и ряд других, не через рефлексию

    Например свежая Cycle ORM юзает именно этот пакет

    ..................................

    получили по API ответ от некоего сервиса, содержащий поля и их значения некоего объекта "Документ", и после на основании некоего конфига мы создаем новый инстанс класса Document, с заполненными свойствами нужными нам значениями. Свойства приватные
    Создавать через конструктор не вариант по своим причинам.

    А вот если прислушаться к вашей задаче, то что мешает создавать через фабричный метод (именованный конструктор)?
    Ответ написан
    1 комментарий
  • Как сложить сумму элементов с одинаковой датой?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    <?php
    
    $json = <<<JSON
    {
      "issuance": {
        "1": {
          "data": "01.20",
          "quantity": 2
        },
        "2": {
          "data": "01.20",
          "quantity": 4
        },
        "3": {
          "data": "04.20",
          "quantity": 2
        },
        "4": {
          "data": "04.20",
          "quantity": 2
        }
       }
    }
    JSON
    ;
    
    $arr = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
    
    $res = new \stdClass();
    $res->issuance = array_values(
        array_reduce(
            $arr['issuance'],
           function ($res, $el) {
                $res[$el['data']]['data'] = $el['data'];
                $res[$el['data']]['quantity'] = isset($res[$el['data']]['quantity'])
                    ? $res[$el['data']]['quantity'] + $el['quantity']
                    : $el['quantity'];
    
                return $res;
            },
            []
        )
    );
    
    var_dump(json_encode($res, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT, 512));

    sandbox.onlinephpfunctions.com/code/c5986c6fd8244a...

    * JSON_THROW_ON_ERROR — этот флаг появился только в 7.3, если версия меньше — убрать
    Ответ написан
    Комментировать
  • Как реализовать облачное хранилище?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Для облачного хранилищ нужно облако (логично, судя по названию)
    Потому нужно создать это самое облако -- начните с OpenStack (на вики)
    PHP SDK: https://github.com/php-opencloud/openstack

    Посмотрите в сторону Swift (на вики)из OpenStack
    https://www.openstack.org/software/releases/train/...

    не работал с данным стеком
    Ответ написан
    Комментировать
  • Как при выполнении Carbon->addDays(int) не обновлять состояние?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    We also provide CarbonImmutable class extending DateTimeImmutable. The same methods are available on both classes but when you use a modifier on a Carbon instance, it modifies and returns the same instance, when you use it on CarbonImmutable, it returns a new instances with the new value.

    https://carbon.nesbot.com/docs/


    CarbonImmutable при изменении данных создает новый обьект, не меняя старый. Так как работает на основе DateTimeImmutable:
    https://www.php.net/manual/ru/class.datetimeimmuta...
    Ответ написан
    1 комментарий
  • Как добавить в ассоциативный массив значение attributes() эллемента xml?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Когда вы превращает документа в SimpleXMLElement, то каждая дочернаяя нода тоже таковой становится и у нее также есть свой набор атрибуттов $element->attributes()
    Итоговый код

    <?php
    
    $string = <<<XML
    <offer id="130" available="true" >
    <url>http://api.loc/index.php?route=product/product&amp;path=3899&amp;product_id=130</url>
    <price>850</price>
    <currencyId>UAH</currencyId>
    <categoryId>3899</categoryId>
    <delivery>true</delivery>
    <stock_quantity>100</stock_quantity>
    <name>Босоножки 82BLUE р. 27 17,5 см Синий</name>
    <vendor>(производитель не указан)</vendor>
    <barcode>200</barcode>
    <param name="Размер обуви">46</param>
    <param name="Размер стельки">19</param>
    </offer>
    XML
    ;
    
    $xml = new SimpleXMLElement($string);
    
    $params = [];
    // Все ноды с тегом param ушли в массив, каждый из из которых  SimpleXMLElement
    foreach($xml->param as $param) {
        // а также @attributes, в котором все атрибуты текущего элемента, достаем тот, что name
        $paramName = $param->attributes()['name'];
        $params[(string) $paramName] = (string) $param;
    }
    
    var_dump($params);
    // [
    //     "Размер обуви"   => string(2) "46"
    //     "Размер стельки" => string(2) "19
    // ]


    UPD: Чтобы руками все не писать и не гадать, какой параметр пришел каким по счету (а могут прийти в одной выгрузке в разном порядке из-за того, что у одного товара просто не указан один из параметров, то предлагаю сделать так:

    1. Завести карту всех соответствий
    $featuresMap = [
        'Размер обуви' => 'size',
        'Размер стельки' => 'stelka',
    ];

    2. И уже динамически собирать с каждого элемента по этой карте параметры
    foreach($xml->param as $param) {
        $paramName = $param->attributes()['name'];
        $paramAlias = $featuresMap[(string) $paramName]
        $params[$paramAlias] = (string) $param;
    }


    И тогда вам нужно будет только расширять свою карту соответствий, а код цикла всегда будет оставаться одним и тем же. Ну добавив пару проверок и например транслитерировать новые значения, добавляя их в БД, и карту уже брать из той же БД.
    Ответ написан
  • Как в типизированных свойствах задать массив типа?

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

    class Bar
    {
        /** @var ArrayCollection|Bar[] */
        public ArrayCollection $props;
    }


    Для решения вашей задачи нужен еще один виток развития языка :)

    НЕсуществующий код!
    class ArrayCollection<T>
    {
        public getElement(): <T>;
        public addElement(<T> $element): void;
    }
    
    class Bar
    {
        public ArrayCollection<Bar> $barCollection;
    }


    Единственное, можно свои коллекции прокачать на проверку типа при создании объекта этой коллекции
    Ответ написан
    Комментировать
  • Что за Undefined offset?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    У вас дампится все отлично — если увидите, на скрине вверху есть слово login
    Очевидно код срабатывает еще раз, там где нет метода и крашится, тк в новый раз этих данных нет...

    Чтобы это узнать, перед получением $s сделайте:
    dd(get_class($event->getController()));
    И покажите полный вывод ВСЕХ значений...

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

    Если выбрали дебаг как раньше на коленке, то у симфы есть функция dump(), все вызовы которой выводятся в панеле разработчика!
    Ответ написан
  • Как спарсить этот JSON?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    <?php
    
    $jsonProducts = '[
      {
        "market_name": "T-shirts",
        "size": "M"
      },
      {
        "market_name": "T-shirts",
        "size": "L"
      }, 
      {
        "market_name": "Jacket",
        "size": "M"
      }, 
      {
        "market_name": "Jeans",
        "size": "XL"
      }
    ]';
    
    $jsonPrice = '{
        "data": [
            {
              "updated_at": 1576048969000,
              "prices": {
                "last": 5300
              },
              "name": "Jacket"
            },
            {
              "updated_at": 1576048969000,
              "prices": {
                "last": 2000.12
              },
              "name": "T-shirts"
            }
        ]
    }';
    
    $products = json_decode($jsonProducts, true);
    $price = array_column(json_decode($jsonPrice, true)['data'], 'prices', 'name');
    
    $result = array_map(function($item) use ($price) {
        $product              = new \stdClass();
        $product->market_name = $item['market_name'];
        $product->size        = $item['size'];
        $product->price       = $price[$item['market_name']]['last'] ?? null;
        
        return $product;
    }, $products);
    
    $result = json_encode($result, JSON_PRETTY_PRINT);
    
    var_dump($result);
    // [
    //     {
    //         "market_name": "T-shirts",
    //         "size": "M",
    //         "price": 2000.12
    //     },
    //     {
    //         "market_name": "T-shirts",
    //         "size": "L",
    //         "price": 2000.12
    //     },
    //     {
    //         "market_name": "Jacket",
    //         "size": "M",
    //         "price": 5300
    //     },
    //     {
    //         "market_name": "Jeans",
    //         "size": "XL",
    //         "price": null
    //     }
    // ]


    sandbox.onlinephpfunctions.com/code/935656788a07af...
    Ответ написан
  • DI: как инстанциировать реализацию интерфейса с кастомным значением параметра конструктора?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Используйте фабрику:

    - В конфиге контейнера
    // регайте ваш интерфейс, но ссылаясь на метод фабрики,
    // которая и произведет ваш сервис
    \App\Writer\WriterInterface::class => DI\factory('\App\Writer\TextWriterFactory::create'),

    - Код фабрики:
    class TextWriterFactory implements WriterFactoryInterface
    {
        public function __construct(Settings $settings, DataConverter $dataConverter)
        {
            $this->settings = $settings;
            $this->dataConverter = $dataConverter;
        }
    
        public function create(string $filePath): WriterInterface
        {
            return new TextWriter($this->settings, $this->dataConverter, $filePath);
        }
    }


    Источник: php-di.org/doc/php-definitions.html#factories
    Ответ написан
  • Как переписать preg_replace_callback()?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Callback можно задавать как
    Как массив объекта и его метода: [$obj, 'myCallbackMethod']
    Для статического метода: [MyClass::class, 'myCallbackMethod']
    Другой способ для статического метода: 'MyClass::myCallbackMethod'

    Пример для вас:
    <?php
    
    class TestListener
    {
      //   сигнатуру тут лишнюю для простоты примера просто убрал
      public function onContentPrepare(&$row) {
    
        $row->text = preg_replace_callback('|{gold}|', [$this, 'insertgold'], $row->text);
      } 
    
      public function insertgold(array $matches)
      {
          return $matches[0] . 'Called';
      }
    }
    
    
    // Example
    $string = new stdClass();
    $string->text = 'TestText with gold  and gold ';
    
    $listener = new TestListener();
    $listener->onContentPrepare($string); 
    var_dump($string->text ); //TestText with goldCalled  and goldCalled
    Ответ написан
  • Как разрулить конфликт зависимостей composer?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Если под 3 версию не хватает адаптеров, то нужно:
    • либо доработать адаптеры под 3-ю версию и внести в клад в опенсорс,
    • либо сделать форк 2 версии для самой библиотеки и для common, адаптировать common (форк) под работу с symfony/http-foundation 4 версии, выложить на Packagist и затянуть к себе уже свой форк


    Приходилось как-то пару раз мутыжиться со вторым способом... гемморно, но нужно было
    Может так статься, что адаптер форкнуть будет проще
    Ответ написан
    9 комментариев
  • Как асинхронно выполнить скирпт php?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Возьмите любой асинхронный фреймворк на php:
    RoadRunner
    ReactPHP

    С помощью него запускайте свой воркер
    И результат через веб-сокет отдавайте на клиент
    Ответ написан
    Комментировать