Махровый fullstack
16 subscribers
66 photos
27 links
Личный блог @nornik:
- Бэкенд, фронтенд и случайные мысли - в произвольных сочетаниях.
- Иногда литература, иногда всё, что взбредёт в голову.
Download Telegram
Подруга подкинула проблем...

И да, спасибо группе "2ва Самолёта", за максимально релевантное название, неожиданно случившегося опыта.🤔

Кто работает с laravel знает, или по крайней мере, догадывается, что в ней (или в нём?) есть всё, собственно как и в Греции. Сегодня хочу рассмотреть один такой момент, который, как мне кажется, довольно интересный, а именно использование трейта softDeletes и не самая очевидная его особенность.

Нацепив на модель трейт softDeletes, мы её награждаем полем deleted_at, в результате чего запись из БД не удаляется физически, а помечается на удаление - deleted_at становится не null. Это полезно, если мы хотим сохранить данные для какого-то аудита или защитить данные от случайных приступов криворукости.

Стоит отметить, что сам по себе трейт не создаёт поле deleted_at и не добавляет соответствующих полей в БД. Поля для БД необходимо предусмотреть на этапе проектировния таблицы и указать явно в миграции $table->softDeletes() и, лучше добавить по полю индекс.

Пупупупупу-у-у-у! А у меня ж индексы везде стоят? Ведь да же?..😰


Итак, всё, вроде бы, круто - данные сохранили, индексы, для +10 к скорости выборки, добавили. При использовании ORM удалённые записи не возвращаются в запросах, model-route-binding тоже сыпит 404 ошибкой при обращении к удалённым записям. Всё хорошо!

Хорошо-хорошо, да не очень-то...


Для меня стало неожиданным сюрпризом, что в правилах валидации, таких как exists и unique, эта магия не работает 😭. Да, блин! В правилах валидации требуется явное указание, что нужно исключать записи, помеченные на удаление, например так:

Rule::exists(PromoCode::class, 'code')->withoutTrashed();


Laravel весь такой магический, что волей-неволей приучает нас, что везде ожидается одинаковое поведение, но суровая жопа реальности сказала, что магии не существует! Самое обидное, что в основной документации об этом ни слова, но, справедливости ради, есть в API документации.

Почему такая несправедливость?

Потому что softDeletes является частью мира ORM и представляет собой глобальный скоуп. В то время как валидаторы - это прямые запросы к БД и ни про какие ORM и scope слыхом не слыхивали.

Что жжжж, ожидание-реальность как оно есть. 😮

Учите матчасть!

——

🎶 А пока писал пост заслушал до дыр:
- Дарья Безбородова, Дмитрий Гусаров - Винил
- Дарья Безбородова, Дмитрий Гусаров - Блюз о крыльях, ногах и хвостах.

Просто восторг!

#laravel #softDeletes #php #backend #web #разработка
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1🔥1
Что ж, 2025‑й честно старался, но не справился и мы выстояли.
Значит, всё было не зря.

С Новым годом и с новыми силами! 🎅
Please open Telegram to view this post
VIEW IN TELEGRAM
2025: 1813 часов за клавиатурой и другие итоги года

Пока идут длинные выходные, можно устроить небольшое ретро на прошедшие 365 дней. Раньше я такого не делал, так что не судите строго, братцы!

👩‍💻 Fullstack

Начнём с основной деятельности - благо, её проще отследить благодаря сервису WakaTime.

За год я накодил 1 813 часов - в среднем это 7 часов 20 минут в день. И это только чистое время кодинга: без учёта созвонов, писем, чатов и философских размышлений о смысле жизни.

При этом самый длинный день кодинга длился 12 часов и 51 минуту - видимо, поймал волну 🧑‍💻.

А самым продуктивным днём недели была среда (потому что в среду мало созвонов? 😏 )

В этом году самым востребованными языками у меня были PHP и TypeScript, которые использовались в проектах на Laravel, Yii2, Vue/Nuxt и React/Next. Так же было много:
- SQL,
- Sass/SCSS
- CoffeeScript
- Docker
- Bash
- немного Go и ряд других языков.

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

📖 Книги

Не осталась обделённой и моя слабость - книги.

За год было прочитано почти 6 кг (5.89 кг, если точнее), что составляет 5 696 страниц или 12 книг.
По жанрам разбивка получилась такая:
- 58% - фантастика,
- 25% - фентези,
- 17% - детектив.

Конечно, хотелось бы читать больше, но кто тогда будет работать? Зато книги действительно читались, а не просто украшали полку.

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

💪 Спорт

Дабы окончательно не превратиться в офисный кактус, спорт был со мной на протяжении всего года:

- 114 часов кардио - 684 км, сожжено примерно 45 600 ккал
- 156 часов силовых - 93 600 ккал в минус

До Аполлона, конечно, как до Китая на корячках...

☺️ A ещё...

- 80 285 очков заработал в "зелёной сове", провёл 3 935 минут занятий в 6 языках в течение 397 дней без перерыва;
- посетил IT-конфу за последние много лет и напомнил себе, что айти существует не только в чатах и таск-трекерах;
- накидал простенькую сцену платформера в Unity, дабы запустить немного свежего воздуха в трудовые будни;
- и начал вести этот канал - покоряем новые горизонты.

——

Всё ещё не выгорел и всё ещё интересно.
Значит, можно смело двигаться дальше - в 2026‑й!

