Download the PHP package earc/router without Composer
On this page you can find all versions of the php package earc/router. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Package router
Short Description eArc - the explicit architecture framework - router component
License MIT
Informations about the package router
earc Router
This is the router component of the earc framework. It also can be used within other frameworks or as standalone component.
The earc router does not use any configured routes - they are expressed via the filesystem which is transformed into an observer tree.
Given this direct mapping between the url and the directory structure, understanding
the apps routing process is as simple as typing tree
at the base of the routing
directory.
table of contents
- install
- bootstrap
- configure
- basic usage
- the controller
- the response controller
- implementing class specific type hint support
- implementing type hint support for a category of classes
- type hint request key mapping
- predefined type hints
- type hint default values
- the router event
- routes with special characters
- advanced usage
- pre and post processing
- via listeners attached to the route
- via live cycle hooks
- customized events
- routing/event tree inheritance
- subsystem handling
- further decoupling
- customized routes
- rewriting of routes
- the redirect directive
- the lookup directive
- the routing directory
- mapping routes
- rewriting of routes
- serializing events
- caching the routing tree
- pre and post processing
- further reading
- releases
- release 3.1
- release 3.0
- release 2.1
- release 2.0
- release 1.1
- release 1.0
- release 0.1
install
bootstrap
Place the following code snippets in the section where your script/framework is bootstrapped.
1 . Make use of the composer namespace driven autoloading.
2 . Then bootstrap earc/di for dependency injection and earc/core for the configuration file.
3 . Configure the router (see configure).
4 . And dispatch the router event to call the responsible controller(s).
configure
earc/router uses earc/event-tree to pass routing events to observers represented by the directory structure. You need a folder within your namespace that is the root for the event tree.
Put the parameters in a file named .earc-config.php
beneath
the vendor folder.
The path to the event tree root folder has to be absolute or relative to your projects vendor directory.
Of course you can use a YAML file to define the configuration array.
basic usage
Since we use the native tree data structures of the modern operating systems to organize our code it is a tiny step to define our routes and targeting controller.
It is as easy as it can get.
- Go to the event tree root directory and make a new subdirectory
routing
. - For every route make subdirectories for the fixed part and put a class at
the end extending the
eArc\Router\AbstractController
. - Use the
process()
method and the passedRouterEvent
to hook in your controller logic.
the controller
Given you plan to use the urls /admin/user
, /admin/user/edit/{id}
, /admin/user/add
and /admin/user/delete
for their obvious purpose, you have two options:
-
Either you place one controller in the
routing/admin/user
directory (with no subdirectories namededit
,add
ordelete
). Then all user managing logic has to be spawned in this one controller. - Or you place one controller in the
routing/admin/user
directory and a second in therouting/admin/user/edit
directory and another in therouting/admin/user/add
directory and the last in therouting/admin/user/delete
directory.
The second is the recommended way.
A controller would look like this:
One action in every controller is supreme to the traditional way of parametrized method calling:
- It forces programmers to move business logic out of the controller.
- Every action is exposed to pre and post-processing. You can add the logic without touching anything but the filesystem. (See pre and-post processing via listeners attached to the route for details.)
If you want to stick to the traditional way you may implement the
logic in an abstract BaseController
extending the AbstractController
. Or use
the live cycle hooks of the earc router (recommended). For
the parametrized method calling logic itself you will need three lines of code at
most.
Since your controllers all have different namespaces you can name them all
Controller
. But it is recommended to name them in a more explicit way.
the response controller
Whereas the AbstractController
is a traditional
earc/event-tree listener, the
AbstractResponseController
is a step away from events towards the requirements
of routing. It is available since release 2.1 and supports parameter injection
and transformation (via the
earc/parameter-transformer).
Hint: It is an architectural decision which controller type to prefer. It depends
on your project. Traditional web apps should use the AbstractResponseController
whereas event driven designs may prefer the AbstractController
.
This type of controller would look like this:
Hint: Nullable types transform the string parameter 'null'
to the null
value.
That makes it possible to send a null value as part of an url.
The controller code looks a bit cleaner and saves you a view lines. The earc router
does know the build in primitive types of php, the entities defined via
earc/data, all services that can be build
via earc/di and the interfaces it is shipped
with (RouterEventInterface
,RouteInformationInterface
and the RequestInformationInterface
).
You have to extend the existing logic to support other type hints. This can be done in two separate ways. (The example uses the well known doctrine orm.)
implementing class specific type hint support
To support type hints for a single class implement the ParameterTransformerFactoryInterface
.
Now User
object will be injected into the respond
method if the argument
userObject
of the Request
holds a valid user id.
implementing type hint support for a category of classes
To support a wider range of classes let a service implement the
ParameterTransformerFactoryServiceInterface
and tag it.
You can mix both. The ParameterTransformerFactoryServiceInterface
interface approach is
more code efficient but the ParameterTransformerFactoryInterface
is a tiny pinch
ahead in terms of runtime.
type hint request key mapping
Since the Route
parameter are accessed via numbers you can not name the respond
method parameters alike.
For all cases the parameter name can not match the input variables you can define
a mapping. Just overwrite the getInputKeyMapping
method of the AbstractResponseController
predefined type hints
The response controller has three predefined type hints:
RouterEventInterface
RouteInformationInterface
RequestInformationInterface
You can access the related objects simply by type hinting them. To extend the predefined type hints extend theAbstractResponseController
and overwrite thegetPredefinedTypeHints
method.
type hint default values
The parameter default value is used if the transformation results in a null
value.
If no default value is hinted the null
value is used.
Hint: A detailed documentation of the transformation process can be found on the pages of the earc/parameter-transformer.
the router event
The router event is always build with an url, request method and variables. Each router event instance is always bound to a valid request and can be easily serialized and saved for later use if necessary. By passing additional parameters to the event you can overwrite the url, the request method and the variables.
This gives you the freedom to do whatever pre processing you need, rebuild a saved request or simulate one for some integration tests.
If not supplied or passed as null
value the url is set to the path extracted
from the $_SERVER['REQUEST_URI']
. The default of the request method is the value
of the SERVER['REQUEST_METHOD']
. The request variables are set via an auto import
of the 'INPUT_*' variables if not passed to the constructor.
Every router event carries the information about the request and the route. They
are saved in a request immutable (access via $event->getRequest()
) and a route
immutable (access via $event->getRoute()
). For details consult the
eArc\Router\Interface\RouteInformationInterface
and the
eArc\Router\Interface\RequestInformationInterface
.
routes with special characters
The namespace constrains on characters (especially not allowing -
, .
and ~
)
limits the usable route names. The fastest solution is to use the .redirect
directive of the earc/event-tree. Place a plain text file named .redirect
in
the parent directory. Suppose you want to use the url /spe~ci-al.chars
but you
use the namespace compatible substitute /special_characters
. Then put in the
routing
directory the following file:
Now the route /spe~ci-al.chars
is represented by the directory
routing/special_characters
.
The .redirect
directive is explained in the
The redirect directive section in detail.
advanced usage
pre and post-processing
There are a tons of examples where logic needs to be executed before or after the controller logic. They can be divided into three cases.
- The logic is specific to one route/controller.
- The logic is specific to a sub route/set of controllers.
- The logic is useful for all or nearly all controllers.
via listeners attached to the route
The first two cases can use the fact that the earc/router uses the earc/event-tree
package. The route events travel from the routing
folder to the targeted controller
and can be intercepted via listeners. The router event triggers all listeners that
implement the eArc\Router\Interface\RouterListenerInterface
.
Lets start with the second case first.
Suppose you want to check the admin privileges for all routes starting with /admin
.
Simply put a class in the routing/admin
folder that implements the RouterListenerInterface
and process the event analogue to the controller. You can even kill the event if
it shall not reach any controller. (For details consult the documentation of
earc/event-tree.) Of course the listener
can dispatch a new route event that is targeting /login
or
/error-pages/access-denied
.
Please note a new router event does not redirect the browser client. It simply
changes the flow of the apps request processing. To make an redirect change the
path from /login
to /redirect/login
and place into the routing/redirect
folder
an listener or controller that makes an redirect.
As you may have noticed you can use the router event to decouple your logic in a very transparent way.
Let us look at the second case now.
You can simply put a listener in the same directory as the controller and it will
be called. In order to tell the router that it shall be called before or after the
controller it has to implement the eArc\EventTree\Interfaces\SortableListenerInterface
.
If getPatience()
returns a positive float it will be called after the controller
and vice versa as the controller has a patience of 0. If you have more than one
listener in one directory you can specify an order by the SortableListenerInterface
too. If you want to make it configurable use the di_param
function of the earc/di
package in the return statement.
If a listener shall be called only if the route is targeting the controller, the
listener has to implement the eArc\EventTree\Interfaces\PhaseSpecificListenerInterface
and return the ObserverTree::PHASE_DESTINATION
constant.
To learn more of listener patience
and event phases
consult the documentation of
the earc/event-tree.
via live cycle hooks
The simplest way to implement the third case and hook into the live cycle of all controllers is to extend your controllers from your own base controller. You can do pre and post-processing, log exceptions or implement the old style of action handling - having many actions in one controller.
- Pro: It is simple and fast.
- Contra: It is not flexible. For example if you have a core app and many client apps extending the core app, there is no way the clients can change the flow. Even such basic overwriting techniques as decoration or blacklisting can not be applied to the base controller without decorating every single controller.
Perhaps you heard of the open-closed-principle (OCP) already. As you have seen above inheritance is not suitable to follow the OCP on a large scale. The program flow is not open for modification anymore. To overcome this earc/router exposes the calling of the listeners/controllers on the event tree.
Extend your event tree root by a folder named earc
, earc/lifecycle
and
earc/lifecycle/router
. If you place in the last folder a class implementing
the eArc\EventTree\Interfaces\ListenerInterface
you can intercept the
eArc\Router\LifeCycle\RouterLifeCycleEvent
.
Lets do the above example again using the force of the event tree.
We need to put three classes into the earc/lifecycle/router
folder:
And last but not least we blacklist the original ExecuteCallListener
in the
configuration section. As the controllers should not be called twice.
Now our logic is open (for extension) and closed (for modification) on an app inheritance scale.
Instead of blacklisting the ExecuteCallListener
you can decorate
it if you prefer.
Notes:
- Decoration can be done anywhere in the code prior to the call but blacklisting has to be done before the first event ist dispatched.
- In the case of decoration you have to place the decorating
ExecuteCallListener
outside the event tree.
routing/event tree inheritance
As you may have noticed, the original ExecuteCallListener
lives outside of your
event tree root directory as part of the vendor directory but is called by the event.
This is what is called event tree inheritance. If you consult your configuration
you will notice there are two earc.event_tree.directories
. Yours and the
earc/router/earc-event-tree
. If you take the two event trees and combine them
at their roots you get the event tree that is actual used. You can combine as
many trees as you want. A leaf exists if it exists in at least one tree.
The routing part is combined too, not surprisingly though. It gives you the ability to define routes package wise.
Event tree inheritance is a mighty tool but can be confusing for beginners. You
can use the view-tree
command line tool of the earc/event-tree package
to draw (and grep
) a representation of the actual tree.
customized events
To keep your components decoupled the event should be the only place where runtime information is kept (when a listener/controller has finished his work). As the runtime information is app specific it is part of your architectural responsibility to design your own events.
Best practice is to use interfaces to describe the runtime information. Follow
the interface segregation principle
(ISP). Design
objects that implement the interface(s) and extend the eArc\Router\RouterEvent
to provide these objects.
Don't forget to replace the router event in your bootstrap section.
Now all runtime information that has to be exchanged between your listeners/controllers is exposed, easy to find and easy to understand.
subsystem handling
If you need a router event that triggers only a subset of listeners/controllers,
you can modify the getApplicableListener()
method provided by the EventInterface
.
It returns an array of all listener interfaces that are called by the event.
For example if a core app supports several versions you can use separate listeners/controllers for different versions this way. If a controller supports more than one version it simply implements more than one listener interface.
Other use cases where this functionality comes handy:
- Some part of the app is only available in some country or to some language.
- Some part of the app is only active in debug mode.
- The app behaviour changes significantly for power users paying more money.
- Different parts of the app can be toggled.
- Different phases of processing routes.
further decoupling
You can use both the event tree and the router tree (you can look at it as a subset of the event tree) for a better decoupling of your code. Lets do one more example.
Nearly all web apps do some sort of rendering. Rendering need three things come together: The data, the template and the engine. The data does not change on behalf of visualisation, the template and the engine does.
The controllers control the data generation and persistence mechanisms of your app, they should not control the visualisation layer too. The underlying principle is the famous single-responsibility principle (SRP).
Therefore it is a bad practice to inject a template engine into a controller or use a template annotation within. The controller should not know about these things.
The route determines the controller and together with some parameter the data, but in old fashioned frameworks like symfony it seems that it also determines the template. That is not correct and should not be the way you code. I have seen uncountable examples where two actions/routes do the same thing generating the same response data just to get access to different templates or return a json representation instead of a template representation of the data.
Think of it a bit and you realize that the different routes are just different presentation parameters in these cases. Old fashioned MVCs does not support decoupling very well, so the programmers need to get inventive in a bad way.
How can we do better?
Every controller returns data that is very specific. You shouldn't return it as array, you should return it as object. Once you have an object you have an identifier for the collection of templates the app can use. If there is more than one template available in this collection the representation parameter comes into play. Remember the parameter are part of the event, but the representation parameter is a valid part of the returned data too - possibly transformed by the controller.
Once the controller has processed there is the data and the keys to choose from the templates a single one. All attached to the event. That smells for post processing!
After implementing we can change the chosen template by just changing the assignment of the object class, representation parameter and template. If we want to change the template engine, we just need to decorate one listener. We do not need to change the code of all controllers or need to comply to an engine interface that does not fit to our new use case.
Hint: It might be a good idea to organize your templates in the same or similar directory structure as the returned objects. This way you reduce the configuration overhead significantly. Note that this directory structure can be completely different from the routing directory structure.
customized routes
rewriting of routes
There are several reasons for a route to change. Backward compatibility, customer request or SEO are just three of it. On a app without routing tree inheritance (event tree inheritance on the routing part) it is easy. Just rename and restructure the folders.
the redirect directive
If you need the same content under different routes you should use the .redirect
directive of the earc/event-tree. It is just a file named .redirect
. You can place
it in every folder where redirection should take place. Every line is a redirection.
At the beginning of the line you put the sub folder name you want to redirect
(it does not need to exist) and at second place separated by a blank you put the
target path relative to the event trees root directory. ~/
is a shortcut to
reference the current directory.
To exclude an existing or inherited directory just leave the target empty. .redirect
directives are part of the event tree inheritance. If several .redirect
directives
of the same path exists naming the same sub folder the ordering of the
´earc.event_tree.directories´ is important. The directives are overwritten in
the order their directory tree are registered. You can use the target shortcut ~
to cancel an redirect.
For example the rewrite of the route /imported/products
to /products/imported
would
take two steps (for each rewritten part one):
1 ) Place into the routing
directory the .redirect
directive
2 ) Place into the imported
directory the .redirect
directive
Obviously using this changes the directory arguments of the route but it does not change the called controller or the order the listeners are called.
To rewrite the base leafs put the .redirect
directive into the event tree root.
the lookup directive
Every .redirect
directive you use destroys a bit of the clarity the explicit
design of the event tree gives to you. Therefore making massive use of the .redirect
directive is an anti pattern. If you need to redirect quite a bit of the tree
it is better to rewrite it and use the .lookup
directive to include the listeners
of the old tree.
Like the .redirect
directive the .lookup
directive is a plain text file.
If you put a path in there it will be included. That means every listener in the
linked leaf of the event tree will be handled as if it would reside in the current
leaf. Every line is an separate include. The path has to be relative to the event
tree root.
the routing directory
If you rewrite a routing tree it is a best practice to use a new routing directory.
If you don't, you will need beside the .lookup
directive (which is easy to understand)
the .redirect
directive (which can be far more confusing) to exclude unwanted
routing leafs.
To achieve this just configure a new routing dir for your routing event.
You can define different routing directories for different events. The first key
the event passes an instanceof
check against defines the routing directory. If
none passes the routing directory is routing
.
mapping routes
There are some use cases where a mapping is superior to the concepts of the earc
router. Think of a multinational site prefixing the routes with the language key
like /en
, /de
or /fr
. Then you could make sub directories to the routing
directory named en
, de
and fr
and place in every dir the .lookup
for a
controller who sets the language locale and makes the rerouting. There is nothing
wrong with that but its much more efficient to extract the language and cut down
the route by a few lines of code.
serializing events
Routing events can be serialized.
This makes it easy as drinking a cup of tea to check the access rights of users and reroute them after they logged in or have registered.
However note that unserialized events need to be dispatched again. They lose their position in the routing tree and begin their travel from the routing directory. This way it is really hard to mess it up.
caching the routing tree
The concept of the routing tree is deeply rooted in the file system. Filesystem access is not cheap in terms of time and can be a bottle neck. If you use a file cache like ACPu it is worth considering loading the event tree structure (even if it may be huge) into memory.
The configuration steps are described in the earc/event-tree documentation.
further reading
-
The earc/router is build on top of the earc/event-tree. Please feel free to consult the earc/event-tree documentation.
-
To take full advantage of the global container free dependency injection system earc/di reading its documentation might be a good idea.
- To handle edge cases of the type hint transformation the documentation of the earc/parameter-transformer gives insight.
releases
release 4.0
- PHP ^8.0 only
- Type hint transformation of the
AbstractResponseController
uses the earc/parameter-transformer. eArc\Router\Interfaces\ParameterFactoryInterface
is replaced byeArc\ParameterTransformer\Interfaces\ParameterTransformerFactoryInterface
release 3.1
AbstractResponseController
supports type hints for earc/data entities
release 3.0
- PHP ^7.3 || PHP ^8.0
release 2.1
- response controller
- parameter injection
- parameter transformer
ParameterFactoryInterface
AbstractResponseController::USE_REQUEST_KEYS
- response controller default value
release 2.0
- bootstrap via earc/core
- caching of the routing tree
release 1.1
RequestInformationInterface::getArg()
andRouteInformationInterface::getParam()
now accept a default parameter
release 1.0
- The route is now matched against the
routing
part of an earc/event-tree. - The dispatcher is now part of earc/router instead of earc/core and dispatches an earc/event-tree event.
- Introduces the immutable objects
Route
andRequest
. Both are attached as payload to the dispatched earc/event-tree event. - There are no access and main-controller anymore. Controller are now disguised earc/event-tree listener.
- The router live cycle is exposed via an event tree. Making it easy to implement pre and post-processing while following the open-closed-principle on a large scale.
release 0.1
The first official release.