Skip to content

Commit de873e4

Browse files
committed
added AGENTS.md
1 parent c417ed2 commit de873e4

2 files changed

Lines changed: 139 additions & 1 deletion

File tree

.gitattributes

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.gitattributes export-ignore
22
.github/ export-ignore
33
.gitignore export-ignore
4-
CLAUDE.md export-ignore
4+
AGENTS.md export-ignore
55
ncs.* export-ignore
66
phpstan*.neon export-ignore
77
tests/ export-ignore

AGENTS.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# To My Agents!
2+
3+
It is my fervent wish that this file guide every AI coding agent working with code in this repository.
4+
5+
## Project Overview
6+
7+
BypassFinals is a PHP library that removes `final` and `readonly` keywords from source code on-the-fly, enabling mocking of final classes and methods for testing purposes. It works by intercepting PHP's file stream operations and modifying code before it's parsed.
8+
9+
## Key Commands
10+
11+
### Testing
12+
```bash
13+
# Run all tests (composer script runs `tester tests -s`)
14+
composer run tester
15+
# or
16+
vendor/bin/tester tests -s
17+
18+
# Run specific test file
19+
vendor/bin/tester tests/BypassFinals/BypassFinals.phpt -s
20+
```
21+
22+
### Static Analysis
23+
```bash
24+
# Run PHPStan static analysis
25+
composer run phpstan
26+
# or
27+
vendor/bin/phpstan analyse
28+
```
29+
30+
### Development Setup
31+
```bash
32+
# Install dependencies
33+
composer install --dev
34+
35+
# Run tests in different PHP versions (see .github/workflows/tests.yml for supported versions)
36+
# Supports PHP 8.2 through 8.5
37+
```
38+
39+
## Architecture Overview
40+
41+
### Core Components
42+
43+
1. **BypassFinals** (`src/BypassFinals.php`) - Main API class that:
44+
- Manages configuration (allow/deny paths, cache directory)
45+
- Registers custom stream wrapper
46+
- Provides token removal logic
47+
- Tracks debugging information
48+
49+
2. **MutatingWrapper** (`src/MutatingWrapper.php`) - Stream wrapper that:
50+
- Intercepts file operations for `.php` files
51+
- Modifies PHP source code on-the-fly if path is allowed
52+
- Delegates non-PHP operations to underlying wrapper
53+
54+
3. **NativeWrapper** (`src/NativeWrapper.php`) - Native file operations wrapper that:
55+
- Implements all PHP stream wrapper methods
56+
- Temporarily restores native protocol for actual file operations
57+
- Handles file, directory, and metadata operations
58+
59+
4. **StreamWrapper** (`src/StreamWrapper.php`) - `@internal` interface that:
60+
- Defines the prototype of the underlying wrapper used by `MutatingWrapper`
61+
- Mirrors PHP's documentation-only `streamWrapper` prototype, used for type-checking
62+
63+
5. **PHPUnitExtension** (`src/PHPUnitExtension.php`) - PHPUnit 10+ integration that:
64+
- Automatically denies the PHPUnit vendor path (`*/vendor/phpunit/*`)
65+
- Configures BypassFinals from phpunit.xml parameters (`bypassReadOnly`, `bypassFinal`, `cacheDirectory`)
66+
- Enables the library during test bootstrap
67+
68+
6. **bootstrap.php** (`src/bootstrap.php`) - Standalone bootstrap that requires the
69+
wrapper classes and calls `enable()`. Designed to be loaded *before*
70+
`vendor/autoload.php` so classes registered in autoload files are also processed.
71+
72+
### How It Works
73+
74+
1. **Stream Wrapper Registration**: Replaces PHP's native `file://` protocol handler with `MutatingWrapper`
75+
2. **File Interception**: When PHP loads a `.php` file, `MutatingWrapper` intercepts the operation
76+
3. **Code Modification**: Uses PHP's tokenizer to remove `final` and `readonly` tokens from source
77+
4. **Caching**: Optional filesystem caching of modified code using SHA1 hashes
78+
5. **Path Filtering**: Allow/deny path rules control which files get modified
79+
80+
### Testing Strategy
81+
82+
- **Nette Tester**: Uses `.phpt` files for comprehensive test coverage
83+
- **Fixtures**: Test files in `tests/BypassFinals/fixtures/` demonstrate actual `final` class removal
84+
- **Stream Operations**: Tests verify that custom stream wrapper doesn't break normal file operations
85+
- **Path Filtering**: Tests ensure allow/deny path rules work correctly
86+
- **Caching**: Tests verify cache functionality with hash-based storage
87+
- **Edge Cases**: Tests handle syntax errors, missing files, and other error conditions
88+
89+
### Configuration Patterns
90+
91+
The library supports multiple configuration approaches:
92+
93+
1. **Direct PHP API** (`enable()` defaults: both `$bypassReadOnly` and `$bypassFinal` are `true`):
94+
```php
95+
DG\BypassFinals::enable();
96+
DG\BypassFinals::allowPaths(['*/src/*']);
97+
DG\BypassFinals::setCacheDirectory('/tmp/cache');
98+
```
99+
100+
2. **PHPUnit XML Extension** (parameters are optional; omitted boolean params default to `true`):
101+
```xml
102+
<extensions>
103+
<bootstrap class="DG\BypassFinals\PHPUnitExtension">
104+
<parameter name="bypassFinal" value="true"/>
105+
<parameter name="bypassReadOnly" value="false"/>
106+
<parameter name="cacheDirectory" value="./cache"/>
107+
</bootstrap>
108+
</extensions>
109+
```
110+
111+
3. **Standalone bootstrap** (load before `vendor/autoload.php` to also process autoload-registered classes):
112+
```php
113+
// tests/bootstrap.php
114+
require __DIR__ . '/../vendor/dg/bypass-finals/src/bootstrap.php';
115+
require __DIR__ . '/../vendor/autoload.php';
116+
```
117+
118+
### Performance Considerations
119+
120+
- **Early Initialization**: Must be enabled before classes are loaded
121+
- **Caching**: Use `setCacheDirectory()` to avoid repeated tokenization
122+
- **Path Filtering**: Use allow/deny paths to limit scope and improve performance
123+
- **Stream Wrapper Overhead**: Minimal overhead for non-PHP files due to delegation
124+
125+
### Debugging
126+
127+
Use `DG\BypassFinals::debugInfo()` to troubleshoot issues. This outputs:
128+
- Configuration status (final/readonly bypass enabled)
129+
- Call stack showing where `enable()` was called
130+
- Classes loaded before BypassFinals was started (these cannot be modified)
131+
- List of files that were successfully modified
132+
133+
### Compatibility
134+
135+
- **PHP Versions**: 8.2 through 8.5 (minimum is now `>=8.2`, per `composer.json`)
136+
- **readonly Support**: enabled by default (the 8.2 minimum guarantees the `T_READONLY` token exists)
137+
- **Testing Frameworks**: PHPUnit, Mockery, Nette Tester
138+
- **Internal Classes**: Cannot modify PHP internal classes like `Closure`

0 commit comments

Comments
 (0)