Skip to content

Instantly share code, notes, and snippets.

@standa
Created September 25, 2018 13:20
Show Gist options
  • Save standa/7c07f457d158bfe29297df627b13ab33 to your computer and use it in GitHub Desktop.
Save standa/7c07f457d158bfe29297df627b13ab33 to your computer and use it in GitHub Desktop.
Router with callables
<?php
namespace League\Framework\Server\Controller;
use League\Plates\Engine;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
/**
* Example implementation of the default routing strategy (RequestResponseStrategy)
*
* Each action may have up to three parameters: request, response and an array of arguments
*/
class DefaultController
{
private $tpl;
/**
* Example of how to inject dependencies
*
* @param Engine $tpl
*/
public function __construct(Engine $tpl)
{
$this->tpl = $tpl;
}
/**
* GET /server.html
*
* @param ServerRequestInterface $request
* @param ResponseInterface $response
* @return ResponseInterface
* @throws \InvalidArgumentException
* @throws \RuntimeException
*/
public function serverTemplateAction(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
$response->getBody()->write($this->tpl->render('Default/Index', [
'title' => 'Default route', 'name' => $request->getServerParams()['HTTP_HOST']
]));
return $response->withStatus(200);
}
/**
* GET /user/{id:number}
*
* @param ServerRequestInterface $request
* @param ResponseInterface $response
* @param string[] $args
* @return ResponseInterface
* @throws \InvalidArgumentException
* @throws \RuntimeException
*/
public function userIdAction(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
{
$response->getBody()->write($this->tpl->render('Default/Index', [
'title' => 'User '.$args['id'].' route', 'name' => 'User ID: '.$args['id']
]));
return $response->withStatus(200);
}
}
<?php
/**
* @var League\Plates\Template\Template $this
* @var string $name name of the current user
* @var string $title <title> of the page
*/
// $this->layout('Layout/default', ['title' => 'User Profile'])
use League\Framework\Server\Core\Router;
$this->layout('Layout/NarrowJumbotron', ['title' => $title]);
?>
<div class="jumbotron">
<?php if ($name !== '') { ?>
<h1 class="display-3">Hello <?=$this->e($name);?>!</h1>
<p class="lead">Cras justo odio, dapibus ac facilisis in, egestas eget quam. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.</p>
<p><a class="btn btn-lg btn-success" href="<?=$this->route(Router::GET_SERVER_HTML); ?>" role="button">Hello <?= $this->e($name); ?></a></p>
<?php } else { ?>
<h1 class="display-3">Register now!</h1>
<p class="lead">Cras justo odio, dapibus ac facilisis in, egestas eget quam. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.</p>
<p><a class="btn btn-lg btn-success" href="<?=$this->route(Router::GET_SERVER_HTML); ?>" role="button">Sign up today</a></p>
<?php } ?>
</div>
<div class="row marketing">
<div class="col-lg-6">
<h4>Subheading</h4>
<p>Donec id elit non mi porta gravida at eget metus. Maecenas faucibus mollis interdum.</p>
<h4>Subheading</h4>
<p>Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cras mattis consectetur purus sit amet fermentum.</p>
<h4>Subheading</h4>
<p>Maecenas sed diam eget risus varius blandit sit amet non magna.</p>
</div>
<div class="col-lg-6">
<h4>Subheading</h4>
<p>Donec id elit non mi porta gravida at eget metus. Maecenas faucibus mollis interdum.</p>
<h4>Subheading</h4>
<p>Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cras mattis consectetur purus sit amet fermentum.</p>
<h4>Subheading</h4>
<p>Maecenas sed diam eget risus varius blandit sit amet non magna.</p>
</div>
</div>
<?php
namespace League\Framework\Server\Controller;
use League\Route\Http\Exception\ImATeapotException;
use Psr\Http\Message\ServerRequestInterface;
/**
* Controllers with JsonStrategy do not have response objects, response is a plain array of data.
*
* @todo use fractal library here
*
*/
class JsonController
{
/**
* GET /test.json
*
* Example of the simplest implementation
*
* @param ServerRequestInterface $request
* @return array
*/
public function testAction(ServerRequestInterface $request): array
{
return [
'error' => false,
'code' => 200,
'message' => 'Hello World'
];
}
/**
* GET /user/1.json
*
* Example of getting URI parameters
*
* @param ServerRequestInterface $request
* @param array $args
* @return array
*/
public function userIdAction(ServerRequestInterface $request, array $args): array {
return [
'error' => false,
'code' => 200,
'ip' => $request->getServerParams()['REMOTE_ADDR'],
'message' => 'User ID '.$args['id'].' found.'
];
}
/**
* GET /teapot.json
*
* Return HTTP 418 I'm a teapot
*
* {"status_code":418,"reason_phrase":"I'm a teapot"}
*
* @param ServerRequestInterface $request
* @throws ImATeapotException
*/
public function teaPotAction(ServerRequestInterface $request)
{
throw new ImATeapotException();
}
}
<?php
namespace League\Framework\Server\Core;
use function League\Framework\Server\container;
use League\Framework\Server\Controller\BazosController;
use League\Framework\Server\Controller\ExceptionsController;
use League\Framework\Server\Controller\DefaultController;
use League\Framework\Server\Controller\JsonController;
use function League\Framework\Server\tpl;
use League\Route\Http\Exception as HttpException;
use League\Route\Route;
use League\Route\RouteCollection;
use League\Route\Strategy\JsonStrategy;
use League\Route\Strategy\RequestResponseStrategy;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
/**
* After you have defined your routes in routes(), simply call dispatch() in your index.php script
*
* @example Router::dispatch() called in index.php to generate the output
* @example Router::route(Router::GET_TEAPOT_JSON) to get a path to the route
*/
class Router implements RouterInterface
{
const GET_SERVER_HTML = '/server.html';
const GET_USER_ID_NUMBER = '/user/{id:number}';
const GET_TEST_JSON = '/test.json';
const GET_USER_ID_NUMBER_JSON = '/user/{id:number}.json';
const GET_TEAPOT_JSON = '/teapot.json';
const GET_CSS_BOOTSTRAP_MIN_CSS = '/css/bootstrap.min.css';
const GET_JS_BOOTSTRAP_MIN_JS = '/js/bootstrap.min.js';
const GET_JS_JQUERY_MIN_JS = '/js/jquery.min.js';
/**
* @todo Define your routing here
*
* @param RouteCollection $routes
* @throws \InvalidArgumentException
* @throws \RuntimeException
*/
private static function mapRoutes(RouteCollection $routes)
{
self::mapExampleRoutes($routes);
self::mapBootstrapRoutes($routes);
}
public static function dispatch()
{
// @todo emit events here that will be processed by individual implementations: react, plates, json
try {
$route = new RouteCollection(container());
$route->setStrategy(new RequestResponseStrategy());
//$route->setStrategy(new ParamStrategy());
// A long row of $route->get() calls
self::mapRoutes($route);
$response = $route->dispatch(container()->get('request'), container()->get('response'));
container()->get('emitter')->emit($response);
} catch (HttpException\NotFoundException $e) {
(new ExceptionsController(
container(),
container()->get('request'),
container()->get('response'))
)->notFoundException($e);
} catch (HttpException $e) {
(new ExceptionsController(
container(),
container()->get('request'),
container()->get('response'))
)->httpException($e);
}
}
/**
* Get path a to a specific resource
*
* @todo route params validation, or maybe use something from fast-route directly
*
* @param string $route
* @param array ...$params
* @return string
* @throws \InvalidArgumentException if route params count does not match $params count
*/
public static function route(string $route, ...$params)
{
if (!self::isClassConstantByValue($route)) {
throw new \InvalidArgumentException('Route has to be one of the '.__CLASS__.' class constants.');
}
$matches = [];
if (preg_match_all('/\{[^}]+\}/', $route, $matches, PREG_SET_ORDER)
&& count($params) !== count($matches)
) {
throw new \InvalidArgumentException(sprintf(
'Route %s has %d params but %d received.',
$route,
count($matches),
count($params)
));
}
if (count($params) < 1) {
return $route;
}
foreach ($matches as $i => $match) {
$route = str_replace($match, $params[$i], $route);
}
return $route;
}
/**
* Example routes
*
* @param RouteCollection $routes
*/
private static function mapExampleRoutes(RouteCollection $routes)
{
// Basic html template using a templating system
$routes->get(self::GET_SERVER_HTML, [new DefaultController(tpl()), 'serverTemplateAction']);
// Pass on user id parameter into an html template
$routes->get(self::GET_USER_ID_NUMBER, [new DefaultController(tpl()), 'userIdAction']);
// The simplest implementation of json routing
$routes->get(self::GET_TEST_JSON, [new JsonController(), 'testAction'])->setStrategy(new JsonStrategy());
// Json routing with user id parameter
$routes->get(self::GET_USER_ID_NUMBER_JSON, [new JsonController(), 'userIdAction'])->setStrategy(new JsonStrategy());
// Json HTTP status code: returns HTTP 418
$routes->get(self::GET_TEAPOT_JSON, [new JsonController(), 'teaPotAction'])->setStrategy(new JsonStrategy());
}
/**
* Twitter bootstrap - custom mapping with no controller
*
* @param RouteCollection $routes
* @throws \RuntimeException
*/
private static function mapBootstrapRoutes(RouteCollection $routes)
{
$routes->get(self::GET_CSS_BOOTSTRAP_MIN_CSS,
function (ServerRequestInterface $request, ResponseInterface $response): ResponseInterface {
$response->getBody()->write(file_get_contents(__DIR__.'/../../vendor/twbs/bootstrap/dist/css/bootstrap.min.css'));
return $response->withHeader('Content-Type', 'text/css');
}
);
$routes->get(self::GET_JS_BOOTSTRAP_MIN_JS,
function (ServerRequestInterface $request, ResponseInterface $response): ResponseInterface {
$response->getBody()->write(file_get_contents(__DIR__.'/../../vendor/twbs/bootstrap/dist/js/bootstrap.min.js'));
return $response->withHeader('Content-Type', 'text/javascript');
}
);
$routes->get(self::GET_JS_JQUERY_MIN_JS,
function (ServerRequestInterface $request, ResponseInterface $response): ResponseInterface {
$response->getBody()->write(file_get_contents(__DIR__.'/../../vendor/components/jquery/jquery.min.js'));
return $response->withHeader('Content-Type', 'text/javascript');
}
);
}
private static function isClassConstantByValue(string $value): bool
{
static $constants;
if ($constants === null) {
$class = new \ReflectionClass(__CLASS__);
$constants = array_values($class->getConstants());
}
return in_array($value, $constants, true);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment