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

LevelBest forTradeoff
UnitBusiness rules and value objectsFast, but does not prove integration.
IntegrationRepositories, SQL, filesystem, and APIsMore realistic, slower, needs controlled dependencies.
HTTP/featureRoutes, authentication, validation, and response behaviorCovers more layers but failures are less isolated.
End-to-endCritical user journeysMost 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

  1. Reduce impact first: disable the feature, roll back, or apply a safe configuration change.
  2. Preserve evidence before restarting or clearing logs.
  3. Identify the first incorrect state, not only the final exception.
  4. Reproduce in a controlled environment.
  5. Add a regression test that fails for the original defect.
  6. Deploy the smallest safe correction.
  7. Review why existing checks and alerts did not catch it sooner.