Introduce method-level caching for department reports to enhance performance

- Wrap report calculation logic in `methodCacheWithParameters` for 2-minute caching.
- Standardize reusable caching logic in `db_object_t` trait.
- Refactor query-based methods to integrate caching and improve maintainability.
This commit is contained in:
Jeppe Bundgaard
2026-01-22 12:18:39 +01:00
parent d58fa69560
commit e0e479a15d
4 changed files with 233 additions and 186 deletions
-1
View File
@@ -397,5 +397,4 @@ class redis implements redis_i
// Get multiple keys from Redis
return $this->redis->mget($array_map);
}
}
@@ -75,17 +75,18 @@ class department_daily_reports_o extends db
*/
public function getProductsSoldOnDate(string $date, int $department_id, int $product_id, string $date_to = null): int
{
global /** @var db $db */
$db;
// If the date_to is null, set it to the date
if ($date_to === null) {
$date_to = $date; // Making the report for an entire day
}
$department = (new departments_o())->select($department_id);
if (!$department) {
throw new Exception("Department with ID {$department_id} not found");
}
return $department->getTotalAddonsSoldInDepartment([$product_id], $date, $date_to, $department_id);
return self::methodCacheWithParameters(__METHOD__, func_get_args(), 120, function () use ($date, $department_id, $product_id, $date_to) {
global /** @var db $db */
$db;
// If the date_to is null, set it to the date
if ($date_to === null) {
$date_to = $date; // Making the report for an entire day
}
// Method cache for 2 minutes
$department = (new departments_o())->select($department_id);
$department->requireSelected();
return $department->getTotalAddonsSoldInDepartment([$product_id], $date, $date_to, $department_id);
});
}
/**
@@ -127,37 +128,39 @@ class department_daily_reports_o extends db
*/
public function getTransactionProductsOnDateCount(string $date, int $department_id, string $date_to = null): int
{
global /** @var db $db */
$db;
// If the date_to is null, set it to the date
if ($date_to === null) {
$date_to = $date; // Making the report for an entire day
}
// Set the date time to cover the entire day
$date = date('Y-m-d 00:00:00', strtotime($date));
$date_to = date('Y-m-d 23:59:59', strtotime($date_to));
$conn = $db->conn();
$stmt = $conn->prepare(
'SELECT SUM(oi.quantity) as amount FROM orders o
JOIN order_items oi ON o.id = oi.order_id
WHERE o.department_id = ? AND DATE(o.created_at) BETWEEN ? AND ? AND o.deleted_at IS NULL AND oi.deleted_at IS NULL'
);
return self::methodCacheWithParameters(__METHOD__, func_get_args(), 120, function() use ($date, $department_id, $date_to) {
global /** @var db $db */
$db;
// If the date_to is null, set it to the date
if ($date_to === null) {
$date_to = $date; // Making the report for an entire day
}
// Set the date time to cover the entire day
$date = date('Y-m-d 00:00:00', strtotime($date));
$date_to = date('Y-m-d 23:59:59', strtotime($date_to));
$conn = $db->conn();
$stmt = $conn->prepare(
'SELECT SUM(oi.quantity) as amount FROM orders o
JOIN order_items oi ON o.id = oi.order_id
WHERE o.department_id = ? AND DATE(o.created_at) BETWEEN ? AND ? AND o.deleted_at IS NULL AND oi.deleted_at IS NULL'
);
if ($stmt) {
$stmt->bind_param('iss', $department_id, $date, $date_to); // Bind parameters (i = integer, s = string)
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
if ($stmt) {
$stmt->bind_param('iss', $department_id, $date, $date_to); // Bind parameters (i = integer, s = string)
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
// Access the "amount" field
$amount = $data['amount'];
// Access the "amount" field
$amount = $data['amount'];
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
return (int)$amount;
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
return (int)$amount;
});
}
/**
@@ -170,38 +173,40 @@ class department_daily_reports_o extends db
*/
public function getTransactionsOnDateCount(string $date, int $department_id, string $date_to = null): int
{
global /** @var db $db */
$db;
// If the date_to is null, set it to the date
if ($date_to === null) {
$date_to = $date; // Making the report for an entire day
}
// Set the date time to cover the entire day
$date = date('Y-m-d 00:00:00', strtotime($date));
$date_to = date('Y-m-d 23:59:59', strtotime($date_to));
$conn = $db->conn();
// Make sure only to count orders with at least one non-deleted order item
$stmt = $conn->prepare(
'SELECT COUNT(DISTINCT o.id) as amount FROM orders o
JOIN order_items oi ON o.id = oi.order_id
WHERE o.department_id = ? AND DATE(o.created_at) BETWEEN ? AND ? AND o.deleted_at IS NULL AND oi.deleted_at IS NULL'
);
return self::methodCacheWithParameters(__METHOD__, func_get_args(), 120, function() use ($date, $department_id, $date_to) {
global /** @var db $db */
$db;
// If the date_to is null, set it to the date
if ($date_to === null) {
$date_to = $date; // Making the report for an entire day
}
// Set the date time to cover the entire day
$date = date('Y-m-d 00:00:00', strtotime($date));
$date_to = date('Y-m-d 23:59:59', strtotime($date_to));
$conn = $db->conn();
// Make sure only to count orders with at least one non-deleted order item
$stmt = $conn->prepare(
'SELECT COUNT(DISTINCT o.id) as amount FROM orders o
JOIN order_items oi ON o.id = oi.order_id
WHERE o.department_id = ? AND DATE(o.created_at) BETWEEN ? AND ? AND o.deleted_at IS NULL AND oi.deleted_at IS NULL'
);
if ($stmt) {
$stmt->bind_param('iss', $department_id, $date, $date_to); // Bind parameters (i = integer, s = string)
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
if ($stmt) {
$stmt->bind_param('iss', $department_id, $date, $date_to); // Bind parameters (i = integer, s = string)
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
// Access the "amount" field
$amount = $data['amount'];
// Access the "amount" field
$amount = $data['amount'];
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
return (int)$amount;
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
return (int)$amount;
});
}
/**
@@ -314,41 +319,44 @@ class department_daily_reports_o extends db
public function getTransactionsOnDateEarnings(string $date, int $department_id, string $date_to = null): int
{
global /** @var db $db */
$db;
// If the date_to is null, set it to the date
if ($date_to === null) {
$date_to = $date; // Making the report for an entire day
}
// Set the date time to cover the entire day
$date = date('Y-m-d 00:00:00', strtotime($date));
$date_to = date('Y-m-d 23:59:59', strtotime($date_to));
$conn = $db->conn();
// Get all the product prices, in all the orders (Not counting removed orders), for the given date and department, and sum them
$stmt = $conn->prepare(
'SELECT SUM(oi.price * oi.quantity) as amount FROM orders o
JOIN order_items oi ON o.id = oi.order_id
WHERE o.department_id = ? AND DATE(o.created_at) BETWEEN ? AND ? AND o.deleted_at IS NULL AND oi.deleted_at IS NULL'
);
if ($stmt) {
$stmt->bind_param('iss', $department_id, $date, $date_to); // Bind parameters (i = integer, s = string)
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
return self::methodCacheWithParameters(__METHOD__, func_get_args(), 120, function() use ($date, $department_id, $date_to) {
// Access the "amount" field
if (!$data) {
// If there are no orders, set the amount to 0
$amount = 0;
} else {
$amount = $data['amount'];
global /** @var db $db */
$db;
// If the date_to is null, set it to the date
if ($date_to === null) {
$date_to = $date; // Making the report for an entire day
}
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
return (int)$amount;
// Set the date time to cover the entire day
$date = date('Y-m-d 00:00:00', strtotime($date));
$date_to = date('Y-m-d 23:59:59', strtotime($date_to));
$conn = $db->conn();
// Get all the product prices, in all the orders (Not counting removed orders), for the given date and department, and sum them
$stmt = $conn->prepare(
'SELECT SUM(oi.price * oi.quantity) as amount FROM orders o
JOIN order_items oi ON o.id = oi.order_id
WHERE o.department_id = ? AND DATE(o.created_at) BETWEEN ? AND ? AND o.deleted_at IS NULL AND oi.deleted_at IS NULL'
);
if ($stmt) {
$stmt->bind_param('iss', $department_id, $date, $date_to); // Bind parameters (i = integer, s = string)
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
// Access the "amount" field
if (!$data) {
// If there are no orders, set the amount to 0
$amount = 0;
} else {
$amount = $data['amount'];
}
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
return (int)$amount;
});
}
/**
@@ -501,42 +509,44 @@ class department_daily_reports_o extends db
*/
public function getTransactionsOnDateWashesCount(string $date, int $department_id, string $date_to = null): int
{
global /** @var db $db */
$db;
// If the date_to is null, set it to the date
if ($date_to === null) {
$date_to = $date; // Making the report for an entire day
}
// Set the date time to cover the entire day
$date = date('Y-m-d 00:00:00', strtotime($date));
$date_to = date('Y-m-d 23:59:59', strtotime($date_to));
$conn = $db->conn();
// Get all the product prices, in all the orders (Not counting removed orders), for the given date and department, and sum them
$stmt = $conn->prepare(
'SELECT COUNT(DISTINCT o.id) as amount FROM orders o
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE o.department_id = ? AND DATE(o.created_at) BETWEEN ? AND ? AND o.deleted_at IS NULL AND oi.deleted_at IS NULL AND p.is_wash = 1'
);
if ($stmt) {
$stmt->bind_param('iss', $department_id, $date, $date_to); // Bind parameters (i = integer, s = string)
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
// Access the "amount" field
if (!$data) {
// If there are no orders, set the amount to 0
$amount = 0;
} else {
$amount = $data['amount'];
return self::methodCacheWithParameters(__METHOD__, func_get_args(), 120, function() use ($date, $department_id, $date_to) {
global /** @var db $db */
$db;
// If the date_to is null, set it to the date
if ($date_to === null) {
$date_to = $date; // Making the report for an entire day
}
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
return (int)$amount;
// Set the date time to cover the entire day
$date = date('Y-m-d 00:00:00', strtotime($date));
$date_to = date('Y-m-d 23:59:59', strtotime($date_to));
$conn = $db->conn();
// Get all the product prices, in all the orders (Not counting removed orders), for the given date and department, and sum them
$stmt = $conn->prepare(
'SELECT COUNT(DISTINCT o.id) as amount FROM orders o
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE o.department_id = ? AND DATE(o.created_at) BETWEEN ? AND ? AND o.deleted_at IS NULL AND oi.deleted_at IS NULL AND p.is_wash = 1'
);
if ($stmt) {
$stmt->bind_param('iss', $department_id, $date, $date_to); // Bind parameters (i = integer, s = string)
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
// Access the "amount" field
if (!$data) {
// If there are no orders, set the amount to 0
$amount = 0;
} else {
$amount = $data['amount'];
}
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
return (int)$amount;
});
}
/**
@@ -552,48 +562,51 @@ class department_daily_reports_o extends db
*/
public function getTransactionsOnDateWaterUsage(string $date, int $department_id, string $date_to = null): int
{
global /** @var db $db */
$db;
// If the date_to is null, set it to the date
if ($date_to === null) {
$date_to = $date; // Making the report for an entire day
}
// Set the date time to cover the entire day
$date = date('Y-m-d 00:00:00', strtotime($date));
$date_to = date('Y-m-d 23:59:59', strtotime($date_to));
$conn = $db->conn();
// Get the earliest report before the given date
$stmt = $conn->prepare(
'SELECT water_usage FROM department_daily_reports o
WHERE o.created_at < ? AND o.department_id = ? AND o.water_usage > 0 ORDER BY o.created_at DESC LIMIT 1'
);
if ($stmt) {
$stmt->bind_param('si', $date, $department_id); // Bind parameters
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
$earliest_usage = $data ? (int)$data['water_usage'] : 0;
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
// Get the report closest to the date_to, but not after
$stmt = $conn->prepare(
'SELECT water_usage FROM department_daily_reports o
WHERE o.created_at <= ? AND o.department_id = ? AND o.water_usage > 0 ORDER BY o.created_at DESC LIMIT 1'
);
if ($stmt) {
$stmt->bind_param('si', $date_to, $department_id); // Bind parameters
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
$latest_usage = $data ? (int)$data['water_usage'] : 0;
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
return $latest_usage - $earliest_usage;
return self::methodCacheWithParameters(__METHOD__, func_get_args(), 120, function() use ($date, $department_id, $date_to) {
global /** @var db $db */
$db;
// If the date_to is null, set it to the date
if ($date_to === null) {
$date_to = $date; // Making the report for an entire day
}
// Set the date time to cover the entire day
$date = date('Y-m-d 00:00:00', strtotime($date));
$date_to = date('Y-m-d 23:59:59', strtotime($date_to));
$conn = $db->conn();
// Get the earliest report before the given date
$stmt = $conn->prepare(
'SELECT water_usage FROM department_daily_reports o
WHERE o.created_at < ? AND o.department_id = ? AND o.water_usage > 0 ORDER BY o.created_at DESC LIMIT 1'
);
if ($stmt) {
$stmt->bind_param('si', $date, $department_id); // Bind parameters
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
$earliest_usage = $data ? (int)$data['water_usage'] : 0;
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
// Get the report closest to the date_to, but not after
$stmt = $conn->prepare(
'SELECT water_usage FROM department_daily_reports o
WHERE o.created_at <= ? AND o.department_id = ? AND o.water_usage > 0 ORDER BY o.created_at DESC LIMIT 1'
);
if ($stmt) {
$stmt->bind_param('si', $date_to, $department_id); // Bind parameters
$stmt->execute();
$result = $stmt->get_result(); // Get the result set from the statement
$data = $result->fetch_assoc(); // Fetch the result as an associative array
$latest_usage = $data ? (int)$data['water_usage'] : 0;
$stmt->close(); // Close the statement
} else {
// Handle query preparation error
die('Query preparation failed: ' . $conn->error);
}
return $latest_usage - $earliest_usage;
});
}
}
+8 -6
View File
@@ -654,12 +654,14 @@ class departments_o extends db
*/
public function getTotalMaxAddonsInDepartment(array $product_ids, string $date_start, string $date_end, int $department_id): int
{
$total_max_addons = 0;
foreach ($product_ids as $product_id) {
$addon_max_count = (new product_options_o())->getMaxAddonsInDateRange($product_id, $date_start, $date_end, $department_id);
$total_max_addons += $addon_max_count;
}
return $total_max_addons;
return self::methodCacheWithParameters(__METHOD__, func_get_args(), 120, function() use ($product_ids, $date_start, $date_end, $department_id) {
$total_max_addons = 0;
foreach ($product_ids as $product_id) {
$addon_max_count = (new product_options_o())->getMaxAddonsInDateRange($product_id, $date_start, $date_end, $department_id);
$total_max_addons += $addon_max_count;
}
return $total_max_addons;
});
}
/**
+33
View File
@@ -1005,6 +1005,39 @@ trait db_object_t
return $data;
}
/**
* Method cache with parameters
* @param string $method The method to cache the result of
* @param array $params The parameters to pass to the method
* @param int $expiration The expiration time in seconds
* @param callable|null $function The function to call if the cached result is not found, it should return the result to be cached
* @return mixed The cached result of the method
*/
public function methodCacheWithParameters(string $method, array $params = [], int $expiration = 120, callable $function = null): mixed
{
// Generate the cache key
$objectId = "methodCacheWithParameters";
$key = $method . '_' . md5(serialize($params));
// Try to get the cached result
$cachedResult = $this->getCached($key, $objectId);
// If the cached result is found, return it
if ($cachedResult !== null) {
return $cachedResult;
}
// If the cached result is not found, call the function to get the result
if ($function) {
$result = call_user_func_array($function, $params);
} else {
// If no function is provided, call the method on this object
$result = call_user_func_array([$this, $method], $params);
}
// Cache the result
$this->cache($key, $result, $objectId);
// Set the expiration time
$this->setCachedExpiration($key, $expiration, $objectId);
return $result;
}
/**
* Delete cached object
* @param string $key The key to delete the cached object