• Какой паттерн использовать для задачи получение заказов, отправка статусов заказов из нескольких разных внешних источников?

    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...
    Ответ написан
  • Скажите как вам мой код?

    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

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

    это основное... к код-стайлу пока претензии не имеют смысла
    Ответ написан
  • Symfony - в чём разница между listener и subscriber, и что означает теговать (tags)?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Listener — слушатель одного или более событий, некий такой атомарный исполнитель. Не знает на что он подписан, но легко управляется через конфигурацию. Если разные слушает — нужно самому логику разделения обработки писать.
    Subscriber — подписчик имеет реализацию интерфейса SubscriberInterface с методом getSubscribedEvents(), то есть сам объект слушателя знает, на что он подписан и какие методы какие события будут отрабатывать

    По сути одно и тоже, но такая разница иногда пригождается. Иногда нужно иметь контроль снаружи, иногда наоборот.

    Теги — отдельный механизм контейнера. Проставляя тег на набор сервисов — мы во время компиляции контейнера можем достать помеченные определенным тегом сервисы и заинжектить в др сервис (очень часто используется мной например), отсортировать по допатрибутам или сделать любую иную логику
    https://symfony.com/doc/current/service_container/...
    Ну например есть десяток сервисов, которые обрабывают каждый свой тип файла, еще и лежат в разных бандлах. Как получить их в одной фабрике? Правильно — проставить им тег, в компиляторе приложения достать все и в фабрику засунуть. При появлении нового сервиса с тегом - он автоматом будет в фабрике.

    В коде самого фркймворка этот механизм активно используется. Например есть разного рода констрейнты - добавить новый вы можете и автоматом зарегистрировать, проставив тег (на самом деле симфони-разработчики пошли дальше и тег навешивают по интерфейсу, но важен механизм) и данный констрейнт будет доступен коду, в котором требуются все констрейнты (опять той же фабрике)
    Ответ написан
  • Как создать собственный валидатор в symfony?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Я вроде читаю https://symfony.com/doc/4.4/validation/custom_cons... но чё то понять не могу.

    /**
    * @Annotation
    */
    class ContainsAlphanumeric extends Constraint
    {
    public function validate($value, Constraint $constraint) {}
    }


    Небольшая путаница произошла по спешке: аннотация (констрейнт) — это одно, валидатор — другое, они связаны, но это две разные вещи одной системы.

    Constraint — по сути выражение/аннотация, по которой будет срабатывать валидатор. Она принимает в себя некие параметры и содержит логику к чему применяться — к классу или филду класса.

    Создаем свой констрейнт:
    В нашем констрейнте мы планируем, что мы сможем передать некий дополнительный параметр размер, а если не передадим, то его значение будет 55 (ну так хотим). Причем это будет относиться не к кполю, которое валидируем, а именно к констрейнту, То есть то, что он отдаст валидатору. Ну например возраст или некий пинг,да что угодно, в общем ориентируясь на который валидатор применит некую логику.
    /**
     * @Annotation
     */
    final class AnyConstraint
    {
        /**
         * @Required
         * @var int
         */
        public $size = 55;
    
        /**
         * Вот тут мы укажем некий валидатор, тот класс, который и будет валидировать наше значение
         * Дял удобства он будет называться также + слово Validator, в нашем случае AnyConstraintValidator
         */
        public function validatedBy()
        {
            return \get_class($this).'Validator';
        }
    }


    Создаем собственно валидатор
    на который выше в методе validatedBy() мы уже указали

    class AnyConstraintValidator extends ConstraintValidator
    {
        public function validate($value, Constraint $constraint): void
        {
            // Тут логика, при которой или все пройдет до return - все отлично
            // Или вообще исключение вывалится (если пришло чего-то не то)
            // Или (для чего и используется валидатор) создастся violation
    
            // например число в объекте больше того, которое мы указали в size (по дефолту если не указали: 55)
            if($value > $constraint->size) {
                $this->context->buildViolation('Тут пинг пришел вообще большой  —  такой нельзя, брат')
                    ->setParameter('{{ string }}', $value)
                    ->addViolation();
            }
        }
    }


    Используем:
    class AnyEntity
    {
        /**
    
         * Если значение больше 55  — то не пройдет валидацию
         * @AnyConstraint(120)
         */
        protected $name;
    }


    Помимо всего у констрейнта можно указать к чему применять — к методу, классу или к переменной класса
    • Через аннотацию у класса констрейнта: @Target({"PROPERTY", "METHOD", "ANNOTATION"})
    • Через метод переопределенный метод getTarget()
    Ответ написан
  • Как правильно организовать запись в бд, поле пригласил?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Тут не нужна другая сущность.

    One-To-Many, Self-referencing
    You can also setup a one-to-many association that is self-referencing. In this example we setup a hierarchy of Category objects by creating a self referencing relationship. This effectively models a hierarchy of categories and from the database perspective is known as an adjacency list approach.

    class User
    {
        /**
         * Кого пригласил
         * @var ArrayCollection
         * @ORM\OneToMany(targetEntity="User", mappedBy="invitedBy")
         */
        private $invitedMembers;
    
        /**
         * Кем был приглашен
         * @var User
         * @ORM\ManyToOne(targetEntity="User", inversedBy="invitedMembers")
         */
        private $invitedBy;
    
        public function __construct() {
            $this->invitedMembers = new \Doctrine\Common\Collections\ArrayCollection();
        }
    }
    Ответ написан
  • Есть ли готовые компоненты для 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, с заполненными свойствами нужными нам значениями. Свойства приватные
    Создавать через конструктор не вариант по своим причинам.

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

    Maksclub
    @Maksclub
    maksfedorov.ru
    Некоторые случаи использования коллбека (помимо асинхронного упомянутого использования):

    В коде: отложенный (ленивый) вызов.
    Например есть некий контейнер, в него мы регистрируем некие объекты с некоторой логикой.
    Но так как очень много таких кандидатов и они все запускаются, замедляя и работу и нагружая ресурсы, то мы бы хотели, чтобы контейнер просто знал о них, но при инициализации не запускал весь код создания этих объектов. На помощь приходят коллбеки — в коллбеке мы описываем логику создания объекта и (внимание) регистрируем именно коллбек с этой логикой создания, все — это очень быстро и легко, тк не наплодили объектов. Далее только при вызове нужного объекта контейнер видит, что зареган коллбек и вызывает его, тем самым инициализирует создание нужного объекта, описанное в этом коллбеке! ПРОФИТ!

    В коде: рекурсивно применить некоторый функционал к элементам коллекции.
    Очень популярное использование в Java в стримах.
    Есть коллекция элементов, запускаем некий map(), reduce(), filter(). walk() по набору элементов (коллекции) передавая в этот обход коллбек, и ожидаем, что получим результирующий набор после применения коллбека к каждому элементу. То есть этот коллбек будет выполнени внутри обхода, сама конструкция этих методов подразумевает, что вы передадите что-то что потом вызовется.

    Между системами: асинхронное взаимодействие
    Hook, callback
    Есть программа, которая ждет событий от внешней системы. Например есть платежная система Яндекс.Касса и есть ваш магазин. Вы отправили клиента на оплату в платежку, но он ушел "в путешествие" и вы не можете узнать судьбу платежа сразу. Вы можете бомбить сайт платежки, проверяя статус по номеру транзакции, а можете дождаться обратного вызова (коллбека, хука) от платежной системы с событием "Оплатил" или "Не оплатил, тк не хватило".
    Также в др системах — само оповестит, вызвав обратно уже меня.
    Ответ написан
  • Можете найти простой ответ на второй вопрос из собеседования?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Как сделать так, чтобы, получив обновлённое приложение, конкуренты не смогли узнать его новые функции?

    Фиче-свитчи Приложение может быть обновлено и быть с новыми фичами, но они будут отключены, причем выключатели находятся не на стороне клиента, чтобы могли конкуренты узнать или СМИ или не важно кто, а на стороне бекенда — каждый раз приложение будет запрашивать данный список. И управляя бекендом — можно тестировать, включать по релизу/событию.
    Кроме того, что никто не узнает, так еще и можно делать так, что фичи будут включаться на части пользователей, чтобы протестировать на боевых пользователей и выкатывать по мере уверенности на все большей и большей доле.
    Ответ написан
  • Как сложить сумму элементов с одинаковой датой?

    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, если версия меньше — убрать
    Ответ написан
  • Как использовать Jms Serializer с Doctrine с настройкой Lazy?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Аннотация @Accessor

    /** @Accessor(getter="getName") */
    private $name;
    
    public function getName()
    {
         return $this->name;
    }

    При вызове указанного геттера произойдет вызов и запрос. Соответственно lazy вернет результат.

    Если yaml, то в той же доке указано как задать аксессор.
    Не забудьте почистить кеш (не всегда чистится консольной командой, удалите папку jms - когда с 3й работал, то были такие глюки) .


    А вообще, если пишите с нуля, то знайте — от JMS Serializer все уходят в сторону Symfony Serializer
    В контейнере уже должен быть у вас сервис @serializer сразу после установки компонента и можно просто заинжектить через SerializerInterface в конструкторе
    Ответ написан
  • Почему дефолтные параметры методов бесполезны?

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

    public const DEFAULT_VAR = 4444;
    public const DEFAULT_VAR_2 = 55555;
    
    public function test(?int $var = self::DEFAULT_VAR, ?int $var2 = self::DEFAULT_VAR_2){
        echo $var;
        echo var2;
    }
    
    ...
    
    $obj->test(Obj::DEFAULT_VAR, 9999);

    Если прям нужно что-то сложное, то аргументом могут статькак DTO, так и ValueObject (в зависимости от контекста)
    public function test(AnyType $anyType) {}
    
    ...
    
    $obj->test($valueObject);

    у которого дефолтные значения есть и который вы можете настроить нужным образом

    Если какой-то сложный случай, то скорее всего надо разбивать дальше, например на один обязательный и второй как некий конфиг/расширенные настройки или некими др эвристиками. Все можно сделать красиво и немногословно. Nullable аргументы — для меня плохой запах и я предпочитаю Null Object, понятное дело по ситуации.
    Если речь пошла про Dto, то можно сделать примерно так:
    class OrderDto::__construct(ind $id, DateTimeImmutable $createdAt, array $products = []) {}
    class UserDto::__construct(ind $id) {}
    class OrderDetailsDto::__construct(OrderDto $userDto, ?UserDto $userDto = null) {}


    В случае однотипных объектов можно так:
    class SettingsDto::__construct(SettingDto...$settingDto) {}
    ...
    // использовать тогда так:
    new SettingsDto(
        new SettingDto(1, 'one'),
        new SettingDto(2, 'two'),
        ...,
        new SettingDto(n, 'n')
    )

    То есть в одном объекте настроек можно передавать объекты конкретных настроек
    Ответ написан
  • Зачем писать два слэша?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    В строке PHP обратный слэш служит экраном, соответственно когда нужен реально слэш (а для неймспейса он реально нужен), то его использование провоцирует на неправильное поведение, и его нужно сам заэкранировать.

    5e0d2dcd61e13770335616.png
    https://www.php.net/manual/ru/language.types.string.php

    Напоминание:
    Когда будете использовать регулярные выражения в переменных, а точнее обратный слэш, то это особо пригодится:
    '\\d+'
    Ответ написан
  • Как реализовать облачное хранилище?

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

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

    не работал с данным стеком
    Ответ написан
  • Как сделать, чтобы возраст у перса игрока увеличивался на одну единицу через 24 часа с момента регистрации?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Дата рождения в сочетании с датой текущей - есть возраст.
    27 декабря родился, значит сейчас 4 годика :) и зачислять ничего не нужно, разница просто есть.

    Работа с датой из коробки во всех языках и базах данных.

    Возраст: 0.
    Через 24 часа:
    Возраст: 1/2/и так до бесконечности.
    Нет, не в годах/etc, а именно чтобы просто цифра увеличилась в строке.
    P.s: извините, забыл упомянуть, что "лет" годы" и прочее просто слово.

    Мы вас поняли, об этом и говорим! В коде ниже поменять маску формата, и возраст будет не днями, а чем-то другим, что зададите, (%a - число дней)
    Пример на коленке: (без проверок, образец)
    <?php
    
    class Person
    {
        /** @var  \DateTimeImmutable */
        private $registeredAt;
    
        /** 
         * Тут число дней как значение, но можете число лет, месяцев, как вам угодно
         * @var string 
         */
        private $ageFormat = '%a';
        
        public function __construct()
        {
            // тут для примера зашил дату декабря прошлого года для наглядности, 
            // в реале значение даты рождения из БД будет браться
            $this->registeredAt = new \DateTimeImmutable('2018-12-28');
        }
        
        public function getAge(): int
        {
            $now = new \DateTimeImmutable();
            
            return (int) $this->registeredAt->diff($now)->format($this->ageFormat);
        }
    }
    
    $person = new Person();
    
    // 368 лет (дней) на 31 декабря 2019 года по отношению к "дате рождения"  2018-12-28
    // 1 января 2019 года будет 369
    // 2 января 2019 года будет 370
    // ...
    // крч, всегда отдаст твой "возраст" актуальный в нужных попугаях
    echo $person->getAge();


    Более подробно про дату: https://www.php.net/manual/ru/class.datetimeimmuta...
    Про маски для формата diff: https://www.php.net/manual/ru/function.date-diff.php
    Ответ написан
  • Как в spring вернуть лист объектов?

    Maksclub
    @Maksclub
    maksfedorov.ru
    @Repository
    class ShipRepository {
     
        @PersistenceContext
        private EntityManager entityManager;
     
        @Override
        public List<Ship> findLimitedTo(int limit) {
            return entityManager.createQuery("SELECT s FROM Ship s ORDER BY s.id",
              Ship.class).setMaxResults(limit).getResultList();
        }
    }
    Ответ написан
  • Как при выполнении 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...
    Ответ написан