From e4cf40d09748b17cb34ed5ba6efe4e295bc0294f Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 22 Feb 2018 22:18:40 -0500 Subject: [PATCH] Make Server/CommandRunner proxy event managers. Instead of copying state around, we can use the existing interfaces to create proxies for evented applications. This lets us make evented and non-evented applications work the same as far as the Server/CommandRunner are concerned. --- src/Console/CommandRunner.php | 87 +++++++++++++++----- src/Http/Server.php | 85 ++++++++++++++----- tests/TestCase/Console/CommandRunnerTest.php | 45 ++++++++-- tests/TestCase/Http/ServerTest.php | 47 +++++++++++ 4 files changed, 212 insertions(+), 52 deletions(-) diff --git a/src/Console/CommandRunner.php b/src/Console/CommandRunner.php index b599f5c3cee..96c70437580 100644 --- a/src/Console/CommandRunner.php +++ b/src/Console/CommandRunner.php @@ -23,10 +23,11 @@ use Cake\Console\Shell; use Cake\Core\ConsoleApplicationInterface; use Cake\Core\PluginApplicationInterface; -use Cake\Event\EventApplicationInterface; use Cake\Event\EventDispatcherInterface; use Cake\Event\EventDispatcherTrait; +use Cake\Event\EventManager; use Cake\Utility\Inflector; +use InvalidArgumentException; use RuntimeException; /** @@ -34,7 +35,14 @@ */ class CommandRunner implements EventDispatcherInterface { - use EventDispatcherTrait; + /** + * Alias methods away so we can implement proxying methods. + */ + use EventDispatcherTrait { + eventManager as private _eventManager; + getEventManager as private _getEventManager; + setEventManager as private _setEventManager; + } /** * The application console commands are being run for. @@ -65,7 +73,7 @@ class CommandRunner implements EventDispatcherInterface */ public function __construct(ConsoleApplicationInterface $app, $root = 'cake') { - $this->setApp($app); + $this->app = $app; $this->root = $root; $this->aliases = [ '--version' => 'version', @@ -97,23 +105,6 @@ public function setAliases(array $aliases) return $this; } - /** - * Set the application. - * - * @param \Cake\Core\ConsoleApplicationInterface $app The application to run CLI commands for. - * @return $this - */ - public function setApp(ConsoleApplicationInterface $app) - { - $this->app = $app; - - if ($app instanceof EventDispatcherInterface) { - $this->setEventManager($app->getEventManager()); - } - - return $this; - } - /** * Run the command contained in $argv. * @@ -194,10 +185,62 @@ protected function bootstrap() if ($this->app instanceof PluginApplicationInterface) { $this->app->pluginBootstrap(); - $events = $this->app->events($this->getEventManager()); + $events = $this->app->getEventManager(); + $events = $this->app->events($events); $events = $this->app->pluginEvents($events); - $this->setEventManager($events); + $this->app->setEventManager($events); + } + } + + /** + * Get the application's event manager or the global one. + * + * @return \Cake\Event\EventManagerInterface + */ + public function getEventManager() + { + if ($this->app instanceof PluginApplicationInterface) { + return $this->app->getEventManager(); + } + + return EventManager::instance(); + } + + /** + * Get/set the application's event manager. + * + * If the application does not support events and this method is used as + * a setter, an exception will be raised. + * + * @param \Cake\Event\EventManagerInterface $events The event manager to set. + * @return \Cake\Event\EventManagerInterface|$this + * @deprecated Will be removed in 4.0 + */ + public function eventManager(EventManager $events = null) + { + if ($eventManager === null) { + return $this->getEventManager(); } + + return $this->setEventManager($events); + } + + /** + * Get/set the application's event manager. + * + * If the application does not support events and this method is used as + * a setter, an exception will be raised. + * + * @param \Cake\Event\EventManagerInterface $events The event manager to set. + * @return $this + */ + public function setEventManager(EventManager $events) + { + if ($this->app instanceof PluginApplicationInterface) { + return $this->app->setEventManager($events); + } + + throw new InvalidArgumentException('Cannot set the event manager, the application does not support events.'); } /** diff --git a/src/Http/Server.php b/src/Http/Server.php index 6e54a61dc07..fcfdc481162 100644 --- a/src/Http/Server.php +++ b/src/Http/Server.php @@ -16,9 +16,10 @@ use Cake\Core\HttpApplicationInterface; use Cake\Core\PluginApplicationInterface; -use Cake\Event\EventApplicationInterface; use Cake\Event\EventDispatcherInterface; use Cake\Event\EventDispatcherTrait; +use Cake\Event\EventManager; +use InvalidArgumentException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use RuntimeException; @@ -30,7 +31,14 @@ class Server implements EventDispatcherInterface { - use EventDispatcherTrait; + /** + * Alias methods away so we can implement proxying methods. + */ + use EventDispatcherTrait { + eventManager as private _eventManager; + getEventManager as private _getEventManager; + setEventManager as private _setEventManager; + } /** * @var \Cake\Core\HttpApplicationInterface @@ -49,7 +57,7 @@ class Server implements EventDispatcherInterface */ public function __construct(HttpApplicationInterface $app) { - $this->setApp($app); + $this->app = $app; $this->setRunner(new Runner()); } @@ -115,9 +123,10 @@ protected function bootstrap() if ($this->app instanceof PluginApplicationInterface) { $this->app->pluginBootstrap(); - $events = $this->app->events($this->getEventManager()); + $events = $this->app->getEventManager(); + $events = $this->app->events($events); $events = $this->app->pluginEvents($events); - $this->setEventManager($events); + $this->app->setEventManager($events); } } @@ -138,42 +147,76 @@ public function emit(ResponseInterface $response, EmitterInterface $emitter = nu } /** - * Set the application. + * Get the current application. + * + * @return \Cake\Core\HttpApplicationInterface The application that will be run. + */ + public function getApp() + { + return $this->app; + } + + /** + * Set the runner * - * @param \Cake\Core\HttpApplicationInterface $app The application to set. + * @param \Cake\Http\Runner $runner The runner to use. * @return $this */ - public function setApp(HttpApplicationInterface $app) + public function setRunner(Runner $runner) { - $this->app = $app; + $this->runner = $runner; + + return $this; + } - if ($app instanceof EventDispatcherInterface) { - $this->setEventManager($app->getEventManager()); + /** + * Get the application's event manager or the global one. + * + * @return \Cake\Event\EventManagerInterface + */ + public function getEventManager() + { + if ($this->app instanceof PluginApplicationInterface) { + return $this->app->getEventManager(); } - return $this; + return EventManager::instance(); } /** - * Get the current application. + * Get/set the application's event manager. * - * @return \Cake\Core\HttpApplicationInterface The application that will be run. + * If the application does not support events and this method is used as + * a setter, an exception will be raised. + * + * @param \Cake\Event\EventManagerInterface $events The event manager to set. + * @return \Cake\Event\EventManagerInterface|$this + * @deprecated Will be removed in 4.0 */ - public function getApp() + public function eventManager(EventManager $events = null) { - return $this->app; + if ($eventManager === null) { + return $this->getEventManager(); + } + + return $this->setEventManager($events); } /** - * Set the runner + * Get/set the application's event manager. * - * @param \Cake\Http\Runner $runner The runner to use. + * If the application does not support events and this method is used as + * a setter, an exception will be raised. + * + * @param \Cake\Event\EventManagerInterface $events The event manager to set. * @return $this */ - public function setRunner(Runner $runner) + public function setEventManager(EventManager $events) { - $this->runner = $runner; + if ($this->app instanceof PluginApplicationInterface) { + return $this->app->setEventManager($events); + } - return $this; + throw new InvalidArgumentException('Cannot set the event manager, the application does not support events.'); } } diff --git a/tests/TestCase/Console/CommandRunnerTest.php b/tests/TestCase/Console/CommandRunnerTest.php index 08804fd699c..deb569ab617 100644 --- a/tests/TestCase/Console/CommandRunnerTest.php +++ b/tests/TestCase/Console/CommandRunnerTest.php @@ -19,11 +19,13 @@ use Cake\Console\ConsoleIo; use Cake\Console\Shell; use Cake\Core\Configure; +use Cake\Core\ConsoleApplicationInterface; use Cake\Event\EventList; use Cake\Event\EventManager; use Cake\Http\BaseApplication; use Cake\TestSuite\Stub\ConsoleOutput; use Cake\TestSuite\TestCase; +use InvalidArgumentException; use TestApp\Command\DemoCommand; use TestApp\Http\EventApplication; use TestApp\Shell\SampleShell; @@ -53,24 +55,49 @@ public function setUp() } /** - * test set on the app + * test event manager proxies to the application. * * @return void */ - public function testSetApp() + public function testEventManagerProxies() { - $app = $this->getMockBuilder(BaseApplication::class) - ->setConstructorArgs([$this->config]) - ->getMock(); - - $manager = new EventManager(); - $app->method('getEventManager') - ->willReturn($manager); + $app = $this->getMockForAbstractClass( + BaseApplication::class, + [$this->config] + ); $runner = new CommandRunner($app); $this->assertSame($app->getEventManager(), $runner->getEventManager()); } + /** + * test event manager cannot be set on applications without events. + * + * @return void + */ + public function testGetEventManagerNonEventedApplication() + { + $app = $this->createMock(ConsoleApplicationInterface::class); + + $runner = new CommandRunner($app); + $this->assertSame(EventManager::instance(), $runner->getEventManager()); + } + + /** + * test event manager cannot be set on applications without events. + * + * @return void + */ + public function testSetEventManagerNonEventedApplication() + { + $this->expectException(InvalidArgumentException::class); + $app = $this->createMock(ConsoleApplicationInterface::class); + + $events = new EventManager(); + $runner = new CommandRunner($app); + $runner->setEventManager($events); + } + /** * Test that the console hook not returning a command collection * raises an error. diff --git a/tests/TestCase/Http/ServerTest.php b/tests/TestCase/Http/ServerTest.php index 6fb4c1802e8..4e2702fcd0c 100644 --- a/tests/TestCase/Http/ServerTest.php +++ b/tests/TestCase/Http/ServerTest.php @@ -14,13 +14,16 @@ */ namespace Cake\Test\TestCase; +use Cake\Core\HttpApplicationInterface; use Cake\Event\Event; use Cake\Event\EventList; use Cake\Event\EventManager; +use Cake\Http\BaseApplication; use Cake\Http\CallbackStream; use Cake\Http\MiddlewareQueue; use Cake\Http\Server; use Cake\TestSuite\TestCase; +use InvalidArgumentException; use Psr\Http\Message\ResponseInterface; use RuntimeException; use TestApp\Http\BadResponseApplication; @@ -301,4 +304,48 @@ public function testBuildMiddlewareEvent() $this->assertInstanceOf('Closure', $this->middleware->get(3), '2nd last middleware is a closure'); $this->assertSame($app, $this->middleware->get(4), 'Last middleware is an app instance'); } + + /** + * test event manager proxies to the application. + * + * @return void + */ + public function testEventManagerProxies() + { + $app = $this->getMockForAbstractClass( + BaseApplication::class, + [$this->config] + ); + + $server = new Server($app); + $this->assertSame($app->getEventManager(), $server->getEventManager()); + } + + /** + * test event manager cannot be set on applications without events. + * + * @return void + */ + public function testGetEventManagerNonEventedApplication() + { + $app = $this->createMock(HttpApplicationInterface::class); + + $server = new Server($app); + $this->assertSame(EventManager::instance(), $server->getEventManager()); + } + + /** + * test event manager cannot be set on applications without events. + * + * @return void + */ + public function testSetEventManagerNonEventedApplication() + { + $this->expectException(InvalidArgumentException::class); + $app = $this->createMock(HttpApplicationInterface::class); + + $events = new EventManager(); + $server = new Server($app); + $server->setEventManager($events); + } }