#итоги #год_2025
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥2
Лангр. Сыр, который не прочь побаловать себя игристым

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

Что же ты такое

Лангр (фр. Langres) - французский мягкий сыр из коровьего молока со сморщенной корочкой оранжевого цвета. Корочка обычно клейкая и немного влажная. Свой цвет корочка получает за счёт того, что во время созревания сыр протирают рассолом с добавлением оранжевого красителя из семян аннато. А ещё, у головки сыра имеется ямка в верхней части - кто-то называет её короной, а турофилы называют его "фонтаном" (уж не знаю почему 🤷‍♀️).

Головки сыра, как правило, делают небольшими - диаметром 7,5 - 9 см, высотой 4 - 6 см и весом около 150 г. На ранней стадии созревания тело сыра может быть упругим (мне такие не попадались ни разу), а с течением времени становится более сливочным и текучим (самый самолёт как по мне 😋).

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

История и происхождение

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

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

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

Однако, в мае 1991 года сыр лангр получил сертификат AOC (Appellation d’Origine Contrôlée), который подтверждает его происхождение и стандарты производства. С 2009 года сыр имеет статус Appellation d’Origine Protégée (защищённое обозначение происхождения).

Традиции и особенности 🍷

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

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

А ещё лангр не принято резать на кусочки, его едят ложкой.

Источники: novostioede.ru, tastingtable.com, ruwiki.ru

#внезапно #позновательно #изюм #сыр #лангр
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🤩2
Читальня. Ричард Морган – Видоизменённый углерод

— Возвращение из мёртвых может быть очень мучительным —

Заметили как в последнее время посыпались новости то про лекарства от рака, то про искусственную кровь, то про увеличение средней продолжительности жизни человека, - вот и мировые лидеры в кулуарах обсуждают средний срок жизни в 150 - 170 лет. Некоторые учёные просят не помирать ближайшие 10 лет, потому что скоро наука сможет возвращать тело человека в состояние, как у 20-ти летнего. Глядишь такими темпами и на нашем веку бессмертие заработает.

А вот в мире, созданном Ричардом Морганом, бессмертие уже изобретено и доступно не только лишь всем. При наличии достаточного количества кредитов можно позволить себе прожить не одну сотню лет.

Бессмертие не тела, но разума.

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

А средства, как полагается, есть только у богатейших мира сего, а остальным достаётся что осталось, по ОМС.

На этой идее - перемещения сознания в разные тела - и построен роман.

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

События в книге развиваются с бешеным темпом: перестрелки сменяют друг друга, кровь льётся литрами, а тела разлетаются на части - здесь потеря руки или головы не всегда означает смерть, а иногда лишь временное неудобство. В минуты затишья - допросы, разговоры, виртуальная реальность, наркотики, секс и Эдгар Аллан По. Всё это описано жёстко, откровенно, красочно, брутально, но при этом удивительно увлекательно.

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

Итого: отличный бодрый, местами жестокий, детектив о будущем, в котором технологии шагнули далеко вперёд, а вот человечность - совсем не факт.

А какое отношение имеет к этой истории Эдгар Алан По я предлагаю выяснить самостоятельно.

8 "жеребцов" из 10

#книги #почитать #РичардМорган #Морган #ТакесиКовач #ВидоизменённыйУглерод #детектив #киберпанк #нуар #фантастика
👍2🔥1
Каждому проекту - домен. Каждому разработчику - покой
- Как забыть про порты в локальной разработке с Traefik -

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

И вот о последнем я бы поговорил отдельно... Согласитесь, все эти бесконечные localhost:8081, localhost:4200, localhost:8082 наводят тоску. Наверное, я их даже несколько ненавижу. И дело не только в том, что порты в адресной строке выглядят не гармонично, так они ещё и запоминаются не очень хорошо. А если ещё приходится переключаться между проектами, или проект требует нескольких открытых приложений, то в адресной строке начинается хаос.

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

Но выход есть - и он называется Traefik 🤘

Traefik - мой новый лучший друг в мире Docker. Это обратный прокси-сервер, который отлично понимает docker и берёт на себя всю грязную работу по маршрутизации приложений. Он может многое: балансировку, контроль доступа, HTTPS. Но в контексте локальной разработки мне от него нужна маршрутизация. А приятным бонусом при работе с ним - это возможность установить SSL-сертификат - большего для локальной разработки и не надо.

Принципиальная схема работы выглядит так:
Browser (service.local)
│
▼
Traefik
│
▼
Docker network
│
▼
App / API / SPA


Вся магия в том, что наружу смотрит только Traefik, а сами сервисы живут внутри общей docker-сети.

Как это работает локально:
- поднимаем Traefik в отдельном docker-compose и подключаем его к общей внешней сети;
- все сервисы, работающие по HTTP, размещаем в той же сети;
- вместо проброса портов используем метки (labels) в Docker - Traefik сам найдёт сервисы и настроит маршрутизацию.

Конфиг для сервиса выглядит примерно так:
services:
app:
image: my-cool-app
networks:
- web
- default
labels:
- traefik.enable=true
- traefik.http.routers.project1.rule=Host(`project1.local`)
- traefik.http.services.project1.loadbalancer.server.port=80

networks:
web:
external: true

Ву-а-ля!

Открываем в браузере project1.local без всяких бесячих портов.

Каеф! 😎

#docker #traefik #devops #backend #разработка
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4
Дорогой дневник, в меня вставили параметры. JsonResource
- Как сломать JsonResource, не ломая JsonResource -

