Beer::PHP 🍺
2.25K subscribers
10 photos
75 links
Тут публікуються короткі замітки про PHP, Linux, Unit Testing, DB, OOP тощо, витяги зі статей, книг, відео, курсів та інших матеріалів.

Тепер тобі більше не потрібно перегортати тонни інформації ;)

@genkovich — написати автору каналу.
Download Telegram
Локальная разработка пакетов (composer + phpstorm)

Буквально пару дней назад мой коллега спросил "а как ты работаешь с мультирепозиториями?", "О чём ты?" — спросил я, "Ну вот тебе нужно сделать sdk и сразу протестить внутри приложения, что ты делаешь?". И вот уже через несколько часов я действительно разбирался с этой проблемой, решением которой захотел поделиться.

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

0. создаете папку
1. в ней composer init
2. создать структуру папок и прописать psr-4 (вот пост с подробностями)
3. не забудьте git init :) точно пригодится

Дальше начинается самое интересное. Как же подключить наш локальный пакет к существующему vendor?

Оказывается всё достаточно просто. Нужно добавить в composer.json директорию repositories, добавить запись с типом path, а урл — относительный путь в директорию с пакетом:

"repositories": [
{
"type": "path",
"url": "../my-package"
}
],
"minimum-stability": "dev"

и не забыть понизить minimum-stability до dev. Выглядит вот так.

👍 Дальше делаем composer require package/name и вуаля! Теперь мы можем смело править файлы пакета, без дополнительных коммитов, пуша в удаленный репозиторий и прочих прелестей. Всё потому, что фактически композер сделал симлинку и подтягивает изменённые файлы напрямую.

💁‍♂️ Ну, а для того чтобы во время разработки не прыгать между окнами, достаточно добавить в PHPStorm -> Settings -> Version Control тот самый локальный репозиторий, затем открыть папку проекта и выбрать attach. После этого находясь в одном окне вы можете спокойно править файлы и проекта и пакета, при этом каждый будет, пуллиться, фетчится и даже коммититься в свой гит репозиторий :)

#middle #phpstorm #packages
Decorator pattern

Не подумайте, меня не покусал Егор Бугаенко (для тех кто в теме, кто нет — погуглите), мои взгляды не настолько радикальны, однако я действительно считаю, что этому паттерну уделяют мало внимания. Потому решил познакомить с ним тех, кто не знаком и освежить в памяти для тех, кто уже давно им пользуется.

Декоратор — (wiki) структурный шаблон проектирования, предназначенный для динамического подключения дополнительного поведения к объекту.

👉 Если простым языком, то суть данного паттерна заключается в "оборачивании" существующего объекта новым функционалом, при этом оригинальный интерфейс объекта остается неизменным. В данном примере WrapperObject и есть наш декоратор.

В чем преимущества?

1. Его не получится просто так создать без существования базового класса.
2. Так как базовый класс и класс обертка имплементируют один интерфейс — то они взаимозаменяемы.
3. Мы расширяем поведение без изменения исходного кода.

Стоп, да это же прям Open-Closed Principle! И да, это отличная альтернатива наследованию. Также вы можете использовать несколько разных обёрток одновременно.

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

Представим, что у нас есть какой-то Mailer, который мы описали в services.yaml и теперь мы хотим логировать отправку всей почты. Для этого у контейнера есть директива decorates. Подобная запись подменит основной mailer на mailer_logger и наши письма начнут начнут логироваться. Для ссылки на исходный класс нужно использовать decorating_service_id + .inner (или просто '@.inner' начиная с Symfony 5.1).

👍 Но что если мы хотим не только логировать, но и отправлять наши письма через очередь? Нет проблем, мы можем добавить еще одну запись. Но как задать порядок в котором будут вызваны декораторы? Для этого существует директива decoration_priority (по умолчанию 0). Соответственно чем выше приоритет — тем раньше применится декоратор (всё логично). Например такой код сначала залогирует (1), а потом уже отправит в очередь (2) наше сообщение:

$this->services['mailer'] = new LoggerDecorator(new QueueDecorator(new Mailler()));

