Как найти ошибки в коде php

Вчера всё работало, а сегодня не работает / Код не работает как задумано

или

Debugging (Отладка)


В чем заключается процесс отладки? Что это такое?

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


Важное замечание:

Есть много IDE и редакторов кода, которые позволяют производить отладку. Процесс настройки в них у всех различается. Поэтому стОит обратиться к документации по настройке отладки для непосредственно той среды разработки и той версии, в которой работаете именно ВЫ.

На текущий момент будет рассмотрен пример с PHPStorm 2017.


Подготовка

Для начала необходимо, чтобы в PHP имелась библиотека для отладки под названием xdebug. Если её еще нет, то надо установить.

ВАЖНО! Для очень новых версий PHP (например 8), требуется и новый xdebug, а он, в свою очередь, работает на порту 9003. Не пропустите указание правильного порта в IDE!! (Примерно в разделе PHP -> Debug -> Debug Port . Где точно — зависит от конкретной IDE)

Для WINDOWS:

скачать dll, например на xdebug.org.

Обычно все библиотеки лежат в папке ext внутри папки PHP. Туда и надо поместить dll.

Далее в php.ini прописываем настройки:

[Xdebug]
zend_extension="C:/server/php/ext/php_xdebug.dll" // <!-- тут свой путь до dll!!! Это для среды Windows. 
; Для Linux путь должен быть что-то типа zend_extension=/usr/lib/php/20151012/xdebug.so 
xdebug.default_enable = 1
xdebug.remote_enable = 1
xdebug.remote_handler = "dbgp"
xdebug.remote_host = "localhost"
xdebug.remote_port = 9000
xdebug.auto_trace = 0

Перезагружаем сервер, на всякий случай.

Для UBUNTU:

  • sudo apt update ИЛИ sudo apt-get update

  • sudo apt install php-xdebug или если нужнен отладчик для конкретной версии PHP, то sudo apt install php7.0-xdebug где 7.0 указывается версия PHP

  • sudo nano /etc/php/7.0/mods-available/xdebug.ini

    вписываем строки:

     zend_extension=/usr/lib/php/20151012/xdebug.so
     xdebug.remote_autostart = 1
     xdebug.remote_enable = 1
     xdebug.remote_handler = dbgp
     xdebug.remote_host = 127.0.0.1
     xdebug.remote_log = /tmp/xdebug_remote.log
     xdebug.remote_mode = req
    

    Примечание: каталог 20151012, скорее всего, будет другим. cd в /usr/lib/php и проверьте, в каком каталоге в этом формате находится файл xdebug.so, и используйте этот путь. 7.0 — тоже отличается, в зависимости от того, какая версия у вас используется

  • Перезагружаем сервер, на всякий случай.


Теперь если в файле .php написать phpinfo(); то можно будет увидеть в самом низу такую картину:

введите сюда описание изображения

Открываем PHPStorm

  • нажимаем create project from existing files
  • выбираем Web server is installed locally, source files are located under its document root
  • выбираем папку с файлами, и нажав вверху кнопку «Project Root» помечаем папку как корень проекта
  • нажимаем «Next»
  • нажимаем Add new local server

введите сюда описание изображения

  • вводим имя сервера любое и Web Server root URL. В рассматриваемом примере это http://localhost/testy2

введите сюда описание изображения

  • нажимаем «Next» и затем «Finish»

Запуск

Для начала в левой части панели с кодом на любой строке можно кликнуть ЛКМ, тем самым поставив точку останова (breakpoint — брейкпойнт). Это то место, где отладчик автоматически остановит выполнение PHP, как только до него дойдёт. Количество breakpoint’ов не ограничено. Можно ставить везде и много.

введите сюда описание изображения

Если кликнуть ПКМ и во всплывающем меню выбрать Debug (или в верхнем меню — RunDebug), то при первом запуске PHPStorm попросит настроить интерпретатор. Т.е. надо выбрать версию PHP из папки, где он лежит, чтобы шторм знал, какую версию он будет отлаживать.

введите сюда описание изображения

Теперь можно нажать Debug!!!

В данном случае, т.к. функция вызывается сразу на той же странице, то при нажатии кнопки Debug — отладчик моментально вызовет функцию, выполнение «заморозится» на первом же брейкпойнте. В ином случае, для активации требуется исполнить действие, при котором произойдет исполнение нужного участка кода (клик на кнопку, передача POST-запроса с формы с данными и другие действия).

введите сюда описание изображения

Цифрами обозначены:

  1. Стэк вызовов, все вложенные вызовы, которые привели к текущему месту кода.
  2. Переменные. На текущий момент строки ниже номера 3 ещё не выполнились, поэтому определена лишь $data
  3. Показывает текущие значения любых переменных и выражений. В любой момент здесь можно нажать на +, вписать имя любой переменной и посмотреть её значение в реальном времени. Например: $data или $nums[0], а можно и $nums[i] и item['test']['data'][$name[5]][$info[$key[1]]] и т.д. На текущий момент строки ниже номера 3 ещё не выполнились, поэтому $sum и $output обозначены красным цветом с надписью «cannot evaluate expression».

Процесс

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

введите сюда описание изображения

Show Execution Point (Alt+F10) — переносит в файл и текущую линию отлаживаемого скрипта. Например, если файлов много, решили посмотреть что в других вкладках, а потом забыли где у вас отладка :)

Step Over (F8) — делает один шаг, не заходя внутрь функции. Т.е. если на текущей линии есть какая-то функция, а не просто переменная со значением, то при клике данной кнопки, отладчик не будет заходить внутрь неё.

Step Into (F7) — делает шаг. Но в отличие от предыдущей, если есть вложенный вызов (например функция), то заходит внутрь неё.

