# 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]);
```
