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

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Попытка не пытка.

    1. Пагинация (постраничная навигация) НЕ НАСЛЕДУЕТ соединение, а ИСПОЛЬЗУЕТ его. наследовать = быть КОПИЕЙ С ИЗМЕНЕНИЯМИ, использовать = требовать в коде (ЗАВИСЕТЬ). В ООП можно писать "extends" (наследовать), а можно пробрасывать через конструктор (зависимости). То что вы сделали - это использование "наследования" для "зависимости", оно к вам завтра вернется, когда что-то захотите ещё сюда всунуть, как будете второе наследование делать?

    2. Размышляйте так. Чтобы сделать навигацию вам нужно:
    1 - имя таблицы
    2 - число страниц
    3 - отступ, чтобы переходить на вторую страницу
    4 - (необязательно) SELECT COUNT результатов, чтобы показать сколько всего страниц
    5 - фильтр, чтобы скрыть ненужные результаты (самый прикол в том, что фильтр универсальный сделать не выйдет, т.к. придется написать SQL заново, он весь про фильтры)
    6 - сортировка, чтобы выдать в нужном порядке (бывает несколько-ступенчатая сортировка "сначала-по-подгруппе-потом-по-цене", но её никто никогда не делает, лень правит мир)

    Вы здесь крышей упадете писать одну функцию, которая делает это всё.

    К человекам здесь пришло понимание, что нужно передавать "недоделанный" запрос из функции в функцию, дописывая в него кусочек. Для удобства запрос оборачивают в объект, которые называют Query.

    Если вы пытаетесь сделать на чистом SQL запросы - вам придется очень многое изучить. Поэтому как решение я предлагаю такую последовательность действий:
    1 - Изучаете, что такое composer, ставите в ваш проект какой-нибудь пакет (пока любой, например, symfony/var-dumper), добиваетесь что работает функция dd() (этот пакет её содержит, вам не нужно функцию создавать) в вашем коде. На самом деле там один консольный вызов и одна строка в коде, там нет "методики на 40 действий"
    2 - ставите пакет illuminate/database
    3 - гуглите статью "eloquent without laravel"
    4 - создаете модельку под вашу таблицу
    5 - на модельке вызываете метод ->paginate(), где его для вас заботливо написали
    6 - и только здесь начинаете изучать детали, которые хотели выучить сразу, чтобы сделать не просто пагинацию, а фильтруемую и сортируемую пагинацию (если оно надо). Скорее всего у вас появятся классы FilterBuilder, SorterBuilder, PaginationBuilder и Pagination, в которой будет метод вроде ->paginate($query, $paginationBuilder)
    7 - что такое паттерн "строитель" (Builder) читать на https://refactoring.guru

    У вас долгий путь впереди. Если вы делаете задачу впервые, возможно, вы ушатаете неделю или полторы.

    3. Кое-что о запросах. Пагинация с OFFSET имеет смысл только в очень простых запросах. На практике запросы имеют UNION, JOIN и "subquery", чтобы собирать данные, и кое-где работать не будет. Рекомендуется либо сразу оттолкнуться от (id > ?) вместо OFFSET (а заодно охренеть от того, что "айди больше" + "сортировка" опять не пашут, то есть вам по сути надо сделать "rowNum > ?", но почти все СУБД не дают такого параметра и придется КЕШИРОВАТЬ сделав запрос первый раз тяжелый, а следующий раз выбирать номер результата в кеше), либо делать велосипед с выгрузкой результатов в файл, например, и потом с помощью условного $splFileObject->fseek() выбирать прямым указанием номер первой строки и число результатов после (сложнее, веселая задачка потренироваться, на практике - создает больше гемора, чем пользы)
    Ответ написан
  • Как запустить файл.py с помощью php?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Попробуйте proc_open() вместо shell_exec(), и не забывайте прокидывать в него $env и $cwd. У вас хоть поток stderr будет, откуда можно почитать что там сломалось.

    Посмотрите, как реализовано, но я там пока с логгерами натупил, выключить нельзя, так что скопируйте код.
    https://github.com/6562680/support/blob/main/src/X...
    https://github.com/6562680/support/blob/main/src/X...
    Ответ написан
  • CQRS/ES (или это EventDriven вообще) - Кто поможет разобраться с терминами?

    gzhegow
    @gzhegow Автор вопроса
    aka "ОбнимиБизнесмена"
    Вопрос все еще открыт.

    Из того к чему пришел опрашивая несколько человек (среди них два архитектора, два сисадмина и два программиста):

    - в идеи нет никакой магии. простейшая реализация - закидываем код проекта в github, запускаем его на двух машинах вместо одной, просто вместо http-роутера мы используем message-брокер + eventbus, и соотственно каждая из машин обслуживает свои роуты. придумывать "ивенты" которые продвигают рабочий процесс, где один ивент является началом другого рабочего процесса - сулит головняк, такой же как если бы мы в пхп не использовали классы-сервисы, а вообще всё и всегда делали на ивентах. задолбались бы и запутались кто начало кто конец.

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

    - брокеры сообщений. краткий обзор их такой - они работают всегда в одном из трех режимов. первый - сообщение:
    1) "точка-точка" - "я отослал, ты придешь - получишь".
    2) "трансляция" - я вещаю, кто хочет слушает, кто не пришел - пропустил
    3) "трансляция-точка" - я вещаю, все слушают, кто не пришел - получит когда подключится.
    Команды работают в режиме "трансляция-точка", ивенты "трансляция", уведомления "точка-точка".
    Если бы мы делали хранение истории транзакций - то здесь ивенты должны работать в "трансляция-точка", ведь нам нельзя потерять ни один из них, их надо в постоянный ящик сохранить. Ещё в брокерах есть понятие "подтверждение". Это про надежность. После получения сообщения если выставить режим работы "нужно подтверждать" - непрочитанное удаляется только тогда, когда его подтвердили, иначе запихивается обратно в канал, причем не "следующим", а по временной марке на свое место. Это нужно там где есть "входящая точка", и да, если команда вернула "готово" или "невозможно" - сообщение подтверждается, если команда вернула "перебой связи" и "что-то пошло не так" - сообщение не подтверждается.

    - параллелизация. пока не понятно, какие проблемы вызовет параллель всего, попробую методом тыка. Когда мы использовали роутер - параллелизацией занимался nginx/fpm запуская десяток процессов. И если "обновить заказ" запускалось раньше чем "создать его", мы справедливо получали ошибку мол "невозможно" или "заказ не существует". Когда у нас есть брокер - то параллельно будут созданы "джобы" на первой машине, и их нужно резолвить на второй. Если мы дальше синхронно будем выполнять команды - это будет дольше, чем раньше, пользователей много, а поток всего один. Нам нужно их выполнять точно так же параллельно, только без nginx, а вручную, используя "ext-pcntl" для unix или "composer require react/async" для windows.

    - не путать event-sourcing и event-driven-design (что я и сделал в начале топика). ивент сорсинг в широких кругах известен как "история транзакций". Что получается, когда мы все приложение делаем в режиме "история транзакций" - получается биткоин. Кто-то миллионер, а вся планета тратит электричество на расчет его данных и мощностей все равно не хватает. у вас есть своя планета с людьми и компьютерами? думаю, пока нет.

    - в ивент-драйвен-дизайне очень легко посчитать, что весь бизнес процесс должен состоять из цепочки ивентов, которые запускают маленькие скрипты, и контроллирующей системы, которая знает в каком порядке команда ждет эти ивенты. но в этот самый момент мы уничтожили преимущества ивент-драйвен - мы создали точку, которая "знает", узкое место, сломав которое - всё ляжет. мы начинали EDD потому что одна машина была точкой, которая лагает и всё лежит. создав полный ивент драйвен - мы снова сделаем такую точку, и тут же себя зароем. ивенты стоит использовать как уведомление, а не как двигатель прогресса. а прогресс двигают все те же обычные скрипты, которые висят. идея только в том, что они висят уже не на машине-фронтенде, а на одном из бекендов, в виде воркеров, а фронтенд летает быстро, только не умеет выполнять задачи, но умеет сказать "задача сейчас выполняется или нет". Точно так же как работает компьютер - северный мост работает с видеокартой, южный - с задачами, просто потому что видео должно обновляться так часто как возможно.

    Поправляйте кто знает.
    Ответ написан
    Комментировать
  • Как выполнять SOAP запросы в нескольких параллельных потоках?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Если SOAP запрос не удастся сделать через Curl, сделав все заголовки ручками, то установите "composer require react/async".

    <?php
    
    $loop = Loop::get();
    
    $limitThreads = 3;
    
    $soaps = [
      // ...
    ];
    
    $tasks = [];
    
    $limit = $limitThreads;
    while ($limit--) {
       $soap = array_shift($soaps);
    
       $tasks[] = function () use ($soap) {
         // do soap request...
         return $result = doSoapRequest($soap);
       };
    }
    
    if ($tasks) {
      $results = await(parallel($tasks));
    
      foreach ($results as $result) {
        var_dump($result);
      }
    }
    
    $loop->run();
    Ответ написан
    Комментировать
  • Правильно ли рендерить вёрстку на сервере?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Позвольте поправить.

    Это не "правильно", это "возможно".

    Использование API уменьшает число данных в одном запросе, но заставляет вас делать авторизацию, делать всевозможные защиты. Если у вас есть фронтенд, который так и так делает запросы асинхронные (fetch/$.ajax), то разумнее конечно возвращать данные.

    С другой стороны, если ваш фронтенд не умеет в виртуальное дом-дерево (у вас не фреймворк, а нативный яваскрипт) - то при первой отдаче страницы в конце html вы можете отправить
    <template id="mytemplate"><div>{{data}}</div></template>
    , чтобы его схватить яваскриптом и генерировать верстку на основе него.

    Делать апи запрос, который возвращает html - это возможно, но это не то что "неправильно", это вы соединяете две задачи, каждая из которых может в будущем быть дополнена, в одну - и усложняете доработку в итоге. Если они по отдельности - доделать это проще. Апи запрос может быть использован где-то еще, если с версткой - то нет. Верстка же может быть изменена под новую тему оформления, если вместе - то сложнее.

    Считается, что доделывать должно быть легко, поэтому это "неправильно".

    Но вернуть верстку первым запросом когда человек только на сайт вошел - это нормальная практика и так было, пока было мало фронтенд фреймворков, в которых верстка вшивается в код самого фронтенда. Пока люди использовали handlebars/jquery это было нормально и вполне работало, а главное - не требовался специалист, который знает конкретный фреймворк, т.к. верстку знает большинство.
    Ответ написан
    Комментировать
  • Какие используются практики для отладки кода в проекте, построенном на микросервисах в Docker контейнерах?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Если вас интересует неверная работа одного микросервиса, то точно так же как вы делали. Контейнеры так или иначе содержат веб-проект и умеют отвечать на запросы с морды/браузера/курла.

    Какие именно запросы делаеть - вам должны показать, т.к. это чьёто изобретение: что на вход, что на выход. Некоторые в этом вопросе очень-по-человечески халявят - они говорят "вот там документация есть, почитай". Вроде правильно говорят, только каждый по-своему её понимает, быть уверенным что есть "стандарт мысли" это глупость, все равно должен быть старший/ответственный/предыдущий у кого спрашивать.

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

    В компьютере много устройств. Если бы они напрямую друг с другом общались, мы бы никогда не добились такого развития цифровой техники. В компьютере есть ШИНА, и управляющее устройство, которое поочередно работает то с первым устройством, то со вторым, и когда они одновременно что-то хотят - они ставятся один за другим с проверкой "кто раньше и кто сейчас может".

    Таким образом дебажится лог запросов и ответов с "шины" (эта шина пишется хорошим сеньором, либо ставится и долго настраивается хорошим сеньором), чтобы понять кто когда кому чего спрашивал.

    Если в вашем проекте сервисы друг другу запросы шлют как хотят, что вы не можете это проследить и некому рассказать - трехмесячный отсчет до того момента, пока "вас тоже выгоняют на рынок" уже пошел. Это проект убийца. Владельцы его у кого-то купили, или им архитекторы насоветовали наставить полторы тысячи технологий потому что они "новые", а теперь никто не знает как это вообще включать. И они рандомом подбирают разрабов до тех пор пока кто-то не воскрикнет Эврика и всё сделает, тогда ему кинут купюрку и тоже выгонят. Как сказал Ротенберг - "у нас в стране много кулибиных, дайте бизнесу волю и они сами ЧТО-ТО-ТАМ сделают" (цитата).

    Если вы все же очень хотите сохранить это рабочее место (но дальше - только хуже) - вы можете либо дампать в браузере, делая прямые запросы, логику смотреть в бд, а если логика прям жесткая - подключите пакет Monolog\Logger в dev-dependencies ко всем проектам, и включите Handler который складывает все логи в канал с одним и тем же местом хранения, и потом смотрите в каком порядке логи идут (у вас как раз и получится логгер вместо шины, только он не дает никому комманд, а только пишет что за чем произошло, это тоже задача шины и её мощь)

    Берегите себя. Бизнес о вас не будет заботится. А в текущей обстановке с чрезвычайными мерами, когда даже священников просят втирать дичь - тем более.
    Ответ написан
    2 комментария
  • Как сделать логирование для многопоточного curl?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Почитать и подумать (желательно):

    https://github.com/6562680/support/blob/main/src/X...
    https://github.com/6562680/support/blob/main/src/X...

    Устанавливаем:

    composer require monolog/monolog
    composer require gzhegow/support:1.16.21


    Запускаем:

    require __DIR__ . '/vendor/autoload.php';
    
    
    // const
    define('__LOGS__', __DIR__ . '/logs');
    
    // vars
    $channelName = 'curl';
    
    // dirs
    if (! is_dir($var = __LOGS__)) mkdir($var, 0775, true); // на unix есть ограничение в /etc/php/fpm с помощью umask и listen/owner, и вместо 0775 может быть создано меньше полномочий
    
    // init
    $logger = new \Monolog\Logger($channelName, [
      new \Monolog\Handler\StreamHandler(__LOGS__ . '/monolog/' . date('Y-m-d') . '/' . date('H') . '_' . $channelName . '.log'), // в файл
      // new \Monolog\Handler\StreamHandler('php://stdout'), // для консоли
      // new \Monolog\Handler\StreamHandler('php://output'), // для браузера
      // куда-нибудь ещё...
    ]);
    
    $xcurl = \Gzhegow\Support\XCurl::getInstance();
    
    // data
    $urls = [
      // index => url
      'myIndex1' => 'https://google.com',
      0 => 'https://google.com',
    ];
    
    // action
    $curls = [];
    foreach ($urls as $index => $url) {
      // >>> логируем, что делаем запрос на внешний адрес
      $logger->notice('Fetching url: ' . $urls[ $index ]);
    
      $ch = curl_init($url);
      // curl_setopt($ch, CURLOPT_HEADER, 0); // и т.д.
    
      $curls[ $index ] = $ch;
    }
    
    $retry = null; // создаем пустую переменную, она по ссылке будет передавать состояние в каждом шаге цикла
    $retries = 3; // три повтора, условия выхода из бесконечного цикла
    foreach ($generator = $xcurl->walkmulti($curls, $retry) as $step => [ $index, $curl ]) {
      // $retry автоматически становится null в начале каждой проверки (так написал в walkmulti). То есть если проверок не сделать, $retry будет null и повторов не будет.
    
      // проверяем ошибку. добавь столько ЕСЛИ, сколько нужно. длина контента. время запроса. код ответа, всё в этот ЕСЛИ
      if (curl_errno($curl)) {
        // проверяем условие, после которого повторы делать не надо. например, у нас только три попытки
        if ($step >= $retries) {
          // >>> логируем, что больше не выйдет
          $logger->notice('Unable to retry url: ' . $urls[ $index ]);
    
          $retry = false; // false = больше не спрашивай про него, результат не сохраняй
          // $retry = null; // null = сохрани результат последнего и больше не спрашивай про него
          continue;
        
        } else {
          // >>> логируем что пошел повтор
          $logger->notice('Retrying url: ' . $urls[ $index ] . '. Retries left: ' . $retries - $step);
    
          $ch = curl_init($urls[ $index ]); // в curl нельзя повторно выполнить запрос по тому же ресурсу, собираем заново такой же как был, вероятно - с новой прокси
          // curl_setopt($ch, CURLOPT_HEADER, 0); // и т.д.
    
          $retry = $ch; // передаем по ссылке новый курл, который после всех проверок выполнится в пачке ещё раз
          continue;
        }
      }
    }
    
    $results = $generator->getReturn();
    
    var_dump($results); // [ 'myIndex1' => <content>, 0 => <content> ]
    Ответ написан
    Комментировать
  • Как указать Access-Control-Allow-Origin в php?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    CORS защищает от запроса не со стороннего сайта (сервера), а со стороннего клиента (браузера).

    С помощью CORS мы даем команду "проверь, что ajax запрос был запущен со страницы, которую мы отправили клиенту", т.к. заголовок origin при ajax запросе будет равен домену, на странице которого находился браузер, когда запрос делал. Этот origin в итоге и проверяется на соответствие разрешенному.

    То есть он защищает от того, чтобы кто-то на своем сайте не разместил javascript код, который запрашивает данные напрямую с вашего сайта и показывает их на том сайте, как будто данные принадлежали не вам, а другому сайту.

    Если вы хотите запретить запрос с другого сайта (сервера), это сделать геморойно и простейшая для реализации защита - это капча на случай если другой сервер делает слишком много запросов = условная проверка IP адреса, который сервер отсылает (но его можно подменить используя прокси, поэтому читай дальше)

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

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

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

    Полная защита может быть так сделана - все запросы логики разрешены только аяксом и стоят корсы, что только с вашего сайта. А все не-ajax и не-GET запросы проверяются на список разрешенных IP адресов. Но даже тут заголовки можно сгенерировать так "как будто это ajax запрос" и все равно пробить.
    Ответ написан
    Комментировать
  • Как соединить две модели в коллекции?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Коллекция это про пачку.

    Создав класс коллекции компаний и запихнув туда контакты ты сказал примерно так "несколько компаний имеют общие контакты в рамках этой пачки компаний".

    Иногда это имеет смысл, но это такие редкие случаи, что так не надо делать. "независимость" модели это про другое, это про то что контакты являются частью одной задачи из ТЗ, а компании - другой. Но это не мешает одним иметь другое в подчинении, если в тз написано что компания имеет контакты.

    Если ты решил сделать что контакты имеют не только компания или же разные компании могут иметь одни и те же контакты, то это новая таблица company_contact, где указаны все связки компаний с контактами, а не коллекция.
    Ответ написан
    3 комментария
  • Есть ли способ добавить свои методы не трогая модель в laravel?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Или так

    <?php
    
    namespace App\Model;
    
    use ВашПакет\Product;
    
    class ProductDecorator
    {
      protected $decorated;
    
      public function __construct(Product $product) {
        $this->decorated = $product;
      }
    
      public function __call($method, $args) { return call_user_func_array([ $this->decorated, $method ], $args); }
      public function __callStatic($method, $args) { return call_user_func_array([ static::class, $method ], $args); }
    
      public function newMethod(): void
      {
        echo "Hello World!";
      }
    }


    В первом случае модель на автомате получит другую таблицу (она из имени класса делается), придется ставить protected $table = 'table_name'; а как захочешь добавить префикс в БД идти искать где префиксы вписывать ибо имена статикой. А добавишь в конфиг базы префикс - придется использовать модели в миграциях, а там в основном строки.

    Во втором случае некоторые методы могут не работать из-за того что возвращают void например.

    Так что:

    <?php
    class ProductService
    {
      public function doSome(Product $product) : Product
      {
        $product->updated_at = new \DateTime('now');
        return $product;
      }
    }
    Ответ написан
    Комментировать
  • Можно ли через .htaccess сократить id get запроса сайт.ru/profile.php?id=2 до сайт.ru/id2?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Да, только оно будет читаться наоборот.

    "Если /id2 (или начинается на /id\d+) то перейти на страницу /profile.php?id=2 либо /profile/id/2"

    которая уже будет разбираться скриптом, как будто было profile.php?id=2
    Ответ написан
    3 комментария
  • Есть ли listIterator в php?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Как сказал Ипатьев, в php всё делается с помощью цикла foreach (да, здесь не только for и while), а чтобы получать курсор - у каждого списка или хешмапа (здесь для удобства массив это и то и другое, что закинешь внутрь тем и будет) - есть внутренний указатель который можно двигать функциями current()/prev()/next()/reset()/end().

    Итераторы есть, но ими пользуются реже. В основном чтобы обходить директории рекурсивно, или многоуровневые хешмапы с путём, для стеков и рекурсий, и конечно для генераторов.

    ArrayIterator() применяется почти никогда, если применяется то разве что для сведения нескольких "итерабельных" типов к единому способу обхода (чрезвычайно редкая задача), особенно когда в php появились интерфейсы \Traversable и проверка is_iterable(), позволяющая тупо передать что-угодно в foreach() чтобы его обойти.
    Ответ написан
    Комментировать
  • Неправильно работает округление, это баг?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    floor() - округление "отбрасывает дробную часть" при положительном аргументе, и округляет в меньшую сторону при отрицательном. То есть [ floor(-1.65/0.55) = -3 ], а [ floor(1.65/0.55) = 2 ]
    round() - округляет по математическим правилам
    ceil() - округляет в большую сторону
    Ответ написан
    1 комментарий
  • Нужно ли использовать interface?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Вы когда на работу устраиваетесь - заключаете контракт. Контракт описывает что вы должны уметь. Это вот интерфейс "на вас".
    Тогда как вы сами - это "класс", который что-то уже умеет.

    Если ваши классы ничего ЧТО ТРЕБУЕТСЯ ДРУГИМ не делают - интерфейс на них не обязателен.

    Интерфейс выполняет две задачи. Даёт классу назначение (имя в рамках задачи) и контроллирует, что класс что-то умеет.

    * Ещё он умеет константы хранить, но этим редко пользуются (а зря, интерфейс меняется от проекта-к-проекту, а реализация - от задачи-к-задаче, стало быть объявленные константы вполне себе могут быть в интерфейсе, т.к. реализацию могут удалить)

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

    Вторая функция применяется для проверки входных данных, когда вы в функции указываете тип данных для параметра, и программа упадет в ошибку, если тип данных неверный. Этот же принцип используется для организации ПОДМЕНЫ одного класса на другой. Если смотреть на вашу задачу (хотя здесь интерфейс не нужен) , то вместо того, чтобы на входе писать RurMoney вы сможете написать MoneyInterface, если вам не важно, какая валюта придет в функцию, но они все умеют в ваш ->getName()

    В некоторых программах есть классы-данные, в которых нет методов (либо есть только геттеры и сеттеры - это вот ваш случай). Эти классы называют ValueObject. Для них интерфейс писать не надо. Чаще всего для них даже несколько классов делать не надо, просто один класс с разными свойствами. Но вот если методы есть - то нужно сделать АБСТРАКТНЫЙ КЛАСС, от него наследовать конкретные классы, тогда на входе проверять не интерфейс, а абстрактный класс. Абстрактный класс объединяет в себе "действия по-умолчанию" и "проверку умения" (через абстрактные методы). Они похожи на те, что в вашей задаче - getName() есть у всех - отличный способ положить этот метод в абстракцию, и не писать интерфейс, т.к. у вас уже есть абстрактный класс.

    К чему мы приходим.
    Интерфейсы чаще всего применяются, когда мы делаем класс умеющий несколько разных действий, класс-сервис, и мы подразумеваем, что когда класс кому-то не понравится, он возможно захочет удалить ваш и написать свой. Поэтому мы на входе пишем интерфейс и пусть свой туда подсовывает изменив настройки контейнера.

    Необходимость интерфейса появляется тогда, когда другой класс начинает использовать методы вашего, но при этом вы считаете необходимым сделать возможным подмену вашего на полностью чужой. Если он должен быть не "полностью чужой", а только пару методов, то есть "вы требуете наследования" - можно обойтись абстрактным классом. Интефейс - когда речь заходит о "подмене", а не о "видах одного и того же".

    Конкретно вашу задачу лучше решать разобравшись как настраивать вот этот пакет:
    https://github.com/moneyphp/money
    Ответ написан
    6 комментариев
  • Как обработать ошибки curl php, если connect_time = 0?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Думаю, вы правильно мыслите, то есть надо просто добавить пару проверок

    Я тоже предпочитаю curl, а не Guzzle и им подобные.
    Моя проверка как правило так выглядит:

    // двойная инверсия как раз про "если хоть одно не выполнилось"
    if (! (! curl_errno($ch)
    && 200 === curl_info($ch, CURLINFO_HTTP_CODE)
    && strlen(curlmulti_get_content($ch)) // для wordpress вместо strlen будет empty() т.к. их апишка ещё цифру 0 возвращает, когда отработало, но плохо
    )) {
      // ... retry
    }


    И я оставил только CURLOPT_CONNECT_TIMEOUT, т.к. CURLOPT_TIMEOUT - это время на выполнение функции полностью (сломало меня, когда я много запросов запустил и поставил тайаут 5, так все курлы не успевали выполнится в мульти), вне зависимости от сети.

    Так же у меня тоже была проблема, что курл единожды использованный нельзя просто "запустить еще раз", надо клонировать, а ресурс - не клонируется. Решил её через свою обертку curlBlueprint куда я напихиваю опции, а потом создаю ресурсы при помощи широко-понятных методов get/post/put... в итоге запросить повторно для меня это $chNew = $cbp->{get|post}(curl_getinfo($chUsed, CURLINFO_EFFECTIVE_URL));

    https://github.com/6562680/support посмотрите реализацию XCurl.

    При этом единственное, что отличает "юзаный курл" от "не юзаного" - это total_time, который у свежего строго 0.0. Ну и да, код ошибки тоже у свежего 0.
    Ответ написан
    Комментировать
  • Как вызывать динамический метод класса как статический в PHP?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Тебе нравится концепция ларавельных фасадов, которые публичные методы статически вызывают.

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

    Способ ООПшный - это сделать второй класс куда скопировать все методы, но тело сделать так, чтобы в каждом методе вызывался "сиглтон" или брался класс из контейнера. Или так как Сергей delphinpro описал, тело не копируешь, но делаешь магию, в итоге один класс у тебя с пабликами, а второй вызывает через статики, но класса все равно в итоге два. В ларе тоже. Фасад у них обертка для исполнителя, а не сам исполнитель.

    Причина (но не проблема) этого - отсутствие в пхп понятия "перегрузка метода" которое позволяет в одном классе написать один и тот же метод 3-4 раза указав разный набор параметров и по числу переданных параметров будет выбрано что это есть. Это штука конечно модная, но хорошо, что её нет. Когда она есть код будет более странным и непредсказуемым при беглом чтении. В пхп решили это не делать. Вернее как сказать - понятие ввели, как написал Сергей delphinpro, но реализация идет через 4 разных метода, которые ты потом с помощью if() вручную определяешь.

    У меня так в библиотеке сделаны модули, я специально писал генератор фасадов, потому что у меня 20 классов, во всех методов около 10, и надо чтоб хочешь - фасадом как в ларе, хочешь иньекцией зависимости как предполагает паттерн инжектор, хочешь трейтом можно было подключать когда лень и побыстрому, хочешь - aware интерфейсом через сеттер как в симфони прямо контейнером. https://github.com/6562680/support

    Но "ООПшным" способом - это если хочеться разобраться "как". Если хочется решить проблему - бери статью о фасадах в ларе и делай по ней.

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

    Судя по твоему примеру с моделью ты пытаешься дать модели некое действие, потому что так подразумевает логика твоей программы, то есть ты условно хочешь научить модель Юзер создавать или там менять как-то другого/несколько/всех юзеров. Поздравляю, ты подошел к понятию "агрегата", который в книгах много где встречается. Но это слово упрощается до слова "класс-сервис", когда ты свой метод создаешь не в модели Юзер, а в другом классе, называешь его UserService, и потом его на входе конструктора ожидаешь, и он туда будет подброшен, и не надо будет статикой ничего делать.

    Но да, есть и тут подводный камень. Когда хочешь изнутри модели в методе вызвать, там же конструктор уже занят $attributes, надо наследовать, а потом создавать модельки уже не через new, а через контейнер, чтобы подбрасывалось - но это боль. Поэтому перейди к работе с классами сервисами. Если ты хочешь поменять модель - ты свою модель передаешь в метод сервиса параметром, где сервис над моделью делает действие и выдает измененную.
    Ответ написан
    4 комментария
  • Логические операторы PHP: and, or vs &&, ||?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Ответ написан
    Комментировать
  • Какой есть быстрый способ сравнить многомерный массив?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Для начала четыре миллиона говорит о том, что задача не разбита на куски. Это очень большой массив, и его надо делать в режиме "возьми 10тыщ, сделай, возьми еще 10тыщ, сделай". Редко, но бывается когда все 4 млн важны, но как правило можно по тому что сделал создавать "подсчитанный массив", где есть те данные, что ты обработал и которые влияют на ещё необработанное.

    Второе. array_search() точно делать не надо. Существенно дешевле просто обойти циклом, т.к. в этом случае все значения будут по ссылке и не будут сильно засирать память. array_search() так или иначе в каждом шаге обходит почти весь массив пока найдет что сказали. Проще делать
    foreach ($arr1 as $k => $v) {
      if (isset($arr2[ $i ]) || array_key_exists($i, $arr2)) {
        //...
      }
    }


    Третье для работы не с двухуровневым массивом, а с трех и более уровневым - надо делать на ссылках. У меня пару функций есть для этого, посмотрите реализации, если вам удастся их понять.

    https://github.com/6562680/support/blob/main/src/X...

    Для многоуровневых вы будете использовать walkeach(), get() и has()
    Для двухуровневого вам это всё не нужно, вы используете foreach в foreach.

    Четвертое. Задача бывает "выдать что они разные" а бывает "посчитать различия". Для первой foreach -> foreach сам бог велел. И как только нашли что разное - сразу break или break(2); и дальше не делаем. Для второй задачи сложнее, надо строить третий массив, а значит обходить всё и всегда.

    Пятое. Часто задача может быть упрощена до "сравнить списки". В этом случае

    // берем пачку, от которой скрипт не повиснет на час...
    while (array_splice($arr1, 0, 10000) as $arrA) {
      $list = array_column($arrA, 'column'); // и вот у нас одноуровневый список колонки, который нужно сравнивать с таким же одноуровневым из другого массива, что много проще делать.
    }


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

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Как я вижу тут два способа.

    Способ первый. Дерево есть дерево. Сначала строится дерево всего что может быть, потом контроллеры работают с деревом. Если нету в дереве - 404. Здесь будет работать механизм SubstituteBindings, когда ты на входе пишешь LiteraController::literaAction(Category $model), которая по твоей букве ищет категорию, у которой slug - твоя литера - и т.к. её нет - автоматически выбрасывается 404. Делать труднее, изменять труднее, зато потом получаешь все плюшки работы с деревом и возможностью по нему всячески бегать, подсчитывать суммы элементов в нем и так далее.

    Способ второй. Твой. Роут работает с любой литерой, а внутри роута pages.books.litera тянет всё что начинается на букву такую-то, и если ничего не вытянул принудительно кидает 404. Это нормальное абсолютно поведение, когда ты бросаешь 404 вручную, а не автоматически. Делать легче, менять легче, плюшек нет. Зато дешевле. Заказчик захочет изменить - надо ПЕРЕ-делывать и наращивать функционал, доделывать - не прокатит.
    Ответ написан
  • Как оптимизировать foreach или запустить параллельное выполнение?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Асинхронка - это далеко не всегда фоновые задачи. Хотя если считать любое приложение умеющее в "одновременно" - фоновой задачей - то да. Даже браузер параллельку делает, обращаясь к штуке под названием WebApis в каждом шаге отрисовочного event-loop если хоть что-то висит в ожидании на этом шаге.

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

    Это значит вы запрашиваете записи не по одной, а делаете функцию, которая очень любит foreach а данные хранятся не в переменной value, а в $values.

    В этом случае ваша логика уже представляет собой не действие от а до я, а сначала циклом сделать для всех А, потом циклом сделать Б, потом циклом сделать В. И вот на каком-то из этих циклов вы втыкаете туда multicurl/guzzle, чтобы самую длительную (но не самую тяжелую) выполнить сразу в 100 потоков и получить 100 ответов.

    В принципе авторы выше подсказали решение проще, ибо долго пояснять иное. Они сказали - чем переписывать логику, проще запустить 100 действий А-Я и для этого применить очереди, а знач их настроить, понять, поиграться, столкнуться с приколами и нежданчиками. По мне начать стоит с переписывания слабого места в этом скрипте на циклы, понять что можно "работать с пачками", но в компаниях это как правило не любят - переписывание - это всегда косяки, поэтому легче запустить уже написанное в поточном режиме (и получить серверные косяки, которые вас и добьют :( ), чем попробовать написать действие для "пачки" вместо "действия для одной"
    Ответ написан
    Комментировать