adriengras/guard-php

A tiny guard() helper for PHP (Symfony/Laravel/plain).

1.0.4 2025-08-11 08:39 UTC

This package is auto-updated.

Last update: 2025-08-11 09:38:38 UTC


README

GitHub Workflow Status Packagist Version PHP Version License

A tiny PHP library bringing the guard concept (inspired by Kotlin, Swift, Dart…) to PHP.
Available globally via Composer — works in plain PHP, Symfony, and Laravel.

✨ Features

  • Global guard() function available everywhere
  • Throws an exception if a condition is not met
  • Supports:
    • Simple error message
    • Existing exception instance
    • Lazy exception factory (callable)
  • Caller blame mode: the error is shown as if it occurred where guard() was called, not inside the package
  • PHP 8.1+ compatible
  • Zero runtime dependencies

❓ Why this package?

Defensive programming is a key practice to make your code more reliable and easier to debug.
Languages like Kotlin and Swift provide a native guard statement to quickly validate assumptions and fail fast when something is wrong.

In PHP, similar checks often look like this:

if ($price < 0) {
    throw new InvalidArgumentException('Price must be non-negative');
}

This package brings a concise, expressive, and consistent way to write those checks:

guard($price >= 0, 'Price must be non-negative');

Benefits:

  • Readability – One-line, expressive intent
  • Consistency – Same syntax across all projects and frameworks
  • Less boilerplate – No repetitive if + throw blocks
  • Framework-agnostic – Works in plain PHP, Symfony, Laravel…

⚙️ How it works — Caller blame mode

By default, guard() is in caller blame mode ($blameCaller = true):

  • It scans the debug backtrace to find the first frame outside vendor/ and outside the guard.php file itself.
  • It then tries to rewrite the $file and $line properties of the exception via ReflectionProperty so the error appears to originate from your code.
  • If PHP forbids modifying these properties (some runtimes mark them internally as readonly), guard() falls back to wrapping the original exception in an ErrorException:
    • The new exception has file and line set to the caller’s location
    • The original exception is preserved as $previous

Example — without blame:

In vendor/your-vendor/guard-php/src/functions.php line 47:
Price must be non-negative

Example — with blame (default):

In src/Service/CheckoutService.php line 123:
Price must be non-negative

You can disable caller blame explicitly:

guard($condition, 'message', null, null, false);

📦 Installation

composer require adriengras/guard-php

The global function is automatically loaded via autoload.files — no extra configuration needed.

🚀 Usage

Basic example

guard($price >= 0, 'Price must be non-negative');

With a custom exception

guard($user !== null, new DomainException('User not found'));

Lazy exception (callable)

guard($stock >= $qty, fn() => new OutOfRangeException("Not enough stock for SKU {$sku}"));

🧩 Framework compatibility

  • Symfony – works out-of-the-box in controllers, services, commands…
  • Laravel – works directly in controllers, jobs, events…
  • Plain PHP – just require 'vendor/autoload.php';

🧪 Tests

Tests are written with Pest.

Run tests:

composer test

Run tests with coverage:

XDEBUG_MODE=coverage composer test:cov

🤝 Contributing

Contributions are welcome!
If you’d like to contribute:

  • Fork the repository
  • Create a new branch (git checkout -b feature/my-feature)
  • Make your changes
  • Run the tests (composer test)
  • Submit a Pull Request

📄 License

MIT - You can find the licence in the LICENSE file.