114 lines
4.2 KiB
Markdown
114 lines
4.2 KiB
Markdown
|
|
# CLAUDE.md
|
||
|
|
|
||
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||
|
|
|
||
|
|
## Package
|
||
|
|
|
||
|
|
`pronchev/pinecore` — minimal PHP framework for FrankenPHP long-running workers.
|
||
|
|
|
||
|
|
**Namespace:** `Pronchev\Pinecore\` → `src/` (PSR-4)
|
||
|
|
|
||
|
|
No lint or test commands configured yet.
|
||
|
|
|
||
|
|
## Architecture
|
||
|
|
|
||
|
|
**Request lifecycle:**
|
||
|
|
|
||
|
|
```
|
||
|
|
HTTP → Caddy (:80) → /worker.php → FrankenPHP worker loop
|
||
|
|
Kernel::boot() [once on startup]
|
||
|
|
→ Environment::detect() → Config::load() → ContainerFactory::build()
|
||
|
|
loop:
|
||
|
|
→ HttpApplication::handleRequest($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER)
|
||
|
|
→ Request::fromGlobals()
|
||
|
|
→ OPTIONS? → CORS headers + 204 (no routing)
|
||
|
|
→ Router::match() → 404 / 405 / RouteMatch
|
||
|
|
→ MiddlewarePipeline::run()
|
||
|
|
→ Controller::__invoke(Request): Response
|
||
|
|
→ Response + CORS headers → emit()
|
||
|
|
→ Application::terminate()
|
||
|
|
→ gc_collect_cycles()
|
||
|
|
```
|
||
|
|
|
||
|
|
**Key components:**
|
||
|
|
|
||
|
|
| Path | Purpose |
|
||
|
|
|---|---|
|
||
|
|
| `src/Kernel.php` | One-time bootstrap |
|
||
|
|
| `src/Http/` | `HttpApplication`, `Request`, `Response`, `Router`, `RouteDefinition`, `MiddlewarePipeline`, `HttpException` |
|
||
|
|
| `src/Console/` | `ConsoleApplication`, `ConsoleRouter`, `ConsoleInput`, `ConsoleOutput`, `ConsoleDefinition` |
|
||
|
|
| `src/Auth/` | `JwtService`, `AuthMiddleware`, `AuthContext`, `AuthException`, `UserProviderInterface` |
|
||
|
|
| `src/Log/` | `StdoutLogger`, `FileLogger`, `CompositeLogger`, `NullLogger` |
|
||
|
|
| `src/Orm/` | `AbstractMongoRepository`, `MongoHydrator`, `EntityMap`, `IdGenerator`, attributes |
|
||
|
|
| `src/Model/` | Marker interfaces: `Entity`, `MongoEntity`, `SqlEntity`, `Dto` |
|
||
|
|
| `src/ExceptionHandler.php` | Catches `Throwable` escaping the worker loop |
|
||
|
|
| `src/Config.php` | Static config loader: `Config::get('section.key')` |
|
||
|
|
| `src/ContainerFactory.php` | Builds PHP-DI container from `config/services.php` |
|
||
|
|
|
||
|
|
## HTTP
|
||
|
|
|
||
|
|
```php
|
||
|
|
// RouteDefinition: method, path, controller class, middleware array
|
||
|
|
new RouteDefinition('GET', '/users/{id}', GetUserController::class, [AuthMiddleware::class])
|
||
|
|
|
||
|
|
// Controller — invokable, resolved via DI
|
||
|
|
final class GetUserController {
|
||
|
|
public function __invoke(Request $request): Response {
|
||
|
|
$id = $request->pathParams['id']; // path param
|
||
|
|
$user = $request->get('auth')->user; // from AuthMiddleware
|
||
|
|
return Response::json([...]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Throw from anywhere — caught centrally
|
||
|
|
throw new HttpException('Forbidden', 403);
|
||
|
|
```
|
||
|
|
|
||
|
|
## Auth
|
||
|
|
|
||
|
|
`AuthMiddleware` requires `UserProviderInterface` to be bound in DI:
|
||
|
|
```php
|
||
|
|
// UserProviderInterface: findById(string $id): object
|
||
|
|
// In app's config/services.php:
|
||
|
|
UserProviderInterface::class => fn($c) => $c->get(UserRepository::class),
|
||
|
|
```
|
||
|
|
|
||
|
|
`JwtService::issue(string $userId): string` — issues a JWT. Accepts only a string ID, no dependency on app models.
|
||
|
|
|
||
|
|
## Logging
|
||
|
|
|
||
|
|
```php
|
||
|
|
// Inject LoggerInterface via DI constructor
|
||
|
|
$this->logger->error('Something failed', ['exception' => $e, 'path' => $request->path]);
|
||
|
|
```
|
||
|
|
|
||
|
|
Output: JSON to stdout — `ts`, `level`, `channel`, `message`, `context`.
|
||
|
|
Level: `LOG_LEVEL` env var (default `debug`).
|
||
|
|
File logging: set `LOG_FILE=/path/to/file.log` — `FileLogger` activates automatically.
|
||
|
|
To add a backend: extend the `array_filter([...])` in the `LoggerInterface` factory in `config/services.php`.
|
||
|
|
|
||
|
|
## ORM
|
||
|
|
|
||
|
|
Entities implement `MongoEntity`, use PHP 8 attributes:
|
||
|
|
```php
|
||
|
|
#[Collection(name: 'users')]
|
||
|
|
final class User implements MongoEntity {
|
||
|
|
public function __construct(
|
||
|
|
#[Id] public readonly string $id,
|
||
|
|
#[Field] public readonly string $email,
|
||
|
|
) {}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Repository extends `AbstractMongoRepository`, declare `#[ForEntity(Foo::class)]` on the class.
|
||
|
|
Implement typed `save(Foo $e): Foo { return $this->persist($e); }` — not an override of the protected `persist()`.
|
||
|
|
`#[Id]` maps to a custom field (`id`), not MongoDB's `_id` (which remains a native ObjectId).
|
||
|
|
`array` fields (e.g. `string[]`) are hydrated automatically — BSONArray is converted transparently.
|
||
|
|
|
||
|
|
## Code Style
|
||
|
|
|
||
|
|
EditorConfig enforces:
|
||
|
|
- PHP: UTF-8, LF, 4-space indent, 120-char line limit (PSR-12)
|
||
|
|
- JS/TS: 2-space indent, 100-char line limit
|
||
|
|
- Templates (Blade/Twig), YAML, JSON, Docker: 2-space indent
|