├── .gitignore ├── .travis.yml ├── LICENSE ├── composer.json ├── humbug.json.dist ├── phpunit.xml ├── readme.md ├── src ├── Memorizator.php ├── Storage.php ├── Utils.php └── functions.php └── tests ├── MemorizatorTest.php ├── Memorize ├── MemorizeInClosureTest.php ├── MemorizeInFunctionTest.php ├── MemorizeInObjectTest.php └── MemorizeInStaticTest.php ├── StorageTest.php ├── Support ├── FactorialCalculator.php ├── Singleton.php ├── StaticFactorialCalculator.php ├── ValueObject.php └── functions.php └── UtilsTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.lock 3 | .idea 4 | humbuglog.txt -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | install: 3 | - composer install 4 | php: 5 | - '5.4' 6 | - '5.5' 7 | - '5.6' 8 | - '7.0' 9 | - '7.1' 10 | - hhvm 11 | - nightly 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Alexey Solodkiy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solodkiy/memorize", 3 | "description": "Simple cache for pure functions", 4 | "type": "library", 5 | "require": { 6 | "php": ">=5.4" 7 | }, 8 | "require-dev": { 9 | "phpunit/phpunit": "4.8", 10 | "humbug/humbug": "~1.0@dev" 11 | }, 12 | "autoload": { 13 | "psr-4": { 14 | "Solodkiy\\Memorize\\": "src/" 15 | }, 16 | "files": ["src/functions.php"] 17 | }, 18 | "autoload-dev": { 19 | "psr-4": { 20 | "Solodkiy\\Memorize\\": "tests/" 21 | }, 22 | "files": ["tests/Support/functions.php"] 23 | }, 24 | "license": "MIT", 25 | "authors": [ 26 | { 27 | "name": "Alexey Solodkiy", 28 | "email": "work@x1.by" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /humbug.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "directories": [ 4 | "src" 5 | ] 6 | }, 7 | "timeout": 10, 8 | "logs": { 9 | "text": "humbuglog.txt" 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | tests 5 | 6 | 7 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Memorize 2 | -------- 3 | [![Build Status](https://travis-ci.org/Solodkiy/memorize.svg?branch=master)](https://travis-ci.org/Solodkiy/memorize) 4 | [![Latest Stable Version](https://poser.pugx.org/solodkiy/memorize/v/stable)](https://packagist.org/packages/solodkiy/memorize) 5 | [![Total Downloads](https://poser.pugx.org/solodkiy/memorize/downloads)](https://packagist.org/packages/solodkiy/memorize) 6 | 7 | Memorize is php analog of python [@lazy decorator](https://pypi.python.org/pypi/lazy). 8 | 9 | Memorize provides simple in-var cache for closures. It can be used to create lazy functions. 10 | Function takes closure and optional argument paramsHash. 11 | If the closure with the same arguments was run before memorize will return result from cache without the closure call. At the first call result will be calculated and stored in cache. 12 | 13 | Cache is stored in global space for static methods and simple functions. For closures defined in object method cache will be stored in this object (two objects have different cache). 14 | By design cache is stored only while script is running. There is no way to set ttl for cache or invalidate it. 15 | Memorize automatically calculates paramsHash for closures with scalar arguments. If your closure has objects as arguments you must calculate and pass paramsHash as the second argument. (see [MemorizeInObjectTest::correctAddDaysToTime](tests/Memorize/MemorizeInObjectTest.php) test) 16 | 17 | Notice that memorize calculates closure hash by file name, start line and end line of closure declaration. Memorize will not work correctly if you declare two closures in one line (e.g: after code obfuscation). 18 | 19 | Install 20 | ------- 21 | ``` 22 | composer require solodkiy/memorize 23 | ``` 24 | 25 | Examples 26 | -------- 27 | 28 | ### Singleton with memorize 29 | Before: 30 | ```php 31 | class Singleton 32 | { 33 | /** 34 | * @var Singleton 35 | */ 36 | private static $instance; 37 | 38 | private function __construct() 39 | { 40 | // private 41 | } 42 | 43 | public static function getInstance() 44 | { 45 | if (is_null(self::$instance)) { 46 | self::$instance = new Singleton(); 47 | } 48 | return self::$instance; 49 | } 50 | } 51 | ``` 52 | After: 53 | ```php 54 | class Singleton 55 | { 56 | private function __construct() 57 | { 58 | // private 59 | } 60 | 61 | public static function getInstance() 62 | { 63 | return memorize(function() { 64 | return new Singleton(); 65 | }); 66 | } 67 | } 68 | ``` 69 | 70 | ### Lazy recursive factorial function 71 | ```php 72 | function factorial($number) 73 | { 74 | return memorize(function () use ($number) { 75 | if ($number <= 0) { 76 | throw new \InvalidArgumentException('Number must be natural'); 77 | } 78 | return ($number == 1) 79 | ? 1 80 | : $number * factorial($number - 1); 81 | }); 82 | } 83 | 84 | ``` 85 | See [MemorizeInFunctionTest](tests/Memorize/MemorizeInFunctionTest.php) 86 | 87 | ### Also it correct works in objects. 88 | Before: 89 | ```php 90 | class TableGateway 91 | { 92 | /** 93 | * @var array 94 | */ 95 | private $cacheStatistic = []; 96 | 97 | public function getA($accountId) 98 | { 99 | return $this->calculateStatistic($accountId)['a']; 100 | } 101 | 102 | public function getB($accountId) 103 | { 104 | return $this->calculateStatistic($accountId)['b']; 105 | } 106 | 107 | private function calculateStatistic($accountId) 108 | { 109 | if (!isset($this->cacheStatistic[$accountId])) { 110 | $sql = 'SELECT AVG(price) AS a ...'; 111 | $result = $this->db->getRows($sql, [$accountId]); 112 | $this->cacheStatistic[$accountId] = $result; 113 | } 114 | return $this->cacheStatistic[$accountId]; 115 | } 116 | } 117 | ``` 118 | After: 119 | ```php 120 | class TableGateway 121 | { 122 | public function getA($accountId) 123 | { 124 | return $this->calculateStatistic($accountId)['a']; 125 | } 126 | 127 | public function getB($accountId) 128 | { 129 | return $this->calculateStatistic($accountId)['b']; 130 | } 131 | 132 | private function calculateStatistic($accountId) 133 | { 134 | return memorize(function () use ($accountId) { 135 | $sql = 'SELECT AVG(price) AS a ...'; 136 | return $this->db->getRows($sql, [$accountId]); 137 | }); 138 | } 139 | } 140 | 141 | ``` 142 | See [MemorizeFunctionInObjectTest::testTwoObjects](tests/Memorize/MemorizeInObjectTest.php) 143 | -------------------------------------------------------------------------------- /src/Memorizator.php: -------------------------------------------------------------------------------- 1 | has($contextName, $paramsHash)) { 13 | $returnPair = $storage->get($contextName, $paramsHash); 14 | } else { 15 | $returnPair = self::runLambda($lambda); 16 | if (self::shouldMemorizeResult($returnPair, $memorizeExceptions)) { 17 | $storage->set($contextName, $paramsHash, $returnPair); 18 | } 19 | } 20 | 21 | return self::returnResult($returnPair); 22 | } 23 | 24 | private static function runLambda(callable $lambda) 25 | { 26 | $lambdaReflection = new \ReflectionFunction($lambda); 27 | if ($lambdaReflection->getNumberOfRequiredParameters()) { 28 | throw new RuntimeException('You can memorize only lambda without params'); 29 | } 30 | 31 | $returnValue = null; 32 | $exception = null; 33 | try { 34 | $returnValue = $lambda(); 35 | } catch (\Exception $e) { 36 | $exception = $e; 37 | } 38 | return [$returnValue, $exception]; 39 | } 40 | 41 | private static function shouldMemorizeResult($returnPair, $memorizeExceptions = true) 42 | { 43 | if (!$memorizeExceptions && self::isException($returnPair)) { 44 | return false; 45 | } 46 | return true; 47 | } 48 | 49 | private static function isException(array $returnPair) 50 | { 51 | list($value, $exception) = $returnPair; 52 | return !is_null($exception); 53 | } 54 | 55 | private static function returnResult(array $returnPair) 56 | { 57 | list($value, $exception) = $returnPair; 58 | if ($exception) { 59 | throw $exception; 60 | } else { 61 | return $value; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Storage.php: -------------------------------------------------------------------------------- 1 | data[$name][$paramsHash] = $value; 18 | } 19 | 20 | public function has($name, $paramsHash) 21 | { 22 | list($find, $value) = $this->find($name, $paramsHash); 23 | 24 | return $find; 25 | } 26 | 27 | public function get($name, $paramsHash) 28 | { 29 | list($find, $value) = $this->find($name, $paramsHash); 30 | if ($find) { 31 | return $value; 32 | } else { 33 | throw new RuntimeException('Item not found'); 34 | } 35 | } 36 | 37 | private function find($name, $paramsHash) 38 | { 39 | Utils::assertString($name); 40 | Utils::assertString($paramsHash); 41 | if (array_key_exists($name, $this->data)) { 42 | if (array_key_exists($paramsHash, $this->data[$name])) { 43 | $value = $this->data[$name][$paramsHash]; 44 | return [true, $value]; 45 | } 46 | } 47 | return [false, null]; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Utils.php: -------------------------------------------------------------------------------- 1 | $value) { 33 | $result .= self::stringify($key).'-'.self::stringify($value).','; 34 | } 35 | return $result; 36 | } elseif (is_object($item)) { 37 | if (method_exists($item, 'hash')) { 38 | return get_class($item).':'.$item->hash(); 39 | } else { 40 | throw new \RuntimeException('Only objects with method hash might be stringify'); 41 | } 42 | } 43 | 44 | throw new \Exception('Unknown type '.gettype($item)); 45 | } 46 | 47 | private static function stringifyClosure(\Closure $item) 48 | { 49 | $reflection = new \ReflectionFunction($item); 50 | return 'c:'.$reflection->getFileName().'-'.$reflection->getStartLine().'-'.$reflection->getEndLine(); 51 | } 52 | 53 | //------------------------ 54 | // Asserts from webmozart 55 | 56 | public static function assertString($value) 57 | { 58 | if (!is_string($value)) { 59 | throw new InvalidArgumentException(sprintf( 60 | 'Expected a string. Got: %s', 61 | self::typeToString($value) 62 | )); 63 | } 64 | } 65 | 66 | private static function typeToString($value) 67 | { 68 | return is_object($value) ? get_class($value) : gettype($value); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/functions.php: -------------------------------------------------------------------------------- 1 | getClosureThis(); 21 | if ($that) { 22 | if (!isset($that->_memorizeStorage)) { 23 | $that->_memorizeStorage = new Storage(); 24 | } 25 | return $that->_memorizeStorage; 26 | } else { 27 | global $_globalMemorizeStorage; 28 | if (is_null($_globalMemorizeStorage)) { 29 | $_globalMemorizeStorage = new Storage(); 30 | } 31 | return $_globalMemorizeStorage; 32 | } 33 | }; 34 | 35 | if (is_null($paramsHash)) { 36 | $reflection = new ReflectionFunction($lambda); 37 | $paramsHash = Utils::hash($reflection->getStaticVariables()); 38 | } 39 | $contextName = Utils::stringify($lambda); 40 | 41 | return Memorizator::memorize($contextName, $lambda, $paramsHash, $getStorage($lambda)); 42 | } 43 | -------------------------------------------------------------------------------- /tests/MemorizatorTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(0, $counter); 20 | 21 | // First try 22 | $value = Memorizator::memorize('context1', $lambda, '', $storage); 23 | $this->assertEquals(1, $counter); 24 | $this->assertEquals('value', $value); 25 | 26 | // Second 27 | $value = Memorizator::memorize('context1', $lambda, '', $storage); 28 | $this->assertEquals(1, $counter); 29 | $this->assertEquals('value', $value); 30 | 31 | // Another context 32 | $value = Memorizator::memorize('context2', $lambda, '', $storage); 33 | $this->assertEquals(2, $counter); 34 | $this->assertEquals('value', $value); 35 | 36 | // Another params hash 37 | $value = Memorizator::memorize('context1', $lambda, 'new', $storage); 38 | $this->assertEquals(3, $counter); 39 | $this->assertEquals('value', $value); 40 | 41 | // Another storage 42 | $storage2 = new Storage(); 43 | $value = Memorizator::memorize('context', $lambda, '', $storage2); 44 | $this->assertEquals(4, $counter); 45 | $this->assertEquals('value', $value); 46 | } 47 | 48 | public function testMemorizeException() 49 | { 50 | $originalException = new \DomainException('Test exception'); 51 | $counter = 0; 52 | $lambda = function () use (&$counter, $originalException) { 53 | $counter++; 54 | throw $originalException; 55 | }; 56 | 57 | $storage = new Storage(); 58 | 59 | // First try 60 | $exception = $this->getException(function () use ($lambda, $storage) { 61 | Memorizator::memorize('context', $lambda, '', $storage); 62 | }); 63 | $this->assertEquals(1, $counter); 64 | $this->assertEquals($originalException, $exception); 65 | 66 | // Second 67 | $exception = $this->getException(function () use ($lambda, $storage) { 68 | Memorizator::memorize('context', $lambda, '', $storage); 69 | }); 70 | $this->assertEquals(1, $counter); 71 | $this->assertEquals($originalException, $exception); 72 | } 73 | 74 | public function testNotMemorizeException() 75 | { 76 | $originalException = new \DomainException('Test exception'); 77 | $counter = 0; 78 | $lambda = function () use (&$counter, $originalException) { 79 | $counter++; 80 | throw $originalException; 81 | }; 82 | 83 | $storage = new Storage(); 84 | 85 | // First try 86 | $exception = $this->getException(function () use ($lambda, $storage) { 87 | Memorizator::memorize('context', $lambda, '', $storage, false); 88 | }); 89 | $this->assertEquals(1, $counter); 90 | $this->assertEquals($originalException, $exception); 91 | 92 | // Second 93 | $exception = $this->getException(function () use ($lambda, $storage) { 94 | Memorizator::memorize('context', $lambda, '', $storage, false); 95 | }); 96 | $this->assertEquals(2, $counter); 97 | $this->assertEquals($originalException, $exception); 98 | } 99 | 100 | private function getException(callable $lambda) 101 | { 102 | try { 103 | $lambda(); 104 | } catch (\Exception $e) { 105 | return $e; 106 | } 107 | return null; 108 | } 109 | 110 | 111 | /** 112 | * @expectedException \RuntimeException 113 | */ 114 | public function testIncorrectLambda() 115 | { 116 | $incorrectLambda = function ($a, $b = null) { 117 | return 1; 118 | }; 119 | Memorizator::memorize('context', $incorrectLambda, '', new Storage()); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /tests/Memorize/MemorizeInClosureTest.php: -------------------------------------------------------------------------------- 1 | workLog = []; 13 | } 14 | 15 | public function testMemorizeWithGuessParameters() 16 | { 17 | $function1 = function ($number) { 18 | return memorize(function () use ($number) { 19 | $this->workLog[] = 'a '. $number; 20 | return $number; 21 | }); 22 | }; 23 | 24 | $function2 = function ($number) { 25 | return memorize(function () use ($number) { 26 | $this->workLog[] = 'b '.$number; 27 | return $number; 28 | }); 29 | }; 30 | 31 | $this->assertEquals(5, $function1(5)); 32 | $this->assertEquals(5, $function1(5)); 33 | $this->assertEquals(['a 5'], $this->workLog); 34 | $this->workLog = []; 35 | 36 | $this->assertEquals(5, $function2(5)); 37 | $this->assertEquals(4, $function2(4)); 38 | $this->assertEquals(5, $function2(5)); 39 | $this->assertEquals(4, $function2(4)); 40 | $this->assertEquals(['b 5', 'b 4'], $this->workLog); 41 | } 42 | 43 | public function testCircle() 44 | { 45 | $numbers = []; 46 | foreach (range(1, 100) as $i) { 47 | $number = $i % 10; 48 | $numbers[] = memorize(function () use ($number) { 49 | $this->workLog[] = 'c '. $number; 50 | return $number; 51 | }); 52 | }; 53 | 54 | $this->assertEquals(100, count($numbers)); 55 | $this->assertEquals(10, count($this->workLog)); 56 | $this->assertEquals(10, count(array_unique($numbers))); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/Memorize/MemorizeInFunctionTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([], $workLog); 14 | $this->assertEquals(120, factorial(5)); 15 | $this->assertEquals([ 16 | 'factorial(5)', 17 | 'factorial(4)', 18 | 'factorial(3)', 19 | 'factorial(2)', 20 | 'factorial(1)', 21 | ], $workLog); 22 | 23 | $workLog = []; 24 | 25 | // get from cache 26 | $this->assertEquals(6, factorial(3)); 27 | $this->assertEquals([], $workLog); 28 | 29 | // get first 5 from cache 30 | $this->assertEquals(720, factorial(6)); 31 | $this->assertEquals([ 32 | 'factorial(6)' 33 | ], $workLog); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Memorize/MemorizeInObjectTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([], $object->readWorkLog()); 18 | $this->assertEquals(120, $object->factorial(5)); 19 | $this->assertEquals([ 20 | 'factorial(5)', 21 | 'factorial(4)', 22 | 'factorial(3)', 23 | 'factorial(2)', 24 | 'factorial(1)', 25 | ], $object->readWorkLog()); 26 | 27 | 28 | // get from cache 29 | $this->assertEquals(6, $object->factorial(3)); 30 | $this->assertEquals([], $object->readWorkLog()); 31 | 32 | // get first 5 from cache 33 | $this->assertEquals(720, $object->factorial(6)); 34 | $this->assertEquals([ 35 | 'factorial(6)' 36 | ], $object->readWorkLog()); 37 | } 38 | 39 | public function testTwoObjects() 40 | { 41 | $object1 = new FactorialCalculator(false); 42 | 43 | $this->assertEquals(6, $object1->factorial(3)); 44 | $this->assertEquals([ 45 | 'factorial(3)', 46 | 'factorial(2)', 47 | 'factorial(1)', 48 | ], $object1->readWorkLog()); 49 | 50 | $object2 = new FactorialCalculator(true); 51 | $this->assertEquals(945, $object2->factorial(9)); 52 | $this->assertEquals([ 53 | 'factorial(9)', 54 | 'factorial(7)', 55 | 'factorial(5)', 56 | 'factorial(3)', 57 | 'factorial(1)', 58 | ], $object2->readWorkLog()); 59 | } 60 | 61 | 62 | /** 63 | * @expectedException RuntimeException 64 | */ 65 | public function testIncorrectMemorizeObjects() 66 | { 67 | $day = new \DateTime('2015-01-01'); 68 | $this->assertEquals(3, $this->incorrectAddDaysToTime($day, 2)); 69 | } 70 | 71 | private function incorrectAddDaysToTime(\DateTime $time, $days) 72 | { 73 | return memorize(function () use ($time, $days) { 74 | return $time->add(new \DateInterval($days.' days')); 75 | }); 76 | } 77 | 78 | public function testCorrectMemorizeObjectsParams() 79 | { 80 | $counter1 = 0; 81 | $day = new \DateTime('2015-01-01'); 82 | $this->assertEquals(new \DateTime('2015-01-03'), $this->correctAddDaysToTime($day, 2, $counter1)); 83 | $this->assertEquals(new \DateTime('2015-01-03'), $this->correctAddDaysToTime($day, 2, $counter1)); 84 | $this->assertEquals(1, $counter1); 85 | 86 | $counter2 = 0; 87 | $day2 = new \DateTime('2015-10-01'); 88 | $this->assertEquals(new \DateTime('2015-10-04'), $this->correctAddDaysToTime($day2, 3, $counter2)); 89 | $this->assertEquals(new \DateTime('2015-10-04'), $this->correctAddDaysToTime($day2, 3, $counter2)); 90 | $this->assertEquals(1, $counter2); 91 | } 92 | 93 | private function correctAddDaysToTime(\DateTime $time, $days, &$counter) 94 | { 95 | return memorize(function () use ($time, $days, &$counter) { 96 | $counter++; 97 | $newTime = clone $time; 98 | return $newTime->add(DateInterval::createFromDateString($days.' days')); 99 | }, $time->getTimestamp().'-'.$days); 100 | } 101 | 102 | public function testMemorizeMethodWithoutParams() 103 | { 104 | $numbers = []; 105 | foreach (range(1, 100) as $try) { 106 | $numbers[] = $this->getRandNumber(); 107 | } 108 | $this->assertEquals(1, count(array_unique($numbers))); 109 | } 110 | 111 | private function getRandNumber() 112 | { 113 | return memorize(function () { 114 | return rand(1, 1000000); 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /tests/Memorize/MemorizeInStaticTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([], StaticFactorialCalculator::readWorkLog()); 15 | $this->assertEquals(120, StaticFactorialCalculator::factorial(5)); 16 | $this->assertEquals([ 17 | 'factorial(5)', 18 | 'factorial(4)', 19 | 'factorial(3)', 20 | 'factorial(2)', 21 | 'factorial(1)', 22 | ], StaticFactorialCalculator::readWorkLog()); 23 | 24 | 25 | // get from cache 26 | $this->assertEquals(6, StaticFactorialCalculator::factorial(3)); 27 | $this->assertEquals([], StaticFactorialCalculator::readWorkLog()); 28 | 29 | // get first 5 from cache 30 | $this->assertEquals(720, StaticFactorialCalculator::factorial(6)); 31 | $this->assertEquals([ 32 | 'factorial(6)' 33 | ], StaticFactorialCalculator::readWorkLog()); 34 | } 35 | 36 | public function testSingleton() 37 | { 38 | $first = Singleton::getInstance(); 39 | $second = Singleton::getInstance(); 40 | $this->assertSame($first, $second); 41 | $this->assertInstanceOf('Solodkiy\Memorize\Support\Singleton', $first); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/StorageTest.php: -------------------------------------------------------------------------------- 1 | assertFalse($storage->has('item1', '')); 14 | $this->assertFalse($storage->has('item1', 'param1')); 15 | $this->assertFalse($storage->has('item2', '')); 16 | 17 | $storage->set('item1', '', 'value'); 18 | 19 | $this->assertTrue($storage->has('item1', '')); 20 | $this->assertFalse($storage->has('item2', '')); 21 | $this->assertFalse($storage->has('item1', 'param1')); 22 | 23 | $storage->set('item1', 'param1', 'value'); 24 | 25 | $this->assertTrue($storage->has('item1', '')); 26 | $this->assertFalse($storage->has('item2', '')); 27 | $this->assertTrue($storage->has('item1', 'param1')); 28 | } 29 | 30 | public function testGetSimple() 31 | { 32 | $storage = new Storage(); 33 | $storage->set('item1', 'param1', 'value'); 34 | $this->assertEquals('value', $storage->get('item1', 'param1')); 35 | } 36 | 37 | /** 38 | * @expectedException InvalidArgumentException 39 | */ 40 | public function testHasParamHashInt() 41 | { 42 | $storage = new Storage(); 43 | $storage->has('item1', 100); 44 | /* 45 | $this->assertFalse($storage->has('item1', 100)); 46 | $storage->set('item1', 100, 'value'); 47 | $this->assertTrue($storage->has('item1', 100)); 48 | $this->assertFalse($storage->has('item1', 101)); 49 | */ 50 | } 51 | 52 | /** 53 | * @expectedException \RuntimeException 54 | */ 55 | public function testGetNotExists() 56 | { 57 | $storage = new Storage(); 58 | $this->assertEquals('value', $storage->get('item1', '')); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/Support/FactorialCalculator.php: -------------------------------------------------------------------------------- 1 | doubleMode = $doubleMode; 22 | } 23 | 24 | public function factorial($number) 25 | { 26 | return memorize(function () use ($number) { 27 | $this->workLog[] = 'factorial('.$number.')'; 28 | 29 | if ($number <= 0) { 30 | throw new \InvalidArgumentException('Number must be natural'); 31 | } 32 | if ($this->doubleMode) { 33 | if ($number % 2 == 0) { 34 | throw new \InvalidArgumentException('Number must be odd'); 35 | } 36 | return ($number == 1) 37 | ? 1 38 | : $number * $this->factorial($number - 2); 39 | } else { 40 | return ($number == 1) 41 | ? 1 42 | : $number * $this->factorial($number - 1); 43 | } 44 | }); 45 | } 46 | 47 | /** 48 | * @return array 49 | */ 50 | public function readWorkLog() 51 | { 52 | $log = $this->workLog; 53 | $this->workLog = []; 54 | return $log; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Support/Singleton.php: -------------------------------------------------------------------------------- 1 | val = $val; 16 | } 17 | 18 | /** 19 | * @return mixed 20 | */ 21 | public function getVal() 22 | { 23 | return $this->val; 24 | } 25 | 26 | public function hash() 27 | { 28 | return md5($this->val); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Support/functions.php: -------------------------------------------------------------------------------- 1 | assertEquals($string, Utils::stringify($item)); 18 | } 19 | 20 | /** 21 | * @expectedException \RuntimeException 22 | */ 23 | public function testStringifyResource() 24 | { 25 | Utils::stringify(fopen('php://temp', 'r')); 26 | } 27 | 28 | public function testStringifyLambda() 29 | { 30 | $string = Utils::stringify(function () { 31 | return 1; 32 | }); 33 | 34 | $this->assertEquals('c:'.__FILE__.'-30-32', $string); 35 | } 36 | 37 | /** 38 | * @expectedException \RuntimeException 39 | */ 40 | public function testStringifyCustomObject() 41 | { 42 | Utils::stringify(new \DateTime('now')); 43 | } 44 | 45 | public function hashProvider() 46 | { 47 | return [ 48 | 'int' => ['i:15;', 15], 49 | 'float' => ['d:15.5;', 15.5], 50 | 'string' => ['s:3:"str";', 'str'], 51 | 'empty string' => ['s:0:"";', ''], 52 | 'null' => ['N;', null], 53 | 'false' => ['b:0;', false], 54 | 'true' => ['b:1;', true], 55 | 'zero' => ['i:0;', 0], 56 | 'empty array' => ['a:', []], 57 | 'array' => ['a:i:0;-i:1;,i:1;-i:2;,i:2;-i:3;,', [1,2,3]], 58 | 'object' => ['Solodkiy\Memorize\Support\ValueObject:eccbc87e4b5ce2fe28308fd9f2a7baf3', new ValueObject(3)], 59 | 'object2' => ['Solodkiy\Memorize\Support\ValueObject:a87ff679a2f3e71d9181a67b7542122c', new ValueObject(4)], 60 | 'array objects' => ['a:i:0;-Solodkiy\Memorize\Support\ValueObject:c4ca4238a0b923820dcc509a6f75849b,i:1;-Solodkiy\Memorize\Support\ValueObject:c81e728d9d4c2f636f067f89cc14862c,', [new ValueObject(1), new ValueObject(2)]], 61 | 'assoc array' => ['a:s:1:"a";-i:1;,s:1:"b";-Solodkiy\Memorize\Support\ValueObject:e4da3b7fbbce2345d7772b0674a318d5,', ['a' => 1, 'b' => new ValueObject(5)]], 62 | ]; 63 | } 64 | 65 | 66 | public function testHash() 67 | { 68 | $items = [0, null, false, '']; 69 | $hashes = array_map(function ($item) { 70 | return Utils::hash($item); 71 | }, $items); 72 | 73 | $this->assertEquals(count(array_unique($hashes)), 4) ; 74 | } 75 | 76 | public function testHashObjects() 77 | { 78 | $this->assertEquals(Utils::hash(new ValueObject(1)), Utils::hash(new ValueObject(1))); 79 | } 80 | } 81 | --------------------------------------------------------------------------------