• Как реализовать хранение изображений отдельно от кода и запрос нужного размера на лету?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Это болючая задача которую мне приходится решать на каждом проекте.
    "Готовое" решение для симфони - liip imagine bundle, но пока все конфиги изучишь - неделя пройдет.

    Я обычно подключаю вручную:
    1. свой класс отвечающий за масштабирование + обрезку + смену формата на webp
    2. 2 маршрута API - один называю blueprint, второй image. Блюпринт выдает JSON-ом все возможные ссылки с предустановленными параметрами, а image - отдает картинку
    3. и оборачиваю это в phpleague flysystem, чтобы при случае переключиться на амазоновское, дропбоксовое или свое собственное хранилище, а изначально использовать родную файловую систему
    4. для обрезки подключаю gumlet, он может не идеален, но немного проще чем использовать встроенный gd или устанавливаемый imagick, где придется учить настройки, гумлет все-таки немного преднастроен по качеству и сжатию (под капотом все равно gd или imagick используется, просто обертка)

    Вот пример на тестовом сайте:
    /api/v1/uploads/image/2024/02/000029_1605538227_415880_big1.jpg?presets=group.all
    /uploads/image/size-original/2024/02/000029_1605538227_415880_big1.jpg
    /uploads/image/size-jpg2webp/2024/02/000029_1605538227_415880_big1.webp
    /uploads/image/size-jpg2webp-100-100/2024/02/000029_1605538227_415880_big1.webp

    65f2b0d786d48373675526.jpeg

    Но там много моментов, которые так просто не описать.
    1. Во первых сначала масштабирование. Тут придется думать по длинной стороне, короткой стороне, или по aspect ratio.
    2. Потом обрезка, если идеально привести не удалось. Отдельно придется подумать где точка центра - вверху-центре или в центре-центре.
    3. Потом после обрезки скорее всего ты получишь raw content изображения, чтобы его сохранить в файл нужно его поймать ob_start()/ob_get_clean(), и обеспечить чтобы входящая ссылка всегда приводила к созданию картинки с тем же путем, и если она уже есть - не тратила время а отдавала готовую. Опять же - если сделать "произвольный размер" - то твой сервер очень легко нагнуть, сделав мини-ддос, чтобы он сгенерировал все возможные сочетания размеров от 0 до 2000 по X/Y, получится 4 миллиона картинок. Поэтому так или иначе надо предусматривать ключ или пароль, позволяющий это делать, чтобы снаружи не долбили ерундой.
    4. Потом форматы. Родное изображение было в .jpg, а итоговый хочется webp. Получается что итоговое изображение будет либо с двумя расширениями (и пхпшная функция pathinfo еще заставит попотеть), либо оригинальное разрешение будет получено из jpg2webp, а итоговое можно указать любое, но работать должно только если оно webp.

    В общем, гемор что надо тебя ждет. Но реализуемо.
    Ответ написан
    1 комментарий
  • Как можно установить время неответа на CURLOPT_WRITEFUNCTION?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Вы решаете задачу потокового получения данных разовым запросом к камере.

    Потоковое получение данных это когда ты используешь fsockopen() или socket_create() в этом случае ты "подписываешься" на камеру и ловишь весь траффик, который оттуда приходит. И это соединение само по себе сломается, если камера перестала отвечать.

    Под капотом оно работает как опросник на уровне операционной системы, который с некой частотой проверяет информацию в ячейке памяти.

    Так как вы делаете тоже можно, но ваш вызов надо заворачивать в

    while (true) {
      // > запрос (сам по себе займет несколько секунд, если повезет - меньше секунды, таймауты в этом случае нужны чтобы при проблемах сети, но когда все устройства работают - немножко подождать, секунду-полторы, а не 20)
    
      // где-то тут брать значение счетчика microtime(true) и писать функцию считающую разницу по вашим критериям, будь это 20 сек с последнего запроса или 20 сек с последних полученных данных
    
      usleep(1); // подождать минимум 1 микросекунду (1 кадр), позволив этому ядру проверить другие ожидающие задачи операционной системы, чтобы процессор не задымился от 100% нагрузки в случае если запрос перестанет происходить
      // usleep можно ставить и больше, например 1*1E5 или 100 000, это 100 мс, но в принципе операционке достаточно 1 кадр, она все равно вернется только после того как закончит проверку всех устройств, т.е. по факту пройдет не 1 мкс, а "минимум 1 мкс".
    }


    но правильно, это все таки подключится сокет соединением на порт камеры.

    И нет, этот скрипт не нужно потом запускать из браузера. Он запускается из командой строки/total_commander вызовом "php index.php", что приведет к запуску окошка в котором он работает. Из браузера что-то с while (true) запустишь и оно рано или поздно сляжет по php execution time.
    Ответ написан
  • CMS для небольшого веб-проекта?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Я бы сказал Вордпресс но соглашусь с тем что сначала нужно нанять инженера и спросить с чем он работал и пусть на этом и делает.

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

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

    А возьмешь фреймворк, где изначально ничего нет кроме инструментов - придется многое создавать с нуля из того что в ворпдрессе уже есть. Я бы сказал что мое решение это вп на админку а остальное на фреймворке, ну тут тоже есть проблемы, в моменте например авторизации. ВП всё хранит поначалу очень удобно, но далее эти таблицы usermeta/postmeta/termmeta становятся гигантскими и возникает необходимость поверх него подрубить свои и тут же сталкиваешься с проблемами уже другого порядка - как обеспечить чтобы в твоих таблицах и в вордпрессе все было синхронно, хотя самому проекту вообще это не нужно, но не сделав - будешь постоянно ловить исключения, что мол юзер есть, а его почему-то нет и так далее. Но он уже написан под вп и теперь это придется оплатить.
    Ответ написан
    Комментировать
  • При установке проекта laravel, захожу в package.json и там нет изначально laravel-mix, почему?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Они наигрались с миксом, теперь у них Витя. Такой же кривой, но еще и Витя. Vite это оно.
    Ответ написан
  • Как лучше реализовать возможность передать \Closure, чтобы запустить его не-блокирующим способом?

    gzhegow
    @gzhegow Автор вопроса
    aka "ОбнимиБизнесмена"
    Раз нет ответа отвечу сам. Видимо промиз. Он как раз и есть мост между параллелью и синхроном. И коль скоро деферед дает отложенный запуск и позволяет проверять состояние то пожалуй он лучше еще. Пора уже пср щикам написать интерфейс на дефер и промиз
    Ответ написан
  • Как запустить Gulp из Php?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Из PHP этого делать не нужно, вы все равно не получите потока данных, только поток отчетов.

    Напишите "npm init", и в файле package.json сделайте секцию "scripts", потом запускайте из консоли перед установкой проекта на новую машину "npm run my_script_that_runs_gulp".

    То есть проект оборачивают в контейнеры, чтобы это автоматизировать, а не заставляют язык программирования делать всё, тем более чтобы это запустить все равно придется открыть консоль и вызвать команду с php, или пройти по ссылке, чтобы веб сервер сделал это для вас.

    Способ есть, для этого надо искать в доке как выдать на выход поток самих данных из функций галпа, который все равно сделан на node, перенаправить их в stdout, и мы можем подписаться на поток данных зачем-то и что-то с ними делать, но обычно это не нужно, т.к. галп это и есть обертка для потоков на node js.
    Ответ написан
  • Middleware пример архитектуры для цикла запрос - контроллер - ответ, да?

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

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

    общая идея - вызывается метод контроллера, вокруг которого обернуто N мидлваров, каждый из которых что-то делает и либо останавливает цепь, либо вызывает ->next() чтобы передать следующему вплоть до самого действия
    Ответ написан
    4 комментария
  • Как получить данные с помощью Socialite Providers laravel?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Опять новое название из лары. Опять продвижение?

    Ну и вот ты вводишь в гугл. И на первой странице напЫсано
    https://laravel.com/docs/9.x/socialite#routing

    И там есть ->user() такой же как ->redirect()
    Ответ написан
  • Есть способ выдергивать JS обьекты/JSON из тегов script в HTML документе средствами PHP?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    да.
    1. $html = file_get_contents('https://bel.vkusvill.ru/shops/'); // получаем сорс
    2. preg_match_all('~' . preg_quote('<script>.+</script>', '/') . '~', $html); // ищем все теги script
    3. циклом проходимся, берем нужный.
    Ответ написан
  • Как удалять файлы с сервера по условию, что они не используются в текущий момент?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    А вот что выглядит вкусным
    https://github.com/jack-theripper/yandex

    Сначала я думал что это жесткий REST-api, в котором есть "дай список", "закачай", "отдай" и "удали". Но там лежит вкусная папка Stream.... где есть метод "считать несколько байт прямо с диска".

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

    Но я не проверял, работает ли это вообще, а с видео давно бы уже пора мне научиться работать.

    По сути это вытесняющий кеш и будет как сказал Ипатьев. Но это будет кеш который в моменте хранит только например 8килобайт на юзера.

    А если вы как и сказали - подключите сюда еще сокеты (не веб-сокеты, а просто сокеты, tcp например), то вы еще и предотвратите отдачу целого фильма в скрипте даже если пользователь перестал смотреть. Вы просто в шаге цикла while () сможете проверять - есть ли кому контент слать. И если некому то прекращаете чтение.
    Ответ написан
    1 комментарий
  • Как сделать кнопку которая загружала бы фотографии с пк в папки на сервере?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Простейшая реализация
    <form type="multipart/form-data">
    <input type="file" multiple="multiple" />
    <button type="submit" />
    </form>


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

    Задача лютой сложности для тех, кто все это не знает, хотя кажется "загрузить картинки".

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

    Есть мааааленькая вероятность, что ваш старый код уже работает и нужно только какую-то строку исправить. Проверяйте удачу!
    Ответ написан
    9 комментариев
  • Когда стоит начинать учить фреймворки?

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

    ООП, композер и пакеты - наш выбор. Дредноут где "всё включено" и без бутылки водки ничего нельзя отключить потому что "оно так настроено" - уже нет.

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

    Столкнуться с ними стоит. Но вот надеятся что автоматическая штука включающая всё что нужно - это решение - это этап развития, который пройти нужно, а потом забыть.
    Ответ написан
    2 комментария
  • Как задать условие "если url начинается с my.site/me/*, то..." в php?

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

    Помоему url()->is($regex) или $route->is($regex), гуглите
    Ответ написан
    Комментировать
  • Как лучше работать в postgresql и php с временными зонами?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    1) Сначала в консольке там есть timezone + dpkg-reconfigure, там гуглим как сделать UTC
    2) потом в приложении date_default_timezone_set() тоже ставим в UTC, и все новые даты (new \DateTime) у вас будут создаваться в UTC.
    3) не забываем, что таймзона есть и в большинстве СУБД. Это бы всё да тестировать как одно на другое влияет, дескать приводит или не приводит, но для начала можно тупо везде это выставить.

    * Я предполагаю что если передать со своей таймзоной в БД - оно сохранит верно, а вот если не передать ничего то пустая дата создастся в указанной таймзоне, что при запросе выдаст например 00.00.0000 00:00:00+02:00 вместо нуля по UTC, тоже касается любителей сравнивать с NOW() или проставлять по дефолту CURRENT_TIMESTAMP(), они скорее всего будут работать по настройке.

    На практике я не встречал тех, кто упарывается в таймзону и в UTC. Попробовал когда-то в это топить - дали по шапке "у нас так не принято в компании". Каво? Некоторые ставят принудительно Europe/Moscow только в пхп и думают что все сделали (ведь на сервере время вполне может быть например Amsterdam, и что самое интересное - таймзона при сборке пхп из исходников шьется вовнутрь и может быть вообще другая, и тут уже каша начинается, ты не знаешь и не можешь это исправить) и потом с этим долбятся, потому чтоб поменять в системе - надо контейнеры делать, а девопсом 90% из них не является, а контейнеры они понимают только на словах, а еще это надо просчитывать на что повлияет, а они боятся, и может даже правильно... Короче. Вам нужно понять принцип как её устанавливать, а не пытаться затребовать у всех UTC. Но если вы с начала проект делаете и сами собираете окружение - то поставить не лишне.

    В итоге конвертировать их придется только при выводе в таймзону пользователя. Для этого пишем какой-то класс Calendar и там метод formatDate(), он на вход может принимать таймзону, которую вам пользователь заранее аяксом передал и вы её положили в сессию например или в кеш на 15 минут.

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

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

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

    Для этого вам надо открыть файлик и написать все возможные варианты с таймзонами и вывести их var_dump, чтобы понять принцип что на что влияет.

    Иммутаблы это ересь для тех, кто пока ещё не умеет работать с датами или не понимает ООП.

    Разница между иммутаблом и не иммутаблом в том, что первый по результатам любого действия возвращает другой объект с датой, чтобы вы случайно не изменили старую. Если вы понимаете, что времядата это объект, то вы этого не сделаете. Точно так же как вызывая какой-нибудь $config->set(); вы понимаете что вы меняете конфиг, а не создаете новый. Вполне можно использовать clone/new чтобы создать несколько дат перед тем как начать их менять.
    Ответ написан
    2 комментария
  • Как записать значения через модель в разные таблицы с одним ID?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Вы либо прочитали либо нащупали Single Table Inheritance.

    Его смысл в том, что центральная модель "агрегат" имеет id - AUTOINCREMENT, а зависимые от неё имеют такой же тип, но НЕ_АВТОИНКРЕМЕНТ. При этом центральная модель часто вообще не содержит информации, а является только регистром айдишек для привязки к ней любого числа таблиц завтра.

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

    В вашем случае нужно поставить на Category.id
    UNSIGNED INT
    AUTOINCREMENT

    а на CategoryDesc.id
    UNSIGNED INT
    FOREIGN KEY TO Category.id
    UNIQUE

    Потому что описание у одной категории логически одно.

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

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

    Сейчас вы получаете ошибку потому, что AUTOINCREMENT не предполагает что вы сами ставите значение id, оно просто берется "следующее".

    Дальше едем. Вы подняли проблему последовательности записи. Её можно делать двумя путями.

    1) После выполнения запроса проверить поле полученной модели. Ларавель записывает айдишку на свое место после того как запрос выполнился.

    $category = Category::create();
    
    $categoryDesc = new CategoryDesc();
    $categoryDesc->category_id = $category->id;
    $categoryDesc->save();


    2) Вы можете сами назначить айди для целой пачки записей ещё до того, как пойдет запрос в базу. Читайте это как "дать своё айди прямо в коде". Его конечно не запишешь потом в INT AUTOINCREMENT, но можно создать поле `uid` и туда его вручную записывать и не использовать автоинкремент совсем. Что бы это дало? Например batch insert (вставку пачками), который запишет сразу целую пачку, если это возможно. Не то чтобы он невозможен и с auto_increment, но вот разделение логики и работы с БД будет невозможно, следующий шаг отталкивается от прошлого, который еще не сделан и не может быть сделан. Без неё - запишет первую половину, потом ляжет на ошибке, а потом надо удалять, и заниматься оборачиванием половины кода в транзакции, хотя они предназначены не для этого. Точнее так, для этого тоже, но они медленны, и сделав их везде - производительность получится нулевая, а ларавель не предоставляя нам удобного пулинга заставляет нас с этим сражаться. Эта проблема появится не сразу, но позже вы поймете, вы нащупали её. То есть транзакция по хорошему одна на вызов скрипта, даже не так - на вызов действия бизнес логики, бывают действия которые многое делают и там будет несколько, но сначала логика, потом запись в бд, потом дальше что-то делаем. А ларавель либо заставит обернуть всю программу в транзакцию, увеличив её время и вызвав блокировки в будущем, либо заставит вас удалять всё руками.

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

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

    3) Либо так, как написал выше Дмитрий, используя связи, которые в ларавеле очень читабельны и понятны, но вопрос последовательности и пуллинга все равно открытый.
    Ответ написан
  • Возможно ли добавить use переменные после объявления анонимной функции?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Процесс, который вы хотите называется "функции высшего порядка"

    // функция "высшего" порядка - что в ней высшего - надо спросить у того кто придумал, высший человек наверное
    
    $fnPow = function ($x, $y) {
      return pow($x, $y);
    };
    
    $fnPowExtended = function () use ($fnPow, $b, $c) {
      var_dump($b);
      var_dump($c);
      return $fnPow($b, $c);
    };
    
    $fnPowExtended(2, 3);
    // 2
    // 3
    // 8


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

    $fnRecursion = function () use (&$fnRecursion) {
      $fnRecursion(); // infinite recursion here
    }


    Я полагаю что ваш интерес вызван желанием создать функцию bind()/call()/apply(), как в javascript. В ПХП этим путем не пошли, т.к. ООП все же хоть и дороже в разработке, но понижает порог входа новых людей в проект. Решение спорное, т.к. в ООП надо уметь, а функционалка может быть лишь бы было. И понимая что "старый код не трогать, а заменять" это вроде не так уж плохо. Но в пхп часто старый код именно трогают, доделывают, подпиливают. Функционалка усложняет процесс понимания "что происходит", поэтому ей редко пользуются. К тому же в пхп до недавнего времени вообще короткой записи не было, и писать function () use () {}, и потом переживать, что там чото с памятью может быть и пересылом часто сложнее, чем нажать CTRL+N в IDE и создать новый класс.

    bind() должна бы выглядеть примерно так в PHP (но ей здесь никто не будет пользоваться):

    /**
         * bind
         * копирует тело функции и присваивает аргументы на их места в переданном порядке
         * bind('is_array', [], 1, 2) -> Closure of (function is_array($var = []))
         *
         * @param null|object $newthis
         * @param callable    $func
         * @param mixed       ...$arguments
         *
         * @return \Closure
         */
        public function bind(?object $newthis, callable $func, ...$arguments) : \Closure
        {
            if (! is_string($func)) {
                $bind = $arguments;
    
            } else {
                // string
                $bind = [];
    
                $rf = new \ReflectionFunction($func);
    
                $requiredCnt = $rf->getNumberOfRequiredParameters();
    
                while ( $requiredCnt-- ) {
                    $bind[] = null !== key($arguments)
                        ? current($arguments)
                        : null;
    
                    next($arguments);
                }
    
                $func = \Closure::fromCallable($func);
            }
    
            if (null !== $newthis) {
                if (! $func instanceof \Closure) {
                    $func = \Closure::fromCallable($func); // throws exception if not possible
                }
    
                $func->bindTo($newthis); // throws exception if not possible
            }
    
            $result = static function (...$args) use ($func, $bind) {
                $bind = array_replace(
                    $bind,
                    array_slice($args, 0, count($bind))
                );
    
                return call_user_func_array($func, $bind);
            };
    
            return $result;
        }


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

    ===

    В ООП для подобных вещей используют декорирование (decoration) (не путать с кАррирование (currying)). Вы пишете класс, который на вход ожидает обьект другого класса, чтобы нарастить его функционал, не влазя в детали.

    Чуть позже в python поняли, что можно не только готовые объекты заворачивать, но и декорировать любую функцию уже на этапе описания. На псевдокоде это выглядит так:

    [send_telegram] // use result to call new function
    [send_email] // use result to call new function
    [push_event] // use result to call new function
    function register() {
      // do some
    }


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

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

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

    2) Функции работают с памятью несколько по особенному. То есть если не писать перед функцией слово `static`, что означает "не хранить внутри $this того места где её создали", можно получить интересную картинку если эту функцию вызовут в рекурсии. Оригинальный класс, который для неё $this, накопив данные и начав копировать эту функцию в момент того как вы пишете $a = function () {}, будет каждому экземпляру копировать данные этого объекта. И случайно в коде выполнив циклом 300 раз $a = function () {} вы 300 раз скопируете данные, которые возможно представляли собой "много текста" и ушатаете память.

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

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

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Вы делаете сайт на локале. Показываете с экраном или программой управления, даете пощелкать. А потом сообщаете что установите на сайт не раньше чем он оплатит.
    Ответ написан
    Комментировать
  • Почему не срабатывает команда artisan php artisan make:model Flight -a в Laravel?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    На вашем линуксе не установлено расширение ext-dom.
    Какого черта мэйкер его хочет - а болт его знает. Искать надо.
    Ответ написан
    Комментировать
  • Как соотнести каждый элемент строки массива с каждым элементом других строк этого массива?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    На пхп из коробки сложновато, вы говорите об операции "product" которая является частью комбинаторики.

    Вот у меня класс есть, где я это из python портировал:
    https://github.com/6562680/support/blob/main/src/X...

    Но мне кажется, вам будет сложно разобрать что там написано поэтому я вам простой код напишу под вашу задачу:
    $arr = [
    	['aa', 'bb', 'cc'],
    	['dd', 'ee', 'ff', 'gg'],
    	['hh', 'ii'],
    ];
    
    function _array_product(array $pools) {
        $result = [ [] ];
    
        foreach ( $pools as $pool ) {
            $resultCurrent = [];
    
            foreach ( $result as $x ) {
                foreach ( $pool as $y ) {
                    $resultCurrent[] = array_merge($x, [ $y ]);
                }
            }
    
            $result = $resultCurrent;
        }
    
        foreach ( $result as $item ) {
            yield $item;
        }
    }
    
    foreach (_array_product($arr) as $val) {
    	var_dump($val);
    }
    Ответ написан
    2 комментария
  • Как использовать Vite вместе с sass внутри docker?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Для того чтобы адекватно работать с webpack, стоит попробовать установить symfony/encore, и написать класс из 3-4 методов, чтобы по имени файла находил его же в папке /build/ с помощью генерируемого при сборке manifest.json.

    После этого вы привычным образом пишете "npm install {пакет}" и подключаете его, сборка encore соберет из него папку /build, у которой будут файлы так называемые entrypoints, по факту файлы page-home.js, page-product.js - их вы сами так назовете. В каждом из этих файлов будут работать ваши import/require и даже динамический импорт. Дальше в файлах шаблонов в блоке вы вызовите что-то вроде $encore->styles('page-index') и $encore->scripts('page-index') - их правда придется на пхп написать, но там строчек 20 кода.

    И всё, на любой странице яваскрипт любой сложности просто берет и работает, как раньше, когда вы писали <script src>

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

    Второй вопрос - что делать с докером.

    Да, нужно Dockerfile написать где подменить CMD на ваш скрипт .sh который выполняет ваши команды, которые вы на локале пишете.

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

    Были б вы в телеге, я б показал как.
    Ответ написан
    1 комментарий