CodeWay — Путь кода
17 subscribers
30 photos
22 videos
8 files
16 links
Здесь пишут о разработке на PHP, Laravel и всём, что с этим связано.
Автор канала — веб-разработчик Алексей Васильев.
Download Telegram
Итоги
Чтобы отвязаться от конкретного варианта сервиса, нужно:
1) формализовать выполняемые над ним операции;
2) создать на основе этого списка интерфейс;
3) переделать все обращения к сервису на обращения к соответствующим методам интерфейса;
4) детали работы с сервисом перенести в класс-реализацию интерфейса;
5) при наличии контейнера настроить в контейнере привязку реализации к интерфейсу, иначе создавать соответствующий класс и передавать в методы вручную.
Eloquent: за и против

Как и в любом серьёзном фреймворке, в Laravel есть встроенная ORM — она называется Eloquent. Предполагается, что разработчик будет использовать именно её. Но хороший специалист всегда знает границы применимости своих инструментов. Потому давайте разберёмся, какие за и против есть у этой ORM.

https://telegra.ph/Eloquent-za-i-protiv-04-23
Трудности при использовании массивов

Массив в PHP — тип крайне удобный для сколько-нибудь организованных данных: элементы можно пронумеровать или поименовать и затем пачкой обработать. В этом отношении он просто незаменим, и плюсы от использования массивов очевидны (например, лёгкий доступ к каждому элементу). Потому давайте поговорим о проблемах. В первую очередь они возникают, если массив собирается не в том же блоке кода, где затем используется, а где-то ранее (например, в другом классе).

Если говорить в целом, то массив, который собран не сейчас и/или не вами, представляет собой настоящий чёрный ящик, о котором толком ничего неизвестно, кроме того, что содержащиеся в нём элементы в принципе можно перебрать.

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

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

3. Неизвестны типы элементов (не говоря уже о значениях).
Каждый элемент нужно проверять, насколько он соответствует нужным нам критериям, и затем решать, что с ним делать, если он не подходит. Даже сравнительно дёшево узнать полный набор типов элементов без полного их перебора мы и то не можем.

4. Как следствие предыдущего, неизвестна вложенность.
Любой элемент массива может быть ещё одним массивом, и так далее до бесконечности. Обойти каждый элемент этого "дерева" — задача нетривиальная.

Что в итоге?
Я призываю вас подходить к использованию массивов с должной осторожностью и, где возможно, не применять их в чистом виде — например, применять классы-обёртки, которые обеспечивают большую предсказуемость организации данных. Как вариант — типизированные коллекции, в которые можно добавить только элементы одного типа. Или ValueObject-ы с жёстко заданными свойствами и их типами (вместо массива с несколькими именованными значениями для передачи в другой модуль проекта).
👍1
Создать свой composer-пакет: общий план

