It reads the URL route and parses the values of path, so it could be interpreted manually or automatically in the fastest way possible (for example, to implement an MVC system).
Unlikely other libraries, this library does not have dependencies, and it is contained in a single class, so it is compatible with any PHP project, for example WordPress, Laravel, Drupal, a custom PHP project, etc.
This library is based in CoC Convention over Configuration. It reduces the boilerplate but it has fixed functionalities. This library does not allow to use custom "routes" but it covers practically all cases, so it increases the performance and usability while it sacrifices flexibility.
Let's say we have the next URL http://somedomain.dom/Customer/Update/2 This library converts this URL into variables that could be process or directly calling a method.
route.php
$route=new RouteOne('http://www.somedomain.dom');
$route->addPath('api/{controller}/{action}/{id}');
$route->addPath('{controller}/{action}/{id}/{idparent}');
$route->fetchPath();
$this->callObjectEx('cocacolacontroller{controller}Controller');
controllerCustomerController.php class
namespace cocacolacontroller;
class CustomerController {
public function updateAction($id=null,$idparent=null,$event=null) {
echo "We want to update the customer $id";
}
}
Let's say we do the next operation:
A user calls the next website http://somedomain.com/Customer/Insert, he wants to show a form to insert a customer
use eftecRouteOneRouteOne;
$route=new RouteOne('.',null,null); // Create the RouteOneClass
$route->fetch(); // fetch all the input values (from the route, get, post and such).
$route->callObject('somenamespace\controller\%sController'); // where it will call the class CustomerController*
or
use eftecRouteOneRouteOne;
$route=new RouteOne('.',null,null); // Create the RouteOneClass
$route->fetch(); // fetch all the input values (from the route, get, post and such).
$route->callObjectEx('somenamespace\controller\{controller}Controller'); // where it will call the class CustomerController*
This code calls to the method InsertActionGet (GET), InsertActionPost (POST) or InsertAction (GET/POST) inside the class Customer
The method called is written as follows:
class Customer {
public function insertAction($id="",$idparent="",$event="") {
// here we do our operation.
}
}
Let's se we want to Update a Customer number 20, then we could call the next page
http://somedomain.com/Customer/Update/20
where 20 is the "$id" of the customer to edit (it could be a number of a string)
And what if we want to Update a Customer number 20 of the business APPL
http://somedomain.com/Customer/Update/20/APPL
Where APPL is the idparent
Now, let's say we click on some button, or we do some action. It could be captured by the field _event, and it is read by the argument $event. This variable could be sent via GET or POST.
http://somedomain.com/Customer/Update/20/APPL?_event=click
Note: Modules are obtained automatically if you use addPath() and fetchPath(), so you don't need to specify it. Now, let's say our system is modular, and we have several customers (internal customers, external, etc.)
$route=new RouteOne('.',null,true); // true indicates it is modular.
or
$route=new RouteOne('.',null,['Internal']); // or we determine the module automatically. In this case, every url that starts with Internal
then
$route->fetch();
$route->callObject('somenamespace\%2s%\controller\%1sController');
http://somedomain.com/Internal/Customer/Update/20/APPL?_event=click
Then, the first ramification is the name of the module (Internal) and it calls the class somenamespaceInternalcontrollerCustomerController
composer require eftec/RouteOne
Linux:
vendor/bin/RouteOnecli -init (if the binary does not work, then chage the permission to execution)
Windows:
.vendorbinrouteonecli.bat -init
It will create the file .htaccess and the file route.php and route.php will have a default configuration.
const BASEURL="http://localhost"; // Base url edit this value.
const BASEWEBNS="eftec\controller"; // Base namespace (web) edit this value
const BASEAPINS="eftec\api"; // Base namespace (api) edit this value
Later, you can add or edit the code in this file.
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
RewriteEngine On
DirectoryIndex route.php
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Send Requests To Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ route.php?req=$1 [L,QSA]
</IfModule>
If your web host doesn't allow the FollowSymlinks option, try replacing it with Options +SymLinksIfOwnerMatch.
The important line is:
RewriteRule ^(.*)$ route.php?req=$1 [L,QSA] # The router to call.
server {
listen 80;
server_name localhost;
root /example.com/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
index index.html index.htm index.php;
charset utf-8;
location / {
try_files $uri $uri/ /router.php?req=$document_uri&$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ .php$ {
fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /.(?!well-known).* {
deny all;
}
}
The important line is:
try_files $uri $uri/ /router.php?req=$document_uri&$query_string;
server {
listen 80;
server_name localhost;
root c:/www;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
index index.html index.htm index.php;
charset utf-8;
location / {
try_files $uri $uri/ /router.php?req=$document_uri&$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ .php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /.(?!well-known).* {
deny all;
}
}
The important line is:
try_files $uri $uri/ /router.php?req=$document_uri&$query_string;
where router.php is the file that it will work as router. ?req=$1 is important because the system will read the route from "req"
// router.php
$route=new RouteOne(); // Create the RouteOneClass
$route->fetch(); // fetch all the input values (from the route, get, post and such).
$route->callObject('somenamespace\controller\%sController'); // where it will call the class somenamespacecontrollerCustomerController
Note:
If you want to use an argument different as "req", then you can change it using the next code:
$route->argumentName='newargument';
Since 1.21, it is possible to use a custom path instead of a pre-defined path. It is the recommended way. The other method is still present.
Syntax:
clearPath()
It clears all the paths defined
Syntax:
addPath($path, $name = null,callable $middleWare=null)
It adds a paths that could be evaluated using fetchPath()
Example:
$this->addPath('api/{controller}/{action}/{id:0}','apipath');
$this->addPath('/api/{controller}/{action}/{id:0}/','apipath'); // "/" at the beginner and end are trimmed.
$this->addPath('{controller}/{action}/{id:0}','webpath');
$this->addPath('{controller:root}/{action}/{id:0}','webpath'); // root path using default
$this->addPath('somepath','namepath',
function(callable $next,$id=null,$idparent=null,$event=null) {
echo "middlewaren";
$result=$next($id,$idparent,$event); // calling the controller
echo "endmiddlewaren";
return $result;
});
Note:
The first part of the path, before the "{" is used to determine which path will be used.
Example "path/{controller}" and "path/{controller}/{id}", the system will consider that are the same path
parameter string $path The path, example "aaa/{controller}/{action:default}/{id}"
Where default is the optional default value.
parameter string|null $name (optional), the name of the path
parameter callable|null $middleWare A callable function used for middleware.
The first argument of the function must be a callable method
The next arguments must be the arguments defined by callObjectEx
(id,idparent,event)
The path could start with a static location but the rest of the path must be defined by variables (enclosed by {}) and separated by "/".
You can also set a default value for a path by writing ":" after the name of the variable: {name:defaultvalue}
The name could be obtained using $this->currentPath. If you add a name with the same name, then it is replaced.
If you don't set a name, then it uses an autonumeric.
The name is also returned when you call $this->fetchPath()
Example:
$this->addPath('{controller}/{id}/{idparent}');
$this->addPath('myapi/otherfolder/{controller}/{id}/{idparent}');
$this->addPath('{controller:defcontroller}/{action:defaction}/{id:1}/{idparent:2}');
// url: /dummy/10/20 =>(controller: dummy, id=10, idparent=20)
// url: /myapi/otherfolder/dummy/10/20 =>(controller: dummy, id=10, idparent=20)
You can define different paths, however it only uses the first part of the path that matches some URL. 'path/somepath/{id}' will work 'path/{id}/other' will not work
Syntax:
fetchPath()
It fetches the path previously defined by addPath, and it returns the name(or number) of the path. If not found, then it returns false
Example:
$route=new RouteOne('http://www.example.dom');
$route->addPath('{controller}/{id}/{idparent}','optionalname');
// if the url is : http://www.example.dom/customer/1/200 then it will return
echo $route->fetchPath(); // optionalname
echo $route->controller; // customer
echo $route->id; // 1
echo $route->idparent; // 200
It gets a query value (URL).
Note: This query does not include the values "req","_event" and "_extra"
Example:
// http://localhost/..../?id=hi
$id=$router->getQuery("id"); // hi
$nf=$router->getQuery("something","not found"); // not found
It sets a query value
Example:
$route->setQuery("id","hi");
$id=$router->getQuery("id"); // hi
Sintax:
fetchPath()
Fetch the values from the route, and the values are processed.
Sintax
callObjectEx($classStructure, $throwOnError, $method, $methodGet, $methodPost,$arguments,$injectArguments)
It creates a new instance of an object (for example, a Controller object) and calls the method.
Note: It is an advanced version of this::callObject()
This method uses {} to replace values based in the next variables:
Tag | Description |
---|---|
{controller} | The name of the controller |
{action} | The current action |
{event} | The current event |
{type} | The current type of path (ws,controller,front,api) |
{module} | The current module (if module is active) |
{id} | The current id |
{idparent} | The current idparent |
{category} | The current category |
{subcategory} | The current subcategory |
{subsubcategory} | The current subsubcategory |
Example:
// controller example http://somedomain/Customer/Insert/23
$this->callObjectEx('cocacolacontroller{controller}Controller');
// it calls the method cocacolacontrollerCustomer::InsertAction(23,'','');
// front example: http://somedomain/product/coffee/nescafe/1
$this->callObjectEx('cocacolacontroller{category}Controller' // the class to call
,false // if error then it throw an error
,'{subcategory}' // the method to call (get, post or any other method)
,null // the method to call (method get)
,null // the method to call (method post)
,['subsubcategory','id'] // the arguments to call the method
,['arg1','arg2']); // arguments that will be passed to the constructor of the instance
// it calls the method cocacolacontrollerproduct::coffee('nescafe','1');
Call a method inside an object using the current route.
Example:
Router:
$databaseService=new SomeDatabaseService();
$route=new RouteOne();
$route->callObjectEx('cocacolacontroller{controller}Controller' // the class to call
,false // if error then it throw an error
,'{action}Action' // the method to call (get, post or any other method)
,'{action}Action{verb}' // the method to call (method get)
,'{action}Action{verb}' // the method to call (method post)
,['id', 'idparent', 'event'] // the arguments to call the method
,[$databaseService,$route]); // (optional)arguments that will be passed to the constructor of the instance
Controller:
namespace cocacolacontroller;
class CustomerController {
protected $databaseService;
protected $route;
public function __construct($databaseService,$route) {
// optional: injecting services
$this->databaseService=$databaseService;
$this->route=$route;
}
// any action GET or POST
public function GreenAction($id="",$idparent="",$event="") {
}
// GET only action (optional)
public function BlueActionGET($id="",$idparent="",$event="") {
// **my code goes here.**
}
// POST only action (optional)
public function YellowActionPOST($id="",$idparent="",$event="") {
// **my code goes here.**
}
// GET only action (optional)
public function RedActionGET($id="",$idparent="",$event="") {
// **my code goes here.**
}
// any action GET or POST
public function RedAction($id="",$idparent="",$event="") {
// **my code goes here.**
}
}
Results:
url | method called |
---|---|
http://localhost/Customer/Green (GET) | GreenAction |
http://localhost/Customer/Green/20/30?_event=click (GET) | GreenAction($id=20, $idparent=30, $event='click') |
http://localhost/Customer/Green (POST) | GreenAction |
http://localhost/Customer/Blue (GET) | BlueActionGET |
http://localhost/Customer/Blue (POST) | ERROR |
http://localhost/Customer/Yellow (GET) | ERROR |
http://localhost/Customer/Yellow (POST) | YellowActionPOST |
http://localhost/Customer/Red (GET) | RedActionGET (It has priority over RedAction) |
http://localhost/Customer/Red (POST) | RedAction |
http://localhost/Customer/Orange | ERROR |
It calls (include) a php file using the current name of the controller
Syntax:
getHeader($key, $valueIfNotFound = null)
It gets the current header (if any). If the value is not found, then it returns $valueIfNotFound. Note, the $key is always converted to uppercase.
Example:
$token=$this->getHeader('token','TOKEN NOT FOUND');
Syntax:
getBody($jsonDeserialize = false, $asAssociative = true)
It gets the body of a request.
Example:
$body=$this->getBody(); // '{"id"=>1,"name"=>john}' (as string)
$body=$this->getBody(true); // stdClass {id=>1,name=>john}
$body=$this->getBody(true,true); // ["id"=>1,"name"=>john]
Returns the current base url without traling space, paremters or queries
Note: this function relies on $_SERVER['SERVER_NAME'], and it could be modified by the end-user
It returns the current server without trailing slash.
$route->getCurrentServer(); // http://somedomain
It sets the current server name. It is used by getCurrentUrl() and getCurrentServer().
Note: If $this->setCurrentServer() is not set, then it uses $_SERVER['SERVER_NAME'], and it could be modified
by the user.
$route->setCurrentServer('localhost');
$route->setCurrentServer('127.0.0.1');
$route->setCurrentServer('domain.dom');
It gets the (full) url based in the information in the class.
$route->getUrl(); // http://somedomain/controller/action/id
$route->getUrl('id=20'); // http://somedomain/controller/action/id?id=20
$route->getUrl('id=20',true); // http://somedomain/controller/action/id?id=20&field=20&field2=40
It builds an url based in custom values
$route->url(null,"Customer","Update",20); // Customer/Update/20
It builds an url (front) based in custom values
$route->url(null,"Daily","Milk",20); // Daily/Milk/20
If the subdomain is empty or different to www, then it redirect to www.domain.com.
Note: It doesn't work with localhost, domain without TLD (netbios) or ip domains. It is on purpose.
Note: If this code needs to redirect, then it stops the execution of the code. Usually it must be called at the
top of the code
$route->alwaysWWW(); // if the domain is somedomain.dom/url, then it redirects to www.somedomain.dom/url
$route->alwaysWWW(true); // if the domain is http: somedomain.dom/url, then it redirects to https: www.somedomain.dom/url
If the page is loaded as http, then it redirects to https.
Note: It doesn't work with localhost, domain without TLD (netbios) or ip domains. It is on purpose.
Note: If this code needs to redirect, then it stops the execution of the code. Usually it must be called at
the top of the code
$route->alwaysHTTPS(); // http://somedomain.com ---> https://somedomain.com
$route->alwaysHTTPS(); // http://localhost ---> // http://localhost
$route->alwaysHTTPS(); // http://127.0.0.1 ---> // http://127.0.0.1
$route->alwaysHTTPS(); // http://mypc ---> // http://mypc
If the subdomain is www (example www.domain.dom) then it redirect to a naked domain domain.dom
Note: It doesn't work with localhost, domain without TLD (netbios) or ip domains. It is on purpose.
Note: If this code needs to redirect, then it stops the execution of the code. Usually,
it must be called at the top of the code
$route->alwaysNakedDomain(); // if the domain is www.somedomain.dom/url, then it redirects to somedomain.dom/url
$route->alwaysNakedDomain(true); // if the domain is http: www.somedomain.dom/url, then it redirects to https: somedomain.dom/url
Field | path | Description | Example |
---|---|---|---|
$argumentName | The name of the argument used by Apache .Htaccess and nginx | $this-argumentName='req'; | |
$base | It is the base url. | $this->base=0; | |
$type | It is the type of url (api,ws,controller or front) | echo $this->type; // api | |
$module | {module} | It's the current module | echo $this->module; |
$controller | {controller} | It's the controller. | echo $this->controller; |
$action | {action} | It's the action. | echo $this->action; |
$id | {id} | It's the identifier | echo $this->id; |
$event | {event} | It's the event (such as "click on button). | echo$this->event; |
$idparent | {idparent} | It is the current parent id (if any) | echo $this->idparent; |
$extra | {extra} | It's the event (such as "click on button) | echo $this->extra; |
$category | {category} | The current category. It is useful for the type 'front' | echo $this->category; |
$subcategory | {subcategory} | The current sub-category. It is useful for the type 'front' | echo $this->subcategory; |
$subsubcategory | {subsubcategory} | The current sub-sub-category. It is useful for the type 'front' | echo $this->subsubcategory; |
$identify | It is an associative array that helps to identify the api and ws route. | $this->identify=['api'=>'apiurl','ws'=>'webservices','controller'=>'']; | |
$isPostBack | its true if the page is POST, otherwise false. | if ($this->isPostBack) { ... }; | |
$verb | {verb} | The current verb, it could be GET,POST,PUT and DELETE. | if ($this->verb) { ... }; |
Example:
$this->addPath('portal/web/{controller}/{action:list}');
$this->fetchPath();
var_dump($this-action); // it shows the current action or the default value "list" if none.
Field | Description | Example |
---|---|---|
$allowedVerbs | A list with allowed verbs | $this->allowedVerbs=['GET', 'POST', 'PUT', 'DELETE']; |
$allowedFields | A list with allowed fields used by callObjectEx() | $this->allowedFields=['controller', 'action', 'verb', 'event', 'type', 'module', 'id' , 'idparent','category', 'subcategory', 'subsubcategory']; |
setWhitelist() | It sets an associative array with the whitelist to controller, action, category, subcategory, subsubcategory and module. If not set (null default value), then it allows any entry. Currently it only work with controller and category |
$this->setWhitelist('controller','Purchase','Invoice','Customer'); $this->setWhitelist('controller',null) // allows any controller; |
Whitelisting a method allows two operations:
For example:
// Example, value not in the whitelist: someweb.dom/customer/list
$this->setWhiteList('controller',['Product','Client']);
$this->fetch();
var_dump($this->controller); // null or the default value
var_dump($this->notAllowed); // true (whitelist error)
// Example, value in the whitelist but with the wrong case: someweb.dom/customer/list
$this->setWhiteList('controller',['Customer']);
$this->fetch();
var_dump($this->controller); // it shows "Customer" instead of "customer"
var_dump($this->notAllowed); // false (not error with the validation of the whitelist)
// reset whitelist for controllers
$this->setWhiteList('controller',null);
RouteOne contains a basic CLI to create and initialize the configuration. The binary RouteOnecli is located in the vendor/bin folder
./vendor/bin/RouteOnecli
enter router and press enter.
In the router menu, it will show the next screen:
Pending means that the operation is pending to do, or it requires something to configure.
Once done, configure will be marked as "ok"
Now, lets configure the paths