├── .travis.yml ├── LICENSE ├── README.md ├── Vagrantfile ├── behat.yml ├── composer.json ├── composer.lock ├── doc ├── 00-intro.md ├── 01-installation.md ├── 02-usage.md └── 03-annotations.md ├── features └── predis_repository.feature ├── phpunit.xml.dist ├── provisioning └── puppet │ └── manifests │ ├── files │ ├── iptables │ └── php.ini │ └── site.pp ├── src └── Tystr │ └── RedisOrm │ ├── Annotations │ ├── Date.php │ ├── Field.php │ ├── Id.php │ ├── Index.php │ ├── Prefix.php │ └── SortedIndex.php │ ├── Context │ ├── BaseContext.php │ └── MainContext.php │ ├── Criteria │ ├── AndGroup.php │ ├── AndGroupInterface.php │ ├── Criteria.php │ ├── CriteriaInterface.php │ ├── EqualTo.php │ ├── EqualToInterface.php │ ├── GreaterThan.php │ ├── GreaterThanInterface.php │ ├── GreaterThanXDaysAgoInterface.php │ ├── LessThan.php │ ├── LessThanInterface.php │ ├── LessThanXDaysAgoInterface.php │ ├── OrGroup.php │ ├── OrGroupInterface.php │ ├── Restriction.php │ ├── RestrictionInterface.php │ ├── Restrictions.php │ └── RestrictionsKeyGenerator.php │ ├── DataTransformer │ ├── DataTypes.php │ └── TimestampToDatetimeTransformer.php │ ├── Exception │ ├── InvalidArgumentException.php │ ├── InvalidCriteriaException.php │ ├── InvalidMetadataException.php │ └── InvalidRestrictionValue.php │ ├── Hydrator │ ├── ObjectHydrator.php │ └── ObjectHydratorInterface.php │ ├── KeyNamingStrategy │ ├── ColonDelimitedKeyNamingStrategy.php │ └── KeyNamingStrategyInterface.php │ ├── Metadata │ ├── AnnotationMetadataLoader.php │ ├── Metadata.php │ └── MetadataRegistry.php │ ├── Query │ └── ZRangeByScore.php │ ├── Repository │ └── ObjectRepository.php │ └── Test │ └── Model │ ├── Car.php │ ├── User.php │ └── UserList.php └── tests ├── Tystr └── RedisOrm │ └── Tests │ ├── Criteria │ └── RestrictionsKeyGeneratorTest.php │ ├── DataTransformer │ └── TimestampToDatetimeTransformerTest.php │ ├── Hydrator │ └── ObjectHydratorTest.php │ ├── KeyNamingStrategy │ └── ColonDelimitedKeyNamingStrategyTest.php │ ├── Metadata │ ├── MetadataRegistryTest.php │ └── MetadataTest.php │ └── Model │ └── Person.php └── bootstrap.php /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3.3 5 | - 5.3 6 | - 5.4 7 | - 5.5 8 | - 5.6 9 | 10 | services: 11 | - redis-server 12 | 13 | before_script: composer install -n 14 | 15 | script: 16 | - ./vendor/bin/behat 17 | - ./vendor/bin/phpunit 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2016 Tyler Stroud 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Redis ORM 2 | ========= 3 | [![Build Status](https://travis-ci.org/tystr/redis-orm.svg?branch=master)](https://travis-ci.org/tystr/redis-orm) 4 | 5 | This is a small object mapper library designed to assist in storing objects into a [Redis][1] database 6 | while maintaining indexes for the fields of the object for efficient querying and filtering. 7 | 8 | This project is under development. Use at your own risk. 9 | 10 | TODO: 11 | 12 | - Associated objects 13 | - Use redis transactions 14 | 15 | Support 16 | ======= 17 | 18 | Freenode IRC: #redis-orm 19 | 20 | Installation 21 | ============ 22 | Add to your project via composer: 23 | 24 | $ composer.phar require tystr/redis-orm:1.0.*@dev 25 | 26 | Setting up the Development Environment 27 | ====================================== 28 | You'll need [Vagrant][2] installed and configured correctly. 29 | 30 | Simply run the following command to get your VM up and running: 31 | 32 | $ vagrant up 33 | 34 | To run the [Behat][3] test suite: 35 | 36 | $ vagrant ssh 37 | $ cd /vagrant 38 | $ vendor/bin/behat 39 | 40 | Read The Documentation 41 | ====================== 42 | * **[Intro](doc/00-intro.md)** 43 | * **[Installation](doc/01-installation.md)** 44 | * **[Usage](doc/02-usage.md)** 45 | * **[Annotations](doc/03-annotations.md)** 46 | 47 | [1]: http://redis.io/ 48 | [2]: http://vagrantup.com/ 49 | [3]: http://docs.behat.org/en/v3.0/ -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | VAGRANTFILE_API_VERSION = "2" 5 | 6 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 7 | config.vm.box = "nrel-CentOS-6.4-x86_64" 8 | config.vm.box_url = "http://developer.nrel.gov/downloads/vagrant-boxes/CentOS-6.4-x86_64-v20131103.box" 9 | config.vm.hostname = "tystr-redis-orm.dev" 10 | 11 | config.vm.network :private_network, ip: "192.168.42.42" 12 | config.vm.synced_folder ".", "/vagrant", :nfs => true 13 | 14 | config.vm.provider :virtualbox do |vb| 15 | vb.customize ["modifyvm", :id, "--memory", "2048"] 16 | vb.customize ["modifyvm", :id, "--cpus", "4"] 17 | end 18 | 19 | config.vm.provision :puppet do |puppet| 20 | puppet.manifests_path = "provisioning/puppet/manifests" 21 | puppet.manifest_file = "site.pp" 22 | end 23 | end -------------------------------------------------------------------------------- /behat.yml: -------------------------------------------------------------------------------- 1 | default: 2 | suites: 3 | default: 4 | contexts: [ Tystr\RedisOrm\Context\MainContext ] -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tystr/redis-orm", 3 | "type": "library", 4 | "description": "A simple PHP object mapper to assist in persisting objects in Redis and indexing them for efficient searching and filtering.", 5 | "keywords" : [ "redis", "orm", "redis-orm", "key", "value"], 6 | "require": { 7 | "predis/predis": "~1.0", 8 | "doctrine/annotations": "~1.2", 9 | "symfony/config": "~2.5", 10 | "doctrine/collections": "~1.2" 11 | }, 12 | "require-dev": { 13 | "phpunit/phpunit": "4.1.*", 14 | "fzaninotto/faker": "1.5.*@dev", 15 | "behat/behat": "~3.0.6" 16 | }, 17 | "autoload": { 18 | "psr-4": { "Tystr\\RedisOrm\\": "src/Tystr/RedisOrm" } 19 | }, 20 | "autoload-dev": { 21 | "files": [ "vendor/phpunit/phpunit/src/Framework/Assert/Functions.php" ] 22 | }, 23 | "license": "MIT", 24 | "authors": [ 25 | { 26 | "name": "Tyler Stroud", 27 | "email": "tyler@tylerstroud.com" 28 | } 29 | ], 30 | "minimum-stability": "stable", 31 | "extra": { 32 | "branch-alias": { 33 | "dev-master": "1.0.x-dev" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "88962a50ce65b0092441220d9184d5da", 8 | "packages": [ 9 | { 10 | "name": "doctrine/annotations", 11 | "version": "v1.2.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/doctrine/annotations.git", 15 | "reference": "d9b1a37e9351ddde1f19f09a02e3d6ee92e82efd" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/doctrine/annotations/zipball/d9b1a37e9351ddde1f19f09a02e3d6ee92e82efd", 20 | "reference": "d9b1a37e9351ddde1f19f09a02e3d6ee92e82efd", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "doctrine/lexer": "1.*", 25 | "php": ">=5.3.2" 26 | }, 27 | "require-dev": { 28 | "doctrine/cache": "1.*", 29 | "phpunit/phpunit": "4.*" 30 | }, 31 | "type": "library", 32 | "extra": { 33 | "branch-alias": { 34 | "dev-master": "1.3.x-dev" 35 | } 36 | }, 37 | "autoload": { 38 | "psr-0": { 39 | "Doctrine\\Common\\Annotations\\": "lib/" 40 | } 41 | }, 42 | "notification-url": "https://packagist.org/downloads/", 43 | "license": [ 44 | "MIT" 45 | ], 46 | "authors": [ 47 | { 48 | "name": "Jonathan Wage", 49 | "email": "jonwage@gmail.com", 50 | "homepage": "http://www.jwage.com/", 51 | "role": "Creator" 52 | }, 53 | { 54 | "name": "Guilherme Blanco", 55 | "email": "guilhermeblanco@gmail.com", 56 | "homepage": "http://www.instaclick.com" 57 | }, 58 | { 59 | "name": "Roman Borschel", 60 | "email": "roman@code-factory.org" 61 | }, 62 | { 63 | "name": "Benjamin Eberlei", 64 | "email": "kontakt@beberlei.de" 65 | }, 66 | { 67 | "name": "Johannes Schmitt", 68 | "email": "schmittjoh@gmail.com", 69 | "homepage": "https://github.com/schmittjoh", 70 | "role": "Developer of wrapped JMSSerializerBundle" 71 | } 72 | ], 73 | "description": "Docblock Annotations Parser", 74 | "homepage": "http://www.doctrine-project.org", 75 | "keywords": [ 76 | "annotations", 77 | "docblock", 78 | "parser" 79 | ], 80 | "time": "2014-07-06 15:52:21" 81 | }, 82 | { 83 | "name": "doctrine/collections", 84 | "version": "v1.2", 85 | "source": { 86 | "type": "git", 87 | "url": "https://github.com/doctrine/collections.git", 88 | "reference": "b99c5c46c87126201899afe88ec490a25eedd6a2" 89 | }, 90 | "dist": { 91 | "type": "zip", 92 | "url": "https://api.github.com/repos/doctrine/collections/zipball/b99c5c46c87126201899afe88ec490a25eedd6a2", 93 | "reference": "b99c5c46c87126201899afe88ec490a25eedd6a2", 94 | "shasum": "" 95 | }, 96 | "require": { 97 | "php": ">=5.3.2" 98 | }, 99 | "type": "library", 100 | "extra": { 101 | "branch-alias": { 102 | "dev-master": "1.2.x-dev" 103 | } 104 | }, 105 | "autoload": { 106 | "psr-0": { 107 | "Doctrine\\Common\\Collections\\": "lib/" 108 | } 109 | }, 110 | "notification-url": "https://packagist.org/downloads/", 111 | "license": [ 112 | "MIT" 113 | ], 114 | "authors": [ 115 | { 116 | "name": "Jonathan Wage", 117 | "email": "jonwage@gmail.com", 118 | "homepage": "http://www.jwage.com/", 119 | "role": "Creator" 120 | }, 121 | { 122 | "name": "Guilherme Blanco", 123 | "email": "guilhermeblanco@gmail.com", 124 | "homepage": "http://www.instaclick.com" 125 | }, 126 | { 127 | "name": "Roman Borschel", 128 | "email": "roman@code-factory.org" 129 | }, 130 | { 131 | "name": "Benjamin Eberlei", 132 | "email": "kontakt@beberlei.de" 133 | }, 134 | { 135 | "name": "Johannes Schmitt", 136 | "email": "schmittjoh@gmail.com", 137 | "homepage": "https://github.com/schmittjoh", 138 | "role": "Developer of wrapped JMSSerializerBundle" 139 | } 140 | ], 141 | "description": "Collections Abstraction library", 142 | "homepage": "http://www.doctrine-project.org", 143 | "keywords": [ 144 | "array", 145 | "collections", 146 | "iterator" 147 | ], 148 | "time": "2014-02-03 23:07:43" 149 | }, 150 | { 151 | "name": "doctrine/lexer", 152 | "version": "v1.0", 153 | "source": { 154 | "type": "git", 155 | "url": "https://github.com/doctrine/lexer.git", 156 | "reference": "2f708a85bb3aab5d99dab8be435abd73e0b18acb" 157 | }, 158 | "dist": { 159 | "type": "zip", 160 | "url": "https://api.github.com/repos/doctrine/lexer/zipball/2f708a85bb3aab5d99dab8be435abd73e0b18acb", 161 | "reference": "2f708a85bb3aab5d99dab8be435abd73e0b18acb", 162 | "shasum": "" 163 | }, 164 | "require": { 165 | "php": ">=5.3.2" 166 | }, 167 | "type": "library", 168 | "autoload": { 169 | "psr-0": { 170 | "Doctrine\\Common\\Lexer\\": "lib/" 171 | } 172 | }, 173 | "notification-url": "https://packagist.org/downloads/", 174 | "license": [ 175 | "MIT" 176 | ], 177 | "authors": [ 178 | { 179 | "name": "Guilherme Blanco", 180 | "email": "guilhermeblanco@gmail.com", 181 | "homepage": "http://www.instaclick.com" 182 | }, 183 | { 184 | "name": "Roman Borschel", 185 | "email": "roman@code-factory.org" 186 | }, 187 | { 188 | "name": "Johannes Schmitt", 189 | "email": "schmittjoh@gmail.com", 190 | "homepage": "https://github.com/schmittjoh", 191 | "role": "Developer of wrapped JMSSerializerBundle" 192 | } 193 | ], 194 | "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", 195 | "homepage": "http://www.doctrine-project.org", 196 | "keywords": [ 197 | "lexer", 198 | "parser" 199 | ], 200 | "time": "2013-01-12 18:59:04" 201 | }, 202 | { 203 | "name": "predis/predis", 204 | "version": "v1.0.0", 205 | "source": { 206 | "type": "git", 207 | "url": "https://github.com/nrk/predis.git", 208 | "reference": "d4be306d0aca28b5633b96adef03b29fea569c2f" 209 | }, 210 | "dist": { 211 | "type": "zip", 212 | "url": "https://api.github.com/repos/nrk/predis/zipball/d4be306d0aca28b5633b96adef03b29fea569c2f", 213 | "reference": "d4be306d0aca28b5633b96adef03b29fea569c2f", 214 | "shasum": "" 215 | }, 216 | "require": { 217 | "php": ">=5.3.2" 218 | }, 219 | "require-dev": { 220 | "phpunit/phpunit": "~4.0" 221 | }, 222 | "suggest": { 223 | "ext-curl": "Allows access to Webdis when paired with phpiredis", 224 | "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol" 225 | }, 226 | "type": "library", 227 | "extra": { 228 | "branch-alias": { 229 | "dev-master": "1.0-dev" 230 | } 231 | }, 232 | "autoload": { 233 | "psr-4": { 234 | "Predis\\": "src/" 235 | } 236 | }, 237 | "notification-url": "https://packagist.org/downloads/", 238 | "license": [ 239 | "MIT" 240 | ], 241 | "authors": [ 242 | { 243 | "name": "Daniele Alessandri", 244 | "email": "suppakilla@gmail.com", 245 | "homepage": "http://clorophilla.net" 246 | } 247 | ], 248 | "description": "Flexible and feature-complete PHP client library for Redis", 249 | "homepage": "http://github.com/nrk/predis", 250 | "keywords": [ 251 | "nosql", 252 | "predis", 253 | "redis" 254 | ], 255 | "time": "2014-08-01 09:59:50" 256 | }, 257 | { 258 | "name": "symfony/config", 259 | "version": "v2.5.3", 260 | "target-dir": "Symfony/Component/Config", 261 | "source": { 262 | "type": "git", 263 | "url": "https://github.com/symfony/Config.git", 264 | "reference": "8d044668c7ccb4ade684e368d910e3aadcff6f6c" 265 | }, 266 | "dist": { 267 | "type": "zip", 268 | "url": "https://api.github.com/repos/symfony/Config/zipball/8d044668c7ccb4ade684e368d910e3aadcff6f6c", 269 | "reference": "8d044668c7ccb4ade684e368d910e3aadcff6f6c", 270 | "shasum": "" 271 | }, 272 | "require": { 273 | "php": ">=5.3.3", 274 | "symfony/filesystem": "~2.3" 275 | }, 276 | "type": "library", 277 | "extra": { 278 | "branch-alias": { 279 | "dev-master": "2.5-dev" 280 | } 281 | }, 282 | "autoload": { 283 | "psr-0": { 284 | "Symfony\\Component\\Config\\": "" 285 | } 286 | }, 287 | "notification-url": "https://packagist.org/downloads/", 288 | "license": [ 289 | "MIT" 290 | ], 291 | "authors": [ 292 | { 293 | "name": "Symfony Community", 294 | "homepage": "http://symfony.com/contributors" 295 | }, 296 | { 297 | "name": "Fabien Potencier", 298 | "email": "fabien@symfony.com" 299 | } 300 | ], 301 | "description": "Symfony Config Component", 302 | "homepage": "http://symfony.com", 303 | "time": "2014-08-05 09:00:40" 304 | }, 305 | { 306 | "name": "symfony/filesystem", 307 | "version": "v2.5.3", 308 | "target-dir": "Symfony/Component/Filesystem", 309 | "source": { 310 | "type": "git", 311 | "url": "https://github.com/symfony/Filesystem.git", 312 | "reference": "c1309b0ee195ad264a4314435bdaecdfacb8ae9c" 313 | }, 314 | "dist": { 315 | "type": "zip", 316 | "url": "https://api.github.com/repos/symfony/Filesystem/zipball/c1309b0ee195ad264a4314435bdaecdfacb8ae9c", 317 | "reference": "c1309b0ee195ad264a4314435bdaecdfacb8ae9c", 318 | "shasum": "" 319 | }, 320 | "require": { 321 | "php": ">=5.3.3" 322 | }, 323 | "type": "library", 324 | "extra": { 325 | "branch-alias": { 326 | "dev-master": "2.5-dev" 327 | } 328 | }, 329 | "autoload": { 330 | "psr-0": { 331 | "Symfony\\Component\\Filesystem\\": "" 332 | } 333 | }, 334 | "notification-url": "https://packagist.org/downloads/", 335 | "license": [ 336 | "MIT" 337 | ], 338 | "authors": [ 339 | { 340 | "name": "Symfony Community", 341 | "homepage": "http://symfony.com/contributors" 342 | }, 343 | { 344 | "name": "Fabien Potencier", 345 | "email": "fabien@symfony.com" 346 | } 347 | ], 348 | "description": "Symfony Filesystem Component", 349 | "homepage": "http://symfony.com", 350 | "time": "2014-07-09 09:05:48" 351 | } 352 | ], 353 | "packages-dev": [ 354 | { 355 | "name": "behat/behat", 356 | "version": "v3.0.12", 357 | "source": { 358 | "type": "git", 359 | "url": "https://github.com/Behat/Behat.git", 360 | "reference": "0fb9a9494a92374ddcc741374cef68c4cbf6fa05" 361 | }, 362 | "dist": { 363 | "type": "zip", 364 | "url": "https://api.github.com/repos/Behat/Behat/zipball/0fb9a9494a92374ddcc741374cef68c4cbf6fa05", 365 | "reference": "0fb9a9494a92374ddcc741374cef68c4cbf6fa05", 366 | "shasum": "" 367 | }, 368 | "require": { 369 | "behat/gherkin": "~4.3", 370 | "behat/transliterator": "~1.0", 371 | "ext-mbstring": "*", 372 | "php": ">=5.3.3", 373 | "symfony/class-loader": "~2.1", 374 | "symfony/config": "~2.3", 375 | "symfony/console": "~2.1", 376 | "symfony/dependency-injection": "~2.1", 377 | "symfony/event-dispatcher": "~2.1", 378 | "symfony/translation": "~2.1", 379 | "symfony/yaml": "~2.1" 380 | }, 381 | "require-dev": { 382 | "phpspec/prophecy-phpunit": "~1.0", 383 | "phpunit/phpunit": "~4.0.7", 384 | "symfony/process": "~2.1" 385 | }, 386 | "suggest": { 387 | "behat/mink-extension": "for integration with Mink testing framework", 388 | "behat/symfony2-extension": "for integration with Symfony2 web framework", 389 | "behat/yii-extension": "for integration with Yii web framework" 390 | }, 391 | "bin": [ 392 | "bin/behat" 393 | ], 394 | "type": "library", 395 | "extra": { 396 | "branch-alias": { 397 | "dev-master": "3.0.x-dev" 398 | } 399 | }, 400 | "autoload": { 401 | "psr-0": { 402 | "Behat\\Behat": "src/", 403 | "Behat\\Testwork": "src/" 404 | } 405 | }, 406 | "notification-url": "https://packagist.org/downloads/", 407 | "license": [ 408 | "MIT" 409 | ], 410 | "authors": [ 411 | { 412 | "name": "Konstantin Kudryashov", 413 | "email": "ever.zet@gmail.com", 414 | "homepage": "http://everzet.com" 415 | } 416 | ], 417 | "description": "Scenario-oriented BDD framework for PHP 5.3", 418 | "homepage": "http://behat.org/", 419 | "keywords": [ 420 | "Agile", 421 | "BDD", 422 | "ScenarioBDD", 423 | "Scrum", 424 | "StoryBDD", 425 | "User story", 426 | "business", 427 | "development", 428 | "documentation", 429 | "examples", 430 | "symfony", 431 | "testing" 432 | ], 433 | "time": "2014-07-17 06:37:41" 434 | }, 435 | { 436 | "name": "behat/gherkin", 437 | "version": "v4.3.0", 438 | "source": { 439 | "type": "git", 440 | "url": "https://github.com/Behat/Gherkin.git", 441 | "reference": "43777c51058b77bcd84a8775b7a6ad05e986b5db" 442 | }, 443 | "dist": { 444 | "type": "zip", 445 | "url": "https://api.github.com/repos/Behat/Gherkin/zipball/43777c51058b77bcd84a8775b7a6ad05e986b5db", 446 | "reference": "43777c51058b77bcd84a8775b7a6ad05e986b5db", 447 | "shasum": "" 448 | }, 449 | "require": { 450 | "php": ">=5.3.1" 451 | }, 452 | "require-dev": { 453 | "phpunit/phpunit": "~4.0", 454 | "symfony/yaml": "~2.1" 455 | }, 456 | "suggest": { 457 | "symfony/yaml": "If you want to parse features, represented in YAML files" 458 | }, 459 | "type": "library", 460 | "extra": { 461 | "branch-alias": { 462 | "dev-master": "4.2-dev" 463 | } 464 | }, 465 | "autoload": { 466 | "psr-0": { 467 | "Behat\\Gherkin": "src/" 468 | } 469 | }, 470 | "notification-url": "https://packagist.org/downloads/", 471 | "license": [ 472 | "MIT" 473 | ], 474 | "authors": [ 475 | { 476 | "name": "Konstantin Kudryashov", 477 | "email": "ever.zet@gmail.com", 478 | "homepage": "http://everzet.com" 479 | } 480 | ], 481 | "description": "Gherkin DSL parser for PHP 5.3", 482 | "homepage": "http://behat.org/", 483 | "keywords": [ 484 | "BDD", 485 | "Behat", 486 | "Cucumber", 487 | "DSL", 488 | "gherkin", 489 | "parser" 490 | ], 491 | "time": "2014-06-06 01:24:32" 492 | }, 493 | { 494 | "name": "behat/transliterator", 495 | "version": "v1.0.1", 496 | "source": { 497 | "type": "git", 498 | "url": "https://github.com/Behat/Transliterator.git", 499 | "reference": "c93521d3462a554332d1ef5bb0e9b5b8ca4106c4" 500 | }, 501 | "dist": { 502 | "type": "zip", 503 | "url": "https://api.github.com/repos/Behat/Transliterator/zipball/c93521d3462a554332d1ef5bb0e9b5b8ca4106c4", 504 | "reference": "c93521d3462a554332d1ef5bb0e9b5b8ca4106c4", 505 | "shasum": "" 506 | }, 507 | "require": { 508 | "php": ">=5.3.3" 509 | }, 510 | "type": "library", 511 | "extra": { 512 | "branch-alias": { 513 | "dev-master": "1.0-dev" 514 | } 515 | }, 516 | "autoload": { 517 | "psr-0": { 518 | "Behat\\Transliterator": "src/" 519 | } 520 | }, 521 | "notification-url": "https://packagist.org/downloads/", 522 | "license": [ 523 | "Artistic-1.0" 524 | ], 525 | "description": "String transliterator", 526 | "keywords": [ 527 | "i18n", 528 | "slug", 529 | "transliterator" 530 | ], 531 | "time": "2014-05-15 22:08:22" 532 | }, 533 | { 534 | "name": "fzaninotto/faker", 535 | "version": "dev-master", 536 | "source": { 537 | "type": "git", 538 | "url": "https://github.com/fzaninotto/Faker.git", 539 | "reference": "d46b8f55dd8cffec8655a844c22f4e5d08ff8d89" 540 | }, 541 | "dist": { 542 | "type": "zip", 543 | "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/d46b8f55dd8cffec8655a844c22f4e5d08ff8d89", 544 | "reference": "d46b8f55dd8cffec8655a844c22f4e5d08ff8d89", 545 | "shasum": "" 546 | }, 547 | "require": { 548 | "php": ">=5.3.3" 549 | }, 550 | "require-dev": { 551 | "phpunit/phpunit": "~4.0", 552 | "squizlabs/php_codesniffer": "~1.5" 553 | }, 554 | "type": "library", 555 | "extra": { 556 | "branch-alias": { 557 | "dev-master": "1.5.x-dev" 558 | } 559 | }, 560 | "autoload": { 561 | "psr-0": { 562 | "Faker": "src/", 563 | "Faker\\PHPUnit": "test/" 564 | } 565 | }, 566 | "notification-url": "https://packagist.org/downloads/", 567 | "license": [ 568 | "MIT" 569 | ], 570 | "authors": [ 571 | { 572 | "name": "François Zaninotto" 573 | } 574 | ], 575 | "description": "Faker is a PHP library that generates fake data for you.", 576 | "keywords": [ 577 | "data", 578 | "faker", 579 | "fixtures" 580 | ], 581 | "time": "2014-08-19 08:06:48" 582 | }, 583 | { 584 | "name": "phpunit/php-code-coverage", 585 | "version": "2.0.10", 586 | "source": { 587 | "type": "git", 588 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 589 | "reference": "6d196af48e8c100a3ae881940123e693da5a9217" 590 | }, 591 | "dist": { 592 | "type": "zip", 593 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6d196af48e8c100a3ae881940123e693da5a9217", 594 | "reference": "6d196af48e8c100a3ae881940123e693da5a9217", 595 | "shasum": "" 596 | }, 597 | "require": { 598 | "php": ">=5.3.3", 599 | "phpunit/php-file-iterator": "~1.3.1", 600 | "phpunit/php-text-template": "~1.2.0", 601 | "phpunit/php-token-stream": "~1.2.2", 602 | "sebastian/environment": "~1.0.0", 603 | "sebastian/version": "~1.0.3" 604 | }, 605 | "require-dev": { 606 | "ext-xdebug": ">=2.1.4", 607 | "phpunit/phpunit": "~4.0.14" 608 | }, 609 | "suggest": { 610 | "ext-dom": "*", 611 | "ext-xdebug": ">=2.2.1", 612 | "ext-xmlwriter": "*" 613 | }, 614 | "type": "library", 615 | "extra": { 616 | "branch-alias": { 617 | "dev-master": "2.0.x-dev" 618 | } 619 | }, 620 | "autoload": { 621 | "classmap": [ 622 | "src/" 623 | ] 624 | }, 625 | "notification-url": "https://packagist.org/downloads/", 626 | "include-path": [ 627 | "" 628 | ], 629 | "license": [ 630 | "BSD-3-Clause" 631 | ], 632 | "authors": [ 633 | { 634 | "name": "Sebastian Bergmann", 635 | "email": "sb@sebastian-bergmann.de", 636 | "role": "lead" 637 | } 638 | ], 639 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 640 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 641 | "keywords": [ 642 | "coverage", 643 | "testing", 644 | "xunit" 645 | ], 646 | "time": "2014-08-06 06:39:42" 647 | }, 648 | { 649 | "name": "phpunit/php-file-iterator", 650 | "version": "1.3.4", 651 | "source": { 652 | "type": "git", 653 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 654 | "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb" 655 | }, 656 | "dist": { 657 | "type": "zip", 658 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/acd690379117b042d1c8af1fafd61bde001bf6bb", 659 | "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb", 660 | "shasum": "" 661 | }, 662 | "require": { 663 | "php": ">=5.3.3" 664 | }, 665 | "type": "library", 666 | "autoload": { 667 | "classmap": [ 668 | "File/" 669 | ] 670 | }, 671 | "notification-url": "https://packagist.org/downloads/", 672 | "include-path": [ 673 | "" 674 | ], 675 | "license": [ 676 | "BSD-3-Clause" 677 | ], 678 | "authors": [ 679 | { 680 | "name": "Sebastian Bergmann", 681 | "email": "sb@sebastian-bergmann.de", 682 | "role": "lead" 683 | } 684 | ], 685 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 686 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 687 | "keywords": [ 688 | "filesystem", 689 | "iterator" 690 | ], 691 | "time": "2013-10-10 15:34:57" 692 | }, 693 | { 694 | "name": "phpunit/php-text-template", 695 | "version": "1.2.0", 696 | "source": { 697 | "type": "git", 698 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 699 | "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a" 700 | }, 701 | "dist": { 702 | "type": "zip", 703 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", 704 | "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", 705 | "shasum": "" 706 | }, 707 | "require": { 708 | "php": ">=5.3.3" 709 | }, 710 | "type": "library", 711 | "autoload": { 712 | "classmap": [ 713 | "Text/" 714 | ] 715 | }, 716 | "notification-url": "https://packagist.org/downloads/", 717 | "include-path": [ 718 | "" 719 | ], 720 | "license": [ 721 | "BSD-3-Clause" 722 | ], 723 | "authors": [ 724 | { 725 | "name": "Sebastian Bergmann", 726 | "email": "sb@sebastian-bergmann.de", 727 | "role": "lead" 728 | } 729 | ], 730 | "description": "Simple template engine.", 731 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 732 | "keywords": [ 733 | "template" 734 | ], 735 | "time": "2014-01-30 17:20:04" 736 | }, 737 | { 738 | "name": "phpunit/php-timer", 739 | "version": "1.0.5", 740 | "source": { 741 | "type": "git", 742 | "url": "https://github.com/sebastianbergmann/php-timer.git", 743 | "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c" 744 | }, 745 | "dist": { 746 | "type": "zip", 747 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/19689d4354b295ee3d8c54b4f42c3efb69cbc17c", 748 | "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c", 749 | "shasum": "" 750 | }, 751 | "require": { 752 | "php": ">=5.3.3" 753 | }, 754 | "type": "library", 755 | "autoload": { 756 | "classmap": [ 757 | "PHP/" 758 | ] 759 | }, 760 | "notification-url": "https://packagist.org/downloads/", 761 | "include-path": [ 762 | "" 763 | ], 764 | "license": [ 765 | "BSD-3-Clause" 766 | ], 767 | "authors": [ 768 | { 769 | "name": "Sebastian Bergmann", 770 | "email": "sb@sebastian-bergmann.de", 771 | "role": "lead" 772 | } 773 | ], 774 | "description": "Utility class for timing", 775 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 776 | "keywords": [ 777 | "timer" 778 | ], 779 | "time": "2013-08-02 07:42:54" 780 | }, 781 | { 782 | "name": "phpunit/php-token-stream", 783 | "version": "1.2.2", 784 | "source": { 785 | "type": "git", 786 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 787 | "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32" 788 | }, 789 | "dist": { 790 | "type": "zip", 791 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/ad4e1e23ae01b483c16f600ff1bebec184588e32", 792 | "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32", 793 | "shasum": "" 794 | }, 795 | "require": { 796 | "ext-tokenizer": "*", 797 | "php": ">=5.3.3" 798 | }, 799 | "type": "library", 800 | "extra": { 801 | "branch-alias": { 802 | "dev-master": "1.2-dev" 803 | } 804 | }, 805 | "autoload": { 806 | "classmap": [ 807 | "PHP/" 808 | ] 809 | }, 810 | "notification-url": "https://packagist.org/downloads/", 811 | "include-path": [ 812 | "" 813 | ], 814 | "license": [ 815 | "BSD-3-Clause" 816 | ], 817 | "authors": [ 818 | { 819 | "name": "Sebastian Bergmann", 820 | "email": "sb@sebastian-bergmann.de", 821 | "role": "lead" 822 | } 823 | ], 824 | "description": "Wrapper around PHP's tokenizer extension.", 825 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 826 | "keywords": [ 827 | "tokenizer" 828 | ], 829 | "time": "2014-03-03 05:10:30" 830 | }, 831 | { 832 | "name": "phpunit/phpunit", 833 | "version": "4.1.6", 834 | "source": { 835 | "type": "git", 836 | "url": "https://github.com/sebastianbergmann/phpunit.git", 837 | "reference": "241116219bb7e3b8111a36ffd8f37546888738d6" 838 | }, 839 | "dist": { 840 | "type": "zip", 841 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/241116219bb7e3b8111a36ffd8f37546888738d6", 842 | "reference": "241116219bb7e3b8111a36ffd8f37546888738d6", 843 | "shasum": "" 844 | }, 845 | "require": { 846 | "ext-dom": "*", 847 | "ext-json": "*", 848 | "ext-pcre": "*", 849 | "ext-reflection": "*", 850 | "ext-spl": "*", 851 | "php": ">=5.3.3", 852 | "phpunit/php-code-coverage": "~2.0", 853 | "phpunit/php-file-iterator": "~1.3.1", 854 | "phpunit/php-text-template": "~1.2", 855 | "phpunit/php-timer": "~1.0.2", 856 | "phpunit/phpunit-mock-objects": "2.1.5", 857 | "sebastian/comparator": "~1.0", 858 | "sebastian/diff": "~1.1", 859 | "sebastian/environment": "~1.0", 860 | "sebastian/exporter": "~1.0", 861 | "sebastian/version": "~1.0", 862 | "symfony/yaml": "~2.0" 863 | }, 864 | "suggest": { 865 | "phpunit/php-invoker": "~1.1" 866 | }, 867 | "bin": [ 868 | "phpunit" 869 | ], 870 | "type": "library", 871 | "extra": { 872 | "branch-alias": { 873 | "dev-master": "4.1.x-dev" 874 | } 875 | }, 876 | "autoload": { 877 | "classmap": [ 878 | "src/" 879 | ] 880 | }, 881 | "notification-url": "https://packagist.org/downloads/", 882 | "include-path": [ 883 | "", 884 | "../../symfony/yaml/" 885 | ], 886 | "license": [ 887 | "BSD-3-Clause" 888 | ], 889 | "authors": [ 890 | { 891 | "name": "Sebastian Bergmann", 892 | "email": "sebastian@phpunit.de", 893 | "role": "lead" 894 | } 895 | ], 896 | "description": "The PHP Unit Testing framework.", 897 | "homepage": "http://www.phpunit.de/", 898 | "keywords": [ 899 | "phpunit", 900 | "testing", 901 | "xunit" 902 | ], 903 | "time": "2014-08-17 08:07:02" 904 | }, 905 | { 906 | "name": "phpunit/phpunit-mock-objects", 907 | "version": "2.1.5", 908 | "source": { 909 | "type": "git", 910 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", 911 | "reference": "7878b9c41edb3afab92b85edf5f0981014a2713a" 912 | }, 913 | "dist": { 914 | "type": "zip", 915 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/7878b9c41edb3afab92b85edf5f0981014a2713a", 916 | "reference": "7878b9c41edb3afab92b85edf5f0981014a2713a", 917 | "shasum": "" 918 | }, 919 | "require": { 920 | "php": ">=5.3.3", 921 | "phpunit/php-text-template": "~1.2" 922 | }, 923 | "require-dev": { 924 | "phpunit/phpunit": "~4.1" 925 | }, 926 | "suggest": { 927 | "ext-soap": "*" 928 | }, 929 | "type": "library", 930 | "extra": { 931 | "branch-alias": { 932 | "dev-master": "2.1.x-dev" 933 | } 934 | }, 935 | "autoload": { 936 | "classmap": [ 937 | "src/" 938 | ] 939 | }, 940 | "notification-url": "https://packagist.org/downloads/", 941 | "include-path": [ 942 | "" 943 | ], 944 | "license": [ 945 | "BSD-3-Clause" 946 | ], 947 | "authors": [ 948 | { 949 | "name": "Sebastian Bergmann", 950 | "email": "sb@sebastian-bergmann.de", 951 | "role": "lead" 952 | } 953 | ], 954 | "description": "Mock Object library for PHPUnit", 955 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", 956 | "keywords": [ 957 | "mock", 958 | "xunit" 959 | ], 960 | "time": "2014-06-12 07:22:15" 961 | }, 962 | { 963 | "name": "sebastian/comparator", 964 | "version": "1.0.0", 965 | "source": { 966 | "type": "git", 967 | "url": "https://github.com/sebastianbergmann/comparator.git", 968 | "reference": "f7069ee51fa9fb6c038e16a9d0e3439f5449dcf2" 969 | }, 970 | "dist": { 971 | "type": "zip", 972 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/f7069ee51fa9fb6c038e16a9d0e3439f5449dcf2", 973 | "reference": "f7069ee51fa9fb6c038e16a9d0e3439f5449dcf2", 974 | "shasum": "" 975 | }, 976 | "require": { 977 | "php": ">=5.3.3", 978 | "sebastian/diff": "~1.1", 979 | "sebastian/exporter": "~1.0" 980 | }, 981 | "require-dev": { 982 | "phpunit/phpunit": "~4.1" 983 | }, 984 | "type": "library", 985 | "extra": { 986 | "branch-alias": { 987 | "dev-master": "1.0.x-dev" 988 | } 989 | }, 990 | "autoload": { 991 | "classmap": [ 992 | "src/" 993 | ] 994 | }, 995 | "notification-url": "https://packagist.org/downloads/", 996 | "license": [ 997 | "BSD-3-Clause" 998 | ], 999 | "authors": [ 1000 | { 1001 | "name": "Sebastian Bergmann", 1002 | "email": "sebastian@phpunit.de", 1003 | "role": "lead" 1004 | }, 1005 | { 1006 | "name": "Jeff Welch", 1007 | "email": "whatthejeff@gmail.com" 1008 | }, 1009 | { 1010 | "name": "Volker Dusch", 1011 | "email": "github@wallbash.com" 1012 | }, 1013 | { 1014 | "name": "Bernhard Schussek", 1015 | "email": "bschussek@2bepublished.at" 1016 | } 1017 | ], 1018 | "description": "Provides the functionality to compare PHP values for equality", 1019 | "homepage": "http://www.github.com/sebastianbergmann/comparator", 1020 | "keywords": [ 1021 | "comparator", 1022 | "compare", 1023 | "equality" 1024 | ], 1025 | "time": "2014-05-02 07:05:58" 1026 | }, 1027 | { 1028 | "name": "sebastian/diff", 1029 | "version": "1.1.0", 1030 | "source": { 1031 | "type": "git", 1032 | "url": "https://github.com/sebastianbergmann/diff.git", 1033 | "reference": "1e091702a5a38e6b4c1ba9ca816e3dd343df2e2d" 1034 | }, 1035 | "dist": { 1036 | "type": "zip", 1037 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/1e091702a5a38e6b4c1ba9ca816e3dd343df2e2d", 1038 | "reference": "1e091702a5a38e6b4c1ba9ca816e3dd343df2e2d", 1039 | "shasum": "" 1040 | }, 1041 | "require": { 1042 | "php": ">=5.3.3" 1043 | }, 1044 | "type": "library", 1045 | "extra": { 1046 | "branch-alias": { 1047 | "dev-master": "1.1-dev" 1048 | } 1049 | }, 1050 | "autoload": { 1051 | "classmap": [ 1052 | "src/" 1053 | ] 1054 | }, 1055 | "notification-url": "https://packagist.org/downloads/", 1056 | "license": [ 1057 | "BSD-3-Clause" 1058 | ], 1059 | "authors": [ 1060 | { 1061 | "name": "Sebastian Bergmann", 1062 | "email": "sebastian@phpunit.de", 1063 | "role": "lead" 1064 | }, 1065 | { 1066 | "name": "Kore Nordmann", 1067 | "email": "mail@kore-nordmann.de" 1068 | } 1069 | ], 1070 | "description": "Diff implementation", 1071 | "homepage": "http://www.github.com/sebastianbergmann/diff", 1072 | "keywords": [ 1073 | "diff" 1074 | ], 1075 | "time": "2013-08-03 16:46:33" 1076 | }, 1077 | { 1078 | "name": "sebastian/environment", 1079 | "version": "1.0.0", 1080 | "source": { 1081 | "type": "git", 1082 | "url": "https://github.com/sebastianbergmann/environment.git", 1083 | "reference": "79517609ec01139cd7e9fded0dd7ce08c952ef6a" 1084 | }, 1085 | "dist": { 1086 | "type": "zip", 1087 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/79517609ec01139cd7e9fded0dd7ce08c952ef6a", 1088 | "reference": "79517609ec01139cd7e9fded0dd7ce08c952ef6a", 1089 | "shasum": "" 1090 | }, 1091 | "require": { 1092 | "php": ">=5.3.3" 1093 | }, 1094 | "require-dev": { 1095 | "phpunit/phpunit": "4.0.*@dev" 1096 | }, 1097 | "type": "library", 1098 | "extra": { 1099 | "branch-alias": { 1100 | "dev-master": "1.0.x-dev" 1101 | } 1102 | }, 1103 | "autoload": { 1104 | "classmap": [ 1105 | "src/" 1106 | ] 1107 | }, 1108 | "notification-url": "https://packagist.org/downloads/", 1109 | "license": [ 1110 | "BSD-3-Clause" 1111 | ], 1112 | "authors": [ 1113 | { 1114 | "name": "Sebastian Bergmann", 1115 | "email": "sebastian@phpunit.de", 1116 | "role": "lead" 1117 | } 1118 | ], 1119 | "description": "Provides functionality to handle HHVM/PHP environments", 1120 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1121 | "keywords": [ 1122 | "Xdebug", 1123 | "environment", 1124 | "hhvm" 1125 | ], 1126 | "time": "2014-02-18 16:17:19" 1127 | }, 1128 | { 1129 | "name": "sebastian/exporter", 1130 | "version": "1.0.1", 1131 | "source": { 1132 | "type": "git", 1133 | "url": "https://github.com/sebastianbergmann/exporter.git", 1134 | "reference": "1f9a98e6f5dfe0524cb8c6166f7c82f3e9ae1529" 1135 | }, 1136 | "dist": { 1137 | "type": "zip", 1138 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/1f9a98e6f5dfe0524cb8c6166f7c82f3e9ae1529", 1139 | "reference": "1f9a98e6f5dfe0524cb8c6166f7c82f3e9ae1529", 1140 | "shasum": "" 1141 | }, 1142 | "require": { 1143 | "php": ">=5.3.3" 1144 | }, 1145 | "require-dev": { 1146 | "phpunit/phpunit": "4.0.*@dev" 1147 | }, 1148 | "type": "library", 1149 | "extra": { 1150 | "branch-alias": { 1151 | "dev-master": "1.0.x-dev" 1152 | } 1153 | }, 1154 | "autoload": { 1155 | "classmap": [ 1156 | "src/" 1157 | ] 1158 | }, 1159 | "notification-url": "https://packagist.org/downloads/", 1160 | "license": [ 1161 | "BSD-3-Clause" 1162 | ], 1163 | "authors": [ 1164 | { 1165 | "name": "Sebastian Bergmann", 1166 | "email": "sebastian@phpunit.de", 1167 | "role": "lead" 1168 | }, 1169 | { 1170 | "name": "Jeff Welch", 1171 | "email": "whatthejeff@gmail.com" 1172 | }, 1173 | { 1174 | "name": "Volker Dusch", 1175 | "email": "github@wallbash.com" 1176 | }, 1177 | { 1178 | "name": "Adam Harvey", 1179 | "email": "aharvey@php.net", 1180 | "role": "Lead" 1181 | }, 1182 | { 1183 | "name": "Bernhard Schussek", 1184 | "email": "bschussek@2bepublished.at" 1185 | } 1186 | ], 1187 | "description": "Provides the functionality to export PHP variables for visualization", 1188 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 1189 | "keywords": [ 1190 | "export", 1191 | "exporter" 1192 | ], 1193 | "time": "2014-02-16 08:26:31" 1194 | }, 1195 | { 1196 | "name": "sebastian/version", 1197 | "version": "1.0.3", 1198 | "source": { 1199 | "type": "git", 1200 | "url": "https://github.com/sebastianbergmann/version.git", 1201 | "reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43" 1202 | }, 1203 | "dist": { 1204 | "type": "zip", 1205 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43", 1206 | "reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43", 1207 | "shasum": "" 1208 | }, 1209 | "type": "library", 1210 | "autoload": { 1211 | "classmap": [ 1212 | "src/" 1213 | ] 1214 | }, 1215 | "notification-url": "https://packagist.org/downloads/", 1216 | "license": [ 1217 | "BSD-3-Clause" 1218 | ], 1219 | "authors": [ 1220 | { 1221 | "name": "Sebastian Bergmann", 1222 | "email": "sebastian@phpunit.de", 1223 | "role": "lead" 1224 | } 1225 | ], 1226 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1227 | "homepage": "https://github.com/sebastianbergmann/version", 1228 | "time": "2014-03-07 15:35:33" 1229 | }, 1230 | { 1231 | "name": "symfony/class-loader", 1232 | "version": "v2.5.3", 1233 | "target-dir": "Symfony/Component/ClassLoader", 1234 | "source": { 1235 | "type": "git", 1236 | "url": "https://github.com/symfony/ClassLoader.git", 1237 | "reference": "25b74b67d455268415d7d34b41f8b7df4cb33ba4" 1238 | }, 1239 | "dist": { 1240 | "type": "zip", 1241 | "url": "https://api.github.com/repos/symfony/ClassLoader/zipball/25b74b67d455268415d7d34b41f8b7df4cb33ba4", 1242 | "reference": "25b74b67d455268415d7d34b41f8b7df4cb33ba4", 1243 | "shasum": "" 1244 | }, 1245 | "require": { 1246 | "php": ">=5.3.3" 1247 | }, 1248 | "require-dev": { 1249 | "symfony/finder": "~2.0" 1250 | }, 1251 | "type": "library", 1252 | "extra": { 1253 | "branch-alias": { 1254 | "dev-master": "2.5-dev" 1255 | } 1256 | }, 1257 | "autoload": { 1258 | "psr-0": { 1259 | "Symfony\\Component\\ClassLoader\\": "" 1260 | } 1261 | }, 1262 | "notification-url": "https://packagist.org/downloads/", 1263 | "license": [ 1264 | "MIT" 1265 | ], 1266 | "authors": [ 1267 | { 1268 | "name": "Symfony Community", 1269 | "homepage": "http://symfony.com/contributors" 1270 | }, 1271 | { 1272 | "name": "Fabien Potencier", 1273 | "email": "fabien@symfony.com" 1274 | } 1275 | ], 1276 | "description": "Symfony ClassLoader Component", 1277 | "homepage": "http://symfony.com", 1278 | "time": "2014-07-09 09:05:48" 1279 | }, 1280 | { 1281 | "name": "symfony/console", 1282 | "version": "v2.5.3", 1283 | "target-dir": "Symfony/Component/Console", 1284 | "source": { 1285 | "type": "git", 1286 | "url": "https://github.com/symfony/Console.git", 1287 | "reference": "cd2d1e4bac2206b337326b0140ff475fe9ad5f63" 1288 | }, 1289 | "dist": { 1290 | "type": "zip", 1291 | "url": "https://api.github.com/repos/symfony/Console/zipball/cd2d1e4bac2206b337326b0140ff475fe9ad5f63", 1292 | "reference": "cd2d1e4bac2206b337326b0140ff475fe9ad5f63", 1293 | "shasum": "" 1294 | }, 1295 | "require": { 1296 | "php": ">=5.3.3" 1297 | }, 1298 | "require-dev": { 1299 | "psr/log": "~1.0", 1300 | "symfony/event-dispatcher": "~2.1" 1301 | }, 1302 | "suggest": { 1303 | "psr/log": "For using the console logger", 1304 | "symfony/event-dispatcher": "" 1305 | }, 1306 | "type": "library", 1307 | "extra": { 1308 | "branch-alias": { 1309 | "dev-master": "2.5-dev" 1310 | } 1311 | }, 1312 | "autoload": { 1313 | "psr-0": { 1314 | "Symfony\\Component\\Console\\": "" 1315 | } 1316 | }, 1317 | "notification-url": "https://packagist.org/downloads/", 1318 | "license": [ 1319 | "MIT" 1320 | ], 1321 | "authors": [ 1322 | { 1323 | "name": "Symfony Community", 1324 | "homepage": "http://symfony.com/contributors" 1325 | }, 1326 | { 1327 | "name": "Fabien Potencier", 1328 | "email": "fabien@symfony.com" 1329 | } 1330 | ], 1331 | "description": "Symfony Console Component", 1332 | "homepage": "http://symfony.com", 1333 | "time": "2014-08-05 09:00:40" 1334 | }, 1335 | { 1336 | "name": "symfony/dependency-injection", 1337 | "version": "v2.5.3", 1338 | "target-dir": "Symfony/Component/DependencyInjection", 1339 | "source": { 1340 | "type": "git", 1341 | "url": "https://github.com/symfony/DependencyInjection.git", 1342 | "reference": "54529fdc797a88c030441773adadcc759bb102c2" 1343 | }, 1344 | "dist": { 1345 | "type": "zip", 1346 | "url": "https://api.github.com/repos/symfony/DependencyInjection/zipball/54529fdc797a88c030441773adadcc759bb102c2", 1347 | "reference": "54529fdc797a88c030441773adadcc759bb102c2", 1348 | "shasum": "" 1349 | }, 1350 | "require": { 1351 | "php": ">=5.3.3" 1352 | }, 1353 | "require-dev": { 1354 | "symfony/config": "~2.2", 1355 | "symfony/expression-language": "~2.4", 1356 | "symfony/yaml": "~2.0" 1357 | }, 1358 | "suggest": { 1359 | "symfony/config": "", 1360 | "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", 1361 | "symfony/yaml": "" 1362 | }, 1363 | "type": "library", 1364 | "extra": { 1365 | "branch-alias": { 1366 | "dev-master": "2.5-dev" 1367 | } 1368 | }, 1369 | "autoload": { 1370 | "psr-0": { 1371 | "Symfony\\Component\\DependencyInjection\\": "" 1372 | } 1373 | }, 1374 | "notification-url": "https://packagist.org/downloads/", 1375 | "license": [ 1376 | "MIT" 1377 | ], 1378 | "authors": [ 1379 | { 1380 | "name": "Symfony Community", 1381 | "homepage": "http://symfony.com/contributors" 1382 | }, 1383 | { 1384 | "name": "Fabien Potencier", 1385 | "email": "fabien@symfony.com" 1386 | } 1387 | ], 1388 | "description": "Symfony DependencyInjection Component", 1389 | "homepage": "http://symfony.com", 1390 | "time": "2014-08-06 06:44:37" 1391 | }, 1392 | { 1393 | "name": "symfony/event-dispatcher", 1394 | "version": "v2.5.3", 1395 | "target-dir": "Symfony/Component/EventDispatcher", 1396 | "source": { 1397 | "type": "git", 1398 | "url": "https://github.com/symfony/EventDispatcher.git", 1399 | "reference": "8faf5cc7e80fde74a650a36e60d32ce3c3e0457b" 1400 | }, 1401 | "dist": { 1402 | "type": "zip", 1403 | "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/8faf5cc7e80fde74a650a36e60d32ce3c3e0457b", 1404 | "reference": "8faf5cc7e80fde74a650a36e60d32ce3c3e0457b", 1405 | "shasum": "" 1406 | }, 1407 | "require": { 1408 | "php": ">=5.3.3" 1409 | }, 1410 | "require-dev": { 1411 | "psr/log": "~1.0", 1412 | "symfony/config": "~2.0", 1413 | "symfony/dependency-injection": "~2.0", 1414 | "symfony/stopwatch": "~2.2" 1415 | }, 1416 | "suggest": { 1417 | "symfony/dependency-injection": "", 1418 | "symfony/http-kernel": "" 1419 | }, 1420 | "type": "library", 1421 | "extra": { 1422 | "branch-alias": { 1423 | "dev-master": "2.5-dev" 1424 | } 1425 | }, 1426 | "autoload": { 1427 | "psr-0": { 1428 | "Symfony\\Component\\EventDispatcher\\": "" 1429 | } 1430 | }, 1431 | "notification-url": "https://packagist.org/downloads/", 1432 | "license": [ 1433 | "MIT" 1434 | ], 1435 | "authors": [ 1436 | { 1437 | "name": "Symfony Community", 1438 | "homepage": "http://symfony.com/contributors" 1439 | }, 1440 | { 1441 | "name": "Fabien Potencier", 1442 | "email": "fabien@symfony.com" 1443 | } 1444 | ], 1445 | "description": "Symfony EventDispatcher Component", 1446 | "homepage": "http://symfony.com", 1447 | "time": "2014-07-28 13:20:46" 1448 | }, 1449 | { 1450 | "name": "symfony/translation", 1451 | "version": "v2.5.3", 1452 | "target-dir": "Symfony/Component/Translation", 1453 | "source": { 1454 | "type": "git", 1455 | "url": "https://github.com/symfony/Translation.git", 1456 | "reference": "ae573e45b099b1e2d332930ac626cd4270e09539" 1457 | }, 1458 | "dist": { 1459 | "type": "zip", 1460 | "url": "https://api.github.com/repos/symfony/Translation/zipball/ae573e45b099b1e2d332930ac626cd4270e09539", 1461 | "reference": "ae573e45b099b1e2d332930ac626cd4270e09539", 1462 | "shasum": "" 1463 | }, 1464 | "require": { 1465 | "php": ">=5.3.3" 1466 | }, 1467 | "require-dev": { 1468 | "symfony/config": "~2.0", 1469 | "symfony/yaml": "~2.2" 1470 | }, 1471 | "suggest": { 1472 | "symfony/config": "", 1473 | "symfony/yaml": "" 1474 | }, 1475 | "type": "library", 1476 | "extra": { 1477 | "branch-alias": { 1478 | "dev-master": "2.5-dev" 1479 | } 1480 | }, 1481 | "autoload": { 1482 | "psr-0": { 1483 | "Symfony\\Component\\Translation\\": "" 1484 | } 1485 | }, 1486 | "notification-url": "https://packagist.org/downloads/", 1487 | "license": [ 1488 | "MIT" 1489 | ], 1490 | "authors": [ 1491 | { 1492 | "name": "Symfony Community", 1493 | "homepage": "http://symfony.com/contributors" 1494 | }, 1495 | { 1496 | "name": "Fabien Potencier", 1497 | "email": "fabien@symfony.com" 1498 | } 1499 | ], 1500 | "description": "Symfony Translation Component", 1501 | "homepage": "http://symfony.com", 1502 | "time": "2014-07-28 13:20:46" 1503 | }, 1504 | { 1505 | "name": "symfony/yaml", 1506 | "version": "v2.5.3", 1507 | "target-dir": "Symfony/Component/Yaml", 1508 | "source": { 1509 | "type": "git", 1510 | "url": "https://github.com/symfony/Yaml.git", 1511 | "reference": "5a75366ae9ca8b4792cd0083e4ca4dff9fe96f1f" 1512 | }, 1513 | "dist": { 1514 | "type": "zip", 1515 | "url": "https://api.github.com/repos/symfony/Yaml/zipball/5a75366ae9ca8b4792cd0083e4ca4dff9fe96f1f", 1516 | "reference": "5a75366ae9ca8b4792cd0083e4ca4dff9fe96f1f", 1517 | "shasum": "" 1518 | }, 1519 | "require": { 1520 | "php": ">=5.3.3" 1521 | }, 1522 | "type": "library", 1523 | "extra": { 1524 | "branch-alias": { 1525 | "dev-master": "2.5-dev" 1526 | } 1527 | }, 1528 | "autoload": { 1529 | "psr-0": { 1530 | "Symfony\\Component\\Yaml\\": "" 1531 | } 1532 | }, 1533 | "notification-url": "https://packagist.org/downloads/", 1534 | "license": [ 1535 | "MIT" 1536 | ], 1537 | "authors": [ 1538 | { 1539 | "name": "Symfony Community", 1540 | "homepage": "http://symfony.com/contributors" 1541 | }, 1542 | { 1543 | "name": "Fabien Potencier", 1544 | "email": "fabien@symfony.com" 1545 | } 1546 | ], 1547 | "description": "Symfony Yaml Component", 1548 | "homepage": "http://symfony.com", 1549 | "time": "2014-08-05 09:00:40" 1550 | } 1551 | ], 1552 | "aliases": [], 1553 | "minimum-stability": "stable", 1554 | "stability-flags": { 1555 | "fzaninotto/faker": 20 1556 | }, 1557 | "prefer-stable": false, 1558 | "platform": [], 1559 | "platform-dev": [] 1560 | } 1561 | -------------------------------------------------------------------------------- /doc/00-intro.md: -------------------------------------------------------------------------------- 1 | Intro 2 | ===== 3 | 4 | RedisOrm is a simple library to facilitate storing and finding objects in redis 5 | in an efficient manner. 6 | 7 | Each object is stored with it's properties as a hash under a key named by the 8 | object's `Id`. To facilitate searching for an object using it's property values 9 | as criteria, indexes can be created. An "index" is a key with the property name 10 | and value as the key name, and a set of `Id`s of objects whose property values 11 | match that of the key. Filtering is done by intersecting these index sets to find 12 | objects that match all the given criteria. This is an extremely efficient way to 13 | filter large amounts of data. 14 | 15 | This library ensures that the appropriate keys are set/unset as neededw while 16 | providing a simple interface through which to access your objects. 17 | 18 | Next: [Installation](01-installation.md) 19 | -------------------------------------------------------------------------------- /doc/01-installation.md: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Install via composer: 5 | 6 | $ composer.phar require tystr/redis-orm:1.0.*@dev 7 | 8 | Then, simply include the composer autoloader in your project: 9 | ```PHP 10 | id = $id; 49 | $this->name = $name; 50 | $this->createdAt = $createdAt; 51 | } 52 | // Getters and Setters 53 | } 54 | 55 | ``` 56 | ## Saving Objects 57 | 58 | Now, the repository class will know which indexes to create and what to use as the values. 59 | 60 | Instantiating the `ObjectRepository` class and saving your object is simple: 61 | ```PHP 62 | save($car); 73 | ``` 74 | 75 | ## Finding Objects 76 | ```PHP 77 | addRestriction(Tystr\RedirOrm\Criteria\Restrictions::equalTo('id', '123)); 81 | 82 | // This returns an array of hydrated objects that match, or an empty array if no matches are found 83 | $results = $repository->findBy($criteria); 84 | ``` 85 | 86 | Next: [Annotations](03-annotations.md) -------------------------------------------------------------------------------- /doc/03-annotations.md: -------------------------------------------------------------------------------- 1 | Annotations 2 | =========== 3 | 4 | ## @Prefix 5 | Define this property to customize the prefix used for the key name that will contain the hash of the object's data. If 6 | you omit this annotation, the class name will be used. 7 | 8 | ## @Id 9 | Each class must have One `@Id` annotated property. The value of this property will be used when creating the indexes. 10 | 11 | Note: It's up to you to ensure that this is some unique value for each of your objects. Otherwise, you will see that 12 | saving objects with the same id will overwrite the entries in the indexes and the key containing the data for the object. 13 | 14 | ## @Field 15 | Any properties you want to be stored in redis must be annotated with this annotation. You may specify the type as one of 16 | the following: 17 | 18 | string 19 | integer 20 | double 21 | boolean 22 | date 23 | collection 24 | hash 25 | 26 | The `collection` type denotes a numeric indexed array. 27 | The `hash` type denotes an associative array. 28 | 29 | If you wish to customize the name of the field, to may do so. 30 | 31 | /** 32 | * @Field(name="some_property", type="string") 33 | */ 34 | protected $someProperty; 35 | 36 | ## @Index 37 | Define this annotation on fields by which you want to be able to search. 38 | The `@Index` annotation indicates that a key will be created with the name and value of this property as the key name. 39 | The value of the `@Id` will be inserted into the key's set. Read more about redis sets [here][1]. 40 | 41 | ## @SortedIndex 42 | Define this annotation on properties by which you want to perform range queries. 43 | 44 | ## @Date 45 | A subset of `@SortedIndex`, define this annotation on properties that you want to perform date range queries 46 | The `@Date` annotation indicates that a key will be created with the name and value of this property as the key name, 47 | and the value of `@Id` annotated property will be inserted into the keys sorted set. This property's value will be 48 | converted to a timestamp and used as the score by which the object will be sorted. Read more about redis sorted sets 49 | [here][2]. 50 | 51 | [1]: http://redis.io/topics/data-types#sets 52 | [2]: http://redis.io/topics/data-types#sorted-sets 53 | -------------------------------------------------------------------------------- /features/predis_repository.feature: -------------------------------------------------------------------------------- 1 | Feature: Repository persistence 2 | Objects should be stored in redis with the appropriate "index" keys 3 | 4 | Scenario: Saving an object with "indexed" properties 5 | Given the following Car: 6 | | id | make | model | engine_type | color | 7 | | 1 | Tesla | Model S | V8 | red | 8 | Then there should be 7 keys in the database 9 | And the following keys should exist: 10 | | name | value | 11 | | make:Tesla | 1 | 12 | | model:Model S | 1 | 13 | | engine_type:V8 | 1 | 14 | | color:red | 1 | 15 | 16 | Scenario: Finding an object by it's id 17 | Given the following Car: 18 | | id | make | model | engine_type | color | 19 | | 1 | Tesla | Model S | V8 | red | 20 | And the car with id "1" has the property "attributes" with the following values: 21 | | is_favorite | yes | 22 | | is_slow | no | 23 | | is_awd | yes | 24 | And the car with id "1" has the property "owners" with the following values: 25 | | 0 | one | 26 | | 1 | two | 27 | When I find a Car by id 1 28 | Then there should be 1 car 29 | And the car with the id 1 should have the following properties: 30 | | id | make | model | engine_type | color | 31 | | 1 | Tesla | Model S | V8 | red | 32 | And the car with the id 1 should have property "attributes" with the following values: 33 | | is_favorite | yes | 34 | | is_slow | no | 35 | | is_awd | yes | 36 | 37 | @findby 38 | Scenario: Finding an object by a property value 39 | Given the following Car: 40 | | id | make | model | engine_type | color | 41 | | 1 | BMW | M5 | V10 | red | 42 | | 2 | Tesla | Model S | what? | blue | 43 | When I find cars where the property "color" is "red" 44 | Then there should be 1 car 45 | 46 | Scenario: Saving an object with null property values removes the id from index field for that property 47 | Given the following Car: 48 | | id | make | model | engine_type | color | 49 | | 1 | Tesla | Model S | V8 | red | 50 | | 2 | Porsche | 911 | V8 | yellow | 51 | Then there should be 2 items in the "manufacture_date" key 52 | When I set the manufacture date to null 53 | Then there should be 1 items in the "manufacture_date" key 54 | And When I set the color for the car "1" to "null" 55 | Then there should be 0 items in the "color:red" key 56 | 57 | Scenario: Saving an object with a different property value removes the id from the index of the original value; 58 | Given the following Car: 59 | | id | make | model | engine_type | color | 60 | | 1 | Tesla | Model S | V8 | red | 61 | And When I set the color for the car "1" to "blue" 62 | Then there should be 0 items in the "color:red" key 63 | And there should be 1 items in the "color:blue" key 64 | 65 | @boolean 66 | Scenario: Saving an object with a boolean property 67 | Given the following Car: 68 | | id | make | model | engine_type | active | 69 | | 1 | Tesla | Model S | V8 | 1 | 70 | Then there should be 1 items in the "active:1" key 71 | And there should be 0 items in the "active:0" key 72 | And When I set the active for the car "1" to "false" 73 | Then there should be 0 items in the "active:1" key 74 | And there should be 1 items in the "active:0" key 75 | And the car with the id "1" should have property "active" with the value "false": 76 | 77 | @filter 78 | Scenario: Finding and filtering data 79 | Users exist in the database 80 | Lists contain criteria that determines which users belong to the list 81 | Filtering the users using a list should return only users who's data match the lists criteria 82 | 83 | Given the following users: 84 | | email | dob | signup | last_open | last_click | email_opt | subscribed | 85 | | test@test.com | 1980-01-01 | 2014-06-25 | 2014-07-01 | 2014-07-01 | 1 | 1 | 86 | | test1@test.com | 1980-01-01 | 2014-06-25 | 2014-07-01 | 2014-07-01 | 1 | 1 | 87 | | test2@test.com | 1980-01-01 | 2014-06-25 | 2014-07-01 | 2014-07-01 | 1 | 1 | 88 | | test3@test.com | 1980-01-01 | 2014-06-25 | 2014-07-01 | 2014-07-01 | 1 | 1 | 89 | | test4@test.com | 1980-01-01 | 2014-06-25 | 2014-07-01 | 2014-07-01 | 1 | 1 | 90 | 91 | | test5@test.com | 1980-01-01 | 2014-06-25 | 2014-06-01 | 2014-07-01 | 1 | 1 | 92 | | test6@test.com | 1980-01-01 | 2014-06-25 | 2014-06-01 | 2014-07-01 | 1 | 1 | 93 | | test7@test.com | 1980-01-01 | 2014-06-25 | 2014-06-01 | 2014-07-01 | 1 | 0 | 94 | | test8@test.com | 1980-01-01 | 2014-06-25 | 2014-06-01 | 2014-07-01 | 1 | 1 | 95 | 96 | | test9@test.com | 1980-01-01 | 2014-06-25 | 2013-06-01 | 2014-07-01 | 0 | 1 | 97 | | test10@test.com | 1980-01-01 | 2014-06-25 | 2013-06-01 | 2014-07-01 | 1 | 1 | 98 | | test11@test.com | 1980-01-01 | 2014-06-25 | 2013-06-01 | 2014-07-01 | 1 | 1 | 99 | 100 | Given the list "test" has the following criteria: 101 | | name | key | value | 102 | | equalTo | subscribed | 1 | 103 | | greaterThan | last_open | 2014-01-01 | 104 | | lessThan | last_open | 2014-06-30 | 105 | Then the list "test" should have 3 users 106 | 107 | Given the list "test2" has the following criteria: 108 | | name | key | value | 109 | | lessThan | last_open | 2014-06-30 | 110 | Then the list "test2" should have 7 users 111 | 112 | Given the list "test3" has the following criteria: 113 | | name | key | value | 114 | | greaterThan | last_open | 2014-06-30 | 115 | Then the list "test3" should have 5 users 116 | 117 | Given the list "test4" has the following criteria: 118 | | name | key | value | 119 | | equalTo | subscribed | 1 | 120 | Then the list "test4" should have 11 users 121 | 122 | Given the list "test5" has the following criteria: 123 | | name | key | value | 124 | | equalTo | subscribed | 1 | 125 | | equalTo | email_opt | 1 | 126 | Then the list "test5" should have 10 users 127 | 128 | 129 | @filter-or 130 | Scenario: simple or 131 | Given the following users: 132 | | email | dob | signup | last_open | last_click | email_opt | subscribed | 133 | | test1@test.com | 1980-01-01 | 2014-06-25 | 2014-07-01 | 2014-07-01 | 1 | 1 | 134 | | test2@test.com | 1980-01-02 | 2014-06-26 | 2014-07-02 | 2014-07-02 | 0 | 1 | 135 | | test3@test.com | 1980-01-03 | 2014-06-27 | 2014-07-03 | 2014-07-03 | 1 | 0 | 136 | 137 | And the list "test6" has the following criteria: 138 | | name | key | value | 139 | | equalTo | email_opt | 1 | 140 | | equalTo | email_opt | 0 | 141 | | orGroup | | 1,2 | 142 | Then the list "test6" should have 3 users 143 | And the list "test6" should have the ids "test1@test.com,test2@test.com,test3@test.com" 144 | 145 | 146 | @filter-and 147 | Scenario: simple and 148 | Given the following users: 149 | | email | dob | signup | last_open | last_click | email_opt | subscribed | 150 | | test1@test.com | 1980-01-01 | 2014-06-25 | 2014-07-01 | 2014-07-01 | 1 | 1 | 151 | | test2@test.com | 1980-01-02 | 2014-06-26 | 2014-07-02 | 2014-07-02 | 0 | 1 | 152 | | test3@test.com | 1980-01-03 | 2014-06-27 | 2014-07-03 | 2014-07-03 | 1 | 0 | 153 | 154 | And the list "test7" has the following criteria: 155 | | name | key | value | 156 | | equalTo | email_opt | 1 | 157 | | equalTo | subscribed | 0 | 158 | | andGroup | | 1,2 | 159 | Then the list "test7" should have 1 users 160 | And the list "test7" should have the ids "test3@test.com" 161 | 162 | 163 | @filter-nested-composition 164 | Scenario: nested composition 165 | Given the following users: 166 | | email | dob | signup | last_open | last_click | email_opt | subscribed | 167 | | test1@test.com | 1980-01-01 | 2014-06-25 | 2014-07-01 | 2014-07-01 | 1 | 1 | 168 | | test2@test.com | 1980-01-02 | 2014-06-26 | 2014-07-02 | 2014-07-02 | 0 | 1 | 169 | | test3@test.com | 1980-01-03 | 2014-06-27 | 2014-07-03 | 2014-07-03 | 1 | 0 | 170 | 171 | And the list "test8" has the following criteria: 172 | | name | key | value | 173 | | equalTo | email_opt | 1 | 174 | | equalTo | email_opt | 0 | 175 | | greaterThan | last_open | 2014-07-01 | 176 | | orGroup | | 1,2 | 177 | | andGroup | | 3,4 | 178 | 179 | 180 | Then the list "test8" should have 2 users 181 | And the list "test8" should have the ids "test2@test.com, test3@test.com" -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | ./tests 12 | 13 | 14 | 15 | 16 | 17 | ./ 18 | 19 | ./tests 20 | ./vendor 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /provisioning/puppet/manifests/files/iptables: -------------------------------------------------------------------------------- 1 | # Firewall configuration written by system-config-firewall 2 | # Manual customization of this file is not recommended. 3 | *filter 4 | :INPUT ACCEPT [0:0] 5 | :FORWARD ACCEPT [0:0] 6 | :OUTPUT ACCEPT [0:0] 7 | -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 8 | -A INPUT -p icmp -j ACCEPT 9 | -A INPUT -i lo -j ACCEPT 10 | -A INPUT -m state --state NEW -p tcp --dport 80 -j ACCEPT 11 | -A INPUT -m state --state NEW -p tcp --dport 443 -j ACCEPT 12 | -A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT 13 | -A INPUT -j REJECT --reject-with icmp-host-prohibited 14 | -A FORWARD -j REJECT --reject-with icmp-host-prohibited 15 | COMMIT 16 | -------------------------------------------------------------------------------- /provisioning/puppet/manifests/site.pp: -------------------------------------------------------------------------------- 1 | node default { 2 | $project_root = '/vagrant' 3 | 4 | yumrepo { 'ius': 5 | descr => 'IUS Community Repository $releasever - $basearch', 6 | mirrorlist => 'http://dmirr.iuscommunity.org/mirrorlist/?repo=ius-el6&arch=$basearch', 7 | gpgcheck => 0, 8 | enabled => 1, 9 | priority => 1 10 | } 11 | yumrepo { "epel": 12 | descr => 'epel', 13 | mirrorlist => 'http://mirrors.fedoraproject.org/mirrorlist?repo=epel-6&arch=$basearch', 14 | enabled => 1, 15 | gpgcheck => 0 16 | } 17 | Yumrepo <| |> -> Package <| |> -> File <| |> 18 | 19 | # PHP 20 | $php_packages = [ 21 | 'php55u', 22 | 'php55u-intl', 23 | 'php55u-xml', 24 | 'php55u-mbstring', 25 | 'php55u-fpm', 26 | 'php55u-opcache', 27 | 'php55u-pdo', 28 | 'php55u-mysqlnd', 29 | ] 30 | package { $php_packages: 31 | ensure => latest 32 | } 33 | 34 | file { '/etc/php.ini': 35 | source => '/vagrant/provisioning/puppet/manifests/files/php.ini', 36 | owner => 'root', 37 | group => 'root', 38 | mode => '0644' 39 | } 40 | 41 | 42 | # Composer 43 | exec { 'install-composer': 44 | command => 'curl -sS https://getcomposer.org/installer | php', 45 | cwd => $project_root, 46 | user => 'vagrant', 47 | path => ['/usr/bin'], 48 | creates => "${project_root}/composer.phar" 49 | } 50 | Package[$php_packages] -> Exec['install-composer'] 51 | 52 | # Composer install 53 | package { 'git': 54 | ensure => latest 55 | } 56 | exec { 'composer-install-dependencies': 57 | command => 'php composer.phar install --prefer-source -o -n', 58 | environment => ['COMPOSER_HOME=/vagrant'], 59 | cwd => $project_root, 60 | user => 'vagrant', 61 | path => ['/usr/bin'], 62 | timeout => 600 63 | } 64 | Exec['install-composer'] -> 65 | Package['git'] -> 66 | Exec['composer-install-dependencies'] 67 | 68 | # Firewall 69 | service { 'iptables': 70 | enable => true, 71 | ensure => running 72 | } 73 | file { '/etc/sysconfig/iptables': 74 | ensure => present, 75 | source => '/vagrant/provisioning/puppet/manifests/files/iptables', 76 | owner => 'root', 77 | group => 'root', 78 | mode => '0600' 79 | } 80 | File['/etc/sysconfig/iptables'] ~> Service['iptables'] 81 | 82 | # Redis 83 | package { 'redis28u': 84 | ensure => installed 85 | } 86 | service { 'redis': 87 | enable => true, 88 | ensure => running 89 | } 90 | Package['redis28u'] ~> Service['redis'] 91 | } 92 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Annotations/Date.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class Date extends SortedIndex 13 | { 14 | /** 15 | * @var string 16 | */ 17 | public $name; 18 | } 19 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Annotations/Field.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class Field extends Annotation 14 | { 15 | public $name; 16 | 17 | public $type; 18 | } -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Annotations/Id.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class Id extends Annotation 13 | { 14 | /** 15 | * @var string 16 | */ 17 | public $name; 18 | } 19 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Annotations/Index.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class Index extends Annotation 13 | { 14 | /** 15 | * @var string 16 | */ 17 | public $name; 18 | } 19 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Annotations/Prefix.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class Prefix extends Annotation 13 | { 14 | /** 15 | * @var string 16 | */ 17 | public $name; 18 | } 19 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Annotations/SortedIndex.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class SortedIndex extends Annotation 13 | { 14 | /** 15 | * @var string 16 | */ 17 | public $name; 18 | } 19 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Context/BaseContext.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class BaseContext implements SnippetAcceptingContext 12 | { 13 | /** 14 | * @var Client 15 | */ 16 | protected $redis; 17 | 18 | public function __construct() 19 | { 20 | AnnotationRegistry::registerAutoloadNamespace('Tystr\RedisOrm\Annotations', __DIR__.'/../../../'); 21 | $this->redis = new Client(); 22 | } 23 | 24 | /** 25 | * @BeforeScenario 26 | */ 27 | public function flushRedis() 28 | { 29 | $this->redis->flushall(); 30 | } 31 | 32 | /** 33 | * @Transform /^(\d+)$/ 34 | */ 35 | public function castStringToNumber($string) 36 | { 37 | return intval($string); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Context/MainContext.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class MainContext extends BaseContext 22 | { 23 | /** 24 | * @var ObjectRepository 25 | */ 26 | protected $repository; 27 | 28 | /** 29 | * @var ObjectRepository 30 | */ 31 | protected $userRepository; 32 | 33 | /** 34 | * @var array 35 | */ 36 | protected $lists = array(); 37 | 38 | /** 39 | * @var array|Car[] 40 | */ 41 | protected $cars = array(); 42 | 43 | public function __construct() 44 | { 45 | parent::__construct(); 46 | 47 | $keyNamingStrategy = new ColonDelimitedKeyNamingStrategy(); 48 | $loader = new AnnotationMetadataLoader('/tmp'); 49 | $metadataRegistry = new MetadataRegistry($loader); 50 | $this->repository = new ObjectRepository( 51 | $this->redis, 52 | $keyNamingStrategy, 53 | 'Tystr\RedisOrm\Test\Model\Car', 54 | $metadataRegistry 55 | ); 56 | $this->userRepository = new ObjectRepository( 57 | $this->redis, 58 | $keyNamingStrategy, 59 | 'Tystr\RedisOrm\Test\Model\User', 60 | $metadataRegistry 61 | ); 62 | } 63 | 64 | /** 65 | * @Given /the following Cars?:/ 66 | */ 67 | public function theFollowingCars(TableNode $table) 68 | { 69 | foreach ($table->getHash() as $data) { 70 | $car = new Car(); 71 | $car->setId($data['id']); 72 | $car->setColor(isset($data['color']) ? $data['color'] : null); 73 | $car->setEngineType($data['engine_type']); 74 | $car->setMake($data['make']); 75 | $car->setModel($data['model']); 76 | $car->setManufactureDate(new \DateTime('2013-01-01')); 77 | if (isset($data['active'])) { 78 | $car->setActive((bool)$data['active']); 79 | } 80 | $this->repository->save($car); 81 | } 82 | } 83 | /** 84 | * @Given the car with id :id has the property :propertyName with the following values: 85 | */ 86 | public function theCarWithIdHasThePropertyWithTheFollowingValues($id, $propertyName, TableNode $values) 87 | { 88 | $car = $this->repository->find($id); 89 | $data = array(); 90 | foreach ($values->getRowsHash() as $key => $value) { 91 | $data[$key] = $value; 92 | } 93 | $setter = 'set'.ucfirst(strtolower($propertyName)); 94 | $car->$setter($data); 95 | $this->repository->save($car); 96 | } 97 | 98 | /** 99 | * @Then the car with the id :id should have property :propertyName with the following values: 100 | */ 101 | public function theCarWithTheIdShouldHavePropertyWithTheFollowingValues($id, $propertyName, TableNode $values) 102 | { 103 | $car = $this->repository->find($id); 104 | $getter = 'get'.ucfirst(strtolower($propertyName)); 105 | $data = $car->$getter(); 106 | foreach ($values->getRowsHash() as $key => $value) { 107 | assertTrue(isset($data[$key])); 108 | assertEquals($value, $data[$key]); 109 | } 110 | } 111 | 112 | /** 113 | * @Then the car with the id :id should have property :propertyName with the value :expectedValue: 114 | */ 115 | public function theCarWithTheIdShouldHavePropertyWithValue($id, $propertyName, $expectedValue) 116 | { 117 | if ('true' === $expectedValue) { 118 | $expectedValue = true; 119 | } elseif ('false' === $expectedValue) { 120 | $expectedValue = false; 121 | } 122 | 123 | $car = $this->repository->find($id); 124 | $getter = 'get'.ucfirst(strtolower($propertyName)); 125 | assertSame($expectedValue, $car->$getter()); 126 | } 127 | 128 | /** 129 | * @Then there should be :count keys in the database 130 | */ 131 | public function thereShouldBeKeysInTheDatabase($count) 132 | { 133 | assertCount($count, $this->redis->keys('*')); 134 | } 135 | 136 | /** 137 | * @Then the following keys should exist: 138 | */ 139 | public function theFollowingKeysShouldExist(TableNode $table) 140 | { 141 | foreach ($table->getHash() as $key) { 142 | assertTrue($this->redis->sismember($key['name'], $key['value'])); 143 | } 144 | } 145 | 146 | /** 147 | * @When I find a Car by id :id 148 | */ 149 | public function iFindACarById($id) 150 | { 151 | $this->cars[] = $this->repository->find($id); 152 | } 153 | 154 | /** 155 | * @When I find cars where the property :name is :value 156 | */ 157 | public function iFindCarsThatHavePropertyValue($name, $value) 158 | { 159 | $restriction = Restrictions::equalTo($name, $value); 160 | $criteria = new Criteria(); 161 | $criteria->addRestriction($restriction); 162 | $this->cars = $this->repository->findBy($criteria); 163 | } 164 | 165 | /** 166 | * @Then there should be :count car 167 | */ 168 | public function iThereShouldBeCarReturned($count) 169 | { 170 | assertCount($count, $this->cars); 171 | assertInstanceOf('Tystr\RedisOrm\Test\Model\Car', $this->cars[0]); 172 | } 173 | 174 | /** 175 | * @Then the car with the id :arg1 should have the following properties: 176 | */ 177 | public function theCarWithTheIdShouldHaveTheFollowingProperties($id, TableNode $table) 178 | { 179 | $car = $this->getObjectById($id); 180 | 181 | $expected = $table->getHash(); 182 | assertEquals($expected[0]['make'], $car->getMake()); 183 | assertEquals($expected[0]['model'], $car->getModel()); 184 | assertEquals($expected[0]['engine_type'], $car->getEngineType()); 185 | assertEquals($expected[0]['color'], $car->getColor()); 186 | } 187 | 188 | /** 189 | * @When I set the manufacture date to null 190 | */ 191 | public function iSetTheManufactureDateToNull() 192 | { 193 | $car = $this->getObjectById(1); 194 | $car->setManufactureDate(null); 195 | $this->repository->save($car); 196 | } 197 | 198 | /** 199 | * @Then When I set the color for the car :id to :color 200 | */ 201 | public function whenISetTheColorForTheCarTo($id, $color) 202 | { 203 | $car = $this->getObjectById($id); 204 | $color = $color == 'null' ? null : $color; 205 | $car->setColor($color); 206 | $this->repository->save($car); 207 | } 208 | 209 | /** 210 | * @Then When I set the active for the car :id to :active 211 | */ 212 | public function whenISetTheActiveForTheCarTo($id, $active) 213 | { 214 | $car = $this->getObjectById($id); 215 | if ($active === 'true') { 216 | $active = true; 217 | } elseif ($active === 'false') { 218 | $active = false; 219 | } 220 | $car->setActive($active); 221 | $this->repository->save($car); 222 | } 223 | 224 | /** 225 | * @Then there should be :count items in the :key key 226 | */ 227 | public function thereShouldBeItemsInTheKey($count, $key) 228 | { 229 | $type = $this->redis->type($key); 230 | if ('set' == $type) { 231 | assertEquals($count, $this->redis->scard($key)); 232 | } else { 233 | assertEquals($count, $this->redis->zcard($key)); 234 | } 235 | } 236 | 237 | /** 238 | * @Given the following users: 239 | */ 240 | public function theFollowingUsers(TableNode $table) 241 | { 242 | foreach ($table->getHash() as $row) { 243 | $email = $row['email']; 244 | unset($row['email']); 245 | $dob = $row['dob']; 246 | unset($row['dob']); 247 | $signup = $row['signup']; 248 | unset($row['signup']); 249 | $lastOpen = $row['last_open']; 250 | unset($row['last_open']); 251 | $lastClick = $row['last_click']; 252 | unset($row['last_click']); 253 | 254 | $user = new User($email, $row); 255 | $user->setDateOfBirth(new \DateTime($dob)); 256 | $user->setSignupDate(new \DateTime($signup)); 257 | $user->setLastOpen(new \DateTime($lastOpen)); 258 | $user->setLastClick(new \DateTime($lastClick)); 259 | $this->userRepository->save($user); 260 | } 261 | } 262 | 263 | /** 264 | * @Given the list :listName has the following criteria: 265 | */ 266 | public function theListHasTheFollowingCriteria($listName, TableNode $table) 267 | { 268 | $restrictionRows = $table->getHash(); 269 | 270 | $restrictions = array(); 271 | $restrictionsBelongingToParent = array(); 272 | 273 | foreach ($restrictionRows as $rowNum => $row) { 274 | $methodName = $row['name']; 275 | $dummyRestriction = Restrictions::$methodName('', array()); 276 | if ($dummyRestriction instanceof AndGroupInterface || $dummyRestriction instanceof OrGroupInterface) { 277 | $value = array(); 278 | foreach (explode(',', $row['value']) as $childId) { 279 | $childId = trim($childId); 280 | if ($childId !== null) { 281 | $value[] = $restrictions[$childId]; 282 | $restrictionsBelongingToParent[] = $childId; 283 | } 284 | } 285 | } else { 286 | $value = $this->translateRestrictionValue($row); 287 | } 288 | $restriction = Restrictions::$methodName($row['key'], $value); 289 | $restrictions[$rowNum + 1] = $restriction; 290 | } 291 | 292 | $parentlessRestrictions = array(); 293 | $parentlessRestrictionIds = array_diff(array_keys($restrictions), $restrictionsBelongingToParent); 294 | foreach ($parentlessRestrictionIds as $id) { 295 | $parentlessRestrictions[] = $restrictions[$id]; 296 | } 297 | 298 | $criteria = new Criteria(new ArrayCollection($parentlessRestrictions)); 299 | $this->lists[$listName] = $criteria; 300 | } 301 | 302 | /** 303 | * @param $restriction 304 | * 305 | * @return \DateTime|string 306 | */ 307 | private function translateRestrictionValue($restriction) 308 | { 309 | if (in_array($restriction['key'], array('signup', 'last_open', 'last_click', 'dob'))) { 310 | $value = new \DateTime($restriction['value']); 311 | $value = $value->format('U'); 312 | } else { 313 | $value = $restriction['value']; 314 | } 315 | 316 | return $value; 317 | } 318 | 319 | /** 320 | * @Then the list :listName should have :count users 321 | */ 322 | public function theListShouldHaveRecipients($listName, $count) 323 | { 324 | assertCount($count, $this->userRepository->findBy($this->lists[$listName])); 325 | } 326 | 327 | /** 328 | * @Then the list :listName should have the ids :ids 329 | */ 330 | public function theListShouldHaveIds($listName, $ids) 331 | { 332 | $resultIds = $this->userRepository->findIdsBy($this->lists[$listName]); 333 | $expectedIds = array_map('trim', explode(',', $ids)); 334 | $idDiff = array_diff($resultIds, $expectedIds); 335 | assertEquals(array(), $idDiff); 336 | assertCount(count($expectedIds), $resultIds); 337 | } 338 | 339 | /** 340 | * @param int$id 341 | * 342 | * @return object 343 | */ 344 | public function getObjectById($id) 345 | { 346 | $object = $this->repository->find($id); 347 | assertNotNull($object); 348 | 349 | return $object; 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Criteria/AndGroup.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class AndGroup extends Restriction implements AndGroupInterface 9 | { 10 | /** 11 | * @return Restrictions[] 12 | */ 13 | public function getValue() { 14 | return $this->value; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Criteria/AndGroupInterface.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | interface AndGroupInterface 9 | { 10 | /** 11 | * @return Restrictions[] 12 | */ 13 | public function getValue(); 14 | } 15 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Criteria/Criteria.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class Criteria implements CriteriaInterface 12 | { 13 | /** 14 | * @var Collection 15 | */ 16 | protected $restrictions; 17 | 18 | /** 19 | * @param Collection $restrictions 20 | */ 21 | public function __construct(Collection $restrictions = null) 22 | { 23 | if (null === $restrictions) { 24 | $restrictions = new ArrayCollection(); 25 | } 26 | $this->restrictions = $restrictions; 27 | } 28 | 29 | /** 30 | * @return Collection 31 | */ 32 | public function getRestrictions() 33 | { 34 | return $this->restrictions; 35 | } 36 | 37 | /** 38 | * @param Collection $restrictions 39 | */ 40 | public function setRestrictions(Collection $restrictions) 41 | { 42 | $this->restrictions = $restrictions; 43 | } 44 | 45 | /** 46 | * @param Restriction $restriction 47 | */ 48 | public function addRestriction(RestrictionInterface $restriction) 49 | { 50 | $this->restrictions->add($restriction); 51 | } 52 | 53 | /** 54 | * @param Restriction $restriction 55 | */ 56 | public function removeRestriction(RestrictionInterface $restriction) 57 | { 58 | $this->restrictions->removeElement($restriction); 59 | } 60 | 61 | /** 62 | * @param Restriction $expectedRestriction 63 | * 64 | * @return bool 65 | */ 66 | public function hasRestriction(RestrictionInterface $expectedRestriction) 67 | { 68 | foreach ($this->restrictions as $restriction) { 69 | if ($restriction->equals($expectedRestriction)) { 70 | return true; 71 | } 72 | } 73 | 74 | return false; 75 | } 76 | 77 | /** 78 | * @return string 79 | */ 80 | public function __toString() 81 | { 82 | $keyGenerator = new RestrictionsKeyGenerator(); 83 | $keyGenerator->getKeyName($this->getRestrictions()); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Criteria/CriteriaInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface CriteriaInterface 11 | { 12 | /** 13 | * @return array|Restriction[] 14 | */ 15 | public function getRestrictions(); 16 | 17 | /** 18 | * @param Collection $restrictions 19 | */ 20 | public function setRestrictions(Collection $restrictions); 21 | 22 | /** 23 | * @param Restriction $restriction 24 | */ 25 | public function addRestriction(RestrictionInterface $restriction); 26 | 27 | /** 28 | * @param Restriction $restriction 29 | */ 30 | public function removeRestriction(RestrictionInterface $restriction); 31 | 32 | /** 33 | * @param Restriction $restriction 34 | * @return bool 35 | */ 36 | public function hasRestriction(RestrictionInterface $restriction); 37 | 38 | /** 39 | * @return string 40 | */ 41 | public function __toString(); 42 | } -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Criteria/EqualTo.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class EqualTo extends Restriction implements EqualToInterface 9 | { 10 | } 11 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Criteria/EqualToInterface.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | interface EqualToInterface 9 | { 10 | } 11 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Criteria/GreaterThan.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class GreaterThan extends Restriction implements GreaterThanInterface 9 | { 10 | /** 11 | * @return int|string 12 | */ 13 | public function getValue() 14 | { 15 | return (int) $this->value; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Criteria/GreaterThanInterface.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | interface GreaterThanInterface 9 | { 10 | /** 11 | * @return int 12 | */ 13 | public function getValue(); 14 | } 15 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Criteria/GreaterThanXDaysAgoInterface.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | interface GreaterThanXDaysAgoInterface 9 | { 10 | /** 11 | * @return string 12 | */ 13 | public function getValue(); 14 | } 15 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Criteria/LessThan.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class LessThan extends Restriction implements LessThanInterface 9 | { 10 | /** 11 | * @return int|string 12 | */ 13 | public function getValue() 14 | { 15 | return (int) $this->value; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Criteria/LessThanInterface.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | interface LessThanInterface 9 | { 10 | /** 11 | * @return int 12 | */ 13 | public function getValue(); 14 | } 15 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Criteria/LessThanXDaysAgoInterface.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | interface LessThanXDaysAgoInterface 9 | { 10 | /** 11 | * @return string 12 | */ 13 | public function getValue(); 14 | } 15 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Criteria/OrGroup.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class OrGroup extends Restriction implements OrGroupInterface 9 | { 10 | /** 11 | * @return Restrictions[] 12 | */ 13 | public function getValue() { 14 | return $this->value; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Criteria/OrGroupInterface.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | interface OrGroupInterface 9 | { 10 | /** 11 | * @return Restrictions[] 12 | */ 13 | public function getValue(); 14 | } 15 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Criteria/Restriction.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | abstract class Restriction implements RestrictionInterface 9 | { 10 | /** 11 | * @var string 12 | */ 13 | protected $key; 14 | 15 | /** 16 | * @var string|int 17 | */ 18 | protected $value; 19 | 20 | /** 21 | * @param string $key 22 | * @param string|int $value 23 | */ 24 | public function __construct($key, $value) 25 | { 26 | $this->key = $key; 27 | $this->value = $value; 28 | } 29 | 30 | /** 31 | * @return string 32 | */ 33 | public function getKey() 34 | { 35 | return $this->key; 36 | } 37 | 38 | /** 39 | * @return int|string 40 | */ 41 | public function getValue() 42 | { 43 | return $this->value; 44 | } 45 | 46 | /** 47 | * @param string|int|bool $value 48 | */ 49 | public function setValue($value) 50 | { 51 | $this->value = $value; 52 | } 53 | 54 | /** 55 | * @param Restriction $restriction 56 | * 57 | * @return bool 58 | */ 59 | public function equals(RestrictionInterface $restriction) 60 | { 61 | return get_class($restriction) === get_class($this) && 62 | $restriction->getKey() === $this->getKey() && 63 | $restriction->getValue() === $this->getValue(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Criteria/RestrictionInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface RestrictionInterface 11 | { 12 | /** 13 | * @return string 14 | */ 15 | public function getKey(); 16 | 17 | /** 18 | * @return mixed 19 | */ 20 | public function getValue(); 21 | 22 | /** 23 | * @param mixed $value 24 | */ 25 | public function setValue($value); 26 | 27 | /** 28 | * @param RestrictionInterface $restriction 29 | * @return bool 30 | */ 31 | public function equals(RestrictionInterface $restriction); 32 | } -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Criteria/Restrictions.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class Restrictions 9 | { 10 | /** 11 | * @param string $key 12 | * @param int $value 13 | * 14 | * @return GreaterThan 15 | */ 16 | public static function greaterThan($key, $value) 17 | { 18 | return new GreaterThan($key, (int) $value); 19 | } 20 | 21 | /** 22 | * @param string $key 23 | * @param int $value 24 | * 25 | * @return LessThan 26 | */ 27 | public static function lessThan($key, $value) 28 | { 29 | return new LessThan($key, (int) $value); 30 | } 31 | 32 | /** 33 | * @param string $key 34 | * @param int $value 35 | * 36 | * @return EqualTo 37 | */ 38 | public static function equalTo($key, $value) 39 | { 40 | return new EqualTo($key, $value); 41 | } 42 | 43 | /** 44 | * @param string $key 45 | * @param Restriction[] $value 46 | * 47 | * @return AndGroup 48 | */ 49 | public static function andGroup($key, $value) 50 | { 51 | return new AndGroup($key, $value); 52 | } 53 | 54 | /** 55 | * @param string $key 56 | * @param Restriction[] $value 57 | * 58 | * @return And 59 | */ 60 | public static function orGroup($key, $value) 61 | { 62 | return new OrGroup($key, $value); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Criteria/RestrictionsKeyGenerator.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class RestrictionsKeyGenerator 9 | { 10 | /** 11 | * @param array $parts 12 | * 13 | * @return string 14 | */ 15 | public function getKeyName(array $parts) 16 | { 17 | return $this->_createKeyStringForRestriction($parts); 18 | } 19 | 20 | private function _createKeyStringForRestriction($restrictions) 21 | { 22 | $string = ''; 23 | foreach ($restrictions as $restriction) { 24 | $value = $restriction->getValue(); 25 | $finalValue = ''; 26 | 27 | if (is_array($value) || $value instanceof \Traversable) { 28 | $finalValue .= '('.$this->_createKeyStringForRestriction($value).')'; 29 | } else { 30 | $finalValue = $restriction->getValue(); 31 | } 32 | 33 | $string .= sprintf( 34 | '%s %s %s, ', 35 | $restriction->getKey(), 36 | get_class($restriction), 37 | $finalValue 38 | ); 39 | } 40 | return rtrim($string,', '); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/DataTransformer/DataTypes.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | final class DataTypes 9 | { 10 | const DATE = 'date'; 11 | const STRING = 'string'; 12 | const INTEGER = 'integer'; 13 | const DOUBLE = 'double'; 14 | const BOOLEAN = 'boolean'; 15 | 16 | /** 17 | * Denotes a numeric indexed array 18 | */ 19 | const COLLECTION = 'collection'; 20 | 21 | /** 22 | * Denotes an associative array 23 | */ 24 | const HASH = 'hash'; 25 | 26 | /** 27 | * @param string $dataType 28 | * @return bool 29 | */ 30 | static public function isValidDataType($dataType) 31 | { 32 | $reflClass = new \ReflectionClass(new static()); 33 | $constants = $reflClass->getConstants(); 34 | 35 | return in_array($dataType, $constants); 36 | } 37 | 38 | private function __construct() 39 | { 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/DataTransformer/TimestampToDatetimeTransformer.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class TimestampToDatetimeTransformer 12 | { 13 | /** 14 | * @param mixed $value 15 | * @return string 16 | */ 17 | public function transform($value) 18 | { 19 | return DateTime::createFromFormat('U', $value); 20 | } 21 | 22 | /** 23 | * @param mixed $value 24 | * @return mixed|string 25 | */ 26 | public function reverseTransform($value) 27 | { 28 | if (!$value instanceof DateTime) { 29 | throw new InvalidArgumentException( 30 | sprintf( 31 | 'The value must be an instance of \DateTime, "%s given.', 32 | gettype($value) 33 | ) 34 | ); 35 | } 36 | 37 | return $value->format('U'); 38 | } 39 | } -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class InvalidArgumentException extends \InvalidArgumentException 9 | { 10 | } 11 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Exception/InvalidCriteriaException.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class InvalidCriteriaException extends \InvalidArgumentException 9 | { 10 | } 11 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Exception/InvalidMetadataException.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class InvalidMetadataException extends \InvalidArgumentException 9 | { 10 | } 11 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Exception/InvalidRestrictionValue.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class InvalidRestrictionValue extends \InvalidArgumentException 9 | { 10 | } -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Hydrator/ObjectHydrator.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class ObjectHydrator implements ObjectHydratorInterface 14 | { 15 | /** 16 | * @param object $object 17 | * @param array $data 18 | * @param Metadata $metadata 19 | * @return object 20 | */ 21 | public function hydrate($object, array $data, Metadata $metadata) 22 | { 23 | $reflClass = new \ReflectionClass(get_class($object)); 24 | foreach ($reflClass->getProperties() as $property) { 25 | $mapping = $metadata->getPropertyMapping($property->getName()); 26 | if (null == $mapping) { 27 | continue; 28 | } 29 | $property->setAccessible(true); 30 | if (DataTypes::COLLECTION == $mapping['type']) { 31 | $value = array(); 32 | foreach (array_keys($data) as $key) { 33 | if (0 === stripos($key, $mapping['name'].':')) { 34 | $value[] = $data[$key]; 35 | } 36 | } 37 | $property->setValue($object, $value); 38 | 39 | continue; 40 | } elseif (DataTypes::HASH == $mapping['type']) { 41 | $value = array(); 42 | foreach (array_keys($data) as $key) { 43 | if (0 === stripos($key, $mapping['name'].':')) { 44 | $newKey = substr($key, strrpos($key, ':')+1); 45 | $value[$newKey] = $data[$key]; 46 | } 47 | } 48 | $property->setValue($object, $value); 49 | 50 | continue; 51 | } 52 | 53 | if (!isset($data[$mapping['name']])) { 54 | $data[$mapping['name']] = null; 55 | } 56 | $property->setValue($object, $this->transformValue($mapping['type'], $data[$mapping['name']])); 57 | } 58 | 59 | return $object; 60 | } 61 | 62 | /** 63 | * @param object $object 64 | * @param Metadata $metadata 65 | * @return array 66 | */ 67 | public function toArray($object, Metadata $metadata) 68 | { 69 | $reflClass = new \ReflectionClass(get_class($object)); 70 | $data = array(); 71 | foreach ($reflClass->getProperties() as $property) { 72 | $mapping = $metadata->getPropertyMapping($property->getName()); 73 | if (null == $mapping) { 74 | continue; 75 | } 76 | $property->setAccessible(true); 77 | if ($mapping['type'] == DataTypes::COLLECTION || DataTypes::HASH == $mapping['type']) { 78 | foreach ((array)$property->getValue($object) as $key => $value) { 79 | $data[$mapping['name'].':'.$key] = $value; 80 | } 81 | } else { 82 | $data[$mapping['name']] = $this->reverseTransformValue($mapping['type'], $property->getValue($object)); 83 | } 84 | } 85 | 86 | return $data; 87 | } 88 | 89 | /** 90 | * @param string $type 91 | * @param mixed $value 92 | * @return mixed 93 | */ 94 | protected function transformValue($type, $value) 95 | { 96 | switch ($type) { 97 | case DataTypes::STRING: 98 | return strval($value); 99 | case DataTYpes::INTEGER: 100 | return intval($value); 101 | case DataTypes::DOUBLE: 102 | return doubleval($value); 103 | case DataTYpes::BOOLEAN: 104 | return (bool) $value; 105 | case DataTypes::COLLECTION: 106 | return (array) $value; 107 | case DataTypes::DATE: 108 | if (null === $value || '' === $value) { 109 | return null; 110 | } 111 | $transformer = new TimestampToDatetimeTransformer(); 112 | 113 | return $transformer->transform($value); 114 | default: 115 | // @todo Lookup custom data transformer for custom configured types? 116 | return null; 117 | } 118 | } 119 | 120 | /** 121 | * @param string $type 122 | * @param mixed $value 123 | * @return mixed|string 124 | */ 125 | protected function reverseTransformValue($type, $value) 126 | { 127 | if ($type == DataTypes::DATE && $value instanceof \DateTime) { 128 | $transformer = new TimestampToDatetimeTransformer(); 129 | 130 | return $transformer->reverseTransform($value); 131 | } 132 | 133 | if ($type === DataTypes::BOOLEAN) { 134 | return intval($value); 135 | } 136 | 137 | return $value; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Hydrator/ObjectHydratorInterface.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | interface ObjectHydratorInterface 10 | { 11 | /** 12 | * @param object $object 13 | * @param array $data 14 | * @param Metadata $metadata 15 | * @return object 16 | */ 17 | public function hydrate($object, array $data, Metadata $metadata); 18 | 19 | /** 20 | * @param object $object 21 | * @param Metadata $metadata 22 | * @return array 23 | */ 24 | public function toArray($object, Metadata $metadata); 25 | } 26 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/KeyNamingStrategy/ColonDelimitedKeyNamingStrategy.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class ColonDelimitedKeyNamingStrategy implements KeyNamingStrategyInterface 9 | { 10 | /** 11 | * @param array $parts 12 | * @return string 13 | */ 14 | public function getKeyName(array $parts) 15 | { 16 | $key = ''; 17 | foreach ($parts as $part) { 18 | $key .= $part.':'; 19 | } 20 | 21 | return rtrim($key, ':'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/KeyNamingStrategy/KeyNamingStrategyInterface.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | interface KeyNamingStrategyInterface 9 | { 10 | /** 11 | * @param array $parts 12 | * @return string 13 | */ 14 | public function getKeyName(array $parts); 15 | } 16 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Metadata/AnnotationMetadataLoader.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class AnnotationMetadataLoader extends Loader 23 | { 24 | /** 25 | * @var Metadata 26 | */ 27 | protected $metadata; 28 | 29 | /** 30 | * @var string 31 | */ 32 | protected $cacheDir; 33 | 34 | /** 35 | * @param string $cacheDir 36 | */ 37 | public function __construct($cacheDir) 38 | { 39 | $this->cacheDir = $cacheDir; 40 | } 41 | 42 | /** 43 | * @param object $resource 44 | * @param null $type 45 | * @return Metadata 46 | */ 47 | public function load($resource, $type = null) 48 | { 49 | $cachePath = sprintf( 50 | '/%s/__CG__%s_Metadata.php', 51 | $this->cacheDir, 52 | str_replace('\\', '_', $resource) 53 | ); 54 | $cache = new ConfigCache($cachePath, false); 55 | 56 | if ($cache->isFresh()) { 57 | return require $cachePath; 58 | } 59 | $this->metadata = new Metadata(); 60 | 61 | $reader = new AnnotationReader(); 62 | $reflClass = new ReflectionClass($resource); 63 | 64 | $this->loadClassAnnotations($reader, $reflClass); 65 | $this->loadPropertyAnnotations($reader, $reflClass); 66 | $errors = $this->validateMetadata($this->metadata); 67 | if (count($errors) > 0) { 68 | throw new InvalidMetadataException( 69 | sprintf( 70 | 'The loaded metadata for resource "%s" is invalid: %s', 71 | $resource, 72 | implode(', ', $errors) 73 | ) 74 | ); 75 | } 76 | 77 | $code = "metadata, true).';'; 78 | $cache->write($code); 79 | 80 | return $this->metadata; 81 | } 82 | 83 | /** 84 | * @param mixed $resource 85 | * @param null $type 86 | * @return bool 87 | */ 88 | public function supports($resource, $type = null) 89 | { 90 | return is_string($resource) && class_exists($resource); 91 | } 92 | 93 | /** 94 | * @param AnnotationReader $reader 95 | * @param ReflectionClass $reflClass 96 | */ 97 | protected function loadClassAnnotations(AnnotationReader $reader, ReflectionClass $reflClass) 98 | { 99 | foreach ($reader->getClassAnnotations($reflClass) as $annotation) { 100 | if ($annotation instanceof Prefix) { 101 | $this->metadata->setPrefix($annotation->value); 102 | } 103 | } 104 | } 105 | 106 | /** 107 | * @param AnnotationReader $reader 108 | * @param \ReflectionClass $reflClass 109 | */ 110 | protected function loadPropertyAnnotations(AnnotationReader $reader, ReflectionClass $reflClass) 111 | { 112 | foreach ($reflClass->getProperties() as $property) { 113 | foreach ($reader->getPropertyAnnotations($property) as $annotation) { 114 | if ($annotation instanceof SortedIndex) { 115 | $this->metadata->addSortedIndex( 116 | $property->getName(), $this->getKeyNameFromAnnotation($property, $annotation) 117 | ); 118 | } elseif ($annotation instanceof Index) { 119 | $this->metadata->addIndex( 120 | $property->getName(), $this->getKeyNameFromAnnotation($property, $annotation) 121 | ); 122 | } elseif ($annotation instanceof Id) { 123 | $this->metadata->setId($property->getName()); 124 | } elseif ($annotation instanceof Field) { 125 | $this->metadata->addPropertyMapping( 126 | $property->getName(), 127 | array('type' => $annotation->type, 'name' => $annotation->name) 128 | ); 129 | } 130 | } 131 | } 132 | } 133 | 134 | /** 135 | * @param Metadata $metadata 136 | * @return array 137 | */ 138 | protected function validateMetadata(Metadata $metadata) 139 | { 140 | $errors = array(); 141 | if (null == $metadata->getId()) { 142 | $errors[] = 'Id cannot be null.'; 143 | } 144 | 145 | if (null == $metadata->getPrefix()) { 146 | $errors[] = 'Prefix cannot be null.'; 147 | } 148 | 149 | return $errors; 150 | } 151 | 152 | /** 153 | * @param ReflectionProperty $property 154 | * @param object $annotation 155 | * @return string 156 | */ 157 | protected function getKeyNameFromAnnotation(ReflectionProperty $property, $annotation) 158 | { 159 | if (!is_object($annotation) || !property_exists($annotation, 'name')) { 160 | return $property->getName(); 161 | } 162 | 163 | return null === $annotation->name ? $property->getName() : $annotation->name; 164 | } 165 | } -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Metadata/Metadata.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class Metadata 12 | { 13 | /** 14 | * @var string 15 | */ 16 | protected $id; 17 | 18 | /** 19 | * @var string 20 | */ 21 | protected $prefix; 22 | 23 | /** 24 | * @var array 25 | */ 26 | protected $indexes = array(); 27 | 28 | /** 29 | * @var array 30 | */ 31 | protected $sortedIndexes = array(); 32 | 33 | /** 34 | * keys: name 35 | * @var array 36 | */ 37 | protected $propertyMappings = array(); 38 | 39 | /** 40 | * @return array 41 | */ 42 | public function getIndexes() 43 | { 44 | return $this->indexes; 45 | } 46 | 47 | /** 48 | * @param array $indexes 49 | */ 50 | public function setIndexes($indexes) 51 | { 52 | $this->indexes = $indexes; 53 | } 54 | 55 | /** 56 | * @param string $property 57 | * @param string $index 58 | */ 59 | public function addIndex($property, $index) 60 | { 61 | $this->indexes[$property] = $index; 62 | } 63 | 64 | /** 65 | * @param string $name 66 | * @return bool 67 | */ 68 | public function hasIndex($name) 69 | { 70 | return isset($this->indexes[$name]); 71 | } 72 | 73 | /** 74 | * @return string 75 | */ 76 | public function getPrefix() 77 | { 78 | return $this->prefix; 79 | } 80 | 81 | /** 82 | * @param string $prefix 83 | */ 84 | public function setPrefix($prefix) 85 | { 86 | $this->prefix = $prefix; 87 | } 88 | 89 | /** 90 | * @return array 91 | */ 92 | public function getSortedIndexes() 93 | { 94 | return $this->sortedIndexes; 95 | } 96 | 97 | /** 98 | * @param array $sortedIndexes 99 | */ 100 | public function setSortedIndexes($sortedIndexes) 101 | { 102 | $this->sortedIndexes = $sortedIndexes; 103 | } 104 | 105 | /** 106 | * @param string $propertyName 107 | * @param string $sortedIndex 108 | */ 109 | public function addSortedIndex($propertyName, $sortedIndex) 110 | { 111 | $this->sortedIndexes[$propertyName] = $sortedIndex; 112 | } 113 | 114 | /** 115 | * @param string $propertyName 116 | * @return string|null 117 | */ 118 | public function getSortedIndex($propertyName) 119 | { 120 | if (isset($this->sortedIndexes[$propertyName])) { 121 | return $this->sortedIndexes[$propertyName]; 122 | } 123 | 124 | return null; 125 | } 126 | 127 | /** 128 | * @return string 129 | */ 130 | public function getId() 131 | { 132 | return $this->id; 133 | } 134 | 135 | /** 136 | * @param string $id 137 | */ 138 | public function setId($id) 139 | { 140 | $this->id = $id; 141 | } 142 | 143 | /** 144 | * @return array 145 | */ 146 | public function getPropertyMappings() 147 | { 148 | return $this->propertyMappings; 149 | } 150 | 151 | /** 152 | * @param array $propertyMappings 153 | */ 154 | public function setPropertyMappings(array $propertyMappings) 155 | { 156 | $this->propertyMappings = $propertyMappings; 157 | } 158 | 159 | /** 160 | * @param string $propertyName 161 | * @param string $mapping 162 | */ 163 | public function addPropertyMapping($propertyName, $mapping) 164 | { 165 | if (!isset($mapping['type'])) { 166 | throw new InvalidArgumentException(sprintf('Invalid @Field mapping for property "%s".', $propertyName)); 167 | } 168 | if (!DataTypes::isValidDataType($mapping['type'])) { 169 | throw new InvalidArgumentException( 170 | sprintf( 171 | 'Invalid @Field mapping for property "%s": the specified type "%s" is invalid.', 172 | $propertyName, 173 | $mapping['type'] 174 | ) 175 | ); 176 | } 177 | $this->propertyMappings[$propertyName]['type'] = $mapping['type']; 178 | $this->propertyMappings[$propertyName]['name'] = isset($mapping['name']) && null !== $mapping['name'] ? 179 | $mapping['name'] : $propertyName; 180 | } 181 | 182 | /** 183 | * @param string $propertyName 184 | * @return null 185 | */ 186 | public function getPropertyMapping($propertyName) 187 | { 188 | if (isset($this->propertyMappings[$propertyName])) { 189 | return $this->propertyMappings[$propertyName]; 190 | } 191 | 192 | return null; 193 | } 194 | 195 | /** 196 | * @param string $mappedName 197 | * @return string 198 | */ 199 | public function getMappingForMappedName($mappedName) 200 | { 201 | foreach ($this->propertyMappings as $propertyName => $mapping) { 202 | if ($mappedName === $mapping['name']) { 203 | $mapping['propertyName'] = $propertyName; 204 | 205 | return $mapping; 206 | } 207 | } 208 | } 209 | 210 | /** 211 | * @param array $array 212 | * @return Metadata 213 | */ 214 | static public function __set_state(array $array) 215 | { 216 | $metadata = new static(); 217 | $metadata->setId($array['id']); 218 | $metadata->setPrefix($array['prefix']); 219 | $metadata->setIndexes($array['indexes']); 220 | $metadata->setSortedIndexes($array['sortedIndexes']); 221 | $metadata->setPropertyMappings($array['propertyMappings']); 222 | 223 | return $metadata; 224 | } 225 | } -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Metadata/MetadataRegistry.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class MetadataRegistry 11 | { 12 | /** 13 | * @var array 14 | */ 15 | protected $metadata = array(); 16 | 17 | /** 18 | * @var LoaderInterface 19 | */ 20 | protected $loader; 21 | 22 | /** 23 | * @param LoaderInterface $loader 24 | */ 25 | public function __construct(LoaderInterface $loader) 26 | { 27 | $this->loader = $loader; 28 | } 29 | 30 | /** 31 | * @param string $class 32 | * @return Metadata 33 | */ 34 | public function getMetadataFor($class) 35 | { 36 | if (!array_key_exists($class, $this->metadata)) { 37 | $this->metadata[$class] = $this->loader->load($class); 38 | } 39 | 40 | return $this->metadata[$class]; 41 | } 42 | } -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Query/ZRangeByScore.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class ZRangeByScore 9 | { 10 | /** 11 | * @var string 12 | */ 13 | protected $key; 14 | 15 | /** 16 | * @var int|string 17 | */ 18 | protected $min; 19 | 20 | /** 21 | * @var int|string 22 | */ 23 | protected $max; 24 | 25 | /** 26 | * @param string $key 27 | * @param string $min 28 | * @param string $max 29 | */ 30 | public function __construct($key, $min = '-inf', $max = '+inf') 31 | { 32 | $this->key = $key; 33 | $this->min = $min; 34 | $this->max = $max; 35 | } 36 | 37 | /** 38 | * @return string 39 | */ 40 | public function getKey() 41 | { 42 | return $this->key; 43 | } 44 | 45 | /** 46 | * @param string $key 47 | */ 48 | public function setKey($key) 49 | { 50 | $this->key = $key; 51 | } 52 | 53 | /** 54 | * @return int|string 55 | */ 56 | public function getMax() 57 | { 58 | return $this->max; 59 | } 60 | 61 | /** 62 | * @param int|string $max 63 | */ 64 | public function setMax($max) 65 | { 66 | $this->max = $max; 67 | } 68 | 69 | /** 70 | * @return int|string 71 | */ 72 | public function getMin() 73 | { 74 | return $this->min; 75 | } 76 | 77 | /** 78 | * @param int|string $min 79 | */ 80 | public function setMin($min) 81 | { 82 | $this->min = $min; 83 | } 84 | 85 | /** 86 | * @return array 87 | */ 88 | public function toArray() 89 | { 90 | return array($this->key, $this->min, $this->max); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Repository/ObjectRepository.php: -------------------------------------------------------------------------------- 1 | 34 | */ 35 | class ObjectRepository 36 | { 37 | const OP_UNION = 0; 38 | const OP_INTERSECT = 1; 39 | 40 | /** 41 | * @var string 42 | */ 43 | protected $className; 44 | 45 | /** 46 | * @var Client 47 | */ 48 | protected $redis; 49 | 50 | /** 51 | * @var KeyNamingStrategyInterface 52 | */ 53 | protected $keyNamingStrategy; 54 | 55 | /** 56 | * @var RestrictionsKeyGenerator 57 | */ 58 | protected $restrictionsKeyGenerator; 59 | 60 | /** 61 | * @var string 62 | */ 63 | protected $prefix; 64 | 65 | /** 66 | * @var ObjectHydratorInterface 67 | */ 68 | protected $hydrator; 69 | 70 | /** 71 | * @var MetadataRegistry 72 | */ 73 | protected $metadataRegistry; 74 | 75 | /** 76 | * @param Client $redis 77 | * @param KeyNamingStrategyInterface $keyNamingStrategy 78 | * @param string $className 79 | * @param ObjectHydratorInterface $objectHydrator 80 | */ 81 | public function __construct( 82 | Client $redis, 83 | KeyNamingStrategyInterface $keyNamingStrategy, 84 | $className, 85 | MetadataRegistry $metadataRegistry, 86 | ObjectHydratorInterface $objectHydrator = null 87 | ) { 88 | $this->redis = $redis; 89 | $this->keyNamingStrategy = $keyNamingStrategy; 90 | $this->className = $className; 91 | $this->metadataRegistry = $metadataRegistry; 92 | $this->hydrator = $objectHydrator ?: new ObjectHydrator(); 93 | $this->restrictionsKeyGenerator = new RestrictionsKeyGenerator(); 94 | } 95 | 96 | /** 97 | * @param object $object 98 | */ 99 | public function save($object) 100 | { 101 | if (!is_object($object)) { 102 | throw new InvalidArgumentException( 103 | sprintf( 104 | 'You must pass an object to Tystr\RedisOrm\Repository\PredisRepository::save(), %s given.', 105 | gettype($object) 106 | ) 107 | ); 108 | } 109 | 110 | $metadata = $this->getMetadataFor($this->className); 111 | $key = $this->keyNamingStrategy->getKeyName(array($metadata->getPrefix(), $this->getIdForClass($object, $metadata))); 112 | 113 | $originalData = $this->redis->hgetall($key); 114 | $transaction = $this->redis->transaction(); 115 | 116 | $transaction->hmset( 117 | $key, 118 | $newData = $this->hydrator->toArray($object, $metadata) 119 | ); 120 | 121 | // @todo compute changeset here 122 | 123 | $this->handleProperties($object, $metadata, $originalData, $newData, $transaction); 124 | 125 | $transaction->execute(); 126 | } 127 | 128 | /** 129 | * @param mixed $id 130 | * 131 | * @return object 132 | */ 133 | public function find($id) 134 | { 135 | $metadata = $this->getMetadataFor($this->className); 136 | $key = $this->keyNamingStrategy->getKeyName(array($metadata->getPrefix(), $id)); 137 | $data = $this->redis->hgetall($key); 138 | if (empty($data)) { 139 | return; 140 | } 141 | 142 | return $this->hydrator->hydrate($this->newObject(), $data, $metadata); 143 | } 144 | 145 | /** 146 | * @param CriteriaInterface $criteria 147 | */ 148 | public function count(CriteriaInterface $criteria) 149 | { 150 | return $this->findIdsBy($criteria, true); 151 | } 152 | 153 | /** 154 | * @param CriteriaInterface $criteria 155 | * 156 | * @return array|object[] 157 | */ 158 | public function findBy(CriteriaInterface $criteria) 159 | { 160 | $ids = $this->findIdsBy($criteria); 161 | $results = array(); 162 | foreach ($ids as $id) { 163 | $results[] = $this->find($id); 164 | } 165 | 166 | return $results; 167 | } 168 | 169 | /** 170 | * @param CriteriaInterface $criteria 171 | * 172 | * @throws InvalidCriteriaException 173 | * 174 | * @return array 175 | */ 176 | public function findIdsBy(CriteriaInterface $criteria, $countOnly = false) 177 | { 178 | $resultKey = $this->getResults($criteria->getRestrictions(), self::OP_INTERSECT); 179 | 180 | if ($countOnly) { 181 | return $this->redis->zcard($resultKey); 182 | } 183 | 184 | return $this->redis->zrange($resultKey, 0, -1); 185 | } 186 | 187 | private function getResults($restrictions, $setOperation) 188 | { 189 | $keys = array(); 190 | $rangeQueries = array(); 191 | 192 | if (count($restrictions) == 0) { 193 | throw new InvalidCriteriaException('Criteria must have at least 1 restriction, found 0.'); 194 | } 195 | 196 | foreach ($restrictions as $restriction) { 197 | if ($restriction instanceof EqualToInterface) { 198 | $keys[] = $this->keyNamingStrategy->getKeyName(array($restriction->getKey(), $restriction->getValue())); 199 | } elseif ($restriction instanceof LessThanInterface) { 200 | $key = $restriction->getKey(); 201 | $query = isset($rangeQueries[$key]) ? $rangeQueries[$key] : new ZRangeByScore($key); 202 | $query->setMax($restriction->getValue()); 203 | $rangeQueries[$key] = $query; 204 | } elseif ($restriction instanceof GreaterThanInterface) { 205 | $key = $restriction->getKey(); 206 | $query = isset($rangeQueries[$key]) ? $rangeQueries[$key] : new ZRangeByScore($key); 207 | $query->setMin($restriction->getValue()); 208 | $rangeQueries[$key] = $query; 209 | } elseif ($restriction instanceof LessThanXDaysAgoInterface) { 210 | $key = $restriction->getKey(); 211 | $query = isset($rangeQueries[$key]) ? $rangeQueries[$key] : new ZRangeByScore($key); 212 | $value = strtotime($restriction->getValue()); 213 | if (false === $value) { 214 | throw new InvalidRestrictionValue( 215 | sprintf('The value "%s" is not a valid format. Must be similar to "5 days ago" or "1 month 15 days ago".', $restriction->getValue()) 216 | ); 217 | } 218 | $date = DateTime::createFromFormat('U', $value); 219 | $date->setTime(0, 0, 0); 220 | $query->setMin($date->format('U')); 221 | $rangeQueries[$key] = $query; 222 | } elseif ($restriction instanceof GreaterThanXDaysAgoInterface) { 223 | $key = $restriction->getKey(); 224 | $query = isset($rangeQueries[$key]) ? $rangeQueries[$key] : new ZRangeByScore($key); 225 | $value = strtotime($restriction->getValue()); 226 | if (false === $value) { 227 | throw new InvalidRestrictionValue( 228 | sprintf('The value "%s" is not a valid format. Must be similar to "5 days ago" or "1 month 15 days ago".', $restriction->getValue()) 229 | ); 230 | } 231 | $date = DateTime::createFromFormat('U', $value); 232 | $date->setTime(0, 0, 0); 233 | $query->setMax($date->format('U')); 234 | $rangeQueries[$key] = $query; 235 | } elseif ($restriction instanceof AndGroupInterface) { 236 | $keys[] = $this->getResults($restriction->getValue(), self::OP_INTERSECT); 237 | } elseif ($restriction instanceof OrGroupInterface) { 238 | $keys[] = $this->getResults($restriction->getValue(), self::OP_UNION); 239 | } else { 240 | throw new \InvalidArgumentException( 241 | sprintf( 242 | 'Either the given restriction is of an invalid type, or the restriction type "%s" has not been implemented.', 243 | get_class($restriction) 244 | ) 245 | ); 246 | } 247 | } 248 | 249 | /* 250 | //@ TODO redo optimization. If we are at the top level set of restrictions, we can return the restriction data directly 251 | if (count($rangeQueries) == 0) { 252 | if ($countOnly) { 253 | $tmpKey = 'redis-orm:cache:'.md5(time().$criteria->__toString()); 254 | array_unshift($keys, $tmpKey); 255 | call_user_func_array(array($this->redis, $setOperation === self::OP_UNION ? 'sunionstore' : 'sinterstore'), $keys); 256 | $this->redis->expire($tmpKey, 1200); 257 | 258 | return $this->redis->scard($tmpKey); 259 | } 260 | return call_user_func_array(array($this->redis, $setOperation === self::OP_UNION ? 'sunion' : 'sinter'), $keys); 261 | } 262 | */ 263 | 264 | $restrictionsKey = $this->restrictionsKeyGenerator->getKeyName( 265 | $restrictions instanceof Collection ? $restrictions->toArray() : $restrictions 266 | ); 267 | 268 | $tmpKey = $this->keyNamingStrategy->getKeyName(array('redis-orm:cache', str_replace(' ', '.', microtime()), md5($restrictionsKey))); 269 | $rangeKeys = $this->handleRangeQueries($rangeQueries, $tmpKey); 270 | 271 | //$keys = array_merge($keys, array_keys($rangeQueries)); 272 | $keys = array_merge($rangeKeys, $keys); 273 | array_unshift($keys, $tmpKey, count($keys)); 274 | array_push($keys, 'AGGREGATE', 'MAX'); 275 | call_user_func_array(array($this->redis, $setOperation === self::OP_UNION ? 'zunionstore' : 'zinterstore'), $keys); 276 | 277 | //$this->handleRangeQueries($rangeQueries, $tmpKey); 278 | $this->redis->expire($tmpKey, 1200); 279 | 280 | return $tmpKey; 281 | } 282 | 283 | /** 284 | * @param CriteriaInterface $criteria 285 | * 286 | * @throws InvalidCriteriaException 287 | * 288 | * @return string 289 | */ 290 | protected function handleCriteria(CriteriaInterface $criteria) 291 | { 292 | $keys = array(); 293 | $rangeQueries = array(); 294 | $restrictions = $criteria->getRestrictions(); 295 | if ($restrictions->count() == 0) { 296 | throw new InvalidCriteriaException('Criteria must have at least 1 restriction, found 0.'); 297 | } 298 | 299 | foreach ($restrictions as $restriction) { 300 | if ($restriction instanceof EqualToInterface) { 301 | $keys[] = $this->keyNamingStrategy->getKeyName(array($restriction->getKey(), $restriction->getValue())); 302 | } elseif ($restriction instanceof LessThanInterface) { 303 | $key = $restriction->getKey(); 304 | $query = isset($rangeQueries[$key]) ? $rangeQueries[$key] : new ZRangeByScore($key); 305 | $query->setMax($restriction->getValue()); 306 | $rangeQueries[$key] = $query; 307 | } elseif ($restriction instanceof GreaterThanInterface) { 308 | $key = $restriction->getKey(); 309 | $query = isset($rangeQueries[$key]) ? $rangeQueries[$key] : new ZRangeByScore($key); 310 | $query->setMin($restriction->getValue()); 311 | $rangeQueries[$key] = $query; 312 | } elseif ($restriction instanceof LessThanXDaysAgoInterface) { 313 | $key = $restriction->getKey(); 314 | $query = isset($rangeQueries[$key]) ? $rangeQueries[$key] : new ZRangeByScore($key); 315 | $value = strtotime($restriction->getValue()); 316 | if (false === $value) { 317 | throw new InvalidRestrictionValue( 318 | sprintf('The value "%s" is not a valid format. Must be similar to "5 days ago" or "1 month 15 days ago".', $restriction->getValue()) 319 | ); 320 | } 321 | $date = DateTime::createFromFormat('U', $value); 322 | $date->setTime(0, 0, 0); 323 | $query->setMin($date->format('U')); 324 | $rangeQueries[$key] = $query; 325 | } elseif ($restriction instanceof GreaterThanXDaysAgoInterface) { 326 | $key = $restriction->getKey(); 327 | $query = isset($rangeQueries[$key]) ? $rangeQueries[$key] : new ZRangeByScore($key); 328 | $value = strtotime($restriction->getValue()); 329 | if (false === $value) { 330 | throw new InvalidRestrictionValue( 331 | sprintf('The value "%s" is not a valid format. Must be similar to "5 days ago" or "1 month 15 days ago".', $restriction->getValue()) 332 | ); 333 | } 334 | $date = DateTime::createFromFormat('U', $value); 335 | $date->setTime(0, 0, 0); 336 | $query->setMax($date->format('U')); 337 | $rangeQueries[$key] = $query; 338 | } else { 339 | throw new \InvalidArgumentException( 340 | sprintf( 341 | 'Either the given restriction is of an invalid type, or the restriction type "%s" has not been implemented.', 342 | get_class($restriction) 343 | ) 344 | ); 345 | } 346 | } 347 | 348 | if (count($rangeQueries) == 0) { 349 | return call_user_func_array(array($this->redis, 'sinter'), array($keys)); 350 | } 351 | 352 | $tmpKey = 'redis-orm:cache:'.md5(time().$criteria->__toString()); 353 | $rangeKeys = $this->handleRangeQueries($rangeQueries, $tmpKey); 354 | 355 | //$keys = array_merge($keys, array_keys($rangeQueries)); 356 | $keys = array_merge($keys, $rangeKeys); 357 | array_unshift($keys, $tmpKey, count($keys)); 358 | array_push($keys, 'AGGREGATE', 'MAX'); 359 | call_user_func_array(array($this->redis, 'zinterstore'), $keys); 360 | 361 | $this->handleRangeQueries($rangeQueries, $tmpKey); 362 | $this->redis->expire($tmpKey, 1200); 363 | 364 | return $tmpKey; 365 | } 366 | 367 | /** 368 | * @param array $rangeQueries 369 | * @param string $key 370 | * 371 | * @return int 372 | */ 373 | protected function handleRangeQueries(array $rangeQueries, $key) 374 | { 375 | $resultKeys = array(); 376 | foreach ($rangeQueries as $rangeQuery) { 377 | if (!$rangeQuery instanceof ZRangeByScore) { 378 | throw new \InvalidArgumentException( 379 | sprintf( 380 | 'Range queries must be instances of "Tystr\RedisOrm\Query\ZRangeByScore", "%s" given.', 381 | get_class($rangeQuery) 382 | ) 383 | ); 384 | } 385 | $resultKey = sprintf('%s:%s', $key, $rangeQuery->getKey()); 386 | $this->redis->zinterstore($resultKey, array($rangeQuery->getKey())); 387 | 388 | $min = $rangeQuery->getMin(); 389 | if ($min != '-inf') { 390 | $this->redis->zremrangebyscore($resultKey, '-inf', $min); 391 | } 392 | 393 | $max = $rangeQuery->getMax(); 394 | if ($max != '+inf') { 395 | $this->redis->zremrangebyscore($resultKey, $max, '+inf'); 396 | } 397 | $this->redis->expire($resultKey, 1200); 398 | 399 | $resultKeys[] = $resultKey; 400 | } 401 | 402 | return $resultKeys; 403 | } 404 | 405 | /** 406 | * @param string $className 407 | * 408 | * @return Metadata 409 | */ 410 | protected function getMetadataFor($className) 411 | { 412 | return $this->metadataRegistry->getMetadataFor($className); 413 | } 414 | 415 | /** 416 | * @param object $object 417 | * @param Metadata $metadata 418 | * @param array $originalData 419 | * @param array $newData 420 | * @param MultiExec $transaction 421 | */ 422 | protected function handleProperties($object, Metadata $metadata, array $originalData, array $newData, MultiExec $transaction) 423 | { 424 | $reflClass = new ReflectionClass($object); 425 | foreach ($metadata->getIndexes() as $propertyName => $keyName) { 426 | $this->handleIndex($reflClass, $object, $propertyName, $keyName, $metadata, $originalData, $transaction); 427 | } 428 | 429 | foreach ($metadata->getSortedIndexes() as $propertyName => $keyName) { 430 | $this->handleSortedIndex($reflClass, $object, $propertyName, $keyName, $metadata, $newData, $transaction); 431 | } 432 | } 433 | 434 | /** 435 | * @param ReflectionClass $reflClass 436 | * @param object $object 437 | * @param string $propertyName 438 | * @param Metadata $metadata 439 | * @param array $originalData 440 | * @param MultiExec $transaction 441 | */ 442 | protected function handleIndex(ReflectionClass $reflClass, $object, $propertyName, $keyName, Metadata $metadata, array $originalData, $transaction) 443 | { 444 | $property = $reflClass->getProperty($propertyName); 445 | $property->setAccessible(true); 446 | $value = $property->getValue($object); 447 | $mapping = $metadata->getPropertyMapping($propertyName); 448 | 449 | // Grab the intval here to prevent boolean false from being string cast to "" instead of "0" 450 | if (DataTypes::BOOLEAN === $mapping['type']) { 451 | $value = intval($value); 452 | } elseif (DataTypes::HASH == $mapping['type']) { 453 | foreach ($value as $key => $val) { 454 | if ((null === $val && isset($originalData[$mapping['name'].':'.$key])) || 455 | (isset($originalData[$mapping['name'].':'.$key]) && $originalData[$mapping['name'].':'.$key] != $val) 456 | ) { 457 | $transaction->srem( 458 | $this->keyNamingStrategy->getKeyName(array($key, $originalData[$mapping['name'].':'.$key])), 459 | $this->getIdForClass($object, $metadata) 460 | ); 461 | } 462 | $transaction->sadd( 463 | $this->keyNamingStrategy->getKeyName(array($key, $val)), 464 | $this->getIdForClass($object, $metadata) 465 | ); 466 | } 467 | 468 | return; 469 | } 470 | if (null === $value && isset($originalData[$keyName]) || isset($originalData[$keyName]) && $value !== $originalData[$keyName]) { 471 | $key = $this->keyNamingStrategy->getKeyName(array($keyName, $originalData[$keyName])); 472 | $transaction->srem( 473 | $key, 474 | $this->getIdForClass($object, $metadata) 475 | ); 476 | } 477 | $key = $this->keyNamingStrategy->getKeyName(array($keyName, $value)); 478 | $transaction->sadd($key, $this->getIdForClass($object, $metadata)); 479 | } 480 | 481 | /** 482 | * @param ReflectionClass $reflClass 483 | * @param object $object 484 | * @param string $propertyName 485 | * @param Metadata $metadata 486 | * @param array $newData 487 | * @param MultiExec $transaction 488 | */ 489 | protected function handleSortedIndex(ReflectionClass $reflClass, $object, $propertyName, $keyName, Metadata $metadata, array $newData, $transaction) 490 | { 491 | $property = $reflClass->getProperty($propertyName); 492 | $property->setAccessible(true); 493 | $mapping = $metadata->getPropertyMapping($propertyName); 494 | 495 | if (!isset($newData[$mapping['name']]) || null === $newData[$mapping['name']]) { 496 | $transaction->zrem($this->keyNamingStrategy->getKeyName(array($keyName, $newData[$mapping['name']])), $this->getIdForClass($object, $metadata)); 497 | 498 | return; 499 | } 500 | 501 | $transaction->zadd( 502 | $this->keyNamingStrategy->getKeyName(array($keyName)), 503 | $newData[$mapping['name']], 504 | $this->getIdForClass($object, $metadata) 505 | ); 506 | } 507 | 508 | /** 509 | * @param ReflectionClass $reflClass 510 | * @param Metadata $metadata 511 | * 512 | * @return string|int 513 | */ 514 | protected function getIdForClass($object, Metadata $metadata) 515 | { 516 | $getter = 'get'.ucfirst(strtolower($metadata->getId())); 517 | if (!method_exists($object, $getter)) { 518 | throw new \RuntimeException( 519 | sprintf( 520 | 'The class "%s" must have a "%s" method for accessing the property mapped as the id field (%s)', 521 | get_class($object), 522 | $getter, 523 | $metadata->getId() 524 | ) 525 | ); 526 | } 527 | 528 | return $object->$getter(); 529 | } 530 | 531 | /** 532 | * @return object 533 | */ 534 | protected function newObject() 535 | { 536 | if (version_compare(PHP_VERSION, '5.4') >= 0) { 537 | $reflClass = new ReflectionClass($this->className); 538 | 539 | return $reflClass->newInstanceWithoutConstructor(); 540 | } 541 | 542 | return unserialize(sprintf('O:%d:"%s":0:{}', strlen($this->className), $this->className)); 543 | } 544 | } 545 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Test/Model/Car.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class Car 18 | { 19 | /** 20 | * @var string 21 | * @Field(type="integer") 22 | * @Id 23 | */ 24 | protected $id; 25 | 26 | /** 27 | * @var string 28 | * @Field(type="string") 29 | * @Index 30 | */ 31 | protected $make; 32 | 33 | /** 34 | * @var string 35 | * @Field(type="string") 36 | * @Index 37 | */ 38 | protected $model; 39 | 40 | /** 41 | * @var string 42 | * @Field(type="string") 43 | * @Index(name="engine_type") 44 | */ 45 | protected $engineType; 46 | 47 | /** 48 | * @var string 49 | * @Field(type="string") 50 | * @Index 51 | */ 52 | protected $color; 53 | 54 | /** 55 | * @var DateTime 56 | * @Field(type="date", name="manufacture_date") 57 | * @Date(name="manufacture_date") 58 | */ 59 | protected $manufactureDate; 60 | 61 | /** 62 | * @var array 63 | * @Field(type="collection") 64 | */ 65 | protected $owners; 66 | 67 | /** 68 | * @var array 69 | * @Field(type="hash") 70 | */ 71 | protected $attributes; 72 | 73 | /** 74 | * @var bool 75 | * @Field(type="boolean") 76 | * @Index 77 | */ 78 | protected $active; 79 | 80 | /** 81 | * @return string 82 | */ 83 | public function getColor() 84 | { 85 | return $this->color; 86 | } 87 | 88 | /** 89 | * @param string $color 90 | */ 91 | public function setColor($color) 92 | { 93 | $this->color = $color; 94 | } 95 | 96 | /** 97 | * @return string 98 | */ 99 | public function getEngineType() 100 | { 101 | return $this->engineType; 102 | } 103 | 104 | /** 105 | * @param string $engineType 106 | */ 107 | public function setEngineType($engineType) 108 | { 109 | $this->engineType = $engineType; 110 | } 111 | 112 | /** 113 | * @return string 114 | */ 115 | public function getId() 116 | { 117 | return $this->id; 118 | } 119 | 120 | /** 121 | * @param string $id 122 | */ 123 | public function setId($id) 124 | { 125 | $this->id = $id; 126 | } 127 | 128 | /** 129 | * @return string 130 | */ 131 | public function getMake() 132 | { 133 | return $this->make; 134 | } 135 | 136 | /** 137 | * @param string $make 138 | */ 139 | public function setMake($make) 140 | { 141 | $this->make = $make; 142 | } 143 | 144 | /** 145 | * @return string 146 | */ 147 | public function getModel() 148 | { 149 | return $this->model; 150 | } 151 | 152 | /** 153 | * @param string $model 154 | */ 155 | public function setModel($model) 156 | { 157 | $this->model = $model; 158 | } 159 | 160 | /** 161 | * @return DateTime 162 | */ 163 | public function getManufactureDate() 164 | { 165 | return $this->manufactureDate; 166 | } 167 | 168 | /** 169 | * @param DateTime $manufactureDate 170 | */ 171 | public function setManufactureDate(DateTime $manufactureDate = null) 172 | { 173 | $this->manufactureDate = $manufactureDate; 174 | } 175 | 176 | /** 177 | * @return array 178 | */ 179 | public function getOwners() 180 | { 181 | return $this->owners; 182 | } 183 | 184 | /** 185 | * @param array $owners 186 | */ 187 | public function setOwners(array $owners) 188 | { 189 | $this->owners = $owners; 190 | } 191 | 192 | /** 193 | * @return array 194 | */ 195 | public function getAttributes() 196 | { 197 | return $this->attributes; 198 | } 199 | 200 | /** 201 | * @param array $attributes 202 | */ 203 | public function setAttributes(array $attributes) 204 | { 205 | $this->attributes = $attributes; 206 | } 207 | 208 | /** 209 | * @param string $key 210 | * @param mixed $value 211 | */ 212 | public function setAttribute($key, $value) 213 | { 214 | $this->attributes[$key] = $value; 215 | } 216 | 217 | /** 218 | * @param string $key 219 | * @return null 220 | */ 221 | public function getAttribute($key) 222 | { 223 | return isset($this->attributes[$key]) ? $this->attributes[$key] : null; 224 | } 225 | 226 | /** 227 | * @return boolean 228 | */ 229 | public function getActive() 230 | { 231 | return $this->active; 232 | } 233 | 234 | /** 235 | * @param boolean $active 236 | */ 237 | public function setActive($active) 238 | { 239 | $this->active = $active; 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Test/Model/User.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class User 18 | { 19 | /** 20 | * @Id 21 | * 22 | * @var string 23 | */ 24 | protected $email; 25 | 26 | /** 27 | * @Field(type="date") 28 | * @Date(name="dob") 29 | * 30 | * @var \DateTime 31 | */ 32 | protected $dateOfBirth; 33 | 34 | /** 35 | * @Field(type="date") 36 | * @Date(name="signup") 37 | * 38 | * @var \DateTime 39 | */ 40 | protected $signupDate; 41 | 42 | /** 43 | * @Field(type="date") 44 | * @Date(name="last_open") 45 | * 46 | * @var \DateTime 47 | */ 48 | protected $lastOpen; 49 | 50 | /** 51 | * @Field(type="date") 52 | * @Date(name="last_click") 53 | * 54 | * @var \DateTime 55 | */ 56 | protected $lastClick; 57 | 58 | /** 59 | * @Field(type="hash") 60 | * @Index 61 | * 62 | * @var array 63 | */ 64 | protected $attributes = array(); 65 | 66 | /** 67 | * @param string $email 68 | * @param array $attributes 69 | */ 70 | public function __construct($email, array $attributes = array()) 71 | { 72 | $this->email = $email; 73 | $this->attributes = $attributes; 74 | } 75 | 76 | /** 77 | * @return string 78 | */ 79 | public function getId() 80 | { 81 | return $this->email; 82 | } 83 | 84 | /** 85 | * @return ArrayCollection 86 | */ 87 | public function getAttributes() 88 | { 89 | return $this->attributes; 90 | } 91 | 92 | /** 93 | * @param array $attributes 94 | */ 95 | public function setAttributes(array $attributes) 96 | { 97 | $this->attributes = $attributes; 98 | } 99 | 100 | /** 101 | * @param string $key 102 | * 103 | * @return bool 104 | */ 105 | public function hasAttribute($key) 106 | { 107 | return isset($this->attributes[$key]); 108 | } 109 | 110 | /** 111 | * @return string 112 | */ 113 | public function getEmail() 114 | { 115 | return $this->email; 116 | } 117 | 118 | /** 119 | * @param string $email 120 | */ 121 | public function setEmail($email) 122 | { 123 | $this->email = $email; 124 | } 125 | 126 | /** 127 | * @return \DateTime 128 | */ 129 | public function getDateOfBirth() 130 | { 131 | return $this->dateOfBirth; 132 | } 133 | 134 | /** 135 | * @param \DateTime $dateOfBirth 136 | */ 137 | public function setDateOfBirth(\DateTime $dateOfBirth) 138 | { 139 | $this->dateOfBirth = $dateOfBirth; 140 | } 141 | 142 | /** 143 | * @return \DateTime 144 | */ 145 | public function getSignupDate() 146 | { 147 | return $this->signupDate; 148 | } 149 | 150 | /** 151 | * @param \DateTime $signupDate 152 | */ 153 | public function setSignupDate(\DateTime $signupDate) 154 | { 155 | $this->signupDate = $signupDate; 156 | } 157 | 158 | /** 159 | * @return \DateTime 160 | */ 161 | public function getLastClick() 162 | { 163 | return $this->lastClick; 164 | } 165 | 166 | /** 167 | * @param \DateTime $lastClick 168 | */ 169 | public function setLastClick(\DateTime $lastClick) 170 | { 171 | $this->lastClick = $lastClick; 172 | } 173 | 174 | /** 175 | * @return \DateTime 176 | */ 177 | public function getLastOpen() 178 | { 179 | return $this->lastOpen; 180 | } 181 | 182 | /** 183 | * @param \DateTime $lastOpen 184 | */ 185 | public function setLastOpen(\DateTime $lastOpen) 186 | { 187 | $this->lastOpen = $lastOpen; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/Tystr/RedisOrm/Test/Model/UserList.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class UserList 11 | { 12 | /** 13 | * @var string 14 | */ 15 | protected $name; 16 | 17 | /** 18 | * @var Criteria 19 | */ 20 | protected $criteria; 21 | 22 | 23 | /** 24 | * @param string $name 25 | * @param Criteria $criteria 26 | */ 27 | public function __construct($name, Criteria $criteria) 28 | { 29 | $this->name = $name; 30 | $this->criteria = $criteria; 31 | } 32 | 33 | /** 34 | * @return string 35 | */ 36 | public function getName() 37 | { 38 | return $this->name; 39 | } 40 | 41 | /** 42 | * @param string $name 43 | */ 44 | public function setName($name) 45 | { 46 | $this->name = $name; 47 | } 48 | 49 | /** 50 | * @return Criteria 51 | */ 52 | public function getCriteria() 53 | { 54 | return $this->criteria; 55 | } 56 | 57 | /** 58 | * @param Criteria $criteria 59 | */ 60 | public function setCriteria(Criteria $criteria) 61 | { 62 | $this->criteria = $criteria; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/Tystr/RedisOrm/Tests/Criteria/RestrictionsKeyGeneratorTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class RestrictionsKeyGeneratorTest extends PHPUnit_Framework_TestCase 11 | { 12 | /** 13 | * @param $restrictions 14 | * @param $expectedKey 15 | * @dataProvider dataProvider 16 | */ 17 | public function testKeyGenerator($restrictions, $expectedKey) 18 | { 19 | $keyGenerator = new RestrictionsKeyGenerator(); 20 | assertEquals($expectedKey, $keyGenerator->getKeyName($restrictions)); 21 | } 22 | 23 | public function dataProvider() 24 | { 25 | return array( 26 | array(array(new EqualTo('aKey', 'aValue')), 'aKey Tystr\RedisOrm\Criteria\EqualTo aValue'), 27 | array(array(new EqualTo('aKey', 'aValue'), new EqualTo('aKey2', 'aValue2')), 'aKey Tystr\RedisOrm\Criteria\EqualTo aValue, aKey2 Tystr\RedisOrm\Criteria\EqualTo aValue2'), 28 | array(array(new AndGroup('aKey', array())), 'aKey Tystr\RedisOrm\Criteria\AndGroup ()'), 29 | array(array(new AndGroup('aKey', array(new EqualTo('equalKey', 'equalValue')))), 'aKey Tystr\RedisOrm\Criteria\AndGroup (equalKey Tystr\RedisOrm\Criteria\EqualTo equalValue)'), 30 | array(array(new OrGroup('aKey', array(new EqualTo('equalKey', 'equalValue')))), 'aKey Tystr\RedisOrm\Criteria\OrGroup (equalKey Tystr\RedisOrm\Criteria\EqualTo equalValue)'), 31 | array(array(new AndGroup('aKey', array(new EqualTo('equal1', 'value1'), new EqualTo('equal2', 'value2')))), 'aKey Tystr\RedisOrm\Criteria\AndGroup (equal1 Tystr\RedisOrm\Criteria\EqualTo value1, equal2 Tystr\RedisOrm\Criteria\EqualTo value2)'), 32 | array(array(new AndGroup('andKey1', array()),new AndGroup('andKey2', array())), 'andKey1 Tystr\RedisOrm\Criteria\AndGroup (), andKey2 Tystr\RedisOrm\Criteria\AndGroup ()'), 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Tystr/RedisOrm/Tests/DataTransformer/TimestampToDatetimeTransformerTest.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class TimestampToDatetimeTransformerTest extends \PHPUnit_Framework_TestCase 13 | { 14 | public function testTransform() 15 | { 16 | $transformer = new TimestampToDatetimeTransformer(); 17 | assertEquals(new DateTime('2014-01-01'), $transformer->transform(1388534400)); 18 | } 19 | 20 | public function testReverseTransform() 21 | { 22 | $transformer = new TimestampToDatetimeTransformer(); 23 | assertEquals(1388534400, $transformer->reverseTransform(new DateTime('2014-01-01'))); 24 | } 25 | 26 | public function testReverseTransformRequiresDateTimeObject() 27 | { 28 | $transformer = new TimestampToDatetimeTransformer(); 29 | $this->setExpectedException('Tystr\RedisOrm\Exception\InvalidArgumentException'); 30 | $transformer->reverseTransform(123); 31 | } 32 | } -------------------------------------------------------------------------------- /tests/Tystr/RedisOrm/Tests/Hydrator/ObjectHydratorTest.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class ObjectHydratorTest extends \PHPUnit_Framework_TestCase 16 | { 17 | /** 18 | * @var ObjectHydrator 19 | */ 20 | protected $hydrator; 21 | 22 | /** 23 | * @var Person 24 | */ 25 | protected $person; 26 | 27 | /** 28 | * @var Metadata 29 | */ 30 | protected $metadata; 31 | 32 | public function setUp() 33 | { 34 | $this->hydrator = new ObjectHydrator(); 35 | $this->person = new Person('Tyler'); 36 | $loader = new AnnotationMetadataLoader('/tmp'); 37 | $registry = new MetadataRegistry($loader); 38 | $this->metadata = $registry->getMetadataFor(get_class($this->person)); 39 | } 40 | 41 | public function testHydrateInteger() 42 | { 43 | $data = array('id' => "1"); 44 | $hydratedPerson = $this->hydrator->hydrate($this->person, $data, $this->metadata); 45 | assertInternalType('integer', $hydratedPerson->id); 46 | assertEquals(1, $hydratedPerson->id); 47 | } 48 | 49 | public function testHydrateString() 50 | { 51 | $data = array('address' => "123 Main St."); 52 | $hydratedPerson = $this->hydrator->hydrate($this->person, $data, $this->metadata); 53 | assertInternalType('string', $hydratedPerson->address); 54 | assertEquals('123 Main St.', $hydratedPerson->address); 55 | } 56 | 57 | public function testHydrateFloat() 58 | { 59 | $data = array('money' => "10.95"); 60 | $hydratedPerson = $this->hydrator->hydrate($this->person, $data, $this->metadata); 61 | assertInternalType('float', $hydratedPerson->money); 62 | assertEquals(10.95, $hydratedPerson->money); 63 | } 64 | 65 | public function testHydrateDateTime() 66 | { 67 | $dob = new \DateTime('1989-01-01'); 68 | $data = array('dob' => $dob->format('U')); 69 | $hydratedPerson = $this->hydrator->hydrate($this->person, $data, $this->metadata); 70 | assertInstanceOf('DateTime', $hydratedPerson->dob); 71 | assertEquals('1989-01-01', $hydratedPerson->dob->format('Y-m-d')); 72 | } 73 | 74 | public function testHydrateWithName() 75 | { 76 | $data = array('first_name' => 'Tyler'); 77 | $hydratedPerson = $this->hydrator->hydrate($this->person, $data, $this->metadata); 78 | 79 | assertEquals('Tyler', $hydratedPerson->firstName); 80 | } 81 | 82 | public function testToArrayWithString() 83 | { 84 | $this->person->address = '123 Main St.'; 85 | $array = $this->hydrator->toArray($this->person, $this->metadata); 86 | 87 | assertArrayHasKey('address', $array); 88 | assertInternalType('string', $array['address']); 89 | assertEquals('123 Main St.', $array['address']); 90 | } 91 | 92 | public function testToArrayWithInteger() 93 | { 94 | $this->person->id = 10; 95 | $array = $this->hydrator->toArray($this->person, $this->metadata); 96 | 97 | assertArrayHasKey('id', $array); 98 | assertInternalType('integer', $array['id']); 99 | assertEquals(10, $array['id']); 100 | } 101 | 102 | public function testToArrayWithFloat() 103 | { 104 | $this->person->money = 9.95; 105 | $array = $this->hydrator->toArray($this->person, $this->metadata); 106 | 107 | assertArrayHasKey('money', $array); 108 | assertInternalType('float', $array['money']); 109 | assertEquals(9.95, $array['money']); 110 | } 111 | 112 | public function testToArrayWithDateTime() 113 | { 114 | $this->person->dob = new DateTime('1984-01-01'); 115 | $array = $this->hydrator->toArray($this->person, $this->metadata); 116 | 117 | assertArrayHasKey('dob', $array); 118 | assertInternalType('string', $array['dob']); 119 | assertEquals('441763200', $array['dob']); 120 | } 121 | 122 | public function testToArrayWithName() 123 | { 124 | $this->person->firstName = 'Tyler'; 125 | $array = $this->hydrator->toArray($this->person, $this->metadata); 126 | 127 | assertArrayHasKey('first_name', $array); 128 | assertInternalType('string', $array['first_name']); 129 | assertEquals('Tyler', $array['first_name']); 130 | } 131 | } -------------------------------------------------------------------------------- /tests/Tystr/RedisOrm/Tests/KeyNamingStrategy/ColonDelimitedKeyNamingStrategyTest.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class ColonDelimitedKeyNamingStrategyTest extends \PHPUnit_Framework_TestCase 9 | { 10 | public function testGetKeyName() 11 | { 12 | $parts = array('prefix', 'user', 123456); 13 | $strategy = new ColonDelimitedKeyNamingStrategy(); 14 | 15 | assertEquals('prefix:user:123456', $strategy->getKeyName($parts)); 16 | } 17 | } -------------------------------------------------------------------------------- /tests/Tystr/RedisOrm/Tests/Metadata/MetadataRegistryTest.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class MetadataRegistryTest extends \PHPUnit_Framework_TestCase 15 | { 16 | public function testGetMetadataForWith() 17 | { 18 | $loader = $this->getMockBuilder('Tystr\RedisOrm\Metadata\AnnotationMetadataLoader')->disableOriginalConstructor()->getMock(); 19 | $registry = new MetadataRegistry($loader); 20 | $class = 'Tystr\RedisOrm\Tests\Model\Person'; 21 | 22 | $expectedMetadata = new Metadata(); 23 | 24 | $loader->expects($this->once()) 25 | ->method('load') 26 | ->with($class) 27 | ->will($this->returnValue($expectedMetadata)); 28 | 29 | $metadata = $registry->getMetadataFor($class); 30 | $this->assertSame($expectedMetadata, $metadata); 31 | 32 | // The following call should not trigger a call to LoaderInterface::load() 33 | $metadata = $registry->getMetadataFor($class); 34 | $this->assertSame($expectedMetadata, $metadata); 35 | } 36 | } -------------------------------------------------------------------------------- /tests/Tystr/RedisOrm/Tests/Metadata/MetadataTest.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class MetadataTest extends \PHPUnit_Framework_TestCase 12 | { 13 | public function testAddPropertyMappingWithInvalidMappingThrowsException() 14 | { 15 | $metadata = new Metadata(); 16 | $this->setExpectedException('InvalidArgumentException'); 17 | $metadata->addPropertyMapping('someProperty', null); 18 | } 19 | 20 | public function testAddPropertyMappingWithInvalidDataTypeThrowsException() 21 | { 22 | $metadata = new Metadata(); 23 | $this->setExpectedException('InvalidArgumentException'); 24 | $metadata->addPropertyMapping('someProperty', array('name' => 'some_property', 'type' => 'INVALID TYPE')); 25 | } 26 | 27 | public function testAddPropertyMapping() 28 | { 29 | $metadata = new Metadata(); 30 | $mapping = array('type' => 'string'); 31 | $metadata->addPropertyMapping('property', $mapping); 32 | 33 | $mapping['name'] = 'property'; 34 | assertEquals($metadata->getPropertyMapping('property'), $mapping); 35 | } 36 | 37 | public function testAddPropertyMappingWithName() 38 | { 39 | $metadata = new Metadata(); 40 | $mapping = array('name' => 'some_property', 'type' => 'string'); 41 | $metadata->addPropertyMapping('someProperty', $mapping); 42 | 43 | assertEquals($metadata->getPropertyMapping('someProperty'), $mapping); 44 | } 45 | 46 | public function testGetMappingForMappedName() 47 | { 48 | $metadata = new Metadata(); 49 | $mapping = array('name' => 'some_property', 'type' => 'string'); 50 | $metadata->addPropertyMapping('someProperty', $mapping); 51 | 52 | $mapping['propertyName'] = 'someProperty'; 53 | assertEquals($mapping, $metadata->getMappingForMappedName('some_property')); 54 | } 55 | 56 | public function testSetState() 57 | { 58 | $data = array( 59 | 'id' => 1, 60 | 'prefix' => 'some_', 61 | 'indexes' => array('index1', 'index2'), 62 | 'sortedIndexes' => array('sortedIndex1', 'sortedIndex2'), 63 | 'propertyMappings' => array( 64 | array('name' => 'property', 'type' => 'string') 65 | ) 66 | ); 67 | 68 | $metadata = Metadata::__set_state($data); 69 | assertInstanceOf('Tystr\RedisOrm\Metadata\Metadata', $metadata); 70 | assertEquals($data['id'], $metadata->getId()); 71 | assertEquals($data['prefix'], $metadata->getPrefix()); 72 | assertEquals($data['indexes'], $metadata->getIndexes()); 73 | assertEquals($data['sortedIndexes'], $metadata->getSortedIndexes()); 74 | assertEquals($data['propertyMappings'], $metadata->getPropertyMappings()); 75 | } 76 | } -------------------------------------------------------------------------------- /tests/Tystr/RedisOrm/Tests/Model/Person.php: -------------------------------------------------------------------------------- 1 | name = $name; 99 | $this->dob = $dob; 100 | } 101 | } -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | addPsr4('Tystr\\', __DIR__.'/Tystr'); 5 | 6 | use Doctrine\Common\Annotations\AnnotationRegistry; 7 | AnnotationRegistry::registerLoader(array($loader, 'loadClass')); 8 | --------------------------------------------------------------------------------