Skip to content

Commit ba598e8

Browse files
authored
Merge pull request #17 from kodedphp/shmop
2 parents d6dac1c + 2cfe921 commit ba598e8

19 files changed

+249
-85
lines changed

.travis.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@ cache:
1212
php:
1313
- 7.2
1414
- 7.3
15+
- 7.4
16+
- nightly
1517

1618
matrix:
1719
fast_finish: true
20+
allow_failures:
21+
- php: nightly
1822

1923
services:
2024
- redis-server

Client/CacheClientFactory.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212

1313
namespace Koded\Caching\Client;
1414

15+
use Error;
1516
use Exception;
1617
use Koded\Caching\{Cache, CacheException};
17-
use Koded\Caching\Configuration\{FileConfiguration, MemcachedConfiguration, PredisConfiguration, RedisConfiguration};
18+
use Koded\Caching\Configuration\{MemcachedConfiguration, PredisConfiguration, RedisConfiguration};
1819
use Koded\Stdlib\Interfaces\{Configuration, ConfigurationFactory, Serializer};
1920
use Koded\Stdlib\Serializer\SerializerFactory;
2021
use Psr\Log\{LoggerInterface, NullLogger};
@@ -58,8 +59,10 @@ public function new(string $client = ''): Cache
5859
/** @var PredisConfiguration $config */
5960
return $this->createPredisClient($config);
6061

62+
case 'shmop':
63+
return new ShmopClient((string)$config->get('dir'), $config->get('ttl'));
64+
6165
case 'file':
62-
/** @var FileConfiguration $config */
6366
return new FileClient($this->getLogger($config), (string)$config->get('dir'), $config->get('ttl'));
6467

6568
case 'memory':
@@ -134,7 +137,7 @@ private function newRedis(RedisConfiguration $conf): \Redis
134137
catch (\RedisException $e) {
135138
error_log('[Redis] ' . $e->getMessage());
136139
throw CacheException::withConnectionErrorFor('Redis');
137-
} catch (Exception $e) {
140+
} catch (Exception | Error $e) {
138141
throw CacheException::from($e);
139142
}
140143
}

Client/ClientTrait.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,10 @@
1717

