Add caching layer and database helper

- Add CacheHelper for file-based caching with TTL support
- Add Database helper for centralized connection management
- Update WorkflowModel to cache status transitions (10 min TTL)
- Update UserPreferencesModel to cache user prefs (5 min TTL)
- Update manage_workflows.php to clear cache on changes
- Update get_users.php to use Database helper (example migration)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-29 10:53:26 -05:00
parent 1101558fca
commit d2a8c73e2c
6 changed files with 504 additions and 91 deletions

View File

@@ -1,14 +1,49 @@
<?php
/**
* WorkflowModel - Handles status transition workflows and validation
*
* Uses caching for frequently accessed transition rules since they rarely change.
*/
require_once dirname(__DIR__) . '/helpers/CacheHelper.php';
class WorkflowModel {
private $conn;
private static $CACHE_PREFIX = 'workflow';
private static $CACHE_TTL = 600; // 10 minutes
public function __construct($conn) {
$this->conn = $conn;
}
/**
* Get all active transitions (with caching)
*
* @return array All active transitions indexed by from_status
*/
private function getAllTransitions() {
return CacheHelper::remember(self::$CACHE_PREFIX, 'all_transitions', function() {
$sql = "SELECT from_status, to_status, requires_comment, requires_admin
FROM status_transitions
WHERE is_active = TRUE";
$result = $this->conn->query($sql);
$transitions = [];
while ($row = $result->fetch_assoc()) {
$from = $row['from_status'];
if (!isset($transitions[$from])) {
$transitions[$from] = [];
}
$transitions[$from][$row['to_status']] = [
'to_status' => $row['to_status'],
'requires_comment' => (bool)$row['requires_comment'],
'requires_admin' => (bool)$row['requires_admin']
];
}
return $transitions;
}, self::$CACHE_TTL);
}
/**
* Get allowed status transitions for a given status
*
@@ -16,21 +51,13 @@ class WorkflowModel {
* @return array Array of allowed transitions with requirements
*/
public function getAllowedTransitions($currentStatus) {
$sql = "SELECT to_status, requires_comment, requires_admin
FROM status_transitions
WHERE from_status = ? AND is_active = TRUE";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("s", $currentStatus);
$stmt->execute();
$result = $stmt->get_result();
$allTransitions = $this->getAllTransitions();
$transitions = [];
while ($row = $result->fetch_assoc()) {
$transitions[] = $row;
if (!isset($allTransitions[$currentStatus])) {
return [];
}
$stmt->close();
return $transitions;
return array_values($allTransitions[$currentStatus]);
}
/**
@@ -47,22 +74,15 @@ class WorkflowModel {
return true;
}
$sql = "SELECT requires_admin FROM status_transitions
WHERE from_status = ? AND to_status = ? AND is_active = TRUE";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("ss", $fromStatus, $toStatus);
$stmt->execute();
$result = $stmt->get_result();
$allTransitions = $this->getAllTransitions();
if ($result->num_rows === 0) {
$stmt->close();
if (!isset($allTransitions[$fromStatus][$toStatus])) {
return false; // Transition not defined
}
$row = $result->fetch_assoc();
$stmt->close();
$transition = $allTransitions[$fromStatus][$toStatus];
if ($row['requires_admin'] && !$isAdmin) {
if ($transition['requires_admin'] && !$isAdmin) {
return false; // Admin required
}
@@ -75,18 +95,20 @@ class WorkflowModel {
* @return array Array of unique status values
*/
public function getAllStatuses() {
$sql = "SELECT DISTINCT from_status as status FROM status_transitions
UNION
SELECT DISTINCT to_status as status FROM status_transitions
ORDER BY status";
$result = $this->conn->query($sql);
return CacheHelper::remember(self::$CACHE_PREFIX, 'all_statuses', function() {
$sql = "SELECT DISTINCT from_status as status FROM status_transitions
UNION
SELECT DISTINCT to_status as status FROM status_transitions
ORDER BY status";
$result = $this->conn->query($sql);
$statuses = [];
while ($row = $result->fetch_assoc()) {
$statuses[] = $row['status'];
}
$statuses = [];
while ($row = $result->fetch_assoc()) {
$statuses[] = $row['status'];
}
return $statuses;
return $statuses;
}, self::$CACHE_TTL);
}
/**
@@ -97,21 +119,23 @@ class WorkflowModel {
* @return array|null Transition requirements or null if not found
*/
public function getTransitionRequirements($fromStatus, $toStatus) {
$sql = "SELECT requires_comment, requires_admin
FROM status_transitions
WHERE from_status = ? AND to_status = ? AND is_active = TRUE";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("ss", $fromStatus, $toStatus);
$stmt->execute();
$result = $stmt->get_result();
$allTransitions = $this->getAllTransitions();
if ($result->num_rows === 0) {
$stmt->close();
if (!isset($allTransitions[$fromStatus][$toStatus])) {
return null;
}
$row = $result->fetch_assoc();
$stmt->close();
return $row;
$transition = $allTransitions[$fromStatus][$toStatus];
return [
'requires_comment' => $transition['requires_comment'],
'requires_admin' => $transition['requires_admin']
];
}
/**
* Clear workflow cache (call when transitions are modified)
*/
public static function clearCache() {
CacheHelper::delete(self::$CACHE_PREFIX);
}
}