• Логика в геттере возможна ли?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Нет

    Геттеры могут быть в транспортных классах и иногда в инфраструктурных/библиотечных.

    Иначе это нарушение инкапсуляции: открывают доступ к тому, что закрыли. Как и сеттеры — дают доступ к тому, что закрыто, но на запись.
    Крч чит, хотя соответствуют определению инкапсуляции и дату-хиддингу

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

    Maksclub
    @Maksclub
    maksfedorov.ru
    Имитация вашего объекта:
    class Relay
    {
        private $privateProperty;
    
        public function __construct()
        {
            $this->privateProperty = new \stdClass();
        }
    
        // метод, который работает с приватным свойством
        public function call()
        {
            return $this->privateProperty;
        }
    }


    Способ 1: Нативный PhpUnit + Reflection API
    use PHPUnit\Framework\TestCase;
    
    class RelayTest extends TestCase
    {
        public function testCall(): void
        {
            $reflectionClass = new \ReflectionClass(Relay::class);
            $reflectionProperty = $reflectionClass->getProperty('privateProperty');
            $reflectionProperty->setAccessible(true);
    
            // создаем наш объект БЕЗ конструктора
            $relay = $reflectionClass->newInstanceWithoutConstructor();
    
            // Меняем свойство и вызываем метод, работающий с этим приватным полем
            $reflectionProperty->setValue($relay, 1111);
            self::assertEquals(1111, $relay->call());
    
            // Меняем свойство и вызываем метод, работающий с этим приватным полем
            $reflectionProperty->setValue($relay, 'aaaa');
            self::assertEquals('aaaa', $relay->call());
        }
    }


    Способ 2: Через Codeception Stub
    class RelayTest extends TestCase
        public function testCall(): void
        {
            /** @var Example $stub */
            $stub = Stub::make(Relay::class, [
                'privateProperty' => 1111,
            ]);
            self::assertEquals(1111, $stub->call());
    
            $stub = Stub::make(Relay::class, [
                'privateProperty' => 'aaaa',
            ]);
            self::assertEquals('aaaa', $stub->call());
        }
    }

    Отступления и полезные советы:
    • Почему-то стандартный, давно используемый мною, способ с инъекцией в мок приватного филда не зашел и выдавал null всегда.
    • Юзайте Inversion of Control Principle, например инъекцию зависимости через конструктор или инъекцию через метод с присвоением в конструкторе NullObject. Оба способа будут хороши для тестирования.
    Ответ написан
    1 комментарий
  • Как найти контроллер формы в symfony?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Данный контроллер, указанный в шаблоне отрисовывает (создает) форму по пути bundle:controller:action, а вот обрабатывает другой/другие. Вообще текущий шаблон вызывается искомым контроллером или несколькими.

    Ищем контроллер по обработке формы:

    1a. Или открываете гугл-консоль вкладку Network -> XHR и при отправке формы смотрите — на какой роут идет отправка
    1b. Или открываете дебаг-консоль Symfony и при отправке формы смотрите — на какой роут идет отправка

    2. Ищите роут: bin/consol debug:route <route>, где <route> — путь от path, можно через grep искать: bin/console d:r | grep <route_part>

    Команда выдаст вам примерно такую инфу:
    $ bin/console d:r home
    
    +--------------+----------------------------------------------------------------+
    | Property     | Value                                                          |
    +--------------+----------------------------------------------------------------+
    | Route Name   | home                                                           |
    | Path         | /{_locale}                                                     |
    | Path Regex   | {^/(?P<_locale>en|ru)?$}sDu                                    |
    | Requirements | _locale: en|ru                                                 |
    | Class        | Symfony\Component\Routing\Route                                |
    | Defaults     | _controller: \App\Controller\HomeController::index() |
    |              | _locale: en                                                    |
    | Options      | compiler_class: Symfony\Component\Routing\RouteCompiler        |
    |              | utf8: true                                                     |
    +--------------+----------------------------------------------------------------+


    но там могут быть переменные (id например или слаг), нужно без них... в любом случае путь найдете по команде bin/consol debug:route название вашего роута (справа пути с выражениями, слева названия роута — его и подставить в первую команду нужно):
    users.show           ANY      ANY      ANY    /admin/users/{id}                                 
    app_login            ANY      ANY      ANY    /login                                            
    app_logout           GET      ANY      ANY    /logout                                                               
    oauth.fake           ANY      ANY      ANY    /fake_login                                           
    auth.signup          ANY      ANY      ANY    /signup


    3. Там где-то есть обработка формы, находите что-то типа if ($form->isSubmitted() && $form->isValid()) и уже внедряете свою капчу
    4. PROFIT!
    Ответ написан
    7 комментариев
  • Полиморфизм в базе данных, как организовать таблицы?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Разделяйте ответственности
    OrganisationSeller и PersonSeller могут наследоваться от Seller
    и для реализации такого можно реализовать такой паттерн Single Table Inheritance

    А вот обычные Organisation и Person связывайте с абстрактным Seller через обычную связь. Конкретные наследники будут лежать в зависимости от типа

    Итого будет так:
    (OrganisationSeller extends Seller) будет объектом Organisation.seller

    Готовые решения:
    Ответ написан
    Комментировать
  • Symfony - Repository Как избежать двойного кода в этом случае?

    Maksclub
    @Maksclub
    maksfedorov.ru
    flush() нужно вынести, потому что вы можете заперсистить 100 объектов и сделать один flush — одна череда обращений к Бд. В вашем же случае будет 100 и более обращений к БД

    Чтобы сделать универсальный метод для персиста в репозиториях, раз уж вы решили так делать (многие считают это верным) — можно сделать универсальный код (трейт к примеру), в котором будет проверка через classMetadata, что сохраняемый объект нужного типа для репозитория (тут не забываем про наследование сущностей, is_a()и вот это все) и если придет не тот объект — кидать InvalidArgumentException

    НО! Если вы репозитории размещаете в своем домене/пакете/неймпсейсе, то вам не избежать дублирования кода, и это не так уж и страшно... Связанность и паутина наследований страшнее. Репозиториев все равно мало во всем коде относительно всей базы кодовой.
    Ответ написан
    2 комментария
  • Что такое атрибуты в языке программирования?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Это аналоги аннотаций (которые есть уже в PHP через библиотеки типа Doctrine Annotations)
    5f2e640b82f02855914596.png

    и которые в свою очередь аналоги Java Annotations
    5f2e64693dc84892539290.png

    Тк в PHP это реализовывалось отдельным парсингом определенно оформленных комментариев — стало по сути отдельным языком и получило популярность в Symfony и др инструментах — решили внедрить как в Java нативно на уровне языка.

    Аннотации/атрибуты нужны для того, чтобы можно было писать логику в аспектно-ориентированном стиле, подробнее.
    То есть некоторая логика, которая выполняется несколько отдельно (декорирует и/или меняет поведение или ещё как-то иначе улучшает программу).
    Это очень выразительный, удобный способ для использования, хотя иногда и сложный для того, чтобы разобраться в кишках работы того или иного инструмента. Более подробно: Wiki: Аннотация (Java)
    Ответ написан
    1 комментарий
  • Как собрать массив определённого ключа другого массива?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    <?php
    
    // Ваши данные в упрощенном виде (без мусора)
    $array = [
        'numbers' => [
            [
                'numbers' => [
                    [
                        'formattedValue' => 111,
                    ],
                    [
                        'formattedValue' => 3333,
                    ],
                    [
                        'formattedValue' => 5555,
                    ],
                ],
            ],
            [
                'numbers' => [
                    [
                        'formattedValue' => 200111,
                    ],
                    [
                        'formattedValue' =>  2003333,
                    ],
                    [
                        'formattedValue' =>  2005555,
                    ],
                ],
            ],
        ],
    ];
    
    // Найдет все значения во всех вложенных элементах с нужным ключом
    // и сделает это рекурсивно — то есть во всех массивах и их детях.
    function searchValues(array $arr, string $search, array $res = []): array {
        array_walk_recursive($arr, function($value, $key) use (&$res, $search) {
            if ($key === $search) {
                $res[] = $value;
            }
        });
    
        return $res;
    }
    
    // Ищем в нашем массиве все значения с ключом `formattedValue` 
    // и складываем найденные значения в пустой массив (по дефолту), 
    // но можно передать 3-м параметром и имеющийся массив, тогда значения туда добавятся.
    $result = searchValues($array, 'formattedValue')
    
    var_dump($result);

    sandbox.onlinephpfunctions.com/code/d91d34104073f8...
    Ответ написан
    Комментировать
  • Как подключить Doctrine в свой проект?

    Maksclub
    @Maksclub
    maksfedorov.ru
    По мануалу создание EM:
    Официальная дока простая же: https://www.doctrine-project.org/projects/doctrine...

    Создаётся подключение, создаётся массив с путями, где лежат сущности и на основе этой инфы создаётся EntityManager

    На примере Slim создание и регистрация EM:
    Можете подсмотреть как тут настраиваюсь доктрину.
    Да, в контексте контейнера, но вы увидите как это все не сильно отличается от оригинального мануала:
    www.slimframework.com/docs/v3/cookbook/database-do...

    Репозитории:
    Репозитории в доктрине либо общие (с ограниченным интерфейсом EntityRepository), либо создаются фабрикой вами или вообще руками как по последней ссылке... Если работали с симфони, то вы знаете, что надо было зарегать репы...
    Ответ написан
  • Как правильно в доктрине построить запрос для фильтра со сложными условиями?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Вы в цикле для разных фильтров добавляет в один объект QueryBuilder параметры с одним ключом values, потом при сборке запроса Doctrine достает первый найденный Parameter с ключом values (а их таких несколько, но ей достаточно первого).

    Код Доктрины:
    5f21da43a9cea902909869.png

    То есть для цвета, года, уровня — всегда достается первый параметр с ключом values

    Предлагаю подставлять вместо этого слова разный алиас, например key:
    [$normalizedKey, $key] = $this->getKeys($qb);
    
    $qb
    	->andWhere(sprintf('%s IN (:%s)', $normalizedKey, $key))
    	->setParameter($key, $this->getValue());
    Ответ написан
    1 комментарий
  • Можно ли в Symfony 3 при создании теста в Codeception авторизовать пользователя, создать сессию?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Можете сделать фейковый аутентификатор
    Symfony\Component\Security\Guard\AuthenticatorInterface
    и подсовывать юзера с нужными правами в тестовой среде, тогда легко можно проверять права доступа

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

    Maksclub
    @Maksclub
    maksfedorov.ru
    Помимо "удобства" есть еще какой-то тайный смысл ?
    Новичкам очень удобно и просто, зачем сложно как хлоп и сделал

    Для меня хелперы всегда ассоциировались с
    Как сказал однажды Аршавин (поплатившись своей карьерой из-за этих слов) "Ваши ожидания — это ваши проблемы" :)

    Да, все покрыто этой магией, по сути в Ларавел реализовали свой язык, из-за чего он стал понятным и удобным для толп "программистов" и для быстрого штампования однообразной логики

    В больших проектах выбор падает все же на DI:
    Stop Using Facades
    https://blog.amezmo.com/replacing-your-facades-wit...

    Вот еще немного особенностей от Кирилла Несмеянова с таймингом:
    https://youtu.be/QHLZohh7Tkw?t=2203
    Ответ написан
  • Как установить файл из git?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Composer classmap
    + унаследовать интерфейс, сделав ему неймспейс, достаточно базовая операция для php

    Итого:
    • сделать репу с классом и composer.json и подключить ее или через Loading a package from a VCS repository или packagist/satis завести пакет
    • внутри репы зарегать свой класс в этом composer.json в глобальном неймспейсе через classmap
    • в этой же репе обернуть этот класс новым интерфейсом (или не делать этого)

    Или вообще подрубить прямо в своем проекте
    Ответ написан
    1 комментарий
  • Какую опцию использовать для json_encode, чтобы он не экранировал слеш?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    JSON_UNESCAPED_SLASHES
    Ответ написан
    Комментировать
  • Выборка и сравнение данных массива PHP?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Сделал на примере массивов, можно на объекты переделать :)

    В одну итерацию (если, конечно, я правильно понял задачу): родитель и дети лежат плоско и для одного уровня вложенности. Если есть вложенности, то код будет немного по-другому выглядеть.

    <?php
    
    $data = [
        [
            'id' => 4,
        ],
        [
            'id' => 1,
            'start' => 1,
            'end' => 50,
            'parent_id' => 4,
        ],
        [
            'id' => 1,
            'start' => 10,
            'end' => 50,
            'parent_id' => 4,
        ],
        [
            'id' => 1,
            'start' => 5,
            'end' => 100,
            'parent_id' => 4,
        ],
    ];
    
    
    function aggregateExtremumBorders(array $elements) {
        return array_reduce($elements, function($res, $element) {
            if (!isset($element['parent_id'])) {
                return $res;
            }
            
            $parentId = $element['parent_id'];
            $parent = $res[$parentId] ?? ['id' => $parentId, 'min' => null, 'max' => null];
    
            $parent['min'] = min($parent['min'], $element['start']) ?: $element['start'];
            $parent['max'] = max($parent['max'], $element['end']) ?: $element['end'];
            
            $res[$parentId] = $parent;
            
            return $res;
        }, []);
    }
    
    var_dump(aggregateExtremumBorders($data));

    sandbox.onlinephpfunctions.com/code/7b43e0e4d8cf9d...
    Ответ написан
    1 комментарий
  • Удалили пакет из packagist и github, как работать с composer дальше?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    • Или запушить код в свой репозиторий и сделать пакажист-пакет (или не делать)
    • Или перенести код из папки vendor выше в проект и добавить неймспейс в composer.json


    PS: если репа нежелательна и ее могут удалить, то если выбрали первый вариант, то размещайте его в приватной репе и подключайте через https://getcomposer.org/doc/05-repositories.md#loa...
    Ответ написан
  • Закон Деметры и Doctrine?

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


    Для решения задач в другом модуле/домене нет необходимости иметь сущности др проекта/модуля/бандла. Пример в модуле доставки
    • не нужно знать ВСЕ о товаре и логике его сбора, нужно знать размеры и название
    • в этом модуле не нужно знать и о заказе все — только адрес, время. История его обработки и статусы (все это есть в агрегате/сущности Заказ), влияние акций и промо — это знать не нужно, но оно есть!... передавая сущность куда-то вы передаете знания и связываете разные модули
    • не нужно знать о пользователе много, кроме телефона, имени и адреса


    Потому вы можете из БД выбирать только необходимые данные конкретному модулю. Этакие проекции по потребностям. Это есть соблюдение закона Деметры — вы не лезете в другой модуль, вы лезете в инфраструктуру (вселенную), которая даст вам ровно те знания и состояние, которые нужны . То есть за своими данными.. Более того эти знания могут собираться различными способами и не обязательно из реляций доставаться, а могут быть агрегированные в NoSQL виде к примеру.

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

    Maksclub
    @Maksclub
    maksfedorov.ru
    Ответ по теме:
    • или сделайте поле lastPrice и храните последнюю цену там (ну или через кастомный гидратор ее в поле продукта вставляйте)
    • или выбирайте необходимые данные голым SQL/DQL в DTO объекты,
    • или на крайний — отдельно выбирайте последние цены для ваших товаров, но тут по сути некоторое подобие второго варианта


    Получать последнюю цену в массив цен — некорректная и очень поганая задумка, даже если у вас получится сделать это. Вы получите "битый" бизнес-объект и вообще все это плохо и не удобно и не правильно.

    Совет по запросу:
    Лучше сделайте не вложенным запросом, а через джойн
    SELECT product.*, last_prices.*
    FROM product
    LEFT JOIN (
         SELECT product_id AS id, max(id) as price_id
         FROM price
         GROUP BY product_id
    ) last_prices  ON last_prices.id=product.id
    Ответ написан
    Комментировать
  • Что за мистика с условием при дробных числах?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    А в чем подвох? Почему без round не работает?


    Нормально и инженерно: по ссылке в этом ответе Не могу понять почему работает последняя исключения?
    Познавательно и просто (в самом начале видео про эту ошибку как раз): Опасные ошибки, которые мы не замечаем
    Ответ написан
    Комментировать
  • Зачем нужно знать эффективность\сложность алгоритма?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Есть два алгоритма для одной задачи:
    - один проходит по всем элементам один раз и что-то делает, выполняя поставленную задачу O(N)
    - другой проходит по всем элементам для каждого элемента: O(N^2)

    При количестве элементов N=10, в цикле в первом случае будет 10 операций, во втором случае 100 (казалось бы, в 10 раз больше всего, как и элементнов)
    Но при увеличении до N=1000 в первом случае 1000 проходов, во втором уже 1 000 000 ! Видите как сильно растет разница.

    Даже при небольших значениях N это может быть важно, если каждая операция долгая/тяжелая и даже 2-3х кратное увеличение может быть проблемой.
    Ответ написан
    Комментировать