#php #oop #patterns #middle
DRY (Don't Repeat Yourself)

Буквально сегодня сидел и смотрел на пачку похожих классов, код которых на 98,2% одинаков. Очень сильно подмывало схлопнуть это всё, но как правильно это сделать? Давайте разберемся подробнее.

👉 Многие считают, что DRY запрещает дублировать код, но это не так. Вся суть таится немного глубже. Давайте разберемся с определением:

"Каждая часть знания должна иметь единственное, непротиворечивое и авторитетное представление в рамках системы"

Вся тайна кроется как раз в определении "часть знания". Что же это такое?

👍 Речь идёт не о написанном коде, а о знаниях в "предметной области" или "бизнес правилах". Давайте рассмотрим пример { pic | gist }

В данном случае код выглядит одинаково, однако, это может продиктовано абсолютно разными требованиями бизнеса:

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

Подсознательно нам хочется взять и вынести дублирующийся код в один источник, но что если какое-то из правил изменится? Например мы купили новый транспорт для доставки и теперь можем доставлять до 10 единиц товара?

❗️ Очень важно, чтобы у вашего кода могла быть только "одна причина для изменения" (прямо как в SRP), ведь правила вашего бизнеса в одной предметной области могут (и должны) меняться независимо от другой.

💡 Когда-то М. Фаулер популяризировал так называемое Rule of three. Звучит примерно так: "Два экземпляра аналогичного кода не требуют рефакторинга, но, когда аналогичный код используется три раза, его следует извлечь в новую процедуру." Это правило, в первую очередь, отлично помогает избежать преждевременного обобщения, когда с виду похожие вещи имеют разные зоны ответственности и потому не являются дублированием.

👌 DRY — это принцип, однако принципы — не правила. Они лишь инструменты, помогающие идти в правильном направлении. Очевидно, что не стоит везде повторять бизнес-логику, но также не нужно и объединять всё подряд. Необходимо искать баланс, который зависит от текущей ситуации.

#php #junior #source
CGI, FastCGI, php-fpm

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

👉 CGI

Common Gateway Interface
— это стандарт (протокол, спецификация, соглашение, набор правил), который описывает, как веб-сервер должен запускать скрипты (в т.ч. PHP, а также другие программы), как должен передавать им параметры HTTP-запроса и как скрипты должны передавать результаты своей работы веб-серверу. То есть CGI был разработан для того, чтобы расширить возможности веб-сервера и дать возможность создания и обработки динамического контента.

👉 FastCGI

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

👉 PHP-FPM

F
astCGI Process Manager — альтернативная реализация PHP FastCGI, которая позволяет вам достаточно гибко настраивать те самые процессы, о которых я писал выше.

Как это работает?

📌 Представим, что в наше приложение одновременно приходит 50 клиентов. Сначала они обращаются в наш NGINX, который по факту пробрасывает запросы сквозь себя на PHP-FPM (за исключением запросов за статическими ресурсами/файлами), а дальше PHP-FPM пытается обработать все запросы с помощью своих процессов (воркеров).

📌 Допустим у нас запущено 5 воркеров, как на картинке. В таком случае во время одновременного запроса первые 5 клиентов будут обрабатываться сразу, а остальные 45 становятся в очередь и ждут, когда первые 5 закончат обработку. PHP-FPM позволяет задавать настройки в зависимости от ваших потребностей, будь то динамическое увеличение воркеров для того чтобы клиенты не ждали в очереди или экономия ресурсов с целью ускорения обработки запросов со статическим кол-вом воркеров.

👍 Если интересно более подробно почитать про настройку php-fpm, то ставь 🍺. Надеюсь стало немного понятнее и теперь никто не будет впадать в ступор при виде этих аббревиатур. 😉

#php #server #source
Fluent interface. Evil or not?

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

Прежде всего давайте синхронизируемся что же такое fluent interface?

Каждый из вас наверняка видел конструкцию наподобие:

$example = Example::create()->addFoo()->deleteBar()->build();

👉 А добиться этого можно с помощью вот такого нехитрого фокуса [pic | code ]. При такой реализации мы сможем добавлять в массив строки не вызывая каждый раз переменную [pic | code ].

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

📌 Первая проблема — подобные конструкции начинают вставлять везде, где только можно. Возьмём пример с какой-нибудь сущностью Order. Предположим у вас есть сеттеры (об этом зле у нас будет отдельный разговор, но потом. Пока предположим, что у вас они есть) setId(), setTitle(), setDescription(). Всё выглядит достаточно удобно, в сущности всего 3 метода, мы можем вызывать их цепочкой. Однако, представим, что нам нужно добавить информацию о доставке setCountry(), setCity(), setAddress().

Что мы делаем? Правильно, мы берем и просто добавляем еще 3 метода, потому что нам хочется вызывать их цепочкой. Затем мы хотим добавить инфу о получателе и добавляем еще 5 методов, инфу о скидках — еще 4 и т.д. В итоге наша сущность раздувается до нереальных размеров, данные никак не разбиты на логические (бизнесовые) куски (смотри whole objects).

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

📌 Второедекорирование. Давайте представим, что мы хотим расширить наш класс Example и добавить логирование [pic | code ]. На первый взгляд всё выглядит логично, но сколько раз вызовется логгер? Один(!). А всё потому, что нашу обёртку теперь тоже нужно сделать "текучей", что тоже не сразу очевидно и часто бывает местом для багов.

📌 Третье — их сложнее мокать. Не секрет, что мокая объект мы создаем Null Object, где все методы являются заглушками. Чтобы протестировать логику и случайно не оборвать нашу цепочку — нам придётся описывать КАЖДЫЙ метод (а напомню, что в примере с Order их больше 20) примерно такой конструкцией, даже если на самом деле его вызов ни на что не влияет в нашем тесте.

$example
->expects($this->any())
->method('addBar')
->willReturnSelf();

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

👍 Есть места, где использование данного похода достаточно хорошо себя показывает и это билдеры (в множестве их проявлений):

$queryBuilder
->select('u')
->from('User u')
->where('u.id = :identifier')
->orderBy('u.name', 'ASC')
->setParameter('identifier', 100);


Так зло или нет?!

Сам по себе данный подход ни в коем случае не является абсолютным злом, однако, прежде чем его использовать вам следует хорошо подумать:
1. Не создаёте ли вы себе дополнительных проблем?
2. В какой момент и что пошло не так, если в Entity вам нужно засэтить десяток полей?
3. Точно ли всё очевидно?

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

#php #junior #source
Настройка PHP-FPM (part 1)

Ну что, вы просто не оставили мне выбора, почти 400 🍺, круто! Сразу оговорюсь, что материал, который здесь приведен — очень сухая и субъективная выжимка, потому как материала слишком много (и уже получилось несколько частей), но я не стал углубляться в дебри. Постарался сделать так, чтобы было понятно откуда ноги растут и куда копать, если что.

👉 Давайте по порядку. В конфигурации нас будут интересовать 2 места:

• основной файл конфигурации /etc/php/(версия)/fpm/php-fpm.conf
• и файлы пулов /etc/php/(версия)/fpm/pool.d/

📌 С понятием Pool вы будете сталкиваться достаточно часто. Pool – это группа процессов, выделенная для обработки запросов, поступающих на определённый порт или unix-socket. В PHP-FPM возможно настраивать (и использовать) сразу несколько пулов, для решения разных, отдельных задач.

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

Для того чтобы создать несколько отдельных пулов, внутри директории pool.d следует создать отдельный файл под каждый пул. Сразу скажу, что настройки в этих файлах будут иметь приоритет выше, чем те, что в php.ini. По умолчанию у нас описан пул в файле www.conf. Открыв его мы сразу видим [www], это и есть наше имя пула. В зависимости от потребностей вы можете создать пул с любым именем. Например вы можете настроить отдельными пулами [backend] и [frontend]. Имя пула должно быть в самом верху в квадратных скобках.

На что следует обратить внимание, это то как проходят данные от веб-сервера к вашим php процессам. Это отражено в директиве listen:

listen = /var/run/php8-fpm.sock

По факту, таким образом в нашей операционной системе мы задаём адрес (socket или host:port) , который будет принимать FastCGI-запросы. И затем уже в нашем nginx конфиге мы указываем по какому адресу нам стоит обращаться, а следовательно какой пулл с какими настройками будет использован:

### nginx config ###

location ~ \.php$ {
...
fastcgi_pass unix:/var/run/php8-fpm.sock;
...
}

🔨 Коротко про права доступа: Чтобы кто попало не писал в наш сокет - запрещаем это делать путём указания прав доступа к нему. Для этого предназначены строчки listen.owner, listen.group и listen.mode. По умолчанию стоит группа и пользователь www-data (как у вашего веб-сервера) и права 0660, что означает, что владелец и пользователь могут читать и редактировать, а все остальные не могут делать ничего.

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

Так вот, параметр listen.backlog отвечает за размер очереди одновременно ожидающих подключений к нашему сокету. В зависимости от версии и операционной системы вы можете увидеть значение по умолчанию 511, 128, 65535, -1 (подразумевая неограниченно, но это не так) и т.д.

Какое значение установить? Зависит от задачи которую вы решаете:

❗️Если значение слишком большое, а php-fpm не успевает обрабатывать все запросы, то nginx дождется тайм-аута и отключится, выкинув 504 ошибку.

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

👌 Лучший метод расчета — определить размер в соответствии с QPS (query per second) у вашего production сервера, накинуть 30-50%, и убедиться, что железо справляется с таким кол-вом запросов. Тогда во время пиковой нагрузки (черная пятница/новый год) вы конечно рискуете, что некоторые из пользователей отвалятся получая 502, но не потеряете всех из-за зависшего железа.

🧐 Думаю на сегодня достаточно, в следующей части рассмотрим настройки самого Process Manager (pm), узнаем, какие стратегии лучше использовать для решения различных задач. Если у вас есть вопросы — их можно обсудить со мной и другими подписчиками в нашем чатике.

#php #server #middle #source
Настройка PHP-FPM (part 2)

Окей, вот мы переходим к самому интересному, по моему мнению, разделу: управление процессами.

👉 С помощью параметра pm можно настроить стратегию запуска дочерних процессов. Всего есть 3 режима работы.

📌 Static — гарантирует, что обработка запросов всегда доступна фиксированному количеству дочерних процессов (кол-во которых устанавливается с помощью pm.max_children). При такой схеме кол-во дочерних процессов не меняется, а значит они всегда занимают определенный объем ОЗУ и в случае пиковых нагрузок у вас могут быть сложности (клиенты будут становиться в очередь). С другой стороны — запросам не нужно ждать запуска новых процессов, а значит на это не тратятся дополнительные ресурсы, что делает static самым быстрым подходом. Такую стратегию лучше использовать когда у вас постоянная высокая нагрузка и большой объём оперативной памяти.

pm.max_children — очень важный параметр, который работает для всех трёх режимов, означает максимально возможное количество дочерних процессов, если значение будет слишком маленьким, то при возрастании нагрузки, лимит исчерпается и ваш сайт начнёт тупить, если слишком большим - исчерпается оперативная память и что-то упадёт;

Как посчитать значение pm.max_children?

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

Для этого определяем сколько памяти кушает каждый наш процесс (найдите для вашей ОС, вот пример для Ubuntu):

ps --no-headers -o "rss,cmd" -C php-fpm | awk '{ sum+=$1 } END { printf ("%d%s\n", sum/NR/1024,"Mb") }'

Например я получил, что в среднем один процесс кушает 60 МБайт. Допустим у меня на серваке всего 16 ГБайт, 8 из них уже использует БД (кеши и прочее), еще 2 другие приложения, остаётся 6. Какой-то запас необходимо оставить, итого 4 ГБ / 60 МБ = 66 процессов.

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

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

Помимо pm.max_children в настройке этой стратегии принимают участие еще 3 параметра:

pm.start_servers — количество процессов, запускаемых при старте PHP-FPM. Видел 2 рекомендации по подсчёту: Значение равно кол-ву ядер (но не нашел информации где это обоснованно) или 25% от pm.max_children, который считается также, как для static.

pm.min_spare_servers — минимальное количество бездействующих дочерних процессов. Чтобы новый процесс не создавался прямо в момент подключения нового клиента — PHP-FPM создаёт процессы заранее. Например на старте у нас было 10 процессов, и значение min_spare_servers мы установили равным 10. Пришел клиент и сразу подключился к одному из поднятых процессов, а в это время заботливый FPM создаёт еще один 11й процесс, чтобы бездействующих снова стало 10. Когда общее кол-во процессов упирается в pm.max_children, то новые свободные процессы не создаются (что логично). Также рекомендуют брать значение 25% от pm.max_children.

pm.max_spare_servers — максимальное количество бездействующих дочерних процессов. При падении нагрузки, PHP-FPM будет убивать лишние процессы высвобождая ресурсы. Если значение свободных процессов больше, чем pm.max_spare_servers, то главному процессу будет отправлен SIGCHLD, чтобы он сократил кол-во дочерних процессов и они всегда находились в пределе между pm.min_spare_servers и pm.max_spare_servers. Считают как 75% от pm.max_children.

👍 Как вы уже догадались, вас ждёт 3я часть из данного цикла. Если вам есть что добавить — обязательно пишите мне лично или добавляйтесь в наш уютный чатик :)