Step Out (Shift+F8) — выполняет команды до завершения текущей функции. Удобно, если случайно вошли во вложенный вызов и нужно быстро из него выйти, не завершая при этом отладку.

Rerun (Ctrl+F5) — перезапускает отладку.

Resume Program(F9) — продолжает выполнение скрипта с текущего момента. Если больше нет других точек останова, то отладка заканчивается и скрипт продолжает работу. В ином случае работа прерывается на следующей точке останова.

Stop (Ctrl+F2) — завершает отладку.

View Breakpoints (Ctrl+Shift+F8) — просмотр всех установленных брейкпойнтов.

Mute Breakpoints — отключает брейкпойнты.

Итак, в текущем коде видно значение входного параметра:

  • $data = "23 24 11 18" — строка с данными через пробел
  • $nums = (4) ["23", "24", "11", "18"] — массив, который получился из входной переменной.

введите сюда описание изображения

Если нажмем F8 2 раза, то окажемся на строке 7; во вкладках Watches и Variables и в самой странице с кодом увидим, что переменная $sum была инициализирована и её значение равно 0.

Если теперь нажмем F8, то попадем внутрь цикла foreach и, нажимая теперь F8, пока не окончится цикл, можно будет наблюдать на каждой итерации, как значения $num и $sum постоянно изменяются. Тем самым мы можем проследить шаг за шагом весь процесс изменения любых переменных и значений на любом этапе, который интересует.

Дальнейшие нажатия F8 переместят линию кода на строки 11, 12 и, наконец, 15.


Дополнительно

Если нажать на View Breakpoints в левой панели, то можно не только посмотреть все брейкпойнты, но в появившемся окне можно еще более тонко настроить условие, при котором на данной отметке надо остановиться.
В функции выше, например, нужно остановиться только когда $sum превысит значение 20.

введите сюда описание изображения

Это удобно, если останов нужен только при определённом значении, а не всегда (особенно в случае с циклами).

Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Отладка

Основы PHP

Debug

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

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

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

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

Прочитайте сообщение об ошибке, поймите его — это ключевое действие, на основе которого можно планировать дальнейшие шаги:

Error: Call to undefined function AppUsersundef()

/usr/src/app/src/Users.php:9
/usr/src/app/tests/UsersTest.php:27

Вывод ошибок делится на две части: непосредственно сообщение с ошибкой и бэктрейс.

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

Отладка всегда сводится к двум вещам:

  • Перевести сообщение об ошибке
  • Найти в бэктрейсе место в своем коде, после которого произошла ошибка

Каждая строчка в бэктрейсе представляет собой указание на файл и строчку, в которой была вызвана соответствующая функция. Бэктрейс называется back, потому что вывод строк идет в обратном порядке: наверху находится последний вызов, внизу — первый.

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

Типы ошибок

Наиболее простые и понятные ошибки — синтаксические. Они связаны с тем, что код записан неверно: например, забыта точка с запятой в конце инструкции.

В выводе таких ошибок всегда присутствуют фразы parse error и syntax error.

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

PHP Parse error:  syntax error, unexpected '}' in /usr/src/app/src/Users.php on line 7

Еще одна большая группа ошибок называется ошибками программирования. Например, к ним относятся:

  • Вызов несуществующей функции
  • Использование необъявленной переменной
  • Передача неверных аргументов в функции, например, аргументов, имеющих неверный тип

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

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

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

<?php

// Функция должна считать сумму чисел, но считает разность:
function sum($a, $b)
{
    return $a - $b;
}

Отладка

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

Рассмотрим конкретный пример. Ниже описана функция, которая считает сумму чисел от числа $start до числа $finish. Если начало равно трем, а конец — пяти, то программа должна вычислить: 3 + 4 + 5:

<?php

function sumOfSeries($start, $finish)
{
    $sum = 0;
    for ($i = $start; $i < $finish; $i++) {
        $sum += $i;
    }

    return $sum;
}

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

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

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

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

Глядя на код функции sumOfSeries замечаем, что основных переменных там две: $i и $sum, именно они меняются в цикле. Из этого можно сделать ровно один вывод: нужно явно посмотреть, какие значения им даются на каждой итерации. После этого найти ошибку не составит труда.

Один из способов отслеживать значения переменных во время выполнения кода связан с использованием отладчиков. Отладчики интегрируются с популярными редакторами и позволяют визуально выполнить код по шагам, отслеживая любые изменения. Подробнее о том, как их использовать можно прочитать во множестве статей. Их можно найти по запросу: xdebug php <название редактора>.

PHP Debugging

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

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

<?php
function sumOfSeries($start, $finish)
{
    $sum = 0;
    for ($i = $start; $i < $finish; $i++) {
        print_r('new iteration !!!!');
        print_r($i);
        $sum += $i;
        print_r($sum);
    }

    return $sum;
}

sumOfSeries(3, 5);

// new iteration !!!!
// 3
// 3
// new iteration !!!!
// 4
// 7

То, что печатается на экран, отображается во вкладке OUTPUT, на которую автоматически переключается редактор во время проверки. Из этого вывода сразу можно понять, что количество итераций цикла меньше, чем нужно на одну. Почему-то не выполняется сложение для последнего числа, которое обозначено как $finish. И действительно, если посмотреть на определение, то видно, что там используется $i < $finish вместо $i <= $finish.

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

Для решения этой задачи на Хекслете подключена библиотека var-dumper. Она предоставляет две функции: dump и dd, которые доступны в любом месте программы. Первая просто красиво выводит переданный аргумент, а вторая — еще и останавливает выполнение кода:

<?php

$var = 'hello, world!';
dump($var);

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


Дополнительные материалы

  1. Как найти ошибки в коде?
  2. Psysh: REPL и интерактивный дебаггер для PHP

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