Сегодня хочется поговорить или о немного вредном, или о немного неправильном. О чём-то не по феншую. Не исключаю, что это вообще не lara-way и в приличном чате меня аккуратно, но уверенно запинают ногами! 👊

НО, почему бы и нет...

Разрабатывая API на Laravel, мы постоянно имеем дело с JsonResource.

Чем же он хорош, наверняка, не спросите вы, но я напомню:
— скрывает истинную структуру модели,
— определяет контракт ответа,
— позволяет добавлять вычисляемые поля,
— форматирует данные.

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

Сам JsonResource не должен быть перегружен сложной логикой - и это правильно! Но в моём случае потребовалось извне передать в коллекцию параметры для модификации вывода.

И тут из зала: "Йо! Мэн! Создай отдельный ресурс и не делай нам мозги!".

Что ж. Справедливо.
Но плодить новые сущности из-за пары флагов - право, не хотелось.

Другие знатоки скажут: "Чел! Добавь атрибутов к реквесту!".

Да, почему бы и не атрибуты:
$request->attributes->add([
'param1' => 'foo',
'param2' => 'bar',
]);


Тоже решение, но выглядит громоздко, и плохо документируется.

Мы пойдём другим путём!

Сначала попробовал расширить конструктор:
public function __construct($resource, bool $param = false)
{
parent::__construct($resource);

$this->param = $param;
}

Но это работает только для единичного ресурса, а вот для коллекции - облом. А ещё ресурс нужно создавать вручную, а не через фабрику — не наш метод — я фабрику люблю.

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

class JsonResourceWithParams extends JsonResource
{
public static function collectionWithData($resources, $params)
{
return self::collection($resources)->each(function(self $r) use ($params) {
foreach ($params as $param => $value) {
if (property_exists($r, $param)) {
$r->{$param} = $value;
}
}
});
}
}

А ещё, добавляя свойства ресурса, и документируется неплохо.

Да. Не по учебнику. ☝️

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

#эксперименты #laravel #backend #web #разработка
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🤔1
Читальня. Питер В. Бретт – Копьё пустыни

— Обзор первой книги - Меченый
— Обзор третьей книги - Дневная битва
— Обзор четвёртой книги - Трон Черепов


— В Красии ты никчёмен, если никто не пытается тебя убить —

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

У демонов появляются новые юниты - князья и хамелеоны - лишь они теперь представляют реальную опасность человеку. Остальные представители подземной фауны уже не внушают прежнего ужаса: люди вооружились мечеными копьями, щитами и стрелами, научились создавать плащи невидимки, а города защищают «великие метки», к которым демоны не подходят.

Но главная эскалация происходит не в ночи - а среди людей.

Мы подробно знакомимся с внутренним укладом Красии - общества глубоко религиозного и воинственного, построенного на жёсткой кастовой системе. Через историю становления Ахмана Джардира - Избавителя всея Красии - мы постепенно погружаемся в культуру народа пустыни. Быт описан детально, а мир Красии ощущается цельным и продуманным. В повествование добавляется много новых терминов: шарум, дама'тинг, алагай, шарадж, андрах и т.д. Они сначала сбивают с толку, но со временем начинаешь улавливать логику языка и ориентироваться становится значительно проще.

Убедив себя и свой народ в собственной мессианской роли, Джардир отправляется на север объединять человечество и готовить его к последней битве с демонами. И его не остановит тот факт, что объединение придётся проводить огнём и мечом. Через кровь, насилие и террор - к высшей цели.

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

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

Так же не забыты и второстепенные персонажи: очень интересен хаффит Аббан, который как то самое ружьё, которое обязательно выстрелит. Мать Лиши, Элона, - очень противоречивая дама, но весьма прямолинейна и точна в своих суждениях; отпрыски Джардира, которым тоже, судя по всему, отведено не последнее место в будущих книгах, а ещё Гаред, Кендалл, королевские особы...список довольно внушительный и у каждого своя история.

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

9 шарумов из 10

#книги #почитать #ПитерВБретт #Бретт #ВселеннаяМеченого #КопьёПустыни #тёмное #фентези #магия #монстры
🔥1
Cмотря какой fabric, смотря сколько details, очень очень affordable
- Очищающее пламя языкового пуризма -

Итак, с 1 марта 2026 года в России действует закон №168-ФЗ, ограничивающий использование иностранных слов в публичной информации.

Посему в Лету должнымогут кануть различные sale, кавер, стартап, коучинг и прочие all in.

🇷🇺 Россия

Так-то это не первое подобное законотворчество: в XVIII веке после реформ Петра I появилось много заимствований, в том числе голландских и немецких. Позже, с модой на всё французское в XVIII–XIX веках, в речь массово вошли галлицизмы. 

Поэтому Екатерина II При составлении Словаря Академии Российской указывала академикам:
В сочиняемом академией словаре избегать всевозможным образом слов чужеземных, а наипаче речений, заменяя оные слова или древними или вновь составленными.

Однако полностью остановить заимствования не удалось. 

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

—

Был как-то на одном проекте коллега, который очень любил "свитчиться между тасками" и слушать это было примерно так же органично, как и знаменитое задорновское "вам чизоф отслайсить?" 🤦‍♂️

—

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

Ну ладно, интернет придумает что-нибудь креативнее ✍️

Но вообще языковой пуризм - явление довольно распространённое.

Вот пара особо ярких примеров для ознакомления:

