- Deprecated : This package has already been merged with https://github.com/rosengate/exedraA Minimal annotation and reflection based anemic routeful controller for Exedra.
In a simple word, a routeable-action-controller component in steroid.
Writing a lot of \Closure for your deep nested routing can get messier and not so IDE-friendly as they grow much bigger.
This package is built to tackle the issue and give you a nice routing controller over your routing groups.
The controller is anemic, flattened and incapable of construction, but knows very well about the routing design.
The annotation design is fairly simple, just a @property-value mapping. Nothing much!
This package works only for Exedra. It cannot be used independantly, because.. Of course, it's part of Exedra routing component.
Install through composer
composer require exedron\routeller dev-master
Setup the service provider
// your application instance
$app->provider->add(\Exedron\Routeller\Provider::class);Alternatively, you may manually add the handler, and set up additional things.
$app->routingFactory->addGroupHandler(new \Exedron\Routeller\Handler($app));
$app->map->addExecuteHandler('routeller_execute', ExecuteHandler::class);Enable a file based cache
$options = array(
'auto_reload' => true
);
$cache = new \Exedron\Routeller\Cache\FileCache(__DIR__ .'/routing_caches')
$app->provider->add(new \Exedron\Routeller\Provider($cache, $options));The auto_reload option lets it reload the cache each time there's some change to the controllers.
On your preferred route, add use the controller using the group() method.
$app->map['web']->group(\App\Controller\WebController::class);The controller MUST by type of \Exedron\Routeller\Controller\Controller.
<?php
namespace App\Controller;
use Exedron\Routeller\Controller\Controller;
class WebController extends Controller {
}Annotate the method with the route properties. The method name has to be prefixed with execute.
<?php
namespace App\Controller;
use Exedra\Runtime\Context;
class WebController extends \Exedron\Routeller\Controller\Controller
{
/**
* @path /
*/
public function executeIndex(Context $context)
{
return 'This is index page';
}
/**
* @name about-us
* @path /about-us
* @method GET|POST
*/
public function executeAboutUs(Context $context)
{
return 'This is about page';
}
}Doing above is similar to doing :
use Exedra\Routing\Group;
use Exedra\Runtime\Context;
$app->map['web']->group(function(Group $group) {
$group['index']->any('/')->execute(function(Context $context) {
return 'This is index page';
});
$group['about-us']->any('/about-us')->execute(function(Context $context) {
return 'This is about page';
});
});You may want to customize the route more object orientedly, prefix with route.
/**
* @path /faq
*/
public function routeFaq(\Exedra\Routing\Route $route)
{
$route->execute(function() {
});
}Add a middleware for the current group's route, by prefixing the method name with middleware.
This method expects no annotation.
public function middlewareAuth(Context $context)
{
if(!$context->session->has('user_id'))
return $context->redirect->route('@web.login');
return $context->next($context);
}The middleware name is optional though, so, you can still set it.
/**
* @name csrf
*/
public function middlewareCsrf()
{
return $context->next($context);
}Add a subgroup route. The method name must be prefixed with group.
The method must return the routing group pattern.
/**
* @path /products
*/
public function groupProducts()
{
return \App\Controller\ProductController::class;
}Similar to group prefix, except that this one have their group resolved immediately.
/**
* @path /:product-id
*/
public function subProduct(Group $group)
{
$group['get']->get('/')->execute(function() {
// do your things
});
$group['update']->post('/')->execute(function() {
// do your things
});
}You can also do a usual routing by prefixing the method name with setup. This method expects no annotation.
public function setup(Group $group)
{
$group->get('/')->execute(function() {
});
}
public function setupCustom(Group $group)
{
// do another thing
}This method also receives Exedra\Application as the second argument.
/**
* @path /comments
*/
public function setup(Group $group, \Exedra\Application $app)
{
$group->get('/')->execute(function() {
});
}This package also support a simple restful mapping.
Prefix each method with the http verb as you like.
/**
* Get all products
* @path /
*/
public function getProducts(Context $context)
{
}/**
* Create a new product
* @path /
*/
pubic function postProducts(Context $context)
{
}/**
* GET the product
* @path /[:id]
*/
pubic function getProduct(Context $context)
{
}/**
* DELETE the product
* @path /[:id]
*/
pubic function getProduct(Context $context)
{
}And you can have a route with only the verb.
public function get(Context $context)
{
}
public function post(Context $context)
{
}Of course, this is just a sample. Best way to do a resourceful design in Exedra, is through a subrouting.
Inject with known service(s)
use Exedra\Url\UrlGenerator;
/**
* @inject context.url
* @path /
*/
public function get(UrlGenerator $url)
{
echo $url->current();
}Multiple services
use Exedra\Runtime\Context;
use Exedra\Application;
use Exedra\Routing\Group;
/**
* @inject context, url, self.response, app, app.map
* @path /
*/
public function post(Context $context, $url, $response, Application $app, Group $map)
{
}selfandcontextis the same thing that is a type of \Exedra\Runtime\Context, the context of the current runtime.- the services prefixed with
app.will instead look inside theExedra\Applicationcontainer - without a prefix,
context.,self.orapp., the resolve will only look for the service registered the Context object.
/**
* @tag users
* @ajax true
* @middleware \App\Middleware\Auth
*/
pubic function executeUsers()
{
}public function middlewareAuth(\Exedra\Runtime\Context $context)
{
if($context->attr('need_auth', false) && !$context->session->has('user_id'))
throw new NotLoggedInException;
return $context->next($context);
}
/**
* @attr.need_auth true
* @path /admin
* @method any
*/
public function groupAdmin()
{
return Admin::class;
}/**
* @name admin_default
* @method GET|POST
* @path /admin/:controller/:action
* @middleware \App\Middleware\Auth
* @middleware \App\Middleware\Csrf
* @middleware \App\Middleware\Limiter
* @tag admin_default
* @attr.session_timeout 36000
* @config.request_limit 15
*/
public function executeAdminDefault($context)
{
// nah, just a sample.
$controller = $context->param('controller');
$action = $context->param('action');
return (new $controller)->{$action}($context);
}The non route property tags like return, param, and throws tags and will not be parsed.
This package also provides a similar command on the original route listing, except that it added a little bit more details on the result.
$app = new \Exedra\Application(__DIR__);
//... do some routing
$console = new \Symfony\Component\Console\Application();
$console->add(new \Exedron\Routeller\Console\Commands\RouteListCommand($app, $app->map));
$console->run();For some type of usage, like executable and grouped kind of route, the route name will be taken from the case-lowered remaining method name, IF no @name property is annotated.
It takes a combination of verb and the method name. For example,
public function postProducts()
{
}
public function get()
{
}Above routing will have a method name like .post-products. (@web.products.post-products)
For verb only method name, it'll just be the verb as the name. And an absolute name for it would look something like : @web.products.get
Routing order is being read from top to above. So, it matters how you code the routing.