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
200 lines
5.7 KiB
Markdown
200 lines
5.7 KiB
Markdown
# 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.
|