chore: rename codebase map files to lowercase
This commit is contained in:
183
.paul/codebase/conventions.md
Normal file
183
.paul/codebase/conventions.md
Normal file
@@ -0,0 +1,183 @@
|
||||
# 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]);
|
||||
```
|
||||
Reference in New Issue
Block a user