🇮🇸 Исландия

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

В 1964 году была создана Исландская языковая комиссия, которая вырабатывает рекомендации по сохранению единства и целостности исландского языка. Новые слова создаются на основе древненорвежских или античных корней. Например, слово rafmagn («электричество») буквально переводится как «сила янтаря». Также практикуется придание старому слову нового значения: sími (раньше -  «длинная нить») стало означать «телефон».


🇫🇷 Франция

Во Франции пуризм начал формироваться ещё в XVII веке. Основанная в 1635 году Французская академия ставила целью «очистку» языка и продвижение le bon usage — правильного употребления французского. В XX веке борьба за чистоту языка усилилась из-за роста влияния английского языка.

В 1975 году был принят закон Ба-Лорьоля (loi Bas-Lauriol), который обязывал использовать французский язык в рекламе, технических описаниях товаров, трудовых договорах, а радио обязано транслировать минимум 35% песен на французском.

В 1994 году был принят закон Турбона, который ужесточал положения закона 1975 года и регулировал использование иностранных слов.

В настоящее время во Франции действует Комиссия по обогащению французского языка, которая создаёт новые слова для замены англицизмов. Например, вместо английского computer во французском используется ordinateur, вместо fax - télécopie, а вместо e-mail - courriel.


Так же элементы языкового пуризма были в Турции, Германии, Хорватии, Венгрии, Великобритании, США и других странах. Явление это не самое редкое, надо сказать.

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

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

Ну а пока...

Мёржим, пушим, билдим, фиксим,
Если не упало, – да и фиг с ним!
Фиксим, билдим, мёржим, пушим,
Эй! Кьюэй! Не плюй мне в душу!

©️ Арчет

#внезапно #позновательно #изюм #пуризм
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Docker разжирел - пора на сушку
- Как не дать docker съесть лишнего -

Случилось страшное - на SSD осталось 60Гб свободного места.
Если не считать, что львиная доля диска занята чем-то не сильно нужным, то docker, разросшийся до 70Гб вызвал оторопь и ужас. 😱

Посмотрим, на каких харчах разжирел docker:
— docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          212       51        51.18GB   20.17GB (39%)
Containers      62        1         337MB     337MB (99%)
Local Volumes   33        6         3.661GB   476.3MB (13%)
Build Cache     2028      0         17.18GB   3.089GB


Время проводить уборку! 🛁

— docker container prune - удаляет остановленные контейнеры,
— docker image prune -a - удаляет все неиспользуемые образы,
— docker builder prune -a - удаляет кэш билдов (иногда именно он жрёт десятки гигабайт),
— docker network prune - удаляет неиспользуемые сети

Или можно заменить всё это одной командой: docker system prune -a - она почистит контейнеры, образы, build cache и сети разом.

В целом, это безопасные команды, но если хочется хардкора и хождения по лезвию бритвы, то можно сделать так: ☢️ docker system prune -a --volumes - и отправить к праотцам ещё и volumes - вместе с базами данных, логами и всем нажитым непосильным трудом.

Что бы не писать это каждый раз руками, можно в ~/.zshrc добавить простенький алиас:
alias dclean='docker container prune -f && docker image prune -a -f && docker builder prune -a -f'


Или немного улучшим, с лимитом по старости объектов:
dclean() {
local DAYS="${1:-7}"

if ! command -v docker >/dev/null 2>&1; then
echo "docker не найден в PATH"
return 1
fi

echo "== Docker disk usage BEFORE =="
docker system df || return 1

echo
echo "== Pruning stopped containers =="
docker container prune -f || return 1

echo
echo "== Pruning images unused by containers (older than ${DAYS} days) =="
docker image prune -a -f --filter "until=${DAYS}d" || return 1

echo
echo "== Pruning build cache (older than ${DAYS} days) =="
docker builder prune -a -f --filter "until=${DAYS}d" || return 1

echo
echo "== Docker disk usage AFTER =="
docker system df || return 1
}


И запускаем: dclean 14 - удаляет всё старше 14 дней.

Проводите регулярную чистку и соблюдайте КБЖУ! 🍔

#backend #devops #docker #разработка
Please open Telegram to view this post
VIEW IN TELEGRAM
✍1🐳1
По заветам Мюллера, или верить нельзя никому

- Контрразведчик должен знать всегда, как никто другой, что верить в наше время нельзя никому, порой даже самому себе -

TL;DR
HTTP Content-Type может врать. Если MIME критичен — определяйте тип файла по magic numbers, а не по заголовкам.


А вот ещё был случай...

Задача, в целом, хрестоматийная: берём ссылку на изображение и делаем из неё тумбочку меньшего размера. Казалось бы, что могло пойти не так? Как оказалось - могло. 😈

Алгоритм тумбочкования, в принципе, стандартный:
- получаем информацию об оригинальном изображении;
- получаем MIME для определения функции создания изображения;
- ImageMagick делает свой magick ✨ и вуа-ля!

Но в этот раз всё сломал MIME!

Картинка-источник лежала на CDN, а тип изображения определялся по HTTP-заголовку Content-Type, который получали при скачивании изображения. И веселью не было предела, когда для обычного jpg сервер внезапно начал возвращать image/png.

Вывод: никогда нельзя доверять HTTP MIME, если MIME на что-то влияет фатально.

А чтобы всё было по красоте - воспользуемся магией! В частности magic numbers.

