php/CLAUDE.md
Snider e27f8b1088
Some checks failed
CI / PHP 8.4 (push) Failing after 2m3s
CI / PHP 8.3 (push) Failing after 2m11s
fix(plugin): add commands/ auto-discover directory, fix plugin.json schema
Commands must be in `.claude-plugin/commands/` for Claude Code auto-discovery.
Fixed plugin.json to use `{name, description, file}` array format.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-13 10:18:41 +00:00

187 lines
6.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Commands
```bash
composer test # Run all tests (PHPUnit)
composer test -- --filter=Name # Run single test by name
composer test -- --testsuite=Unit # Run specific test suite
composer pint # Format code with Laravel Pint
./vendor/bin/pint --dirty # Format only changed files
vendor/bin/phpstan analyse --memory-limit=2G # Static analysis (level 1)
vendor/bin/psalm --show-info=false # Type checking (level 8)
vendor/bin/rector process --dry-run # Code modernisation preview
composer audit # Security vulnerability check
```
## CI Matrix
Tests run against PHP 8.2/8.3/8.4 × Laravel 11/12 (excluding PHP 8.2 + Laravel 12). CI also runs Pint (`--test`), PHPStan, Psalm, and `composer audit`.
## Coding Standards
- **UK English**: colour, organisation, centre (never American spellings)
- **Strict types**: `declare(strict_types=1);` in every PHP file
- **Type hints**: All parameters and return types required
- **Testing**: PHPUnit with Orchestra Testbench
- **License**: EUPL-1.2
## Architecture
### Event-Driven Module Loading
Modules declare interest in lifecycle events via static `$listens` arrays and are only instantiated when those events fire:
```
LifecycleEventProvider::register()
└── ModuleScanner::scan() # Finds Boot.php files with $listens
└── ModuleRegistry::register() # Wires LazyModuleListener for each event
```
**Key benefit**: Web requests don't load admin modules; API requests don't load web modules.
### Frontages
Frontages are ServiceProviders in `src/Core/Front/` that fire context-specific lifecycle events:
| Frontage | Event | Middleware | Fires When |
|----------|-------|------------|------------|
| Web | `WebRoutesRegistering` | `web` | Public routes |
| Admin | `AdminPanelBooting` | `admin` | Admin panel |
| Api | `ApiRoutesRegistering` | `api` | REST endpoints |
| Client | `ClientRoutesRegistering` | `client` | Authenticated SaaS |
| Cli | `ConsoleBooting` | - | Artisan commands |
| Mcp | `McpToolsRegistering` | - | MCP tool handlers |
| - | `FrameworkBooted` | - | Late-stage initialisation |
### L1 Packages
Subdirectories under `src/Core/` are self-contained "L1 packages" with their own Boot.php, migrations, tests, and views:
```
src/Core/Actions/ # Action pattern + scheduled action scanning
src/Core/Activity/ # Activity logging (wraps spatie/laravel-activitylog)
src/Core/Bouncer/ # Security blocking/redirects + honeypot + action gate
src/Core/Cdn/ # CDN integration
src/Core/Config/ # Dynamic configuration with two-tier caching
src/Core/Front/ # Frontage system (Web, Admin, Api, Client, Cli, Mcp)
src/Core/Lang/ # Translation system with ICU + locale fallback chains
src/Core/Media/ # Media handling with thumbnail helpers
src/Core/Search/ # Search functionality
src/Core/Seo/ # SEO utilities
src/Core/Service/ # Service discovery and dependency resolution
src/Core/Storage/ # Storage with Redis circuit breaker + fallback
src/Core/Webhook/ # Webhook system + CronTrigger scheduled action
```
### Module Pattern
```php
class Boot
{
public static array $listens = [
WebRoutesRegistering::class => 'onWebRoutes',
AdminPanelBooting::class => ['onAdmin', 10], // With priority (higher = runs first)
];
public function onWebRoutes(WebRoutesRegistering $event): void
{
$event->views('example', __DIR__.'/Views');
$event->routes(fn () => require __DIR__.'/Routes/web.php');
$event->livewire('example.widget', ExampleWidget::class);
}
}
```
Scaffold new modules with artisan: `make:mod`, `make:website`, `make:plug`.
### Namespace Mapping
| Path | Namespace |
|------|-----------|
| `src/Core/` | `Core\` |
| `src/Mod/` | `Core\Mod\` |
| `src/Plug/` | `Core\Plug\` |
| `src/Website/` | `Core\Website\` |
| `app/Mod/` | `Mod\` |
### Actions Pattern
Single-purpose business logic classes with static `run()` helper:
```php
use Core\Actions\Action;
class CreateOrder
{
use Action;
public function __construct(private OrderService $orders) {}
public function handle(User $user, array $data): Order
{
return $this->orders->create($user, $data);
}
}
// Usage: CreateOrder::run($user, $validated);
```
### Scheduled Actions
Actions can be marked for scheduled execution with the `#[Scheduled]` attribute. `ScheduledActionScanner` discovers these by scanning for the attribute.
```php
use Core\Actions\Scheduled;
#[Scheduled(frequency: 'dailyAt:09:00', timezone: 'Europe/London')]
class PublishDigest
{
use Action;
public function handle(): void { /* ... */ }
}
```
Frequency strings map to Laravel Schedule methods: `everyMinute`, `dailyAt:09:00`, `weeklyOn:1,09:00`, etc.
### Multi-Tenant Isolation
Models using `Core\Mod\Tenant\Concerns\BelongsToWorkspace` are automatically scoped to the current workspace. The `workspace_id` is set on create and queries are filtered transparently.
### Seeder Ordering
Seeders use PHP attributes for dependency ordering:
```php
#[SeederPriority(50)] // Lower runs first (default 50)
#[SeederAfter(FeatureSeeder::class)]
class PackageSeeder extends Seeder { }
```
### HLCRF Layout System
Data-driven layouts with five regions (Header, Left, Content, Right, Footer):
```php
$page = Layout::make('HCF')->h(view('header'))->c($content)->f(view('footer'));
```
Variant strings: `C` (content only), `HCF` (standard page), `HLCF` (with sidebar), `HLCRF` (full dashboard).
### Go Bridge
`pkg/php/` contains Go code for a native desktop HTTP bridge (PHP-to-native calls), container/deployment utilities (Coolify), and Dockerfile generation.
## Testing
Uses Orchestra Testbench with in-memory SQLite. Tests can live:
- `tests/Feature/` and `tests/Unit/` - main test suites
- `src/Core/{Package}/Tests/` - L1 package co-located tests
- `src/Mod/{Module}/Tests/` - module co-located tests
Test fixtures are in `tests/Fixtures/`. Base test class provides:
```php
$this->getFixturePath('Mod') // Returns tests/Fixtures/Mod path
```