If you’re using PHP or adopting a PHP application, you need to know how to debug in PHP. Debugging PHP errors in a production environment is one of the most frustrating things a developer can go through. Identifying the underlying cause is challenging when error reports are ambiguous.

Nobody enjoys debugging their code. However, if you want to create great web applications, you must fully complete the process.

In this guide, you will learn certain PHP debugging methods that apply to practically every programming. I’ll go over the specifics of PHP, starting with the fundamentals and working my way up to fully integrated debugging approaches.

We will cover the following:

  1. Dumping Variables to stdout
  2. Switch Error Reporting Level
  3. Monitor Error Log
  4. PHP Debugging Tools
  5. Tools to Consider for Debugging

Dumping Variables to stdout

One approach to observe what’s going on in your PHP application is to use the var_dump function.

When you don’t have any other options and need a quick way to debug PHP script, you can generally output values. This may entail running var_dump or logging a series of events.

var_dump() is a PHP native function that shows structured, human-readable data about one (or more) expressions. This is especially beneficial when working with arrays and objects, as var_dump() recursively reveals their structure, providing you the most complete picture of what’s going on. It will print the value of a variable to stdout.

You can also use additional routines to debug PHP scripts through outputs. Here are a few functions:

  • var_dump ($var) — Dumps the type and value of the variable to stdout.
  • print_r ($var) — It prints the variable value to stdout in a human-readable format.
  • get_defined_vars() — It gets all defined variables, including built-ins and custom variables (to see them, use print_r).
  • debug_zval_dump ($var) — Dumps the variable together with its reference counts. This is helpful when updating a single reference from multiple pathways.
  • debug_print_backtrace() — It prints the current function call-chain in a backtrace.
  • debug_backtrace() — It obtains a backtrace. Asynchronously, you can print_r, log it to a file, or send it to a logging endpoint.

Read this blog to learn more about these functions for PHP debugging.

Here’s some code that shows how to utilize each of these helpful debugging functions:

<?php

$myVar = "Hello!!!";
var_dump($myVar);
print_r($myVar);
$allVars = get_defined_vars();
print_r($allVars);
debug_zval_dump($allVars);

function sayHello($hello) {
    echo $hello;
    debug_print_backtrace();
}
sayHello($myVar);

?>

These functions allow you to debug your PHP code quickly.

Monitor Error Log

To debug your code, you need to know about the error. Unless you monitor the logs all day and night, you won’t know when something bad happened.

Learn How to Log to Console in PHP and Why Should You Do It.

Sending your PHP logs to a provider that will handle a few important things for you is the best option:

Many PHP logging utilities can be configured to operate with Atatus. Atatus works with PHP that handles all of these tasks for you. It also captures traces automatically—and only when it should.

You want to be able to see all of your logs in one location. If you can keep your logs and metrics in one place across all instances. You’d be able to recognize issues no matter where it occurs.

  • Traces
    It’s not only a stack dump that reveals what happened when an issue occurred. It’s also a means of monitoring performance, which is frequently a symptom or cause of a bug.
  • Deduplication of Log Entries
    When a bug generates an error, the logs can quickly fill up. It’s a show-stopper to just go through the logs and find hundreds or thousands of identical entries.
  • Alerting
    Alerting is a way to send notifications automatically about an issue to a group email or an individual. This could be due to a server problem or errors in your logs. It should be configurable, and you should be able to make changes to it.

Without a doubt, Atatus is an excellent tool for discovering bugs. However, after you’ve found them, you must correct them.

Switch Error Reporting Level

Error reporting in PHP can be configured in a few different ways. You must have access to the php.ini file to use it. You might also use the access configuration. If you can’t utilize configuration files, you can use a script to change the values. This is possible, but consider how you’d change modes after your application has been deployed.

The correct degrees of error logging can be achieved using a mix of settings. You should consider the following options:

  • error_reporting — It determines the logging level. During development, E_NOTICE is useful since it alerts you to errors such as unassigned variables.
  • display_errors — It instructs PHP on whether and where error messages should be displayed.
  • display_startup_errors — Only use it when you’re debugging.
  • log_errors and error_log — It communicates with each other to send errors to a log file. Rather of displaying them to end-users, do this in production.

The PHP manual goes over these options in greater depth and provides further information. Even if your logging settings are perfect, you must still monitor for errors.

PHP Debugging Tools

One of several debugging tools can be used to attach a debugger client to PHP code. Debug utilities such as Xdebug and ZendDebugger are supported by PhpStorm.

We are polyglots (people who know or use several languages), so we need an IDE that supports many languages. We’ve used Xdebug with Visual Studio before, so let’s see how to do it with VS Code.

The debug server is identical, but each client (IDE or CLI) has a slightly different configuration. The debug server (a Zend extension) opens a port through which the client communicates with the server. It’s merely a matter of setting up the right components and installing them.

The following are the steps to programming in PHP:

  • In VS Code, look for PHP extensions.
  • Install the PHP Debug extension to your PHP configuration.
  • Click “Reload” to reload VS Code.
  • Install the Xdebug program. The PHP Debug extension for VS Code is limited to Xdebug integration. If we install PHP 7.0, the correct version of Xdebug must be downloaded from the download page.
<?php
   phpinfo();
?>
  • Put it in the PHP/ext directory once you’ve found the proper version.
  • The next step is to set up PHP to use the extension and enable remote debugging. In the php.ini file provided in PHP Info, add the following configuration:
; set the extension path
zend_extension="/absolute/path/to/xdebug.so"

