5.4 KiB
5.4 KiB
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.
// 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_INTfor integers
Security Patterns
CSRF
// 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):
$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
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:
$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})fromjquery-alerts.js - Never use native
alert()orconfirm()
CSS / SCSS
- All styles go in
resources/scss/— never inline<style>orstyle=""attributes in PHP templates - CSS custom properties for dynamic colors:
style="--status-color: <?= $e($color) ?>"→ used viavar(--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
// 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.logwith JSON context - Shows
messagein debug mode,"Internal server error"in production
Log format: [2026-04-26 14:30:00] ERROR message {"context":"value"}
Routing Convention
// 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]);