Magic numbers - это уникальная, заранее оговорённая, сигнатура файла, записанная в его первых байтах.
Формат | Magic bytes
PNG | 89 50 4E 47 0D 0A 1A 0A
JPEG | FF D8 FF
GIF | 47 49 46 38
WebP | 52 49 46 46 .... 57 45 42 50


И тогда у нас получается быстрая и удобная функция, определяющая тип прям из ДНК файла:
private function getSourceMimeType(string $path = ""): bool|string
{
$finfo = new finfo(FILEINFO_MIME_TYPE);

if (is_readable($path)) {
return $finfo->file($path);
}

if (filter_var($path, FILTER_VALIDATE_URL)) {
$fp = @fopen($path, 'rb');

if ($fp) {
$data = fread($fp, 256);
fclose($fp);

return $finfo->buffer($data);
}
}

return false;
}


Какие плюсы:
- используем finfo, он умеет работать как с локальными файлами, так и с буфером данных;
- если нужно узнать тип файла, допустим, по HTTP, то достаточно скачать первые 256 байт для корректного определения MIME;

Мораль: как учит нас практика - верить нельзя никому. Даже Content-Type. Мне - можно!

Будьте бдительны, друзья, и враг не пройдёт!

P.S.: пока писал заметку нашёл ещё хороший вариант, но подходит только для изображений:
$size = getimagesize($url);
echo $size['mime'];

Нюансы:
- работает с URL;
- читает только начало файла;
- сразу возвращает MIME изображения;

#эксперименты #backend #web #разработка
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🔥1
⚠️ Алярм или день Д не за горами

1 апреля близко-близко
И сердце бьётся как олень
Не подведи меня...
а, это про другой день же!

Что ж, весна приносит не только слякоть и долгожданное солнышко, но и некоторые сетевые проблемы, коих в ближайшей перспективе, может только прибавиться. 📡

Поэтому - на всякий случай - оставляю, альтернативные точки связи:

📱 Вспомнил про ВК:
- сделал cообщество https://vk.ru/ordo_code;
- и там же канал https://vk.ru/im/channels/-236655784;

Как этим всем управлять и что там вообще можно делать - я пока представляю смутно.

Для меня ВК всегда был чем-то непонятным, и, честно говоря, таким он и остался. Но реалии требуют адаптироваться - попробую с ним как-то подружиться.

📲 Конечно же MAX - https://max.ru/channel_ordo_code

Тут всё довольно привычно и максимально тележно.

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

🐦 Голубиную почту оставим совсем на чёрный день, но яйцами уже закупился - начинаю высиживать.

В общем, присоединяйтесь!
Будем на связи, в это непростое сепаратное время.

#на_связи
Please open Telegram to view this post
VIEW IN TELEGRAM
👀1
Даже в Laravel не обойтись без политики

— Как проверить права доступа к модели через Policy —

Сегодня хочу поговорить о политике.
Чего я хуже остальных, что ли... ✊

TL;DR
Проверку доступа к модели удобно выносить в Policy и подключать в карте маршрутов.


Возникла задача добавить проверку при удаления файла - разрешить пользователю удалять только свои собственные файлы.

Самый простой и правильный способ сделать это в Laravel - использовать Policy.

Policy - специальные классы в Laravel, позволяющие определить логику авторизации для конкретной модели или ресурса.

Преимущества подхода с политиками:

Централизация логики доступа

Поведение задаётся в одном месте - в классе политики
public function delete(User $user, Attachment $attachment)
{
return $attachment->user_id === $user->id;
}


Переиспользование
Policy можно использовать в разных частях приложения

В контроллере:
$this->authorize('delete', $attachment);

В шаблонах:
@can('delete', $attachment)
<button>Delete</button>
@endcan

В маршрутах:
->can('delete', 'attachment')


Улучшение читаемости
$this->authorize('delete', $attachment);

более очевидно считывается, чем
if ($attachment->user_id !== auth()->id())


Масштабируемость
Логика доступа со временем может усложняться (роли, админы, shared-ресурсы и т.д.), но благодаря централизованному описанию политики изменения вносятся в одном месте.

Расширяемость
Можно добавлять собственные методы политик, а не ограничиваться только стандартными.

Тестируемость
Отдельный метод политики легко покрывается тестами.

Чёткое разделение ответственности
— Controller - обрабатывает запрос;
— Policy - определяет доступ;
— Model - определяет данные;

Policy обнаруживаются автоматически в проекте, если структура проекта стандартная:
app/
├─ Models/
│ └─ Attachment.php
└─ Policies/
└─ AttachmentPolicy.php

Laravel автоматически сопоставит:
Attachment → AttachmentPolicy

Никакой регистрации не требуется.
(Но при необходимости политику можно зарегистрировать вручную в AppServiceProvider.)

—————

Политики создаются командой:
php artisan make:policy AttachmentPolicy --model=Attachment

А дальше происходит магия соглашения именования и политика применяется к модели. ✨

Для своего случая я выбрал вариант проверки доступности на уровне обработки маршрута:
Route::post('/upload/{attachment}/delete', [UploadController::class, 'delete'])
->can('delete', 'attachment')
->name('upload.remove');

В итоге получается аккуратное, архитектурно правильное и читаемое решение.

Если файл принадлежит пользователю - удаляем, а нет - извините, политика. 📄

#laravel #policy #authorize #backend #web #разработка
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2❤1
Читальня. Питер В. Бретт – Дневная битва