1818
trait ClientTrait
1919
{
20-
2120
/** @var int|null Global TTL for caching, used as default expiration time in cache clients */
2221
private $ttl;
2322

24-
/** @var \Memcached | \Redis | \Predis\Client | \Koded\Caching\Client\FileClient | \Koded\Caching\Client\MemoryClient */
23+
/** @var \Memcached | \Redis | \Predis\Client | \Koded\Caching\Client\FileClient | \Koded\Caching\Client\MemoryClient | \Koded\Caching\Client\ShmopClient */
2524
private $client;
2625

2726
public function getTtl(): ?int

Client/FileClient.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
use Koded\Caching\{Cache, CacheException};
1616
use Psr\Log\LoggerInterface;
1717
use function Koded\Caching\verify_key;
18-
use function Koded\Stdlib\{now, rmdir};
18+
use function Koded\Stdlib\rmdir;
1919

2020
/**
2121
* @property FileClient client
@@ -89,16 +89,16 @@ public function clear()
8989
public function has($key, &$filename = '', &$cache = null)
9090
{
9191
verify_key($key);
92-
9392
$filename = $this->filename($key, false);
93+
9494
if (false === is_file($filename)) {
9595
return false;
9696
}
9797

9898
/** @noinspection PhpIncludeInspection */
9999
$cache = include $filename;
100100

101-
if ($cache['timestamp'] <= now()->getTimestamp()) {
101+
if ($cache['timestamp'] <= time()) {
102102
unlink($filename);
103103

104104
return false;
@@ -142,7 +142,7 @@ private function filename(string $key, bool $create): string
142142
*
143143
* @throws CacheException
144144
*/
145-
private function setDirectory(string $directory)
145+
private function setDirectory(string $directory): void
146146
{
147147
// Overrule shell misconfiguration or the web server
148148
umask(umask() | 0002);

Client/ShmopClient.php

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Koded package.
5+
*
6+
* (c) Mihail Binev <[email protected]>
7+
*
8+
* Please view the LICENSE distributed with this source code
9+
* for the full copyright and license information.
10+
*
11+
*/
12+
13+
namespace Koded\Caching\Client;
14+
15+
use Exception;
16+
use Koded\Caching\{Cache, CacheException};
17+
use function Koded\Caching\verify_key;
18+
19+
/**
20+
* @property ShmopClient client
21+
*
22+
*/
23+
final class ShmopClient implements Cache
24+
{
25+
use ClientTrait, MultiplesTrait;
26+
27+
/** @var string */
28+
private $dir;
29+
30+
public function __construct(string $dir, ?int $ttl)
31+
{
32+
$this->dir = $dir;
33+
$this->ttl = $ttl;
34+
$this->setDirectory($dir);
35+
}
36+
37+
38+
public function get($key, $default = null)
39+
{
40+
if (false === $this->has($key, $filename)) {
41+
return $default;
42+
}
43+
44+
try {
45+
$resource = shmop_open(fileinode($filename), 'a', 0, 0);
46+
return unserialize(shmop_read($resource, 0, shmop_size($resource)));
47+
} finally {
48+
shmop_close($resource);
49+
}
50+
}
51+
52+
53+
public function set($key, $value, $ttl = null)
54+
{
55+
verify_key($key);
56+
57+
if (1 > $expiration = $this->timestampWithGlobalTtl($ttl, Cache::DATE_FAR_FAR_AWAY)) {
58+
// The item is considered expired and must be deleted
59+
return $this->delete($key);
60+
}
61+
62+
$value = serialize($value);
63+
$size = strlen($value);
64+
$filename = $this->filename($key, true);
65+
66+
try {
67+
$resource = shmop_open(fileinode($filename), 'n', 0666, $size);
68+
} catch (Exception $e) {
69+
$resource = shmop_open(fileinode($filename), 'w', 0666, $size);
70+
}
71+
72+
try {
73+
return shmop_write($resource, $value, 0) === $size
74+
&& false !== file_put_contents($filename . '-ttl', $expiration);
75+
76+
} finally {
77+
shmop_close($resource);
78+
}
79+
}
80+
81+
82+
public function delete($key)
83+
{
84+
if (false === $this->has($key, $filename)) {
85+
return true;
86+
}
87+
88+
return $this->expire($filename);
89+
}
90+
91+
92+
public function clear()
93+
{
94+
foreach ((glob($this->dir . 'shmop-*.cache*') ?: []) as $filename) {
95+
$this->expire($filename);
96+
}
97+
return true;
98+
}
99+
100+
101+
public function has($key, &$filename = '')
102+
{
103+
verify_key($key);
104+
$filename = $this->filename($key, false);
105+
$expiration = (int)(@file_get_contents($filename . '-ttl') ?: 0);
106+
107+
if ($expiration <= time()) {
108+
$this->expire($filename);
109+
return false;
110+
}
111+
112+
return true;
113+
}
114+
115+
116+
private function filename(string $key, bool $create): string
117+
{
118+
$filename = $this->dir . 'shmop-' . sha1($key) . '.cache';
119+
120+
if ($create) {
121+
touch($filename);
122+
touch($filename . '-ttl');
123+
chmod($filename, 0666);
124+
chmod($filename . '-ttl', 0666);
125+
}
126+
127+
return $filename;
128+
}
129+
130+
/**
131+
* Prepares the cache directory.
132+
*
133+
* @param string $directory
134+
*
135+
* @throws CacheException
136+
*/
137+
private function setDirectory(string $directory): void
138+
{
139+
// Overrule shell misconfiguration or the web server
140+
umask(umask() | 0002);
141+
$dir = $directory ?: sys_get_temp_dir();
142+
$dir = rtrim($dir, '/') . '/';
143+
144+
if (false === is_dir($dir) && false === mkdir($dir, 0775, true)) {
145+
throw CacheException::forCreatingDirectory($dir);
146+
}
147+
148+
$this->dir = $dir;
149+
}
150+
151+
private function expire(string $filename): bool
152+
{
153+
if (false === $resource = @shmop_open(fileinode($filename), 'w', 0, 0)) {
154+
return false;
155+
}
156+
157+
try {
158+
unlink($filename . '-ttl');
159+
return shmop_delete($resource);
160+
} finally {
161+
shmop_close($resource);
162+
}
163+
}
164+
}

Configuration/FileConfiguration.php

Lines changed: 0 additions & 20 deletions
This file was deleted.

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,18 @@ There are many configuration options for this package.
242242
Please refer to [Predis configuration page][6].
243243

244244

245+
### Shared Memory (shmop)
246+
247+
Requires a [PHP shmop extension][11].
248+
249+
```php
250+
$cache = simple_cache_factory('shmop', [
251+
'dir' => '/path/to/app/cache', // optional
252+
'ttl' => null, // global TTL
253+
]);
254+
```
255+
256+
245257
### FileConfiguration
246258

247259
This is the slowest cache client, please avoid it for production environments.
@@ -281,4 +293,5 @@ The code is distributed under the terms of [The 3-Clause BSD license](LICENSE).
281293
[7]: https://github.com/phpredis/phpredis#connect-open
282294
[8]: http://php.net/sys_get_temp_dir
283295
[9]: http://php.net/json_encode
284-
[10]: https://www.php-fig.org/psr/psr-16/
296+
[10]: https://www.php-fig.org/psr/psr-16/
297+
[11]: https://www.php.net/manual/en/book.shmop.php

Tests/Integration/FileClientTest.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,6 @@ public function createSimpleCache()
2727

2828
protected function setUp(): void
2929
{
30-
$this->skippedTests = [
31-
'testSetMultipleInvalidKeys' => '',
32-
];
33-
3430
$this->dir = vfsStream::setup();
3531
parent::setUp();
3632
}

Tests/Integration/MemcachedClientTest.php

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,6 @@ protected function setUp(): void
3535

3636
$this->skippedTests = [
3737
'testBasicUsageWithLongKey' => 'Memcached max key length is 250 chars',
38-
39-
'testSet' => '',
40-
'testSetTtl' => '',
41-
'testSetExpiredTtl' => '',
42-
'testGet' => '',
43-
'testDelete' => '',
44-
'testClear' => '',
45-
46-
'testSetMultiple' => '',
47-
'testSetMultipleWithIntegerArrayKey' => '',
48-
'testSetMultipleTtl' => '',
49-
'testSetMultipleExpiredTtl' => '',
50-
'testSetMultipleWithGenerator' => '',
51-
'testGetMultiple' => '',
52-
'testSetMultipleInvalidKeys' => '',
5338
];
5439
}
5540
}

Tests/Integration/MemoryClientTest.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@ class MemoryClientTest extends SimpleCacheTest
1515
*/
1616
public function createSimpleCache()
1717
{
18-
$this->skippedTests = [
19-
'testSetMultipleInvalidKeys' => '',
20-
];
21-
2218
return simple_cache_factory('memory');
2319
}
2420
}

0 commit comments

Comments
 (0)