Обложка канала

Beer::PHP 🍺. Страница 2

3025 @beerphp

Здесь публикуются короткие заметки о PHP, Linux, Unit Testing, DB, OOP, etc., выдержки из статей, книг, видео, курсов и других материалов. Теперь тебе больше не нужно перерывать тонны информации ;)

  • Beer::PHP 🍺

    Настройка 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
  • Beer::PHP 🍺

    Настройка 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
  • Beer::PHP 🍺

    Настройка 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
  • Реклама

  • Beer::PHP 🍺

    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
  • Beer::PHP 🍺

    CGI, FastCGI, php-fpm Уверен, многие из вас видели эти аббревиатуры, но, как оказалось, не все знают, как они связаны и что обозначают. Конечно, эта тема может быть более актуальна тем, кто занимается настройкой ПО (типа DevOps), но считаю, что хотя-бы общее представление должно быть у всех, кто так или иначе занимается разработкой на PHP, потому решил поверхностно описать эти вопросы. 👉 CGI Common Gateway Interface — это стандарт (протокол, спецификация, соглашение, набор правил), который описывает, как веб-сервер должен запускать скрипты (в т.ч. PHP, а также другие программы), как должен передавать им параметры HTTP-запроса и как скрипты должны передавать результаты своей работы веб-серверу. То есть CGI был разработан для того, чтобы расширить возможности веб-сервера и дать возможность создания и обработки динамического контента. 👉 FastCGI По факту это дальнейшее развитие технологии CGI, является более производительным и безопасным. В чем же главное отличие? В CGI-режиме на каждый запрос создается отдельный процесс, "умирающий" после окончания обработки. В FastCGI процесс работает в качестве демона, то есть один и тот же процесс обрабатывает различные HTTP запросы один за другим. PHP из коробки умеет работать и в FastCGI режиме. 👉 PHP-FPM FastCGI Process Manager — альтернативная реализация PHP FastCGI, которая позволяет вам достаточно гибко настраивать те самые процессы, о которых я писал выше. ❓ Как это работает? 📌 Представим, что в наше приложение одновременно приходит 50 клиентов. Сначала они обращаются в наш NGINX, который по факту пробрасывает запросы сквозь себя на PHP-FPM (за исключением запросов за статическими ресурсами/файлами), а дальше PHP-FPM пытается обработать все запросы с помощью своих процессов (воркеров). 📌 Допустим у нас запущено 5 воркеров, как на картинке. В таком случае во время одновременного запроса первые 5 клиентов будут обрабатываться сразу, а остальные 45 становятся в очередь и ждут, когда первые 5 закончат обработку. PHP-FPM позволяет задавать настройки в зависимости от ваших потребностей, будь то динамическое увеличение воркеров для того чтобы клиенты не ждали в очереди или экономия ресурсов с целью ускорения обработки запросов со статическим кол-вом воркеров. 👍 Если интересно более подробно почитать про настройку php-fpm, то ставь 🍺. Надеюсь стало немного понятнее и теперь никто не будет впадать в ступор при виде этих аббревиатур. 😉 #php #server #source
  • Beer::PHP 🍺

    DRY (Don't Repeat Yourself) Буквально сегодня сидел и смотрел на пачку похожих классов, код которых на 98,2% одинаков. Очень сильно подмывало схлопнуть это всё, но как правильно это сделать? Давайте разберемся подробнее. 👉 Многие считают, что DRY запрещает дублировать код, но это не так. Вся суть таится немного глубже. Давайте разберемся с определением: "Каждая часть знания должна иметь единственное, непротиворечивое и авторитетное представление в рамках системы" ❓ Вся тайна кроется как раз в определении "часть знания". Что же это такое? 👍 Речь идёт не о написанном коде, а о знаниях в "предметной области" или "бизнес правилах". Давайте рассмотрим пример { pic | gist } В данном случае код выглядит одинаково, однако, это может продиктовано абсолютно разными требованиями бизнеса: 📌Мы ограничиваем корзину тремя единицами товара из-за высокого спроса, а нам выгодно продать его как можно большему кол-ву покупателей. 📌В то же время мы ограничиваем доставку тремя единицами, потому что наш транспорт физически не может вместить большего объема. Подсознательно нам хочется взять и вынести дублирующийся код в один источник, но что если какое-то из правил изменится? Например мы купили новый транспорт для доставки и теперь можем доставлять до 10 единиц товара? ❗️ Очень важно, чтобы у вашего кода могла быть только "одна причина для изменения" (прямо как в SRP), ведь правила вашего бизнеса в одной предметной области могут (и должны) меняться независимо от другой. 💡 Когда-то М. Фаулер популяризировал так называемое Rule of three. Звучит примерно так: "Два экземпляра аналогичного кода не требуют рефакторинга, но, когда аналогичный код используется три раза, его следует извлечь в новую процедуру." Это правило, в первую очередь, отлично помогает избежать преждевременного обобщения, когда с виду похожие вещи имеют разные зоны ответственности и потому не являются дублированием. 👌 DRY — это принцип, однако принципы — не правила. Они лишь инструменты, помогающие идти в правильном направлении. Очевидно, что не стоит везде повторять бизнес-логику, но также не нужно и объединять всё подряд. Необходимо искать баланс, который зависит от текущей ситуации. #php #junior #source
  • Beer::PHP 🍺

    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
  • Beer::PHP 🍺

    Локальная разработка пакетов (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
  • Beer::PHP 🍺

    Битовые операции (часть 2) В прошлой части мы рассмотрели побитовые сдвиги влево и вправо, сегодня рассмотрим остальные 4 операции — AND & , OR | , XOR ^, NOT ~. Для примера рассмотрим простую систему разграничения прав доступа к сайту. 📌 У нас будут доступны следующие права доступа: Чтение, Создание, Редактирование, Удаление. То есть всего 4 значения, их можно представить в виде 4-х битного числа, в котором 1 — означает, что у пользователя есть данное право, а 0 — нет. Разберем код из предыдущей части: define('U_READ', 1 << 0); // 0001 define('U_CREATE', 1 << 1); // 0010 define('U_EDIT', 1 << 2); // 0100 define('U_DELETE', 1 << 3); // 1000 define('U_ALL', U_READ | U_CREATE | U_EDIT | U_DELETE); // 1111 В первых 4 строках мы задали константы с помощью сдвига влево. А в пятой строке использовали оператор OR |. Он выполняет операцию над каждым битом своих операндов. Бит результата устанавливается, если соответствующий бит установлен хотя бы в одном операнде. Например: $x = 3; // 0011 $y = 5; // 0101 echo $x | $y; // 0111 (7) Таким образом мы можем задать любые разрешения для пользователя: $userPermission = U_READ; // только право чтения $userPermission = U_READ | U_CREATE; // можно читать и создавать $userPermission = U_ALL ^ U_DELETE; // все права кроме удаления $userPermission = U_ALL & ~ U_DELETE; // тоже все права кроме удаления 📌 В данном примере мы видим XOR ^ (исключающее или). Бит устанавливается, если соответствующий бит установлен в одном (но не в обоих) из двух операндов. $x = 3; // 0011 $y = 5; // 0101 echo $x ^ $y; // 0110 (6) В нашем случае: U_ALL 1111 U_DELETE 1000 RESULT 0111 📌 В следующей строке сразу 2 оператора AND & и NOT ~. Оператор & выполняет операцию логическое И над каждым битом своих операндов. Бит результата устанавливается, если соответствующий бит установлен в обоих операндах: $x = 3; // 0011 $y = 5; // 0101 echo $x & $y; // 0001 (1) 📌 Оператор NOT ~ представляет собой унарный оператор, указываемый перед своим единственным операндом. Он выполняет инверсию всех битов операнда. Из-за способа представления целых со знаком в PHP применение оператора ~ к значению эквивалентно изменению его знака и вычитанию 1. $y = 5; // 0101 echo ~$y; // 1010 (-6) Таким образом в нашем примере сначала сработает оператор NOT. U_DELETE из 1000 станет 0111, а затем вызовется оператор & U_ALL 1111 ~ U_DELETE 0111 RESULT 0111 ❗️ Разница между этими вариантами в том, что в первом случае просто переключается бит, если был 1, то станет 0, и наоборот. Второй же вариант делает бит равным 0, независимо от его текущего значения. Если мы хотим убрать какое-нибудь право доступа, то пишем так: $userPermission &= ~ U_DELETE; // запретить удаление 👉 Для проверки битов (в нашем случае прав доступа) можно использовать следующие конструкции. if ($userPermission & U_READ) // есть ли право чтения? if ($userPermission & (U_READ | U_DELETE)) // есть ли право чтения и/или удаления Еще один пример: // Вместо if ($error['type'] == E_ERROR || $error['type'] == E_PARSE || $error['type'] == E_COMPILE_ERROR) {} // Или if (in_array($error['type'], [E_ERROR, E_PARSE, E_COMPILE_ERROR])) {} // Можно использовать if ($error['type'] & (E_ERROR | E_PARSE | E_COMPILE_ERROR)) {} 👍 Несмотря на то, что коды ошибок в PHP специально заточены под битовые операции, тем не менее, достаточно часто для проверки кодов ошибок используются обычные операторы сравнения. Но теперь вы знаете, что можно сравнивать и побитово ;) #php #junior #source
  • Beer::PHP 🍺

    Битовые операции (часть 1, сдвиг влево и вправо) 📌 В мире PHP эти операции встречаются редко, однако в статьях, книгах, либках и других источниках легко можно встретить запись типа: ~ $memory = memory_get_usage() >> 20; И тут наступает ступор, что за ">>"? Лезешь в доку а там $a >> $b — Сдвиг вправо. Все биты переменной $a сдвигаются на $b позиций вправо (каждая позиция подразумевает "деление на 2"). ❓ Чё сдвигается, куда? Зачем вообще оно надо? Давайте разбираться. Думаю многие знают, что число в двоичной (бинарной) системе исчисления представляет собой набор нулей и единиц. Например число 6 будет представлено как 00000110. 👍 Побитовый сдвиг в PHP - это арифметическая операция. Биты, сдвинутые за границы числа, отбрасываются. Сдвиг влево дополняет число нулями справа, сдвигая в то же время знаковый бит числа влево, что означает что знак операнда не сохраняется. Сдвиг вправо сохраняет копию сдвинутого знакового бита слева, что означает что знак операнда сохраняется. Возвращаемся к нашему оператору >>
    $n = 6;       // 00000110
    $k = $n >> 1; // 00000011
    
    
    👉 Здесь четко видно, что мы отбросили самый правый бит, а слева дополнили нулём. Соответственно запись 00000011 в двоичном представлении равна 3 в десятичном. То есть фактически сдвинув один бит — мы поделили на 2, а если сдвинем на 2 бита, то еще раз поделим на 2 (то есть на 4), на 3 бита — получится что на 8. ❗️ Стоп. Да это же степени двойки! Возвращаясь к нашему первому примеру >> 20, получится, что мы делим наше исходное значение на 2 в степени 20. Легко запомнить, что 2^10 = 1024. Тогда:
    $memory = memory_get_usage() / (1024 * 1024);
    
    
    Так как функция memory_get_usage() возвращает значение в байтах, то мы всего-лишь перевели всё в Мб. Получается достаточно удобно:
    >> 10 приводит в Кб
    >> 20 приводит в Мб
    >> 30 приводит в Гб
    
    
    В то время как сдвиг вправо означает деление, то сдвиг влево — умножение. $y = 5; // 000000101 echo $y << 2; // 000010100 (5 * 4 = 20) ❤️ Однако, самое вкусное использование побитовых операций заключается не в умножении и делении, а именно в использовании побитовой маски, например для разграничений прав доступа, или других подобных операций:
    define('U_READ', 1 << 0);                               // 0001
    define('U_CREATE', 1 << 1);                             // 0010
    define('U_EDIT', 1 << 2);                               // 0100
    define('U_DELETE', 1 << 3);                             // 1000
    define('U_ALL', U_READ | U_CREATE | U_EDIT | U_DELETE); // 1111
    
    
    Выглядит интересно, но об этом и других битовых операциях мы поговорим в следующей части :) #php #junior #source