— Обзор первой книги - Меченый
— Обзор второй книги - Копьё пустыни
— Обзор четвёртой книги - Трон Черепов


— Такова инэвера —

Продолжаем погружаться в мир Тесы и противостояние людей и демонов людей в третьей книге.

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

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

В остальном, перед нами большое количество политики: придворные игры, интриги, манипуляции и борьба за власть. Красийцы, в лице Джардира, строят планы по завоеванию объединению всё ещё разрозненных поселений северян. 👑

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

Герои прокачивают свои магические навыки: Ренна, Арлен, Рожер и даже Лиша, со своим почти научным подходом к делу магии - у всех появляются новые перки. Люди активно совершенствуют оружие и доспехи, покрывая всё - от кончиков пальцев до острия копья - защитными и атакующими метками. Все готовятся к очередному пришествию демонической орды.

Рожер, кстати, прокачался не только в магии: он взял лучшее из красийских традиций - окружил себя двойной красотой. Причём, если с демонами он справляется довольно уверенно, то в домашней дипломатии всё заметно сложнее и запутанее. 🚹

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

Итого: хорошее продолжение истории. На мой взгляд, немного слабее предыдущих книг, но в целом планку держит на высоте.

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

7 хора из 10 🎲

#книги #почитать #ПитерВБретт #Бретт #ВселеннаяМеченого #ДневняБитва #тёмное #фентези #магия #монстры
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Не каналами едиными
- Динамическое логирование через Monolog без config/logging.php -

Что ж, поговорим о логировании.

TL;DR
Если каналы Laravel не подходят - используем Monolog напрямую. RotatingFileHandler, маленький сервис - и каждый генератор пишет в свой лог.


Laravel под капотом имеет удобную систему логирования на основе библиотеки Monolog и каналов логирования. В большинстве случаев этого более чем достаточно, но бывают и исключения, когда каналы начинают мешать.

Мне необходимо было генерировать собственные логи под определённую рутину. ✍️

Что за рутина? Микросервис по рассылке автоматических push-уведомлений.

Микросервис на основе генераторов формирует и отправляет пользователям push-уведомления. При этом необходимо логировать процесс генерации: логика довольно ветвистая, сценариев много, а потому нужен глаз да глаз.

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

Не долго кручинясь и не сильно вешая буйну голову, накидал небольшой сервис логирования, который работает без заранее прописанных каналов: 💡
class LoggerService
{
protected Logger $logger;

public function __construct(string $filename = 'custom.log')
{
$this->logger = new Logger('custom');

$this->logger->pushHandler(
new RotatingFileHandler(
storage_path('logs/' . $filename),
14,
Logger::DEBUG
)
);
}
....
}

RotatingFileHandler автоматически создаёт ежедневные файлы логов:
storage/logs/push-generator-1-2026-04-07.log
storage/logs/push-generator-1-2026-04-08.log

Ну а дальше всё просто:
$logger = new LoggerService('push-generator-1.log');

$logger->info('Начало генерации');
$logger->warning('Устройство пропущено', ['device_id' => 42]);
$logger->error('Ошибка отправки', ['token' => $token]);

В итоге каждый генератор может писать в свой собственный лог, не требуя объявления каналов в config/logging.php.

Иногда лучший способ использовать Laravel - это перестать использовать Laravel. 😏

#laravel #logging #monolog #backend #web #разработка
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Как я перестал бояться SQL... и создал полезного монстра

— Как один SQL заменил пачку запросов и килограммы PHP —

Ох, маркетинг, такой маркетинг!

TL;DR
Собрал сегмент “недосмотренных сериалов” одним SQL-запросом через WITH + оконные функции. Убрал N+1, сократил нагрузку на бэкенд и получил готовые данные под пуши прямо из БД.

Есть задача: вернуть пользователя к просмотру сериала, который он недосмотрел, и отправить ему push с призывом продолжить. 🍿

Изи-пизи... если бы не ряд условий:
- надо найти пользователей, которые смотрели хоть что-то; 
- при этом отсеять тех, кто уже досмотрел сериал; 
- выбрать “приоритетный” сериал:
— сначала по дате последнего просмотра; 
— если одинаково - по количеству просмотренных эпизодов;

Решая задачу «в лоб», на горизонте замаячило большое количество запросов к БД, обработка данных на стороне бэкенда, забитая память и прочие радости. В общем, в перспективе запахло это всё не очень, и я начал терять волю. 🤨

А ещё это ломало мою выстроенную парадигму:
запрос к БД -> получение данных -> подготовка данных -> отправка сообщения.
Так как требовало дополнительной реализации обработчика, а я человек ленивый. 🙄

Вспомнив своё банковское прошлое (ммм, какие там делали причудливые запросы к БД) решил попробовать утащить всю логику в SQL.

В итоге решение можно представить в виде такой схемы: 💡
view_histories (история просмотров)
↓
series_stats (агрегация)
↓
episodes_total (общее число эпизодов)
↓
filtered_series (отсекаем досмотренные)
↓
ranked_series (ранжирование)
↓
финальный SELECT

Не совсем тривиальный SELECT, да? 😄