; allow remote debugging
[XDebug]
xdebug.remote_enable = 1
xdebug.remote_autostart = 1
  • It will configure the PHP server to work with XDebug. No matter which IDE you use, the processes are the same.
  • Xdebug creates an HTTP port for your debugger to connect to. The client must still be set to connect to the server and utilize the debugging protocol.
  • Finally, set up VS Code to connect with Xdebug. After a few simple steps, the attachment is completed automatically.

Configuring Your IDE

You must configure IDE to attach to the debugger after installing Xdebug. This is equivalent to adding a debug configuration in Visual Studio Code. Fortunately, it is automatic and only a few simple steps are required:

  • Switch to the debug mode.
  • The languages menu will appear when you click the gear.
  • Choose PHP. The default configuration will be generated by Visual Studio Code.
  • The PHP server should be reloaded. We’ll need to install a third-party extension called «PHP Server» to make this easier. To control the PHP server, use the context menu (right-click).
  • It puts the IDE in a position where it can connect to Xdebug. A TCP port on the debug server is used to communicate with the debugger. By default, Xdebug uses the DBGp protocol via port 9000.

Attaching a Debugger

We’ve installed a debugging extension and configured our IDE. It’s now time to connect to the debugger.

A launch.json file was generated by the PHP Debug plugin for VS Code. That file is placed in the project’s root .vscode directory. Here’s what it resulted in:

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [{
            "name": "Listen for XDebug",
            "type": "php",
            "request": "launch",
            "port": 9000
        },
        {
            "name": "Launch currently open script",
            "type": "php",
            "request": "launch",
            "program": "${file}",
            "cwd": "${fileDirname}",
            "port": 9000
        }
    ]
}

It’s going to add two new launch configurations. These can be found in the debug view. With the current script, we can either connect to an existing server or start a new one. Since I already have phpinfo running, I’ll begin by selecting Listen for XDebug to connect to that server.

The debug toolbar will appear once you’ve been connected.

A control mechanism can be found in most debuggers. You can use this to start, stop, step, and restart your debugger. We’re connected and ready because we see a stop and pause icon, so let’s get stepping.

Setting PHP Breakpoints

The first step is to create a php breakpoint where you believe there is an issue. We normally plug one in right before we enter the problem code so we can see what’s going on. To get things started, let’s add one to the phpinfo script.

By clicking in the left margin, you may usually set a breakpoint on the next line. Alternatively, place your cursor on the line and press F9. If you have many function calls on the same line, this is a good approach to make sure the php breakpoint is set on the right one. In the left margin, a red dot should emerge. This is an example of a breakpoint. It should also appear in the «breakpoints» section.

We’re still looking at things in Debug view. One php breakpoint was set. You can now right-click the red breakpoint circle in the margin next to the code and select Edit breakpoint. Conditions come in handy when you have a large collection but only one element is causing a problem. Conditionals are something we use all the time.

You can log a message and break after a particular number of hits, in addition to conditional breakpoints. When you have code that repeats without a specific unique value to break on, the latter is useful.

For example, you may have code that renders components in a collection. You can simply set the hit count to 13 if the 13th component produces a disaster. We’ve had to manually count too many times to notice how useful this feature is.

Tools to Consider for Debugging

Finally, we’d like to highlight a handful of useful tools that we’ve found beneficial in the debugging process. We won’t go into great detail about how to install and configure these extensions and add-ons, but we wanted to bring them up because they can make our life so much easier.

  1. Xdebug
    Xdebug is a PHP extension that seeks to make the process of debugging your applications a little easier. Xdebug is extremely customizable and adaptable to a wide range of scenarios. Stack traces, for example, can be set to four different levels of detail. This means you can change the sensitivity of Xdebug’s output to acquire more detailed information about your application’s behavior.
  2. FirePHP
    FirePHP is a PHP library and Firefox add-on that can be extremely beneficial when working with AJAX. Essentially, FirePHP allows you to use a single method call to log debug information to the Firebug console. All data logged to the Firebug console is sent via response headers, so it does not affect how the browser renders the website.

Conclusion

In this article, we’ve covered a lot. It’ll be enough to get you started debugging PHP. This is a complex subject, but the best way to learn is to practice. You’ll develop a reputation as a bug exterminator extraordinaire if you practice debugging until you’ve mastered the technique.

Hopefully, you’ve learned how to debug in PHP, use var_dump(), and more. Similarly, I hope you will find Xdebug and FirePHP beneficial and that they will make your development cycle easier.


Atatus:
PHP Performance Monitoring and Log Management

Atatus is a Full Stack Observability Platform that collects all requests to your PHP applications without requiring you to change your source code. However, the tool does more than just keep track of your application’s performance. It monitors logs from all of your PHP applications and systems into a centralized and easy-to-navigate user interface, allowing you to troubleshoot faster.

We give a cost-effective, scalable method to centralized PHP logging, so you can obtain total insight across your complex architecture. To cut through the noise and focus on the key events that matter, you can search the logs by hostname, service, source, messages, and more. When you can correlate log events with APM slow traces and errors, troubleshooting becomes easy.

Try your 14-day free trial of Atatus.

Время на прочтение
10 мин

Количество просмотров 12K

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

Пример гипотезы:

Функции strpos легко передать аргументы в неправильном порядке. 

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

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

Предпосылки

Вот уже несколько месяцев я занимаюсь поддержкой PHP линтера NoVerify (почитать о котором можно в статье NoVerify: линтер для PHP от Команды ВКонтакте).

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

Ранее я активно разрабатывал go-critic и ситуация была схожей, с той лишь разницей, что анализировались исходники на Go, а не на PHP. Когда я узнал об утилите gogrep, мой мир перевернулся. Как видно из названия, эта утилита имеет что-то общее с grep’ом, только поиск производится не по регулярным выражениям, а по синтаксическим шаблонам (позже объясню, что это значит).

