Try Live
Add Docs
Rankings
Pricing
Docs
Install
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
BypassFinals
https://github.com/dg/bypass-finals
Admin
BypassFinals is a PHP tool that strips away final and readonly keywords from code on-the-fly,
...
Tokens:
3,803
Snippets:
27
Trust Score:
9.9
Update:
2 months ago
Context
Skills
Chat
Benchmark
88.3
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# BypassFinals BypassFinals is a PHP library that removes `final` and `readonly` keywords from PHP source code on-the-fly during runtime. This enables developers to mock final classes and methods in their unit tests, which is particularly useful when working with third-party libraries or legacy code that uses final declarations extensively. The library works by registering a custom PHP stream wrapper that intercepts file reads and modifies the source code before it's parsed by PHP. The library seamlessly integrates with popular PHP testing frameworks including PHPUnit, Mockery, and Nette Tester. It supports PHP versions 7.1 through 8.4 and can be easily installed via Composer. BypassFinals uses pattern-based path filtering to control which files are modified, and includes optional caching to improve performance in large codebases. ## Installation Install BypassFinals via Composer as a development dependency. ```bash composer require dg/bypass-finals --dev ``` ## DG\BypassFinals::enable() Enables the BypassFinals stream wrapper to intercept PHP file loading and remove `final` and `readonly` keywords. This method should be called as early as possible in your test bootstrap, ideally right after loading the Composer autoloader, to ensure all classes are processed before they are used. ```php <?php // tests/bootstrap.php require __DIR__ . '/../vendor/autoload.php'; // Enable both final and readonly bypassing (default) DG\BypassFinals::enable(); // Or disable readonly bypassing while keeping final bypassing DG\BypassFinals::enable(bypassReadOnly: false); // Or disable final bypassing while keeping readonly bypassing DG\BypassFinals::enable(bypassReadOnly: true, bypassFinal: false); // Now you can mock final classes in your tests // Example with PHPUnit: class MyTest extends \PHPUnit\Framework\TestCase { public function testMockingFinalClass(): void { // FinalService is declared as "final class FinalService" $mock = $this->createMock(FinalService::class); $mock->method('process')->willReturn('mocked result'); $this->assertEquals('mocked result', $mock->process()); } } ``` ## DG\BypassFinals::allowPaths() Restricts BypassFinals to only modify files matching the specified path patterns. This whitelist approach is useful when you only need to mock classes from specific directories or vendor packages, improving performance and avoiding potential conflicts with other libraries. ```php <?php require __DIR__ . '/../vendor/autoload.php'; DG\BypassFinals::enable(); // Only bypass finals in Nette framework files DG\BypassFinals::allowPaths([ '*/Nette/*', ]); // Allow multiple path patterns DG\BypassFinals::allowPaths([ '*/vendor/doctrine/*', '*/vendor/symfony/*', '*/src/Legacy/*', ]); // Patterns support wildcards: // * matches any characters within a single directory // Use full path patterns for precise control DG\BypassFinals::allowPaths([ '/var/www/myproject/vendor/acme/*', ]); ``` ## DG\BypassFinals::denyPaths() Excludes specific paths from modification even when they would otherwise be allowed. This blacklist approach is useful for solving compatibility issues with certain frameworks or libraries that break when their final/readonly keywords are removed. ```php <?php require __DIR__ . '/../vendor/autoload.php'; DG\BypassFinals::enable(); // Deny specific paths that cause issues DG\BypassFinals::denyPaths([ '*/vendor/phpunit/*', '*/vendor/mockery/*', ]); // Combine with allowPaths for fine-grained control DG\BypassFinals::allowPaths([ '*/vendor/*', ]); DG\BypassFinals::denyPaths([ '*/vendor/phpunit/*', '*/vendor/symfony/dependency-injection/*', ]); // Deny paths are evaluated after allow paths // A file must match an allow pattern AND not match any deny patterns ``` ## DG\BypassFinals::setCacheDirectory() Configures a directory for caching transformed PHP files to improve performance. When enabled, BypassFinals stores the modified source code and reuses it on subsequent requests, avoiding repeated parsing and transformation of the same files. ```php <?php require __DIR__ . '/../vendor/autoload.php'; // The cache directory must already exist $cacheDir = __DIR__ . '/../var/cache/bypass-finals'; if (!is_dir($cacheDir)) { mkdir($cacheDir, 0755, true); } DG\BypassFinals::enable(); DG\BypassFinals::setCacheDirectory($cacheDir); // Cache files are named using SHA1 hash of the original content // This ensures cache invalidation when source files change // To clear the cache, simply delete the cache directory contents: // rm -rf var/cache/bypass-finals/* ``` ## DG\BypassFinals::debugInfo() Outputs diagnostic information to help troubleshoot issues when BypassFinals doesn't work as expected. This method displays the current configuration, the call stack when enable() was invoked, classes that were loaded before BypassFinals started, and a list of files that were successfully modified. ```php <?php require __DIR__ . '/../vendor/autoload.php'; DG\BypassFinals::enable(); DG\BypassFinals::allowPaths(['*/src/*']); // Run some tests or load some classes... require __DIR__ . '/../src/FinalClass.php'; // Output debug information DG\BypassFinals::debugInfo(); // Output example: // <xmp> // BypassFinals Debug Information // ------------------------------ // // Configuration: // Bypass 'final': enabled // Bypass 'readonly': enabled // // From where BypassFinals::enable() was started: // #0 in /var/www/tests/bootstrap.php:5 // // Classes already loaded before BypassFinals was started: // no classes // // Files where BypassFinals removed final/readonly: // - /var/www/src/FinalClass.php // </xmp> ``` ## PHPUnit Extension Integration For PHPUnit 10 and newer, BypassFinals provides a dedicated extension that can be configured directly in your phpunit.xml file. This approach automatically enables BypassFinals before any tests run and excludes PHPUnit's own files from modification to prevent conflicts. ```xml <!-- phpunit.xml --> <?xml version="1.0" encoding="UTF-8"?> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd" bootstrap="vendor/autoload.php" colors="true"> <testsuites> <testsuite name="Unit"> <directory>tests</directory> </testsuite> </testsuites> <!-- Basic extension registration --> <extensions> <bootstrap class="DG\BypassFinals\PHPUnitExtension"/> </extensions> <!-- Or with full configuration --> <extensions> <bootstrap class="DG\BypassFinals\PHPUnitExtension"> <parameter name="bypassFinal" value="true"/> <parameter name="bypassReadOnly" value="false"/> <parameter name="cacheDirectory" value="./var/cache/bypass-finals"/> </bootstrap> </extensions> </phpunit> ``` ## Complete Test Bootstrap Example A comprehensive example showing how to configure BypassFinals for a typical PHPUnit test suite with path filtering and caching enabled. ```php <?php // tests/bootstrap.php declare(strict_types=1); require __DIR__ . '/../vendor/autoload.php'; // Enable BypassFinals early, before any classes are loaded DG\BypassFinals::enable(); // Only modify files in vendor directory (third-party finals) // and in the src/Legacy directory (legacy code with finals) DG\BypassFinals::allowPaths([ '*/vendor/*', '*/src/Legacy/*', ]); // Exclude testing libraries to prevent conflicts DG\BypassFinals::denyPaths([ '*/vendor/phpunit/*', '*/vendor/mockery/*', '*/vendor/nette/tester/*', ]); // Enable caching for better performance $cacheDir = __DIR__ . '/../var/cache/bypass-finals'; if (!is_dir($cacheDir)) { mkdir($cacheDir, 0755, true); } DG\BypassFinals::setCacheDirectory($cacheDir); // Example test class // tests/Unit/PaymentServiceTest.php use PHPUnit\Framework\TestCase; use Vendor\PaymentGateway\FinalPaymentProcessor; // This is a final class class PaymentServiceTest extends TestCase { public function testPaymentProcessing(): void { // Create a mock of the final class - this works because BypassFinals // removed the 'final' keyword when the class was loaded $processor = $this->createMock(FinalPaymentProcessor::class); $processor->expects($this->once()) ->method('charge') ->with(100.00, 'USD') ->willReturn(['status' => 'success', 'transaction_id' => 'txn_123']); $service = new PaymentService($processor); $result = $service->processPayment(100.00, 'USD'); $this->assertEquals('success', $result['status']); } } ``` ## Summary BypassFinals is an essential tool for PHP developers who need to write comprehensive unit tests for codebases that use final classes and readonly properties. The primary use cases include: mocking third-party library classes that are declared final, testing legacy code without modifying original source files, and enabling dependency injection testing patterns in projects with strict immutability requirements. The library's path filtering system allows precise control over which files are modified, making it safe to use alongside frameworks that depend on their final declarations. Integration with existing projects is straightforward - simply require the package as a dev dependency, call `DG\BypassFinals::enable()` in your test bootstrap before any classes are loaded, and optionally configure path filters and caching for optimal performance. For PHPUnit users, the built-in extension provides zero-configuration setup through phpunit.xml. The library operates transparently at the stream wrapper level, meaning no changes to your actual source code are needed, and the modifications only affect the runtime representation of classes during test execution.