Доменные примитивы - это небольшие Value Object-ы, которые инкапсулируют важное бизнес-значение и правила валидации.
Зачем нужны?
Гарантия корректности данных - нельзя создать Email без проверки.
Правила в одном месте - валидация email не расползается по проекту.
Устойчивость к ошибкам - невозможно случайно передать “строку, которая должна быть email”, но ей не является.
Когда важные значения передаются как обычные строки - например email или UUID — мы создаём себе лишнюю работу.
Пример классического подхода:
Каждый раз, в каждом месте, где проходит значение, нужно:
- проверить, что поле не пустое
- провалидировать формат email
- нормализовать (обрезать пробелы, привести к lowercase)
- убедиться, что оно прошло все фильтры
- повторить проверки в сервисе
- повторить проверки в репозитории перед записью
Любая пропущенная проверка = баг.
Решение - доменные примитивы
Это email пользователя, который мы получаем из формы и передаём дальше.
Валидируем и нормализуем его теперь только один раз — в конструкторе.
Пример использования
✔️ дальше по коду email уже гарантированно валиден
✔️ не нужно повторять проверки
✔️ весь код упрощается
Это уникальный идентификатор события (Event), который используется в системе повсеместно.
Раньше приходилось валидировать UUID в контроллере, сервисе, репозитории — теперь только один раз.
Пример использования
✔️ дальше по системе UUID всегда валидный
✔️ нет повторной валидации
✔️ нет риска случайно передать мусор
Зачем нужны?
Гарантия корректности данных - нельзя создать Email без проверки.
Правила в одном месте - валидация email не расползается по проекту.
Устойчивость к ошибкам - невозможно случайно передать “строку, которая должна быть email”, но ей не является.
Когда важные значения передаются как обычные строки - например email или UUID — мы создаём себе лишнюю работу.
Пример классического подхода:
$email = $_POST['email'];
$service->registerUser($email);
Каждый раз, в каждом месте, где проходит значение, нужно:
- проверить, что поле не пустое
- провалидировать формат email
- нормализовать (обрезать пробелы, привести к lowercase)
- убедиться, что оно прошло все фильтры
- повторить проверки в сервисе
- повторить проверки в репозитории перед записью
Любая пропущенная проверка = баг.
Решение - доменные примитивы
Это email пользователя, который мы получаем из формы и передаём дальше.
Валидируем и нормализуем его теперь только один раз — в конструкторе.
final class Email
{
private string $value;
public function __construct(string $value)
{
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException("Invalid email: $value");
}
$this->value = strtolower(trim($value));
// Здесь можно добавить свои проверки:
// - запрещённые домены
// - длина
// - бизнес-правила
}
// остальные методы...
}
Пример использования
$emailInput = $_POST['email'] ?? null;
if (!$emailInput) {
throw new RuntimeException("Email is required.");
}
try {
$email = new Email($emailInput); // валидация и нормализация происходят здесь
} catch (InvalidArgumentException $e) {
// пример обработки ошибок
// можно вывести сообщение пользователю или записать в лог
die("Ошибка: " . $e->getMessage());
}
$service->registerUser($email);
✔️ дальше по коду email уже гарантированно валиден
✔️ не нужно повторять проверки
✔️ весь код упрощается
Это уникальный идентификатор события (Event), который используется в системе повсеместно.
Раньше приходилось валидировать UUID в контроллере, сервисе, репозитории — теперь только один раз.
final class EventUuid
{
private string $value;
public function __construct(string $value)
{
if (!preg_match('/^[0-9a-fA-F-]{36}$/', $value)) {
throw new InvalidArgumentException("Invalid UUID: $value");
}
$this->value = strtolower($value);
// Здесь можно добавить свои проверки:
// - версия UUID
// - запрещённые значения
// - проверка на корректность формата
}
// остальные методы...
}
Пример использования
$uuidInput = $_GET['event_uuid'] ?? null;
if (!$uuidInput) {
throw new RuntimeException("Event UUID is required.");
}
try {
$uuid = new EventUuid($uuidInput); // строгая проверка прямо здесь
} catch (InvalidArgumentException $e) {
die("Ошибка UUID: " . $e->getMessage());
}
$eventService->load($uuid);
✔️ дальше по системе UUID всегда валидный
✔️ нет повторной валидации
✔️ нет риска случайно передать мусор
❤2🍾1💋1
🔐 50 000+ роутеров ASUS взломаны
Исследователи выявили продолжающуюся кампанию по массовому взлому домашних и офисных маршрутизаторов ASUS. Атаки используют набор известных уязвимостей в прошивке ASUSWRT — и превращают роутеры в узлы скрытой прокси-сети.
Как ломают:
Злоумышленники эксплуатируют сразу несколько n-day уязвимостей
После эксплуатации уязвимости на устройство загружается вредоносная конфигурация, злоумышленники:
География заражений
Тайвань и ЮВА
Что делать владельцам ASUS прямо сейчас
Исследователи выявили продолжающуюся кампанию по массовому взлому домашних и офисных маршрутизаторов ASUS. Атаки используют набор известных уязвимостей в прошивке ASUSWRT — и превращают роутеры в узлы скрытой прокси-сети.
Как ломают:
Злоумышленники эксплуатируют сразу несколько n-day уязвимостей
CVE-2023-41345 — несанитизированный ввод → выполнение произвольного кода.
CVE-2023-41346 — обход аутентификации в веб-интерфейсе.
CVE-2023-41347 / 41348 — уязвимости в API прошивки, позволяющие получить root-доступ.
CVE-2024-12912 — RCE в компонентах ASUSWRT.
CVE-2025-2492 — свежая брешь, используемая в последних волнах атак.
После эксплуатации уязвимости на устройство загружается вредоносная конфигурация, злоумышленники:
открывают скрытый SSH-порт (53282),
добавляют свой SSH-ключ,
создают самоподписанный TLS-сертификат на 100 лет,
превращают роутер в прокси-узел для командно-контрольной инфраструктуры.
География заражений
Тайвань и ЮВА
Что делать владельцам ASUS прямо сейчас
Обновить прошивку до последней версии.
Проверить SSH и удалить неизвестные ключи.
Отключить удалённый доступ по WAN (SSH/HTTP/HTTPS).
При подозрении на взлом — полный сброс NVRAM и заводские настройки.
❤1😱1🍾1💋1
PHP оператор match - это современная, более строгая и выразительная альтернатива switch. В PHP с версии 8.0
Основные преимущества:
Возвращает значение — это выражение, а не оператор.
Строгое сравнение (===) — в отличие от switch.
Не требует break — исключает распространённые ошибки.
Компактный синтаксис — меньше “шумного” кода.
Может работать с несколькими значениями в одной ветке.
Удобен при:
простой маршрутизации входящих данных;
замене длинных if/elseif цепочек;
преобразовании значений одной формы в другую;
выборе обработчиков по типу/статусу.
Пример 1: замена switch
Было:
Стало:
Пример 2: строгие сравнения
switch бы выбрал 1 => «число» из-за нестрогого сравнения.
Пример 3: несколько значений в одной ветке
Пример 4: выполнение логики в блоке
Можно возвращать результат выражения:
Пример 5: match без значения (аналог switch(true))
Пример 6: выброс исключения
Пример 7: Карта констант/enum (идеально для PHP 8.1 enum)
match лучше не использовать, когда требуется сложная логика в ветках (много кода, побочные эффекты), когда нужно поведение switch с fallthrough*, когда сравнение должно быть нестрогим ==, когда порядок проверки условий важен или зависит от вычислений, а также когда результат не нужно возвращать — в таких случаях обычные if/elseif или switch будут проще и понятнее.
*Fallthrough - это поведение оператора switch, при котором выполнение проваливается из одного case в следующий, если не стоит break.
Если $value = 1, то выведет:
То есть после совпадения case 1 управление продолжит выполняться в case 2.
В match такое невозможно: каждая ветка — отдельное выражение без перехода на следующую.
Основные преимущества:
Возвращает значение — это выражение, а не оператор.
Строгое сравнение (===) — в отличие от switch.
Не требует break — исключает распространённые ошибки.
Компактный синтаксис — меньше “шумного” кода.
Может работать с несколькими значениями в одной ветке.
Удобен при:
простой маршрутизации входящих данных;
замене длинных if/elseif цепочек;
преобразовании значений одной формы в другую;
выборе обработчиков по типу/статусу.
$result = match ($value) {
1 => "one",
2 => "two",
default => "unknown",
};Пример 1: замена switch
Было:
switch ($status) {
case 'new':
$text = 'Новая заявка';
break;
case 'processing':
$text = 'В обработке';
break;
default:
$text = 'Неизвестно';
}Стало:
$text = match ($status) {
'new' => 'Новая заявка',
'processing' => 'В обработке',
default => 'Неизвестно',
};Пример 2: строгие сравнения
$value = "1";
$result = match ($value) {
1 => "число",
"1" => "строка",
};
// $result = "строка"
switch бы выбрал 1 => «число» из-за нестрогого сравнения.
Пример 3: несколько значений в одной ветке
$type = "jpg";
$category = match ($type) {
'jpg', 'png', 'gif' => 'image',
'mp4', 'mov' => 'video',
default => 'unknown',
};
Пример 4: выполнение логики в блоке
Можно возвращать результат выражения:
$price = 150;
$discount = match (true) {
$price < 100 => 0,
$price < 200 => 10,
default => 20,
};
Пример 5: match без значения (аналог switch(true))
$result = match (true) {
$a > 10 => 'больше 10',
$a > 5 => 'больше 5',
default => 'меньше или равно 5',
};Пример 6: выброс исключения
$action = match ($route) {
'home' => fn() => showHome(),
'login' => fn() => showLogin(),
default => throw new Exception("Маршрут не найден"),
};Пример 7: Карта констант/enum (идеально для PHP 8.1 enum)
enum Role { case USER; case ADMIN; }
function label(Role $role): string
{
return match ($role) {
Role::USER => 'Пользователь',
Role::ADMIN => 'Администратор',
};
}match лучше не использовать, когда требуется сложная логика в ветках (много кода, побочные эффекты), когда нужно поведение switch с fallthrough*, когда сравнение должно быть нестрогим ==, когда порядок проверки условий важен или зависит от вычислений, а также когда результат не нужно возвращать — в таких случаях обычные if/elseif или switch будут проще и понятнее.
*Fallthrough - это поведение оператора switch, при котором выполнение проваливается из одного case в следующий, если не стоит break.
switch ($value) {
case 1:
echo "one";
// нет break — происходит fallthrough
case 2:
echo "two";
break;
}Если $value = 1, то выведет:
one
two
То есть после совпадения case 1 управление продолжит выполняться в case 2.
В match такое невозможно: каждая ветка — отдельное выражение без перехода на следующую.
💋2❤1
Основные классы временной сложности алгоритмов
1. O(1) - супербыстро (лучшее, что может быть)
2. O(log n) - очень быстро (бинарный поиск, деревья)
3. O(n) - нормально / быстро (один проход по массиву)
4. O(n log n) - средне / довольно быстро (умные сортировки)
5. O(n²) - медленно (вложенные циклы, пузырёк)
6. O(n³) - очень медленно (тройные циклы)
7. O(2ⁿ) - ужасно медленно (перебор подмножеств)
8. O(n!) - катастрофически медленно (перестановки, полный перебор)
Примеры:
O(1) - Константная
Алгоритм работает за одинаковое время, независимо от размера входных данных.
Доступ к элементу массива по индексу:
O(log n) - Логарифмическая
Каждый шаг уменьшает проблему в два раза. Очень быстрый рост.
Бинарный поиск в отсортированном массиве:
O(n) - Линейная
Время растёт пропорционально количеству элементов. Проходим все элементы один раз.
O(n log n) - Линейно-логарифмическая
Чаще всего в «умных» сортировках. Рекурсивная сортировка слиянием
O(n²) - Квадратичная
Каждый элемент сравнивается с каждым → вложенные циклы.
Пузырьковая сортировка
O(n³) - Кубическая
Три вложенных цикла. Обычное умножение матриц.
O(2ⁿ) - Экспоненциальная
Количество операций удваивается с каждым n.
Перебор всех подмножеств:
O(n!) - Факториальная
Самые медленные - все перестановки.
Больше примеров: https://zalki-lab.ru/time-complexity-of-algorithms/
1. O(1) - супербыстро (лучшее, что может быть)
2. O(log n) - очень быстро (бинарный поиск, деревья)
3. O(n) - нормально / быстро (один проход по массиву)
4. O(n log n) - средне / довольно быстро (умные сортировки)
5. O(n²) - медленно (вложенные циклы, пузырёк)
6. O(n³) - очень медленно (тройные циклы)
7. O(2ⁿ) - ужасно медленно (перебор подмножеств)
8. O(n!) - катастрофически медленно (перестановки, полный перебор)
Примеры:
O(1) - Константная
Алгоритм работает за одинаковое время, независимо от размера входных данных.
Доступ к элементу массива по индексу:
$arr = [10, 20, 30, 40];
echo $arr[2]; // O(1)
O(log n) - Логарифмическая
Каждый шаг уменьшает проблему в два раза. Очень быстрый рост.
Бинарный поиск в отсортированном массиве:
function binarySearch($arr, $target) {
$l = 0;
$r = count($arr) - 1;
while ($l <= $r) {
$mid = intdiv($l + $r, 2);
if ($arr[$mid] == $target) return $mid;
if ($arr[$mid] < $target) {
$l = $mid + 1;
} else {
$r = $mid - 1;
}
}
return -1;
}O(n) - Линейная
Время растёт пропорционально количеству элементов. Проходим все элементы один раз.
function linearSearch($arr, $target) {
foreach ($arr as $item) {
if ($item === $target) return true;
}
return false;
}O(n log n) - Линейно-логарифмическая
Чаще всего в «умных» сортировках. Рекурсивная сортировка слиянием
function mergeSort($arr) {
if (count($arr) <= 1) return $arr;
$mid = intdiv(count($arr), 2);
$left = array_slice($arr, 0, $mid);
$right = array_slice($arr, $mid);
return merge(
mergeSort($left),
mergeSort($right)
);
}
function merge($left, $right) {
$result = [];
while (count($left) && count($right)) {
if ($left[0] < $right[0]) {
$result[] = array_shift($left);
} else {
$result[] = array_shift($right);
}
}
return array_merge($result, $left, $right);
}O(n²) - Квадратичная
Каждый элемент сравнивается с каждым → вложенные циклы.
Пузырьковая сортировка
function bubbleSort($arr) {
$n = count($arr);
for ($i = 0; $i < $n; $i++) {
for ($j = 0; $j < $n - 1; $j++) {
if ($arr[$j] > $arr[$j + 1]) {
[$arr[$j], $arr[$j + 1]] = [$arr[$j + 1], $arr[$j]];
}
}
}
return $arr;
}O(n³) - Кубическая
Три вложенных цикла. Обычное умножение матриц.
function multiplyMatrices($A, $B) {
$n = count($A);
$C = array_fill(0, $n, array_fill(0, $n, 0));
for ($i = 0; $i < $n; $i++) {
for ($j = 0; $j < $n; $j++) {
for ($k = 0; $k < $n; $k++) {
$C[$i][$j] += $A[$i][$k] * $B[$k][$j];
}
}
}
return $C;
}O(2ⁿ) - Экспоненциальная
Количество операций удваивается с каждым n.
Перебор всех подмножеств:
function subsets($arr) {
if (!$arr) return [[]];
$first = array_shift($arr);
$rest = subsets($arr);
$withFirst = [];
foreach ($rest as $subset) {
$withFirst[] = array_merge([$first], $subset);
}
return array_merge($rest, $withFirst);
}O(n!) - Факториальная
Самые медленные - все перестановки.
function permutations($arr) {
if (count($arr) <= 1) return [$arr];
$result = [];
foreach ($arr as $i => $val) {
$rest = $arr;
unset($rest[$i]);
$rest = array_values($rest);
foreach (permutations($rest) as $perm) {
$result[] = array_merge([$val], $perm);
}
}
return $result;
}Больше примеров: https://zalki-lab.ru/time-complexity-of-algorithms/
Zalki-Lab
Основные классы временной сложности алгоритмов - Zalki-Lab
Временные сложности алгоритмов - это способ оценить, как быстро растёт время выполнения кода, когда увеличивается количество данных.
❤1🍾1💋1
Вот собственно и код для тестов и понимания, почему вложенные циклы - зло:
<?php
// Increase the size for clearer speed difference
$usersCount = 5000;
$ordersCount = 5000;
// Generate test users
$users = [];
for ($i = 1; $i <= $usersCount; $i++) {
$users[] = ['id' => $i];
}
// Generate random test orders
$orders = [];
for ($i = 1; $i <= $ordersCount; $i++) {
$orders[] = [
'id' => $i,
'user_id' => rand(1, $usersCount)
];
}
// -------------------------------
// ❌ BAD: Nested loops (O(n²))
// -------------------------------
$start1 = microtime(true);
$resultBad = [];
foreach ($users as $user) {
foreach ($orders as $order) {
if ($order['user_id'] === $user['id']) {
$resultBad[$user['id']][] = $order;
}
}
}
$timeBad = microtime(true) - $start1;
// -------------------------------
// ✔️ GOOD: Indexed map (O(n))
// -------------------------------
$start2 = microtime(true);
$ordersByUser = [];
foreach ($orders as $order) {
$ordersByUser[$order['user_id']][] = $order;
}
$resultGood = [];
foreach ($users as $user) {
$resultGood[$user['id']] = $ordersByUser[$user['id']] ?? [];
}
$timeGood = microtime(true) - $start2;
// -------------------------------
// OUTPUT RESULTS
// -------------------------------
echo "BAD O(n²) nested loops: " . round($timeBad, 4) . " sec\n";
echo "GOOD O(n) indexed map: " . round($timeGood, 4) . " sec\n";
❤1🍾1💋1
💳 PCI DSS или как хранить платежную информацию банковских карт
PCI DSS (Payment Card Industry Data Security Standard) — международный стандарт безопасности данных платёжных карт. Его соблюдение обязательно для всех, кто принимает, передаёт, обрабатывает или хранит данные банковских карт.
Для чего нужен PCI DSS?
Защищает данные держателей карт от кражи.
Снижает риск мошенничества.
Требуется платёжными системами (Visa, Mastercard и др.).
Является обязательным для интернет-магазинов, эквайринга, процессинговых центров, платёжных сервисов.
Что можно хранить в открытом виде
(должно быть защищено организационными мерами, но не требует криптографического шифрования):
Имя держателя карты (Cardholder Name)
Номер карты (PAN) — только в маскированном виде (например: 4111 11** **** 1111)
Срок действия карты (Expiration Date)
Тип карты (Visa/Mastercard и т.п.)
Что нельзя хранить вообще
(хранение запрещено после авторизации):
CVV/CVC/CVV2
PIN-код
Данные PIN-блока
Track 1 и Track 2 данные магнитной полосы
Полней PAN в открытом виде (если не зашифрован)
Что обязательно шифровать
(если такие данные хранятся — исключительно в зашифрованном виде):
Полный номер карты (PAN / Primary Account Number)
Должен быть защищён криптографией, токенизацией или хешированием.
Любые данные держателя карты, которые позволяют однозначно идентифицировать карту, если они хранятся вместе с PAN.
Передача карточных данных по сети — всегда только через защищённые каналы (TLS 1.2+).
PCI DSS (Payment Card Industry Data Security Standard) — международный стандарт безопасности данных платёжных карт. Его соблюдение обязательно для всех, кто принимает, передаёт, обрабатывает или хранит данные банковских карт.
Для чего нужен PCI DSS?
Защищает данные держателей карт от кражи.
Снижает риск мошенничества.
Требуется платёжными системами (Visa, Mastercard и др.).
Является обязательным для интернет-магазинов, эквайринга, процессинговых центров, платёжных сервисов.
Что можно хранить в открытом виде
(должно быть защищено организационными мерами, но не требует криптографического шифрования):
Имя держателя карты (Cardholder Name)
Номер карты (PAN) — только в маскированном виде (например: 4111 11** **** 1111)
Срок действия карты (Expiration Date)
Тип карты (Visa/Mastercard и т.п.)
Что нельзя хранить вообще
(хранение запрещено после авторизации):
CVV/CVC/CVV2
PIN-код
Данные PIN-блока
Track 1 и Track 2 данные магнитной полосы
Полней PAN в открытом виде (если не зашифрован)
Что обязательно шифровать
(если такие данные хранятся — исключительно в зашифрованном виде):
Полный номер карты (PAN / Primary Account Number)
Должен быть защищён криптографией, токенизацией или хешированием.
Любые данные держателя карты, которые позволяют однозначно идентифицировать карту, если они хранятся вместе с PAN.
Передача карточных данных по сети — всегда только через защищённые каналы (TLS 1.2+).
❤1🎉1💋1
Cloudflare отразил DDoS атаку мощностью в 29.7 Tbps
https://blog.cloudflare.com/ddos-threat-report-2025-q3/
https://blog.cloudflare.com/ddos-threat-report-2025-q3/
❤2😱1💯1
Критическая уязвимость в Next.js
Недавно была выявлена критическая уязвимость CVE-2025-66478, затрагивающая проекты на Next.js, использующие React Server Components (RSC). Проблема возникла из-за небезопасной обработки протокола RSC («Flight»), что позволяет злоумышленнику отправить специально сформированный запрос и добиться удалённого выполнения произвольного кода (RCE) на сервере - без аутентификации.
Затронутые версии
Next.js 15.x
Next.js 16.x
Canary-версии 14.x начиная с 14.3.0-canary.77 и выше
Проблема устранена в патч-релизах:
15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7
16.0.7
Недавно была выявлена критическая уязвимость CVE-2025-66478, затрагивающая проекты на Next.js, использующие React Server Components (RSC). Проблема возникла из-за небезопасной обработки протокола RSC («Flight»), что позволяет злоумышленнику отправить специально сформированный запрос и добиться удалённого выполнения произвольного кода (RCE) на сервере - без аутентификации.
Затронутые версии
Next.js 15.x
Next.js 16.x
Canary-версии 14.x начиная с 14.3.0-canary.77 и выше
Проблема устранена в патч-релизах:
15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7
16.0.7
Чеклист SRP - проверка сервисов
Этот чеклист используется для проверки сервисов и помогает убедиться, что каждый сервис выполняет одну понятную задачу.
Он помогает вовремя заметить, когда сервис разрастается, смешивает разные обязанности или начинает контролировать слишком много логики.
1️⃣ «И» в описании
❌ Не надо
«Сервис считает цену билета и применяет агентские скидки»
✅ Надо
«Сервис считает цену билета»
2️⃣ Много причин для изменения
❌ Не надо
Сервис меняется, если:
меняются правила ценообразования
меняется логика агентов
меняется структура БД
✅ Надо
Сервис меняется только при изменении логики ценообразования.
3️⃣ Слишком много зависимостей
❌ Не надо
✅ Надо
4️⃣ Прямое использование ORM
❌ Не надо
Сервис сам делает запросы к БД и одновременно решает бизнес-задачу.
✅ Надо
Сервис получает готовые данные и только считает результат.
5️⃣ Много доменных понятий
❌ Не надо
Сервис знает про:
события
билеты
агентов
типы цен
скидки
✅ Надо
Сервис знает минимальный набор, нужный для своей задачи.
6️⃣ Внутреннее состояние
❌ Не надо
Сервис запоминает данные между вызовами.
✅ Надо
Все данные передаются через аргументы методов.
7️⃣ Ошибки как возвращаемые значения
❌ Не надо
Метод возвращает «результат или ошибку».
✅ Надо
Метод либо возвращает результат, либо явно падает.
8️⃣ Валидация + логика + сохранение
❌ Не надо
Один класс:
проверяет данные
считает бизнес-правила
сохраняет в БД
✅ Надо
валидация — отдельно
бизнес-логика — в сервисе
сохранение — в репозитории
Как использовать
Пройдитесь по пунктам 1–8 для сервиса.
Если 2 и более «❌» — сервис нужно дробить.
Этот чеклист используется для проверки сервисов и помогает убедиться, что каждый сервис выполняет одну понятную задачу.
Он помогает вовремя заметить, когда сервис разрастается, смешивает разные обязанности или начинает контролировать слишком много логики.
1️⃣ «И» в описании
❌ Не надо
«Сервис считает цену билета и применяет агентские скидки»
✅ Надо
«Сервис считает цену билета»
2️⃣ Много причин для изменения
❌ Не надо
Сервис меняется, если:
меняются правила ценообразования
меняется логика агентов
меняется структура БД
✅ Надо
Сервис меняется только при изменении логики ценообразования.
3️⃣ Слишком много зависимостей
❌ Не надо
__construct(
EventRepository,
AgentRepository,
PriceTypeRepository,
Logger,
Cache
)
✅ Надо
__construct(
PriceCalculator
)
4️⃣ Прямое использование ORM
❌ Не надо
Сервис сам делает запросы к БД и одновременно решает бизнес-задачу.
✅ Надо
Сервис получает готовые данные и только считает результат.
5️⃣ Много доменных понятий
❌ Не надо
Сервис знает про:
события
билеты
агентов
типы цен
скидки
✅ Надо
Сервис знает минимальный набор, нужный для своей задачи.
6️⃣ Внутреннее состояние
❌ Не надо
$this->event = $event;
Сервис запоминает данные между вызовами.
✅ Надо
Все данные передаются через аргументы методов.
7️⃣ Ошибки как возвращаемые значения
❌ Не надо
Метод возвращает «результат или ошибку».
✅ Надо
Метод либо возвращает результат, либо явно падает.
8️⃣ Валидация + логика + сохранение
❌ Не надо
Один класс:
проверяет данные
считает бизнес-правила
сохраняет в БД
✅ Надо
валидация — отдельно
бизнес-логика — в сервисе
сохранение — в репозитории
Как использовать
Пройдитесь по пунктам 1–8 для сервиса.
Если 2 и более «❌» — сервис нужно дробить.
❤1👍1
Технический долг
Технический долг - это то, что возникает, когда сегодняшние упрощения становятся завтрашними проблемами.
Когда мы как команда ставим скорость выше ясности или дизайна, мы накапливаем технический долг, который позже влияет на всех.
Основные виды
Долг в коде - дублирование, длинные функции, захардкоженные значения
Архитектурный долг - плохие абстракции, сильная связность
Долг в тестах - отсутствующие или устаревшие тесты
Процессный долг - отсутствие оценок, неясная ответственность, слабая документация
Пример
Позже:
Проблемы:
Логика ролей дублируется по кодовой базе
Любое изменение требует правок в нескольких местах
Высокий риск ошибок
Как нужно сразу:
Технический долг - это то, что возникает, когда сегодняшние упрощения становятся завтрашними проблемами.
Когда мы как команда ставим скорость выше ясности или дизайна, мы накапливаем технический долг, который позже влияет на всех.
Основные виды
Долг в коде - дублирование, длинные функции, захардкоженные значения
Архитектурный долг - плохие абстракции, сильная связность
Долг в тестах - отсутствующие или устаревшие тесты
Процессный долг - отсутствие оценок, неясная ответственность, слабая документация
Пример
// Бизнес-логика напрямую зависит от ролей
if ($user->role === 'admin') {
allowAccess();
}
Позже:
// Бизнес-логика напрямую зависит от ролей
if (
$user->role === 'admin' ||
$user->role === 'manager' ||
$user->role === 'owner'
) {
allowAccess();
}
Проблемы:
Логика ролей дублируется по кодовой базе
Любое изменение требует правок в нескольких местах
Высокий риск ошибок
Как нужно сразу:
function canManage(User $user): bool
{
return in_array($user->role, ['admin', 'manager', 'owner'], true);
}
if (canManage($user)) {
allowAccess();
}
❤1🍾1💋1
API First
В современной разработке API давно перестал быть «побочным продуктом» backend-кода. Для многих команд API - это основа продукта, вокруг которой строятся веб-клиенты, мобильные приложения, интеграции и партнёрские сервисы. Именно из этого понимания вырос подход API First.
Под API-контрактом понимается формальное описание:
эндпоинтов
входных и выходных данных
кодов ошибок
обязательных и необязательных полей
правил версионирования
Обычно контракт описывается в формате OpenAPI (Swagger), реже - в Postman Collections или GraphQL-схемах.
Почему API First важен
1. Frontend и backend могут работать одновременно. Контракт уже есть - ждать реализацию не нужно.
2. Меньше интеграционных баговю Большая часть ошибок возникает не в бизнес-логике, а на границе систем.
API First снижает этот риск.
Простой пример OpenAPI
Backend обязан вернуть
В современной разработке API давно перестал быть «побочным продуктом» backend-кода. Для многих команд API - это основа продукта, вокруг которой строятся веб-клиенты, мобильные приложения, интеграции и партнёрские сервисы. Именно из этого понимания вырос подход API First.
API First - это подход к разработке, при котором сначала проектируется API-контракт, а уже затем пишется реализация.
Под API-контрактом понимается формальное описание:
эндпоинтов
входных и выходных данных
кодов ошибок
обязательных и необязательных полей
правил версионирования
Обычно контракт описывается в формате OpenAPI (Swagger), реже - в Postman Collections или GraphQL-схемах.
Ключевая идея: сначала контракт, потом код.
Почему API First важен
1. Frontend и backend могут работать одновременно. Контракт уже есть - ждать реализацию не нужно.
2. Меньше интеграционных баговю Большая часть ошибок возникает не в бизнес-логике, а на границе систем.
API First снижает этот риск.
Простой пример OpenAPI
GET /users/{id}
responses:
200:
content:
application/json:
schema:
type: object
required: [id, name]
properties:
id:
type: integer
name:
type: stringBackend обязан вернуть
{
"id": 5,
"name": "Alex"
}Линейное преобразование для расчёта рейтинга пользователя
Во многих backend-системах возникает одна и та же задача: у пользователя есть несколько метрик, а нам нужно одно число, по которому можно принимать решения.
Например:
- кто VIP
- кого продвигать
- кому дать скидку
- кто особо не активен
Для этого идеально подходит линейное преобразование.
Математически оно выглядит так:
X - вектор признаков пользователя
W - веса (важность каждого признака)
b - смещение (порог / базовый уровень)
Зачем вообще нужно преобразование
Без преобразования у нас есть набор разрозненных данных:
- частота покупок
- сумма покупок
- давность последней активности
С ними сложно:
- сортировать пользователей
- сравнивать между собой
- принимать автоматические решения
Линейное преобразование решает эту проблему, сводя всё к
Определяем данные для расчета
Это и есть наш вектор признаков:
Нормализуем
Признаки находятся в разных масштабах:
заказы - единицы
суммы - тысячи
дни - десятки
Если их не привести к одному диапазону, большие числа “сломают” формулу.
Простейшая нормализация:
Теперь все значения лежат в диапазоне 0..1.
Задаём веса - бизнес-смысл формулы
Веса отвечают на вопрос:
что для нас важнее?
Допустим:
Тогда:
Знак веса важен:
+ усиливает вклад признака
- уменьшает рейтинг при росте признака
Считаем рейтинг пользователя
Формула
Пример пользователя:
6 заказов → 0.6
18 000 ₽ → 0.36
3 дня → 0.05
средний чек 4200 → 0.42
Подставляем:
0.455 - рейтинг пользователя
Пример расчета на PHP - https://zalki-lab.ru/linear-transformation/
Во многих backend-системах возникает одна и та же задача: у пользователя есть несколько метрик, а нам нужно одно число, по которому можно принимать решения.
Например:
- кто VIP
- кого продвигать
- кому дать скидку
- кто особо не активен
Для этого идеально подходит линейное преобразование.
Линейное преобразование - это способ: превратить набор показателей в одну итоговую оценку, учитывая важность каждого показателя.
Математически оно выглядит так:
score = X @ W + bX - вектор признаков пользователя
W - веса (важность каждого признака)
b - смещение (порог / базовый уровень)
Зачем вообще нужно преобразование
Без преобразования у нас есть набор разрозненных данных:
- частота покупок
- сумма покупок
- давность последней активности
С ними сложно:
- сортировать пользователей
- сравнивать между собой
- принимать автоматические решения
Линейное преобразование решает эту проблему, сводя всё к
одному числу - рейтингу.Определяем данные для расчета
orders_30d — количество заказов за 30 дней
spend_30d — сумма покупок за 30 дней
days_since_last — дней с последней покупки
avg_check_90d — средний чек за 90 дней
Это и есть наш вектор признаков:
X = [orders_30d, spend_30d, days_since_last, avg_check_90d]
Нормализуем
Признаки находятся в разных масштабах:
заказы - единицы
суммы - тысячи
дни - десятки
Если их не привести к одному диапазону, большие числа “сломают” формулу.
Простейшая нормализация:
orders_norm = min(orders_30d, 10) / 10
spend_norm = min(spend_30d, 50000) / 50000
recency_norm = min(days_since_last, 60) / 60
avg_check_norm = min(avg_check_90d, 10000) / 10000
Теперь все значения лежат в диапазоне 0..1.
Задаём веса - бизнес-смысл формулы
Веса отвечают на вопрос:
что для нас важнее?
Допустим:
частота покупок - очень важна
сумма - важна
давность последней покупки - негативный фактор
средний чек - дополнительный плюс
Тогда:
W = [ 0.45, 0.35, -0.50, 0.20 ]
b = 0
Знак веса важен:
+ усиливает вклад признака
- уменьшает рейтинг при росте признака
Считаем рейтинг пользователя
Формула
score =
orders_norm * 0.45 +
spend_norm * 0.35 +
recency_norm * (-0.50) +
avg_check_norm * 0.20
Пример пользователя:
6 заказов → 0.6
18 000 ₽ → 0.36
3 дня → 0.05
средний чек 4200 → 0.42
Подставляем:
score =
0.6 * 0.45 +
0.36 * 0.35 +
0.05 * (-0.50) +
0.42 * 0.20
= 0.455
0.455 - рейтинг пользователя
Пример расчета на PHP - https://zalki-lab.ru/linear-transformation/
Вредоносные расширения Chrome крали переписки ChatGPT и DeepSeek
Исследователи OX Security обнаружили две вредоносные расширения для Google Chrome, которые экспортировали переписки пользователей с ChatGPT и DeepSeek, а также историю браузера на серверы злоумышленников. Пострадали более 900 000 пользователей.
•
•
Расширения маскировались под полезные AI-инструменты и просили разрешение на «анонимные данные», но на самом деле получали доступ к содержимому веб-страниц.
Исследователи OX Security обнаружили две вредоносные расширения для Google Chrome, которые экспортировали переписки пользователей с ChatGPT и DeepSeek, а также историю браузера на серверы злоумышленников. Пострадали более 900 000 пользователей.
•
Chat GPT for Chrome with GPT-5, Claude Sonnet & DeepSeek AI - более 600 000 установок, имело Google «Featured» badge в магазине.•
AI Sidebar with Deepseek, ChatGPT, Claude and more - более 300 000 установок.Расширения маскировались под полезные AI-инструменты и просили разрешение на «анонимные данные», но на самом деле получали доступ к содержимому веб-страниц.
Каждые ~30 минут отправляли на серверы злоумышленников полные переписки из ChatGPT и DeepSeek и URL всех открытых вкладок браузера.
Как узнать, какой процесс пишет в файл
Допустим есть файл
В таких случаях помогает команда
lsof (List Open Files) показывает, какие файлы сейчас открыты процессами в системе.
- какой процесс держит файл открытым
- его PID
- от какого пользователя он запущен
- открыт ли файл на запись
Выполняем
Пример вывода
- COMMAND - имя процесса
- PID - идентификатор процесса
- FD - дескриптор файла
Если в колонке FD есть w, значит файл открыт на запись.
lsof показывает не того, кто писал когда-то, а того, кто держит файл открытым прямо сейчас. Обычно это и есть источник постоянной записи.
Как остановить процесс
Допустим есть файл
debug.log который постоянно растет. Наша задача понять, какой процесс это делает и грохнуть его.В таких случаях помогает команда
lsoflsof (List Open Files) показывает, какие файлы сейчас открыты процессами в системе.
- какой процесс держит файл открытым
- его PID
- от какого пользователя он запущен
- открыт ли файл на запись
Выполняем
sudo lsof debug.log
Пример вывода
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
php-fpm 1234 www-data 3w REG 8,1 12345 111 debug.log
- COMMAND - имя процесса
- PID - идентификатор процесса
- FD - дескриптор файла
Если в колонке FD есть w, значит файл открыт на запись.
lsof показывает не того, кто писал когда-то, а того, кто держит файл открытым прямо сейчас. Обычно это и есть источник постоянной записи.
Как остановить процесс
# Аккуратное завершение
sudo kill 1234
# Принудительное завершение
sudo kill -9 1234
❤🔥1🤩1🙏1
Популярный плагин для WordPress W3 Total Cache оказался уязвимым к критической атаке (CVE-2025-9501),
которая при определённых условиях может привести к удалённому выполнению кода на сервере и полной компрометации сайта
- обновляемся до версии 2.8.13 или выше
которая при определённых условиях может привести к удалённому выполнению кода на сервере и полной компрометации сайта
- обновляемся до версии 2.8.13 или выше
❤🔥1👍1💅1
Декомпозиция в коде
Декомпозиция — это разбиение сложной задачи или большого куска логики на более мелкие, понятные и независимые части.
Зачем нужна декомпозиция
- Читаемость — код проще понимать.
- Поддерживаемость — легче вносить изменения.
- Тестируемость — маленькие функции проще покрывать тестами.
- Повторное использование — куски логики можно переиспользовать.
- Снижение связанности — меньше "магии" и скрытых зависимостей.
Простой пример
Без декомпозиции
Тут всё в одной функции — проверки, расчёты, сохранение, уведомления.
С декомпозицией
Теперь:
- каждая функция делает одну вещь
- код легче тестировать
- можно менять расчёт суммы без риска сломать email
Декомпозиция — это разбиение сложной задачи или большого куска логики на более мелкие, понятные и независимые части.
Вместо одного огромного файла/функции на 500 строк — делаем несколько маленьких, каждая из которых отвечает за свою конкретную задачу.
Зачем нужна декомпозиция
- Читаемость — код проще понимать.
- Поддерживаемость — легче вносить изменения.
- Тестируемость — маленькие функции проще покрывать тестами.
- Повторное использование — куски логики можно переиспользовать.
- Снижение связанности — меньше "магии" и скрытых зависимостей.
Простой пример
Без декомпозиции
function createOrder($userId, $products) {
// проверка пользователя
$user = findUser($userId);
if (!$user) {
throw new Exception("User not found");
}
// подсчёт суммы
$total = 0;
foreach ($products as $product) {
$total += $product['price'] * $product['quantity'];
}
// создание заказа
$orderId = saveOrder($userId, $total);
// отправка email
sendEmail($user['email'], "Order created");
return $orderId;
}Тут всё в одной функции — проверки, расчёты, сохранение, уведомления.
С декомпозицией
function createOrder($userId, $products) {
$user = getValidatedUser($userId);
$total = calculateOrderTotal($products);
$orderId = saveOrder($userId, $total);
notifyUserAboutOrder($user);
return $orderId;
}Теперь:
- каждая функция делает одну вещь
- код легче тестировать
- можно менять расчёт суммы без риска сломать email
Чеклист, когда стоит сменить работу
- 2+ года без финансового прогресса при росте скиллов и обязанностей
- Нет прозрачных критериев роста
- Нет обратной связи
- Обещания без конкретики
- Ты перерос задачи
- Компания стагнирует
- Результаты работы обесцениваются
Почему важно разделять логику и мутации состояния в коде
Одна из самых частых архитектурных ошибок — функции, которые одновременно считают результат и изменяют состояние системы.
Здесь функция:
• считает цену
• меняет состояние объекта
• пишет в базу
Это усложняет тестирование, повторное использование и делает код менее предсказуемым.
Лучше — разделить чистую логику и мутации
Мутация состояния:
Теперь код:
• проще тестировать
• проще переиспользовать
• легче читать
• меньше скрытых побочных эффектов
Разделяйте их — и архитектура станет заметно чище.
Одна из самых частых архитектурных ошибок — функции, которые одновременно считают результат и изменяют состояние системы.
function calculateTotalPrice(Order $order): float {
$total = 0;
foreach ($order->items as $item) {
$total += $item->price * $item->qty;
}
$order->total = $total;
$order->save();
return $total;
}Здесь функция:
• считает цену
• меняет состояние объекта
• пишет в базу
Это усложняет тестирование, повторное использование и делает код менее предсказуемым.
Лучше — разделить чистую логику и мутации
function calculateTotalPrice(array $items): float
{
$total = 0;
foreach ($items as $item) {
$total += $item->price * $item->qty;
}
return $total;
}
Мутация состояния:
$total = calculateTotalPrice($order->items);
$order->total = $total;
$order->save();
Теперь код:
• проще тестировать
• проще переиспользовать
• легче читать
• меньше скрытых побочных эффектов
Если функция что-то сохраняет, отправляет или пишет — это mutation.
Если читает данные и возвращает результат — это pure logic.
Разделяйте их — и архитектура станет заметно чище.