├── .gitignore ├── .travis.yml ├── tests ├── BaseTest.php └── DriverTest.php ├── phpunit.xml ├── composer.json ├── src ├── Indatus │ └── LaravelPSRedis │ │ └── LaravelPSRedisServiceProvider.php └── lib │ └── Driver.php ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | - hhvm 8 | 9 | before_script: 10 | - travis_retry composer self-update 11 | - travis_retry composer install --prefer-source --no-interaction --dev 12 | 13 | script: phpunit 14 | -------------------------------------------------------------------------------- /tests/BaseTest.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | abstract class BaseTest extends TestCase 13 | { 14 | public function setUp() 15 | { 16 | parent::setUp(); 17 | } 18 | /** 19 | * Create the application 20 | * 21 | * @return mixed 22 | */ 23 | public function createApplication() 24 | { 25 | $unitTesting = true; 26 | $testEnvironment = 'testing'; 27 | 28 | return require __DIR__ . '/../../../../bootstrap/start.php'; 29 | } 30 | 31 | public function tearDown() 32 | { 33 | m::close(); 34 | } 35 | } -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | 20 | 21 | ./vendor 22 | 23 | 24 | ./src/lib 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "indatus/laravel-ps-redis", 3 | "description": "A simple sentinel/redis driver wrapper for laravel.", 4 | "authors": [ 5 | { 6 | "name": "Damien Russell", 7 | "email": "drussell@indatus.com" 8 | } 9 | ], 10 | "require": { 11 | "php": ">=5.6.0", 12 | "illuminate/support": "5.*", 13 | "jamescauwelier/psredis": "~1.1" 14 | }, 15 | "require-dev": { 16 | "laravel/framework": "5.*", 17 | "phpunit/phpunit": "4.*", 18 | "mockery/mockery": "0.9.*" 19 | }, 20 | "autoload": { 21 | "classmap": [ 22 | "src/lib" 23 | ], 24 | "psr-0": { 25 | "Indatus\\LaravelPSRedis\\": "src/" 26 | } 27 | }, 28 | "psr-4": { 29 | "": "src/lib" 30 | }, 31 | "minimum-stability": "stable", 32 | "license": "MIT" 33 | } 34 | -------------------------------------------------------------------------------- /src/Indatus/LaravelPSRedis/LaravelPSRedisServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton( 24 | 'redis', 25 | function () { 26 | $driver = new Driver(); 27 | return new RedisManager('predis', $driver->getConfig()); 28 | } 29 | ); 30 | } 31 | 32 | /** 33 | * Get the services provided by the provider. 34 | * 35 | * @return array 36 | */ 37 | public function provides() 38 | { 39 | return ['redis']; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Indatus 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 | 23 | -------------------------------------------------------------------------------- /src/lib/Driver.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Driver 16 | { 17 | /** @var MasterDiscovery $masterDiscovery The mechanism for determining the master */ 18 | protected $masterDiscovery; 19 | 20 | /** @var HAClient $HAClient is the highly available client which handles the auto-failover. */ 21 | protected $HAClient; 22 | 23 | 24 | /** 25 | * Constructor 26 | * 27 | * @return void 28 | */ 29 | public function __construct() 30 | { 31 | $this->setUpMasterDiscovery(); 32 | 33 | $this->addSentinels(); 34 | 35 | $this->HAClient = new HAClient( 36 | $this->masterDiscovery 37 | ); 38 | } 39 | 40 | 41 | /** 42 | * Get the config values for the redis database. 43 | * 44 | * @return array 45 | */ 46 | public function getConfig() 47 | { 48 | return [ 49 | 'cluster' => Config::get('database.redis.cluster'), 50 | 'default' => [ 51 | 'host' => $this->HAClient->getIpAddress(), 52 | 'port' => $this->HAClient->getPort(), 53 | 'password' => Config::get('database.redis.password', null), 54 | 'database' => Config::get('database.redis.database', 0), 55 | ] 56 | ]; 57 | } 58 | 59 | public function getBackOffStrategy() 60 | { 61 | /** @var array $backOffConfig */ 62 | $backOffConfig = Config::get('database.redis.backoff-strategy'); 63 | 64 | /** @var Incremental $incrementalBackOff */ 65 | $incrementalBackOff = new Incremental( 66 | $backOffConfig['wait-time'], 67 | $backOffConfig['increment'] 68 | ); 69 | 70 | $incrementalBackOff->setMaxAttempts($backOffConfig['max-attempts']); 71 | 72 | return $incrementalBackOff; 73 | } 74 | 75 | public function setUpMasterDiscovery() 76 | { 77 | $this->masterDiscovery = new MasterDiscovery(Config::get('database.redis.nodeSetName')); 78 | 79 | $this->masterDiscovery->setBackoffStrategy($this->getBackOffStrategy()); 80 | } 81 | 82 | public function addSentinels() 83 | { 84 | $clients = Config::get('database.redis.masters'); 85 | foreach($clients as $client) { 86 | $sentinel = new PSRedisClient($client['host'], $client['port']); 87 | 88 | $this->masterDiscovery->addSentinel($sentinel); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tests/DriverTest.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class DriverTest extends BaseTest 15 | { 16 | /** @var \Indatus\LaravelPSRedis\Driver */ 17 | protected $driverUnderTest; 18 | 19 | /** @var array */ 20 | protected $masters; 21 | 22 | /** @var \Mockery\MockInterface */ 23 | protected $masterDiscovery; 24 | 25 | /** @var \Mockery\MockInterface */ 26 | protected $HAClient; 27 | 28 | /** @var \Mockery\MockInterface */ 29 | protected $PSRedisClient; 30 | 31 | /** @var array */ 32 | protected $backOffStrategy; 33 | 34 | /** @var \Mockery\MockInterface */ 35 | protected $incremental; 36 | 37 | public function setUp() 38 | { 39 | parent::setUp(); 40 | 41 | // some of the classes from the PSRedis package implement the 42 | // `__call()` methods and define a default value for the method arguments 43 | // php doesn't like while using the `E_STRICT` error level 44 | if(defined('E_STRICT')) { 45 | error_reporting('E_ALL ^ E_STRICT'); 46 | } 47 | 48 | $this->masters = [ 49 | [ 50 | 'host' => 'host1.domain.com', 51 | 'port' => '222222' 52 | ], 53 | [ 54 | 'host' => 'host2.domain.com', 55 | 'port' => '222222' 56 | ] 57 | ]; 58 | 59 | $this->backOffStrategy = [ 60 | 'max-attempts' => 10, // the maximum-number of attempt possible to find master 61 | 'wait-time' => 500, // miliseconds to wait for the next attempt 62 | 'increment' => 1.5, // multiplier used to increment the back off time on each try 63 | ]; 64 | 65 | $this->masterDiscovery = m::mock('PSRedis\MasterDiscovery')->makePartial(); 66 | $this->app->instance('PSRedis\MasterDiscovery', $this->masterDiscovery); 67 | 68 | $this->HAClient = m::mock('PSRedis\HAClient')->makePartial(); 69 | $this->app->instance('PSRedis\HAClient', $this->HAClient); 70 | 71 | $this->PSRedisClient = m::mock('PSRedis\Client'); 72 | $this->app->instance('PSRedis\Client', $this->PSRedisClient); 73 | } 74 | 75 | /** 76 | * Can we instantiate the class? 77 | * 78 | * @test 79 | */ 80 | public function it_instantiates() 81 | { 82 | Config::shouldReceive('get')->once()->with('database.redis.nodeSetName')->andReturn('node-set'); 83 | Config::shouldReceive('get')->once()->with('database.redis.masters')->andReturn($this->masters); 84 | Config::shouldReceive('get')->with('database.redis.backoff-strategy')->andReturn($this->backOffStrategy); 85 | 86 | $this->driverUnderTest = new Driver(); 87 | 88 | $this->assertInstanceOf('Indatus\LaravelPSRedis\Driver', $this->driverUnderTest); 89 | } 90 | 91 | /** 92 | * Can it get the proper config array? 93 | * 94 | * @test 95 | */ 96 | public function it_gets_config_values() 97 | { 98 | $expectedConfig = [ 99 | 'cluster' => false, 100 | 'default' => [ 101 | 'host' => $this->masters[0]['host'], 102 | 'port' => $this->masters[0]['port'] 103 | ] 104 | ]; 105 | 106 | Config::shouldReceive('get')->once()->with('database.redis.nodeSetName')->andReturn('node-set'); 107 | Config::shouldReceive('get')->once()->with('database.redis.masters')->andReturn($this->masters); 108 | Config::shouldReceive('get')->with('database.redis.backoff-strategy')->andReturn($this->backOffStrategy); 109 | Config::shouldReceive('get')->with('database.redis.cluster')->andReturn(false); 110 | 111 | $this->HAClient->shouldReceive('getIpAddress')->once()->andReturn($this->masters[0]['host']); 112 | $this->HAClient->shouldReceive('getPort')->once()->andReturn($this->masters[0]['port']); 113 | 114 | $this->driverUnderTest = new Driver(); 115 | 116 | $configUnderTest = $this->driverUnderTest->getConfig(); 117 | 118 | $this->assertEquals($expectedConfig, $configUnderTest); 119 | } 120 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel-PSRedis 2 | 3 | > DEPRECATED, see https://github.com/laravel/framework/pull/18850 for built-in sentinel support 4 | 5 | A simple sentinel/redis driver wrapper for laravel. 6 | 7 | The default laravel redis driver supports redis clusters, however, it does not support high availability with redis, which is where Laravel-PSRedis comes to the rescue. 8 | 9 | With Laravel-PSRedis you'll get all the laravel redis magic that you aleady have such 10 | as `Redis::set()` and `Redis::get()`, and even session, queue, and cache support using redis, 11 | you'll just be able to leverage High Avaliability redis instances instead of a simple cluster. 12 | 13 | We do this by asking your [Redis Sentinels](http://redis.io/topics/sentinel) the location of your master before creating our Redis bindings in the IOC Container. By doing this we ensure anytime your app has a connection to your redis instance, that connection is to master. 14 | 15 | ## README Contents 16 | 17 | * [Installation](#installation) 18 | * [Laravel 5 Installation](#installation-for-Laravel-5) 19 | * [Laravel 4 Installation](#installation-for-Laravel-4) 20 | * [Configuration](#configuration) 21 | * [Service Provider](#the-service-provider) 22 | * [Contributing](#contributing); 23 | * [Testing](#testing) 24 | * [License](#license) 25 | 26 | 27 | ## Installation 28 | 29 | 30 | ### Installation for Laravel 5 31 | 32 | You can install Laravel-PSRedis easily with composer. 33 | 34 | ``` 35 | "require": { 36 | "indatus/laravel-ps-redis": "^1.2", 37 | }, 38 | ``` 39 | 40 | 41 | ### Installation for Laravel 4 42 | 43 | If you're using Laravel 4 then the installation is slightly different. Laravel-PSRedis depends on `sparkcentral/psredis` which requires `'predis/predis': '>=1.0'` in it's stable release. I've taken the liberty of forking `sparkcentral/psredis` and rolling back `predis/predis` to `0.8.7` 44 | which is required by laravel 4. To utilize this fork simply require both `indatus\larave-ps-redis` and `sparkcentral/psredis` in your composer.json. And add a repository to point to the fork. Like so: 45 | 46 | ``` 47 | "repositories": [ 48 | { 49 | "type": "vcs", 50 | "url": "https://github.com/Olofguard/PSRedis" 51 | } 52 | ], 53 | "require": { 54 | "indatus/laravel-ps-redis": "dev-master", 55 | "sparkcentral/psredis": "dev-master" 56 | }, 57 | ``` 58 | 59 | This will help composer form an installable set of packages, otherwise composer complains about laravel needing `predis/predis` at version `0.8.7` while `sparkcentral/psredis` is installing `1.0.*`. 60 | 61 | 62 | ## Configuration 63 | 64 | Next, just fill in your sentinel/redis server info in the `app/config/database.php` config files that already exist in your application. 65 | 66 | You may already have some default laravel config values in place in your database config file that looks like this. 67 | 68 | ``` 69 | /* 70 | |-------------------------------------------------------------------------- 71 | | Redis Databases 72 | |-------------------------------------------------------------------------- 73 | | 74 | | Redis is an open source, fast, and advanced key-value store that also 75 | | provides a richer set of commands than a typical key-value systems 76 | | such as APC or Memcached. Laravel makes it easy to dig right in. 77 | | 78 | */ 79 | 'redis' => [ 80 | 'cluster' => false, 81 | 'default' => [ 82 | 'host' => '127.0.0.1', 83 | 'port' => 6379, 84 | 'database' => 0, 85 | ], 86 | ], 87 | ``` 88 | 89 | Just overwrite those with the values below and fill in your server info. 90 | 91 | ``` 92 | 'redis' => [ 93 | 94 | /** the name of the redis node set */ 95 | 'nodeSetName' => 'sentinel-node-set', 96 | 97 | 'cluster' => false, 98 | 99 | /** Array of sentinels */ 100 | 'masters' => [ 101 | [ 102 | 'host' => 'sentinel-instance.domain.com', 103 | 'port' => '26379', 104 | ], 105 | [ 106 | 'host' => 'sentinel-instance.domain.com', 107 | 'port' => '26379', 108 | ] 109 | ], 110 | 111 | /** how long to wait and try again if we fail to connect to master */ 112 | 'backoff-strategy' => [ 113 | 'max-attempts' => 10, // the maximum-number of attempt possible to find master 114 | 'wait-time' => 500, // miliseconds to wait for the next attempt 115 | 'increment' => 1.5, // multiplier used to increment the back off time on each try 116 | ] 117 | ]; 118 | ``` 119 | 120 | 121 | ### The Service Provider 122 | 123 | Finally, you just need to add the service provider to the providers array in `app.php` and comment or remove the 124 | redis service provider. 125 | 126 | ``` 127 | /* 128 | |-------------------------------------------------------------------------- 129 | | Autoloaded Service Providers 130 | |-------------------------------------------------------------------------- 131 | | 132 | */ 133 | 'providers' => [ 134 | ... 135 | // 'Illuminate\Redis\RedisServiceProvider', # comment this out 136 | 'Indatus\LaravelPSRedis\LaravelPSRedisServiceProvider' # add this 137 | ], 138 | ``` 139 | 140 | > Note: you may have to `composer dump-autoload` after adding the service provider 141 | 142 | 143 | ## Contributing 144 | 145 | 1. Fork it 146 | 2. Create your feature branch (git checkout -b my-new-feature) 147 | 3. Commit your changes (git commit -m 'Added some feature') 148 | 4. Push to the branch (git push origin my-new-feature) 149 | 5. Create new Pull Request 150 | 151 | 152 | ## Testing 153 | 154 | Feel free to clone the repo and run the unit tests locally. 155 | 156 | ``` 157 | ./vendor/bin/phpunit -c ./phpunit.xml 158 | ``` 159 | 160 | 161 | ## License 162 | [The MIT License (MIT)](https://github.com/Indatus/laravel-PSRedis/blob/master/LICENSE) 163 | --------------------------------------------------------------------------------