Я уже не хотел жить без умного grep, поэтому в один вечер решил сесть и написать phpgrep.

Анализируемый корпус

Чтобы было увлекательно, мы сразу погрузимся в применение. Будем анализировать небольшой набор достаточно известных и крупных PHP проектов, доступных на GitHub.

В наш набор попали следующие проекты:

Для людей, которые замышляют то, что мы с вами замышляем, это очень аппетитный набор.

Итак, поехали!

Использование присваивания в качестве выражения

Если присваивание используется как выражение, причём:

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

то, скорее всего, в коде ошибка.

Для начала, возьмём за «логический контекст» следующие конструкции:

  1. Выражение внутри «if ($cond)«.
  2. Условие тернарного оператора: «$cond ? $x : $y«.
  3. Условия продолжений циклов «while ($cond)» и «for ($init; $cond; $post)«.

В правой части присваивания мы ожидаем константы или литералы.

Зачем на нужны такие ограничения?

Начнём с (1):

# утилита поиска по синтаксическим шаблонам
# |     производим поиск по текущей директории (и всем дочерним)
# |     |
# |     |
phpgrep . 'if ($_ = []) $_' # 1
#         |               
#         |               
#         строка шаблона поиска

# Дополнительные 3 шаблона через отдельные запуски.
phpgrep . 'if ($_ = ${"const"}) $_' # 2
phpgrep . 'if ($_ = ${"str"}) $_'   # 3
phpgrep . 'if ($_ = ${"num"}) $_'   # 4

Здесь мы видим 4 шаблона, единственным различием между которыми выступает присваиваемое выражение (RHS). Начнём с первого из них.

Шаблон «if ($_ = []) $_» захватывает if, у которого любому выражению присваивается пустой массив. $_ сопоставляется с любым expression или statement.

         литерал пустого массива (RHS)
         |
if ($_ = []) $_
    |        |
    |        любое тело if'а, причём не важно, с {} или без
    любой LHS у присваивания

В следующих примерах используются более сложные группы const, str и num. В отличие от $_ они описывают ограничения на совместимые операции.

  • const — именованная константа или константа класса.
  • str — строковой литерал любого типа.
  • num — числовой литерал любого типа.

Этих шаблонов достаточно, чтобы добиться нескольких срабатываний на корпусе.

⎆ moodle/blocks/rss_client/viewfeed.php#L37:

if ($courseid = SITEID) {
    $courseid = 0;
}

Вторым срабатыванием в moodle стала зависимость ADOdb. В upstream библиотеки проблема всё ещё присутствует.

⎆ ADOdb/drivers/adodb-odbtp.inc.php#L741:

В этом фрагменте прекрасно многое, но для нас релевантна только первая строка. Вместо сравнения поля databaseType мы выполняем присваивание и всегда входим внутрь условия.

Ещё одно интересное место, где мы хотим выполнять действия только для «корректных» записей, но, вместо этого, выполняем их всегда и, более того, отмечаем любую запись как корректную!

⎆ moodle/question/format/blackboard_six/formatqti.php#L598:

// For BB Fill in the Blank, only interested in correct answers.
if ($response->feedback = 'correct') {
    // ...
}

Расширенный список шаблонов для этой проверки

Повторим то, что мы изучили:

  • Шаблоны выглядят как PHP-код, который они находят.
  • $_ обозначает «что угодно». Можно сравнить с . в регулярных выражениях.
  • ${"<class>"} работает как $_ с ограничением на тип элементов AST.

Стоит ещё подчеркнуть, что всё, кроме переменных, сопоставляется дословно (literally). Это значит, что шаблону «array(1, 2 + 3)» будет удовлетворять лишь идентичный по синтаксической структуре код (пробелы не влияют). С другой стороны, шаблону «array($_, $_)» удовлетворяет любой литерал массива из двух элементов.

Сравнение выражения с самим собой

Потребность сравнить что-либо с самим собой возникает очень редко. Это может быть проверка на NaN, но как минимум в половине случаев это ошибка copy/paste.

⎆ Wikia/app/extensions/SemanticDrilldown/includes/SD_FilterValue.php#L103:

if ( $fv1->month == $fv1->month ) return 0;

Справа должно быть «$fv2->month«.

Для выражения повторяющихся частей в шаблоне мы используем переменные с именами, отличными от «_«. Механизм повторений в шаблоне похож на обратные ссылки в регулярных выражениях.

Шаблон «$x == $x» будет как раз тем, что найдёт пример выше. Вместо «x» может использоваться любое имя. Здесь важно лишь то, чтобы имена были идентичны. Переменные шаблона, которые имеют различающиеся имена, не обязаны иметь совпадающее содержимое при захвате.

Следующий пример найден с помощью «$x <= $x«.

⎆ Drupal/core/modules/views/tests/src/Unit/ViewsDataTest.php#L166:

$prev = $base_tables[$base_tables_keys[$i - 1]];
$current = $base_tables[$base_tables_keys[$i]];
$this->assertTrue(
  $prev['weight'] <= $current['weight'] &&
  $prev['title'] <= $prev['title'], // <-------------- ошибка
  'The tables are sorted as expected.');

Дублирующиеся подвыражения

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

Один из моих любимцев — «$_ ? $x : $x«.
Это тернарный оператор, у которого true/false ветки идентичны.

⎆ joomla-cms/libraries/src/User/UserHelper.php#L522:

return ($show_encrypt) ? '{SHA256}' . $encrypted : '{SHA256}' . $encrypted;

Обе ветви дублируются, что подсказывает о потенциальной проблеме в коде. Если мы посмотрим на код вокруг, то сможем понять, что там должно было быть вместо этого. В угоду читабельности я вырезал часть кода и сократил название переменной $encrypted до $enc.

