code quality and tooling

Reading Stack Traces

A stack trace shows how PHP reached an error. It lists the chain of function and method calls that were active when the failure happened.

Beginners often read only the final line and guess. A better habit is to read the error message, find the first useful application file, and trace the data backwards to the caller that passed the wrong value.

A small failing program

This script throws an exception when a product has no name.

PHP example
<?php

declare(strict_types=1);

function validateProduct(array $product): void
{
    if (trim($product['name'] ?? '') === '') {
        throw new InvalidArgumentException('Product name is required.');
    }
}

function buildReportLine(array $product): string
{
    validateProduct($product);

    return $product['name'];
}

function printReport(array $products): void
{
    foreach ($products as $product) {
        echo buildReportLine($product) . PHP_EOL;
    }
}

printReport([
    ['name' => 'Notebook'],
    ['name' => ''],
]);

When the second product is processed, validateProduct() throws.

What the trace tells you

A simplified trace might look like this:

Fatal error: Uncaught InvalidArgumentException: Product name is required.
in /app/report.php:9
Stack trace:
#0 /app/report.php(15): validateProduct(Array)
#1 /app/report.php(23): buildReportLine(Array)
#2 /app/report.php(27): printReport(Array)
#3 {main}

Read it in parts:

  • the error type is InvalidArgumentException
  • the message is Product name is required.
  • the exception was thrown on /app/report.php:9
  • validateProduct() was called by buildReportLine()
  • buildReportLine() was called by printReport()
  • printReport() was called by the main script

The top of the trace is where the failure was detected. The lower frames show how the program got there.

The throwing line is not always the root cause

Line 9 is where the exception was thrown, but the real problem is the empty product name passed into the report.

The fix might be one of several things:

  • clean the data before calling printReport()
  • skip invalid products
  • reject the input earlier
  • show a controlled error message
  • correct the test fixture or seed data

Do not delete the validation just because it is the line that failed. The validation may be doing the right thing.

Find the first useful application frame

Framework traces can be long. They may include routing, middleware, controllers, container calls, template rendering, or vendor package files.

In a real project, first identify:

  • the exception type and message
  • the file and line where the error was thrown
  • the first frame that belongs to the application code
  • the caller that supplied the bad data

Vendor frames are not useless, but most beginner fixes happen in the application's own code.

Type errors have useful traces too

A TypeError often means a function received the wrong kind of value.

PHP example
<?php

declare(strict_types=1);

function formatMoney(int $pennies): string
{
    return 'GBP ' . number_format($pennies / 100, 2);
}

echo formatMoney('499');

With strict types, passing a string where an integer is required fails. The trace tells you where the function was called with the wrong value.

The fix is usually at the boundary: convert or validate input before calling the typed function.

Use the trace to ask better questions

When reading a trace, ask:

  • What exact error happened?
  • Which file and line detected it?
  • Which function called that line?
  • What value was passed in?
  • Where did that value originally come from?
  • Should the value be fixed, rejected, converted, or handled?

This turns debugging from guessing into a methodical process.

A practical debugging workflow

Start with the message. Open the file and line where the error was thrown. Read the function around that line. Then move down the trace to the caller. Keep moving until you find the place where the wrong data entered the flow.

If the trace is from a framework, skip past vendor frames until you reach your controller, command, job, service, or test. That is usually where the practical fix belongs.

Before moving on, make sure you can read a short stack trace and identify the error message, the throwing line, the caller, and the likely place to fix the data.

Practice

Task: Read A Validation Stack Trace

Read this trace and answer the questions below.

Fatal error: Uncaught InvalidArgumentException: Product name is required.
in /app/report.php:9
Stack trace:
#0 /app/report.php(15): validateProduct(Array)
#1 /app/report.php(23): buildReportLine(Array)
#2 /app/report.php(27): printReport(Array)
#3 {main}

Questions

  • What type of error happened?
  • What is the error message?
  • Which function detected the problem?
  • Which function called it?
  • Is the likely fix to remove validation, or to fix the product data before it reaches the report?

Write your answers in short sentences.

Show solution

The error type is InvalidArgumentException.

The message is Product name is required.

The problem was detected inside validateProduct(), shown by this frame:

#0 /app/report.php(15): validateProduct(Array)

validateProduct() was called by buildReportLine(), and buildReportLine() was called by printReport().

The likely fix is to fix, reject, or skip the invalid product data before the report is built. Removing validation would hide the symptom and allow bad data to continue through the program.