#php #server #middle #source
Настройка PHP-FPM (part 3)

Ранее мы рассмотрели 2 стратегии запуска дочерних процессов процессов и проговорили об основах работы самого fpm.

Итак третья стратегия:

📌 Ondemand — на старте у вас есть 0 (ноль, зеро) рабочих процессов. Процессы создаются когда появляются новые запросы. Такая стратегия подойдет для проекта с низким трафиком и ограниченным ресурсом. С одной стороны у вас не будет запущено лишних процессов, с другой клиентам придётся немного подождать, пока fpm создаст для них процесс.

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

Для настройки этого режима используется 2 параметра — уже хорошо известный нам pm.max_children и pm.process_idle_timeout. Второй параметр устанавливает время через которое нужно убить ваш дочерний процесс, если тот свободен и простаивает. Маленькое значение конечно поможет вам быстро высвободить память, но если у вас есть скачки трафика, то лучше заменить стандартные 10 секунда на несколько минут, а может и несколько десятков минут.

⚡️И еще несколько полезных параметров:

Как же подобрать оптимальное значение pm.max_children в процессе использования? Для этого можно посмотреть статистику в реальном времени воспользовавшись параметром pm.status_path, который задаст адрес для просмотра страницы. Сколько процессов запущено, сколько из них находится в ожидании, а также какая длина очереди ожидающих выполнения запросов и всё что вас интересует — можно здесь найти. Данные значения можно отображать в xml, json, html, что может быть полезно в разных ситуациях (например если вы собираете данные с помощью prometeus).

