Debugging (PHP) Applications

PHP Benelux, 28. 1. 2018
© David Buchmann, Liip AG




Security Bugs

This workshop focuses on finding the location of bugs. Scanning for security vulnerabilities is worth its own Workshop.

Bug Report

Look at the list of open bugs if already reported


What did you do - how to reproduce?
What did you expect to happen?
What was the actual result?
Screenshot if visual, copy console output if text (use ``` in github)

For OSS: Pull request with failing test, or even a fix?


Demo Tutorial Application

Demo Tutorial Application

vagrant up
vagrant ssh

Bug Hunting


git reset --hard
git checkout [branchname]
If you get random errors about files in /vagrant/var/cache/dev/ not found, do:
rm -rf var/cache/dev/
I often use obvious errors to illustrate a case. Real bugs will often be more complicated, but the strategies apply...

[b1] Bug 1


Look at exception message

[b1] RTFEM: Wrong Typehint

Cannot autowire service "App\Importer\Likes": argument "$em" of method "__construct()" references class "Doctrine\ORM\EntityManager" but no such service exists. Try changing the type-hint to one of its parents: interface "Doctrine\O RM\EntityManagerInterface", or interface "Doctrine\Common\Persistence\ObjectManager".

Exceptions are your friends, read them carefully.

[b2] Bug 2

bin/console app:import

Argument -v makes output more verbose.
Look at the stack trace.

[b2] Stacktrace

App\Importer\Mapper\LikeMapper->map() at .../Likes.php:49
App\Importer\Likes->import() at .../AppImportCommand.php:60
App\Command\AppImportCommand->execute() at .../Command.php:252 ...

Exception stack trace provides context of errors

[b3] DDD: Die Driven Debugging

bin/console app:import

The CustomerMapper checks if customer id already exists.
Add die('cache')|found|new in CustomerMapper::map

[b3] DDD

$customer = $this->customerRepository->find($data['ProductId']);

At most, use die() to check if you are in the expected place of the code

[b4] Use proper exceptions

bin/console app:import

Search code for "oops"
Search for die( and throw exceptions with decent messages

[b4] Use proper exceptions

do not make your collegues and your future self hate you

Demo: Step by step debugger

                ; config to run the remote debugger

Demo: Step by step debugger

  1. Configure your IDE
  2. Set IDE to listen to debugging
  3. ifconfig to find IP of vboxnet1
  4. php -d xdebug.remote_host= ./bin/console app:import

[b5] Segfault/Infinite Loops


At which test does it hang? Try --debug

[b5] Infinite Loop

Last line says CsvProductLoaderTest::testLoadFileNotFound

progressive report to see which test fails hard

[b6] Search codebase for context


Look at the page source for some context, then search the codebase to see what happens around it.

[b6] Search codebase for context

<h2 style="color:green;">Found {{ customer_count }} customers {{ render(controller('App\\Controller\\HomeController::extra')) }}

Program output can provide context where to look for errors

[b7] Search in logfiles

bin/console app:import --reset -q
http://debugging.lo shows only 99 customers instead of 100

Search for non-debug messages in logfile:
echo '' > var/log/dev.log
bin/console app:import --reset -q
cat var/log/dev.log | grep -v DEBUG

[b7] Search in logfiles

app.WARNING: Invalid number of columns for App\Importer\Loader\CsvLikeLoader at CSV row 26

Search logfiles for id's or message fragments. It helps if you know when the problem happened.

[b8] Unit Test (for debugging)

We built a (horrible) security into our website. But its faulty:

Build a unit test for the HomeController::index method to cover all cases.

[b8] Unit Test

if (!stripos($secret, 'david')) {

Build a unit test when debugging a specific component. Often you want to keep that test afterwards.

[b9] Do not assume

all green, great?
run bin/console app:import