Flood\Component: Route
The routing component is responsible for what request will execute what.
Given that task, the component need to get, for every app that should have routing, all information about protocols, domains, paths, query, SEO rules like www or no www/trailing slash or not.
Also it needs to know what should be done when which request is executed.
For using the individual callbacks, just assign dispatchers for the keys and triggers used.
Just searching for turning on debug?
<?php \Flood\Component\Route\Route::$debug = true;
will enable output in exceptions of this component and other outputs like which routes are added
Storage Handler
A storage handler will know how to read and set the routes, and mostly how to build what should be executed.
Some storage namespaces are per default included in this component. These are reserved and/or added per default to the needed events:
hook
: handles all routes to one hook and specify the locale based domain and url rules, built inredirect
: default redirector dispatcherresponse
: stores callback information, PHP callables with a JSON markup, built infolder
: cdn component reserved namingfile
: cdn component reserved naming
{
"<hook-id>": {
"route": {
"hook": {},
"redirect": {},
"response": {},
"folder": {},
"file": {}
}
}
}
Pseudo-code selector: hook.<hook-id>.route.<handler-key> | <hook-id>.route.<handler-key>
Register a new Handler
Handlers are the main objects for receiving and storing the data of each elements of the hook's route array.
The handlers for hook
and response
are built-in, but are registered like external handlers.
The built-in handler are using the special raw
container syntax, the CDN component on the other hand only the simple object mode.
First the container syntax, it is possible to bypass protected
and private
when the method it is in an child of \Flood\Component\Route\Hook\Hook
.
The executing routing object must be the correct one, when overwritten somewhere check the order and/or when was added what and to which container.
<?php
namespace Flood\Component\Route;
Container::i()->register('route-storage-object-hook', [Container::_hook(), 'addHookRoute']);
// Flood\Component\Route\Hook->addHookRoute($data['hook']);
Container::i()->register('route-storage-object-response', [Container::_hook(), 'addRouteResponse']);
// Flood\Component\Route\Hook->addRouteResponse($data['response']);
// Examples of other components:
// for: hook.<hook-id>.route.folder
Container::i()->register('route-storage-object-folder', function () {
return new FloodCdn\Route\Folder();
});
// for: hook.<hook-id>.route.file
Container::i()->register('route-storage-object-file', function () {
return new FloodCdn\Route\File();
});
Events
An event generates the routes at a given point, they must get their information from Flood\Component\Route\Container::_hook()->get('key')
.
Events are called in the execution of RouteGenerator
, to each event just register the needed callback for each handler.
hook-list
: calls the event with parameter$hook_id
locale-list
: calls the event with parameter$hook_id, $locale
The events are saved in Flood\Component\Route\Hook\RouteGenerator
, it is accessible from the container with Container::i()->_hookRouteGenerator()
. They are executed in the order they where assigned.
Even the internal generateByLocalList
is assigned to the event hook-list
.
In the same style like that method and the built-in response handler you could extend those handlers. The hook
namespace is handled as parent and there are no further event handlers.
The event handler needs to return an object of Symfony\Component\Routing\RouteCollection
.
<?php
// hook-list
Container::i()->_hookRouteGenerator()->registerEventHandler(
'hook-list',
[Container::i()->_hookRouteGenerator(), 'generateByLocaleList']
);
// locale-list
Container::i()->_hookRouteGenerator()->registerEventHandler(
'locale-list',
function ($hook_id, $locale) {
return Container::i()->_hookRouteGenerator()->generateResponse($hook_id, $locale);
}
);
// Examples of other components:
Container::_hookRouteGenerator()->registerEventHandler('hook-list', function ($hook_id) {
return Container::_hookStorage()->get($hook_id)->getStorageObject('folder')->generateByHook($hook_id);
});
Container::_hookRouteGenerator()->registerEventHandler('hook-list', function ($hook_id) {
return Container::_hookStorage()->get($hook_id)->getStorageObject('file')->generateByHook($hook_id);
});
Callables
Everything is based on callables
, these provide the methodology of executing and generating call sequences based on strings or saving the execution now in a variable and executing it later.
Basic PHP callables example:
<?php
// 1: function callback
call_user_func('the_function_name');
// 2: static method callback
call_user_func(['ClassName', 'theMethodName']);
// 3: object method call
$obj = new MyClass();
call_user_func([$obj, 'myCallbackMethod']);
// 4: static class method call (As of PHP 5.2.3)
call_user_func('MyClass::myCallbackMethod');
// 5: Relative static class method call (As of PHP 5.3.0)
class A {
public static function who() {
echo "A\n";
}
}
class B extends A {
public static function who() {
echo "B\n";
}
}
call_user_func(['B', 'parent::who']); // A
// 6: Objects implementing __invoke can be used as callables (since PHP 5.3)
class C {
public function __invoke($name) {
echo 'Hello ', $name, "\n";
}
}
$c = new C();
call_user_func($c, 'PHP!');
But those callables need to be converted from JSON, what need some markup in JSON.
hook
provides the basic information for all other callback extensions.
response
is the default callback handler for executing locale
based requests.
Markup of Callables in JSON
The markup:
hook
// todo
response
A sample markup, see the differences at response.<locale>
for the two types lazy and extended. The type is determined through the locale scope, exists a 'locale' with class
or call-list
as index, type 'lazy' is used.
Lazy Markup
Markup Type Lazy Example
{
"response":{
"*": {
"namespace": "Flood\\Hydro\\App",
"register-autoload": true,
"type": "object",
"call-list": [
"call",
"handle",
"respond"
]
},
"home": {
"*": "/",
"de-DE": "/",
"en-US": "/",
"class": "Home"
},
"blog": {
"de-DE": "blog/{path-dynamic}",
"en-US": "blog/{path-dynamic}",
"class": "Blog"
}
}
}
Extended Markup
Markup Type Extended Example
{
"response":{
"*": {
"namespace": "Flood\\Hydro\\App",
"register-autoload": true,
"type": "object",
"call-list": [
"call",
"handle",
"respond"
]
},
"home": {
"*": {
"structure-child": "/",
"class": "HomeLocaleSwitch"
},
"de-DE": {
"structure-child": "/",
"class": "Home"
},
"en-US": {
"structure-child": "/",
"class": "Home"
}
},
"blog": {
"de-DE": "blog/{path-dynamic}",
"en-US": "blog/{path-dynamic}",
"class": "Blog"
}
}
}
Explained
The types could be mixed of cause, they only need to be persistent for one response element response.<id>
.
response['*']
is the master ident, when used, values not defined in the other id's will be searched for here. It provides the data for Flood\Component\Route\Hook\Hook->route_response_build
but will not be added to Flood\Component\Route\Hook\Hook->route_response
.
When the value is also not found there, it will fetch getHookData
as fallback.
Leading to the getter method getResponseData
which is used to generate the response routes. This method has implemented the overwriting logic and serves as single getter for all information.
<?php
Container::i()->_hookStorage()->get($hook_id)->getResponseData('namespace', $response_id, $locale);
Container::i()->_hookStorage()->get($hook_id)->getResponseData('ssl', $response_id, $locale);
Container::i()->_hookStorage()->get($hook_id)->getResponseData('structure-child', $response_id, $locale);
Container::i()->_hookStorage()->get($hook_id)->getResponseData(['uri', 'path'], $response_id, $locale);
Example case in JSON:
namespace
is saved in<hook-id>.response[*]
ssl
is saved in<hook-id>.route.hook[*]
structure-child
is saved in<hook-id>.route.response.<response_id>.<locale>
['uri', 'path']
as selectors arrays are used to fetch the specific value, e.g.<hook-id>.route.hook.<locale>.uri.path
They of cause could also be saved in some other value of those that get overwritten/inherited.
General priority:
<hook-id>.route.response.<response_id>.<locale>
will overwrite<hook-id>.route.response[*]
will overwrite<hook-id>.route.hook.<locale>
will overwrite<hook-id>.route.hook[*]
Callback Call Types
The setting for responses consists of:
namespace
: used to prefixclass
controller-path
: used for autoload, only needed at autoloadregister-autoload
: registers an spl autoload for the namespace and pathtype
: which type the callback is based on, see 'The Types'call-list
: which functions and methods should be executed- has reserved keys, see call-param
- will execute in order they are defined, only
respond
will be called at the end no matter where it is placed
call-param
: which parameteres should be added to which call- uses the call-list string as index
- has reserved keys
__construct
: will be added at specific types when a object is initiated__invoke
: will be added at specific types when a object is invokedrespond
: the function/method will be called and returned at last
Types
There are four possible types object
, static
, invoke
and function
. These will use the other possible settings to run and require what need to be done.
object
- initiates the object and calls all methods in call-list
{
"response": {
"*": {
"namespace": "Flood\\Component\\Route\\DemoHook",
"controller-path": "hook/demo/Controller/",
"register-autoload": true,
"type": "object",
"call-list": [
"call",
"handle",
"respond"
],
"call-param": {
"call": {
"some-param": "value"
}
}
},
"home": {
"*": "/",
"en-US": "/",
"class": "Home"
}
}
}
Would be this execution that will happen when the route matches home
.'en-US'
<?php
$controller = function () {
$obj = new \Flood\Component\Route\DemoHook\Home();
call_user_func_array([$obj, 'call'], ['some-param' => 'value']);
call_user_func([$obj, 'handle']);
return call_user_func([$obj, 'respond']);
};
- Supports:
call-list
,call-param
,namespace
,controller-path
,register-autoload
- Implements:
call-param.'__construct'
- passes the call params as array to the constructorcall-list.'respond'
- will be returned at last- Requires:
call-list
,namespace
,class
static
- calls all call-list
as static methods of namespace
.'\'
.class
{
"response": {
"*": {
"namespace": "Flood\\Component\\Route\\DemoHook",
"controller-path": "hook/demo/Controller/",
"register-autoload": true,
"type": "static",
"call-list": [
"call",
"handle",
"respond"
]
},
"home": {
"*": "/",
"en-US": "/",
"class": "Home"
}
}
}
<?php
$controller = function () {
call_user_func(['Flood\Component\Route\DemoHook\Home', 'call']);
call_user_func(['Flood\Component\Route\DemoHook\Home', 'handle']);
return call_user_func(['Flood\Component\Route\DemoHook\Home', 'respond']);
};
- Supports:
call-list
,call-param
,namespace
,controller-path
,register-autoload
- Implements:
call-list.'respond'
- will be returned at last- Requires:
call-list
,namespace
,class
invoke
- initiates the object and calls it with __invoke
{
"response": {
"*": {
"namespace": "Flood\\Component\\Route\\DemoHook",
"controller-path": "hook/demo/Controller/",
"register-autoload": true,
"type": "invoke",
"call-list": [
"call",
"handle",
"respond"
]
},
"home": {
"*": "/",
"en-US": "/",
"class": "Home"
}
}
}
<?php
$controller = function () {
$obj = new \Flood\Component\Route\DemoHook\Home();
call_user_func([$obj]);
call_user_func([$obj, 'call']);
call_user_func([$obj, 'handle']);
return call_user_func([$obj, 'respond']);
};
- Supports:
call-list
,call-param
,namespace
,controller-path
,register-autoload
- Implements:
call-param.'__construct'
- passes the call params as array to the constructorcall-param.'__invoke'
- passes the call params as params to__invoke
call-list.'respond'
- will be returned at last- Requires:
namespace
,class
function
- calls all call-list
as functions
{
"response": {
"*": {
"type": "function",
"call-list": [
"someFunction",
"anotherFunction"
]
},
"home": {
"*": "/",
"en-US": "/",
"call-list": [
"someFunction",
"anotherFunction"
]
}
}
}
At lazy, the functions need to be added to each response, but not the type when all of the responses use functions.
<?php
$controller = function () {
call_user_func('someFunction');
call_user_func('anotherFunction');
};
- Supports:
call-list
,call-param
- Implements:
call-list.'respond'
- will be returned at last
Parameters
You could specify per call
parameters which should be assigned:
{
"blog": {
"en-US": {
"namespace": "",
"type": "static",
"call-list": [
"staticCall",
"staticHandle"
],
"call-param": {
"staticCall": {
"test_param": "value-of-param"
}
},
"class": "Home"
}
}
}
This will call \Home::staticCall('value-of-param')
.
The array call-param
has indices that are used as foreign key to call-list
, when a matching call is happening the array keys are used as parameters for the method call.
Possible Future:
All further indices will be used as parameter foreign keys. The result of
getResponseData($param_key, $id, $locale)
will be used as value.
Requirements, Options, Defaults for the Route
To each route could be added requirements, options and defaults.
They will be added to the symfony route like they exist, only at defaults
the _controller
will also be added.
Converting to Symfony Route
These builded callbacks, informations and more now need to be created as Symfony Routes,
structure to path
In Flood the part that controls which uri path will match is builded in a few steps. This builded string is added to the Symfony Route path
property.
For a response the procedure is the following, the values could be overwritten like described previous:
- get
<hook-id>.route.response.<any>.structure
if is string - merge/replace
<hook-id>.route.response.<any>.structure-child
into placeholder{structure-child}
of previous result <hook-id>.route.hook.<any>.uri.path
into{hook-path}
of previous result<hook-id>.route.hook.<any>.locale-path
into{locale-path}
of previous result- replace the remaining placeholders dynamic if a value exists with
getResponseDate
and the value is astring
orint
host to host
As host will be used <hook-id>.route.hook.<any>.uri.host
and performed the following:
- wildcard, if
<hook-id>.route.hook.<any>.uri.path
is*
add a placeholder with{host}
as the host - if
<hook-id>.route.hook.<any>.www
is true, prefix the host withwww
- host doesn't get other placeholders changed
Symfony Construct
The routes are added to a RouteCollection, thous each route will need to have an unique name, at responses the name is generated with the <hook-id>
and the <hook-id>.route.response.<any>
and the locale
.
<?php
// `name` must be unique among all generated routes
$collection = new Symfony\Component\Routing\RouteCollection();
$collection->add(
$hook_id . ':' . $response_id . ':' . $locale,
new Symfony\Component\Routing\Route(
(isset($response['path']) ? $response['path'] : ''),
(isset($response['defaults']) ? $response['defaults'] : []),
(isset($response['requirements']) ? $response['requirements'] : []),
(isset($response['options']) ? $response['options'] : []),
(isset($response['host']) ? $response['host'] : ''),
(isset($response['schemes']) ? $response['schemes'] : []),
(isset($response['methods']) ? $response['methods'] : []),
(isset($response['condition']) ? $response['condition'] : '')
)
);
Further Guides
Created | Last Update