Skip to content

Commit

Permalink
Merge pull request #17 from kodedphp/shmop
Browse files Browse the repository at this point in the history
  • Loading branch information
kodeart authored Dec 11, 2019
2 parents d6dac1c + 2cfe921 commit ba598e8
Show file tree
Hide file tree
Showing 19 changed files with 249 additions and 85 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@ cache:
php:
- 7.2
- 7.3
- 7.4
- nightly

matrix:
fast_finish: true
allow_failures:
- php: nightly

services:
- redis-server
Expand Down
9 changes: 6 additions & 3 deletions Client/CacheClientFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@

namespace Koded\Caching\Client;

use Error;
use Exception;
use Koded\Caching\{Cache, CacheException};
use Koded\Caching\Configuration\{FileConfiguration, MemcachedConfiguration, PredisConfiguration, RedisConfiguration};
use Koded\Caching\Configuration\{MemcachedConfiguration, PredisConfiguration, RedisConfiguration};
use Koded\Stdlib\Interfaces\{Configuration, ConfigurationFactory, Serializer};
use Koded\Stdlib\Serializer\SerializerFactory;
use Psr\Log\{LoggerInterface, NullLogger};
Expand Down Expand Up @@ -58,8 +59,10 @@ public function new(string $client = ''): Cache
/** @var PredisConfiguration $config */
return $this->createPredisClient($config);

case 'shmop':
return new ShmopClient((string)$config->get('dir'), $config->get('ttl'));

case 'file':
/** @var FileConfiguration $config */
return new FileClient($this->getLogger($config), (string)$config->get('dir'), $config->get('ttl'));

case 'memory':
Expand Down Expand Up @@ -134,7 +137,7 @@ private function newRedis(RedisConfiguration $conf): \Redis
catch (\RedisException $e) {
error_log('[Redis] ' . $e->getMessage());
throw CacheException::withConnectionErrorFor('Redis');
} catch (Exception $e) {
} catch (Exception | Error $e) {
throw CacheException::from($e);
}
}
Expand Down
3 changes: 1 addition & 2 deletions Client/ClientTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@

