Skip to content

Commit b29fd7c

Browse files
committed
Stabilized version
1 parent 367ce19 commit b29fd7c

27 files changed

Lines changed: 495 additions & 446 deletions

.gitattributes

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
*.php diff=php
22

3-
/build export-ignore
4-
/Tests export-ignore
5-
/phpunit.* export-ignore
6-
/*.yml export-ignore
3+
/build export-ignore
4+
/Tests export-ignore
5+
/phpunit.* export-ignore
6+
/composer.* export-ignore
7+
/*.yml export-ignore

.gitignore

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
11
build
22
vendor
3-
.DS_Store
43
.idea
5-
composer.lock
6-
phpunit.phar
7-
*.yml
8-
.env
9-
!.travis.yml
4+
composer.lock

CacheItem.php

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,79 +7,81 @@
77

88
abstract class CacheItem implements CacheItemInterface
99
{
10+
/** @var string */
11+
protected $key;
12+
1013
/** @var mixed */
1114
protected $value;
1215

13-
/** @var Cache */
14-
protected $client;
16+
/** @var bool */
17+
protected $isHit = false;
1518

16-
/** @var string */
17-
private $key;
19+
/** @var int Number of seconds for the expiration time */
20+
protected $expiresAt;
1821

19-
/** @var int Unix timestamp for expiration time */
20-
private $expiresAt;
2122

22-
public function __construct(Cache $client, string $key)
23+
public function __construct($key, ?int $ttl)
2324
{
25+
verify_key($key);
2426
$this->key = $key;
25-
$this->client = $client;
27+
$this->expiresAt = $ttl;
2628
}
2729

28-
public function __destruct()
29-
{
30-
unset($this->client);
31-
}
3230

3331
public function getKey(): string
3432
{
3533
return $this->key;
3634
}
3735

36+
3837
public function get()
3938
{
40-
return $this->isHit() ? $this->value : null;
39+
return $this->value;
4140
}
4241

42+
4343
public function isHit(): bool
4444
{
45-
return $this->client->has($this->key);
45+
return $this->isHit;
4646
}
4747

48+
4849
public function set($value)
4950
{
5051
$this->value = $value;
5152

5253
return $this;
5354
}
5455

