├── .gitignore ├── .styleci.yml ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── Database.php ├── RedisServiceProvider.php ├── RedisStore.php ├── RedisTaggedCache.php ├── Repository.php └── RepositoryTrait.php └── tests ├── RedisStoreTest.php └── RepositoryTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.lock 3 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: laravel 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.5.9 5 | - 5.5 6 | - 5.6 7 | - 7.0 8 | - 7.1 9 | 10 | install: travis_retry composer install --no-interaction --prefer-source 11 | 12 | script: vendor/bin/phpunit 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Till Krüss 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PhpRedis driver for Laravel & Lumen 2 | 3 | [![Build Status](https://travis-ci.org/tillkruss/laravel-phpredis.svg?branch=master)](https://travis-ci.org/tillkruss/laravel-phpredis) 4 | [![Latest Stable Version](https://poser.pugx.org/tillkruss/laravel-phpredis/v/stable)](https://packagist.org/packages/tillkruss/laravel-phpredis) 5 | [![License](https://poser.pugx.org/tillkruss/laravel-phpredis/license)](https://packagist.org/packages/tillkruss/laravel-phpredis) 6 | 7 | This package provides a drop-in replacement for Laravel’s and Lumen’s `RedisServiceProvider`, that adds compatibility for PhpRedis, the PECL Redis Extension. 8 | 9 | Using PhpRedis instead of Predis with Laravel’s default `RedisServiceProvider` will result in false-positives across the framework, because PhpRedis returns `false` instead of `null` if a key does not exist. 10 | 11 | 12 | ## Requirements 13 | 14 | - PHP 5.5.9+ 15 | - Laravel 5.1+ 16 | - Lumen 5.1+ 17 | - PhpRedis 2.2.8+ 18 | 19 | 20 | ## Laravel Installation 21 | 22 | First, install this package via Composer: 23 | 24 | ``` 25 | composer require tillkruss/laravel-phpredis 26 | ``` 27 | 28 | Then open your `app` configuration file and remove (or comment-out) the default Redis service provider from your `providers` list: 29 | 30 | ```php 31 | // Illuminate\Redis\RedisServiceProvider::class, 32 | ``` 33 | 34 | Next, register the new service provider by adding it to the end of your `providers` list: 35 | 36 | ```php 37 | TillKruss\LaravelPhpRedis\RedisServiceProvider::class, 38 | ``` 39 | 40 | Finally, make sure you already renamed or removed the alias for Redis in your `aliases` list. 41 | 42 | 43 | ## Lumen Installation 44 | 45 | First, install this package via Composer: 46 | 47 | ``` 48 | composer require tillkruss/laravel-phpredis 49 | ``` 50 | 51 | If you haven’t already, install `illuminate/redis` as well: 52 | 53 | ``` 54 | composer require illuminate/redis 55 | ``` 56 | 57 | Next, register the Redis service provider in your `bootstrap/app.php` file. 58 | 59 | ```php 60 | $app->register(TillKruss\LaravelPhpRedis\RedisServiceProvider::class); 61 | ``` 62 | 63 | Finally, if you have not called `$app->withEloquent()` in your `bootstrap/app.php` file, then you need to call `$app->configure('database');` to ensure the Redis database configuration is properly loaded. 64 | 65 | 66 | ## License 67 | 68 | This package is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT). 69 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tillkruss/laravel-phpredis", 3 | "description": "A Redis driver for Laravel and Lumen that works with PhpRedis, the PECL Redis Extension.", 4 | "keywords": ["laravel", "redis", "phpredis"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Till Krüss", 9 | "homepage": "https://till.im" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.5.9" 14 | }, 15 | "require-dev": { 16 | "phpunit/phpunit": "~4.0", 17 | "mockery/mockery": "^0.9.4", 18 | "illuminate/cache": "~5.2", 19 | "illuminate/redis": "~5.2" 20 | }, 21 | "autoload": { 22 | "psr-4": { 23 | "TillKruss\\LaravelPhpRedis\\": "src/" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Database.php: -------------------------------------------------------------------------------- 1 | $server) { 24 | $client = new Redis(); 25 | 26 | $timeout = empty($server['timeout']) ? 0 : $server['timeout']; 27 | 28 | if (isset($server['persistent']) && $server['persistent']) { 29 | $client->pconnect($server['host'], $server['port'], $timeout); 30 | } else { 31 | $client->connect($server['host'], $server['port'], $timeout); 32 | } 33 | 34 | if (! empty($server['prefix'])) { 35 | $client->setOption(Redis::OPT_PREFIX, $server['prefix']); 36 | } 37 | 38 | if (! empty($server['password'])) { 39 | $client->auth($server['password']); 40 | } 41 | 42 | if (! empty($server['database'])) { 43 | $client->select($server['database']); 44 | } 45 | 46 | $clients[$key] = $client; 47 | } 48 | 49 | return $clients; 50 | } 51 | 52 | /** 53 | * Create a new aggregate client supporting sharding. 54 | * 55 | * @param array $servers 56 | * @param array $options 57 | * @return array 58 | */ 59 | protected function createAggregateClient(array $servers, array $options = []) 60 | { 61 | $servers = array_map([$this, 'buildClusterSeed'], $servers); 62 | 63 | $timeout = empty($options['timeout']) ? 0 : $options['timeout']; 64 | $persistent = isset($options['persistent']) && $options['persistent']; 65 | 66 | return ['default' => new RedisCluster( 67 | null, array_values($servers), $timeout, null, $persistent 68 | )]; 69 | } 70 | 71 | /** 72 | * Build a cluster seed string. 73 | * 74 | * @param array $server 75 | * @return string 76 | */ 77 | protected function buildClusterSeed($server) 78 | { 79 | $parameters = []; 80 | 81 | foreach (['database', 'timeout', 'prefix'] as $parameter) { 82 | if (! empty($server[$parameter])) { 83 | $parameters[$parameter] = $server[$parameter]; 84 | } 85 | } 86 | 87 | if (! empty($server['password'])) { 88 | $parameters['auth'] = $server['password']; 89 | } 90 | 91 | return $server['host'].':'.$server['port'].'?'.http_build_query($parameters); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/RedisServiceProvider.php: -------------------------------------------------------------------------------- 1 | app['config']['cache.prefix']), 27 | Arr::get($config, 'connection', 'default') 28 | ); 29 | 30 | $repository = new Repository($store); 31 | 32 | if ($app->bound('Illuminate\Contracts\Events\Dispatcher')) { 33 | $repository->setEventDispatcher( 34 | $app['Illuminate\Contracts\Events\Dispatcher'] 35 | ); 36 | } 37 | 38 | return $repository; 39 | }); 40 | } 41 | 42 | /** 43 | * Register custom bindings. 44 | * 45 | * @return void 46 | */ 47 | public function register() 48 | { 49 | $this->app->singleton('redis', function ($app) { 50 | return new Database($app['config']['database.redis']); 51 | }); 52 | } 53 | 54 | /** 55 | * Get the services provided by the provider. 56 | * 57 | * @return array 58 | */ 59 | public function provides() 60 | { 61 | return ['redis']; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/RedisStore.php: -------------------------------------------------------------------------------- 1 | connection()->get($this->prefix.$key); 19 | 20 | if (! is_null($value) && $value !== false) { 21 | return is_numeric($value) ? $value : unserialize($value); 22 | } 23 | } 24 | 25 | /** 26 | * Begin executing a new tags operation. 27 | * 28 | * @param array|mixed $names 29 | * @return \TillKruss\LaravelPhpRedis\RedisTaggedCache 30 | */ 31 | public function tags($names) 32 | { 33 | return new RedisTaggedCache($this, new TagSet($this, is_array($names) ? $names : func_get_args())); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/RedisTaggedCache.php: -------------------------------------------------------------------------------- 1 | get($key); 18 | 19 | return ! is_null($value) && $value !== false; 20 | } 21 | 22 | /** 23 | * Retrieve an item from the cache by key. 24 | * 25 | * @param string $key 26 | * @param mixed $default 27 | * @return mixed 28 | */ 29 | public function get($key, $default = null) 30 | { 31 | if (is_array($key)) { 32 | return $this->many($key); 33 | } 34 | 35 | $value = $this->store->get($this->itemKey($key)); 36 | 37 | if (is_null($value) || $value === false) { 38 | $this->fireCacheEvent('missed', [$key]); 39 | 40 | $value = value($default); 41 | } else { 42 | $this->fireCacheEvent('hit', [$key, $value]); 43 | } 44 | 45 | return $value; 46 | } 47 | 48 | /** 49 | * Retrieve multiple items from the cache by key. 50 | * 51 | * Items not found in the cache will have a null value. 52 | * 53 | * @param array $keys 54 | * @return array 55 | */ 56 | public function many(array $keys) 57 | { 58 | $normalizedKeys = []; 59 | 60 | foreach ($keys as $key => $value) { 61 | $normalizedKeys[] = is_string($key) ? $key : $value; 62 | } 63 | 64 | $values = $this->store->many($normalizedKeys); 65 | 66 | foreach ($values as $key => &$value) { 67 | if (is_null($value) || $value === false) { 68 | $this->fireCacheEvent('missed', [$key]); 69 | 70 | $value = isset($keys[$key]) ? value($keys[$key]) : null; 71 | } else { 72 | $this->fireCacheEvent('hit', [$key, $value]); 73 | } 74 | } 75 | 76 | return $values; 77 | } 78 | 79 | /** 80 | * Store an item in the cache if the key does not exist. 81 | * 82 | * @param string $key 83 | * @param mixed $value 84 | * @param \DateTime|int $minutes 85 | * @return bool 86 | */ 87 | public function add($key, $value, $minutes) 88 | { 89 | $minutes = $this->getMinutes($minutes); 90 | 91 | if (is_null($minutes)) { 92 | return false; 93 | } 94 | 95 | if (method_exists($this->store, 'add')) { 96 | return $this->store->add($this->itemKey($key), $value, $minutes); 97 | } 98 | 99 | $result = $this->get($key); 100 | 101 | if (is_null($result) || $result === false) { 102 | $this->put($key, $value, $minutes); 103 | 104 | return true; 105 | } 106 | 107 | return false; 108 | } 109 | 110 | /** 111 | * Get an item from the cache, or store the default value. 112 | * 113 | * @param string $key 114 | * @param \DateTime|int $minutes 115 | * @param \Closure $callback 116 | * @return mixed 117 | */ 118 | public function remember($key, $minutes, Closure $callback) 119 | { 120 | $value = $this->get($key); 121 | 122 | // If the item exists in the cache we will just return this immediately 123 | // otherwise we will execute the given Closure and cache the result 124 | // of that execution for the given number of minutes in storage. 125 | if (! is_null($value) && $value !== false) { 126 | return $value; 127 | } 128 | 129 | $this->put($key, $value = $callback(), $minutes); 130 | 131 | return $value; 132 | } 133 | 134 | /** 135 | * Get an item from the cache, or store the default value forever. 136 | * 137 | * @param string $key 138 | * @param \Closure $callback 139 | * @return mixed 140 | */ 141 | public function rememberForever($key, Closure $callback) 142 | { 143 | $value = $this->get($key); 144 | 145 | // If the item exists in the cache we will just return this immediately 146 | // otherwise we will execute the given Closure and cache the result 147 | // of that execution for the given number of minutes. It's easy. 148 | if (! is_null($value) && $value !== false) { 149 | return $value; 150 | } 151 | 152 | $this->forever($key, $value = $callback()); 153 | 154 | return $value; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /tests/RedisStoreTest.php: -------------------------------------------------------------------------------- 1 | redisStore(); 8 | $redis->getRedis()->shouldReceive('connection')->once()->with('default')->andReturn($redis->getRedis()); 9 | $redis->getRedis()->shouldReceive('get')->once()->with('foo')->andReturn(null); 10 | $this->assertNull($redis->get('foo')); 11 | } 12 | 13 | public function testGetReturnsNullWhenResultIsFalse() 14 | { 15 | $redis = $this->redisStore(); 16 | $redis->getRedis()->shouldReceive('connection')->once()->with('default')->andReturn($redis->getRedis()); 17 | $redis->getRedis()->shouldReceive('get')->once()->with('foo')->andReturn(false); 18 | $this->assertNull($redis->get('foo')); 19 | } 20 | 21 | protected function redisStore() 22 | { 23 | return new TillKruss\LaravelPhpRedis\RedisStore( 24 | Mockery::mock('TillKruss\LaravelPhpRedis\Database') 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/RepositoryTest.php: -------------------------------------------------------------------------------- 1 | repository(); 8 | $redis->getStore()->shouldReceive('get')->once()->with('foo')->andReturn(null); 9 | $this->assertNull($redis->get('foo')); 10 | } 11 | 12 | public function testGetReturnsNullWhenResultIsFalse() 13 | { 14 | $redis = $this->repository(); 15 | $redis->getStore()->shouldReceive('get')->once()->with('foo')->andReturn(false); 16 | $this->assertNull($redis->get('foo')); 17 | } 18 | 19 | public function testGetReturnsStoredValue() 20 | { 21 | $redis = $this->repository(); 22 | $redis->getStore()->shouldReceive('get')->once()->with('foo')->andReturn('bar'); 23 | $this->assertEquals('bar', $redis->get('foo')); 24 | } 25 | 26 | public function testHasReturnsFalseWhenResultIsNull() 27 | { 28 | $redis = $this->repository(); 29 | $redis->getStore()->shouldReceive('get')->once()->with('foo')->andReturn(null); 30 | $this->assertFalse($redis->has('foo')); 31 | } 32 | 33 | public function testHasReturnsFalseWhenResultIsFalse() 34 | { 35 | $redis = $this->repository(); 36 | $redis->getStore()->shouldReceive('get')->once()->with('foo')->andReturn(false); 37 | $this->assertFalse($redis->has('foo')); 38 | } 39 | 40 | // ToDo: Tests for `many()` 41 | 42 | public function testAddStoresItemWhenGetResultIsNull() 43 | { 44 | $redis = $this->repository(); 45 | $redis->getStore()->shouldReceive('get')->once()->with('foo')->andReturn(null); 46 | $redis->getStore()->shouldReceive('put')->once()->with('foo', 'bar', 60); 47 | $this->assertTrue($redis->add('foo', 'bar', 60)); 48 | } 49 | 50 | public function testAddStoresItemWhenGetResultIsFalse() 51 | { 52 | $redis = $this->repository(); 53 | $redis->getStore()->shouldReceive('get')->once()->with('foo')->andReturn(false); 54 | $redis->getStore()->shouldReceive('put')->once()->with('foo', 'bar', 60); 55 | $this->assertTrue($redis->add('foo', 'bar', 60)); 56 | } 57 | 58 | // ToDo: Tests for `remember()` 59 | // ToDo: Tests for `rememberForever()` 60 | 61 | protected function repository() 62 | { 63 | return new TillKruss\LaravelPhpRedis\Repository( 64 | Mockery::mock('TillKruss\LaravelPhpRedis\RedisStore') 65 | ); 66 | } 67 | } 68 | --------------------------------------------------------------------------------