# 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 // 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 ``` **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 `