Весь SQL приводить не буду - он разросся строк до ~70 и слегка пугает неподготовленного зрителя, но, в целом, картина примерно такая:
WITH series_stats AS (
...
),
episodes_total AS (
...
),
filtered_series AS (
SELECT
ss.*,
et.total_episodes
FROM series_stats ss
INNER JOIN episodes_total AS et
ON et.content_id = ss.content_id
WHERE ss.watched_episodes < et.total_episodes
),
ranked_series AS (
SELECT
fs.*,
ROW_NUMBER() OVER (
PARTITION BY fs.app_user_id
ORDER BY fs.last_viewed_at DESC, fs.watched_episodes DESC, fs.content_id DESC
) AS rn
FROM filtered_series AS fs
)
SELECT DISTINCT
...
JSON_OBJECT(
'content_id', rs.content_id,
'watched_episodes', rs.watched_episodes,
'total_episodes', rs.total_episodes,
'last_viewed_at', rs.last_viewed_at
) AS params
FROM app_users AS au
...
INNER JOIN ranked_series rs
ON rs.app_user_id = au.id
AND rs.rn = 1
...

Да, выглядит немного монструозно, но результат - просто конфетка. ❤️

Что тут интересного:
- использую цепочку CTE (Common Table Expression) через WITH для подготовки данных;
- считаю просмотры и отдельно количество эпизодов;
- отсекаю полностью просмотренные сериалы прямо в БД;
- ранжирую сериалы внутри каждого пользователя через оконные функции (ROW_NUMBER);
- собираю результат в JSON (JSON_OBJECT), чтобы не городить DTO на бэкенде;

⚠️ Но WITH не всегда бесплатно. Иногда MySQL может материализовать CTE и положить данные физически на диск, поэтому в тяжёлых запросах не забываем про EXPLAIN.

It's alive! It's alive!

#sql #cte #with #backend #разработка
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Читальня. Д. Кюнскен – Квантовый сад

— Обзор первой книги - Квантовый волшебник


— "Срань Господня!" - крикнул Стилс. - "Он антиматерией стреляет!" —

На манеже всё те же — Белизариус Архона снова тащит нас в квантовую шизофрению. Перед нами прямое продолжение немного неоднозначного, но всё-таки весьма интересного «Квантового волшебника».

Мы оказываемся ровно там, где закончилась история первой части: Белизариус всех объегорил, умыкнул врата времени у Суб-Сахарского Союза и нашёл свою асисяй. И тут бы жить-поживать, да добра наживать, но суровая жопа реальности находит нашего героя в виде Конгрегации.

Тут Белизариус рефлексирует.

Конгрегация задумывается о возможностях Homo quantus и в итоге начинают видеть угрозу в квантовых людях. Дабы пресечь угрозу на корню - решают проблему основательно и геноцидят весь родной род Архоны.

Тут Белизариус снова рефлексирует.

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

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

В казармах Белизариус тоже рефлексирует.

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

Над растениями пуще прежнего рефлексирует Белизариус.

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

И тут... Ха! Нет! Тут Белизариус не рефлексирует! 😄

Итого: книга ощутимо слабее Квантового волшебника - душевных переживаний слишком много, описаний 22-х мерного пространства хватило ещё в первой книге, а сюжет какой-то невзрачный по итогу. И знаете... возглас Стилса очень хорошо описывает послевкусие от книги. Не-не, я не про антиматерию ;)

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

5 пугало из 10

#книги #почитать #ДерекКюнскен #Кюнскен #КвантоваяЭволюция #КвантовыйСад #хроно #фантастика #космос
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥1
Призраки в сети — тайна пропадающего домена

— О чём молчат минималистичные Docker-образы —

TL;DR
Если Docker-контейнер на php:*-alpine периодически ловит cURL error 6: Could not resolve host, - проблема может быть в musl, а не в Docker или DNS.


Интересный кейс случился на днях. В логах приложения стали сыпаться сообщения:
cURL error 6: Could not resolve host: here-some-secret-domain.com

🤌

Первым делом проверил что там видит docker у себя внутрях при резолве домена:
docker compose exec php getent hosts here-some-secret-domain.com

Оказалось - нифига он не видит.

Проверил что видит сам хост:
dig here-some-secret-domain.com
;; communications error to 172.16.2.231#53: timed out

Видит! Видит таймаут. 🤦‍♂️

Пропинговал DNS-сервер, которые выдал провайдер - не пингуется.

На всякий случай проверил резолв на общеизвестных DNS-серверах:
dig @8.8.8.8 here-some-secret-domain.com
; <<>> DiG 9.10.6 <<>> @8.8.8.8 here-some-secret-domain.com
; (1 server found)

И получил вполне бодрый ответ - домен резолвится отлично.

Сделав единственно логичный вывод, - проблема на стороне хостера с DNS-сервером - приступил к лечению:
— отредачил /etc/resolv.conf, указав, что используем 8.8.8.8 и 4.4.4.4;
— обновил DNS на интерфейсе resolvectl dns eth0 4.4.4.4 8.8.8.8;
— прописал адреса DNS для docker пожёстче:
services:
php:
dns:
- 8.8.8.8
- 4.4.4.4

Отписал в поддержку о проблеме и пошёл пить кофе, и жевать конфеты! ☕️

В безмятежном состоянии пребывал не долго - повальная ошибка cURL error 6: Could not resolve host ушла, но периодически нет-нет, да и появится. И это было весьма болезнено. 😭

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

И вот тут я реально приуныл, потому что никаких явных причин для такого поведения просто не было.

Пробовалось многое:
— перевод запросов к DNS на tcp;
— разумеется, несколько ребутов;
— пересоздание сетей docker;
— молитвы Омниссии 🤖;

