Skip to content

Commit 4e14db1

Browse files
committed
added CLAUDE.md
1 parent efbd325 commit 4e14db1

2 files changed

Lines changed: 363 additions & 0 deletions

File tree

‎.gitattributes‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.gitattributes export-ignore
22
.github/ export-ignore
33
.gitignore export-ignore
4+
CLAUDE.md export-ignore
45
ncs.* export-ignore
56
phpstan*.neon export-ignore
67
tests/ export-ignore

‎CLAUDE.md‎

Lines changed: 362 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,362 @@
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 Bootstrap** is a foundational library for the Nette Framework that handles application initialization and Dependency Injection (DI) container generation. It's a standalone package (`nette/bootstrap`) that:
8+
9+
- Initializes application environment settings (debug mode, timezone, etc.)
10+
- Generates and manages the DI container
11+
- Loads and processes NEON configuration files
12+
- Integrates all Nette framework extensions
13+
- Provides fluent interface for application configuration
14+
15+
**Requirements:** PHP 8.3-8.5
16+
17+
## Basic Usage
18+
19+
Typical bootstrap sequence for standalone usage (outside of full Nette framework):
20+
21+
```php
22+
// 1. Create configurator
23+
$configurator = new Nette\Bootstrap\Configurator;
24+
25+
// 2. Set temp directory (required - for DI container cache)
26+
$configurator->setTempDirectory(__DIR__ . '/temp');
27+
28+
// 3. Load configuration files
29+
$configurator->addConfig(__DIR__ . '/database.neon');
30+
// Multiple configs can be added - later files override earlier ones
31+
$configurator->addConfig(__DIR__ . '/services.neon');
32+
33+
// 4. Optional: Add dynamic parameters (not cached, evaluated per request)
34+
$configurator->addDynamicParameters([
35+
'remoteIp' => $_SERVER['REMOTE_ADDR'],
36+
]);
37+
38+
// 5. Create DI container
39+
$container = $configurator->createContainer();
40+
41+
// 6. Get services from container
42+
$db = $container->getByType(Nette\Database\Connection::class);
43+
// or by name when multiple instances exist
44+
$db = $container->getByName('database.main.connection');
45+
```
46+
47+
**Important:** On Linux/macOS, set write permissions for the `temp/` directory.
48+
49+
### Development vs Production Mode
50+
51+
The container behavior differs between modes:
52+
53+
- **Development mode:** Container auto-updates when configuration files change (convenience)
54+
- **Production mode:** Container generated once, changes ignored (performance)
55+
56+
**Autodetection:**
57+
- Development: Running on localhost (`127.0.0.1` or `::1`) without proxy
58+
- Production: All other cases
59+
60+
**Manual control:**
61+
62+
```php
63+
// Enable for specific IP addresses
64+
$configurator->setDebugMode('23.75.345.200');
65+
// or array of IPs
66+
$configurator->setDebugMode(['23.75.345.200', '192.168.1.100']);
67+
68+
// Combine IP with cookie secret (recommended for production debugging)
69+
// Requires 'nette-debug' cookie with value 'secret1234'
70+
$configurator->setDebugMode('secret1234@23.75.345.200');
71+
72+
// Force disable even for localhost
73+
$configurator->setDebugMode(false);
74+
```
75+
76+
### Retrieving Services from Container
77+
78+
```php
79+
// By type (when only one instance exists)
80+
$db = $container->getByType(Nette\Database\Connection::class);
81+
$explorer = $container->getByType(Nette\Database\Explorer::class);
82+
83+
// By name (when multiple instances exist)
84+
$mainDb = $container->getByName('database.main.connection');
85+
$logDb = $container->getByName('database.log.connection');
86+
```
87+
88+
## Essential Commands
89+
90+
### Testing
91+
92+
```bash
93+
# Run all tests
94+
vendor/bin/tester tests -s -C
95+
96+
# Run all tests with coverage info
97+
vendor/bin/tester tests -s -C
98+
99+
# Run specific test file
100+
vendor/bin/tester tests/Bootstrap/Configurator.basic.phpt
101+
102+
# Run tests with lowest dependencies (CI check)
103+
composer update --prefer-lowest && vendor/bin/tester tests -s -C
104+
```
105+
106+
### Code Quality
107+
108+
```bash
109+
# Run PHPStan static analysis
110+
composer phpstan
111+
112+
# Run PHPStan without progress bar (CI mode)
113+
composer phpstan -- --no-progress
114+
```
115+
116+
### Development
117+
118+
```bash
119+
# Install dependencies
120+
composer install
121+
122+
# Update dependencies
123+
composer update
124+
```
125+
126+
## Architecture
127+
128+
### Core Component: Configurator Class
129+
130+
Location: `src/Bootstrap/Configurator.php`
131+
132+
The `Configurator` class is the heart of this library. It follows a fluent interface pattern and handles:
133+
134+
**1. Environment Setup:**
135+
- `setDebugMode()` - Toggle debug/production mode with IP-based detection
136+
- `setTempDirectory()` - Set cache directory for DI container
137+
- `setTimeZone()` - Configure PHP timezone
138+
- `enableTracy()` - Enable Tracy debugger (if installed)
139+
- `excludeExtension()` - Exclude specific extensions; without arguments disables all automatic registration (defaults and discovered)
140+
141+
**2. Parameter Management:**
142+
- **Static parameters** (set at config time, cached): `addStaticParameters()`
143+
- **Dynamic parameters** (evaluated per request, not cached): `addDynamicParameters()`
144+
- Useful for runtime values like `$_SERVER['REMOTE_ADDR']`, current user, etc.
145+
- Referenced in config using `%parameterName%` notation
146+
147+
**Default parameters available:**
148+
- `appDir` - Application directory
149+
- `wwwDir` - Web root directory
150+
- `tempDir` - Temporary/cache directory
151+
- `vendorDir` - Composer vendor directory
152+
- `rootDir` - Project root (auto-detected from Composer)
153+
- `baseUrl` - Base URL (dynamically resolved from HTTP request)
154+
- `debugMode` / `productionMode` - Environment flags
155+
- `consoleMode` - CLI detection
156+
157+
**3. Configuration Loading:**
158+
- `addConfig(string|array)` - Load NEON files or inline arrays
159+
- Multiple configs are merged (later configs override earlier ones)
160+
- Parameter expansion with `%paramName%` syntax
161+
- Recursive parameter substitution with type coercion
162+
163+
**4. Container Generation:**
164+
- `createContainer()` - Create and initialize DI container
165+
- `loadContainer()` - Load cached container or generate new one
166+
- Caching based on: configs, static params, dynamic param names, PHP version, Composer mtime
167+
168+
### Default Extensions System
169+
170+
Bootstrap pre-registers framework extensions in `$defaultExtensions`:
171+
172+
```
173+
application, assets, cache, constants, database, forms, http,
174+
latte, mail, php, routing, security, session, tracy
175+
```
176+
177+
Each extension processes its own configuration section (e.g., `database:`, `forms:`, `mail:`).
178+
179+
### Extension Auto-Discovery
180+
181+
Bootstrap automatically discovers and registers DI extensions from installed Composer packages. This enables zero-configuration setup - just install a package and its extensions work automatically.
182+
183+
**How it works:**
184+
1. Package authors declare extensions in `composer.json` under `extra.nette.di-extensions`
185+
2. Bootstrap reads `vendor/composer/installed.json` at container compilation time
186+
3. `$defaultExtensions` are registered first (hand-tuned order, take precedence); discovered extensions are appended after them, sorted alphabetically by name
187+
4. Cache is invalidated when Composer autoloader changes
188+
189+
**Long-term direction:** `$defaultExtensions` is being gradually emptied in favour of discovery. The DI core extensions (decorator, di, extensions, inject, search) already come via discovery from nette/di metadata. `constants` and `php` must stay in defaults for now - the root package is not listed in installed.json.
190+
191+
**Package declaration format:**
192+
193+
```json
194+
{
195+
"extra": {
196+
"nette": {
197+
"di-extensions": {
198+
"myext": "Vendor\\Package\\DI\\MyExtension",
199+
"another": {
200+
"class": "Vendor\\Package\\DI\\AnotherExtension",
201+
"args": ["%debugMode%", "%tempDir%"]
202+
}
203+
}
204+
}
205+
}
206+
}
207+
```
208+
209+
**Excluding extensions:**
210+
211+
```php
212+
// Exclude specific extensions (works for both discovered and default)
213+
$configurator->excludeExtension('database', 'mail');
214+
215+
// Disable all automatic registration (no defaults, no discovery)
216+
$configurator->excludeExtension();
217+
```
218+
219+
See `docs/discovery.md` for complete documentation.
220+
221+
### Custom Bootstrap Extensions
222+
223+
Two extensions in `src/Bootstrap/Extensions/`:
224+
225+
**ConstantsExtension** - Defines PHP constants from configuration:
226+
```neon
227+
constants:
228+
MY_CONST: value
229+
ANOTHER: %parameter%
230+
```
231+
232+
**PhpExtension** - Sets PHP ini directives:
233+
```neon
234+
php:
235+
date.timezone: Europe/Prague
236+
display_errors: "0"
237+
```
238+
239+
### Configuration Flow
240+
241+
```
242+
1. User creates Configurator and sets environment
243+
2. User adds config files via addConfig()
244+
3. Configurator loads all NEON configs
245+
4. Extensions discovered from installed packages (via installed.json)
246+
5. $defaultExtensions registered first, discovered appended alphabetically (defaults win on conflict)
247+
6. Excluded extensions removed
248+
7. Each extension processes its config section
249+
8. DI Compiler generates PHP container class
250+
9. Container cached in tempDir/cache/nette.configurator/
251+
10. Dynamic parameters injected at runtime
252+
```
253+
254+
### Debug Mode Detection
255+
256+
`Configurator::detectDebugMode($list)` supports:
257+
- **IP whitelist matching** - Single IP or CIDR subnet (e.g., `'23.75.345.200'` or `['192.168.1.0/24']`)
258+
- **Cookie-based secret** - Format: `'secret@ip'` (e.g., `'secret1234@23.75.345.200'`)
259+
- Checks for `nette-debug` cookie with the secret value
260+
- Recommended for production debugging - safe even if IP changes
261+
- **X-Forwarded-For** proxy header detection
262+
- **Auto-enables** for localhost (`127.0.0.1`, `::1`)
263+
- **Fallback** to computer hostname when no REMOTE_ADDR (CLI mode)
264+
265+
## Testing Infrastructure
266+
267+
**Framework:** Nette Tester with PHPT format (18 test files)
268+
269+
**Test Organization:**
270+
```
271+
tests/
272+
├── bootstrap.php # Test setup with getTempDir() helper
273+
├── Bootstrap/
274+
│ ├── Configurator.*.phpt
275+
│ ├── ConstantsExtension.phpt
276+
│ ├── PhpExtension.phpt
277+
│ └── files/ # NEON test fixtures
278+
└── tmp/ # Temporary test output
279+
```
280+
281+
**Test Bootstrap Helpers:**
282+
- `getTempDir()` - Creates per-process temp directory with automatic garbage collection
283+
- `test($title, $function)` - Simple test wrapper
284+
285+
**Test Pattern:**
286+
```php
287+
require __DIR__ . '/../bootstrap.php';
288+
289+
$configurator = new Configurator;
290+
$configurator->setTempDirectory(getTempDir());
291+
$configurator->addConfig('files/config.neon');
292+
$container = $configurator->createContainer();
293+
294+
Assert::same('expected', $container->parameters['foo']);
295+
```
296+
297+
## CI/CD
298+
299+
**GitHub Actions workflows:**
300+
301+
1. **Tests** (`.github/workflows/tests.yml`):
302+
- Matrix: PHP 8.3, 8.4, 8.5
303+
- Runs `vendor/bin/tester tests -s -C`
304+
- Lowest dependencies test with `composer update --prefer-lowest`
305+
- Code coverage with phpdbg and php-coveralls
306+
307+
2. **Static Analysis** (`.github/workflows/static-analysis.yml`):
308+
- Runs on master branch (informative only)
309+
- Command: `composer phpstan -- --no-progress`
310+
- Continues on error
311+
312+
## NEON Configuration Format
313+
314+
Bootstrap uses NEON (Nette's configuration language). Key sections:
315+
316+
**Parameters:**
317+
```neon
318+
parameters:
319+
appDir: /path/to/app
320+
debugMode: true
321+
custom: value
322+
```
323+
324+
**Extensions:**
325+
```neon
326+
extensions:
327+
myExt: App\MyExtension
328+
```
329+
330+
**Framework-specific sections:**
331+
```neon
332+
constants:
333+
MY_CONST: value
334+
335+
php:
336+
date.timezone: Europe/Prague
337+
338+
database:
339+
dsn: "mysql:host=127.0.0.1;dbname=test"
340+
user: root
341+
password: secret
342+
```
343+
344+
**Parameter expansion** - Use `%paramName%` to reference parameters in config files.
345+
346+
## Key Design Patterns
347+
348+
1. **Fluent Interface** - All configuration methods return `static` for method chaining
349+
2. **Extension Pattern** - Extensible via DI extensions with `onCompile` callback
350+
3. **Container Caching** - Lazy loading with intelligent cache invalidation
351+
4. **Parameter Substitution** - Both compile-time (static) and runtime (dynamic) parameters
352+
5. **Singleton DI Container** - Generated PHP class with lazy service initialization
353+
354+
## Important Notes
355+
356+
- Container cache key includes PHP version, so cache is invalidated on PHP upgrades
357+
- Dynamic parameters are NOT cached - they're evaluated per request
358+
- Configuration merging uses `DI\Config\Helpers::merge()` for deep array merging
359+
- Debug mode can be forced via cookie secret (useful for production debugging)
360+
- The library has no direct dependency on tracy/tracy (optional integration)
361+
- Extension auto-discovery is enabled by default; `$defaultExtensions` always take precedence over discovered
362+
- Cache is automatically invalidated after `composer install/update` (via ClassLoader mtime check)

0 commit comments

Comments
 (0)