case 'crypt-blowfish':
    return ($show_encrypt ? '{crypt}' : '') . crypt($plaintext, $salt);
case 'md5-base64':
    return ($show_encrypt) ? '{MD5}' . $enc : $enc;
case 'ssha':
    return ($show_encrypt) ? '{SSHA}' . $enc : $enc;
case 'smd5':
    return ($show_encrypt) ? '{SMD5}' . $enc : $enc;
case 'sha256':
    return ($show_encrypt) ? '{SHA256}' . $enc : '{SHA256}' . $enc;
default:
    return ($show_encrypt) ? '{MD5}' . $enc : $enc;

Я бы поставил на то, что коду необходим следующий патч:

- ($show_encrypt) ? '{SHA256}' . $encrypted : '{SHA256}' . $encrypted;
+ ($show_encrypt) ? '{SHA256}' . $encrypted : $encrypted;

Опасные приоритеты операций в PHP

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

Во многих языках программирования выражение «x & mask != 0» имеет интуитивный смысл. Если mask описывает какой-то бит, то данный код проверяет, что в x этот бит не равен нулю. К сожалению, для PHP это выражение будет вычисляться так: «x & (mask != 0)«, что почти всегда не то что вам нужно.

WordPress, Joomla и moodle используют SimplePie.

⎆ SimplePie/library/SimplePie/Locator.php#L254
⎆ SimplePie/library/SimplePie/Locator.php#L384
⎆ SimplePie/library/SimplePie/Locator.php#L412
⎆ SimplePie/library/SimplePie/Sanitize.php#L349
⎆ SimplePie/library/SimplePie.php#L1634

$feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0

SIMPLEPIE_FILE_SOURCE_REMOTE определён как 1, поэтому выражение будет эквивалентно:

$feed->method & (1 === 0)
// =>
$feed->method & false

Примеры шаблонов для поиска

Продолжая тему неожиданных приоритетов операций, вы можете почитать про тернарный оператор в PHP. На хабре этому даже посвятили статью: Порядок выполнения тернарного оператора.

Можно ли такие места найти с помощью phpgrep? Ответ: да!

phpgrep . '$_ == $_ ? $_ : $_ ? $_ : $_'
phpgrep . '$_ != $_ ? $_ : $_ ? $_ : $_'

Прелести проверки URL с помощью регулярных выражений

⎆ Wikia/app/maintenance/wikia/updateCentralInterwiki.inc#L95:

if ( preg_match( '/(wowwiki.com|wikia.com|falloutvault.com)/', $url ) ) {
    $local = 1;
} else {
    $local = 0;
}

По задумке автора кода, мы проверяем URL на совпадение с одним из 3-х вариантов. К сожалению, символ . не экранирован, что приведёт к тому, что вместо falloutvault.com мы можем завести себе falloutvaultxcom на любом домене и пройти проверку.

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

Найти такие места можно с помощью запуска phpgrep:

phpgrep . 'preg_match(${"pat:str"}, ${"*"})' 'pat~[^\].(com|ru|net|org)b'

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

Фильтры можно применять к любой переменной шаблона. Кроме регулярных выражений есть также структурные операторы = и !=. Полный список можно найти в документации.

${"*"} захватывает произвольное количество любых аргументов, поэтому нам можно не волноваться за опциональные параметры функции preg_match.

Дубликаты ключей в литерале массива

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

<?php
var_dump(['a' => 1, 'a' => 2]);
// Результат: array(1) {["a"]=> int(2)}

Мы можем найти такие массивы с помощью phpgrep:

[${"*"}, $k => $_, ${"*"}, $k => $_, ${"*"}]

Этот шаблон можно расшифровать так: «литерал массива, в котором есть хотя бы два идентичных ключа в произвольной позиции». Выражения ${"*"} помогают нам описать «произвольную позицию», допуская 0-N элементов до, между и после интересующих нас ключей.

⎆ Wikia/app/extensions/wikia/WikiaMiniUpload/WikiaMiniUpload_body.php#L23:

$script_a = [
    'wmu_back' => wfMessage( 'wmu_back' )->escaped(),
    'wmu_back' => wfMessage( 'wmu_back' )->escaped(),
    // ...
];

В данном случае это не является грубой ошибкой, но мне известны случаи, когда дублирование ключей в крупных (100+ элементов) массивах несло как минимум неожиданное поведение, в котором один из ключей перекрывал значение другого.


На этом наш краткий экскурс на примерах окончен. Если вам хочется ещё, в конце статьи описано, как получить все результаты.

Что же такое phpgrep?

Большая часть редакторов и IDE используют для поиска кода (если это не поиск по специальному символу типа класса или переменной) обычный текстовой поиск — проще говоря, что-то вроде grep’а.

Вы вводите «$x«, находите «$x«. Вам могут быть доступны регулярные выражения, тогда вы можете пытаться, по сути, парсить PHP-код регулярками. Иногда это даже работает, если ищется что-то вполне определённое и простое — например, «любая переменная с некоторым суффиксом». Но если эта переменная с суффиксом должна быть частью другого составного выражения, возникают трудности.

phpgrep — это инструмент для удобного поиска PHP-кода, который позволяет искать не с помощью text-oriented регулярок, а с помощью syntax-aware шаблонов.

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

Опциональный контент: Quick Start

Quick start

Установка

Для amd64 есть готовые релизные сборки под Linux и Windows, но если у вас установлен Go, то достаточно одной команды, чтобы получить свежий бинарник под вашу платформу:

go get -v github.com/quasilyte/phpgrep/cmd/phpgrep

Если $GOPATH/bin находится в системном $PATH, то команда phpgrep станет сразу же доступной. Чтобы это проверить, попробуйте запустить команду с параметром -help:

