• Когда применяем паттерн Стратегия, а когда Декоратор?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Стратегия = полиморфизм, то есть мы завязаны на некий интерфейс, а какая реализация — нам не важно. Это история про зависимости. Ну например почтальон отдает пенсию бабушкам (любым, какой бабушке именно — зависит от стратегии, КОТОРАЯ НЕ СВЯЗАНА с модификацией конкретной бабушки:)

    Декоратор, это про добавить функционал в рамках одного интерфейса, тут вообще не рассматривается вопрос каких-либо отношений (к примеру бабушки и почтальона), тут рассматривается — бабушка в шубе или бабушка с загаром или бабушка на коляске, все та же бабушка, но "обернутая" неким поведением :) Главное что бабушка всегда остается быть той самой для всех бабушкой. То есть это не противопоставление — ни в начале ни в середине мы не завязываемся на дополнительное поведение бабушки у почтальона. Бабушка и все, а какая именно — зависит от стратегии разноса (например по названию улицы). Если выйдет к нему "декорированная" бабушка-качок — пенсию он даст ей также, как и не качку, тк она для него всего лишь некий субъект/абстракция, главное чтобы возраст и ФИО сошлись.

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

    Кое-где не корректные аналогии, и в аналогии стратегия есть бабушка, но в целом для понимания норм и не критично :)
    Ответ написан
    Комментировать
  • Существует ли Translatable Behavior для Doctrine с хранением переводов в json типе?

    Maksclub
    @Maksclub
    maksfedorov.ru
    По указанной ссылке нормально решение сам автор себе подкинул c ValueObject, в независимости от того, есть JSON поддержка в СУБД или нет.

    Если вы захотите в этот тип заинжектить какое-то поведение из фреймворка, то можно это сделать так:
    достать Type и добавить ему поведение в Kernel::boot(), так сделано например тут:
    https://github.com/dunglas/doctrine-json-odm/blob/...

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

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    $loader = new \Twig\Loader\ArrayLoader([
        'you_name.html' => 'Hello {{ name }}!',
    ]);
    $twig = new \Twig\Environment($loader);
    
    echo $twig->render('you_name.html', ['name' => 'Fabien']);

    Источник: Built-in Loaders¶

    Ну и понятное дело, можете прокси сделать какой-нибудь сервис в контейнере, чтобы руками это каждый раз не городить
    Ответ написан
    3 комментария
  • Является ли это (делегирование) нарушением принципа единственной ответственности – SRP?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Если в объекте Order будет зависимость ProductList, то не нарушает SRP, тк этот объект (судя по названию) VO и состояние этого объекта необходимо для работы Order.

    Но вызывает вопросы способ работы с ним:
    создавать отдельный метод getProductList, в котором будет инициализирован класс

    Почему не в конструкторе объекта Order? Или почему не из зависимости извне (DI) вообще получать товары?
    class Order
    {
         private ProductStorageInterface $products;
    
         public function __construct(ProductStorageInterface $products) 
         {
               $this->products = $products;
         }
         
         public function refundProduct(Product $product): void
         {
               // логика возврата товара 
               // и соответственно изменение  состава $this->products
         }
    }


    И товары могут подгрузиться из памяти, БД или АПИ внешней.
    Тут заодно и принцип инверсии зависимости

    UPD: Не называйте метод getProductList(), это ужасно! Называйте как есть: createProductList() или loadProductList(), ну в зависимости от логики.
    get -- слишком общее слово и совсем не говорит о том, что происходит. Если все кругом будет сервисами (service) с методами get, то вам весело будет жить...
    Ответ написан
    4 комментария
  • Как связать JSON с POJO без родительского элемента?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Может как-то так?
    ObjectMapper mapper = new ObjectMapper();
    JsonNode node = mapper.readTree(jsonString).path("data");
    List<MyData> myData = Arrays.asList(mapper.readValue(node.toString(), MyData[].class));
    Ответ написан
    2 комментария
  • Как в Symfony форму из контроллера передать значения choices для ChoiceType?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Для выбора сущностей в формах есть EntityType:
    https://symfony.com/doc/3.4/reference/forms/types/...
    Ответ написан
    Комментировать
  • Для чего так делают?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Интерфейс -- чистая абстракция.

    Это конечно хорошо, что:
    class Dog extends Animal{}
    class Penguin extends Animal{}


    Но walk() может и друг-человек или мой ребенок, а не только животное,
    и плавать может еще рыбка, а не только животное

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

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    $allAnswers = [
        1 => [2,3,4,5],      // верный, тк даны только верные ответы
        2 => [7,8,9,52],     // не верный, дан неверный 52
        3 => [10,11,12,25],  // не верный, дан неверный 25
        4 => [13],           // не верный, тк пропущены верные ответы 14 и 15
    ];
    $dbAnswers = [
         // данные из БД
    ];
    
    /** 
     *  Группирует любой массив  по любому полю $indexKey в качестве индекса, 
     *  и $columnKey в качестве группируемых значений
     */
    function groupBy(array $input, string $indexKey, string $columnKey): array
    {
        return array_reduce($input,  function($res, $data) use ($indexKey, $columnKey){
            if (!isset($data[$indexKey], $data[$columnKey])) {
                 return $res;
            }
            
            $res[$data[$indexKey]][] = $data[$columnKey];
                
            return $res;
        }, []);
    }
    
    /** 
     *  Маппинг введенных ответов с верными ответами по каждому вопросу
     */
    function computeAnswersResult(array $inputAnswers, array $questionsWithCorrectAnswers): array
    {
        $correctAnswers = $questionsWithCorrectAnswers[$questIn] ?? [];
    
        $result = [];
        foreach($inputAnswers as $questIn => $answesrIn) {
            $errors = array_diff($answesrIn, $correctAnswers);
            $corrects = array_diff($answesrIn, $errors); // если бы не нужно было выводить, то код сильно бы сократился
            $correctDiff = array_diff($correctAnswers, $corrects);
        
            // Если нет лишних ответов и число  введенных верных ответов 
            // совпадает с числом верных в БД, то статус положительный
            $completed = \count($errors) === 0 && \count($correctDiff) === 0;
        
            $result[$questIn] = [
                'errors'    => $errors,
                'corrects'  => $corrects,
                'completed' => $completed,
            ];
        }
        
        return $result;
    }
    
    // Группируем верные ответы по каждому вопросу
    $questionsWithCorrectAnswers = groupBy($dbAnswers, 'id_question', 'id');
    // Маппим введенные ответы на правильные и получаем результат
    $result = computeAnswersResult($allAnswers, $questionsWithCorrectAnswers);
    // Посчитаем успешные
    $completedCount = \count(
        array_filter($result, function($answer) {
            return $answer['completed'] ?? false;
        })
    );
    
    //  ОТОБРАЖЕНИЕ       
    $html = '';
    foreach ($result as $questId => $details) {
        $status = $details['completed'] ? 'верный' : 'не верный';
        
        $html .= 'Вопрос №:' . $questId . PHP_EOL;
        $html .= 'Правильные ответы: '  . implode(', ', $details['corrects']) . PHP_EOL;
        $html .= 'Результат вопроса: '  . $status . PHP_EOL;
        $html .= '-------------------'  .  PHP_EOL;
    }
    $html .= 'Всего верных: ' . $completedCount;
    
    echo $html;
    Ответ написан
  • Версионность API в Symfony?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Предлагаю работать как все норм ребята
    • Создать папку контроллеров /V2/ (ну или/и добавить V2 в название контроллера для удобного поиска в IDE)
    • Настроить префикс для всех этих контроллеров как /v2 одной строкой в роутах (ну или если в аннотациях задаете -- в них указать префикс)

    Дока: Route Groups and Prefixes¶

    И не мудохаться с квери-параметром
    Такие возможности у вас под рукой, какой параметр вы там надумали как в нулевых?!

    Ну это конечно, если вы в контроллерах говна не нагородили, если сделано чисто -- то контроллер как расходка,
    контроллер + парочка ДТО под нужную версию

    UPD от tommy-vercetti:
    Не стоит юзать JMS сериалайзер или Symfony Serializer с аннотациями для сущностей. Для респонсов можно использовать обычные DTO-шки либо fractal.

    API доку удобно писать с помощью https://github.com/bukalapak/snowboard
    Ответ написан
  • Как замокать метод phpunit?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Скорее всего вам это не нужно, так как приватные методы не нужно мокать, в общем случае. Мокают методы ВНЕШНИХ зависимостей

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

    Пример (не забываем про методологию AAA):
    // Arrange
    $provider = $this
         ->getMockBuilder(UserProvider::class)
         ->setMethodsExcept(['delete']) // перечисленные тут методы будут настоящими, хоть и мок
         ->setConstructorArgs([])       // сюда зависимости конструктора передать
         ->getMock();
    $provider = $provider
         ->expects($this->once())
         ->method('canDelete') // мокаем приватный метод
         ->willReturn(true);
    
    // Action
    $result = $provider->delete();
    
    // Assert
    $this->assertEquals(true, $result);
    Ответ написан
    Комментировать
  • Почему работает только index.php?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    вы привели не весь конфиг, скорее всего у вас что-то типа такого:
    location / {
          try_files $uri /index.php?$args;
    }

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

    Если нужны некоторые пути, то можете как-то так сделать:
    location / {
          try_files $uri @rewrite;
    }
    
    location @rewrite {
          rewrite ^/first/?$  first.php;
          rewrite ^/second/?$ second.php;
    
          rewrite ^ /index.php;
    }
    Ответ написан
  • Не могу подключить класс через Composer. Может что то не так делаю?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    1. в файле composer.json:
    "application\\": "application",
    замените на "application\\": "application/",

    2. Подключите в ваш файл autoload.php:
    require __DIR__.'/../vendor/autoload.php';

    3. выполните команду composer dump-autoload
    Ответ написан
    Комментировать
  • В чём отличие методологии от парадигмы?

    Maksclub
    @Maksclub
    maksfedorov.ru
    абстрактные термины тянут абстрактные определения в угоду контекстов и ситуации

    парадигма = уклад
    методология = способ

    и живите счастливо
    Ответ написан
    1 комментарий
  • Как посчитать количество элементов в строке в руби?

    Maksclub
    @Maksclub
    maksfedorov.ru
    f = '1232132131, 12321321, 34232423423, 324234234234'
    f.split.size ## 4
    f.split(', ').size ## 4, вот так корректней


    Подробнее о нюансах: https://apidock.com/ruby/String/split
    Ответ написан
    Комментировать
  • Как сделать юнит тест для метода отправки сообщений на Email?

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

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

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


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

    Maksclub
    @Maksclub
    maksfedorov.ru
    new Assert\Collection([
    'address' => ...

    У данного ассерта нет поля 'address', есть например fields...

    Можете сделать через Expression
    new Assert\Expression([
        'expression' => 'this.isValidAddress()',
        'message'    => 'Адрес или пустой или массив',
    ])

    Тогда нужно будет добавить в класс, где поле address метод
    public function isValidAddress(): bool
    {
          return $this->address === null || \is_array($this->address);
    }


    UPD: Если доменную модель нужно валидировать, значит она может пребывать в невалидных состояних, что говорит о плохом дизайне (что само в свою очередь допустимо, если не раскидываться словами "доменная" и "модель").
    Такое бывает, когда объекты значатся объектами номинально и представляют из себя анемичные (без поведения и инкапсуляции) объекты, а обычные структуры (пусть и типизированные и называющиеся со слова class). Это способ процедурного программирования через ООП-синтаксис. В силу слабой типизации PHP такое программирование является обходным вариантом, чтобы типы сохранить, но к ООП парадигме отношения не имеет. Потому тут сильно вперед вырывается тот же Golang, проще описывать такие процедурные вещи и типизированный.

    Валидировать можно и нужно ДТО к примеру, модель доменную не нужно -- она всегда валидна в любом из состояний, по определению. Автомобиль либо едет сам, если есть бензин, либо нет, никто его извне не "едет" после некой проверки наличия бензина извне.
    Ответ написан
  • Почему IDE не видит методы такого объекта?

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

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

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

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

    Maksclub
    @Maksclub
    maksfedorov.ru

    В блок server добавьте блок location следующего вида:

    location / {
        root /data/www;
    }


    Этот блок location задаёт “/” в качестве префикса, который сравнивается с URI из запроса. Для подходящих запросов добавлением URI к пути, указанному в директиве root, то есть, в данном случае, к /data/www, получается путь к запрашиваемому файлу в локальной файловой системе.

    из доки: https://nginx.org/ru/docs/beginners_guide.html
    Ответ написан
  • Какой самый популярный пакет GO, реализующий авторизацию?

    Maksclub
    @Maksclub
    maksfedorov.ru
    https://github.com/avelino/awesome-go#authenticati...

    Из перечисленных посмотрите на звезды -- это некий критерий популярности
    Ну и учитывайте, что там все вместе: и аутентификация и авторизация -- если вам нужна авторизация -- учитывайте этот момент.
    Ответ написан
    Комментировать