Раз уж начал, то стоит и упомянуть и ping.path, в которой также можно указать адрес страницы. С её помощью можно убедиться, что FPM жив и отвечает. Вам будет отдаваться ответ с кодом 200, а контент страницы можно задать в ping.response, по умолчанию увидите pong. Почему не использовать status page? Просто потому, что этот вариант требует меньшего ресурса и его можно чаще опрашивать т.к. ему не нужно проводить дополнительных манипуляций.

Как помочь освободить память?

pm.max_requests — это максимальное количество запросов, которое обработает дочерний процесс, прежде чем будет уничтожен. Принудительное уничтожение процесса позволяет избежать ситуации в которой память дочернего процесса "разбухнет" по причине утечек (т.к процесс продолжает работу после от запроса к запросу). С другой стороны, слишком маленькое значение приведет к частым перезапускам, что приведет к потерям в производительности. По умолчанию стоит 0, но стоит с ним поиграться. Для стратегии static под большими нагрузками я бы начал со значения в 50000, ведь мы специально её выбрали, чтобы не тратить ресурсы на работу с процессами. Для dynamic и ondemand нужно выбирать меньшее значение.

request_terminate_timeout — устанавливает максимальное время выполнения дочернего процесса, прежде чем он будет уничтожен. Это позволяет избегать долгих запросов, если по какой-либо причине было изменено значение max_execution_time в настройках интерпретатора. Значение стоит установить исходя из логики обрабатываемых приложений, скажем 60s (1 минута).

