UPDATE
This commit is contained in:
198
.paul/codebase/conventions.md
Normal file
198
.paul/codebase/conventions.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# Code Conventions
|
||||
|
||||
## Naming
|
||||
|
||||
| Entity | Convention | Example |
|
||||
|--------|-----------|---------|
|
||||
| Classes | PascalCase | `ProductRepository`, `ShopCategoryController` |
|
||||
| Methods | camelCase | `getQuantity()`, `categoryDetails()` |
|
||||
| Admin action methods | snake_case | `view_list()`, `category_edit()` |
|
||||
| Variables | camelCase | `$mockDb`, `$formViewModel`, `$postData` |
|
||||
| Constants | UPPER_SNAKE_CASE | `MAX_PER_PAGE`, `SORT_TYPES` |
|
||||
| DB tables | `pp_` prefix + snake_case | `pp_shop_products` |
|
||||
| DB columns | snake_case | `price_brutto`, `parent_id`, `lang_id` |
|
||||
| File (new) | `ClassName.php` | `ProductRepository.php` |
|
||||
| File (legacy) | `class.ClassName.php` | (leave, do not rename) |
|
||||
| Templates | kebab-case | `shop-category/category-edit.php` |
|
||||
|
||||
## Medoo ORM Patterns
|
||||
|
||||
```php
|
||||
// Get single record — returns array or null
|
||||
$product = $this->db->get('pp_shop_products', '*', ['id' => $id]);
|
||||
|
||||
// Get single column value
|
||||
$qty = $this->db->get('pp_shop_products', 'quantity', ['id' => $id]);
|
||||
|
||||
// Select multiple records — always guard against false return
|
||||
$rows = $this->db->select('pp_shop_categories', '*', [
|
||||
'parent_id' => $parentId,
|
||||
'ORDER' => ['o' => 'ASC'],
|
||||
]);
|
||||
if (!is_array($rows)) { return []; }
|
||||
|
||||
// Count
|
||||
$count = $this->db->count('pp_shop_products', ['category_id' => $catId]);
|
||||
|
||||
// Update
|
||||
$this->db->update('pp_shop_products', ['quantity' => 10], ['id' => $id]);
|
||||
|
||||
// Delete — ALWAYS 2 arguments, never 3!
|
||||
$this->db->delete('pp_shop_categories', ['id' => $id]);
|
||||
|
||||
// Insert, then check ID for success
|
||||
$this->db->insert('pp_shop_products', $data);
|
||||
$newId = $this->db->id();
|
||||
```
|
||||
|
||||
**Critical pitfalls:**
|
||||
- `$mdb->delete()` takes **2 args** — passing 3 causes silent bugs
|
||||
- `$mdb->get()` returns `null` (not `false`) when no record found
|
||||
- Always check `!is_array()` on `select()` results before iterating
|
||||
|
||||
## Redis Cache Patterns
|
||||
|
||||
```php
|
||||
$cache = new \Shared\Cache\CacheHandler();
|
||||
|
||||
// Read (data is serialized)
|
||||
$raw = $cache->get('shop\\product:' . $id . ':' . $lang . ':' . $hash);
|
||||
if ($raw) {
|
||||
return unserialize($raw);
|
||||
}
|
||||
|
||||
// Write
|
||||
$cache->set(
|
||||
'shop\\product:' . $id . ':' . $lang . ':' . $hash,
|
||||
serialize($data),
|
||||
86400 // TTL in seconds
|
||||
);
|
||||
|
||||
// Delete one key
|
||||
$cache->delete($key);
|
||||
|
||||
// Delete by pattern
|
||||
$cache->deletePattern("shop\\product:$id:*");
|
||||
|
||||
// Clear all product cache variations
|
||||
\Shared\Helpers\Helpers::clear_product_cache($productId);
|
||||
```
|
||||
|
||||
## Template Rendering
|
||||
|
||||
```php
|
||||
// In controller — always return string
|
||||
return \Shared\Tpl\Tpl::view('module/template-name', [
|
||||
'varName' => $value,
|
||||
]);
|
||||
|
||||
// In template — variables available as $this->varName
|
||||
<h1><?= $this->varName ?></h1>
|
||||
|
||||
// XSS escape
|
||||
<span><?= $tpl->secureHTML($this->userInput) ?></span>
|
||||
```
|
||||
|
||||
## AJAX Response Format
|
||||
|
||||
```php
|
||||
// Standard JSON response
|
||||
echo json_encode([
|
||||
'status' => 'ok', // or 'error'
|
||||
'msg' => 'Zapisano.',
|
||||
'id' => (int)$savedId,
|
||||
]);
|
||||
exit;
|
||||
```
|
||||
|
||||
## Form Handling (Admin)
|
||||
|
||||
```php
|
||||
// Define form
|
||||
$form = new FormEditViewModel('Category', 'Edit');
|
||||
$form->addField(FormField::text('name', ['label' => 'Nazwa', 'required' => true]));
|
||||
$form->addField(FormField::select('status', ['label' => 'Status', 'options' => [...]]));
|
||||
$form->addTab('General', [$field1, $field2]);
|
||||
$form->addAction(new FormAction('save', 'Zapisz', FormAction::TYPE_SUBMIT));
|
||||
|
||||
// Validate & process POST
|
||||
$handler = new FormRequestHandler($validator);
|
||||
$result = $handler->handleSubmit($form, $_POST);
|
||||
if (!$result['success']) {
|
||||
// return form with errors
|
||||
}
|
||||
|
||||
// Render form
|
||||
return Tpl::view('components/form-edit', ['form' => $form]);
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```php
|
||||
// Wrap risky operations — especially external API calls and file operations
|
||||
try {
|
||||
$cache->deletePattern("shop\\product:$id:*");
|
||||
} catch (\Exception $e) {
|
||||
error_log("Cache clear failed: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// API — always return structured error
|
||||
if (!$this->authenticate()) {
|
||||
self::sendError('UNAUTHORIZED', 'Invalid API key', 401);
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
### XSS
|
||||
```php
|
||||
// In templates — use secureHTML for user-sourced strings
|
||||
<?= $tpl->secureHTML($this->categoryName) ?>
|
||||
|
||||
// Or use htmlspecialchars directly
|
||||
<?= htmlspecialchars($value, ENT_QUOTES, 'UTF-8') ?>
|
||||
```
|
||||
|
||||
### SQL Injection
|
||||
- All queries via Medoo — never concatenate SQL strings
|
||||
- Use Medoo array syntax or `?` placeholders only
|
||||
|
||||
### Session Security
|
||||
```php
|
||||
// IP-binding on every request
|
||||
if ($_SESSION['ip'] !== $_SERVER['REMOTE_ADDR']) {
|
||||
session_destroy();
|
||||
header('Location: /');
|
||||
exit;
|
||||
}
|
||||
```
|
||||
|
||||
### API Auth
|
||||
```php
|
||||
// Timing-safe comparison
|
||||
return hash_equals($storedKey, $headerKey);
|
||||
```
|
||||
|
||||
## i18n / Translations
|
||||
|
||||
- Language stored in `$_SESSION['current-lang']`
|
||||
- Translations cached in `$_SESSION['lang-{lang_id}']`
|
||||
- DB table: `pp_langs`, keys fetched via `LanguagesRepository`
|
||||
- Helper: `\Shared\Helpers\Helpers::lang($key)` returns translation string
|
||||
|
||||
## PHP Version Constraints (< 8.0)
|
||||
|
||||
```php
|
||||
// ❌ FORBIDDEN
|
||||
$result = match($x) { 1 => 'a' };
|
||||
function foo(int|string $x) {}
|
||||
str_contains($s, 'needle');
|
||||
str_starts_with($s, 'pre');
|
||||
|
||||
// ✅ USE INSTEAD
|
||||
$result = $x === 1 ? 'a' : 'b';
|
||||
function foo($x) {} // + @param int|string in docblock
|
||||
strpos($s, 'needle') !== false
|
||||
strncmp($pre, $s, strlen($pre)) === 0
|
||||
```
|
||||
Reference in New Issue
Block a user