# Helpers — `app/Helpers/` All reusable, stateless utility functions live here as `public static` methods. Never duplicate logic as private controller methods. ## Convention ```php // Always call as static — no instance needed HelperClass::methodName($arg); ``` If no existing class fits, create a new `PascalCase.php` static class here. --- ## `DB.php` — Database All queries use prepared statements. Never interpolate values into SQL. ### Methods ```php Db::select($sql, $params) // returns array of rows Db::getRow($sql, $params) // returns single row or null Db::insert($table, $data) // returns last insert ID Db::update($table, $data, $where, $whereParams) // returns affected row count Db::delete($table, $where, $whereParams) // returns affected row count Db::execute($sql, $params) // for custom queries (UPDATE/INSERT/etc.) Db::beginTransaction() Db::commit() Db::rollback() Db::useConnection('legalwriter') // switch DB connection Db::useConnection('default') // switch back ``` ### Examples ```php // SELECT multiple rows $users = Db::select("SELECT * FROM sp_users WHERE active = :active", [':active' => 1]); // SELECT single row $user = Db::getRow("SELECT * FROM sp_users WHERE id = :id", [':id' => $id]); if (!$user) { die('Not found'); } // INSERT $userId = Db::insert('sp_users', [ 'name' => 'Jane Doe', 'email' => 'jane@example.com', 'created_at' => date('Y-m-d H:i:s') ]); // UPDATE Db::update('sp_users', ['name' => 'Jane Smith', 'updated_at' => date('Y-m-d H:i:s')], 'id = :id', [':id' => $userId] ); // DELETE Db::delete('sp_users', 'id = :id', [':id' => $userId]); // Soft delete (preferred) Db::update('sp_users', ['deleted_at' => date('Y-m-d H:i:s')], 'id = :id', [':id' => $userId]); // Custom query Db::execute("UPDATE sp_users SET last_login = NOW() WHERE id = :id", [':id' => $id]); // Transaction Db::beginTransaction(); try { $orderId = Db::insert('sp_orders', ['user_id' => $userId, 'total' => 100]); Db::execute("UPDATE sp_users SET balance = balance - 100 WHERE id = :id", [':id' => $userId]); Db::commit(); } catch (Exception $e) { Db::rollback(); throw $e; } ``` ### Complex Query Patterns ```php // Aggregation $stats = Db::getRow("SELECT COUNT(*) as total, SUM(amount) as revenue FROM sp_orders WHERE status = 'completed'"); echo $stats['total']; // Subquery $users = Db::select(" SELECT u.*, (SELECT COUNT(*) FROM sp_orders WHERE user_id = u.id) as order_count FROM sp_users u WHERE u.active = 1 "); // Dynamic filter building $query = "SELECT * FROM sp_clients WHERE 1=1"; $params = []; if (!empty($_GET['q'])) { $query .= " AND (name LIKE :q OR email LIKE :q)"; $params[':q'] = '%' . $_GET['q'] . '%'; } if (!empty($_GET['status'])) { $query .= " AND status = :status"; $params[':status'] = $_GET['status']; } $clients = Db::select($query, $params); // Pagination $page = max(1, (int)($_GET['page'] ?? 1)); $perPage = 20; $offset = ($page - 1) * $perPage; $total = Db::getRow("SELECT COUNT(*) as count FROM sp_clients"); $rows = Db::select("SELECT * FROM sp_clients LIMIT :limit OFFSET :offset", [':limit' => $perPage, ':offset' => $offset] ); // Use EXISTS instead of COUNT for existence checks (faster) $exists = Db::getRow("SELECT 1 FROM sp_users WHERE email = :email LIMIT 1", [':email' => $email]); if ($exists) { ... } ``` --- ## `Auth.php` — Authentication ```php Auth::handleLogin() // redirect to /login if not authenticated Auth::can('users.create') // returns bool — check permission Auth::requirePermission('users.delete') // halts with 403 JSON if denied Auth::isLoggedIn() // returns bool Auth::getUserId() // returns current user ID from session Auth::getUser() // returns current user row Auth::login($userId) // set session login state Auth::logout() // destroy session ``` --- ## `Session.php` — Session Management ```php Session::start() Session::set($key, $value) Session::get($key, $default = null) Session::has($key) Session::delete($key) Session::destroy() Session::flash($key, $value) // set one-time flash message Session::getFlash($key) // get and delete flash message ``` --- ## `Validator.php` — Input Validation ```php Validator::required($value) // not empty Validator::email($value) // valid email format Validator::minLength($value, $min) Validator::maxLength($value, $max) Validator::numeric($value) Validator::alpha($value) Validator::alphanumeric($value) Validator::url($value) Validator::date($value) ``` --- ## `Functions.php` — General Purpose Key reusable helpers: ```php // Decrypt tokenized org slug → fetch active org row (dies on invalid) Functions::resolveOrg($slug) // Decrypt tokenized user slug → fetch user+role row (dies on invalid) Functions::resolveUser($slug) // AES-256-CBC URL-safe encrypt / decrypt Functions::encryptData($data, $key) Functions::decryptData($token, $key) ``` --- ## `Hash.php` — Cryptography Crypto utilities (hashing, token generation, etc.). --- ## Adding New Helpers 1. Find the most semantically appropriate existing class. 2. Add a `public static function` to it. 3. If nothing fits, create `app/Helpers/NewHelper.php` as a static class. 4. Call from any controller or other helper — no instantiation needed.