Skip to content

Commit 87a3f05

Browse files
committed
added AGENTS.md & DOCS
1 parent 0fd3475 commit 87a3f05

3 files changed

Lines changed: 399 additions & 0 deletions

File tree

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
.gitattributes export-ignore
22
.github/ export-ignore
33
.gitignore export-ignore
4+
AGENTS.md export-ignore
45
ncs.* export-ignore
56
phpstan*.neon export-ignore
7+
docs/ export-ignore
68
tests/ export-ignore
79

810
*.php* diff=php

AGENTS.md

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
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+
## Documentation
6+
7+
Any distilled, agent-facing documentation for this package - how it works
8+
internally and the rationale behind key design decisions - lives in `docs/`.
9+
Consult it before non-trivial changes; it is the source of truth from which the
10+
public manual is distilled.
11+
12+
## Project Overview
13+
14+
Nette RobotLoader is a high-performance PHP autoloader library that automatically discovers and loads classes, interfaces, traits, and enums without requiring strict PSR-4 naming conventions. It's part of the Nette Framework ecosystem but works as a standalone component.
15+
16+
**Key characteristics:**
17+
- Single-class implementation (~500 LOC in `src/RobotLoader/RobotLoader.php`)
18+
- Intelligent caching with platform-specific optimizations (Linux vs Windows)
19+
- Cache stampede prevention for production environments
20+
- Token-based PHP file parsing using `\PhpToken::tokenize()`
21+
- Automatic cache invalidation on file changes in development mode
22+
23+
## Essential Commands
24+
25+
### Testing
26+
```bash
27+
# Run all tests
28+
composer run tester
29+
30+
# Run specific test file
31+
vendor/bin/tester tests/Loaders/RobotLoader.phpt -s
32+
33+
# Run tests in a specific directory
34+
vendor/bin/tester tests/Loaders/ -s
35+
36+
# Run tests with verbose output (-s shows skipped tests)
37+
vendor/bin/tester tests -s -p php
38+
```
39+
40+
### Code Quality
41+
```bash
42+
# Static analysis (PHPStan level 8)
43+
composer run phpstan
44+
```
45+
46+
### Development
47+
```bash
48+
# Install dependencies
49+
composer install
50+
51+
# Update dependencies
52+
composer update
53+
54+
# Generate API documentation (if available)
55+
# See https://api.nette.org/robot-loader/
56+
```
57+
58+
## Architecture
59+
60+
### Core Component: RobotLoader Class
61+
62+
The entire library is a single class (`Nette\Loaders\RobotLoader`) with these primary responsibilities:
63+
64+
1. **Directory Scanning** - Recursively indexes PHP files using `Nette\Utils\Finder`
65+
2. **Class Extraction** - Token-based parsing to find classes/interfaces/traits/enums
66+
3. **Caching System** - Three-tier cache: `$classes`, `$missingClasses`, `$emptyFiles`
67+
4. **Autoloading** - Registers with PHP's `spl_autoload_register()`
68+
5. **Change Detection** - mtime-based incremental updates
69+
70+
### State Management
71+
72+
```php
73+
// Three-tier caching state
74+
private array $classes = []; // class => [file, mtime]
75+
private array $missingClasses = []; // class => retry_counter (max 3)
76+
private array $emptyFiles = []; // file => mtime (optimization)
77+
```
78+
79+
### Platform-Specific Optimizations
80+
81+
**Linux:** Direct cache include without locks + atomic rename
82+
**Windows:** Mandatory file locking (files can't be renamed while open)
83+
84+
The code checks `Nette\Utils\Helpers::IsWindows` to determine the platform and adjusts locking strategy accordingly. On Windows, `rename()` over a momentarily locked target is additionally retried a few times (see `atomicWrite()`).
85+
86+
### Cache Stampede Prevention
87+
88+
When multiple concurrent requests hit production before cache exists:
89+
1. First request acquires exclusive lock (LOCK_EX)
90+
2. Subsequent requests wait with shared lock (LOCK_SH)
91+
3. After first request builds cache, others reuse it
92+
4. Double-check pattern: re-read cache after acquiring exclusive lock
93+
94+
### Key Methods
95+
96+
- `addDirectory()` / `excludeDirectory()` - Configure scan paths
97+
- `register()` - Activate autoloader (calls `spl_autoload_register()`)
98+
- `refresh()` - Smart cache refresh (scans only changed files)
99+
- `rebuild()` - Full rebuild from scratch
100+
- `scanPhp()` - Token-based class extraction from PHP files
101+
- `loadCache()` / `saveCache()` - Atomic cache operations with locking
102+
103+
## Usage Patterns
104+
105+
### Standalone Usage
106+
107+
Basic setup for any PHP application:
108+
109+
```php
110+
$loader = new Nette\Loaders\RobotLoader;
111+
$loader->addDirectory(__DIR__ . '/app');
112+
$loader->addDirectory(__DIR__ . '/libs');
113+
$loader->setCacheDirectory(__DIR__ . '/temp');
114+
$loader->register(); // Activate RobotLoader
115+
```
116+
117+
### Nette Application Integration
118+
119+
When used within a Nette Application (recommended approach), RobotLoader setup is simplified through the `$configurator` object in `Bootstrap.php`:
120+
121+
```php
122+
$configurator = new Nette\Bootstrap\Configurator;
123+
// ...
124+
$configurator->setCacheDirectory(__DIR__ . '/../temp');
125+
$configurator->createRobotLoader()
126+
->addDirectory(__DIR__)
127+
->addDirectory(__DIR__ . '/../libs')
128+
->register();
129+
```
130+
131+
**Benefits:**
132+
- Automatic temp directory configuration
133+
- Fluent interface for directory setup
134+
- Auto-refresh automatically disabled in production mode
135+
- Integrated with Nette Application lifecycle
136+
137+
### As PHP Files Analyzer (Without Autoloading)
138+
139+
RobotLoader can be used purely for indexing classes without autoloading:
140+
141+
```php
142+
$loader = new Nette\Loaders\RobotLoader;
143+
$loader->addDirectory(__DIR__ . '/app');
144+
$loader->setCacheDirectory(__DIR__ . '/temp');
145+
$loader->refresh(); // Scans directories using cache
146+
$classes = $loader->getIndexedClasses(); // Returns class => file array
147+
```
148+
149+
Use `rebuild()` instead of `refresh()` to force full rebuild from scratch.
150+
151+
### RobotLoader vs PSR-4
152+
153+
**Use RobotLoader when:**
154+
- Directory structure doesn't match namespace structure
155+
- Working with legacy code that doesn't follow PSR-4
156+
- You want automatic discovery without strict conventions
157+
- Need to load from multiple disparate directories
158+
159+
**Use PSR-4 (Composer) when:**
160+
- Building new applications with consistent structure
161+
- Following modern PHP standards strictly
162+
- Directory structure matches namespace structure (e.g., `App\Core\RouterFactory``/path/to/App/Core/RouterFactory.php`)
163+
164+
**Both can be used together** - PSR-4 for your structured code, RobotLoader for legacy dependencies or non-standard libraries.
165+
166+
### Production vs Development Configuration
167+
168+
**Development:**
169+
```php
170+
$loader->setAutoRefresh(true); // Default - automatically updates cache
171+
```
172+
173+
**Production:**
174+
```php
175+
$loader->setAutoRefresh(false); // Disable auto-refresh for performance
176+
// Clear cache when deploying: rm -rf temp/cache
177+
```
178+
179+
In Nette Application, this is handled automatically based on debug mode.
180+
181+
## Testing Framework
182+
183+
Uses **Nette Tester** with `.phpt` file format:
184+
185+
```php
186+
<?php
187+
/**
188+
* Test: Description of what is being tested
189+
*/
190+
declare(strict_types=1);
191+
192+
use Tester\Assert;
193+
require __DIR__ . '/../bootstrap.php';
194+
195+
// Test code using Assert methods
196+
Assert::same('expected', $actual);
197+
Assert::exception(fn() => $code(), ExceptionClass::class, 'Message pattern %a%');
198+
```
199+
200+
### Test Utilities
201+
202+
**`getTempDir()` function** (in `tests/bootstrap.php`):
203+
- Creates per-process temp directories (`tests/tmp/<pid>`)
204+
- Garbage collection with file locking for parallel safety
205+
- Automatically used by tests for cache directories
206+
207+
### Test Coverage Areas
208+
209+
1. **RobotLoader.phpt** - Basic functionality, directory/file scanning, exclusions
210+
2. **RobotLoader.rebuild.phpt** - Cache rebuild behavior
211+
3. **RobotLoader.renamed.phpt** - File rename detection
212+
4. **RobotLoader.caseSensitivity.phpt** - Case-sensitive class matching
213+
5. **RobotLoader.relative.phpt** - Relative path handling
214+
6. **RobotLoader.phar.phpt** - PHAR archive support
215+
7. **RobotLoader.stress.phpt** - Concurrency testing (50 parallel runs via `@multiple`)
216+
8. **RobotLoader.emptyArrayVariadicArgument.phpt** - Edge case handling
217+
9. **types/TypesTest.phpt** - PHPStan type inference test
218+
219+
## Coding Conventions
220+
221+
Follows **Nette Coding Standard** (based on PSR-12) with these specifics:
222+
223+
- `declare(strict_types=1)` in all PHP files
224+
- Tabs for indentation
225+
- Return type and opening brace on separate lines for multi-parameter methods
226+
- Two spaces after `@param` and `@return` in phpDoc
227+
- Document shut-up operator usage: `@mkdir($dir); // @ - directory may already exist`
228+
229+
### Exception Handling
230+
231+
- `ParseError` - PHP syntax errors in scanned files (configurable)
232+
- `Nette\InvalidStateException` - Duplicate class definitions
233+
- `Nette\IOException` - Directory not found
234+
- `Nette\InvalidArgumentException` - Cache directory path is not absolute
235+
- `Nette\NotSupportedException` - Tokenizer extension not loaded
236+
- `LogicException` - Cache directory not set before use
237+
- `RuntimeException` - Cache file write failures, lock acquisition failures
238+
239+
## CI/CD Pipeline
240+
241+
Three GitHub Actions workflows:
242+
243+
1. **Coding Style** - Code checker + coding standard enforcement
244+
2. **Static Analysis** - PHPStan (on push and pull request)
245+
3. **Tests** - Matrix testing across PHP versions (8.1-8.5)
246+
247+
## Dependencies
248+
249+
- **PHP**: 8.1 - 8.5
250+
- **ext-tokenizer**: Required for PHP parsing
251+
- **nette/utils**: ^4.0.6 (FileSystem, Finder, Helpers)
252+
- **Dev**: nette/tester, tracy/tracy, phpstan/phpstan + nette/phpstan-rules
253+
254+
## Common Development Patterns
255+
256+
### Adding New Functionality
257+
258+
When modifying RobotLoader:
259+
1. Consider backward compatibility (library is mature and widely used)
260+
2. Add comprehensive tests covering edge cases
261+
3. Update phpDoc annotations for IDE support
262+
4. Test on both Linux and Windows if touching file operations
263+
5. Consider performance implications (this is a hot path in applications)
264+
265+
### Debugging Tests
266+
267+
```bash
268+
# Run single test with Tracy debugger
269+
vendor/bin/tester tests/Loaders/RobotLoader.phpt -s
270+
271+
# Check test temp files (not auto-cleaned during failures)
272+
ls tests/tmp/
273+
274+
# Manual cleanup
275+
rm -rf tests/tmp/*
276+
```
277+
278+
### Performance Considerations
279+
280+
- Cache operations are atomic to prevent corruption
281+
- OPcache invalidation after cache updates (`opcache_invalidate()`)
282+
- Lazy initialization - cache loaded only on first autoload attempt
283+
- Empty files tracked separately to avoid re-scanning
284+
- Missing class retry limit (3 attempts) prevents infinite loops

0 commit comments

Comments
 (0)