👉 Также достаточно полезным инструментом для анализа может быть slowlog.

request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/slowlog-site.log

Таким образом мы указали файл, в который будет писаться журнал всех запросов, которые выполнялись дольше 5 секунд. Не забудьте — память на сервере не резиновая :)

🍻 Ну что, с циклом статей по PHP-FPM будем заканчивать. Не забывайте следить за показателями на своём серваке, анализировать и настраивать на основе именно вашей нагрузки.

#php #server #middle #source
DateTimeImmutable vs DateTimeInterface

Очень давно не выходили посты, пора возвращаться в строй и вот один из черновиков, который я таки добил.

Все мы знаем (надеюсь), что для работы с датой и временем в PHP cуществует класс DateTime. Однако, даже работая с этим классом, в котором решены многие проблемы, мы можем набить себе кучу шишек. И вот как избежать некоторых из них:

📌 Используйте только DateTimeImmutable

Изначально DateTime задумывался как Value Object, а одним из главных принципов создания VO является иммутабельность. Думаю, что никому не будет приятно попадать в подобные ситуации, когда создавая один объект, мы случайно аффектим и второй [pic ]. Ведь именно такие замечаешь не сразу т.к. на первый взгляд всё логично написано. Конечно можно решить это использовав clone, но зачем?

👍 Вместо того, чтобы добавлять костыли для предотвращения неожиданных изменений, используйте DateTimeImmutable, который является тем самым иммутабельным Value Object'ом, а значит под капотом, будет создавать новый объект, то есть сделает clone за вас.

📌 Не используйте DateTimeInterface

Но почему? Да, я и сам его использовал до недавнего времени, но спасибо коллегам по работе за подсказку. В PHP изначально появился класс DateTime. Очевидно, это удобный инструмент, который стали использовать в большом кол-ве проектов, но быстро поняли, что мутабельность это ошибка. Тогда для соблюдения обратной совместимости и более плавного перехода прогеров на DateTimeImmutable — выпустили "патч" в виде DateTimeInterface.

👉 А как же завязываться на абстракции, а не на реализации? Во-первых, вы просто не можете создать пользовательский класс, который бы имплементировал этот интефейс. Во-вторых необходимость в интерфейсах для Value Object's крайне спорная затея. Также были намерения по выпилу данного интефейса. Ну и согласно PSR-20 (за который проголосовали но еще не зарелизили) мы будем завязаны именно на реализации DateTimeImmutable.

📌 Всегда используйте информацию о часовом поясе

Не нужно от неё абстрагироваться, да и в большинстве случаев не получится. Это хорошо соотносится с whole value concept, то есть работать с временем без часового пояса равнозначна работе с деньгами без валюты. Есть несколько нюансов как правильно это делать, но это уже совсем другая история ☺️.

—————
И да, так как в телеграмм появились реакции, которые можно использовать вместе с комментариями (но нет 🍺 и 🐒), то попробуем заюзать их в тестовом формате. Так что ставьте 🔥и пишите комменты :)

#php #datetime #junior
👉 Так совпало, что когда CrosserBot запостил своё сообщение у меня в канале, я бежал по воздушной тревоге в укрытие.


❗️Хочу донести до вас несколько месседжей:

1. Я из Украины. Я родился и вырос в Донецке. Я был вынужден выехать из своего любимого города.
2. То, что сделали CrosserBot — они сделали необдуманно, хотя я сильно благодарен им за смелость, за их позицию, которую я разделяю и поддерживаю.
3. Я верю, что в нашем комьюнити все здраво и критически мыслящие люди, которые способны на DYOR (do your own research).
4. От себя лично я прошу воздержаться от резких высказываний с одной и с другой стороны. Это не поможет. Это вызывает ненависть, которая на самом деле никому не нужна.
5. Все войны заканчиваются. Эта не будет исключением.

