????
| Current Path : /home/arabianr/www/wp-content/plugins/simplybook/app/Services/Entities/ |
| Current File : /home/arabianr/www/wp-content/plugins/simplybook/app/Services/Entities/AbstractEntityService.php |
<?php
namespace SimplyBook\Services\Entities;
use Carbon\Carbon;
use SimplyBook\Http\ApiClient;
use SimplyBook\Support\Helpers\Storage;
use SimplyBook\Support\Utility\StringUtility;
/**
* Base class for services that fetch, cache, and persist entity data from
* the SimplyBook API. Child classes must implement the {@see fetch()} method
* to define how data is retrieved from the API.
*
* Namespace collision with Http\Entities is on purpose to indicate similarities
* between entity data services and API endpoint services. When the remote API
* supports CRUD actions for the child-classes of this abstract class then
* these classes (and their logic) should be moved to Http\Entities.
*/
abstract class AbstractEntityService
{
/**
* Prefix for cache identifier. Used for option names and wp_cache keys,
* suffixed with the identifier in {@see getCacheName}.
*
* Property can be overridden by child implementations, but is advised not
* to be changed. Some older classes like {@see SubscriptionDataService}
* override it for backward compatibility.
*/
protected string $cachePrefix = 'simplybook_cached';
/**
* Identifier is used for cache keys, option names and consistency
* across all fetch data services. Example: "company_info"
*/
protected string $identifier;
/**
* Indicates if the data fetched by this service should be persisted
* in the database using update_option/get_option. When false, the data
* is only cached in memory for the duration of the request.
*/
protected bool $persistent = true;
protected ApiClient $client;
final public function __construct(ApiClient $client)
{
$this->client = $client;
if (empty($this->identifier)) {
throw new \RuntimeException('Service identifier must be defined.');
}
$this->identifier = StringUtility::toSnakeCase($this->identifier);
}
/**
* Method is used for implementing the API request logic. Child
* implementation calls the correct ApiClient method and returns the raw
* response as an array without mutating it. Mutation belongs in
* {@see processData()}.
*/
abstract public function fetch(): array;
/**
* Method is used for fetching fresh data from the API and persisting it,
* so other parts of the plugin can work with a consistent and cached shape.
*/
final public function restore(): array
{
$data = $this->fetch();
return $this->save($data);
}
/**
* Method is used for storing a normalized version of the data and adding
* a timestamp that allows strict expiration checks. Child classes can
* implement {@see processData()} to mutate the data before it is persisted.
* Afterward "updated_at_utc" is always added or updated, which is used for
* expiration checks in {@see self::all()}.
*/
final public function save(array $data): array
{
$data = $this->processData($data);
$data['updated_at_utc'] = Carbon::now('UTC')->toDateTimeString();
$this->persistData($data);
$this->dispatchDataLoaded($data);
return $data;
}
/**
* Method is used for retrieving a specific key from the cached data using
* Storage so dot notation can be used by consumers.
*
* A colon can be used instead of a dot to avoid conflicts in route params.
*
* @example /wp-json/simplybook/v1/subscription_data/limits:booking-website
*
* @param mixed $default Default value when the key is not found.
* @return mixed
*/
final public function search(string $key, $default = null)
{
$storage = new Storage($this->all());
$key = str_replace(':', '.', $key);
return $storage->get($key, $default);
}
/**
* Method is used for retrieving data from persistent storage while always
* using wp_cache to prevent repeated option reads within a request.
*
* When $hasExpiration is true, the timestamp is used to verify the data is
* still fresh based on {@see getDataTimeThreshold()}. When the data is
* stale, an empty array is returned.
*/
final public function all(bool $hasExpiration = false): array
{
$cacheName = $this->getWpCacheName($hasExpiration);
$cacheValue = wp_cache_get($cacheName, $this->getWpCacheGroup(), false, $found);
if ($found && is_array($cacheValue)) {
return $cacheValue;
}
$data = $this->readPersistedData();
if (empty($data) || empty($data['updated_at_utc'])) {
return [];
}
if ($hasExpiration === true) {
$updatedAt = Carbon::parse($data['updated_at_utc']);
if ($updatedAt->diffInSeconds(Carbon::now('UTC')) > $this->getDataTimeThreshold()) {
return [];
}
}
$this->dispatchDataLoaded($data);
wp_cache_set(
$cacheName,
$data,
$this->getWpCacheGroup(),
$this->getDataTimeThreshold()
);
return $data;
}
/**
* Method can be used by child implementations to normalize or transform
* the raw data fetched from the API before it is persisted. By default,
* it returns the data as-is.
*/
protected function processData(array $data): array
{
return $data;
}
/**
* Method is used for configuring how long data is considered fresh, which
* is used for strict expiration checks and for wp_cache TTL. By default,
* it is set to 5 minutes, but child implementations can override it.
*/
protected function getDataTimeThreshold(): int
{
return 5 * MINUTE_IN_SECONDS;
}
/**
* Child classes can use this method to dispatch an event or perform
* additional logic when data is saved via {@see save} or from
* persistent storage via {@see all}.
*/
protected function dispatchDataLoaded(array $data): void
{
}
/**
* Method is used for configuring the wp_cache group used for caching
* the data in memory. By default, it is set to "simplybook", but child
* implementations can override it.
*/
protected function getWpCacheGroup(): string
{
return 'simplybook';
}
/**
* Returns the wp_cache key name based on the identifier and expiration
* requirement. Not overridable by child classes.
*/
private function getWpCacheName(bool $hasExpiration): string
{
return $this->getCacheName() . '_' . ($hasExpiration ? 'with-expiration' : 'no-expiration');
}
/**
* Persist the data to the database using update_option. When persistence
* is disabled, this method does nothing. Not overridable by child classes.
*/
private function persistData(array $data): void
{
if ($this->persistent === false) {
return;
}
update_option($this->getCacheName(), $data, false);
}
/**
* Read the persisted data from the database using get_option. When
* persistence is disabled, this method returns an empty array. Not
* overridable by child classes.
*/
private function readPersistedData(): array
{
if ($this->persistent === false) {
return [];
}
$data = get_option($this->getCacheName(), []);
return is_array($data) ? $data : [];
}
/**
* Get the cache name used for option names and wp_cache keys. Not
* overridable by child classes.
*/
private function getCacheName(): string
{
return $this->cachePrefix . '_' . $this->identifier;
}
}