Skip to content

Commit

Permalink
Add Server and BaseApplication.
Browse files Browse the repository at this point in the history
These classes tie the various parts of Cake\Http into something useful.
The BaseApplication is used by developers to define their middleware and
bootstrap their application. The Server is used to dispatch requests
into the Application and emit the responses.
  • Loading branch information
markstory committed Apr 23, 2016
1 parent 90ce428 commit 91234f4
Show file tree
Hide file tree
Showing 7 changed files with 574 additions and 0 deletions.
98 changes: 98 additions & 0 deletions src/Http/BaseApplication.php
@@ -0,0 +1,98 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @since 3.3.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Http;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

/**
* Base class for application classes.
*
* The application class is responsible for bootstrapping the application,
* and ensuring that middleware is attached. It is also invoked as the last piece
* of middleware, and delegates request/response handling to the correct controller.
*/
abstract class BaseApplication
{

/**
* @var string Contains the path of the config directory
*/
protected $configDir;

/**
* Constructor
*
* @param string $configDir The directory the bootstrap configuration is held in.
*/
public function __construct($configDir)
{
$this->configDir = $configDir;
}

/**
* @param \Cake\Http\MiddlewareStack $middleware The middleware stack to set in your App Class
* @return \Cake\Http\MiddlewareStack
*/
abstract public function middleware($middleware);

/**
* Load all the application configuration and bootstrap logic.
*
* @return void
*/
public function bootstrap()
{
// Load traditional bootstrap file..
require_once $this->configDir . '/bootstrap.php';

// Load other config files your application needs.
}

/**
* Invoke the application.
*
* - Convert the PSR request/response into CakePHP equivalents.
* - Create the controller that will handle this request.
* - Invoke the controller.
*
* @param \Psr\Http\Message\ServerRequestInterface $request The request
* @param \Psr\Http\Message\ResponseInterface $response The response
* @param callable $next The next middleware
* @return \Psr\Http\Message\ResponseInterface
*/
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
{
// Convert the request/response to CakePHP equivalents.
$cakeRequest = RequestTransformer::toCake($request);
$cakeResponse = ResponseTransformer::toCake($response);

// Dispatch the request/response to CakePHP
$cakeResponse = $this->getDispatcher()->dispatch($cakeRequest, $cakeResponse);

// Convert the response back into a PSR7 object.
return $next($request, ResponseTransformer::toPsr($cakeResponse));
}

/**
* Get the ActionDispatcher.
*
* @return \Spekkoek\ActionDispatcher
*/
protected function getDispatcher()
{
return new ActionDispatcher();
}
}
142 changes: 142 additions & 0 deletions src/Http/Server.php
@@ -0,0 +1,142 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @since 3.3.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Http;

use Cake\Event\EventDispatcherTrait;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use RuntimeException;
use Zend\Diactoros\Response;
use Zend\Diactoros\Response\EmitterInterface;
use Zend\Diactoros\Response\SapiStreamEmitter;

/**
* Runs an application invoking all the PSR7 middleware and the registered application.
*/
class Server
{

use EventDispatcherTrait;

/**
* @var \Cake\Http\BaseApplication
*/
protected $app;

/**
* @var \Cake\Http\Runner
*/
protected $runner;

/**
* Constructor
*
* @param \Cake\Http\BaseApplication $app The application to use.
*/
public function __construct(BaseApplication $app)
{
$this->setApp($app);
$this->setRunner(new Runner());
}

/**
* Run the request/response through the Application and its middleware.
*
* This will invoke the following methods:
*
* - App->bootstrap() - Perform any bootstrapping logic for your application here.
* - App->middleware() - Attach any application middleware here.
* - Trigger the 'Server.buildMiddleware' event. You can use this to modify the
* from event listeners.
* - Run the middleware stack including the application.
*
* @param \Psr\Http\Message\ServerRequestInterface $request The request to use or null.
* @param \Psr\Http\Message\ResponseInterface $response The response to use or null.
* @return \Psr\Http\Message\ResponseInterface
* @throws \RuntimeException When the application does not make a response.
*/
public function run(ServerRequestInterface $request = null, ResponseInterface $response = null)
{
$this->app->bootstrap();
$request = $request ?: ServerRequestFactory::fromGlobals();
$response = $response ?: new Response();

$middleware = $this->app->middleware(new MiddlewareStack());
if (!($middleware instanceof MiddlewareStack)) {
throw new RuntimeException('The application `middleware` method did not return a middleware stack.');
}
$middleware->push($this->app);
$this->dispatchEvent('Server.buildMiddleware', ['middleware' => $middleware]);
$response = $this->runner->run($middleware, $request, $response);

if (!($response instanceof ResponseInterface)) {
throw new RuntimeException(sprintf(
'Application did not create a response. Got "%s" instead.',
is_object($response) ? get_class($response) : $response
));
}
return $response;
}

/**
* Emit the response using the PHP SAPI.
*
* @param \Psr\Http\Message\ResponseInterface $response The response to emit
* @param \Zend\Diactoros\Response\EmitterInterface $emitter The emitter to use.
* When null, a SAPI Stream Emitter will be used.
* @return void
*/
public function emit(ResponseInterface $response, EmitterInterface $emitter = null)
{
if (!$emitter) {
$emitter = new SapiStreamEmitter();
}
$emitter->emit($response);
}

/**
* Set the application.
*
* @param BaseApplication $app The application to set.
* @return $this
*/
public function setApp(BaseApplication $app)
{
$this->app = $app;
return $this;
}

/**
* Get the current application.
*
* @return BaseApplication The application that will be run.
*/
public function getApp()
{
return $this->app;
}

/**
* Set the runner
*
* @param \Cake\Http\Runner $runner The runner to use.
* @return $this
*/
public function setRunner(Runner $runner)
{
$this->runner = $runner;
return $this;
}
}
51 changes: 51 additions & 0 deletions tests/TestCase/Http/BaseApplicationTest.php
@@ -0,0 +1,51 @@
<?php
namespace Cake\Test\TestCase;

use Cake\Core\Configure;
use Cake\Http\BaseApplication;
use Cake\Http\ServerRequestFactory;
use Cake\TestSuite\TestCase;
use Zend\Diactoros\Response;

/**
* Base application test.
*/
class BaseApplicationTest extends TestCase
{
/**
* Setup
*
* @return void
*/
public function setUp()
{
parent::setUp();
Configure::write('App.namespace', 'TestApp');
}

/**
* Integration test for a simple controller.
*
* @return void
*/
public function testInvoke()
{
$next = function ($req, $res) {
return $res;
};
$response = new Response();
$request = ServerRequestFactory::fromGlobals(['REQUEST_URI' => '/cakes']);
$request = $request->withAttribute('params', [
'controller' => 'Cakes',
'action' => 'index',
'plugin' => null,
'pass' => []
]);

$path = dirname(dirname(__DIR__));
$app = $this->getMockForAbstractClass('Cake\Http\BaseApplication', [$path]);
$result = $app($request, $response, $next);
$this->assertInstanceOf('Psr\Http\Message\ResponseInterface', $result);
$this->assertEquals('Hello Jane', '' . $result->getBody());
}
}

0 comments on commit 91234f4

Please sign in to comment.