Safe PHP

repository·master·Indexed 25 days ago

https://github.com/thecodingmachine/safe

A library that redeclares core PHP functions and classes (such as DateTime and DateTimeImmutable) within the Safe namespace. It replaces functions that return false on error with variants that throw exceptions, promoting cleaner error handling via try/catch blocks. The library includes integration for PHPStan via thecodingmachine/phpstan-safe-rule and automated refactoring support through Rector.

Tokens
2.8K
Snippets
6
Records
24
Agent score
82%

What's inside thecodingmachine/safe

  1. What is Safe PHP?

    master

    Safe PHP (thecodingmachine/safe) is a library that redeclares core PHP functions within the Safe namespace. While standard PHP functions often return false on failure, the Safe variants are designed to throw exceptions instead. This allows for cleaner code by removing the need for explicit false checks and enabling standard try/catch error handling.

    Key features:

    • All core PHP functions that return false on error are available in the Safe namespace.
    • Provides Safe\DateTime and Safe\DateTimeImmutable classes where methods throw exceptions instead of returning false.
  2. Understand the performance impact of Safe

    master

    Because PHP does not support autoloading for functions, Safe requires approximately 84 files (containing over 1000 functions) via Composer's vendor/autoload.php on every request.

    Benchmark results using Blackfire show that the overhead for loading these files is approximately 700µs.

    Comparison:

    • Composer autoload without Safe: ~264µs
    • Composer autoload with Safe: ~1.03ms
  3. Automated refactoring with Rector

    master

    If you have a large codebase and want to migrate to Safe PHP functions, you can use the bundled Rector configuration to perform a bulk namespace replacement.

    1. Install Rector

    composer require --dev rector/rector

    2. Run the migration

    Run the following command, replacing src/ with your actual source directory:

    vendor/bin/rector process src/ --config vendor/thecodingmachine/safe/rector-migrate.php

    Important Note: Rector performs a "dumb" replacement of function names. It does not automatically convert existing if ($result === false) error handling logic into try/catch blocks. You must manually refactor existing error checks to use try/catch with the appropriate exceptions (e.g., \Safe\FilesystemException).

    Example of manual refactoring required after Rector:

    Before (Unsafe):

    if (!mkdir($dirPath)) {
        // Do something on error
    }

    After Rector (Still using if-check, but with Safe namespace):

    if (!\Safe\mkdir($dirPath)) {
        // Do something on error
    }

    Corrected (Manual refactor to use exceptions):

    try {
        \Safe\mkdir($dirPath);
    } catch (\Safe\FilesystemException $e) {
        // Do something on error
    }
  4. Reproduce Safe performance benchmarks

    master

    To reproduce the performance tests, use the provided test directories and Docker environment. The tests compare loading the Composer autoloader with Safe versus without Safe.

    1. Prepare the test environments

    Install dependencies for both test scenarios:

    cd test_with_safe
    composer install
    cd ..
    cd test_without_safe
    composer install
    cd ..

    2. Start the profiling environment

    Use the provided docker-compose.yml to start a PHP 7.2 environment with Blackfire enabled. You must provide your Blackfire credentials as environment variables:

    BLACKFIRE_SERVER_ID=[xyz] BLACKFIRE_SERVER_TOKEN=[abc] docker-compose up

    3. Profile the results

    Once the environment is running, you can access the endpoints and profile them using the Blackfire companion:

    • With Safe: http://localhost:8888/test_with_safe
    • Without Safe: http://localhost:8888/test_without_safe
  5. Integrate Safe PHP with PHPStan

    master

    To ensure you don't accidentally use unsafe core PHP functions, you can use the thecodingmachine/phpstan-safe-rule. This rule will trigger a warning in PHPStan whenever an unsafe function is used without an explicit use function Safe\... import.

    1. Install the rule

    composer require --dev thecodingmachine/phpstan-safe-rule

    2. Configure PHPStan

    Add the following to your phpstan.neon file:

    includes:
        - vendor/thecodingmachine/phpstan-safe-rule/phpstan-safe-rule.neon
  6. Use Safe\DateTime for exception-based error handling

    master
    The Safe\DateTime class is a wrapper around the native PHP \DateTime class. Unlike the standard PHP implementation which often returns false on failure, Safe\DateTime throws a Safe\Exceptions\DatetimeException when an operation fails. This allows for more robust error handling using try/catch blocks instead of checking return values for false.
  7. Use Safe PHP functions

    master

    To use the safe versions of functions, you must explicitly import them from the Safe namespace using use function statements. This prevents accidental usage of the standard, unsafe PHP core functions.

    Example of safe usage:

    use function Safe\file_get_contents;
    use function Safe\json_decode;
    
    // This code is safe and simple! It will throw exceptions on failure
    $content = file_get_contents('foobar.json');
    $foobar = json_decode($content);
    use function Safe\file_get_contents;
    use function Safe\json_decode;
    
    // This code is both safe and simple!
    $content = file_get_contents('foobar.json');
    $foobar = json_decode($content);
  8. Configure Blackfire environment variables in Docker Compose

    master

    When using the provided Docker Compose setup for performance testing, you can configure Blackfire credentials in two ways:

    1. Pass through host variables: The blackfire service is configured to automatically expose the BLACKFIRE_SERVER_ID and BLACKFIRE_SERVER_TOKEN environment variables from your host machine.
    2. Define global credentials: You can explicitly define the credentials within the environment section of the blackfire service using the following keys:
      • BLACKFIRE_SERVER_ID
      • BLACKFIRE_SERVER_TOKEN
    services:
      blackfire:
        image: blackfire/blackfire
        environment:
        # Option 1: Pass through host variables
        - BLACKFIRE_SERVER_ID
        - BLACKFIRE_SERVER_TOKEN
        # Option 2: Explicitly define credentials
        - BLACKFIRE_SERVER_ID=SERVER-ID
        - BLACKFIRE_SERVER_TOKEN=SERVER-TOKEN