⭕️ Что касается моей позиции:

1. Войска РФ вторглись на территорию суверенного государства. В результате этого действия гибнут люди с двух сторон. Этому нет оправдания.
2. Сейчас в РФ не может быть позиции "я вне политики". Политика коснулась каждого. Сегодня в РФ пора это принять. Или бояться автозаков и смериться с тем, что происходит с экономикой или бороться с этим.
3. Если вы боитесь сесть на 15 лет, я вас не осуждаю, это нормально. Только нужно принять тот факт, что вам угрожают не украинцы, а власти РФ.

‼️ Итог:

Я временно закрываю комментарии, чтобы не раздувать вражду и не вызывать ненависть с обоих сторон. Точно знаю, что словесные перепалки делают только хуже. Несколько дней назад я сделал отдельный канал, в котором делаю посты о текущей ситуации, сам проверяю инфу на предмет правдивости. Если вам не хватает политической информации — Welcome.

———————————————————

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

Все буде Україна 🇺🇦❤️
Шифрование (Part 1)

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

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

✔️1. Выбрать какой-то алгоритм шифрования, то есть договориться по какому принципу мы будем преобразовывать наши данные.
✔️2. Ключ (шифр) — тайная информация (набор цифр и букв) которая используется выбранным алгоритмом для шифрования и расшифровки.

Чем шифрование отличается от хеширования?

Хеширование мы уже рассматривали ранее. Зашифрованные данные мы можем вернуть в исходное состояние, а захешированные нет.

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

👌 Симметричное и асимметричное шифрование

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

🤟 Неоспоримым преимуществом такого подхода является его скорость т.к. он требует малое количество вычислительной мощности.

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

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

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

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

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

😒 Однако у этого подхода есть и свой минус — он требует намного больше вычислительной мощности.

Можно ли зашифровать данные закрытым ключом, а расшифровать открытым? — Можно. Но зачем это делать и как это используется напишу в следующей части.

—————
Для тех кому интересно поиграться накидал на коленке пример того, как работает RSA. Да, на PHP. Да, страдал. Но надеюсь вышло понятно.

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

#php #crypto #middle ❤️ Все буде Україна 🇺🇦
Шифрование (Part 2) — Разбираемся с ЭЦП

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

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

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

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

👉 Немого освежим знания про хеширование.

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

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

Так как же работает ЭЦП?

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

Псевдокод для понимания

Что такое АЦСК и зачем нам сертификаты?

Всё выглядит отлично, кроме одного момента: откуда мы знаем, что открытый ключ на самом деле принадлежит правдивому источнику, а не был отправлен нам каким-то злоумышленником?

📃 К сожалению в таких условиях нам не обойтись без третьей стороны, которой все могут доверять. На арену выходит "Аккредитованный центр сертификации ключей" (АЦСК).

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

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

💩 Собираем всё в кучу

☝️ Получается, чтобы абсолютно безопасно передать данные нужно выполнить следующие шаги:
1. Вы и получатель должны сгенерировать по паре ключей
2. Открытые ключи подписать в АЦСК и обменяться ими
3. Данные, которые нужно засекретить, шифруете открытым ключем получателя
4. Затем подписываете своим закрытым ключом уже зашифрованные данные и отправляем

1. Получатель сначала проверяет ваш сертификат (убеждается что вы это вы)
2. Затем вашим открытым ключем проверяет подпись (убеждается в целостности и что данные пришли именно от вас)
3. Своим закрытым ключем расшифровывает данные и получает то, что хотел

❗️Такая сложная схема используется не всегда. Можно обойтись без АЦСК, когда мы доверяем полученным открытым ключам (напр. мы сами его положили на сервак для доступа по ssh) или используем так называемые "сети доверия" (OpenPGP).

—————

💬 Не стесняйтесь писать в комментарии своё мнение, вопросы, замечания и предложения ;)

#php #crypto #middle ❤️ Все буде Україна 🇺🇦
Как https нас защищает?

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

☝️ Как правило, запросы передаются посредством обычного HTTP, в котором и запрос клиента, и ответ сервера будут в открытом виде. Это значит, что злоумышленник может получить отправленные вами "sensitive data" (пароли, данные карт, коды подтверждения и т.д.) и воспользоваться ими.

Что такое TLS?

Transport Layer Security (TLS)
— это тот самый механизм (протокол), который обеспечивает безопасное HTTP соединение (фактически наследник SSL).

⚠️ TLS расположен на уровень ниже протокола HTTP в модели OSI. Это означит, что в процессе выполнения запроса сперва происходят все действия, связанные с TLS, а уже потом, всё что связано с HTTP-соединением.

🔑 TLS использует асимметричное шифрование для генерации общего секретного ключа и аутентификации (то есть удостоверения в том, что вы – тот за кого себя выдаете), а также симметричное (с общим секретным ключом) для шифрования запросов и ответов.