trait ClientTrait
{

/** @var int|null Global TTL for caching, used as default expiration time in cache clients */
private $ttl;

/** @var \Memcached | \Redis | \Predis\Client | \Koded\Caching\Client\FileClient | \Koded\Caching\Client\MemoryClient */
/** @var \Memcached | \Redis | \Predis\Client | \Koded\Caching\Client\FileClient | \Koded\Caching\Client\MemoryClient | \Koded\Caching\Client\ShmopClient */
private $client;

public function getTtl(): ?int
Expand Down
8 changes: 4 additions & 4 deletions Client/FileClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
use Koded\Caching\{Cache, CacheException};
use Psr\Log\LoggerInterface;
use function Koded\Caching\verify_key;
use function Koded\Stdlib\{now, rmdir};
use function Koded\Stdlib\rmdir;

/**
* @property FileClient client
Expand Down Expand Up @@ -89,16 +89,16 @@ public function clear()
public function has($key, &$filename = '', &$cache = null)
{
verify_key($key);

$filename = $this->filename($key, false);

if (false === is_file($filename)) {
return false;
}

/** @noinspection PhpIncludeInspection */
$cache = include $filename;

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

return false;
Expand Down Expand Up @@ -142,7 +142,7 @@ private function filename(string $key, bool $create): string
*
* @throws CacheException
*/
private function setDirectory(string $directory)
private function setDirectory(string $directory): void
{
// Overrule shell misconfiguration or the web server
umask(umask() | 0002);
Expand Down
164 changes: 164 additions & 0 deletions Client/ShmopClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
<?php

/*
* This file is part of the Koded package.
*
* (c) Mihail Binev <[email protected]>
*
* Please view the LICENSE distributed with this source code
* for the full copyright and license information.
*
*/

namespace Koded\Caching\Client;

use Exception;
use Koded\Caching\{Cache, CacheException};
use function Koded\Caching\verify_key;

/**
* @property ShmopClient client
*
*/
final class ShmopClient implements Cache
{
use ClientTrait, MultiplesTrait;

/** @var string */
private $dir;

public function __construct(string $dir, ?int $ttl)
{
$this->dir = $dir;
$this->ttl = $ttl;
$this->setDirectory($dir);
}


public function get($key, $default = null)
{
if (false === $this->has($key, $filename)) {
return $default;
}

try {
$resource = shmop_open(fileinode($filename), 'a', 0, 0);
return unserialize(shmop_read($resource, 0, shmop_size($resource)));
} finally {
shmop_close($resource);
}
}


public function set($key, $value, $ttl = null)
{
verify_key($key);

if (1 > $expiration = $this->timestampWithGlobalTtl($ttl, Cache::DATE_FAR_FAR_AWAY)) {
// The item is considered expired and must be deleted
return $this->delete($key);
}

$value = serialize($value);
$size = strlen($value);
$filename = $this->filename($key, true);

try {
$resource = shmop_open(fileinode($filename), 'n', 0666, $size);
} catch (Exception $e) {
$resource = shmop_open(fileinode($filename), 'w', 0666, $size);
}

try {
return shmop_write($resource, $value, 0) === $size
&& false !== file_put_contents($filename . '-ttl', $expiration);

} finally {
shmop_close($resource);
}
}


public function delete($key)
{
if (false === $this->has($key, $filename)) {
return true;
}

return $this->expire($filename);
}


public function clear()
{
foreach ((glob($this->dir . 'shmop-*.cache*') ?: []) as $filename) {
$this->expire($filename);
}
return true;
}


public function has($key, &$filename = '')
{
verify_key($key);
$filename = $this->filename($key, false);
$expiration = (int)(@file_get_contents($filename . '-ttl') ?: 0);

if ($expiration <= time()) {
$this->expire($filename);
return false;
}

return true;
}


private function filename(string $key, bool $create): string
{
$filename = $this->dir . 'shmop-' . sha1($key) . '.cache';

if ($create) {
touch($filename);
touch($filename . '-ttl');
chmod($filename, 0666);
chmod($filename . '-ttl', 0666);
}

return $filename;
}

/**
* Prepares the cache directory.
*
* @param string $directory
*
* @throws CacheException
*/
private function setDirectory(string $directory): void
{
// Overrule shell misconfiguration or the web server
umask(umask() | 0002);
$dir = $directory ?: sys_get_temp_dir();
$dir = rtrim($dir, '/') . '/';

if (false === is_dir($dir) && false === mkdir($dir, 0775, true)) {
throw CacheException::forCreatingDirectory($dir);
}

$this->dir = $dir;
}

private function expire(string $filename): bool
{
if (false === $resource = @shmop_open(fileinode($filename), 'w', 0, 0)) {
return false;
}

try {
unlink($filename . '-ttl');
return shmop_delete($resource);
} finally {
shmop_close($resource);
}
}
}
20 changes: 0 additions & 20 deletions Configuration/FileConfiguration.php

This file was deleted.

15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,18 @@ There are many configuration options for this package.
Please refer to [Predis configuration page][6].


### Shared Memory (shmop)

Requires a [PHP shmop extension][11].

```php
$cache = simple_cache_factory('shmop', [
'dir' => '/path/to/app/cache', // optional
'ttl' => null, // global TTL
]);
```


### FileConfiguration

This is the slowest cache client, please avoid it for production environments.
Expand Down Expand Up @@ -281,4 +293,5 @@ The code is distributed under the terms of [The 3-Clause BSD license](LICENSE).
[7]: https://github.com/phpredis/phpredis#connect-open
[8]: http://php.net/sys_get_temp_dir
[9]: http://php.net/json_encode
[10]: https://www.php-fig.org/psr/psr-16/
[10]: https://www.php-fig.org/psr/psr-16/
[11]: https://www.php.net/manual/en/book.shmop.php
4 changes: 0 additions & 4 deletions Tests/Integration/FileClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ public function createSimpleCache()

protected function setUp(): void
{
$this->skippedTests = [
'testSetMultipleInvalidKeys' => '',
];

$this->dir = vfsStream::setup();
parent::setUp();
}
Expand Down
15 changes: 0 additions & 15 deletions Tests/Integration/MemcachedClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,6 @@ protected function setUp(): void

$this->skippedTests = [
'testBasicUsageWithLongKey' => 'Memcached max key length is 250 chars',

'testSet' => '',
'testSetTtl' => '',
'testSetExpiredTtl' => '',
'testGet' => '',
'testDelete' => '',
'testClear' => '',

'testSetMultiple' => '',
'testSetMultipleWithIntegerArrayKey' => '',
'testSetMultipleTtl' => '',
'testSetMultipleExpiredTtl' => '',
'testSetMultipleWithGenerator' => '',
'testGetMultiple' => '',
'testSetMultipleInvalidKeys' => '',
];
}
}
4 changes: 0 additions & 4 deletions Tests/Integration/MemoryClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ class MemoryClientTest extends SimpleCacheTest
*/
public function createSimpleCache()
{
$this->skippedTests = [
'testSetMultipleInvalidKeys' => '',
];

return simple_cache_factory('memory');
}
}
4 changes: 0 additions & 4 deletions Tests/Integration/PredisClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ class PredisClientTest extends SimpleCacheTest
*/
public function createSimpleCache()
{
$this->skippedTests = [
'testSetMultipleInvalidKeys' => '',
];

return simple_cache_factory('predis', [
'host' => getenv('REDIS_SERVER_HOST'),
]);
Expand Down
4 changes: 0 additions & 4 deletions Tests/Integration/PredisJsonClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ class PredisJsonClientTest extends SimpleCacheTest
*/
public function createSimpleCache()
{
$this->skippedTests = [
'testSetMultipleInvalidKeys' => '',
];

return simple_cache_factory('predis', [
'host' => getenv('REDIS_SERVER_HOST'),
'port' => getenv('REDIS_SERVER_PORT'),
Expand Down
4 changes: 0 additions & 4 deletions Tests/Integration/RedisClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ class RedisClientTest extends SimpleCacheTest
*/
public function createSimpleCache()
{
$this->skippedTests = [
'testSetMultipleInvalidKeys' => '',
];

return simple_cache_factory('redis', [
'host' => getenv('REDIS_SERVER_HOST'),
]);
Expand Down
4 changes: 0 additions & 4 deletions Tests/Integration/RedisJsonClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ class RedisJsonClientTest extends SimpleCacheTest
*/
public function createSimpleCache()
{
$this->skippedTests = [
'testSetMultipleInvalidKeys' => '',
];

return simple_cache_factory('redis', [
'host' => getenv('REDIS_SERVER_HOST'),
'serializer' => Serializer::JSON,
Expand Down
Loading

0 comments on commit ba598e8

Please sign in to comment.