Browse Source

Merge pull request #12741 from cakephp/array-cache

3.next - Add ArrayCache engine
Mark Story 7 years ago
parent
commit
f703dbcdc5
2 changed files with 464 additions and 0 deletions
  1. 183 0
      src/Cache/Engine/ArrayEngine.php
  2. 281 0
      tests/TestCase/Cache/Engine/ArrayEngineTest.php

+ 183 - 0
src/Cache/Engine/ArrayEngine.php

@@ -0,0 +1,183 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ * @link          https://cakephp.org CakePHP(tm) Project
+ * @since         3.7.0
+ * @license       https://opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Cache\Engine;
+
+use Cake\Cache\CacheEngine;
+
+/**
+ * Array storage engine for cache.
+ *
+ * Not actually a persistent cache engine. All data is only
+ * stored in memory for the duration of a single process. While not
+ * useful in production settings this engine can be useful in tests
+ * or console tools where you don't want the overhead of interacting
+ * with a cache servers, but want the work saving properties a cache
+ * provides.
+ */
+class ArrayEngine extends CacheEngine
+{
+    /**
+     * Cached data.
+     *
+     * Structured as [key => [exp => expiration, val => value]]
+     *
+     * @var array
+     */
+    protected $data = [];
+
+    /**
+     * Write data for key into cache
+     *
+     * @param string $key Identifier for the data
+     * @param mixed $value Data to be cached
+     * @return bool True if the data was successfully cached, false on failure
+     */
+    public function write($key, $value)
+    {
+        $key = $this->_key($key);
+        $expires = time() + $this->_config['duration'];
+        $this->data[$key] = ['exp' => $expires, 'val' => $value];
+
+        return true;
+    }
+
+    /**
+     * Read a key from the cache
+     *
+     * @param string $key Identifier for the data
+     * @return mixed The cached data, or false if the data doesn't exist,
+     *   has expired, or if there was an error fetching it
+     */
+    public function read($key)
+    {
+        $key = $this->_key($key);
+        if (!isset($this->data[$key])) {
+            return false;
+        }
+        $data = $this->data[$key];
+
+        // Check expiration
+        $now = time();
+        if ($data['exp'] <= $now) {
+            unset($this->data[$key]);
+
+            return false;
+        }
+
+        return $data['val'];
+    }
+
+    /**
+     * Increments the value of an integer cached key
+     *
+     * @param string $key Identifier for the data
+     * @param int $offset How much to increment
+     * @return bool|int New incremented value, false otherwise
+     */
+    public function increment($key, $offset = 1)
+    {
+        if (!$this->read($key)) {
+            $this->write($key, 0);
+        }
+        $key = $this->_key($key);
+        $this->data[$key]['val'] += $offset;
+
+        return $this->data[$key]['val'];
+    }
+
+    /**
+     * Decrements the value of an integer cached key
+     *
+     * @param string $key Identifier for the data
+     * @param int $offset How much to subtract
+     * @return bool|int New decremented value, false otherwise
+     */
+    public function decrement($key, $offset = 1)
+    {
+        if (!$this->read($key)) {
+            $this->write($key, 0);
+        }
+        $key = $this->_key($key);
+        $this->data[$key]['val'] -= $offset;
+
+        return $this->data[$key]['val'];
+    }
+
+    /**
+     * Delete a key from the cache
+     *
+     * @param string $key Identifier for the data
+     * @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed
+     */
+    public function delete($key)
+    {
+        $key = $this->_key($key);
+        unset($this->data[$key]);
+
+        return true;
+    }
+
+    /**
+     * Delete all keys from the cache. This will clear every cache config using APC.
+     *
+     * @param bool $check Unused argument required by interface.
+     * @return bool True Returns true.
+     */
+    public function clear($check)
+    {
+        $this->data = [];
+
+        return true;
+    }
+
+    /**
+     * Returns the `group value` for each of the configured groups
+     * If the group initial value was not found, then it initializes
+     * the group accordingly.
+     *
+     * @return array
+     */
+    public function groups()
+    {
+        $result = [];
+        foreach ($this->_config['groups'] as $group) {
+            $key = $this->_config['prefix'] . $group;
+            if (!isset($this->data[$key])) {
+                $this->data[$key] = ['exp' => PHP_INT_MAX, 'val' => 1];
+            }
+            $value = $this->data[$key]['val'];
+            $result[] = $group . $value;
+        }
+
+        return $result;
+    }
+
+    /**
+     * Increments the group value to simulate deletion of all keys under a group
+     * old values will remain in storage until they expire.
+     *
+     * @param string $group The group to clear.
+     * @return bool success
+     */
+    public function clearGroup($group)
+    {
+        $key = $this->_config['prefix'] . $group;
+        if (isset($this->data[$key])) {
+            $this->data[$key]['val'] += 1;
+        }
+
+        return true;
+    }
+}