phpgrep -help

Если же ничего не происходит, найдите, куда Go установил бинарник и добавьте его в переменную окружения $PATH.

Старый и надёжный способ посмотреть $GOPATH, даже если он не выставлен явно:

go env GOPATH

Использование

Создайте тестовый файл hello.php:

<?php
function f(...$xs) {}
f(10);
f(20);
f(30);
f($x);
f();

Запустите на нём phpgrep:

# phpgrep hello.php 'f(${"x:int"})' 'x!=20'
hello.php:3: f(10)
hello.php:5: f(30)

Мы нашли все вызовы функции f с одним аргументом-числом, значение которого не равно 20.

Как работает phpgrep

Для разбора PHP используется библиотека github.com/z7zmey/php-parser. Она достаточно хороша, но некоторые ограничения phpgrep следуют из особенностей используемого парсера. Особенно много трудностей возникает при попытках нормально работать со скобочками.

Принцип работы phpgrep прост:

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

Самое интересное — это как именно сопоставляются на равенство два AST-узла. Иногда тривиально: один-к-одному, а мета-узлы могут захватывать более одного элемента. Примерами мета-узлов является ${"*"} и ${"str"}.

Заключение

Было бы нечестно говорить о phpgrep, не упомянув structural search and replace (SSR) из PhpStorm. Они решают похожие задачи, причём у SSR есть свои преимущества, например, интеграция в IDE, а phpgrep может похвастаться тем, что является standalone программой, которую гораздо проще поставить, например, на CI.

Помимо прочего, phpgrep — это ещё и библиотека, которую можно использовать в своих программах для матчинга PHP кода. Особенно это полезно для линтеров и кодогенерации.

Буду рад, если этот инструмент будет вам полезен. Если же эта статья просто мотивирует вас посмотреть в сторону вышеупомянутого SSR, тоже хорошо.

Дополнительные материалы

Полный список шаблонов, который был использован для анализа, можно найти в файле patterns.txt. Рядом с этим файлом можно найти скрипт phpgrep-lint.sh, упрощающий запуск phpgrep со списком шаблонов.

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

Черпать вдохновение на шаблоны проверок можно, например, из статей PVS studio. Мне очень понравилась Logical Expressions: Mistakes Made by Professionals, которая трансформируется во что-то такое:

# Для "x != y || x != z":
phpgrep . '$x != $a || $x != $b'
phpgrep . '$x !== $a || $x != $b'
phpgrep . '$x != $a || $x !== $b'
phpgrep . '$x !== $a || $x !== $b'

Вам также может быть интересна презентация phpgrep: syntax-aware code search.

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

Welcome to a tutorial on how to debug PHP code. When it comes to troubleshooting, some beginners foam in the mouth and go into a trance of “very difficult, only for experts”. No. Debugging is a basic skill that does not require any special tools.

The simplest form of debugging in PHP involves:

  1. Turning on error reporting.
  2. Reading the error messages.
  3. Tracing where the problem is and fixing it.

That’s all, don’t understand what the fuss is all about. Read on for more debugging tips!

TABLE OF CONTENTS

PART 1) TURN ON ERROR REPORTING

The first step of debugging is to stop working in the dark, at least learn how to get the error message.

PHP ERROR REPORTING MECHANICS

1-set-reporting.php

<?php
// (A) ERROR REPORTING LEVEL
// E_ALL, E_WARNING, E_STRICT, E_PARSE, E_DEPRECATED, E_NOTICE
error_reporting(E_ALL & ~E_NOTICE); // ALL EXCEPT NOTICES
// error_reporting(E_ALL); // ALL KINDS OF ERROR
// error_reporting(0); // NO ERROR REPORTING
 
// (B) ERROR LOG
ini_set("log_errors", 1); // SAVE ERROR TO LOG FILE
ini_set("error_log", __DIR__ . DIRECTORY_SEPARATOR . "error.log"); // LOG FILE
 
// (C) DISPLAY ERROR MESSAGES
ini_set("display_errors", 1);

When it comes to error reporting in PHP, there are only 3 mechanics:

  1. error_reporting() – Set the error reporting level. PHP has a long list of error levels, from important E_WARNING to “notice only” E_NOTICE. Will leave a link below to the full list.
  2. Save the error into a log file.
    • log_errors specifies if you want to save the errors into a log file.
    • error_log is the target log file.
  3. display_errors display the error on the screen.
    • Don’t confuse this with error_reporting(). Error reporting sets what kinds of errors need to be reported.
    • display_errors simply sets if the error should be displayed on the screen.
    • That is, we can turn off display_errors but still keep the errors in a log file. But if we turn off error_reporting(), no errors will be reported at all.

RECOMMENDED FOR TEST SERVER

  • error_reporting(E_ALL & ~E_NOTICE);
  • ini_set("display_errors", 1);
  • ini_set("log_errors", 0);

When you are working on your test server, just set PHP to show all errors.

RECOMMENDED FOR LIVE SERVER

  • error_reporting(E_ALL & ~E_NOTICE);
  • ini_set("display_errors", 0);
  • ini_set("log_errors", 1);
  • ini_set("error_log", "PATH/error.log");

Yep, these should be hard-coded into php.ini already. On a live server, we hide the error messages but keep them in a log file. For security purposes, we don’t want to show the error message, and script name to the user.

PART 2) COMMON MISTAKES

Now that you have eyes on an error message, the next step is to make some sense of what it means. Here are some common examples.

FATAL ERRORS – UNDEFINED FUNCTIONS & FILES

2a-error-fatal.php

<?php
$foobar = doesnotexist();
require "doesnotexist.php";

Fatal error: Uncaught Error: Call to undefined function doesnotexist() in D:http2a-error-fatal.php:2 Stack trace: #0 {main} thrown in D:http2a-error-fatal.php on line 2

