• Нужно ли использовать interface?

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

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

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

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

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

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

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

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

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

    Конкретно вашу задачу лучше решать разобравшись как настраивать вот этот пакет:
    https://github.com/moneyphp/money
    Ответ написан
    6 комментариев
  • Как работает этот рекурсивный алгоритм разложения на слагаемые?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Алгоритм и вправду написан "чудесно", поди разбери.

    Но логика такая.
    Берем цифру 5.
    Её можно получить как
    1 + 1 + 1 + 1 + 1
    2 + 1 + 1 + 1
    2 + 2 + 1
    3 + 1 + 1
    3 + 2
    4 + 1
    (остальные - повторы)

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

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

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Логотип не всегда нужно пережимать в действии "добавление чего-то". Так можно, но не всегда нужно. (Нужно когда у вас жесткое ограничение на место на диске и вам не хочется хранить гигантские двухметровые оригиналы на амазон, т.к. это платно будет за каждый мегабайт)

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

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

    Посмотрите пакет league/glide.
    Ответ написан
    4 комментария
  • Как обработать ошибки 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 комментария
  • Хелпер Laravel dd() возвращает ошибку 500?

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

    Запрос для апи должен вернуть заголовки CORS которые не могут быть возвращены, т.к. ты прервал программу с помощью dd(). Браузеры в итоге пишут вроде 500, а вкладки "контент" вообще нет. Причем если напрямую в браузер адрес апи вбить то ответ есть. Но есть же POST запросы, которые надо расширением делать заголовок на POST менять или писать "псевдо-пост" типа "если есть флаг $_GET['method']=POST то воспринимать как пост. Для дебага конечно, на проде нельзя.

    Чтобы их вернуть нужно "как-то" после dd() вернуть ещё респонс и отправить его. И это только пол-проблемы, т.к. запроса будет два. И заголовки нужно вернуть и в OPTIONS запросе (одни) и в обычном запросе (другие), на чем часто горят задницы многих разработчиков типа "задолбался с cors". Ну да, там и вправду творческое задание. Только лучше иметь установленный файрфокс, чтоб дебажить, вслепую быстро доводит до срыва.

    Один из способов - зайти в доку symfony/var-dumper, сделать маленький класс из двух методов и там с помощью varcloner вернуть вывод dd() в переменную. После этого вместо dd() вывести его результат через echo, и позволить запросу завершиться (но это не всегда возможно, если ты поймал критическую ошибку - в этом случае управление перейдет Exceptions\Handler.php (в ларе) или если скрипт очень длинный, тогда будешь ждать минуту каждый раз чтоб дебагнуть.

    Второй способ - вместо dd() бросать исключение и его обрабатывать в Handler.php, который формирует ответ-исключение (может делать, если режим сайта дебаг и тд.) там тоже можно преобразовать в другой вид. Но опять же хотя обработка исключений это стандартный процесс для большинства фреймворков - мне его задумка не очень нравится просто потому, что у нас есть контроллер с респонсами, а тут еще одна приблуда, которая до кучи еще и настроена по-своему во имя возможностей которыми почти никто не пользуется. Но тем не менее код выполнится уже на этапе response->send() а значит отправятся нужные заголовки, а потом выведется dd() который написано выводить если "исключение такое-то".

    Обычный трайкетч в файле index.php часто делает то же самое и более податлив к допилам, чем разбираться как это же делает "настроенный кем-то обработчик" который умеет два метода - render() и notify() который один для отрисовки, второй для отправки куда-то, создается ощущение, что "больше ничего нельзя", а код это все таки свобода мысли.

    Есть ещё совсем уж злой способ - register_shutdown_function(), где ты принудительно вернешь пустой респонс с 500 ошибкой, но вот вывод dd() придется прокидывать через костыль. И потом register_shutdown_function() особенно весело мирить с каким-то шаблонизатором типа Plates, Twig или Blade, которые по дефолту как правило просто не выводят исходник html, если поймают внутри ошибку тупо завершая ob_get_clean() вместо ob_get_flush(), в итоге эксепшен есть, а dd() нету, т.к. он был в хтмл, который остановился и очистился.

    Оба варианта не очень логичны и костыльны, зато позволяют потом в том же постмане или браузере видеть в апи не json, а нашу dd() шку. Там помучаться надо немного, но сильно сократит время на создание полного зеркала апи в постмане или сваггере и слежения за ним. Все-таки сказать человеку "нажми F12" против убить день на то, чтобы он выучил возможности сваггера, а потом ты их выучил, а потом ты их описал, потом понял ограничения - лишние действия под сомнительный итог "у нас документация по канонам". Все равно придется объяснять новым как что работает.
    Ответ написан
    2 комментария
  • Логические операторы PHP: and, or vs &&, ||?

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

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    В пхп асинхронной работы в коробке нету (но сделать её - можно). Это значит, что либо вы делаете два скрипта, причем один пишет "в файл" (а лучше другое быстрое хранилище) твой процент, а второй в нужный момент этот файл считывает и показывает тебе процент в текущий момент.

    Для понимания хорошо помогает ролик про javascript event-loop. Идея там в том, что любая задача делится на шаги, и в конце запускается бесконечный цикл, висящий на списке запланированного. Пока список не пустой, выполняется 1 шаг для каждой цепочки (то есть step1-step1-step1) и куда-то пишется прогресс. Потом цикл повторяется, выполняется второй шаг (step2-step2-step2). Важно здесь не ошибится и не сделать ненароком асинхронку, которая делает (step1-step2-step3, step1-step2-step3) это нарушит параллельность. Дальше разработчик решает - шаг это одна запись, или например 10 тыщ.

    Вот в том месте где вы пишете:
    echo $i . '-' . $progress . '%' . PHP_EOL;
    должна быть запись в какое-то место, в кеш или в редис.

    А другим скриптом вы это место потом выводите по первому требованию.

    Есть более сложная реализация, когда открывается сокет и все равно два скрипта - один делает задачу, второй подключается. Первый периодически пишет в сокет что-то вроде "progress:90", а второй, подключившись, получает сообщение и видит, что начинается со слова progress и выводит уже такое.

    Часто эту задачу решают, когда работают например с пережиманием одного большого файла. В этом случае нужно брать размер выходного файла, который в данный момент делается и делить размер на предполагаемый конечный размер. Этим кстати объясняются старые приколы windows, когда она писала 99% а потом долго долго висела, т.к. спрогнозировать сколько будет весить выходной файл бывает сложно.

    Что до конкретно вашего случая - не до конца понимаю зачем в таком цикле делать опросник прогресса выполнения. Вы решаете консольную задачу, выводите в консоли $total - $i, и будете видеть сколько осталось. Разве что у вас есть необходимость ещё кому-то это показать - то да, как написал выше.

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

    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 вручную, а не автоматически. Делать легче, менять легче, плюшек нет. Зато дешевле. Заказчик захочет изменить - надо ПЕРЕ-делывать и наращивать функционал, доделывать - не прокатит.
    Ответ написан
  • Почему не применяются настройки подключения в Laravel на лету?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Там есть приколы связанные с инициализацией и что каждая модель умеет в getConnection() который может быть дефолтный. Надо не конфиг менять, а в самой модельке втыкивать. Не спрашивай почему. Что у Тима в голове сложный вопрос...
    Ответ написан
    1 комментарий
  • Как оптимизировать foreach или запустить параллельное выполнение?

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

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

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

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

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

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Такой спор с мозговыносящими технологиями, аж помутнело всё.

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

    Простейший способ - взять пару букв того, пару букв этого и этого, слепить вместе. Получится группа. Группу (все записи разделенные переносом строки) положить в файл с именем "(парубукв_source)(парубукв_id)(парубукв_subid).txt". На сами группы запомнить по принципу "группа такая - знач файл такой". В вышеуказанном случае имя файла именно это и делает.

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

    И да, нет смысла делать индекс на 60 букв, потому что айдишник уникален. Получится 50 миллиардов записей и из них 50 миллиардов групп. Это не имеет смысла. Смысл в том чтобы 50 миллиардов оказались в 10 тысячах групп, и при поиске пришлось просмотреть не 50 миллиардов, а пару групп. Учитывая что ты работаешь с айдишниками, то скорее всего ты будешь вязать одну запись, то есть группа вообще будет одна.

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

    "Переиндексирование" - по одной или всё вместе. По одной - находим в группах, удаляем из групп. Все вместе - создаем соседнюю папку для index_2, делаем, удаляем старую папку, переименовываем новую на место старой. Так даже в elastic делается, там "переиндексирование" - это создание нового индекса и снос старого.

    Чем более твои айдишники уникальны, тем сложнее это группировать, тем меньше смысла от индекса. Возвращаемся к самой задаче. 200 тысяч в день записей появляется. Это какая машина в день сама столько делает? Если машин несколько, то машина (её номер) - тоже группа, уменьшит уже на порядок, но скорее всего нет, т.к. в условии поиска номер машины не будет важен. Если одна машина всё это делает, можно критерий какой-то добавить, а хоть бы "день когда добавилось", или "тип записи" (это всё имеет смысл, если данные все настолько уникальные, что прям их никак не сгруппировать). Если критерий не добавить вообще никак - знач как описал ранее - лепи пару букв из тех полей, по которым будешь искать.

    ps. есть вероятность, что "не используйте базу данных" имеется в виду "code first" - напишите на объектах, чтобы мы потом сами бд выбрали, а не сделайте "без бд".
    Ответ написан
    2 комментария
  • Как отсортировать массив по дате в формате от и до?

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

    $dates = [];
    for ($i = 0; $i < 10; $i++) {
        $dates[] = new DateTime('now +' . $i . 'days');
    }
    
    usort($dates,function ($a, $b) {
        return null
            ?? ( $b < $a ? -1 : null )
            ?? ( $b > $a ? 1 : null )
            ?? 0;
    });
    
    var_dump($dates);


    делает, что сказали.

    ====

    Если твоя задача сортировать массив по возрастанию РАЗНИЦЫ дат, то нужно так:

    $dates = [];
    for ($i = 0; $i < 10; $i++) {
        $dates[] = [
            new DateTime('now +' . $i . 'days'),
            new DateTime('now +' . $i+$i . 'days')
        ];
    }
    
    usort($dates,function ($a, $b) {
        $diffA = $a[1]->getTimestamp() - $a[0]->getTimestamp();
        $diffB = $b[1]->getTimestamp() - $b[0]->getTimestamp();
    	
        return null
            ?? ( $diffA < $diffB ? -1 : null )
            ?? ( $diffA > $diffB ? 1 : null )
            ?? 0;
    });
    
    var_dump($dates);


    Помнить, что на getTimestamp() влияет временная зона. То есть 2 часа по Гринвичу меньше чем 2 часа по Москве, это как бы понятно, просто здесь тоже это важно.

    ====

    Третий вариант, который "возможно" ты хотел получить здесь - как определить пересечение дат:

    # опционально, можно донастраивать с помощью знаков `<=` `>=`, чтоб получить "включая-исключая"
    # и оператора NOT, чтобы инвертировать.
    # можно играться с оператором OR вместо AND, но это уровень 2, тебе не надо
    # ещё есть кейз "вне моего промежутка", который можно получить AND двух имеющихся или NOT для "внутри промежутка"
    
    # дай промежутки, которые помещаются в мои рамки
    # '2022-01-02 00:00:00' < date_from < date_to < '2022-01-04 00:00:00'
    # |----------|
    #    |----|
    # AND `date_from` > '2022-01-02 00:00:00'
    # AND `date_to` < '2022-01-04 00:00:00'
    # вернуло бы [ '2022-01-03 00:00:00', '2022-01-03 00:00:00' ]
    
    # дай промежутки, которые пересекаются с моим только началом
    # '2022-01-02 00:00:00' < date_from < '2022-01-04 00:00:00' < date_to
    # |------|
    #     |------|
    # AND `date_from` > '2022-01-02 00:00:00'
    # AND `date_to` > '2022-01-04 00:00:00'
    # вернуло бы [ '2022-01-03 00:00:00', '2022-01-05 00:00:00' ]
    
    # дай промежутки, которые пересекаются с моим только концом
    # date_from < '2022-01-02 00:00:00' < date_to < '2022-01-04 00:00:00'
    #    |------|
    # |------|       
    # AND `date_from` < '2022-01-02 00:00:00'
    # AND `date_to` < '2022-01-04 00:00:00'
    # вернуло бы [ '2022-01-01 00:00:00', '2022-01-03 00:00:00' ]
    
    # дай промежутки, которые шире моих рамок (рамка внутри промежутка и потому пересечения нет, пересечение целиком)
    # date_from < '2022-01-02 00:00:00' < '2022-01-04 00:00:00' < date_to
    #    |----|
    # |----------|
    # AND `date_from` < '2022-01-02 00:00:00'
    # AND `date_to` > '2022-01-04 00:00:00'
    # вернуло бы [ '2022-01-01 00:00:00', '2022-01-05 00:00:00' ]


    Чаще всего задача звучит "найдите все пересечения сразу", чтобы убедится, что туда можно что-то еще добавить и оно не закосячит потом. Поэтому и начинают играться с OR чтобы не писать NOT ( AND / AND / AND / AND )

    Вот тут есть уровень 2 и 3 и тд.
    www.michurin.net/computer-science/boolean-logic.html
    Ответ написан
    1 комментарий
  • Почему не получаю Bearer токен?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Похоже, что CORS передают привет.

    Access-Control-Allow-Origin
    Access-Control-Allow-Headers
    Access-Control-Allow-Authorization

    Гуглим...

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

    ps. корсы проще всего тестировать в Firefox.
    Ответ написан
  • Как в Eloquent (или ActiveRecord) получить всех потомков n-ного уровня, в таблице с полями id и parent_id? Нужны ли воспомогательные таблицы?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Нужны ли воспомогательные таблицы?

    Да, желательно.

    Это вообще реально, или нужно raw-запросы писать на SQL?

    Чтобы было еще быстрее - да. А без дополнительной таблицы - тем более да.
    Вот как делать: https://www.percona.com/blog/2011/02/14/moving-sub...

    Ибо я полагаю, что если записей будет over 1M

    Индексы решат проблему с миллионом. А еще проблему миллиона решает взгляд на то, что если ты решил "вывести на страницу миллион" - то ты что-то не так обсудил с заказчиком. Человеку от 20 записей уже становится голове тяжело, а лям - это забей.

    Есть паттерн Closure Table (я рекомендую его), есть паттерн Nested Sets (для меня тяжелый в чтении, и средний в реализации) и есть паттерн Path Enumeration (самый простой при реализации, и без дополнительной таблицы, но в итоге может не всё) и опирается на WHERE LIKE %path/to/parent%.

    Все они реализуют дерево в таблице, позволяя избежать необходимости поддержки рекурсивных запросов, которая в MySQL есть только в более поздних версиях и есть в постгресе. Но честно, я когда разбирался в WITH RECURSIVE понял что с паттернами легче было.
    Ответ написан
    1 комментарий
  • Как получить название устройства при get php?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Вы зачем-то отбрасываете идею "передать с фронта fingerprint и сохранить его в сессию".
    На клиенте доступ к устройству получить легче, чем гадать на сервере.
    Ответ написан
    Комментировать
  • Зачем задавать приватный модификатор доступа для свойств класса?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Я когда-то задал тот же самый вопрос и нашел на него ответ.

    Для начала "private/protected/public" это больше история про наследование, чем про "доступ" или про "капсуляцию". Тут просто эти три слова несколькой обязанностей выполняют.

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

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

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

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

    Теперь про свойства.

    "Публичные" свойства как во всех языках нужны, чтобы просто к ним обращаться и вписывать туда значение. Плюсы как бы - обычная переменная, меньше кода писать. Минусы? Обьекты постоянны от функции к функции. Где-то на уровне 10м-20м кто-то всунет в этот обьект то, что ты бы не хотел там видеть и оно всунется, а ты потом офигеешь, что "а че, так можно было чтоли?" - да. То есть публичные свойства стоит использовать только в каких-то может одноразовых объектах, типа DTO/ValueObject паттерн, просто потому что от "защиты" там только число кода добавится, они просто как массивы только с именованными заранее полями. Удобно писать, редактор подсказки показывает. И все равно остается опасность того что выше написано, поэтому как бы предполагается что вот дто он такой, одноразовый, и пишут в него только в начале приложения, разбирая запрос на куски, запихнул что не надо - получай.

    "Защищенные/Приватные" свойства здесь выполняют почти ту же задачу, что и защищенные методы (про наследование). Дополнительная фишка - в защищенное свойство ты можешь методами написать КАК ИМЕННО ты кладешь туда данные или всобачить небольшую проверку. Не стоит увлекаться тысячей проверок - в коде утонешь, но проверку Типа - можно, почему нет. Раньше типизированных свойств не было, так это был вообще единственный способ убедится что в поле лежит нужного класса другой объект. Я вот раньше писал set()/get()/delete()/add()/put() и кучу всего пока не понял, что можно обобщить до двух set(?$items) и add($items), причем set() - вызовет add() и если туда null передать, то оно либо поставит по умолчанию либо очистит, то есть ещё и delete() выполняет когда нужно.

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

    Это не значит что есть норма "сколько", это значит что нужно понимать, что в свойствах большой обьем желательно не копить. Все поля имеющие "массив по-умолчанию" можно сделать методами, тогда вызов метода даст тебе данные в одной функции, а по завершению - их вычистит. А положить их в свойства и запомнить навсегда (это же удобно! а ещё быстро считывается!) - это печально.
    Ответ написан
    Комментировать
  • Как сделать так, чтобы когда урок начинался (наступало 8:00), то выводилось "Урок начался!", а когда заканчивался (8:40) - "Урок закончился!"?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    А шо не так, все прально напЫсано. Только интервалы почему-то нулевые, отрабатывают либо никогда либо каждый кадр...
    Ответ написан
  • Как отправить multipart/form-data в Nyholm\Psr7?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Эм, вы что-то напутали.

    'multipart/form-data' - это формат запроса. Вы от клиента присылаете его на сервер.

    Отдавать 'multipart/form-data' это как просить сервер заполнить форму на компьютере пользователя.

    Там нет формы.

    То есть сам паттерн Psr7 это не про "сделай запрос куда-то". Это "разбери $_GET/$_POST/$_FILES" так, чтобы получились стандартные ServerRequestInterface с которыми потом работает приложение. То есть "создать на сервере multipart/form-data $request" будет что-то примерно ::createFromGlobals() без параметров, и оно само его создаст, если вы отправите на сервер форму.

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

    Чтобы ваша задача стала реальностью её текст должен звучать примерно так: "Клиент с сайта отправляет слово `привет`, а я на сервере в этот момент подменяю слово на файл с именем `привет.txt` и делаю вид будто юзер отправил мне файл." Если вы хотите клиенту файл отдать - то это Content-Disposition: attachment, а не Content-Type: multipart/form-data.

    Если вы хотите отправить запрос - вам нужно использовать GuzzleHttp или curl, а не ServerRequestFactory, то есть Psr7 - это парсер входящего реквеста, а не генератор хттп запросов куда-то. Технически как-то можно их приравнять... но что получится вы уже видели.
    Ответ написан
    Комментировать