Add Redis integration and improve order processing

Introduces Redis integration with a client class, traits, and interface for caching and object persistence. Adds Redis tests, updates `composer` to include Predis, and extends configurations to support Redis. Implements a new `delete` method for bookings and enhances pagination to include order details.
This commit is contained in:
Jepp9350
2025-01-16 11:27:19 +01:00
parent a794203456
commit 34fc194df1
13 changed files with 573 additions and 30 deletions
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace classes;
use interfaces\redis_i;
class redis implements redis_i
{
use \traits\redis_t;
public function __construct()
{
global $REDIS_CONFIG;
// Make sure the Redis configuration is set
if (!isset($REDIS_CONFIG)) {
throw new \Exception('Redis configuration is not set');
}
// Apply the Redis configuration
$this->redis_host = $REDIS_CONFIG['host'];
$this->redis_database = $REDIS_CONFIG['database'];
$this->redis_password = $REDIS_CONFIG['password'];
}
}
+3 -2
View File
@@ -68,7 +68,7 @@ class response implements response_i
$this->error('Internal server error' . ($error ? ': ' . $error : ''), 500);
}
public function paginate(int $page, int $per_page, int $total, string $search = null, array $filters = null): void
public function paginate(int $page, int $per_page, int $total, string $search = null, array $filters = null, array $order = null): void
{
// If the total is 0, return 1 page, 0 total
if ($total === 0) {
@@ -79,7 +79,8 @@ class response implements response_i
'per_page' => $per_page,
'total' => $total,
'search' => $search,
'filters' => $filters
'filters' => $filters,
'order' => $order
]);
}
+4
View File
@@ -16,6 +16,10 @@ if ($args[1] === 'run') {
echo "Running the minio test script";
require_once 'tests/minio/minioTest.php';
break;
case 'redis-test':
echo "Running the redis test script";
require_once 'tests/redis/redisTest.php';
break;
default:
echo "Invalid script name";
break;
+2 -1
View File
@@ -7,6 +7,7 @@
"ext-openssl": "*",
"ext-curl": "*",
"ext-json": "*",
"aws/aws-sdk-php": "^3.0"
"aws/aws-sdk-php": "^3.0",
"predis/predis": "*"
}
}
Generated
+68 -7
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "54763573d702cd186fb68aefa41a0c42",
"content-hash": "00925965cc45d732b603e3a25c5d5047",
"packages": [
{
"name": "aws/aws-crt-php",
@@ -62,16 +62,16 @@
},
{
"name": "aws/aws-sdk-php",
"version": "3.336.14",
"version": "3.337.0",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "dc9ac0ab313bbfc4e41635ce6d6083f28d202bf0"
"reference": "7e40cecb3ce66749bbd5eaa9f370de48c16acd6c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/dc9ac0ab313bbfc4e41635ce6d6083f28d202bf0",
"reference": "dc9ac0ab313bbfc4e41635ce6d6083f28d202bf0",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/7e40cecb3ce66749bbd5eaa9f370de48c16acd6c",
"reference": "7e40cecb3ce66749bbd5eaa9f370de48c16acd6c",
"shasum": ""
},
"require": {
@@ -154,9 +154,9 @@
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.336.14"
"source": "https://github.com/aws/aws-sdk-php/tree/3.337.0"
},
"time": "2025-01-13T19:04:40+00:00"
"time": "2025-01-15T19:58:52+00:00"
},
{
"name": "guzzlehttp/guzzle",
@@ -549,6 +549,67 @@
},
"time": "2024-09-04T18:46:31+00:00"
},
{
"name": "predis/predis",
"version": "v2.3.0",
"source": {
"type": "git",
"url": "https://github.com/predis/predis.git",
"reference": "bac46bfdb78cd6e9c7926c697012aae740cb9ec9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/predis/predis/zipball/bac46bfdb78cd6e9c7926c697012aae740cb9ec9",
"reference": "bac46bfdb78cd6e9c7926c697012aae740cb9ec9",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.3",
"phpstan/phpstan": "^1.9",
"phpunit/phpunit": "^8.0 || ^9.4"
},
"suggest": {
"ext-relay": "Faster connection with in-memory caching (>=0.6.2)"
},
"type": "library",
"autoload": {
"psr-4": {
"Predis\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Till Krüss",
"homepage": "https://till.im",
"role": "Maintainer"
}
],
"description": "A flexible and feature-complete Redis client for PHP.",
"homepage": "http://github.com/predis/predis",
"keywords": [
"nosql",
"predis",
"redis"
],
"support": {
"issues": "https://github.com/predis/predis/issues",
"source": "https://github.com/predis/predis/tree/v2.3.0"
},
"funding": [
{
"url": "https://github.com/sponsors/tillkruss",
"type": "github"
}
],
"time": "2024-11-21T20:00:02+00:00"
},
{
"name": "psr/http-client",
"version": "1.0.3",
+6 -1
View File
@@ -1,4 +1,4 @@
<?php global $CONFIG_DB, $DEBUG, $ENCRYPTION_KEY, $CORS, $ECONOMIC_API, $WORDPRESS_STATIC_TOKEN, $USE_PROD_ECONOMIC_IN_DEBUG, $EMAIL_WASH_CERTIFICATE_TOKEN, $MINIO;
<?php global $CONFIG_DB, $DEBUG, $ENCRYPTION_KEY, $CORS, $ECONOMIC_API, $WORDPRESS_STATIC_TOKEN, $USE_PROD_ECONOMIC_IN_DEBUG, $REDIS_CONFIG, $EMAIL_WASH_CERTIFICATE_TOKEN, $MINIO;
$CONFIG_DB = [
'host' => '', // IP address of the database server e.g. 127.0.0.1
'user' => '', // Username of the database server e.g. root
@@ -27,4 +27,9 @@ $MINIO = [
'endpoint' => '', // Minio endpoint e.g. http://0.0.0.0:9000
'access_key' => '', // Minio access
'secret_key' => '' // Minio secret key
];
$REDIS_CONFIG = [
'host' => '', // Redis host (IP address)
'database' => 0, // Redis database number (0-15)
'password' => '' // Redis password
];
+1
View File
@@ -37,6 +37,7 @@ require_once 'classes/response.php';
require_once 'classes/request.php';
require_once 'classes/ratelimit.php';
require_once 'classes/wash_certificate_store.php';
require_once 'classes/redis.php';
/**
* Modules
+7
View File
@@ -0,0 +1,7 @@
<?php
namespace interfaces;
interface redis_i
{
}
+25 -18
View File
@@ -92,24 +92,6 @@ class bookings_o extends db
$db->query($sql);
}
public function getObjectProperties(): void
{
$this->customer_number = new object_property($this->table, $this->id, 'customer_number', 'int', true);
$this->wash_type = new object_property($this->table, $this->id, 'wash_type', 'string', true);
$this->contact_email = new object_property($this->table, $this->id, 'contact_email', 'string', true);
$this->reference_number = new object_property($this->table, $this->id, 'reference_number', 'string', true);
$this->regNrTraekker = new object_property($this->table, $this->id, 'regNrTraekker', 'string', true);
$this->regNrTrailer = new object_property($this->table, $this->id, 'regNrTrailer', 'string', true);
$this->washCertificateEmail = new object_property($this->table, $this->id, 'washCertificateEmail', 'string', true);
$this->date = new object_property($this->table, $this->id, 'date', 'string', true);
$this->department = new object_property($this->table, $this->id, 'department', 'string', true);
$this->pickup_bool = new object_property($this->table, $this->id, 'pickup_bool', 'int', true);
$this->notes = new object_property($this->table, $this->id, 'notes', 'string', true);
$this->washCertificateStatus = new object_property($this->table, $this->id, 'washCertificateStatus', 'string', true);
$this->washCertificateUrl = new object_property($this->table, $this->id, 'washCertificateUrl', 'string', true);
$this->status = new object_property($this->table, $this->id, 'status', 'string', true);
}
public function getCustomerBookingsPaginated(int $customer_number, int $page = 1, int $limit = 10, array $order = ['id' => 'DESC'], string $search = null, array $filters = null): array
{
global /** @var response $response */
@@ -132,4 +114,29 @@ class bookings_o extends db
$row = $db->fetch_assoc($result);
return $row['count'];
}
public function delete(int $id): void
{
$this->id = $id;
$this->getObjectProperties();
$this->status->set('cancelled');
}
public function getObjectProperties(): void
{
$this->customer_number = new object_property($this->table, $this->id, 'customer_number', 'int', true);
$this->wash_type = new object_property($this->table, $this->id, 'wash_type', 'string', true);
$this->contact_email = new object_property($this->table, $this->id, 'contact_email', 'string', true);
$this->reference_number = new object_property($this->table, $this->id, 'reference_number', 'string', true);
$this->regNrTraekker = new object_property($this->table, $this->id, 'regNrTraekker', 'string', true);
$this->regNrTrailer = new object_property($this->table, $this->id, 'regNrTrailer', 'string', true);
$this->washCertificateEmail = new object_property($this->table, $this->id, 'washCertificateEmail', 'string', true);
$this->date = new object_property($this->table, $this->id, 'date', 'string', true);
$this->department = new object_property($this->table, $this->id, 'department', 'string', true);
$this->pickup_bool = new object_property($this->table, $this->id, 'pickup_bool', 'int', true);
$this->notes = new object_property($this->table, $this->id, 'notes', 'string', true);
$this->washCertificateStatus = new object_property($this->table, $this->id, 'washCertificateStatus', 'string', true);
$this->washCertificateUrl = new object_property($this->table, $this->id, 'washCertificateUrl', 'string', true);
$this->status = new object_property($this->table, $this->id, 'status', 'string', true);
}
}
+29
View File
@@ -192,5 +192,34 @@ class bookingsRoute
["link" => $wash_certificate_store->getWashCertificateDownload($id)]
);
});
$this->post('/admin/bookings/delete', function () {
// Require the user to be logged in
global /** @var response $response */
$response;
$this->requirePermission('delete_booking');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if (!$user->exists()) {
$response->error('User not found', 400);
}
// Check if the required fields are set
$id = $response->getRequestParameter('id');
// Make sure the id is a number
if (!is_numeric($id)) {
$response->error('id parameter must be a number got: ' . $id, 400);
}
// Make sure the user is allowed to delete the booking
if (!$user->hasAccessToBooking($id)) {
$response->error('You are not allowed to delete this booking', 400);
}
// Delete the booking
(new bookings_o())->delete($id);
// Return success
$response->success(
["message" => "Booking deleted"]
);
});
}
}
+149
View File
@@ -0,0 +1,149 @@
<?php
// prevent direct access
if (!defined('WD')) {
exit;
}
global $REDIS_CONFIG;
use classes\redis;
function warn($message): void
{
echo "\n\033[33m$message\033[0m\n";
}
// Check if the MINIO array is set
if (!isset($REDIS_CONFIG)) {
throw new Exception('REDIS array is not set in config.php');
}
// Check if the MINIO array has the required keys
if (!isset($REDIS_CONFIG['host']) || !isset($REDIS_CONFIG['database']) || !isset($REDIS_CONFIG['password'])) {
throw new Exception('REDIS array is missing required keys');
}
// Create a new Redis object
$redis = new redis();
// Connect to the Redis server
$redis->connect();
// Check if the Redis server is connected
if ($redis->is_connected()) {
// Show success message
echo "\nConnected to the Redis server";
} else {
// Show error message
warn('Failed to connect to the Redis server');
}
// Set a value in the Redis server
$time_start = microtime(true);
$redis->set('test', 'Hello, World!');
$time_end = microtime(true);
// Check if the request was successful
if ($redis->get('test') === 'Hello, World!') {
// Show success message
echo "\nValue set successfully ( " . ($time_end - $time_start) . 's )';
} else {
// Show error message
warn('Failed to set value in the Redis server ( ' . ($time_end - $time_start) . 's )');
}
// Get the value from the Redis server
$time_start_get = microtime(true);
$value = $redis->get('test');
$time_end_get = microtime(true);
// Check if the request was successful
if ($value === 'Hello, World!') {
// Show success message
echo "\nValue retrieved successfully: $value ( " . ($time_end_get - $time_start_get) . 's )';
} else {
// Show error message
warn('Failed to retrieve value from the Redis server ( ' . ($time_end_get - $time_start_get) . 's )');
}
// Delete the value from the Redis server
$redis->delete('test');
// Get the value from the Redis server
$value = $redis->get('test');
// Check if the request was successful
if ($value === null) {
// Show success message
echo "\nValue deleted successfully";
} else {
// Show error message
warn('Failed to delete value from the Redis server');
}
// Get all keys from the Redis server
$keys = $redis->keys();
// Check if the request was successful
if (count($keys) > 0) {
// Show the keys
echo "\nKeys found: " . implode(', ', $keys);
} else {
// Show warning message
warn('No keys found');
}
// Add 500 keys to the Redis server
$time_start = microtime(true);
for ( $i = 0; $i < 200; $i++ ) {
$redis->set('key_' . $i, 'value_' . $i);
}
$time_end = microtime(true);
// Get all keys from the Redis server
$keys = $redis->keys();
// Check if the request was successful
if (count($keys) === 200) {
// Show success message
echo "\n200 keys added successfully ( " . $time_end - $time_start . 's )';
} else {
// Show error message
warn('Failed to add 200 keys to the Redis server ( ' . ($time_end - $time_start) . 's )');
}
// Remove all keys from the Redis server
$time_start = microtime(true);
foreach ( $keys as $key ) {
$redis->delete($key);
}
$time_end = microtime(true);
// Get all keys from the Redis server
$keys = $redis->keys();
// Check if the request was successful
if (count($keys) === 0) {
// Show success message
echo "\n200 keys removed successfully ( " . ($time_end - $time_start) . 's )';
} else {
// Show error message
warn('Failed to remove 200 keys from the Redis server ( ' . ($time_end - $time_start) . 's )');
}
// Add a pre-defined object to the Redis server
$redis->add_object('order', '1', 'Order 1');
// Get the object from the Redis server
$object = $redis->get_object('order', '1');
// Check if the request was successful
if ($object === 'Order 1') {
// Show success message
echo "\nObject retrieved successfully: $object";
} else {
// Show error message
warn('Failed to retrieve object from the Redis server');
}
// Delete the object from the Redis server
$redis->delete_object('order', '1');
// Get the object from the Redis server
$object = $redis->get_object('order', '1');
// Check if the request was successful
if ($object === null) {
// Show success message
echo "\nObject deleted successfully";
} else {
// Show error message
warn('Failed to delete object from the Redis server');
}
// Disconnect from the Redis server
$redis->disconnect();
+7 -1
View File
@@ -84,6 +84,12 @@ trait db_object_t
if ($order) {
$order = explode(':', $order);
$order = [$order[0] => $order[1]];
// Make the order direction uppercase
$order = array_map('strtoupper', $order);
// Check if the order direction is valid
if (!in_array($order[array_key_first($order)], ['ASC', 'DESC'])) {
throw new Exception('Invalid order direction, must be ASC or DESC');
}
}
// List the objects with pagination
if ($page && $limit) {
@@ -203,7 +209,7 @@ trait db_object_t
$countStmt->close();
// Provide pagination metadata
$response->paginate($page, $limit, $total, $search, $filters);
$response->paginate($page, $limit, $total, $search, $filters, $order);
return $objects;
}
+249
View File
@@ -0,0 +1,249 @@
<?php
namespace traits;
use classes\redis;
use Exception;
use Predis\Client as PredisClient;
trait redis_t
{
/**
* The Redis scheme
* @var string
*/
protected string $redis_scheme = 'tcp';
/**
* The Redis host
* @var string
*/
protected string $redis_host = '';
/**
* The Redis port
* @var int
*/
protected int $redis_port = 6379;
/**
* The Redis password
* @var string
*/
protected string $redis_password = '';
/**
* The Redis database
* @var int
*/
protected int $redis_database = 0;
/**
* The object types that can be stored in the Redis server, this is used to prevent unauthorized objects from being stored
* @var array
*/
private array $object_types = [
'order', // The order objects (orders table in the database)
];
/**
* Get the Redis client
* @var PredisClient
*/
private PredisClient $redis;
/**
* Does the key exist in the Redis server
* @param string $key
* @return bool
*/
public function exists(string $key): bool
{
return $this->redis->exists($key);
}
/**
* Set the value in the Redis server
* @param string $key
* @param string $value
* @return redis|redis_t
*/
public function set(string $key, string $value): self
{
$this->redis->set($key, $value);
return $this;
}
/**
* Get all keys from the Redis server
* @return array
*/
public function keys(): array
{
return $this->redis->keys('*');
}
/**
* Get the Redis client
* @return PredisClient
* @throws Exception
*/
public function get_client(): PredisClient
{
if (!self::is_connected()) {
self::connect();
}
return $this->redis;
}
/**
* Check if the Redis server is connected
* @return bool
*/
public function is_connected(): bool
{
return isset($this->redis);
}
/**
* Connect to the Redis server
* @throws Exception
*/
public function connect(): self
{
$this->redis = new PredisClient([
'scheme' => $this->redis_scheme,
'host' => $this->redis_host,
'port' => $this->redis_port,
'password' => $this->redis_password,
'database' => $this->redis_database
]);
// Check if the connection was successful
if (!self::is_connected()) {
throw new Exception('Could not connect to the Redis server');
}
return $this;
}
/**
* Add a pre-defined object to the Redis server
* @param string $object_type
* @param string $key
* @param string $value
* @param int $expiration
* @return redis|redis_t
* @throws Exception
*/
public function add_object(string $object_type, string $key, string $value, int $expiration = 60): self
{
// Check if the object type is valid
if (!self::is_valid_object_type($object_type)) {
throw new Exception('Invalid object type');
}
// Set the object in the Redis server
$this->setEx($object_type . ':' . $key, $value, $expiration);
return $this;
}
/**
* Check if the object type is valid
* @param string $key
* @return bool
*/
private function is_valid_object_type(string $key): bool
{
return in_array(self::get_object_type($key), $this->object_types);
}
/**
* Get the object type
* @param string $key
* @return string
*/
private function get_object_type(string $key): string
{
return explode(':', $key)[0];
}
/**
* Set the value in the Redis server with an expiration time
* @param string $key
* @param string $value
* @param int $expiration The expiration time in seconds
* @return redis|redis_t
*/
public function setEx(string $key, string $value, int $expiration = 60): self
{
$this->redis->setex($key, $expiration, $value);
return $this;
}
/**
* Delete a pre-defined object from the Redis server
* @param string $object_type
* @param string $key
* @return redis|redis_t
* @throws Exception
*/
public function delete_object(string $object_type, string $key): self
{
// Check if the object type is valid
if (!self::is_valid_object_type($object_type)) {
throw new Exception('Invalid object type');
}
// Delete the object from the Redis server
$this->delete($object_type . ':' . $key);
return $this;
}
/**
* Delete the value from the Redis server
* @param string $key
* @return redis|redis_t
*/
public function delete(string $key): self
{
$this->redis->del($key);
return $this;
}
/**
* Get a pre-defined object from the Redis server
* @param string $object_type
* @param string $key
* @return string|null
* @throws Exception
*/
public function get_object(string $object_type, string $key): string|null
{
// Check if the object type is valid
if (!self::is_valid_object_type($object_type)) {
throw new Exception('Invalid object type');
}
return $this->get($object_type . ':' . $key);
}
/**
* Get the value from the Redis server
* @param string $key
* @return string|null
*/
public function get(string $key): string|null
{
return $this->redis->get($key);
}
/**
* Disconnect from the Redis server when the object is destroyed
*/
public function __destruct()
{
if (self::is_connected()) {
self::disconnect();
}
}
/**
* Disconnect from the Redis server
*/
public function disconnect(): void
{
$this->redis->disconnect();
}
}