From 22f87b94d424cb4a65f3f5a469397b46e25effb8 Mon Sep 17 00:00:00 2001 From: Sergey Date: Wed, 15 Jan 2014 07:35:56 -0800 Subject: [PATCH] v3.1 Release with more stable client-server protocol Fixed: headers problem if flush() or ob_end_clean() is called before script shutdown Fixed: problem with frameworks that overrides $_SESSION handler see https://github.com/barbushin/php-console#troubleshooting-with-_session-handler-overridden-in-some-frameworks Migration: Update PHP Console extension to last version >= 3.0.20 [Fixes #34] [Relative #36] --- README.md | 17 ++- src/PhpConsole/Auth.php | 2 +- src/PhpConsole/Connector.php | 108 ++++++++++---------- src/PhpConsole/Dispatcher.php | 2 +- src/PhpConsole/Dispatcher/Debug.php | 2 +- src/PhpConsole/Dispatcher/Errors.php | 2 +- src/PhpConsole/Dispatcher/Evaluate.php | 2 +- src/PhpConsole/Dumper.php | 2 +- src/PhpConsole/EvalProvider.php | 2 +- src/PhpConsole/Handler.php | 2 +- src/PhpConsole/Helper.php | 2 +- src/PhpConsole/OldVersionAdapter.php | 2 +- src/PhpConsole/PsrLogger.php | 2 +- src/PhpConsole/Storage.php | 40 ++++++++ src/PhpConsole/Storage/AllKeysList.php | 71 +++++++++++++ src/PhpConsole/Storage/ExpiringKeyValue.php | 60 +++++++++++ src/PhpConsole/Storage/File.php | 93 +++++++++++++++++ src/PhpConsole/Storage/Memcache.php | 54 ++++++++++ src/PhpConsole/Storage/Session.php | 38 +++++++ tests/ClientEmulator/Connector.php | 8 +- tests/README.md | 7 ++ tests/Test/Storage.php | 50 +++++++++ tests/Test/Storage/File.php | 40 ++++++++ tests/Test/Storage/Memcache.php | 21 ++++ tests/Test/Storage/Session.php | 14 +++ tests/tmp/README.md | 1 + 26 files changed, 574 insertions(+), 70 deletions(-) create mode 100644 src/PhpConsole/Storage.php create mode 100644 src/PhpConsole/Storage/AllKeysList.php create mode 100644 src/PhpConsole/Storage/ExpiringKeyValue.php create mode 100644 src/PhpConsole/Storage/File.php create mode 100644 src/PhpConsole/Storage/Memcache.php create mode 100644 src/PhpConsole/Storage/Session.php create mode 100644 tests/README.md create mode 100644 tests/Test/Storage.php create mode 100644 tests/Test/Storage/File.php create mode 100644 tests/Test/Storage/Memcache.php create mode 100644 tests/Test/Storage/Session.php create mode 100644 tests/tmp/README.md diff --git a/README.md b/README.md index 3ce887d..27839f4 100644 --- a/README.md +++ b/README.md @@ -65,11 +65,24 @@ You can try most of PHP Console features on [live demo](http://php-console.com/i ## Connector -There is a [PhpConsole\Connector](src/PhpConsole/Connector.php) class that initializes connection between PHP server and Google Chrome extension. Connection is initalized when `PhpConsole\Connector` instance is initialized: +There is a [PhpConsole\Connector](src/PhpConsole/Connector.php) class that initializes connection between PHP server and Google Chrome extension. Connection is initalized when [PhpConsole\Connector](src/PhpConsole/Connector.php) instance is initialized: $connector = PhpConsole\Connector::getInstance(); -`PhpConsole\Connector` uses headers to communicate with client, so it must be initialized before any output. Also there must be no `flush()` and `while(ob_get_level()) ob_end_clean();` calls and etc code flushing headers. +Also it will be initialized when you call `PhpConsole\Handler::getInstance()` or `PhpConsole\Helper::register()`. + +### Communication protocol + +PHP Console uses headers to communicate with client, so `PhpConsole\Connector::getInstance()` or `PhpConsole\Handler::getInstance()` must be called before any output. If headers are sent before script shut down or PHP Console response package size is out of web-server headers size limit, then PHP Console will store response data in [PhpConsole\Storage](src/PhpConsole/Storage.php) implementation and send it to client in STDOUT, in additional HTTP request. So there is no limits in PHP Console response package size. + +### Troubleshooting with $_SESSION handler overridden in some frameworks + +By default PHP Console uses [PhpConsole\Storage\Session](src/PhpConsole/Storage/Session.php) for postponed responses, so all temporary data will be stored in `$_SESSION`. But there is some problem with frameworks like [Symfony](http://symfony.com) and [Laravel](http://laravel.com) that overrides PHP session handler. In this case you should use any other [PhpConsole\Storage](src/PhpConsole/Storage.php) implementation like: + + // Can be called only before PhpConsole\Connector::getInstance() and PhpConsole\Handler::getInstance() + PhpConsole\Connector::setPostponeStorage(new PhpConsole\Storage\File('/tmp/pc.data')); + +See all available [PhpConsole\Storage](src/PhpConsole/Storage.php) implementations in [/src/PhpConsole/Storage](src/PhpConsole/Storage). ### Strip sources base path diff --git a/src/PhpConsole/Auth.php b/src/PhpConsole/Auth.php index 15d508e..9c12a98 100644 --- a/src/PhpConsole/Auth.php +++ b/src/PhpConsole/Auth.php @@ -6,7 +6,7 @@ * PHP Console client authorization credentials & validation class * * @package PhpConsole - * @version 3.0 + * @version 3.1 * @link http://php-console.com * @author Sergey Barbushin http://linkedin.com/in/barbushin * @copyright © Sergey Barbushin, 2011-2013. All rights reserved. diff --git a/src/PhpConsole/Connector.php b/src/PhpConsole/Connector.php index 468cfec..f7aa2f4 100644 --- a/src/PhpConsole/Connector.php +++ b/src/PhpConsole/Connector.php @@ -9,7 +9,7 @@ * https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef * * @package PhpConsole - * @version 3.0 + * @version 3.1 * @link http://php-console.com * @author Sergey Barbushin http://linkedin.com/in/barbushin * @copyright © Sergey Barbushin, 2011-2013. All rights reserved. @@ -22,14 +22,16 @@ class Connector { const CLIENT_INFO_COOKIE = 'php-console-client'; const CLIENT_ENCODING = 'UTF-8'; const HEADER_NAME = 'PHP-Console'; + const POSTPONE_HEADER_NAME = 'PHP-Console-Postpone'; const POST_VAR_NAME = '__PHP_Console'; - const SESSION_KEY = '__PHP_Console'; const POSTPONE_REQUESTS_LIMIT = 10; const PHP_HEADERS_SIZE = 1000; // maximum PHP response headers size const CLIENT_HEADERS_LIMIT = 200000; /** @var Connector */ protected static $instance; + /** @var Storage|null */ + private static $postponeStorage; /** @var Dumper|null */ protected $dumper; @@ -40,7 +42,7 @@ class Connector { /** @var Dispatcher\Evaluate|null */ protected $evalDispatcher; /** @var string */ - protected $serverEncoding; + protected $serverEncoding = self::CLIENT_ENCODING; protected $sourcesBasePath; protected $headersLimit; @@ -50,6 +52,7 @@ class Connector { private $auth; /** @var Message[] */ private $messages = array(); + private $postponeResponseId; private $isSslOnlyMode = false; private $isActiveClient = false; private $isAuthorized = false; @@ -66,6 +69,29 @@ public static function getInstance() { return self::$instance; } + /** + * Set storage for postponed response data. Storage\Session is used by default, but if you have problems with overridden session handler you should use another one. + * IMPORTANT: This method cannot be called after Connector::getInstance() + * @param Storage $storage + * @throws \Exception + */ + public static function setPostponeStorage(Storage $storage) { + if(self::$instance) { + throw new \Exception(__METHOD__ . ' can be called only before ' . __CLASS__ . '::getInstance()'); + } + static::$postponeStorage = $storage; + } + + /** + * @return Storage + */ + private final function getPostponeStorage() { + if(!static::$postponeStorage) { + static::$postponeStorage = new Storage\Session(); + } + return static::$postponeStorage; + } + protected function __construct() { $this->initConnection(); $this->setServerEncoding(ini_get('mbstring.internal_encoding') ? : self::CLIENT_ENCODING); @@ -102,8 +128,8 @@ private final function initConnection() { ? 4096 // default headers limit for Nginx : 8192 // default headers limit for all other web-servers ); - $this->listenGetPostponedResponse(); + $this->postponeResponseId = $this->setPostponeHeader(); } } @@ -463,17 +489,31 @@ private final function proceedResponsePackage() { $responseData = $this->serializeResponse($response); - if(strlen($responseData) > $this->headersLimit) { - $responseData = $this->serializeResponse(new PostponedResponse(array( - 'id' => $this->postponeResponse($responseData) - ))); + if(strlen($responseData) > $this->headersLimit || !$this->setHeaderData($responseData, self::HEADER_NAME, false)) { + $this->getPostponeStorage()->push($this->postponeResponseId, $responseData); } + } + } + + private final function setPostponeHeader() { + $postponeResponseId = mt_rand() . mt_rand() . mt_rand(); + $this->setHeaderData($this->serializeResponse( + new PostponedResponse(array( + 'id' => $postponeResponseId + )) + ), self::POSTPONE_HEADER_NAME, true); + return $postponeResponseId; + } - if(headers_sent($file, $line)) { - throw new \Exception('Unable to process response data, headers already sent in ' . $file . ':' . $line . '. Try to use ob_start()'); + private final function setHeaderData($responseData, $headerName, $throwException = true) { + if(headers_sent($file, $line)) { + if($throwException) { + throw new \Exception('Unable to process response data, headers already sent in ' . $file . ':' . $line . '. Try to use ob_start() and don\'t use flush().'); } - header(self::HEADER_NAME . ': ' . $responseData); + return false; } + header($headerName . ': ' . $responseData); + return true; } protected function objectToArray(&$var) { @@ -497,55 +537,11 @@ protected function serializeResponse(DataObject $response) { private final function listenGetPostponedResponse() { if(isset($_POST[self::POST_VAR_NAME]['getPostponedResponse'])) { header('Content-Type: application/json; charset=' . self::CLIENT_ENCODING); - echo $this->getPostponedResponse($_POST[self::POST_VAR_NAME]['getPostponedResponse']); + echo $this->getPostponeStorage()->pop($_POST[self::POST_VAR_NAME]['getPostponedResponse']); $this->breakClientConnection(); exit; } } - - /** - * Store postponed response data - * @param string $responseData - * @return string id - */ - protected function postponeResponse($responseData) { - $responses =& $this->getSessionPostponedResponses(); - while(count($responses) >= static::POSTPONE_REQUESTS_LIMIT) { - array_shift($responses); - } - $id = mt_rand() . mt_rand(); - $responses[$id] = $responseData; - return $id; - } - - /** - * Get postponed response data by id - * @param string $responseId - * @return string|null - */ - protected function getPostponedResponse($responseId) { - $responses =& $this->getSessionPostponedResponses(); - if(isset($responses[$responseId])) { - $responseData = $responses[$responseId]; - unset($responses[$responseId]); - return $responseData; - } - } - - /** - * Returns reference to postponed responses array in $_SESSION - * @return array - */ - protected function &getSessionPostponedResponses() { - if(PHP_VERSION >= '5.4' ? session_status() != PHP_SESSION_ACTIVE : !session_id()) { - session_start(); - register_shutdown_function('session_write_close'); // force saving session data if session handler is overridden - } - if(!isset($_SESSION[static::SESSION_KEY]['postpone'])) { - $_SESSION[static::SESSION_KEY]['postpone'] = array(); - } - return $_SESSION[static::SESSION_KEY]['postpone']; - } } abstract class DataObject { diff --git a/src/PhpConsole/Dispatcher.php b/src/PhpConsole/Dispatcher.php index 0aa8324..16dbb59 100644 --- a/src/PhpConsole/Dispatcher.php +++ b/src/PhpConsole/Dispatcher.php @@ -6,7 +6,7 @@ * Abstract class of dispatchers that sends different kind data to connector as client expected messages * * @package PhpConsole - * @version 3.0 + * @version 3.1 * @link http://php-console.com * @author Sergey Barbushin http://linkedin.com/in/barbushin * @copyright © Sergey Barbushin, 2011-2013. All rights reserved. diff --git a/src/PhpConsole/Dispatcher/Debug.php b/src/PhpConsole/Dispatcher/Debug.php index 0738d0c..a22952e 100644 --- a/src/PhpConsole/Dispatcher/Debug.php +++ b/src/PhpConsole/Dispatcher/Debug.php @@ -6,7 +6,7 @@ * Sends debug data to connector as client expected messages * * @package PhpConsole - * @version 3.0 + * @version 3.1 * @link http://php-console.com * @author Sergey Barbushin http://linkedin.com/in/barbushin * @copyright © Sergey Barbushin, 2011-2013. All rights reserved. diff --git a/src/PhpConsole/Dispatcher/Errors.php b/src/PhpConsole/Dispatcher/Errors.php index 0b829ab..6fb777d 100644 --- a/src/PhpConsole/Dispatcher/Errors.php +++ b/src/PhpConsole/Dispatcher/Errors.php @@ -6,7 +6,7 @@ * Sends system errors and exceptions to connector as client expected messages * * @package PhpConsole - * @version 3.0 + * @version 3.1 * @link http://php-console.com * @author Sergey Barbushin http://linkedin.com/in/barbushin * @copyright © Sergey Barbushin, 2011-2013. All rights reserved. diff --git a/src/PhpConsole/Dispatcher/Evaluate.php b/src/PhpConsole/Dispatcher/Evaluate.php index cd66cc2..55bb57c 100644 --- a/src/PhpConsole/Dispatcher/Evaluate.php +++ b/src/PhpConsole/Dispatcher/Evaluate.php @@ -6,7 +6,7 @@ * Executes client code and sends result data to connector as client expected messages * * @package PhpConsole - * @version 3.0 + * @version 3.1 * @link http://php-console.com * @author Sergey Barbushin http://linkedin.com/in/barbushin * @copyright © Sergey Barbushin, 2011-2013. All rights reserved. diff --git a/src/PhpConsole/Dumper.php b/src/PhpConsole/Dumper.php index 555109c..7bf36b6 100644 --- a/src/PhpConsole/Dumper.php +++ b/src/PhpConsole/Dumper.php @@ -6,7 +6,7 @@ * Convert any type of var to string or array with different kind of limits * * @package PhpConsole - * @version 3.0 + * @version 3.1 * @link http://php-console.com * @author Sergey Barbushin http://linkedin.com/in/barbushin * @copyright © Sergey Barbushin, 2011-2013. All rights reserved. diff --git a/src/PhpConsole/EvalProvider.php b/src/PhpConsole/EvalProvider.php index 37e918c..180c6f6 100644 --- a/src/PhpConsole/EvalProvider.php +++ b/src/PhpConsole/EvalProvider.php @@ -6,7 +6,7 @@ * Execute PHP code with some security & accessibility tweaks * * @package PhpConsole - * @version 3.0 + * @version 3.1 * @link http://php-console.com * @author Sergey Barbushin http://linkedin.com/in/barbushin * @copyright © Sergey Barbushin, 2011-2013. All rights reserved. diff --git a/src/PhpConsole/Handler.php b/src/PhpConsole/Handler.php index 2be6913..e632549 100644 --- a/src/PhpConsole/Handler.php +++ b/src/PhpConsole/Handler.php @@ -10,7 +10,7 @@ * https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef * * @package PhpConsole - * @version 3.0 + * @version 3.1 * @link http://php-console.com * @author Sergey Barbushin http://linkedin.com/in/barbushin * @copyright © Sergey Barbushin, 2011-2013. All rights reserved. diff --git a/src/PhpConsole/Helper.php b/src/PhpConsole/Helper.php index d19a66e..195ff2b 100644 --- a/src/PhpConsole/Helper.php +++ b/src/PhpConsole/Helper.php @@ -12,7 +12,7 @@ * It will be the same as calling Handler::getInstance()->debug($var, 'db') * * @package PhpConsole - * @version 3.0 + * @version 3.1 * @link http://php-console.com * @author Sergey Barbushin http://linkedin.com/in/barbushin * @copyright © Sergey Barbushin, 2011-2013. All rights reserved. diff --git a/src/PhpConsole/OldVersionAdapter.php b/src/PhpConsole/OldVersionAdapter.php index ab52a36..613a9d9 100644 --- a/src/PhpConsole/OldVersionAdapter.php +++ b/src/PhpConsole/OldVersionAdapter.php @@ -23,7 +23,7 @@ * IMPORTANT: This adapter will be removed in PhpConsole > v3, so it's strongly recommended to migrate your code using original PhpConsole v3 methods * * @package PhpConsole - * @version 3.0 + * @version 3.1 * @link http://php-console.com * @author Sergey Barbushin http://linkedin.com/in/barbushin * @copyright © Sergey Barbushin, 2011-2013. All rights reserved. diff --git a/src/PhpConsole/PsrLogger.php b/src/PhpConsole/PsrLogger.php index b1c89f5..094c580 100644 --- a/src/PhpConsole/PsrLogger.php +++ b/src/PhpConsole/PsrLogger.php @@ -9,7 +9,7 @@ * IMPORTANT: https://github.com/php-fig/log must be installed & autoloaded * * @package PhpConsole - * @version 3.0 + * @version 3.1 * @link http://php-console.com * @author Sergey Barbushin http://linkedin.com/in/barbushin * @copyright © Sergey Barbushin, 2011-2013. All rights reserved. diff --git a/src/PhpConsole/Storage.php b/src/PhpConsole/Storage.php new file mode 100644 index 0000000..bb36c21 --- /dev/null +++ b/src/PhpConsole/Storage.php @@ -0,0 +1,40 @@ +keyLifetime = $seconds; + } +} diff --git a/src/PhpConsole/Storage/AllKeysList.php b/src/PhpConsole/Storage/AllKeysList.php new file mode 100644 index 0000000..53c3d95 --- /dev/null +++ b/src/PhpConsole/Storage/AllKeysList.php @@ -0,0 +1,71 @@ +getKeysData(); + if(isset($keysData[$key])) { + $keyData = $keysData[$key]['data']; + unset($keysData[$key]); + $this->saveKeysData($keysData); + return $keyData; + } + } + + /** + * Save postponed data to storage + * @param string $key + * @param string $data + */ + public function push($key, $data) { + $keysData = $this->getKeysData(); + $this->clearExpiredKeys($keysData); + $keysData[$key] = array( + 'time' => time(), + 'data' => $data + ); + $this->saveKeysData($keysData); + } + + /** + * Remove postponed data that is out of limit + * @param array $keysData + */ + protected function clearExpiredKeys(array &$keysData) { + $expireTime = time() - $this->keyLifetime; + foreach($keysData as $key => $item) { + if($item['time'] < $expireTime) { + unset($keysData[$key]); + } + } + } +} diff --git a/src/PhpConsole/Storage/ExpiringKeyValue.php b/src/PhpConsole/Storage/ExpiringKeyValue.php new file mode 100644 index 0000000..2ed308d --- /dev/null +++ b/src/PhpConsole/Storage/ExpiringKeyValue.php @@ -0,0 +1,60 @@ +get($key); + if($data) { + $this->delete($key); + } + return $data; + } + + /** + * Save postponed data to storage + * @param string $key + * @param string $data + */ + public function push($key, $data) { + $this->set($key, $data, $this->keyLifetime); + } +} diff --git a/src/PhpConsole/Storage/File.php b/src/PhpConsole/Storage/File.php new file mode 100644 index 0000000..7a4e4a9 --- /dev/null +++ b/src/PhpConsole/Storage/File.php @@ -0,0 +1,93 @@ +filePath = realpath($filePath); + + if($validatePathNotUnderDocRoot && $this->isPathUnderDocRoot()) { + throw new \Exception('Path ' . $this->filePath . ' is under DOCUMENT_ROOT. It\'s insecure!'); + } + } + + protected function isPathUnderDocRoot() { + return !empty($_SERVER['DOCUMENT_ROOT']) && strpos($this->filePath, $_SERVER['DOCUMENT_ROOT']) === 0; + } + + protected function initFileHandler() { + $this->fileHandler = fopen($this->filePath, 'a+b'); + if(!$this->fileHandler) { + throw new \Exception('Unable to read/write file ' . $this->filePath); + } + while(!flock($this->fileHandler, LOCK_EX | LOCK_NB)) { + usleep(10000); + } + fseek($this->fileHandler, 0); + } + + /** + * @throws \Exception + * @return array + */ + protected function getKeysData() { + return json_decode(fgets($this->fileHandler), true) ? : array(); + } + + /** + * @param array $keysData + */ + protected function saveKeysData(array $keysData) { + ftruncate($this->fileHandler, 0); + fwrite($this->fileHandler, json_encode($keysData, defined('JSON_UNESCAPED_UNICODE') ? JSON_UNESCAPED_UNICODE : null)); + } + + protected function closeFileHandler() { + if($this->fileHandler) { + flock($this->fileHandler, LOCK_UN); + fclose($this->fileHandler); + $this->fileHandler = null; + } + } + + public function pop($key) { + $this->initFileHandler(); + $result = parent::pop($key); + $this->closeFileHandler(); + return $result; + } + + public function push($key, $data) { + $this->initFileHandler(); + parent::push($key, $data); + $this->closeFileHandler(); + } + + public function __destruct() { + $this->closeFileHandler(); + } +} diff --git a/src/PhpConsole/Storage/Memcache.php b/src/PhpConsole/Storage/Memcache.php new file mode 100644 index 0000000..300dd97 --- /dev/null +++ b/src/PhpConsole/Storage/Memcache.php @@ -0,0 +1,54 @@ +connect($host, $port)) { + throw new \Exception('Unable to connect to Memcache server'); + } + } + + /** + * Save data by auto-expire key + * @param $key + * @param string $data + * @param int $expire + */ + protected function set($key, $data, $expire) { + $this->memcache->set($key, $data, null, $expire); + } + + /** + * Get data by key if not expired + * @param $key + * @return string + */ + protected function get($key) { + return $this->memcache->get($key); + } + + /** + * Remove key in store + * @param $key + * @return mixed + */ + protected function delete($key) { + $this->memcache->delete($key); + } +} diff --git a/src/PhpConsole/Storage/Session.php b/src/PhpConsole/Storage/Session.php new file mode 100644 index 0000000..3ff8bbb --- /dev/null +++ b/src/PhpConsole/Storage/Session.php @@ -0,0 +1,38 @@ +sessionKey = $sessionKey; + } + + protected function getKeysData() { + return isset($_SESSION[$this->sessionKey]) ? $_SESSION[$this->sessionKey] : array(); + } + + protected function saveKeysData(array $keysData) { + $_SESSION[$this->sessionKey] = $keysData; + } +} diff --git a/tests/ClientEmulator/Connector.php b/tests/ClientEmulator/Connector.php index 86b83a0..a6cb25a 100644 --- a/tests/ClientEmulator/Connector.php +++ b/tests/ClientEmulator/Connector.php @@ -159,7 +159,13 @@ protected function runScript($_alias, $_params) { protected function parseHeaderData($headersData) { if(preg_match_all('/\n\s*' . preg_quote(\PhpConsole\Connector::HEADER_NAME) . '\s*:\s*(.*?)[\r\n]/', $headersData, $m)) { if(count($m[1]) > 1) { - throw new \Exception('There is more than one PHP Console header'); + throw new \Exception('There is more than one "' . \PhpConsole\Connector::HEADER_NAME . '" header'); + } + return rawurldecode($m[1][0]); + } + elseif(preg_match_all('/\n\s*' . preg_quote(\PhpConsole\Connector::POSTPONE_HEADER_NAME) . '\s*:\s*(.*?)[\r\n]/', $headersData, $m)) { + if(count($m[1]) > 1) { + throw new \Exception('There is more than one "' . \PhpConsole\Connector::POSTPONE_HEADER_NAME . '" header'); } return rawurldecode($m[1][0]); } diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..3755bdb --- /dev/null +++ b/tests/README.md @@ -0,0 +1,7 @@ +Before run tests you have to: + + 1. Make directory `./tmp` writable + 2. Run `composer install` + 3. Edit `./config.php` + 4. Run PHP with `-d auto_prepend_file=./vendor/autoload.php` + 5. Use `./phpunit.xml` as test configuration file diff --git a/tests/Test/Storage.php b/tests/Test/Storage.php new file mode 100644 index 0000000..d32ca68 --- /dev/null +++ b/tests/Test/Storage.php @@ -0,0 +1,50 @@ +storage = $this->initStorage(); + } + + protected function generateKey() { + return mt_rand() . mt_rand() . mt_rand(); + } + + public function testPush() { + $key = $this->generateKey(); + $data = $this->generateKey(); + $this->storage->push($key, $data); + $this->assertEquals($data, $this->storage->pop($key)); + } + + public function testPop() { + $key = $this->generateKey(); + $data = $this->generateKey(); + $this->storage->push($key, $data); + $this->storage->pop($key); + $this->assertNull($this->storage->pop($key)); + } + + /** + * @group slow + */ + public function testSetKeyLifetime() { + $key = $this->generateKey(); + $this->storage->setKeyLifetime(1); + $this->storage->push($key, 123); + sleep(2); + $this->storage->push($this->generateKey(), 123); + $this->assertNull($this->storage->pop($key)); + } +} diff --git a/tests/Test/Storage/File.php b/tests/Test/Storage/File.php new file mode 100644 index 0000000..5dade0a --- /dev/null +++ b/tests/Test/Storage/File.php @@ -0,0 +1,40 @@ +filePath = \PhpConsole\Test\BASE_DIR . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . 'file_storage_test.data'; + if(file_exists($this->filePath)) { + unlink($this->filePath); + } + return new \PhpConsole\Storage\File($this->filePath, $validatePathUnderDocRoot); + } + + protected function tearDown() { + parent::tearDown(); + if(file_exists($this->filePath)) { + unlink($this->filePath); + } + } + + /** + * @expectedException \Exception + */ + public function testPathUnderDocRootThrowsException() { + $_SERVER['DOCUMENT_ROOT'] = dirname($this->filePath); + $this->initStorage(true); + } + + public function testPathNotUnderDocRoot() { + $_SERVER['DOCUMENT_ROOT'] = $this->filePath . DIRECTORY_SEPARATOR . 'bla'; + $this->initStorage(true); + } +} diff --git a/tests/Test/Storage/Memcache.php b/tests/Test/Storage/Memcache.php new file mode 100644 index 0000000..09eff60 --- /dev/null +++ b/tests/Test/Storage/Memcache.php @@ -0,0 +1,21 @@ +markTestSkipped('Memcache extension not installed'); + } + try { + return new \PhpConsole\Storage\Memcache(); + } + catch(\Exception $exception) { + $this->markTestSkipped('Unable to connect to memcache server'); + } + } +} diff --git a/tests/Test/Storage/Session.php b/tests/Test/Storage/Session.php new file mode 100644 index 0000000..b6b9d71 --- /dev/null +++ b/tests/Test/Storage/Session.php @@ -0,0 +1,14 @@ +