То есть сначала, клиент и сервер должны безопасно договориться об общем "сеансовом" ключе. Для этого используют алгоритм обмена ключами Ди́ффи — Хе́ллмана (DH). В это раз поленюсь и оставлю вам ссылку на видос о том, как он работает.

👉 Итак, мы получили секретный ключ, но откуда мы знаем, что общаемся с нужным нам сервером, а не со злоумышленником?

❤️ Аутентификация

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

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

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

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

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

#php #crypto #middle ❤️ Все буде Україна 🇺🇦
Поговорим о времени

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

📚 Теория:

🕔 Всемирное время — UTC. Было введено вместо устаревшего среднего времени по Гринвичу (GMT), поскольку шкала GMT является неравномерной и связана с суточным вращением Земли. Шкала UTC, в свою очередь основана на равномерной шкале атомного времени (TAI).

Часовые пояса вокруг земного шара выражаются как положительное или отрицательное смещение относительно UTC.

❗️ Часовой пояс и смещение — не одно и то же. Почему? Всему виной летнее время (DST — Daylight Saving Time).

👉 Часовой пояс может иметь одно или несколько смещений. Какое именно время принято в качестве стандартного, зависит от текущих политических и/или экономических причин в конкретной стране.

Так как время по UTC не переводится ни зимой, ни летом, то, для тех мест, где есть переход на летнее время и происходит смещение относительно UTC.

⌚️ Unix время — это количество секунд, прошедших с полуночи (00:00:00 UTC) 1 января 1970 года и представлено целым числом.

🔧 Практика:

Следует работать с Unix временем. Такой формат удобно использовать для сравнения и хранения дат. При необходимости его легко преобразовать в любой подходящий формат (и обратно).

На всякий случай упомяну про "критические даты", например 19 января 2038 года в 03:14:08 число секунд достигнет 2^31, что может привести к ошибочной интерпретации этого числа как отрицательного. Возможное решение проблемы состоит в использовании не 32-битной, а 64-битной переменной, которой хватит на 292 млрд лет.

Если вам нужно хранить время только что произошедшего события, текущее время, по факту определённого действия, храните его в UTC (напр. для PostgreSQL —TIMESTAMP WITH TIME ZONE). Это могут быть записи в логах, время регистрации пользователя, совершения заказа или отправки письма.

Нужно ли хранить часовой пояс пользователя?

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

Если время привязано к пользователю — сохраняйте локальное время пользователя и его смещение.

Фактически 1660050128, 2022–09–9T16:02:08+03:00 и 2022–09–9T13:02:08+00:00 — это одно и то же время. Сохраняя смещение мы оставляем важную информацию, которая может оказаться нужной как с точки зрения бизнеса, так и для внутренней отладки, принятия других решений. Иными словами если у вас есть информация, и её хранение вам не доставляет (существенных) дополнительных усилий — не выбрасывайте её. Вы не сможете получить её обратно.

————
🔥 Вот те самые несколько нюансов, которыми хотелось поделиться. Пишите в комментарии, если есть что дополнить, возможно вместе соберём еще несколько дельных советов😉.

#php #datetime #middle
Value Object or Data Transfer Object?

Существует некоторая путаница когда речь заходит об этих двух понятиях. Что ж, давайте разбираться.

Data Transfer Object (DTO)уже писал ранее о нём, это объект, который содержит в себе примитивные типы (string, int, bool, etc.). Его задача определить схему передаваемых данных, декларируя имена полей и их типы. То есть используя DTO, мы гарантируем, что сможем обратиться к конкретному полю, которое будет содержать ожидаемый тип данных.

↔️ DTO обычно используются для передачи данных между различными сервисами или приложениями, либо между слоями внутри одного приложения.

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

Value Object — это полноценный объект вашей доменной модели, он гарантирует, что значения имеют смысл с точки зрения предметной области (вашего бизнеса) , то есть строки больше не будут пустыми, там где не должны, а числа будут проверены на соответствие правильному диапазону.

👍 Также value object's должны соответствовать whole value concept, могут содержать какую-то логику, помимо валидации. Пример

Value Object часто используются в предметной области (Domain и Application Layer) и не используются для передачи данных между приложениями. В определенных случаях их удобно использовать и в инфраструктурной части приложения, например чтобы избежать дублирования правил валидации данных.

🫣 Иммутабельность

Данное свойство почему-то пытаются использовать в качестве доказательства, что перед нами непосредственно Value Object. Да, безусловно VO должен быть иммутабельным, однако это совсем не означает, что DTO не может быть таковым. Более того, очень рекомендую готовить его именно таким образом.

Может ли Value Object находиться внутри DTO?

Интересный вопрос, с одной стороны хочется сразу просто ответить "нет", ведь мы только что проговорили, что VO не подходит для передачи. С другой, я многократно видел как внутри используется тот же DateTimeImmutable, который фактически является VO.

