Files
shopPRO/.paul/codebase/conventions.md
2026-03-12 13:36:06 +01:00

5.1 KiB

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

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

$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

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

// Standard JSON response
echo json_encode([
    'status' => 'ok',    // or 'error'
    'msg'    => 'Zapisano.',
    'id'     => (int)$savedId,
]);
exit;

Form Handling (Admin)

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

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

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

// IP-binding on every request
if ($_SESSION['ip'] !== $_SERVER['REMOTE_ADDR']) {
    session_destroy();
    header('Location: /');
    exit;
}

API Auth

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

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