
2024-05-10
Как профилировать код на php
2023-11-03
Для начала давайте разберемся что вообще такое профилирование и для чего оно нам нужно и в какой ситуации нам его надо применять.
Профилирование кода - процесс при котором разработчик ищет узкие места в коде, которые дают деградацию по скорости работы или по излишнему потреблению памяти или других ресурсов сервера. Наверняка вы слышали о том, что существует такое понятие как "расчетные нагрузки" - это совокупность тех нагрузок на продукт, которые он должен выдерживать и еще иметь какой-то запас. В идеальной картине мира наш продукт должен соответствовать требованиям по нагрузкам, но что делать если это не так? Например в одно чудесное утро к нам приходят админы и говорят, что видят по графикам, что либо наш продукт высасывает всю оперативную память, либо перегружает все ядра процессора, либо просто работает слишком долго и не соответствует ожиданиям саппорта по скорости работы. В этот момент нам надо разобраться что же вдруг пошло не так, ведь на альфа-тестировании и на ручном тестировании локально все было хорошо, а в проде вдруг появились проблемы.
И вот, чтобы понять где конкретно находится проблема или проблемы - нам нужно профилировать код.
При профилировании кода есть несколько правил, которые нужно строго соблюдать, иначе у вас ничего не получится и вы уйдете вообще не в ту степь абсолютно.
- XDebug должен быть выключен - нельзя заниматься профилированием кода с включенным XDebug, потому что он сам по себе сжирает очень много ресурсов и дает аффекты на скорости выполнения скриптов. Соответственно используя XDebug вы попросту получите ложные диагностические данные и будете на основании их строить ложные теории об исправлении ошибок там, где их потенциально даже и нет.
- Профилирование кода должно производиться на том же объеме данных, том же окружении или максимально близком к тому, где воспроизводится проблема - например у Symfony в .env файле есть переключатель dev/prod окружения. Очень важно во время профилирования переключаться в prod режим, чистить и прогревать кэш и только после этого приступать к профилированию. Иначе вы опять же получите ложные диагностические данные и точно так же будете строить ложные теории.
- Часто "узким горлышком" системы является реляционная база данных - поэтому одним из первых мест куда стоит посмотреть является именно база данных и запросы к ней. Чуть ниже я более детально расскажу о том, что именно стоит проверять.
- Также часто проблемными местами являются циклы, которые могут выполнять излишнюю или ненужную работу. Они могут быть написаны не оптимально или могут быть условно бесконечными ( когда вместо 100 итераций осуществляется 1 000 000 итераций из-за неверной логики внутри цикла ).
- Вы должны хорошо знать тот функционал, который вы собираетесь профилировать и очень четко уметь отследить его от начала и до самого конца без наличия "белых пятен".
- Не стоит пренебрегать подходом "пристального взгляда" - возможно ошибка банальная и кроется где-то на поверхности, не стоит сразу тратить целую тучу времени на детализацию - для начала стоит просто внимательно прочитать код.
Частенько бывает так, что проблема кроется где-то очень на поверхности и достаточно просто внимательно прочитать код. Например в моей практике был случай, что разработчик в одном из методов оставил бесконечный цикл. Так как логика была очень ветвестая и сложная, а система написана с участием очередей, то QA эту проблему не выявило, просто не попало во время тестирования в этот очень узкий сценарий.
Соответственно локально все было хорошо, а при выводе в прод через какой-то промежуток времени мы увидели, что идет какая-то странная деградация пиками на графиках. Что происходило в реальности. Код примерно в 3% случаев заходил в этот сценарий, внутри бесконечного цикла выжирал всю оперативу, падал и заново ставил сам себя в очередь на обработку. В итоге мы получали на графиках очень интересную картинку деградации ресурсов сервера.
Провели дополнительное ревью всех задач, которые входили в релиз и обнаружили бесконечный цикл, который был реализован через while (true) конструкцию с выходом по определенному условию. И получалось так, что выход из цикла происходил не в 100% случаев, а лишь в 97% случаев.
Конечно же это не является критично обязательным требованием, потому что случаи бывают абсолютно разные, но оно является очень очень ощутимо желательным. При прочих равных у человека, который знает функционал, шансов найти баг путем профилирования и отладки намного больше, чем у человека, который его впервые видит или работал очень давно.
Поэтому по возможности стоит конечно, либо это доверять тем, кто уже работал с данным кодом, либо предварительно детально его изучить, почитать ТЗ и проясниться как он вообще должен работать.
Тут всё довольно тривиально - любые отладчики потребляют ресурсы сервера зачастую даже в большем количестве, чем сами скрипты, которые выполняются. Соответственно, например вы будете искать утечки оперативной памяти, а XDebug будет ее потреблять больше, чем сама утечка. Причем потребляет он ее не равномерно, а по какой-то своей логике. Соответственно вы не сможете адекватно составить список мест где сколько памяти потребляется и соответственно будете строить гипотезы и исследовать куски кода, которые скорее всего работают нормально. А как только отключите XDebug - все сразу встанет на свои места.
Любую отладку необходимо проводить исключительно в том же окружении и на том же объеме данных, а желательно еще и на тех же самых данных, на которых проблема воспроизводится. Это аксиома, нарушая которую вы можете потратить огромное количество времени и не достигнуть результата и даже не приблизиться к нему.
Во-первых, скорее всего вы будете работать во фреймворке, а каждый фреймворк имеет разные настройки в зависимости от окружения dev / prod. Это как и настройки кэщирования, так и настройки БД и очень много чего еще. Это может играть роль при профилировании в части скорости работы, а так же объеме потребляемой памяти.
Во-вторых, объем данных, на которых вы будете пытаться воспроизводить проблему должен быть таким же, как и там, где проблема вознкиает. В противном случае, с огромной долей вероятности, вы потратите кучу времени, а результатов не добьетесь. Особенно в случае, если вы профилируете скорость работы скриптов. Потому что на малом объеме данных всё всегда работает отлично - проблемы всегда начинаются на большом объеме данных.
В-третьих, совсем желательно, чтобы и данные были бы с прода, залитые в локальную базу данных. Тут может возникнуть проблема с it-sec, потому что они могут воспротивиться предоставлять доступ к боевым данным, но в этом случае можно запросить обфусцированные данные. Обфускация - это такой процесс, когда структура данных остается той же самой, но при этом данные немного все же отличаются. Например может быть изменены символы в имени ( но не количество символов ), номер карты, сумма операции и прочие параметры. За счет такого подхода - вы максимально приблизитесь к реальной боевой среде и повысите свои шансы на более быстрый поиск точки / точек проблемы.
Самым простым способом профилирования скриптов в плане скорости работы - это использование функции microtime() встроенной в язык php.
Первым делом еще до любого профилирования и до любой оптимизации стоит воспроизвести проблему и произвести контрольные замеры скорости работы вашего скрипта, чтобы у вас потом было понимание "как было" и "как стало". Как их сделать - поймете прочитав чуть ниже. Только в случае с контрольными замерами в отличие от профилирования - вы ставите точки контроля только в начале и в конце и не ставите в середине скрипта.
Предположим, что у нас есть какой-то вот такой вот скрипт ( естественно он не работает, это просто пример ):
<?php
class SomeCliCommand
{
private $currencyRateModel, $currencyModel, $merchantModel, $merchantConfigurationModel, $currencyService;
public function exec()
{
$currencyRates = $this->currencyRateModel->find();
foreach ($currencyRates as $currencyRate) {
$currencyPair = $this->currencyModel->getCurrencyPair($currencyRate);
$merchants = $this->merchantModel->getAllMerchants();
foreach ($merchants as $merchant) {
$configurations = $this->merchantConfigurationModel->getByMerchant($merchant, $currencyPair, $currencyRate);
}
$this->currencyService->prepareCurrencyRatesWithMarkups($currencyPair, $currencyRate, $merchants, $configurations);
}
}
}
Мы знаем, что на небольшом объеме данных мы всё проверяли и код работает. Но в проде на большом объеме данных код начинает деградировать по скорости работы очень сильно. Первым делом нам нужно понять в каком именно месте происходит основная деградация кода. Для этого расставим по коду логирование с использованием microtime() следующим образом ( сейчас мы не обращаем внимание на форматирование кода, вся отладка смещена максимально влево для того, чтобы ее можно было потом легко отыскать глазами в коде и удалить ):
<?php
class SomeCliCommand
{
private $currencyRateModel, $currencyModel, $merchantModel, $merchantConfigurationModel, $currencyService;
public function exec()
{
$a = microtime(true);
$currencyRates = $this->currencyRateModel->find();
file_put_contents('./log.txt', sprintf('1: %s'.PHP_EOL, microtime(true) - $a), FILE_APPEND);
foreach ($currencyRates as $currencyRate) {
$a = microtime(true);
$currencyPair = $this->currencyModel->getCurrencyPair($currencyRate);
file_put_contents('./log.txt', sprintf('2: %s'.PHP_EOL, microtime(true) - $a), FILE_APPEND);
$a = microtime(true);
$merchants = $this->merchantModel->getAllMerchants();
file_put_contents('./log.txt', sprintf('3: %s'.PHP_EOL, microtime(true) - $a), FILE_APPEND);
$a = microtime(true);
foreach ($merchants as $merchant) {
$configurations = $this->merchantConfigurationModel->getByMerchant($merchant, $currencyPair, $currencyRate);
}
file_put_contents('./log.txt', sprintf('4: %s'.PHP_EOL, microtime(true) - $a), FILE_APPEND);
$a = microtime(true);
$this->currencyService->prepareCurrencyRatesWithMarkups($currencyPair, $currencyRate, $merchants, $configurations);
file_put_contents('./log.txt', sprintf('5: %s'.PHP_EOL, microtime(true) - $a), FILE_APPEND);
}
}
}
После запуска скрипта в файле log.txt мы получим примерно такие данные:
1: 0.00017619132995605
2: 0.00087404251098633
3: 0.10452103614807
4: 0.43679308891296
5: 2.0005280971527
2: 0.00093698501586914
3: 0.11457705497742
4: 1.0363759994507
5: 2.0004959106445
2: 0.00088787078857422
3: 0.1244900226593
4: 1.6367290019989
5: 2.000559091568
2: 0.00094509124755859
3: 0.13469290733337
4: 2.2366151809692
5: 2.0005509853363
2: 0.00094890594482422
3: 0.14480495452881
4: 2.8366689682007
5: 2.000519990921
2: 0.0009150505065918
3: 0.15450501441956
4: 3.4365401268005
5: 2.0006878376007
2: 0.00095605850219727
3: 0.16457486152649
4: 4.0365629196167
5: 2.0005469322205
2: 0.00095105171203613
3: 0.17456483840942
4: 4.6367800235748
5: 2.0005588531494
2: 0.00094914436340332
3: 0.18463087081909
4: 5.2365169525146
5: 2.0005400180817
2: 0.00095009803771973
3: 0.1945698261261
4: 5.8369708061218
5: 2.0007538795471
Как проанализировать эти данные.
Для начала смотрим п.1 - он у нас встречается всего один раз в коде и отрабатывает довольно быстро - значит с ним вряд ли есть какие-либо проблемы.
Далее у нас начинаются циклические операции - значит нам надо посмотреть наличие деградации на периоде цикла.
Смотрим первое, среднее (примерно из центра) и последнее значения по п.2 - это будут значения:
0.00087404251098633
0.00094890594482422
0.00095009803771973
Среди этих значений есть небольшой разбег, но так как это микросекунды и разбег даже меньше тысячной микросекунды, то подобной крошечной деградацией можно пренебречь.
Переходим к пункту 3 и смотрим его значения по тому же алгоримту:
0.10452103614807
0.14480495452881
0.1945698261261
Здесь мы уже можем наблюдать более существенную деградацию скорости работы, которая исчисляется в сотых долях микросекунды. Это не прям так, чтобы очень критично, но на большом объеме данных также играет роль. В идеальной картине мира даже такой деградации быть не должно.
Переходим к пункту 4 и смотрим его значения по тому же алгоритму:
0.43679308891296
2.8366689682007
5.8369708061218
Видим, что здесь деградация идёт уже просто чудовищная, измеримая микросекундами - значит скорее всего это если не основной, то один из основных виновников нашего торжества.
Но на этом мы еще не радуемся и не прекращаем анализ и переходим к пункту 5:
2.0005280971527
2.000519990921
2.0007538795471
Мы не видим, что здесь нет особой деградации, но работает оно медленно. Просто медленно, но без деградации от объемов.
Итого в сухом остатке обращаясь к скрипту мы понимаем, что у нас есть проблемы по пунктам 3, 4 и 5. Теперь ищем эти места в нашем скрипте:
И теперь смотрим на скрипт и думаем что мы можем с этим сделать, чтобы исправить положение.
Смотрим на строчку 18 и мы понимаем, что у нас в цикле идет обращение к БД каждый раз с одним и тем же запросом - мы получаем список всех мерчантов каждый раз в цикле снова и снова, хотя это достаточно сделать всего 1 раз и потом просто использовать его единожды и всё. Соответственно мы выносим 18 строку за пределы цикла. Таким образом наш код становится следующего вида:
<?php
class SomeCliCommand
{
private $currencyRateModel, $currencyModel, $merchantModel, $merchantConfigurationModel, $currencyService;
public function exec()
{
$currencyRates = $this->currencyRateModel->find();
$merchants = $this->merchantModel->getAllMerchants();
foreach ($currencyRates as $currencyRate) {
$currencyPair = $this->currencyModel->getCurrencyPair($currencyRate);
foreach ($merchants as $merchant) {
$configurations = $this->merchantConfigurationModel->getByMerchant($merchant, $currencyPair, $currencyRate);
}
$this->currencyService->prepareCurrencyRatesWithMarkups($currencyPair, $currencyRate, $merchants, $configurations);
}
}
}
Можно сказать, что этот пункт мы поправили - осталось еще два. Давайте рассмотрим строки 21-23 - там мы видим цикл, который перебирает список мерчантов и для каждого мерчанта обращается к БД. Скорее всего там может быть какой-то запрос вида
SELECT * FROM merchant_configuration WHERE merchant_id = $merchantId AND currency_pair_id = $currencyPair AND currency_rate_id = $currencyRate;
Что мы можем сделать в данной ситуации?
1) мы можем переделать запрос с "WHERE merchant_id =" на "WHERE merchant_id IN ()" и таким образом как минимум мы либо сократим количество итераций ( если количество мерчантов большое, то нельзя пихать всех сразу ), либо, если количество мерчантов не большое, то сократим вообще до одного запроса вместо десятков, сотен и тысяч.
2) мы должны проверить существует ли для таблицы merchant_configuration составной индекс по полям currency_pair_id, currency_rate_id, merchant_id. Если таблица большая ( а она скорее всего большая ) и в ней нет составного индекса для такого запроса - то запрос будет изначально очень медленный и еще и будет деградировать и мешать другим процессам системы. Как правильно создавать составные индексы мы рассмотрим в другой статье - сейчас нам важно просто убедиться, что он есть и что он корректный. Если его нет или он не корректный - то поправить или создать корректный.
Теперь наш код примет вот такой вот вид:
<?php
class SomeCliCommand
{
private $currencyRateModel, $currencyModel, $merchantModel, $merchantConfigurationModel, $currencyService;
public function exec()
{
$currencyRates = $this->currencyRateModel->find();
$merchants = $this->merchantModel->getAllMerchants();
foreach ($currencyRates as $currencyRate) {
$currencyPair = $this->currencyModel->getCurrencyPair($currencyRate);
$configurations = $this->merchantConfigurationModel->getByMerchant($merchants, $currencyPair, $currencyRate);
$this->currencyService->prepareCurrencyRatesWithMarkups($currencyPair, $currencyRate, $merchants, $configurations);
}
}
}
И у нас остается последний пятый пункт, где мы вызываем метод prepareCurrencyRatesWithMarkups() из сервиса валют, в котором нет деградации, но он просто медленно работает. Здесь всё очень просто - мы открываем этот метод и начинаем там делать всё то же самое, что делали здесь.
Таким образом мы разматываем всю цепочку от самого начала и до самого конца.
После того как мы размотали всю цепочку и все поправили - делаем итоговые замеры скорости работы ( полный прогон ). Если результаты нас удовлетворяют - то всё хорошо, заливаем фиксы в род и радуемся жизни. Если результаты нас не удовлетворяют - то в этом случае необходимо пересматривать архитектуру приложения и либо внедрять кэширование, либо очереди, либо масштабироваться горизонтально или еще как-то - тут уже всё зависит от ситуации, но это совсем другая история. С уровня кода можно считать, что всё поправлено, что было можно.
Утечки памяти ищутся абсолютно по тому же принципу, что и замеры скорости работы скрипта с той лишь разницей, что мы замеряем не время работы, а потребляемую память функцией memory_get_usage(). Функция на вход принимает bool аргумент. Если передать true - то она будет показывать максимально аллоцированный объем памяти за всё время работы скрипта, а если ничего не передать или передать false - то будет показывать используемую память в текущий момент времени. Пользоваться можно как удобнее, я покажу способ как удобнее делать лично мне.
При диагностике утечек памяти необходимо всегда помнить один очень важный нюанс. Мы не стремимся снизить количество потребляемой памяти до нуля. В подавляющем большинстве случаев это невозможно. Эту работу стоит делить на 2 подхода. Первый - устранение утечек и замеры. Второй - на основании замеров либо ищем возможности сократить количество потребляемой памяти либо расширяем железо. Нужно понимать, что скрипты потребляют память и это нормально, если они это делают в разумных пределах. Но при этом не стоит переусердствовать в оптимизации. Если вы понимаете, что вы уже довольно ощутимо соптимизировались, но при этом все равно постоянно находитесь у верхних границ объема потребляемой памяти и при этом утечек нет - то значит нужно расширить железо на сервере ( сколько бы там админ ни возмущался ( если вдруг будет возмущаться ) ).
Утечки памяти возникают в основном при работе внутри очередей и демонов, которые работают беспрерывно во времени ( не так как страница загрузилась и закончила работу ). Например у вас есть демон, который запущен в бесконечном цикле и который наблюдает за появлением в БД новых записей раз в секунду. В нем могут быть утечки памяти - и это определяется по тому признаку, что он аллоцирует память и не отпускает ее для прочих процессов. То есть демон запустился, отработал свою полезную нагрузку, завершил работу, но при этом не отпустил память, которую использовал в процессе работы. Это называется утечкой. А если ваш скрипт просто потребляет много памяти - это НЕ утечка. Это тоже стоит проверить и оптимизировать возможно, но это не утечка. Мы далее рассмотрим оба варианта.
Как и в прошлый раз еще до любого профилирования и оптимизации - нужно сделать контрольные замеры и понять сколько памяти вообще сейчас потребляет скрипт, чтобы потом было понимание "как было" и "как стало". Как их сделать - поймете прочитав чуть ниже. Только в случае с контрольными замерами в отличие от профилирования - вы ставите точки контроля только в начале и в конце и не ставите в середине скрипта.
Первым мы рассмотрим именно утечку памяти на примере демона (утечка):
<?php
$contents = [];
$i = 0;
$a = memory_get_usage(true);
while (true) {
if (++$i % 250000 === 0) {
$char = readline(PHP_EOL . 'stop?' . PHP_EOL);
file_put_contents('./log.txt', sprintf($i.': %s'.PHP_EOL, memory_get_usage(true) - $a), FILE_APPEND);
if ($char === 'y') break;
}
$contents[] = file_get_contents('../1.json');
}
Что делает скрипт... Запуская этот скрипт через консоль мы запускаем "как бы демона", который в бесконечном цикле осуществляет какую-то полезную работу. В данном случае его полезная работа заключается в том, чтобы за 250 000 итераций прочитать файл 250 000 раз и сложить его данные в массив (а после например отправить данные кому-то на почту в виде CSV файлика). То есть после того как он обработает 250 000 итераций и отправит их на почту - они ему перестают быть нужны. Но посмотрим, что происходит с памятью во время работы данного скрипта. Раз в 250 000 итераций он останавливается и спрашивает "остановиться или нет?" и логирует количество потребляемой памяти. Посмотрим что он залогирует например на 4 итерациях по 250 000 штук:
250000: 236982272 byte (226 Mb)
500000: 473960448 byte (452 Mb)
750000: 719327232 byte (686 Mb)
1000000: 947916800 byte (904 Mb)
Мы видим, что с каждой итерацией по 250 000 чтений файла - мы постоянно прибавляем объем аллоцированной памяти и не отпускаем ее и наш массив с данными всё пухнет. И так будет продолжаться до тех пор пока мы не упадем с ошибкой, что память исчерпана.
Как же это можно поправить:
<?php
$contents = [];
$i = 0;
$a = memory_get_usage(true);
while (true) {
if (++$i % 250000 === 0) {
$char = readline(PHP_EOL . 'stop?' . PHP_EOL);
file_put_contents('./log.txt', sprintf($i.': %s'.PHP_EOL, memory_get_usage(true) - $a), FILE_APPEND);
unset($contents);
$contents = [];
if ($char === 'y') break;
}
$contents[] = file_get_contents('../1.json');
}
Мы добавляем unset и пересоздаем переменную $contents между file_put_contents и проверкой $char. Запускаем скрипт снова и смотрим результаты:
250000: 236982272
500000: 236982272
750000: 236982272
1000000: 236982272
Да, мы аллоцировали 226 Mb из оперативной памяти, но этот показатель перестал расти с количеством итераций. То есть у нас получилось оптимизировать работу скрипта и избавиться от утечки памяти.
Наиболее актуальны утечки памяти именно в демонах и длительных cli командах, по той причине, что они аллоцируют ресурсы сервера и не отпускают их либо до тех пор пока не свалятся в ошибку либо до завершения своей работы. В PHP-FPM ( когда через браузер ) эта проблема стоит не так остро, но зависит от объемов утечки памяти. Если утечка небольшая - то либо скрипт выполнится быстрее, чем утечет достаточное количество для выброса ошибки, либо скрипт будет убит по таймауту. Если утечка большая - то там конечно же тоже будет ошибка падения по памяти. Но в демонах - это в разы критичнее. Даже крохотная утечка в демоне может долго-долго капать и в итоге скрипт будет падать и может аффектить бизнес-логику и прочие системы, оставляя не валидные данные в БД например.
Теперь поговорим про слишком большую аллокацию памяти и как с ней бороться. Большая часть разработчиков сталкивалась с ошибкой вида "Fatal error: Allowed memory size of XXX bytes exhausted" - именно эта ошибка и говорит о том, что превышен лимит использования оперативной памяти.
Очень многие сайты, рекомендуют решать эту проблему перенастройкой php.ini файла и параметра memory_limit или же предлагают зашивать прямо в код строки вида ini_set('memory_limit', '100M'). Я считаю подобные решения недопустимыми в 98% случаев ( особенно в демонах и cli ). Потому что подобные решения применяют в основном новички и подобным образом проблема не решается, а только скрывается. Это аналогично тому, что вы пришли к доктору со словами "у меня аппендицит болит, я помираю!", а он вам отвечает "я вам ща укол лошадиного транквилизатора поставлю и вы ничего чувствовать не будете. помрёте правда к утру, но за то ничего чувствовать не будете, болеть не будет". Вот примерно так же и тут. Такими способами - вы сильно скрываете очень большую проблему.
Почему проблема больше, чем кажется на первый взгляд. Казалось бы... Ну увеличил размер допустимого лимита памяти, что такого... Но печальная правда в том, что в большинстве случаев это придется делать часто и много и в итоге код превратится в неуправляемую помойку с кучей допусков на "авось не стрельнет", а оно стрельнет... рано или поздно...
Как же с этим бороться. А бороться с одной стороны не сложно, а с другой стороны сложно. Алгоритм действий примерно такой:
1) как и с утечками - сделать контрольные замеры
2) как и с утечками - сделать замеры внутри кода расставив диагностику и выявить те точки, где память уходит в пики по потреблению
3) оптимизировать
В подавляющем большинстве случаев проблемы обнаружатся в вашем коде ( и это хорошо ), на втором месте по популярности - это сторонние библиотеки, встроенные компоненты фреймворков и CMS ( это чуть хуже ) и уже реже - это встроенные функции php ( это прям плохо ).
Давайте рассмотрим каждый из случаев по отдельности.
Проблемы в вашем коде - это наиболее просто и быстро устраняемые проблемы. Часто они связаны с неоптимальными выборками из БД, когда например вы вместо выборок чанками - делаете огромную выборку всех объектов из БД и пытаетесь держать это в переменной. Также часто встречается не оптимальная работа с циклами, когда, например вы сделали какую-то крупную выборку и пытаетесь ее прожевать в цикле без очистки переменных и не освобождая память. Здесь каждую проблемную точку нужно искать и индивидуально принимать решение, которое чаще всего сводится к тому, чтобы немного оптимизировать код. Также частой причиной превышения лимита памяти является "переборщ" с ООП. Объекты при прочих равных весят намного больше, чем массивы, особенно если у них идет какое-то глубокое наследование через 100500 паттернов абсолютно ненужных зачастую. Поэтому будьте аккуратны с ООП, чтобы не заООПшить код насмерть.
Проблемы в сторонних библиотеках, встроенных компонентах фреймворков и CMS - здесь уже вопрос решается несколько сложнее, потому чато данные компоненты ставятся через composer и их изменить не так то просто, особенно, если вы часто обновляетесь. Автор той или иной библиотеки не всегда адекватно может реагировать на критику и даже если вы внесёте изменения в саму библиотеку и по всем правилам оформите баг и мерж реквест - то совсем не факт, что владелец репозитория его быстро посмотрит, примет и задеплоит новую версию. А рабочий код вам нужен уже сейчас. Поэтому будет вставать дилемма, либо вносить изменения у себя локально, держать vendor в репозитории и держать в памяти, что обновлять текущие-зафиксенные библиотеки нельзя, либо искать какие-то другие обходные пути, например писать свою собственную библиотеку, либо форкаться от исходной библиотеки другого автора, либо искать другую библиотеку. Но есть и хорошие новости - обычно те библиотеки и готовые компоненты, которые текут по памяти - широко известны и об этом много где написано. Поэтому, если при выборе библиотеки обратить на это внимание - то скорее всего проблемы удастся избежать.
Проблемы во встроенных функциях php - это самый плохой случай, потому что с этим вы ничего самостоятельно сделать не сможете. Если какая-то встроенная в язык функция течет по памяти - то исправить это смогут только разработчики языка в следующем релизе в лучшем случае и если им об этом сообщат и если они смогут это поправить. Поэтому рассчитывать на скорое решение в данном случае вообще не приходится от слова совсем. Единственно верный для вас в этом случае путь будет - отказаться от использования конкретной функции и придумывать другие методы разработки конкретного функционала.
Надеюсь вам понравилась данная статья и поможет вам с профилированием и отладкой вашего кода. Подписывайтесь на мой ТГ-канал, чтобы не пропустить новые статьи и обновления по ссылке.
Ура! Я наконец-то дописал статью как собирать собственные бандлы на Symfony 6!!!
Статья про EasyAdmin всё ещё в процессе )))
Не, ну мне же надо на чем-то тестировать твиттер локальный...
Я тут еще много полезного буду выкладывать, так что заходите обязательно почитать.
Сайтик пока что в разработке - это далеко не окончательная версия - по сути это то что удалось слепить за 8 часов.
Комментарии