A Tale Framework Component
A middleware for talesoft/tale-app
that allows easy instanciation and handling of controllers.
You can either use single, static controllers or use a dispatcher that automatically handles everything.
Install via Composer
composer require "talesoft/tale-controller:*"
composer install
A single controller can make up a whole website. This is really useful for small websites with 5-10 sub-pages. No configuration needed.
use Tale\App;
use Tale\Controller;
//Define a controller of some kind
class MyController extends Controller
{
//GET|POST /
public function indexAction()
{
$res = $this->getResponse();
$res->getBody()->write('Hello index!');
return $res;
}
//GET|POST /?action=about-us
public function aboutUsAction()
{
$res = $this->getResponse();
$res->getBody()->write('About us!');
return $res;
}
//GET /?action=contact
public function getContactAction()
{
$res = $this->getResponse();
$res->getBody()->write('Contact form!');
return $res;
}
//POST /?action=contact
public function postContactAction()
{
//Handle contact form
$res = $this->getResponse();
$res->getBody()->write('Success!');
return $res;
}
}
//Create a new app context
$app = new App();
//Make sure we can target the "action" somehow.
//Normally you'd use a router, we use a simple GET-variable in this case
//"index.php?action=about-us" would dispatch "MyController->aboutUsAction"
//This is a simple middleware mapping query's "action" to the required request attribute "action"
$app->append(function($req, $res, $next) {
$params = $req->getQueryParams();
$action = isset($params['action']) ? $params['action'] : null;
if ($action)
$req = $req->withAttribute('action', $action);
return $next($req, $res);
});
//Append our controller middleware
$app->append(MyController::class);
//Display the app
$app->display();
When apps get larger, you want to split functionality into single modules. With the Dispatcher you can control an automatic controller dispatching mechanism.
Imagine the following controller structure:
/
/index.php
/app
/controllers
IndexController.php
ContactController.php
PortfolioController.php
/Admin
/IndexController.php
This is a common case that the dispatcher can handle with a low configuration profile.
use Tale\App;
use Tale\Controller\Dispatcher;
//Create a new app context
$app = new App([
'controller' => [
'nameSpace' => 'My\\Controllers',
'loader' => ['path' => __DIR__.'/app/controllers']
]
]);
//This is a middleware mapping "module", "controller" and "action" GET-values to
//ServerRequestInterface-attributes
$app->append(function($req, $res, $next) {
$params = $req->getQueryParams();
$module = isset($params['module']) ? $params['module'] : null;
$controller = isset($params['controller']) ? $params['controller'] : null;
$action = isset($params['action']) ? $params['action'] : null;
if ($module)
$req = $req->withAttribute('module', $module);
if ($controller)
$req = $req->withAttribute('controller', $controller);
if ($action)
$req = $req->withAttribute('action', $action);
return $next($req, $res);
});
//Append our dispatcher middleware
$app->append(Dispatcher::class);
//Display the app
$app->display();
Now you could call the editAction
of the Admin\IndexController
by requesting
index.php?module=admin&action=edit
Notice that all values are completely optional.
The following attributes are handled by the dispatcher:
Tells the dispatcher which namespace to find controllers in.
The controller.nameSpace
option will be prepended in any case.
Tells the dispatcher, which controller to load.
my-blog
will be parsed to MyBlogController
The following attributes are handled by the controllers:
Tells the controller which action to call.
edit-user
will be parsed to editUserAction
If there's an getEditUserAction
-method, that one will only listen to GET
-requests
The same goes for POST
-requests with postEditUserAction
.
Not prefixing will handle all request methods.
Specifies the first parameter given to the action.
Allowed values are numerical values and canonical strings (some-user-name
)
Specifies the format the result should appear in.
This mostly equals the file extension of the called URI (/some-file.xml
will yield format xml
)
This format is to be used by some kind of output formatter/renderer.
What if there's no fitting controller/it doesn't extend the correct class/the input is malformed etc.
That's all checked by tale-controller. Upon any kind of failure, control will be passed on to the next middleware.
Handling 404 is as simple as adding an "end"-middleware that results in said 404-error
$app->append(Dispatcher::class)
->append(function($req, $res) {
$res->getBody()->write('<h1>404 - Not found!</h1>');
return $res->withStatus(404);
});
This module is specially designed to work with the Tale\Router
.
You can use it stand-alone, but it will require extra-work (but is still really cool!)
Here's an example of how it could look like by installing talesoft/tale-router
via composer
env.json
{
"middlewares": ["Tale\\Router"],
"routes": {
"/blog/:action?/:id?": "My\\Controller\\BlogController",
"/:controller?/:action?/:id?.:format?": "Tale\\Controller\\Dispatcher"
},
"controller": {
"nameSpace": "My\\Controller",
"loader": {
"path": "{{path}}/app/controllers"
}
}
}
```php
**index.php**
```php
use Tale\App;
$app = new App(['path' => __DIR__]);
$app->display();
All configuration options.
controller.defaultModule The default module to use (Default: null)
controller.defaultController The default controller to use (Default: index)
controller.defaultAction The default action to use (Default: index)
controller.defaultId The default ID to use (Default: null)
controller.defaultFormat The default format to use (Default: html)
controller.nameSpace The namespace where controllers reside in (Default: null)
controller.modules A map [module-name => namespace] for module mapping
controller.controllerPattern The pattern for controllers (Default: %sController)
controller.controllerInflection How to inflect the controller name (Default: [Tale\Inflector, camelize]
controller.actionPattern The pattern for actions (Default: %sAction)
controller.actonInflection How to inflect the action name (Default: [Tale\Inflector, variablize]
controller.getActionPattern The pattern for GET actions (Default: get%sAction)
controller.getActonInflection How to inflect the GET action name (Default: [Tale\Inflector, camelize]
controller.postActionPattern The pattern for POST actions (Default: post%sAction)
controller.postActonInflection How to inflect the POST action name (Default: [Tale\Inflector, camelize]
controller.loader.enabled Enable an auto-loader for controllers (Default: true)
controller.loader.path The path for controller classes (Default: getcwd()/controllers)
controller.loader.pattern The pattern for controller loading (Default: %s.php)
Using multiple dispatchers is as easy as extending the dispatcher. You can set an option namespace to load different configuration values.
class FirstDispatcher
{
public function getOptionNameSpace()
{
return 'firstDispatcher';
}
}
class SecondDispatcher
{
public function getOptionNameSpace()
{
return 'secondDispatcher';
}
}
$app->get(Router::class)
->all('/:controller?/:action?', FirstDispatcher::class)
->all('/sub-module/:controller?/:action?', SecondDispatcher::class);
$app->display();