На фига козе баян Зачем вам ввязываться в это мероприятие, вы должны решить для себя сами. Я буду считать, что по какой-то причине вы уже решились взяться за пакет, но не знаете, что делать дальше. Что ж, в добрый путь. Ну и да, у меня есть уже свой пакет (https://packagist.org/packages/garmonic/laravel-features-arch), так что кое-что об этом процессе мне известно, поверьте.

https://telegra.ph/Sozdat-svoj-composer-paket-obshchij-plan-05-14

#пакеты #composer #php #laravel
👍1
Объединить нельзя дублировать

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

1. Привести код на одном из проектов к актуальным требованиям, на второй скопировать.

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

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

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

Плюсы:
- никакого дублирования.

Минусы:
- появляется зависимость вызывающего проекта от вызываемого. При проблемах со связью функционал перестанет работать.

3. Сохранить непосредственную отработку запроса на обоих проектах, а побочную (например, обработку события) на одном, со второго вызывать.

Плюсы:
- сохраняется известная независимость проектов.

Минусы:
- если побочная часть функционала так же важна для бизнес-логики, как и основная, то вариант аналогичен 2-му: частичная обработка запроса = отсутствие его обработки.

4. Вынести весь функционал на отдельный внешний сервис (API), с обоих проектов вызывать его.

Плюсы:
- отсутствие дублирования, подлинная единая ответственность;
- проекты по-прежнему независимы.

Минусы:
- логику кода придётся прилично переделать в обоих местах;
- даже если подходящий сервис уже есть, функционал нельзя будет перенести «как есть», адаптация понадобится. Если же API надо создавать с нуля, то это дополнительный геморрой свои дополнительные затраты;
- проблемы со связью никто не отменял — API точно так же может отвалиться, и тогда функционал не будет работать ни на одном из проектов.

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

Чтобы не было недопонимания: я имею в виду экземпляры или наследников стандартного PHP-класса Exception, либо Throwable. Так что же с такими объектами делать?

Ответ зависит от того, в каком вы контексте и откуда объект исключения берётся.

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

    throw new Exception('Что-то пошло не так...')

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

2. Обработать.
Вы ожидаете, что в вызываемом вами коде что-то может поломаться, и пишете, например,

    try {
// основная логика
} catch (LogicException $exception) {
// обработка исключения
}

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

try {
// основная логика
} catch (LogicException $exception) {
return response()->json(['success'=>false, 'message'=>'Переданные данные ошибочны'], 400);
}

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

try {
// основная логика
} catch (LogicException $exception) {
throw new CustomException($exception->getMessage()); // вариант 1
//или
throw new CustomException('Текст ошибки', 0, $exception); // вариант 2
}

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

    // Может быть выброшено исключение, не нужно обрабатывать, пусть валится в 500-ю
$result = someAction();

Так что смотрите по ситуации и выбирайте тот вариант, который для неё наиболее уместен.
👍2
Когда и как делить источник данных на два

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

Как возникает проблема
На этапе проектирования и начальной стадии существования проекта одного хранилища данных каждого типа вполне достаточно: скажем одна MariaDB-база, инстанс Redis для кэша и файловое хранилище типа S3. Ничего лишнего, удобно и понятно, как с этим всем работать. Но, если вы всё правильно делаете и пользователи довольны вашим сервисом, их число постепенно растёт. Запросов к вашим ресурсам в секунду становится всё больше — например, начинали с 5, потом стало 10, 15, 20, 30, наконец, 50. Через день заглядывает хмурый сисадмин. Он бурчит, что сервак не справляется, и надо с этим что-то делать. И с каждым разом его голос звучит всё более мрачно.

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

Адаптация кода
По поводу инфраструктуры разобрались, а как же код? Ведь он был написан с расчётом на один источник данных. Его придётся адаптировать под новые реалии. Если обращения к источнику данных собраны в ряде компактных классов (типа *Repository), то доделать их будет легко — указать в них другое соединение или файловую систему. Возможно, просто поменять где-то привязки в контейнере. Иначе придётся перерывать весь проект, чтобы провести такие изменения. Но так или иначе должно быть введено правило: читать из источника "для чтения", писать в источник "для записи".

Что с архитектурой?
С точки зрения архитектуры полезным будет применение подхода CQRS, в основе которого — принципиальное разделение потоков получения данных и изменения данных. Для этого создаются разные наборы классов со своей логикой. Возможно, про CQRS я напишу ещё отдельный пост, а пока можно прочитать, к примеру, эту статью.

Итоги
1. Делить хранилище данных надо тогда, когда нагрузка на него потенциально приближается к критической (ну или несколько раньше).
2. В первую очередь стоит разграничить запись и чтение, и распределить ресурсы соответствующих источников в зависимости от нагрузки на соответствующий поток.
3. После изменения инфраструктуры потребуется адаптация кода по правилу "читать из источника на чтение, писать в источник на запись".
4. При решении архитектурного вопроса в связи с адаптацией стоит рассмотреть вариант с CQRS — насколько он применим и реализуем в вашем случае.
👍1
Когда пригодятся типы-перечисления

Вопрос стал особенно актуальным в связи с появлением таких типов в PHP 8.1. Теперь они нативно поддерживаются языком.
Короткий ответ: перечисления применимы везде, где значение переменной должно быть выбрано из закрытого (и достаточно ограниченного) списка.

Вот пример перечисления для цветов светофора. Кроме как "красный" (TrafficLightColor::Red), "жёлтый" (TrafficLightColor::Yellow), "зелёный" (TrafficLightColor::Green), ничего выбрать невозможно. Это может быть очень удобно.

<?php
enum TrafficLightColor
{
case Red;
case Yellow;
case Green;
}
?>

Теперь поясню подробнее.
В разработке то и дело приходится иметь дело с такими специфическими данными, типа статусов. Каково может быть состояние письма, например? "Подготовлено", "Отправлено", "Не удалось отправить". Четвёртого не дано. Или, как вариант, источники данных для приходящих картинок. Вряд ли их будет даже десять. И вот что со всем этим делать?

Глобальных проблем тут две: валидация и понимание происходящего.

По поводу валидации. Если мы храним значения перечислимого типа как строки или числа, мы вынуждены каждое приходящее в соответствующую переменную значение проверять — укладывается ли оно в наше перечисление? А что, если просто случайно совпало, а строка/число означает совсем другое? Обычная проблема использования скалярных типов не по назначению. В таких случаях переходят к ValueObject-ам (в которых и пишется валидация), но Enum-ы куда удобнее для работы с перечислениями.

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

<?php
const MAIL_STATUSES = ['prepared', 'sent', 'sending_failed'];
//...
$result = Mail::send($letter);
if($result->status == 'send') { // Всего одна буква разницы, а условие уже работает неправильно
return response()->json(['message'=>'Письмо отправлено']);
} else {
return response()->json(['message'=>'Ошибки при отправке']);
}

И вот, почта уходит, а сообщение говорит об обратном. Были бы эти статусы вариантами Enum, то даже редактор уже ткнул бы разработчика в ошибку, указав, что такого варианта нет.

Вместо вывода: если вы уже перешли на PHP 8.1 и работаете с подобными значениями из ограниченного списка, присмотритесь к Enum.
К вопросу о доступе

Практически для каждой системы актуален вопрос контроля доступа к информации и к манипуляциям с ней. Предположим для простоты, что речь идёт о полноценном сайте — с API немного другая история, но глобальных отличий мы не найдём.

В конечном счёте, мы должны уметь отвечать на такой вопрос: может ли пользователь с такими-то данными сеанса выполнить такое-то действие? Что делать в случае отрицательного ответа — вопрос вторичный и зависит от требований заказчика. Чаще всего либо ставится редирект на другую страницу (главную, например), либо отдаётся 403 статус/страница ошибки с указанием, что не так.

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

1. Авторизован ли пользователь.
Самая простая и понятная тема. Такая проверка присутствует если не на всех сайтах, то на 99% точно. Например, может быть закрыта страница профиля или возможность комментировать без авторизации.

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

3. Относится ли пользователь к определённой группе (имеет определённую роль).
Такое применяется на более сложных сайтах, где много пользователей с разными правами (user, manager, admin). Подобная система требует заведения дополнительных таблиц в БД и модуля контроля/изменения принадлежности пользователя к группе. Возможно, его придётся написать. Даже если этот модуль берётся готовым, он требует настройки путём указания ответов на такие вопросы:
- Может ли быть у пользователя больше одной роли?
- Как именно назначаются первые роли?
- Кто вправе менять/добавлять роль пользователя и по каким правилам?
- Какая логика должна выполняться при изменении/добавлении роли?

4. Имеет ли пользователь определённое разрешение?
Это ещё более сложная история. Разрешения делаются максимально дробными (буквально, отдельное на каждое возможное действие) и заводятся в виде отдельных сущностей. Чаще всего система разрешений соседствует с системой групп. Например, разрешения привязываются к группам, и список тех разрешений, которые есть у пользователя, получаются на основе списка групп, в которых он состоит. Но встречаются такие случаи, когда сверх разрешений по группам пользователю могут выдаваться ещё какие-то персональные, и их тоже надо учитывать при контроле доступа.

5. Соответствуют ли данные запроса некоторым требованиям?
Я здесь имею в виду метаданные — IP-адрес, заголовки, куки и тому подобное. Например, определять страну или регион по IP-адресу и редиректить на соответствующий раздел (ну или показывать, что в тех местах мы пока не работаем).

Если вы знаете ещё о каких-то проверяемых при контроле доступа условиях, напишите об этом в комментариях.
👍1
ArchDays Recap: впечатления от митапа

Митап прошёл в очном формате 27 июля в БЦ "Академик" на проспекте Вернадского. Я присутствовал там лично и хочу поделиться впечатлениями.

Финальная программа митапа выглядела примерно так:

1. Алексей Маликов. О чём я не думал три года назад. (в докладе о распиле монолита)
2. Сергей Баранов. Многоликий DDD.
3. Евгений Пешков. Про агрегаты.

Митап был задуман как нечто, идущее "по следам" прошедших конференций по архитектуре ArchDays. Собственно, это было заложено в самом названии: Recap означает "резюме, краткое повторение". И замысел этот очень сказался на двух докладах из трёх (1-м и 3-м), что в моём случае было скорее к худшему. Ведь конференций я в глаза не видел, а просто заинтересовался мероприятием самим по себе. Потому ценность третьего выступления, на мой взгляд, была сильно ниже — Евгений только ответил на частый вопрос из поступавших ему после прошлого выступления (про границы агрегатов — я бы назвал их классами сущностей), после чего предложил задавать новые. Хотя мысль о том, что в классе-агрегате в первую очередь надо обращать внимание на консистентность данных, потом на читаемость кода, и лишь потом на производительность, прозвучала очень ясно и я отметил её для себя.

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

Доклад Сергея Баранова на фоне других выглядел подлинным "основным блюдом" и мог бы украсить любую подходящую конференцию. Докладчику удалось полноценно погрузить аудиторию в одну из практик DDD (Domain Driven Design) — ивент-шторминг. Про DDD мои представления пока довольно поверхностны и ограничиваются минимальной теорией, так что мне было интересно лучше узнать детали этой концепции. Если вкратце, то ивент-шторминг — это некое собрание бизнес-экспертов с техническими с целью выработки общего понимания бизнес-процессов компании. Максимально простым и понятным способом, буквально на стикерах. События, команды и правила пишутся на стикерах разных цветов, образовывается большая карта деятельности бизнеса, которая затем делится на ряд ограниченных контекстов (термин DDD). Хорошее понимание системы приносит пользу буквально каждому участнику IT-процессов, от тестировщика до CIO (IT-директора). Сергей отдельно остановился на том, в чём именно эта польза заключается для того или иного специалиста.

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

#code_way #archdays_recap #ddd
Ряд фотографий с ArchDays Recap
Зачем писать консольные команды

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

Этот пост я разделю на две части: собственно, "зачем" и "как". Для начала поговорим про "зачем". В части "как" (в следующем посте) будет пример написания простой консольной команды для Laravel — как вы помните, я специализируюсь именно на этом фреймворке. Общие идеи будут вам полезны в любом случае, за деталями обратитесь к документации своего фреймворка.

https://telegra.ph/Zachem-pisat-konsolnye-komandy-08-16

#php #laravel #консоль
Как писать консольные команды

Зачем всё это нужно, мы рассмотрели в предыдущем посте, теперь перейдём к техническим деталям.
Разумеется, вам пригодится документация по консольным командам для вашего фреймворка. Например, для Laravel её можно найти тут:
https://laravel.com/docs/9.x/artisan.

Начну с общих замечаний, которые могут быть вам полезны в любом случае.
1. Консольная команда — это класс, а выполняться будет определённый метод этого класса. Соответственно, применимы все принципы ООП, о которых вы только могли подумать. Например, можно вынести отдельные части функционала в другие методы и вызывать из основного. Зависимости, определённые в контейнере, тоже вполне можно интегрировать привычным способом.
2. Всё, относящееся к специфике консольной команды, описывается в ней же. Я имею в виду, текстовое описание, аргументы и тому подобное.
3. Никакие специфически браузерные вещи (определение каких-то параметров через куки или заголовки) не будут доступны.

Теперь приступим к написанию примерной консольной команды. Для генерации начального кода в Laravel понадобится выполнить такую команду:
php artisan make:command {имяКлассаКоманды}
после чего в папке app/Console появится файл примерно следующего вида:
https://github.com/codewayblog/console1/blob/df4f9d0181f320c4d586f9dc000085d625724b8a/SomeCommand.php

Естественно, мы должны знать, какую цель преследует наша консольная команда, и назвать её соответственным образом. SomeCommand я выбрал просто для примера. Предположим, мы хотим собирать некоторую статистику сделок данного пользователя и посылать её ему на почту. Предположим также, что такой функционал у нас уже есть, но вызывается самим пользователем из графического интерфейса. Вот наш примерный файл с операцией:
https://github.com/codewayblog/console1/blob/main/SendUserOrderStats.php
Всё, что ему нужно — наличие действительного пользователя. Отлично, теперь можем заняться классом команды.

Для начала укажем описание в свойстве $description
Отправляет сводные данные по последним сделкам пользователя
и заголовок команды в свойстве $signature
send:order_stats {user}
Первая часть до пробела — то, как мы хотим вызывать команду, затем в фигурных скобках — параметр, данные пользователя, которые нам нужны. Не будем мудрствовать лукаво и станем исходить из email — как-никак, самое универсальное поле.

В конструктор ничего не будем добавлять, наше внимание сосредоточится на методе handle(), который и будет в конечном счёте вызываться.
Когда у нас будет e-mail, то останется только написать:
$user = User::where('email', $email)->first();
if($user instanceof User) {
$action = new SendUserOrderStats($user);
$action->execute();
}
... и дело в шляпе. Ну ладно, надо будет добавить пару информационных сообщений — об успешности операции или об отсутствии пользователя, но идея такая. Добавим этот код в handle().
IDE начинает ругаться на то, что $email не инициализирован. Разумеется, мы должны получить его из аргумента команды.
$email = $this->argument('user');
Вот в таком виде
https://github.com/codewayblog/console1/blob/c5f8b9aec83437c4502d732d8b1cf7732c2f1ace/SomeCommand.php
код уже будет работать. Осталось его немного облагородить.

Добавляем необходимые сообщения, и наша консольная команда готова.
https://github.com/codewayblog/console1/blob/main/SomeCommand.php

Запускать её надо будет примерно так:
php artisan send:order_stats test@test.com

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

#php #laravel #консоль
👍1
Некоторые особенности MongoDB

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

1. Коллекции.
Базы данных в MongoDB делятся не на таблицы, а на коллекции — по сути дела, одномерные массивы данных, состоящие из отдельных записей.

2. Формат данных.
Каждая запись в коллекции — это JSON-документ. Формально его формат называется BSON, но с точки зрения работы с ним разницы никакой, можете считать, что это JSON. Со всеми вытекающими из этого последствиями — совершенно произвольный набор полей (что положите, то и будет) и произвольной же вложенностью. Подчинённые данные для элементов удобно хранить как раз в таких вложенных наборах — к примеру, ссылки на посты, связанные с текущим.

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

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

5. Поиск.
В поиске тоже есть своя специфика. Например, запрос на выборку, обращённый напрямую к БД, тоже задаётся в виде JSON. Например, такая запись
{post_id: 4},
или в полном виде
db.comments.find({post_id: 4}),
равносильна SQL-запросу типа
SELECT * FROM `comments` WHERE `post_id` = '4'.
Также MongoDB по умолчанию не делает никакой разницы между полем со значением NULL и отсутствующим в этой записи вовсе, потому запрос
{category: null}
отбёрет не только те, у которых поле category содержит null, но и те, в которых нет такого поля. Различить их можно, применяя специальные операторы.
Так, запрос
{category: {$type: 'null'}}
выберет только category = null,
а запрос на существование
{category: {$exists: false}}
вернёт все записи, где нет поля category.

Вообще, MongoDB — это довольно интересная система, советую поизучать её практически. А я на сегодня закончу, оставив напоследок ссылку на документацию по БД: https://www.mongodb.com/docs/.
👍2
Как работают очереди в Laravel

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

Рассмотрим вкратце:
- для чего нужны очереди
- как произвести первоначальную настройку проекта
- как создать класс задания
- как вызвать задание в нужном месте
- как работает процесс обработки заданий

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

Как произвести первоначальную настройку проекта
Для начала нам нужен драйвер очередей, он указывается в файле config/queue.php (пример моего файла тут: https://github.com/codewayblog/laravel-queues-how/blob/master/queue.php), также в .env-файле стоит указать дефолтный драйвер (ключ QUEUE_CONNECTION). Вот мой env-файл: https://github.com/codewayblog/laravel-queues-how/blob/master/.env. Как видите, я использовал RabbitMQ. Он нуждается в отдельной настройке, но для пробы можно обойтись базой данных (драйвер database).
Подробнее о настройке драйверов очередей можно почитать в документации Laravel: https://laravel.com/docs/9.x/queues#driver-prerequisites.

Как создать класс задания
Выполним консольную команду php artisan make:job {имя-класса}. В папке app/Jobs создатся шаблонный класс с указанным именем и методом handle(). В конструкторе привязываем в класс данные, которые нужны для выполнения задания, в методе handle() — ту логику, которая нужна для собственно выполнения. Стоит учесть, что выполняться задача будет в консольном режиме, поэтому всякие браузерные штуки будут ей недоступны — их надо зашить ещё в момент создания.
Пример класса задания: https://github.com/codewayblog/laravel-queues-how/blob/master/SomeJob.php

Как вызвать задание в нужном месте
Обращаемся к статическому методу dispatch() класса команды. Передаём туда все те же аргументы, которые требуются в конструкторе — они будут туда проброшены внутри dispatch().
Пример вызова: https://github.com/codewayblog/laravel-queues-how/blob/dfeae6f24ce66a7aad1e99562e987b9c17aef376/MainController.php#L17

Как работает процесс обработки заданий
Во время вызова класс задания создаётся, сохраняет нужные данные и сериализуется. В таком упакованном виде задание помещается в очередь (как именно, зависит от драйвера очередей) и там ждёт своего часа.
Обработка заданий запускается с помощью команды php artisan queue:work. В цикле обработчик забирает задание, распаковывает его обратно в экземпляр класса, и вызывает метод handle().

Так в общих чертах устроены очереди в Laravel. Интересный и нужный функционал, на мой взгляд.
👍1