+ 281 - 0
tests/TestCase/Cache/Engine/ArrayEngineTest.php

@@ -0,0 +1,281 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ * @link          https://cakephp.org CakePHP(tm) Project
+ * @since         3.7.0
+ * @license       https://opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Test\TestCase\Cache\Engine;
+
+use Cake\Cache\Cache;
+use Cake\TestSuite\TestCase;
+
+/**
+ * ArrayEngineTest class
+ */
+class ArrayEngineTest extends TestCase
+{
+    /**
+     * setUp method
+     *
+     * @return void
+     */
+    public function setUp()
+    {
+        parent::setUp();
+
+        Cache::enable();
+        $this->_configCache();
+        Cache::clearAll();
+    }
+
+    /**
+     * tearDown method
+     *
+     * @return void
+     */
+    public function tearDown()
+    {
+        parent::tearDown();
+        Cache::drop('array');
+        Cache::drop('array_groups');
+    }
+
+    /**
+     * Helper method for testing.
+     *
+     * @param array $config
+     * @return void
+     */
+    protected function _configCache($config = [])
+    {
+        $defaults = [
+            'className' => 'Array',
+            'prefix' => 'cake_',
+            'warnOnWriteFailures' => true,
+        ];
+        Cache::drop('array');
+        Cache::setConfig('array', array_merge($defaults, $config));
+    }
+
+    /**
+     * testReadAndWriteCache method
+     *
+     * @return void
+     */
+    public function testReadAndWriteCache()
+    {
+        $this->_configCache(['duration' => 1]);
+
+        $result = Cache::read('test', 'array');
+        $expecting = '';
+        $this->assertEquals($expecting, $result);
+
+        $data = 'this is a test of the emergency broadcasting system';
+        $result = Cache::write('test', $data, 'array');
+        $this->assertTrue($result);
+
+        $result = Cache::read('test', 'array');
+        $expecting = $data;
+        $this->assertEquals($expecting, $result);
+
+        Cache::delete('test', 'array');
+    }
+
+    /**
+     * testExpiry method
+     *
+     * @return void
+     */
+    public function testExpiry()
+    {
+        $this->_configCache(['duration' => 1]);
+
+        $result = Cache::read('test', 'array');
+        $this->assertFalse($result);
+
+        $data = 'this is a test of the emergency broadcasting system';
+        $result = Cache::write('other_test', $data, 'array');
+        $this->assertTrue($result);
+
+        sleep(2);
+        $result = Cache::read('other_test', 'array');
+        $this->assertFalse($result);
+    }
+
+    /**
+     * testDeleteCache method
+     *
+     * @return void
+     */
+    public function testDeleteCache()
+    {
+        $data = 'this is a test of the emergency broadcasting system';
+        $result = Cache::write('delete_test', $data, 'array');
+        $this->assertTrue($result);
+
+        $result = Cache::delete('delete_test', 'array');
+        $this->assertTrue($result);
+    }
+
+    /**
+     * testDecrement method
+     *
+     * @return void
+     */
+    public function testDecrement()
+    {
+        $result = Cache::write('test_decrement', 5, 'array');
+        $this->assertTrue($result);
+
+        $result = Cache::decrement('test_decrement', 1, 'array');
+        $this->assertEquals(4, $result);
+
+        $result = Cache::read('test_decrement', 'array');
+        $this->assertEquals(4, $result);
+
+        $result = Cache::decrement('test_decrement', 2, 'array');
+        $this->assertEquals(2, $result);
+
+        $result = Cache::read('test_decrement', 'array');
+        $this->assertEquals(2, $result);
+    }
+
+    /**
+     * testIncrement method
+     *
+     * @return void
+     */
+    public function testIncrement()
+    {
+        $result = Cache::write('test_increment', 5, 'array');
+        $this->assertTrue($result);
+
+        $result = Cache::increment('test_increment', 1, 'array');
+        $this->assertSame(6, $result);
+
+        $result = Cache::read('test_increment', 'array');
+        $this->assertSame(6, $result);
+
+        $result = Cache::increment('test_increment', 2, 'array');
+        $this->assertSame(8, $result);
+
+        $result = Cache::read('test_increment', 'array');
+        $this->assertSame(8, $result);
+    }
+
+    /**
+     * test the clearing of cache keys
+     *
+     * @return void
+     */
+    public function testClear()
+    {
+        Cache::write('some_value', 'value', 'array');
+
+        $result = Cache::clear(false, 'array');
+        $this->assertTrue($result);
+        $this->assertFalse(Cache::read('some_value', 'array'));
+    }
+
+    /**
+     * Tests that configuring groups for stored keys return the correct values when read/written
+     * Shows that altering the group value is equivalent to deleting all keys under the same
+     * group
+     *
+     * @return void
+     */
+    public function testGroupsReadWrite()
+    {
+        Cache::setConfig('array_groups', [
+            'engine' => 'array',
+            'duration' => 30,
+            'groups' => ['group_a', 'group_b'],
+            'prefix' => 'test_',
+            'warnOnWriteFailures' => true,
+        ]);
+        $this->assertTrue(Cache::write('test_groups', 'value', 'array_groups'));
+        $this->assertEquals('value', Cache::read('test_groups', 'array_groups'));
+
+        Cache::clearGroup('group_a', 'array_groups');
+        $this->assertFalse(Cache::read('test_groups', 'array_groups'));
+        $this->assertTrue(Cache::write('test_groups', 'value2', 'array_groups'));
+        $this->assertEquals('value2', Cache::read('test_groups', 'array_groups'));
+
+        Cache::clearGroup('group_b', 'array_groups');
+        $this->assertFalse(Cache::read('test_groups', 'array_groups'));
+        $this->assertTrue(Cache::write('test_groups', 'value3', 'array_groups'));
+        $this->assertEquals('value3', Cache::read('test_groups', 'array_groups'));
+    }
+
+    /**
+     * Tests that deleting from a groups-enabled config is possible
+     *
+     * @return void
+     */
+    public function testGroupDelete()
+    {
+        Cache::setConfig('array_groups', [
+            'engine' => 'array',
+            'duration' => 10,
+            'groups' => ['group_a', 'group_b'],
+            'prefix' => 'test_',
+            'warnOnWriteFailures' => true,
+        ]);
+        $this->assertTrue(Cache::write('test_groups', 'value', 'array_groups'));
+        $this->assertEquals('value', Cache::read('test_groups', 'array_groups'));
+
+        $this->assertTrue(Cache::delete('test_groups', 'array_groups'));
+        $this->assertFalse(Cache::read('test_groups', 'array_groups'));
+    }
+
+    /**
+     * Test clearing a cache group
+     *
+     * @return void
+     */
+    public function testGroupClear()
+    {
+        Cache::setConfig('array_groups', [
+            'engine' => 'array',
+            'duration' => 10,
+            'groups' => ['group_a', 'group_b'],
+            'prefix' => 'test_',
+            'warnOnWriteFailures' => true,
+        ]);
+
+        $this->assertTrue(Cache::write('test_groups', 'value', 'array_groups'));
+        $this->assertTrue(Cache::clearGroup('group_a', 'array_groups'));
+        $this->assertFalse(Cache::read('test_groups', 'array_groups'));
+
+        $this->assertTrue(Cache::write('test_groups', 'value2', 'array_groups'));
+        $this->assertTrue(Cache::clearGroup('group_b', 'array_groups'));
+        $this->assertFalse(Cache::read('test_groups', 'array_groups'));
+    }
+
+    /**
+     * Test add
+     *
+     * @return void
+     */
+    public function testAdd()
+    {
+        Cache::delete('test_add_key', 'array');
+
+        $result = Cache::add('test_add_key', 'test data', 'array');
+        $this->assertTrue($result);
+
+        $expected = 'test data';
+        $result = Cache::read('test_add_key', 'array');
+        $this->assertEquals($expected, $result);
+
+        $result = Cache::add('test_add_key', 'test data 2', 'array');
+        $this->assertFalse($result);
+    }
+}