Files
orderPRO/.paul/codebase/CONVENTIONS.md
Jacek Pyziak b1b2cc5827 chore: generate codebase map in .paul/codebase/
INDEX, STACK, ARCHITECTURE, CONVENTIONS, TESTING, INTEGRATIONS, CONCERNS
2026-04-26 21:39:12 +02:00

184 lines
5.4 KiB
Markdown

# Conventions & Patterns
## Naming
| Element | Convention | Example |
|---------|-----------|---------|
| Classes | PascalCase | `OrdersController`, `AllegroApiClient` |
| Methods / variables | camelCase | `findDetails()`, `$statusCode` |
| Constants | UPPER_SNAKE_CASE | `SESSION_KEY`, `OPTION_KEYS` |
| DB columns | snake_case | `source_order_id`, `payment_status` |
| PHP files | Match class name | `OrdersController.php` |
| View files | kebab-case | `table-list.php`, `order-status-panel.php` |
| SCSS partials | `_kebab-case.scss` | `_automation.scss` |
| No abbreviations | Full names | `$translatedText` not `$t` (except loop indices) |
## Code Constraints (CLAUDE.md)
- Max **~50 lines** per method; longer → split
- Max **3 nesting levels** (if/foreach); deeper → extract to method
- Single Responsibility: one class = one job
- All classes are `final` (no accidental inheritance)
- `declare(strict_types=1)` in every file
- Comments only for **WHY**, never for WHAT
## Database Pattern
**PDO prepared statements only — no ORM, no string concatenation.**
```php
// Correct
$stmt = $pdo->prepare('SELECT * FROM orders WHERE id = :id');
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->execute();
// Never
$pdo->query("SELECT * FROM orders WHERE id = $id"); // forbidden
```
- `ATTR_EMULATE_PREPARES = false` (real server-side preparation)
- `ATTR_ERRMODE = ERRMODE_EXCEPTION`
- Parameter type hints: `PDO::PARAM_INT` for integers
## Security Patterns
### CSRF
```php
// Generate (in controller)
'csrfToken' => Csrf::token() // stores in $_SESSION['_csrf_token']
// In view
<input type="hidden" name="_token" value="<?= $e($csrfToken) ?>">
// Validate (in controller)
if (!Csrf::validate((string) $request->input('_token', ''))) { ... }
```
Field name is always `_token`. Uses `hash_equals()` for timing-safe comparison.
### XSS Escaping
All user-controlled output escaped with `$e()` helper (available in all views):
```php
$e = fn(mixed $v): string => htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8');
// Usage
<?= $e($order['customer_name']) ?>
<?= $e($t('orders.status.label')) ?>
```
**Never** output raw variables without `$e()`.
### Session
Configured with: `cookie_httponly=true`, `cookie_secure=true`, `cookie_samesite=Lax`, `use_strict_mode=true`.
Access via `Session::get()` / `Session::set()` helpers — not raw `$_SESSION` in business logic.
## Controller Pattern
```php
final class OrdersController {
public function __construct(
private readonly Template $template,
private readonly Translator $translator,
private readonly OrdersRepository $orders,
// ...
) {}
public function index(Request $request): Response {
// 1. Parse & validate input
$filters = ['search' => trim((string) $request->input('search', ''))];
// 2. Call repository
$result = $this->orders->paginate($filters);
// 3. Prepare view data
$rows = array_map(fn($row) => $this->toTableRow($row), $result['items']);
// 4. Render
return Response::html(
$this->template->render('orders/index', ['rows' => $rows], 'layouts/app')
);
}
}
```
## View Pattern
Views use two magic helpers injected by `Template::renderFile()`:
- `$e($value)` — HTML-escape
- `$t($key, $replace)` — translate
Layout composition:
```php
$this->template->render('orders/index', $data, 'layouts/app')
// renders views/orders/index.php, wraps in views/layouts/app.php via $content
```
## UI Rules
### Alerts & Confirmations
- **Always** use `window.OrderProAlerts.confirm({message, onConfirm})` from `jquery-alerts.js`
- **Never** use native `alert()` or `confirm()`
### CSS / SCSS
- All styles go in `resources/scss/` — never inline `<style>` or `style=""` attributes in PHP templates
- CSS custom properties for dynamic colors: `style="--status-color: <?= $e($color) ?>"` → used via `var(--status-color)` in SCSS
- Build: `npm run build:assets`
- UI must be **compact** — maximize info density, minimize whitespace
### Reusable Components
- Extract repeated UI blocks to `resources/views/components/`
- Current components: `table-list.php`, `order-status-panel.php`
- Changes to a component must be verified in **all** places it is used
## Flash Messages
```php
// Set (in controller)
Flash::set('error', $this->translator->get('auth.errors.csrf_expired'));
return Response::redirect('/login');
// Read (in view)
<?php if (!empty($errorMessage)): ?>
<div class="alert alert--danger"><?= $e($errorMessage) ?></div>
<?php endif; ?>
```
## Exception Hierarchy
```
OrderProException (base)
├── AllegroApiException
├── AllegroOAuthException
├── ApaczkaApiException
├── IntegrationConfigException
└── ShipmentException
```
Throw specific domain exceptions, not generic `\Exception`.
## Error Handling
Global exception handler in `Application::registerErrorHandlers()`:
- Always logs to `storage/logs/app.log` with JSON context
- Shows `message` in debug mode, `"Internal server error"` in production
Log format: `[2026-04-26 14:30:00] ERROR message {"context":"value"}`
## Routing Convention
```php
// Public
$router->get('/login', [AuthController::class, 'showLogin']);
$router->post('/login', [AuthController::class, 'login']);
// Authenticated
$router->get('/orders', [OrdersController::class, 'index'], [$authMiddleware]);
// JSON API with API key
$router->post('/api/print-jobs', [PrintApiController::class, 'store'], [$apiKeyMiddleware]);
```