diff --git a/.travis.yml b/.travis.yml
index f088192a1..2aa812ed6 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -68,13 +68,7 @@ install:
- ln -s ${TRAVIS_BUILD_DIR}/.temp ${TRAVIS_BUILD_DIR}/cphalcon/.temp
- ( cd cphalcon; zephir fullclean && zephir generate $ZEND_BACKEND )
- ( cd cphalcon/ext; export CFLAGS="-g3 -O1 -std=gnu90 -Wall -DZEPHIR_RELEASE=1"; /usr/bin/phpize &> /dev/null && ./configure --silent --enable-phalcon &> /dev/null && make --silent -j3 &> /dev/null && make --silent install )
- - phpenv config-add tests/_ci/phalcon.ini
- - phpenv config-add tests/_ci/redis.ini
- - if [[ "${PHP_MAJOR:0:1}" != "7" ]]; then phpenv config-add tests/_ci/mongo.ini; fi;
- - phpenv config-add tests/_ci/mongodb.ini
- - phpenv config-add tests/_ci/memcached.ini
- - pecl channel-update pecl.php.net
- - if [[ "${PHP_MAJOR:0:1}" = "7" ]]; then printf "\n" | pecl install yaml-2.0.0 > /dev/null 2>&1; else printf "\n" | pecl install yaml > /dev/null 2>&1; fi;
+ - ( bash tests/_ci/install_prereqs_$PHP_MAJOR.sh )
before_script:
- stty cols 160
diff --git a/Library/Phalcon/Annotations/Extended/AbstractAdapter.php b/Library/Phalcon/Annotations/Extended/AbstractAdapter.php
new file mode 100644
index 000000000..ba7afcf23
--- /dev/null
+++ b/Library/Phalcon/Annotations/Extended/AbstractAdapter.php
@@ -0,0 +1,92 @@
+ |
+ +------------------------------------------------------------------------+
+*/
+
+namespace Phalcon\Annotations\Extended;
+
+use Phalcon\Annotations\Adapter;
+use Phalcon\Annotations\Exception;
+use Phalcon\Annotations\Reflection;
+use Phalcon\Traits\ConfigurableTrait;
+
+/**
+ * Phalcon\Annotations\Extended\AbstractAdapter
+ *
+ * This is the base class for Phalcon\Annotations\Extended adapters
+ *
+ * @package Phalcon\Annotations\Extended
+ */
+abstract class AbstractAdapter extends Adapter implements AdapterInterface
+{
+ use ConfigurableTrait;
+
+ /**
+ * Configurable properties.
+ * @var array
+ */
+ protected $configurable = [];
+
+ /**
+ * AbstractAdapter constructor.
+ *
+ * @param array $options
+ */
+ public function __construct(array $options = [])
+ {
+ $this->setParameters($options);
+ }
+
+ /**
+ * Returns prefixed identifier.
+ *
+ * @param string $id
+ * @return string
+ */
+ abstract protected function getPrefixedIdentifier($id);
+
+ /**
+ * Check and cast returned result.
+ *
+ * @param mixed $result
+ * @return bool
+ */
+ protected function castResult($result)
+ {
+ if ($result instanceof Reflection) {
+ return $result;
+ }
+
+ return false;
+ }
+
+ /**
+ * Check annotation key.
+ *
+ * @param string $key
+ *
+ * @throws Exception
+ */
+ protected function checkKey($key)
+ {
+ if (!is_string($key)) {
+ throw new Exception(
+ sprintf('Invalid key type key to retrieve annotations. Expected string but got %s.', gettype($key))
+ );
+ }
+ }
+}
diff --git a/Library/Phalcon/Annotations/Extended/Adapter/Apc.php b/Library/Phalcon/Annotations/Extended/Adapter/Apc.php
new file mode 100644
index 000000000..49982066f
--- /dev/null
+++ b/Library/Phalcon/Annotations/Extended/Adapter/Apc.php
@@ -0,0 +1,201 @@
+ |
+ +------------------------------------------------------------------------+
+*/
+
+namespace Phalcon\Annotations\Extended\Adapter;
+
+use Phalcon\Annotations\Exception;
+use Phalcon\Annotations\Reflection;
+use Phalcon\Annotations\Extended\AbstractAdapter;
+
+/**
+ * Phalcon\Annotations\Extended\Adapter\Apc
+ *
+ * Extended Apc adapter for storing annotations in the APC(u).
+ * This adapter is suitable for production.
+ *
+ *
+ * use Phalcon\Annotations\Extended\Adapter\Apc;
+ *
+ * $annotations = new Apc(
+ * [
+ * 'prefix' => 'app-annotations', // Optional
+ * 'lifetime' => 8600, // Optional
+ * 'statsKey' => '_PHAN', // Optional
+ * ]
+ * );
+ *
+ *
+ * @package Phalcon\Annotations\Extended\Adapter
+ */
+class Apc extends AbstractAdapter
+{
+ /**
+ * The storage key prefix.
+ * @var string
+ */
+ protected $prefix = '';
+
+ /**
+ * The storage lifetime.
+ * @var int
+ */
+ protected $lifetime = 8600;
+
+ /**
+ * Storage stats key
+ * @var string
+ */
+ protected $statsKey = '_PHAN';
+
+ /**
+ * Configurable properties.
+ * @var array
+ */
+ protected $configurable = [
+ 'prefix',
+ 'lifetime',
+ 'statsKey',
+ ];
+
+ /**
+ * Reads parsed annotations from APC(u).
+ *
+ * @param string $key
+ * @return Reflection|bool
+ *
+ * @throws Exception
+ */
+ public function read($key)
+ {
+ $this->checkKey($key);
+
+ $prefixedKey = $this->getPrefixedIdentifier($key);
+
+ if (function_exists('apcu_fetch')) {
+ $result = apcu_fetch($prefixedKey);
+ } else {
+ $result = apc_fetch($prefixedKey);
+ }
+
+ return $this->castResult($result);
+ }
+
+ /**
+ * Writes parsed annotations to APC(u)
+ *
+ * @param string $key
+ * @param Reflection $data
+ * @return bool
+ *
+ * @throws Exception
+ */
+ public function write($key, Reflection $data)
+ {
+ $this->checkKey($key);
+
+ $prefixedKey = $this->getPrefixedIdentifier($key);
+
+ if (function_exists('apcu_store')) {
+ return apcu_store($prefixedKey, $data, $this->lifetime);
+ }
+
+ return apc_store($prefixedKey, $data, $this->lifetime);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ *
+ * use Phalcon\Annotations\Extended\Apc;
+ *
+ * $annotations = new Apc(['prefix' => 'app-annotations']);
+ * $annotations->flush();
+ *
+ *
+ * @return bool
+ */
+ public function flush()
+ {
+ $prefixPattern = '#^_PHAN' . preg_quote("{$this->prefix}", '#') . '#';
+
+ if (class_exists('\APCuIterator')) {
+ foreach (new \APCuIterator($prefixPattern) as $item) {
+ apcu_delete($item['key']);
+ }
+
+ return true;
+ }
+
+ foreach (new \APCIterator('user', $prefixPattern) as $item) {
+ apc_delete($item['key']);
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $id
+ * @return string
+ */
+ protected function getPrefixedIdentifier($id)
+ {
+ return $this->statsKey . $this->prefix . $id;
+ }
+
+ /**
+ * Sets the storage key prefix.
+ *
+ * @param string $prefix The storage key prefix.
+ * @return $this
+ */
+ protected function setPrefix($prefix)
+ {
+ $this->prefix = (string) $prefix;
+
+ return $this;
+ }
+
+ /**
+ * Sets the storage lifetime.
+ *
+ * @param int $lifetime The storage lifetime.
+ * @return $this
+ */
+ protected function setLifetime($lifetime)
+ {
+ $this->lifetime = (int) $lifetime;
+
+ return $this;
+ }
+
+ /**
+ * Sets the storage stats key.
+ *
+ * @param string $statsKey The storage key prefix.
+ * @return $this
+ */
+ protected function setStatsKey($statsKey)
+ {
+ $this->statsKey = (string) $statsKey;
+
+ return $this;
+ }
+}
diff --git a/Library/Phalcon/Annotations/Extended/Adapter/Files.php b/Library/Phalcon/Annotations/Extended/Adapter/Files.php
new file mode 100644
index 000000000..a22d0ceef
--- /dev/null
+++ b/Library/Phalcon/Annotations/Extended/Adapter/Files.php
@@ -0,0 +1,167 @@
+ |
+ +------------------------------------------------------------------------+
+*/
+
+namespace Phalcon\Annotations\Extended\Adapter;
+
+use Phalcon\Annotations\Exception;
+use Phalcon\Annotations\Reflection;
+use Phalcon\Annotations\Extended\AbstractAdapter;
+
+/**
+ * Phalcon\Annotations\Extended\Adapter\Files
+ *
+ * Stores the parsed annotations in files.
+ * This adapter is suitable for production.
+ *
+ *
+ * use Phalcon\Annotations\Adapter\Files;
+ *
+ * $annotations = new Files(
+ * [
+ * "annotationsDir" => "app/cache/annotations/",
+ * ]
+ * );
+ *
+ *
+ * @package Phalcon\Annotations\Extended\Adapter
+ */
+class Files extends AbstractAdapter
+{
+ protected $annotationsDir = './';
+
+ /**
+ * Configurable properties.
+ * @var array
+ */
+ protected $configurable = [
+ 'annotationsDir',
+ ];
+
+ /**
+ * Files adapter constructor.
+ *
+ * @param array $options
+ */
+ public function __construct(array $options = [])
+ {
+ if (!isset($options['annotationsDir'])) {
+ $options['annotationsDir'] =sys_get_temp_dir();
+ }
+
+ parent::__construct($options);
+ }
+
+ /**
+ * Sets the annotations dir.
+ *
+ * @param string $annotationsDir The storage key prefix.
+ * @return $this
+ */
+ protected function setAnnotationsDir($annotationsDir)
+ {
+ $annotationsDir = (string) $annotationsDir;
+ $this->annotationsDir = rtrim($annotationsDir, '\\/') . DIRECTORY_SEPARATOR;
+
+ return $this;
+ }
+
+ /**
+ * Reads parsed annotations from memory.
+ *
+ * @param string $key
+ * @return Reflection|bool
+ *
+ * @throws Exception
+ */
+ public function read($key)
+ {
+ $this->checkKey($key);
+
+ $result = null;
+ $path = $this->getPrefixedIdentifier($key);
+
+ if (file_exists($path)) {
+ /** @noinspection PhpIncludeInspection */
+ $result = require $path;
+ }
+
+ return $this->castResult($result);
+ }
+
+ /**
+ * Writes parsed annotations to files.
+ *
+ * @param string $key
+ * @param Reflection $reflection
+ * @return bool
+ *
+ * @throws Exception
+ */
+ public function write($key, Reflection $reflection)
+ {
+ $this->checkKey($key);
+
+ $path = $this->getPrefixedIdentifier($key);
+
+ if (file_put_contents($path, '
+ * use Phalcon\Annotations\Extended\Files;
+ *
+ * $annotations = new Files(['annotationsDir' => BASE_DIR . '/cache/']);
+ * $annotations->flush();
+ *
+ *
+ * @return bool
+ */
+ public function flush()
+ {
+ $iterator = new \DirectoryIterator($this->annotationsDir);
+ foreach ($iterator as $item) {
+ if ($item->isDot() || !$item->isFile() || $item->getExtension() !== 'php') {
+ continue;
+ }
+
+ unlink($item->getPathname());
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $id
+ * @return string
+ */
+ protected function getPrefixedIdentifier($key)
+ {
+ $key = strtolower(str_replace(['\\', '/', ':'], '_', $key));
+
+ return $this->annotationsDir . preg_replace('#_{2,}#', '_', $key) . '.php';
+ }
+}
diff --git a/Library/Phalcon/Annotations/Extended/Adapter/Memory.php b/Library/Phalcon/Annotations/Extended/Adapter/Memory.php
new file mode 100644
index 000000000..63bd07ba9
--- /dev/null
+++ b/Library/Phalcon/Annotations/Extended/Adapter/Memory.php
@@ -0,0 +1,109 @@
+ |
+ +------------------------------------------------------------------------+
+*/
+
+namespace Phalcon\Annotations\Extended\Adapter;
+
+use Phalcon\Annotations\Exception;
+use Phalcon\Annotations\Reflection;
+use Phalcon\Annotations\Extended\AbstractAdapter;
+
+/**
+ * Phalcon\Annotations\Extended\Adapter\Memory
+ *
+ * Extended Memory adapter for storing annotations in memory.
+ * This adapter is the suitable development/testing.
+ *
+ *
+ * use Phalcon\Annotations\Extended\Adapter\Memory;
+ *
+ * $annotations = new Memory();
+ *
+ *
+ * @package Phalcon\Annotations\Extended\Adapter
+ */
+class Memory extends AbstractAdapter
+{
+ protected $data = [];
+
+ /**
+ * Reads parsed annotations from memory.
+ *
+ * @param string $key
+ * @return Reflection|bool
+ *
+ * @throws Exception
+ */
+ public function read($key)
+ {
+ $this->checkKey($key);
+
+ $result = null;
+ $prefixedKey = $this->getPrefixedIdentifier($key);
+
+ if (isset($this->data[$prefixedKey])) {
+ $result = $this->data[$prefixedKey];
+ }
+
+ return $this->castResult($result);
+ }
+
+ /**
+ * Writes parsed annotations to memory.
+ *
+ * @param string $key
+ * @param Reflection $reflection
+ * @return bool
+ *
+ * @throws Exception
+ */
+ public function write($key, Reflection $reflection)
+ {
+ $this->checkKey($key);
+
+ $prefixedKey = $this->getPrefixedIdentifier($key);
+
+ $this->data[$prefixedKey] = $reflection;
+
+
+ return true;
+ }
+
+ /**
+ * Immediately invalidates all existing items.
+ *
+ * @return bool
+ */
+ public function flush()
+ {
+ $this->data = [];
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param string $id
+ * @return string
+ */
+ protected function getPrefixedIdentifier($key)
+ {
+ return strtolower($key);
+ }
+}
diff --git a/Library/Phalcon/Annotations/Extended/AdapterInterface.php b/Library/Phalcon/Annotations/Extended/AdapterInterface.php
new file mode 100644
index 000000000..b1d37ea26
--- /dev/null
+++ b/Library/Phalcon/Annotations/Extended/AdapterInterface.php
@@ -0,0 +1,57 @@
+ |
+ +------------------------------------------------------------------------+
+*/
+
+namespace Phalcon\Annotations\Extended;
+
+use Phalcon\Annotations\Reflection;
+use Phalcon\Annotations\AdapterInterface as BaseInterface;
+
+/**
+ * Phalcon\Annotations\Extended\AdapterInterface
+ *
+ * This interface must be implemented by adapters in Phalcon\Annotations\Extended
+ *
+ * @package Phalcon\Annotations\Extended
+ */
+interface AdapterInterface extends BaseInterface
+{
+ /**
+ * Reads parsed annotations from the current storage.
+ *
+ * @param string $key
+ * @return Reflection|bool
+ */
+ public function read($key);
+
+ /**
+ * Writes parsed annotations to the current storage.
+ *
+ * @param string $key
+ * @param Reflection $reflection
+ * @return bool
+ */
+ public function write($key, Reflection $reflection);
+
+ /**
+ * Immediately invalidates all existing items.
+ *
+ * @return bool
+ */
+ public function flush();
+}
diff --git a/Library/Phalcon/Annotations/Extended/README.md b/Library/Phalcon/Annotations/Extended/README.md
new file mode 100644
index 000000000..ac7a64f20
--- /dev/null
+++ b/Library/Phalcon/Annotations/Extended/README.md
@@ -0,0 +1,54 @@
+# Phalcon\Annotations\Extended\Adapter
+
+The main goals of this package:
+
+* Extended `AdapterInterface` with methods `read`, `write` and `flush`
+* Work only with `Phalcon\Annotations\Reflection` (`read` and `write`)
+* Ability to set custom `statsKey`
+* Work separately from current `Phalcon\Cache\BackendInterface`
+
+In the future a set of these adapters will be part of the Phalcon Framework.
+Usage examples of the adapters available here:
+
+## Apc
+
+Stores the parsed annotations in the [Alternative PHP Cache (APC)](http://php.net/manual/en/intro.apcu.php)
+using either _APCu_ or _APC_ extension. This adapter is suitable for production.
+
+```php
+use Phalcon\Annotations\Extended\Adapter\Apc;
+
+$di->set('annotations', function () {
+ return new Apc([
+ 'lifetime' => 8600, // Optional
+ 'statsSey' => '_PHAN', // Optional
+ 'prefix' => 'app-annotations-', // Optional
+ ]);
+});
+```
+
+## Memory
+
+Stores the parsed annotations in the memory. This adapter is the suitable development/testing.
+
+```php
+use Phalcon\Annotations\Extended\Adapter\Memory;
+
+$di->set('annotations', function () {
+ return new Memory();
+});
+```
+
+## Files
+
+Stores the parsed annotations in files. This adapter is suitable for production.
+
+```php
+use Phalcon\Annotations\Adapter\Files;
+
+$annotations = new Files(
+ [
+ "annotationsDir" => "app/cache/annotations/",
+ ]
+);
+```
diff --git a/README.md b/README.md
index 37f8827a5..98188212f 100644
--- a/README.md
+++ b/README.md
@@ -97,6 +97,9 @@ See [CONTRIBUTING.md](CONTRIBUTING.md)
* [Phalcon\Annotations\Adapter\Memcached](Library/Phalcon/Annotations/Adapter) - Memcached adapter for storing annotations (@igusev)
* [Phalcon\Annotations\Adapter\Redis](Library/Phalcon/Annotations/Adapter) - Redis adapter for storing annotations (@sergeyklay)
* [Phalcon\Annotations\Adapter\Aerospike](Library/Phalcon/Annotations/Adapter) - Aerospike adapter for storing annotations (@sergeyklay)
+* [Phalcon\Annotations\Extended\Adapter\Apc](Library/Phalcon/Annotations/Extended/Adapter) - Extended Apc adapter for storing annotations in the APC(u) (@sergeyklay)
+* [Phalcon\Annotations\Extended\Adapter\Memory](Library/Phalcon/Annotations/Extended/Adapter) - Extended Memory adapter for storing annotations in the memory (@sergeyklay)
+* [Phalcon\Annotations\Extended\Adapter\Files](Library/Phalcon/Annotations/Extended/Adapter) - Extended Files adapter for storing annotations in files (@sergeyklay)
### Behaviors
* [Phalcon\Mvc\Model\Behavior\Blameable](Library/Phalcon/Mvc/Model/Behavior) - logs with every created or updated row in your database who created and who updated it (@phalcon)
diff --git a/tests/_ci/apc_bc.ini b/tests/_ci/apc_bc.ini
new file mode 100644
index 000000000..6f0dcf505
--- /dev/null
+++ b/tests/_ci/apc_bc.ini
@@ -0,0 +1,15 @@
+; See https://pear.php.net/bugs/bug.php?id=21007
+
+[APCu]
+extension=apcu.so
+
+[APC]
+extension=apc.so
+apc.enabled=1
+apc.enable_cli=1
+
+; If the system is flooded with i/o and some update procedures are taking longer than 2 seconds,
+; this setting should be increased to enable the protection on those slower update operations.
+apc.file_update_protection=0
+
+; apc.cache_by_default=0
diff --git a/tests/_ci/install_prereqs_5.5.sh b/tests/_ci/install_prereqs_5.5.sh
new file mode 100755
index 000000000..7976782e9
--- /dev/null
+++ b/tests/_ci/install_prereqs_5.5.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+#
+# Phalcon Framework
+#
+# Copyright (c) 2011-2016 Phalcon Team (https://www.phalconphp.com)
+#
+# This source file is subject to the New BSD License that is bundled
+# with this package in the file LICENSE.txt.
+#
+# If you did not receive a copy of the license and are unable to
+# obtain it through the world-wide-web, please send an email
+# to license@phalconphp.com so we can send you a copy immediately.
+
+CURRENT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+TRAVIS_BUILD_DIR="${TRAVIS_BUILD_DIR:-$(dirname $(dirname $CURRENT_DIR))}"
+
+pecl channel-update pecl.php.net
+
+printf "\n" | pecl install apcu-4.0.11 &> /dev/null
+
+echo "apc.enable_cli=On" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
+
+printf "\n" | pecl install yaml > /dev/null 2>&1
+
+phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/phalcon.ini
+phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/redis.ini
+phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/mongo.ini
+phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/mongodb.ini
+phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/memcached.ini
diff --git a/tests/_ci/install_prereqs_5.6.sh b/tests/_ci/install_prereqs_5.6.sh
new file mode 100755
index 000000000..7976782e9
--- /dev/null
+++ b/tests/_ci/install_prereqs_5.6.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+#
+# Phalcon Framework
+#
+# Copyright (c) 2011-2016 Phalcon Team (https://www.phalconphp.com)
+#
+# This source file is subject to the New BSD License that is bundled
+# with this package in the file LICENSE.txt.
+#
+# If you did not receive a copy of the license and are unable to
+# obtain it through the world-wide-web, please send an email
+# to license@phalconphp.com so we can send you a copy immediately.
+
+CURRENT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+TRAVIS_BUILD_DIR="${TRAVIS_BUILD_DIR:-$(dirname $(dirname $CURRENT_DIR))}"
+
+pecl channel-update pecl.php.net
+
+printf "\n" | pecl install apcu-4.0.11 &> /dev/null
+
+echo "apc.enable_cli=On" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
+
+printf "\n" | pecl install yaml > /dev/null 2>&1
+
+phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/phalcon.ini
+phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/redis.ini
+phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/mongo.ini
+phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/mongodb.ini
+phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/memcached.ini
diff --git a/tests/_ci/install_prereqs_7.0.sh b/tests/_ci/install_prereqs_7.0.sh
new file mode 100755
index 000000000..d604e41a1
--- /dev/null
+++ b/tests/_ci/install_prereqs_7.0.sh
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+#
+# Phalcon Framework
+#
+# Copyright (c) 2011-2016 Phalcon Team (https://www.phalconphp.com)
+#
+# This source file is subject to the New BSD License that is bundled
+# with this package in the file LICENSE.txt.
+#
+# If you did not receive a copy of the license and are unable to
+# obtain it through the world-wide-web, please send an email
+# to license@phalconphp.com so we can send you a copy immediately.
+
+CURRENT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+TRAVIS_BUILD_DIR="${TRAVIS_BUILD_DIR:-$(dirname $(dirname $CURRENT_DIR))}"
+
+CFLAGS="-O2 -g3 -fno-strict-aliasing -std=gnu90";
+
+pecl channel-update pecl.php.net
+
+install_apcu() {
+ # See https://github.com/krakjoe/apcu/issues/203
+ git clone -q https://github.com/krakjoe/apcu -b v5.1.7 /tmp/apcu
+ cd /tmp/apcu
+
+ phpize &> /dev/null
+ ./configure &> /dev/null
+
+ make --silent -j4 &> /dev/null
+ make --silent install
+}
+
+install_apcu_bc() {
+ git clone -q https://github.com/krakjoe/apcu-bc /tmp/apcu-bc
+ cd /tmp/apcu-bc
+
+ phpize &> /dev/null
+ ./configure &> /dev/null
+
+ make --silent -j4 &> /dev/null
+ make --silent install
+}
+
+install_apcu
+install_apcu_bc
+
+phpenv config-add "${TRAVIS_BUILD_DIR}/tests/_ci/apc_bc.ini"
+
+printf "\n" | pecl install yaml-2.0.0 >/dev/null 2>&1
+
+phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/phalcon.ini
+phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/redis.ini
+phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/mongodb.ini
+phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/memcached.ini
diff --git a/tests/unit.suite.5.yml b/tests/unit.suite.5.yml
index a74416b10..07d369f7f 100644
--- a/tests/unit.suite.5.yml
+++ b/tests/unit.suite.5.yml
@@ -7,9 +7,11 @@ modules:
# enabled modules and helpers
enabled:
- Helper\Unit
+ - Filesystem
- Aerospike
- Asserts
- Mockery
+ - Apc
- Db
config:
Db:
diff --git a/tests/unit.suite.yml b/tests/unit.suite.yml
index ba1047bf5..60320e213 100644
--- a/tests/unit.suite.yml
+++ b/tests/unit.suite.yml
@@ -7,8 +7,10 @@ modules:
# enabled modules and helpers
enabled:
- Helper\Unit
+ - Filesystem
- Asserts
- Mockery
+ - Apc
- Db
config:
Db:
diff --git a/tests/unit/Annotations/Extended/Adapter/ApcTest.php b/tests/unit/Annotations/Extended/Adapter/ApcTest.php
new file mode 100644
index 000000000..c0b3bb056
--- /dev/null
+++ b/tests/unit/Annotations/Extended/Adapter/ApcTest.php
@@ -0,0 +1,176 @@
+markTestSkipped('Warning: apc extension is not loaded');
+ }
+
+ if (!ini_get('apc.enabled') || (PHP_SAPI === 'cli' && !ini_get('apc.enable_cli'))) {
+ $this->markTestSkipped('Warning: apc.enable_cli must be set to "On"');
+ }
+
+ if (extension_loaded('apcu') && version_compare(phpversion('apcu'), '5.1.6', '=')) {
+ $this->markTestSkipped('Warning: APCu v5.1.6 was broken. See: https://github.com/krakjoe/apcu/issues/203');
+ }
+ }
+
+ /** @test */
+ public function shouldReadFromApcWithoutAnyAdditionalParameter()
+ {
+ $reflection = $this->getReflection();
+ $annotations = new Apc();
+
+ $this->tester->haveInApc('_PHAN' . 'read-1', $reflection);
+ $this->assertEquals($reflection, $annotations->read('read-1'));
+ }
+
+ /** @test */
+ public function shouldReadFromApcWithPrefix()
+ {
+ $reflection = $this->getReflection();
+ $annotations = new Apc(['prefix' => 'prefix-']);
+
+ $this->tester->haveInApc('_PHAN' . 'prefix-read-2', $reflection);
+ $this->assertEquals($reflection, $annotations->read('read-2'));
+ }
+
+ /** @test */
+ public function shouldWriteToTheApcWithoutAnyAdditionalParameter()
+ {
+ $reflection = $this->getReflection();
+ $annotations = new Apc();
+
+ $this->assertTrue($annotations->write('write-1', $reflection));
+ $this->assertEquals($reflection, $this->tester->grabValueFromApc('_PHAN' . 'write-1'));
+ }
+
+ /** @test */
+ public function shouldWriteToTheApcWithPrefix()
+ {
+ $reflection = $this->getReflection();
+ $annotations = new Apc(['prefix' => 'prefix-']);
+
+ $this->assertTrue($annotations->write('write-2', $reflection));
+ $this->assertEquals($reflection, $this->tester->grabValueFromApc('_PHAN' . 'prefix-write-2'));
+ }
+
+ /** @test */
+ public function shouldFlushTheApcStorageWithoutAnyAdditionalParameter()
+ {
+ $reflection = $this->getReflection();
+ $annotations = new Apc();
+
+ $this->tester->haveInApc('_PHAN' . 'flush-1', $reflection);
+ $this->tester->haveInApc('_ANOTHER' . 'flush-1', $reflection);
+
+ $this->assertTrue($annotations->flush());
+ $this->tester->dontSeeInApc('_PHAN' . 'flush-1');
+ $this->tester->seeInApc('_ANOTHER' . 'flush-1', $reflection);
+ }
+
+ /** @test */
+ public function shouldFlushTheApcStorageWithPrefix()
+ {
+ $reflection = $this->getReflection();
+ $annotations = new Apc(['prefix' => 'prefix-']);
+
+ $this->tester->haveInApc('_PHAN' . 'prefix-flush-2', $reflection);
+ $this->tester->haveInApc('_ANOTHER' . 'prefix-flush-2', $reflection);
+
+ $this->assertTrue($annotations->flush());
+ $this->tester->dontSeeInApc('_PHAN' . 'prefix-flush-2');
+ $this->tester->seeInApc('_ANOTHER' . 'prefix-flush-2', $reflection);
+ }
+
+ /** @test */
+ public function shouldReadAndWriteFromApcWithoutAnyAdditionalParameter()
+ {
+ $reflection = $this->getReflection();
+ $annotations = new Apc();
+
+ $this->assertTrue($annotations->write('read-write-1', $reflection));
+ $this->assertEquals($reflection, $annotations->read('read-write-1'));
+ $this->assertEquals($reflection, $this->tester->grabValueFromApc('_PHAN' . 'read-write-1'));
+ }
+
+ /** @test */
+ public function shouldReadAndWriteFromApcWithPrefix()
+ {
+ $reflection = $this->getReflection();
+ $annotations = new Apc(['prefix' => 'prefix-']);
+
+ $this->assertTrue($annotations->write('read-write-2', $reflection));
+ $this->assertEquals($reflection, $annotations->read('read-write-2'));
+ $this->assertEquals($reflection, $this->tester->grabValueFromApc('_PHAN' . 'prefix-read-write-2'));
+ }
+
+ /**
+ * @test
+ * @dataProvider providerKey
+ * @param mixed $key
+ * @param string $prefix
+ * @param string|null $statsKey
+ * @param string $expected
+ */
+ public function shouldGetValueFromApcByUsingPrefixedIdentifier($key, $prefix, $statsKey, $expected)
+ {
+ if ($statsKey === null) {
+ $options = ['prefix' => $prefix];
+ } else {
+ $options = ['prefix' => $prefix, 'statsKey' => $statsKey];
+ }
+
+ $annotations = new Apc($options);
+ $reflectedMethod = new ReflectionMethod(get_class($annotations), 'getPrefixedIdentifier');
+ $reflectedMethod->setAccessible(true);
+
+ $this->assertEquals($expected, $reflectedMethod->invoke($annotations, $key));
+ }
+
+ public function providerKey()
+ {
+ return [
+ ['key1', '', null, '_PHANkey1' ],
+ ['key1', '-some-', '_PHAN', '_PHAN-some-key1'],
+ [1, '', null, '_PHAN1' ],
+ [1, 2, '_PHAN', '_PHAN21' ],
+ ['_key1', '', null, '_PHAN_key1' ],
+ ['_key1', '/', '_PHAN', '_PHAN/_key1' ],
+ ['key1', '#', null, '_PHAN#key1' ],
+ ['key1', '', '', 'key1' ],
+ ['key1', '', '_XXX', '_XXXkey1' ],
+ ['key1', 'xxx-', '', 'xxx-key1' ],
+ ];
+ }
+
+ protected function getReflection()
+ {
+ return Reflection::__set_state([
+ '_reflectionData' => [
+ 'class' => [],
+ 'methods' => [],
+ 'properties' => [],
+ ]
+ ]);
+ }
+}
diff --git a/tests/unit/Annotations/Extended/Adapter/FilesTest.php b/tests/unit/Annotations/Extended/Adapter/FilesTest.php
new file mode 100644
index 000000000..8195a790e
--- /dev/null
+++ b/tests/unit/Annotations/Extended/Adapter/FilesTest.php
@@ -0,0 +1,147 @@
+getReflection();
+ $annotations = new Files();
+
+ $this->tester->amInPath(sys_get_temp_dir());
+ $this->tester->writeToFile('read-1.php', 'assertEquals($reflection, $annotations->read('read-1'));
+
+ $this->tester->deleteFile('read-1.php');
+ }
+
+ /** @test */
+ public function shouldReadFromFileDirectoryWithAnnotationsDir()
+ {
+ $reflection = $this->getReflection();
+ $annotations = new Files(['annotationsDir' => codecept_output_dir()]);
+
+ $this->tester->amInPath(codecept_output_dir());
+ $this->tester->writeToFile('read-2.php', 'assertEquals($reflection, $annotations->read('read-2'));
+
+ $this->tester->deleteFile('read-2.php');
+ }
+
+ /** @test */
+ public function shouldWriteToTheFileDirectoryWithoutAnyAdditionalParameter()
+ {
+ $reflection = $this->getReflection();
+ $annotations = new Files();
+
+ $this->assertTrue($annotations->write('write-1', $reflection));
+
+ $this->tester->amInPath(sys_get_temp_dir());
+ $this->tester->seeFileFound('write-1.php');
+ $this->tester->seeFileContentsEqual('tester->deleteFile('write-1.php');
+ }
+
+ /** @test */
+ public function shouldWriteToTheFileDirectoryWithAnnotationsDir()
+ {
+ $reflection = $this->getReflection();
+ $annotations = new Files(['annotationsDir' => codecept_output_dir()]);
+
+ $this->assertTrue($annotations->write('write-2', $reflection));
+
+ $this->tester->amInPath(codecept_output_dir());
+ $this->tester->seeFileFound('write-2.php');
+ $this->tester->seeFileContentsEqual('tester->deleteFile('write-2.php');
+ }
+
+ /** @test */
+ public function shouldFlushTheFileDirectoryStorageWithoutAnyAdditionalParameter()
+ {
+ $reflection = $this->getReflection();
+ $annotations = new Files();
+
+ $this->tester->amInPath(sys_get_temp_dir());
+ $this->tester->writeToFile('flush-1.php', 'tester->writeToFile('flush-2.php', 'assertTrue($annotations->flush());
+
+ $this->tester->dontSeeFileFound('flush-1.php');
+ $this->tester->dontSeeFileFound('flush-2.php');
+ }
+
+ /** @test */
+ public function shouldFlushTheFileDirectoryStorageWithAnnotationsDir()
+ {
+ $reflection = $this->getReflection();
+ $annotations = new Files(['annotationsDir' => codecept_output_dir()]);
+
+ $this->tester->amInPath(codecept_output_dir());
+ $this->tester->writeToFile('flush-3.php', 'tester->writeToFile('flush-4.php', 'assertTrue($annotations->flush());
+
+ $this->tester->dontSeeFileFound('flush-3.php');
+ $this->tester->dontSeeFileFound('flush-4.php');
+ }
+
+ /** @test */
+ public function shouldReadAndWriteFromFileDirectoryWithoutAnyAdditionalParameter()
+ {
+ $reflection = $this->getReflection();
+ $annotations = new Files();
+
+ $this->assertTrue($annotations->write('read-write-1', $reflection));
+ $this->assertEquals($reflection, $annotations->read('read-write-1'));
+
+ $this->tester->amInPath(sys_get_temp_dir());
+ $this->tester->seeFileContentsEqual('tester->deleteFile('read-write-1.php');
+ }
+
+ /** @test */
+ public function shouldReadAndWriteFromFileDirectoryWithAnnotationsDir()
+ {
+ $reflection = $this->getReflection();
+ $annotations = new Files(['annotationsDir' => codecept_output_dir()]);
+
+ $this->assertTrue($annotations->write('read-write-2', $reflection));
+ $this->assertEquals($reflection, $annotations->read('read-write-2'));
+
+ $this->tester->amInPath(codecept_output_dir());
+ $this->tester->seeFileContentsEqual('tester->deleteFile('read-write-2.php');
+ }
+
+
+ protected function getReflection()
+ {
+ return Reflection::__set_state([
+ '_reflectionData' => [
+ 'class' => [],
+ 'methods' => [],
+ 'properties' => [],
+ ]
+ ]);
+ }
+}
diff --git a/tests/unit/Annotations/Extended/Adapter/MemoryTest.php b/tests/unit/Annotations/Extended/Adapter/MemoryTest.php
new file mode 100644
index 000000000..e247c72cb
--- /dev/null
+++ b/tests/unit/Annotations/Extended/Adapter/MemoryTest.php
@@ -0,0 +1,133 @@
+getReflection();
+ $annotations = new Memory();
+
+ $this->haveInMemory($annotations, 'read-1', $reflection);
+ $this->assertEquals($reflection, $annotations->read('read-1'));
+ }
+
+ /** @test */
+ public function shouldWriteToTheMemoryWithoutAnyAdditionalParameter()
+ {
+ $reflection = $this->getReflection();
+ $annotations = new Memory();
+
+ $this->assertTrue($annotations->write('write-1', $reflection));
+ $this->assertEquals($reflection, $this->grabValueFromMemory($annotations, 'write-1'));
+ }
+
+ /** @test */
+ public function shouldFlushTheMemoryStorageWithoutAnyAdditionalParameter()
+ {
+ $reflection = $this->getReflection();
+ $annotations = new Memory();
+
+ $this->haveInMemory($annotations, 'flush-1', $reflection);
+
+ $this->assertTrue($annotations->flush());
+ $this->dontSeeInMemory($annotations, 'flush-1');
+ }
+
+ /** @test */
+ public function shouldReadAndWriteFromMemoryWithoutAnyAdditionalParameter()
+ {
+ $reflection = $this->getReflection();
+ $annotations = new Memory();
+
+ $this->assertTrue($annotations->write('read-write-1', $reflection));
+ $this->assertEquals($reflection, $annotations->read('read-write-1'));
+ $this->assertEquals($reflection, $this->grabValueFromMemory($annotations, 'read-write-1'));
+ }
+
+ /**
+ * @test
+ * @dataProvider providerKey
+ * @param mixed $key
+ * @param string $expected
+ */
+ public function shouldGetValueFromMemoryByUsingPrefixedIdentifier($key, $expected)
+ {
+ $annotations = new Memory();
+ $reflectedMethod = new ReflectionMethod(get_class($annotations), 'getPrefixedIdentifier');
+ $reflectedMethod->setAccessible(true);
+
+ $this->assertEquals($expected, $reflectedMethod->invoke($annotations, $key));
+ }
+
+ public function providerKey()
+ {
+ return [
+ ['Key1', 'key1'],
+ ['KEY', 'key' ],
+ [1, '1' ],
+ ['____', '____'],
+ ];
+ }
+
+ protected function getReflection()
+ {
+ return Reflection::__set_state([
+ '_reflectionData' => [
+ 'class' => [],
+ 'methods' => [],
+ 'properties' => [],
+ ]
+ ]);
+ }
+
+ protected function haveInMemory($object, $key, $value)
+ {
+ $reflectedProperty = new ReflectionProperty(get_class($object), 'data');
+ $reflectedProperty->setAccessible(true);
+
+ $data = $reflectedProperty->getValue($object);
+ $data[$key] = $value;
+
+ $reflectedProperty->setValue($object, $data);
+ }
+
+ protected function grabValueFromMemory($object, $key)
+ {
+ $reflectedProperty = new ReflectionProperty(get_class($object), 'data');
+ $reflectedProperty->setAccessible(true);
+
+ $data = $reflectedProperty->getValue($object);
+
+ return $data[$key];
+ }
+
+ protected function dontSeeInMemory($object, $key, $value = false)
+ {
+ $reflectedProperty = new ReflectionProperty(get_class($object), 'data');
+ $reflectedProperty->setAccessible(true);
+
+ $data = $reflectedProperty->getValue($object);
+
+ if ($value === false) {
+ $this->assertArrayNotHasKey($key, $data);
+ } else {
+ $this->assertSame($value, $data[$key]);
+ }
+ }
+}