diff --git a/phalcon/Storage/Adapter/AbstractAdapter.zep b/phalcon/Storage/Adapter/AbstractAdapter.zep index cca8cb1a209..4a99c4dac2c 100644 --- a/phalcon/Storage/Adapter/AbstractAdapter.zep +++ b/phalcon/Storage/Adapter/AbstractAdapter.zep @@ -142,7 +142,7 @@ abstract class AbstractAdapter implements AdapterInterface return this->defaultTtl; } - if ttl instanceof \DateInterval { + if typeof ttl === "object" && ttl instanceof \DateInterval { let dateTime = new \DateTime("@0"); return dateTime->add(ttl)->getTimestamp(); } diff --git a/phalcon/Storage/Adapter/Redis.zep b/phalcon/Storage/Adapter/Redis.zep index bc764fa3be3..84defbd5c58 100644 --- a/phalcon/Storage/Adapter/Redis.zep +++ b/phalcon/Storage/Adapter/Redis.zep @@ -52,19 +52,11 @@ class Redis extends AbstractAdapter */ public function clear() -> bool { - var key, keys, result; - - let result = true, - keys = this->getKeys(); + var connection; - for key in keys { - let key = str_replace(this->prefix, "", key); - if !this->delete(key) { - let result = false; - } - } + let connection = this->getAdapter(); - return result; + return connection->flushDB(); } /** diff --git a/phalcon/Storage/Adapter/Stream.zep b/phalcon/Storage/Adapter/Stream.zep new file mode 100644 index 00000000000..ee53c8227aa --- /dev/null +++ b/phalcon/Storage/Adapter/Stream.zep @@ -0,0 +1,239 @@ + +/** + * This file is part of the Phalcon Framework. + * + * (c) Phalcon Team + * + * For the full copyright and license information, please view the LICENSE.txt + * file that was distributed with this source code. + */ + +namespace Phalcon\Storage\Adapter; + +use Phalcon\Helper\Arr; +use Phalcon\Helper\Str; +use Phalcon\Storage\Adapter\AbstractAdapter; +use Phalcon\Storage\Exception; +use Phalcon\Storage\Serializer\SerializerInterface; + +/** + * Phalcon\Storage\Adapter\Stream + * + * Stream adapter + */ +class Stream extends AbstractAdapter +{ + /** + * @var string + */ + protected cacheDir = ""; + + /** + * @var array + */ + protected options = []; + + /** + * Constructor + */ + public function __construct(array! options = []) + { + var cacheDir; + string className; + + let cacheDir = Arr::get(options, "cacheDir", ""); + if empty cacheDir { + throw new Exception("The 'cacheDir' must be specified in the options"); + } + + parent::__construct(options); + + /** + * Lets set some defaults and options here + */ + let this->cacheDir = rtrim(cacheDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, + this->prefix = "phstrm-", + this->options = options; + + let className = "Phalcon\\Storage\\Serializer\\" . this->defaultSerializer; + let this->serializer = new {className}(); + } + + /** + * Flushes/clears the cache + */ + public function clear() -> bool + { + var dirIterator, iterator, file, mask, result; + + let result = true, + mask = Str::folderFromFile(this->prefix), + dirIterator = new \RecursiveDirectoryIterator(this->cacheDir), + iterator = new \RecursiveIteratorIterator( + dirIterator, + \RecursiveIteratorIterator::CHILD_FIRST + ); + + for file in iterator { + if file->isDir && Str::startsWith(file->getPathname(), this->cacheDir . this->prefix) { + if !unlink(file->getPathname()) { + let result = false; + } + } + } + + return result; + } + + /** + * Decrements a stored number + */ + public function decrement(string! key, int value = 1) -> int | bool + { + var prefixedKey; + + let prefixedKey = this->getPrefixedKey(key); + + return value; + } + + /** + * Reads data from the adapter + */ + public function delete(string! key) -> bool + { + var exists; + + let key = this->getKey(key), + exists = file_exists(this->cacheDir . key); + + if !exists { + return false; + } + + return unlink(this->cacheDir . key); + } + + /** + * Reads data from the adapter + */ + public function get(string! key, var defaultValue = null) -> var + { + var content, payload; + + let key = this->getKey(key) . key, + payload = file_get_contents(this->cacheDir . key); + + if typeof payload !== "string" { + return defaultValue; + } + + let payload = json_decode(payload, true); + + if json_last_error() !== JSON_ERROR_NONE { + return defaultValue; + } + + if this->isExpired(payload) { + return defaultValue; + } + + let content = Arr::get(payload, "content", null); + + return this->getUnserializedData(content, defaultValue); + } + + /** + * Returns the already connected adapter or connects to the Memcached + * server(s) + */ + public function getAdapter() -> var + { + return this->adapter; + } + + /** + * Stores data in the adapter + */ + public function getKeys() -> array + { + return []; + } + + /** + * Checks if an element exists in the cache and is not expired + */ + public function has(string! key) -> bool + { + var exists, payload; + + let key = this->getKey(key), + exists = file_exists(this->cacheDir . key); + + if !exists { + return false; + } + + let payload = file_get_contents(this->cacheDir . key), + payload = json_decode(payload); + + return !this->isExpired(payload); + } + + /** + * Increments a stored number + */ + public function increment(string! key, int value = 1) -> int | bool + { + var prefixedKey; + + let prefixedKey = this->getPrefixedKey(key); + + return apcu_inc(prefixedKey, value); + } + + /** + * Stores data in the adapter + */ + public function set(string! key, var value, var ttl = null) -> bool + { + var folder; + array payload; + + let payload = [ + "created" : time(), + "ttl" : this->getTtl(ttl), + "content" : this->getSerializedData(value) + ], + payload = json_encode(payload), + folder = this->getKey(key), + key = folder . key; + + if !is_dir(this->cacheDir . folder) { + mkdir(this->cacheDir . folder, 0777, true); + } + + return false !== file_put_contents(this->cacheDir . key, payload); + } + + /** + * Returns the calculated folder with the key + */ + private function getKey(string! key) -> string + { + return Str::folderFromFile(this->prefix . key); + } + + /** + * Returns if the cache has expired for this item or not + */ + private function isExpired(array! payload) -> bool + { + var created, ttl; + + let created = Arr::get(payload, "created", time()), + ttl = Arr::get(payload, "ttl", 3600); + + return (created + ttl) < time(); + } +} diff --git a/tests/unit/Storage/Adapter/Stream/ClearCest.php b/tests/unit/Storage/Adapter/Stream/ClearCest.php new file mode 100644 index 00000000000..c0d228740e4 --- /dev/null +++ b/tests/unit/Storage/Adapter/Stream/ClearCest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE.txt + * file that was distributed with this source code. + */ + +namespace Phalcon\Test\Unit\Storage\Adapter\Stream; + +use UnitTester; + +/** + * Class ClearCest + */ +class ClearCest +{ + /** + * Tests Phalcon\Storage\Adapter\Stream :: clear() + * + * @param UnitTester $I + * + * @author Phalcon Team + * @since 2019-04-24 + */ + public function storageAdapterStreamClear(UnitTester $I) + { + $I->wantToTest('Storage\Adapter\Stream - clear()'); + + $I->skipTest('Need implementation'); + } +} diff --git a/tests/unit/Storage/Adapter/Stream/ConstructCest.php b/tests/unit/Storage/Adapter/Stream/ConstructCest.php new file mode 100644 index 00000000000..809d0355baa --- /dev/null +++ b/tests/unit/Storage/Adapter/Stream/ConstructCest.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE.txt + * file that was distributed with this source code. + */ + +namespace Phalcon\Test\Unit\Storage\Adapter\Stream; + +use Phalcon\Storage\Adapter\AdapterInterface; +use Phalcon\Storage\Adapter\Stream; +use Phalcon\Storage\Exception; +use UnitTester; +use function outputFolder; + +/** + * Class ConstructCest + */ +class ConstructCest +{ + /** + * Tests Phalcon\Storage\Adapter\Stream :: __construct() + * + * @param UnitTester $I + * + * @author Phalcon Team + * @since 2019-04-24 + */ + public function storageAdapterStreamConstruct(UnitTester $I) + { + $I->wantToTest('Storage\Adapter\Stream - __construct()'); + $adapter = new Stream(['cacheDir' => outputFolder()]); + + $class = Stream::class; + $I->assertInstanceOf($class, $adapter); + + $class = AdapterInterface::class; + $I->assertInstanceOf($class, $adapter); + } + + /** + * Tests Phalcon\Storage\Adapter\Stream :: __construct() - exception + * + * @param UnitTester $I + * + * @author Phalcon Team + * @since 2019-04-24 + */ + public function storageAdapterStreamConstructException(UnitTester $I) + { + $I->wantToTest('Storage\Adapter\Stream - __construct() - exception'); + + $I->expectThrowable( + new Exception("The 'cacheDir' must be specified in the options"), + function () { + $adapter = new Stream(); + } + ); + } +} diff --git a/tests/unit/Storage/Adapter/Stream/DecrementCest.php b/tests/unit/Storage/Adapter/Stream/DecrementCest.php new file mode 100644 index 00000000000..41408a2aaad --- /dev/null +++ b/tests/unit/Storage/Adapter/Stream/DecrementCest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE.txt + * file that was distributed with this source code. + */ + +namespace Phalcon\Test\Unit\Storage\Adapter\Stream; + +use UnitTester; + +/** + * Class DecrementCest + */ +class DecrementCest +{ + /** + * Tests Phalcon\Storage\Adapter\Stream :: decrement() + * + * @param UnitTester $I + * + * @author Phalcon Team + * @since 2019-04-24 + */ + public function storageAdapterStreamDecrement(UnitTester $I) + { + $I->wantToTest('Storage\Adapter\Stream - decrement()'); + + $I->skipTest('Need implementation'); + } +} diff --git a/tests/unit/Storage/Adapter/Stream/DeleteCest.php b/tests/unit/Storage/Adapter/Stream/DeleteCest.php new file mode 100644 index 00000000000..c07283768a4 --- /dev/null +++ b/tests/unit/Storage/Adapter/Stream/DeleteCest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE.txt + * file that was distributed with this source code. + */ + +namespace Phalcon\Test\Unit\Storage\Adapter\Stream; + +use UnitTester; + +/** + * Class DeleteCest + */ +class DeleteCest +{ + /** + * Tests Phalcon\Storage\Adapter\Stream :: delete() + * + * @param UnitTester $I + * + * @author Phalcon Team + * @since 2019-04-24 + */ + public function storageAdapterStreamDelete(UnitTester $I) + { + $I->wantToTest('Storage\Adapter\Stream - delete()'); + + $I->skipTest('Need implementation'); + } +} diff --git a/tests/unit/Storage/Adapter/Stream/GetAdapterCest.php b/tests/unit/Storage/Adapter/Stream/GetAdapterCest.php new file mode 100644 index 00000000000..9a19e4b33a0 --- /dev/null +++ b/tests/unit/Storage/Adapter/Stream/GetAdapterCest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE.txt + * file that was distributed with this source code. + */ + +namespace Phalcon\Test\Unit\Storage\Adapter\Stream; + +use Phalcon\Storage\Adapter\Stream; +use UnitTester; + +/** + * Class GetAdapterCest + */ +class GetAdapterCest +{ + /** + * Tests Phalcon\Storage\Adapter\Stream :: getAdapter() + * + * @param UnitTester $I + * + * @author Phalcon Team + * @since 2019-04-24 + */ + public function storageAdapterStreamGetAdapter(UnitTester $I) + { + $I->wantToTest('Storage\Adapter\Stream - getAdapter()'); + + $adapter = new Stream(['cacheDir' => '/tmp']); + + $actual = $adapter->getAdapter(); + $I->assertNull($actual); + } +} diff --git a/tests/unit/Storage/Adapter/Stream/GetKeysCest.php b/tests/unit/Storage/Adapter/Stream/GetKeysCest.php new file mode 100644 index 00000000000..40b9da5923f --- /dev/null +++ b/tests/unit/Storage/Adapter/Stream/GetKeysCest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE.txt + * file that was distributed with this source code. + */ + +namespace Phalcon\Test\Unit\Storage\Adapter\Stream; + +use UnitTester; + +/** + * Class GetKeysCest + */ +class GetKeysCest +{ + /** + * Tests Phalcon\Storage\Adapter\Stream :: getKeys() + * + * @param UnitTester $I + * + * @author Phalcon Team + * @since 2019-04-24 + */ + public function storageAdapterStreamGetKeys(UnitTester $I) + { + $I->wantToTest('Storage\Adapter\Stream - getKeys()'); + + $I->skipTest('Need implementation'); + } +} diff --git a/tests/unit/Storage/Adapter/Stream/GetPrefixCest.php b/tests/unit/Storage/Adapter/Stream/GetPrefixCest.php new file mode 100644 index 00000000000..384fb144378 --- /dev/null +++ b/tests/unit/Storage/Adapter/Stream/GetPrefixCest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE.txt + * file that was distributed with this source code. + */ + +namespace Phalcon\Test\Unit\Storage\Adapter\Stream; + +use UnitTester; + +/** + * Class GetPrefixCest + */ +class GetPrefixCest +{ + /** + * Tests Phalcon\Storage\Adapter\Stream :: getPrefix() + * + * @param UnitTester $I + * + * @author Phalcon Team + * @since 2019-04-24 + */ + public function storageAdapterStreamGetPrefix(UnitTester $I) + { + $I->wantToTest('Storage\Adapter\Stream - getPrefix()'); + + $I->skipTest('Need implementation'); + } +} diff --git a/tests/unit/Storage/Adapter/Stream/GetSetCest.php b/tests/unit/Storage/Adapter/Stream/GetSetCest.php new file mode 100644 index 00000000000..4939628512f --- /dev/null +++ b/tests/unit/Storage/Adapter/Stream/GetSetCest.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE.txt + * file that was distributed with this source code. + */ + +namespace Phalcon\Test\Unit\Storage\Adapter\Stream; + +use Phalcon\Storage\Adapter\Stream; +use UnitTester; +use function outputFolder; + +/** + * Class GetSetCest + */ +class GetSetCest +{ + /** + * Tests Phalcon\Storage\Adapter\Stream :: set() + * + * @param UnitTester $I + * + * @author Phalcon Team + * @since 2019-04-24 + */ + public function storageAdapterStreamSet(UnitTester $I) + { + $I->wantToTest('Storage\Adapter\Stream - get()/set()'); + + $adapter = new Stream(['cacheDir' => outputFolder()]); + + $data = 'Phalcon Framework'; + $result = $adapter->set('test-key', $data); + $I->assertTrue($result); + + $target = outputFolder() . 'ph/st/rm/-t/es/t-/k/'; + $I->amInPath($target); + $I->openFile('test-key'); + $expected = '"ttl":3600,"content":"s:17:\"Phalcon Framework\";'; + $I->seeInThisFile($expected); + $I->safeDeleteFile($target . 'test-key'); + } + + /** + * Tests Phalcon\Storage\Adapter\Stream :: get()/set() + * + * @param UnitTester $I + * + * @author Phalcon Team + * @since 2019-04-24 + */ + public function storageAdapterStreamGet(UnitTester $I) + { + $I->wantToTest('Storage\Adapter\Stream - get()/set()'); + + $adapter = new Stream(['cacheDir' => outputFolder()]); + + $target = outputFolder() . 'ph/st/rm/-t/es/t-/k/'; + $data = 'Phalcon Framework'; + $result = $adapter->set('test-key', $data); + $I->assertTrue($result); + +// $contents = '{"created":1556068288,"ttl":3600,"content":"s:17:\"Phalcon Framework\";}'; +// $actual = file_put_contents($target . 'test-key', $contents); +// $I->assertNotFalse($actual); + + $expected = 'Phalcon Framework'; + $actual = $adapter->get('test-key'); + $I->assertNotNull($actual); + $I->assertEquals($expected, $actual); + + $I->safeDeleteFile($target . 'test-key'); + } +} diff --git a/tests/unit/Storage/Adapter/Stream/HasCest.php b/tests/unit/Storage/Adapter/Stream/HasCest.php new file mode 100644 index 00000000000..9d3833dbe86 --- /dev/null +++ b/tests/unit/Storage/Adapter/Stream/HasCest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE.txt + * file that was distributed with this source code. + */ + +namespace Phalcon\Test\Unit\Storage\Adapter\Stream; + +use Phalcon\Storage\Adapter\Stream; +use UnitTester; +use function outputFolder; +use function uniqid; + +/** + * Class HasCest + */ +class HasCest +{ + /** + * Tests Phalcon\Storage\Adapter\Stream :: has() + * + * @param UnitTester $I + * + * @author Phalcon Team + * @since 2019-04-24 + */ + public function storageAdapterStreamHas(UnitTester $I) + { + $I->wantToTest('Storage\Adapter\Stream - has()'); + $adapter = new Stream(['cacheDir' => outputFolder()]); + + $key = uniqid(); + + $actual = $adapter->has($key); + $I->assertFalse($actual); + + $adapter->set($key, 'test'); + $actual = $adapter->has($key); + $I->assertTrue($actual); + } +} diff --git a/tests/unit/Storage/Adapter/Stream/IncrementCest.php b/tests/unit/Storage/Adapter/Stream/IncrementCest.php new file mode 100644 index 00000000000..d24f97d03b1 --- /dev/null +++ b/tests/unit/Storage/Adapter/Stream/IncrementCest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE.txt + * file that was distributed with this source code. + */ + +namespace Phalcon\Test\Unit\Storage\Adapter\Stream; + +use UnitTester; + +/** + * Class IncrementCest + */ +class IncrementCest +{ + /** + * Tests Phalcon\Storage\Adapter\Stream :: increment() + * + * @param UnitTester $I + * + * @author Phalcon Team + * @since 2019-04-24 + */ + public function storageAdapterStreamIncrement(UnitTester $I) + { + $I->wantToTest('Storage\Adapter\Stream - increment()'); + + $I->skipTest('Need implementation'); + } +}