Clean-room copy of the reusable engines from comiida, with all instance data, secrets, dependencies, and build output excluded: - app/ Astro theme skeleton (no comiida blog posts; hero image -> placeholder) - api/ SeedProject PHP framework (no vendor/.env/config.php) - content-pipeline/ engine only (scripts/admin/prompts; empty runtime state) - astroagent.config.json + app/.astroagent/skills Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SYHWLHihq3v9nxNwoPCKSn
5.7 KiB
5.7 KiB
Helpers — app/Helpers/
All reusable, stateless utility functions live here as public static methods.
Never duplicate logic as private controller methods.
Convention
// 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
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
// 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
// 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
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
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
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:
// 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
- Find the most semantically appropriate existing class.
- Add a
public static functionto it. - If nothing fits, create
app/Helpers/NewHelper.phpas a static class. - Call from any controller or other helper — no instantiation needed.