diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d2d82e106c..692bff039ce 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ -# [3.1.0](https://github.com/phalcon/cphalcon/releases/tag/v3.1.0) (2016-XX-XX)`` +# [3.1.0](https://github.com/phalcon/cphalcon/releases/tag/v3.1.0) (2016-XX-XX) - Added `Phalcon\Validation\Validator\Callback`, `Phalcon\Validation::getData` - Added the ability to truncate database tables +- Added `Phalcon\Mvc\Model\Binder`, class used for binding models to parameters in dispatcher, micro, added `Phalcon\Dispatcher::getBoundModels` and `Phalcon\Mvc\Micro::getBoundModels` to getting bound models, added `Phalcon\Mvc\Micro\Collection\LazyLoader::callMethod` # [3.0.2](https://github.com/phalcon/cphalcon/releases/tag/v3.0.2) (2016-XX-XX) - Fixed saving snapshot data while caching model [#12170](https://github.com/phalcon/cphalcon/issues/12170), [#12000](https://github.com/phalcon/cphalcon/issues/12000) diff --git a/phalcon/dispatcher.zep b/phalcon/dispatcher.zep index 5eaf61b016d..487d9b431ab 100755 --- a/phalcon/dispatcher.zep +++ b/phalcon/dispatcher.zep @@ -26,6 +26,8 @@ use Phalcon\DispatcherInterface; use Phalcon\Events\ManagerInterface; use Phalcon\Di\InjectionAwareInterface; use Phalcon\Events\EventsAwareInterface; +use Phalcon\Mvc\Model\Binder; +use Phalcon\Mvc\Model\BinderInterface; /** * Phalcon\Dispatcher @@ -78,6 +80,8 @@ abstract class Dispatcher implements DispatcherInterface, InjectionAwareInterfac protected _modelBinding = false; + protected _modelBinder = null; + const EXCEPTION_NO_DI = 0; const EXCEPTION_CYCLIC_ROUTING = 1; @@ -322,11 +326,61 @@ abstract class Dispatcher implements DispatcherInterface, InjectionAwareInterfac /** * Enable/Disable model binding during dispatch * - * @param boolean value + * + * $di->set('dispatcher', function() { + * $dispatcher = new Dispatcher(); + * + * $dispatcher->setModelBinding(true, 'cache'); + * return $dispatcher; + * }); + * */ - public function setModelBinding(boolean value) + public function setModelBinding(boolean value, var cache = null) -> { + var dependencyInjector; + + if typeof cache == "string" { + let dependencyInjector = this->_dependencyInjector; + let cache = dependencyInjector->get(cache); + } + let this->_modelBinding = value; + if value { + let this->_modelBinder = new Binder(cache); + } + + return this; + } + + /** + * Enable model binding during dispatch + * + * + * $di->set('dispatcher', function() { + * $dispatcher = new Dispatcher(); + * + * $dispatcher->setModelBinder(new Binder(), 'cache'); + * return $dispatcher; + * }); + * + */ + public function setModelBinder( modelBinder, var cache = null) -> + { + var dependencyInjector; + + if typeof cache == "string" { + let dependencyInjector = this->_dependencyInjector; + let cache = dependencyInjector->get(cache); + } + + if cache != null { + modelBinder->setCache(cache); + } + + let this->_modelBinding = true; + let this->_modelBinder = modelBinder; + + return this; } /** @@ -362,9 +416,8 @@ abstract class Dispatcher implements DispatcherInterface, InjectionAwareInterfac int numberDispatches; var value, handler, dependencyInjector, namespaceName, handlerName, actionName, params, eventsManager, - actionSuffix, handlerClass, status, actionMethod, reflectionMethod, methodParams, - className, paramKey, methodParam, modelName, bindModel, - wasFresh = false, e; + actionSuffix, handlerClass, status, actionMethod, modelBinder, wasFresh = false, + e, bindCacheKey; let dependencyInjector = this->_dependencyInjector; if typeof dependencyInjector != "object" { @@ -551,34 +604,10 @@ abstract class Dispatcher implements DispatcherInterface, InjectionAwareInterfac } } - if this->_modelBinding === true { - //Check if we can bind a model based on what the controller action is expecting - let reflectionMethod = new \ReflectionMethod(handlerClass, actionMethod); - let methodParams = reflectionMethod->getParameters(); - - for paramKey, methodParam in methodParams { - if methodParam->getClass() { - let className = methodParam->getClass()->getName(); - if typeof className == "string" { - //If we are in a base class and the child implements BindModelInterface we getModelName - if className == "Phalcon\\Mvc\\Model" { - if in_array("Phalcon\\Mvc\\Controller\\BindModelInterface", class_implements(handlerClass)) { - let modelName = call_user_func([handlerClass, "getModelName"]); - let bindModel = call_user_func_array([modelName, "findFirst"], [params[paramKey]]); - let params[paramKey] = bindModel; - break; - } - } - - //Check if Model is defined - if is_subclass_of(className, "Phalcon\\Mvc\\Model") { - let bindModel = call_user_func_array([className, "findFirst"], [params[paramKey]]); - let params[paramKey] = bindModel; - break; - } - } - } - } + if this->_modelBinding { + let modelBinder = this->_modelBinder; + let bindCacheKey = "_PHMB_" . handlerClass . "_" . actionMethod; + let params = modelBinder->bindToHandler(handler, params, bindCacheKey, actionMethod); } let this->_lastHandler = handler; @@ -736,6 +765,32 @@ abstract class Dispatcher implements DispatcherInterface, InjectionAwareInterfac return call_user_func_array([handler, actionMethod], params); } + /** + * Returns bound models from binder instance + * + * + * class UserController extends Controller + * { + * public function showAction(User $user) + * { + * $boundModels = $this->dispatcher->getBoundModels(); // return array with $user + * } + * } + * + */ + public function getBoundModels() -> array + { + var modelBinder; + + let modelBinder = this->_modelBinder; + + if modelBinder != null { + return modelBinder->getBoundModels(); + } + + return []; + } + /** * Set empty properties to their defaults (where defaults are available) */ diff --git a/phalcon/mvc/micro.zep b/phalcon/mvc/micro.zep index b4a6d0cb758..6a08d5f2903 100644 --- a/phalcon/mvc/micro.zep +++ b/phalcon/mvc/micro.zep @@ -30,6 +30,8 @@ use Phalcon\Http\ResponseInterface; use Phalcon\Mvc\Router\RouteInterface; use Phalcon\Mvc\Micro\MiddlewareInterface; use Phalcon\Mvc\Micro\CollectionInterface; +use Phalcon\Mvc\Controller; +use Phalcon\Mvc\Model\BinderInterface; /** * Phalcon\Mvc\Micro @@ -76,6 +78,8 @@ class Micro extends Injectable implements \ArrayAccess protected _returnedValue; + protected _modelBinder; + /** * Phalcon\Mvc\Micro constructor */ @@ -365,7 +369,7 @@ class Micro extends Injectable implements \ArrayAccess public function mount( collection) -> { var mainHandler, handlers, lazyHandler, prefix, methods, pattern, - subHandler, realHandler, prefixedPattern, route, handler, name; + subHandler, realHandler, prefixedPattern, route, handler, name, modelBinder; /** * Get the main handler @@ -581,7 +585,7 @@ class Micro extends Injectable implements \ArrayAccess var dependencyInjector, eventsManager, status = null, router, matchedRoute, handler, beforeHandlers, params, returnedValue, e, errorHandler, afterHandlers, notFoundHandler, finishHandlers, finish, before, after, - response; + response, modelBinder, bindCacheKey, routeName, realHandler = null, methodName, lazyReturned; let dependencyInjector = this->_dependencyInjector; if typeof dependencyInjector != "object" { @@ -690,17 +694,52 @@ class Micro extends Injectable implements \ArrayAccess let params = router->getParams(); + let modelBinder = this->_modelBinder; + /** * Bound the app to the handler */ if typeof handler == "object" && handler instanceof \Closure { let handler = \Closure::bind(handler, this); + if modelBinder != null { + let routeName = matchedRoute->getName(); + if routeName != null { + let bindCacheKey = "_PHMB_" . routeName; + } else { + let bindCacheKey = "_PHMB_" . matchedRoute->getPattern(); + } + let params = modelBinder->bindToHandler(handler, params, bindCacheKey); + } } /** * Calling the Handler in the PHP userland */ - let returnedValue = call_user_func_array(handler, params); + + if typeof handler == "array" { + + let realHandler = handler[0]; + + if realHandler instanceof Controller && modelBinder != null { + let methodName = handler[1]; + let bindCacheKey = "_PHMB_" . get_class(realHandler) . "_" . methodName; + let params = modelBinder->bindToHandler(realHandler, params, bindCacheKey, methodName); + } + } + + /** + * Instead of double call_user_func_array when lazy loading we will just call method + */ + if realHandler != null && realHandler instanceof LazyLoader { + let methodName = handler[1]; + /** + * There is seg fault if we try set directly value of method to returnedValue + */ + let lazyReturned = realHandler->callMethod(methodName, params, modelBinder); + let returnedValue = lazyReturned; + } else { + let returnedValue = call_user_func_array(handler, params); + } /** * Update the returned value @@ -1040,4 +1079,46 @@ class Micro extends Injectable implements \ArrayAccess { return this->_handlers; } + + /** + * Sets model binder + * + * + * $micro = new Micro($di); + * $micro->setModelBinder(new Binder(), 'cache'); + * + */ + public function setModelBinder( modelBinder, var cache = null) -> + { + var dependencyInjector; + + if typeof cache == "string" { + let dependencyInjector = this->_dependencyInjector; + let cache = dependencyInjector->get(cache); + } + + if cache != null { + modelBinder->setCache(cache); + } + + let this->_modelBinder = modelBinder; + + return this; + } + + /** + * Returns bound models from binder instance + */ + public function getBoundModels() -> array + { + var modelBinder; + + let modelBinder = this->_modelBinder; + + if modelBinder != null { + return modelBinder->getBoundModels(); + } + + return []; + } } diff --git a/phalcon/mvc/micro/lazyloader.zep b/phalcon/mvc/micro/lazyloader.zep index d1e4ab44940..d3369098b05 100644 --- a/phalcon/mvc/micro/lazyloader.zep +++ b/phalcon/mvc/micro/lazyloader.zep @@ -19,6 +19,8 @@ namespace Phalcon\Mvc\Micro; +use Phalcon\Mvc\Model\BinderInterface; + /** * Phalcon\Mvc\Micro\LazyLoader * @@ -28,6 +30,8 @@ class LazyLoader { protected _handler; + protected _modelBinder; + protected _definition { get }; @@ -49,19 +53,41 @@ class LazyLoader */ public function __call(string! method, arguments) { - var handler, definition; + var handler, definition, modelBinder, bindCacheKey; let handler = this->_handler; + let definition = this->_definition; + if typeof handler != "object" { - let definition = this->_definition; let handler = new {definition}(); let this->_handler = handler; } + let modelBinder = this->_modelBinder; + + if modelBinder != null { + let bindCacheKey = "_PHMB_" . definition . "_" . method; + let arguments = modelBinder->bindToHandler(handler, arguments, bindCacheKey, method); + } + /** * Call the handler */ return call_user_func_array([handler, method], arguments); } + + /** + * Calling __call method + * + * @param string method + * @param array arguments + * @return mixed + */ + public function callMethod(string! method, arguments, modelBinder = null) + { + let this->_modelBinder = modelBinder; + + return this->__call(method, arguments); + } } diff --git a/phalcon/mvc/model/binder.zep b/phalcon/mvc/model/binder.zep new file mode 100644 index 00000000000..2bb72cde253 --- /dev/null +++ b/phalcon/mvc/model/binder.zep @@ -0,0 +1,196 @@ + +/* + +------------------------------------------------------------------------+ + | Phalcon Framework | + +------------------------------------------------------------------------+ + | Copyright (c) 2011-2016 Phalcon Team (http://www.phalconphp.com) | + +------------------------------------------------------------------------+ + | This source file is subject to the New BSD License that is bundled | + | with this package in the file docs/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. | + +------------------------------------------------------------------------+ + | Authors: Andres Gutierrez | + | Eduar Carvajal | + | Wojciech Ślawski | + +------------------------------------------------------------------------+ + */ + +namespace Phalcon\Mvc\Model; + +use Phalcon\Mvc\Controller\BindModelInterface; +use Phalcon\Mvc\Model\Binder\BindableInterface; +use Phalcon\Cache\BackendInterface; + +/** + * Phalcon\Mvc\Model\Binding + * + * This is an class for binding models into params for handler + */ +class Binder implements BinderInterface +{ + /** + * Array for storing active bound models + * + * @var array + */ + protected boundModels = [] { get }; + + /** + * Cache object used for caching parameters for model binding + */ + protected cache ; + + /** + * Internal cache for caching parameters for model binding during request + */ + protected internalCache = []; + + /** + * Phalcon\Mvc\Model\Binder constructor + */ + public function __construct( cache = null) + { + let this->cache = cache; + } + + /** + * Gets cache instance + */ + public function setCache( cache) -> + { + let this->cache = cache; + + return this; + } + + /** + * Sets cache instance + */ + public function getCache() -> + { + return this->cache; + } + + /** + * Bind models into params in proper handler + */ + public function bindToHandler(object handler, array params, string cacheKey, var methodName = null) -> array + { + var paramKey, className, boundModel, paramsCache; + + if handler instanceof \Closure || methodName != null { + let this->boundModels = []; + let paramsCache = this->getParamsFromCache(cacheKey); + if typeof paramsCache == "array" { + for paramKey, className in paramsCache { + let boundModel = {className}::findFirst(params[paramKey]); + let params[paramKey] = boundModel; + let this->boundModels[paramKey] = boundModel; + } + + return params; + } + + return this->getParamsFromReflection(handler, params, cacheKey, methodName); + } + throw new Exception("You must specify methodName for handler or pass Closure as handler"); + } + + /** + * Get params classes from cache by key + */ + protected function getParamsFromCache(string cacheKey) -> array | null + { + var cache, internalParams; + + if fetch internalParams, this->internalCache[cacheKey] { + return internalParams; + } + + let cache = this->cache; + + if cache != null && cache->exists(cacheKey) { + return cache->get(cacheKey); + } + + return null; + } + + /** + * Get modified params for handler using reflection + */ + protected function getParamsFromReflection(object handler, array params, string cacheKey, var methodName) -> array + { + var methodParams, reflection, paramKey, methodParam, paramsCache, className, realClasses = null, + boundModel, cache, handlerClass, reflectionClass, paramsKeys; + let paramsCache = []; + if methodName != null { + let reflection = new \ReflectionMethod(handler, methodName); + } else { + let reflection = new \ReflectionFunction(handler); + } + + let cache = this->cache; + + let methodParams = reflection->getParameters(); + let paramsKeys = array_keys(params); + for paramKey, methodParam in methodParams { + let reflectionClass = methodParam->getClass(); + + if !reflectionClass { + continue; + } + + let className = reflectionClass->getName(); + if !isset params[paramKey] { + let paramKey = paramsKeys[paramKey]; + } + let boundModel = null; + if className == "Phalcon\\Mvc\\Model" { + if realClasses == null { + if handler instanceof BindModelInterface { + let handlerClass = get_class(handler); + let realClasses = {handlerClass}::getModelName(); + } elseif handler instanceof BindableInterface { + let realClasses = handler->getModelName(); + } else { + throw new Exception("Handler must implement Phalcon\\Mvc\\Model\\Binder\\BindableInterface in order to use Phalcon\\Mvc\\Model as parameter"); + } + } + if typeof realClasses == "array" { + if fetch className, realClasses[paramKey] { + let boundModel = {className}::findFirst(params[paramKey]); + } else { + throw new Exception("You should provide model class name for ".paramKey." parameter"); + } + } elseif typeof realClasses == "string" { + let boundModel = {realClasses}::findFirst(params[paramKey]); + let className = realClasses; + } else { + throw new Exception("getModelName should return array or string"); + } + } elseif is_subclass_of(className, "Phalcon\\Mvc\\Model") { + let boundModel = {className}::findFirst(params[paramKey]); + } + + if boundModel != null { + let params[paramKey] = boundModel; + let this->boundModels[paramKey] = boundModel; + if cache != null { + let paramsCache[paramKey] = className; + } + } + } + + if cache != null { + cache->save(cacheKey, paramsCache); + } + + let this->internalCache[cacheKey] = paramsCache; + + return params; + } +} diff --git a/phalcon/mvc/model/binder/bindableinterface.zep b/phalcon/mvc/model/binder/bindableinterface.zep new file mode 100644 index 00000000000..0e47b1394d9 --- /dev/null +++ b/phalcon/mvc/model/binder/bindableinterface.zep @@ -0,0 +1,34 @@ + +/* + +------------------------------------------------------------------------+ + | Phalcon Framework | + +------------------------------------------------------------------------+ + | Copyright (c) 2011-2016 Phalcon Team (https://phalconphp.com) | + +------------------------------------------------------------------------+ + | This source file is subject to the New BSD License that is bundled | + | with this package in the file docs/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. | + +------------------------------------------------------------------------+ + | Authors: Andres Gutierrez | + | Eduar Carvajal | + | Wojciech Ślawski | + +------------------------------------------------------------------------+ + */ + +namespace Phalcon\Mvc\Model\Binder; + +/** + * Phalcon\Mvc\Controller\Binder\BindableInterface + * + * Interface for bindable classes + */ +interface BindableInterface +{ + /** + * Return the model name or models names and parameters keys associated with this class + */ + abstract function getModelName() -> string | array; +} diff --git a/phalcon/mvc/model/binderinterface.zep b/phalcon/mvc/model/binderinterface.zep new file mode 100644 index 00000000000..b3376ffc02a --- /dev/null +++ b/phalcon/mvc/model/binderinterface.zep @@ -0,0 +1,51 @@ + +/* + +------------------------------------------------------------------------+ + | Phalcon Framework | + +------------------------------------------------------------------------+ + | Copyright (c) 2011-2016 Phalcon Team (http://www.phalconphp.com) | + +------------------------------------------------------------------------+ + | This source file is subject to the New BSD License that is bundled | + | with this package in the file docs/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. | + +------------------------------------------------------------------------+ + | Authors: Andres Gutierrez | + | Eduar Carvajal | + | Wojciech Ślawski | + +------------------------------------------------------------------------+ + */ + +namespace Phalcon\Mvc\Model; + +use Phalcon\Cache\BackendInterface; + +/** + * Phalcon\Mvc\Model\BinderInterface + * + * Interface for Phalcon\Mvc\Model\Binder + */ +interface BinderInterface +{ + /** + * Gets active bound models + */ + public function getBoundModels() -> array; + + /** + * Gets cache instance + */ + public function getCache() -> ; + + /** + * Sets cache instance + */ + public function setCache( cache) -> ; + + /** + * Bind models into params in proper handler + */ + public function bindToHandler(object handler, array params, string cacheKey, string! methodName = null) -> array; +} diff --git a/tests/_data/controllers/Test10Controller.php b/tests/_data/controllers/Test10Controller.php index b9e81aee322..40b837c4669 100644 --- a/tests/_data/controllers/Test10Controller.php +++ b/tests/_data/controllers/Test10Controller.php @@ -2,6 +2,7 @@ use Phalcon\Mvc\Controller; use Phalcon\Test\Models\People; +use Phalcon\Test\Models\Robots; class Test10Controller extends Controller { @@ -9,4 +10,9 @@ public function viewAction(People $people) { return $people; } + + public function multipleAction(People $people, Robots $robots) + { + return [$people, $robots]; + } } diff --git a/tests/_data/controllers/Test11Controller.php b/tests/_data/controllers/Test11Controller.php new file mode 100644 index 00000000000..09e3f445209 --- /dev/null +++ b/tests/_data/controllers/Test11Controller.php @@ -0,0 +1,26 @@ + 'Phalcon\Test\Models\People', + 'robots' => 'Phalcon\Test\Models\Robots' + ]; + } + + public function viewAction(Model $people) + { + return $people; + } + + public function multipleAction(Model $people, Model $robots) + { + return [$people, $robots]; + } +} diff --git a/tests/integration/Mvc/ControllersCest.php b/tests/integration/Mvc/ControllersCest.php index 1a3e3689b48..57a12b7bd51 100644 --- a/tests/integration/Mvc/ControllersCest.php +++ b/tests/integration/Mvc/ControllersCest.php @@ -3,6 +3,7 @@ namespace Phalcon\Test\Integration\Mvc; use Phalcon\Di; +use Phalcon\Test\Integration\Mvc\Model\BinderCest; use Test4Controller; use IntegrationTester; use Phalcon\Mvc\Dispatcher; @@ -64,6 +65,11 @@ public function testControllers(IntegrationTester $I) $I->assertEquals(count($view->getParamsToView()), 1); } + /** + * @todo Remove in 4.0.0 + * @see BinderCest::testDispatcher() + * @param IntegrationTester $I + */ public function testModelBinding(IntegrationTester $I) { $dispatcher = new Dispatcher; diff --git a/tests/integration/Mvc/Model/BinderCest.php b/tests/integration/Mvc/Model/BinderCest.php new file mode 100644 index 00000000000..61d8ba6233a --- /dev/null +++ b/tests/integration/Mvc/Model/BinderCest.php @@ -0,0 +1,849 @@ + + * @author Serghei Iakovlev + * @author Wojciech Ślawski + * @package Phalcon\Test\Integration\Mvc\Model + * + * The contents of this file are subject to the New BSD License that is + * bundled with this package in the file docs/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 that we can send you a copy immediately. + */ +class BinderCest +{ + /** + * @var Apc + */ + private $cache; + + /** + * @var Binder + */ + private $modelBinder; + + /** + * @var Manager + */ + private $modelsManager; + + /** + * @var Robots + */ + private $robot; + + /** + * @var People + */ + private $people; + + /** + * Executed before each test + * + * @param IntegrationTester $I + */ + public function _before(IntegrationTester $I) + { + Di::setDefault($I->getApplication()->getDI()); + + $this->cache = new Apc(new Data(['lifetime' => 20])); + $this->modelBinder = new Binder($this->cache); + + $this->modelsManager = $I->getApplication()->getDI()->getShared('modelsManager'); + $this->robot = Robots::findFirst(); + $this->people = People::findFirst(); + + $I->haveServiceInDi( + 'modelsMetadata', + function () { + return new Memory; + }, + true + ); + } + + /** + * Tests dispatcher and single model + * + * @author Wojciech Ślawski + * @since 2016-10-29 + * + * @param IntegrationTester $I + */ + public function testDispatcherSingleBinding(IntegrationTester $I) + { + $dispatcher = $this->createDispatcher(); + $this->assertDispatcher($dispatcher, $I); + + for ($i = 0; $i <= 1; $i++) { + $returnedValue = $this->returnDispatcherValueForAction( + $dispatcher, + 'test10', + 'view', + ['people' => $this->people->cedula] + ); + $I->assertInstanceOf('Phalcon\Test\Models\People', $returnedValue); + $I->assertEquals($this->people->cedula, $returnedValue->cedula); + $I->assertEquals( + [ + 'people' => 'Phalcon\\Test\\Models\\People', + ], + $this->cache->get('_PHMB_Test10Controller_viewAction') + ); + } + } + + /** + * Tests dispatcher and multiple model + * + * @author Wojciech Ślawski + * @since 2016-10-29 + * + * @param IntegrationTester $I + */ + public function testDispatcherMultiBinding(IntegrationTester $I) + { + + $dispatcher = $this->createDispatcher(); + $this->assertDispatcher($dispatcher, $I); + + for ($i = 0; $i <= 1; $i++) { + $returnedValue = $this->returnDispatcherValueForAction( + $dispatcher, + 'test10', + 'multiple', + [ + 'people' => $this->people->cedula, + 'robots' => $this->robot->id, + ] + ); + $I->assertInstanceOf('Phalcon\Test\Models\People', $returnedValue[0]); + $I->assertInstanceOf('Phalcon\Test\Models\Robots', $returnedValue[1]); + $I->assertEquals($this->people->cedula, $returnedValue[0]->cedula); + $I->assertEquals($this->robot->id, $returnedValue[1]->id); + $I->assertEquals( + [ + 'people' => 'Phalcon\\Test\\Models\\People', + 'robots' => 'Phalcon\\Test\\Models\\Robots', + ], + $this->cache->get('_PHMB_Test10Controller_multipleAction') + ); + } + } + + /** + * Tests dispatcher and single model with interface + * + * @author Wojciech Ślawski + * @since 2016-10-29 + * + * @param IntegrationTester $I + */ + public function testDispatcherSingleBindingWithInterface(IntegrationTester $I) + { + $dispatcher = $this->createDispatcher(); + $this->assertDispatcher($dispatcher, $I); + + for ($i = 0; $i <= 1; $i++) { + $returnedValue = $this->returnDispatcherValueForAction( + $dispatcher, + 'test11', + 'view', + [ + 'people' => $this->people->cedula, + ] + ); + + $I->assertInstanceOf('Phalcon\Test\Models\People', $returnedValue); + $I->assertEquals($this->people->cedula, $returnedValue->cedula); + $I->assertEquals( + [ + 'people' => 'Phalcon\\Test\\Models\\People', + ], + $this->cache->get('_PHMB_Test11Controller_viewAction') + ); + } + } + + /** + * Tests dispatcher and multi model with interface + * + * @author Wojciech Ślawski + * @since 2016-10-29 + * + * @param IntegrationTester $I + */ + public function testDispatcherMultiBindingWithInterface(IntegrationTester $I) + { + $dispatcher = $this->createDispatcher(); + $this->assertDispatcher($dispatcher, $I); + + for ($i = 0; $i <= 1; $i++) { + $returnedValue = $this->returnDispatcherValueForAction( + $dispatcher, + 'test11', + 'multiple', + [ + 'people' => $this->people->cedula, + 'robots' => $this->robot->id, + ] + ); + $I->assertInstanceOf('Phalcon\Test\Models\People', $returnedValue[0]); + $I->assertInstanceOf('Phalcon\Test\Models\Robots', $returnedValue[1]); + $I->assertEquals($this->people->cedula, $returnedValue[0]->cedula); + $I->assertEquals($this->robot->id, $returnedValue[1]->id); + $I->assertEquals( + [ + 'people' => 'Phalcon\\Test\\Models\\People', + 'robots' => 'Phalcon\\Test\\Models\\Robots', + ], + $this->cache->get('_PHMB_Test11Controller_multipleAction') + ); + } + } + + /** + * Tests dispatcher and single binding exception + * + * @author Wojciech Ślawski + * @since 2016-10-29 + * + * @param IntegrationTester $I + */ + public function testDispatcherSingleBindingException(IntegrationTester $I) + { + $dispatcher = $this->createDispatcher(false); + $this->assertDispatcher($dispatcher, $I); + $this->returnDispatcherValueForAction( + $dispatcher, + 'test9', + 'view', + ['people' => $this->people->cedula], + false + ); + try { + $dispatcher->dispatch(); + + $I->assertTrue( + false, + 'Here must be the exception about passing non model to the controller action' + ); + } catch (\Exception $e) { + // PHP 5.x + $I->assertEquals( + 'Argument 1 passed to Test9Controller::viewAction() must be an instance of Phalcon\Mvc\Model, string given', + $e->getMessage() + ); + } catch (\TypeError $e) { + // PHP 7.x + $I->assertEquals( + 'Argument 1 passed to Test9Controller::viewAction() must be an instance of Phalcon\Mvc\Model, string given', + $e->getMessage() + ); + } + + $dispatcher->setModelBinder($this->modelBinder); + + for ($i = 0; $i <= 1; $i++) { + + $dispatcher->dispatch(); + + $returnedValue = $dispatcher->getReturnedValue(); + $I->assertInstanceOf('Phalcon\Test\Models\People', $returnedValue); + $I->assertEquals($returnedValue->cedula, $this->people->cedula); + $I->assertEquals( + [ + 'people' => 'Phalcon\\Test\\Models\\People', + ], + $this->cache->get('_PHMB_Test9Controller_viewAction') + ); + } + } + + /** + * Tests micro with handlers and model binder + * + * @author Wojciech Ślawski + * @since 2016-10-29 + * + * @param IntegrationTester $I + */ + public function testMicroHandlerSingleBinding(IntegrationTester $I) + { + $micro = $this->createMicro($I); + + $micro->get( + '/{people}', + function (People $people) { + return $people; + } + )->setName('people'); + + for ($i = 0; $i <= 1; $i++) { + $returnedValue = $micro->handle('/'.$this->people->cedula); + + $I->assertInstanceOf('Phalcon\Test\Models\People', $returnedValue); + $I->assertEquals($this->people->cedula, $returnedValue->cedula); + $I->assertEquals( + [ + 'people' => 'Phalcon\\Test\\Models\\People', + ], + $this->cache->get('_PHMB_people') + ); + } + } + + /** + * Tests micro with handler and multi binding + * + * @author Wojciech Ślawski + * @since 2016-10-29 + * + * @param IntegrationTester $I + */ + public function testMicroHandlerMultiBinding(IntegrationTester $I) + { + $micro = $this->createMicro($I); + $micro->get( + '/{people}/robot/{robots}', + function (People $people, Robots $robot) { + return [$people, $robot]; + } + ); + + for ($i = 0; $i <= 1; $i++) { + + $returnedValue = $micro->handle('/'.$this->people->cedula.'/robot/'.$this->robot->id); + + $I->assertInstanceOf('Phalcon\Test\Models\People', $returnedValue[0]); + $I->assertInstanceOf('Phalcon\Test\Models\Robots', $returnedValue[1]); + $I->assertEquals($this->people->cedula, $returnedValue[0]->cedula); + $I->assertEquals($this->robot->id, $returnedValue[1]->id); + $I->assertEquals( + [ + 'people' => 'Phalcon\\Test\\Models\\People', + 'robots' => 'Phalcon\\Test\\Models\\Robots', + ], + $this->cache->get('_PHMB_/{people}/robot/{robots}') + ); + } + } + + /** + * Tests micro with handler and single binding exception + * + * @author Wojciech Ślawski + * @since 2016-10-29 + * + * @param IntegrationTester $I + */ + public function testMicroHandlerSingleBindingException(IntegrationTester $I) + { + $micro = $this->createMicro($I, false); + $micro->get( + '/{people}', + function (People $people) { + return $people; + } + ); + try { + $micro->handle('/'.$this->people->cedula); + + $I->assertTrue( + false, + 'Here must be the exception about passing non model to the micro handler' + ); + } catch (\Exception $e) { + // PHP 5.x + $I->assertEquals( + 'Argument 1 passed to Phalcon\Test\Integration\Mvc\Model\BinderCest::Phalcon\Test\Integration\Mvc\Model\{closure}() must be an instance of Phalcon\Test\Models\People, string given', + $e->getMessage() + ); + } catch (\TypeError $e) { + // PHP 7.x + $I->assertEquals( + 'Argument 1 passed to Phalcon\Test\Integration\Mvc\Model\BinderCest::Phalcon\Test\Integration\Mvc\Model\{closure}() must be an instance of Phalcon\Test\Models\People, string given', + $e->getMessage() + ); + } + + $micro->setModelBinder($this->modelBinder); + + for ($i = 0; $i <= 1; $i++) { + $returnedValue = $micro->handle('/'.$this->people->cedula); + + $I->assertInstanceOf('Phalcon\Test\Models\People', $returnedValue); + $I->assertEquals($this->people->cedula, $returnedValue->cedula); + $I->assertEquals( + [ + 'people' => 'Phalcon\\Test\\Models\\People', + ], + $this->cache->get('_PHMB_/{people}') + ); + } + } + + /** + * Tests micro with controllers and model binder + * + * @author Wojciech Ślawski + * @since 2016-10-29 + * + * @param IntegrationTester $I + */ + public function testMicroControllerSingleBinding(IntegrationTester $I) + { + $micro = $this->createMicro($I); + + $test10 = new Collection(); + $test10->setHandler(new \Test10Controller()); + $test10->get('/{people}', 'viewAction'); + $micro->mount($test10); + + for ($i = 0; $i <= 1; $i++) { + $returnedValue = $micro->handle('/'.$this->people->cedula); + + $I->assertInstanceOf('Phalcon\Test\Models\People', $returnedValue); + $I->assertEquals($this->people->cedula, $returnedValue->cedula); + $I->assertEquals( + [ + 'people' => 'Phalcon\\Test\\Models\\People', + ], + $this->cache->get('_PHMB_Test10Controller_viewAction') + ); + } + } + + /** + * Tests micro with controllers and model binder + * + * @author Wojciech Ślawski + * @since 2016-10-29 + * + * @param IntegrationTester $I + */ + public function testMicroControllerMultiBinding(IntegrationTester $I) + { + $micro = $this->createMicro($I); + + $test10 = new Collection(); + $test10->setHandler(new \Test10Controller()); + $test10->get('/{people}/robot/{robots}', 'multipleAction'); + $micro->mount($test10); + + for ($i = 0; $i <= 1; $i++) { + $returnedValue = $micro->handle('/'.$this->people->cedula.'/robot/'.$this->robot->id); + + $I->assertInstanceOf('Phalcon\Test\Models\People', $returnedValue[0]); + $I->assertInstanceOf('Phalcon\Test\Models\Robots', $returnedValue[1]); + $I->assertEquals($this->people->cedula, $returnedValue[0]->cedula); + $I->assertEquals($this->robot->id, $returnedValue[1]->id); + $I->assertEquals( + [ + 'people' => 'Phalcon\\Test\\Models\\People', + 'robots' => 'Phalcon\\Test\\Models\\Robots', + ], + $this->cache->get('_PHMB_Test10Controller_multipleAction') + ); + } + } + + /** + * Tests micro with controller and single binding using interface + * + * @author Wojciech Ślawski + * @since 2016-10-29 + * + * @param IntegrationTester $I + */ + public function testMicroControllerSingleBindingWithInterface(IntegrationTester $I) + { + $micro = $this->createMicro($I); + + $test11 = new Collection(); + $test11->setHandler(new \Test11Controller()); + $test11->get('/{people}', 'viewAction'); + $micro->mount($test11); + + for ($i = 0; $i <= 1; $i++) { + $returnedValue = $micro->handle('/'.$this->people->cedula); + $I->assertInstanceOf('Phalcon\Test\Models\People', $returnedValue); + $I->assertEquals($this->people->cedula, $returnedValue->cedula); + $I->assertEquals( + [ + 'people' => 'Phalcon\\Test\\Models\\People', + ], + $this->cache->get('_PHMB_Test11Controller_viewAction') + ); + } + } + + /** + * Tests micro with controller and multi binding using interface + * + * @author Wojciech Ślawski + * @since 2016-10-29 + * + * @param IntegrationTester $I + */ + public function testMicroControllerMultiBindingWithInterface(IntegrationTester $I) + { + $micro = $this->createMicro($I); + + $test11 = new Collection(); + $test11->setHandler(new \Test11Controller()); + $test11->get('/{people}/robot/{robots}', 'multipleAction'); + $micro->mount($test11); + + for ($i = 0; $i <= 1; $i++) { + $returnedValue = $micro->handle('/'.$this->people->cedula.'/robot/'.$this->robot->id); + + $I->assertInstanceOf('Phalcon\Test\Models\People', $returnedValue[0]); + $I->assertInstanceOf('Phalcon\Test\Models\Robots', $returnedValue[1]); + $I->assertEquals($this->people->cedula, $returnedValue[0]->cedula); + $I->assertEquals($this->robot->id, $returnedValue[1]->id); + $I->assertEquals( + [ + 'people' => 'Phalcon\\Test\\Models\\People', + 'robots' => 'Phalcon\\Test\\Models\\Robots', + ], + $this->cache->get('_PHMB_Test11Controller_multipleAction') + ); + } + } + + /** + * Tests micro with controller and single binding exception + * + * @author Wojciech Ślawski + * @since 2016-10-29 + * + * @param IntegrationTester $I + */ + public function testMicroControllerSingleBindingException(IntegrationTester $I) + { + $micro = $this->createMicro($I, false); + + $test9 = new Collection(); + $test9->setHandler(new \Test9Controller()); + $test9->get('/{people}', 'viewAction'); + $micro->mount($test9); + + try { + $micro->handle('/'.$this->people->cedula); + + $I->assertTrue( + false, + 'Here must be the exception about passing non model to the micro handler' + ); + } catch (\Exception $e) { + // PHP 5.x + $I->assertEquals( + 'Argument 1 passed to Test9Controller::viewAction() must be an instance of Phalcon\Mvc\Model, string given', + $e->getMessage() + ); + } catch (\TypeError $e) { + // PHP 7.x + $I->assertEquals( + 'Argument 1 passed to Test9Controller::viewAction() must be an instance of Phalcon\Mvc\Model, string given', + $e->getMessage() + ); + } + + $micro->setModelBinder($this->modelBinder); + + for ($i = 0; $i <= 1; $i++) { + $returnedValue = $micro->handle('/'.$this->people->cedula); + + $I->assertInstanceOf('Phalcon\Test\Models\People', $returnedValue); + $I->assertEquals($this->people->cedula, $returnedValue->cedula); + $I->assertEquals( + [ + 'people' => 'Phalcon\\Test\\Models\\People', + ], + $this->cache->get('_PHMB_Test9Controller_viewAction') + ); + } + } + + /** + * Tests micro with lazy controllers and model binder + * + * @author Wojciech Ślawski + * @since 2016-10-29 + * + * @param IntegrationTester $I + */ + public function testMicroLazySingleBinding(IntegrationTester $I) + { + $micro = $this->createMicro($I); + + $test10 = new Collection(); + $test10->setHandler('Test10Controller', true); + $test10->get('/{people}', 'viewAction'); + $micro->mount($test10); + + for ($i = 0; $i <= 1; $i++) { + $returnedValue = $micro->handle('/'.$this->people->cedula); + + $I->assertInstanceOf('Phalcon\Test\Models\People', $returnedValue); + $I->assertEquals($this->people->cedula, $returnedValue->cedula); + $I->assertEquals( + [ + 'people' => 'Phalcon\\Test\\Models\\People', + ], + $this->cache->get('_PHMB_Test10Controller_viewAction') + ); + } + } + + /** + * Tests micro with lazy controllers and model binder + * + * @author Wojciech Ślawski + * @since 2016-10-29 + * + * @param IntegrationTester $I + */ + public function testMicroLazyMultiBinding(IntegrationTester $I) + { + $micro = $this->createMicro($I); + + $test10 = new Collection(); + $test10->setHandler('Test10Controller', true); + $test10->get('/{people}/robot/{robots}', 'multipleAction'); + $micro->mount($test10); + + for ($i = 0; $i <= 1; $i++) { + $returnedValue = $micro->handle('/'.$this->people->cedula.'/robot/'.$this->robot->id); + + $I->assertInstanceOf('Phalcon\Test\Models\People', $returnedValue[0]); + $I->assertInstanceOf('Phalcon\Test\Models\Robots', $returnedValue[1]); + $I->assertEquals($this->people->cedula, $returnedValue[0]->cedula); + $I->assertEquals($this->robot->id, $returnedValue[1]->id); + $I->assertEquals( + [ + 'people' => 'Phalcon\\Test\\Models\\People', + 'robots' => 'Phalcon\\Test\\Models\\Robots', + ], + $this->cache->get('_PHMB_Test10Controller_multipleAction') + ); + } + } + + /** + * Tests micro with lazy controllers and model binder + * + * @author Wojciech Ślawski + * @since 2016-10-29 + * + * @param IntegrationTester $I + */ + public function testMicroLazySingleBindingWithInterface(IntegrationTester $I) + { + $micro = $this->createMicro($I); + + $test11 = new Collection(); + $test11->setHandler('Test11Controller', true); + $test11->get('/{people}', 'viewAction'); + $micro->mount($test11); + + for ($i = 0; $i <= 1; $i++) { + $returnedValue = $micro->handle('/'.$this->people->cedula); + + $I->assertInstanceOf('Phalcon\Test\Models\People', $returnedValue); + $I->assertEquals($this->people->cedula, $returnedValue->cedula); + $I->assertEquals( + [ + 'people' => 'Phalcon\\Test\\Models\\People', + ], + $this->cache->get('_PHMB_Test11Controller_viewAction') + ); + } + } + + /** + * Tests micro with lazy controllers and model binder + * + * @author Wojciech Ślawski + * @since 2016-10-29 + * + * @param IntegrationTester $I + */ + public function testMicroLazyMultiBindingWithInterface(IntegrationTester $I) + { + $micro = $this->createMicro($I); + + $test11 = new Collection(); + $test11->setHandler('Test11Controller', true); + $test11->get('/{people}/robot/{robots}', 'multipleAction'); + $micro->mount($test11); + + for ($i = 0; $i <= 1; $i++) { + $returnedValue = $micro->handle('/'.$this->people->cedula.'/robot/'.$this->robot->id); + + $I->assertInstanceOf('Phalcon\Test\Models\People', $returnedValue[0]); + $I->assertInstanceOf('Phalcon\Test\Models\Robots', $returnedValue[1]); + $I->assertEquals($this->people->cedula, $returnedValue[0]->cedula); + $I->assertEquals($this->robot->id, $returnedValue[1]->id); + $I->assertEquals( + [ + 'people' => 'Phalcon\\Test\\Models\\People', + 'robots' => 'Phalcon\\Test\\Models\\Robots', + ], + $this->cache->get('_PHMB_Test11Controller_multipleAction') + ); + } + } + + /** + * Tests micro with lazy controllers and model binder + * + * @author Wojciech Ślawski + * @since 2016-10-29 + * + * @param IntegrationTester $I + */ + public function testMicroLazySingleBindingException(IntegrationTester $I) + { + $micro = $this->createMicro($I, false); + + $test9 = new Collection(); + $test9->setHandler('Test9Controller', true); + $test9->get('/{people}', 'viewAction'); + $micro->mount($test9); + + try { + $micro->handle('/'.$this->people->cedula); + + $I->assertTrue( + false, + 'Here must be the exception about passing non model to the micro handler' + ); + } catch (\Exception $e) { + // PHP 5.x + $I->assertEquals( + 'Argument 1 passed to Test9Controller::viewAction() must be an instance of Phalcon\Mvc\Model, string given', + $e->getMessage() + ); + } catch (\TypeError $e) { + // PHP 7.x + $I->assertEquals( + 'Argument 1 passed to Test9Controller::viewAction() must be an instance of Phalcon\Mvc\Model, string given', + $e->getMessage() + ); + } + + $micro->setModelBinder($this->modelBinder); + + for ($i = 0; $i <= 1; $i++) { + + $returnedValue = $micro->handle('/'.$this->people->cedula); + + $I->assertInstanceOf('Phalcon\Test\Models\People', $returnedValue); + $I->assertEquals($this->people->cedula, $returnedValue->cedula); + $I->assertEquals( + [ + 'people' => 'Phalcon\\Test\\Models\\People', + ], + $this->cache->get('_PHMB_Test9Controller_viewAction') + ); + } + } + + /** + * @param bool $useModelBinder + * @return Dispatcher + */ + private function createDispatcher($useModelBinder = true) + { + $this->cache->flush(); + $dispatcher = new Dispatcher; + if ($useModelBinder) { + $dispatcher->setModelBinder($this->modelBinder); + } + $dispatcher->setDI(Di::getDefault()); + + return $dispatcher; + } + + /** + * @param $dispatcher + * @param IntegrationTester $I + */ + private function assertDispatcher($dispatcher, IntegrationTester $I) + { + $I->assertInstanceOf('Phalcon\Di', $dispatcher->getDI()); + $I->haveServiceInDi('dispatcher', $dispatcher); + } + + /** + * @param Dispatcher $dispatcher + * @param $controllerName + * @param $actionName + * @param $params + * @param bool $returnValue + * @return mixed + */ + private function returnDispatcherValueForAction( + Dispatcher $dispatcher, + $controllerName, + $actionName, + $params, + $returnValue = true + ) { + $dispatcher->setReturnedValue(null); + $dispatcher->setControllerName($controllerName); + $dispatcher->setActionName($actionName); + $dispatcher->setParams($params); + + if ($returnValue) { + $dispatcher->dispatch(); + + return $dispatcher->getReturnedValue(); + } + + return null; + } + + /** + * @param IntegrationTester $I + * @param bool $useModelBinder + * @return Micro + */ + private function createMicro(IntegrationTester $I, $useModelBinder = true) + { + $this->cache->flush(); + $micro = new Micro(Di::getDefault()); + if ($useModelBinder) { + $micro->setModelBinder($this->modelBinder); + } + + $I->assertInstanceOf('Phalcon\Di', $micro->getDI()); + + return $micro; + } +}