mirror of
https://github.com/predis/predis.git
synced 2026-08-21 02:31:51 +00:00
Compare commits
988 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 239864d9a0 | |||
| b006d2306c | |||
| 34e394cc10 | |||
| 79539c3bcf | |||
| 74817b5596 | |||
| f967ecd66b | |||
| 261d0b824f | |||
| b77da8476c | |||
| 3fbd29d00b | |||
| 8373663486 | |||
| 18853d1dbb | |||
| 1da827c0ae | |||
| aa8fd5cea4 | |||
| 8d353cc223 | |||
| 0a580c9d1b | |||
| 58d1cc12dc | |||
| a8862f0fdd | |||
| e148dc831a | |||
| c4b592298c | |||
| 0e3b105959 | |||
| 749bbfe0e3 | |||
| c2ae1c612e | |||
| a85f89e2c7 | |||
| dbb802f0f1 | |||
| 64b79511bd | |||
| 5a9e747b01 | |||
| cb0fbd01b4 | |||
| 83df2ef11c | |||
| c94e5d3a10 | |||
| c90fbfc0ff | |||
| 21d6f0e7c6 | |||
| f4a950b275 | |||
| 5ec51c899f | |||
| e1d700dd0c | |||
| 809f76a7b3 | |||
| ab6cbe7e10 | |||
| 80bcb9f475 | |||
| c8e671a42a | |||
| 838e6af403 | |||
| ba2ffb612a | |||
| fdaf73987b | |||
| 50f8922df2 | |||
| 60733eb5c9 | |||
| a13dd4e180 | |||
| 1678e486b1 | |||
| 5f2eea628e | |||
| 0f01b89d44 | |||
| 9d0201ed9e | |||
| 0cead0245d | |||
| 0a4eed1dff | |||
| d2debfd43e | |||
| 9a1884d960 | |||
| a6dd685f8d | |||
| f499d5695a | |||
| 23c4b95a0b | |||
| 80cb31514a | |||
| 6306509086 | |||
| 2aab474670 | |||
| d628c0f23b | |||
| ad92618003 | |||
| 0fb7674762 | |||
| e778baa491 | |||
| d88a280977 | |||
| eb6cbef4e7 | |||
| 2aa771004d | |||
| 43bf3096a0 | |||
| 317fa42891 | |||
| b45f87caf2 | |||
| b7cba07014 | |||
| 56627f30f8 | |||
| 9d4524cae7 | |||
| fd1c6f244f | |||
| aa4d0a05ac | |||
| d45b5f7ec8 | |||
| 87ca5a1a49 | |||
| e5ec950a11 | |||
| 3f2c2b9031 | |||
| 88ea08fe83 | |||
| bd48225edc | |||
| ea9809d4e8 | |||
| 09cb6677e2 | |||
| c32f6c7152 | |||
| 78fd475747 | |||
| 86a4343e59 | |||
| ecf1cc07e5 | |||
| 2fc1ea88cd | |||
| 72e9187662 | |||
| 98410bbcaa | |||
| 5ea1d6fe92 | |||
| 73780da52d | |||
| 381023ad8f | |||
| c3c819cc9c | |||
| 987f27556f | |||
| a9b42924e5 | |||
| e4e2238f34 | |||
| e1f6c8644f | |||
| d16a1c8705 | |||
| 32aff28ef5 | |||
| eb906bb29c | |||
| 33ef043b8e | |||
| 973c9a7031 | |||
| db55fae961 | |||
| 5192005862 | |||
| cdc6075b01 | |||
| 623d1cc1a2 | |||
| d53069f096 | |||
| 8565a42605 | |||
| aac811f897 | |||
| 4e664c6c90 | |||
| b1a51b2ef9 | |||
| de02c81398 | |||
| 62f8730f1a | |||
| 9b8b362747 | |||
| ee9e09aa07 | |||
| 66f98186f0 | |||
| 463f2d655c | |||
| b2db97d983 | |||
| 9da1648145 | |||
| 5ad00774e1 | |||
| ff004aeaaa | |||
| df2c9eb7ff | |||
| 4d8348158f | |||
| 0ab0aa14fb | |||
| 85c978fdad | |||
| b7f411a252 | |||
| 9866500ad6 | |||
| 73ee5aabad | |||
| e88938cdfd | |||
| 93ae376618 | |||
| 7028082b7f | |||
| d09d8edc7d | |||
| 70e42f8fca | |||
| 695b6dd7b3 | |||
| 6ce09a2a8b | |||
| bb03602326 | |||
| 5c9adbacd7 | |||
| 8533dbdb0b | |||
| d78e1b6ab7 | |||
| d5aec78086 | |||
| 0faae20737 | |||
| 4eb4960b63 | |||
| 75c64b7f2f | |||
| d78780382a | |||
| 98aacd8d9a | |||
| 4824af1563 | |||
| cb18d67b6a | |||
| 4576dc029f | |||
| aa458a1922 | |||
| 2d2930d24f | |||
| 7e0ec1a265 | |||
| 04e597ebba | |||
| d681bff4ab | |||
| 3dea41aa66 | |||
| 69c66e157f | |||
| dd50cfe47e | |||
| 8451146d38 | |||
| d45ce35923 | |||
| 6cd124cc59 | |||
| d6fa4a3292 | |||
| 5b771704d3 | |||
| 68d972a1b1 | |||
| 4bf035918d | |||
| 94c4e2ffee | |||
| 6e2fd181f1 | |||
| 1c21fcc5b3 | |||
| 531261b0fc | |||
| 7434a9bd0e | |||
| c4123ce7ce | |||
| 536992e277 | |||
| 5a735aa670 | |||
| 15f2776d77 | |||
| dc1103582e | |||
| 8c0498a893 | |||
| aba24d0f6f | |||
| a1df4f20da | |||
| 116eaba75e | |||
| de4bae3f9b | |||
| b50a5975ac | |||
| 1c1b4096f6 | |||
| f8e4ba152f | |||
| 6b6b73f5f4 | |||
| 729e40d6c0 | |||
| fbcfdc343e | |||
| 8cbcb09c4c | |||
| 2781bd780f | |||
| 9675626aac | |||
| f8829985f7 | |||
| e2e809c9d4 | |||
| ef0067e1a4 | |||
| da343046e5 | |||
| 76d6681f68 | |||
| 8d01be388d | |||
| c38376dcc4 | |||
| c354d02105 | |||
| d6685a424a | |||
| 05ef62ff97 | |||
| b76e876b73 | |||
| ebd895a67d | |||
| 923e7ed5fd | |||
| 9e0fe7bdc7 | |||
| 5d774dc581 | |||
| dffbb8b042 | |||
| 06d5475129 | |||
| 1536455fea | |||
| aa1b070f9b | |||
| 3e125c964c | |||
| e9fdc47d3f | |||
| d3930ea298 | |||
| 0efcbb7992 | |||
| f2af247c63 | |||
| a5cf6d72cd | |||
| 7173f0c80c | |||
| 495e3c6f8a | |||
| 3465bc7c1f | |||
| 8bf896f81f | |||
| 0085bc08ae | |||
| f7232b9ddc | |||
| 95cc528904 | |||
| 4a16cb9417 | |||
| 94758d5f58 | |||
| acc0f04bdb | |||
| 09de7be7cb | |||
| 70b279bbdc | |||
| f8cbbc5b8f | |||
| 83720075f3 | |||
| 9901233fa1 | |||
| dcb4093046 | |||
| 1e8af648cb | |||
| ff5e3515c3 | |||
| 59813cd74e | |||
| bfd96b15dc | |||
| 51b466267d | |||
| 78f3596566 | |||
| ac01c1a17f | |||
| 46920c9c77 | |||
| b26d6103a6 | |||
| a4760d65cd | |||
| 49aa6f0aca | |||
| 292f9939b2 | |||
| daa36a980d | |||
| 0b09d2c359 | |||
| 0a622aee31 | |||
| 040dfebd9c | |||
| 0e4604a9bb | |||
| deb64c295a | |||
| aac06a4cd8 | |||
| dfb5cf2c1b | |||
| a1024bba53 | |||
| f357cafb14 | |||
| 2b4cf80d90 | |||
| 6cd1f03834 | |||
| 732d7f96d1 | |||
| b4e1ab106c | |||
| 5a2c61c02b | |||
| 1b9e10bdd8 | |||
| 37e2b546b0 | |||
| f163e1221b | |||
| 1bb2ab95ef | |||
| ea18afc820 | |||
| baa91da35b | |||
| 6d85a81da4 | |||
| f913e81b9f | |||
| 003b3d180d | |||
| a6e1ac11d0 | |||
| be46ab813c | |||
| 6918de6e4b | |||
| cfa97dd9f5 | |||
| 1e8f790ee4 | |||
| c655affe5f | |||
| 457fd6076d | |||
| 282287765b | |||
| 00d6160549 | |||
| 576163724c | |||
| 9df9dbdedc | |||
| 8d7f5099c4 | |||
| e8daf45269 | |||
| 8567b05459 | |||
| 6e76ea6c7e | |||
| e774a301c4 | |||
| 2b85824d74 | |||
| 81422b9992 | |||
| c230b6e831 | |||
| 69d4c013f7 | |||
| 1ac9cc9c71 | |||
| b2f59a0c63 | |||
| 93971bcb45 | |||
| e54bc31212 | |||
| b9eddae96f | |||
| 7683f97ff2 | |||
| f4f9d02551 | |||
| bacdb7ff62 | |||
| e0c6ad58be | |||
| febd99421f | |||
| 8559c2cb30 | |||
| 2ba54a0e1c | |||
| 024a31d571 | |||
| d0572b0efc | |||
| 0f80cb55bb | |||
| 55a3fd65a2 | |||
| 6c732c737b | |||
| 559853bdd5 | |||
| 28e1e7ad25 | |||
| 98112fdb39 | |||
| 0e8d978348 | |||
| 469f3cc68b | |||
| 36f8210864 | |||
| 81be513eaa | |||
| 4b73afc316 | |||
| fd6a14d80b | |||
| f2d92f1e29 | |||
| c3cafaa9a6 | |||
| 89d6c0e9b6 | |||
| 9f51263136 | |||
| a75e52cf5a | |||
| b865cee1f8 | |||
| 1f0be85a3d | |||
| 2578e154d4 | |||
| 81da6d2553 | |||
| 188eaf035b | |||
| 6638776d85 | |||
| 511fe0bbab | |||
| 56a3e327fc | |||
| 63cf5048a7 | |||
| 177d7e1c6a | |||
| 8a84f8b6cf | |||
| 09f15a9cb9 | |||
| 2b4885abc3 | |||
| 649e71c0ab | |||
| 14ac381aad | |||
| 78360ea27b | |||
| 214b7d2dc9 | |||
| 2d0b30dac5 | |||
| 75afe831b0 | |||
| c36e6fd900 | |||
| 743ccc39ae | |||
| 41c29bed4e | |||
| 90f37f8698 | |||
| dc889be2a2 | |||
| ca7bd8149f | |||
| 9426d78b90 | |||
| 6b6c91cd15 | |||
| b511911ddb | |||
| eb450ae0b4 | |||
| ca033d15a9 | |||
| 9443da1948 | |||
| 298dc87613 | |||
| 92697aa075 | |||
| 7c393cbc09 | |||
| 5b6e4dc3dd | |||
| b37d072b68 | |||
| 608c48146f | |||
| bcba9e1009 | |||
| 64a84d9020 | |||
| a3b4c2244a | |||
| 4805a048ed | |||
| c394458b80 | |||
| 51d0b58cd3 | |||
| 890ace8565 | |||
| 8ba432e8f7 | |||
| d61bb3af07 | |||
| da381fffa9 | |||
| 7b01bc0644 | |||
| 7893fd1faa | |||
| 785670dcf7 | |||
| ec723fd4a9 | |||
| b67af29eb6 | |||
| dc14c29676 | |||
| ce625ca902 | |||
| 46d293a716 | |||
| fcd41a4787 | |||
| 8dc9450f39 | |||
| 6ad522f492 | |||
| 317fb6c398 | |||
| 93163bdfb3 | |||
| 626520b836 | |||
| aaea628c55 | |||
| 135fd5004b | |||
| c25a815317 | |||
| 39a9fc2ddd | |||
| f5bca54bba | |||
| 364aa180f9 | |||
| 4da506cd9e | |||
| ef5034dc72 | |||
| 994f045a4a | |||
| 8cd8d4e580 | |||
| 22f1aafae8 | |||
| c47b009b69 | |||
| 2cff6f0886 | |||
| 63cb150fc2 | |||
| 466c666547 | |||
| 8d5c794efe | |||
| e3fba5d4b7 | |||
| a8400f2ed8 | |||
| 44629a33db | |||
| d38f164d3f | |||
| 9bc5685959 | |||
| 565690a5b1 | |||
| bdbbe18e6c | |||
| 78027f0498 | |||
| 104cd1eae7 | |||
| 5148ce16c6 | |||
| 4b9ab0bcce | |||
| 39ed8e3e8f | |||
| 6f4347010d | |||
| c43278eceb | |||
| 5561ce6bd3 | |||
| 6730661b4a | |||
| 4e0c09a3cf | |||
| 71aad42534 | |||
| 25b7d72fc9 | |||
| 0f88225104 | |||
| 8be921cb66 | |||
| e12d80b67f | |||
| 8984af1201 | |||
| dc9bd4e3a3 | |||
| 2c4073ecab | |||
| 1d6d0f3555 | |||
| efdc16476d | |||
| 469d5127aa | |||
| 9dcabc3264 | |||
| 6e13a8af44 | |||
| 58e61ae1ca | |||
| ab1aeb3c01 | |||
| 1e8446ef63 | |||
| 371ac91777 | |||
| dd20e8557f | |||
| fae8a79cf2 | |||
| da25d6b6ff | |||
| cad5d16eae | |||
| a910807666 | |||
| d81346641f | |||
| 1b3478ec2e | |||
| 9ab18ff2b4 | |||
| 2babb8744a | |||
| d9b95d0bc7 | |||
| d79ff39882 | |||
| 559c5097cd | |||
| 367ca6e31e | |||
| 4a8c678561 | |||
| a5bf0eb41a | |||
| 1ae30d62db | |||
| 64119bb4a0 | |||
| 1d635c370f | |||
| 1260c91f63 | |||
| 728afe7fcb | |||
| 058e11257b | |||
| 31bd53b1ed | |||
| a894f470bd | |||
| b7c715a03f | |||
| 4882e23319 | |||
| 7bdc2e6195 | |||
| 0fee0c6d97 | |||
| 5d42b41c22 | |||
| 2c1f349925 | |||
| d784838447 | |||
| 654907857d | |||
| 5d04cf540d | |||
| 51b998720e | |||
| 4e0709ad3d | |||
| f75974b5d8 | |||
| 5680cd40b5 | |||
| 43c9900804 | |||
| 5d5001c177 | |||
| 3d4957b06d | |||
| 734eba3165 | |||
| 22cb11d057 | |||
| 20f9c0c155 | |||
| 29f26b0f9b | |||
| 2f23fb03f0 | |||
| d160cd9175 | |||
| ce75acf81f | |||
| b9ca78ac43 | |||
| 4e6ed3f26d | |||
| 1f2104ef7f | |||
| 9608a97469 | |||
| b4d5b19b30 | |||
| 20d872a306 | |||
| 4251664095 | |||
| b5affe739c | |||
| f19c6335d1 | |||
| e070f83dc5 | |||
| c107d937fa | |||
| 68dba4cb12 | |||
| f1072f8344 | |||
| 5e41657363 | |||
| 04b8a8f75c | |||
| f7d9c4bac7 | |||
| 49355b0881 | |||
| c33d053531 | |||
| 0d28395aa0 | |||
| 7ebbe25171 | |||
| 1296717c2d | |||
| 94a2ddeca9 | |||
| 07584f859c | |||
| 45a5dd49a7 | |||
| bd8a44101c | |||
| 71e7cb1399 | |||
| f085d2f97d | |||
| 647cd53235 | |||
| 8fdf935605 | |||
| 7742fd5a22 | |||
| 7241fe6419 | |||
| d01e738d59 | |||
| 0bdcf299c2 | |||
| 03d408ddad | |||
| 3733e790eb | |||
| a9e34cabb1 | |||
| b30f495345 | |||
| c6fa5232bc | |||
| 2b62b75557 | |||
| 6151d6d53e | |||
| db51f6e0d0 | |||
| 62744fbb02 | |||
| 05b111d6d2 | |||
| fc51f23ca7 | |||
| b85bc00880 | |||
| 86256f2819 | |||
| bc040fae6f | |||
| e7d2717ee1 | |||
| 0de4af07dd | |||
| ff7812282e | |||
| e5a85dfce8 | |||
| 4b3ba03a3e | |||
| 776501b31a | |||
| 2c8d256bdc | |||
| 0195a7488a | |||
| a8e063e855 | |||
| 2cdcd297f0 | |||
| 65c0336d2f | |||
| 8c207f71bd | |||
| 42f4663050 | |||
| 3801928e0d | |||
| d59556cae1 | |||
| 667ce97213 | |||
| 101fffbabe | |||
| b35dcd515b | |||
| df0943120e | |||
| b72b12c5cd | |||
| 3f2814c254 | |||
| 1bd4a25e36 | |||
| 24aaa2ac23 | |||
| ba4ebb3799 | |||
| 087b8b0b2b | |||
| b3910466c9 | |||
| 6ad73c782a | |||
| fee3866f54 | |||
| dfad0d3d24 | |||
| 1ba1408d39 | |||
| e048ac2d51 | |||
| fb3088b4d3 | |||
| 962037321a | |||
| 42521f2558 | |||
| 2012963ed0 | |||
| deeb58644a | |||
| 9f05f50899 | |||
| fd97cd1bad | |||
| cc2349c785 | |||
| 8efe76ad60 | |||
| 2010774b68 | |||
| 909b06e805 | |||
| 13ac03e134 | |||
| 092b822033 | |||
| 0e0cd865e7 | |||
| 228a2ceb2e | |||
| b688beaff7 | |||
| 51e0d61833 | |||
| 6e6a28a9f0 | |||
| 486225111d | |||
| cd8b3535ef | |||
| 984fd06c77 | |||
| b087b9814d | |||
| 4d1dcdb280 | |||
| 893618c985 | |||
| 0255261cd5 | |||
| 5f0799ab65 | |||
| e97e13bde5 | |||
| 46db0409a1 | |||
| cb363b52f6 | |||
| 724543a4e6 | |||
| 150795f999 | |||
| 9b4a915a52 | |||
| b47a3f9ab8 | |||
| 5a80876c64 | |||
| b5ebf48318 | |||
| 09b5d7ac61 | |||
| 812d37b2d4 | |||
| 859ee35c68 | |||
| ca22208ce8 | |||
| a386263d84 | |||
| 2bdfed5071 | |||
| bd027341be | |||
| 4fc87fa161 | |||
| be1f0914b1 | |||
| 804a5d95fb | |||
| 650d6a07e0 | |||
| d4694da487 | |||
| 7e4b367a1d | |||
| 19b703588b | |||
| b5a1ba9795 | |||
| 21372bef6d | |||
| e507198cc3 | |||
| 88a9f509b4 | |||
| b2575bd667 | |||
| 86edc50ac4 | |||
| dac0104267 | |||
| f7a3388865 | |||
| 7b4da3cd78 | |||
| 6dd3565c67 | |||
| 3ca4645231 | |||
| c07c610a0d | |||
| 880d2d24b9 | |||
| 4ef8948b5e | |||
| b396eb3c48 | |||
| b4a90df812 | |||
| dd3faa3d53 | |||
| 819f3ca6ed | |||
| 8787930977 | |||
| a8705b9535 | |||
| 66f6610c72 | |||
| 9bc50e321e | |||
| 4af1b7744c | |||
| cbc7929f6e | |||
| 1a175da7ca | |||
| f6eab6ec3a | |||
| 07d9717f8a | |||
| 3f3c235363 | |||
| a348e32144 | |||
| 55899e7741 | |||
| d1157a506d | |||
| bea8a3239d | |||
| 839ce08487 | |||
| 944a478272 | |||
| 83bab78bd1 | |||
| b22cb6f76d | |||
| 5527fc34e7 | |||
| 7979d9ab64 | |||
| 0396320951 | |||
| 20ce6e523a | |||
| 44d4c77918 | |||
| 65fc4f7d55 | |||
| 6e2bba3ddb | |||
| 2a47dc8275 | |||
| 9340b3ddce | |||
| db95350c37 | |||
| 80f1cedf79 | |||
| 33f6419700 | |||
| 771a0ee179 | |||
| 1d9b53d443 | |||
| 86df741937 | |||
| 4ea8489110 | |||
| ae96b0f39f | |||
| 1ee7926673 | |||
| b5d748156e | |||
| 5e05febf33 | |||
| 07b4238ce7 | |||
| c0d9b53ff7 | |||
| 9954bd5a12 | |||
| 4b25c41f78 | |||
| ac471eeff3 | |||
| da2cd3ee7e | |||
| bfa9cc7469 | |||
| 87a1f43487 | |||
| 556f7b0ec2 | |||
| 7146a32f6d | |||
| 595692d50f | |||
| c2a61a3113 | |||
| 27d56a7ac9 | |||
| 4c84eb181b | |||
| 503cc86f7d | |||
| 8300b1d63c | |||
| 62b4e52eef | |||
| 8a2f53059a | |||
| c2097b4242 | |||
| 6f51521f81 | |||
| 609db5159e | |||
| 5ab210fd9d | |||
| 6fe57bae3f | |||
| 9c0012837a | |||
| 741495037e | |||
| 9d5dc120dc | |||
| 74f6f9ab85 | |||
| 0d6ffaf0f0 | |||
| 9389f217c8 | |||
| 3f720ad867 | |||
| 17f51420ce | |||
| 3d9ddeef98 | |||
| 85c1a7a852 | |||
| 094a5bcc90 | |||
| f637872421 | |||
| de1a183315 | |||
| ab45b7a461 | |||
| 2ed31eeb16 | |||
| 3b70d1e020 | |||
| 2201dd1df2 | |||
| 658782f531 | |||
| c4064e5801 | |||
| d6a445f1ec | |||
| 0f751cabcb | |||
| b007b753f5 | |||
| b84c84007f | |||
| 4e0e1908cd | |||
| 930a1096e1 | |||
| cdfd0b9f5c | |||
| cb1f27a403 | |||
| e4c45455fe | |||
| ceca866158 | |||
| 665e428366 | |||
| 31e068796f | |||
| e84844e33c | |||
| 21ac9b06f5 | |||
| e5ff0ab992 | |||
| 743feb0634 | |||
| afd78b49a6 | |||
| 94d0805cf6 | |||
| 15af329aa3 | |||
| a10a8e3c9b | |||
| 3181f6c592 | |||
| b11ab53bb1 | |||
| 82759cd6ec | |||
| b5a89a0f50 | |||
| 2853214ddc | |||
| e5e7933aef | |||
| a81f76d376 | |||
| 9b17352d9c | |||
| 14d4492ad6 | |||
| 995de40734 | |||
| 190b687789 | |||
| e94fbc3287 | |||
| 826db07900 | |||
| be4d01c071 | |||
| 74b6d05b48 | |||
| 58eb4e2477 | |||
| 3186da49b4 | |||
| 3f8b639e2b | |||
| 6ce35c97e9 | |||
| b03e6c4f02 | |||
| 7bef3abf91 | |||
| 3b4ef4f33e | |||
| f668560a5a | |||
| 6c9b246b2b | |||
| 878b338e87 | |||
| 38e76cbbc8 | |||
| f8844ef8b8 | |||
| 3681c1e622 | |||
| 99c8acfc8c | |||
| 30bc53ee75 | |||
| 0d5fb3319d | |||
| c3ce210150 | |||
| 8b749386df | |||
| 6abac4590d | |||
| a627b91be1 | |||
| 5077996940 | |||
| db807e7a91 | |||
| 3021cbd76b | |||
| 2c4a9e92a6 | |||
| d077aa67db | |||
| 0af002b3b9 | |||
| a3218721c1 | |||
| 21c959a451 | |||
| ba9c3ed3db | |||
| eba7f84335 | |||
| 1e1a904d1a | |||
| 33ffca23a0 | |||
| 6fada0c35c | |||
| 99129fbf91 | |||
| 5431af0c4b | |||
| 89c311d0de | |||
| fda4f32f06 | |||
| 9fcdc73499 | |||
| ba4dc10936 | |||
| aec3fcf3e8 | |||
| 1746948d61 | |||
| d05007b5db | |||
| fa08ff1b18 | |||
| 9e0f0078ea | |||
| 4ad4b9cf77 | |||
| d2b0326087 | |||
| 9f58d8a663 | |||
| c6b8336346 | |||
| ac1e6e1846 | |||
| 30f908cd85 | |||
| 2a78142b28 | |||
| 978d7087d5 | |||
| 97439a7e08 | |||
| 5a25cf33be | |||
| 237f3e3d7e | |||
| 784b33358d | |||
| 2b2ca8e8dc | |||
| af03916369 | |||
| 8e4ef21b41 | |||
| 65c530a13b | |||
| dcafee61a8 | |||
| c1587ccf54 | |||
| 44839a7a6f | |||
| 9d6ffcc5f2 | |||
| 679c7ffdd5 | |||
| 09a7a18ac0 | |||
| 03cff6a813 | |||
| f6bd5757af | |||
| 8a145bc6c9 | |||
| fc3ee47140 | |||
| ddb3180700 | |||
| f7bef784af | |||
| c5d353b754 | |||
| fb2bc5c8e9 | |||
| 68f77444b6 | |||
| b6d93b5b34 | |||
| 4e6ed1fd5b | |||
| fc9deb68be | |||
| b47adffd4f | |||
| 3d80ff489a | |||
| 4705b27302 | |||
| 9877e0c380 | |||
| 92e2455189 | |||
| e1d8ee5d9b | |||
| 74145ce45c | |||
| 898b038f48 | |||
| aacf2bc65d | |||
| 11c490e978 | |||
| db2469341f | |||
| c1108c6e92 | |||
| c9567c8606 | |||
| 014adfe75d | |||
| 2853ba7cef | |||
| 539a4bbdcd | |||
| bf3776b47a | |||
| fe0a940f18 | |||
| c5169694ef | |||
| a992d9d0b8 | |||
| c94cf5ef32 | |||
| 5d37ea1258 | |||
| 8392f465cc | |||
| b3e7cfe408 | |||
| b013bce140 | |||
| f1488534b6 | |||
| d1282261b8 | |||
| f1223c1a90 | |||
| bcfb568d1a | |||
| 48a47aa503 | |||
| 8eb59c8852 | |||
| cd9995b972 | |||
| 131e4c9d03 | |||
| cd828ab18a | |||
| 33b71a085c | |||
| 48762ec876 | |||
| 3b2681f2c8 | |||
| 62b8569dc8 | |||
| f4309893bc | |||
| c247d6201a | |||
| df268a1143 | |||
| d04c6d2670 | |||
| 8a76c8c849 | |||
| a7ae86689d | |||
| 6de7222857 | |||
| fb30e22c30 | |||
| 2bb763dbf6 | |||
| afff56d2ec | |||
| d717d906e8 | |||
| 51352bcbb3 | |||
| 2b809325e0 | |||
| 306ed28c5d | |||
| d94ad9942f | |||
| dcbd943505 | |||
| b74e2c22f7 | |||
| 48728d2672 | |||
| e134e21b78 | |||
| f0f1647773 | |||
| d270205a8c | |||
| a1db042d17 | |||
| e645ed7d3f | |||
| 9ae69e6c51 | |||
| 3a46f8a7c5 | |||
| b202e6cae7 | |||
| 5ca82fb56b | |||
| 1530c7c0d6 | |||
| 11b2f3aca3 | |||
| 6b82b412fb | |||
| 858d7372ca | |||
| 99042f6b9c | |||
| 725d5abe4a | |||
| 46b56437a4 | |||
| 4a497eea79 | |||
| cf21a43211 | |||
| 70487bbfb3 | |||
| 922beeceea | |||
| 02d7400d1f | |||
| 3a2e977ee0 | |||
| eac195eaea | |||
| daf88645f2 | |||
| ecc7499b47 | |||
| 64cb73fbb9 | |||
| 0b9bca8ce1 | |||
| e1924551b1 | |||
| 171a28fe26 | |||
| ca5cd2f42a | |||
| 1e4661cfc8 | |||
| e146fa9e65 | |||
| 21b3fdb3e1 | |||
| cd9dbb763e | |||
| fffa7acd83 | |||
| f419d15f82 | |||
| 7b1713398d | |||
| bbc94863d1 | |||
| 9e58ad7648 | |||
| 1d45649f86 | |||
| 29f18254bb | |||
| 4c8f31f532 | |||
| 96e7a58f9f | |||
| c131d2f7b6 | |||
| fc8f363fe5 | |||
| 49a140e3af | |||
| 73b4760fc4 | |||
| 8f601b8420 | |||
| 4a6bab995e | |||
| b3ea36f3fa | |||
| 8769567c7e | |||
| 5124fd1d43 | |||
| 2eec3929fb | |||
| aa0ea048ae | |||
| f67082072d | |||
| ac5f34b0ad | |||
| 78f11f1a0f | |||
| 9468891faf | |||
| 9b849d49c0 | |||
| ff2129bddf | |||
| b40c2fd214 | |||
| 3487cc219c | |||
| 4aca9852fe | |||
| 104bfa3332 | |||
| 68397fe958 | |||
| 8428f9916a | |||
| 2aec7bfeda | |||
| 870a7b9e78 | |||
| b4b06acfe7 | |||
| fbd5059fb2 | |||
| c5f6ca1d90 | |||
| 6ea96e82f9 | |||
| e7b8b50dda | |||
| a1f202527f | |||
| 01b59a546c | |||
| 565a647a3d | |||
| 9c494c6052 | |||
| 46f5408490 | |||
| 1520e2c48b | |||
| 618708b853 | |||
| ad48369a6b | |||
| 802e77da26 | |||
| 4dc1099103 | |||
| f1df8b163c | |||
| 12298d599b | |||
| d2cd1b70b8 | |||
| 7d447f8493 | |||
| 9f21f5a635 | |||
| e3ae04437c | |||
| 2af73f4fa1 | |||
| a4df829b19 | |||
| 22807e8163 | |||
| 1bb04baf78 | |||
| f073bc8f70 | |||
| 50970c003b | |||
| 34cfebdc14 | |||
| 735c92089c | |||
| 3853e62d34 | |||
| ed105ab4fc | |||
| adb489b4d3 | |||
| 6d1c4a2096 | |||
| 5cf10ddbfa | |||
| 5defb14698 | |||
| 3b74aa8f82 | |||
| ac2b718387 | |||
| 89e46912a2 | |||
| 200d864df3 | |||
| 35b8588dc4 | |||
| 371a7ad69b | |||
| 5ae476ee16 | |||
| f4f19ace0a | |||
| 50092d1421 | |||
| 5287bb2387 | |||
| 66a0458018 | |||
| 2543bb30cc | |||
| 1fd715e7a5 | |||
| e191d0a7ce | |||
| a2c9cb38a5 | |||
| 72c41a25cb | |||
| b5a6c4e5c3 | |||
| dd07c12402 | |||
| 42aa8333fc | |||
| 25ac5c3ca4 |
@@ -0,0 +1,7 @@
|
||||
*.tgz
|
||||
*.phar
|
||||
phpunit.xml
|
||||
package.xml
|
||||
composer.lock
|
||||
experiments/
|
||||
vendor/
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
language: php
|
||||
php:
|
||||
- 5.3
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- hhvm
|
||||
branches:
|
||||
except:
|
||||
- v0.5
|
||||
- v0.6
|
||||
- php5.2_backport
|
||||
- documentation
|
||||
services: redis-server
|
||||
before_script:
|
||||
- composer self-update
|
||||
- composer install --no-interaction --prefer-source --dev
|
||||
script:
|
||||
- vendor/bin/phpunit -c phpunit.xml.travisci
|
||||
matrix:
|
||||
allow_failures:
|
||||
- php: hhvm
|
||||
fast_finish: true
|
||||
@@ -1,124 +0,0 @@
|
||||
v0.6.0 (2010-05-24)
|
||||
* Switched to the new multi-bulk request protocol for all of the commands
|
||||
in the Redis 1.2 and Redis 2.0 profiles. Inline and bulk requests are now
|
||||
deprecated as they will be removed in future releases of Redis.
|
||||
|
||||
* The default server profile is "2.0" (targeting Redis 2.0.x). If you are
|
||||
using older versions of Redis, it is highly recommended that you specify
|
||||
which server profile the client should use (e.g. "1.2" when connecting
|
||||
to instances of Redis 1.2.x).
|
||||
|
||||
* Support for Redis 1.0 is now optional and it is provided by requiring
|
||||
'Predis_Compatibility.php' before creating an instance of Predis\Client.
|
||||
|
||||
* New commands added to the Redis 2.0 profile since Predis 0.5.1:
|
||||
- Strings: SETEX, APPEND, SUBSTR
|
||||
- ZSets : ZCOUNT, ZRANK, ZUNIONSTORE, ZINTERSTORE, ZREMBYRANK, ZREVRANK
|
||||
- Hashes : HSET, HSETNX, HMSET, HINCRBY, HGET, HMGET, HDEL, HEXISTS,
|
||||
HLEN, HKEYS, HVALS, HGETALL
|
||||
- PubSub : PUBLISH, SUBSCRIBE, UNSUBSCRIBE
|
||||
- Misc. : DISCARD, CONFIG
|
||||
|
||||
* Introduced client-level options with the new Predis\ClientOptions class.
|
||||
Options can be passed to Predis\Client::__construct in its second argument
|
||||
as an array or an instance of Predis\ClientOptions. For brevity's sake and
|
||||
compatibility with older versions, the constructor of Predis\Client still
|
||||
accepts an instance of Predis\RedisServerProfile in its second argument.
|
||||
The currently supported client options are:
|
||||
- profile [default: "2.0" as of Predis 0.6.0]
|
||||
specifies which server profile to use when connecting to Redis. This
|
||||
option accepts an instance of Predis\RedisServerProfile or a string
|
||||
that indicates the target version.
|
||||
- key_distribution [default: Predis\Distribution\HashRing]
|
||||
specifies which key distribution strategy to use to distribute keys
|
||||
among the servers that compose a cluster. This option accepts an
|
||||
instance of Predis\Distribution\IDistributionStrategy so that users
|
||||
can implement their own key distribution strategy. Optionally, the new
|
||||
Predis\Distribution\KetamaPureRing class also provides a pure-PHP
|
||||
implementation of the same algorithm used by libketama.
|
||||
- throw_on_error [default: TRUE]
|
||||
server errors can optionally be handled "silently": instead of throwing
|
||||
an exception, the client returns an error response type.
|
||||
- iterable_multibulk [EXPERIMENTAL - default: FALSE]
|
||||
in addition to the classic way of fetching a whole multibulk reply
|
||||
into an array, the client can now optionally stream a multibulk reply
|
||||
down to the user code by using PHP iterators. It is just a little bit
|
||||
slower, but it can save a lot of memory in certain scenarios.
|
||||
|
||||
* New parameters for connections:
|
||||
- alias [default: not set]
|
||||
every connection can now be identified by an alias that is useful to
|
||||
get a certain connection when connected to a cluster of Redis servers.
|
||||
- weight [default: not set]
|
||||
allows the client to balance the keys asymmetrically across multiple
|
||||
servers. This might be useful when you have servers with different
|
||||
amounts of memory and you want to distribute the load of your keys
|
||||
accordingly.
|
||||
- connection_async [default: FALSE]
|
||||
estabilish connections to servers in a non-blocking way, so that the
|
||||
client is not blocked while the underlying resource performs the actual
|
||||
connection.
|
||||
- connection_persistent [default: FALSE]
|
||||
the underlying connection resource is left open when a script ends its
|
||||
lifecycle. Persistent connections can lead to unpredictable or strange
|
||||
behaviours, so they should be used with extreme care.
|
||||
|
||||
* Introduced the Predis\Pipeline\IPipelineExecutor interface. Classes that
|
||||
implements this interface are used internally by the Predis\CommandPipeline
|
||||
class to change the behaviour of the pipeline when writing/reading commands
|
||||
from one or multiple servers. Here is the list of the default executors:
|
||||
- Predis\Pipeline\StandardExecutor
|
||||
Exceptions generated by server errors might be thrown depending on the
|
||||
options passed to the client (see "throw_on_error"). Instead, protocol
|
||||
or network errors always throw exceptions. This is the default executor
|
||||
for single and clustered connections and shares the same behaviour of
|
||||
Predis 0.5.x.
|
||||
- Predis\Pipeline\SafeExecutor
|
||||
Exceptions generated by server, protocol or network errors are not
|
||||
thrown, instead they are returned in the response array as instances of
|
||||
ResponseError or CommunicationException.
|
||||
- Predis\Pipeline\SafeClusterExecutor
|
||||
This executor shares the same behaviour of Predis\Pipeline\SafeExecutor
|
||||
but it is geared towards clustered connections.
|
||||
|
||||
* Support for PUBSUB is handled by the new Predis\PubSubContext class, which
|
||||
could also be used to build a callback dispatcher for PUBSUB scenarios.
|
||||
|
||||
* When connected to a cluster of connections, it is now possible to get a
|
||||
new Predis\Client instance for a single connection of the cluster by
|
||||
passing its alias/index to the new Predis\Client::getClientFor() method.
|
||||
|
||||
* CommandPipeline and MultiExecBlock return their instances when invoking
|
||||
commands, thus allowing method chaining in pipelines and multi-exec blocks.
|
||||
|
||||
* MultiExecBlock instances can handle the new DISCARD command.
|
||||
|
||||
* Connections now support float values for the connection_timeout parameter
|
||||
to express timeouts with a microsecond resolution.
|
||||
|
||||
* FIX: TCP connections now respect the read/write timeout parameter when
|
||||
reading the payload of server responses. Previously, stream_get_contents()
|
||||
was being used internally to read data from a connection but it looks like
|
||||
PHP does not honour the specified timeout for socket streams when inside
|
||||
this function.
|
||||
|
||||
* FIX: The GET parameter for the SORT command now accepts also multiple key
|
||||
patterns by passing an array of strings. (ISSUE #1).
|
||||
|
||||
* FIX: Replies to the DEL command return the number of elements deleted by
|
||||
the server and not 0 or 1 interpreted as a boolean response. (ISSUE #4).
|
||||
|
||||
v0.5.1 (2010-01-23)
|
||||
* RPOPLPUSH has been changed from bulk command to inline command in Redis
|
||||
1.2.1, so ListPopLastPushHead now extends InlineCommand. The old RPOPLPUSH
|
||||
behavior is still available via the ListPopLastPushHeadBulk class so that
|
||||
you can override the server profile if you need the old (and uncorrect)
|
||||
behaviour when connecting to a Redis 1.2.0 instance.
|
||||
|
||||
* Added missing support for BGREWRITEAOF for Redis >= 1.2.0
|
||||
|
||||
* Implemented a factory method for the RedisServerProfile class to ease the
|
||||
creation of new server profile instances based on a version string.
|
||||
|
||||
v0.5.0 (2010-01-09)
|
||||
* First versioned release of Predis
|
||||
@@ -0,0 +1,53 @@
|
||||
# Namespaces, interfaces and classes renamed in Predis v0.8 #
|
||||
____________________________________________
|
||||
|
||||
Some namespaces, interfaces and classes in Predis v0.8 have been renamed to follow a common rule inspired
|
||||
by the naming conventions adopted by the Symfony2 project. This is a list of all the changes:
|
||||
|
||||
### Renamed namespaces ###
|
||||
|
||||
- `Predis\Network` => `Predis\Connection`
|
||||
- `Predis\Profiles` => `Predis\Profile`
|
||||
- `Predis\Iterators` => `Predis\Iterator`
|
||||
- `Predis\Options` => `Predis\Option`
|
||||
- `Predis\Commands` => `Predis\Command`
|
||||
- `Predis\Commands\Processors` => `Predis\Command\Processor`
|
||||
|
||||
### Renamed interfaces ###
|
||||
|
||||
- `Predis\IReplyObject` => `Predis\ResponseObjectInterface`
|
||||
- `Predis\IRedisServerError` => `Predis\ResponseErrorInterface`
|
||||
- `Predis\Options\IOption` => `Predis\Option\OptionInterface`
|
||||
- `Predis\Options\IClientOptions` => `Predis\Option\ClientOptionsInterface`
|
||||
- `Predis\Profile\IServerProfile` => `Predis\Profile\ServerProfileInterface`
|
||||
- `Predis\Pipeline\IPipelineExecutor` => `Predis\Pipeline\PipelineExecutorInterface`
|
||||
- `Predis\Distribution\INodeKeyGenerator` => `Predis\Distribution\HashGeneratorInterface`
|
||||
- `Predis\Distribution\IDistributionStrategy` => `Predis\Distribution\DistributionStrategyInterface`
|
||||
- `Predis\Protocol\IProtocolProcessor` => `Predis\Protocol\ProtocolInterface`
|
||||
- `Predis\Protocol\IResponseReader` => `Predis\Protocol\ResponseReaderInterface`
|
||||
- `Predis\Protocol\IResponseHandler` => `Predis\Protocol\ResponseHandlerInterface`
|
||||
- `Predis\Protocol\ICommandSerializer` => `Predis\Protocol\CommandSerializerInterface`
|
||||
- `Predis\Protocol\IComposableProtocolProcessor` => `Predis\Protocol\ComposableProtocolInterface`
|
||||
- `Predis\Network\IConnection` => `Predis\Connection\ConnectionInterface`
|
||||
- `Predis\Network\IConnectionSingle` => `Predis\Connection\SingleConnectionInterface`
|
||||
- `Predis\Network\IConnectionComposable` => `Predis\Connection\ComposableConnectionInterface`
|
||||
- `Predis\Network\IConnectionCluster` => `Predis\Connection\ClusterConnectionInterface`
|
||||
- `Predis\Network\IConnectionReplication` => `Predis\Connection\ReplicationConnectionInterface`
|
||||
- `Predis\Commands\ICommand` => `Predis\Command\CommandInterface`
|
||||
- `Predis\Commands\IPrefixable` => `Predis\Command\PrefixableCommandInterface`
|
||||
- `Predis\Command\Processor\ICommandProcessor` => `Predis\Command\Processor\CommandProcessorInterface`
|
||||
- `Predis\Command\Processor\ICommandProcessorChain` => `Predis\Command\Processor\CommandProcessorChainInterface`
|
||||
- `Predis\Command\Processor\IProcessingSupport` => `Predis\Command\Processor\CommandProcessingInterface`
|
||||
|
||||
### Renamed classes ###
|
||||
|
||||
- `Predis\Commands\Command` => `Predis\Command\AbstractCommand`
|
||||
- `Predis\Network\ConnectionBase` => `Predis\Connection\AbstractConnection`
|
||||
|
||||
### Classes or interfaces moved to different namespaces ###
|
||||
|
||||
- `Predis\MonitorContext` => `Predis\Monitor\MonitorContext`
|
||||
- `Predis\ConnectionParameters` => `Predis\Connection\ConnectionParameters`
|
||||
- `Predis\ConnectionParametersInterface` => `Predis\Connection\ConnectionParametersInterface`
|
||||
- `Predis\ConnectionFactory` => `Predis\Connection\ConnectionFactory`
|
||||
- `Predis\ConnectionFactoryInterface` => `Predis\Connection\ConnectionFactoryInterface`
|
||||
+655
@@ -0,0 +1,655 @@
|
||||
v0.8.6 (2014-07-15)
|
||||
================================================================================
|
||||
|
||||
- Redis 2.8 is not the default server profile as there are no changes that would
|
||||
break compatibility with previous releases.
|
||||
|
||||
- Added `PFADD`, `PFCOUNT`, `PFMERGE` to the server profile for Redis 2.8 for
|
||||
handling the HyperLogLog data structure introduced in Redis 2.8.9.
|
||||
|
||||
- Added `ZLEXCOUNT`, `ZRANGEBYLEX`, `ZREMRANGEBYLEX` to the server profile for
|
||||
Redis 2.8 for handling lexicographic operations on members of sorted sets.
|
||||
|
||||
- Added support for key hash tags when using redis-cluster (Redis 3.0.0b1).
|
||||
|
||||
- __FIX__: minor tweaks to make Predis compatible with HHVM >= 2.4.0.
|
||||
|
||||
- __FIX__: responses to `INFO` are now properly parsed and will not break when
|
||||
redis sentinel is being used (ISSUE #154).
|
||||
|
||||
- __FIX__: added missing support for `INCRBYFLOAT` in cluster and replication
|
||||
configurations (ISSUE #159).
|
||||
|
||||
- __FIX__: fix parsing of the output of `CLUSTER NODES` to fetch the slots map
|
||||
from a node when redis-cluster has slaves in its configuration (ISSUE #165).
|
||||
|
||||
- __FIX__: prevent a stack overflow when iterating over large Redis collections
|
||||
using our abstraction for cursor-based iterators (ISSUE #182).
|
||||
|
||||
- __FIX__: properly discards transactions when the server immediately returns an
|
||||
error response (e.g. -OOM or -ERR on invalid arguments for a command) instead
|
||||
of a +QUEUED response (ISSUE #187).
|
||||
|
||||
- Upgraded to PHPUnit 4.* for the test suite.
|
||||
|
||||
|
||||
v0.8.5 (2014-01-16)
|
||||
================================================================================
|
||||
|
||||
- Added `2.8` in the server profiles aliases list for Redis 2.8. `2.6` is still
|
||||
the default server profile and `dev` now targets Redis 3.0.
|
||||
|
||||
- Added `SCAN`, `SSCAN`, `ZSCAN`, `HSCAN` to the server profile for Redis 2.8.
|
||||
|
||||
- Implemented PHP iterators for incremental iterations over Redis collections:
|
||||
|
||||
- keyspace (cursor-based iterator using `SCAN`)
|
||||
- sets (cursor-based iterator using `SSCAN`)
|
||||
- sorted sets (cursor-based iterator using `ZSCAN`)
|
||||
- hashes (cursor-based iterator using `HSCAN`)
|
||||
- lists (plain iterator using `LRANGE`)
|
||||
|
||||
- It is now possible to execute "raw commands" using `Predis\Command\RawCommand`
|
||||
and a variable list of command arguments. Input arguments are not filtered and
|
||||
responses are not parsed, which means arguments must follow the signature of
|
||||
the command as defined by Redis and complex responses are left untouched.
|
||||
|
||||
- URI parsing for connection parameters has been improved and has slightly less
|
||||
overhead when the number of fields in the querystring grows. New features are:
|
||||
|
||||
- Parsing does not break when value of a field contains one or more "=".
|
||||
- Repeated fieldnames using [] produce an array of values.
|
||||
- Empty or incomplete "key=value" pairs result in an empty string for "key".
|
||||
|
||||
- Various improvements and fixes to the redis-cluster connection backend:
|
||||
|
||||
- __FIX__: the `ASKING` command is sent upon -ASK redirections.
|
||||
- An updated slots-map can be fetched from nodes using the `CLUSTER NODES`
|
||||
command. By default this is a manual operation but can be enabled to get
|
||||
automatically done upon -MOVED redirections.
|
||||
- It is possible to specify a common set of connection parameters that are
|
||||
applied to connections created on the fly upon redirections to nodes not
|
||||
part of the initial pool.
|
||||
|
||||
- List of deprecated methods:
|
||||
|
||||
- `Predis\Client::multiExec()`: superseded by `Predis\Client::transaction()`
|
||||
and to be removed in the next major release.
|
||||
- `Predis\Client::pubSub()`: superseded by `Predis\Client::pubSubLoop()` and
|
||||
to be removed in the next major release. This change was needed due to the
|
||||
recently introduced `PUBSUB` command in Redis 2.8.
|
||||
|
||||
|
||||
v0.8.4 (2013-07-27)
|
||||
================================================================================
|
||||
|
||||
- Added `DUMP` and `RESTORE` to the server profile for Redis 2.6.
|
||||
|
||||
- Connection exceptions now report basic host details in their messages.
|
||||
|
||||
- Allow `Predis\Connection\PhpiredisConnection` to use a random IP when a host
|
||||
actually has several IPs (ISSUE #116).
|
||||
|
||||
- __FIX__: allow `HMSET` when using a cluster of Redis nodes with client-side
|
||||
sharding or redis-cluster (ISSUE #106).
|
||||
|
||||
- __FIX__: set `WITHSCORES` modifer for `ZRANGE`, `ZREVRANGE`, `ZRANGEBYSCORE`
|
||||
and `ZREVRANGEBYSCORE` only when the options array passed to these commands
|
||||
has `WITHSCORES` set to `true` (ISSUE #107).
|
||||
|
||||
- __FIX__: scripted commands falling back from `EVALSHA` to `EVAL` resulted in
|
||||
PHP errors when using a prefixed client (ISSUE #109).
|
||||
|
||||
- __FIX__: `Predis\PubSub\DispatcherLoop` now works properly when using key
|
||||
prefixing (ISSUE #114).
|
||||
|
||||
|
||||
v0.8.3 (2013-02-18)
|
||||
================================================================================
|
||||
|
||||
- Added `CLIENT SETNAME` and `CLIENT GETNAME` (ISSUE #102).
|
||||
|
||||
- Implemented the `Predis\Connection\PhpiredisStreamConnection` class using the
|
||||
`phpiredis` extension like `Predis\Connection\PhpiredisStreamConnection`, but
|
||||
without requiring the `socket` extension since it relies on PHP's streams.
|
||||
|
||||
- Added support for the TCP_NODELAY flag via the `tcp_nodelay` parameter for
|
||||
stream-based connections, namely `Predis\Connection\StreamConnection` and
|
||||
`Predis\Connection\PhpiredisStreamConnection` (requires PHP >= 5.4.0).
|
||||
|
||||
- Updated the aggregated connection class for redis-cluster to work with 16384
|
||||
hash slots instead of 4096 to reflect the recent change from redis unstable
|
||||
([see this commit](https://github.com/antirez/redis/commit/ebd666d)).
|
||||
|
||||
- The constructor of `Predis\Client` now accepts a callable as first argument
|
||||
returning `Predis\Connection\ConnectionInterface`. Users can create their
|
||||
own self-contained strategies to create and set up the underlying connection.
|
||||
|
||||
- Users should return `0` from `Predis\Command\ScriptedCommand::getKeysCount()`
|
||||
instead of `FALSE` to indicate that all of the arguments of a Lua script must
|
||||
be used to populate `ARGV[]`. This does not represent a breaking change.
|
||||
|
||||
- The `Predis\Helpers` class has been deprecated and it will be removed in
|
||||
future releases.
|
||||
|
||||
|
||||
v0.8.2 (2013-02-03)
|
||||
================================================================================
|
||||
|
||||
- Added `Predis\Session\SessionHandler` to make it easy to store PHP sessions
|
||||
on Redis using Predis. Please note that this class needs either PHP >= 5.4.0
|
||||
or a polyfill for PHP's `SessionHandlerInterface`.
|
||||
|
||||
- Added the ability to get the default value of a client option directly from
|
||||
`Predis\Option\ClientOption` using the `getDefault()` method by passing the
|
||||
option name or its instance.
|
||||
|
||||
- __FIX__: the standard pipeline executor was not using the response parser
|
||||
methods associated to commands to process raw responses (ISSUE #101).
|
||||
|
||||
|
||||
v0.8.1 (2013-01-19)
|
||||
================================================================================
|
||||
|
||||
- The `connections` client option can now accept a callable object returning
|
||||
an instance of `Predis\Connection\ConnectionFactoryInterface`.
|
||||
|
||||
- Client options accepting callable objects as factories now pass their actual
|
||||
instance to the callable as the second argument.
|
||||
|
||||
- `Predis\Command\Processor\KeyPrefixProcessor` can now be directly casted to
|
||||
string to obtain the current prefix, useful with string interpolation.
|
||||
|
||||
- Added an optional callable argument to `Predis\Cluster\Distribution\HashRing`
|
||||
and `Predis\Cluster\Distribution\KetamaPureRing` constructor that can be used
|
||||
to customize how the distributor should extract the connection hash when
|
||||
initializing the nodes distribution (ISSUE #36).
|
||||
|
||||
- Correctly handle `TTL` and `PTTL` returning -2 on non existing keys starting
|
||||
with Redis 2.8.
|
||||
|
||||
- __FIX__: a missing use directive in `Predis\Transaction\MultiExecContext`
|
||||
caused PHP errors when Redis did not return `+QUEUED` replies to commands
|
||||
when inside a MULTI / EXEC context.
|
||||
|
||||
- __FIX__: the `parseResponse()` method implemented for a scripted command was
|
||||
ignored when retrying to execute a Lua script by falling back to `EVAL` after
|
||||
a `-NOSCRIPT` error (ISSUE #94).
|
||||
|
||||
- __FIX__: when subclassing `Predis\Client` the `getClientFor()` method returns
|
||||
a new instance of the subclass instead of a new instance of `Predis\Client`.
|
||||
|
||||
|
||||
v0.8.0 (2012-10-23)
|
||||
================================================================================
|
||||
|
||||
- The default server profile for Redis is now `2.6`.
|
||||
|
||||
- Certain connection parameters have been renamed:
|
||||
|
||||
- `connection_async` is now `async_connect`
|
||||
- `connection_timeout` is now `timeout`
|
||||
- `connection_persistent` is now `persistent`
|
||||
|
||||
- The `throw_errors` connection parameter has been removed and replaced by the
|
||||
new `exceptions` client option since exceptions on `-ERR` replies returned by
|
||||
Redis are not generated by connection classes anymore but instead are thrown
|
||||
by the client class and other abstractions such as pipeline contexts.
|
||||
|
||||
- Added smart support for redis-cluster (Redis v3.0) in addition to the usual
|
||||
cluster implementation that uses client-side sharding.
|
||||
|
||||
- Various namespaces and classes have been renamed to follow rules inspired by
|
||||
the Symfony2 naming conventions.
|
||||
|
||||
- The second argument of the constructor of `Predis\Client` does not accept
|
||||
strings or instances of `Predis\Profile\ServerProfileInterface` anymore.
|
||||
To specify a server profile you must explicitly set `profile` in the array
|
||||
of client options.
|
||||
|
||||
- `Predis\Command\ScriptedCommand` internally relies on `EVALSHA` instead of
|
||||
`EVAL` thus avoiding to send Lua scripts bodies on each request. The client
|
||||
automatically resends the command falling back to `EVAL` when Redis returns a
|
||||
`-NOSCRIPT` error. Automatic fallback to `EVAL` does not work with pipelines,
|
||||
inside a `MULTI / EXEC` context or with plain `EVALSHA` commands.
|
||||
|
||||
- Complex responses are no more parsed by connection classes as they must be
|
||||
processed by consumer classes using the handler associated to the issued
|
||||
command. This means that executing commands directly on connections only
|
||||
returns simple Redis types, but nothing changes when using `Predis\Client`
|
||||
or the provided abstractions for pipelines and transactions.
|
||||
|
||||
- Iterators for multi-bulk replies now skip the response parsing method of the
|
||||
command that generated the response and are passed directly to user code.
|
||||
Pipeline and transaction objects still consume automatically iterators.
|
||||
|
||||
- Cluster and replication connections now extend a new common interface,
|
||||
`Predis\Connection\AggregatedConnectionInterface`.
|
||||
|
||||
- `Predis\Connection\MasterSlaveReplication` now uses an external strategy
|
||||
class to handle the logic for checking readable / writable commands and Lua
|
||||
scripts.
|
||||
|
||||
- Command pipelines have been optimized for both speed and code cleanness, but
|
||||
at the cost of bringing a breaking change in the signature of the interface
|
||||
for pipeline executors.
|
||||
|
||||
- Added a new pipeline executor that sends commands wrapped in a MULTI / EXEC
|
||||
context to make the execution atomic: if a pipeline fails at a certain point
|
||||
then the whole pipeline is discarded.
|
||||
|
||||
- The key-hashing mechanism for commands is now handled externally and is no
|
||||
more a competence of each command class. This change is neeeded to support
|
||||
both client-side sharding and Redis cluster.
|
||||
|
||||
- `Predis\Options\Option` is now abstract, see `Predis\Option\AbstractOption`.
|
||||
|
||||
|
||||
v0.7.3 (2012-06-01)
|
||||
================================================================================
|
||||
|
||||
- New commands available in the Redis v2.6 profile (dev): `BITOP`, `BITCOUNT`.
|
||||
|
||||
- When the number of keys `Predis\Commands\ScriptedCommand` is negative, Predis
|
||||
will count from the end of the arguments list to calculate the actual number
|
||||
of keys that will be interpreted as elements for `KEYS` by the underlying
|
||||
`EVAL` command.
|
||||
|
||||
- __FIX__: `examples\CustomDistributionStrategy.php` had a mistyped constructor
|
||||
call and produced a bad distribution due to an error as pointed in ISSUE #63.
|
||||
This bug is limited to the above mentioned example and does not affect the
|
||||
classes implemented in the `Predis\Distribution` namespace.
|
||||
|
||||
- __FIX__: `Predis\Commands\ServerEvalSHA::getScriptHash()` was calculating the
|
||||
hash while it just needs to return the first argument of the command.
|
||||
|
||||
- __FIX__: `Predis\Autoloader` has been modified to allow cascading autoloaders
|
||||
for the `Predis` namespace.
|
||||
|
||||
|
||||
v0.7.2 (2012-04-01)
|
||||
================================================================================
|
||||
|
||||
- Added `2.6` in the server profiles aliases list for the upcoming Redis 2.6.
|
||||
`2.4` is still the default server profile. `dev` now targets Redis 2.8.
|
||||
|
||||
- Connection instances can be serialized and unserialized using `serialize()`
|
||||
and `unserialize()`. This is handy in certain scenarios such as client-side
|
||||
clustering or replication to lower the overhead of initializing a connection
|
||||
object with many sub-connections since unserializing them can be up to 5x
|
||||
times faster.
|
||||
|
||||
- Reworked the default autoloader to make it faster. It is also possible to
|
||||
prepend it in PHP's autoload stack.
|
||||
|
||||
- __FIX__: fixed parsing of the payload returned by `MONITOR` with Redis 2.6.
|
||||
|
||||
|
||||
v0.7.1 (2011-12-27)
|
||||
================================================================================
|
||||
|
||||
- The PEAR channel on PearHub has been deprecated in favour of `pear.nrk.io`.
|
||||
|
||||
- Miscellaneous minor fixes.
|
||||
|
||||
- Added transparent support for master / slave replication configurations where
|
||||
write operations are performed on the master server and read operations are
|
||||
routed to one of the slaves. Please refer to ISSUE #21 for a bit of history
|
||||
and more details about replication support in Predis.
|
||||
|
||||
- The `profile` client option now accepts a callable object used to initialize
|
||||
a new instance of `Predis\Profiles\IServerProfile`.
|
||||
|
||||
- Exposed a method for MULTI / EXEC contexts that adds the ability to execute
|
||||
instances of Redis commands against transaction objects.
|
||||
|
||||
|
||||
v0.7.0 (2011-12-11)
|
||||
================================================================================
|
||||
|
||||
- Predis now adheres to the PSR-0 standard which means that there is no more a
|
||||
single file holding all the classes of the library, but multiple files (one
|
||||
for each class). You can use any PSR-0 compatible autoloader to load Predis
|
||||
or just leverage the default one shipped with the library by requiring the
|
||||
`Predis/Autoloader.php` and call `Predis\Autoloader::register()`.
|
||||
|
||||
- The default server profile for Redis is now 2.4. The `dev` profile supports
|
||||
all the features of Redis 2.6 (currently unstable) such as Lua scripting.
|
||||
|
||||
- Support for long aliases (method names) for Redis commands has been dropped.
|
||||
|
||||
- Redis 1.0 is no more supported. From now on Predis will use only the unified
|
||||
protocol to serialize commands.
|
||||
|
||||
- It is possible to prefix keys transparently on a client-level basis with the
|
||||
new `prefix` client option.
|
||||
|
||||
- An external connection factory is used to initialize new connection instances
|
||||
and developers can now register their own connection classes using the new
|
||||
`connections` client option.
|
||||
|
||||
- It is possible to connect locally to Redis using UNIX domain sockets. Just
|
||||
use `unix:///path/to/redis.sock` or a named array just like in the following
|
||||
example: `array('scheme' => 'unix', 'path' => '/path/to/redis.sock');`.
|
||||
|
||||
- If the `phpiredis` extension is loaded by PHP, it is now possible to use an
|
||||
alternative connection class that leverages it to make Predis faster on many
|
||||
cases, especially when dealing with big multibulk replies, with the the only
|
||||
downside that persistent connections are not supported. Please refer to the
|
||||
documentation to see how to activate this class using the new `connections`
|
||||
client option.
|
||||
|
||||
- Predis is capable to talk with Webdis, albeit with some limitations such as
|
||||
the lack of pipelining and transactions, just by using the `http` scheme in
|
||||
in the connection parameters. All is needed is PHP with the `curl` and the
|
||||
`phpiredis` extensions loaded.
|
||||
|
||||
- Way too many changes in the public API to make a list here, we just tried to
|
||||
make all the Redis commands compatible with previous releases of v0.6 so that
|
||||
you do not have to worry if you are simply using Predis as a client. Probably
|
||||
the only breaking changes that should be mentioned here are:
|
||||
|
||||
- `throw_on_error` has been renamed to `throw_errors` and it is a connection
|
||||
parameter instead of a client option, along with `iterable_multibulk`.
|
||||
|
||||
- `key_distribution` has been removed from the client options. To customize
|
||||
the distribution strategy you must provide a callable object to the new
|
||||
`cluster` client option to configure and then return a new instance of
|
||||
`Predis\Network\IConnectionCluster`.
|
||||
|
||||
- `Predis\Client::create()` has been removed. Just use the constructor to set
|
||||
up a new instance of `Predis\Client`.
|
||||
|
||||
- `Predis\Client::pipelineSafe()` was deprecated in Predis v0.6.1 and now has
|
||||
finally removed. Use `Predis\Client::pipeline(array('safe' => true))`.
|
||||
|
||||
- `Predis\Client::rawCommand()` has been removed due to inconsistencies with
|
||||
the underlying connection abstractions. You can still get the raw resource
|
||||
out of a connection with `Predis\Network\IConnectionSingle::getResource()`
|
||||
so that you can talk directly with Redis.
|
||||
|
||||
- The `Predis\MultiBulkCommand` class has been merged into `Predis\Command` and
|
||||
thus removed. Serialization of commands is now a competence of connections.
|
||||
|
||||
- The `Predis\IConnection` interface has been splitted into two new interfaces:
|
||||
`Predis\Network\IConnectionSingle` and `Predis\Network\IConnectionCluster`.
|
||||
|
||||
- The constructor of `Predis\Client` now accepts more type of arguments such as
|
||||
instances of `Predis\IConnectionParameters` and `Predis\Network\IConnection`.
|
||||
|
||||
|
||||
v0.6.6 (2011-04-01)
|
||||
================================================================================
|
||||
|
||||
- Switched to Redis 2.2 as the default server profile (there are no changes
|
||||
that would break compatibility with previous releases). Long command names
|
||||
are no more supported by default but if you need them you can still require
|
||||
`Predis_Compatibility.php` to avoid breaking compatibility.
|
||||
|
||||
- Added a `VERSION` constant to `Predis\Client`.
|
||||
|
||||
- Some performance improvements for multibulk replies (parsing them is about
|
||||
16% faster than the previous version). A few core classes have been heavily
|
||||
optimized to reduce overhead when creating new instances.
|
||||
|
||||
- Predis now uses by default a new protocol reader, more lightweight and
|
||||
faster than the default handler-based one. Users can revert to the old
|
||||
protocol reader with the `reader` client option set to `composable`.
|
||||
This client option can also accept custom reader classes implementing the
|
||||
new `Predis\IResponseReader` interface.
|
||||
|
||||
- Added support for connecting to Redis using UNIX domain sockets (ISSUE #25).
|
||||
|
||||
- The `read_write_timeout` connection parameter can now be set to 0 or false
|
||||
to disable read and write timeouts on connections. The old behaviour of -1
|
||||
is still intact.
|
||||
|
||||
- `ZUNIONSTORE` and `ZINTERSTORE` can accept an array to specify a list of the
|
||||
source keys to be used to populate the destination key.
|
||||
|
||||
- `MGET`, `SINTER`, `SUNION` and `SDIFF` can accept an array to specify a list
|
||||
of keys. `SINTERSTORE`, `SUNIONSTORE` and `SDIFFSTORE` can also accept an
|
||||
array to specify the list of source keys.
|
||||
|
||||
- `SUBSCRIBE` and `PSUBSCRIBE` can accept a list of channels for subscription.
|
||||
|
||||
- __FIX__: some client-side clean-ups for `MULTI/EXEC` were handled incorrectly
|
||||
in a couple of corner cases (ISSUE #27).
|
||||
|
||||
|
||||
v0.6.5 (2011-02-12)
|
||||
================================================================================
|
||||
|
||||
- __FIX__: due to an untested internal change introduced in v0.6.4, a wrong
|
||||
handling of bulk reads of zero-length values was producing protocol
|
||||
desynchronization errors (ISSUE #20).
|
||||
|
||||
|
||||
v0.6.4 (2011-02-12)
|
||||
================================================================================
|
||||
|
||||
- Various performance improvements (15% ~ 25%) especially when dealing with
|
||||
long multibulk replies or when using clustered connections.
|
||||
|
||||
- Added the `on_retry` option to `Predis\MultiExecBlock` that can be used to
|
||||
specify an external callback (or any callable object) that gets invoked
|
||||
whenever a transaction is aborted by the server.
|
||||
|
||||
- Added inline (p)subscribtion via options when initializing an instance of
|
||||
`Predis\PubSubContext`.
|
||||
|
||||
|
||||
v0.6.3 (2011-01-01)
|
||||
================================================================================
|
||||
|
||||
- New commands available in the Redis v2.2 profile (dev):
|
||||
- Strings: `SETRANGE`, `GETRANGE`, `SETBIT`, `GETBIT`
|
||||
- Lists : `BRPOPLPUSH`
|
||||
|
||||
- The abstraction for `MULTI/EXEC` transactions has been dramatically improved
|
||||
by providing support for check-and-set (CAS) operations when using Redis >=
|
||||
2.2. Aborted transactions can also be optionally replayed in automatic up
|
||||
to a user-defined number of times, after which a `Predis\AbortedMultiExec`
|
||||
exception is thrown.
|
||||
|
||||
|
||||
v0.6.2 (2010-11-28)
|
||||
================================================================================
|
||||
|
||||
- Minor internal improvements and clean ups.
|
||||
|
||||
- New commands available in the Redis v2.2 profile (dev):
|
||||
- Strings: `STRLEN`
|
||||
- Lists : `LINSERT`, `RPUSHX`, `LPUSHX`
|
||||
- ZSets : `ZREVRANGEBYSCORE`
|
||||
- Misc. : `PERSIST`
|
||||
|
||||
- WATCH also accepts a single array parameter with the keys that should be
|
||||
monitored during a transaction.
|
||||
|
||||
- Improved the behaviour of `Predis\MultiExecBlock` in certain corner cases.
|
||||
|
||||
- Improved parameters checking for the SORT command.
|
||||
|
||||
- __FIX__: the `STORE` parameter for the `SORT` command didn't work correctly
|
||||
when using `0` as the target key (ISSUE #13).
|
||||
|
||||
- __FIX__: the methods for `UNWATCH` and `DISCARD` do not break anymore method
|
||||
chaining with `Predis\MultiExecBlock`.
|
||||
|
||||
|
||||
v0.6.1 (2010-07-11)
|
||||
================================================================================
|
||||
|
||||
- Minor internal improvements and clean ups.
|
||||
|
||||
- New commands available in the Redis v2.2 profile (dev):
|
||||
- Misc. : `WATCH`, `UNWATCH`
|
||||
|
||||
- Optional modifiers for `ZRANGE`, `ZREVRANGE` and `ZRANGEBYSCORE` queries are
|
||||
supported using an associative array passed as the last argument of their
|
||||
respective methods.
|
||||
|
||||
- The `LIMIT` modifier for `ZRANGEBYSCORE` can be specified using either:
|
||||
- an indexed array: `array($offset, $count)`
|
||||
- an associative array: `array('offset' => $offset, 'count' => $count)`
|
||||
|
||||
- The method `Predis\Client::__construct()` now accepts also instances of
|
||||
`Predis\ConnectionParameters`.
|
||||
|
||||
- `Predis\MultiExecBlock` and `Predis\PubSubContext` now throw an exception
|
||||
when trying to create their instances using a profile that does not
|
||||
support the required Redis commands or when the client is connected to
|
||||
a cluster of connections.
|
||||
|
||||
- Various improvements to `Predis\MultiExecBlock`:
|
||||
- fixes and more consistent behaviour across various usage cases.
|
||||
- support for `WATCH` and `UNWATCH` when using the current development
|
||||
profile (Redis v2.2) and aborted transactions.
|
||||
|
||||
- New signature for `Predis\Client::multiExec()` which is now able to accept
|
||||
an array of options for the underlying instance of `Predis\MultiExecBlock`.
|
||||
Backwards compatibility with previous releases of Predis is ensured.
|
||||
|
||||
- New signature for `Predis\Client::pipeline()` which is now able to accept
|
||||
an array of options for the underlying instance of Predis\CommandPipeline.
|
||||
Backwards compatibility with previous releases of Predis is ensured.
|
||||
The method `Predis\Client::pipelineSafe()` is to be considered deprecated.
|
||||
|
||||
- __FIX__: The `WEIGHT` modifier for `ZUNIONSTORE` and `ZINTERSTORE` was
|
||||
handled incorrectly with more than two weights specified.
|
||||
|
||||
|
||||
v0.6.0 (2010-05-24)
|
||||
================================================================================
|
||||
|
||||
- Switched to the new multi-bulk request protocol for all of the commands
|
||||
in the Redis 1.2 and Redis 2.0 profiles. Inline and bulk requests are now
|
||||
deprecated as they will be removed in future releases of Redis.
|
||||
|
||||
- The default server profile is `2.0` (targeting Redis 2.0.x). If you are
|
||||
using older versions of Redis, it is highly recommended that you specify
|
||||
which server profile the client should use (e.g. `1.2` when connecting
|
||||
to instances of Redis 1.2.x).
|
||||
|
||||
- Support for Redis 1.0 is now optional and it is provided by requiring
|
||||
'Predis_Compatibility.php' before creating an instance of `Predis\Client`.
|
||||
|
||||
- New commands added to the Redis 2.0 profile since Predis 0.5.1:
|
||||
- Strings: `SETEX`, `APPEND`, `SUBSTR`
|
||||
- ZSets : `ZCOUNT`, `ZRANK`, `ZUNIONSTORE`, `ZINTERSTORE`, `ZREMBYRANK`,
|
||||
`ZREVRANK`
|
||||
- Hashes : `HSET`, `HSETNX`, `HMSET`, `HINCRBY`, `HGET`, `HMGET`, `HDEL`,
|
||||
`HEXISTS`, `HLEN`, `HKEYS`, `HVALS`, `HGETALL`
|
||||
- PubSub : `PUBLISH`, `SUBSCRIBE`, `UNSUBSCRIBE`
|
||||
- Misc. : `DISCARD`, `CONFIG`
|
||||
|
||||
- Introduced client-level options with the new `Predis\ClientOptions` class.
|
||||
Options can be passed to the constructor of `Predis\Client` in its second
|
||||
argument as an array or an instance of `Predis\ClientOptions`. For brevity's
|
||||
sake and compatibility with older versions, the constructor still accepts
|
||||
an instance of `Predis\RedisServerProfile` in its second argument. The
|
||||
currently supported client options are:
|
||||
|
||||
- `profile` [default: `2.0` as of Predis 0.6.0]: specifies which server
|
||||
profile to use when connecting to Redis. This option accepts an instance
|
||||
of `Predis\RedisServerProfile` or a string that indicates the version.
|
||||
|
||||
- `key_distribution` [default: `Predis\Distribution\HashRing`]: specifies
|
||||
which key distribution strategy to use to distribute keys among the
|
||||
servers that compose a cluster. This option accepts an instance of
|
||||
`Predis\Distribution\IDistributionStrategy` so that users can implement
|
||||
their own key distribution strategy. `Predis\Distribution\KetamaPureRing`
|
||||
is an alternative distribution strategy providing a pure-PHP implementation
|
||||
of the same algorithm used by libketama.
|
||||
|
||||
- `throw_on_error` [default: `TRUE`]: server errors can optionally be handled
|
||||
"silently": instead of throwing an exception, the client returns an error
|
||||
response type.
|
||||
|
||||
- `iterable_multibulk` [EXPERIMENTAL - default: `FALSE`]: in addition to the
|
||||
classic way of fetching a whole multibulk reply into an array, the client
|
||||
can now optionally stream a multibulk reply down to the user code by using
|
||||
PHP iterators. It is just a little bit slower, but it can save a lot of
|
||||
memory in certain scenarios.
|
||||
|
||||
- New parameters for connections:
|
||||
|
||||
- `alias` [default: not set]: every connection can now be identified by an
|
||||
alias that is useful to get a specific connections when connected to a
|
||||
cluster of Redis servers.
|
||||
- `weight` [default: not set]: allows to balance keys asymmetrically across
|
||||
multiple servers. This is useful when you have servers with different
|
||||
amounts of memory to distribute the load of your keys accordingly.
|
||||
- `connection_async` [default: `FALSE`]: estabilish connections to servers
|
||||
in a non-blocking way, so that the client is not blocked while the socket
|
||||
resource performs the actual connection.
|
||||
- `connection_persistent` [default: `FALSE`]: the underlying socket resource
|
||||
is left open when a script ends its lifecycle. Persistent connections can
|
||||
lead to unpredictable or strange behaviours, so they should be used with
|
||||
extreme care.
|
||||
|
||||
- Introduced the `Predis\Pipeline\IPipelineExecutor` interface. Classes that
|
||||
implements this interface are used internally by the `Predis\CommandPipeline`
|
||||
class to change the behaviour of the pipeline when writing/reading commands
|
||||
from one or multiple servers. Here is the list of the default executors:
|
||||
|
||||
- `Predis\Pipeline\StandardExecutor`: exceptions generated by server errors
|
||||
might be thrown depending on the options passed to the client (see the
|
||||
`throw_on_error` client option). Instead, protocol or network errors always
|
||||
throw exceptions. This is the default executor for single and clustered
|
||||
connections and shares the same behaviour of Predis 0.5.x.
|
||||
- `Predis\Pipeline\SafeExecutor`: exceptions generated by server, protocol
|
||||
or network errors are not thrown but returned in the response array as
|
||||
instances of `Predis\ResponseError` or `Predis\CommunicationException`.
|
||||
- `Predis\Pipeline\SafeClusterExecutor`: this executor shares the same
|
||||
behaviour of `Predis\Pipeline\SafeExecutor` but it is geared towards
|
||||
clustered connections.
|
||||
|
||||
- Support for PUB/SUB is handled by the new `Predis\PubSubContext` class, which
|
||||
could also be used to build a callback dispatcher for PUB/SUB scenarios.
|
||||
|
||||
- When connected to a cluster of connections, it is now possible to get a
|
||||
new `Predis\Client` instance for a single connection of the cluster by
|
||||
passing its alias/index to the new `Predis\Client::getClientFor()` method.
|
||||
|
||||
- `Predis\CommandPipeline` and `Predis\MultiExecBlock` return their instances
|
||||
when invokink commands, thus allowing method chaining in pipelines and
|
||||
multi-exec blocks.
|
||||
|
||||
- `Predis\MultiExecBlock` can handle the new `DISCARD` command.
|
||||
|
||||
- Connections now support float values for the `connection_timeout` parameter
|
||||
to express timeouts with a microsecond resolution.
|
||||
|
||||
- __FIX__: TCP connections now respect the read/write timeout parameter when
|
||||
reading the payload of server responses. Previously, `stream_get_contents()`
|
||||
was being used internally to read data from a connection but it looks like
|
||||
PHP does not honour the specified timeout for socket streams when inside
|
||||
this function.
|
||||
|
||||
- __FIX__: The `GET` parameter for the `SORT` command now accepts also multiple
|
||||
key patterns by passing an array of strings. (ISSUE #1).
|
||||
|
||||
* __FIX__: Replies to the `DEL` command return the number of elements deleted
|
||||
by the server and not 0 or 1 interpreted as a boolean response. (ISSUE #4).
|
||||
|
||||
|
||||
v0.5.1 (2010-01-23)
|
||||
================================================================================
|
||||
|
||||
* `RPOPLPUSH` has been changed from bulk command to inline command in Redis
|
||||
1.2.1, so `ListPopLastPushHead` now extends `InlineCommand`. The old behavior
|
||||
is still available via the `ListPopLastPushHeadBulk` class so that you can
|
||||
override the server profile if you need the old (and uncorrect) behaviour
|
||||
when connecting to a Redis 1.2.0 instance.
|
||||
|
||||
* Added missing support for `BGREWRITEAOF` for Redis >= 1.2.0.
|
||||
|
||||
* Implemented a factory method for the `RedisServerProfile` class to ease the
|
||||
creation of new server profile instances based on a version string.
|
||||
|
||||
|
||||
v0.5.0 (2010-01-09)
|
||||
================================================================================
|
||||
* First versioned release of Predis
|
||||
@@ -0,0 +1,44 @@
|
||||
## Filing bug reports ##
|
||||
|
||||
Bugs or feature requests can be posted on the [GitHub issues](http://github.com/nrk/predis/issues)
|
||||
section of the project.
|
||||
|
||||
When reporting bugs, in addition to the obvious description of your issue you __must__ always provide
|
||||
some essential information about your environment such as:
|
||||
|
||||
1. version of Predis (check the `VERSION` file or the `Predis\Client::VERSION` constant).
|
||||
2. version of Redis (check `redis_version` returned by [`INFO`](http://redis.io/commands/info)).
|
||||
3. version of PHP.
|
||||
4. name and version of the operating system.
|
||||
5. when possible, a small snippet of code that reproduces the issue.
|
||||
|
||||
__Think about it__: we do not have a crystal ball and cannot predict things or peer into the unknown
|
||||
so please provide as much details as possible to help us isolating issues and fix them.
|
||||
|
||||
__Never__ use GitHub issues to post generic questions about Predis! When you have questions about
|
||||
how Predis works or how it can be used, please just hop me an email and I will get back to you as
|
||||
soon as possible.
|
||||
|
||||
|
||||
## Contributing code ##
|
||||
|
||||
If you want to work on Predis, it is highly recommended that you first run the test suite in order
|
||||
to check that everything is OK and report strange behaviours or bugs. When modifying Predis please
|
||||
make sure that no warnings or notices are emitted by PHP running the interpreter in your development
|
||||
environment with the `error_reporting` variable set to `E_ALL | E_STRICT`.
|
||||
|
||||
The recommended way to contribute to Predis is to fork the project on GitHub, create topic branches
|
||||
on your newly created repository to fix bugs or add new features (possibly with tests covering your
|
||||
modifications) and then open a pull request with a description of the applied changes. Obviously you
|
||||
can use any other Git hosting provider of your preference.
|
||||
|
||||
We always aim for consistency in our code base so you should follow basic coding rules as defined by
|
||||
[PSR-1](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md)
|
||||
and [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)
|
||||
and stick with the conventions used in Predis to name classes and interfaces. Indentation should be
|
||||
done with 4 spaces and code should be wrapped at 100 columns (please try to stay within this limit
|
||||
even if the above mentioned official coding guidelines set the soft limit to 120 columns).
|
||||
|
||||
Please follow these [commit guidelines](http://git-scm.com/book/ch5-2.html#Commit-Guidelines) when
|
||||
committing your code to Git and always write a meaningful (not necessarily extended) description of
|
||||
your changes before opening pull requests.
|
||||
@@ -0,0 +1,169 @@
|
||||
# Some frequently asked questions about Predis #
|
||||
________________________________________________
|
||||
|
||||
### What is the point of Predis? ###
|
||||
|
||||
The main point of Predis is about offering a highly customizable and extensible client for Redis,
|
||||
that can be easily extended by developers while still being reasonabily fast. With Predis you can
|
||||
swap almost any class with your own custom implementation: you can have custom connection classes,
|
||||
new distribution strategies for client-side sharding, or handlers to replace or add Redis commands.
|
||||
All of this can be achieved without messing with the source code of the library and directly in your
|
||||
own application. Given the fast pace at which Redis is developed and adds new features, this can be
|
||||
a great asset since it allows developers to add new and still missing features or commands or change
|
||||
the standard behaviour of the library without the need to break dependencies in production code (at
|
||||
least to some degree).
|
||||
|
||||
### Does Predis support UNIX domain sockets and persistent connections? ###
|
||||
|
||||
Yes. Obviously persistent connections actually work only when using PHP configured as a persistent
|
||||
process reused by the web server (see [PHP-FPM](http://php-fpm.org)).
|
||||
|
||||
### Does Predis support transparent (de)serialization of values? ###
|
||||
|
||||
No and it will not ever do that by default. The reason behind this decision is that serialization is
|
||||
usually something that developers prefer to customize depending on their needs and can not be easily
|
||||
generalized when using Redis because of the many possible access patterns for your data. This does
|
||||
not mean that it is impossible to have such a feature since you can leverage the extensibility of
|
||||
this library to define your own serialization-aware commands. You can find more details about how to
|
||||
do that [on this issue](http://github.com/nrk/predis/issues/29#issuecomment-1202624).
|
||||
|
||||
### How can I force Predis to connect to Redis before sending any command? ###
|
||||
|
||||
Explicitly connecting to Redis is usually not needed since the client initializes connections lazily
|
||||
only when they are needed. Admittedly, this behavior can be inconvenient in certain scenarios when
|
||||
you absolutely need to perform an upfront check to determine if the server is up and running and
|
||||
eventually catch exceptions on failures. Forcing the client to open the underlying connection can be
|
||||
done by invoking `Predis\Client::connect()`:
|
||||
|
||||
```php
|
||||
$client = new Predis\Client();
|
||||
|
||||
try {
|
||||
$client->connect();
|
||||
} catch (Predis\Connection\ConnectionException $exception) {
|
||||
// We could not connect to Redis! Your handling code goes here.
|
||||
}
|
||||
|
||||
$client->info();
|
||||
```
|
||||
|
||||
### How Predis abstracts Redis commands? ###
|
||||
|
||||
The approach used to implement Redis commands is quite simple: by default each command follows the
|
||||
same signature as defined on the [Redis documentation](http://redis.io/commands) which makes things
|
||||
pretty easy if you already know how Redis works or you need to look up how to use certain commands.
|
||||
Alternatively, variadic commands can accept an array for keys or values (depending on the command)
|
||||
instead of a list of arguments. Commands such as [`RPUSH`](http://redis.io/commands/rpush) and
|
||||
[`HMSET`](http://redis.io/commands/hmset) are great examples:
|
||||
|
||||
```php
|
||||
$client->rpush('my:list', 'value1', 'value2', 'value3'); // plain method arguments
|
||||
$client->rpush('my:list', ['value1', 'value2', 'value3']); // single argument array
|
||||
|
||||
$client->hmset('my:hash', 'field1', 'value1', 'field2', 'value2'); // plain method arguments
|
||||
$client->hmset('my:hash', ['field1'=>'value1', 'field2'=>'value2']); // single named array
|
||||
```
|
||||
|
||||
An exception to this rule is [`SORT`](http://redis.io/commands/sort) for which modifiers are passed
|
||||
[using a named array](tests/Predis/Command/KeySortTest.php#L54-L75).
|
||||
|
||||
|
||||
# Speaking about performances... #
|
||||
_________________________________________________
|
||||
|
||||
|
||||
### Predis is a pure-PHP implementation: it can not be fast enough! ###
|
||||
|
||||
It really depends, but most of the times the answer is: _yes, it is fast enough_. I will give you a
|
||||
couple of easy numbers with a simple test that uses a single client and is executed by PHP 5.5.6
|
||||
against a local instance of Redis 2.8 that runs under Ubuntu 13.10 on a Intel Q6600:
|
||||
|
||||
```
|
||||
21000 SET/sec using 12 bytes for both key and value.
|
||||
21000 GET/sec while retrieving the very same values.
|
||||
0.130 seconds to fetch 30000 keys using _KEYS *_.
|
||||
```
|
||||
|
||||
How does it compare with [__phpredis__](http://github.com/nicolasff/phpredis), a nice C extension
|
||||
providing an efficient client for Redis?
|
||||
|
||||
```
|
||||
30100 SET/sec using 12 bytes for both key and value
|
||||
29400 GET/sec while retrieving the very same values
|
||||
0.035 seconds to fetch 30000 keys using "KEYS *"".
|
||||
```
|
||||
|
||||
Wow __phpredis__ seems much faster! Well, we are comparing a C extension with a pure-PHP library so
|
||||
lower numbers are quite expected but there is a fundamental flaw in them: is this really how you are
|
||||
going to use Redis in your application? Are you really going to send thousands of commands using a
|
||||
for-loop on each page request using a single client instance? If so... well I guess you are probably
|
||||
doing something wrong. Also, if you need to `SET` or `GET` multiple keys you should definitely use
|
||||
commands such as `MSET` and `MGET`. You can also use pipelining to get more performances when this
|
||||
technique can be used.
|
||||
|
||||
There is one more thing: we have tested the overhead of Predis by connecting on a localhost instance
|
||||
of Redis but how these numbers change when we hit the physical network by connecting to remote Redis
|
||||
instances?
|
||||
|
||||
```
|
||||
Using Predis:
|
||||
3200 SET/sec using 12 bytes for both key and value
|
||||
3200 GET/sec while retrieving the very same values
|
||||
0.132 seconds to fetch 30000 keys using "KEYS *".
|
||||
|
||||
Using phpredis:
|
||||
3500 SET/sec using 12 bytes for both key and value
|
||||
3500 GET/sec while retrieving the very same values
|
||||
0.045 seconds to fetch 30000 keys using "KEYS *".
|
||||
```
|
||||
|
||||
There you go, you get almost the same average numbers and the reason is simple: network latency is a
|
||||
real performance killer and you cannot do (almost) anything about that. As a disclaimer, remember
|
||||
that we are measuring the overhead of client libraries implementations and the effects of network
|
||||
round-trip times, so we are not really measuring how fast Redis is. Redis shines best with thousands
|
||||
of concurrent clients doing requests! Also, actual performances should be measured according to how
|
||||
your application will use Redis.
|
||||
|
||||
### I am convinced, but performances for multi-bulk responses are still worse ###
|
||||
|
||||
Fair enough, but there is an option available if you need even more speed and consists on installing
|
||||
__[phpiredis](http://github.com/nrk/phpiredis)__ (note the additional _i_ in the name) and let the
|
||||
client use it. __phpiredis__ is another C extension that wraps __hiredis__ (the official C client
|
||||
library for Redis) with a thin layer exposing its features to PHP. You can then choose between two
|
||||
different connection classes:
|
||||
|
||||
- `Predis\Connection\PhpiredisStreamConnection` (using native PHP streams).
|
||||
- `Predis\Connection\PhpiredisConnection` (requires `ext-socket`).
|
||||
|
||||
You will now get the benefits of a faster protocol serializer and parser just by adding a couple of
|
||||
lines of code:
|
||||
|
||||
```php
|
||||
$client = new Predis\Client('tcp://127.0.0.1', array(
|
||||
'connections' => array(
|
||||
'tcp' => 'Predis\Connection\PhpiredisStreamConnection',
|
||||
'unix' => 'Predis\Connection\PhpiredisConnection',
|
||||
),
|
||||
));
|
||||
```
|
||||
|
||||
Dead simple. Nothing changes in the way you use the library in your application. So how fast is it
|
||||
our basic benchmark script now? There are not much improvements for inline or short bulk responses
|
||||
like the ones returned by `SET` and `GET`, but the speed for parsing multi-bulk responses is now on
|
||||
par with phpredis:
|
||||
|
||||
```
|
||||
Fatching 30000 keys with _KEYS *_ using Predis paired with phpiredis::
|
||||
|
||||
0.035 seconds from a local Redis instance
|
||||
0.047 seconds from a remote Redis instance
|
||||
```
|
||||
|
||||
### If I need an extension to get better performances, why not using phpredis? ###
|
||||
|
||||
Good question. Generically speaking if you need absolute uber-speed using Redis on the localhost and
|
||||
you do not care about abstractions built around some Redis features such as MULTI / EXEC, or if you
|
||||
do not need any kind of extensibility or guaranteed backwards compatibility with different versions
|
||||
of Redis (Predis currently supports from 1.2 up to 2.8 and the current development version), then
|
||||
using __phpredis__ makes absolutely sense. Otherwise, Predis is perfect for the job and by adding
|
||||
__phpiredis__ you can get a nice speed bump almost for free.
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2009-2010 Daniele Alessandri
|
||||
Copyright (c) 2009-2014 Daniele Alessandri
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
|
||||
-141
@@ -1,141 +0,0 @@
|
||||
# Predis #
|
||||
|
||||
## About ##
|
||||
|
||||
Predis is a flexible and feature-complete PHP client library for the Redis key-value
|
||||
database. It currently comes in two flavors:
|
||||
|
||||
- the mainline client library, which targets PHP 5.3.x and leverages a lot of the
|
||||
features introduced in this new version of the PHP interpreter.
|
||||
- a backport to PHP 5.2.x for those who can not upgrade their environment yet
|
||||
(it admittedly has a lower priority compared to the mainline library, although we
|
||||
try to keep the two versions aligned as much as possible).
|
||||
|
||||
Please refer to the TODO file to see which issues are still pending and what is due
|
||||
to be implemented soon in Predis.
|
||||
|
||||
|
||||
## Main features ##
|
||||
|
||||
- Full support for Redis 2.0. Different versions of Redis are supported via server profiles.
|
||||
- Client-side sharding (support for consistent hashing and custom distribution strategies).
|
||||
- Command pipelining on single and multiple connections (transparent).
|
||||
- Lazy connections (connections to Redis instances are only established just in time).
|
||||
- Flexible system to define and register your own set of commands to a client instance.
|
||||
|
||||
|
||||
## Quick examples ##
|
||||
|
||||
See the [official wiki](http://wiki.github.com/nrk/predis) of the project for a more
|
||||
complete coverage of all the features available in Predis.
|
||||
|
||||
### Connecting to a local instance of Redis ###
|
||||
|
||||
You don't have to specify a tcp host and port when connecting to Redis instances
|
||||
running on the localhost on the default port:
|
||||
|
||||
$redis = new Predis\Client();
|
||||
$redis->set('library', 'predis');
|
||||
$value = $redis->get('library');
|
||||
|
||||
|
||||
### Pipelining multiple commands to a remote instance of Redis ##
|
||||
|
||||
Pipelining helps with performances when there is the need to issue many commands
|
||||
to a server in one go:
|
||||
|
||||
$redis = new Predis\Client('redis://10.0.0.1:6379/');
|
||||
$replies = $redis->pipeline(function($pipe) {
|
||||
$pipe->ping();
|
||||
$pipe->incrby('counter', 10);
|
||||
$pipe->incrby('counter', 30);
|
||||
$pipe->get('counter');
|
||||
});
|
||||
|
||||
|
||||
### Pipelining multiple commands to multiple instances of Redis (sharding) ##
|
||||
|
||||
Predis supports data sharding using consistent-hashing on keys on the client side.
|
||||
Furthermore, a pipeline can be initialized on a cluster of redis instances in the
|
||||
same exact way they are created on single connection. Sharding is still transparent
|
||||
to the user:
|
||||
|
||||
$redis = Predis\Client::create(
|
||||
array('host' => '10.0.0.1', 'port' => 6379),
|
||||
array('host' => '10.0.0.2', 'port' => 6379)
|
||||
);
|
||||
|
||||
$replies = $redis->pipeline(function($pipe) {
|
||||
for ($i = 0; $i < 1000; $i++) {
|
||||
$pipe->set("key:$i", str_pad($i, 4, '0', 0));
|
||||
$pipe->get("key:$i");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
### Definition and runtime registration of new commands on the client ###
|
||||
|
||||
Let's suppose Redis just added the support for a brand new feature associated
|
||||
with a new command. If you want to start using the above mentioned new feature
|
||||
right away without messing with Predis source code or waiting for it to find
|
||||
its way into a stable Predis release, then you can start off by creating a new
|
||||
class that matches the command type and its behaviour and then bind it to a
|
||||
client instance at runtime. Actually, it is easier done than said:
|
||||
|
||||
class BrandNewRedisCommand extends \Predis\MultiBulkCommand {
|
||||
public function getCommandId() { return 'NEWCMD'; }
|
||||
}
|
||||
|
||||
$redis = new Predis\Client();
|
||||
$redis->getProfile()->registerCommand('BrandNewRedisCommand', 'newcmd');
|
||||
$redis->newcmd();
|
||||
|
||||
|
||||
## Development ##
|
||||
|
||||
Predis is fully backed up by a test suite which tries to cover all the aspects of the
|
||||
client library and the interaction of every single command with a Redis server. If you
|
||||
want to work on Predis, it is highly recommended that you first run the test suite to
|
||||
be sure that everything is OK, and report strange behaviours or bugs.
|
||||
|
||||
The recommended way to contribute to Predis is to fork the project on GitHub, fix or
|
||||
add features on your newly created repository and then submit issues on the Predis
|
||||
issue tracker with a link to your repository. Obviously, you can use any other Git
|
||||
hosting provider of you preference. Diff patches will be accepted too, even though
|
||||
they are not the preferred way to contribute to Predis.
|
||||
|
||||
When modifying Predis please be sure that no warnings or notices are emitted by PHP
|
||||
by running the interpreter in your development environment with the "error_reporting"
|
||||
variable set to E_ALL | E_STRICT.
|
||||
|
||||
|
||||
## Dependencies ##
|
||||
|
||||
- PHP >= 5.3.0 (for the mainline client library)
|
||||
- PHP >= 5.2.6 (for the backported client library)
|
||||
- PHPUnit (needed to run the test suite)
|
||||
|
||||
## Links ##
|
||||
|
||||
### Project ###
|
||||
- [Source code](http://github.com/nrk/predis/)
|
||||
- [Wiki](http://wiki.github.com/nrk/predis/)
|
||||
- [Issue tracker](http://github.com/nrk/predis/issues)
|
||||
|
||||
### Related ###
|
||||
- [Redis](http://code.google.com/p/redis/)
|
||||
- [PHP](http://php.net/)
|
||||
- [PHPUnit](http://www.phpunit.de/)
|
||||
- [Git](http://git-scm.com/)
|
||||
|
||||
## Author ##
|
||||
|
||||
[Daniele Alessandri](mailto:suppakilla@gmail.com)
|
||||
|
||||
## Contributors ##
|
||||
|
||||
[Lorenzo Castelli](http://github.com/lcastelli)
|
||||
|
||||
## License ##
|
||||
|
||||
The code for Predis is distributed under the terms of the MIT license (see LICENSE).
|
||||
@@ -0,0 +1,329 @@
|
||||
# Predis #
|
||||
|
||||
[](https://packagist.org/packages/predis/predis)
|
||||
[](https://packagist.org/packages/predis/predis)
|
||||
|
||||
Predis is a flexible and feature-complete [Redis](http://redis.io) client library for PHP >= 5.3.
|
||||
|
||||
By default Predis does not require any additional C extension, but it can be optionally paired with
|
||||
[phpiredis](https://github.com/nrk/phpiredis) to lower the overhead of serializing and parsing the
|
||||
Redis protocol. An asynchronous implementation of the client, albeit experimental, is also available
|
||||
through [Predis\Async](https://github.com/nrk/predis-async).
|
||||
|
||||
Predis can be used with [HHVM](http://www.hhvm.com) >= 2.4.0, but there are no guarantees you will
|
||||
not run into unexpected issues (especially with the JIT compiler enabled via `Eval.Jit = true`) due
|
||||
to HHVM being still under heavy development, thus unstable and not yet 100% compatible with PHP.
|
||||
|
||||
More details about the project can be found in our [frequently asked questions](FAQ.md) section or
|
||||
on the online [wiki](https://github.com/nrk/predis/wiki).
|
||||
|
||||
|
||||
## Main features ##
|
||||
|
||||
- Wide range of Redis versions supported (from __1.2__ to __2.8__ and unstable) using profiles.
|
||||
- Clustering via client-side sharding using consistent hashing or custom distributors.
|
||||
- Smart support for [redis-cluster](http://redis.io/topics/cluster-spec) (Redis >= 3.0).
|
||||
- Support for master-slave replication configurations (write on master, read from slaves).
|
||||
- Transparent key prefixing for all Redis commands.
|
||||
- Command pipelining (works on both single and aggregate connections).
|
||||
- Abstraction for Redis transactions (Redis >= 2.0) supporting CAS operations (Redis >= 2.2).
|
||||
- Abstraction for Lua scripting (Redis >= 2.6) with automatic switching between `EVALSHA` or `EVAL`.
|
||||
- Abstraction for `SCAN`, `SSCAN`, `ZSCAN` and `HSCAN` (Redis >= 2.8) based on PHP iterators.
|
||||
- Connections to Redis are established lazily by the client upon the first command.
|
||||
- Support for both TCP/IP and UNIX domain sockets and persistent connections.
|
||||
- Support for [Webdis](http://webd.is) (both `ext-curl` and `ext-phpiredis` are needed).
|
||||
- Support for custom connection classes for providing different network or protocol backends.
|
||||
- Flexible system for defining and registering custom sets of supported commands or profiles.
|
||||
|
||||
|
||||
## How to use Predis ##
|
||||
|
||||
Predis is available on [Packagist](http://packagist.org/packages/predis/predis) which allows a quick
|
||||
installation using [Composer](http://packagist.org/about-composer). Alternatively, the library can
|
||||
be found on our [own PEAR channel](http://pear.nrk.io) for a more traditional installation via PEAR.
|
||||
Ultimately, archives of each release are [available on GitHub](https://github.com/nrk/predis/tags).
|
||||
|
||||
|
||||
### Loading the library ###
|
||||
|
||||
Predis relies on the autoloading features of PHP to load its files when needed and complies with the
|
||||
[PSR-0 standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) which makes
|
||||
it compatible with most PHP frameworks. Autoloading is handled automatically when dependencies are
|
||||
managed using Composer, but you can also leverage its own autoloader if you are going to use it in a
|
||||
project or script without any PSR-0 compliant autoloading facility:
|
||||
|
||||
```php
|
||||
// Prepend a base path if Predis is not available in your "include_path".
|
||||
require 'Predis/Autoloader.php';
|
||||
|
||||
Predis\Autoloader::register();
|
||||
```
|
||||
|
||||
It is possible to easily create a [phar](http://www.php.net/manual/en/intro.phar.php) archive from
|
||||
the repository just by launching `bin/create-phar`. The generated phar contains a stub defining an
|
||||
autoloader function for Predis, so you just need to require the phar to start using the library.
|
||||
Alternatively, it is also possible to generate one single PHP file that holds every class like older
|
||||
versions of Predis by launching `bin/create-single-file`, but this practice __is not__ encouraged.
|
||||
|
||||
|
||||
### Connecting to Redis ###
|
||||
|
||||
When not specifying any connection parameter to create a new client, Predis assumes `127.0.0.1` and
|
||||
`6379` as the default host and port and uses a connection timeout of 5 seconds:
|
||||
|
||||
```php
|
||||
$client = new Predis\Client();
|
||||
$client->set('foo', 'bar');
|
||||
$value = $client->get('foo');
|
||||
```
|
||||
|
||||
Connection parameters can be supplied either in the form of URI strings or named arrays. While the
|
||||
latter is the preferred way to supply parameters, URI strings can be useful for quick configurations
|
||||
or when parameters are read from a non-structured source:
|
||||
|
||||
```php
|
||||
// Named array of connection parameters:
|
||||
$client = new Predis\Client([
|
||||
'scheme' => 'tcp',
|
||||
'host' => '10.0.0.1',
|
||||
'port' => 6379,
|
||||
]);
|
||||
|
||||
// Same set of parameters, but using an URI string:
|
||||
$client = new Predis\Client('tcp://10.0.0.1:6379');
|
||||
```
|
||||
|
||||
When an array of connections parameters is provided, Predis automatically works in clustering mode
|
||||
using client-side sharding. Both named arrays and URI strings can be mixed for providing each node
|
||||
configuration:
|
||||
|
||||
```php
|
||||
$client = new Predis\Client([
|
||||
'tcp://10.0.0.1?alias=first-node',
|
||||
['host' => '10.0.0.2', 'alias' => 'second-node'],
|
||||
]);
|
||||
```
|
||||
|
||||
The actual list of supported connection parameters can vary depending on each connection backend so
|
||||
it is recommended to refer to their specific documentation for details.
|
||||
|
||||
|
||||
### Client configuration ###
|
||||
|
||||
Various aspects of the client can be easily configured by passing options to the second argument of
|
||||
`Predis\Client::__construct()`. Options are managed using a mini DI-alike container and their values
|
||||
are usually lazily initialized only when needed. Predis by default supports the following options:
|
||||
|
||||
- `profile`: which profile to use in order to match a specific version of Redis.
|
||||
- `prefix`: a prefix string that is automatically applied to keys found in commands.
|
||||
- `exceptions`: whether the client should throw or return responses upon Redis errors.
|
||||
- `connections`: connection backends or a connection factory to be used by the client.
|
||||
- `cluster`: which backend to use for clustering (predis, redis or custom configuration).
|
||||
- `replication`: which backend to use for replication (predis or custom configuration).
|
||||
|
||||
Users can provide custom option values, they are stored in the options container and can be accessed
|
||||
later through the library.
|
||||
|
||||
|
||||
### Aggregate connections ###
|
||||
|
||||
Predis is able to aggregate multiple connections which is the base for clustering and replication.
|
||||
By default the client implements clustering using either client-side sharding (default) or a Redis
|
||||
backed solution using [redis-cluster](http://redis.io/topics/cluster-tutorial). As for replication,
|
||||
Predis can handle single-master and multiple-slaves setups by executing read operations on slaves
|
||||
and switching to the master for write operations. The replication behaviour is fully configurable.
|
||||
|
||||
|
||||
### Command pipelines ###
|
||||
|
||||
Pipelining can help with performances when many commands need to be sent to a server by reducing the
|
||||
latency introduced by network round-trip timings. Pipelining also works with aggregate connections.
|
||||
The client can execute the pipeline inside a callable block or return a pipeline instance with the
|
||||
ability to chain commands thanks to its fluent interface:
|
||||
|
||||
```php
|
||||
// Executes a pipeline inside a given callable block:
|
||||
$responses = $client->pipeline(function ($pipe) {
|
||||
for ($i = 0; $i < 1000; $i++) {
|
||||
$pipe->set("key:$i", str_pad($i, 4, '0', 0));
|
||||
$pipe->get("key:$i");
|
||||
}
|
||||
});
|
||||
|
||||
// Returns a pipeline instance with fluent interface:
|
||||
$responses = $client->pipeline()->set('foo', 'bar')->get('foo')->execute();
|
||||
```
|
||||
|
||||
|
||||
### Transactions ###
|
||||
|
||||
The client provides an abstraction for Redis transactions based on `MULTI` and `EXEC` with a similar
|
||||
interface to command pipelines:
|
||||
|
||||
```php
|
||||
// Executes a transaction inside a given callable block:
|
||||
$responses = $client->transaction(function ($tx) {
|
||||
$tx->set('foo', 'bar');
|
||||
$tx->get('foo');
|
||||
});
|
||||
|
||||
// Returns a transaction instance with fluent interface:
|
||||
$responses = $client->transaction()->set('foo', 'bar')->get('foo')->execute();
|
||||
```
|
||||
|
||||
This abstraction can perform check-and-set operations thanks to `WATCH` and `UNWATCH` and provides
|
||||
automatic retries of transactions aborted by Redis when `WATCH`ed keys are touched. For an example
|
||||
of a transaction using CAS you can see [the following example](examples/TransactionWithCAS.php).
|
||||
|
||||
__NOTE__: the method `transaction()` is available since `v0.8.5`, older versions used `multiExec()`
|
||||
for the same purpose but it has been deprecated and will be removed in the next major release.
|
||||
|
||||
|
||||
### Customizable connection backends ###
|
||||
|
||||
Predis can use different connection backends to connect to Redis. Two of them leverage a third party
|
||||
extension such as [phpiredis](https://github.com/nrk/phpiredis) resulting in major performance gains
|
||||
especially when dealing with big multibulk responses. While one is based on PHP streams, the other
|
||||
is based on socket resources provided by `ext-socket`. Both support TCP/IP or UNIX domain sockets:
|
||||
|
||||
```php
|
||||
$client = new Predis\Client('tcp://127.0.0.1', [
|
||||
'connections' => [
|
||||
'tcp' => 'Predis\Connection\PhpiredisStreamConnection', // PHP streams
|
||||
'unix' => 'Predis\Connection\PhpiredisConnection', // ext-socket
|
||||
],
|
||||
]);
|
||||
```
|
||||
|
||||
Developers can create their own connection classes to add support for new network backends, extend
|
||||
existing ones or provide completely different implementations. Connection classes must implement
|
||||
`Predis\Connection\SingleConnectionInterface` or extend `Predis\Connection\AbstractConnection`:
|
||||
|
||||
```php
|
||||
class MyConnectionClass implements Predis\Connection\SingleConnectionInterface
|
||||
{
|
||||
// Implementation goes here...
|
||||
}
|
||||
|
||||
// Use MyConnectionClass to handle connections for the `tcp` scheme:
|
||||
$client = new Predis\Client('tcp://127.0.0.1', [
|
||||
'connections' => ['tcp' => 'MyConnectionClass'],
|
||||
]);
|
||||
```
|
||||
|
||||
For a more in-depth insight on how to create new connection backends you can refer to the actual
|
||||
implementation of the standard connection classes available in the `Predis\Connection` namespace.
|
||||
|
||||
|
||||
### Adding support for new commands ###
|
||||
|
||||
While we try to update Predis to stay up to date with all the commands available in Redis, you might
|
||||
prefer to stick with an older version of the library or provide a different way to filter arguments
|
||||
or parse responses for specific commands. To achieve that, Predis provides the ability to implement
|
||||
new command classes to define or override commands in the server profiles used by the client:
|
||||
|
||||
```php
|
||||
// Define a new command by extending Predis\Command\AbstractCommand:
|
||||
class BrandNewRedisCommand extends Predis\Command\AbstractCommand
|
||||
{
|
||||
public function getId()
|
||||
{
|
||||
return 'NEWCMD';
|
||||
}
|
||||
}
|
||||
|
||||
// Inject your command in the current profile:
|
||||
$client = new Predis\Client();
|
||||
$client->getProfile()->defineCommand('newcmd', 'BrandNewRedisCommand');
|
||||
|
||||
$response = $client->newcmd();
|
||||
```
|
||||
|
||||
|
||||
### Scriptable commands ###
|
||||
|
||||
A scriptable command is just an abstraction for [Lua scripting](http://redis.io/commands/eval) that
|
||||
aims to simplify the usage of scripting with Redis >= 2.6. Scriptable commands can be registered in
|
||||
the server profile used by the client and are accessible as if they were plain Redis commands, but
|
||||
they define a Lua script that gets transmitted to Redis for remote execution. Internally, scriptable
|
||||
commands use by default [EVALSHA](http://redis.io/commands/evalsha) and identify a Lua script by its
|
||||
SHA1 hash to save bandwidth but [EVAL](http://redis.io/commands/eval) is automatically preferred as
|
||||
a fall back when needed:
|
||||
|
||||
```php
|
||||
// Define a new scriptable command by extending Predis\Command\ScriptedCommand:
|
||||
class ListPushRandomValue extends Predis\Command\ScriptedCommand
|
||||
{
|
||||
public function getKeysCount()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public function getScript()
|
||||
{
|
||||
return <<<LUA
|
||||
math.randomseed(ARGV[1])
|
||||
local rnd = tostring(math.random())
|
||||
redis.call('lpush', KEYS[1], rnd)
|
||||
return rnd
|
||||
LUA;
|
||||
}
|
||||
}
|
||||
|
||||
// Inject your scriptable command in the current profile:
|
||||
$client = new Predis\Client();
|
||||
$client->getProfile()->defineCommand('lpushrand', 'ListPushRandomValue');
|
||||
|
||||
$response = $client->lpushrand('random_values', $seed = mt_rand());
|
||||
```
|
||||
|
||||
|
||||
## Development ##
|
||||
|
||||
|
||||
### Reporting bugs and contributing code ###
|
||||
|
||||
Contributions to Predis are highly appreciated either in the form of pull requests for new features,
|
||||
bug fixes, or just bug reports. We only ask you to adhere to a [basic set of rules](CONTRIBUTING.md)
|
||||
before submitting your changes or filing bugs on the issue tracker to make it easier for everyone to
|
||||
stay consistent while working on the project.
|
||||
|
||||
|
||||
### Test suite ###
|
||||
|
||||
__ATTENTION__: Do not ever run the test suite shipped with Predis against instances of Redis running
|
||||
in production environments or containing data you are interested in!
|
||||
|
||||
Predis has a comprehensive test suite covering every aspect of the library. This test suite performs
|
||||
integration tests against a running instance of Redis (>= 2.4.0 is required) to verify the correct
|
||||
behaviour of the implementation of each command and automatically skips commands not defined in the
|
||||
specified Redis profile. If you do not have Redis up and running, integration tests can be disabled.
|
||||
By default the test suite is configured to execute integration tests using the profile for Redis 2.8
|
||||
(which is the current stable version of Redis) but can optionally target a Redis instance built from
|
||||
the `unstable` branch by modifying `phpunit.xml` and setting `REDIS_SERVER_VERSION` to `dev` so that
|
||||
the development server profile will be used. You can refer to [the tests README](tests/README.md)
|
||||
for more detailed information about testing Predis.
|
||||
|
||||
Predis uses Travis CI for continuous integration and the history for past and current builds can be
|
||||
found [on its project page](http://travis-ci.org/nrk/predis).
|
||||
|
||||
|
||||
## Other ##
|
||||
|
||||
|
||||
### Project related links ###
|
||||
|
||||
- [Source code](https://github.com/nrk/predis)
|
||||
- [Wiki](https://wiki.github.com/nrk/predis)
|
||||
- [Issue tracker](https://github.com/nrk/predis/issues)
|
||||
- [PEAR channel](http://pear.nrk.io)
|
||||
|
||||
|
||||
### Author ###
|
||||
|
||||
- [Daniele Alessandri](mailto:suppakilla@gmail.com) ([twitter](http://twitter.com/JoL1hAHN))
|
||||
|
||||
|
||||
### License ###
|
||||
|
||||
The code for Predis is distributed under the terms of the MIT license (see [LICENSE](LICENSE)).
|
||||
@@ -1,7 +0,0 @@
|
||||
* Documentation! The README is obviously not enought to show how to use
|
||||
Predis as it does not cover all of its features.
|
||||
|
||||
* Add more tests targeting specific functions of the library.
|
||||
|
||||
* Missing tests for commands:
|
||||
PUBLISH, SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
require __DIR__.'/lib/Predis/Autoloader.php';
|
||||
|
||||
Predis\Autoloader::register();
|
||||
Executable
+271
@@ -0,0 +1,271 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// This script can be used to automatically generate a file with the scheleton
|
||||
// of a test case to test a Redis command by specifying the name of the class
|
||||
// in the Predis\Command namespace (only classes in this namespace are valid).
|
||||
// For example, to generate a test case for SET (which is represented by the
|
||||
// Predis\Command\StringSet class):
|
||||
//
|
||||
// $ ./bin/generate-command-test.php --class=StringSet
|
||||
//
|
||||
// Here is a list of optional arguments:
|
||||
//
|
||||
// --realm: each command has its own realm (commands that operate on strings,
|
||||
// lists, sets and such) but while this realm is usually inferred from the name
|
||||
// of the specified class, sometimes it can be useful to override it with a
|
||||
// custom one.
|
||||
//
|
||||
// --output: write the generated test case to the specified path instead of
|
||||
// the default one.
|
||||
//
|
||||
// --overwrite: pre-existing test files are not overwritten unless this option
|
||||
// is explicitly specified.
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
use Predis\Command\CommandInterface;
|
||||
use Predis\Command\PrefixableCommandInterface;
|
||||
|
||||
class CommandTestCaseGenerator
|
||||
{
|
||||
private $options;
|
||||
|
||||
public function __construct(Array $options)
|
||||
{
|
||||
if (!isset($options['class'])) {
|
||||
throw new RuntimeException("Missing 'class' option.");
|
||||
}
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
public static function fromCommandLine()
|
||||
{
|
||||
$parameters = array(
|
||||
'c:' => 'class:',
|
||||
'r::' => 'realm::',
|
||||
'o::' => 'output::',
|
||||
'x::' => 'overwrite::'
|
||||
);
|
||||
|
||||
$getops = getopt(implode(array_keys($parameters)), $parameters);
|
||||
|
||||
$options = array(
|
||||
'overwrite' => false,
|
||||
'tests' => __DIR__.'/../tests',
|
||||
);
|
||||
|
||||
foreach ($getops as $option => $value) {
|
||||
switch ($option) {
|
||||
case 'c':
|
||||
case 'class':
|
||||
$options['class'] = $value;
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
case 'realm':
|
||||
$options['realm'] = $value;
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
case 'output':
|
||||
$options['output'] = $value;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
case 'overwrite':
|
||||
$options['overwrite'] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($options['class'])) {
|
||||
throw new RuntimeException("Missing 'class' option.");
|
||||
}
|
||||
|
||||
$options['fqn'] = "Predis\\Command\\{$options['class']}";
|
||||
$options['path'] = "Predis/Command/{$options['class']}.php";
|
||||
|
||||
$source = __DIR__.'/../lib/'.$options['path'];
|
||||
if (!file_exists($source)) {
|
||||
throw new RuntimeException("Cannot find class file for {$options['fqn']} in $source.");
|
||||
}
|
||||
|
||||
if (!isset($options['output'])) {
|
||||
$options['output'] = sprintf("%s/%s", $options['tests'], str_replace('.php', 'Test.php', $options['path']));
|
||||
}
|
||||
|
||||
return new self($options);
|
||||
}
|
||||
|
||||
protected function getTestRealm()
|
||||
{
|
||||
if (isset($this->options['realm'])) {
|
||||
if (!$this->options['realm']) {
|
||||
throw new RuntimeException('Invalid value for realm has been sepcified (empty).');
|
||||
}
|
||||
return $this->options['realm'];
|
||||
}
|
||||
|
||||
$fqnParts = explode('\\', $this->options['fqn']);
|
||||
$class = array_pop($fqnParts);
|
||||
list($realm,) = preg_split('/([[:upper:]][[:lower:]]+)/', $class, 2, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
return strtolower($realm);
|
||||
}
|
||||
|
||||
public function generate()
|
||||
{
|
||||
$reflection = new ReflectionClass($class = $this->options['fqn']);
|
||||
|
||||
if (!$reflection->isInstantiable()) {
|
||||
throw new RuntimeException("Class $class must be instantiable, abstract classes or interfaces are not allowed.");
|
||||
}
|
||||
if (!$reflection->implementsInterface('Predis\Command\CommandInterface')) {
|
||||
throw new RuntimeException("Class $class must implement Predis\Command\CommandInterface.");
|
||||
}
|
||||
|
||||
$instance = $reflection->newInstance();
|
||||
$buffer = $this->getTestCaseBuffer($instance);
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$options = $this->options;
|
||||
if (file_exists($options['output']) && !$options['overwrite']) {
|
||||
throw new RuntimeException("File {$options['output']} already exist. Specify the --overwrite option to overwrite the existing file.");
|
||||
}
|
||||
file_put_contents($options['output'], $this->generate());
|
||||
}
|
||||
|
||||
protected function getTestCaseBuffer(CommandInterface $instance)
|
||||
{
|
||||
$id = $instance->getId();
|
||||
$fqn = get_class($instance);
|
||||
$fqnParts = explode('\\', $fqn);
|
||||
$class = array_pop($fqnParts) . "Test";
|
||||
$realm = $this->getTestRealm();
|
||||
|
||||
$buffer =<<<PHP
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @group commands
|
||||
* @group realm-$realm
|
||||
*/
|
||||
class $class extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCommand()
|
||||
{
|
||||
return '$fqn';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedId()
|
||||
{
|
||||
return '$id';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArguments()
|
||||
{
|
||||
\$this->markTestIncomplete('This test has not been implemented yet.');
|
||||
|
||||
\$arguments = array(/* add arguments */);
|
||||
\$expected = array(/* add arguments */);
|
||||
|
||||
\$command = \$this->getCommand();
|
||||
\$command->setArguments(\$arguments);
|
||||
|
||||
\$this->assertSame(\$expected, \$command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseResponse()
|
||||
{
|
||||
\$this->markTestIncomplete('This test has not been implemented yet.');
|
||||
|
||||
\$raw = null;
|
||||
\$expected = null;
|
||||
|
||||
\$command = \$this->getCommand();
|
||||
|
||||
\$this->assertSame(\$expected, \$command->parseResponse(\$raw));
|
||||
}
|
||||
|
||||
PHP;
|
||||
|
||||
if ($instance instanceof PrefixableCommandInterface) {
|
||||
$buffer .=<<<PHP
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testPrefixKeys()
|
||||
{
|
||||
\$this->markTestIncomplete('This test has not been implemented yet.');
|
||||
|
||||
\$arguments = array(/* add arguments */);
|
||||
\$expected = array(/* add arguments */);
|
||||
|
||||
\$command = \$this->getCommandWithArgumentsArray(\$arguments);
|
||||
\$command->prefixKeys('prefix:');
|
||||
|
||||
\$this->assertSame(\$expected, \$command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testPrefixKeysIgnoredOnEmptyArguments()
|
||||
{
|
||||
\$command = \$this->getCommand();
|
||||
\$command->prefixKeys('prefix:');
|
||||
|
||||
\$this->assertSame(array(), \$command->getArguments());
|
||||
}
|
||||
|
||||
PHP;
|
||||
}
|
||||
|
||||
return "$buffer}\n";
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------- //
|
||||
|
||||
require __DIR__.'/../autoload.php';
|
||||
|
||||
$generator = CommandTestCaseGenerator::fromCommandLine();
|
||||
$generator->save();
|
||||
Executable
+221
@@ -0,0 +1,221 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// In order to be able to execute this script to create a PEAR package of Predis
|
||||
// the `pear` binary must be available and executable in your $PATH.
|
||||
// The parts used to parse author and version strings are taken from Onion (used
|
||||
// by this library in the past) just to keep on relying on the package.ini file
|
||||
// to simplify things. We might consider to switch to using the PEAR classes
|
||||
// directly in the future.
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
function executeWithBackup($file, $callback)
|
||||
{
|
||||
$exception = null;
|
||||
$backup = "$file.backup";
|
||||
|
||||
copy($file, $backup);
|
||||
|
||||
try {
|
||||
call_user_func($callback, $file);
|
||||
} catch (Exception $exception) {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
unlink($file);
|
||||
rename($backup, $file);
|
||||
|
||||
if ($exception) {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
function parseAuthor($string)
|
||||
{
|
||||
$author = array();
|
||||
|
||||
if (preg_match('/^\s*(.+?)\s*(?:"(\S+)"\s*)?<(\S+)>\s*$/x', $string , $regs)) {
|
||||
if (count($regs) == 4) {
|
||||
list($orig,$name,$user,$email) = $regs;
|
||||
$author['name'] = $name;
|
||||
$author['user'] = $user;
|
||||
$author['email'] = $email;
|
||||
} elseif (count($regs) == 3) {
|
||||
list($orig,$name,$email) = $regs;
|
||||
$author['name'] = $name;
|
||||
$author['email'] = $email;
|
||||
}
|
||||
} else {
|
||||
$author['name'] = $string;
|
||||
}
|
||||
|
||||
return $author;
|
||||
}
|
||||
|
||||
function parseVersion($string)
|
||||
{
|
||||
$version_pattern = '([0-9.]+)';
|
||||
|
||||
if (preg_match("/^\s*$version_pattern\s*\$/x", $string, $regs)) {
|
||||
return array('min' => $regs[1] ?: '0.0.0');
|
||||
} elseif (preg_match("/^\s*[>=]+\s*$version_pattern\s*\$/x", $string, $regs)) {
|
||||
return array('min' => $regs[1] ?: '0.0.0');
|
||||
} elseif (preg_match("/^\s*[<=]+\s*$version_pattern\s*\$/x", $string, $regs)) {
|
||||
return array('max' => $regs[1]);
|
||||
} elseif (preg_match("/^\s*$version_pattern\s*<=>\s*$version_pattern\s*\$/x", $string, $regs)) {
|
||||
return array(
|
||||
'min' => $regs[1] ?: '0.0.0',
|
||||
'max' => $regs[2],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function addRolePath($pkg, $path, $role)
|
||||
{
|
||||
if (is_dir($path)) {
|
||||
$dirRoot = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
$dirTree = new RecursiveIteratorIterator($dirRoot, RecursiveIteratorIterator::CHILD_FIRST);
|
||||
|
||||
foreach ($dirTree as $fileinfo) {
|
||||
if ($fileinfo->isFile()) {
|
||||
addPackageFile($pkg, $fileinfo, $role, $path);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach (glob($path) as $filename) {
|
||||
addPackageFile($pkg, new SplFileInfo($filename), $role);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addPackageFile($pkg, $fileinfo, $role, $baseDir = '')
|
||||
{
|
||||
$fileNode = $pkg->contents->dir->addChild('file');
|
||||
$fileNode->addAttribute('name', $filepath = $fileinfo->getPathname());
|
||||
$fileNode->addAttribute('role', $role);
|
||||
$fileNode->addAttribute('md5sum', md5_file($filepath));
|
||||
|
||||
$installNode = $pkg->phprelease->filelist->addChild('install');
|
||||
$installNode->addAttribute('name', $filepath);
|
||||
$installNode->addAttribute('as', !$baseDir ? basename($filepath) : substr($filepath, strlen($baseDir) + 1));
|
||||
}
|
||||
|
||||
function generatePackageXml($packageINI)
|
||||
{
|
||||
$XML = <<<XML
|
||||
<?xml version="1.0"?>
|
||||
<package packagerversion="1.4.10" version="2.0"
|
||||
xmlns="http://pear.php.net/dtd/package-2.0"
|
||||
xmlns:tasks="http://pear.php.net/dtd/tasks-1.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
|
||||
http://pear.php.net/dtd/tasks-1.0.xsd
|
||||
http://pear.php.net/dtd/package-2.0
|
||||
http://pear.php.net/dtd/package-2.0.xsd" />
|
||||
XML;
|
||||
|
||||
$cfg = parse_ini_file($packageINI, true);
|
||||
$pkg = new SimpleXMLElement($XML);
|
||||
|
||||
$pkg->name = $cfg['package']['name'];
|
||||
$pkg->channel = $cfg['package']['channel'];
|
||||
$pkg->summary = $cfg['package']['desc'];
|
||||
$pkg->description = $cfg['package']['desc'];
|
||||
|
||||
$author = parseAuthor($cfg['package']['author']);
|
||||
$pkg->addChild('lead');
|
||||
$pkg->lead->name = $author['name'];
|
||||
$pkg->lead->user = $author['user'];
|
||||
$pkg->lead->email = $author['email'];
|
||||
$pkg->lead->active = 'yes';
|
||||
|
||||
$datetime = new DateTime('now');
|
||||
$pkg->date = $datetime->format('Y-m-d');
|
||||
$pkg->time = $datetime->format('H:i:s');
|
||||
|
||||
$pkg->addChild('version');
|
||||
$pkg->version->release = $cfg['package']['version'];
|
||||
$pkg->version->api = $cfg['package']['version'];
|
||||
|
||||
$pkg->addChild('stability');
|
||||
$pkg->stability->release = $cfg['package']['stability'];
|
||||
$pkg->stability->api = $cfg['package']['stability'];
|
||||
|
||||
$pkg->license = $cfg['package']['license'];
|
||||
$pkg->notes = '-';
|
||||
|
||||
$pkg->addChild('contents')->addChild('dir')->addAttribute('name', '/');
|
||||
|
||||
$pkg->addChild('dependencies')->addChild('required');
|
||||
foreach ($cfg['require'] as $required => $version) {
|
||||
$version = parseVersion($version);
|
||||
$pkg->dependencies->required->addChild($required);
|
||||
|
||||
if (isset($version['min'])) {
|
||||
$pkg->dependencies->required->$required->min = $version['min'];
|
||||
}
|
||||
if (isset($version['max'])) {
|
||||
$pkg->dependencies->required->$required->min = $version['max'];
|
||||
}
|
||||
}
|
||||
|
||||
$pkg->addChild('phprelease')->addChild('filelist');
|
||||
|
||||
$pathToRole = array(
|
||||
'doc' => 'doc', 'docs' => 'doc', 'examples' => 'doc',
|
||||
'lib' => 'php', 'src' => 'php',
|
||||
'test' => 'test', 'tests' => 'test',
|
||||
);
|
||||
|
||||
foreach (array_merge($pathToRole, $cfg['roles'] ?: array()) as $path => $role) {
|
||||
addRolePath($pkg, $path, $role);
|
||||
}
|
||||
|
||||
return $pkg;
|
||||
}
|
||||
|
||||
function savePackageXml($xml)
|
||||
{
|
||||
$dom = new DOMDocument("1.0");
|
||||
$dom->preserveWhiteSpace = false;
|
||||
$dom->formatOutput = true;
|
||||
$dom->loadXML($xml->asXML());
|
||||
|
||||
file_put_contents('package.xml', $dom->saveXML());
|
||||
}
|
||||
|
||||
function buildPackage()
|
||||
{
|
||||
passthru('pear -q package && rm package.xml');
|
||||
}
|
||||
|
||||
function modifyPhpunitXml($file)
|
||||
{
|
||||
$cfg = new SimpleXMLElement($file, null, true);
|
||||
|
||||
$cfg[0]['bootstrap'] = str_replace('tests/', '', $cfg[0]['bootstrap']);
|
||||
$cfg->testsuites->testsuite->directory = str_replace('tests/', '', $cfg->testsuites->testsuite->directory);
|
||||
|
||||
$cfg->saveXml($file);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
executeWithBackup(__DIR__.'/../phpunit.xml.dist', function ($file) {
|
||||
modifyPhpunitXml($file);
|
||||
|
||||
$pkg = generatePackageXml('package.ini');
|
||||
savePackageXml($pkg);
|
||||
|
||||
buildPackage();
|
||||
});
|
||||
Executable
+71
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// In order to be able to execute this script to create a Phar archive of Predis,
|
||||
// the Phar module must be loaded and the "phar.readonly" directive php.ini must
|
||||
// be set to "off". You can change the values in the $options array to customize
|
||||
// the creation of the Phar archive to better suit your needs.
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
$options = array(
|
||||
'name' => 'predis',
|
||||
'project_path' => __DIR__ . '/../lib/',
|
||||
'compression' => Phar::NONE,
|
||||
'append_version' => true,
|
||||
);
|
||||
|
||||
function getPharFilename($options)
|
||||
{
|
||||
$filename = $options['name'];
|
||||
|
||||
// NOTE: do not consider "append_version" with Phar compression do to a bug in
|
||||
// Phar::compress() when renaming phar archives containing dots in their name.
|
||||
if ($options['append_version'] && $options['compression'] === Phar::NONE) {
|
||||
$versionFile = @fopen(__DIR__ . '/../VERSION', 'r');
|
||||
|
||||
if ($versionFile === false) {
|
||||
throw new Exception("Could not locate the VERSION file.");
|
||||
}
|
||||
|
||||
$version = trim(fgets($versionFile));
|
||||
fclose($versionFile);
|
||||
$filename .= "_$version";
|
||||
}
|
||||
|
||||
return "$filename.phar";
|
||||
}
|
||||
|
||||
function getPharStub($options)
|
||||
{
|
||||
return <<<EOSTUB
|
||||
<?php
|
||||
Phar::mapPhar('predis.phar');
|
||||
spl_autoload_register(function (\$class) {
|
||||
if (strpos(\$class, 'Predis\\\\') === 0) {
|
||||
\$file = 'phar://predis.phar/'.strtr(\$class, '\\\', '/').'.php';
|
||||
if (file_exists(\$file)) {
|
||||
require \$file;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
__HALT_COMPILER();
|
||||
EOSTUB;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
$phar = new Phar(getPharFilename($options));
|
||||
$phar->compress($options['compression']);
|
||||
$phar->setStub(getPharStub($options));
|
||||
$phar->buildFromDirectory($options['project_path']);
|
||||
Executable
+663
@@ -0,0 +1,663 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// This script can be used to automatically glue all the .php files of Predis
|
||||
// into a single monolithic script file that can be used without an autoloader,
|
||||
// just like the other previous versions of the library.
|
||||
//
|
||||
// Much of its complexity is due to the fact that we cannot simply join PHP
|
||||
// files, but namespaces and classes definitions must follow a precise order
|
||||
// when dealing with subclassing and inheritance.
|
||||
//
|
||||
// The current implementation is pretty naïve, but it should do for now.
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
class CommandLine
|
||||
{
|
||||
public static function getOptions()
|
||||
{
|
||||
$parameters = array(
|
||||
's:' => 'source:',
|
||||
'o:' => 'output:',
|
||||
'e:' => 'exclude:',
|
||||
'E:' => 'exclude-classes:',
|
||||
);
|
||||
|
||||
$getops = getopt(implode(array_keys($parameters)), $parameters);
|
||||
|
||||
$options = array(
|
||||
'source' => __DIR__ . "/../lib/",
|
||||
'output' => PredisFile::NS_ROOT . '.php',
|
||||
'exclude' => array(),
|
||||
);
|
||||
|
||||
foreach ($getops as $option => $value) {
|
||||
switch ($option) {
|
||||
case 's':
|
||||
case 'source':
|
||||
$options['source'] = $value;
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
case 'output':
|
||||
$options['output'] = $value;
|
||||
break;
|
||||
|
||||
case 'E':
|
||||
case 'exclude-classes':
|
||||
$options['exclude'] = @file($value, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: $value;
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
case 'exclude':
|
||||
$options['exclude'] = is_array($value) ? $value : array($value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
}
|
||||
|
||||
class PredisFile
|
||||
{
|
||||
const NS_ROOT = 'Predis';
|
||||
|
||||
private $namespaces;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->namespaces = array();
|
||||
}
|
||||
|
||||
public static function from($libraryPath, Array $exclude = array())
|
||||
{
|
||||
$nsroot = self::NS_ROOT;
|
||||
$predisFile = new PredisFile();
|
||||
$libIterator = new RecursiveDirectoryIterator("$libraryPath$nsroot");
|
||||
|
||||
foreach (new RecursiveIteratorIterator($libIterator) as $classFile)
|
||||
{
|
||||
if (!$classFile->isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$namespace = strtr(str_replace($libraryPath, '', $classFile->getPath()), '/', '\\');
|
||||
|
||||
if (in_array(sprintf('%s\\%s', $namespace, $classFile->getBasename('.php')), $exclude)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$phpNamespace = $predisFile->getNamespace($namespace);
|
||||
|
||||
if ($phpNamespace === false) {
|
||||
$phpNamespace = new PhpNamespace($namespace);
|
||||
$predisFile->addNamespace($phpNamespace);
|
||||
}
|
||||
|
||||
$phpClass = new PhpClass($phpNamespace, $classFile);
|
||||
}
|
||||
|
||||
return $predisFile;
|
||||
}
|
||||
|
||||
public function addNamespace(PhpNamespace $namespace)
|
||||
{
|
||||
if (isset($this->namespaces[(string)$namespace])) {
|
||||
throw new InvalidArgumentException("Duplicated namespace");
|
||||
}
|
||||
$this->namespaces[(string)$namespace] = $namespace;
|
||||
}
|
||||
|
||||
public function getNamespaces()
|
||||
{
|
||||
return $this->namespaces;
|
||||
}
|
||||
|
||||
public function getNamespace($namespace)
|
||||
{
|
||||
if (!isset($this->namespaces[$namespace])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->namespaces[$namespace];
|
||||
}
|
||||
|
||||
public function getClassByFQN($classFqn)
|
||||
{
|
||||
if (($nsLastPos = strrpos($classFqn, '\\')) !== false) {
|
||||
$namespace = $this->getNamespace(substr($classFqn, 0, $nsLastPos));
|
||||
if ($namespace === false) {
|
||||
return null;
|
||||
}
|
||||
$className = substr($classFqn, $nsLastPos + 1);
|
||||
|
||||
return $namespace->getClass($className);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function calculateDependencyScores(&$classes, $fqn)
|
||||
{
|
||||
if (!isset($classes[$fqn])) {
|
||||
$classes[$fqn] = 0;
|
||||
}
|
||||
|
||||
$classes[$fqn] += 1;
|
||||
|
||||
if (($phpClass = $this->getClassByFQN($fqn)) === null) {
|
||||
throw new RuntimeException(
|
||||
"Cannot found the class $fqn which is required by other subclasses. Are you missing a file?"
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($phpClass->getDependencies() as $fqn) {
|
||||
$this->calculateDependencyScores($classes, $fqn);
|
||||
}
|
||||
}
|
||||
|
||||
private function getDependencyScores()
|
||||
{
|
||||
$classes = array();
|
||||
|
||||
foreach ($this->getNamespaces() as $phpNamespace) {
|
||||
foreach ($phpNamespace->getClasses() as $phpClass) {
|
||||
$this->calculateDependencyScores($classes, $phpClass->getFQN());
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
private function getOrderedNamespaces($dependencyScores)
|
||||
{
|
||||
$namespaces = array_fill_keys(array_unique(
|
||||
array_map(
|
||||
function ($fqn) { return PhpNamespace::extractName($fqn); },
|
||||
array_keys($dependencyScores)
|
||||
)
|
||||
), 0);
|
||||
|
||||
foreach ($dependencyScores as $classFqn => $score) {
|
||||
$namespaces[PhpNamespace::extractName($classFqn)] += $score;
|
||||
}
|
||||
|
||||
arsort($namespaces);
|
||||
|
||||
return array_keys($namespaces);
|
||||
}
|
||||
|
||||
private function getOrderedClasses(PhpNamespace $phpNamespace, $classes)
|
||||
{
|
||||
$nsClassesFQNs = array_map(function ($cl) { return $cl->getFQN(); }, $phpNamespace->getClasses());
|
||||
$nsOrderedClasses = array();
|
||||
|
||||
foreach ($nsClassesFQNs as $nsClassFQN) {
|
||||
$nsOrderedClasses[$nsClassFQN] = $classes[$nsClassFQN];
|
||||
}
|
||||
|
||||
arsort($nsOrderedClasses);
|
||||
|
||||
return array_keys($nsOrderedClasses);
|
||||
}
|
||||
|
||||
public function getPhpCode()
|
||||
{
|
||||
$buffer = array("<?php\n\n", PhpClass::LICENSE_HEADER, "\n\n");
|
||||
$classes = $this->getDependencyScores();
|
||||
$namespaces = $this->getOrderedNamespaces($classes);
|
||||
|
||||
foreach ($namespaces as $namespace) {
|
||||
$phpNamespace = $this->getNamespace($namespace);
|
||||
|
||||
// generate namespace directive
|
||||
$buffer[] = $phpNamespace->getPhpCode();
|
||||
$buffer[] = "\n";
|
||||
|
||||
// generate use directives
|
||||
$useDirectives = $phpNamespace->getUseDirectives();
|
||||
if (count($useDirectives) > 0) {
|
||||
$buffer[] = $useDirectives->getPhpCode();
|
||||
$buffer[] = "\n";
|
||||
}
|
||||
|
||||
// generate classes bodies
|
||||
$nsClasses = $this->getOrderedClasses($phpNamespace, $classes);
|
||||
foreach ($nsClasses as $classFQN) {
|
||||
$buffer[] = $this->getClassByFQN($classFQN)->getPhpCode();
|
||||
$buffer[] = "\n\n";
|
||||
}
|
||||
|
||||
$buffer[] = "/* " . str_repeat("-", 75) . " */";
|
||||
$buffer[] = "\n\n";
|
||||
}
|
||||
|
||||
return implode($buffer);
|
||||
}
|
||||
|
||||
public function saveTo($outputFile)
|
||||
{
|
||||
// TODO: add more sanity checks
|
||||
if ($outputFile === null || $outputFile === '') {
|
||||
throw new InvalidArgumentException('You must specify a valid output file');
|
||||
}
|
||||
file_put_contents($outputFile, $this->getPhpCode());
|
||||
}
|
||||
}
|
||||
|
||||
class PhpNamespace implements IteratorAggregate
|
||||
{
|
||||
private $namespace;
|
||||
private $classes;
|
||||
|
||||
public function __construct($namespace)
|
||||
{
|
||||
$this->namespace = $namespace;
|
||||
$this->classes = array();
|
||||
$this->useDirectives = new PhpUseDirectives($this);
|
||||
}
|
||||
|
||||
public static function extractName($fqn)
|
||||
{
|
||||
$nsSepLast = strrpos($fqn, '\\');
|
||||
if ($nsSepLast === false) {
|
||||
return $fqn;
|
||||
}
|
||||
$ns = substr($fqn, 0, $nsSepLast);
|
||||
|
||||
return $ns !== '' ? $ns : null;
|
||||
}
|
||||
|
||||
public function addClass(PhpClass $class)
|
||||
{
|
||||
$this->classes[$class->getName()] = $class;
|
||||
}
|
||||
|
||||
public function getClass($className)
|
||||
{
|
||||
if (isset($this->classes[$className])) {
|
||||
return $this->classes[$className];
|
||||
}
|
||||
}
|
||||
|
||||
public function getClasses()
|
||||
{
|
||||
return array_values($this->classes);
|
||||
}
|
||||
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($this->getClasses());
|
||||
}
|
||||
|
||||
public function getUseDirectives()
|
||||
{
|
||||
return $this->useDirectives;
|
||||
}
|
||||
|
||||
public function getPhpCode()
|
||||
{
|
||||
return "namespace $this->namespace;\n";
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->namespace;
|
||||
}
|
||||
}
|
||||
|
||||
class PhpUseDirectives implements Countable, IteratorAggregate
|
||||
{
|
||||
private $use;
|
||||
private $aliases;
|
||||
private $reverseAliases;
|
||||
private $namespace;
|
||||
|
||||
public function __construct(PhpNamespace $namespace)
|
||||
{
|
||||
$this->namespace = $namespace;
|
||||
$this->use = array();
|
||||
$this->aliases = array();
|
||||
$this->reverseAliases = array();
|
||||
}
|
||||
|
||||
public function add($use, $as = null)
|
||||
{
|
||||
if (in_array($use, $this->use)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rename = null;
|
||||
$this->use[] = $use;
|
||||
$aliasedClassName = $as ?: PhpClass::extractName($use);
|
||||
|
||||
if (isset($this->aliases[$aliasedClassName])) {
|
||||
$parentNs = $this->getParentNamespace();
|
||||
|
||||
if ($parentNs && false !== $pos = strrpos($parentNs, '\\')) {
|
||||
$parentNs = substr($parentNs, $pos);
|
||||
}
|
||||
|
||||
$newAlias = "{$parentNs}_{$aliasedClassName}";
|
||||
$rename = (object) array(
|
||||
'namespace' => $this->namespace,
|
||||
'from' => $aliasedClassName,
|
||||
'to' => $newAlias,
|
||||
);
|
||||
|
||||
$this->aliases[$newAlias] = $use;
|
||||
$as = $newAlias;
|
||||
} else {
|
||||
$this->aliases[$aliasedClassName] = $use;
|
||||
}
|
||||
|
||||
if ($as !== null) {
|
||||
$this->reverseAliases[$use] = $as;
|
||||
}
|
||||
|
||||
return $rename;
|
||||
}
|
||||
|
||||
public function getList()
|
||||
{
|
||||
return $this->use;
|
||||
}
|
||||
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($this->getList());
|
||||
}
|
||||
|
||||
public function getPhpCode()
|
||||
{
|
||||
$reverseAliases = $this->reverseAliases;
|
||||
|
||||
$reducer = function ($str, $use) use ($reverseAliases) {
|
||||
if (isset($reverseAliases[$use])) {
|
||||
return $str .= "use $use as {$reverseAliases[$use]};\n";
|
||||
} else {
|
||||
return $str .= "use $use;\n";
|
||||
}
|
||||
};
|
||||
|
||||
return array_reduce($this->getList(), $reducer, '');
|
||||
}
|
||||
|
||||
public function getNamespace()
|
||||
{
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
public function getParentNamespace()
|
||||
{
|
||||
if (false !== $pos = strrpos($this->namespace, '\\')) {
|
||||
return substr($this->namespace, 0, $pos);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getFQN($className)
|
||||
{
|
||||
if (($nsSepFirst = strpos($className, '\\')) === false) {
|
||||
if (isset($this->aliases[$className])) {
|
||||
return $this->aliases[$className];
|
||||
}
|
||||
|
||||
return (string)$this->getNamespace() . "\\$className";
|
||||
}
|
||||
|
||||
if ($nsSepFirst != 0) {
|
||||
throw new InvalidArgumentException("Partially qualified names are not supported");
|
||||
}
|
||||
|
||||
return $className;
|
||||
}
|
||||
|
||||
public function count()
|
||||
{
|
||||
return count($this->use);
|
||||
}
|
||||
}
|
||||
|
||||
class PhpClass
|
||||
{
|
||||
const LICENSE_HEADER = <<<LICENSE
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
LICENSE;
|
||||
|
||||
private $namespace;
|
||||
private $file;
|
||||
private $body;
|
||||
private $implements;
|
||||
private $extends;
|
||||
private $name;
|
||||
|
||||
public function __construct(PhpNamespace $namespace, SplFileInfo $classFile)
|
||||
{
|
||||
$this->namespace = $namespace;
|
||||
$this->file = $classFile;
|
||||
$this->implements = array();
|
||||
$this->extends = array();
|
||||
|
||||
$this->extractData();
|
||||
$namespace->addClass($this);
|
||||
}
|
||||
|
||||
public static function extractName($fqn)
|
||||
{
|
||||
$nsSepLast = strrpos($fqn, '\\');
|
||||
if ($nsSepLast === false) {
|
||||
return $fqn;
|
||||
}
|
||||
|
||||
return substr($fqn, $nsSepLast + 1);
|
||||
}
|
||||
|
||||
private function extractData()
|
||||
{
|
||||
$renames = array();
|
||||
$useDirectives = $this->getNamespace()->getUseDirectives();
|
||||
|
||||
$useExtractor = function ($m) use ($useDirectives, &$renames) {
|
||||
array_shift($m);
|
||||
|
||||
if (isset($m[1])) {
|
||||
$m[1] = str_replace(" as ", '', $m[1]);
|
||||
}
|
||||
|
||||
if ($rename = call_user_func_array(array($useDirectives, 'add'), $m)) {
|
||||
$renames[] = $rename;
|
||||
}
|
||||
};
|
||||
|
||||
$classBuffer = stream_get_contents(fopen($this->getFile()->getPathname(), 'r'));
|
||||
|
||||
$classBuffer = str_replace(self::LICENSE_HEADER, '', $classBuffer);
|
||||
|
||||
$classBuffer = preg_replace('/<\?php\s?\\n\s?/', '', $classBuffer);
|
||||
$classBuffer = preg_replace('/\s?\?>\n?/ms', '', $classBuffer);
|
||||
$classBuffer = preg_replace('/namespace\s+[\w\d_\\\\]+;\s?/', '', $classBuffer);
|
||||
$classBuffer = preg_replace_callback('/use\s+([\w\d_\\\\]+)(\s+as\s+.*)?;\s?\n?/', $useExtractor, $classBuffer);
|
||||
|
||||
foreach ($renames as $rename) {
|
||||
$classBuffer = str_replace($rename->from, $rename->to, $classBuffer);
|
||||
}
|
||||
|
||||
$this->body = trim($classBuffer);
|
||||
|
||||
$this->extractHierarchy();
|
||||
}
|
||||
|
||||
private function extractHierarchy()
|
||||
{
|
||||
$implements = array();
|
||||
$extends = array();
|
||||
|
||||
$extractor = function ($iterator, $callback) {
|
||||
$className = '';
|
||||
$iterator->seek($iterator->key() + 1);
|
||||
|
||||
while ($iterator->valid()) {
|
||||
$token = $iterator->current();
|
||||
|
||||
if (is_string($token)) {
|
||||
if (preg_match('/\s?,\s?/', $token)) {
|
||||
$callback(trim($className));
|
||||
$className = '';
|
||||
} else if ($token == '{') {
|
||||
$callback(trim($className));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($token[0]) {
|
||||
case T_NS_SEPARATOR:
|
||||
$className .= '\\';
|
||||
break;
|
||||
|
||||
case T_STRING:
|
||||
$className .= $token[1];
|
||||
break;
|
||||
|
||||
case T_IMPLEMENTS:
|
||||
case T_EXTENDS:
|
||||
$callback(trim($className));
|
||||
$iterator->seek($iterator->key() - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
$iterator->next();
|
||||
}
|
||||
};
|
||||
|
||||
$tokens = token_get_all("<?php\n" . trim($this->getPhpCode()));
|
||||
$iterator = new ArrayIterator($tokens);
|
||||
|
||||
while ($iterator->valid()) {
|
||||
$token = $iterator->current();
|
||||
if (is_string($token)) {
|
||||
$iterator->next();
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ($token[0]) {
|
||||
case T_CLASS:
|
||||
case T_INTERFACE:
|
||||
$iterator->seek($iterator->key() + 2);
|
||||
$tk = $iterator->current();
|
||||
$this->name = $tk[1];
|
||||
break;
|
||||
|
||||
case T_IMPLEMENTS:
|
||||
$extractor($iterator, function ($fqn) use (&$implements) {
|
||||
$implements[] = $fqn;
|
||||
});
|
||||
break;
|
||||
|
||||
case T_EXTENDS:
|
||||
$extractor($iterator, function ($fqn) use (&$extends) {
|
||||
$extends[] = $fqn;
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
$iterator->next();
|
||||
}
|
||||
|
||||
$this->implements = $this->guessFQN($implements);
|
||||
$this->extends = $this->guessFQN($extends);
|
||||
}
|
||||
|
||||
public function guessFQN($classes)
|
||||
{
|
||||
$useDirectives = $this->getNamespace()->getUseDirectives();
|
||||
return array_map(array($useDirectives, 'getFQN'), $classes);
|
||||
}
|
||||
|
||||
public function getImplementedInterfaces($all = false)
|
||||
{
|
||||
if ($all) {
|
||||
return $this->implements;
|
||||
}
|
||||
|
||||
return array_filter(
|
||||
$this->implements,
|
||||
function ($cn) { return strpos($cn, 'Predis\\') === 0; }
|
||||
);
|
||||
}
|
||||
|
||||
public function getExtendedClasses($all = false)
|
||||
{
|
||||
if ($all) {
|
||||
return $this->extemds;
|
||||
}
|
||||
|
||||
return array_filter(
|
||||
$this->extends,
|
||||
function ($cn) { return strpos($cn, 'Predis\\') === 0; }
|
||||
);
|
||||
}
|
||||
|
||||
public function getDependencies($all = false)
|
||||
{
|
||||
return array_merge(
|
||||
$this->getImplementedInterfaces($all),
|
||||
$this->getExtendedClasses($all)
|
||||
);
|
||||
}
|
||||
|
||||
public function getNamespace()
|
||||
{
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
public function getFile()
|
||||
{
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getFQN()
|
||||
{
|
||||
return (string)$this->getNamespace() . '\\' . $this->name;
|
||||
}
|
||||
|
||||
public function getPhpCode()
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return "class " . $this->getName() . '{ ... }';
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
$options = CommandLine::getOptions();
|
||||
$predisFile = PredisFile::from($options['source'], $options['exclude']);
|
||||
$predisFile->saveTo($options['output']);
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "predis/predis",
|
||||
"type": "library",
|
||||
"description": "Flexible and feature-complete PHP client library for Redis",
|
||||
"keywords": ["nosql", "redis", "predis"],
|
||||
"homepage": "http://github.com/nrk/predis",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Daniele Alessandri",
|
||||
"email": "suppakilla@gmail.com",
|
||||
"homepage": "http://clorophilla.net"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol",
|
||||
"ext-curl": "Allows access to Webdis when paired with phpiredis"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {"Predis": "lib/"}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +1,89 @@
|
||||
<?php
|
||||
require_once 'SharedConfigurations.php';
|
||||
|
||||
// Developers can customize the distribution strategy used by the client
|
||||
// to distribute keys among a cluster of servers simply by creating a class
|
||||
// that implements the Predis\Distribution\IDistributionAlgorithm interface.
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
class NaiveDistributionStrategy
|
||||
implements Predis\Distribution\IDistributionStrategy {
|
||||
require 'SharedConfigurations.php';
|
||||
|
||||
private $_nodes, $_nodesCount;
|
||||
// Developers can customize the distribution strategy used by the client
|
||||
// to distribute keys among a cluster of servers simply by creating a class
|
||||
// that implements Predis\Distribution\DistributionStrategyInterface.
|
||||
|
||||
public function __constructor() {
|
||||
$this->_nodes = array();
|
||||
$this->_nodesCount = 0;
|
||||
use Predis\Connection\PredisCluster;
|
||||
use Predis\Cluster\Distribution\DistributionStrategyInterface;
|
||||
use Predis\Cluster\Hash\HashGeneratorInterface;
|
||||
|
||||
class NaiveDistributionStrategy implements DistributionStrategyInterface, HashGeneratorInterface
|
||||
{
|
||||
private $nodes;
|
||||
private $nodesCount;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->nodes = array();
|
||||
$this->nodesCount = 0;
|
||||
}
|
||||
|
||||
public function add($node, $weight = null) {
|
||||
$this->_nodes[] = $node;
|
||||
$this->_nodesCount++;
|
||||
public function add($node, $weight = null)
|
||||
{
|
||||
$this->nodes[] = $node;
|
||||
$this->nodesCount++;
|
||||
}
|
||||
|
||||
public function remove($node) {
|
||||
$this->_nodes = array_filter($this->_nodes, function($n) use($node) {
|
||||
public function remove($node)
|
||||
{
|
||||
$this->nodes = array_filter($this->nodes, function ($n) use ($node) {
|
||||
return $n !== $node;
|
||||
});
|
||||
$this->_nodesCount = count($this->_nodes);
|
||||
|
||||
$this->nodesCount = count($this->nodes);
|
||||
}
|
||||
|
||||
public function get($key) {
|
||||
$count = $this->_nodesCount;
|
||||
if ($count === 0) {
|
||||
public function get($key)
|
||||
{
|
||||
if (0 === $count = $this->nodesCount) {
|
||||
throw new RuntimeException('No connections');
|
||||
}
|
||||
return $this->_nodes[$count > 1 ? abs(crc32($key) % $count) : 0];
|
||||
|
||||
return $this->nodes[$count > 1 ? abs($key % $count) : 0];
|
||||
}
|
||||
|
||||
public function generateKey($value) {
|
||||
public function hash($value)
|
||||
{
|
||||
return crc32($value);
|
||||
}
|
||||
|
||||
public function getHashGenerator()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
$options = array(
|
||||
'key_distribution' => new NaiveDistributionStrategy(),
|
||||
'cluster' => function () {
|
||||
$distributor = new NaiveDistributionStrategy();
|
||||
$cluster = new PredisCluster($distributor);
|
||||
|
||||
return $cluster;
|
||||
},
|
||||
);
|
||||
|
||||
$redis = new Predis\Client($multiple_servers, $options);
|
||||
$client = new Predis\Client($multiple_servers, $options);
|
||||
|
||||
for ($i = 0; $i < 100; $i++) {
|
||||
$redis->set("key:$i", str_pad($i, 4, '0', 0));
|
||||
$redis->get("key:$i");
|
||||
$client->set("key:$i", str_pad($i, 4, '0', 0));
|
||||
$client->get("key:$i");
|
||||
}
|
||||
|
||||
$server1 = $redis->getClientFor('first')->info();
|
||||
$server2 = $redis->getClientFor('second')->info();
|
||||
$server1 = $client->getClientFor('first')->info();
|
||||
$server2 = $client->getClientFor('second')->info();
|
||||
|
||||
printf("Server '%s' has %d keys while server '%s' has %d keys.\n",
|
||||
printf("Server '%s' has %d keys while server '%s' has %d keys.\n",
|
||||
'first', $server1['db15']['keys'], 'second', $server2['db15']['keys']
|
||||
);
|
||||
?>
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
require 'SharedConfigurations.php';
|
||||
|
||||
/*
|
||||
This is a basic example on how to use the Predis\DispatcherLoop class.
|
||||
|
||||
To see this example in action you can just use redis-cli and publish some
|
||||
messages to the 'events' and 'control' channel, e.g.:
|
||||
|
||||
./redis-cli
|
||||
PUBLISH events first
|
||||
PUBLISH events second
|
||||
PUBLISH events third
|
||||
PUBLISH control terminate_dispatcher
|
||||
*/
|
||||
|
||||
// Create a client and disable r/w timeout on the socket
|
||||
$client = new Predis\Client($single_server + array('read_write_timeout' => 0));
|
||||
|
||||
// Create a Predis\DispatcherLoop instance and attach a bunch of callbacks.
|
||||
$dispatcher = new Predis\PubSub\DispatcherLoop($client);
|
||||
|
||||
// Demonstrate how to use a callable class as a callback for Predis\DispatcherLoop.
|
||||
class EventsListener implements Countable
|
||||
{
|
||||
private $events;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->events = array();
|
||||
}
|
||||
|
||||
public function count()
|
||||
{
|
||||
return count($this->events);
|
||||
}
|
||||
|
||||
public function getEvents()
|
||||
{
|
||||
return $this->events;
|
||||
}
|
||||
|
||||
public function __invoke($payload)
|
||||
{
|
||||
$this->events[] = $payload;
|
||||
}
|
||||
}
|
||||
|
||||
// Attach our callable class to the dispatcher.
|
||||
$dispatcher->attachCallback('events', ($events = new EventsListener()));
|
||||
|
||||
// Attach a function to control the dispatcher loop termination with a message.
|
||||
$dispatcher->attachCallback('control', function ($payload) use ($dispatcher) {
|
||||
if ($payload === 'terminate_dispatcher') {
|
||||
$dispatcher->stop();
|
||||
}
|
||||
});
|
||||
|
||||
// Run the dispatcher loop until the callback attached to the 'control' channel
|
||||
// receives 'terminate_dispatcher' as a message.
|
||||
$dispatcher->run();
|
||||
|
||||
// Display our achievements!
|
||||
echo "We received {$events->count()} messages!\n";
|
||||
|
||||
// Say goodbye :-)
|
||||
$info = $client->info();
|
||||
print_r("Goodbye from Redis v{$info['redis_version']}!\n");
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
require 'SharedConfigurations.php';
|
||||
|
||||
// Predis ships with a KeyPrefixProcessor class that is used to transparently
|
||||
// prefix each key before sending commands to Redis, even for complex commands
|
||||
// such as SORT, ZUNIONSTORE and ZINTERSTORE. Key prefixes are useful to create
|
||||
// user-level namespaces for you keyspace, thus eliminating the need for separate
|
||||
// logical databases.
|
||||
|
||||
$client = new Predis\Client($single_server, array('prefix' => 'nrk:'));
|
||||
|
||||
$client->mset(array('foo' => 'bar', 'lol' => 'wut'));
|
||||
var_dump($client->mget('foo', 'lol'));
|
||||
/*
|
||||
array(2) {
|
||||
[0]=> string(3) "bar"
|
||||
[1]=> string(3) "wut"
|
||||
}
|
||||
*/
|
||||
|
||||
var_dump($client->keys('*'));
|
||||
/*
|
||||
array(2) {
|
||||
[0]=> string(7) "nrk:foo"
|
||||
[1]=> string(7) "nrk:lol"
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
require 'SharedConfigurations.php';
|
||||
|
||||
// Predis supports master / slave replication scenarios where write operations are
|
||||
// performed on the master server and read operations are executed against one of
|
||||
// the slaves. The behaviour of commands or EVAL scripts can be customized at will.
|
||||
// As soon as a write operation is performed, all the subsequent requests (reads
|
||||
// or writes) will be served by the master server.
|
||||
//
|
||||
// This example must be executed with the second Redis server acting as the slave
|
||||
// of the first one using the SLAVEOF command.
|
||||
//
|
||||
|
||||
$parameters = array(
|
||||
'tcp://127.0.0.1:6379?database=15&alias=master',
|
||||
'tcp://127.0.0.1:6380?database=15&alias=slave',
|
||||
);
|
||||
|
||||
$options = array('replication' => true);
|
||||
|
||||
$client = new Predis\Client($parameters, $options);
|
||||
|
||||
// Read operation.
|
||||
$exists = $client->exists('foo') ? 'yes' : 'no';
|
||||
$current = $client->getConnection()->getCurrent()->getParameters();
|
||||
echo "Does 'foo' exist on {$current->alias}? $exists.\n";
|
||||
|
||||
// Write operation.
|
||||
$client->set('foo', 'bar');
|
||||
$current = $client->getConnection()->getCurrent()->getParameters();
|
||||
echo "Now 'foo' has been set to 'bar' on {$current->alias}!\n";
|
||||
|
||||
// Read operation.
|
||||
$bar = $client->get('foo');
|
||||
$current = $client->getConnection()->getCurrent()->getParameters();
|
||||
echo "We just fetched 'foo' from {$current->alias} and its value is '$bar'.\n";
|
||||
|
||||
/* OUTPUT:
|
||||
Does 'foo' exist on slave? yes.
|
||||
Now 'foo' has been set to 'bar' on master!
|
||||
We just fetched 'foo' from master and its value is 'bar'.
|
||||
*/
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
require 'SharedConfigurations.php';
|
||||
|
||||
// Predis allows to set Lua scripts as read-only operations in the context of
|
||||
// replication. This works for both EVAL and EVALSHA and also for the client-side
|
||||
// abstraction built upon them (Predis\Command\ScriptedCommand). This example
|
||||
// shows a slightly more complex configuration that injects a new scripted command
|
||||
// in the server profile used by the new client instance and marks it marks it as
|
||||
// a read-only operation for replication so that it will be executed on slaves.
|
||||
|
||||
use Predis\Command\ScriptedCommand;
|
||||
use Predis\Connection\MasterSlaveReplication;
|
||||
use Predis\Profile\ServerProfile;
|
||||
use Predis\Replication\ReplicationStrategy;
|
||||
|
||||
// ------------------------------------------------------------------------- //
|
||||
|
||||
// Define a new scripted command that returns all the fields
|
||||
// of a variable number of hashes with a single roundtrip.
|
||||
|
||||
class HashMultipleGetAll extends ScriptedCommand {
|
||||
const BODY = <<<EOS
|
||||
local hashes = {}
|
||||
for _, key in pairs(KEYS) do
|
||||
table.insert(hashes, key)
|
||||
table.insert(hashes, redis.call('hgetall', key))
|
||||
end
|
||||
return hashes
|
||||
EOS;
|
||||
|
||||
public function getScript() {
|
||||
return self::BODY;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------- //
|
||||
|
||||
$parameters = array(
|
||||
'tcp://127.0.0.1:6379/?alias=master',
|
||||
'tcp://127.0.0.1:6380/?alias=slave',
|
||||
);
|
||||
|
||||
$options = array(
|
||||
'profile' => function ($options, $option) {
|
||||
$profile = $options->getDefault($option);
|
||||
$profile->defineCommand('hmgetall', 'HashMultipleGetAll');
|
||||
|
||||
return $profile;
|
||||
},
|
||||
'replication' => function ($options) {
|
||||
$strategy = new ReplicationStrategy();
|
||||
$strategy->setScriptReadOnly(HashMultipleGetAll::BODY);
|
||||
|
||||
$replication = new MasterSlaveReplication($strategy);
|
||||
|
||||
return $replication;
|
||||
},
|
||||
);
|
||||
|
||||
// ------------------------------------------------------------------------- //
|
||||
|
||||
$client = new Predis\Client($parameters, $options);
|
||||
|
||||
// Execute the following commands on the master server using redis-cli:
|
||||
// $ ./redis-cli HMSET metavars foo bar hoge piyo
|
||||
// $ ./redis-cli HMSET servers master host1 slave host2
|
||||
|
||||
$hashes = $client->hmgetall('metavars', 'servers');
|
||||
|
||||
$replication = $client->getConnection();
|
||||
$stillOnSlave = $replication->getCurrent() === $replication->getConnectionById('slave');
|
||||
|
||||
echo "Is still on slave? ", $stillOnSlave ? 'YES' : 'NO', "!\n";
|
||||
var_export($hashes);
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
require 'SharedConfigurations.php';
|
||||
|
||||
// This is a basic example on how to use the Predis\MonitorContext class.
|
||||
// You can use redis-cli to send commands to the same Redis instance your client is
|
||||
// connected to, and then type "ECHO QUIT_MONITOR" in redis-cli when you want to
|
||||
// exit the monitor loop and terminate this script in a graceful way.
|
||||
|
||||
// Create a client and disable r/w timeout on the socket.
|
||||
$client = new Predis\Client($single_server + array('read_write_timeout' => 0));
|
||||
|
||||
// Use only one instance of DateTime, we will update the timestamp later.
|
||||
$timestamp = new DateTime();
|
||||
|
||||
foreach (($monitor = $client->monitor()) as $event) {
|
||||
$timestamp->setTimestamp((int) $event->timestamp);
|
||||
|
||||
// If we notice a ECHO command with the message QUIT_MONITOR, we close the
|
||||
// monitor context and then break the loop.
|
||||
if ($event->command === 'ECHO' && $event->arguments === '"QUIT_MONITOR"') {
|
||||
echo "Exiting the monitor loop...\n";
|
||||
$monitor->closeContext();
|
||||
break;
|
||||
}
|
||||
|
||||
echo "* Received {$event->command} on DB {$event->database} at {$timestamp->format(DateTime::W3C)}\n";
|
||||
if (isset($event->arguments)) {
|
||||
echo " Arguments: {$event->arguments}\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Say goodbye :-)
|
||||
$info = $client->info();
|
||||
print_r("Goodbye from Redis v{$info['redis_version']}!\n");
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
require 'SharedConfigurations.php';
|
||||
|
||||
// Operations such as LRANGE, ZRANGE and others can potentially generate replies
|
||||
// containing a huge number of items. In some corner cases, such replies might
|
||||
// end up exhausting the maximum allowed memory allocated for a PHP process.
|
||||
// Multibulk iterators can be handy because they allow you to stream multibulk
|
||||
// replies using plain old PHP iterators, making it possible to iterate them with
|
||||
// a classic `foreach` loop and avoiding to consume an excessive amount of memory.
|
||||
//
|
||||
// PS: please note that multibulk iterators are supported only by the standard
|
||||
// connection backend class (Predis\Connection\StreamConnection) and not the
|
||||
// phpiredis-based one (Predis\Connection\PhpiredisConnection).
|
||||
|
||||
// Create a client and force the connection to use iterable multibulk responses.
|
||||
$client = new Predis\Client($single_server + array('iterable_multibulk' => true));
|
||||
|
||||
// Prepare an hash with some fields and their respective values.
|
||||
$client->hmset('metavars', array('foo' => 'bar', 'hoge' => 'piyo', 'lol' => 'wut'));
|
||||
|
||||
// By default multibulk iterators iterate over the reply as a list of items...
|
||||
foreach ($client->hgetall('metavars') as $index => $item) {
|
||||
echo "[$index] $item\n";
|
||||
}
|
||||
|
||||
/* OUTPUT:
|
||||
[0] foo
|
||||
[1] bar
|
||||
[2] hoge
|
||||
[3] piyo
|
||||
[4] lol
|
||||
[5] wut
|
||||
*/
|
||||
|
||||
// ... but certain multibulk replies are better represented as lists of tuples.
|
||||
foreach ($client->hgetall('metavars')->asTuple() as $index => $kv) {
|
||||
list($key, $value) = $kv;
|
||||
|
||||
echo "[$index] $key => $value\n";
|
||||
}
|
||||
|
||||
/* OUTPUT:
|
||||
[0] foo => bar
|
||||
[1] hoge => piyo
|
||||
[2] lol => wut
|
||||
*/
|
||||
@@ -1,20 +1,30 @@
|
||||
<?php
|
||||
require_once 'SharedConfigurations.php';
|
||||
|
||||
// redis can set keys and their relative values in one go
|
||||
// using MSET, then the same values can be retrieved with
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
require 'SharedConfigurations.php';
|
||||
|
||||
// redis can set keys and their relative values in one go
|
||||
// using MSET, then the same values can be retrieved with
|
||||
// a single command using MGET.
|
||||
|
||||
$mkv = array(
|
||||
'usr:0001' => 'First user',
|
||||
'usr:0002' => 'Second user',
|
||||
'usr:0003' => 'Third user'
|
||||
'usr:0002' => 'Second user',
|
||||
'usr:0003' => 'Third user'
|
||||
);
|
||||
|
||||
$redis = new Predis\Client($single_server);
|
||||
$client = new Predis\Client($single_server);
|
||||
|
||||
$redis->mset($mkv);
|
||||
$retval = $redis->mget(array_keys($mkv));
|
||||
$client->mset($mkv);
|
||||
$retval = $client->mget(array_keys($mkv));
|
||||
|
||||
print_r($retval);
|
||||
|
||||
@@ -26,4 +36,3 @@ Array
|
||||
[2] => Third user
|
||||
)
|
||||
*/
|
||||
?>
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
<?php
|
||||
require_once 'SharedConfigurations.php';
|
||||
|
||||
// When you have a whole set of consecutive commands to send to
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
require 'SharedConfigurations.php';
|
||||
|
||||
// When you have a whole set of consecutive commands to send to
|
||||
// a redis server, you can use a pipeline to improve performances.
|
||||
|
||||
$redis = new Predis\Client($single_server);
|
||||
$client = new Predis\Client($single_server);
|
||||
|
||||
$replies = $redis->pipeline(function($pipe) {
|
||||
$replies = $client->pipeline(function ($pipe) {
|
||||
$pipe->ping();
|
||||
$pipe->flushdb();
|
||||
$pipe->incrby('counter', 10);
|
||||
@@ -29,10 +39,9 @@ Array
|
||||
[5] => 40
|
||||
[6] => Array
|
||||
(
|
||||
[0] =>
|
||||
[0] =>
|
||||
[1] => 40
|
||||
)
|
||||
|
||||
)
|
||||
*/
|
||||
?>
|
||||
+22
-15
@@ -1,20 +1,29 @@
|
||||
<?php
|
||||
require_once 'SharedConfigurations.php';
|
||||
|
||||
// Redis 2.0 features new commands that allow clients to subscribe for
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
require 'SharedConfigurations.php';
|
||||
|
||||
// Redis 2.0 features new commands that allow clients to subscribe for
|
||||
// events published on certain channels (PUBSUB).
|
||||
|
||||
// Create a client and disable r/w timeout on the socket
|
||||
$redis = new Predis\Client($single_server + array('read_write_timeout' => -1));
|
||||
$client = new Predis\Client($single_server + array('read_write_timeout' => 0));
|
||||
|
||||
// Initialize a new pubsub context
|
||||
$pubsub = $redis->pubSubContext();
|
||||
$pubsub = $client->pubSubLoop();
|
||||
|
||||
// Subscribe to your channels
|
||||
$pubsub->subscribe('control_channel');
|
||||
$pubsub->subscribe('notifications');
|
||||
$pubsub->subscribe('control_channel', 'notifications');
|
||||
|
||||
// Start processing the pubsup messages. Open a terminal and use redis-cli
|
||||
// Start processing the pubsup messages. Open a terminal and use redis-cli
|
||||
// to push messages to the channels. Examples:
|
||||
// ./redis-cli PUBLISH notifications "this is a test"
|
||||
// ./redis-cli PUBLISH control_channel quit_loop
|
||||
@@ -23,17 +32,16 @@ foreach ($pubsub as $message) {
|
||||
case 'subscribe':
|
||||
echo "Subscribed to {$message->channel}\n";
|
||||
break;
|
||||
|
||||
case 'message':
|
||||
if ($message->channel == 'control_channel') {
|
||||
if ($message->payload == 'quit_loop') {
|
||||
echo "Aborting pubsub loop...\n";
|
||||
$pubsub->unsubscribe();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
echo "Received an unrecognized command: {$message->payload}.\n";
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
echo "Received the following message from {$message->channel}:\n",
|
||||
" {$message->payload}\n\n";
|
||||
}
|
||||
@@ -41,12 +49,11 @@ foreach ($pubsub as $message) {
|
||||
}
|
||||
}
|
||||
|
||||
// Always unset the pubsub context instance when you are done! The
|
||||
// class destructor will take care of cleanups and prevent protocol
|
||||
// Always unset the pubsub context instance when you are done! The
|
||||
// class destructor will take care of cleanups and prevent protocol
|
||||
// desynchronizations between the client and the server.
|
||||
unset($pubsub);
|
||||
|
||||
// Say goodbye :-)
|
||||
$info = $redis->info();
|
||||
$info = $client->info();
|
||||
print_r("Goodbye from Redis v{$info['redis_version']}!\n");
|
||||
?>
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
require 'SharedConfigurations.php';
|
||||
|
||||
use Predis\Collection\Iterator;
|
||||
|
||||
// Redis 2.8 features new commands allowing clients to incrementally
|
||||
// iterate over collections without blocking the server like it happens
|
||||
// when a command such as KEYS is executed on a Redis instance storing
|
||||
// millions of keys. These commands are SCAN (iterates over the keyspace),
|
||||
// SSCAN (iterates over members of a set), ZSCAN (iterates over members
|
||||
// and ranks of a sorted set) and HSCAN (iterates over fields and values
|
||||
// of an hash). Predis provides a specialized abstraction for each command
|
||||
// based on SPL iterators making it possible to easily consume SCAN-based
|
||||
// iterations in your PHP code.
|
||||
//
|
||||
// See http://redis.io/commands/scan for more details.
|
||||
//
|
||||
|
||||
// Create a client using `2.8` as a server profile (needs Redis 2.8!)
|
||||
$client = new Predis\Client($single_server, array('profile' => '2.8'));
|
||||
|
||||
// Prepare some keys for our example
|
||||
$client->del('predis:set', 'predis:zset', 'predis:hash');
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$client->sadd('predis:set', "member:$i");
|
||||
$client->zadd('predis:zset', -$i, "member:$i");
|
||||
$client->hset('predis:hash', "field:$i", "value:$i");
|
||||
}
|
||||
|
||||
// === Keyspace iterator based on SCAN ===
|
||||
echo 'Scan the keyspace matching only our prefixed keys:', PHP_EOL;
|
||||
foreach (new Iterator\Keyspace($client, 'predis:*') as $key) {
|
||||
echo " - $key", PHP_EOL;
|
||||
}
|
||||
|
||||
/* OUTPUT
|
||||
Scan the keyspace matching only our prefixed keys:
|
||||
- predis:zset
|
||||
- predis:set
|
||||
- predis:hash
|
||||
*/
|
||||
|
||||
// === Set iterator based on SSCAN ===
|
||||
echo 'Scan members of `predis:set`:', PHP_EOL;
|
||||
foreach (new Iterator\SetKey($client, 'predis:set') as $member) {
|
||||
echo " - $member", PHP_EOL;
|
||||
}
|
||||
|
||||
/* OUTPUT
|
||||
Scan members of `predis:set`:
|
||||
- member:1
|
||||
- member:4
|
||||
- member:0
|
||||
- member:3
|
||||
- member:2
|
||||
*/
|
||||
|
||||
// === Sorted set iterator based on ZSCAN ===
|
||||
echo 'Scan members and ranks of `predis:zset`:', PHP_EOL;
|
||||
foreach (new Iterator\SortedSetKey($client, 'predis:zset') as $member => $rank) {
|
||||
echo " - $member [rank: $rank]", PHP_EOL;
|
||||
}
|
||||
|
||||
/* OUTPUT
|
||||
Scan members and ranks of `predis:zset`:
|
||||
- member:4 [rank: -4]
|
||||
- member:3 [rank: -3]
|
||||
- member:2 [rank: -2]
|
||||
- member:1 [rank: -1]
|
||||
- member:0 [rank: 0]
|
||||
*/
|
||||
|
||||
// === Hash iterator based on HSCAN ===
|
||||
echo 'Scan fields and values of `predis:hash`:', PHP_EOL;
|
||||
foreach (new Iterator\HashKey($client, 'predis:hash') as $field => $value) {
|
||||
echo " - $field => $value", PHP_EOL;
|
||||
}
|
||||
|
||||
/* OUTPUT
|
||||
Scan fields and values of `predis:hash`:
|
||||
- field:0 => value:0
|
||||
- field:1 => value:1
|
||||
- field:2 => value:2
|
||||
- field:3 => value:3
|
||||
- field:4 => value:4
|
||||
*/
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
require 'SharedConfigurations.php';
|
||||
|
||||
// This example will not work with versions of Redis < 2.6.
|
||||
//
|
||||
// Additionally to the EVAL command defined in the current development profile, the new
|
||||
// Predis\Command\ScriptedCommand base class can be used to build an higher abstraction
|
||||
// for our "scripted" commands so that they will appear just like any other command on
|
||||
// the client-side. This is a quick example used to implement INCREX.
|
||||
|
||||
use Predis\Command\ScriptedCommand;
|
||||
|
||||
class IncrementExistingKeysBy extends ScriptedCommand
|
||||
{
|
||||
public function getKeysCount()
|
||||
{
|
||||
// Tell Predis to use all the arguments but the last one as arguments
|
||||
// for KEYS. The last one will be used to populate ARGV.
|
||||
return -1;
|
||||
}
|
||||
|
||||
public function getScript()
|
||||
{
|
||||
return
|
||||
<<<LUA
|
||||
local cmd, insert = redis.call, table.insert
|
||||
local increment, results = ARGV[1], { }
|
||||
|
||||
for idx, key in ipairs(KEYS) do
|
||||
if cmd('exists', key) == 1 then
|
||||
insert(results, idx, cmd('incrby', key, increment))
|
||||
else
|
||||
insert(results, idx, false)
|
||||
end
|
||||
end
|
||||
|
||||
return results
|
||||
LUA;
|
||||
}
|
||||
}
|
||||
|
||||
$client = new Predis\Client($single_server);
|
||||
|
||||
$client->getProfile()->defineCommand('increxby', 'IncrementExistingKeysBy');
|
||||
|
||||
$client->mset('foo', 10, 'foobar', 100);
|
||||
|
||||
var_export($client->increxby('foo', 'foofoo', 'foobar', 50));
|
||||
|
||||
/*
|
||||
array (
|
||||
0 => 60,
|
||||
1 => NULL,
|
||||
2 => 150,
|
||||
)
|
||||
*/
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
require 'SharedConfigurations.php';
|
||||
|
||||
// This example demonstrates how to leverage Predis to save PHP sessions on Redis.
|
||||
//
|
||||
// The value of `session.gc_maxlifetime` in `php.ini` will be used by default as the
|
||||
// the TTL for keys holding session data on Redis, but this value can be overridden
|
||||
// when creating the session handler instance with the `gc_maxlifetime` option.
|
||||
//
|
||||
// Note that this class needs PHP >= 5.4 but can be used on PHP 5.3 if a polyfill for
|
||||
// SessionHandlerInterface (see http://www.php.net/class.sessionhandlerinterface.php)
|
||||
// is provided either by you or an external package like `symfony/http-foundation`.
|
||||
|
||||
if (!interface_exists('SessionHandlerInterface')) {
|
||||
die("ATTENTION: the session handler implemented by Predis needs PHP >= 5.4.0 or a polyfill ".
|
||||
"for \SessionHandlerInterface either provided by you or an external package.\n");
|
||||
}
|
||||
|
||||
// Instantiate a new client just like you would normally do. We'll prefix our session keys here.
|
||||
$client = new Predis\Client($single_server, array('prefix' => 'sessions:'));
|
||||
|
||||
// Set `gc_maxlifetime` so that a session will be expired after 5 seconds since last access.
|
||||
$handler = new Predis\Session\SessionHandler($client, array('gc_maxlifetime' => 5));
|
||||
|
||||
// Register our session handler (it uses `session_set_save_handler()` internally).
|
||||
$handler->register();
|
||||
|
||||
// Set a fixed session ID just for the sake of our example.
|
||||
session_id('example_session_id');
|
||||
|
||||
session_start();
|
||||
|
||||
if (isset($_SESSION['foo'])) {
|
||||
echo "Session has `foo` set to {$_SESSION['foo']}\n";
|
||||
} else {
|
||||
$_SESSION['foo'] = $value = mt_rand();
|
||||
echo "Empty session, `foo` has been set with $value\n";
|
||||
}
|
||||
@@ -1,9 +1,19 @@
|
||||
<?php
|
||||
require_once '../lib/Predis.php';
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
require __DIR__.'/../autoload.php';
|
||||
|
||||
$single_server = array(
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 6379,
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 6379,
|
||||
'database' => 15
|
||||
);
|
||||
|
||||
@@ -21,4 +31,3 @@ $multiple_servers = array(
|
||||
'alias' => 'second',
|
||||
),
|
||||
);
|
||||
?>
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
require 'SharedConfigurations.php';
|
||||
|
||||
use Predis\Command\CommandInterface;
|
||||
use Predis\Connection\StreamConnection;
|
||||
|
||||
class SimpleDebuggableConnection extends StreamConnection
|
||||
{
|
||||
private $tstart = 0;
|
||||
private $debugBuffer = array();
|
||||
|
||||
public function connect()
|
||||
{
|
||||
$this->tstart = microtime(true);
|
||||
|
||||
parent::connect();
|
||||
}
|
||||
|
||||
private function storeDebug(CommandInterface $command, $direction)
|
||||
{
|
||||
$firtsArg = $command->getArgument(0);
|
||||
$timestamp = round(microtime(true) - $this->tstart, 4);
|
||||
|
||||
$debug = $command->getId();
|
||||
$debug .= isset($firtsArg) ? " $firtsArg " : ' ';
|
||||
$debug .= "$direction $this";
|
||||
$debug .= " [{$timestamp}s]";
|
||||
|
||||
$this->debugBuffer[] = $debug;
|
||||
}
|
||||
|
||||
public function writeCommand(CommandInterface $command)
|
||||
{
|
||||
parent::writeCommand($command);
|
||||
|
||||
$this->storeDebug($command, '->');
|
||||
}
|
||||
|
||||
public function readResponse(CommandInterface $command)
|
||||
{
|
||||
$reply = parent::readResponse($command);
|
||||
$this->storeDebug($command, '<-');
|
||||
|
||||
return $reply;
|
||||
}
|
||||
|
||||
public function getDebugBuffer()
|
||||
{
|
||||
return $this->debugBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
$options = array(
|
||||
'connections' => array(
|
||||
'tcp' => 'SimpleDebuggableConnection',
|
||||
),
|
||||
);
|
||||
|
||||
$client = new Predis\Client($single_server, $options);
|
||||
$client->set('foo', 'bar');
|
||||
$client->get('foo');
|
||||
$client->info();
|
||||
|
||||
print_r($client->getConnection()->getDebugBuffer());
|
||||
|
||||
/* OUTPUT:
|
||||
Array
|
||||
(
|
||||
[0] => SELECT 15 -> 127.0.0.1:6379 [0.0008s]
|
||||
[1] => SELECT 15 <- 127.0.0.1:6379 [0.0012s]
|
||||
[2] => SET foo -> 127.0.0.1:6379 [0.0014s]
|
||||
[3] => SET foo <- 127.0.0.1:6379 [0.0014s]
|
||||
[4] => GET foo -> 127.0.0.1:6379 [0.0016s]
|
||||
[5] => GET foo <- 127.0.0.1:6379 [0.0018s]
|
||||
[6] => INFO -> 127.0.0.1:6379 [0.002s]
|
||||
[7] => INFO <- 127.0.0.1:6379 [0.0025s]
|
||||
)
|
||||
*/
|
||||
@@ -1,16 +1,25 @@
|
||||
<?php
|
||||
require_once 'SharedConfigurations.php';
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
require 'SharedConfigurations.php';
|
||||
|
||||
// simple set and get scenario
|
||||
|
||||
$redis = new Predis\Client($single_server);
|
||||
$client = new Predis\Client($single_server);
|
||||
|
||||
$redis->set('library', 'predis');
|
||||
$retval = $redis->get('library');
|
||||
$client->set('library', 'predis');
|
||||
$retval = $client->get('library');
|
||||
|
||||
print_r($retval);
|
||||
var_dump($retval);
|
||||
|
||||
/* OUTPUT
|
||||
predis
|
||||
string(6) "predis"
|
||||
*/
|
||||
?>
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
require 'SharedConfigurations.php';
|
||||
|
||||
// This is an implementation of an atomic client-side ZPOP using the support for
|
||||
// check-and-set (CAS) operations with MULTI/EXEC transactions, as described in
|
||||
// "WATCH explained" from http://redis.io/topics/transactions
|
||||
//
|
||||
// First, populate your database with a tiny sample data set:
|
||||
//
|
||||
// ./redis-cli
|
||||
// SELECT 15
|
||||
// ZADD zset 1 a
|
||||
// ZADD zset 2 b
|
||||
// ZADD zset 3 c
|
||||
|
||||
function zpop($client, $key)
|
||||
{
|
||||
$element = null;
|
||||
$options = array(
|
||||
'cas' => true, // Initialize with support for CAS operations
|
||||
'watch' => $key, // Key that needs to be WATCHed to detect changes
|
||||
'retry' => 3, // Number of retries on aborted transactions, after
|
||||
// which the client bails out with an exception.
|
||||
);
|
||||
|
||||
$client->transaction($options, function ($tx) use ($key, &$element) {
|
||||
@list($element) = $tx->zrange($key, 0, 0);
|
||||
|
||||
if (isset($element)) {
|
||||
$tx->multi(); // With CAS, MULTI *must* be explicitly invoked.
|
||||
$tx->zrem($key, $element);
|
||||
}
|
||||
});
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
$client = new Predis\Client($single_server);
|
||||
$zpopped = zpop($client, 'zset');
|
||||
|
||||
echo isset($zpopped) ? "ZPOPed $zpopped" : "Nothing to ZPOP!", "\n";
|
||||
-2731
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis;
|
||||
|
||||
/**
|
||||
* Implements a lightweight PSR-0 compliant autoloader.
|
||||
*
|
||||
* @author Eric Naeseth <eric@thumbtack.com>
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class Autoloader
|
||||
{
|
||||
private $directory;
|
||||
private $prefix;
|
||||
private $prefixLength;
|
||||
|
||||
/**
|
||||
* @param string $baseDirectory Base directory where the source files are located.
|
||||
*/
|
||||
public function __construct($baseDirectory = __DIR__)
|
||||
{
|
||||
$this->directory = $baseDirectory;
|
||||
$this->prefix = __NAMESPACE__ . '\\';
|
||||
$this->prefixLength = strlen($this->prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the autoloader class with the PHP SPL autoloader.
|
||||
*
|
||||
* @param bool $prepend Prepend the autoloader on the stack instead of appending it.
|
||||
*/
|
||||
public static function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array(new self, 'autoload'), true, $prepend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a class from a file using its fully qualified name.
|
||||
*
|
||||
* @param string $className Fully qualified name of a class.
|
||||
*/
|
||||
public function autoload($className)
|
||||
{
|
||||
if (0 === strpos($className, $this->prefix)) {
|
||||
$parts = explode('\\', substr($className, $this->prefixLength));
|
||||
$filepath = $this->directory.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $parts).'.php';
|
||||
|
||||
if (is_file($filepath)) {
|
||||
require($filepath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis;
|
||||
|
||||
use Predis\Command\CommandInterface;
|
||||
|
||||
/**
|
||||
* Defines the interface of a basic client object or abstraction that
|
||||
* can send commands to Redis.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
interface BasicClientInterface
|
||||
{
|
||||
/**
|
||||
* Executes the specified Redis command.
|
||||
*
|
||||
* @param CommandInterface $command A Redis command.
|
||||
* @return mixed
|
||||
*/
|
||||
public function executeCommand(CommandInterface $command);
|
||||
}
|
||||
@@ -0,0 +1,465 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis;
|
||||
|
||||
use Predis\Command\CommandInterface;
|
||||
use Predis\Command\ScriptedCommand;
|
||||
use Predis\Connection\AggregatedConnectionInterface;
|
||||
use Predis\Connection\ConnectionInterface;
|
||||
use Predis\Connection\ConnectionFactoryInterface;
|
||||
use Predis\Monitor\MonitorContext;
|
||||
use Predis\Option\ClientOptions;
|
||||
use Predis\Option\ClientOptionsInterface;
|
||||
use Predis\Pipeline\PipelineContext;
|
||||
use Predis\PubSub\PubSubContext;
|
||||
use Predis\Transaction\MultiExecContext;
|
||||
|
||||
/**
|
||||
* Main class that exposes the most high-level interface to interact with Redis.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class Client implements ClientInterface
|
||||
{
|
||||
const VERSION = '0.8.6';
|
||||
|
||||
private $options;
|
||||
private $profile;
|
||||
private $connection;
|
||||
|
||||
/**
|
||||
* Initializes a new client with optional connection parameters and client options.
|
||||
*
|
||||
* @param mixed $parameters Connection parameters for one or multiple servers.
|
||||
* @param mixed $options Options that specify certain behaviours for the client.
|
||||
*/
|
||||
public function __construct($parameters = null, $options = null)
|
||||
{
|
||||
$this->options = $this->filterOptions($options);
|
||||
$this->profile = $this->options->profile;
|
||||
$this->connection = $this->initializeConnection($parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of Predis\Option\ClientOptions from various types of
|
||||
* arguments (string, array, Predis\Profile\ServerProfile) or returns the
|
||||
* passed object if it is an instance of Predis\Option\ClientOptions.
|
||||
*
|
||||
* @param mixed $options Client options.
|
||||
* @return ClientOptions
|
||||
*/
|
||||
protected function filterOptions($options)
|
||||
{
|
||||
if (!isset($options)) {
|
||||
return new ClientOptions();
|
||||
}
|
||||
|
||||
if (is_array($options)) {
|
||||
return new ClientOptions($options);
|
||||
}
|
||||
|
||||
if ($options instanceof ClientOptionsInterface) {
|
||||
return $options;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException("Invalid type for client options");
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes one or multiple connection (cluster) objects from various
|
||||
* types of arguments (string, array) or returns the passed object if it
|
||||
* implements Predis\Connection\ConnectionInterface.
|
||||
*
|
||||
* @param mixed $parameters Connection parameters or instance.
|
||||
* @return ConnectionInterface
|
||||
*/
|
||||
protected function initializeConnection($parameters)
|
||||
{
|
||||
if ($parameters instanceof ConnectionInterface) {
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
if (is_array($parameters) && isset($parameters[0])) {
|
||||
$options = $this->options;
|
||||
$replication = isset($options->replication) && $options->replication;
|
||||
$connection = $options->{$replication ? 'replication' : 'cluster'};
|
||||
|
||||
return $options->connections->createAggregated($connection, $parameters);
|
||||
}
|
||||
|
||||
if (is_callable($parameters)) {
|
||||
$connection = call_user_func($parameters, $this->options);
|
||||
|
||||
if (!$connection instanceof ConnectionInterface) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Callable parameters must return instances of Predis\Connection\ConnectionInterface'
|
||||
);
|
||||
}
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
return $this->options->connections->create($parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProfile()
|
||||
{
|
||||
return $this->profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection factory object used by the client.
|
||||
*
|
||||
* @return ConnectionFactoryInterface
|
||||
*/
|
||||
public function getConnectionFactory()
|
||||
{
|
||||
return $this->options->connections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new instance of a client for the specified connection when the
|
||||
* client is connected to a cluster. The new instance will use the same
|
||||
* options of the original client.
|
||||
*
|
||||
* @param string $connectionID Identifier for the connection.
|
||||
* @return Client
|
||||
*/
|
||||
public function getClientFor($connectionID)
|
||||
{
|
||||
if (!$connection = $this->getConnectionById($connectionID)) {
|
||||
throw new \InvalidArgumentException("Invalid connection ID: '$connectionID'");
|
||||
}
|
||||
|
||||
return new static($connection, $this->options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the connection to the server.
|
||||
*/
|
||||
public function connect()
|
||||
{
|
||||
$this->connection->connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from the server.
|
||||
*/
|
||||
public function disconnect()
|
||||
{
|
||||
$this->connection->disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from the server.
|
||||
*
|
||||
* This method is an alias of disconnect().
|
||||
*/
|
||||
public function quit()
|
||||
{
|
||||
$this->disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the underlying connection is connected to Redis.
|
||||
*
|
||||
* @return bool True means that the connection is open.
|
||||
* False means that the connection is closed.
|
||||
*/
|
||||
public function isConnected()
|
||||
{
|
||||
return $this->connection->isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConnection()
|
||||
{
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single connection out of an aggregated connections instance.
|
||||
*
|
||||
* @param string $connectionId Index or alias of the single connection.
|
||||
* @return Connection\SingleConnectionInterface
|
||||
*/
|
||||
public function getConnectionById($connectionId)
|
||||
{
|
||||
if (!$this->connection instanceof AggregatedConnectionInterface) {
|
||||
throw new NotSupportedException('Retrieving connections by ID is supported only when using aggregated connections');
|
||||
}
|
||||
|
||||
return $this->connection->getConnectionById($connectionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Redis command with the specified arguments and sends a request
|
||||
* to the server.
|
||||
*
|
||||
* @param string $commandID Command ID.
|
||||
* @param array $arguments Arguments for the command.
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($commandID, $arguments)
|
||||
{
|
||||
$command = $this->createCommand($commandID, $arguments);
|
||||
$response = $this->executeCommand($command);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createCommand($commandID, $arguments = array())
|
||||
{
|
||||
return $this->profile->createCommand($commandID, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function executeCommand(CommandInterface $command)
|
||||
{
|
||||
$response = $this->connection->executeCommand($command);
|
||||
|
||||
if ($response instanceof ResponseObjectInterface) {
|
||||
if ($response instanceof ResponseErrorInterface) {
|
||||
$response = $this->onResponseError($command, $response);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
return $command->parseResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles -ERR responses returned by Redis.
|
||||
*
|
||||
* @param CommandInterface $command The command that generated the error.
|
||||
* @param ResponseErrorInterface $response The error response instance.
|
||||
* @return mixed
|
||||
*/
|
||||
protected function onResponseError(CommandInterface $command, ResponseErrorInterface $response)
|
||||
{
|
||||
if ($command instanceof ScriptedCommand && $response->getErrorType() === 'NOSCRIPT') {
|
||||
$eval = $this->createCommand('eval');
|
||||
$eval->setRawArguments($command->getEvalArguments());
|
||||
|
||||
$response = $this->executeCommand($eval);
|
||||
|
||||
if (!$response instanceof ResponseObjectInterface) {
|
||||
$response = $command->parseResponse($response);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ($this->options->exceptions) {
|
||||
throw new ServerException($response->getMessage());
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the specified initializer method on $this with 0, 1 or 2 arguments.
|
||||
*
|
||||
* TODO: Invert $argv and $initializer.
|
||||
*
|
||||
* @param array $argv Arguments for the initializer.
|
||||
* @param string $initializer The initializer method.
|
||||
* @return mixed
|
||||
*/
|
||||
private function sharedInitializer($argv, $initializer)
|
||||
{
|
||||
switch (count($argv)) {
|
||||
case 0:
|
||||
return $this->$initializer();
|
||||
|
||||
case 1:
|
||||
list($arg0) = $argv;
|
||||
|
||||
return is_array($arg0) ? $this->$initializer($arg0) : $this->$initializer(null, $arg0);
|
||||
|
||||
case 2:
|
||||
list($arg0, $arg1) = $argv;
|
||||
|
||||
return $this->$initializer($arg0, $arg1);
|
||||
|
||||
default:
|
||||
return $this->$initializer($this, $argv);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new pipeline context and returns it, or returns the results of
|
||||
* a pipeline executed inside the optionally provided callable object.
|
||||
*
|
||||
* @param mixed ... Options for the context, a callable object, or both.
|
||||
* @return PipelineContext|array
|
||||
*/
|
||||
public function pipeline(/* arguments */)
|
||||
{
|
||||
return $this->sharedInitializer(func_get_args(), 'initPipeline');
|
||||
}
|
||||
|
||||
/**
|
||||
* Pipeline context initializer.
|
||||
*
|
||||
* @param array $options Options for the context.
|
||||
* @param mixed $callable Optional callable object used to execute the context.
|
||||
* @return PipelineContext|array
|
||||
*/
|
||||
protected function initPipeline(Array $options = null, $callable = null)
|
||||
{
|
||||
$executor = isset($options['executor']) ? $options['executor'] : null;
|
||||
|
||||
if (is_callable($executor)) {
|
||||
$executor = call_user_func($executor, $this, $options);
|
||||
}
|
||||
|
||||
$pipeline = new PipelineContext($this, $executor);
|
||||
$replies = $this->pipelineExecute($pipeline, $callable);
|
||||
|
||||
return $replies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a pipeline context when a callable object is passed.
|
||||
*
|
||||
* @param PipelineContext $pipeline Pipeline context instance.
|
||||
* @param mixed $callable Optional callable object used to execute the context.
|
||||
* @return PipelineContext|array
|
||||
*/
|
||||
private function pipelineExecute(PipelineContext $pipeline, $callable)
|
||||
{
|
||||
return isset($callable) ? $pipeline->execute($callable) : $pipeline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new transaction context and returns it, or returns the results of
|
||||
* a transaction executed inside the optionally provided callable object.
|
||||
*
|
||||
* @deprecated You should start using the new Client::transaction() method
|
||||
* as it will replace Client::multiExec() in the next major
|
||||
* version of the library.
|
||||
*
|
||||
* @param mixed ... Options for the context, a callable object, or both.
|
||||
* @return MultiExecContext|array
|
||||
*/
|
||||
public function multiExec(/* arguments */)
|
||||
{
|
||||
return $this->sharedInitializer(func_get_args(), 'initMultiExec');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new transaction context and returns it, or returns the results of
|
||||
* a transaction executed inside the optionally provided callable object.
|
||||
*
|
||||
* @param mixed ... Options for the context, a callable object, or both.
|
||||
* @return MultiExecContext|array
|
||||
*/
|
||||
public function transaction(/* arguments */)
|
||||
{
|
||||
return $this->sharedInitializer(func_get_args(), 'initMultiExec');
|
||||
}
|
||||
|
||||
/**
|
||||
* Transaction context initializer.
|
||||
*
|
||||
* @param array $options Options for the context.
|
||||
* @param mixed $callable Optional callable object used to execute the context.
|
||||
* @return MultiExecContext|array
|
||||
*/
|
||||
protected function initMultiExec(Array $options = null, $callable = null)
|
||||
{
|
||||
$transaction = new MultiExecContext($this, $options ?: array());
|
||||
|
||||
return isset($callable) ? $transaction->execute($callable) : $transaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Publish / Subscribe context and returns it, or executes it
|
||||
* inside the optionally provided callable object.
|
||||
*
|
||||
* @deprecated This method will change in the next major release to support
|
||||
* the new PUBSUB command introduced in Redis 2.8. Please use
|
||||
* Client::pubSubLoop() to create Predis\PubSub\PubSubContext
|
||||
* instances from now on.
|
||||
*
|
||||
* @param mixed ... Options for the context, a callable object, or both.
|
||||
* @return PubSubContext|array
|
||||
*/
|
||||
public function pubSub(/* arguments */)
|
||||
{
|
||||
return call_user_func_array(array($this, 'pubSubLoop'), func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Publish / Subscribe context and returns it, or executes it
|
||||
* inside the optionally provided callable object.
|
||||
*
|
||||
* @param mixed ... Options for the context, a callable object, or both.
|
||||
* @return PubSubContext|array
|
||||
*/
|
||||
public function pubSubLoop(/* arguments */)
|
||||
{
|
||||
return $this->sharedInitializer(func_get_args(), 'initPubSub');
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish / Subscribe context initializer.
|
||||
*
|
||||
* @param array $options Options for the context.
|
||||
* @param mixed $callable Optional callable object used to execute the context.
|
||||
* @return PubSubContext
|
||||
*/
|
||||
protected function initPubSub(Array $options = null, $callable = null)
|
||||
{
|
||||
$pubsub = new PubSubContext($this, $options);
|
||||
|
||||
if (!isset($callable)) {
|
||||
return $pubsub;
|
||||
}
|
||||
|
||||
foreach ($pubsub as $message) {
|
||||
if (call_user_func($callable, $pubsub, $message) === false) {
|
||||
$pubsub->closeContext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new monitor context.
|
||||
*
|
||||
* @return MonitorContext
|
||||
*/
|
||||
public function monitor()
|
||||
{
|
||||
return new MonitorContext($this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis;
|
||||
|
||||
/**
|
||||
* Exception class that identifies client-side errors.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class ClientException extends PredisException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis;
|
||||
|
||||
use Predis\Connection\ConnectionInterface;
|
||||
use Predis\Option\ClientOptionsInterface;
|
||||
use Predis\Profile\ServerProfileInterface;
|
||||
|
||||
/**
|
||||
* Interface defining the most important parts needed to create an
|
||||
* high-level Redis client object that can interact with other
|
||||
* building blocks of Predis.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
interface ClientInterface extends BasicClientInterface
|
||||
{
|
||||
/**
|
||||
* Returns the server profile used by the client.
|
||||
*
|
||||
* @return ServerProfileInterface
|
||||
*/
|
||||
public function getProfile();
|
||||
|
||||
/**
|
||||
* Returns the client options specified upon initialization.
|
||||
*
|
||||
* @return ClientOptionsInterface
|
||||
*/
|
||||
public function getOptions();
|
||||
|
||||
/**
|
||||
* Opens the connection to the server.
|
||||
*/
|
||||
public function connect();
|
||||
|
||||
/**
|
||||
* Disconnects from the server.
|
||||
*/
|
||||
public function disconnect();
|
||||
|
||||
/**
|
||||
* Returns the underlying connection instance.
|
||||
*
|
||||
* @return ConnectionInterface
|
||||
*/
|
||||
public function getConnection();
|
||||
|
||||
/**
|
||||
* Creates a new instance of the specified Redis command.
|
||||
*
|
||||
* @param string $method The name of a Redis command.
|
||||
* @param array $arguments The arguments for the command.
|
||||
* @return Command\CommandInterface
|
||||
*/
|
||||
public function createCommand($method, $arguments = array());
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster;
|
||||
|
||||
use Predis\Command\CommandInterface;
|
||||
|
||||
/**
|
||||
* Interface for classes defining the strategy used to calculate an hash
|
||||
* out of keys extracted from supported commands.
|
||||
*
|
||||
* This is mostly useful to support clustering via client-side sharding.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
interface CommandHashStrategyInterface
|
||||
{
|
||||
/**
|
||||
* Returns the hash for the given command using the specified algorithm, or null
|
||||
* if the command cannot be hashed.
|
||||
*
|
||||
* @param CommandInterface $command Command to be hashed.
|
||||
* @return int
|
||||
*/
|
||||
public function getHash(CommandInterface $command);
|
||||
|
||||
/**
|
||||
* Returns the hash for the given key using the specified algorithm.
|
||||
*
|
||||
* @param string $key Key to be hashed.
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyHash($key);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster\Distribution;
|
||||
|
||||
use Predis\Cluster\Hash\HashGeneratorInterface;
|
||||
|
||||
/**
|
||||
* A distributor implements the logic to automatically distribute
|
||||
* keys among several nodes for client-side sharding.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
interface DistributionStrategyInterface
|
||||
{
|
||||
/**
|
||||
* Adds a node to the distributor with an optional weight.
|
||||
*
|
||||
* @param mixed $node Node object.
|
||||
* @param int $weight Weight for the node.
|
||||
*/
|
||||
public function add($node, $weight = null);
|
||||
|
||||
/**
|
||||
* Removes a node from the distributor.
|
||||
*
|
||||
* @param mixed $node Node object.
|
||||
*/
|
||||
public function remove($node);
|
||||
|
||||
/**
|
||||
* Gets a node from the distributor using the computed hash of a key.
|
||||
*
|
||||
* @param mixed $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($key);
|
||||
|
||||
/**
|
||||
* Returns the underlying hash generator instance.
|
||||
*
|
||||
* @return HashGeneratorInterface
|
||||
*/
|
||||
public function getHashGenerator();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster\Distribution;
|
||||
|
||||
/**
|
||||
* Exception class that identifies empty rings.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class EmptyRingException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster\Distribution;
|
||||
|
||||
use Predis\Cluster\Hash\HashGeneratorInterface;
|
||||
|
||||
/**
|
||||
* This class implements an hashring-based distributor that uses the same
|
||||
* algorithm of memcache to distribute keys in a cluster using client-side
|
||||
* sharding.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
* @author Lorenzo Castelli <lcastelli@gmail.com>
|
||||
*/
|
||||
class HashRing implements DistributionStrategyInterface, HashGeneratorInterface
|
||||
{
|
||||
const DEFAULT_REPLICAS = 128;
|
||||
const DEFAULT_WEIGHT = 100;
|
||||
|
||||
private $ring;
|
||||
private $ringKeys;
|
||||
private $ringKeysCount;
|
||||
private $replicas;
|
||||
private $nodeHashCallback;
|
||||
private $nodes = array();
|
||||
|
||||
/**
|
||||
* @param int $replicas Number of replicas in the ring.
|
||||
* @param mixed $nodeHashCallback Callback returning the string used to calculate the hash of a node.
|
||||
*/
|
||||
public function __construct($replicas = self::DEFAULT_REPLICAS, $nodeHashCallback = null)
|
||||
{
|
||||
$this->replicas = $replicas;
|
||||
$this->nodeHashCallback = $nodeHashCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a node to the ring with an optional weight.
|
||||
*
|
||||
* @param mixed $node Node object.
|
||||
* @param int $weight Weight for the node.
|
||||
*/
|
||||
public function add($node, $weight = null)
|
||||
{
|
||||
// In case of collisions in the hashes of the nodes, the node added
|
||||
// last wins, thus the order in which nodes are added is significant.
|
||||
$this->nodes[] = array('object' => $node, 'weight' => (int) $weight ?: $this::DEFAULT_WEIGHT);
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function remove($node)
|
||||
{
|
||||
// A node is removed by resetting the ring so that it's recreated from
|
||||
// scratch, in order to reassign possible hashes with collisions to the
|
||||
// right node according to the order in which they were added in the
|
||||
// first place.
|
||||
for ($i = 0; $i < count($this->nodes); ++$i) {
|
||||
if ($this->nodes[$i]['object'] === $node) {
|
||||
array_splice($this->nodes, $i, 1);
|
||||
$this->reset();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the distributor.
|
||||
*/
|
||||
private function reset()
|
||||
{
|
||||
unset(
|
||||
$this->ring,
|
||||
$this->ringKeys,
|
||||
$this->ringKeysCount
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the initialization status of the distributor.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isInitialized()
|
||||
{
|
||||
return isset($this->ringKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the total weight of all the nodes in the distributor.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function computeTotalWeight()
|
||||
{
|
||||
$totalWeight = 0;
|
||||
|
||||
foreach ($this->nodes as $node) {
|
||||
$totalWeight += $node['weight'];
|
||||
}
|
||||
|
||||
return $totalWeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the distributor.
|
||||
*/
|
||||
private function initialize()
|
||||
{
|
||||
if ($this->isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->nodes) {
|
||||
throw new EmptyRingException('Cannot initialize empty hashring');
|
||||
}
|
||||
|
||||
$this->ring = array();
|
||||
$totalWeight = $this->computeTotalWeight();
|
||||
$nodesCount = count($this->nodes);
|
||||
|
||||
foreach ($this->nodes as $node) {
|
||||
$weightRatio = $node['weight'] / $totalWeight;
|
||||
$this->addNodeToRing($this->ring, $node, $nodesCount, $this->replicas, $weightRatio);
|
||||
}
|
||||
|
||||
ksort($this->ring, SORT_NUMERIC);
|
||||
$this->ringKeys = array_keys($this->ring);
|
||||
$this->ringKeysCount = count($this->ringKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the logic needed to add a node to the hashring.
|
||||
*
|
||||
* @param array $ring Source hashring.
|
||||
* @param mixed $node Node object to be added.
|
||||
* @param int $totalNodes Total number of nodes.
|
||||
* @param int $replicas Number of replicas in the ring.
|
||||
* @param float $weightRatio Weight ratio for the node.
|
||||
*/
|
||||
protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio)
|
||||
{
|
||||
$nodeObject = $node['object'];
|
||||
$nodeHash = $this->getNodeHash($nodeObject);
|
||||
$replicas = (int) round($weightRatio * $totalNodes * $replicas);
|
||||
|
||||
for ($i = 0; $i < $replicas; $i++) {
|
||||
$key = crc32("$nodeHash:$i");
|
||||
$ring[$key] = $nodeObject;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNodeHash($nodeObject)
|
||||
{
|
||||
if ($this->nodeHashCallback === null) {
|
||||
return (string) $nodeObject;
|
||||
}
|
||||
|
||||
return call_user_func($this->nodeHashCallback, $nodeObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the hash for the specified value.
|
||||
*
|
||||
* @param string $value Input value.
|
||||
* @return int
|
||||
*/
|
||||
public function hash($value)
|
||||
{
|
||||
return crc32($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($key)
|
||||
{
|
||||
return $this->ring[$this->getNodeKey($key)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the corrisponding key of a node distributed in the hashring.
|
||||
*
|
||||
* @param int $key Computed hash of a key.
|
||||
* @return int
|
||||
*/
|
||||
private function getNodeKey($key)
|
||||
{
|
||||
$this->initialize();
|
||||
$ringKeys = $this->ringKeys;
|
||||
$upper = $this->ringKeysCount - 1;
|
||||
$lower = 0;
|
||||
|
||||
while ($lower <= $upper) {
|
||||
$index = ($lower + $upper) >> 1;
|
||||
$item = $ringKeys[$index];
|
||||
|
||||
if ($item > $key) {
|
||||
$upper = $index - 1;
|
||||
} elseif ($item < $key) {
|
||||
$lower = $index + 1;
|
||||
} else {
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
|
||||
return $ringKeys[$this->wrapAroundStrategy($upper, $lower, $this->ringKeysCount)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a strategy to deal with wrap-around errors during binary searches.
|
||||
*
|
||||
* @param int $upper
|
||||
* @param int $lower
|
||||
* @param int $ringKeysCount
|
||||
* @return int
|
||||
*/
|
||||
protected function wrapAroundStrategy($upper, $lower, $ringKeysCount)
|
||||
{
|
||||
// Binary search for the last item in ringkeys with a value less or
|
||||
// equal to the key. If no such item exists, return the last item.
|
||||
return $upper >= 0 ? $upper : $ringKeysCount - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHashGenerator()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster\Distribution;
|
||||
|
||||
/**
|
||||
* This class implements an hashring-based distributor that uses the same
|
||||
* algorithm of libketama to distribute keys in a cluster using client-side
|
||||
* sharding.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
* @author Lorenzo Castelli <lcastelli@gmail.com>
|
||||
*/
|
||||
class KetamaPureRing extends HashRing
|
||||
{
|
||||
const DEFAULT_REPLICAS = 160;
|
||||
|
||||
/**
|
||||
* @param mixed $nodeHashCallback Callback returning the string used to calculate the hash of a node.
|
||||
*/
|
||||
public function __construct($nodeHashCallback = null)
|
||||
{
|
||||
parent::__construct($this::DEFAULT_REPLICAS, $nodeHashCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio)
|
||||
{
|
||||
$nodeObject = $node['object'];
|
||||
$nodeHash = $this->getNodeHash($nodeObject);
|
||||
$replicas = (int) floor($weightRatio * $totalNodes * ($replicas / 4));
|
||||
|
||||
for ($i = 0; $i < $replicas; $i++) {
|
||||
$unpackedDigest = unpack('V4', md5("$nodeHash-$i", true));
|
||||
|
||||
foreach ($unpackedDigest as $key) {
|
||||
$ring[$key] = $nodeObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hash($value)
|
||||
{
|
||||
$hash = unpack('V', md5($value, true));
|
||||
|
||||
return $hash[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function wrapAroundStrategy($upper, $lower, $ringKeysCount)
|
||||
{
|
||||
// Binary search for the first item in _ringkeys with a value greater
|
||||
// or equal to the key. If no such item exists, return the first item.
|
||||
return $lower < $ringKeysCount ? $lower : 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster\Hash;
|
||||
|
||||
/**
|
||||
* This class implements the CRC-CCITT-16 algorithm used by redis-cluster.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class CRC16HashGenerator implements HashGeneratorInterface
|
||||
{
|
||||
private static $CCITT_16 = array(
|
||||
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
|
||||
0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
|
||||
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
|
||||
0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
|
||||
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
|
||||
0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
|
||||
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
|
||||
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
|
||||
0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
|
||||
0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
|
||||
0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
|
||||
0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
|
||||
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
|
||||
0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
|
||||
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
|
||||
0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
|
||||
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
|
||||
0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
|
||||
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
|
||||
0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
|
||||
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
|
||||
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
|
||||
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
|
||||
0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
|
||||
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
|
||||
0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
|
||||
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
|
||||
0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
|
||||
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
|
||||
0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
|
||||
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
|
||||
0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0,
|
||||
);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hash($value)
|
||||
{
|
||||
// CRC-CCITT-16 algorithm
|
||||
$crc = 0;
|
||||
$CCITT_16 = self::$CCITT_16;
|
||||
$strlen = strlen($value);
|
||||
|
||||
for ($i = 0; $i < $strlen; $i++) {
|
||||
$crc = (($crc << 8) ^ $CCITT_16[($crc >> 8) ^ ord($value[$i])]) & 0xFFFF;
|
||||
}
|
||||
|
||||
return $crc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster\Hash;
|
||||
|
||||
/**
|
||||
* A generator of node keys implements the logic used to calculate the hash of
|
||||
* a key to distribute the respective operations among nodes.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
interface HashGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* Generates an hash that is used by the distributor algorithm
|
||||
*
|
||||
* @param string $value Value used to generate the hash.
|
||||
* @return int
|
||||
*/
|
||||
public function hash($value);
|
||||
}
|
||||
@@ -0,0 +1,398 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster;
|
||||
|
||||
use Predis\Cluster\Hash\HashGeneratorInterface;
|
||||
use Predis\Command\CommandInterface;
|
||||
use Predis\Command\ScriptedCommand;
|
||||
|
||||
/**
|
||||
* Default class used by Predis for client-side sharding to calculate
|
||||
* hashes out of keys of supported commands.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class PredisClusterHashStrategy implements CommandHashStrategyInterface
|
||||
{
|
||||
private $commands;
|
||||
private $hashGenerator;
|
||||
|
||||
/**
|
||||
* @param HashGeneratorInterface $hashGenerator Hash generator instance.
|
||||
*/
|
||||
public function __construct(HashGeneratorInterface $hashGenerator)
|
||||
{
|
||||
$this->commands = $this->getDefaultCommands();
|
||||
$this->hashGenerator = $hashGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default map of supported commands with their handlers.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDefaultCommands()
|
||||
{
|
||||
$getKeyFromFirstArgument = array($this, 'getKeyFromFirstArgument');
|
||||
$getKeyFromAllArguments = array($this, 'getKeyFromAllArguments');
|
||||
|
||||
return array(
|
||||
/* commands operating on the key space */
|
||||
'EXISTS' => $getKeyFromFirstArgument,
|
||||
'DEL' => $getKeyFromAllArguments,
|
||||
'TYPE' => $getKeyFromFirstArgument,
|
||||
'EXPIRE' => $getKeyFromFirstArgument,
|
||||
'EXPIREAT' => $getKeyFromFirstArgument,
|
||||
'PERSIST' => $getKeyFromFirstArgument,
|
||||
'PEXPIRE' => $getKeyFromFirstArgument,
|
||||
'PEXPIREAT' => $getKeyFromFirstArgument,
|
||||
'TTL' => $getKeyFromFirstArgument,
|
||||
'PTTL' => $getKeyFromFirstArgument,
|
||||
'SORT' => $getKeyFromFirstArgument, // TODO
|
||||
'DUMP' => $getKeyFromFirstArgument,
|
||||
'RESTORE' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on string values */
|
||||
'APPEND' => $getKeyFromFirstArgument,
|
||||
'DECR' => $getKeyFromFirstArgument,
|
||||
'DECRBY' => $getKeyFromFirstArgument,
|
||||
'GET' => $getKeyFromFirstArgument,
|
||||
'GETBIT' => $getKeyFromFirstArgument,
|
||||
'MGET' => $getKeyFromAllArguments,
|
||||
'SET' => $getKeyFromFirstArgument,
|
||||
'GETRANGE' => $getKeyFromFirstArgument,
|
||||
'GETSET' => $getKeyFromFirstArgument,
|
||||
'INCR' => $getKeyFromFirstArgument,
|
||||
'INCRBY' => $getKeyFromFirstArgument,
|
||||
'INCRBYFLOAT' => $getKeyFromFirstArgument,
|
||||
'SETBIT' => $getKeyFromFirstArgument,
|
||||
'SETEX' => $getKeyFromFirstArgument,
|
||||
'MSET' => array($this, 'getKeyFromInterleavedArguments'),
|
||||
'MSETNX' => array($this, 'getKeyFromInterleavedArguments'),
|
||||
'SETNX' => $getKeyFromFirstArgument,
|
||||
'SETRANGE' => $getKeyFromFirstArgument,
|
||||
'STRLEN' => $getKeyFromFirstArgument,
|
||||
'SUBSTR' => $getKeyFromFirstArgument,
|
||||
'BITOP' => array($this, 'getKeyFromBitOp'),
|
||||
'BITCOUNT' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on lists */
|
||||
'LINSERT' => $getKeyFromFirstArgument,
|
||||
'LINDEX' => $getKeyFromFirstArgument,
|
||||
'LLEN' => $getKeyFromFirstArgument,
|
||||
'LPOP' => $getKeyFromFirstArgument,
|
||||
'RPOP' => $getKeyFromFirstArgument,
|
||||
'RPOPLPUSH' => $getKeyFromAllArguments,
|
||||
'BLPOP' => array($this, 'getKeyFromBlockingListCommands'),
|
||||
'BRPOP' => array($this, 'getKeyFromBlockingListCommands'),
|
||||
'BRPOPLPUSH' => array($this, 'getKeyFromBlockingListCommands'),
|
||||
'LPUSH' => $getKeyFromFirstArgument,
|
||||
'LPUSHX' => $getKeyFromFirstArgument,
|
||||
'RPUSH' => $getKeyFromFirstArgument,
|
||||
'RPUSHX' => $getKeyFromFirstArgument,
|
||||
'LRANGE' => $getKeyFromFirstArgument,
|
||||
'LREM' => $getKeyFromFirstArgument,
|
||||
'LSET' => $getKeyFromFirstArgument,
|
||||
'LTRIM' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on sets */
|
||||
'SADD' => $getKeyFromFirstArgument,
|
||||
'SCARD' => $getKeyFromFirstArgument,
|
||||
'SDIFF' => $getKeyFromAllArguments,
|
||||
'SDIFFSTORE' => $getKeyFromAllArguments,
|
||||
'SINTER' => $getKeyFromAllArguments,
|
||||
'SINTERSTORE' => $getKeyFromAllArguments,
|
||||
'SUNION' => $getKeyFromAllArguments,
|
||||
'SUNIONSTORE' => $getKeyFromAllArguments,
|
||||
'SISMEMBER' => $getKeyFromFirstArgument,
|
||||
'SMEMBERS' => $getKeyFromFirstArgument,
|
||||
'SSCAN' => $getKeyFromFirstArgument,
|
||||
'SPOP' => $getKeyFromFirstArgument,
|
||||
'SRANDMEMBER' => $getKeyFromFirstArgument,
|
||||
'SREM' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on sorted sets */
|
||||
'ZADD' => $getKeyFromFirstArgument,
|
||||
'ZCARD' => $getKeyFromFirstArgument,
|
||||
'ZCOUNT' => $getKeyFromFirstArgument,
|
||||
'ZINCRBY' => $getKeyFromFirstArgument,
|
||||
'ZINTERSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
|
||||
'ZRANGE' => $getKeyFromFirstArgument,
|
||||
'ZRANGEBYSCORE' => $getKeyFromFirstArgument,
|
||||
'ZRANK' => $getKeyFromFirstArgument,
|
||||
'ZREM' => $getKeyFromFirstArgument,
|
||||
'ZREMRANGEBYRANK' => $getKeyFromFirstArgument,
|
||||
'ZREMRANGEBYSCORE' => $getKeyFromFirstArgument,
|
||||
'ZREVRANGE' => $getKeyFromFirstArgument,
|
||||
'ZREVRANGEBYSCORE' => $getKeyFromFirstArgument,
|
||||
'ZREVRANK' => $getKeyFromFirstArgument,
|
||||
'ZSCORE' => $getKeyFromFirstArgument,
|
||||
'ZUNIONSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
|
||||
'ZSCAN' => $getKeyFromFirstArgument,
|
||||
'ZLEXCOUNT' => $getKeyFromFirstArgument,
|
||||
'ZRANGEBYLEX' => $getKeyFromFirstArgument,
|
||||
'ZREMRANGEBYLEX' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on hashes */
|
||||
'HDEL' => $getKeyFromFirstArgument,
|
||||
'HEXISTS' => $getKeyFromFirstArgument,
|
||||
'HGET' => $getKeyFromFirstArgument,
|
||||
'HGETALL' => $getKeyFromFirstArgument,
|
||||
'HMGET' => $getKeyFromFirstArgument,
|
||||
'HMSET' => $getKeyFromFirstArgument,
|
||||
'HINCRBY' => $getKeyFromFirstArgument,
|
||||
'HINCRBYFLOAT' => $getKeyFromFirstArgument,
|
||||
'HKEYS' => $getKeyFromFirstArgument,
|
||||
'HLEN' => $getKeyFromFirstArgument,
|
||||
'HSET' => $getKeyFromFirstArgument,
|
||||
'HSETNX' => $getKeyFromFirstArgument,
|
||||
'HVALS' => $getKeyFromFirstArgument,
|
||||
'HSCAN' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on HyperLogLog */
|
||||
'PFADD' => $getKeyFromFirstArgument,
|
||||
'PFCOUNT' => $getKeyFromAllArguments,
|
||||
'PFMERGE' => $getKeyFromAllArguments,
|
||||
|
||||
/* scripting */
|
||||
'EVAL' => array($this, 'getKeyFromScriptingCommands'),
|
||||
'EVALSHA' => array($this, 'getKeyFromScriptingCommands'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of IDs for the supported commands.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSupportedCommands()
|
||||
{
|
||||
return array_keys($this->commands);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an handler for the specified command ID.
|
||||
*
|
||||
* The signature of the callback must have a single parameter
|
||||
* of type Predis\Command\CommandInterface.
|
||||
*
|
||||
* When the callback argument is omitted or NULL, the previously
|
||||
* associated handler for the specified command ID is removed.
|
||||
*
|
||||
* @param string $commandId The ID of the command to be handled.
|
||||
* @param mixed $callback A valid callable object or NULL.
|
||||
*/
|
||||
public function setCommandHandler($commandId, $callback = null)
|
||||
{
|
||||
$commandId = strtoupper($commandId);
|
||||
|
||||
if (!isset($callback)) {
|
||||
unset($this->commands[$commandId]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_callable($callback)) {
|
||||
throw new \InvalidArgumentException("Callback must be a valid callable object or NULL");
|
||||
}
|
||||
|
||||
$this->commands[$commandId] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from the first argument of a command instance.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
* @return string
|
||||
*/
|
||||
protected function getKeyFromFirstArgument(CommandInterface $command)
|
||||
{
|
||||
return $command->getArgument(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from a command with multiple keys only when all keys
|
||||
* in the arguments array produce the same hash.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
* @return string
|
||||
*/
|
||||
protected function getKeyFromAllArguments(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
|
||||
if ($this->checkSameHashForKeys($arguments)) {
|
||||
return $arguments[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from a command with multiple keys only when all keys
|
||||
* in the arguments array produce the same hash.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
* @return string
|
||||
*/
|
||||
protected function getKeyFromInterleavedArguments(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
$keys = array();
|
||||
|
||||
for ($i = 0; $i < count($arguments); $i += 2) {
|
||||
$keys[] = $arguments[$i];
|
||||
}
|
||||
|
||||
if ($this->checkSameHashForKeys($keys)) {
|
||||
return $arguments[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from BLPOP and BRPOP commands.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
* @return string
|
||||
*/
|
||||
protected function getKeyFromBlockingListCommands(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
|
||||
if ($this->checkSameHashForKeys(array_slice($arguments, 0, count($arguments) - 1))) {
|
||||
return $arguments[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from BITOP command.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
* @return string
|
||||
*/
|
||||
protected function getKeyFromBitOp(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
|
||||
if ($this->checkSameHashForKeys(array_slice($arguments, 1, count($arguments)))) {
|
||||
return $arguments[1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from ZINTERSTORE and ZUNIONSTORE commands.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
* @return string
|
||||
*/
|
||||
protected function getKeyFromZsetAggregationCommands(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
$keys = array_merge(array($arguments[0]), array_slice($arguments, 2, $arguments[1]));
|
||||
|
||||
if ($this->checkSameHashForKeys($keys)) {
|
||||
return $arguments[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from EVAL and EVALSHA commands.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
* @return string
|
||||
*/
|
||||
protected function getKeyFromScriptingCommands(CommandInterface $command)
|
||||
{
|
||||
if ($command instanceof ScriptedCommand) {
|
||||
$keys = $command->getKeys();
|
||||
} else {
|
||||
$keys = array_slice($args = $command->getArguments(), 2, $args[1]);
|
||||
}
|
||||
|
||||
if ($keys && $this->checkSameHashForKeys($keys)) {
|
||||
return $keys[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHash(CommandInterface $command)
|
||||
{
|
||||
$hash = $command->getHash();
|
||||
|
||||
if (!isset($hash) && isset($this->commands[$cmdID = $command->getId()])) {
|
||||
$key = call_user_func($this->commands[$cmdID], $command);
|
||||
|
||||
if (isset($key)) {
|
||||
$hash = $this->getKeyHash($key);
|
||||
$command->setHash($hash);
|
||||
}
|
||||
}
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getKeyHash($key)
|
||||
{
|
||||
$key = $this->extractKeyTag($key);
|
||||
$hash = $this->hashGenerator->hash($key);
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified array of keys will generate the same hash.
|
||||
*
|
||||
* @param array $keys Array of keys.
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkSameHashForKeys(Array $keys)
|
||||
{
|
||||
if (!$count = count($keys)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$currentKey = $this->extractKeyTag($keys[0]);
|
||||
|
||||
for ($i = 1; $i < $count; $i++) {
|
||||
$nextKey = $this->extractKeyTag($keys[$i]);
|
||||
|
||||
if ($currentKey !== $nextKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$currentKey = $nextKey;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns only the hashable part of a key (delimited by "{...}"), or the
|
||||
* whole key if a key tag is not found in the string.
|
||||
*
|
||||
* @param string $key A key.
|
||||
* @return string
|
||||
*/
|
||||
protected function extractKeyTag($key)
|
||||
{
|
||||
if (false !== $start = strpos($key, '{')) {
|
||||
if (false !== $end = strpos($key, '}', $start)) {
|
||||
$key = substr($key, ++$start, $end - $start);
|
||||
}
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Cluster;
|
||||
|
||||
use Predis\Cluster\Hash\CRC16HashGenerator;
|
||||
use Predis\Command\CommandInterface;
|
||||
use Predis\Command\ScriptedCommand;
|
||||
|
||||
/**
|
||||
* Default class used by Predis to calculate hashes out of keys of
|
||||
* commands supported by redis-cluster.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class RedisClusterHashStrategy implements CommandHashStrategyInterface
|
||||
{
|
||||
private $commands;
|
||||
private $hashGenerator;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->commands = $this->getDefaultCommands();
|
||||
$this->hashGenerator = new CRC16HashGenerator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default map of supported commands with their handlers.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDefaultCommands()
|
||||
{
|
||||
$getKeyFromFirstArgument = array($this, 'getKeyFromFirstArgument');
|
||||
$getKeyFromAllArguments = array($this, 'getKeyFromAllArguments');
|
||||
|
||||
return array(
|
||||
/* commands operating on the key space */
|
||||
'EXISTS' => $getKeyFromFirstArgument,
|
||||
'DEL' => $getKeyFromAllArguments,
|
||||
'TYPE' => $getKeyFromFirstArgument,
|
||||
'EXPIRE' => $getKeyFromFirstArgument,
|
||||
'EXPIREAT' => $getKeyFromFirstArgument,
|
||||
'PERSIST' => $getKeyFromFirstArgument,
|
||||
'PEXPIRE' => $getKeyFromFirstArgument,
|
||||
'PEXPIREAT' => $getKeyFromFirstArgument,
|
||||
'TTL' => $getKeyFromFirstArgument,
|
||||
'PTTL' => $getKeyFromFirstArgument,
|
||||
'SORT' => $getKeyFromFirstArgument, // TODO
|
||||
|
||||
/* commands operating on string values */
|
||||
'APPEND' => $getKeyFromFirstArgument,
|
||||
'DECR' => $getKeyFromFirstArgument,
|
||||
'DECRBY' => $getKeyFromFirstArgument,
|
||||
'GET' => $getKeyFromFirstArgument,
|
||||
'GETBIT' => $getKeyFromFirstArgument,
|
||||
'MGET' => $getKeyFromAllArguments,
|
||||
'SET' => $getKeyFromFirstArgument,
|
||||
'GETRANGE' => $getKeyFromFirstArgument,
|
||||
'GETSET' => $getKeyFromFirstArgument,
|
||||
'INCR' => $getKeyFromFirstArgument,
|
||||
'INCRBY' => $getKeyFromFirstArgument,
|
||||
'INCRBYFLOAT' => $getKeyFromFirstArgument,
|
||||
'SETBIT' => $getKeyFromFirstArgument,
|
||||
'SETEX' => $getKeyFromFirstArgument,
|
||||
'MSET' => array($this, 'getKeyFromInterleavedArguments'),
|
||||
'MSETNX' => array($this, 'getKeyFromInterleavedArguments'),
|
||||
'SETNX' => $getKeyFromFirstArgument,
|
||||
'SETRANGE' => $getKeyFromFirstArgument,
|
||||
'STRLEN' => $getKeyFromFirstArgument,
|
||||
'SUBSTR' => $getKeyFromFirstArgument,
|
||||
'BITCOUNT' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on lists */
|
||||
'LINSERT' => $getKeyFromFirstArgument,
|
||||
'LINDEX' => $getKeyFromFirstArgument,
|
||||
'LLEN' => $getKeyFromFirstArgument,
|
||||
'LPOP' => $getKeyFromFirstArgument,
|
||||
'RPOP' => $getKeyFromFirstArgument,
|
||||
'BLPOP' => array($this, 'getKeyFromBlockingListCommands'),
|
||||
'BRPOP' => array($this, 'getKeyFromBlockingListCommands'),
|
||||
'LPUSH' => $getKeyFromFirstArgument,
|
||||
'LPUSHX' => $getKeyFromFirstArgument,
|
||||
'RPUSH' => $getKeyFromFirstArgument,
|
||||
'RPUSHX' => $getKeyFromFirstArgument,
|
||||
'LRANGE' => $getKeyFromFirstArgument,
|
||||
'LREM' => $getKeyFromFirstArgument,
|
||||
'LSET' => $getKeyFromFirstArgument,
|
||||
'LTRIM' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on sets */
|
||||
'SADD' => $getKeyFromFirstArgument,
|
||||
'SCARD' => $getKeyFromFirstArgument,
|
||||
'SISMEMBER' => $getKeyFromFirstArgument,
|
||||
'SMEMBERS' => $getKeyFromFirstArgument,
|
||||
'SSCAN' => $getKeyFromFirstArgument,
|
||||
'SPOP' => $getKeyFromFirstArgument,
|
||||
'SRANDMEMBER' => $getKeyFromFirstArgument,
|
||||
'SREM' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on sorted sets */
|
||||
'ZADD' => $getKeyFromFirstArgument,
|
||||
'ZCARD' => $getKeyFromFirstArgument,
|
||||
'ZCOUNT' => $getKeyFromFirstArgument,
|
||||
'ZINCRBY' => $getKeyFromFirstArgument,
|
||||
'ZRANGE' => $getKeyFromFirstArgument,
|
||||
'ZRANGEBYSCORE' => $getKeyFromFirstArgument,
|
||||
'ZRANK' => $getKeyFromFirstArgument,
|
||||
'ZREM' => $getKeyFromFirstArgument,
|
||||
'ZREMRANGEBYRANK' => $getKeyFromFirstArgument,
|
||||
'ZREMRANGEBYSCORE' => $getKeyFromFirstArgument,
|
||||
'ZREVRANGE' => $getKeyFromFirstArgument,
|
||||
'ZREVRANGEBYSCORE' => $getKeyFromFirstArgument,
|
||||
'ZREVRANK' => $getKeyFromFirstArgument,
|
||||
'ZSCORE' => $getKeyFromFirstArgument,
|
||||
'ZSCAN' => $getKeyFromFirstArgument,
|
||||
'ZLEXCOUNT' => $getKeyFromFirstArgument,
|
||||
'ZRANGEBYLEX' => $getKeyFromFirstArgument,
|
||||
'ZREMRANGEBYLEX' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on hashes */
|
||||
'HDEL' => $getKeyFromFirstArgument,
|
||||
'HEXISTS' => $getKeyFromFirstArgument,
|
||||
'HGET' => $getKeyFromFirstArgument,
|
||||
'HGETALL' => $getKeyFromFirstArgument,
|
||||
'HMGET' => $getKeyFromFirstArgument,
|
||||
'HMSET' => $getKeyFromFirstArgument,
|
||||
'HINCRBY' => $getKeyFromFirstArgument,
|
||||
'HINCRBYFLOAT' => $getKeyFromFirstArgument,
|
||||
'HKEYS' => $getKeyFromFirstArgument,
|
||||
'HLEN' => $getKeyFromFirstArgument,
|
||||
'HSET' => $getKeyFromFirstArgument,
|
||||
'HSETNX' => $getKeyFromFirstArgument,
|
||||
'HVALS' => $getKeyFromFirstArgument,
|
||||
'HSCAN' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on HyperLogLog */
|
||||
'PFADD' => $getKeyFromFirstArgument,
|
||||
'PFCOUNT' => $getKeyFromAllArguments,
|
||||
'PFMERGE' => $getKeyFromAllArguments,
|
||||
|
||||
/* scripting */
|
||||
'EVAL' => array($this, 'getKeyFromScriptingCommands'),
|
||||
'EVALSHA' => array($this, 'getKeyFromScriptingCommands'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of IDs for the supported commands.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSupportedCommands()
|
||||
{
|
||||
return array_keys($this->commands);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an handler for the specified command ID.
|
||||
*
|
||||
* The signature of the callback must have a single parameter
|
||||
* of type Predis\Command\CommandInterface.
|
||||
*
|
||||
* When the callback argument is omitted or NULL, the previously
|
||||
* associated handler for the specified command ID is removed.
|
||||
*
|
||||
* @param string $commandId The ID of the command to be handled.
|
||||
* @param mixed $callback A valid callable object or NULL.
|
||||
*/
|
||||
public function setCommandHandler($commandId, $callback = null)
|
||||
{
|
||||
$commandId = strtoupper($commandId);
|
||||
|
||||
if (!isset($callback)) {
|
||||
unset($this->commands[$commandId]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_callable($callback)) {
|
||||
throw new \InvalidArgumentException("Callback must be a valid callable object or NULL");
|
||||
}
|
||||
|
||||
$this->commands[$commandId] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from the first argument of a command instance.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
* @return string
|
||||
*/
|
||||
protected function getKeyFromFirstArgument(CommandInterface $command)
|
||||
{
|
||||
return $command->getArgument(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from a command that can accept multiple keys ensuring
|
||||
* that only one key is actually specified to comply with redis-cluster.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
* @return string
|
||||
*/
|
||||
protected function getKeyFromAllArguments(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
|
||||
if (count($arguments) === 1) {
|
||||
return $arguments[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from a command that can accept multiple keys ensuring
|
||||
* that only one key is actually specified to comply with redis-cluster.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
* @return string
|
||||
*/
|
||||
protected function getKeyFromInterleavedArguments(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
|
||||
if (count($arguments) === 2) {
|
||||
return $arguments[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from BLPOP and BRPOP commands ensuring that only one key
|
||||
* is actually specified to comply with redis-cluster.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
* @return string
|
||||
*/
|
||||
protected function getKeyFromBlockingListCommands(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
|
||||
if (count($arguments) === 2) {
|
||||
return $arguments[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from EVAL and EVALSHA commands.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
* @return string
|
||||
*/
|
||||
protected function getKeyFromScriptingCommands(CommandInterface $command)
|
||||
{
|
||||
if ($command instanceof ScriptedCommand) {
|
||||
$keys = $command->getKeys();
|
||||
} else {
|
||||
$keys = array_slice($args = $command->getArguments(), 2, $args[1]);
|
||||
}
|
||||
|
||||
if (count($keys) === 1) {
|
||||
return $keys[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHash(CommandInterface $command)
|
||||
{
|
||||
$hash = $command->getHash();
|
||||
|
||||
if (!isset($hash) && isset($this->commands[$cmdID = $command->getId()])) {
|
||||
$key = call_user_func($this->commands[$cmdID], $command);
|
||||
|
||||
if (isset($key)) {
|
||||
$hash = $this->hashGenerator->hash($key);
|
||||
$command->setHash($hash);
|
||||
}
|
||||
}
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getKeyHash($key)
|
||||
{
|
||||
$key = $this->extractKeyTag($key);
|
||||
$hash = $this->hashGenerator->hash($key);
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns only the hashable part of a key (delimited by "{...}"), or the
|
||||
* whole key if a key tag is not found in the string.
|
||||
*
|
||||
* @param string $key A key.
|
||||
* @return string
|
||||
*/
|
||||
protected function extractKeyTag($key)
|
||||
{
|
||||
if (false !== $start = strpos($key, '{')) {
|
||||
if (false !== ($end = strpos($key, '}', $start)) && $end !== ++$start) {
|
||||
$key = substr($key, $start, $end - $start);
|
||||
}
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Collection\Iterator;
|
||||
|
||||
use Iterator;
|
||||
use Predis\ClientInterface;
|
||||
use Predis\NotSupportedException;
|
||||
|
||||
/**
|
||||
* Provides the base implementation for a fully-rewindable PHP iterator
|
||||
* that can incrementally iterate over cursor-based collections stored
|
||||
* on Redis using commands in the `SCAN` family.
|
||||
*
|
||||
* Given their incremental nature with multiple fetches, these kind of
|
||||
* iterators offer limited guarantees about the returned elements because
|
||||
* the collection can change several times during the iteration process.
|
||||
*
|
||||
* @see http://redis.io/commands/scan
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
abstract class CursorBasedIterator implements Iterator
|
||||
{
|
||||
protected $client;
|
||||
protected $match;
|
||||
protected $count;
|
||||
|
||||
protected $valid;
|
||||
protected $fetchmore;
|
||||
protected $elements;
|
||||
protected $cursor;
|
||||
protected $position;
|
||||
protected $current;
|
||||
|
||||
/**
|
||||
* @param ClientInterface $client Client connected to Redis.
|
||||
* @param string $match Pattern to match during the server-side iteration.
|
||||
* @param int $count Hints used by Redis to compute the number of results per iteration.
|
||||
*/
|
||||
public function __construct(ClientInterface $client, $match = null, $count = null)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->match = $match;
|
||||
$this->count = $count;
|
||||
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the client instance supports the specified Redis
|
||||
* command required to fetch elements from the server to perform
|
||||
* the iteration.
|
||||
*
|
||||
* @param ClientInterface $client Client connected to Redis.
|
||||
* @param string $commandID Command ID.
|
||||
*/
|
||||
protected function requiredCommand(ClientInterface $client, $commandID)
|
||||
{
|
||||
if (!$client->getProfile()->supportsCommand($commandID)) {
|
||||
throw new NotSupportedException("The specified server profile does not support the `$commandID` command.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the inner state of the iterator.
|
||||
*/
|
||||
protected function reset()
|
||||
{
|
||||
$this->valid = true;
|
||||
$this->fetchmore = true;
|
||||
$this->elements = array();
|
||||
$this->cursor = 0;
|
||||
$this->position = -1;
|
||||
$this->current = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of options for the `SCAN` command.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getScanOptions()
|
||||
{
|
||||
$options = array();
|
||||
|
||||
if (strlen($this->match) > 0) {
|
||||
$options['MATCH'] = $this->match;
|
||||
}
|
||||
|
||||
if ($this->count > 0) {
|
||||
$options['COUNT'] = $this->count;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a new set of elements from the remote collection,
|
||||
* effectively advancing the iteration process.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract protected function executeCommand();
|
||||
|
||||
/**
|
||||
* Populates the local buffer of elements fetched from the
|
||||
* server during the iteration.
|
||||
*/
|
||||
protected function fetch()
|
||||
{
|
||||
list($cursor, $elements) = $this->executeCommand();
|
||||
|
||||
if (!$cursor) {
|
||||
$this->fetchmore = false;
|
||||
}
|
||||
|
||||
$this->cursor = $cursor;
|
||||
$this->elements = $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts next values for key() and current().
|
||||
*/
|
||||
protected function extractNext()
|
||||
{
|
||||
$this->position++;
|
||||
$this->current = array_shift($this->elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
$this->reset();
|
||||
$this->next();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
return $this->current;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
tryFetch: {
|
||||
if (!$this->elements && $this->fetchmore) {
|
||||
$this->fetch();
|
||||
}
|
||||
|
||||
if ($this->elements) {
|
||||
$this->extractNext();
|
||||
} elseif ($this->cursor) {
|
||||
goto tryFetch;
|
||||
} else {
|
||||
$this->valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function valid()
|
||||
{
|
||||
return $this->valid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Collection\Iterator;
|
||||
|
||||
use Predis\ClientInterface;
|
||||
|
||||
/**
|
||||
* Abstracts the iteration of fields and values of an hash
|
||||
* by leveraging the HSCAN command (Redis >= 2.8) wrapped
|
||||
* in a fully-rewindable PHP iterator.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
* @link http://redis.io/commands/scan
|
||||
*/
|
||||
class HashKey extends CursorBasedIterator
|
||||
{
|
||||
protected $key;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(ClientInterface $client, $key, $match = null, $count = null)
|
||||
{
|
||||
$this->requiredCommand($client, 'HSCAN');
|
||||
|
||||
parent::__construct($client, $match, $count);
|
||||
|
||||
$this->key = $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function executeCommand()
|
||||
{
|
||||
return $this->client->hscan($this->key, $this->cursor, $this->getScanOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function extractNext()
|
||||
{
|
||||
$this->position = key($this->elements);
|
||||
$this->current = array_shift($this->elements);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Collection\Iterator;
|
||||
|
||||
use Predis\ClientInterface;
|
||||
|
||||
/**
|
||||
* Abstracts the iteration of the keyspace on a Redis instance
|
||||
* by leveraging the SCAN command (Redis >= 2.8) wrapped in a
|
||||
* fully-rewindable PHP iterator.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
* @link http://redis.io/commands/scan
|
||||
*/
|
||||
class Keyspace extends CursorBasedIterator
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(ClientInterface $client, $match = null, $count = null)
|
||||
{
|
||||
$this->requiredCommand($client, 'SCAN');
|
||||
|
||||
parent::__construct($client, $match, $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function executeCommand()
|
||||
{
|
||||
return $this->client->scan($this->cursor, $this->getScanOptions());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Collection\Iterator;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Iterator;
|
||||
use Predis\ClientInterface;
|
||||
use Predis\NotSupportedException;
|
||||
|
||||
/**
|
||||
* Abstracts the iteration of items stored in a list by leveraging the LRANGE
|
||||
* command wrapped in a fully-rewindable PHP iterator.
|
||||
*
|
||||
* This iterator tries to emulate the behaviour of cursor-based iterators based
|
||||
* on the SCAN-family of commands introduced in Redis <= 2.8, meaning that due
|
||||
* to its incremental nature with multiple fetches it can only offer limited
|
||||
* guarantees on the returned elements because the collection can change several
|
||||
* times (trimmed, deleted, overwritten) during the iteration process.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
* @link http://redis.io/commands/lrange
|
||||
*/
|
||||
class ListKey implements Iterator
|
||||
{
|
||||
protected $client;
|
||||
protected $count;
|
||||
protected $key;
|
||||
|
||||
protected $valid;
|
||||
protected $fetchmore;
|
||||
protected $elements;
|
||||
protected $position;
|
||||
protected $current;
|
||||
|
||||
/**
|
||||
* @param ClientInterface $client Client connected to Redis.
|
||||
* @param string $key Redis list key.
|
||||
* @param int $count Number of items retrieved on each fetch operation.
|
||||
*/
|
||||
public function __construct(ClientInterface $client, $key, $count = 10)
|
||||
{
|
||||
$this->requiredCommand($client, 'LRANGE');
|
||||
|
||||
if ((false === $count = filter_var($count, FILTER_VALIDATE_INT)) || $count < 0) {
|
||||
throw new InvalidArgumentException('The $count argument must be a positive integer.');
|
||||
}
|
||||
|
||||
$this->client = $client;
|
||||
$this->key = $key;
|
||||
$this->count = $count;
|
||||
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the client instance supports the specified Redis
|
||||
* command required to fetch elements from the server to perform
|
||||
* the iteration.
|
||||
*
|
||||
* @param ClientInterface $client Client connected to Redis.
|
||||
* @param string $commandID Command ID.
|
||||
*/
|
||||
protected function requiredCommand(ClientInterface $client, $commandID)
|
||||
{
|
||||
if (!$client->getProfile()->supportsCommand($commandID)) {
|
||||
throw new NotSupportedException("The specified server profile does not support the `$commandID` command.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the inner state of the iterator.
|
||||
*/
|
||||
protected function reset()
|
||||
{
|
||||
$this->valid = true;
|
||||
$this->fetchmore = true;
|
||||
$this->elements = array();
|
||||
$this->position = -1;
|
||||
$this->current = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a new set of elements from the remote collection,
|
||||
* effectively advancing the iteration process.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function executeCommand()
|
||||
{
|
||||
return $this->client->lrange($this->key, $this->position + 1, $this->position + $this->count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the local buffer of elements fetched from the
|
||||
* server during the iteration.
|
||||
*/
|
||||
protected function fetch()
|
||||
{
|
||||
$elements = $this->executeCommand();
|
||||
|
||||
if (count($elements) < $this->count) {
|
||||
$this->fetchmore = false;
|
||||
}
|
||||
|
||||
$this->elements = $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts next values for key() and current().
|
||||
*/
|
||||
protected function extractNext()
|
||||
{
|
||||
$this->position++;
|
||||
$this->current = array_shift($this->elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
$this->reset();
|
||||
$this->next();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
return $this->current;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
if (!$this->elements && $this->fetchmore) {
|
||||
$this->fetch();
|
||||
}
|
||||
|
||||
if ($this->elements) {
|
||||
$this->extractNext();
|
||||
} else {
|
||||
$this->valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function valid()
|
||||
{
|
||||
return $this->valid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Collection\Iterator;
|
||||
|
||||
use Predis\ClientInterface;
|
||||
|
||||
/**
|
||||
* Abstracts the iteration of members stored in a set by
|
||||
* leveraging the SSCAN command (Redis >= 2.8) wrapped in
|
||||
* a fully-rewindable PHP iterator.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
* @link http://redis.io/commands/scan
|
||||
*/
|
||||
class SetKey extends CursorBasedIterator
|
||||
{
|
||||
protected $key;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(ClientInterface $client, $key, $match = null, $count = null)
|
||||
{
|
||||
$this->requiredCommand($client, 'SSCAN');
|
||||
|
||||
parent::__construct($client, $match, $count);
|
||||
|
||||
$this->key = $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function executeCommand()
|
||||
{
|
||||
return $this->client->sscan($this->key, $this->cursor, $this->getScanOptions());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Collection\Iterator;
|
||||
|
||||
use Predis\ClientInterface;
|
||||
|
||||
/**
|
||||
* Abstracts the iteration of members stored in a sorted set
|
||||
* by leveraging the ZSCAN command (Redis >= 2.8) wrapped in
|
||||
* a fully-rewindable PHP iterator.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
* @link http://redis.io/commands/scan
|
||||
*/
|
||||
class SortedSetKey extends CursorBasedIterator
|
||||
{
|
||||
protected $key;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(ClientInterface $client, $key, $match = null, $count = null)
|
||||
{
|
||||
$this->requiredCommand($client, 'ZSCAN');
|
||||
|
||||
parent::__construct($client, $match, $count);
|
||||
|
||||
$this->key = $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function executeCommand()
|
||||
{
|
||||
return $this->client->zscan($this->key, $this->cursor, $this->getScanOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function extractNext()
|
||||
{
|
||||
$element = array_shift($this->elements);
|
||||
|
||||
$this->position = $element[0];
|
||||
$this->current = $element[1];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* Base class for Redis commands.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
abstract class AbstractCommand implements CommandInterface
|
||||
{
|
||||
private $hash;
|
||||
private $arguments = array();
|
||||
|
||||
/**
|
||||
* Returns a filtered array of the arguments.
|
||||
*
|
||||
* @param array $arguments List of arguments.
|
||||
* @return array
|
||||
*/
|
||||
protected function filterArguments(Array $arguments)
|
||||
{
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setArguments(Array $arguments)
|
||||
{
|
||||
$this->arguments = $this->filterArguments($arguments);
|
||||
unset($this->hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setRawArguments(Array $arguments)
|
||||
{
|
||||
$this->arguments = $arguments;
|
||||
unset($this->hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getArguments()
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getArgument($index)
|
||||
{
|
||||
if (isset($this->arguments[$index])) {
|
||||
return $this->arguments[$index];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setHash($hash)
|
||||
{
|
||||
$this->hash = $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHash()
|
||||
{
|
||||
if (isset($this->hash)) {
|
||||
return $this->hash;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parseResponse($data)
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function used to reduce a list of arguments to a string.
|
||||
*
|
||||
* @param string $accumulator Temporary string.
|
||||
* @param string $argument Current argument.
|
||||
* @return string
|
||||
*/
|
||||
protected function toStringArgumentReducer($accumulator, $argument)
|
||||
{
|
||||
if (strlen($argument) > 32) {
|
||||
$argument = substr($argument, 0, 32) . '[...]';
|
||||
}
|
||||
|
||||
$accumulator .= " $argument";
|
||||
|
||||
return $accumulator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a partial string representation of the command with its arguments.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return array_reduce(
|
||||
$this->getArguments(),
|
||||
array($this, 'toStringArgumentReducer'),
|
||||
$this->getId()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the arguments array passed to a Redis command.
|
||||
*
|
||||
* @param array $arguments Arguments for a command.
|
||||
* @return array
|
||||
*/
|
||||
public static function normalizeArguments(Array $arguments)
|
||||
{
|
||||
if (count($arguments) === 1 && is_array($arguments[0])) {
|
||||
return $arguments[0];
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the arguments array passed to a variadic Redis command.
|
||||
*
|
||||
* @param array $arguments Arguments for a command.
|
||||
* @return array
|
||||
*/
|
||||
public static function normalizeVariadic(Array $arguments)
|
||||
{
|
||||
if (count($arguments) === 2 && is_array($arguments[1])) {
|
||||
return array_merge(array($arguments[0]), $arguments[1]);
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* Defines an abstraction representing a Redis command.
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
interface CommandInterface
|
||||
{
|
||||
/**
|
||||
* Gets the ID of a Redis command.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getId();
|
||||
|
||||
/**
|
||||
* Set the hash for the command.
|
||||
*
|
||||
* @param int $hash Calculated hash.
|
||||
*/
|
||||
public function setHash($hash);
|
||||
|
||||
/**
|
||||
* Returns the hash of the command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getHash();
|
||||
|
||||
/**
|
||||
* Sets the arguments for the command.
|
||||
*
|
||||
* @param array $arguments List of arguments.
|
||||
*/
|
||||
public function setArguments(Array $arguments);
|
||||
|
||||
/**
|
||||
* Sets the raw arguments for the command without processing them.
|
||||
*
|
||||
* @param array $arguments List of arguments.
|
||||
*/
|
||||
public function setRawArguments(Array $arguments);
|
||||
|
||||
/**
|
||||
* Gets the arguments of the command.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getArguments();
|
||||
|
||||
/**
|
||||
* Gets the argument of the command at the specified index.
|
||||
*
|
||||
* @param int $index Index of the desired argument.
|
||||
* @return mixed
|
||||
*/
|
||||
public function getArgument($index);
|
||||
|
||||
/**
|
||||
* Parses a reply buffer and returns a PHP object.
|
||||
*
|
||||
* @param string $data Binary string containing the whole reply.
|
||||
* @return mixed
|
||||
*/
|
||||
public function parseResponse($data);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/auth
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class ConnectionAuth extends AbstractCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'AUTH';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/echo
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class ConnectionEcho extends AbstractCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'ECHO';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/ping
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class ConnectionPing extends AbstractCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'PING';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parseResponse($data)
|
||||
{
|
||||
return $data === 'PONG' ? true : false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/quit
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class ConnectionQuit extends AbstractCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'QUIT';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/select
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class ConnectionSelect extends AbstractCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'SELECT';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/hdel
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class HashDelete extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'HDEL';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function filterArguments(Array $arguments)
|
||||
{
|
||||
return self::normalizeVariadic($arguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/hexists
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class HashExists extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'HEXISTS';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parseResponse($data)
|
||||
{
|
||||
return (bool) $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/hget
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class HashGet extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'HGET';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/hgetall
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class HashGetAll extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'HGETALL';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parseResponse($data)
|
||||
{
|
||||
$result = array();
|
||||
|
||||
for ($i = 0; $i < count($data); $i++) {
|
||||
$result[$data[$i]] = $data[++$i];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/hmget
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class HashGetMultiple extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'HMGET';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function filterArguments(Array $arguments)
|
||||
{
|
||||
return self::normalizeVariadic($arguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/hincrby
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class HashIncrementBy extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'HINCRBY';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/hincrbyfloat
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class HashIncrementByFloat extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'HINCRBYFLOAT';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/hkeys
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class HashKeys extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'HKEYS';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/hlen
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class HashLength extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'HLEN';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/hscan
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class HashScan extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'HSCAN';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function filterArguments(Array $arguments)
|
||||
{
|
||||
if (count($arguments) === 3 && is_array($arguments[2])) {
|
||||
$options = $this->prepareOptions(array_pop($arguments));
|
||||
$arguments = array_merge($arguments, $options);
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of options and modifiers compatible with Redis.
|
||||
*
|
||||
* @param array $options List of options.
|
||||
* @return array
|
||||
*/
|
||||
protected function prepareOptions($options)
|
||||
{
|
||||
$options = array_change_key_case($options, CASE_UPPER);
|
||||
$normalized = array();
|
||||
|
||||
if (!empty($options['MATCH'])) {
|
||||
$normalized[] = 'MATCH';
|
||||
$normalized[] = $options['MATCH'];
|
||||
}
|
||||
|
||||
if (!empty($options['COUNT'])) {
|
||||
$normalized[] = 'COUNT';
|
||||
$normalized[] = $options['COUNT'];
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parseResponse($data)
|
||||
{
|
||||
if (is_array($data)) {
|
||||
$data[0] = (int) $data[0];
|
||||
$fields = $data[1];
|
||||
$result = array();
|
||||
|
||||
for ($i = 0; $i < count($fields); $i++) {
|
||||
$result[$fields[$i]] = $fields[++$i];
|
||||
}
|
||||
|
||||
$data[1] = $result;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/hset
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class HashSet extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'HSET';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parseResponse($data)
|
||||
{
|
||||
return (bool) $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/hmset
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class HashSetMultiple extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'HMSET';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function filterArguments(Array $arguments)
|
||||
{
|
||||
if (count($arguments) === 2 && is_array($arguments[1])) {
|
||||
$flattenedKVs = array($arguments[0]);
|
||||
$args = $arguments[1];
|
||||
|
||||
foreach ($args as $k => $v) {
|
||||
$flattenedKVs[] = $k;
|
||||
$flattenedKVs[] = $v;
|
||||
}
|
||||
|
||||
return $flattenedKVs;
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/hsetnx
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class HashSetPreserve extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'HSETNX';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parseResponse($data)
|
||||
{
|
||||
return (bool) $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/hvals
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class HashValues extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'HVALS';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/pfadd
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class HyperLogLogAdd extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'PFADD';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function filterArguments(array $arguments)
|
||||
{
|
||||
return self::normalizeVariadic($arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parseResponse($data)
|
||||
{
|
||||
return (bool) $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/pfcount
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class HyperLogLogCount extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'PFCOUNT';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function filterArguments(array $arguments)
|
||||
{
|
||||
return self::normalizeArguments($arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prefixKeys($prefix)
|
||||
{
|
||||
PrefixHelpers::all($this, $prefix);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/pfmerge
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class HyperLogLogMerge extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'PFMERGE';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function filterArguments(array $arguments)
|
||||
{
|
||||
return self::normalizeArguments($arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prefixKeys($prefix)
|
||||
{
|
||||
PrefixHelpers::all($this, $prefix);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/del
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class KeyDelete extends AbstractCommand implements PrefixableCommandInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'DEL';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function filterArguments(Array $arguments)
|
||||
{
|
||||
return self::normalizeArguments($arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prefixKeys($prefix)
|
||||
{
|
||||
PrefixHelpers::all($this, $prefix);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/dump
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class KeyDump extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'DUMP';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/exists
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class KeyExists extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'EXISTS';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parseResponse($data)
|
||||
{
|
||||
return (bool) $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/expire
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class KeyExpire extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'EXPIRE';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parseResponse($data)
|
||||
{
|
||||
return (bool) $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/expireat
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class KeyExpireAt extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'EXPIREAT';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parseResponse($data)
|
||||
{
|
||||
return (bool) $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/keys
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class KeyKeys extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'KEYS';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/keys
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
* @deprecated
|
||||
*/
|
||||
class KeyKeysV12x extends KeyKeys
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parseResponse($data)
|
||||
{
|
||||
return explode(' ', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/move
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class KeyMove extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'MOVE';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parseResponse($data)
|
||||
{
|
||||
return (bool) $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/persist
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class KeyPersist extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'PERSIST';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parseResponse($data)
|
||||
{
|
||||
return (bool) $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/pexpire
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class KeyPreciseExpire extends KeyExpire
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'PEXPIRE';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/pexpireat
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class KeyPreciseExpireAt extends KeyExpireAt
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'PEXPIREAT';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/pttl
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class KeyPreciseTimeToLive extends KeyTimeToLive
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'PTTL';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/randomkey
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class KeyRandom extends AbstractCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'RANDOMKEY';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parseResponse($data)
|
||||
{
|
||||
return $data !== '' ? $data : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/rename
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class KeyRename extends AbstractCommand implements PrefixableCommandInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'RENAME';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prefixKeys($prefix)
|
||||
{
|
||||
PrefixHelpers::all($this, $prefix);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/renamenx
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class KeyRenamePreserve extends KeyRename
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'RENAMENX';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parseResponse($data)
|
||||
{
|
||||
return (bool) $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/restore
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class KeyRestore extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'RESTORE';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/scan
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class KeyScan extends AbstractCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'SCAN';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function filterArguments(Array $arguments)
|
||||
{
|
||||
if (count($arguments) === 2 && is_array($arguments[1])) {
|
||||
$options = $this->prepareOptions(array_pop($arguments));
|
||||
$arguments = array_merge($arguments, $options);
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of options and modifiers compatible with Redis.
|
||||
*
|
||||
* @param array $options List of options.
|
||||
* @return array
|
||||
*/
|
||||
protected function prepareOptions($options)
|
||||
{
|
||||
$options = array_change_key_case($options, CASE_UPPER);
|
||||
$normalized = array();
|
||||
|
||||
if (!empty($options['MATCH'])) {
|
||||
$normalized[] = 'MATCH';
|
||||
$normalized[] = $options['MATCH'];
|
||||
}
|
||||
|
||||
if (!empty($options['COUNT'])) {
|
||||
$normalized[] = 'COUNT';
|
||||
$normalized[] = $options['COUNT'];
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parseResponse($data)
|
||||
{
|
||||
if (is_array($data)) {
|
||||
$data[0] = (int) $data[0];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/sort
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class KeySort extends AbstractCommand implements PrefixableCommandInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'SORT';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function filterArguments(Array $arguments)
|
||||
{
|
||||
if (count($arguments) === 1) {
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
$query = array($arguments[0]);
|
||||
$sortParams = array_change_key_case($arguments[1], CASE_UPPER);
|
||||
|
||||
if (isset($sortParams['BY'])) {
|
||||
$query[] = 'BY';
|
||||
$query[] = $sortParams['BY'];
|
||||
}
|
||||
|
||||
if (isset($sortParams['GET'])) {
|
||||
$getargs = $sortParams['GET'];
|
||||
|
||||
if (is_array($getargs)) {
|
||||
foreach ($getargs as $getarg) {
|
||||
$query[] = 'GET';
|
||||
$query[] = $getarg;
|
||||
}
|
||||
} else {
|
||||
$query[] = 'GET';
|
||||
$query[] = $getargs;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($sortParams['LIMIT']) &&
|
||||
is_array($sortParams['LIMIT']) &&
|
||||
count($sortParams['LIMIT']) == 2) {
|
||||
|
||||
$query[] = 'LIMIT';
|
||||
$query[] = $sortParams['LIMIT'][0];
|
||||
$query[] = $sortParams['LIMIT'][1];
|
||||
}
|
||||
|
||||
if (isset($sortParams['SORT'])) {
|
||||
$query[] = strtoupper($sortParams['SORT']);
|
||||
}
|
||||
|
||||
if (isset($sortParams['ALPHA']) && $sortParams['ALPHA'] == true) {
|
||||
$query[] = 'ALPHA';
|
||||
}
|
||||
|
||||
if (isset($sortParams['STORE'])) {
|
||||
$query[] = 'STORE';
|
||||
$query[] = $sortParams['STORE'];
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prefixKeys($prefix)
|
||||
{
|
||||
if ($arguments = $this->getArguments()) {
|
||||
$arguments[0] = "$prefix{$arguments[0]}";
|
||||
|
||||
if (($count = count($arguments)) > 1) {
|
||||
for ($i = 1; $i < $count; $i++) {
|
||||
switch ($arguments[$i]) {
|
||||
case 'BY':
|
||||
case 'STORE':
|
||||
$arguments[$i] = "$prefix{$arguments[++$i]}";
|
||||
break;
|
||||
|
||||
case 'GET':
|
||||
$value = $arguments[++$i];
|
||||
if ($value !== '#') {
|
||||
$arguments[$i] = "$prefix$value";
|
||||
}
|
||||
break;
|
||||
|
||||
case 'LIMIT';
|
||||
$i += 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->setRawArguments($arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/ttl
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class KeyTimeToLive extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'TTL';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/type
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class KeyType extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'TYPE';
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user