Testing and Debugging PHP Applications
Testing is not about reaching a percentage. It is about making important behavior repeatable and catching regressions before customers do.
Choose the Right Test Level
| Level | Best for | Tradeoff |
|---|---|---|
| Unit | Business rules and value objects | Fast, but does not prove integration. |
| Integration | Repositories, SQL, filesystem, and APIs | More realistic, slower, needs controlled dependencies. |
| HTTP/feature | Routes, authentication, validation, and response behavior | Covers more layers but failures are less isolated. |
| End-to-end | Critical user journeys | Most realistic and most expensive to maintain. |
A Unit Test for a Business Rule
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class InvoiceLineTest extends TestCase
{
public function testCalculatesTotalInCents(): void
{
$line = new InvoiceLine(
description: 'Charter service',
quantity: 3,
unitPriceCents: 19_500
);
self::assertSame(58_500, $line->totalCents());
}
public function testRejectsZeroQuantity(): void
{
$this->expectException(InvalidArgumentException::class);
new InvoiceLine('Invalid line', 0, 1000);
}
}
Test SQL Against a Real Database Engine
SQLite is useful, but it does not behave exactly like MySQL. Queries that depend on MySQL types, collations, locking, JSON, or generated columns should be tested against an isolated MySQL database.
- Run migrations before the suite.
- Wrap each test in a transaction when possible.
- Seed only the records the test needs.
- Assert database state and externally visible behavior.
- Keep production data out of test environments.
Useful Debugging Tools
php -l src/Service/CreateInvoice.php php -d display_errors=1 -d error_reporting=E_ALL script.php composer test vendor/bin/phpunit --filter InvoiceLineTest # Inspect an HTTP response curl -i https://example.test/api/health # Follow PHP-FPM or application logs journalctl -u php-fpm -f tail -f storage/logs/application.log
Debug the Data Flow
Trace one value from request input through normalization, authorization, business rules, SQL, and output. Many bugs are caused by a value changing type or meaning between layers.
Handling a Production Bug
- Reduce impact first: disable the feature, roll back, or apply a safe configuration change.
- Preserve evidence before restarting or clearing logs.
- Identify the first incorrect state, not only the final exception.
- Reproduce in a controlled environment.
- Add a regression test that fails for the original defect.
- Deploy the smallest safe correction.
- Review why existing checks and alerts did not catch it sooner.