# 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

varName ?>

// XSS escape secureHTML($this->userInput) ?> ``` ## 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 secureHTML($this->categoryName) ?> // Or use htmlspecialchars directly ``` ### 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 ```