├── .composer-auth.json ├── .gitignore ├── .styleci.yml ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── appveyor.yml ├── composer.json ├── phpunit.xml.dist ├── src ├── AbstractRedisStore.php ├── Api │ ├── CountableStore.php │ ├── InvalidKeyException.php │ ├── KeyValueStore.php │ ├── NoSuchKeyException.php │ ├── ReadException.php │ ├── SerializationFailedException.php │ ├── SortableStore.php │ ├── UnserializationFailedException.php │ ├── UnsupportedValueException.php │ └── WriteException.php ├── ArrayStore.php ├── DbalStore.php ├── Decorator │ ├── AbstractDecorator.php │ ├── CachingDecorator.php │ ├── CountableDecorator.php │ └── SortableDecorator.php ├── JsonFileStore.php ├── MongoDbStore.php ├── NullStore.php ├── PhpRedisStore.php ├── PredisStore.php ├── RiakStore.php ├── SerializingArrayStore.php └── Util │ ├── KeyUtil.php │ └── Serializer.php └── tests ├── AbstractCountableStoreTest.php ├── AbstractKeyValueStoreTest.php ├── AbstractMongoDbStoreTest.php ├── AbstractSortableCountableStoreTest.php ├── ArrayStoreTest.php ├── DbalStoreTest.php ├── Decorator ├── AbstractDecoratorTest.php ├── CachingDecoratorTest.php ├── CountableDecoratorTest.php └── SortableDecoratorTest.php ├── Fixtures ├── NotSerializable.php ├── PrivateProperties.php ├── TestClearableCache.php ├── TestClearableFlushableCache.php ├── TestException.php ├── TestFlushableCache.php └── file ├── JsonFileStoreTest.php ├── NonSerializingBinaryMongoDbStoreTest.php ├── NonSerializingMongoDbStoreTest.php ├── NullStoreTest.php ├── PhpRedisStoreTest.php ├── PredisStoreTest.php ├── RiakStoreTest.php ├── SerializingArrayStoreTest.php ├── SerializingBinaryMongoDbStoreTest.php ├── SerializingMongoDbStoreTest.php └── Util └── SerializerTest.php /.composer-auth.json: -------------------------------------------------------------------------------- 1 | { 2 | "github-oauth": { 3 | "github.com": "PLEASE DO NOT USE THIS TOKEN IN YOUR OWN PROJECTS/FORKS", 4 | "github.com": "This token is reserved for testing the webmozart/* repositories", 5 | "github.com": "a9debbffdd953ee9b3b82dbc3b807cde2086bb86" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.lock 3 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: symfony 2 | 3 | enabled: 4 | - ordered_use 5 | - strict 6 | 7 | disabled: 8 | - empty_return 9 | - phpdoc_annotation_without_dot # This is still buggy: https://github.com/symfony/symfony/pull/19198 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | services: 4 | - redis-server 5 | - riak 6 | - mongodb 7 | 8 | branches: 9 | only: 10 | - master 11 | 12 | sudo: false 13 | 14 | cache: 15 | directories: 16 | - $HOME/.composer/cache/files 17 | 18 | matrix: 19 | include: 20 | - php: 5.3 21 | - php: 5.4 22 | - php: 5.5 23 | - php: 5.6 24 | - php: hhvm 25 | - php: nightly 26 | - php: 7.0 27 | env: COVERAGE=yes 28 | - php: 7.0 29 | env: COMPOSER_FLAGS='--prefer-lowest --prefer-stable' 30 | allow_failures: 31 | - php: hhvm 32 | - php: nightly 33 | fast_finish: true 34 | 35 | 36 | before_install: 37 | - if [[ $TRAVIS_PHP_VERSION != hhvm ]]; then echo "extension=redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi 38 | - if [[ $TRAVIS_PHP_VERSION != hhvm && $TRAVIS_PHP_VERSION != 5.3 ]]; then pecl install -f mongodb; echo "extension=mongodb.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi 39 | - if [[ $TRAVIS_PHP_VERSION != hhvm && $COVERAGE != yes ]]; then phpenv config-rm xdebug.ini; fi; 40 | - if [[ $TRAVIS_REPO_SLUG = webmozart/key-value-store ]]; then cp .composer-auth.json ~/.composer/auth.json; fi; 41 | - composer self-update 42 | 43 | install: 44 | # mongodb/mongodb is not compatible with HHVM/5.3 45 | - if [[ $TRAVIS_PHP_VERSION = hhvm || $TRAVIS_PHP_VERSION = 5.3 ]]; then composer remove --dev mongodb/mongodb; fi 46 | - composer update $COMPOSER_FLAGS --prefer-dist --no-interaction 47 | 48 | script: if [[ $COVERAGE = yes ]]; then vendor/bin/phpunit --verbose --coverage-clover=coverage.clover; else vendor/bin/phpunit --verbose; fi 49 | 50 | after_script: if [[ $COVERAGE = yes ]]; then wget https://scrutinizer-ci.com/ocular.phar && php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi 51 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | * 1.0.0-beta8 (2016-08-09) 5 | 6 | * added `MongoDbStore` 7 | * added flag `ArrayStore::SERIALIZE` 8 | * deprecated `SerializingArrayStore` 9 | 10 | * 1.0.0-beta7 (2016-01-14) 11 | 12 | * added `JsonFileStore::NO_SERIALIZE_STRINGS` and `JsonFileStore::NO_SERIALIZE_ARRAYS` 13 | * disabled serialization of `null` values in `JsonFileStore` 14 | * removed code from `JsonFileStore` and relied on webmozart/json instead 15 | * added `JsonFileStore::ESCAPE_GT_LT` 16 | * added `JsonFileStore::ESCAPE_AMPERSAND` 17 | * added `JsonFileStore::ESCAPE_SINGLE_QUOTE` 18 | * added `JsonFileStore::ESCAPE_DOUBLE_QUOTE` 19 | * added `JsonFileStore::NO_ESCAPE_SLASH` 20 | * added `JsonFileStore::NO_ESCAPE_UNICODE` 21 | * added `JsonFileStore::PRETTY_PRINT` 22 | * added `JsonFileStore::TERMINATE_WITH_LINE_FEED` 23 | 24 | * 1.0.0-beta6 (2015-10-02) 25 | 26 | * added `SerializingArrayStore` 27 | * added `DbalStore` 28 | 29 | * 1.0.0-beta5 (2015-08-11) 30 | 31 | * added `AbstractDecorator` 32 | * added `AbstractRedisStore` 33 | * added `CountableStore` 34 | * added `SortableStore` 35 | * renamed `CachedStore` to `CachingDecorator` 36 | * added `CountableDecorator` 37 | * added `SortableDecorator` 38 | * implemented `CountableStore` and `SortableStore` in `ArrayStore`, 39 | `JsonFileStore` and `NullStore` 40 | * made `KeyUtil` final 41 | * made `Serializer` final 42 | 43 | * 1.0.0-beta4 (2015-05-28) 44 | 45 | * added `KeyValueStore::keys()` 46 | * renamed `KeyValueStore::has()` to `exists()` 47 | * added `KeyValueStore::getOrFail()` 48 | * added `KeyValueStore::getMultiple()` 49 | * added `KeyValueStore::getMultipleOrFail()` 50 | 51 | * 1.0.0-beta3 (2015-04-13) 52 | 53 | * replaced `Assert` by webmozart/assert 54 | 55 | * 1.0.0-beta2 (2015-01-21) 56 | 57 | * added `PhpRedisStore` 58 | * added `CachedStore` 59 | * removed optional argument `$cache` from `JsonFileStore::__construct()` 60 | * removed implementations that don't make sense for a key-value store: 61 | * `MemcacheStore` (not persistent) 62 | * `MemcachedStore` (not persistent) 63 | * `SharedMemoryStore` (not persistent) 64 | * added `Serializer` utility 65 | * `KeyValueStore::get()` now throws an exception if the value cannot be unserialized 66 | 67 | * 1.0.0-beta (2015-01-12) 68 | 69 | * first release 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Bernhard Schussek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Webmozart Key-Value-Store 2 | ========================= 3 | 4 | [![Build Status](https://travis-ci.org/webmozart/key-value-store.svg?branch=master)](https://travis-ci.org/webmozart/key-value-store) 5 | [![Build status](https://ci.appveyor.com/api/projects/status/own4xo856g5asgc8/branch/master?svg=true)](https://ci.appveyor.com/project/webmozart/key-value-store/branch/master) 6 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/webmozart/key-value-store/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/webmozart/key-value-store/?branch=master) 7 | [![Latest Stable Version](https://poser.pugx.org/webmozart/key-value-store/v/stable.svg)](https://packagist.org/packages/webmozart/key-value-store) 8 | [![Total Downloads](https://poser.pugx.org/webmozart/key-value-store/downloads.svg)](https://packagist.org/packages/webmozart/key-value-store) 9 | [![Dependency Status](https://www.versioneye.com/php/webmozart:key-value-store/1.0.0/badge.svg)](https://www.versioneye.com/php/webmozart:key-value-store/1.0.0) 10 | 11 | Latest release: [1.0.0](https://packagist.org/packages/webmozart/key-value-store#1.0.0) 12 | 13 | A key-value store API with implementations for different backends. 14 | 15 | [API Documentation] 16 | 17 | All contained key-value stores implement the interface [`KeyValueStore`]. The 18 | following stores are currently supported: 19 | 20 | * [`ArrayStore`] 21 | * [`DbalStore`] 22 | * [`JsonFileStore`] 23 | * [`MongoDbStore`] 24 | * [`NullStore`] 25 | * [`PhpRedisStore`] 26 | * [`PredisStore`] 27 | * [`RiakStore`] 28 | 29 | The interface [`CountableStore`] is supported by the following classes: 30 | 31 | * [`ArrayStore`] 32 | * [`JsonFileStore`] 33 | * [`NullStore`] 34 | * [`CountableDecorator`] 35 | 36 | The interface [`SortableStore`] is supported by the following classes: 37 | 38 | * [`ArrayStore`] 39 | * [`JsonFileStore`] 40 | * [`NullStore`] 41 | * [`SortableDecorator`] 42 | 43 | The decorator [`CachingDecorator`] exists for caching another store instance 44 | in a Doctrine cache. 45 | 46 | FAQ 47 | --- 48 | 49 | **Why not use [Doctrine Cache]?** 50 | 51 | Caching is **not** key-value storage. When you use a cache, you accept that keys 52 | may disappear for various reasons: 53 | 54 | * Keys may expire. 55 | * Keys may be overwritten when the cache is full. 56 | * Keys may be lost after shutdowns. 57 | * ... 58 | 59 | In another word, caches are *volatile*. This is not a problem, since the cached 60 | data is usually stored safely somewhere else. The point of a cache is to provide 61 | high-performance access to frequently needed data. 62 | 63 | Key-value stores, on the other hand, are *persistent*. When you write a key to a 64 | key-value store, you expect it to be there until you delete it. It would be a 65 | disaster if data would silently disappear from a key-value store (or any other 66 | kind of database). 67 | 68 | Hence the two libraries fulfill two very different purposes, even if their 69 | interfaces and implementations are often similar. 70 | 71 | The [`CachingDecorator`] actually uses a Doctrine Cache object to cache the data 72 | of a persistent [`KeyValueStore`]. 73 | 74 | Authors 75 | ------- 76 | 77 | * [Bernhard Schussek] a.k.a. [@webmozart] 78 | * [The Community Contributors] 79 | 80 | Installation 81 | ------------ 82 | 83 | Use [Composer] to install the package: 84 | 85 | ``` 86 | $ composer require webmozart/key-value-store 87 | ``` 88 | 89 | Contribute 90 | ---------- 91 | 92 | Contributions to the package are always welcome! 93 | 94 | * Report any bugs or issues you find on the [issue tracker]. 95 | * You can grab the source code at the package's [Git repository]. 96 | 97 | Support 98 | ------- 99 | 100 | If you are having problems, send a mail to bschussek@gmail.com or shout out to 101 | [@webmozart] on Twitter. 102 | 103 | License 104 | ------- 105 | 106 | All contents of this package are licensed under the [MIT license]. 107 | 108 | [Composer]: https://getcomposer.org 109 | [Bernhard Schussek]: http://webmozarts.com 110 | [The Community Contributors]: https://github.com/webmozart/key-value-store/graphs/contributors 111 | [issue tracker]: https://github.com/webmozart/key-value-store/issues 112 | [Git repository]: https://github.com/webmozart/key-value-store 113 | [@webmozart]: https://twitter.com/webmozart 114 | [MIT license]: LICENSE 115 | [Doctrine Cache]: https://github.com/doctrine/cache 116 | [API Documentation]: https://webmozart.github.io/key-value-store/api 117 | [`KeyValueStore`]: https://webmozart.github.io/key-value-store/api/latest/class-Webmozart.KeyValueStore.Api.KeyValueStore.html 118 | [`CountableStore`]: https://webmozart.github.io/key-value-store/api/latest/class-Webmozart.KeyValueStore.Api.CountableStore.html 119 | [`SortableStore`]: https://webmozart.github.io/key-value-store/api/latest/class-Webmozart.KeyValueStore.Api.SortableStore.html 120 | [`ArrayStore`]: https://webmozart.github.io/key-value-store/api/latest/class-Webmozart.KeyValueStore.ArrayStore.html 121 | [`DbalStore`]: https://webmozart.github.io/key-value-store/api/latest/class-Webmozart.KeyValueStore.DbalStore.html 122 | [`JsonFileStore`]: https://webmozart.github.io/key-value-store/api/latest/class-Webmozart.KeyValueStore.JsonFileStore.html 123 | [`MongoDbStore`]: https://webmozart.github.io/key-value-store/api/latest/class-Webmozart.KeyValueStore.MongoDbStore.html 124 | [`NullStore`]: https://webmozart.github.io/key-value-store/api/latest/class-Webmozart.KeyValueStore.NullStore.html 125 | [`PhpRedisStore`]: https://webmozart.github.io/key-value-store/api/latest/class-Webmozart.KeyValueStore.PhpRedisStore.html 126 | [`PredisStore`]: https://webmozart.github.io/key-value-store/api/latest/class-Webmozart.KeyValueStore.PredisStore.html 127 | [`RiakStore`]: https://webmozart.github.io/key-value-store/api/latest/class-Webmozart.KeyValueStore.RiakStore.html 128 | [`CachingDecorator`]: https://webmozart.github.io/key-value-store/api/latest/class-Webmozart.KeyValueStore.Decorator.CachingDecorator.html 129 | [`CountableDecorator`]: https://webmozart.github.io/key-value-store/api/latest/class-Webmozart.KeyValueStore.Decorator.CountableDecorator.html 130 | [`SortableDecorator`]: https://webmozart.github.io/key-value-store/api/latest/class-Webmozart.KeyValueStore.Decorator.SortableDecorator.html 131 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | build: false 2 | platform: x86 3 | clone_folder: c:\projects\webmozart\key-value-store 4 | 5 | services: 6 | - mongodb 7 | 8 | branches: 9 | only: 10 | - master 11 | 12 | cache: 13 | - c:\php -> appveyor.yml 14 | 15 | init: 16 | - SET PATH=c:\php;%PATH% 17 | - SET COMPOSER_NO_INTERACTION=1 18 | - SET PHP=1 19 | 20 | install: 21 | - IF EXIST c:\php (SET PHP=0) ELSE (mkdir c:\php) 22 | - cd c:\php 23 | - IF %PHP%==1 appveyor DownloadFile http://windows.php.net/downloads/releases/archives/php-7.0.0-nts-Win32-VC14-x86.zip 24 | - IF %PHP%==1 7z x php-7.0.0-nts-Win32-VC14-x86.zip -y >nul 25 | - IF %PHP%==1 del /Q *.zip 26 | - IF %PHP%==1 cd ext 27 | - IF %PHP%==1 appveyor DownloadFile http://windows.php.net/downloads/pecl/releases/mongodb/1.1.8/php_mongodb-1.1.8-7.0-nts-vc14-x86.zip 28 | - IF %PHP%==1 7z x php_mongodb-1.1.8-7.0-nts-vc14-x86.zip php_mongodb.dll -y >nul 29 | - IF %PHP%==1 del /Q *.zip 30 | - IF %PHP%==1 cd .. 31 | - IF %PHP%==1 echo @php %%~dp0composer.phar %%* > composer.bat 32 | - IF %PHP%==1 copy /Y php.ini-development php.ini 33 | - IF %PHP%==1 echo max_execution_time=1200 >> php.ini 34 | - IF %PHP%==1 echo date.timezone="UTC" >> php.ini 35 | - IF %PHP%==1 echo extension_dir=ext >> php.ini 36 | - IF %PHP%==1 echo extension=php_curl.dll >> php.ini 37 | - IF %PHP%==1 echo extension=php_pdo_sqlite.dll >> php.ini 38 | - IF %PHP%==1 echo extension=php_openssl.dll >> php.ini 39 | - IF %PHP%==1 echo extension=php_mbstring.dll >> php.ini 40 | - IF %PHP%==1 echo extension=php_fileinfo.dll >> php.ini 41 | - IF %PHP%==1 echo extension=php_mongodb.dll >> php.ini 42 | - appveyor DownloadFile https://getcomposer.org/composer.phar 43 | - cd c:\projects\webmozart\key-value-store 44 | - mkdir %APPDATA%\Composer 45 | - IF %APPVEYOR_REPO_NAME%==webmozart/key-value-store copy /Y .composer-auth.json %APPDATA%\Composer\auth.json 46 | - composer update --prefer-dist --no-progress --ansi 47 | 48 | test_script: 49 | - cd c:\projects\webmozart\key-value-store 50 | - vendor\bin\phpunit.bat --verbose 51 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webmozart/key-value-store", 3 | "description": "A key-value store API with implementations for different backends.", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Bernhard Schussek", 8 | "email": "bschussek@gmail.com" 9 | } 10 | ], 11 | "require": { 12 | "php": "^5.3.3|^7.0", 13 | "webmozart/assert": "^1.0" 14 | }, 15 | "require-dev": { 16 | "webmozart/json": "^1.1.1", 17 | "symfony/filesystem": "^2.5", 18 | "predis/predis": "^1.0", 19 | "basho/riak": "^1.4", 20 | "doctrine/cache": "^1.4", 21 | "phpunit/phpunit": "^4.6", 22 | "doctrine/dbal" : "^2.4", 23 | "sebastian/version": "^1.0.1", 24 | "mongodb/mongodb": "^1.0" 25 | }, 26 | "suggest": { 27 | "webmozart/json": "to enable the JsonFileStore", 28 | "predis/predis": "to enable the PredisStore", 29 | "basho/riak": "to enable the RiakStore", 30 | "doctrine/cache": "to enable the CachedStore", 31 | "doctrine/dbal": "to enable the DbalStore", 32 | "mongodb/mongodb": "to enable the MongoDbStore" 33 | }, 34 | "autoload": { 35 | "psr-4": { 36 | "Webmozart\\KeyValueStore\\": "src/" 37 | } 38 | }, 39 | "autoload-dev": { 40 | "psr-4": { 41 | "Webmozart\\KeyValueStore\\Tests\\": "tests/" 42 | } 43 | }, 44 | "extra": { 45 | "branch-alias": { 46 | "dev-master": "1.1-dev" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ./tests/ 7 | 8 | 9 | 10 | 11 | 12 | 13 | ./src/ 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/AbstractRedisStore.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore; 13 | 14 | use Exception; 15 | use Webmozart\KeyValueStore\Api\KeyValueStore; 16 | use Webmozart\KeyValueStore\Api\NoSuchKeyException; 17 | use Webmozart\KeyValueStore\Api\ReadException; 18 | use Webmozart\KeyValueStore\Api\WriteException; 19 | use Webmozart\KeyValueStore\Util\KeyUtil; 20 | use Webmozart\KeyValueStore\Util\Serializer; 21 | 22 | /** 23 | * An abstract Redis key-value store to support multiple Redis clients without code duplication. 24 | * 25 | * @since 1.0 26 | * 27 | * @author Bernhard Schussek 28 | * @author Titouan Galopin 29 | */ 30 | abstract class AbstractRedisStore implements KeyValueStore 31 | { 32 | /** 33 | * Redis client. 34 | * 35 | * @var object 36 | */ 37 | protected $client; 38 | 39 | /** 40 | * Return the value corresponding to "not found" 41 | * for the internal client. 42 | * 43 | * @return mixed 44 | */ 45 | abstract protected function clientNotFoundValue(); 46 | 47 | /** 48 | * Call the internal client method to fetch a key. 49 | * Don't have to catch the exceptions. 50 | * 51 | * @param string $key The key to fetch 52 | * 53 | * @return mixed The raw value 54 | */ 55 | abstract protected function clientGet($key); 56 | 57 | /** 58 | * Call the internal client method to fetch multiple keys. 59 | * Don't have to catch the exceptions. 60 | * 61 | * @param array $keys The keys to fetch 62 | * 63 | * @return array The raw values 64 | */ 65 | abstract protected function clientGetMultiple(array $keys); 66 | 67 | /** 68 | * Call the internal client method to set a value associated to a key. 69 | * Don't have to catch the exceptions. 70 | * 71 | * @param string $key 72 | * @param mixed $value 73 | */ 74 | abstract protected function clientSet($key, $value); 75 | 76 | /** 77 | * Call the internal client method to remove a key. 78 | * Don't have to catch the exceptions. 79 | * 80 | * @param string $key 81 | * 82 | * @return bool true if the removal worked, false otherwise 83 | */ 84 | abstract protected function clientRemove($key); 85 | 86 | /** 87 | * Call the internal client method to check if a key exists. 88 | * Don't have to catch the exceptions. 89 | * 90 | * @param string $key 91 | * 92 | * @return bool 93 | */ 94 | abstract protected function clientExists($key); 95 | 96 | /** 97 | * Call the internal client method to clear all the keys. 98 | * Don't have to catch the exceptions. 99 | */ 100 | abstract protected function clientClear(); 101 | 102 | /** 103 | * Call the internal client method to fetch all the keys. 104 | * Don't have to catch the exceptions. 105 | * 106 | * @return array The keys 107 | */ 108 | abstract protected function clientKeys(); 109 | 110 | /** 111 | * {@inheritdoc} 112 | */ 113 | public function getMultiple(array $keys, $default = null) 114 | { 115 | KeyUtil::validateMultiple($keys); 116 | 117 | // Normalize indices of the array 118 | $keys = array_values($keys); 119 | $values = array(); 120 | 121 | try { 122 | $serializedValues = $this->clientGetMultiple($keys); 123 | } catch (Exception $e) { 124 | throw ReadException::forException($e); 125 | } 126 | 127 | foreach ($serializedValues as $i => $serializedValue) { 128 | $values[$keys[$i]] = $this->clientNotFoundValue() === $serializedValue 129 | ? $default 130 | : Serializer::unserialize($serializedValue); 131 | } 132 | 133 | return $values; 134 | } 135 | 136 | /** 137 | * {@inheritdoc} 138 | */ 139 | public function getMultipleOrFail(array $keys) 140 | { 141 | KeyUtil::validateMultiple($keys); 142 | 143 | // Normalize indices of the array 144 | $keys = array_values($keys); 145 | $values = array(); 146 | $notFoundKeys = array(); 147 | 148 | try { 149 | $serializedValues = $this->clientGetMultiple($keys); 150 | } catch (Exception $e) { 151 | throw ReadException::forException($e); 152 | } 153 | 154 | foreach ($serializedValues as $i => $serializedValue) { 155 | if ($this->clientNotFoundValue() === $serializedValue) { 156 | $notFoundKeys[] = $keys[$i]; 157 | } elseif (0 === count($notFoundKeys)) { 158 | $values[$keys[$i]] = Serializer::unserialize($serializedValue); 159 | } 160 | } 161 | 162 | if (0 !== count($notFoundKeys)) { 163 | throw NoSuchKeyException::forKeys($notFoundKeys); 164 | } 165 | 166 | return $values; 167 | } 168 | 169 | /** 170 | * {@inheritdoc} 171 | */ 172 | public function get($key, $default = null) 173 | { 174 | KeyUtil::validate($key); 175 | 176 | try { 177 | $serialized = $this->clientGet($key); 178 | } catch (Exception $e) { 179 | throw ReadException::forException($e); 180 | } 181 | 182 | if ($this->clientNotFoundValue() === $serialized) { 183 | return $default; 184 | } 185 | 186 | return Serializer::unserialize($serialized); 187 | } 188 | 189 | /** 190 | * {@inheritdoc} 191 | */ 192 | public function getOrFail($key) 193 | { 194 | KeyUtil::validate($key); 195 | 196 | try { 197 | $serialized = $this->clientGet($key); 198 | } catch (Exception $e) { 199 | throw ReadException::forException($e); 200 | } 201 | 202 | if ($this->clientNotFoundValue() === $serialized) { 203 | throw NoSuchKeyException::forKey($key); 204 | } 205 | 206 | return Serializer::unserialize($serialized); 207 | } 208 | 209 | /** 210 | * {@inheritdoc} 211 | */ 212 | public function set($key, $value) 213 | { 214 | KeyUtil::validate($key); 215 | 216 | $serialized = Serializer::serialize($value); 217 | 218 | try { 219 | $this->clientSet($key, $serialized); 220 | } catch (Exception $e) { 221 | throw WriteException::forException($e); 222 | } 223 | } 224 | 225 | /** 226 | * {@inheritdoc} 227 | */ 228 | public function remove($key) 229 | { 230 | KeyUtil::validate($key); 231 | 232 | try { 233 | return (bool) $this->clientRemove($key); 234 | } catch (Exception $e) { 235 | throw WriteException::forException($e); 236 | } 237 | } 238 | 239 | /** 240 | * {@inheritdoc} 241 | */ 242 | public function exists($key) 243 | { 244 | KeyUtil::validate($key); 245 | 246 | try { 247 | return $this->clientExists($key); 248 | } catch (Exception $e) { 249 | throw ReadException::forException($e); 250 | } 251 | } 252 | 253 | /** 254 | * {@inheritdoc} 255 | */ 256 | public function clear() 257 | { 258 | try { 259 | $this->clientClear(); 260 | } catch (Exception $e) { 261 | throw WriteException::forException($e); 262 | } 263 | } 264 | 265 | /** 266 | * {@inheritdoc} 267 | */ 268 | public function keys() 269 | { 270 | try { 271 | return $this->clientKeys(); 272 | } catch (Exception $e) { 273 | throw ReadException::forException($e); 274 | } 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /src/Api/CountableStore.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Api; 13 | 14 | /** 15 | * A countable key-value store. 16 | * 17 | * In addition of the properties of a classical store, a countable store 18 | * has the ability to count its keys. 19 | * 20 | * @since 1.0 21 | * 22 | * @author Bernhard Schussek 23 | * @author Titouan Galopin 24 | */ 25 | interface CountableStore extends KeyValueStore 26 | { 27 | /** 28 | * Count the number of keys in the store. 29 | * 30 | * @throws ReadException If the store cannot be read. 31 | */ 32 | public function count(); 33 | } 34 | -------------------------------------------------------------------------------- /src/Api/InvalidKeyException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Api; 13 | 14 | use Exception; 15 | use RuntimeException; 16 | 17 | /** 18 | * Thrown when a key is invalid. 19 | * 20 | * @since 1.0 21 | * 22 | * @author Bernhard Schussek 23 | */ 24 | class InvalidKeyException extends RuntimeException 25 | { 26 | /** 27 | * Creates an exception for an invalid key. 28 | * 29 | * @param mixed $key The invalid key. 30 | * @param Exception|null $cause The exception that caused this exception. 31 | * 32 | * @return static The created exception. 33 | */ 34 | public static function forKey($key, Exception $cause = null) 35 | { 36 | return new static(sprintf( 37 | 'Expected a key of type integer or string. Got: %s', 38 | is_object($key) ? get_class($key) : gettype($key) 39 | ), 0, $cause); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Api/KeyValueStore.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Api; 13 | 14 | /** 15 | * A key-value store. 16 | * 17 | * KeyUtil-value stores support storing values for integer or string keys chosen 18 | * by the user. Any serializable value can be stored, although an implementation 19 | * of this interface may further restrict the range of accepted values. See the 20 | * documentation of the implementation for more information. 21 | * 22 | * @since 1.0 23 | * 24 | * @author Bernhard Schussek 25 | */ 26 | interface KeyValueStore 27 | { 28 | /** 29 | * Sets the value for a key in the store. 30 | * 31 | * The key-value store accepts any serializable value. If a value is not 32 | * serializable, a {@link SerializationFailedException} is thrown. 33 | * Additionally, implementations may put further restrictions on their 34 | * accepted values. If an unsupported value is passed, an 35 | * {@link UnsupportedValueException} is thrown. Check the documentation of 36 | * the implementation to learn more about its supported values. 37 | * 38 | * Any integer or string value is accepted as key. If any other type is 39 | * passed for the key, an {@link InvalidKeyException} is thrown. You should 40 | * make sure that you only pass valid keys to the store. 41 | * 42 | * If the backend of the store cannot be written, a {@link WriteException} 43 | * is thrown. You should always handle this exception in your code: 44 | * 45 | * ```php 46 | * try { 47 | * $store->set($key, $value); 48 | * } catch (WriteException $e) { 49 | * // write failed 50 | * } 51 | * ``` 52 | * 53 | * @param int|string $key The key to set. 54 | * @param mixed $value The value to set for the key. 55 | * 56 | * @throws WriteException If the store cannot be written. 57 | * @throws InvalidKeyException If the key is not a string or integer. 58 | * @throws SerializationFailedException If the value cannot be serialized. 59 | * @throws UnsupportedValueException If the value is not supported by the 60 | * implementation. 61 | */ 62 | public function set($key, $value); 63 | 64 | /** 65 | * Returns the value of a key in the store. 66 | * 67 | * If a key does not exist in the store, the default value passed in the 68 | * second parameter is returned. 69 | * 70 | * Any integer or string value is accepted as key. If any other type is 71 | * passed for the key, an {@link InvalidKeyException} is thrown. You should 72 | * make sure that you only pass valid keys to the store. 73 | * 74 | * If the backend of the store cannot be read, a {@link ReadException} 75 | * is thrown. You should always handle this exception in your code: 76 | * 77 | * ```php 78 | * try { 79 | * $value = $store->get($key); 80 | * } catch (ReadException $e) { 81 | * // read failed 82 | * } 83 | * ``` 84 | * 85 | * @param int|string $key The key to get. 86 | * @param mixed $default The default value to return if the key does 87 | * not exist. 88 | * 89 | * @return mixed The value of the key or the default value if the key does 90 | * not exist. 91 | * 92 | * @throws ReadException If the store cannot be read. 93 | * @throws InvalidKeyException If the key is not a string or integer. 94 | * @throws UnserializationFailedException If the stored value cannot be 95 | * unserialized. 96 | */ 97 | public function get($key, $default = null); 98 | 99 | /** 100 | * Returns the value of a key in the store. 101 | * 102 | * If the key does not exist in the store, an exception is thrown. 103 | * 104 | * Any integer or string value is accepted as key. If any other type is 105 | * passed for the key, an {@link InvalidKeyException} is thrown. You should 106 | * make sure that you only pass valid keys to the store. 107 | * 108 | * If the backend of the store cannot be read, a {@link ReadException} 109 | * is thrown. You should always handle this exception in your code: 110 | * 111 | * ```php 112 | * try { 113 | * $value = $store->getOrFail($key); 114 | * } catch (ReadException $e) { 115 | * // read failed 116 | * } 117 | * ``` 118 | * 119 | * @param int|string $key The key to get. 120 | * 121 | * @return mixed The value of the key. 122 | * 123 | * @throws ReadException If the store cannot be read. 124 | * @throws NoSuchKeyException If the key was not found. 125 | * @throws InvalidKeyException If the key is not a string or integer. 126 | * @throws UnserializationFailedException If the stored value cannot be 127 | * unserialized. 128 | */ 129 | public function getOrFail($key); 130 | 131 | /** 132 | * Returns the values of multiple keys in the store. 133 | * 134 | * The passed default value is returned for keys that don't exist. 135 | * 136 | * Any integer or string value is accepted as key. If any other type is 137 | * passed for the key, an {@link InvalidKeyException} is thrown. You should 138 | * make sure that you only pass valid keys to the store. 139 | * 140 | * If the backend of the store cannot be read, a {@link ReadException} 141 | * is thrown. You should always handle this exception in your code: 142 | * 143 | * ```php 144 | * try { 145 | * $value = $store->getMultiple(array($key1, $key2)); 146 | * } catch (ReadException $e) { 147 | * // read failed 148 | * } 149 | * ``` 150 | * 151 | * @param array $keys The keys to get. The keys must be strings or integers. 152 | * @param mixed $default The default value to return for keys that are not 153 | * found. 154 | * 155 | * @return array The values of the passed keys, indexed by the keys. 156 | * 157 | * @throws ReadException If the store cannot be read. 158 | * @throws InvalidKeyException If a key is not a string or integer. 159 | * @throws UnserializationFailedException If a stored value cannot be 160 | * unserialized. 161 | */ 162 | public function getMultiple(array $keys, $default = null); 163 | 164 | /** 165 | * Returns the values of multiple keys in the store. 166 | * 167 | * If a key does not exist in the store, an exception is thrown. 168 | * 169 | * Any integer or string value is accepted as key. If any other type is 170 | * passed for the key, an {@link InvalidKeyException} is thrown. You should 171 | * make sure that you only pass valid keys to the store. 172 | * 173 | * If the backend of the store cannot be read, a {@link ReadException} 174 | * is thrown. You should always handle this exception in your code: 175 | * 176 | * ```php 177 | * try { 178 | * $value = $store->getMultipleOrFail(array($key1, $key2)); 179 | * } catch (ReadException $e) { 180 | * // read failed 181 | * } 182 | * ``` 183 | * 184 | * @param array $keys The keys to get. The keys must be strings or integers. 185 | * 186 | * @return array The values of the passed keys, indexed by the keys. 187 | * 188 | * @throws ReadException If the store cannot be read. 189 | * @throws NoSuchKeyException If a key was not found. 190 | * @throws InvalidKeyException If a key is not a string or integer. 191 | * @throws UnserializationFailedException If a stored value cannot be 192 | * unserialized. 193 | */ 194 | public function getMultipleOrFail(array $keys); 195 | 196 | /** 197 | * Removes a key from the store. 198 | * 199 | * If the store does not contain the key, this method returns `false`. 200 | * 201 | * Any integer or string value is accepted as key. If any other type is 202 | * passed for the key, an {@link InvalidKeyException} is thrown. You should 203 | * make sure that you only pass valid keys to the store. 204 | * 205 | * If the backend of the store cannot be written, a {@link WriteException} 206 | * is thrown. You should always handle this exception in your code: 207 | * 208 | * ```php 209 | * try { 210 | * $store->remove($key); 211 | * } catch (WriteException $e) { 212 | * // write failed 213 | * } 214 | * ``` 215 | * 216 | * @param int|string $key The key to remove. 217 | * 218 | * @return bool Returns `true` if a key was removed from the store. 219 | * 220 | * @throws WriteException If the store cannot be written. 221 | * @throws InvalidKeyException If the key is not a string or integer. 222 | */ 223 | public function remove($key); 224 | 225 | /** 226 | * Returns whether a key exists. 227 | * 228 | * Any integer or string value is accepted as key. If any other type is 229 | * passed for the key, an {@link InvalidKeyException} is thrown. You should 230 | * make sure that you only pass valid keys to the store. 231 | * 232 | * If the backend of the store cannot be read, a {@link ReadException} 233 | * is thrown. You should always handle this exception in your code: 234 | * 235 | * ```php 236 | * try { 237 | * if ($store->exists($key)) { 238 | * // ... 239 | * } 240 | * } catch (ReadException $e) { 241 | * // read failed 242 | * } 243 | * ``` 244 | * 245 | * @param int|string $key The key to test. 246 | * 247 | * @return bool Whether the key exists in the store. 248 | * 249 | * @throws ReadException If the store cannot be read. 250 | * @throws InvalidKeyException If the key is not a string or integer. 251 | */ 252 | public function exists($key); 253 | 254 | /** 255 | * Removes all keys from the store. 256 | * 257 | * If the backend of the store cannot be written, a {@link WriteException} 258 | * is thrown. You should always handle this exception in your code: 259 | * 260 | * ```php 261 | * try { 262 | * $store->clear(); 263 | * } catch (WriteException $e) { 264 | * // write failed 265 | * } 266 | * ``` 267 | * 268 | * @throws WriteException If the store cannot be written. 269 | */ 270 | public function clear(); 271 | 272 | /** 273 | * Returns all keys currently stored in the store. 274 | * 275 | * If the backend of the store cannot be read, a {@link ReadException} 276 | * is thrown. You should always handle this exception in your code: 277 | * 278 | * ```php 279 | * try { 280 | * foreach ($store->keys() as $key) { 281 | * // ... 282 | * } 283 | * } catch (ReadException $e) { 284 | * // read failed 285 | * } 286 | * ``` 287 | * 288 | * @return array The keys stored in the store. Each key is either a string 289 | * or an integer. The order of the keys is undefined. 290 | * 291 | * @throws ReadException If the store cannot be read. 292 | */ 293 | public function keys(); 294 | } 295 | -------------------------------------------------------------------------------- /src/Api/NoSuchKeyException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Api; 13 | 14 | use Exception; 15 | use RuntimeException; 16 | 17 | /** 18 | * Thrown when a key was not found in the store. 19 | * 20 | * @since 1.0 21 | * 22 | * @author Bernhard Schussek 23 | */ 24 | class NoSuchKeyException extends RuntimeException 25 | { 26 | /** 27 | * Creates an exception for a key that was not found. 28 | * 29 | * @param string|int $key The key that was not found. 30 | * @param Exception|null $cause The exception that caused this exception. 31 | * 32 | * @return static The created exception. 33 | */ 34 | public static function forKey($key, Exception $cause = null) 35 | { 36 | return new static(sprintf( 37 | 'The key "%s" does not exist.', 38 | $key 39 | ), 0, $cause); 40 | } 41 | 42 | /** 43 | * Creates an exception for multiple keys that were not found. 44 | * 45 | * @param array[] $keys The keys that were not found. 46 | * @param Exception|null $cause The exception that caused this exception. 47 | * 48 | * @return static The created exception. 49 | */ 50 | public static function forKeys(array $keys, Exception $cause = null) 51 | { 52 | return new static(sprintf( 53 | 'The keys "%s" does not exist.', 54 | implode('", "', $keys) 55 | ), 0, $cause); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Api/ReadException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Api; 13 | 14 | use Exception; 15 | use RuntimeException; 16 | 17 | /** 18 | * Thrown when a key-value store cannot be read. 19 | * 20 | * @since 1.0 21 | * 22 | * @author Bernhard Schussek 23 | */ 24 | class ReadException extends RuntimeException 25 | { 26 | /** 27 | * Creates a new exception.. 28 | * 29 | * @param Exception $exception The exception that caused this exception. 30 | * 31 | * @return static The new exception. 32 | */ 33 | public static function forException(Exception $exception) 34 | { 35 | return new static(sprintf( 36 | 'Could not read key-value store: %s', 37 | $exception->getMessage() 38 | ), $exception->getCode(), $exception); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Api/SerializationFailedException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Api; 13 | 14 | use Exception; 15 | use RuntimeException; 16 | 17 | /** 18 | * Thrown when a value cannot be serialized. 19 | * 20 | * @since 1.0 21 | * 22 | * @author Bernhard Schussek 23 | */ 24 | class SerializationFailedException extends RuntimeException 25 | { 26 | /** 27 | * Creates a new exception for the given value. 28 | * 29 | * @param mixed $value The value that could not be serialized. 30 | * @param string $reason The reason why the value could not be 31 | * unserialized. 32 | * @param int $code The exception code. 33 | * @param Exception $cause The exception that caused this exception. 34 | * 35 | * @return static The new exception. 36 | */ 37 | public static function forValue($value, $reason = '', $code = 0, Exception $cause = null) 38 | { 39 | return self::forType(is_object($value) ? get_class($value) : gettype($value), $reason, $code, $cause); 40 | } 41 | 42 | /** 43 | * Creates a new exception for the given value type. 44 | * 45 | * @param string $type The type that could not be serialized. 46 | * @param string $reason The reason why the value could not be 47 | * unserialized. 48 | * @param int $code The exception code. 49 | * @param Exception $cause The exception that caused this exception. 50 | * 51 | * @return static The new exception. 52 | */ 53 | public static function forType($type, $reason = '', $code = 0, Exception $cause = null) 54 | { 55 | return new static(sprintf( 56 | 'Could not serialize value of type %s%s', 57 | $type, 58 | $reason ? ': '.$reason : '.' 59 | ), $code, $cause); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Api/SortableStore.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Api; 13 | 14 | /** 15 | * A sortable key-value store. 16 | * 17 | * In addition of the properties of a classical store, a sortable store 18 | * has the ability to sort its values by its keys. 19 | * 20 | * @since 1.0 21 | * 22 | * @author Bernhard Schussek 23 | * @author Titouan Galopin 24 | */ 25 | interface SortableStore extends KeyValueStore 26 | { 27 | /** 28 | * Sort the store by its keys. 29 | * 30 | * The store values will be arranged from lowest to highest when this 31 | * function has completed. 32 | * 33 | * This method accepts an optional second parameter that may be used 34 | * to modify the sorting behavior using the standard sort flags of PHP. 35 | * 36 | * @see http://php.net/manual/en/function.sort.php 37 | * 38 | * @param int $flags Sorting type flags (from the standard PHP sort flags). 39 | * 40 | * @throws ReadException If the store cannot be read. 41 | * @throws WriteException If the store cannot be written. 42 | */ 43 | public function sort($flags = SORT_REGULAR); 44 | } 45 | -------------------------------------------------------------------------------- /src/Api/UnserializationFailedException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Api; 13 | 14 | use Exception; 15 | use RuntimeException; 16 | 17 | /** 18 | * Thrown when a value cannot be unserialized. 19 | * 20 | * @since 1.0 21 | * 22 | * @author Bernhard Schussek 23 | */ 24 | class UnserializationFailedException extends RuntimeException 25 | { 26 | /** 27 | * Creates a new exception for the given value. 28 | * 29 | * @param mixed $value The value that could not be unserialized. 30 | * @param string $reason The reason why the value could not be 31 | * unserialized. 32 | * @param int $code The exception code. 33 | * @param Exception $cause The exception that caused this exception. 34 | * 35 | * @return static The new exception. 36 | */ 37 | public static function forValue($value, $reason = '', $code = 0, Exception $cause = null) 38 | { 39 | return self::forType(is_object($value) ? get_class($value) : gettype($value), $reason, $code, $cause); 40 | } 41 | 42 | /** 43 | * Creates a new exception for the given value type. 44 | * 45 | * @param string $type The type that could not be unserialized. 46 | * @param string $reason The reason why the value could not be 47 | * unserialized. 48 | * @param int $code The exception code. 49 | * @param Exception $cause The exception that caused this exception. 50 | * 51 | * @return static The new exception. 52 | */ 53 | public static function forType($type, $reason = '', $code = 0, Exception $cause = null) 54 | { 55 | return new static(sprintf( 56 | 'Could not unserialize value of type %s%s', 57 | $type, 58 | $reason ? ': '.$reason : '.' 59 | ), $code, $cause); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Api/UnsupportedValueException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Api; 13 | 14 | use Exception; 15 | use RuntimeException; 16 | 17 | /** 18 | * Thrown when an unsupported value is stored in a key-value store. 19 | * 20 | * @since 1.0 21 | * 22 | * @author Bernhard Schussek 23 | */ 24 | class UnsupportedValueException extends RuntimeException 25 | { 26 | /** 27 | * Creates a new exception for the given value type. 28 | * 29 | * @param string $type The name of the unsupported type. 30 | * @param KeyValueStore $store The store that does not support the type. 31 | * @param int $code The exception code. 32 | * @param Exception|null $cause The exception that caused this exception. 33 | * 34 | * @return static The new exception. 35 | */ 36 | public static function forType($type, KeyValueStore $store, $code = 0, Exception $cause = null) 37 | { 38 | return new static(sprintf( 39 | 'Values of type %s are not supported by %s.', 40 | $type, 41 | get_class($store) 42 | ), $code, $cause); 43 | } 44 | 45 | /** 46 | * Creates a new exception for the given value. 47 | * 48 | * @param string $value The unsupported value. 49 | * @param KeyValueStore $store The store that does not support the type. 50 | * @param int $code The exception code. 51 | * @param Exception|null $cause The exception that caused this exception. 52 | * 53 | * @return static The new exception. 54 | */ 55 | public static function forValue($value, KeyValueStore $store, $code = 0, Exception $cause = null) 56 | { 57 | return new static(sprintf( 58 | 'Values of type %s are not supported by %s.', 59 | gettype($value), 60 | get_class($store) 61 | ), $code, $cause); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Api/WriteException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Api; 13 | 14 | use Exception; 15 | use RuntimeException; 16 | 17 | /** 18 | * Thrown when a key-value store cannot be written. 19 | * 20 | * @since 1.0 21 | * 22 | * @author Bernhard Schussek 23 | */ 24 | class WriteException extends RuntimeException 25 | { 26 | /** 27 | * Creates a new exception.. 28 | * 29 | * @param Exception $exception The exception that caused this exception. 30 | * 31 | * @return static The new exception. 32 | */ 33 | public static function forException(Exception $exception) 34 | { 35 | return new static(sprintf( 36 | 'Could not write key-value store: %s', 37 | $exception->getMessage() 38 | ), $exception->getCode(), $exception); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/ArrayStore.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore; 13 | 14 | use Webmozart\KeyValueStore\Api\CountableStore; 15 | use Webmozart\KeyValueStore\Api\NoSuchKeyException; 16 | use Webmozart\KeyValueStore\Api\SortableStore; 17 | use Webmozart\KeyValueStore\Util\KeyUtil; 18 | 19 | /** 20 | * A key-value store backed by a PHP array. 21 | * 22 | * The contents of the store are lost when the store is released from memory. 23 | * 24 | * @since 1.0 25 | * 26 | * @author Bernhard Schussek 27 | */ 28 | class ArrayStore implements SortableStore, CountableStore 29 | { 30 | /** 31 | * Flag: Enable serialization. 32 | */ 33 | const SERIALIZE = 1; 34 | 35 | /** 36 | * @var array 37 | */ 38 | private $array = array(); 39 | 40 | /** 41 | * @var callable 42 | */ 43 | private $serialize; 44 | 45 | /** 46 | * @var callable 47 | */ 48 | private $unserialize; 49 | 50 | /** 51 | * Creates a new store. 52 | * 53 | * @param array $array The values to set initially in the store. 54 | */ 55 | public function __construct(array $array = array(), $flags = 0) 56 | { 57 | if ($flags & self::SERIALIZE) { 58 | $this->serialize = array( 59 | 'Webmozart\KeyValueStore\Util\Serializer', 60 | 'serialize', 61 | ); 62 | $this->unserialize = array( 63 | 'Webmozart\KeyValueStore\Util\Serializer', 64 | 'unserialize', 65 | ); 66 | } else { 67 | $this->serialize = function ($value) { 68 | return $value; 69 | }; 70 | $this->unserialize = function ($value) { 71 | return $value; 72 | }; 73 | } 74 | 75 | foreach ($array as $key => $value) { 76 | $this->set($key, $value); 77 | } 78 | } 79 | 80 | /** 81 | * {@inheritdoc} 82 | */ 83 | public function set($key, $value) 84 | { 85 | KeyUtil::validate($key); 86 | 87 | $this->array[$key] = call_user_func($this->serialize, $value); 88 | } 89 | 90 | /** 91 | * {@inheritdoc} 92 | */ 93 | public function get($key, $default = null) 94 | { 95 | KeyUtil::validate($key); 96 | 97 | if (!array_key_exists($key, $this->array)) { 98 | return $default; 99 | } 100 | 101 | return call_user_func($this->unserialize, $this->array[$key]); 102 | } 103 | 104 | /** 105 | * {@inheritdoc} 106 | */ 107 | public function getOrFail($key) 108 | { 109 | KeyUtil::validate($key); 110 | 111 | if (!array_key_exists($key, $this->array)) { 112 | throw NoSuchKeyException::forKey($key); 113 | } 114 | 115 | return call_user_func($this->unserialize, $this->array[$key]); 116 | } 117 | 118 | /** 119 | * {@inheritdoc} 120 | */ 121 | public function getMultiple(array $keys, $default = null) 122 | { 123 | KeyUtil::validateMultiple($keys); 124 | 125 | $values = array(); 126 | 127 | foreach ($keys as $key) { 128 | $values[$key] = array_key_exists($key, $this->array) 129 | ? call_user_func($this->unserialize, $this->array[$key]) 130 | : $default; 131 | } 132 | 133 | return $values; 134 | } 135 | 136 | /** 137 | * {@inheritdoc} 138 | */ 139 | public function getMultipleOrFail(array $keys) 140 | { 141 | KeyUtil::validateMultiple($keys); 142 | 143 | $notFoundKeys = array_diff($keys, array_keys($this->array)); 144 | 145 | if (count($notFoundKeys) > 0) { 146 | throw NoSuchKeyException::forKeys($notFoundKeys); 147 | } 148 | 149 | return $this->getMultiple($keys); 150 | } 151 | 152 | /** 153 | * {@inheritdoc} 154 | */ 155 | public function remove($key) 156 | { 157 | KeyUtil::validate($key); 158 | 159 | $removed = array_key_exists($key, $this->array); 160 | 161 | unset($this->array[$key]); 162 | 163 | return $removed; 164 | } 165 | 166 | /** 167 | * {@inheritdoc} 168 | */ 169 | public function exists($key) 170 | { 171 | KeyUtil::validate($key); 172 | 173 | return array_key_exists($key, $this->array); 174 | } 175 | 176 | /** 177 | * {@inheritdoc} 178 | */ 179 | public function clear() 180 | { 181 | $this->array = array(); 182 | } 183 | 184 | /** 185 | * {@inheritdoc} 186 | */ 187 | public function keys() 188 | { 189 | return array_keys($this->array); 190 | } 191 | 192 | /** 193 | * Returns the contents of the store as array. 194 | * 195 | * @return array The keys and values in the store. 196 | */ 197 | public function toArray() 198 | { 199 | return array_map($this->unserialize, $this->array); 200 | } 201 | 202 | /** 203 | * {@inheritdoc} 204 | */ 205 | public function sort($flags = SORT_REGULAR) 206 | { 207 | ksort($this->array, $flags); 208 | } 209 | 210 | /** 211 | * {@inheritdoc} 212 | */ 213 | public function count() 214 | { 215 | return count($this->array); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/DbalStore.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore; 13 | 14 | use Doctrine\DBAL\Connection; 15 | use Doctrine\DBAL\Schema\Schema; 16 | use Doctrine\DBAL\Schema\Table; 17 | use Exception; 18 | use PDO; 19 | use Webmozart\Assert\Assert; 20 | use Webmozart\KeyValueStore\Api\KeyValueStore; 21 | use Webmozart\KeyValueStore\Api\NoSuchKeyException; 22 | use Webmozart\KeyValueStore\Api\ReadException; 23 | use Webmozart\KeyValueStore\Api\WriteException; 24 | use Webmozart\KeyValueStore\Util\KeyUtil; 25 | use Webmozart\KeyValueStore\Util\Serializer; 26 | 27 | /** 28 | * A key-value store backed by Doctrine DBAL. 29 | * 30 | * @since 1.0 31 | * 32 | * @author Bernhard Schussek 33 | * @author Michiel Boeckaert 34 | */ 35 | class DbalStore implements KeyValueStore 36 | { 37 | private $connection; 38 | private $tableName; 39 | 40 | /** 41 | * @param Connection $connection A doctrine connection instance 42 | * @param string $tableName The name of the database table 43 | */ 44 | public function __construct(Connection $connection, $tableName = 'store') 45 | { 46 | Assert::stringNotEmpty($tableName, 'The table must be a string. Got: %s'); 47 | 48 | $this->connection = $connection; 49 | $this->tableName = $tableName; 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | public function set($key, $value) 56 | { 57 | KeyUtil::validate($key); 58 | 59 | try { 60 | $existing = $this->exists($key); 61 | } catch (Exception $e) { 62 | throw WriteException::forException($e); 63 | } 64 | 65 | if (false === $existing) { 66 | $this->doInsert($key, $value); 67 | } else { 68 | $this->doUpdate($key, $value); 69 | } 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | public function get($key, $default = null) 76 | { 77 | KeyUtil::validate($key); 78 | 79 | $dbResult = $this->getDbRow($key); 80 | 81 | if (null === $dbResult) { 82 | return $default; 83 | } 84 | 85 | return Serializer::unserialize($dbResult['meta_value']); 86 | } 87 | 88 | /** 89 | * {@inheritdoc} 90 | */ 91 | public function getOrFail($key) 92 | { 93 | KeyUtil::validate($key); 94 | 95 | $dbResult = $this->getDbRow($key); 96 | 97 | if (null === $dbResult) { 98 | throw NoSuchKeyException::forKey($key); 99 | } 100 | 101 | return Serializer::unserialize($dbResult['meta_value']); 102 | } 103 | 104 | /** 105 | * {@inheritdoc} 106 | */ 107 | public function getMultiple(array $keys, $default = null) 108 | { 109 | KeyUtil::validateMultiple($keys); 110 | 111 | // Normalize indices of the array 112 | $keys = array_values($keys); 113 | $data = $this->doGetMultiple($keys); 114 | 115 | $results = array(); 116 | $resolved = array(); 117 | foreach ($data as $row) { 118 | $results[$row['meta_key']] = Serializer::unserialize($row['meta_value']); 119 | $resolved[$row['meta_key']] = $row['meta_key']; 120 | } 121 | 122 | $notResolvedArr = array_diff($keys, $resolved); 123 | foreach ($notResolvedArr as $notResolved) { 124 | $results[$notResolved] = $default; 125 | } 126 | 127 | return $results; 128 | } 129 | 130 | /** 131 | * {@inheritdoc} 132 | */ 133 | public function getMultipleOrFail(array $keys) 134 | { 135 | KeyUtil::validateMultiple($keys); 136 | 137 | // Normalize indices of the array 138 | $keys = array_values($keys); 139 | 140 | $data = $this->doGetMultiple($keys); 141 | 142 | $results = array(); 143 | $resolved = array(); 144 | foreach ($data as $row) { 145 | $results[$row['meta_key']] = Serializer::unserialize($row['meta_value']); 146 | $resolved[] = $row['meta_key']; 147 | } 148 | 149 | $notResolvedArr = array_diff($keys, $resolved); 150 | 151 | if (!empty($notResolvedArr)) { 152 | throw NoSuchKeyException::forKeys($notResolvedArr); 153 | } 154 | 155 | return $results; 156 | } 157 | 158 | /** 159 | * {@inheritdoc} 160 | */ 161 | public function remove($key) 162 | { 163 | KeyUtil::validate($key); 164 | 165 | try { 166 | $result = $this->connection->delete($this->tableName, array('meta_key' => $key)); 167 | } catch (Exception $e) { 168 | throw WriteException::forException($e); 169 | } 170 | 171 | return $result === 1; 172 | } 173 | 174 | /** 175 | * {@inheritdoc} 176 | */ 177 | public function exists($key) 178 | { 179 | KeyUtil::validate($key); 180 | 181 | try { 182 | $result = $this->connection->fetchAssoc('SELECT * FROM '.$this->tableName.' WHERE meta_key = ?', array($key)); 183 | } catch (Exception $e) { 184 | throw ReadException::forException($e); 185 | } 186 | 187 | return $result ? true : false; 188 | } 189 | 190 | /** 191 | * {@inheritdoc} 192 | */ 193 | public function clear() 194 | { 195 | try { 196 | $stmt = $this->connection->query('DELETE FROM '.$this->tableName); 197 | $stmt->execute(); 198 | } catch (Exception $e) { 199 | throw WriteException::forException($e); 200 | } 201 | } 202 | 203 | /** 204 | * {@inheritdoc} 205 | */ 206 | public function keys() 207 | { 208 | try { 209 | $stmt = $this->connection->query('SELECT meta_key FROM '.$this->tableName); 210 | $result = $stmt->fetchAll(PDO::FETCH_COLUMN); 211 | } catch (Exception $e) { 212 | throw ReadException::forException($e); 213 | } 214 | 215 | return $result; 216 | } 217 | 218 | /** 219 | * The name for our DBAL database table. 220 | * 221 | * @return string 222 | */ 223 | public function getTableName() 224 | { 225 | return $this->tableName; 226 | } 227 | 228 | /** 229 | * Object Representation of the table used in this class. 230 | * 231 | * @return Table 232 | */ 233 | public function getTableForCreate() 234 | { 235 | $schema = new Schema(); 236 | 237 | $table = $schema->createTable($this->getTableName()); 238 | 239 | $table->addColumn('id', 'integer', array('autoincrement' => true)); 240 | $table->addColumn('meta_key', 'string', array('length' => 255)); 241 | $table->addColumn('meta_value', 'object'); 242 | $table->setPrimaryKey(array('id')); 243 | $table->addUniqueIndex(array('meta_key')); 244 | 245 | return $table; 246 | } 247 | 248 | private function doInsert($key, $value) 249 | { 250 | $serialized = Serializer::serialize($value); 251 | 252 | try { 253 | $this->connection->insert($this->tableName, array( 254 | 'meta_key' => $key, 255 | 'meta_value' => $serialized, 256 | )); 257 | } catch (Exception $e) { 258 | throw WriteException::forException($e); 259 | } 260 | } 261 | 262 | private function doUpdate($key, $value) 263 | { 264 | $serialized = Serializer::serialize($value); 265 | 266 | try { 267 | $this->connection->update($this->tableName, array( 268 | 'meta_value' => $serialized, 269 | ), array( 270 | 'meta_key' => $key, 271 | )); 272 | } catch (Exception $e) { 273 | throw WriteException::forException($e); 274 | } 275 | } 276 | 277 | private function doGetMultiple(array $keys) 278 | { 279 | try { 280 | $stmt = $this->connection->executeQuery('SELECT * FROM '.$this->tableName.' WHERE meta_key IN (?)', 281 | array($keys), 282 | array(Connection::PARAM_STR_ARRAY) 283 | ); 284 | $data = $stmt->fetchAll(); 285 | } catch (Exception $e) { 286 | throw ReadException::forException($e); 287 | } 288 | 289 | return is_array($data) ? $data : array(); 290 | } 291 | 292 | private function getDbRow($key) 293 | { 294 | try { 295 | $dbResult = $this->connection->fetchAssoc('SELECT meta_value, meta_key FROM '.$this->tableName.' WHERE meta_key = ?', array($key)); 296 | } catch (Exception $e) { 297 | throw ReadException::forException($e); 298 | } 299 | 300 | if (empty($dbResult)) { 301 | return null; 302 | } 303 | 304 | return $dbResult; 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /src/Decorator/AbstractDecorator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Decorator; 13 | 14 | use Webmozart\KeyValueStore\Api\KeyValueStore; 15 | 16 | /** 17 | * A delegating decorator delegate each call of a KeyValueStore method 18 | * to the internal store. 19 | * 20 | * It is used by decorators that need to override only a few specific 21 | * methods (such as SortableDecorator or CountableDecorator). 22 | * 23 | * @since 1.0 24 | * 25 | * @author Bernhard Schussek 26 | * @author Titouan Galopin 27 | */ 28 | class AbstractDecorator implements KeyValueStore 29 | { 30 | /** 31 | * @var KeyValueStore 32 | */ 33 | protected $store; 34 | 35 | /** 36 | * Creates the store. 37 | * 38 | * @param KeyValueStore $store The store to sort. 39 | */ 40 | public function __construct(KeyValueStore $store) 41 | { 42 | $this->store = $store; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function set($key, $value) 49 | { 50 | $this->store->set($key, $value); 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function get($key, $default = null) 57 | { 58 | return $this->store->get($key, $default); 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | public function getOrFail($key) 65 | { 66 | return $this->store->getOrFail($key); 67 | } 68 | 69 | /** 70 | * {@inheritdoc} 71 | */ 72 | public function getMultiple(array $keys, $default = null) 73 | { 74 | return $this->store->getMultiple($keys, $default); 75 | } 76 | 77 | /** 78 | * {@inheritdoc} 79 | */ 80 | public function getMultipleOrFail(array $keys) 81 | { 82 | return $this->store->getMultipleOrFail($keys); 83 | } 84 | 85 | /** 86 | * {@inheritdoc} 87 | */ 88 | public function remove($key) 89 | { 90 | $this->store->remove($key); 91 | } 92 | 93 | /** 94 | * {@inheritdoc} 95 | */ 96 | public function exists($key) 97 | { 98 | return $this->store->exists($key); 99 | } 100 | 101 | /** 102 | * {@inheritdoc} 103 | */ 104 | public function clear() 105 | { 106 | $this->store->clear(); 107 | } 108 | 109 | /** 110 | * {@inheritdoc} 111 | */ 112 | public function keys() 113 | { 114 | return $this->store->keys(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Decorator/CachingDecorator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Decorator; 13 | 14 | use Doctrine\Common\Cache\Cache; 15 | use Doctrine\Common\Cache\ClearableCache; 16 | use Doctrine\Common\Cache\FlushableCache; 17 | use InvalidArgumentException; 18 | use Webmozart\KeyValueStore\Api\KeyValueStore; 19 | use Webmozart\KeyValueStore\Api\NoSuchKeyException; 20 | 21 | /** 22 | * A caching decorator implementing a cache layer for any store. 23 | * 24 | * @since 1.0 25 | * 26 | * @author Bernhard Schussek 27 | */ 28 | class CachingDecorator extends AbstractDecorator 29 | { 30 | /** 31 | * @var Cache 32 | */ 33 | private $cache; 34 | 35 | private $ttl; 36 | 37 | /** 38 | * Creates the store. 39 | * 40 | * @param KeyValueStore $store The cached store. 41 | * @param Cache $cache The cache. 42 | * @param int $ttl The time-to-live for cache entries. If set to 43 | * 0, cache entries never expire. 44 | * 45 | * @throws InvalidArgumentException If the provided cache is not supported 46 | */ 47 | public function __construct(KeyValueStore $store, Cache $cache, $ttl = 0) 48 | { 49 | if (!$cache instanceof ClearableCache && !$cache instanceof FlushableCache) { 50 | throw new InvalidArgumentException(sprintf( 51 | 'The cache must either implement ClearableCache or '. 52 | 'FlushableCache. Got: %s', 53 | get_class($cache) 54 | )); 55 | } 56 | 57 | parent::__construct($store); 58 | 59 | $this->cache = $cache; 60 | $this->ttl = $ttl; 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | public function set($key, $value) 67 | { 68 | $this->store->set($key, $value); 69 | $this->cache->save($key, $value, $this->ttl); 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | public function get($key, $default = null) 76 | { 77 | if ($this->cache->contains($key)) { 78 | return $this->cache->fetch($key); 79 | } 80 | 81 | try { 82 | $value = $this->store->getOrFail($key); 83 | } catch (NoSuchKeyException $e) { 84 | return $default; 85 | } 86 | 87 | $this->cache->save($key, $value, $this->ttl); 88 | 89 | return $value; 90 | } 91 | 92 | /** 93 | * {@inheritdoc} 94 | */ 95 | public function getOrFail($key) 96 | { 97 | if ($this->cache->contains($key)) { 98 | return $this->cache->fetch($key); 99 | } 100 | 101 | $value = $this->store->getOrFail($key); 102 | 103 | $this->cache->save($key, $value, $this->ttl); 104 | 105 | return $value; 106 | } 107 | 108 | /** 109 | * {@inheritdoc} 110 | */ 111 | public function getMultiple(array $keys, $default = null) 112 | { 113 | $values = array(); 114 | 115 | // Read cached values from the cache 116 | foreach ($keys as $i => $key) { 117 | if ($this->cache->contains($key)) { 118 | $values[$key] = $this->cache->fetch($key); 119 | unset($keys[$i]); 120 | } 121 | } 122 | 123 | // Don't write cache, as we can't differentiate between existing and 124 | // non-existing keys 125 | return array_replace($values, $this->store->getMultiple($keys, $default)); 126 | } 127 | 128 | /** 129 | * {@inheritdoc} 130 | */ 131 | public function getMultipleOrFail(array $keys) 132 | { 133 | $values = array(); 134 | 135 | // Read cached values from the cache 136 | foreach ($keys as $i => $key) { 137 | if ($this->cache->contains($key)) { 138 | $values[$key] = $this->cache->fetch($key); 139 | unset($keys[$i]); 140 | } 141 | } 142 | 143 | $values = array_replace($values, $this->store->getMultipleOrFail($keys)); 144 | 145 | // Write newly fetched values to the cache 146 | foreach ($keys as $key) { 147 | $this->cache->save($key, $values[$key], $this->ttl); 148 | } 149 | 150 | return $values; 151 | } 152 | 153 | /** 154 | * {@inheritdoc} 155 | */ 156 | public function remove($key) 157 | { 158 | $this->store->remove($key); 159 | $this->cache->delete($key); 160 | } 161 | 162 | /** 163 | * {@inheritdoc} 164 | */ 165 | public function exists($key) 166 | { 167 | if ($this->cache->contains($key)) { 168 | return true; 169 | } 170 | 171 | return $this->store->exists($key); 172 | } 173 | 174 | /** 175 | * {@inheritdoc} 176 | */ 177 | public function clear() 178 | { 179 | $this->store->clear(); 180 | 181 | if ($this->cache instanceof ClearableCache) { 182 | $this->cache->deleteAll(); 183 | } else { 184 | $this->cache->flushAll(); 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/Decorator/CountableDecorator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Decorator; 13 | 14 | use Webmozart\KeyValueStore\Api\CountableStore; 15 | 16 | /** 17 | * A countable decorator implementing a count system for any store. 18 | * 19 | * @since 1.0 20 | * 21 | * @author Bernhard Schussek 22 | * @author Titouan Galopin 23 | */ 24 | class CountableDecorator extends AbstractDecorator implements CountableStore 25 | { 26 | /** 27 | * In-memory cache invalidated on store modification. 28 | * 29 | * @var int 30 | */ 31 | private $cache; 32 | 33 | /** 34 | * Is the cache fresh enough to be served? 35 | * 36 | * @var bool 37 | */ 38 | private $cacheIsFresh = false; 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function set($key, $value) 44 | { 45 | $this->cacheIsFresh = false; 46 | $this->store->set($key, $value); 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function remove($key) 53 | { 54 | $this->cacheIsFresh = false; 55 | $this->store->remove($key); 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | public function clear() 62 | { 63 | $this->cacheIsFresh = false; 64 | $this->store->clear(); 65 | } 66 | 67 | /** 68 | * {@inheritdoc} 69 | */ 70 | public function count() 71 | { 72 | if (!$this->cacheIsFresh) { 73 | $this->cache = count($this->store->keys()); 74 | $this->cacheIsFresh = true; 75 | } 76 | 77 | return $this->cache; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Decorator/SortableDecorator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Decorator; 13 | 14 | use Webmozart\KeyValueStore\Api\SortableStore; 15 | 16 | /** 17 | * A sortable decorator implementing a sort system for any store. 18 | * 19 | * @since 1.0 20 | * 21 | * @author Bernhard Schussek 22 | * @author Titouan Galopin 23 | */ 24 | class SortableDecorator extends AbstractDecorator implements SortableStore 25 | { 26 | /** 27 | * @var int 28 | */ 29 | private $flags; 30 | 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | public function sort($flags = SORT_REGULAR) 35 | { 36 | $this->flags = $flags; 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | public function set($key, $value) 43 | { 44 | $this->flags = null; 45 | $this->store->set($key, $value); 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public function keys() 52 | { 53 | $keys = $this->store->keys(); 54 | 55 | if (null !== $this->flags) { 56 | sort($keys, $this->flags); 57 | } 58 | 59 | return $keys; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/JsonFileStore.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore; 13 | 14 | use stdClass; 15 | use Webmozart\Assert\Assert; 16 | use Webmozart\Json\DecodingFailedException; 17 | use Webmozart\Json\EncodingFailedException; 18 | use Webmozart\Json\FileNotFoundException; 19 | use Webmozart\Json\IOException; 20 | use Webmozart\Json\JsonDecoder; 21 | use Webmozart\Json\JsonEncoder; 22 | use Webmozart\KeyValueStore\Api\CountableStore; 23 | use Webmozart\KeyValueStore\Api\NoSuchKeyException; 24 | use Webmozart\KeyValueStore\Api\ReadException; 25 | use Webmozart\KeyValueStore\Api\SortableStore; 26 | use Webmozart\KeyValueStore\Api\UnsupportedValueException; 27 | use Webmozart\KeyValueStore\Api\WriteException; 28 | use Webmozart\KeyValueStore\Util\KeyUtil; 29 | use Webmozart\KeyValueStore\Util\Serializer; 30 | 31 | /** 32 | * A key-value store backed by a JSON file. 33 | * 34 | * @since 1.0 35 | * 36 | * @author Bernhard Schussek 37 | */ 38 | class JsonFileStore implements SortableStore, CountableStore 39 | { 40 | /** 41 | * Flag: Disable serialization of strings. 42 | */ 43 | const NO_SERIALIZE_STRINGS = 1; 44 | 45 | /** 46 | * Flag: Disable serialization of arrays. 47 | */ 48 | const NO_SERIALIZE_ARRAYS = 2; 49 | 50 | /** 51 | * Flag: Escape ">" and "<". 52 | */ 53 | const ESCAPE_GT_LT = 4; 54 | 55 | /** 56 | * Flag: Escape "&". 57 | */ 58 | const ESCAPE_AMPERSAND = 8; 59 | 60 | /** 61 | * Flag: Escape single quotes. 62 | */ 63 | const ESCAPE_SINGLE_QUOTE = 16; 64 | 65 | /** 66 | * Flag: Escape double quotes. 67 | */ 68 | const ESCAPE_DOUBLE_QUOTE = 32; 69 | 70 | /** 71 | * Flag: Don't escape forward slashes. 72 | */ 73 | const NO_ESCAPE_SLASH = 64; 74 | 75 | /** 76 | * Flag: Don't escape Unicode characters. 77 | */ 78 | const NO_ESCAPE_UNICODE = 128; 79 | 80 | /** 81 | * Flag: Format the JSON nicely. 82 | */ 83 | const PRETTY_PRINT = 256; 84 | 85 | /** 86 | * Flag: Terminate the JSON with a line feed. 87 | */ 88 | const TERMINATE_WITH_LINE_FEED = 512; 89 | 90 | /** 91 | * This seems to be the biggest float supported by json_encode()/json_decode(). 92 | */ 93 | const MAX_FLOAT = 1.0E+14; 94 | 95 | /** 96 | * @var string 97 | */ 98 | private $path; 99 | 100 | /** 101 | * @var int 102 | */ 103 | private $flags; 104 | 105 | /** 106 | * @var JsonEncoder 107 | */ 108 | private $encoder; 109 | 110 | /** 111 | * @var JsonDecoder 112 | */ 113 | private $decoder; 114 | 115 | public function __construct($path, $flags = 0) 116 | { 117 | Assert::string($path, 'The path must be a string. Got: %s'); 118 | Assert::notEmpty($path, 'The path must not be empty.'); 119 | Assert::integer($flags, 'The flags must be an integer. Got: %s'); 120 | 121 | $this->path = $path; 122 | $this->flags = $flags; 123 | 124 | $this->encoder = new JsonEncoder(); 125 | $this->encoder->setEscapeGtLt($this->flags & self::ESCAPE_GT_LT); 126 | $this->encoder->setEscapeAmpersand($this->flags & self::ESCAPE_AMPERSAND); 127 | $this->encoder->setEscapeSingleQuote($this->flags & self::ESCAPE_SINGLE_QUOTE); 128 | $this->encoder->setEscapeDoubleQuote($this->flags & self::ESCAPE_DOUBLE_QUOTE); 129 | $this->encoder->setEscapeSlash(!($this->flags & self::NO_ESCAPE_SLASH)); 130 | $this->encoder->setEscapeUnicode(!($this->flags & self::NO_ESCAPE_UNICODE)); 131 | $this->encoder->setPrettyPrinting($this->flags & self::PRETTY_PRINT); 132 | $this->encoder->setTerminateWithLineFeed($this->flags & self::TERMINATE_WITH_LINE_FEED); 133 | 134 | $this->decoder = new JsonDecoder(); 135 | $this->decoder->setObjectDecoding(JsonDecoder::ASSOC_ARRAY); 136 | } 137 | 138 | /** 139 | * {@inheritdoc} 140 | */ 141 | public function set($key, $value) 142 | { 143 | KeyUtil::validate($key); 144 | 145 | if (is_float($value) && $value > self::MAX_FLOAT) { 146 | throw new UnsupportedValueException('The JSON file store cannot handle floats larger than 1.0E+14.'); 147 | } 148 | 149 | $data = $this->load(); 150 | $data[$key] = $this->serializeValue($value); 151 | 152 | $this->save($data); 153 | } 154 | 155 | /** 156 | * {@inheritdoc} 157 | */ 158 | public function get($key, $default = null) 159 | { 160 | KeyUtil::validate($key); 161 | 162 | $data = $this->load(); 163 | 164 | if (!array_key_exists($key, $data)) { 165 | return $default; 166 | } 167 | 168 | return $this->unserializeValue($data[$key]); 169 | } 170 | 171 | /** 172 | * {@inheritdoc} 173 | */ 174 | public function getOrFail($key) 175 | { 176 | KeyUtil::validate($key); 177 | 178 | $data = $this->load(); 179 | 180 | if (!array_key_exists($key, $data)) { 181 | throw NoSuchKeyException::forKey($key); 182 | } 183 | 184 | return $this->unserializeValue($data[$key]); 185 | } 186 | 187 | /** 188 | * {@inheritdoc} 189 | */ 190 | public function getMultiple(array $keys, $default = null) 191 | { 192 | $values = array(); 193 | $data = $this->load(); 194 | 195 | foreach ($keys as $key) { 196 | KeyUtil::validate($key); 197 | 198 | if (array_key_exists($key, $data)) { 199 | $value = $this->unserializeValue($data[$key]); 200 | } else { 201 | $value = $default; 202 | } 203 | 204 | $values[$key] = $value; 205 | } 206 | 207 | return $values; 208 | } 209 | 210 | /** 211 | * {@inheritdoc} 212 | */ 213 | public function getMultipleOrFail(array $keys) 214 | { 215 | $values = array(); 216 | $data = $this->load(); 217 | 218 | foreach ($keys as $key) { 219 | KeyUtil::validate($key); 220 | 221 | if (!array_key_exists($key, $data)) { 222 | throw NoSuchKeyException::forKey($key); 223 | } 224 | 225 | $values[$key] = $this->unserializeValue($data[$key]); 226 | } 227 | 228 | return $values; 229 | } 230 | 231 | /** 232 | * {@inheritdoc} 233 | */ 234 | public function remove($key) 235 | { 236 | KeyUtil::validate($key); 237 | 238 | $data = $this->load(); 239 | 240 | if (!array_key_exists($key, $data)) { 241 | return false; 242 | } 243 | 244 | unset($data[$key]); 245 | 246 | $this->save($data); 247 | 248 | return true; 249 | } 250 | 251 | /** 252 | * {@inheritdoc} 253 | */ 254 | public function exists($key) 255 | { 256 | KeyUtil::validate($key); 257 | 258 | $data = $this->load(); 259 | 260 | return array_key_exists($key, $data); 261 | } 262 | 263 | /** 264 | * {@inheritdoc} 265 | */ 266 | public function clear() 267 | { 268 | $this->save(new stdClass()); 269 | } 270 | 271 | /** 272 | * {@inheritdoc} 273 | */ 274 | public function keys() 275 | { 276 | return array_keys($this->load()); 277 | } 278 | 279 | /** 280 | * {@inheritdoc} 281 | */ 282 | public function sort($flags = SORT_REGULAR) 283 | { 284 | $data = $this->load(); 285 | 286 | ksort($data, $flags); 287 | 288 | $this->save($data); 289 | } 290 | 291 | /** 292 | * {@inheritdoc} 293 | */ 294 | public function count() 295 | { 296 | $data = $this->load(); 297 | 298 | return count($data); 299 | } 300 | 301 | private function load() 302 | { 303 | try { 304 | return $this->decoder->decodeFile($this->path); 305 | } catch (FileNotFoundException $e) { 306 | return array(); 307 | } catch (DecodingFailedException $e) { 308 | throw new ReadException($e->getMessage(), 0, $e); 309 | } catch (IOException $e) { 310 | throw new ReadException($e->getMessage(), 0, $e); 311 | } 312 | } 313 | 314 | private function save($data) 315 | { 316 | try { 317 | $this->encoder->encodeFile($data, $this->path); 318 | } catch (EncodingFailedException $e) { 319 | if (JSON_ERROR_UTF8 === $e->getCode()) { 320 | throw UnsupportedValueException::forType('binary', $this); 321 | } 322 | 323 | throw new WriteException($e->getMessage(), 0, $e); 324 | } catch (IOException $e) { 325 | throw new WriteException($e->getMessage(), 0, $e); 326 | } 327 | } 328 | 329 | private function serializeValue($value) 330 | { 331 | // Serialize if we have a string and string serialization is enabled... 332 | $serializeValue = (is_string($value) && !($this->flags & self::NO_SERIALIZE_STRINGS)) 333 | // or we have an array and array serialization is enabled... 334 | || (is_array($value) && !($this->flags & self::NO_SERIALIZE_ARRAYS)) 335 | // or we have any other non-scalar, non-null value 336 | || (null !== $value && !is_scalar($value) && !is_array($value)); 337 | 338 | if ($serializeValue) { 339 | return Serializer::serialize($value); 340 | } 341 | 342 | // If we have an array and array serialization is disabled, serialize 343 | // its entries if necessary 344 | if (is_array($value)) { 345 | return array_map(array($this, 'serializeValue'), $value); 346 | } 347 | 348 | return $value; 349 | } 350 | 351 | private function unserializeValue($value) 352 | { 353 | // Unserialize value if it is a string... 354 | $unserializeValue = is_string($value) && ( 355 | // and string serialization is enabled 356 | !($this->flags & self::NO_SERIALIZE_STRINGS) 357 | // or the string contains a serialized object 358 | || 'O:' === ($prefix = substr($value, 0, 2)) 359 | // or the string contains a serialized array when array 360 | // serialization is enabled 361 | || ('a:' === $prefix && !($this->flags & self::NO_SERIALIZE_ARRAYS)) 362 | ); 363 | 364 | if ($unserializeValue) { 365 | return Serializer::unserialize($value); 366 | } 367 | 368 | if (is_array($value)) { 369 | return array_map(array($this, 'unserializeValue'), $value); 370 | } 371 | 372 | return $value; 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /src/MongoDbStore.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore; 13 | 14 | use Closure; 15 | use Exception; 16 | use MongoDB\BSON\Binary; 17 | use MongoDB\Collection; 18 | use MongoDB\Driver\Exception\UnexpectedValueException; 19 | use Webmozart\KeyValueStore\Api\KeyValueStore; 20 | use Webmozart\KeyValueStore\Api\NoSuchKeyException; 21 | use Webmozart\KeyValueStore\Api\ReadException; 22 | use Webmozart\KeyValueStore\Api\UnserializationFailedException; 23 | use Webmozart\KeyValueStore\Api\UnsupportedValueException; 24 | use Webmozart\KeyValueStore\Api\WriteException; 25 | use Webmozart\KeyValueStore\Util\KeyUtil; 26 | use Webmozart\KeyValueStore\Util\Serializer; 27 | 28 | /** 29 | * A key-value-store backed by MongoDB. 30 | * 31 | * @since 1.0 32 | * 33 | * @author Bernhard Schussek 34 | */ 35 | class MongoDbStore implements KeyValueStore 36 | { 37 | /** 38 | * Flag: Disable serialization. 39 | */ 40 | const NO_SERIALIZE = 1; 41 | 42 | /** 43 | * Flag: Support storage of binary data. 44 | */ 45 | const SUPPORT_BINARY = 2; 46 | 47 | private static $typeMap = array( 48 | 'root' => 'array', 49 | 'document' => 'array', 50 | 'array' => 'array', 51 | ); 52 | 53 | /** 54 | * @var Collection 55 | */ 56 | private $collection; 57 | 58 | /** 59 | * @var Closure 60 | */ 61 | private $serialize; 62 | 63 | /** 64 | * @var Closure 65 | */ 66 | private $unserialize; 67 | 68 | public function __construct(Collection $collection, $flags = 0) 69 | { 70 | $this->collection = $collection; 71 | 72 | if ($flags & self::NO_SERIALIZE) { 73 | if ($flags & self::SUPPORT_BINARY) { 74 | $this->serialize = function ($unserialized) { 75 | if (!is_string($unserialized)) { 76 | throw UnsupportedValueException::forValue($unserialized, $this); 77 | } 78 | 79 | return new Binary($unserialized, Binary::TYPE_GENERIC); 80 | }; 81 | $this->unserialize = function (Binary $serialized) { 82 | return $serialized->getData(); 83 | }; 84 | } else { 85 | $this->serialize = function ($unserialized) { 86 | if (!is_scalar($unserialized) && !is_array($unserialized) && null !== $unserialized) { 87 | throw UnsupportedValueException::forValue($unserialized, $this); 88 | } 89 | 90 | return $unserialized; 91 | }; 92 | $this->unserialize = function ($serialized) { 93 | return $serialized; 94 | }; 95 | } 96 | } else { 97 | if ($flags & self::SUPPORT_BINARY) { 98 | $this->serialize = function ($unserialized) { 99 | return new Binary( 100 | Serializer::serialize($unserialized), 101 | Binary::TYPE_GENERIC 102 | ); 103 | }; 104 | $this->unserialize = function (Binary $serialized) { 105 | return Serializer::unserialize($serialized->getData()); 106 | }; 107 | } else { 108 | $this->serialize = function ($unserialized) { 109 | return Serializer::serialize($unserialized); 110 | }; 111 | $this->unserialize = function ($serialized) { 112 | return Serializer::unserialize($serialized); 113 | }; 114 | } 115 | } 116 | } 117 | 118 | /** 119 | * {@inheritdoc} 120 | */ 121 | public function set($key, $value) 122 | { 123 | KeyUtil::validate($key); 124 | 125 | $serialized = $this->serialize->__invoke($value); 126 | 127 | try { 128 | $this->collection->replaceOne( 129 | array('_id' => $key), 130 | array('_id' => $key, 'value' => $serialized), 131 | array('upsert' => true) 132 | ); 133 | } catch (UnexpectedValueException $e) { 134 | throw UnsupportedValueException::forType('binary', $this, 0, $e); 135 | } catch (Exception $e) { 136 | throw WriteException::forException($e); 137 | } 138 | } 139 | 140 | /** 141 | * {@inheritdoc} 142 | */ 143 | public function get($key, $default = null) 144 | { 145 | KeyUtil::validate($key); 146 | 147 | try { 148 | $document = $this->collection->findOne( 149 | array('_id' => $key), 150 | array('typeMap' => self::$typeMap) 151 | ); 152 | } catch (Exception $e) { 153 | throw ReadException::forException($e); 154 | } 155 | 156 | if (null === $document) { 157 | return $default; 158 | } 159 | 160 | return $this->unserialize->__invoke($document['value']); 161 | } 162 | 163 | /** 164 | * {@inheritdoc} 165 | */ 166 | public function getOrFail($key) 167 | { 168 | KeyUtil::validate($key); 169 | 170 | try { 171 | $document = $this->collection->findOne( 172 | array('_id' => $key), 173 | array('typeMap' => self::$typeMap) 174 | ); 175 | } catch (Exception $e) { 176 | throw ReadException::forException($e); 177 | } 178 | 179 | if (null === $document) { 180 | throw NoSuchKeyException::forKey($key); 181 | } 182 | 183 | return $this->unserialize->__invoke($document['value']); 184 | } 185 | 186 | /** 187 | * {@inheritdoc} 188 | */ 189 | public function getMultiple(array $keys, $default = null) 190 | { 191 | KeyUtil::validateMultiple($keys); 192 | 193 | $values = array_fill_keys($keys, $default); 194 | 195 | try { 196 | $cursor = $this->collection->find( 197 | array('_id' => array('$in' => array_values($keys))), 198 | array('typeMap' => self::$typeMap) 199 | ); 200 | 201 | foreach ($cursor as $document) { 202 | $values[$document['_id']] = $this->unserialize->__invoke($document['value']); 203 | } 204 | } catch (UnserializationFailedException $e) { 205 | throw $e; 206 | } catch (Exception $e) { 207 | throw ReadException::forException($e); 208 | } 209 | 210 | return $values; 211 | } 212 | 213 | /** 214 | * {@inheritdoc} 215 | */ 216 | public function getMultipleOrFail(array $keys) 217 | { 218 | KeyUtil::validateMultiple($keys); 219 | 220 | $values = array(); 221 | 222 | try { 223 | $cursor = $this->collection->find( 224 | array('_id' => array('$in' => array_values($keys))), 225 | array('typeMap' => self::$typeMap) 226 | ); 227 | 228 | foreach ($cursor as $document) { 229 | $values[$document['_id']] = $this->unserialize->__invoke($document['value']); 230 | } 231 | } catch (UnserializationFailedException $e) { 232 | throw $e; 233 | } catch (Exception $e) { 234 | throw ReadException::forException($e); 235 | } 236 | 237 | $notFoundKeys = array_diff($keys, array_keys($values)); 238 | 239 | if (count($notFoundKeys) > 0) { 240 | throw NoSuchKeyException::forKeys($notFoundKeys); 241 | } 242 | 243 | return $values; 244 | } 245 | 246 | /** 247 | * {@inheritdoc} 248 | */ 249 | public function remove($key) 250 | { 251 | KeyUtil::validate($key); 252 | 253 | try { 254 | $result = $this->collection->deleteOne(array('_id' => $key)); 255 | $deletedCount = $result->getDeletedCount(); 256 | } catch (Exception $e) { 257 | throw WriteException::forException($e); 258 | } 259 | 260 | return $deletedCount > 0; 261 | } 262 | 263 | /** 264 | * {@inheritdoc} 265 | */ 266 | public function exists($key) 267 | { 268 | KeyUtil::validate($key); 269 | 270 | try { 271 | $count = $this->collection->count(array('_id' => $key)); 272 | } catch (Exception $e) { 273 | throw ReadException::forException($e); 274 | } 275 | 276 | return $count > 0; 277 | } 278 | 279 | /** 280 | * {@inheritdoc} 281 | */ 282 | public function clear() 283 | { 284 | try { 285 | $this->collection->drop(); 286 | } catch (Exception $e) { 287 | throw WriteException::forException($e); 288 | } 289 | } 290 | 291 | /** 292 | * {@inheritdoc} 293 | */ 294 | public function keys() 295 | { 296 | try { 297 | $cursor = $this->collection->find(array(), array( 298 | 'projection' => array('_id' => 1), 299 | )); 300 | 301 | $keys = array(); 302 | 303 | foreach ($cursor as $document) { 304 | $keys[] = $document['_id']; 305 | } 306 | } catch (Exception $e) { 307 | throw ReadException::forException($e); 308 | } 309 | 310 | return $keys; 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /src/NullStore.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore; 13 | 14 | use Webmozart\KeyValueStore\Api\CountableStore; 15 | use Webmozart\KeyValueStore\Api\NoSuchKeyException; 16 | use Webmozart\KeyValueStore\Api\SortableStore; 17 | 18 | /** 19 | * A key-value store that does nothing. 20 | * 21 | * @since 1.0 22 | * 23 | * @author Bernhard Schussek 24 | */ 25 | class NullStore implements SortableStore, CountableStore 26 | { 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function set($key, $value) 31 | { 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function get($key, $default = null) 38 | { 39 | return $default; 40 | } 41 | 42 | /** 43 | * {@inheritdoc} 44 | */ 45 | public function getOrFail($key) 46 | { 47 | throw NoSuchKeyException::forKey($key); 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public function getMultiple(array $keys, $default = null) 54 | { 55 | return array_fill_keys($keys, $default); 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | public function getMultipleOrFail(array $keys) 62 | { 63 | throw NoSuchKeyException::forKeys($keys); 64 | } 65 | 66 | /** 67 | * {@inheritdoc} 68 | */ 69 | public function remove($key) 70 | { 71 | return false; 72 | } 73 | 74 | /** 75 | * {@inheritdoc} 76 | */ 77 | public function exists($key) 78 | { 79 | return false; 80 | } 81 | 82 | /** 83 | * {@inheritdoc} 84 | */ 85 | public function clear() 86 | { 87 | } 88 | 89 | /** 90 | * {@inheritdoc} 91 | */ 92 | public function keys() 93 | { 94 | return array(); 95 | } 96 | 97 | /** 98 | * {@inheritdoc} 99 | */ 100 | public function sort($flags = SORT_REGULAR) 101 | { 102 | } 103 | 104 | /** 105 | * {@inheritdoc} 106 | */ 107 | public function count() 108 | { 109 | return 0; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/PhpRedisStore.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore; 13 | 14 | use Redis; 15 | 16 | /** 17 | * A key-value store that uses the PhpRedis extension to connect to a Redis instance. 18 | * 19 | * @since 1.0 20 | * 21 | * @author Bernhard Schussek 22 | * @author Philipp Wahala 23 | * @author Titouan Galopin 24 | * 25 | * @link https://github.com/phpredis/phpredis 26 | */ 27 | class PhpRedisStore extends AbstractRedisStore 28 | { 29 | /** 30 | * Creates a store backed by a PhpRedis client. 31 | * 32 | * If no client is passed, a new one is created using the default server 33 | * "127.0.0.1" and the default port 6379. 34 | * 35 | * @param Redis|null $client The client used to connect to Redis. 36 | */ 37 | public function __construct(Redis $client = null) 38 | { 39 | if (null === $client) { 40 | $client = new Redis(); 41 | $client->connect('127.0.0.1', 6379); 42 | } 43 | 44 | $this->client = $client; 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | protected function clientNotFoundValue() 51 | { 52 | return false; 53 | } 54 | 55 | /** 56 | * {@inheritdoc} 57 | */ 58 | protected function clientGet($key) 59 | { 60 | return $this->client->get($key); 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | protected function clientGetMultiple(array $keys) 67 | { 68 | return $this->client->getMultiple($keys); 69 | } 70 | 71 | /** 72 | * {@inheritdoc} 73 | */ 74 | protected function clientSet($key, $value) 75 | { 76 | $this->client->set($key, $value); 77 | } 78 | 79 | /** 80 | * {@inheritdoc} 81 | */ 82 | protected function clientRemove($key) 83 | { 84 | return (bool) $this->client->del($key); 85 | } 86 | 87 | /** 88 | * {@inheritdoc} 89 | */ 90 | protected function clientExists($key) 91 | { 92 | return (bool) $this->client->exists($key); 93 | } 94 | 95 | /** 96 | * {@inheritdoc} 97 | */ 98 | protected function clientClear() 99 | { 100 | $this->client->flushdb(); 101 | } 102 | 103 | /** 104 | * {@inheritdoc} 105 | */ 106 | protected function clientKeys() 107 | { 108 | return $this->client->keys('*'); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/PredisStore.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore; 13 | 14 | use Predis\Client; 15 | use Predis\ClientInterface; 16 | 17 | /** 18 | * A key-value store that uses Predis to connect to a Redis instance. 19 | * 20 | * @since 1.0 21 | * 22 | * @author Bernhard Schussek 23 | * @author Titouan Galopin 24 | */ 25 | class PredisStore extends AbstractRedisStore 26 | { 27 | /** 28 | * Creates a store backed by a Predis client. 29 | * 30 | * If no client is passed, a new one is created using the default server 31 | * "127.0.0.1" and the default port 6379. 32 | * 33 | * @param ClientInterface|null $client The client used to connect to Redis. 34 | */ 35 | public function __construct(ClientInterface $client = null) 36 | { 37 | $this->client = $client ?: new Client(); 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | protected function clientNotFoundValue() 44 | { 45 | return null; 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | protected function clientGet($key) 52 | { 53 | return $this->client->get($key); 54 | } 55 | 56 | /** 57 | * {@inheritdoc} 58 | */ 59 | protected function clientGetMultiple(array $keys) 60 | { 61 | return $this->client->mget($keys); 62 | } 63 | 64 | /** 65 | * {@inheritdoc} 66 | */ 67 | protected function clientSet($key, $value) 68 | { 69 | $this->client->set($key, $value); 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | protected function clientRemove($key) 76 | { 77 | return (bool) $this->client->del($key); 78 | } 79 | 80 | /** 81 | * {@inheritdoc} 82 | */ 83 | protected function clientExists($key) 84 | { 85 | return (bool) $this->client->exists($key); 86 | } 87 | 88 | /** 89 | * {@inheritdoc} 90 | */ 91 | protected function clientClear() 92 | { 93 | $this->client->flushdb(); 94 | } 95 | 96 | /** 97 | * {@inheritdoc} 98 | */ 99 | protected function clientKeys() 100 | { 101 | return $this->client->keys('*'); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/RiakStore.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore; 13 | 14 | use Basho\Riak\Riak; 15 | use Exception; 16 | use Webmozart\KeyValueStore\Api\KeyValueStore; 17 | use Webmozart\KeyValueStore\Api\NoSuchKeyException; 18 | use Webmozart\KeyValueStore\Api\ReadException; 19 | use Webmozart\KeyValueStore\Api\WriteException; 20 | use Webmozart\KeyValueStore\Util\KeyUtil; 21 | use Webmozart\KeyValueStore\Util\Serializer; 22 | 23 | /** 24 | * A key-value store backed by a Riak client. 25 | * 26 | * @since 1.0 27 | * 28 | * @author Bernhard Schussek 29 | */ 30 | class RiakStore implements KeyValueStore 31 | { 32 | /** 33 | * @var string 34 | */ 35 | private $bucketName; 36 | 37 | /** 38 | * @var Riak 39 | */ 40 | private $client; 41 | 42 | /** 43 | * Creates a store backed by a Riak client. 44 | * 45 | * If no client is passed, a new one is created using the default server 46 | * "127.0.0.1" and the default port 8098. 47 | * 48 | * @param string $bucketName The name of the Riak bucket to use. 49 | * @param Riak|null $client The client used to connect to Riak. 50 | */ 51 | public function __construct($bucketName, Riak $client = null) 52 | { 53 | $this->bucketName = $bucketName; 54 | $this->client = $client ?: new Riak(); 55 | } 56 | 57 | /** 58 | * {@inheritdoc} 59 | */ 60 | public function set($key, $value) 61 | { 62 | KeyUtil::validate($key); 63 | 64 | $serialized = Serializer::serialize($value); 65 | 66 | try { 67 | $this->client->bucket($this->bucketName)->newBinary($key, $serialized)->store(); 68 | } catch (Exception $e) { 69 | throw WriteException::forException($e); 70 | } 71 | } 72 | 73 | /** 74 | * {@inheritdoc} 75 | */ 76 | public function get($key, $default = null) 77 | { 78 | KeyUtil::validate($key); 79 | 80 | try { 81 | $object = $this->client->bucket($this->bucketName)->getBinary($key); 82 | $exists = $object->exists(); 83 | } catch (Exception $e) { 84 | throw ReadException::forException($e); 85 | } 86 | 87 | if (!$exists) { 88 | return $default; 89 | } 90 | 91 | return Serializer::unserialize($object->getData()); 92 | } 93 | 94 | /** 95 | * {@inheritdoc} 96 | */ 97 | public function getOrFail($key) 98 | { 99 | KeyUtil::validate($key); 100 | 101 | try { 102 | $object = $this->client->bucket($this->bucketName)->getBinary($key); 103 | $exists = $object->exists(); 104 | } catch (Exception $e) { 105 | throw ReadException::forException($e); 106 | } 107 | 108 | if (!$exists) { 109 | throw NoSuchKeyException::forKey($key); 110 | } 111 | 112 | return Serializer::unserialize($object->getData()); 113 | } 114 | 115 | /** 116 | * {@inheritdoc} 117 | */ 118 | public function getMultiple(array $keys, $default = null) 119 | { 120 | KeyUtil::validateMultiple($keys); 121 | 122 | $values = array(); 123 | 124 | try { 125 | $bucket = $this->client->bucket($this->bucketName); 126 | 127 | foreach ($keys as $key) { 128 | $object = $bucket->getBinary($key); 129 | 130 | $values[$key] = $object->exists() ? $object->getData() : false; 131 | } 132 | } catch (Exception $e) { 133 | throw ReadException::forException($e); 134 | } 135 | 136 | foreach ($values as $key => $value) { 137 | $values[$key] = false === $value 138 | ? $default 139 | : Serializer::unserialize($value); 140 | } 141 | 142 | return $values; 143 | } 144 | 145 | /** 146 | * {@inheritdoc} 147 | */ 148 | public function getMultipleOrFail(array $keys) 149 | { 150 | KeyUtil::validateMultiple($keys); 151 | 152 | $values = array(); 153 | $notFoundKeys = array(); 154 | 155 | try { 156 | $bucket = $this->client->bucket($this->bucketName); 157 | 158 | foreach ($keys as $key) { 159 | $values[$key] = $bucket->getBinary($key); 160 | 161 | if (!$values[$key]->exists()) { 162 | $notFoundKeys[] = $key; 163 | } 164 | } 165 | } catch (Exception $e) { 166 | throw ReadException::forException($e); 167 | } 168 | 169 | if (0 !== count($notFoundKeys)) { 170 | throw NoSuchKeyException::forKeys($notFoundKeys); 171 | } 172 | 173 | foreach ($values as $key => $object) { 174 | $values[$key] = Serializer::unserialize($object->getData()); 175 | } 176 | 177 | return $values; 178 | } 179 | 180 | /** 181 | * {@inheritdoc} 182 | */ 183 | public function remove($key) 184 | { 185 | KeyUtil::validate($key); 186 | 187 | try { 188 | $object = $this->client->bucket($this->bucketName)->get($key); 189 | 190 | if (!$object->exists()) { 191 | return false; 192 | } 193 | 194 | $object->delete(); 195 | } catch (Exception $e) { 196 | throw WriteException::forException($e); 197 | } 198 | 199 | return true; 200 | } 201 | 202 | /** 203 | * {@inheritdoc} 204 | */ 205 | public function exists($key) 206 | { 207 | KeyUtil::validate($key); 208 | 209 | try { 210 | return $this->client->bucket($this->bucketName)->get($key)->exists(); 211 | } catch (Exception $e) { 212 | throw ReadException::forException($e); 213 | } 214 | } 215 | 216 | /** 217 | * {@inheritdoc} 218 | */ 219 | public function clear() 220 | { 221 | try { 222 | $bucket = $this->client->bucket($this->bucketName); 223 | 224 | foreach ($bucket->getKeys() as $key) { 225 | $bucket->get($key)->delete(); 226 | } 227 | } catch (Exception $e) { 228 | throw WriteException::forException($e); 229 | } 230 | } 231 | 232 | /** 233 | * {@inheritdoc} 234 | */ 235 | public function keys() 236 | { 237 | try { 238 | return $this->client->bucket($this->bucketName)->getKeys(); 239 | } catch (Exception $e) { 240 | throw ReadException::forException($e); 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/SerializingArrayStore.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore; 13 | 14 | /** 15 | * A key-value store backed by a PHP array with serialized entries. 16 | * 17 | * The contents of the store are lost when the store is released from memory. 18 | * 19 | * This store behaves more like persistent key-value stores than 20 | * {@link ArrayStore}. It is useful for testing. 21 | * 22 | * @since 1.0 23 | * 24 | * @author Bernhard Schussek 25 | * 26 | * @deprecated Deprecated as of version 1.0, will be removed in version 27 | * 2.0. Use the `ArrayStore` with the `SERIALIZE` flag 28 | * instead. 29 | */ 30 | class SerializingArrayStore extends ArrayStore 31 | { 32 | public function __construct(array $array = array()) 33 | { 34 | parent::__construct($array, parent::SERIALIZE); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Util/KeyUtil.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Util; 13 | 14 | use Webmozart\KeyValueStore\Api\InvalidKeyException; 15 | 16 | /** 17 | * Utility methods for dealing with key-value store keys. 18 | * 19 | * @since 1.0 20 | * 21 | * @author Bernhard Schussek 22 | */ 23 | final class KeyUtil 24 | { 25 | /** 26 | * Validates that a key is valid. 27 | * 28 | * @param mixed $key The tested key. 29 | * 30 | * @throws InvalidKeyException If the key is invalid. 31 | */ 32 | public static function validate($key) 33 | { 34 | if (!is_string($key) && !is_int($key)) { 35 | throw InvalidKeyException::forKey($key); 36 | } 37 | } 38 | 39 | /** 40 | * Validates that multiple keys are valid. 41 | * 42 | * @param array $keys The tested keys. 43 | * 44 | * @throws InvalidKeyException If a key is invalid. 45 | */ 46 | public static function validateMultiple($keys) 47 | { 48 | foreach ($keys as $key) { 49 | if (!is_string($key) && !is_int($key)) { 50 | throw InvalidKeyException::forKey($key); 51 | } 52 | } 53 | } 54 | 55 | private function __construct() 56 | { 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Util/Serializer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Util; 13 | 14 | use Exception; 15 | use Webmozart\KeyValueStore\Api\SerializationFailedException; 16 | use Webmozart\KeyValueStore\Api\UnserializationFailedException; 17 | 18 | /** 19 | * Wrapper for `serialize()`/`unserialize()` that throws proper exceptions. 20 | * 21 | * @since 1.0 22 | * 23 | * @author Bernhard Schussek 24 | */ 25 | final class Serializer 26 | { 27 | /** 28 | * Serializes a value. 29 | * 30 | * @param mixed $value The value to serialize. 31 | * 32 | * @return string The serialized value. 33 | * 34 | * @throws SerializationFailedException If the value cannot be serialized. 35 | */ 36 | public static function serialize($value) 37 | { 38 | if (is_resource($value)) { 39 | throw SerializationFailedException::forValue($value); 40 | } 41 | 42 | try { 43 | $serialized = serialize($value); 44 | } catch (Exception $e) { 45 | throw SerializationFailedException::forValue($value, $e->getMessage(), $e->getCode(), $e); 46 | } 47 | 48 | return $serialized; 49 | } 50 | 51 | /** 52 | * Unserializes a value. 53 | * 54 | * @param mixed $serialized The serialized value. 55 | * 56 | * @return string The unserialized value. 57 | * 58 | * @throws UnserializationFailedException If the value cannot be unserialized. 59 | */ 60 | public static function unserialize($serialized) 61 | { 62 | if (!is_string($serialized)) { 63 | throw UnserializationFailedException::forValue($serialized); 64 | } 65 | 66 | $errorMessage = null; 67 | $errorCode = 0; 68 | 69 | set_error_handler(function ($errno, $errstr) use (&$errorMessage, &$errorCode) { 70 | $errorMessage = $errstr; 71 | $errorCode = $errno; 72 | }); 73 | 74 | $value = unserialize($serialized); 75 | 76 | restore_error_handler(); 77 | 78 | if (null !== $errorMessage) { 79 | if (false !== $pos = strpos($errorMessage, '): ')) { 80 | // cut "unserialize(%path%):" to make message more readable 81 | $errorMessage = substr($errorMessage, $pos + 3); 82 | } 83 | 84 | throw UnserializationFailedException::forValue($serialized, $errorMessage, $errorCode); 85 | } 86 | 87 | return $value; 88 | } 89 | 90 | private function __construct() 91 | { 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /tests/AbstractCountableStoreTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests; 13 | 14 | /** 15 | * @since 1.0 16 | * 17 | * @author Bernhard Schussek 18 | * @author Titouan Galopin 19 | */ 20 | abstract class AbstractCountableStoreTest extends AbstractKeyValueStoreTest 21 | { 22 | /** 23 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 24 | */ 25 | abstract public function testCountThrowsReadExceptionIfReadFails(); 26 | 27 | public function testCountCache() 28 | { 29 | $this->assertEquals(0, $this->store->count()); 30 | 31 | $this->store->set('foo1', 'bar'); 32 | $this->assertEquals(1, $this->store->count()); 33 | 34 | $this->store->set('foo2', 'bar'); 35 | $this->assertEquals(2, $this->store->count()); 36 | 37 | $this->store->set('foo3', 'bar'); 38 | $this->assertEquals(3, $this->store->count()); 39 | 40 | $this->store->remove('foo2'); 41 | $this->assertEquals(2, $this->store->count()); 42 | 43 | $this->store->clear(); 44 | $this->assertEquals(0, $this->store->count()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/AbstractMongoDbStoreTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests; 13 | 14 | use ArrayIterator; 15 | use Exception; 16 | use MongoDB\Client; 17 | use Webmozart\KeyValueStore\MongoDbStore; 18 | use Webmozart\KeyValueStore\Tests\Fixtures\TestException; 19 | 20 | /** 21 | * @since 1.0 22 | * 23 | * @author Bernhard Schussek 24 | */ 25 | abstract class AbstractMongoDbStoreTest extends AbstractKeyValueStoreTest 26 | { 27 | const DATABASE_NAME = 'webmozart-key-value-store-test-db'; 28 | 29 | const COLLECTION_NAME = 'test-collection'; 30 | 31 | private static $supported; 32 | 33 | /** 34 | * @var Client 35 | */ 36 | protected $client; 37 | 38 | public static function setUpBeforeClass() 39 | { 40 | parent::setUpBeforeClass(); 41 | 42 | if (!class_exists('MongoDB\Client')) { 43 | self::$supported = false; 44 | 45 | return; 46 | } 47 | 48 | try { 49 | $client = new Client(); 50 | $client->listDatabases(); 51 | 52 | self::$supported = true; 53 | } catch (Exception $e) { 54 | self::$supported = false; 55 | } 56 | } 57 | 58 | protected function setUp() 59 | { 60 | if (!self::$supported) { 61 | $this->markTestSkipped('MongoDB is not running or the mongodb/mongodb package ist not installed.'); 62 | } 63 | 64 | $this->client = new Client(); 65 | 66 | parent::setUp(); 67 | } 68 | 69 | protected function tearDown() 70 | { 71 | if (!self::$supported) { 72 | return; 73 | } 74 | 75 | parent::tearDown(); 76 | 77 | $this->client->dropDatabase(self::DATABASE_NAME); 78 | } 79 | 80 | /** 81 | * @expectedException \Webmozart\KeyValueStore\Api\WriteException 82 | * @expectedExceptionMessage I failed! 83 | */ 84 | public function testSetThrowsWriteExceptionIfWriteFails() 85 | { 86 | $exception = new TestException('I failed!'); 87 | 88 | $collection = $this->getMockBuilder('MongoDB\Collection') 89 | ->disableOriginalConstructor() 90 | ->getMock(); 91 | 92 | $collection->expects($this->once()) 93 | ->method('replaceOne') 94 | ->willThrowException($exception); 95 | 96 | $store = new MongoDbStore($collection); 97 | $store->set('key', 'value'); 98 | } 99 | 100 | /** 101 | * @expectedException \Webmozart\KeyValueStore\Api\WriteException 102 | * @expectedExceptionMessage I failed! 103 | */ 104 | public function testRemoveThrowsWriteExceptionIfWriteFails() 105 | { 106 | $exception = new TestException('I failed!'); 107 | 108 | $collection = $this->getMockBuilder('MongoDB\Collection') 109 | ->disableOriginalConstructor() 110 | ->getMock(); 111 | 112 | $collection->expects($this->once()) 113 | ->method('deleteOne') 114 | ->willThrowException($exception); 115 | 116 | $store = new MongoDbStore($collection); 117 | $store->remove('key'); 118 | } 119 | 120 | /** 121 | * @expectedException \Webmozart\KeyValueStore\Api\WriteException 122 | * @expectedExceptionMessage I failed! 123 | */ 124 | public function testRemoveThrowsWriteExceptionIfGetDeletedCountFails() 125 | { 126 | $exception = new TestException('I failed!'); 127 | 128 | $collection = $this->getMockBuilder('MongoDB\Collection') 129 | ->disableOriginalConstructor() 130 | ->getMock(); 131 | $result = $this->getMockBuilder('MongoDB\DeleteResult') 132 | ->disableOriginalConstructor() 133 | ->getMock(); 134 | 135 | $collection->expects($this->once()) 136 | ->method('deleteOne') 137 | ->willReturn($result); 138 | 139 | $result->expects($this->once()) 140 | ->method('getDeletedCount') 141 | ->willThrowException($exception); 142 | 143 | $store = new MongoDbStore($collection); 144 | $store->remove('key'); 145 | } 146 | 147 | /** 148 | * @expectedException \Webmozart\KeyValueStore\Api\WriteException 149 | * @expectedExceptionMessage I failed! 150 | */ 151 | public function testClearThrowsWriteExceptionIfWriteFails() 152 | { 153 | $exception = new TestException('I failed!'); 154 | 155 | $collection = $this->getMockBuilder('MongoDB\Collection') 156 | ->disableOriginalConstructor() 157 | ->getMock(); 158 | 159 | $collection->expects($this->once()) 160 | ->method('drop') 161 | ->willThrowException($exception); 162 | 163 | $store = new MongoDbStore($collection); 164 | $store->clear(); 165 | } 166 | 167 | /** 168 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 169 | * @expectedExceptionMessage I failed! 170 | */ 171 | public function testGetThrowsReadExceptionIfReadFails() 172 | { 173 | $exception = new TestException('I failed!'); 174 | 175 | $collection = $this->getMockBuilder('MongoDB\Collection') 176 | ->disableOriginalConstructor() 177 | ->getMock(); 178 | 179 | $collection->expects($this->once()) 180 | ->method('findOne') 181 | ->willThrowException($exception); 182 | 183 | $store = new MongoDbStore($collection); 184 | $store->get('key'); 185 | } 186 | 187 | /** 188 | * @expectedException \Webmozart\KeyValueStore\Api\UnserializationFailedException 189 | */ 190 | public function testGetThrowsExceptionIfNotUnserializable() 191 | { 192 | $collection = $this->getMockBuilder('MongoDB\Collection') 193 | ->disableOriginalConstructor() 194 | ->getMock(); 195 | 196 | $collection->expects($this->once()) 197 | ->method('findOne') 198 | ->willReturn(array('_id' => 'key', 'value' => 'foobar')); 199 | 200 | $store = new MongoDbStore($collection); 201 | $store->get('key'); 202 | } 203 | 204 | /** 205 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 206 | * @expectedExceptionMessage I failed! 207 | */ 208 | public function testGetOrFailThrowsReadExceptionIfReadFails() 209 | { 210 | $exception = new TestException('I failed!'); 211 | 212 | $collection = $this->getMockBuilder('MongoDB\Collection') 213 | ->disableOriginalConstructor() 214 | ->getMock(); 215 | 216 | $collection->expects($this->once()) 217 | ->method('findOne') 218 | ->willThrowException($exception); 219 | 220 | $store = new MongoDbStore($collection); 221 | $store->getOrFail('key'); 222 | } 223 | 224 | /** 225 | * @expectedException \Webmozart\KeyValueStore\Api\UnserializationFailedException 226 | */ 227 | public function testGetOrFailThrowsExceptionIfNotUnserializable() 228 | { 229 | $collection = $this->getMockBuilder('MongoDB\Collection') 230 | ->disableOriginalConstructor() 231 | ->getMock(); 232 | 233 | $collection->expects($this->once()) 234 | ->method('findOne') 235 | ->willReturn(array('_id' => 'key', 'value' => 'foobar')); 236 | 237 | $store = new MongoDbStore($collection); 238 | $store->getOrFail('key'); 239 | } 240 | 241 | /** 242 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 243 | * @expectedExceptionMessage I failed! 244 | */ 245 | public function testGetMultipleThrowsReadExceptionIfReadFails() 246 | { 247 | $exception = new TestException('I failed!'); 248 | 249 | $collection = $this->getMockBuilder('MongoDB\Collection') 250 | ->disableOriginalConstructor() 251 | ->getMock(); 252 | 253 | $collection->expects($this->once()) 254 | ->method('find') 255 | ->willThrowException($exception); 256 | 257 | $store = new MongoDbStore($collection); 258 | $store->getMultiple(array('key')); 259 | } 260 | 261 | /** 262 | * @expectedException \Webmozart\KeyValueStore\Api\UnserializationFailedException 263 | */ 264 | public function testGetMultipleThrowsExceptionIfNotUnserializable() 265 | { 266 | $collection = $this->getMockBuilder('MongoDB\Collection') 267 | ->disableOriginalConstructor() 268 | ->getMock(); 269 | 270 | $cursor = new ArrayIterator(array( 271 | array('_id' => 'key', 'value' => 'foobar'), 272 | )); 273 | 274 | $collection->expects($this->once()) 275 | ->method('find') 276 | ->willReturn($cursor); 277 | 278 | $store = new MongoDbStore($collection); 279 | $store->getMultiple(array('key')); 280 | } 281 | 282 | /** 283 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 284 | * @expectedExceptionMessage I failed! 285 | */ 286 | public function testGetMultipleOrFailThrowsReadExceptionIfReadFails() 287 | { 288 | $exception = new TestException('I failed!'); 289 | 290 | $collection = $this->getMockBuilder('MongoDB\Collection') 291 | ->disableOriginalConstructor() 292 | ->getMock(); 293 | 294 | $collection->expects($this->once()) 295 | ->method('find') 296 | ->willThrowException($exception); 297 | 298 | $store = new MongoDbStore($collection); 299 | $store->getMultipleOrFail(array('key')); 300 | } 301 | 302 | /** 303 | * @expectedException \Webmozart\KeyValueStore\Api\UnserializationFailedException 304 | */ 305 | public function testGetMultipleOrFailThrowsExceptionIfNotUnserializable() 306 | { 307 | $collection = $this->getMockBuilder('MongoDB\Collection') 308 | ->disableOriginalConstructor() 309 | ->getMock(); 310 | 311 | $cursor = new ArrayIterator(array( 312 | array('_id' => 'key', 'value' => 'foobar'), 313 | )); 314 | 315 | $collection->expects($this->once()) 316 | ->method('find') 317 | ->willReturn($cursor); 318 | 319 | $store = new MongoDbStore($collection); 320 | $store->getMultipleOrFail(array('key')); 321 | } 322 | 323 | /** 324 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 325 | * @expectedExceptionMessage I failed! 326 | */ 327 | public function testExistsThrowsReadExceptionIfReadFails() 328 | { 329 | $exception = new TestException('I failed!'); 330 | 331 | $collection = $this->getMockBuilder('MongoDB\Collection') 332 | ->disableOriginalConstructor() 333 | ->getMock(); 334 | 335 | $collection->expects($this->once()) 336 | ->method('count') 337 | ->willThrowException($exception); 338 | 339 | $store = new MongoDbStore($collection); 340 | $store->exists('key'); 341 | } 342 | 343 | /** 344 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 345 | * @expectedExceptionMessage I failed! 346 | */ 347 | public function testKeysThrowsReadExceptionIfReadFails() 348 | { 349 | $exception = new TestException('I failed!'); 350 | 351 | $collection = $this->getMockBuilder('MongoDB\Collection') 352 | ->disableOriginalConstructor() 353 | ->getMock(); 354 | 355 | $collection->expects($this->once()) 356 | ->method('find') 357 | ->willThrowException($exception); 358 | 359 | $store = new MongoDbStore($collection); 360 | $store->keys(); 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /tests/AbstractSortableCountableStoreTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests; 13 | 14 | /** 15 | * @since 1.0 16 | * 17 | * @author Bernhard Schussek 18 | * @author Titouan Galopin 19 | */ 20 | abstract class AbstractSortableCountableStoreTest extends AbstractCountableStoreTest 21 | { 22 | /** 23 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 24 | */ 25 | abstract public function testSortThrowsReadExceptionIfReadFails(); 26 | 27 | /** 28 | * @expectedException \Webmozart\KeyValueStore\Api\WriteException 29 | */ 30 | abstract public function testSortThrowsWriteExceptionIfWriteFails(); 31 | 32 | public function testSortRegularStringKeys() 33 | { 34 | $this->store->set('c', 1); 35 | $this->store->set('a', 2); 36 | $this->store->set('b', 3); 37 | 38 | $this->store->sort(); 39 | 40 | $this->assertSame(array( 41 | 'a' => 2, 42 | 'b' => 3, 43 | 'c' => 1, 44 | ), $this->store->getMultiple($this->store->keys())); 45 | } 46 | 47 | public function testSortRegularIntegerKeys() 48 | { 49 | $this->store->set(3, 'a'); 50 | $this->store->set(1, 'b'); 51 | $this->store->set(2, 'c'); 52 | 53 | $this->store->sort(); 54 | 55 | $this->assertSame(array( 56 | 1 => 'b', 57 | 2 => 'c', 58 | 3 => 'a', 59 | ), $this->store->getMultiple($this->store->keys())); 60 | } 61 | 62 | public function testSortStringStringKeys() 63 | { 64 | $this->store->set('c', 1); 65 | $this->store->set('a', 2); 66 | $this->store->set('b', 3); 67 | 68 | $this->store->sort(SORT_STRING); 69 | 70 | $this->assertSame(array( 71 | 'a' => 2, 72 | 'b' => 3, 73 | 'c' => 1, 74 | ), $this->store->getMultiple($this->store->keys())); 75 | } 76 | 77 | public function testSortNumericIntegerKeys() 78 | { 79 | $this->store->set(3, 'a'); 80 | $this->store->set(1, 'b'); 81 | $this->store->set(2, 'c'); 82 | 83 | $this->store->sort(SORT_NUMERIC); 84 | 85 | $this->assertSame(array( 86 | 1 => 'b', 87 | 2 => 'c', 88 | 3 => 'a', 89 | ), $this->store->getMultiple($this->store->keys())); 90 | } 91 | 92 | public function testSortNaturalStringKeys() 93 | { 94 | if (PHP_VERSION_ID < 50400) { 95 | $this->markTestSkipped('SORT_NATURAL not available'); 96 | } 97 | 98 | $this->store->set('10', 'c'); 99 | $this->store->set('1', 'g'); 100 | $this->store->set('100', 'a'); 101 | $this->store->set('9', 'b'); 102 | $this->store->set('7a', 'h'); 103 | $this->store->set('7b', 'd'); 104 | $this->store->set('_5', 'z'); 105 | 106 | $this->store->sort(SORT_NATURAL); 107 | 108 | $this->assertSame(array( 109 | '1' => 'g', 110 | '7a' => 'h', 111 | '7b' => 'd', 112 | '9' => 'b', 113 | '10' => 'c', 114 | '100' => 'a', 115 | '_5' => 'z', 116 | ), $this->store->getMultiple($this->store->keys())); 117 | } 118 | 119 | public function testSortCaseInsensitiveStringKeys() 120 | { 121 | if (PHP_VERSION_ID < 50400) { 122 | $this->markTestSkipped('SORT_FLAG_CASE not available'); 123 | } 124 | 125 | $this->store->set('_Ac', 'A'); 126 | $this->store->set('abc', 'F'); 127 | $this->store->set('_ab', 'G'); 128 | $this->store->set('ABB', 'E'); 129 | $this->store->set('Bce', 'C'); 130 | $this->store->set('bcd', 'D'); 131 | $this->store->set('bCf', 'E'); 132 | 133 | $this->store->sort(SORT_STRING | SORT_FLAG_CASE); 134 | 135 | $this->assertSame(array( 136 | '_ab' => 'G', 137 | '_Ac' => 'A', 138 | 'ABB' => 'E', 139 | 'abc' => 'F', 140 | 'bcd' => 'D', 141 | 'Bce' => 'C', 142 | 'bCf' => 'E', 143 | ), $this->store->getMultiple($this->store->keys())); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /tests/ArrayStoreTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests; 13 | 14 | use stdClass; 15 | use Webmozart\KeyValueStore\ArrayStore; 16 | 17 | /** 18 | * @since 1.0 19 | * 20 | * @author Bernhard Schussek 21 | */ 22 | class ArrayStoreTest extends AbstractSortableCountableStoreTest 23 | { 24 | protected function createStore() 25 | { 26 | return new ArrayStore(); 27 | } 28 | 29 | protected function createPopulatedStore(array $values) 30 | { 31 | return new ArrayStore($values); 32 | } 33 | 34 | public function testCreateStoreWithElements() 35 | { 36 | $object = new stdClass(); 37 | $store = $this->createPopulatedStore(array(0 => 'a', 1 => $object)); 38 | 39 | $this->assertSame('a', $store->get(0)); 40 | $this->assertEquals($object, $store->get(1)); 41 | } 42 | 43 | /** 44 | * @dataProvider provideInvalidValues 45 | */ 46 | public function testSetThrowsExceptionIfValueNotSerializable($value) 47 | { 48 | // ArrayStore never serializes its values 49 | $this->assertTrue(true); 50 | } 51 | 52 | public function testSetThrowsWriteExceptionIfWriteFails() 53 | { 54 | // ArrayStore never writes a storage 55 | $this->assertTrue(true); 56 | } 57 | 58 | public function testRemoveThrowsWriteExceptionIfWriteFails() 59 | { 60 | // ArrayStore never writes a storage 61 | $this->assertTrue(true); 62 | } 63 | 64 | public function testClearThrowsWriteExceptionIfWriteFails() 65 | { 66 | // ArrayStore never writes a storage 67 | $this->assertTrue(true); 68 | } 69 | 70 | public function testGetThrowsReadExceptionIfReadFails() 71 | { 72 | // ArrayStore never reads a storage 73 | $this->assertTrue(true); 74 | } 75 | 76 | public function testGetThrowsExceptionIfNotUnserializable() 77 | { 78 | // ArrayStore never serializes its values 79 | $this->assertTrue(true); 80 | } 81 | 82 | public function testGetOrFailThrowsReadExceptionIfReadFails() 83 | { 84 | // ArrayStore never reads a storage 85 | $this->assertTrue(true); 86 | } 87 | 88 | public function testGetOrFailThrowsExceptionIfNotUnserializable() 89 | { 90 | // ArrayStore never serializes its values 91 | $this->assertTrue(true); 92 | } 93 | 94 | public function testGetMultipleThrowsReadExceptionIfReadFails() 95 | { 96 | // ArrayStore never reads a storage 97 | $this->assertTrue(true); 98 | } 99 | 100 | public function testGetMultipleThrowsExceptionIfNotUnserializable() 101 | { 102 | // ArrayStore never serializes its values 103 | $this->assertTrue(true); 104 | } 105 | 106 | public function testGetMultipleOrFailThrowsReadExceptionIfReadFails() 107 | { 108 | // ArrayStore never reads a storage 109 | $this->assertTrue(true); 110 | } 111 | 112 | public function testGetMultipleOrFailThrowsExceptionIfNotUnserializable() 113 | { 114 | // ArrayStore never serializes its values 115 | $this->assertTrue(true); 116 | } 117 | 118 | public function testExistsThrowsReadExceptionIfReadFails() 119 | { 120 | // ArrayStore never reads a storage 121 | $this->assertTrue(true); 122 | } 123 | 124 | public function testKeysThrowsReadExceptionIfReadFails() 125 | { 126 | // ArrayStore never reads a storage 127 | $this->assertTrue(true); 128 | } 129 | 130 | public function testSortThrowsReadExceptionIfReadFails() 131 | { 132 | // ArrayStore never reads a storage 133 | $this->assertTrue(true); 134 | } 135 | 136 | public function testSortThrowsWriteExceptionIfWriteFails() 137 | { 138 | // ArrayStore never write a storage 139 | $this->assertTrue(true); 140 | } 141 | 142 | public function testCountThrowsReadExceptionIfReadFails() 143 | { 144 | // ArrayStore never reads a storage 145 | $this->assertTrue(true); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /tests/DbalStoreTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests; 13 | 14 | use Doctrine\DBAL\Connection; 15 | use Doctrine\DBAL\DriverManager; 16 | use Webmozart\KeyValueStore\Api\KeyValueStore; 17 | use Webmozart\KeyValueStore\Api\SortableStore; 18 | use Webmozart\KeyValueStore\DbalStore; 19 | use Webmozart\KeyValueStore\Tests\Fixtures\TestException; 20 | 21 | /** 22 | * @since 1.0 23 | * 24 | * @author Bernhard Schussek 25 | * @author Michiel Boeckaert 26 | */ 27 | class DbalStoreTest extends AbstractKeyValueStoreTest 28 | { 29 | protected static $dbalStore; 30 | 31 | public static function setUpBeforeClass() 32 | { 33 | parent::setUpBeforeClass(); 34 | 35 | $connection = DriverManager::getConnection(array('driver' => 'pdo_sqlite', 'memory' => true)); 36 | $schemaManager = $connection->getSchemaManager(); 37 | $schema = $schemaManager->createSchema(); 38 | self::$dbalStore = new DbalStore($connection, 'store'); 39 | 40 | if (!$schema->hasTable(self::$dbalStore->getTableName())) { 41 | $schemaManager->createTable(self::$dbalStore->getTableForCreate()); 42 | } 43 | } 44 | 45 | /** 46 | * @return KeyValueStore|SortableStore The created store. 47 | */ 48 | protected function createStore() 49 | { 50 | return self::$dbalStore; 51 | } 52 | 53 | public function testSettingAValueTwiceUpdatesTheValue() 54 | { 55 | $this->store->set('a', '123'); 56 | $this->store->set('a', '124'); 57 | $this->assertEquals('124', $this->store->get('a')); 58 | } 59 | 60 | public function provideUnsafeTableNamesSoWeCanBlowUpOurDataBase() 61 | { 62 | return array( 63 | array(null), 64 | array(false), 65 | array(array()), 66 | array(''), 67 | ); 68 | } 69 | 70 | /** 71 | * @dataProvider provideUnsafeTableNamesSoWeCanBlowUpOurDataBase 72 | * @expectedException \InvalidArgumentException 73 | */ 74 | public function testTheTableNameNeedsToBeANotEmptyString($tableName) 75 | { 76 | $connection = $this->getConnectionMock(); 77 | new DbalStore($connection, $tableName); 78 | } 79 | 80 | public function testGetTableNameWorks() 81 | { 82 | $connection = $this->getConnectionMock(); 83 | $store = new DbalStore($connection, 'foo'); 84 | $this->assertEquals('foo', $store->getTableName()); 85 | } 86 | 87 | public function testGetTableNameDefaultsToStore() 88 | { 89 | $connection = $this->getConnectionMock(); 90 | $store = new DbalStore($connection); 91 | $this->assertEquals('store', $store->getTableName()); 92 | } 93 | 94 | /** 95 | * @expectedException \Webmozart\KeyValueStore\Api\WriteException 96 | */ 97 | public function testSetThrowsWriteExceptionIfWriteFails() 98 | { 99 | $connection = $this->getConnectionMock(); 100 | $connection->expects($this->once())->method('fetchAssoc')->willThrowException(new TestException('I failed')); 101 | 102 | $store = new DbalStore($connection, 'store'); 103 | $store->set('foo', 'bar'); 104 | } 105 | 106 | /** 107 | * @expectedException \Webmozart\KeyValueStore\Api\WriteException 108 | */ 109 | public function testRemoveThrowsWriteExceptionIfWriteFails() 110 | { 111 | $connection = $this->getConnectionMock(); 112 | $connection->expects($this->once())->method('delete')->willThrowException(new TestException('I failed')); 113 | 114 | $store = new DbalStore($connection, 'store'); 115 | $store->remove('foo'); 116 | } 117 | 118 | /** 119 | * @expectedException \Webmozart\KeyValueStore\Api\WriteException 120 | */ 121 | public function testClearThrowsWriteExceptionIfWriteFails() 122 | { 123 | $connection = $this->getConnectionMock(); 124 | $connection->expects($this->once())->method('query')->willThrowException(new TestException('I failed')); 125 | 126 | $store = new DbalStore($connection, 'store'); 127 | $store->clear(); 128 | } 129 | 130 | /** 131 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 132 | */ 133 | public function testGetThrowsReadExceptionIfReadFails() 134 | { 135 | $connection = $this->getConnectionMock(); 136 | $connection->expects($this->once())->method('fetchAssoc')->willThrowException(new TestException('I failed')); 137 | 138 | $store = new DbalStore($connection, 'store'); 139 | $store->get('foo'); 140 | } 141 | 142 | /** 143 | * @expectedException \Webmozart\KeyValueStore\Api\UnserializationFailedException 144 | */ 145 | public function testGetThrowsExceptionIfNotUnserializable() 146 | { 147 | $connection = $this->getConnectionMock(); 148 | $connection->expects($this->once())->method('fetchAssoc') 149 | ->willReturn(array('meta_key' => 'foo', 'meta_value' => 'foo_bar')); 150 | 151 | $store = new DbalStore($connection, 'store'); 152 | $store->get('foo'); 153 | } 154 | 155 | /** 156 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 157 | */ 158 | public function testGetOrFailThrowsReadExceptionIfReadFails() 159 | { 160 | $connection = $this->getConnectionMock(); 161 | $connection->expects($this->once())->method('fetchAssoc')->willThrowException(new TestException('I failed')); 162 | 163 | $store = new DbalStore($connection, 'store'); 164 | $store->getOrFail('foo'); 165 | } 166 | 167 | /** 168 | * @expectedException \Webmozart\KeyValueStore\Api\UnserializationFailedException 169 | */ 170 | public function testGetOrFailThrowsExceptionIfNotUnserializable() 171 | { 172 | $connection = $this->getConnectionMock(); 173 | $connection->expects($this->once())->method('fetchAssoc') 174 | ->willReturn(array('meta_key' => 'foo', 'meta_value' => 'foo_bar')); 175 | 176 | $store = new DbalStore($connection, 'store'); 177 | $store->getOrFail('foo'); 178 | } 179 | 180 | /** 181 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 182 | */ 183 | public function testGetMultipleThrowsReadExceptionIfReadFails() 184 | { 185 | $connection = $this->getConnectionMock(); 186 | $connection->expects($this->once())->method('executeQuery')->willThrowException(new TestException('I failed')); 187 | 188 | $store = new DbalStore($connection, 'store'); 189 | $store->getMultiple(array('foo', 'bar')); 190 | } 191 | 192 | /** 193 | * @expectedException \Webmozart\KeyValueStore\Api\UnserializationFailedException 194 | */ 195 | public function testGetMultipleThrowsExceptionIfNotUnserializable() 196 | { 197 | $connection = $this->getConnectionMock(); 198 | $statement = $this->getStatementMock(); 199 | $connection->expects($this->once())->method('executeQuery')->willReturn($statement); 200 | $statement->expects($this->once())->method('fetchAll') 201 | ->willReturn(array(array('meta_key' => 'my_key', 'meta_value' => 'foo_bar'))); 202 | 203 | $store = new DbalStore($connection, 'store'); 204 | $store->getMultiple(array('foo', 'bar')); 205 | } 206 | 207 | /** 208 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 209 | */ 210 | public function testGetMultipleOrFailThrowsReadExceptionIfReadFails() 211 | { 212 | $connection = $this->getConnectionMock(); 213 | $connection->expects($this->once())->method('executeQuery')->willThrowException(new TestException('I failed')); 214 | 215 | $store = new DbalStore($connection, 'store'); 216 | $store->getMultiple(array('foo', 'bar')); 217 | } 218 | 219 | /** 220 | * @expectedException \Webmozart\KeyValueStore\Api\UnserializationFailedException 221 | */ 222 | public function testGetMultipleOrFailThrowsExceptionIfNotUnserializable() 223 | { 224 | $connection = $this->getConnectionMock(); 225 | $statement = $this->getStatementMock(); 226 | $connection->expects($this->once())->method('executeQuery')->willReturn($statement); 227 | $statement->expects($this->once())->method('fetchAll') 228 | ->willReturn(array(array('meta_key' => 'my_key', 'meta_value' => 'foo_bar'))); 229 | 230 | $store = new DbalStore($connection, 'store'); 231 | $store->getMultipleOrFail(array('foo', 'bar')); 232 | } 233 | 234 | /** 235 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 236 | */ 237 | public function testExistsThrowsReadExceptionIfReadFails() 238 | { 239 | $connection = $this->getConnectionMock(); 240 | $connection->expects($this->once())->method('fetchAssoc')->willThrowException(new TestException('I failed')); 241 | 242 | $store = new DbalStore($connection, 'store'); 243 | $store->exists('foo'); 244 | } 245 | 246 | /** 247 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 248 | */ 249 | public function testKeysThrowsReadExceptionIfReadFails() 250 | { 251 | $connection = $this->getConnectionMock(); 252 | $connection->expects($this->once())->method('query')->willThrowException(new TestException('I failed')); 253 | 254 | $store = new DbalStore($connection, 'store'); 255 | $store->keys(); 256 | } 257 | 258 | /** 259 | * @return \PHPUnit_Framework_MockObject_MockObject|Connection 260 | */ 261 | protected function getConnectionMock() 262 | { 263 | $connection = $this->getMockBuilder('Doctrine\DBAL\Connection') 264 | ->disableOriginalConstructor() 265 | ->getMock(); 266 | 267 | return $connection; 268 | } 269 | 270 | /** 271 | * @return \PHPUnit_Framework_MockObject_MockObject 272 | */ 273 | protected function getStatementMock() 274 | { 275 | $statement = $this->getMockBuilder('Doctrine\DBAL\Driver\Statement') 276 | ->disableOriginalConstructor() 277 | ->getMock(); 278 | 279 | return $statement; 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /tests/Decorator/AbstractDecoratorTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests\Decorator; 13 | 14 | use PHPUnit_Framework_MockObject_MockObject; 15 | use PHPUnit_Framework_TestCase; 16 | use Webmozart\KeyValueStore\Api\KeyValueStore; 17 | use Webmozart\KeyValueStore\Decorator\AbstractDecorator; 18 | use Webmozart\KeyValueStore\Decorator\SortableDecorator; 19 | 20 | /** 21 | * @since 1.0 22 | * 23 | * @author Bernhard Schussek 24 | * @author Titouan Galopin 25 | */ 26 | abstract class AbstractDecoratorTest extends PHPUnit_Framework_TestCase 27 | { 28 | /** 29 | * @var PHPUnit_Framework_MockObject_MockObject|KeyValueStore 30 | */ 31 | protected $innerStore; 32 | 33 | /** 34 | * @var SortableDecorator 35 | */ 36 | protected $decorator; 37 | 38 | /** 39 | * @param KeyValueStore $innerStore 40 | * 41 | * @return KeyValueStore|AbstractDecorator The created store. 42 | */ 43 | abstract protected function createDecorator(KeyValueStore $innerStore); 44 | 45 | protected function setUp() 46 | { 47 | $this->innerStore = $this->getMock('Webmozart\KeyValueStore\Api\KeyValueStore'); 48 | $this->decorator = $this->createDecorator($this->innerStore); 49 | } 50 | 51 | protected function tearDown() 52 | { 53 | if ($this->decorator) { 54 | $this->decorator->clear(); 55 | } 56 | } 57 | 58 | public function testGetDelegate() 59 | { 60 | $this->innerStore->expects($this->once()) 61 | ->method('get') 62 | ->with('key'); 63 | 64 | $this->decorator->get('key'); 65 | } 66 | 67 | public function testGetOrFailDelegate() 68 | { 69 | $this->innerStore->expects($this->once()) 70 | ->method('getOrFail') 71 | ->with('key'); 72 | 73 | $this->decorator->getOrFail('key'); 74 | } 75 | 76 | public function testGetMultipleDelegate() 77 | { 78 | $this->innerStore->expects($this->once()) 79 | ->method('getMultiple') 80 | ->with(array('key1', 'key2')); 81 | 82 | $this->decorator->getMultiple(array('key1', 'key2')); 83 | } 84 | 85 | public function testGetMultipleOrFailDelegate() 86 | { 87 | $this->innerStore->expects($this->once()) 88 | ->method('getMultipleOrFail') 89 | ->with(array('key1', 'key2')); 90 | 91 | $this->decorator->getMultipleOrFail(array('key1', 'key2')); 92 | } 93 | 94 | public function testSetDelegate() 95 | { 96 | $this->innerStore->expects($this->once()) 97 | ->method('set') 98 | ->with('key', 'value'); 99 | 100 | $this->decorator->set('key', 'value'); 101 | } 102 | 103 | public function testRemoveDelegate() 104 | { 105 | $this->innerStore->expects($this->once()) 106 | ->method('remove') 107 | ->with('key'); 108 | 109 | $this->decorator->remove('key'); 110 | } 111 | 112 | public function testExistsDelegate() 113 | { 114 | $this->innerStore->expects($this->once()) 115 | ->method('exists') 116 | ->with('key'); 117 | 118 | $this->decorator->exists('key'); 119 | } 120 | 121 | public function testClearDelegate() 122 | { 123 | $this->innerStore->expects($this->once()) 124 | ->method('clear'); 125 | 126 | $this->decorator->clear(); 127 | } 128 | 129 | public function testKeysDelegate() 130 | { 131 | $this->innerStore->expects($this->once()) 132 | ->method('keys'); 133 | 134 | $this->decorator->keys(); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /tests/Decorator/CachingDecoratorTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests\Decorator; 13 | 14 | use Doctrine\Common\Cache\Cache; 15 | use PHPUnit_Framework_MockObject_MockObject; 16 | use Webmozart\KeyValueStore\Api\InvalidKeyException; 17 | use Webmozart\KeyValueStore\Api\KeyValueStore; 18 | use Webmozart\KeyValueStore\Api\NoSuchKeyException; 19 | use Webmozart\KeyValueStore\Api\SerializationFailedException; 20 | use Webmozart\KeyValueStore\Api\WriteException; 21 | use Webmozart\KeyValueStore\Decorator\CachingDecorator; 22 | 23 | /** 24 | * @since 1.0 25 | * 26 | * @author Bernhard Schussek 27 | */ 28 | class CachingDecoratorTest extends AbstractDecoratorTest 29 | { 30 | /** 31 | * @var PHPUnit_Framework_MockObject_MockObject|Cache 32 | */ 33 | private $cache; 34 | 35 | protected function createDecorator(KeyValueStore $innerStore) 36 | { 37 | $this->cache = $this->getMock('Webmozart\KeyValueStore\Tests\Fixtures\TestClearableCache'); 38 | 39 | return new CachingDecorator($this->innerStore, $this->cache); 40 | } 41 | 42 | public function testGetDelegate() 43 | { 44 | // We test the get in another way 45 | $this->assertTrue(true); 46 | } 47 | 48 | public function testGetMultipleDelegate() 49 | { 50 | // We test the getMultiple in another way 51 | $this->assertTrue(true); 52 | } 53 | 54 | public function testGetMultipleOrFailDelegate() 55 | { 56 | // We test the getMultipleOrFail in another way 57 | $this->assertTrue(true); 58 | } 59 | 60 | /** 61 | * @expectedException \InvalidArgumentException 62 | */ 63 | public function testCreateFailsIfNeitherClearableNorFlushable() 64 | { 65 | $this->cache = $this->getMock('Doctrine\Common\Cache\Cache'); 66 | 67 | new CachingDecorator($this->innerStore, $this->cache); 68 | } 69 | 70 | public function testSetWritesToCache() 71 | { 72 | $this->innerStore->expects($this->once()) 73 | ->method('set') 74 | ->with('key', 'value'); 75 | 76 | $this->cache->expects($this->once()) 77 | ->method('save') 78 | ->with('key', 'value'); 79 | 80 | $this->decorator->set('key', 'value'); 81 | } 82 | 83 | public function testSetWritesTtlIfGiven() 84 | { 85 | $this->decorator = new CachingDecorator($this->innerStore, $this->cache, 100); 86 | 87 | $this->innerStore->expects($this->once()) 88 | ->method('set') 89 | ->with('key', 'value'); 90 | 91 | $this->cache->expects($this->once()) 92 | ->method('save') 93 | ->with('key', 'value', 100); 94 | 95 | $this->decorator->set('key', 'value'); 96 | } 97 | 98 | /** 99 | * @expectedException \Webmozart\KeyValueStore\Api\SerializationFailedException 100 | */ 101 | public function testSetDoesNotWriteToCacheIfKeyValueStoreFails() 102 | { 103 | $this->innerStore->expects($this->once()) 104 | ->method('set') 105 | ->with('key', 'value') 106 | ->willThrowException(new SerializationFailedException()); 107 | 108 | $this->cache->expects($this->never()) 109 | ->method('save'); 110 | 111 | $this->decorator->set('key', 'value'); 112 | } 113 | 114 | public function testGetReturnsFromCacheIfCached() 115 | { 116 | $this->cache->expects($this->at(0)) 117 | ->method('contains') 118 | ->with('key') 119 | ->willReturn(true); 120 | 121 | $this->innerStore->expects($this->never()) 122 | ->method('getOrFail'); 123 | 124 | $this->cache->expects($this->at(1)) 125 | ->method('fetch') 126 | ->with('key') 127 | ->willReturn('value'); 128 | 129 | $this->assertSame('value', $this->decorator->get('key')); 130 | } 131 | 132 | public function testGetWritesToCacheIfNotCached() 133 | { 134 | $this->cache->expects($this->at(0)) 135 | ->method('contains') 136 | ->with('key') 137 | ->willReturn(false); 138 | 139 | $this->innerStore->expects($this->once()) 140 | ->method('getOrFail') 141 | ->with('key') 142 | ->willReturn('value'); 143 | 144 | $this->cache->expects($this->at(1)) 145 | ->method('save') 146 | ->with('key', 'value'); 147 | 148 | $this->assertSame('value', $this->decorator->get('key')); 149 | } 150 | 151 | public function testGetWritesTtlIfNotCached() 152 | { 153 | $this->decorator = new CachingDecorator($this->innerStore, $this->cache, 100); 154 | 155 | $this->cache->expects($this->at(0)) 156 | ->method('contains') 157 | ->with('key') 158 | ->willReturn(false); 159 | 160 | $this->innerStore->expects($this->once()) 161 | ->method('getOrFail') 162 | ->with('key') 163 | ->willReturn('value'); 164 | 165 | $this->cache->expects($this->at(1)) 166 | ->method('save') 167 | ->with('key', 'value', 100); 168 | 169 | $this->assertSame('value', $this->decorator->get('key')); 170 | } 171 | 172 | public function testGetDoesNotSaveToCacheIfKeyNotFound() 173 | { 174 | $this->cache->expects($this->once()) 175 | ->method('contains') 176 | ->with('key') 177 | ->willReturn(false); 178 | 179 | $this->innerStore->expects($this->once()) 180 | ->method('getOrFail') 181 | ->willThrowException(NoSuchKeyException::forKey('key')); 182 | 183 | $this->cache->expects($this->never()) 184 | ->method('save'); 185 | 186 | $this->assertSame('default', $this->decorator->get('key', 'default')); 187 | } 188 | 189 | public function testGetOrFailReturnsFromCacheIfCached() 190 | { 191 | $this->cache->expects($this->at(0)) 192 | ->method('contains') 193 | ->with('key') 194 | ->willReturn(true); 195 | 196 | $this->innerStore->expects($this->never()) 197 | ->method('getOrFail'); 198 | 199 | $this->cache->expects($this->at(1)) 200 | ->method('fetch') 201 | ->with('key') 202 | ->willReturn('value'); 203 | 204 | $this->assertSame('value', $this->decorator->getOrFail('key')); 205 | } 206 | 207 | public function testGetOrFailWritesToCacheIfNotCached() 208 | { 209 | $this->cache->expects($this->at(0)) 210 | ->method('contains') 211 | ->with('key') 212 | ->willReturn(false); 213 | 214 | $this->innerStore->expects($this->once()) 215 | ->method('getOrFail') 216 | ->with('key') 217 | ->willReturn('value'); 218 | 219 | $this->cache->expects($this->at(1)) 220 | ->method('save') 221 | ->with('key', 'value'); 222 | 223 | $this->assertSame('value', $this->decorator->getOrFail('key')); 224 | } 225 | 226 | public function testGetOrFailWritesTtlIfNotCached() 227 | { 228 | $this->decorator = new CachingDecorator($this->innerStore, $this->cache, 100); 229 | 230 | $this->cache->expects($this->at(0)) 231 | ->method('contains') 232 | ->with('key') 233 | ->willReturn(false); 234 | 235 | $this->innerStore->expects($this->once()) 236 | ->method('getOrFail') 237 | ->with('key') 238 | ->willReturn('value'); 239 | 240 | $this->cache->expects($this->at(1)) 241 | ->method('save') 242 | ->with('key', 'value', 100); 243 | 244 | $this->assertSame('value', $this->decorator->getOrFail('key')); 245 | } 246 | 247 | /** 248 | * @expectedException \Webmozart\KeyValueStore\Api\NoSuchKeyException 249 | */ 250 | public function testGetOrFailForwardsNoSuchKeyException() 251 | { 252 | $this->cache->expects($this->once()) 253 | ->method('contains') 254 | ->with('key') 255 | ->willReturn(false); 256 | 257 | $this->innerStore->expects($this->once()) 258 | ->method('getOrFail') 259 | ->willThrowException(NoSuchKeyException::forKey('key')); 260 | 261 | $this->cache->expects($this->never()) 262 | ->method('save'); 263 | 264 | $this->decorator->getOrFail('key'); 265 | } 266 | 267 | public function testGetMultipleMergesCachedAndNonCachedEntries() 268 | { 269 | $this->cache->expects($this->exactly(3)) 270 | ->method('contains') 271 | ->willReturnMap(array( 272 | array('a', false), 273 | array('b', true), 274 | array('c', false), 275 | )); 276 | 277 | $this->innerStore->expects($this->once()) 278 | ->method('getMultiple') 279 | ->with(array('a', 2 => 'c'), 'default') 280 | ->willReturn(array( 281 | 'a' => 'value1', 282 | 'c' => 'default', 283 | )); 284 | 285 | $this->cache->expects($this->once()) 286 | ->method('fetch') 287 | ->with('b') 288 | ->willReturn('value2'); 289 | 290 | // We don't know which keys to save and which not 291 | $this->cache->expects($this->never()) 292 | ->method('save'); 293 | 294 | $values = $this->decorator->getMultiple(array('a', 'b', 'c'), 'default'); 295 | 296 | // Undefined order 297 | ksort($values); 298 | 299 | $this->assertSame(array( 300 | 'a' => 'value1', 301 | 'b' => 'value2', 302 | 'c' => 'default', 303 | ), $values); 304 | } 305 | 306 | public function testGetMultipleOrFailMergesCachedAndNonCachedEntries() 307 | { 308 | $this->cache->expects($this->exactly(3)) 309 | ->method('contains') 310 | ->willReturnMap(array( 311 | array('a', false), 312 | array('b', true), 313 | array('c', false), 314 | )); 315 | 316 | $this->innerStore->expects($this->once()) 317 | ->method('getMultipleOrFail') 318 | ->with(array('a', 2 => 'c')) 319 | ->willReturn(array( 320 | 'a' => 'value1', 321 | 'c' => 'value3', 322 | )); 323 | 324 | $this->cache->expects($this->once()) 325 | ->method('fetch') 326 | ->with('b') 327 | ->willReturn('value2'); 328 | 329 | $this->cache->expects($this->exactly(2)) 330 | ->method('save') 331 | ->withConsecutive(array('a', 'value1'), array('c', 'value3')); 332 | 333 | $values = $this->decorator->getMultipleOrFail(array('a', 'b', 'c')); 334 | 335 | // Undefined order 336 | ksort($values); 337 | 338 | $this->assertSame(array( 339 | 'a' => 'value1', 340 | 'b' => 'value2', 341 | 'c' => 'value3', 342 | ), $values); 343 | } 344 | 345 | public function testExistsQueriesCache() 346 | { 347 | $this->cache->expects($this->once()) 348 | ->method('contains') 349 | ->with('key') 350 | ->willReturn(true); 351 | 352 | $this->innerStore->expects($this->never()) 353 | ->method('exists'); 354 | 355 | $this->assertTrue($this->decorator->exists('key')); 356 | } 357 | 358 | /** 359 | * @dataProvider provideTrueFalse 360 | */ 361 | public function testExistsQueriesStoreIfNotCached($result) 362 | { 363 | $this->cache->expects($this->once()) 364 | ->method('contains') 365 | ->with('key') 366 | ->willReturn(false); 367 | 368 | $this->innerStore->expects($this->once()) 369 | ->method('exists') 370 | ->with('key') 371 | ->willReturn($result); 372 | 373 | $this->assertSame($result, $this->decorator->exists('key')); 374 | } 375 | 376 | public function provideTrueFalse() 377 | { 378 | return array( 379 | array(true), 380 | array(false), 381 | ); 382 | } 383 | 384 | public function testRemoveDeletesFromCache() 385 | { 386 | $this->innerStore->expects($this->once()) 387 | ->method('remove') 388 | ->with('key'); 389 | 390 | $this->cache->expects($this->once()) 391 | ->method('delete') 392 | ->with('key'); 393 | 394 | $this->decorator->remove('key'); 395 | } 396 | 397 | /** 398 | * @expectedException \Webmozart\KeyValueStore\Api\InvalidKeyException 399 | */ 400 | public function testRemoveDoesNotDeleteFromCacheIfRemovalFails() 401 | { 402 | $this->innerStore->expects($this->once()) 403 | ->method('remove') 404 | ->with('key') 405 | ->willThrowException(new InvalidKeyException()); 406 | 407 | $this->cache->expects($this->never()) 408 | ->method('delete'); 409 | 410 | $this->decorator->remove('key'); 411 | } 412 | 413 | public function testClearDeletesAllFromCache() 414 | { 415 | $this->cache = $this->getMock('Webmozart\KeyValueStore\Tests\\Fixtures\TestClearableCache'); 416 | $this->decorator = new CachingDecorator($this->innerStore, $this->cache); 417 | 418 | $this->innerStore->expects($this->once()) 419 | ->method('clear'); 420 | 421 | $this->cache->expects($this->once()) 422 | ->method('deleteAll'); 423 | 424 | $this->decorator->clear(); 425 | } 426 | 427 | /** 428 | * @expectedException \Webmozart\KeyValueStore\Api\WriteException 429 | */ 430 | public function testClearDoesNotDeleteAllFromCacheIfClearFails() 431 | { 432 | $this->cache = $this->getMock('Webmozart\KeyValueStore\Tests\\Fixtures\TestClearableCache'); 433 | $this->decorator = new CachingDecorator($this->innerStore, $this->cache); 434 | 435 | $this->innerStore->expects($this->once()) 436 | ->method('clear') 437 | ->willThrowException(new WriteException()); 438 | 439 | $this->cache->expects($this->never()) 440 | ->method('deleteAll'); 441 | 442 | $this->decorator->clear(); 443 | } 444 | 445 | public function testClearFlushesCache() 446 | { 447 | $this->cache = $this->getMock('Webmozart\KeyValueStore\Tests\\Fixtures\TestFlushableCache'); 448 | $this->decorator = new CachingDecorator($this->innerStore, $this->cache); 449 | 450 | $this->innerStore->expects($this->once()) 451 | ->method('clear'); 452 | 453 | $this->cache->expects($this->once()) 454 | ->method('flushAll'); 455 | 456 | $this->decorator->clear(); 457 | } 458 | 459 | public function testClearDeletesAllIfFlushableAndClearable() 460 | { 461 | $this->cache = $this->getMock('Webmozart\KeyValueStore\Tests\\Fixtures\TestClearableFlushableCache'); 462 | $this->decorator = new CachingDecorator($this->innerStore, $this->cache); 463 | 464 | $this->innerStore->expects($this->once()) 465 | ->method('clear'); 466 | 467 | $this->cache->expects($this->once()) 468 | ->method('deleteAll'); 469 | 470 | $this->decorator->clear(); 471 | } 472 | 473 | public function testKeysForwardsKeysFromStore() 474 | { 475 | $this->innerStore->expects($this->once()) 476 | ->method('keys') 477 | ->willReturn(array('a', 'b', 'c')); 478 | 479 | $this->assertSame(array('a', 'b', 'c'), $this->decorator->keys()); 480 | } 481 | } 482 | -------------------------------------------------------------------------------- /tests/Decorator/CountableDecoratorTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests\Decorator; 13 | 14 | use Webmozart\KeyValueStore\Api\KeyValueStore; 15 | use Webmozart\KeyValueStore\Decorator\CountableDecorator; 16 | 17 | /** 18 | * @since 1.0 19 | * 20 | * @author Bernhard Schussek 21 | * @author Titouan Galopin 22 | */ 23 | class CountableDecoratorTest extends AbstractDecoratorTest 24 | { 25 | protected function createDecorator(KeyValueStore $innerStore) 26 | { 27 | return new CountableDecorator($innerStore); 28 | } 29 | 30 | public function testCountCache() 31 | { 32 | $this->innerStore->expects($this->at(0)) 33 | ->method('keys') 34 | ->willReturn(array('key1', 'key2')); 35 | 36 | $this->innerStore->expects($this->at(1)) 37 | ->method('set') 38 | ->with('key3', 'value3'); 39 | 40 | $this->innerStore->expects($this->at(2)) 41 | ->method('keys') 42 | ->willReturn(array('key1', 'key2', 'key3')); 43 | 44 | $this->assertEquals(2, $this->decorator->count()); 45 | $this->assertEquals(2, $this->decorator->count()); 46 | 47 | $this->decorator->set('key3', 'value3'); 48 | 49 | $this->assertEquals(3, $this->decorator->count()); 50 | $this->assertEquals(3, $this->decorator->count()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Decorator/SortableDecoratorTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests\Decorator; 13 | 14 | use Webmozart\KeyValueStore\Api\KeyValueStore; 15 | use Webmozart\KeyValueStore\ArrayStore; 16 | use Webmozart\KeyValueStore\Decorator\SortableDecorator; 17 | 18 | /** 19 | * @since 1.0 20 | * 21 | * @author Bernhard Schussek 22 | * @author Titouan Galopin 23 | */ 24 | class SortableDecoratorTest extends AbstractDecoratorTest 25 | { 26 | protected function createDecorator(KeyValueStore $innerStore) 27 | { 28 | return new SortableDecorator($innerStore); 29 | } 30 | 31 | public function testSortRegularStringKeys() 32 | { 33 | $store = new SortableDecorator(new ArrayStore()); 34 | $store->set('c', 1); 35 | $store->set('a', 2); 36 | $store->set('b', 3); 37 | 38 | $store->sort(); 39 | 40 | $this->assertSame(array( 41 | 'a' => 2, 42 | 'b' => 3, 43 | 'c' => 1, 44 | ), $store->getMultiple($store->keys())); 45 | } 46 | 47 | public function testSortRegularIntegerKeys() 48 | { 49 | $store = new SortableDecorator(new ArrayStore()); 50 | $store->set(3, 'a'); 51 | $store->set(1, 'b'); 52 | $store->set(2, 'c'); 53 | 54 | $store->sort(); 55 | 56 | $this->assertSame(array( 57 | 1 => 'b', 58 | 2 => 'c', 59 | 3 => 'a', 60 | ), $store->getMultiple($store->keys())); 61 | } 62 | 63 | public function testSortStringStringKeys() 64 | { 65 | $store = new SortableDecorator(new ArrayStore()); 66 | $store->set('c', 1); 67 | $store->set('a', 2); 68 | $store->set('b', 3); 69 | 70 | $store->sort(SORT_STRING); 71 | 72 | $this->assertSame(array( 73 | 'a' => 2, 74 | 'b' => 3, 75 | 'c' => 1, 76 | ), $store->getMultiple($store->keys())); 77 | } 78 | 79 | public function testSortNumericIntegerKeys() 80 | { 81 | $store = new SortableDecorator(new ArrayStore()); 82 | $store->set(3, 'a'); 83 | $store->set(1, 'b'); 84 | $store->set(2, 'c'); 85 | 86 | $store->sort(SORT_NUMERIC); 87 | 88 | $this->assertSame(array( 89 | 1 => 'b', 90 | 2 => 'c', 91 | 3 => 'a', 92 | ), $store->getMultiple($store->keys())); 93 | } 94 | 95 | public function testSortNaturalStringKeys() 96 | { 97 | if (PHP_VERSION_ID < 50400) { 98 | $this->markTestSkipped('SORT_NATURAL not available'); 99 | } 100 | 101 | $store = new SortableDecorator(new ArrayStore()); 102 | 103 | $store->set('10', 'c'); 104 | $store->set('1', 'g'); 105 | $store->set('100', 'a'); 106 | $store->set('9', 'b'); 107 | $store->set('7a', 'h'); 108 | $store->set('7b', 'd'); 109 | $store->set('_5', 'z'); 110 | 111 | $store->sort(SORT_NATURAL); 112 | 113 | $this->assertSame(array( 114 | '1' => 'g', 115 | '7a' => 'h', 116 | '7b' => 'd', 117 | '9' => 'b', 118 | '10' => 'c', 119 | '100' => 'a', 120 | '_5' => 'z', 121 | ), $store->getMultiple($store->keys())); 122 | } 123 | 124 | public function testSortCaseInsensitiveStringKeys() 125 | { 126 | if (PHP_VERSION_ID < 50400) { 127 | $this->markTestSkipped('SORT_FLAG_CASE not available'); 128 | } 129 | 130 | $store = new SortableDecorator(new ArrayStore()); 131 | 132 | $store->set('_Ac', 'A'); 133 | $store->set('abc', 'F'); 134 | $store->set('_ab', 'G'); 135 | $store->set('ABB', 'E'); 136 | $store->set('Bce', 'C'); 137 | $store->set('bcd', 'D'); 138 | $store->set('bCf', 'E'); 139 | 140 | $store->sort(SORT_STRING | SORT_FLAG_CASE); 141 | 142 | $this->assertSame(array( 143 | '_ab' => 'G', 144 | '_Ac' => 'A', 145 | 'ABB' => 'E', 146 | 'abc' => 'F', 147 | 'bcd' => 'D', 148 | 'Bce' => 'C', 149 | 'bCf' => 'E', 150 | ), $store->getMultiple($store->keys())); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /tests/Fixtures/NotSerializable.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests\Fixtures; 13 | 14 | use BadMethodCallException; 15 | 16 | /** 17 | * @since 1.0 18 | * 19 | * @author Bernhard Schussek 20 | */ 21 | class NotSerializable 22 | { 23 | public function __sleep() 24 | { 25 | throw new BadMethodCallException('You cannot serialize this object.'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Fixtures/PrivateProperties.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests\Fixtures; 13 | 14 | /** 15 | * @since 1.0 16 | * 17 | * @author Bernhard Schussek 18 | */ 19 | class PrivateProperties 20 | { 21 | private $property; 22 | 23 | public function __construct($property) 24 | { 25 | $this->property = $property; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Fixtures/TestClearableCache.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests\Fixtures; 13 | 14 | use Doctrine\Common\Cache\Cache; 15 | use Doctrine\Common\Cache\ClearableCache; 16 | 17 | /** 18 | * @since 1.0 19 | * 20 | * @author Bernhard Schussek 21 | */ 22 | interface TestClearableCache extends Cache, ClearableCache 23 | { 24 | } 25 | -------------------------------------------------------------------------------- /tests/Fixtures/TestClearableFlushableCache.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests\Fixtures; 13 | 14 | use Doctrine\Common\Cache\Cache; 15 | use Doctrine\Common\Cache\ClearableCache; 16 | use Doctrine\Common\Cache\FlushableCache; 17 | 18 | /** 19 | * @since 1.0 20 | * 21 | * @author Bernhard Schussek 22 | */ 23 | interface TestClearableFlushableCache extends Cache, ClearableCache, FlushableCache 24 | { 25 | } 26 | -------------------------------------------------------------------------------- /tests/Fixtures/TestException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests\Fixtures; 13 | 14 | use Exception; 15 | 16 | /** 17 | * @since 1.0 18 | * 19 | * @author Bernhard Schussek 20 | */ 21 | class TestException extends Exception 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /tests/Fixtures/TestFlushableCache.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests\Fixtures; 13 | 14 | use Doctrine\Common\Cache\Cache; 15 | use Doctrine\Common\Cache\FlushableCache; 16 | 17 | /** 18 | * @since 1.0 19 | * 20 | * @author Bernhard Schussek 21 | */ 22 | interface TestFlushableCache extends Cache, FlushableCache 23 | { 24 | } 25 | -------------------------------------------------------------------------------- /tests/Fixtures/file: -------------------------------------------------------------------------------- 1 | asdfasdfasdf 2 | -------------------------------------------------------------------------------- /tests/NonSerializingBinaryMongoDbStoreTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests; 13 | 14 | use Webmozart\KeyValueStore\MongoDbStore; 15 | 16 | /** 17 | * @since 1.0 18 | * 19 | * @author Bernhard Schussek 20 | */ 21 | class NonSerializingBinaryMongoDbStoreTest extends AbstractMongoDbStoreTest 22 | { 23 | protected function createStore() 24 | { 25 | $collection = $this->client->selectCollection( 26 | self::DATABASE_NAME, 27 | self::COLLECTION_NAME 28 | ); 29 | 30 | return new MongoDbStore($collection, MongoDbStore::NO_SERIALIZE | MongoDbStore::SUPPORT_BINARY); 31 | } 32 | 33 | /** 34 | * @dataProvider provideScalarValues 35 | */ 36 | public function testSetSupportsScalarValues($value) 37 | { 38 | // JSON cannot handle binary data 39 | $this->setExpectedException('\Webmozart\KeyValueStore\Api\UnsupportedValueException'); 40 | 41 | parent::testSetSupportsScalarValues($value); 42 | } 43 | 44 | /** 45 | * @dataProvider provideObjectValues 46 | */ 47 | public function testSetSupportsObjectValues($value) 48 | { 49 | // JSON cannot handle binary data 50 | $this->setExpectedException('\Webmozart\KeyValueStore\Api\UnsupportedValueException'); 51 | 52 | parent::testSetSupportsObjectValues($value); 53 | } 54 | 55 | /** 56 | * @dataProvider provideArrayValues 57 | */ 58 | public function testSetSupportsArrayValues($value) 59 | { 60 | // JSON cannot handle binary data 61 | $this->setExpectedException('\Webmozart\KeyValueStore\Api\UnsupportedValueException'); 62 | 63 | parent::testSetSupportsArrayValues($value); 64 | } 65 | 66 | /** 67 | * @dataProvider provideBinaryArrayValues 68 | */ 69 | public function testSetSupportsBinaryArrayValues($value) 70 | { 71 | // JSON cannot handle binary data 72 | $this->setExpectedException('\Webmozart\KeyValueStore\Api\UnsupportedValueException'); 73 | 74 | parent::testSetSupportsBinaryArrayValues($value); 75 | } 76 | 77 | /** 78 | * @dataProvider provideBinaryObjectValues 79 | */ 80 | public function testSetSupportsBinaryObjectValues($value) 81 | { 82 | // JSON cannot handle binary data 83 | $this->setExpectedException('\Webmozart\KeyValueStore\Api\UnsupportedValueException'); 84 | 85 | parent::testSetSupportsBinaryObjectValues($value); 86 | } 87 | 88 | /** 89 | * @dataProvider provideInvalidValues 90 | */ 91 | public function testSetThrowsExceptionIfValueNotSerializable($value) 92 | { 93 | // JSON cannot handle binary data 94 | $this->setExpectedException('\Webmozart\KeyValueStore\Api\UnsupportedValueException'); 95 | 96 | parent::testSetThrowsExceptionIfValueNotSerializable($value); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /tests/NonSerializingMongoDbStoreTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests; 13 | 14 | use Webmozart\KeyValueStore\MongoDbStore; 15 | 16 | /** 17 | * @since 1.0 18 | * 19 | * @author Bernhard Schussek 20 | */ 21 | class NonSerializingMongoDbStoreTest extends AbstractMongoDbStoreTest 22 | { 23 | protected function createStore() 24 | { 25 | $collection = $this->client->selectCollection( 26 | self::DATABASE_NAME, 27 | self::COLLECTION_NAME 28 | ); 29 | 30 | return new MongoDbStore($collection, MongoDbStore::NO_SERIALIZE); 31 | } 32 | 33 | /** 34 | * @dataProvider provideBinaryValues 35 | */ 36 | public function testSetSupportsBinaryValues($value) 37 | { 38 | // JSON cannot handle binary data 39 | $this->setExpectedException('\Webmozart\KeyValueStore\Api\UnsupportedValueException'); 40 | 41 | parent::testSetSupportsBinaryValues($value); 42 | } 43 | 44 | /** 45 | * @dataProvider provideBinaryArrayValues 46 | */ 47 | public function testSetSupportsBinaryArrayValues($value) 48 | { 49 | // JSON cannot handle binary data 50 | $this->setExpectedException('\Webmozart\KeyValueStore\Api\UnsupportedValueException'); 51 | 52 | parent::testSetSupportsBinaryArrayValues($value); 53 | } 54 | 55 | /** 56 | * @dataProvider provideBinaryObjectValues 57 | */ 58 | public function testSetSupportsBinaryObjectValues($value) 59 | { 60 | // JSON cannot handle binary data 61 | $this->setExpectedException('\Webmozart\KeyValueStore\Api\UnsupportedValueException'); 62 | 63 | parent::testSetSupportsBinaryObjectValues($value); 64 | } 65 | 66 | /** 67 | * @dataProvider provideObjectValues 68 | */ 69 | public function testSetSupportsObjectValues($value) 70 | { 71 | // JSON cannot handle binary data 72 | $this->setExpectedException('\Webmozart\KeyValueStore\Api\UnsupportedValueException'); 73 | 74 | parent::testSetSupportsObjectValues($value); 75 | } 76 | 77 | /** 78 | * @dataProvider provideInvalidValues 79 | */ 80 | public function testSetThrowsExceptionIfValueNotSerializable($value) 81 | { 82 | // JSON cannot handle binary data 83 | $this->setExpectedException('\Webmozart\KeyValueStore\Api\UnsupportedValueException'); 84 | 85 | parent::testSetThrowsExceptionIfValueNotSerializable($value); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/NullStoreTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests; 13 | 14 | use PHPUnit_Framework_TestCase; 15 | use Webmozart\KeyValueStore\NullStore; 16 | 17 | /** 18 | * @since 1.0 19 | * 20 | * @author Bernhard Schussek 21 | */ 22 | class NullStoreTest extends PHPUnit_Framework_TestCase 23 | { 24 | public function testGetAlwaysReturnsDefault() 25 | { 26 | $store = new NullStore(); 27 | 28 | $store->set('foo', 'bar'); 29 | 30 | $this->assertSame('baz', $store->get('foo', 'baz')); 31 | } 32 | 33 | /** 34 | * @expectedException \Webmozart\KeyValueStore\Api\NoSuchKeyException 35 | */ 36 | public function testGetAlwaysThrowsException() 37 | { 38 | $store = new NullStore(); 39 | 40 | $store->set('foo', 'bar'); 41 | 42 | $store->getOrFail('foo'); 43 | } 44 | 45 | public function testGetMultipleAlwaysReturnsDefault() 46 | { 47 | $store = new NullStore(); 48 | 49 | $store->set('foo', 'bar'); 50 | 51 | $this->assertSame(array( 52 | 'foo' => 'default', 53 | 'bar' => 'default', 54 | ), $store->getMultiple(array('foo', 'bar'), 'default')); 55 | } 56 | 57 | /** 58 | * @expectedException \Webmozart\KeyValueStore\Api\NoSuchKeyException 59 | */ 60 | public function testGetMultipleOrFailAlwaysThrowsException() 61 | { 62 | $store = new NullStore(); 63 | 64 | $store->set('foo', 'bar'); 65 | 66 | $store->getMultipleOrFail(array('foo')); 67 | } 68 | 69 | public function testExistsAlwaysReturnsFalse() 70 | { 71 | $store = new NullStore(); 72 | 73 | $store->set('foo', 'bar'); 74 | 75 | $this->assertFalse($store->exists('foo')); 76 | } 77 | 78 | public function testRemoveAlwaysReturnsFalse() 79 | { 80 | $store = new NullStore(); 81 | 82 | $store->set('foo', 'bar'); 83 | 84 | $this->assertFalse($store->remove('foo')); 85 | } 86 | 87 | public function testKeysAlwaysReturnsEmptyArray() 88 | { 89 | $store = new NullStore(); 90 | 91 | $store->set('foo', 'bar'); 92 | 93 | $this->assertSame(array(), $store->keys()); 94 | } 95 | 96 | public function testClearDoesNothing() 97 | { 98 | $store = new NullStore(); 99 | 100 | $store->set('foo1', 'bar1'); 101 | $store->set('foo2', 'bar2'); 102 | $store->clear(); 103 | 104 | $this->assertSame(array(), $store->keys()); 105 | } 106 | 107 | public function testSortDoesNothing() 108 | { 109 | $store = new NullStore(); 110 | 111 | $store->set('foo1', 'bar1'); 112 | $store->set('foo2', 'bar2'); 113 | $store->sort(); 114 | 115 | $this->assertSame(array(), $store->keys()); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /tests/PhpRedisStoreTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests; 13 | 14 | use Redis; 15 | use Webmozart\KeyValueStore\PhpRedisStore; 16 | use Webmozart\KeyValueStore\Tests\Fixtures\TestException; 17 | 18 | /** 19 | * @since 1.0 20 | * 21 | * @author Bernhard Schussek 22 | * @author Philipp Wahala 23 | */ 24 | class PhpRedisStoreTest extends AbstractKeyValueStoreTest 25 | { 26 | private static $supported; 27 | 28 | public static function setUpBeforeClass() 29 | { 30 | parent::setUpBeforeClass(); 31 | 32 | if (!class_exists('\Redis', false)) { 33 | self::$supported = false; 34 | 35 | return; 36 | } 37 | 38 | $redis = new Redis(); 39 | 40 | self::$supported = @$redis->connect('127.0.0.1', 6379); 41 | } 42 | 43 | protected function setUp() 44 | { 45 | if (!self::$supported) { 46 | $this->markTestSkipped('PhpRedis is not available or Redis is not running.'); 47 | } 48 | 49 | parent::setUp(); 50 | } 51 | 52 | protected function createStore() 53 | { 54 | return new PhpRedisStore(); 55 | } 56 | 57 | /** 58 | * @expectedException \Webmozart\KeyValueStore\Api\WriteException 59 | * @expectedExceptionMessage I failed! 60 | */ 61 | public function testSetThrowsWriteExceptionIfWriteFails() 62 | { 63 | $exception = new TestException('I failed!'); 64 | 65 | $redis = $this->getMockBuilder('Redis') 66 | ->disableOriginalConstructor() 67 | ->getMock(); 68 | 69 | $redis->expects($this->once()) 70 | ->method('set') 71 | ->willThrowException($exception); 72 | 73 | $store = new PhpRedisStore($redis); 74 | $store->set('key', 'value'); 75 | } 76 | 77 | /** 78 | * @expectedException \Webmozart\KeyValueStore\Api\WriteException 79 | * @expectedExceptionMessage I failed! 80 | */ 81 | public function testRemoveThrowsWriteExceptionIfWriteFails() 82 | { 83 | $exception = new TestException('I failed!'); 84 | 85 | $redis = $this->getMockBuilder('Redis') 86 | ->disableOriginalConstructor() 87 | ->getMock(); 88 | 89 | $redis->expects($this->once()) 90 | ->method('del') 91 | ->willThrowException($exception); 92 | 93 | $store = new PhpRedisStore($redis); 94 | $store->remove('key'); 95 | } 96 | 97 | /** 98 | * @expectedException \Webmozart\KeyValueStore\Api\WriteException 99 | * @expectedExceptionMessage I failed! 100 | */ 101 | public function testClearThrowsWriteExceptionIfWriteFails() 102 | { 103 | $exception = new TestException('I failed!'); 104 | 105 | $redis = $this->getMockBuilder('Redis') 106 | ->disableOriginalConstructor() 107 | ->getMock(); 108 | 109 | $redis->expects($this->once()) 110 | ->method('flushdb') 111 | ->willThrowException($exception); 112 | 113 | $store = new PhpRedisStore($redis); 114 | $store->clear(); 115 | } 116 | 117 | /** 118 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 119 | * @expectedExceptionMessage I failed! 120 | */ 121 | public function testGetThrowsReadExceptionIfReadFails() 122 | { 123 | $exception = new TestException('I failed!'); 124 | 125 | $redis = $this->getMockBuilder('Redis') 126 | ->disableOriginalConstructor() 127 | ->getMock(); 128 | 129 | $redis->expects($this->once()) 130 | ->method('get') 131 | ->willThrowException($exception); 132 | 133 | $store = new PhpRedisStore($redis); 134 | $store->get('key'); 135 | } 136 | 137 | /** 138 | * @expectedException \Webmozart\KeyValueStore\Api\UnserializationFailedException 139 | */ 140 | public function testGetThrowsExceptionIfNotUnserializable() 141 | { 142 | $redis = $this->getMockBuilder('Redis') 143 | ->disableOriginalConstructor() 144 | ->getMock(); 145 | 146 | $redis->expects($this->once()) 147 | ->method('get') 148 | ->willReturn('foobar'); 149 | 150 | $store = new PhpRedisStore($redis); 151 | $store->get('key'); 152 | } 153 | 154 | /** 155 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 156 | * @expectedExceptionMessage I failed! 157 | */ 158 | public function testGetOrFailThrowsReadExceptionIfReadFails() 159 | { 160 | $exception = new TestException('I failed!'); 161 | 162 | $redis = $this->getMockBuilder('Redis') 163 | ->disableOriginalConstructor() 164 | ->getMock(); 165 | 166 | $redis->expects($this->once()) 167 | ->method('get') 168 | ->willThrowException($exception); 169 | 170 | $store = new PhpRedisStore($redis); 171 | $store->getOrFail('key'); 172 | } 173 | 174 | /** 175 | * @expectedException \Webmozart\KeyValueStore\Api\UnserializationFailedException 176 | */ 177 | public function testGetOrFailThrowsExceptionIfNotUnserializable() 178 | { 179 | $redis = $this->getMockBuilder('Redis') 180 | ->disableOriginalConstructor() 181 | ->getMock(); 182 | 183 | $redis->expects($this->once()) 184 | ->method('get') 185 | ->willReturn('foobar'); 186 | 187 | $store = new PhpRedisStore($redis); 188 | $store->getOrFail('key'); 189 | } 190 | 191 | /** 192 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 193 | * @expectedExceptionMessage I failed! 194 | */ 195 | public function testGetMultipleThrowsReadExceptionIfReadFails() 196 | { 197 | $exception = new TestException('I failed!'); 198 | 199 | $redis = $this->getMockBuilder('Redis') 200 | ->disableOriginalConstructor() 201 | ->getMock(); 202 | 203 | $redis->expects($this->once()) 204 | ->method('getMultiple') 205 | ->willThrowException($exception); 206 | 207 | $store = new PhpRedisStore($redis); 208 | $store->getMultiple(array('key')); 209 | } 210 | 211 | /** 212 | * @expectedException \Webmozart\KeyValueStore\Api\UnserializationFailedException 213 | */ 214 | public function testGetMultipleThrowsExceptionIfNotUnserializable() 215 | { 216 | $redis = $this->getMockBuilder('Redis') 217 | ->disableOriginalConstructor() 218 | ->getMock(); 219 | 220 | $redis->expects($this->once()) 221 | ->method('getMultiple') 222 | ->willReturn(array('foobar')); 223 | 224 | $store = new PhpRedisStore($redis); 225 | $store->getMultiple(array('key')); 226 | } 227 | 228 | /** 229 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 230 | * @expectedExceptionMessage I failed! 231 | */ 232 | public function testGetMultipleOrFailThrowsReadExceptionIfReadFails() 233 | { 234 | $exception = new TestException('I failed!'); 235 | 236 | $redis = $this->getMockBuilder('Redis') 237 | ->disableOriginalConstructor() 238 | ->getMock(); 239 | 240 | $redis->expects($this->once()) 241 | ->method('getMultiple') 242 | ->willThrowException($exception); 243 | 244 | $store = new PhpRedisStore($redis); 245 | $store->getMultipleOrFail(array('key')); 246 | } 247 | 248 | /** 249 | * @expectedException \Webmozart\KeyValueStore\Api\UnserializationFailedException 250 | */ 251 | public function testGetMultipleOrFailThrowsExceptionIfNotUnserializable() 252 | { 253 | $redis = $this->getMockBuilder('Redis') 254 | ->disableOriginalConstructor() 255 | ->getMock(); 256 | 257 | $redis->expects($this->once()) 258 | ->method('getMultiple') 259 | ->willReturn(array('foobar')); 260 | 261 | $store = new PhpRedisStore($redis); 262 | $store->getMultipleOrFail(array('key')); 263 | } 264 | 265 | /** 266 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 267 | * @expectedExceptionMessage I failed! 268 | */ 269 | public function testExistsThrowsReadExceptionIfReadFails() 270 | { 271 | $exception = new TestException('I failed!'); 272 | 273 | $redis = $this->getMockBuilder('Redis') 274 | ->disableOriginalConstructor() 275 | ->getMock(); 276 | 277 | $redis->expects($this->once()) 278 | ->method('exists') 279 | ->willThrowException($exception); 280 | 281 | $store = new PhpRedisStore($redis); 282 | $store->exists('key'); 283 | } 284 | 285 | /** 286 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 287 | * @expectedExceptionMessage I failed! 288 | */ 289 | public function testKeysThrowsReadExceptionIfReadFails() 290 | { 291 | $exception = new TestException('I failed!'); 292 | 293 | $redis = $this->getMockBuilder('Redis') 294 | ->disableOriginalConstructor() 295 | ->getMock(); 296 | 297 | $redis->expects($this->once()) 298 | ->method('keys') 299 | ->willThrowException($exception); 300 | 301 | $store = new PhpRedisStore($redis); 302 | $store->keys(); 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /tests/PredisStoreTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests; 13 | 14 | use Predis\Client; 15 | use Predis\Connection\ConnectionException; 16 | use Webmozart\KeyValueStore\PredisStore; 17 | use Webmozart\KeyValueStore\Tests\Fixtures\TestException; 18 | 19 | /** 20 | * @since 1.0 21 | * 22 | * @author Bernhard Schussek 23 | */ 24 | class PredisStoreTest extends AbstractKeyValueStoreTest 25 | { 26 | private static $supported; 27 | 28 | public static function setUpBeforeClass() 29 | { 30 | parent::setUpBeforeClass(); 31 | 32 | $client = new Client(); 33 | 34 | try { 35 | $client->connect(); 36 | $client->disconnect(); 37 | self::$supported = true; 38 | } catch (ConnectionException $e) { 39 | self::$supported = false; 40 | } 41 | } 42 | 43 | protected function setUp() 44 | { 45 | if (!self::$supported) { 46 | $this->markTestSkipped('Redis is not running.'); 47 | } 48 | 49 | parent::setUp(); 50 | } 51 | 52 | protected function createStore() 53 | { 54 | return new PredisStore(); 55 | } 56 | 57 | /** 58 | * @expectedException \Webmozart\KeyValueStore\Api\WriteException 59 | * @expectedExceptionMessage I failed! 60 | */ 61 | public function testSetThrowsWriteExceptionIfWriteFails() 62 | { 63 | $exception = new TestException('I failed!'); 64 | 65 | $client = $this->getMock('Predis\ClientInterface'); 66 | 67 | $client->expects($this->once()) 68 | ->method('__call') 69 | ->with('set') 70 | ->willThrowException($exception); 71 | 72 | $store = new PredisStore($client); 73 | $store->set('key', 'value'); 74 | } 75 | 76 | /** 77 | * @expectedException \Webmozart\KeyValueStore\Api\WriteException 78 | * @expectedExceptionMessage I failed! 79 | */ 80 | public function testRemoveThrowsWriteExceptionIfWriteFails() 81 | { 82 | $exception = new TestException('I failed!'); 83 | 84 | $client = $this->getMock('Predis\ClientInterface'); 85 | 86 | $client->expects($this->once()) 87 | ->method('__call') 88 | ->with('del') 89 | ->willThrowException($exception); 90 | 91 | $store = new PredisStore($client); 92 | $store->remove('key'); 93 | } 94 | 95 | /** 96 | * @expectedException \Webmozart\KeyValueStore\Api\WriteException 97 | * @expectedExceptionMessage I failed! 98 | */ 99 | public function testClearThrowsWriteExceptionIfWriteFails() 100 | { 101 | $exception = new TestException('I failed!'); 102 | 103 | $client = $this->getMock('Predis\ClientInterface'); 104 | 105 | $client->expects($this->once()) 106 | ->method('__call') 107 | ->with('flushdb') 108 | ->willThrowException($exception); 109 | 110 | $store = new PredisStore($client); 111 | $store->clear(); 112 | } 113 | 114 | /** 115 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 116 | * @expectedExceptionMessage I failed! 117 | */ 118 | public function testGetThrowsReadExceptionIfReadFails() 119 | { 120 | $exception = new TestException('I failed!'); 121 | 122 | $client = $this->getMock('Predis\ClientInterface'); 123 | 124 | $client->expects($this->once()) 125 | ->method('__call') 126 | ->with('get') 127 | ->willThrowException($exception); 128 | 129 | $store = new PredisStore($client); 130 | $store->get('key'); 131 | } 132 | 133 | /** 134 | * @expectedException \Webmozart\KeyValueStore\Api\UnserializationFailedException 135 | */ 136 | public function testGetThrowsExceptionIfNotUnserializable() 137 | { 138 | $client = $this->getMock('Predis\ClientInterface'); 139 | 140 | $client->expects($this->once()) 141 | ->method('__call') 142 | ->with('get') 143 | ->willReturn('foobar'); 144 | 145 | $store = new PredisStore($client); 146 | $store->get('key'); 147 | } 148 | 149 | /** 150 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 151 | * @expectedExceptionMessage I failed! 152 | */ 153 | public function testGetOrFailThrowsReadExceptionIfReadFails() 154 | { 155 | $exception = new TestException('I failed!'); 156 | 157 | $client = $this->getMock('Predis\ClientInterface'); 158 | 159 | $client->expects($this->once()) 160 | ->method('__call') 161 | ->with('get') 162 | ->willThrowException($exception); 163 | 164 | $store = new PredisStore($client); 165 | $store->getOrFail('key'); 166 | } 167 | 168 | /** 169 | * @expectedException \Webmozart\KeyValueStore\Api\UnserializationFailedException 170 | */ 171 | public function testGetOrFailThrowsExceptionIfNotUnserializable() 172 | { 173 | $client = $this->getMock('Predis\ClientInterface'); 174 | 175 | $client->expects($this->once()) 176 | ->method('__call') 177 | ->with('get') 178 | ->willReturn('foobar'); 179 | 180 | $store = new PredisStore($client); 181 | $store->getOrFail('key'); 182 | } 183 | 184 | /** 185 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 186 | * @expectedExceptionMessage I failed! 187 | */ 188 | public function testGetMultipleThrowsReadExceptionIfReadFails() 189 | { 190 | $exception = new TestException('I failed!'); 191 | 192 | $client = $this->getMock('Predis\ClientInterface'); 193 | 194 | $client->expects($this->once()) 195 | ->method('__call') 196 | ->with('mget') 197 | ->willThrowException($exception); 198 | 199 | $store = new PredisStore($client); 200 | $store->getMultiple(array('key')); 201 | } 202 | 203 | /** 204 | * @expectedException \Webmozart\KeyValueStore\Api\UnserializationFailedException 205 | */ 206 | public function testGetMultipleThrowsExceptionIfNotUnserializable() 207 | { 208 | $client = $this->getMock('Predis\ClientInterface'); 209 | 210 | $client->expects($this->once()) 211 | ->method('__call') 212 | ->with('mget') 213 | ->willReturn(array('foobar')); 214 | 215 | $store = new PredisStore($client); 216 | $store->getMultiple(array('key')); 217 | } 218 | 219 | /** 220 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 221 | * @expectedExceptionMessage I failed! 222 | */ 223 | public function testGetMultipleOrFailThrowsReadExceptionIfReadFails() 224 | { 225 | $exception = new TestException('I failed!'); 226 | 227 | $client = $this->getMock('Predis\ClientInterface'); 228 | 229 | $client->expects($this->once()) 230 | ->method('__call') 231 | ->with('mget') 232 | ->willThrowException($exception); 233 | 234 | $store = new PredisStore($client); 235 | $store->getMultipleOrFail(array('key')); 236 | } 237 | 238 | /** 239 | * @expectedException \Webmozart\KeyValueStore\Api\UnserializationFailedException 240 | */ 241 | public function testGetMultipleOrFailThrowsExceptionIfNotUnserializable() 242 | { 243 | $client = $this->getMock('Predis\ClientInterface'); 244 | 245 | $client->expects($this->once()) 246 | ->method('__call') 247 | ->with('mget') 248 | ->willReturn(array('foobar')); 249 | 250 | $store = new PredisStore($client); 251 | $store->getMultipleOrFail(array('key')); 252 | } 253 | 254 | /** 255 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 256 | * @expectedExceptionMessage I failed! 257 | */ 258 | public function testExistsThrowsReadExceptionIfReadFails() 259 | { 260 | $exception = new TestException('I failed!'); 261 | 262 | $client = $this->getMock('Predis\ClientInterface'); 263 | 264 | $client->expects($this->once()) 265 | ->method('__call') 266 | ->with('exists') 267 | ->willThrowException($exception); 268 | 269 | $store = new PredisStore($client); 270 | $store->exists('key'); 271 | } 272 | 273 | /** 274 | * @expectedException \Webmozart\KeyValueStore\Api\ReadException 275 | * @expectedExceptionMessage I failed! 276 | */ 277 | public function testKeysThrowsReadExceptionIfReadFails() 278 | { 279 | $exception = new TestException('I failed!'); 280 | 281 | $client = $this->getMock('Predis\ClientInterface'); 282 | 283 | $client->expects($this->once()) 284 | ->method('__call') 285 | ->with('keys') 286 | ->willThrowException($exception); 287 | 288 | $store = new PredisStore($client); 289 | $store->keys(); 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /tests/SerializingArrayStoreTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests; 13 | 14 | use Webmozart\KeyValueStore\SerializingArrayStore; 15 | 16 | /** 17 | * @since 1.0 18 | * 19 | * @author Bernhard Schussek 20 | */ 21 | class SerializingArrayStoreTest extends ArrayStoreTest 22 | { 23 | protected function createStore() 24 | { 25 | return new SerializingArrayStore(); 26 | } 27 | 28 | protected function createPopulatedStore(array $values) 29 | { 30 | return new SerializingArrayStore($values); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/SerializingBinaryMongoDbStoreTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests; 13 | 14 | use Webmozart\KeyValueStore\MongoDbStore; 15 | 16 | /** 17 | * @since 1.0 18 | * 19 | * @author Bernhard Schussek 20 | */ 21 | class SerializingBinaryMongoDbStoreTest extends AbstractMongoDbStoreTest 22 | { 23 | protected function createStore() 24 | { 25 | $collection = $this->client->selectCollection( 26 | self::DATABASE_NAME, 27 | self::COLLECTION_NAME 28 | ); 29 | 30 | return new MongoDbStore($collection, MongoDbStore::SUPPORT_BINARY); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/SerializingMongoDbStoreTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests; 13 | 14 | use Webmozart\KeyValueStore\MongoDbStore; 15 | 16 | /** 17 | * @since 1.0 18 | * 19 | * @author Bernhard Schussek 20 | */ 21 | class SerializingMongoDbStoreTest extends AbstractMongoDbStoreTest 22 | { 23 | protected function createStore() 24 | { 25 | $collection = $this->client->selectCollection( 26 | self::DATABASE_NAME, 27 | self::COLLECTION_NAME 28 | ); 29 | 30 | return new MongoDbStore($collection); 31 | } 32 | 33 | /** 34 | * @dataProvider provideBinaryValues 35 | */ 36 | public function testSetSupportsBinaryValues($value) 37 | { 38 | // JSON cannot handle binary data 39 | $this->setExpectedException('\Webmozart\KeyValueStore\Api\UnsupportedValueException'); 40 | 41 | parent::testSetSupportsBinaryValues($value); 42 | } 43 | 44 | /** 45 | * @dataProvider provideBinaryArrayValues 46 | */ 47 | public function testSetSupportsBinaryArrayValues($value) 48 | { 49 | // JSON cannot handle binary data 50 | $this->setExpectedException('\Webmozart\KeyValueStore\Api\UnsupportedValueException'); 51 | 52 | parent::testSetSupportsBinaryArrayValues($value); 53 | } 54 | 55 | /** 56 | * @dataProvider provideBinaryObjectValues 57 | */ 58 | public function testSetSupportsBinaryObjectValues($value) 59 | { 60 | // JSON cannot handle binary data 61 | $this->setExpectedException('\Webmozart\KeyValueStore\Api\UnsupportedValueException'); 62 | 63 | parent::testSetSupportsBinaryObjectValues($value); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/Util/SerializerTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Webmozart\KeyValueStore\Tests\Util; 13 | 14 | use PHPUnit_Framework_TestCase; 15 | use Webmozart\KeyValueStore\Api\SerializationFailedException; 16 | use Webmozart\KeyValueStore\Api\UnserializationFailedException; 17 | use Webmozart\KeyValueStore\Tests\Fixtures\NotSerializable; 18 | use Webmozart\KeyValueStore\Util\Serializer; 19 | 20 | /** 21 | * @since 1.0 22 | * 23 | * @author Bernhard Schussek 24 | */ 25 | class SerializerTest extends PHPUnit_Framework_TestCase 26 | { 27 | public function testSerialize() 28 | { 29 | $data = (object) array('foo' => 'bar'); 30 | 31 | $this->assertEquals($data, Serializer::unserialize(Serializer::serialize($data))); 32 | } 33 | 34 | public function testSerializeFailsIfResource() 35 | { 36 | $data = fopen(__FILE__, 'r'); 37 | 38 | try { 39 | Serializer::serialize($data); 40 | $this->fail('Expected a SerializationFailedException'); 41 | } catch (SerializationFailedException $e) { 42 | } 43 | 44 | $this->assertTrue(true, 'Exception caught'); 45 | } 46 | 47 | public function testSerializeFailsIfNotSerializable() 48 | { 49 | $data = new NotSerializable(); 50 | 51 | try { 52 | Serializer::serialize($data); 53 | $this->fail('Expected a SerializationFailedException'); 54 | } catch (SerializationFailedException $e) { 55 | } 56 | 57 | $this->assertTrue(true, 'Exception caught'); 58 | } 59 | 60 | public function testUnserializeFailsIfInvalidString() 61 | { 62 | try { 63 | Serializer::unserialize('foobar'); 64 | $this->fail('Expected an UnserializationFailedException'); 65 | } catch (UnserializationFailedException $e) { 66 | $this->assertContains('Error at offset 0', $e->getMessage()); 67 | } 68 | } 69 | 70 | public function testUnserializeFailsIfNoString() 71 | { 72 | try { 73 | Serializer::unserialize(1234); 74 | $this->fail('Expected an UnserializationFailedException'); 75 | } catch (UnserializationFailedException $e) { 76 | $this->assertContains('Could not unserialize value of type integer.', $e->getMessage()); 77 | } 78 | } 79 | } 80 | --------------------------------------------------------------------------------