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