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

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_INT for 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}) 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

// 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

// 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]);