“Call to undefined function”, what does that mean? If that is not Captain Obvious enough – The function you tried to call is not defined and does not exist. Could be a silly typo mistake, or you forgot to include the file somewhere.

SYNTAX ERRORS – MISSING OPERATORS

2b-error-syntax.php

<?php
$foobar = "Doge Bork"
$hello = "World";
$arr = ["Foo", "Bar'};

Parse error: syntax error, unexpected ‘$hello’ (T_VARIABLE) in D:http2b-error-syntax.php on line 3

“Syntax error on line 3”. Just trace the code back a little, there is a missing ; on line 2. This can also mean a missing ,.(){} somewhere…

WARNINGS – WRONG DATA TYPE

2c-error-warn.php

<?php
function addition ($x, $y) {
  return $x + $y;
}

echo addition(1, "x");

Warning: A non-numeric value encountered in D:http2c-error-warn.php on line 3

Warnings are not critical errors, but they are still of significant importance. For example, putting a string into a function that seemingly needs numbers instead. While this will not break the system, it may end up with the wrong results.

NOTICES – NOT REALLY IMPORTANT…

2d-error-notify.php

<?php
if ($_POST["foo"]) {
  echo "YES!";
}

Notice: Undefined index: foo in D:http2d-error-notify.php on line 2

An annoying kind of “error”. Notices are often of little importance, and they are just here to remind you of stuff like “you have not defined this variable before”, or “this function has been deprecated, not recommended”.

SILENT ERRORS – WRONG RESULTS

2e-error-silent.php

<?php
function add ($x, $y) {
  // Original intention
  // return $x + $y;
  // Someone did a *small* typo mistake
  return $x - $y;
}

echo add(88, 22); 
// Should be 110, but result is 66

These are the deadly ones. These errors do not throw any messages, are hard to spot, and the script continues to run “as usual”. You will only realize that there is an error when the scripts do not produce the correct results.

PART 3) ERROR TRACING & TIPS

Some errors are easy to spot. Just go to the script and line that the error message indicates, eyeball a few lines of code. But how do we deal with complicated scripts? Here are a few troubleshooting tips.

DUMP THE VARIABLES

3a-var-dump.php

<?php
// (A) START SESSION
session_start();

// (B) LET'S SAY WE HAVE A SHOPPING CART
$_SESSION["cart"] = [
  "123" => [
    "name" => "Foo bar",
    "qty" => 99
  ],
  "234" => [
    "name" => "Doge",
    "qty" => 88
  ]
];

// (C) WHAT'S IN THE CART?
print_r($_SESSION);
var_dump($_SESSION);

So, what is in an array? What is in the session that may be causing problems? print_r() and var_dump() are your best friends.

STACK TRACE

3b-first.php

<?php
require "second.php";
$testObj = new Test();
$testObj->foo();

3b-second.php

<?php
class Test {
  function foo () {
    var_dump(debug_backtrace());
    debug_print_backtrace();
  }
}

In big projects, it is common to have multiple scripts sharing many library files. So which scripts and functions were previously called? Use debug_backtrace() and debug_print_backtrace() to find out.

MANUAL BREAKPOINT

3c-halt-section.php

<?php
// (A) INIT
$varA = ["Hello", "World", "Foo", "Bar"];
$varB = "";

// (B) CONCAT
foreach ($varA as $txt) { $varB .= $txt; }

// (C) DISPLAY & STOP HERE
print_r($varA);
echo $varB;
exit();

// (D) MORE PROCESSING
$varA[] = "Doge";
$varB .= "Last";

With debugging tools, we can set “breakpoints” to pause and check what is in which variables at that point. But no worries, we don’t actually need debugging tools to do that – Just output the variables that you want to check and use die() or exit() to manually stop the script at a specific point.

CUSTOM ERROR LOG

3d-custom-error-log.php

<?php
// (A) CURL INIT
$curl = curl_init();
curl_setopt_array($curl, [
  CURLOPT_URL => "https://DOES-NOT-EXIST.com/dummy.php",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HEADER => false
]);

// (B) CURL FETCH
$result = curl_exec($curl);
if ($result === false) {
  error_log("Failed connecting to https://DOES-NOT-EXIST.com", 0);
}
curl_close($curl);

Remember the error log from earlier? Code ninjas can’t be sitting at the desk, monitoring, and waiting for errors to happen 24/7. So utilize the error_log() function to capture your own custom error messages.

USE A GOOD EDITOR OR IDE

Captain Obvious to the rescue! If you have not installed a good code editor, do so now. It helps to automatically indent your code, and highlight all the silly mistakes.

P.S. This is VSCode, and there are plenty of free code editors. Links below.

DOWNLOAD & NOTES

Here is the download link to the example code, so you don’t have to copy-paste everything.

SUPPORT

600+ free tutorials & projects on Code Boxx and still growing. I insist on not turning Code Boxx into a «paid scripts and courses» business, so every little bit of support helps.

Buy Me A Meal Code Boxx eBooks

EXAMPLE CODE DOWNLOAD

Click here for the source code on GitHub gist, just click on “download zip” or do a git clone. I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

LINKS & REFERENCES

  • Runtime Error Configuration – PHP
  • Predefined Error Levels – PHP
  • Error Log – PHP
  • Free Code Editors – Code Boxx

THE END

Thank you for reading, and we have come to the end. I hope that it has helped you to better understand, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!

Понравилась статья? Поделить с друзьями:
  • Как найти ошибки в документе ворд
  • Как найти ошибки в договоре поставки
  • Как найти ошибки в договоре купли продажи
  • Как найти ошибки в договоре аренды
  • Как найти ошибки в диктанте