Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ phpcs.xml
.idea/**/.name
.idea/**/codeStyles
.idea/**/php.xml

## Tests
tests/assets/test-sqlite.db
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
"phpcompatibility/phpcompatibility-wp": "^2.1",
"overtrue/phplint": "^3.4"
},
"suggest": {
"ext-pdo": "PDO is required to use the SQLite cache provider"
},
"scripts": {
"suite": [
"composer test",
Expand Down
1 change: 1 addition & 0 deletions phpstan.dist.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ includes:
- vendor/szepeviktor/phpstan-wordpress/extension.neon

parameters:
editorUrl: 'phpstorm://open?file=%%file%%&line=%%line%%'
level: 5
paths:
- src
Expand Down
100 changes: 52 additions & 48 deletions src/Cache/ArrayCacheProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use DataKit\DataViews\Clock\Clock;
use DataKit\DataViews\Clock\SystemClock;
use DateInterval;
use Exception;

/**
* Cache provider backed by an array.
Expand All @@ -12,121 +14,123 @@
*
* @since $ver$
*/
final class ArrayCacheProvider implements CacheProvider {
final class ArrayCacheProvider extends BaseCacheProvider {
/**
* The cached items.
*
* @since $ver$
*
* @var array
* @var CacheItem[]
*/
private array $items;
private array $items = [];

/**
* The clock instance.
* Contains the reference to the tags with their tagged cache keys.
*
* @since $ver$
*
* @var Clock
* @var array<string, string[]>
*/
private Clock $clock;
private array $tags = [];

/**
* Creates an Array cache provider.
*
* @since $ver$
*
* @param Clock|null $clock The clock instance.
* @param array $items The pre-filled cache items.
*/
public function __construct( ?Clock $clock = null, array $items = [] ) {
public function __construct( ?Clock $clock = null ) {
parent::__construct( $clock );

$this->clock = $clock ?? new SystemClock();
$this->items = $items;
}

/**
* @inheritDoc
*
* @since $ver$
*/
public function set( string $key, $value, ?int $ttl = null ): void {
$time = $ttl
? ( $this->clock->now()->getTimestamp() + $ttl )
: null;
public function set( string $key, $value, ?int $ttl = null, array $tags = [] ): void {
try {
$time = (int) $ttl > 0
? ( $this->clock->now()->add( new DateInterval( 'PT' . $ttl . 'S' ) ) )
: null;
} catch ( Exception $e ) {
throw new \InvalidArgumentException( $e->getMessage(), $e->getCode(), $e );
}

$this->items[ $key ] = compact( 'value', 'time' );
$this->items[ $key ] = new CacheItem( $key, $value, $time, $tags );

$this->add_tags( $key, $tags );
}

/**
* @inheritDoc
*
* @since $ver$
*/
public function get( string $key, $fallback = null ) {
$item = $this->items[ $key ] ?? [];

if ( $this->is_expired( $item ) ) {
unset( $this->items[ $key ] );

return $fallback;
}
public function delete( string $key ): bool {
unset( $this->items[ $key ] );

return $item['value'] ?? $fallback;
return true;
}

/**
* @inheritDoc
*
* @since $ver$
*/
public function has( string $key ): bool {
if (
isset( $this->items[ $key ] )
&& ! $this->is_expired( $this->items[ $key ] )
) {
return true;
}
public function delete_by_tags( array $tags ): bool {
foreach ( $tags as $tag ) {
foreach ( $this->tags[ $tag ] ?? [] as $key ) {
$this->delete( $key );
}

unset( $this->items[ $key ] );
unset( $this->tags[ $tag ] );
}

return false;
return true;
}

/**
* @inheritDoc
*
* @since $ver$
*/
public function delete( string $key ): bool {
unset( $this->items[ $key ] );
public function clear(): bool {
$this->items = [];
$this->tags = [];

return true;
}

/**
* @inheritDoc
* Records a key for all provided tags.
*
* @since $ver$
*
* @param string $key The key to tag.
* @param array $tags The tags.
*/
public function clear(): bool {
$this->items = [];
private function add_tags( string $key, array $tags ): void {
foreach ( $tags as $tag ) {
if ( ! is_string( $tag ) ) {
throw new \InvalidArgumentException( 'A tag must be a string.' );
}

return true;
$this->tags[ $tag ] ??= [];

$this->tags[ $tag ] = array_unique( array_merge( $this->tags[ $tag ], [ $key ] ) );
}
}

/**
* Returns whether the provided cache item is expired.
* @inheritDoc
*
* @since $ver$
*
* @param array $item The cache item.
*
* @return bool Whether the cache is expired.
*/
private function is_expired( array $item ): bool {
return (
( $item['time'] ?? null )
&& $this->clock->now()->getTimestamp() > $item['time']
);
protected function doGet( string $key ): ?CacheItem {
return $this->items[ $key ] ?? null;
}
}
72 changes: 72 additions & 0 deletions src/Cache/BaseCacheProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace DataKit\DataViews\Cache;

use DataKit\DataViews\Clock\Clock;
use DataKit\DataViews\Clock\SystemClock;

/**
* An abstract cache provider that implements default logic.
*
* @since $ver$
*/
abstract class BaseCacheProvider implements CacheProvider {
/**
* The clock.
*
* @since $ver$
* @var Clock
*/
protected Clock $clock;

/**
* Creates the base cache provider.
*
* @since $ver$
*
* @param Clock|null $clock The clock.
*/
public function __construct( ?Clock $clock = null ) {
$this->clock = $clock ?? new SystemClock();
}

/**
* Returns the {@see CacheItem} if found by key.
*
* @param string $key The key.
*
* @return CacheItem|null The cache item.
*/
abstract protected function doGet( string $key ): ?CacheItem;

/**
* @inheritDoc
* @since $ver$
*/
public function get( string $key, $fallback = null ) {
$item = $this->doGet( $key );
if ( ! $item || $item->is_expired( $this->clock->now() ) ) {
$this->delete( $key );

return $fallback;
}

return $item->value();
}

/**
* @inheritDoc
* @since $ver$
*/
public function has( string $key ): bool {
$item = $this->doGet( $key );

if ( ! $item || $item->is_expired( $this->clock->now() ) ) {
$this->delete( $key );

return false;
}

return true;
}
}
Loading