55-
public function expiresAt($expiration)
56-
{
57-
$this->expiresAt = $expiration;
5856

59-
return $this;
57+
public function expiresAfter($time)
58+
{
59+
// The TTL is calculated in the cache client instance
60+
return $this->expiresAt($time);
6061
}
6162

62-
public function expiresAfter($time)
63+
64+
public function expiresAt($expiration)
6365
{
64-
if (null === $time && null !== $global = $this->client->getTtl()) {
65-
$this->expiresAt = time() + cache_ttl($global);
66+
$this->expiresAt = normalize_ttl($expiration ?? $this->expiresAt);
6667

67-
return $this;
68+
if ($this->expiresAt < 1) {
69+
$this->isHit = false;
6870
}
6971

70-
$seconds = cache_ttl($time);
71-
$this->expiresAt = $seconds ? time() + $seconds : null;
72-
7372
return $this;
7473
}
7574

7675
/**
77-
* Returns expiration time for the cache item.
76+
* Returns expiration seconds for the cache item.
77+
* NULL is reserved for clients who do not support expiry
78+
* to implement some custom logic around the TTL.
79+
*
7880
* This method is not part of the PSR-6.
7981
*
80-
* @return int|null|\DateInterval|\DateTimeInterface
82+
* @return int|null
8183
*/
82-
public function ttl()
84+
public function getExpiresAt(): ?int
8385
{
8486
return $this->expiresAt;
8587
}

CacheItemPool.php

Lines changed: 71 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,119 +5,140 @@
55
use Exception;
66
use Koded\Caching\Client\CacheClientFactory;
77
use Psr\Cache\{CacheItemInterface, CacheItemPoolInterface};
8-
use Psr\SimpleCache\CacheInterface;
8+
use function Koded\Stdlib\now;
99

1010

1111
abstract class CacheItemPool implements CacheItemPoolInterface
1212
{
13-
/** @var CacheInterface */
13+
/** @var Cache */
1414
protected $client;
1515

1616
/** @var CacheItemInterface[] */
17-
private $deferred = [];
17+
protected $deferred = [];
18+
1819

1920
abstract public function __construct(CacheClientFactory $factory, string $client);
2021

22+
// @codeCoverageIgnoreStart
2123
public function __destruct()
2224
{
23-
unset($this->client);
25+
$this->commit();
26+
}
27+
// @codeCoverageIgnoreEnd
28+
29+
public function commit(): bool
30+
{
31+
foreach ($this->deferred as $key => $item) {
32+
if (true === $this->save($item)) {
33+
unset($this->deferred[$key]);
34+
}
35+
}
36+
37+
return empty($this->deferred);
38+
}
39+
40+
41+
public function save(CacheItemInterface $item): bool
42+
{
43+
/** @var CacheItem $item */
44+
return $this->client->set($item->getKey(), $item->get(), $item->getExpiresAt());
2445
}
2546

47+
2648
public function getItems(array $keys = []): array
2749
{
28-
$collection = [];
50+
$items = [];
2951
foreach ($keys as $key) {
30-
$collection[$key] = $this->getItem($key);
52+
$items[$key] = $this->getItem($key);
3153
}
3254

33-
return $collection;
55+
return $items;
3456
}
3557

58+
3659
public function getItem($key): CacheItemInterface
3760
{
38-
if (isset($this->deferred[$key])) {
39-
return $this->deferred[$key];
40-
}
41-
4261
try {
43-
return (new class($this->client, $key) extends CacheItem
44-
{
45-
})->set($this->client->get($key));
62+
$item = new class($key, $this->client->getTtl()) extends CacheItem {};
63+
64+
if (false === $this->client->has($key)) {
65+
if (isset($this->deferred[$key])) {
66+
return clone $this->deferred[$key];
67+
}
68+
return $item;
69+
}
70+
71+
(function() {
72+
$this->isHit = true;
73+
74+
return $this;
75+
})->call($item);
76+
77+
return $item->set($this->client->get($key));
4678

4779
} catch (Exception $e) {
48-
throw ExtendedCacheException::from($e);
80+
throw CachePoolException::from($e);
4981
}
5082
}
5183

84+
5285
public function hasItem($key): bool
5386
{
5487
try {
55-
return $this->client->has($key);
88+
return isset($this->deferred[$key]) || $this->client->has($key);
5689
} catch (Exception $e) {
57-
throw ExtendedCacheException::from($e);
90+
throw CachePoolException::from($e);
5891
}
5992
}
6093

94+
6195
public function clear(): bool
6296
{
63-
if ($this->client->clear()) {
97+
if ($cleared = $this->client->clear()) {
6498
$this->deferred = [];
65-
return true;
6699
}
67100

68-
return false;
101+
return $cleared;
69102
}
70103

104+
71105
public function deleteItems(array $keys): bool
72106
{
73-
$deleted = 0;
74-
foreach ($keys as $key) {
75-
$this->deleteItem($key) && ++$deleted;
107+
try {
108+
return $this->client->deleteMultiple($keys);
109+
} catch (Exception $e) {
110+
throw CachePoolException::from($e);
76111
}
77-
78-
return count($keys) === $deleted;
79112
}
80113

114+
81115
public function deleteItem($key): bool
82116
{
83117
try {
84-
if ($this->client->delete($key)) {
118+
if ($deleted = $this->client->delete($key)) {
85119
unset($this->deferred[$key]);
86-
return true;
87120
}
88121

89-
return false;
122+
return $deleted;
90123

91124
} catch (Exception $e) {
92-
throw ExtendedCacheException::from($e);
125+
throw CachePoolException::from($e);
93126
}
94127
}
95128

96-
public function saveDeferred(CacheItemInterface $item): bool
97-
{
98-
$this->deferred[$item->getKey()] = $item;
99129

100-
return true;
101-
}
102-
103-
public function commit(): bool
130+
public function saveDeferred(CacheItemInterface $item): bool
104131
{
105-
foreach ($this->deferred as $key => $item) {
106-
if (true === $this->save($item)) {
107-
unset($this->deferred[$key]);
108-
}
132+
/** @var CacheItem $item */
133+
if (null !== $item->getExpiresAt() && $item->getExpiresAt() <= now()->getTimestamp()) {
134+
return false;
109135
}
110136

111-
return empty($this->deferred);
112-
}
137+
$this->deferred[$item->getKey()] = (function() {
138+
$this->isHit = true;
139+
return $this;
140+
})->call($item);
113141

114-
public function save(CacheItemInterface $item): bool
115-
{
116-
/** @var CacheItem $item */
117-
$value = (function() {
118-
return $this->value;
119-
})->bindTo($item, $item);
120-
121-
return $this->client->set($item->getKey(), $value(), cache_ttl($item->ttl()));
142+
return true;
122143
}
123144
}

CachePool.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
namespace Koded\Caching;
4+
5+
use Exception;
6+
use Koded\Caching\Client\CacheClientFactory;
7+
use Koded\Caching\Configuration\ConfigFactory;
8+
use Psr\Cache\CacheItemPoolInterface;
9+
use Psr\Cache\InvalidArgumentException;
10+
11+
/**
12+
* CachePool creates instances of CacheItemPoolInterface classes.
13+
* The instances are created only once.
14+
*
15+
* Production ready:
16+
*
17+
* - Redis, with redis extension
18+
* - Redis, with Predis library
19+
* - Memcached
20+
*
21+
* Additional for development:
22+
* - File
23+
* - Memory
24+
*
25+
*/
26+
final class CachePool
27+
{
28+
/**
29+
* @var CacheItemPoolInterface[] The registry
30+
*/
31+
private static $instances = [];
32+
33+
/**
34+
* @param string $client The name of the cache client
35+
* @param array $parameters [optional] Configuration parameters for the cache client
36+
*
37+
* @return CacheItemPoolInterface
38+
*/
39+
public static function use(string $client, array $parameters = []): CacheItemPoolInterface
40+
{
41+
$ident = md5($client . serialize($parameters));
42+
43+
if (isset(self::$instances[$ident])) {
44+
return self::$instances[$ident];
45+
}
46+
47+
$factory = new CacheClientFactory(new ConfigFactory($parameters));
48+
49+
return self::$instances[$ident] = new class($factory, strtolower($client)) extends CacheItemPool
50+
{
51+
public function __construct(CacheClientFactory $factory, string $client)
52+
{
53+
$this->client = $factory->new($client);
54+
}
55+
};
56+
}
57+
}
58+
59+
/**
60+
* Implementation for \Psr\Cache\InvalidArgumentException
61+
*
62+
*/
63+
class CachePoolException extends Exception implements InvalidArgumentException
64+
{
65+
public static function from(Exception $e)
66+
{
67+
return new self($e->getMessage(), $e->getCode());
68+
}
69+
}

0 commit comments

Comments
 (0)