В общем, весь опыт накопленный человечеством оказался бесполезен. 😡

Дабы долго не томить - проблема оказалась в образе - php:8.4-fpm-alpine. Alpine использует musl вместо glibc, и у musl исторически бывают очень нестабильные отношения с DNS, Docker networking и резолверами.

Известные проблемы musl с DNS:
— упрощённый DNS‑клиент. musl не реализует все механизмы glibc, например, сложные стратегии ретраев и таймаутов;
— ограниченная обработка ошибок. При проблемах с первым DNS‑сервером musl может не переключиться на следующий в списке;
— особенности кэширования. Поведение кэша DNS в musl отличается от glibc - возможны устаревшие ответы или пропуски обновлений;
— сетевые взаимодействия. В Docker‑сетях (особенно overlay) musl иногда некорректно обрабатывает ответы от встроенного DNS‑сервера Docker (127.0.0.11).
— таймауты. musl имеет жёсткие таймауты по умолчанию, что критично при задержках в сети;

❗️Мораль❗️
Не гонись, поп, за дешевизной -
Малый образ - геморрой длиной в полжизни!


Ну, а alpine теперь буду использовать только для локальной разработки, а ведь образы так нравились своей минималистичностью.

Моё сердце разбито! 😭

#docker #php #laravel #alpine #musl #devops #backend #web #разработка
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Хирургическая очистка кэша в Yii2

— Не все зависимости одинаково вредны —

TL;DR
Волшебный TagDependency позволяет привязать к одному тегу сколько угодно записей кэша и потом инвалидировать их одной командой.


Что ж друзья, сегодня поговорим о зависимостях. Все знают, что это такое. Сначала кажется, что всё под контролем. Потом они начинают влиять на твою жизнь. А потом ты уже не представляешь, как жил без них. 😍

Зависимости бывают у всех. У людей, у проектов, у npm-пакетов... и даже у кэша в Yii2. И штука эта, надо сказать, офигенная, хотя пользуются ей незаслуженно редко. За всю свою практику я ни разу не встречал её на реальных проектах. Обычно же как: ключ, данные, TTL — и погнали дальше.

Кароч! Рассказываю...

Делал я одну фичу. Суть её сейчас не важна, но появилась необходимость складывать данные, получаемые в ходе работы, в кэш. Ну надо так надо - складываем. А что делают с кэшированными данными? Правильно — периодически чистят для поддержания гигиены и актуальности. И вот тут нашла коса на камень.

Ключи кэша в данной фиче составные и зависят от ряда параметров. Ключи примерно такие — cache.for.feature.key1.key2.key3. И их конечное количество неизвестно. Ну как неизвестно... Примерно дохреналиард их. 😫

И как это всё очищать спросите вы. Ведь спросите же?

Вариантов решения вроде бы хватает. Можно хранить список ключей отдельно, можно искать по префиксам, можно генерировать какие-нибудь служебные ключи для очистки, но всё это выглядит как костыли. Не, можно ещё беспощадно сделать — Yii::$app->cache->flush(). Очищай больше, хрен ли его жалеть!

Другое дело — зависимости кэша. 💡

Для своей задачи я использовал: TagDependency — cвязывает кэшированные данные элемента с одним или несколькими тегами и позволяет инвалидировать данные кэша по этому тэгу.

Синтаксис максимально простой и понятный:
Yii::$app->cache->set(
$cacheKey,
$cacheData,
$cacheTtl,
new TagDependency(['tags' => $tag])
);

А инвалидация кэша делается ещё проще:
TagDependency::invalidate(
Yii::$app->cache,
$tag
);

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

И всё — никаких тебе поисков по префиксам, генерации ключей и прочего мракобесия.

Просто, изящно, профессионально! ☕️

#php #yii2 #cache #dependency #backend #web #разработка
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2👍1
Читальня. Питер В. Бретт – Трон черепов

— Обзор первой книги - Меченый
— Обзор второй книги - Копьё пустыни
— Обзор третьей книги - Дневная битва


– Власть развращает, а абсолютная власть развращает абсолютно –

Новая порция приключений на просторах Тесы.

К началу 4-го тома события цикла окончательно разделились на две истории. Мир живёт сам по себе, а Избавители — сами по себе. Да, их пути ещё пересекаются отдельными оказиями, но в целом это уже две разные истории.

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

А вот остальной мир занят более приземлёнными вещами — борьбой за власть и финансовые потоки. Как только мессии канули в лету после эпичной битвы, места вождей внезапно оказались вакантны, и привычная человеческая сущность вылезла наружу.

Первенцы Ахмана искренне желают смерти друг другу и так же искренне хотят усесться на Трон Черепов. Но на пути к заветному стулу стоят и мать, и дядя. И даже Аббан пристраивается сбоку бантиком, чтобы оказаться как можно ближе к трону. Аббан вообще сам себе на уме парень — хорош, чертяка!

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

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

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

По гамбургскому счёту получается странная книга. Событий много. Персонажей много. Политики очень много. А вот основной сюжет почти не двигается вперёд.

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

Итого: читать интересно, но впервые поймал себя на мысли, что местами было скучно. Не из-за отсутствия событий — их как раз хватает. Просто почти все они второстепенны и мало влияют на главное противостояние людей и демонов.

7 кастратов из 10.

#книги #почитать #ПитерВБретт #Бретт #ВселеннаяМеченого #ТронЧерепов #тёмное #фентези #магия #монстры
❤2👍2