Ответы пользователя по тегу PHP
  • PHP: как из даты вычесть дату, и получить дни?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    <?php
    
    $now = new DateTime();
    $date = new DateTime('2020-05-10');
    
    $diff = $date->diff($now)->format("%a");
    
    var_dump($diff);

    Форматы для интервала: https://www.php.net/manual/ru/dateinterval.format.php
    Ответ написан
    Комментировать
  • Скорость проверки наличия значения в массиве. Что быстрее?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    в БД быстрее, чем в файле

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

    нужно проверять доменный адрес его почты
    Не будет ли большой нагрузки на базу данных при переборе значений?

    Что за перебор значений? Поиск по значению — как раз НЕ перебор, а использование оптимизированных структур и обходов по ним, с индексом будет моментально, даже быстро будет если не 74тыс+, а 74млн+
    Ответ написан
  • Как заменить array_map на функцию?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Сделать функцию — означает сделать функцию, которую бы вы вызвали. В данном случае нет функции, которую бы вы вызвали :)

    Буквально от вас хотели это:
    <?php
    
    function cutMenuTitle(array $elements, string $elementKey = 'item', int $limit = 15) {
         return array_map(function($item) use ($elementKey, $limit) {
            if (!isset($item[$elementKey])) {
                  return NULL;
            }
    
            if (mb_strlen($item[$elementKey]) > $limit) {
                return mb_substr($item[$elementKey], 0, $limit - 3) . '…';
            }
    
            return $item[$elementKey];
        }, $elements);
    }
    
    $menu = [
        ['item' => 'Заголовок1 длинный'],
        ['item' => 'Заголовок2'],
    ];
    
    var_dump(cutMenuTitle($menu, 'item', 13));
    // [
    //      "Заголовок1…",
    //      "Заголовок2",
    // ]
    Ответ написан
    1 комментарий
  • Каким паттерном можно заменить трейт?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    По вопросу: Есть такой паттерн, как "поведения", аналог mixin/trait — он не обозначен как паттерн в книгах, тк в книгах его нет.
    Эта реализация есть в фреймворке Yii2. Краткий обзор паттерна:
    https://rmcreative.ru/blog/post/mixin-v-php

    Но трейты лучше :)

    Если учесть, что вы хотите избавиться от наследования, то это не совсем к паттернам, а к принципам и архитектуре, к построению абстракций. А паттерны будут как следствие
    Ответ написан
  • Подобие next в генераторах PHP?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    <?php
    
    $generator = (function (): \Generator {
      yield 1;
      yield 2;
    })()
    
    var_dump($generator->current());
    $generator->next();
    var_dump($generator->current());

    sandbox.onlinephpfunctions.com/code/f1fad691895685...

    https://www.php.net/manual/ru/class.generator.php
    Ответ написан
    Комментировать
  • Php! или === null?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    if($error === null){} предпочтительный (иногда он обязательный по стандартам), тк вариант if(!$error){} — это авто-приведение к bool:

    • например 0 приведется к false, в то время как он может быть валидным интовым значением
    • пустой массив — тоже может быть валидным значением, но с !$arr он приведется к false... конечно допускать в программе одновременно и [] и null стоит как можно реже, но такое может произойти


    !$any можно использовать комфортно, когда вы (и программа) уверена, что работает либо с объектом, либо с bool
    Ответ написан
    Комментировать
  • Практическое руководство к написанию тестов?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Так как проект боевой, то придется делать не так как в учебниках:
    - Сначала нужно будет писать приемочные/функциональные тесты (тк юнит-тесты скорее всего будут ни к месту, да и покрывать все кейсы будет долго) на критичные места и те, на которые дотянетесь
    - Новые фичи должны максимально возможно покрыты юнитами, и по пути старые кейсы, которых затрагивают изменения. Там где не возможны юниты -- делайте функциональные
    - Старайтесь делать тесты на места, которые меняются
    - Помогайте тестам: пишите код с declare(strict_types=1), чтобы сам код отчасти себя тестировал и был уверен в аргументах, пишите код проще и надежнее

    Чисто практические аспекты -- ну тут нюансов много, целые учебники

    Отдельно:
    - Возьмите Codeception к примеру как тестовый фулл-стек фреймворк
    - Учитесь, у меня например :) (в личку)
    Ответ написан
    Комментировать
  • Как работать с несколькими исключениями?

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

    В первом случае исключения можно объединить одним интерфейсом (или родительским исключением). И на этом обобщенном исключении завязываться в другом слое.
    Пример

    class FirstException extend ModuleException {}
    class SecondException extend ModuleException {}
    
    class ModuleFirst 
    {
         public function work()
         {
              throw new FirstException('first);
         }
    }
    class ModuleSecond 
    {
         public function work()
         {
              throw new SecondException('second);
         }
    }

    тогда дальше ловить из слоя можно так
    try {
        $module->work();
    } catch(ModuleException $e) {
         // обработка
    }


    Если взаимодействие между слоями, то оборачивать каждый раз и выбрасывать свое дальше.
    Пример

    class FirstException {}
    class SecondException {}
    
    class ModuleFirst 
    {
         public function work()
         {
             try {
                $module2->work();
            } catch(SecondException $e) {
                 throw new FirstException($e->getMessage, 0, $e);
            }
         }
    }
    
    class ModuleSecond 
    {
         public function work()
         {
              throw new SecondException('second);
         }
    }


    try {
         $module1->work();
    } catch(FirstException $e) {
        // обработка
    }

    Ответ написан
  • Как сделать рандом с процентами?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Можно для каждого значения сделать вес, можете сделать так, чтобы сумма все х весов была 100,
    но не обязательно — это могут быть произвольные значения. Главное, что доля веса от суммы всех весов — будет вероятностью данного элемента.

    Такой способ делает ваши значения управляемыми буквально одним параметром веса.
    И не нужно шатать код.

    Обновление от 13.06.2020:
    Так вышло, что мой тимлид математик и нашел интересное и эффективное решение этой задачи
    <?php
    
    $values = [
        ['value' => 'One', 'weight' => 20],
        ['value' => 'Two', 'weight' => 30],
        ['value' => 'Three', 'weight' => 50]
    ];
    
    function randByWeight(array $arr) {
        $max = 0;
        $result = [];
        foreach($arr as $value) {
            $rand = pow((mt_rand() / (mt_getrandmax() + 1)), 1/$value['weight']);
            if ($rand > $max) {
                $max = $rand;
                $result = $value;
            }
        } 
    
        return $result;
    }
    
    // Например: 'Three' выпадет в 50% случаев, тк его вес -- половина от суммы всех весов
    // Например: 'Two' выпадет в 30% случаев, тк его вес -- 30% от суммы всех весов
    var_dump(randByWeight($values));

    sandbox.onlinephpfunctions.com/code/39de2d54a298de...

    Чем хорошо это решение — его можно использовать прямо в SQL запросе!
    SELECT * FROM table ORDER BY POWER(random(), 1/weight) DESC LIMIT 1


    Классическое решение:
    <?php
    
    $values = [
        ['value' => 'One', 'weight' => 20],
        ['value' => 'Two', 'weight' => 30],
        ['value' => 'Three', 'weight' => 50]
    ];
    
    function randByWeight(array $items) {
        $sum = array_reduce($items, function(int $acc, array $item): int {
            return $acc += $item['weight'];
        }, 0);
        
        $rand = (mt_rand() / (mt_getrandmax() + 1)) * $sum;
    
        foreach($items as $item) {
            if($rand < $item['weight']) {
                return $item;
            }
            
            $rand -= $item['weight'];
        }
    }
    
    // Например: 'Three' выпадет в 50% случаев, тк его вес -- половина от суммы всех весов
    // Например: 'Two' выпадет в 30% случаев, тк его вес -- 30% от суммы всех весов
    var_dump(randByWeight($values));

    sandbox.onlinephpfunctions.com/code/aee9dca651213e...
    Ответ написан
    3 комментария
  • Как клонировать обьект?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Ваш пример скорее сего в методе copy1(), но вдруг и другие понадобятся:
    <?php
    
    class Any
    {
        private $a;
        
        public function __construct(string $a)
        {
            $this->a = $a;
        }
        
       // Простое клонирование 1 в 1
        public function copy1(): self
        {
            return clone $this;
        }
        
        // Для примера, как сделать похожий объект на основе состояния текущего объекта
        public function copy2(): self
        {
            $new = new self($this->a);
            // тут делаем еще что-либо
            // $new->a тут доступна напрямую
    
            return $new;
        }
        
        // Меняем состояние и возвращаем новый объект, иммутабельное поведение
        // хорошая практика
        public function withNewA(string $a): self
        {
            $new = clone $this;
            $new->a = $a;
    
            return $new;
        }
    }
    
    $any = new Any("A");
    
    var_dump($any === $any->copy1());  // false
    var_dump($any === $any->copy2());  // false
    var_dump($any === $any->withNewA('B'));  // false, изменили объект, но получили новый, а не измененный старый

    sandbox.onlinephpfunctions.com/code/30bb3f1fb842ef...
    Ответ написан
    1 комментарий
  • Вывод вложенного массива из многомерного массива?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    В самом низу написал — зачем такой код писать не нужно...

    Готовый код под вашу задачу:
    <?php
    
    $input = '{"success":true,"data":[{"sport_key":"soccer_korea_kleague1","sport_nice":"K League 1","teams":["Jeonbuk Hyundai Motors","Suwon Samsung Bluewings"],"commence_time":1588932000,"home_team":"Jeonbuk Hyundai Motors","sites":[{"site_key":"unibet","site_nice":"Unibet","last_update":1588281960,"odds":{"h2h":[1.75,3.85,3.7]}},{"site_key":"sport888","site_nice":"888sport","last_update":1588282210,"odds":{"h2h":[1.74,3.8,3.65]}},{"site_key":"onexbet","site_nice":"1xBet","last_update":1588282134,"odds":{"h2h":[1.87,3.96,3.5]}},{"site_key":"betfair","site_nice":"Betfair","last_update":1588281947,"odds":{"h2h":[1.68,1.28,1.25],"h2h_lay":[85.0,85.0,85.0]}},{"site_key":"paddypower","site_nice":"Paddy Power","last_update":1588281989,"odds":{"h2h":[1.67,4.2,3.6]}}],"sites_count":5},{"sport_key":"soccer_korea_kleague1","sport_nice":"K League 1","teams":["Sangju Sangmu FC","Ulsan Hyundai FC"],"commence_time":1589000400,"home_team":"Ulsan Hyundai FC","sites":[{"site_key":"unibet","site_nice":"Unibet","last_update":1588281960,"odds":{"h2h":[3.85,1.88,3.3]}},{"site_key":"sport888","site_nice":"888sport","last_update":1588282210,"odds":{"h2h":[3.8,1.85,3.25]}},{"site_key":"onexbet","site_nice":"1xBet","last_update":1588282134,"odds":{"h2h":[3.76,1.92,3.5]}}],"sites_count":3},{"sport_key":"soccer_korea_kleague1","sport_nice":"K League 1","teams":["Daegu FC","Incheon United"],"commence_time":1589009400,"home_team":"Incheon United","sites":[{"site_key":"unibet","site_nice":"Unibet","last_update":1588281960,"odds":{"h2h":[2.23,2.95,3.2]}},{"site_key":"sport888","site_nice":"888sport","last_update":1588282210,"odds":{"h2h":[2.2,2.9,3.2]}},{"site_key":"onexbet","site_nice":"1xBet","last_update":1588282134,"odds":{"h2h":[2.28,2.98,3.3]}}],"sites_count":3},{"sport_key":"soccer_korea_kleague1","sport_nice":"K League 1","teams":["Sangju Sangmu FC","Seongnam FC"],"commence_time":1589018400,"home_team":"Sangju Sangmu FC","sites":[{"site_key":"unibet","site_nice":"Unibet","last_update":1588281960,"odds":{"h2h":[2.38,2.75,3.2]}},{"site_key":"sport888","site_nice":"888sport","last_update":1588282210,"odds":{"h2h":[2.35,2.7,3.15]}}],"sites_count":2},{"sport_key":"soccer_korea_kleague1","sport_nice":"K League 1","teams":["FC Seoul","Gangwon FC"],"commence_time":1589095800,"home_team":"Gangwon FC","sites":[{"site_key":"unibet","site_nice":"Unibet","last_update":1588281960,"odds":{"h2h":[2.38,2.75,3.2]}},{"site_key":"sport888","site_nice":"888sport","last_update":1588282210,"odds":{"h2h":[2.35,2.7,3.15]}},{"site_key":"onexbet","site_nice":"1xBet","last_update":1588282134,"odds":{"h2h":[2.42,2.84,3.2]}}],"sites_count":3}]}';
    $data = json_decode($input, true);
    
    function grabBookerData(array $data, string $bookerName): array {
        return array_map(function(array $item) use ($bookerName) {
            // Собираем результирующий объект события
            $event             = new \stdClass();
            $event->sport_nice = $item['sport_nice'] ?? null;
            $event->teams      = $item['teams'] ?? [];
        
            // Из данных по разным букмекерам оставляем h2h, который нам нужен
            $bookersData = array_column($item['sites'], null, 'site_key');
            $event->h2h  = $bookersData[$bookerName]['odds']['h2h'] ?? [];
            
            return $event;
        }, $data['data'] ?? []);
    }
    
    var_dump(grabBookerData($data, 'unibet')); // итоговый набор данных

    sandbox.onlinephpfunctions.com/code/6cb94c1070692d...

    Зачем не нужно писать такой код:
    Такого плана данные лучше пропускать через сериалайзер, а потом просто фильтрануть в результирующих объектах (или просто достать) нужные данные.
    Плюс использования сериалайзеров — не нужно писать код выше, а нужно только писать свои объекты в уже нужном для вас формате. И потом данные маппить на эти объекты через сериалайзеры, тем самым не заплетаться в исходных данных.

    Сериалайзеры на PHP:
    Fractal (рекомендую)
    Symfony Serializer
    Zend Serializer
    JMS Serializer — его многие ругают за неторопливость и монструозность, но он довольно функциональный и забирает на себя кучу телодвижений, я бы делал с ним. Он становится небольшой проблемой, когда десятки и тысячи операций идут за некоторое непозволительное число серверов/времени.
    Ответ написан
    5 комментариев
  • PHPUNIT: Как возвращать разные значения в методе мока?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Так точно работает:
    $this->anyMock
        ->expects($this->exactly(4))
        ->method('doSomething')
        ->withConsecutive(...$args)
        ->willReturnOnConsecutiveCalls(...$results)

    Кол-во $args и $results и цифра в $this->exactly(4) одинаково,
    $args - массив массивов


    UPD: Сейчас глянул код, по идее ваш способ абсолютно эквивалентный и должен работать, либо приведенный мной будет ругаться также. переменные $response1 и $response2 точно имплементят ResponseInterface?

    Причина проблемы:
    Вы просто настраиваете мок на одно число вызовов, а в ассертах вызываете меньшее число раз (в примере -- настроили на 4 ответа, а ассертов всего 3, то есть 1 остался заряженный) и в моке остается значение, которое потом вызывается каким-либо образом в других ситцациях!
    Классический сайд эффект. Нужно после использованного мока удостовериться, что он пустой или в новом тесте заново его создать, скорее всего у вас мок в приватной переменной класса и потому состояние шарится между методами теста...

    Именно поэтому вам expects() скорее всего и подсказала, в чем дело.

    Решения:
    1. Создавать мок именно в методе, не злобоупотреблять setUp()
    2. Контроллировать число вызовов в методе и аргументов для Consecutive Calls, в этом поможет expects($this->exactly(N))
    Ответ написан
    2 комментария
  • Каким образом получить новый массив из текущего?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    1. Сгруппировать товары по catID и категории также:
    $products = [];  // ваш массив с товарами
    
    /** 
     *  Группирует любой массив  массивов по любому полю $indexKey в качестве индекса
     */
    function groupBy(array $input, string $indexKey): array
    {
        return array_reduce($input,  function($res, $data) use ($indexKey){
            if (!isset($data[$indexKey])) {
                 return $res;
            }
            
            $res[$data[$indexKey]][] = $data;
                
            return $res;
        }, []);
    }
    
    $groupedProducts = groupBy($products, 'catID');
    // такое работает при условии, что категория с одним id будет иметь последнее название из найденных, 
    // то есть если id один, а название разные, то возможны не ожидаемые результаты
    $categories = array_column($products, 'catName', 'catID');


    2. Потом просто идете в цикле и выводите:
    foreach($groupedProducts as $catId => $catProducts) {
         // тут доступно название категории $categories[$catId] и ее $catId
    
         foreach($catProducts as $product) {
              // тут выводим сами товары $product
         }
    }


    Для PHP 5 нужно [] заменить на array(), предлагаю это сделать самому :)
    Ответ написан
    7 комментариев
  • Как правильно отрендерить шаблон из строки?

    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 комментария
  • Для чего так делают?

    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;
    Ответ написан
  • Как замокать метод 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
    Ответ написан
    Комментировать