💬 Напишите в комментарии, что думаете по этому поводу? Своё мнение, а также все самые интересные мысли из комментариев закину отдельным постом ☺️

#php #oop #middle #source ❤️ Все буде Україна 🇺🇦
Law of Demeter (Закон Деметры, LOD)

Этот закон часто используют вместе с темой Tell Don't Ask. Его идея в том, что объект должен обладать ограниченным знанием о других объектах и модулях, взаимодействовать только с теми, кто имеет к нему непосредственное отношение.

Формально правило звучит так, что в клиентском коде вы можете вызывать методы объектов, которые:

Были переданы как аргументы;
Были созданы локально;
Являются глобальными;
Собственные методы;

Пока что непонятно зачем это всё. Давайте разбираться.

Основная задача — создать условия для слабой связанности (loose coupling) между объектами. Но за счёт чего?

🌈 Представьте, вы находитесь в магазине, хотите купить товар стоимостью 25$. Вы дадите продавцу 25$ или вы отдадите продавцу свой кошелек, чтобы тот достал 25$? Давайте разберём этот пример.

👉 В первом случае мы явно знаем, что у объекта SomePerson есть Wallet, который мы хотим получить, чтобы достать (вычесть) из него какую-то сумму денег. Таким образом мы создаём сильную связь (tight coupling) с объектом кошелька, а также раскрываем особенности внутренней реализации.

Чем же лучше второй вариант?

Когда у SomeAnotherPerson мы вызываем метод subtractMoney, это нам даёт дополнительную гибкость, возможность легко изменить внутреннюю реализацию данного метода, не изменяя другие участки кода. Как бонус появляется information hiding, а также это дело менее проблематично мокать в тестах.

🤓 LOD также называют законом "одной точки" (one dot), т.к. ноги растут из Java. В нашем же случае о его применении стоит задуматься при виде более одной стрелочки ->. Обращаю внимание, что речь идёт о методах (или свойствах), которые позволяют получить промежуточный объект, утрируя - о геттерах. То есть две стрелочки в каком нибудь fluent interface (напр. $filter->limit(10)->offset(0)) сюда не относятся.

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

Можно ли нарушать закон Деметры?

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

#php #oop #middle #source
Ну що, поїхали 🍻

Відчув в собі сили та натхнення знов постити щось в канал. Є багато тем, котрі хочеться структуризувати для себе і поділитись з вами. Проте чернеток за цей час накопичилось, тому дайте фідбек, що було б цікаво:
Anonymous Poll
58%
🙈 Щось про архітектуру (чисту, лукову, гексагональну і щось пов'язане)
41%
🏗 Інсайти по інфраструктурі (Redis, Rabbit, Mongo, якісь не зовсім очевидні штуки)
30%
🤖 Алгоритми та структури (базові речі в котрі мало хто занурюється, але цікаві для усвідомлення)
41%
🤔 На мій розсуд (що ближче до душі лежить)
Fwdays PHP Talks

📍Ну а поки йде голосування, а я думаю, про що саме розповісти в наступній темі, запрошую всіх до перегляду свіжого випуску Fwdays PHP Talks.

Поговорили про:
— CI/CD
— Правила ведення гілок
— Де закінчується відповідальність DevOps і починається відповільність розробника
— Автотести в мікросервісах
— І багато суміжних тем

Йожеф Гісем та Дмитро Немеш, дякую за цікаву дискусію 👍 А всіх інших запрошую до перегляду

P.S. Вдячний всім за голосування, коментарі і реакцій до попереднього поста ☺️
Kind Reminder

17 серпня відбудеться PHP fwdays'24 — дійсно крута і велика конфа. Можна доєднатись онлайн або офлайн у Києві.

Для тих хто не був, то вас чекає:
📍Цікаві спікери та практичні доповіді про оптимізацію, міграцію з легасі продуктів, використання AI, Github Copilot та багато іншого.
📍Нетворкінг, Q&A зі спікерами, нові знайомства
📍Вайб відпочинку на морі під час офлайн частини заходу 🏄‍♀️
📍Розіграші та подарунки

Буквально завтра ціна на квитки підніметься, тому сьогодні у вас є шанс придбати квитки, ще і з 10% знижкою. Для цього необхідно використати промокод PHP10Beer за посиланням 👉 https://bit.ly/3WCY1VW
Третій випуск Fwdays PHP Talks

Якщо ви думали, що знов все затихло, то ні, знов взяв участь у "дебатах". На цей раз разом Йожефом Гісемом та Михайлом Боднарчуком подсикутували про:

📍 Laracon: Pest — прорив в тестуванні чи гарний маркетинг?
🤯 Чи можна звільнити всіх мануальних тестувальників?
🗂 Дані в міграціях і як з цим жити?
💻 Мікросервіси і бази даних

То ж залітайте дивитись (або просто слухати) випуск 🍻