Ubiquity User guide¶
Quick start with console¶
Note
If you do not like console mode, you can switch to quick-start with web tools (UbiquityMyAdmin).
Install Composer¶
ubiquity utilizes Composer to manage its dependencies. So, before using, you will need to make sure you have Composer installed on your machine.
Install Ubiquity-devtools¶
Download the Ubiquity-devtools installer using Composer.
composer global require phpmv/ubiquity-devtools
Test your recent installation by doing:
Ubiquity version

You can get at all times help with a command by typing: Ubiquity help
followed by what you are looking for.
Example :
Ubiquity help project
Directory structure¶
The project created in the quick-start folder has a simple and readable structure:
the app folder contains the code of your future application:
app
├ cache
├ config
├ controllers
├ models
└ views
Start-up¶
Go to the newly created folder quick-start and start the build-in php server:
Ubiquity serve
Check the correct operation at the address http://127.0.0.1:8090:

Note
If port 8090 is busy, you can start the server on another port using -p option.
Ubiquity serve -p=8095
Controller¶
The console application dev-tools saves time in repetitive operations. We go through it to create a controller.
Ubiquity controller DefaultController

We can then edit app/controllers/DefaultController
file in our favorite IDE:
1 2 3 4 5 6 7 | namespace controllers;
/**
* Controller DefaultController
*/
class DefaultController extends ControllerBase{
public function index(){}
}
|
Add the traditional message, and test your page at http://127.0.0.1:8090/DefaultController
class DefaultController extends ControllerBase{
public function index(){
echo 'Hello world!';
}
}
For now, we have not defined routes,
Access to the application is thus made according to the following scheme:
controllerName/actionName/param
The default action is the index method, we do not need to specify it in the url.
Route¶
Important
The routing is defined with the attribute Route
(with php>8) or the annotation @route
and is not done in a configuration file:
it’s a design choice.
The automated parameter set to true allows the methods of our class to be defined as sub routes of the main route /hello
.
With annotations:
1 2 3 4 5 6 7 8 9 10 11 12 | namespace controllers;
/**
* Controller DefaultController
* @route("/hello","automated"=>true)
*/
class DefaultController extends ControllerBase{
public function index(){
echo 'Hello world!';
}
}
|
With attributes (php>8):
1 2 3 4 5 6 7 8 9 10 11 | namespace controllers;
use Ubiquity\attributes\items\router\Route;
#[Route('/hello', automated: true)]
class DefaultController extends ControllerBase{
public function index(){
echo 'Hello world!';
}
}
|
Router cache¶
Important
No changes on the routes are effective without initializing the cache.
Annotations are never read at runtime. This is also a design choice.
We can use the console for the cache re-initialization:
Ubiquity init-cache

Let’s check that the route exists:
Ubiquity info:routes

We can now test the page at http://127.0.0.1:8090/hello
Action & route with parameters¶
We will now create an action (sayHello) with a parameter (name), and the associated route (to):
The route will use the parameter name of the action:
Ubiquity action DefaultController.sayHello -p=name -r=to/{name}/

After re-initializing the cache (init-cache command), the info:routes command should display:

Change the code in your IDE: the action must say Hello to somebody…
/**
* @route("to/{name}/")
*/
public function sayHello($name){
echo 'Hello '.$name.'!';
}
and test the page at http://127.0.0.1:8090/hello/to/Mr SMITH
Action, route parameters & view¶
We will now create an action (information) with two parameters (title and message), the associated route (info), and a view to display the message:
The route will use the two parameters of the action.
Ubiquity action DefaultController.information -p=title,message='nothing' -r=info/{title}/{message} -v
Note
The -v (–view) parameter is used to create the view associated with the action.
After re-initializing the cache, we now have 3 routes:

Let’s go back to our development environment and see the generated code:
/**
* @route("info/{title}/{message}")
*/
public function information($title,$message='nothing'){
$this->loadView('DefaultController/information.html');
}
We need to pass the 2 variables to the view:
/**
* @route("info/{title}/{message}")
*/
public function information($title,$message='nothing'){
$this->loadView('DefaultController/information.html',compact('title','message'));
}
And we use our 2 variables in the associated twig view:
<h1>{{title}}</h1>
<div>{{message | raw}}</div>
We can test your page at http://127.0.0.1:8090/hello/info/Quick start/Ubiquity is quiet simple
It’s obvious

Quick start with Webtools¶
Install Composer¶
ubiquity utilizes Composer to manage its dependencies. So, before using, you will need to make sure you have Composer installed on your machine.
Install Ubiquity-devtools¶
Download the Ubiquity-devtools installer using Composer.
composer global require phpmv/ubiquity-devtools
Test your recent installation by doing:
Ubiquity version

You can get at all times help with a command by typing: Ubiquity help
followed by what you are looking for.
Example :
Ubiquity help project
Project creation¶
Create the quick-start projet with Webtools interface (the -a option)
Ubiquity new quick-start -a
Directory structure¶
The project created in the quick-start folder has a simple and readable structure:
the app folder contains the code of your future application:
app
├ cache
├ config
├ controllers
├ models
└ views
Start-up¶
Go to the newly created folder quick-start and start the build-in php server:
Ubiquity serve
Check the correct operation at the address http://127.0.0.1:8090:

Note
If port 8090 is busy, you can start the server on another port using -p option.
Ubiquity serve -p=8095
Controller¶
Goto admin interface by clicking on the button Webtools:

Select the tools you need:

The web application Webtools saves time in repetitive operations.

We go through it to create a controller.
Go to the controllers part, enter DefaultController in the controllerName field and create the controller:

The controller DefaultController is created:

We can then edit app/controllers/DefaultController
file in our favorite IDE:
1 2 3 4 5 6 7 | namespace controllers;
/**
* Controller DefaultController
**/
class DefaultController extends ControllerBase{
public function index(){}
}
|
Add the traditional message, and test your page at http://127.0.0.1:8090/DefaultController
class DefaultController extends ControllerBase{
public function index(){
echo 'Hello world!';
}
}
For now, we have not defined routes,
Access to the application is thus made according to the following scheme:
controllerName/actionName/param
The default action is the index method, we do not need to specify it in the url.
Route¶
Important
The routing is defined with the annotation @route
and is not done in a configuration file:
it’s a design choice.
The automated parameter set to true allows the methods of our class to be defined as sub routes of the main route /hello
.
1 2 3 4 5 6 7 8 9 10 11 12 | namespace controllers;
/**
* Controller DefaultController
* @route("/hello","automated"=>true)
**/
class DefaultController extends ControllerBase{
public function index(){
echo 'Hello world!';
}
}
|
Router cache¶
Important
No changes on the routes are effective without initializing the cache.
Annotations are never read at runtime. This is also a design choice.
We can use the web tools for the cache re-initialization:
Go to the Routes section and click on the re-init cache button

The route now appears in the interface:

We can now test the page by clicking on the GET button or by going to the address http://127.0.0.1:8090/hello
Action & route with parameters¶
We will now create an action (sayHello) with a parameter (name), and the associated route (to):
The route will use the parameter name of the action:
Go to the Controllers section:
- click on the + button associated with DefaultController,
- then select Add new action in.. item.

Enter the action information in the following form:

After re-initializing the cache with the orange button, we can see the new route hello/to/{name}:

Check the route creation by going to the Routes section:

We can now test the page by clicking on the GET button:

We can see the result:

We could directly go to http://127.0.0.1:8090/hello/to/Mr SMITH
address to test
Action, route parameters & view¶
We will now create an action (information) with tow parameters (title and message), the associated route (info), and a view to display the message:
The route will use the two parameters of the action.
In the Controllers section, create another action on DefaultController:

Enter the action information in the following form:

Note
The view checkbox is used to create the view associated with the action.
After re-initializing the cache, we now have 3 routes:

Let’s go back to our development environment and see the generated code:
/**
*@route("info/{title}/{message}")
**/
public function information($title,$message='nothing'){
$this->loadView('DefaultController/information.html');
}
We need to pass the 2 variables to the view:
/**
*@route("info/{title}/{message}")
**/
public function information($title,$message='nothing'){
$this->loadView('DefaultController/information.html',compact('title','message'));
}
And we use our 2 variables in the associated twig view:
<h1>{{title}}</h1>
<div>{{message | raw}}</div>
We can test our page at http://127.0.0.1:8090/hello/info/Quick start/Ubiquity is quiet simple
It’s obvious

Ubiquity-devtools installation¶
Install Composer¶
ubiquity utilizes Composer to manage its dependencies. So, before using, you will need to make sure you have Composer installed on your machine.
Install Ubiquity-devtools¶
Download the Ubiquity-devtools installer using Composer.
composer global require phpmv/ubiquity-devtools
Make sure to place the ~/.composer/vendor/bin
directory in your PATH so the Ubiquity executable can be located by your system.
Once installed, the simple Ubiquity new
command will create a fresh Ubiquity installation in the directory you specify.
For instance, Ubiquity new blog
would create a directory named blog containing an Ubiquity project:
Ubiquity new blog
The semantic option adds Semantic-UI for the front end.
You can see more options about installation by reading the Project creation section.
Project creation¶
After installing Ubiquity-devtools installation, in your terminal, call the new command in the root folder of your web server :
Samples¶
A simple project
Ubiquity new projectName
A project with UbiquityMyAdmin interface
Ubiquity new projectName -a
A project with bootstrap and semantic-ui themes installed
Ubiquity new projectName --themes=bootstrap,semantic
Installer arguments¶
short name | name | role | default | Allowed values | Since devtools |
---|---|---|---|---|---|
b | dbName | Sets the database name. | |||
s | serverName | Defines the db server address. | 127.0.0.1 | ||
p | port | Defines the db server port. | 3306 | ||
u | user | Defines the db server user. | root | ||
w | password | Defines the db server password. | ‘’ | ||
h | themes | Install themes. | semantic,bootstrap,foundation | ||
m | all-models | Creates all models from db. | false | ||
a | admin | Adds UbiquityMyAdmin interface. | false | ||
i | siteUrl | Defines the site URL. | http://127.0.0.1/{projectname} | 1.2.6 | |
e | rewriteBase | Sets the base for rewriting. | /{projectname}/ | 1.2.6 |
Arguments usage¶
short names¶
Example of creation of the blog project, connected to the blogDb database, with generation of all models
Ubiquity new blog -b=blogDb -m=true
long names¶
Example of creation of the blog project, connected to the bogDb database, with generation of all models and integration of semantic theme
Ubiquity new blog --dbName=blogDb --all-models=true --themes=semantic
Running¶
To start the embedded web server and test your pages, run from the application root folder:
Ubiquity serve
The web server is started at 127.0.0.1:8090
Project configuration¶
Normally, the installer limits the modifications to be performed in the configuration files and your application is operational after installation

Main configuration¶
The main configuration of a project is localised in the app/conf/config.php
file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | return array(
"siteUrl"=>"%siteUrl%",
"database"=>[
"dbName"=>"%dbName%",
"serverName"=>"%serverName%",
"port"=>"%port%",
"user"=>"%user%",
"password"=>"%password%"
],
"namespaces"=>[],
"templateEngine"=>'Ubiquity\views\engine\twig\Twig',
"templateEngineOptions"=>array("cache"=>false),
"test"=>false,
"debug"=>false,
"di"=>[%injections%],
"cacheDirectory"=>"cache/",
"mvcNS"=>["models"=>"models","controllers"=>"controllers"]
);
|
Services configuration¶
Services loaded on startup are configured in the app/conf/services.php
file.
1 2 3 4 5 6 7 8 9 10 | use Ubiquity\controllers\Router;
try{
\Ubiquity\cache\CacheManager::startProd($config);
}catch(Exception $e){
//Do something
}
\Ubiquity\orm\DAO::startDatabase($config);
Router::start();
Router::addRoute("_default", "controllers\\IndexController");
|
Pretty URLs¶
Apache¶
The framework ships with an .htaccess file that is used to allow URLs without index.php. If you use Apache to serve your Ubiquity application, be sure to enable the mod_rewrite module.
AddDefaultCharset UTF-8
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /blog/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{HTTP_ACCEPT} !(.*images.*)
RewriteRule ^(.*)$ index.php?c=$1 [L,QSA]
</IfModule>
See Apache configuration for more.
Nginx¶
On Nginx, the following directive in your site configuration will allow “pretty” URLs:
location /{
rewrite ^/(.*)$ /index.php?c=$1 last;
}
See NginX configuration for more.
Laravel Valet Driver¶
Valet is a php development environment for Mac minimalists. No Vagrant
, no /etc/hosts
file. You can even share your sites publicly using local tunnels.
Laravel Valet configures your Mac to always run Nginx
in the background when your machine starts. Then, using DnsMasq
, Valet proxies all requests on the *.test
domain to point to sites installed on your local machine.
Get more info about Laravel Valet
Create UbiquityValetDriver.php
under ~/.config/valet/Drivers/
add below php code and save it.
<?php
class UbiquityValetDriver extends BasicValetDriver{
/**
* Determine if the driver serves the request.
*
* @param string $sitePath
* @param string $siteName
* @param string $uri
* @return bool
*/
public function serves($sitePath, $siteName, $uri){
if(is_dir($sitePath . DIRECTORY_SEPARATOR . '.ubiquity')) {
return true;
}
return false;
}
public function isStaticFile($sitePath, $siteName, $uri){
if(is_file($sitePath . $uri)) {
return $sitePath . $uri;
}
return false;
}
/**
* Get the fully resolved path to the application's front controller.
*
* @param string $sitePath
* @param string $siteName
* @param string $uri
* @return string
*/
public function frontControllerPath($sitePath, $siteName, $uri){
$sitePath.='/public';
$_SERVER['DOCUMENT_ROOT'] = $sitePath;
$_SERVER['SCRIPT_NAME'] = '/index.php';
$_SERVER['SCRIPT_FILENAME'] = $sitePath . '/index.php';
$_SERVER['DOCUMENT_URI'] = $sitePath . '/index.php';
$_SERVER['PHP_SELF'] = '/index.php';
$_GET['c'] = '';
if($uri) {
$_GET['c'] = ltrim($uri, '/');
$_SERVER['PHP_SELF'] = $_SERVER['PHP_SELF']. $uri;
$_SERVER['PATH_INFO'] = $uri;
}
$indexPath = $sitePath . '/index.php';
if(file_exists($indexPath)) {
return $indexPath;
}
}
}
Devtools usage¶
Project creation¶
See Project creation to create a project.
Tip
For all other commands, you must be in your project folder or one of its subfolders.
Important
The .ubiquity
folder created automatically with the project allows the devtools to find the root folder of the project.
If it has been deleted or is no longer present, you must recreate this empty folder.
Controller creation¶
Specifications¶
- command :
controller
- Argument :
controller-name
- aliases :
create-controller
Parameters¶
short name | name | role | default | Allowed values |
---|---|---|---|---|
v | view | Creates the associated view index. | true | true, false |
Samples:¶
Creates the controller controllers\ClientController
class in app/controllers/ClientController.php
:
Ubiquity controller ClientController
Creates the controller controllers\ClientController
class in app/controllers/ClientController.php
and the associated view in app/views/ClientController/index.html
:
Ubiquity controller ClientController -v
Action creation¶
Specifications¶
- command :
action
- Argument :
controller-name.action-name
- aliases :
new-action
Parameters¶
short name | name | role | default | Allowed values |
---|---|---|---|---|
p | params | The action parameters (or arguments). | a,b=5 or $a,$b,$c | |
r | route | The associated route path. | /path/to/route | |
v | create-view | Creates the associated view. | false | true,false |
Samples:¶
Adds the action all
in controller Users
:
Ubiquity action Users.all
code result:
1 2 3 4 5 6 7 8 9 10 11 12 13 | namespace controllers;
/**
* Controller Users
*/
class Users extends ControllerBase{
public function index(){}
public function all(){
}
}
|
Adds the action display
in controller Users
with a parameter:
Ubiquity action Users.display -p=idUser
code result:
1 2 3 4 5 6 7 8 | class Users extends ControllerBase{
public function index(){}
public function display($idUser){
}
}
|
Adds the action display
with an associated route:
Ubiquity action Users.display -p=idUser -r=/users/display/{idUser}
code result:
1 2 3 4 5 6 7 8 9 10 11 12 13 | namespace controllers;
use Ubiquity\attributes\items\router\Route;
class Users extends ControllerBase{
public function index(){}
#[Route('/users/display/{idUser}')]
public function display($idUser){
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 | namespace controllers;
class Users extends ControllerBase{
public function index(){}
/**
*@route("/users/display/{idUser}")
*/
public function display($idUser){
}
}
|
Adds the action search
with multiple parameters:
Ubiquity action Users.search -p=name,address=''
code result:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | namespace controllers;
use Ubiquity\attributes\items\router\Route;
class Users extends ControllerBase{
public function index(){}
#[Route('/users/display/{idUser}')]
public function display($idUser){
}
public function search($name,$address=''){
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | namespace controllers;
class Users extends ControllerBase{
public function index(){}
/**
* @route("/users/display/{idUser}")
*/
public function display($idUser){
}
public function search($name,$address=''){
}
}
|
Adds the action search
and creates the associated view:
Ubiquity action Users.search -p=name,address -v
Model creation¶
Note
Optionally check the database connection settings in the app/config/config.php file before running these commands.
To generate a model corresponding to the user table in database:
Ubiquity model user
Cache initialization¶
To initialize the cache for routing (based on annotations in controllers) and orm (based on annotations in models) :
Ubiquity init-cache
URLs¶
like many other frameworks, if you are using router with it’s default behavior, there is a one-to-one relationship between a URL string and its corresponding controller class/method. The segments in a URI normally follow this pattern:
example.com/controller/method/param
example.com/controller/method/param1/param2
Default method¶
When the URL is composed of a single part, corresponding to the name of a controller, the index method of the controller is automatically called :
URL :
example.com/Products
example.com/Products/index
Controller :
1 2 3 4 5 | class Products extends ControllerBase{
public function index(){
//Default action
}
}
|
Required parameters¶
If the requested method requires parameters, they must be passed in the URL:
Controller :
1 2 3 | class Products extends ControllerBase{
public function display($id){}
}
|
Valid Urls :
example.com/Products/display/1
example.com/Products/display/10/
example.com/Products/display/ECS
Optional parameters¶
The called method can accept optional parameters.
If a parameter is not present in the URL, the default value of the parameter is used.
Controller :
class Products extends ControllerBase{
public function sort($field, $order='ASC'){}
}
Valid Urls :
example.com/Products/sort/name (uses "ASC" for the second parameter)
example.com/Products/sort/name/DESC
example.com/Products/sort/name/ASC
Case sensitivity¶
On Unix systems, the name of the controllers is case-sensitive.
Controller :
class Products extends ControllerBase{
public function caseInsensitive(){}
}
Urls :
example.com/Products/caseInsensitive (valid)
example.com/Products/caseinsensitive (valid because the method names are case insensitive)
example.com/products/caseInsensitive (invalid since the products controller does not exist)
Router¶
Routing can be used in addition to the default mechanism that associates controller/action/{parameters}
with an url.
Dynamic routes¶
Dynamic routes are defined at runtime.
It is possible to define these routes in the app/config/services.php file.
Important
Dynamic routes should only be used if the situation requires it:
- in the case of a micro-application
- if a route must be dynamically defined
In all other cases, it is advisable to declare the routes with annotations, to benefit from caching.
Callback routes¶
The most basic Ubiquity routes accept a Closure.
In the context of micro-applications, this method avoids having to create a controller.
1 2 3 4 5 | use Ubiquity\controllers\Router;
Router::get("foo", function(){
echo 'Hello world!';
});
|
Callback routes can be defined for all http methods with:
- Router::post
- Router::put
- Router::delete
- Router::patch
- Router::options
Controller routes¶
Routes can also be associated more conventionally with an action of a controller:
1 2 3 | use Ubiquity\controllers\Router;
Router::addRoute('bar', \controllers\FooController::class,'index');
|
The method FooController::index()
will be accessible via the url /bar
.
In this case, the FooController must be a class inheriting from Ubiquity\controllers\Controller or one of its subclasses, and must have an index method:
1 2 3 4 5 6 7 8 | namespace controllers;
class FooController extends ControllerBase{
public function index(){
echo 'Hello from foo';
}
}
|
Static routes¶
Static routes are defined using annotation or with php native attributes since Ubiquity 2.4.0
.
Note
These annotations or attributes are never read at runtime.
It is necessary to reset the router cache to take into account the changes made on the routes.
Creation¶
1 2 3 4 5 6 7 8 9 10 | namespace controllers;
use Ubiquity\attributes\items\router\Route;
class ProductsController extends ControllerBase{
#[Route('products')]
public function index(){}
}
|
1 2 3 4 5 6 7 8 9 10 | namespace controllers;
class ProductsController extends ControllerBase{
/**
* @route("products")
*/
public function index(){}
}
|
The method Products::index()
will be accessible via the url /products
.
Note
- The initial or terminal slash is ignored in the path. The following routes are therefore equivalent:
#[Route('products')]
#[Route('/products')]
#[Route('/products/')]
Route parameters¶
A route can have parameters:
1 2 3 4 5 6 7 8 9 10 11 12 13 | namespace controllers;
use Ubiquity\attributes\items\router\Route;
class ProductsController extends ControllerBase{
...
#[Route('products/{value}')]
public function search($value){
// $value will equal the dynamic part of the URL
// e.g. at /products/brocolis, then $value='brocolis'
// ...
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 | namespace controllers;
class ProductsController extends ControllerBase{
...
/**
* @route("products/{value}")
*/
public function search($value){
// $value will equal the dynamic part of the URL
// e.g. at /products/brocolis, then $value='brocolis'
// ...
}
}
|
Route optional parameters¶
A route can define optional parameters, if the associated method has optional arguments:
1 2 3 4 5 6 7 8 9 10 11 | namespace controllers;
use Ubiquity\attributes\items\router\Route;
class ProductsController extends ControllerBase{
...
#[Route('products/all/{pageNum}/{countPerPage}')]
public function list($pageNum,$countPerPage=50){
// ...
}
}
|
1 2 3 4 5 6 7 8 9 10 11 | namespace controllers;
class ProductsController extends ControllerBase{
...
/**
* @route("products/all/{pageNum}/{countPerPage}")
*/
public function list($pageNum,$countPerPage=50){
// ...
}
}
|
Route requirements¶
It is possible to add specifications on the variables passed in the url via the attribute requirements.
1 2 3 4 5 6 7 8 9 10 11 | namespace controllers;
use Ubiquity\attributes\items\router\Route;
class ProductsController extends ControllerBase{
...
#[Route('products/all/{pageNum}/{countPerPage}',requirements: ["pageNum"=>"\d+","countPerPage"=>"\d?"])]
public function list($pageNum,$countPerPage=50){
// ...
}
}
|
1 2 3 4 5 6 7 8 9 10 11 | namespace controllers;
class ProductsController extends ControllerBase{
...
/**
* @route("products/all/{pageNum}/{countPerPage}","requirements"=>["pageNum"=>"\d+","countPerPage"=>"\d?"])
*/
public function list($pageNum,$countPerPage=50){
// ...
}
}
|
- The defined route matches these urls:
products/all/1/20
products/all/5/
- but not with that one:
products/all/test
Parameter typing¶
The route declaration takes into account the data types passed to the action, which avoids adding requirements for simple types (int, bool, float).
1 2 3 4 5 6 7 8 9 10 11 | namespace controllers;
use Ubiquity\attributes\items\router\Route;
class ProductsController extends ControllerBase{
...
#[Route('products/{productNumber}')]
public function one(int $productNumber){
// ...
}
}
|
1 2 3 4 5 6 7 8 9 10 11 | namespace controllers;
class ProductsController extends ControllerBase{
...
/**
* @route("products/{productNumber}")
*/
public function one(int $productNumber){
// ...
}
}
|
- The defined route matches these urls:
products/1
products/20
- but not with that one:
products/test
- Correct values by data type:
int
:1
…bool
:0
or1
float
:1
1.0
…
Route http methods¶
It is possible to specify the http method or methods associated with a route:
1 2 3 4 5 6 7 8 9 10 | namespace controllers;
use Ubiquity\attributes\items\router\Route;
class ProductsController extends ControllerBase{
#[Route('products',methods: ['get','post'])]
public function index(){}
}
|
1 2 3 4 5 6 7 8 9 10 | namespace controllers;
class ProductsController extends ControllerBase{
/**
* @route("products","methods"=>["get","post"])
*/
public function index(){}
}
|
The methods attribute can accept several methods:
@route("testMethods","methods"=>["get","post","delete"])
#[Route('testMethods', methods: ['get','post','delete'])]
The @route annotation or Route attribute defaults to all HTTP methods.
There is a specific annotation for each of the existing HTTP methods:
- @get => Get
- @post => Post
- @put => Put
- @patch => Patch
- @delete => Delete
- @head => Head
- @options => Options
1 2 3 4 5 6 7 8 9 10 | namespace controllers;
use Ubiquity\attributes\items\router\Get;
class ProductsController extends ControllerBase{
#[Get('products')]
public function index(){}
}
|
1 2 3 4 5 6 7 8 9 10 | namespace controllers;
class ProductsController extends ControllerBase{
/**
* @get("products")
*/
public function index(){}
}
|
Route name¶
It is possible to specify the name of a route, this name then facilitates access to the associated url.
If the name attribute is not specified, each route has a default name, based on the pattern controllerName_methodName.
1 2 3 4 5 6 7 8 9 10 | namespace controllers;
use Ubiquity\attributes\items\router\Route;
class ProductsController extends ControllerBase{
#[Route('products',name: 'products.index')]
public function index(){}
}
|
1 2 3 4 5 6 7 8 9 10 | namespace controllers;
class ProductsController extends ControllerBase{
/**
* @route("products","name"=>"products.index")
*/
public function index(){}
}
|
URL or path generation¶
Route names can be used to generate URLs or paths.
Linking to Pages in Twig
<a href="{{ path('products.index') }}">Products</a>
Global route¶
The @route annotation can be used on a controller class :
1 2 3 4 5 6 7 8 9 10 11 | namespace controllers;
use Ubiquity\attributes\items\router\Route;
#[Route('products')]
class ProductsController extends ControllerBase{
...
#[Route('/all')]
public function display(){}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 | namespace controllers;
/**
* @route("/product")
*/
class ProductsController extends ControllerBase{
...
/**
* @route("/all")
*/
public function display(){}
}
|
In this case, the route defined on the controller is used as a prefix for all controller routes :
The generated route for the action display is /product/all
automated routes¶
If a global route is defined, it is possible to add all controller actions as routes (using the global prefix), by setting the automated parameter :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | namespace controllers;
use Ubiquity\attributes\items\router\Route;
#[Route('/products',automated: true)]
class ProductsController extends ControllerBase{
public function index(){}
public function generate(){}
public function display($id){}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 | namespace controllers;
/**
* @route("/product","automated"=>true)
*/
class ProductsController extends ControllerBase{
public function index(){}
public function generate(){}
public function display($id){}
}
|
- The automated attribute defines the 3 routes contained in ProductsController:
- /product/(index/)?
- /product/generate
- /product/display/{id}
inherited routes¶
With the inherited attribute, it is also possible to generate the declared routes in the base classes, or to generate routes associated with base class actions if the automated attribute is set to true in the same time.
The base class:
1 2 3 4 5 6 7 8 9 10 11 12 13 | namespace controllers;
use Ubiquity\attributes\items\router\Route;
abstract class ProductsBase extends ControllerBase{
#[Route('(index/)?')]
public function index(){}
#[Route('sort/{name}')]
public function sortBy($name){}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | namespace controllers;
abstract class ProductsBase extends ControllerBase{
/**
*@route("(index/)?")
*/
public function index(){}
/**
* @route("sort/{name}")
*/
public function sortBy($name){}
}
|
The derived class using inherited members:
1 2 3 4 5 6 7 8 9 10 | namespace controllers;
use Ubiquity\attributes\items\router\Route;
#[Route('/product',inherited: true)]
class ProductsController extends ProductsBase{
public function display(){}
}
|
1 2 3 4 5 6 7 8 9 | namespace controllers;
/**
* @route("/product","inherited"=>true)
*/
class ProductsController extends ProductsBase{
public function display(){}
}
|
- The inherited attribute defines the 2 routes defined in ProductsBase:
- /products/(index/)?
- /products/sort/{name}
If the automated and inherited attributes are combined, the base class actions are also added to the routes.
Global route parameters¶
The global part of a route can define parameters, which will be passed in all generated routes.
These parameters can be retrieved through a public data member:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | namespace controllers;
use Ubiquity\attributes\items\router\Route;
#[Route('/foo/{bar}',automated: true)]
class FooController {
public string $bar;
public function display(){
echo $this->bar;
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | namespace controllers;
/**
* @route("/foo/{bar}","automated"=>true)
*/
class FooController {
public string $bar;
public function display(){
echo $this->bar;
}
}
|
Accessing the url /foo/bar/display
displays the contents of the bar member.
Route without global prefix¶
If the global route is defined on a controller, all the generated routes in this controller are preceded by the prefix.
It is possible to explicitly introduce exceptions on some routes, using the #/
prefix.
1 2 3 4 5 6 7 8 9 10 11 | namespace controllers;
use Ubiquity\attributes\items\router\Route;
#[Route('/foo',automated: true)]
class FooController {
#[Route('#/noRoot')]
public function noRoot(){}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 | namespace controllers;
/**
* @route("/foo","automated"=>true)
*/
class FooController {
/**
* @route("#/noRoot")
*/
public function noRoot(){}
}
|
The controller defines the /noRoot
url, which is not prefixed with the /foo
part.
Route priority¶
The prority parameter of a route allows this route to be resolved in a priority order.
The higher the priority parameter, the more the route will be defined at the beginning of the stack of routes in the cache.
In the example below, the products/all route will be defined before the /products route.
1 2 3 4 5 6 7 8 9 10 11 12 13 | namespace controllers;
use Ubiquity\attributes\items\router\Route;
class ProductsController extends ControllerBase{
#[Route('products', priority: 1)]
public function index(){}
#[Route('products/all', priority: 10)]
public function all(){}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | namespace controllers;
class ProductsController extends ControllerBase{
/**
* @route("products","priority"=>1)
*/
public function index(){}
/**
* @route("products/all","priority"=>10)
*/
public function all(){}
}
|
The default priority value is 0
.
Routes response caching¶
It is possible to cache the response produced by a route:
In this case, the response is cached and is no longer dynamic.
#[Route('products/all', cache: true)]
public function all(){}
/**
* @route("products/all","cache"=>true)
*/
public function all(){}
Cache duration¶
The duration is expressed in seconds, if it is omitted, the duration of the cache is infinite.
#[Route('products/all', cache: true, duration: 3600)]
public function all(){}
/**
* @route("products/all","cache"=>true,"duration"=>3600)
*/
public function all(){}
Cache expiration¶
It is possible to force reloading of the response by deleting the associated cache.
Router::setExpired("products/all");
Dynamic routes caching¶
Dynamic routes can also be cached.
Important
This possibility is only useful if this caching is not done in production, but at the time of initialization of the cache.
Router::get("foo", function(){
echo 'Hello world!';
});
Router::addRoute("string", \controllers\Main::class,"index");
CacheManager::storeDynamicRoutes(false);
Checking routes with devtools :
Ubiquity info:routes

Error management (404 & 500 errors)¶
Default routing system¶
With the default routing system (the controller+action couple defining a route), the error handler can be redefined to customize the error management.
In the configuration file app/config/config.php, add the onError key, associated to a callback defining the error messages:
"onError"=>function ($code, $message = null,$controller=null){
switch($code){
case 404:
$init=($controller==null);
\Ubiquity\controllers\Startup::forward('IndexController/p404',$init,$init);
break;
}
}
Implement the requested action p404 in the IndexController:
...
public function p404(){
echo "<div class='ui error message'><div class='header'>404</div>The page you are looking for doesn't exist!</div>";
}
Routage with annotations¶
It is enough in this case to add a last route disabling the default routing system, and corresponding to the management of the 404 error:
...
#[Route('{url}', priority: -1000)]
public function p404($url){
echo "<div class='ui error message'><div class='header'>404</div>The page `$url` you are looking for doesn't exist!</div>";
}
...
/**
* @route("{url}","priority"=>-1000)
*/
public function p404($url){
echo "<div class='ui error message'><div class='header'>404</div>The page `$url` you are looking for doesn't exist!</div>";
}
Controllers¶
A controller is a PHP class inheriting from Ubiquity\controllers\Controller
, providing an entry point in the application.
Controllers and their methods define accessible URLs.
Controller creation¶
The easiest way to create a controller is to do it from the devtools.
From the command prompt, go to the project folder.
To create the Products controller, use the command:
Ubiquity controller Products
The Products.php
controller is created in the app/controllers
folder of the project.
1 2 3 4 5 6 7 8 9 | namespace controllers;
/**
* Controller Products
*/
class Products extends ControllerBase{
public function index(){}
}
|
It is now possible to access URLs (the index
method is solicited by default):
example.com/Products
example.com/Products/index
Note
A controller can be created manually. In this case, he must respect the following rules:
- The class must be in the app/controllers folder
- The name of the class must match the name of the php file
- The class must inherit from ControllerBase and be defined in the namespace controllers
- and must override the abstract index method
Methods¶
public¶
The second segment of the URI determines which public method in the controller gets called.
The “index” method is always loaded by default if the second segment of the URI is empty.
1 2 3 4 5 6 7 8 | namespace controllers;
class First extends ControllerBase{
public function hello(){
echo "Hello world!";
}
}
|
The hello
method of the First
controller makes the following URL available:
example.com/First/hello
method arguments¶
the arguments of a method must be passed in the url, except if they are optional.
namespace controllers;
class First extends ControllerBase{
public function says($what,$who='world') {
echo $what.' '.$who;
}
}
The hello
method of the First
controller makes the following URLs available:
example.com/First/says/hello (says hello world)
example.com/First/says/Hi/everyone (says Hi everyone)
private¶
Private or protected methods are not accessible from the URL.
Default controller¶
The default controller can be set with the Router, in the services.php
file
Router::start();
Router::addRoute("_default", "controllers\First");
In this case, access to the example.com/
URL loads the controller First and calls the default index method.
views loading¶
loading¶
Views are stored in the app/views
folder. They are loaded from controller methods.
By default, it is possible to create views in php, or with twig.
Twig is the default template engine for html files.
php view loading¶
If the file extension is not specified, the loadView method loads a php file.
namespace controllers;
class First extends ControllerBase{
public function displayPHP(){
//loads the view app/views/index.php
$this->loadView('index');
}
}
twig view loading¶
If the file extension is html, the loadView method loads an html twig file.
namespace controllers;
class First extends ControllerBase{
public function displayTwig(){
//loads the view app/views/index.html
$this->loadView("index.html");
}
}
Default view loading¶
If you use the default view naming method :
The default view associated to an action in a controller is located in views/controller-name/action-name
folder:
views
│
└ Users
└ info.html
1 2 3 4 5 6 7 8 | namespace controllers;
class Users extends BaseController{
...
public function info(){
$this->loadDefaultView();
}
}
|
view parameters¶
One of the missions of the controller is to pass variables to the view.
This can be done at the loading of the view, with an associative array:
class First extends ControllerBase{
public function displayTwigWithVar($name){
$message="hello";
//loads the view app/views/index.html
$this->loadView('index.html', ['recipient'=>$name, 'message'=>$message]);
}
}
The keys of the associative array create variables of the same name in the view.
Using of this variables in Twig:
<h1>{{message}} {{recipient}}</h1>
Variables can also be passed before the view is loaded:
//passing one variable
$this->view->setVar('title','Message');
//passing an array of 2 variables
$this->view->setVars(['message'=>$message,'recipient'=>$name]);
//loading the view that now contains 3 variables
$this->loadView('First/index.html');
view result as string¶
It is possible to load a view, and to return the result in a string, assigning true to the 3rd parameter of the loadview method :
$viewResult=$this->loadView("First/index.html",[],true);
echo $viewResult;
multiple views loading¶
A controller can load multiple views:
namespace controllers;
class Products extends ControllerBase{
public function all(){
$this->loadView('Main/header.html', ['title'=>'Products']);
$this->loadView('Products/index.html',['products'=>$this->products]);
$this->loadView('Main/footer.html');
}
}
Important
A view is often partial. It is therefore important not to systematically integrate the html and body tags defining a complete html page.
views organization¶
It is advisable to organize the views into folders. The most recommended method is to create a folder per controller, and store the associated views there.
To load the index.html
view, stored in app/views/First
:
$this->loadView("First/index.html");
initialize and finalize¶
The initialize method is automatically called before each requested action, the method finalize after each action.
Example of using the initialize and finalize methods with the base class automatically created with a new project:
namespace controllers;
use Ubiquity\controllers\Controller;
use Ubiquity\utils\http\URequest;
/**
* ControllerBase.
*/
abstract class ControllerBase extends Controller{
protected $headerView = "@activeTheme/main/vHeader.html";
protected $footerView = "@activeTheme/main/vFooter.html";
public function initialize() {
if (! URequest::isAjax ()) {
$this->loadView ( $this->headerView );
}
}
public function finalize() {
if (! URequest::isAjax ()) {
$this->loadView ( $this->footerView );
}
}
}
Access control¶
Access control to a controller can be performed manually, using the isValid and onInvalidControl methods.
The isValid method must return a boolean wich determine if access to the action passed as a parameter is possible:
In the following example, access to the actions of the IndexController controller is only possible if an activeUser session variable exists:
class IndexController extends ControllerBase{
...
public function isValid($action){
return USession::exists('activeUser');
}
}
If the activeUser variable does not exist, an unauthorized 401 error is returned.
The onInvalidControl method allows you to customize the unauthorized access:
class IndexController extends ControllerBase{
...
public function isValid($action){
return USession::exists('activeUser');
}
public function onInvalidControl(){
$this->initialize();
$this->loadView('unauthorized.html');
$this->finalize();
}
}
<div class="ui container">
<div class="ui brown icon message">
<i class="ui ban icon"></i>
<div class="content">
<div class="header">
Error 401
</div>
<p>You are not authorized to access to <b>{{app.getController() ~ "::" ~ app.getAction()}}</b>.</p>
</div>
</div>
</div>
It is also possible to automatically generate access control from AuthControllers
Forwarding¶
A redirection is not a simple call to an action of a controller.
The redirection involves the initialize and finalize methods, as well as access control.
The forward method can be invoked without the use of the initialize and finalize methods:
It is possible to redirect to a route by its name:
Dependency injection¶
namespaces¶
The controller namespace is defined by default to controllers in the app/config/config.php file.
Super class¶
Inheritance can be used to factorize controller behavior.
The BaseController class created with a new project is present for this purpose.
Specific controller base classes¶
Controller class | role |
---|---|
Controller | Base class for all controllers |
SimpleViewController | Base class associated with a php template engine (for using with micro-services) |
SimpleViewAsyncController | Base class associated with a php template engine for async servers |
Events¶
Note
The Events module uses the static class EventsManager to manage events.
Framework core events¶
Ubiquity emits events during the different phases of submitting a request.
These events are relatively few in number, to limit their impact on performance.
Part | Event name | Parameters | Occures when |
---|---|---|---|
ViewEvents | BEFORE_RENDER | viewname, parameters | Before rendering a view |
ViewEvents | AFTER_RENDER | viewname, parameters | After rendering a view |
DAOEvents | GET_ALL | objects, classname | After loading multiple objects |
DAOEvents | GET_ONE | object, classname | After loading one object |
DAOEvents | BEFORE_UPDATE | instance | Before updating an object |
DAOEvents | AFTER_UPDATE | instance, result | After updating an object |
DAOEvents | BEFORE_INSERT | instance | Before inserting an object |
DAOEvents | AFTER_INSERT | instance, result | After inserting an object |
RestEvents | BEFORE_INSERT | instance | Before inserting an object |
RestEvents | BEFORE_UPDATE | instance | Before updating an object |
Note
There is no BeforeAction and AfterAction event, since the initialize and finalize methods of the controller class perform this operation.
Listening to an event¶
Example 1 :
Adding an _updated property on modified instances in the database :
1 2 3 4 5 6 7 8 9 10 | use Ubiquity\events\EventsManager;
use Ubiquity\events\DAOEvents;
...
EventsManager::addListener(DAOEvents::AFTER_UPDATE, function($instance,$result){
if($result==1){
$instance->_updated=true;
}
});
|
Note
The parameters passed to the callback function vary according to the event being listened to.
Example 2 :
Modification of the view rendering
1 2 3 4 5 6 7 8 | use Ubiquity\events\EventsManager;
use Ubiquity\events\ViewEvents;
...
EventsManager::addListener(ViewEvents::AFTER_RENDER,function(&$render,$viewname,$datas){
$render='<h1>'.$viewname.'</h1>'.$render;
});
|
Creating your own events¶
Example :
Creating an event to count and store the number of displays per action :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | namespace eventListener;
use Ubiquity\events\EventListenerInterface;
use Ubiquity\utils\base\UArray;
class TracePageEventListener implements EventListenerInterface {
const EVENT_NAME = 'tracePage';
public function on(&...$params) {
$filename = \ROOT . \DS . 'config\stats.php';
$stats = [ ];
if (file_exists ( $filename )) {
$stats = include $filename;
}
$page = $params [0] . '::' . $params [1];
$value = $stats [$page] ?? 0;
$value ++;
$stats [$page] = $value;
UArray::save ( $stats, $filename );
}
}
|
Registering events¶
Registering the TracePageEventListener event in services.php
:
1 2 3 4 5 6 | use Ubiquity\events\EventsManager;
use eventListener\TracePageEventListener;
...
EventsManager::addListener(TracePageEventListener::EVENT_NAME, TracePageEventListener::class);
|
Triggering events¶
An event can be triggered from anywhere, but it makes more sense to do it here in the initialize method of the base controller :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | namespace controllers;
use Ubiquity\controllers\Controller;
use Ubiquity\utils\http\URequest;
use Ubiquity\events\EventsManager;
use eventListener\TracePageEventListener;
use Ubiquity\controllers\Startup;
/**
* ControllerBase.
**/
abstract class ControllerBase extends Controller{
protected $headerView = "@activeTheme/main/vHeader.html";
protected $footerView = "@activeTheme/main/vFooter.html";
public function initialize() {
$controller=Startup::getController();
$action=Startup::getAction();
EventsManager::trigger(TracePageEventListener::EVENT_NAME, $controller,$action);
if (! URequest::isAjax ()) {
$this->loadView ( $this->headerView );
}
}
public function finalize() {
if (! URequest::isAjax ()) {
$this->loadView ( $this->footerView );
}
}
}
|
The result in app/config/stats.php :
return array(
"controllers\\IndexController::index"=>5,
"controllers\\IndexController::ct"=>1,
"controllers\\NewController::index"=>1,
"controllers\\TestUCookieController::index"=>1
);
Events registering optimization¶
It is preferable to cache the registration of listeners, to optimize their loading time :
Create a client script, or a controller action (not accessible in production mode) :
use Ubiquity\events\EventsManager;
public function initEvents(){
EventsManager::start();
EventsManager::addListener(DAOEvents::AFTER_UPDATE, function($instance,$result){
if($result==1){
$instance->_updated=true;
}
});
EventsManager::addListener(TracePageEventListener::EVENT_NAME, TracePageEventListener::class);
EventsManager::store();
}
After running, cache file is generated in app/cache/events/events.cache.php
.
Once the cache is created, the services.php
file just needs to have the line :
\Ubiquity\events\EventsManager::start();
Dependency injection¶
Note
For performance reasons, dependency injection is not used in the core part of the framework.
Dependency Injection (DI) is a design pattern used to implement IoC.
It allows the creation of dependent objects outside of a class and provides those objects to a class through different ways. Using DI, we move the creation and binding of the dependent objects outside of the class that depends on it.
Note
Ubiquity only supports property injection, so as not to require introspection at execution.
Only controllers support dependency injection.
Service autowiring¶
Service creation¶
Create a service
1 2 3 4 5 6 7 8 9 10 11 | namespace services;
class Service{
public function __construct($ctrl){
echo 'Service instanciation in '.get_class($ctrl);
}
public function do($someThink=""){
echo 'do '.$someThink ."in service";
}
}
|
Autowiring in Controller¶
Create a controller that requires the service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | namespace controllers;
/**
* Controller Client
**/
class ClientController extends ControllerBase{
/**
* @autowired
* @var services\Service
*/
private $service;
public function index(){}
/**
* @param \services\Service $service
*/
public function setService($service) {
$this->service = $service;
}
}
|
In the above example, Ubiquity looks for and injects $service when ClientController is created.
- The @autowired annotation requires that:
- the type to be instantiated is declared with the @var annotation
- $service property has a setter, or whether declared public
As the annotations are never read at runtime, it is necessary to generate the cache of the controllers:
Ubiquity init-cache -t=controllers
It remains to check that the service is injected by going to the address /ClientController
.
Service injection¶
Service¶
Let’s now create a second service, requiring a special initialization.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class ServiceWithInit{
private $init;
public function init(){
$this->init=true;
}
public function do(){
if($this->init){
echo 'init well initialized!';
}else{
echo 'Service not initialized';
}
}
}
|
Injection in controller¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | namespace controllers;
/**
* Controller Client
**/
class ClientController extends ControllerBase{
/**
* @autowired
* @var \services\Service
*/
private $service;
/**
* @injected
*/
private $serviceToInit;
public function index(){
$this->serviceToInit->do();
}
/**
* @param \services\Service $service
*/
public function setService($service) {
$this->service = $service;
}
/**
* @param mixed $serviceToInit
*/
public function setServiceToInit($serviceToInit) {
$this->serviceToInit = $serviceToInit;
}
}
|
Di declaration¶
In app/config/config.php
, create a new key for serviceToInit property to inject in di part.
"di"=>["ClientController.serviceToInit"=>function(){
$service=new \services\ServiceWithInit();
$service->init();
return $service;
}
]
generate the cache of the controllers:
Ubiquity init-cache -t=controllers
Check that the service is injected by going to the address /ClientController
.
Note
If the same service is to be used in several controllers, use the wildcard notation :
"di"=>["*.serviceToInit"=>function(){
$service=new \services\ServiceWithInit();
$service->init();
return $service;
}
]
Injection with a qualifier name¶
If the name of the service to be injected is different from the key of the di array, it is possible to use the name attribute of the @injected annotation
In app/config/config.php
, create a new key for serviceToInit property to inject in di part.
"di"=>["*.service"=>function(){
$service=new \services\ServiceWithInit();
$service->init();
return $service;
}
]
/**
* @injected("service")
*/
private $serviceToInit;
Service injection at runtime¶
It is possible to inject services at runtime, without these having been previously declared in the controller classes.
1 2 3 4 5 6 7 | namespace services;
class RuntimeService{
public function __construct($ctrl){
echo 'Service instanciation in '.get_class($ctrl);
}
}
|
In app/config/config.php
, create the @exec key in di part.
"di"=>["@exec"=>"rService"=>function($ctrl){
return new \services\RuntimeService($ctrl);
}
]
With this declaration, the $rService member, instance of RuntimeService, is injected into all the controllers.
It is then advisable to use the javadoc comments to declare $rService in the controllers that use it (to get the code completion on $rService in your IDE).
1 2 3 4 5 6 7 8 9 10 11 12 | namespace controllers;
/**
* Controller Client
* property services\RuntimeService $rService
**/
class MyController extends ControllerBase{
public function index(){
$this->rService->do();
}
}
|
CRUD Controllers¶
- The CRUD controllers allow you to perform basic operations on a Model class:
- Create
- Read
- Update
- Delete
- …
Note
Since version 2.4.6, Two types of CrudController exist:
- ResourceCrudController associated with a model
- MultiResourceCRUDController, displaying an index and allowing to navigate between models.
ResourceCrudController¶
Creation¶
In the admin interface (web-tools), activate the Controllers part, and choose create Resource Crud controller:

- Then fill in the form:
- Enter the controller name
- Select the associated model
- Then click on the validate button

Description of the features¶
The generated controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?php
namespace controllers;
/**
* CRUD Controller UsersController
**/
class UsersController extends \Ubiquity\controllers\crud\CRUDController{
public function __construct(){
parent::__construct();
$this->model= models\User::class;
}
public function _getBaseRoute():string {
return 'UsersController';
}
}
|
Test the created controller by clicking on the get button in front of the index action:

Read (index action)¶

Clicking on a row of the dataTable (instance) displays the objects associated to the instance (details action):

Using the search area:

Create (newModel action)¶
It is possible to create an instance by clicking on the add button

The default form for adding an instance of User:

Update (update action)¶
The edit button on each row allows you to edit an instance

The default form for adding an instance of User:

Delete (delete action)¶
The delete button on each row allows you to edit an instance

Display of the confirmation message before deletion:

Customization¶
Create again a ResourceCrudController from the admin interface:

It is now possible to customize the module using overriding.
Overview¶

Classes overriding¶
ResourceCRUDController methods to override¶
Method | Signification | Default return |
---|---|---|
routes | ||
index() | Default page : list all objects | |
edit($modal=”no”, $ids=””) | Edits an instance | |
newModel($modal=”no”) | Creates a new instance | |
display($modal=”no”,$ids=””) | Displays an instance | |
delete($ids) | Deletes an instance | |
update() | Displays the result of an instance updating | |
showDetail($ids) | Displays associated members with foreign keys | |
refresh_() | Refreshes the area corresponding to the DataTable (#lv) | |
refreshTable($id=null) | //TO COMMENT |
ModelViewer methods to override¶
Method | Signification | Default return |
---|---|---|
index route | ||
getModelDataTable($instances, $model,$totalCount,$page=1) | Creates the dataTable and Adds its behavior | DataTable |
getDataTableInstance($instances,$model,$totalCount,$page=1) | Creates the dataTable | DataTable |
recordsPerPage($model,$totalCount=0) | Returns the count of rows to display (if null there’s no pagination) | null or 6 |
getGroupByFields() | Returns an array of members on which to perform a grouping | [] |
getDataTableRowButtons() | Returns an array of buttons to display for each row [“edit”,”delete”,”display”] | [“edit”,”delete”] |
onDataTableRowButton(HtmlButton $bt, ?string $name) | To override for modifying the dataTable row buttons | |
getCaptions($captions, $className) | Returns the captions of the column headers | all member names |
detail route | ||
showDetailsOnDataTableClick() | To override to make sure that the detail of a clicked object is displayed or not | true |
onDisplayFkElementListDetails($element,$member,$className,$object) | To modify for displaying each element in a list component of foreign objects | |
getFkHeaderElementDetails($member, $className, $object) | Returns the header for a single foreign object (issue from ManyToOne) | HtmlHeader |
getFkElementDetails($member, $className, $object) | Returns a component for displaying a single foreign object (manyToOne relation) | HtmlLabel |
getFkHeaderListDetails($member, $className, $list) | Returns the header for a list of foreign objects (oneToMany or ManyToMany) | HtmlHeader |
getFkListDetails($member, $className, $list) | Returns a list component for displaying a collection of foreign objects (many) | HtmlList |
edit and newModel routes | ||
getForm($identifier, $instance) | Returns the form for adding or modifying an object | HtmlForm |
formHasMessage() | Determines if the form has a message title | true |
getFormModalTitle($instance) | Returns the form modal title | instance class |
onFormModalButtons($btOkay, $btCancel) | Hook for updating modal buttons | |
getFormTitle($form,$instance) | Returns an associative array defining form message title with keys “icon”,”message”,”subMessage” | HtmlForm |
setFormFieldsComponent(DataForm $form,$fieldTypes) | Sets the components for each field | |
onGenerateFormField($field) | For doing something when $field is generated in form | |
isModal($objects, $model) | Condition to determine if the edit or add form is modal for $model objects | count($objects)>5 |
getFormCaptions($captions, $className, $instance) | Returns the captions for form fields | all member names |
display route | ||
getModelDataElement($instance,$model,$modal) | Returns a DataElement object for displaying the instance | DataElement |
getElementCaptions($captions, $className, $instance) | Returns the captions for DataElement fields | all member names |
delete route | ||
onConfirmButtons(HtmlButton $confirmBtn,HtmlButton $cancelBtn) | To override for modifying delete confirmation buttons |
CRUDDatas methods to override¶
Method | Signification | Default return |
---|---|---|
index route | ||
_getInstancesFilter($model) | Adds a condition for filtering the instances displayed in dataTable | 1=1 |
getFieldNames($model) | Returns the fields to display in the index action for $model | all member names |
getSearchFieldNames($model) | Returns the fields to use in search queries | all member names |
edit and newModel routes | ||
getFormFieldNames($model,$instance) | Returns the fields to update in the edit and newModel actions for $model | all member names |
getManyToOneDatas($fkClass,$instance,$member) | Returns a list (filtered) of $fkClass objects to display in an html list | all $fkClass instances |
getOneToManyDatas($fkClass,$instance,$member) | Returns a list (filtered) of $fkClass objects to display in an html list | all $fkClass instances |
getManyToManyDatas($fkClass,$instance,$member) | Returns a list (filtered) of $fkClass objects to display in an html list | all $fkClass instances |
display route | ||
getElementFieldNames($model) | Returns the fields to display in the display action for $model | all member names |
CRUDEvents methods to override¶
Method | Signification | Default return |
---|---|---|
index route | ||
onConfDeleteMessage(CRUDMessage $message,$instance) | Returns the confirmation message displayed before deleting an instance | CRUDMessage |
onSuccessDeleteMessage(CRUDMessage $message,$instance) | RReturns the message displayed after a deletion | CRUDMessage |
onErrorDeleteMessage(CRUDMessage $message,$instance) | Returns the message displayed when an error occurred when deleting | CRUDMessage |
edit and newModel routes | ||
onSuccessUpdateMessage(CRUDMessage $message) | Returns the message displayed when an instance is added or inserted | CRUDMessage |
onErrorUpdateMessage(CRUDMessage $message) | Returns the message displayed when an error occurred when updating or inserting | CRUDMessage |
onNewInstance(object $instance) | Triggered after the creation of a new instance | |
onBeforeUpdate(object $instance, bool $isNew) | Triggered before the instance update | |
all routes | ||
onNotFoundMessage(CRUDMessage $message,$ids) | Returns the message displayed when an instance does not exists | |
onDisplayElements($dataTable,$objects,$refresh) | Triggered after displaying objects in dataTable |
CRUDFiles methods to override¶
Method | Signification | Default return | |
---|---|---|---|
template files | |||
getViewBaseTemplate() | Returns the base template for all Crud actions if getBaseTemplate return a base template filename | @framework/crud/baseTemplate.html | |
getViewIndex() | Returns the template for the index route | @framework/crud/index.html | |
getViewForm() | Returns the template for the edit and newInstance routes | @framework/crud/form.html | |
getViewDisplay() | Returns the template for the display route | @framework/crud/display.html | |
Urls | |||
getRouteRefresh() | Returns the route for refreshing the index route | /refresh_ | |
getRouteDetails() | Returns the route for the detail route, when the user click on a dataTable row | /showDetail | |
getRouteDelete() | Returns the route for deleting an instance | /delete | |
getRouteEdit() | Returns the route for editing an instance | /edit | |
getRouteDisplay() | Returns the route for displaying an instance | /display | |
getRouteRefreshTable() | Returns the route for refreshing the dataTable | /refreshTable | |
getDetailClickURL($model) | Returns the route associated with a foreign key instance in list | “” |
MultiResourceCrudController¶
Note
The MultiResourceCRUDController displays an index allowing to navigate between the CRUDs of the models.
Creation¶
In the admin interface (web-tools), activate the Controllers part, and choose create Index Crud controller:

- Then fill in the form:
- Enter the controller name
- The route path (which must contain the variable part {resource})
- Then click on the validate button

Description of the features¶
The generated controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <?php
namespace controllers;
use Ubiquity\attributes\items\router\Route;
#[Route(path: "/{resource}/crud",inherited: true,automated: true)]
class CrudIndex extends \Ubiquity\controllers\crud\MultiResourceCRUDController{
#[Route(name: "crud.index",priority: -1)]
public function index() {
parent::index();
}
#[Route(path: "#//home/crud",name: "crud.home",priority: 100)]
public function home(){
parent::home();
}
protected function getIndexType():array {
return ['four link cards','card'];
}
public function _getBaseRoute():string {
return "/".$this->resource."/crud";
}
}
|
Test the created controller at /home/crud url:

Customization¶
Create again a MultiResourceCrudController from the admin interface:

It is now possible to customize the module using overriding like the ResourceCRUDControllers.
Specific classes to override¶
MultiResourceCRUDController methods to override¶
Method | Signification | Default return |
---|---|---|
routes | ||
home () | Home page : list all models | |
All routes from CRUDController |
||
Events | ||
onRenderView(array &$data) | On before home page rendering | |
Configuration | ||
hasNavigation() | Returns True for displaying the navigation dropdown menu | True |
getIndexModels() | Returns the list of available models to display | models from default db |
getIndexModelsDetails() | Returns an associative array (title, icon url) for each model | [] |
getIndexDefaultIcon(string $resource) | Returns the icon for a model | A random animal |
getIndexDefaultTitle(string $resource) | Returns the title for a model | The resource name |
getIndexDefaultDesc(string $modelClass) | Returns the description for a model | The complete classname |
getIndexDefaultUrl(string $resource) | Returns the url associated to a model | The route path |
getIndexDefaultMeta(string $modelClass) | Returns the meta for a model | |
getIndexType() | Defines the index component css classes | cards |
getModelName() | Returns the complete model name for $this->resource | From default model NS |
CRUDFiles methods to override¶
Method | Signification | Default return | |
---|---|---|---|
template files | |||
getViewHome() | Returns the base template for the home view | @framework/crud/home.html | |
getViewItemHome() | Returns the template for an item in home route | @framework/crud/itemHome.html | |
getViewNav() | Returns the template for displaying models in a dropdown | @framework/crud/nav.html |
Note
All other methods of the CRUDController
, CRUDFiles
, CRUDEvents
and CRUDDatas
classes can be overridden as for the ResourceCRUDController
.
Auth Controllers¶
- The Auth controllers allow you to perform basic authentification with:
- login with an account
- account creation
- logout
- controllers with required authentication
Creation¶
In the admin interface (web-tools), activate the Controllers part, and choose create Auth controller:

- Then fill in the form:
- Enter the controller name (BaseAuthController in this case)

The generated controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | /**
* Auth Controller BaseAuthController
**/
class BaseAuthController extends \Ubiquity\controllers\auth\AuthController{
protected function onConnect($connected) {
$urlParts=$this->getOriginalURL();
USession::set($this->_getUserSessionKey(), $connected);
if(isset($urlParts)){
Startup::forward(implode("/",$urlParts));
}else{
//TODO
//Forwarding to the default controller/action
}
}
protected function _connect() {
if(URequest::isPost()){
$email=URequest::post($this->_getLoginInputName());
$password=URequest::post($this->_getPasswordInputName());
//TODO
//Loading from the database the user corresponding to the parameters
//Checking user creditentials
//Returning the user
}
return;
}
/**
* {@inheritDoc}
* @see \Ubiquity\controllers\auth\AuthController::isValidUser()
*/
public function _isValidUser($action=null): bool {
return USession::exists($this->_getUserSessionKey());
}
public function _getBaseRoute(): string {
return 'BaseAuthController';
}
}
|
Implementation of the authentification¶
Example of implementation with the administration interface : We will add an authentication check on the admin interface.
Authentication is based on verification of the email/password pair of a model User:

BaseAuthController modification¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | /**
* Auth Controller BaseAuthController
**/
class BaseAuthController extends \Ubiquity\controllers\auth\AuthController{
protected function onConnect($connected) {
$urlParts=$this->getOriginalURL();
USession::set($this->_getUserSessionKey(), $connected);
if(isset($urlParts)){
Startup::forward(implode("/",$urlParts));
}else{
Startup::forward("admin");
}
}
protected function _connect() {
if(URequest::isPost()){
$email=URequest::post($this->_getLoginInputName());
$password=URequest::post($this->_getPasswordInputName());
return DAO::uGetOne(User::class, "email=? and password= ?",false,[$email,$password]);
}
return;
}
/**
* {@inheritDoc}
* @see \Ubiquity\controllers\auth\AuthController::isValidUser()
*/
public function _isValidUser($action=null): bool {
return USession::exists($this->_getUserSessionKey());
}
public function _getBaseRoute(): string {
return 'BaseAuthController';
}
/**
* {@inheritDoc}
* @see \Ubiquity\controllers\auth\AuthController::_getLoginInputName()
*/
public function _getLoginInputName(): string {
return "email";
}
}
|
Admin controller modification¶
Modify the Admin Controller to use BaseAuthController:
1 2 3 4 5 6 | class Admin extends UbiquityMyAdminBaseController{
use WithAuthTrait;
protected function getAuthController(): AuthController {
return $this->_auth ??= new BaseAuthController($this);
}
}
|
Test the administration interface at /admin:

After clicking on login:

If the authentication data entered is invalid:

If the authentication data entered is valid:

Attaching the zone info-user¶
Modify the BaseAuthController controller:
1 2 3 4 5 6 7 8 9 | /**
* Auth Controller BaseAuthController
**/
class BaseAuthController extends \Ubiquity\controllers\auth\AuthController{
...
public function _displayInfoAsString(): bool {
return true;
}
}
|
The _userInfo area is now present on every page of the administration:

It can be displayed in any twig template:
{{ _userInfo | raw }}
Description of the features¶
Customizing templates¶
index.html template¶
The index.html template manages the connection:

Example with the _userInfo area:
Create a new AuthController named PersoAuthController:

Edit the template app/views/PersoAuthController/info.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | {% extends "@framework/auth/info.html" %}
{% block _before %}
<div class="ui tertiary inverted red segment">
{% endblock %}
{% block _userInfo %}
{{ parent() }}
{% endblock %}
{% block _logoutButton %}
{{ parent() }}
{% endblock %}
{% block _logoutCaption %}
{{ parent() }}
{% endblock %}
{% block _loginButton %}
{{ parent() }}
{% endblock %}
{% block _loginCaption %}
{{ parent() }}
{% endblock %}
{% block _after %}
</div>
{% endblock %}
|
Change the AuthController Admin controller:
1 2 3 4 5 6 | class Admin extends UbiquityMyAdminBaseController{
use WithAuthTrait;
protected function getAuthController(): AuthController {
return $this->_auth ??= new PersoAuthController($this);
}
}
|

Customizing messages¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class PersoAuthController extends \controllers\BaseAuth{
...
/**
* {@inheritDoc}
* @see \Ubiquity\controllers\auth\AuthController::badLoginMessage()
*/
protected function badLoginMessage(\Ubiquity\utils\flash\FlashMessage $fMessage) {
$fMessage->setTitle("Erreur d'authentification");
$fMessage->setContent("Login ou mot de passe incorrects !");
$this->_setLoginCaption("Essayer à nouveau");
}
...
}
|
Self-check connection¶
1 2 3 4 5 6 7 8 9 10 11 | class PersoAuthController extends \controllers\BaseAuth{
...
/**
* {@inheritDoc}
* @see \Ubiquity\controllers\auth\AuthController::_checkConnectionTimeout()
*/
public function _checkConnectionTimeout() {
return 10000;
}
...
}
|
Limitation of connection attempts¶
1 2 3 4 5 6 7 8 9 10 11 | class PersoAuthController extends \controllers\BaseAuth{
...
/**
* {@inheritDoc}
* @see \Ubiquity\controllers\auth\AuthController::attemptsNumber()
*/
protected function attemptsNumber(): int {
return 3;
}
...
}
|
Account recovery¶
account recovery is used to reset the account password.
A password reset email is sent, to an email address corresponding to an active account.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class PersoAuthController extends \controllers\BaseAuth{
...
protected function hasAccountRecovery():bool{
return true;
}
protected function _sendEmailAccountRecovery(string $email,string $validationURL,string $expire):bool {
MailerManager::start();
$mail=new AuthAccountRecoveryMail();
$mail->to($connected->getEmail());
$mail->setUrl($validationURL);
$mail->setExpire($expire);
return MailerManager::send($mail);
}
protected function passwordResetAction(string $email,string $newPasswordHash):bool {
//To implement for modifying the user password
}
protected function isValidEmailForRecovery(string $email):bool {
//To implement: return true if a valid account match with this email
}
}
|

Note
By default, the link can only be used on the same machine, within a predetermined period of time (which can be modified by overriding the accountRecoveryDuration
method).
Activation of MFA/2FA¶
Multi-factor authentication can be enabled conditionally, based on the pre-logged-in user’s information.
Note
Phase 2 of the authentication is done in the example below by sending a random code by email.
The AuthMailerClass class is available in the Ubiquity-mailer
package.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class PersoAuthController extends \controllers\BaseAuth{
...
/**
* {@inheritDoc}
* @see \Ubiquity\controllers\auth\AuthController::has2FA()
*/
protected function has2FA($accountValue=null):bool{
return true;
}
protected function _send2FACode(string $code, $connected):void {
MailerManager::start();
$mail=new AuthMailerClass();
$mail->to($connected->getEmail());
$mail->setCode($code);
MailerManager::send($mail);
}
...
}
|

Note
It is possible to customize the creation of the generated code, as well as the prefix used.
The sample below is implemented with robthree/twofactorauth
library.
protected function generate2FACode():string{
$tfa=new TwoFactorAuth();
return $tfa->createSecret();
}
protected function towFACodePrefix():string{
return 'U-';
}
Account creation¶
The activation of the account creation is also optional:

1 2 3 4 5 6 7 | class PersoAuthController extends \controllers\BaseAuth{
...
protected function hasAccountCreation():bool{
return true;
}
...
}
|

In this case, the _create method must be overridden in order to create the account:
protected function _create(string $login, string $password): ?bool {
if(!DAO::exists(User::class,'login= ?',[$login])){
$user=new User();
$user->setLogin($login);
$user->setPassword($password);
URequest::setValuesToObject($user);//for the others params in the POST.
return DAO::insert($user);
}
return false;
}
You can check the validity/availability of the login before validating the account creation form:
protected function newAccountCreationRule(string $accountName): ?bool {
return !DAO::exists(User::class,'login= ?',[$accountName]);
}

A confirmation action (email verification) may be requested from the user:
protected function hasEmailValidation(): bool {
return true;
}
protected function _sendEmailValidation(string $email,string $validationURL,string $expire):void {
MailerManager::start();
$mail=new AuthEmailValidationMail();
$mail->to($connected->getEmail());
$mail->setUrl($validationURL);
$mail->setExpire($expire);
MailerManager::send($mail);
}
Note
It is possible to customize these parts by overriding the associated methods, or by modifying the interfaces in the concerned templates.
Database¶
The DAO class is responsible for loading and persistence operations on models :
Connecting to the database¶
Check that the database connection parameters are correctly entered in the configuration file:
Ubiquity config -f=database

Transparent connection¶
Since Ubiquity 2.3.0, The connection to the database is done automatically the first time you request it:
use Ubiquity\orm\DAO;
$firstUser=DAO::getById(User::class,1);//Automatically start the database
This is the case for all methods in the DAO class used to perform CRUD operations.
Explicit connection¶
In some cases, however, it may be useful to make an explicit connection to the database, especially to check the connection.
use Ubiquity\orm\DAO;
use Ubiquity\controllers\Startup;
...
try{
$config=\Ubiquity\controllers\Startup::getConfig();
DAO::startDatabase($config);
$users=DAO::getAll(User::class,'');
}catch(Exception $e){
echo $e->getMessage();
}
Multiple connections¶
Adding a new connection¶
Ubiquity allows you to manage several connections to databases.
With Webtools¶
In the Models part, choose Add new connection button:

Define the connection configuration parameters:

Generate models for the new connection:
The generated models include the @database
annotation or the Database
attribute mentioning their link to the connection.
<?php
namespace models\tests;
use Ubiquity\attributes\items\Database;
use Ubiquity\attributes\items\Table;
#[Database('tests')]
#[Table('groupe')]
class Groupe{
...
}
<?php
namespace models\tests;
/**
* @database('tests')
* @table('groupe')
*/
class Groupe{
...
}
Models are generated in a sub-folder of models
.
With several connections, do not forget to add the following line to the services.php
file:
\Ubiquity\orm\DAO::start();
The start
method performs the match between each model and its associated connection.
ORM¶
Note
if you want to automatically generate the models, consult the generating models part.
A model class is just a plain old php object without inheritance.
Models are located by default in the app\models folder.
Object Relational Mapping (ORM) relies on member annotations or attributes (since PHP8) in the model class.
Models definition¶
A basic model¶
- A model must define its primary key using the @id annotation on the members concerned
- Serialized members must have getters and setters
- Without any other annotation, a class corresponds to a table with the same name in the database, each member corresponds to a field of this table
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | namespace models;
use Ubiquity\attributes\items\Id;
class User{
#[Id]
private $id;
private $firstname;
public function getFirstname(){
return $this->firstname;
}
public function setFirstname($firstname){
$this->firstname=$firstname;
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | namespace models;
class User{
/**
* @id
*/
private $id;
private $firstname;
public function getFirstname(){
return $this->firstname;
}
public function setFirstname($firstname){
$this->firstname=$firstname;
}
}
|
Mapping¶
Table->Class¶
If the name of the table is different from the name of the class, the annotation @table allows to specify the name of the table.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | namespace models;
use Ubiquity\attributes\items\Table;
use Ubiquity\attributes\items\Id;
#[Table('user')]
class User{
#[Id]
private $id;
private $firstname;
public function getFirstname(){
return $this->firstname;
}
public function setFirstname($firstname){
$this->firstname=$firstname;
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | namespace models;
/**
* @table("name"=>"user")
*/
class User{
/**
* @id
*/
private $id;
private $firstname;
public function getFirstname(){
return $this->firstname;
}
public function setFirstname($firstname){
$this->firstname=$firstname;
}
}
|
Field->Member¶
If the name of a field is different from the name of a member in the class, the annotation @column allows to specify a different field name.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | namespace models;
use Ubiquity\attributes\items\Table;
use Ubiquity\attributes\items\Id;
use Ubiquity\attributes\items\Column;
#[Table('user')
class User{
#[Id]
private $id;
#[Column('column_name')]
private $firstname;
public function getFirstname(){
return $this->firstname;
}
public function setFirstname($firstname){
$this->firstname=$firstname;
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | namespace models;
/**
* @table("user")
*/
class User{
/**
* @id
*/
private $id;
/**
* column("user_name")
*/
private $firstname;
public function getFirstname(){
return $this->firstname;
}
public function setFirstname($firstname){
$this->firstname=$firstname;
}
}
|
Associations¶
Note
Naming convention
Foreign key field names consist of the primary key name of the referenced table followed by the name of the referenced table whose first letter is capitalized.
Example
idUser
for the table user
whose primary key is id
ManyToOne¶
A user belongs to an organization:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | namespace models;
use Ubiquity\attributes\items\ManyToOne;
use Ubiquity\attributes\items\Id;
use Ubiquity\attributes\items\JoinColumn;
class User{
#[Id]
private $id;
private $firstname;
#[ManyToOne]
#[JoinColumn(className: \models\Organization::class, name: 'idOrganization', nullable: false)]
private $organization;
public function getOrganization(){
return $this->organization;
}
public function setOrganization($organization){
$this->organization=$organization;
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | namespace models;
class User{
/**
* @id
*/
private $id;
private $firstname;
/**
* @manyToOne
* @joinColumn("className"=>"models\\Organization","name"=>"idOrganization","nullable"=>false)
*/
private $organization;
public function getOrganization(){
return $this->organization;
}
public function setOrganization($organization){
$this->organization=$organization;
}
}
|
The @joinColumn annotation or the JoinColumn attribute specifies that:
- The member $organization is an instance of modelsOrganization
- The table user has a foreign key idOrganization refering to organization primary key
- This foreign key is not null => a user will always have an organization
OneToMany¶
An organization has many users:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | namespace models;
use Ubiquity\attributes\items\OneToMany;
use Ubiquity\attributes\items\Id;
class Organization{
#[Id]
private $id;
private $name;
#[OneToMany(mappedBy: 'organization', className: \models\User::class)]
private $users;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | namespace models;
class Organization{
/**
* @id
*/
private $id;
private $name;
/**
* @oneToMany("mappedBy"=>"organization","className"=>"models\\User")
*/
private $users;
}
|
In this case, the association is bi-directional.
The @oneToMany annotation must just specify:
- The class of each user in users array : modelsUser
- the value of @mappedBy is the name of the association-mapping attribute on the owning side : $organization in User class
ManyToMany¶
- A user can belong to groups.
- A group consists of multiple users.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | namespace models;
use Ubiquity\attributes\items\ManyToMany;
use Ubiquity\attributes\items\Id;
use Ubiquity\attributes\items\JoinTable;
class User{
#[Id]
private $id;
private $firstname;
#[ManyToMany(targetEntity: \models\Group::class, inversedBy: 'users')]
#[JoinTable(name: 'groupusers')]
private $groups;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | namespace models;
class User{
/**
* @id
*/
private $id;
private $firstname;
/**
* @manyToMany("targetEntity"=>"models\\Group","inversedBy"=>"users")
* @joinTable("name"=>"groupusers")
*/
private $groups;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | namespace models;
use Ubiquity\attributes\items\ManyToMany;
use Ubiquity\attributes\items\Id;
use Ubiquity\attributes\items\JoinTable;
class Group{
#[Id]
private $id;
private $name;
#[ManyToMany(targetEntity: \models\User::class, inversedBy: 'groups')]
#[JoinTable(name: 'groupusers')]
private $users;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | namespace models;
class Group{
/**
* @id
*/
private $id;
private $name;
/**
* @manyToMany("targetEntity"=>"models\\User","inversedBy"=>"groups")
* @joinTable("name"=>"groupusers")
*/
private $users;
}
|
If the naming conventions are not respected for foreign keys,
it is possible to specify the related fields.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | namespace models;
use Ubiquity\attributes\items\ManyToMany;
use Ubiquity\attributes\items\Id;
use Ubiquity\attributes\items\JoinTable;
class Group{
#[Id]
private $id;
private $name;
#[ManyToMany(targetEntity: \models\User::class, inversedBy: 'groupes')]
#[JoinTable(name: 'groupeusers',
joinColumns: ['name'=>'id_groupe','referencedColumnName'=>'id'],
inverseJoinColumns: ['name'=>'id_user','referencedColumnName'=>'id'])]
private $users;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | namespace models;
class Group{
/**
* @id
*/
private $id;
private $name;
/**
* @manyToMany("targetEntity"=>"models\\User","inversedBy"=>"groupes")
* @joinTable("name"=>"groupeusers",
* "joinColumns"=>["name"=>"id_groupe","referencedColumnName"=>"id"],
* "inverseJoinColumns"=>["name"=>"id_user","referencedColumnName"=>"id"])
*/
private $users;
}
|
ORM Annotations¶
Annotations for classes¶
@annotation | role | properties | role |
---|---|---|---|
@database | Defines the associated database offset (defined in config file) | ||
@table | Defines the associated table name. |
Annotations for members¶
@annotation | role | properties | role |
---|---|---|---|
@id | Defines the primary key(s). | ||
@column | Specify the associated field characteristics. | name | Name of the associated field |
nullable | true if value can be null | ||
dbType | Type of the field in database | ||
@transient | Specify that the field is not persistent. |
Associations¶
@annotation (extends) | role | properties [optional] | role |
---|---|---|---|
@manyToOne | Defines a single-valued association to another entity class. | ||
@joinColumn (@column) | Indicates the foreign key in manyToOne asso. | className | Class of the member |
[referencedColumnName] | Name of the associated column | ||
@oneToMany | Defines a multi-valued association to another entity class. | className | Class of the objects in member |
[mappedBy] | Name of the association-mapping attribute on the owning side | ||
@manyToMany | Defines a many-valued association with many-to-many multiplicity | targetEntity | Class of the objects in member |
[inversedBy] | Name of the association-member on the inverse-side | ||
[mappedBy] | Name of the association-member on the owning side | ||
@joinTable | Defines the association table for many-to-many multiplicity | name | The name of the association table |
[joinColumns] | @column => name and referencedColumnName for this side | ||
[inverseJoinColumns] | @column => name and referencedColumnName for the other side |
DAO¶
The DAO class is responsible for loading and persistence operations on models :
Connecting to the database¶
Check that the database connection parameters are correctly entered in the configuration file:
Ubiquity config -f=database
Since 2.3.0 release
Database startup with DAO::startDatabase($config)
in services.php file is useless, no need to start the database, the connection is made automatically at the first request.
Use DAO::start()
in app/config/services.php file when using several databases (with multi db feature)
Loading data¶
Loading an instance¶
Loading an instance of the models\User class with id 5
use Ubiquity\orm\DAO;
use models\User;
$user=DAO::getById(User::class, 5);
Loading an instance using a condition:
use Ubiquity\orm\DAO;
use models\User;
DAO::getOne(User::class, 'name= ?',false,['DOE']);
BelongsTo loading¶
By default, members defined by a belongsTo relationship are automatically loaded
Each user belongs to only one category:
$user=DAO::getById(User::class,5);
echo $user->getCategory()->getName();
It is possible to prevent this default loading ; the third parameter allows the loading or not of belongsTo members:
$user=DAO::getOne(User::class,5, false);
echo $user->getCategory();// NULL
HasMany loading¶
Loading hasMany members must always be explicit ; the third parameter allows the explicit loading of members.
Each user has many groups:
$user=DAO::getOne(User::class,5,['groupes']);
foreach($user->getGroupes() as $groupe){
echo $groupe->getName().'<br>';
}
Composite primary key¶
Either the ProductDetail model corresponding to a product ordered on a command and whose primary key is composite:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | namespace models;
use Ubiquity\attributes\items\Id;
class ProductDetail{
#[Id]
private $idProduct;
#[Id]
private $idCommand;
...
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | namespace models;
class ProductDetail{
/**
* @id
*/
private $idProduct;
/**
* @id
*/
private $idCommand;
...
}
|
The second parameter $keyValues can be an array if the primary key is composite:
$productDetail=DAO::getOne(ProductDetail::class,[18,'BF327']);
echo 'Command:'.$productDetail->getCommande().'<br>';
echo 'Product:'.$productDetail->getProduct().'<br>';
Loading multiple objects¶
Loading instances of the User class:
$users=DAO::getAll(User::class);
foreach($users as $user){
echo $user->getName()."<br>";
}
Querying using conditions¶
Simple queries¶
The condition parameter is equivalent to the WHERE part of an SQL statement:
$users=DAO::getAll(User::class,'firstName like "bren%" and not suspended',false);
To avoid SQL injections and benefit from the preparation of statements, it is preferable to perform a parameterized query:
$users=DAO::getAll(User::class,'firstName like ? and suspended= ?',false,['bren%',false]);
UQueries¶
The use of U-queries allows to set conditions on associate members:
Selection of users whose organization has the domain lecnam.net:
$users=DAO::uGetAll(User::class,'organization.domain= ?',false,['lecnam.net']);
It is possible to view the generated request in the logs (if logging is enabled):

The result can be verified by selecting all users in this organization:
$organization=DAO::getOne(Organization::class,'domain= ?',['users'],['lecnam.net']);
$users=$organization->getUsers();
The corresponding logs:

Counting¶
Existence testing¶
if(DAO::exists(User::class,'lastname like ?',['SMITH'])){
//there's a Mr SMITH
}
Counting¶
To count the instances, what not to do, if users are not already loaded:
$users=DAO::getAll(User::class);
echo "there are ". \count($users) ." users";
What needs to be done:
$count=DAO::count(User::class);
echo "there are $count users";
With a condition:
$notSuspendedCount=DAO::count(User::class, 'suspended = ?', [false]);
with a condition on associated objects:
Number of users belonging to the OTAN named organization.
$count=DAO::uCount(User::class,'organization.name= ?',['OTAN']);
Modifying data¶
Adding an instance¶
Adding an organization:
$orga=new Organization();
$orga->setName('Foo');
$orga->setDomain('foo.net');
if(DAO::save($orga)){
echo $orga.' added in database';
}
Adding an instance of User, in an organization:
$orga=DAO::getById(Organization::class, 1);
$user=new User();
$user->setFirstname('DOE');
$user->setLastname('John');
$user->setEmail('doe@bar.net');
$user->setOrganization($orga);
if(DAO::save($user)){
echo $user.' added in database in '.$orga;
}
Updating an instance¶
First, the instance must be loaded:
$orga=DAO::getOne(Organization::class,'domain= ?',false,['foo.net']);
$orga->setAliases('foo.org');
if(DAO::save($orga)){
echo $orga.' updated in database';
}
Deleting an instance¶
If the instance is loaded from database:
$orga=DAO::getById(Organization::class,5,false);
if(DAO::remove($orga)){
echo $orga.' deleted from database';
}
If the instance is not loaded, it is more appropriate to use the delete method:
if(DAO::delete(Organization::class,5)){
echo 'Organization deleted from database';
}
Deleting multiple instances¶
Deletion of multiple instances without prior loading:
if($res=DAO::deleteAll(models\User::class, 'id in (?,?,?)',[1,2,3])){
echo "$res elements deleted";
}
Bulk queries¶
Bulk queries allow several operations (insertion, modification or deletion) to be performed in a single query, which contributes to improved performance.
Bulk inserts¶
Insertions example:
$u = new User();
$u->setName('Martin1');
DAO::toInsert($u);
$u = new User();
$u->setName('Martin2');
DAO::toInsert($u);
//Perform inserts
DAO::flushInserts();
Bulk updates¶
Updates example:
$users = DAO::getAll(User::class, 'name like ?', false, [
'Martin%'
]);
foreach ($users as $user) {
$user->setName(\strtoupper($user->getName()));
DAO::toUpdate($user);
}
DAO::flushUpdates();
Bulk deletes¶
Deletions example
$users = DAO::getAll(User::class, 'name like ?', false, [
'BULK%'
]);
DAO::toDeletes($users);
DAO::flushDeletes();
The DAO::flush() method can be called if insertions, updates or deletions are pending.
Transactions¶
Explicit transactions¶
All DAO operations can be inserted into a transaction, so that a series of changes can be atomized:
try{
DAO::beginTransaction();
$orga=new Organization();
$orga->setName('Foo');
DAO::save($orga);
$user=new User();
$user->setFirstname('DOE');
$user->setOrganization($orga);
DAO::save($user);
DAO::commit();
}catch (\Exception $e){
DAO::rollBack();
}
In case of multiple databases defined in the configuration, transaction-related methods can take the database offset defined in parameter.
DAO::beginTransaction('db-messagerie');
//some DAO operations on messagerie models
DAO::commit('db-messagerie');
Implicit transactions¶
Some DAO methods implicitly use transactions to group together insert, update or delete operations.
$users=DAO::getAll(User::class);
foreach ($users as $user){
$user->setSuspended(true);
DAO::toUpdate($user);
}
DAO::updateGroups();//Perform updates in a transaction
SDAO class¶
The SDAO class accelerates CRUD operations for the business classes without relationships.
Models must in this case declare public members only, and not respect the usual encapsulation.
1 2 3 4 5 6 7 8 9 10 11 | namespace models;
class Product{
/**
* @id
*/
public $id;
public $name;
...
}
|
The SDAO class inherits from DAO and has the same methods for performing CRUD operations.
use Ubiquity\orm\DAO;
$product=DAO::getById(Product::class, 5);
Prepared DAO queries¶
Preparing certain requests can improve performance with Swoole, Workerman or Roadrunner servers.
This preparation initializes the objects that will then be used to execute the query.
This initialization is done at server startup, or at the startup of each worker, if such an event exists.
Swoole sample¶
Request¶
Note
For all Http features, Ubiquity uses technical classes containing static methods. This is a design choice to avoid dependency injection that would degrade performances.
The URequest class provides additional functionality to more easily manipulate native $_POST and $_GET php arrays.
Retrieving data¶
From the get method¶
The get method returns the null value if the key name does not exist in the get variables.
use Ubiquity\utils\http\URequest;
$name=URequest::get("name");
The get method can be called with the optional second parameter returning a value if the key does not exist in the get variables.
$name=URequest::get("name",1);
From the post method¶
The post method returns the null value if the key name does not exist in the post variables.
use Ubiquity\utils\http\URequest;
$name=URequest::post("name");
The post method can be called with the optional second parameter returning a value if the key does not exist in the post variables.
$name=URequest::post("name",1);
The getPost method applies a callback to the elements of the $_POST array and return them (default callback : htmlEntities) :
$protectedValues=URequest::getPost();
Retrieving and assigning multiple data¶
It is common to assign the values of an associative array to the members of an object.
This is the case for example when validating an object modification form.
The setValuesToObject method performs this operation :
Consider a User class:
class User {
private $id;
private $firstname;
private $lastname;
public function setId($id){
$this->id=$id;
}
public function getId(){
return $this->id;
}
public function setFirstname($firstname){
$this->firstname=$firstname;
}
public function getFirstname(){
return $this->firstname;
}
public function setLastname($lastname){
$this->lastname=$lastname;
}
public function getLastname(){
return $this->lastname;
}
}
Consider a form to modify a user:
<form method="post" action="Users/update">
<input type="hidden" name="id" value="{{user.id}}">
<label for="firstname">Firstname:</label>
<input type="text" id="firstname" name="firstname" value="{{user.firstname}}">
<label for="lastname">Lastname:</label>
<input type="text" id="lastname" name="lastname" value="{{user.lastname}}">
<input type="submit" value="validate modifications">
</form>
The update action of the Users controller must update the user instance from POST values.
Using the setPostValuesToObject method avoids the assignment of variables posted one by one to the members of the object.
It is also possible to use setGetValuesToObject for the get method, or setValuesToObject to assign the values of any associative array to an object.
1 2 3 4 5 6 7 8 9 10 11 12 13 | namespace controllers;
use Ubiquity\orm\DAO;
use Uniquity\utils\http\URequest;
class Users extends BaseController{
...
public function update(){
$user=DAO::getOne("models\User",URequest::post("id"));
URequest::setPostValuesToObject($user);
DAO::update($user);
}
}
|
Note
SetValuesToObject methods use setters to modify the members of an object. The class concerned must therefore implement setters for all modifiable members.
Testing the request¶
isPost¶
The isPost method returns true if the request was submitted via the POST method:
In the case below, the initialize method only loads the vHeader.html view if the request is not an Ajax request.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | namespace controllers;
use Ubiquity\orm\DAO;
use Ubiquity\utils\http\URequest;
class Users extends BaseController{
...
public function update(){
if(URequest::isPost()){
$user=DAO::getOne("models\User",URequest::post("id"));
URequest::setPostValuesToObject($user);
DAO::update($user);
}
}
}
|
isAjax¶
The isAjax method returns true if the query is an Ajax query:
1 2 3 4 5 6 7 | ...
public function initialize(){
if(!URequest::isAjax()){
$this->loadView("main/vHeader.html");
}
}
...
|
isCrossSite¶
The isCrossSite method verifies that the query is not cross-site.
Response¶
Note
For all Http features, Ubiquity uses technical classes containing static methods. This is a design choice to avoid dependency injection that would degrade performances.
The UResponse class handles only the headers, not the response body, which is conventionally provided by the content displayed by the calls used to output data (echo, print …).
The UResponse class provides additional functionality to more easily manipulate response headers.
Adding or modifying headers¶
use Ubiquity\utils\http\UResponse;
$animal='camel';
UResponse::header('Animal',$animal);
Forcing multiple header of the same type:
UResponse::header('Animal','monkey',false);
Forces the HTTP response code to the specified value:
UResponse::header('Messages',$message,false,500);
Defining specific headers¶
content-type¶
Setting the response content-type to application/json:
UResponse::asJSON();
Setting the response content-type to text/html:
UResponse::asHtml();
Setting the response content-type to plain/text:
UResponse::asText();
Setting the response content-type to application/xml:
UResponse::asXml();
Defining specific encoding (default value is always utf-8):
UResponse::asHtml('iso-8859-1');
Accept¶
Define which content types, expressed as MIME types, the client is able to understand.
See Accept default values
UResponse::setAccept('text/html');
CORS responses headers¶
Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell a browser to let your web application running at one origin (domain) have permission to access selected resources from a server at a different origin.
Access-Control-Allow-Origin¶
Setting allowed origin:
UResponse::setAccessControlOrigin('http://myDomain/');
Access-Control-Allow-methods¶
Defining allowed methods:
UResponse::setAccessControlMethods('GET, POST, PUT, DELETE, PATCH, OPTIONS');
Access-Control-Allow-headers¶
Defining allowed headers:
UResponse::setAccessControlHeaders('X-Requested-With, Content-Type, Accept, Origin, Authorization');
Global CORS activation¶
enabling CORS for a domain with default values:
- allowed methods:
GET, POST, PUT, DELETE, PATCH, OPTIONS
- allowed headers:
X-Requested-With, Content-Type, Accept, Origin, Authorization
UResponse::enableCors('http://myDomain/');
Testing response headers¶
Checking if headers have been sent:
if(!UResponse::isSent()){
//do something if headers are not send
}
Testing if response content-type is application/json:
Important
This method only works if you used the UResponse class to set the headers.
if(UResponse::isJSON()){
//do something if response is a JSON response
}
Session¶
Note
For all Http features, Ubiquity uses technical classes containing static methods. This is a design choice to avoid dependency injection that would degrade performances.
The USession class provides additional functionality to more easily manipulate native $_SESSION php array.
Starting the session¶
The Http session is started automatically if the sessionName key is populated in the app/config.php configuration file:
<?php
return array(
...
"sessionName"=>"key-for-app",
...
);
If the sessionName key is not populated, it is necessary to start the session explicitly to use it:
use Ubiquity\utils\http\USession;
...
USession::start("key-for-app");
Note
The name parameter is optional but recommended to avoid conflicting variables.
Creating or editing a session variable¶
use Ubiquity\utils\http\USession;
USession::set("name","SMITH");
USession::set("activeUser",$user);
Retrieving data¶
The get method returns the null value if the key name does not exist in the session variables.
use Ubiquity\utils\http\USession;
$name=USession::get("name");
The get method can be called with the optional second parameter returning a value if the key does not exist in the session variables.
$name=USession::get("page",1);
Note
The session method is an alias of the get method.
The getAll method returns all session vars:
$sessionVars=USession::getAll();
Testing¶
The exists method tests the existence of a variable in session.
if(USession::exists("name")){
//do something when name key exists in session
}
The isStarted method checks the session start
if(USession::isStarted()){
//do something if the session is started
}
Explicit closing of the session¶
The terminate method closes the session correctly and deletes all session variables created:
USession::terminate();
Cookie¶
Note
For all Http features, Ubiquity uses technical classes containing static methods. This is a design choice to avoid dependency injection that would degrade performances.
The UCookie class provides additional functionality to more easily manipulate native $_COOKIES php array.
Cookie creation or modification¶
use Ubiquity\utils\http\UCookie;
$cookie_name = 'user';
$cookie_value = 'John Doe';
UCookie::set($cookie_name, $cookie_value);//duration : 1 day
Creating a cookie that lasts 5 days:
UCookie::set($cookie_name, $cookie_value,5*60*60*24);
On a particular domain:
UCookie::set($cookie_name, $cookie_value,5*60*60*24,'/admin');
Sending a cookie without urlencoding the cookie value:
UCookie::setRaw($cookie_name, $cookie_value);
Testing the cookie creation:
if(UCookie::setRaw($cookie_name, $cookie_value)){
//cookie created
}
Retrieving a Cookie¶
$userName=UCookie::get('user');
Testing the existence¶
if(UCookie::exists('user')){
//do something if cookie user exists
}
Using a default value¶
If the page cookie does not exist, the default value of 1 is returned:
$page=UCookie::get('page',1);
Deleting all cookies¶
Deleting all cookies from the entire domain:
UCookie::deleteAll();
Deleting all cookies from the domain admin:
UCookie::deleteAll('/admin');
Views¶
Ubiquity uses Twig as the default template engine (see Twig documentation).
The views are located in the app/views folder. They must have the .html extension for being interpreted by Twig.
Ubiquity can also be used with a PHP view system, to get better performance, or simply to allow the use of php in the views.
Loading¶
Views are loaded from controllers:
1 2 3 4 5 6 7 8 9 | namespace controllers;
class Users extends BaseController{
...
public function index(){
$this->loadView("index.html");
}
}
}
|
Default view loading¶
If you use the default view naming method :
The default view associated to an action in a controller is located in views/controller-name/action-name
folder:
views
│
└ Users
└ info.html
1 2 3 4 5 6 7 8 9 | namespace controllers;
class Users extends BaseController{
...
public function info(){
$this->loadDefaultView();
}
}
}
|
Loading and passing variables¶
Variables are passed to the view with an associative array. Each key creates a variable of the same name in the view.
1 2 3 4 5 6 7 8 9 | namespace controllers;
class Users extends BaseController{
...
public function display($message,$type){
$this->loadView("users/display.html",["message"=>$message,"type"=>$type]);
}
}
}
|
In this case, it is usefull to call Compact for creating an array containing variables and their values :
1 2 3 4 5 6 7 8 9 | namespace controllers;
class Users extends BaseController{
...
public function display($message,$type){
$this->loadView("users/display.html",compact("message","type"));
}
}
}
|
Displaying in view¶
The view can then display the variables:
<h2>{{type}}</h2>
<div>{{message}}</div>
Variables may have attributes or elements you can access, too.
You can use a dot (.) to access attributes of a variable (methods or properties of a PHP object, or items of a PHP array), or the so-called “subscript” syntax ([]):
{{ foo.bar }}
{{ foo['bar'] }}
Ubiquity extra functions¶
Global app
variable provides access to predefined Ubiquity Twig features:
app
is an instance ofFramework
and provides access to public methods of this class.
Get framework installed version:
{{ app.version() }}
Return the active controller and action names:
{{ app.getController() }}
{{ app.getAction() }}
Return global wrapper classes :
For request:
{{ app.getRequest().isAjax() }}
For session :
{{ app.getSession().get('homePage','index') }}
see Framework class in API for more.
PHP view loading¶
Disable if necessary Twig in the configuration file by deleting the templateEngine key.
Then create a controller that inherits from SimpleViewController
, or SimpleViewAsyncController
if you use Swoole or Workerman:
1 2 3 4 5 6 7 8 9 10 11 | namespace controllers;
use Ubiquity\controllers\SimpleViewController;
class Users extends SimpleViewController{
...
public function display($message,$type){
$this->loadView("users/display.php",compact("message","type"));
}
}
}
|
Note
In this case, the functions for loading assets and themes are not supported.
Assets¶
Assets correspond to javascript files, style sheets, fonts, images to include in your application.
They are located from the public/assets folder.
It is preferable to separate resources into sub-folders by type.
public/assets
├ css
│ ├ style.css
│ └ semantic.min.css
└ js
└ jquery.min.js
Integration of css or js files :
{{ css('css/style.css') }}
{{ css('css/semantic.min.css') }}
{{ js('js/jquery.min.js') }}
{{ css('https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css') }}
{{ js('https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.js') }}
CDN with extra parameters:
{{ css('https://cdn.jsdelivr.net/npm/foundation-sites@6.5.3/dist/css/foundation.min.css',{crossorigin: 'anonymous',integrity: 'sha256-/PFxCnsMh+...'}) }}
Themes¶
Note
The themes are totally useless if you only have one presentation to apply.
Ubiquity support themes wich can have it’s own assets and views according to theme template to be rendered by controller.
Each controller action can render a specific theme, or they can use the default theme configured at config.php file in templateEngineOptions => array("activeTheme" => "semantic")
.
Ubiquity is shipped with 3 default themes : Bootstrap, Foundation and Semantic-UI.
Installing a theme¶
With devtools, run :
Ubiquity install-theme bootstrap
The installed theme is one of bootstrap, foundation or semantic.
With webtools, you can do the same, provided that the devtools are installed and accessible (Ubiquity folder added in the system path) :

Creating a new theme¶
With devtools, run :
Ubiquity create-theme myTheme
Creating a new theme from Bootstrap, Semantic…
With devtools, run :
Ubiquity create-theme myBootstrap -x=bootstrap
With webtools :

Theme functioning and structure¶
Structure¶
Theme view folder
The views of a theme are located from the app/views/themes/theme-name folder
app/views
└ themes
├ bootstrap
│ └ main
│ ├ vHeader.html
│ └ vFooter.html
└ semantic
└ main
├ vHeader.html
└ vFooter.html
The controller base class is responsible for loading views to define the header and footer of each page :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <?php
namespace controllers;
use Ubiquity\controllers\Controller;
use Ubiquity\utils\http\URequest;
/**
* ControllerBase.
**/
abstract class ControllerBase extends Controller{
protected $headerView = "@activeTheme/main/vHeader.html";
protected $footerView = "@activeTheme/main/vFooter.html";
public function initialize() {
if (! URequest::isAjax ()) {
$this->loadView ( $this->headerView );
}
}
public function finalize() {
if (! URequest::isAjax ()) {
$this->loadView ( $this->footerView );
}
}
}
|
Theme assets folder
The assets of a theme are created inside public/assets/theme-name
folder.
The structure of the assets folder is often as follows :
public/assets/bootstrap
├ css
│ ├ style.css
│ └ all.min.css
├ scss
│ ├ myVariables.scss
│ └ app.scss
├ webfonts
│
└ img
Change of the active theme¶
Persistent change¶
activeTheme is defined in app/config/config.php
with templateEngineOptions => array("activeTheme" => "semantic")
The active theme can be changed with devtools :
Ubiquity config:set --templateEngineOptions.activeTheme=bootstrap
It can also be done from the home page, or with webtools :
From the home page :

From the webtools :

This change can also be made at runtime :
From a controller :
ThemeManager::saveActiveTheme('bootstrap');
Non-persistent local change¶
To set a specific theme for all actions within a controller, the simplest method is to override the controller’s initialize method :
1 2 3 4 5 6 7 8 9 10 11 | namespace controllers;
use \Ubiquity\themes\ThemesManager;
class Users extends BaseController{
public function initialize(){
parent::intialize();
ThemesManager::setActiveTheme('bootstrap');
}
}
|
Or if the change should only concern one action :
1 2 3 4 5 6 7 8 9 10 11 | namespace controllers;
use \Ubiquity\themes\ThemesManager;
class Users extends BaseController{
public function doStuff(){
ThemesManager::setActiveTheme('bootstrap');
...
}
}
|
Conditional theme change, regardless of the controller :
Example with a modification of the theme according to a variable passed in the URL
1 2 3 4 5 6 7 8 9 10 | use Ubiquity\themes\ThemesManager;
use Ubiquity\utils\http\URequest;
...
ThemesManager::onBeforeRender(function(){
if(URequest::get("th")=='bootstrap'){
ThemesManager::setActiveTheme("bootstrap");
}
});
|
Mobile device support¶
Add a mobile device detection tool.
Installing MobileDetect:
composer require mobiledetect/mobiledetectlib
It is generally easier to create different views per device.
Create a specific theme for the mobile part (by creating a folder views/themes/mobile
and putting the views specific to mobile devices in it).
It is important in this case to use the same file names for the mobile and non-mobile part.
It is also advisable in this case that all view loadings use the @activeTheme namespace:
$this->loadView("@activeTheme/index.html");
index.html must be available in this case in the folders views
and views/themes/mobile
.
View and assets loading¶
Views¶
For loading a view from the activeTheme folder, you can use the @activeTheme namespace :
1 2 3 4 5 6 7 8 9 | namespace controllers;
class Users extends BaseController{
public function action(){
$this->loadView('@activeTheme/action.html');
...
}
}
|
If the activeTheme is bootstrap, the loaded view is app/views/themes/bootstrap/action.html
.
DefaultView¶
If you follow the Ubiquity view naming model, the default view loaded for an action in a controller when a theme is active is :
app/views/themes/theme-name/controller-name/action-name.html
.
For example, if the activeTheme is bootstrap, the default view for the action display in the Users controller must be loacated in
app/views/themes/bootstrap/Users/display.html
.
1 2 3 4 5 6 7 8 9 | namespace controllers;
class Users extends BaseController{
public function display(){
$this->loadDefaultView();
...
}
}
|
Note
The devtools commands to create a controller or an action and their associated view use the @activeTheme folder if a theme is active.
Ubiquity controller Users -v
Ubiquity action Users.display -v
Assets loading¶
The mechanism is the same as for the views : @activeTheme
namespace refers to the public/assets/theme-name/
folder
{{ css('@activeTheme/css/style.css') }}
{{ js('@activeTheme/js/scripts.js') }}
{{ img('@activeTheme/img/image-name.png', {alt: 'Image Alt Name', class: 'css-class'}) }}
If the bootstrap theme is active,
the assets folder is public/assets/bootstrap/
.
Css compilation¶
For Bootstrap or foundation, install sass:
npm install -g sass
Then run from the project root folder:
For bootstrap:
ssass public/assets/bootstrap/scss/app.scss public/assets/bootstrap/css/style.css --load-path=vendor
For foundation:
ssass public/assets/foundation/scss/app.scss public/assets/foundation/css/style.css --load-path=vendor
jQuery Semantic-UI¶
By default, Ubiquity uses the phpMv-UI library for the client-rich part.
PhpMv-UI allows to create components based on Semantic-UI or Bootstrap and to generate jQuery scripts in PHP.
This library is used for the webtools administration interface.
Integration¶
By default, a $jquery variable is injected in controllers at runtime.
This operation is done using dependency injection, in app/config.php
:
...
"di"=>array(
"@exec"=>array(
"jquery"=>function ($controller){
return \Ajax\php\ubiquity\JsUtils::diSemantic($controller);
}
)
)
...
So there’s nothing to do,
but to facilitate its use and allow code completion in a controller, it is recommended to add the following code documentation:
/**
* Controller FooController
* @property \Ajax\php\ubiquity\JsUtils $jquery
**/
class FooController extends ControllerBase{
public function index(){}
}
jQuery¶
Href to ajax requests¶
Create a new Controller and its associated view, then define the folowing routes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | namespace controllers;
class FooController extends ControllerBase {
public function index() {
$this->loadview("FooController/index.html");
}
/**
*
*@get("a","name"=>"action.a")
*/
public function aAction() {
echo "a";
}
/**
*
*@get("b","name"=>"action.b")
*/
public function bAction() {
echo "b";
}
}
|
The associated view:
<a href="{{path('action.a')}}">Action a</a>
<a href="{{path('action.b')}}">Action b</a>
Initialize router cache:
Ubiquity init:cache -t=controllers
Test this page in your browser at http://127.0.0.1:8090/FooController
.
Transformation of requests into Ajax requests¶
The result of each ajax request should be displayed in an area of the page defined by its jQuery selector (.result span
)
namespace controllers;
/**
* @property \Ajax\php\ubiquity\JsUtils $jquery
*/
class FooController extends ControllerBase {
public function index() {
$this->jquery->getHref('a','.result span');
$this->jquery->renderView("FooController/index.html");
}
...
}
<a href="{{path('action.a')}}">Action a</a>
<a href="{{path('action.b')}}">Action b</a>
<div class='result'>
Selected action:
<span>No One</span>
</div>
{{ script_foot | raw }}
Note
The script_foot
variable contains the generated jquery script produced by the renderView method.
The raw filter marks the value as being “safe”, which means that in an environment with automatic escaping enabled this variable will not be escaped.
Let’s add a little css to make it more professional:
<div class="ui buttons">
<a class="ui button" href="{{path('action.a')}}">Action a</a>
<a class="ui button" href="{{path('action.b')}}">Action b</a>
</div>
<div class='ui segment result'>
Selected action:
<span class="ui label">No One</span>
</div>
{{ script_foot | raw }}
If we want to add a new link whose result should be displayed in another area, it is possible to specify it via the data-target attribute
The new action:
namespace controllers;
class FooController extends ControllerBase {
...
/**
*@get("c","name"=>"action.c")
*/
public function cAction() {
echo \rand(0, 1000);
}
}
The associated view:
<div class="ui buttons">
<a class="ui button" href="{{path('action.a')}}">Action a</a>
<a class="ui button" href="{{path('action.b')}}">Action b</a>
<a class="ui button" href="{{path('action.c')}}" data-target=".result p">Action c</a>
</div>
<div class='ui segment result'>
Selected action:
<span class="ui label">No One</span>
<p></p>
</div>
{{ script_foot | raw }}

Definition of the ajax request attributes:¶
In the folowing example, the parameters passed to the attributes variable of the getHref
method:
- remove the history of the navigation,
- make the ajax loader internal to the clicked button.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | namespace controllers;
/**
* @property \Ajax\php\ubiquity\JsUtils $jquery
*/
class FooController extends ControllerBase {
public function index() {
$this->jquery->getHref('a','.result span', [
'hasLoader' => 'internal',
'historize' => false
]);
$this->jquery->renderView("FooController/index.html");
}
...
}
|
Note
It is possible to use the postHref
method to use the POST http method.
Classical ajax requests¶
For this example, create the following database:
CREATE DATABASE `uguide` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE `uguide`;
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`firstname` varchar(30) NOT NULL,
`lastname` varchar(30) NOT NULL,
`password` varchar(30) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `user` (`id`, `firstname`, `lastname`) VALUES
(1, 'You', 'Evan'),
(2, 'Potencier', 'Fabien'),
(3, 'Otwell', 'Taylor');
ALTER TABLE `user` ADD PRIMARY KEY (`id`);
ALTER TABLE `user`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
Connect the application to the database, and generate the User class:
With devtools:
Ubiquity config:set --database.dbName=uguide
Ubiquity all-models
Create a new Controller UsersJqueryController
Ubiquity controller UsersJqueryController -v
Create the folowing actions in UsersJqueryController:

Index action¶
The index action must display a button to obtain the list of users, loaded via an ajax request:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | namespace controllers;
/**
* Controller UsersJqueryController
*
* @property \Ajax\php\ubiquity\JsUtils $jquery
* @route("users")
*/
class UsersJqueryController extends ControllerBase {
/**
*
* {@inheritdoc}
* @see \Ubiquity\controllers\Controller::index()
* @get
*/
public function index() {
$this->jquery->getOnClick('#users-bt', Router::path('display.users'), '#users', [
'hasLoader' => 'internal'
]);
$this->jquery->renderDefaultView();
}
}
|
The default view associated to index action:
<div class="ui container">
<div id="users-bt" class="ui button">
<i class="ui users icon"></i>
Display <b>users</b>
</div>
<p></p>
<div id="users">
</div>
</div>
{{ script_foot | raw }}
displayUsers action¶
All users are displayed, and a click on a user must display the user details via a posted ajax request:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | namespace controllers;
/**
* Controller UsersJqueryController
*
* @property \Ajax\php\ubiquity\JsUtils $jquery
* @route("users")
*/
class UsersJqueryController extends ControllerBase {
...
/**
*
* @get("all","name"=>"display.users","cache"=>true)
*/
public function displayUsers() {
$users = DAO::getAll(User::class);
$this->jquery->click('#close-bt', '$("#users").html("");');
$this->jquery->postOnClick('li[data-ajax]', Router::path('display.one.user', [
""
]), '{}', '#user-detail', [
'attr' => 'data-ajax',
'hasLoader' => false
]);
$this->jquery->renderDefaultView([
'users' => $users
]);
}
|
The view associated to displayUsers action:
<div class="ui top attached header">
<i class="users circular icon"></i>
<div class="content">Users</div>
</div>
<div class="ui attached segment">
<ul id='users-content'>
{% for user in users %}
<li data-ajax="{{user.id}}">{{user.firstname }} {{user.lastname}}</li>
{% endfor %}
</ul>
<div id='user-detail'></div>
</div>
<div class="ui bottom attached inverted segment">
<div id="close-bt" class="ui inverted button">Close</div>
</div>
{{ script_foot | raw }}
displayOneUser action¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | namespace controllers;
/**
* Controller UsersJqueryController
*
* @property \Ajax\php\ubiquity\JsUtils $jquery
* @route("users")
*/
class UsersJqueryController extends ControllerBase {
...
/**
*
* @post("{userId}","name"=>"display.one.user","cache"=>true,"duration"=>3600)
*/
public function displayOneUser($userId) {
$user = DAO::getById(User::class, $userId);
$this->jquery->hide('#users-content', '', '', true);
$this->jquery->click('#close-user-bt', '$("#user-detail").html("");$("#users-content").show();');
$this->jquery->renderDefaultView([
'user' => $user
]);
}
|
The view associated to displayOneUser action:
<div class="ui label">
<i class="ui user icon"></i>
Id
<div class="detail">{{user.id}}</div>
</div>
<div class="ui label">
Firstname
<div class="detail">{{user.firstname}}</div>
</div>
<div class="ui label">
Lastname
<div class="detail">{{user.lastname}}</div>
</div>
<p></p>
<div id="close-user-bt" class="ui black button">
<i class="ui users icon"></i>
Return to users
</div>
{{ script_foot | raw }}
Semantic components¶
Next, we are going to make a controller implementing the same functionalities as before, but using PhpMv-UI components (Semantic part).
HtmlButton sample¶
Create a new Controller UsersJqueryController
Ubiquity controller UsersCompoController -v
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | namespace controllers;
use Ubiquity\controllers\Router;
/**
* Controller UsersCompoController
*
* @property \Ajax\php\ubiquity\JsUtils $jquery
* @route("users-compo")
*/
class UsersCompoController extends ControllerBase {
private function semantic() {
return $this->jquery->semantic();
}
/**
*
* @get
*/
public function index() {
$bt = $this->semantic()->htmlButton('users-bt', 'Display users');
$bt->addIcon('users');
$bt->getOnClick(Router::path('display.compo.users'), '#users', [
'hasLoader' => 'internal'
]);
$this->jquery->renderDefaultView();
}
|
Note
Calling renderView or renderDefaultView on the JQuery object performs the compilation of the component, and generates the corresponding HTML and JS.
The associated view integrates the button component with the q array available in the view :
<div class="ui container">
{{ q['users-bt'] | raw }}
<p></p>
<div id="users">
</div>
</div>
{{ script_foot | raw }}
//todo DataTable sample +++++++++++++++++
Normalizers¶
Note
The Normalizer module uses the static class NormalizersManager to manage normalization.
Validators¶
Note
The Validators module uses the static class ValidatorsManager to manage validation.
Validators are used to check that the member datas of an object complies with certain constraints.
Adding validators¶
Either the Author class that we want to use in our application :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | namespace models;
class Author {
/**
* @var string
* @validator("notEmpty")
*/
private $name;
public function getName(){
return $this->name;
}
public function setName($name){
$this->name=$name;
}
}
|
We added a validation constraint on the name member with the @validator annotation, so that it is not empty.
Generating cache¶
Run this command in console mode to create the cache data of the Author class :
Ubiquity init-cache -t=models
Validator cache is generated in app/cache/contents/validators/models/Author.cache.php
.
Validating instances¶
an instance¶
public function testValidateAuthor(){
$author=new Author();
//Do something with $author
$violations=ValidatorsManager::validate($author);
if(sizeof($violations)>0){
echo implode('<br>', ValidatorsManager::validate($author));
}else{
echo 'The author is valid!';
}
}
if the name of the author is empty, this action should display:
name : This value should not be empty
The validate method returns an array of ConstraintViolation instances.
multiple instances¶
public function testValidateAuthors(){
$authors=DAO::getAll(Author::class);
$violations=ValidatorsManager::validateInstances($author);
foreach($violations as $violation){
echo $violation.'<br>';
}
}
Models generation with default validators¶
When classes are automatically generated from the database, default validators are associated with members, based on the fields’ metadatas.
Ubiquity create-model User
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | namespace models;
class User{
/**
* @id
* @column("name"=>"id","nullable"=>false,"dbType"=>"int(11)")
* @validator("id","constraints"=>array("autoinc"=>true))
**/
private $id;
/**
* @column("name"=>"firstname","nullable"=>false,"dbType"=>"varchar(65)")
* @validator("length","constraints"=>array("max"=>65,"notNull"=>true))
**/
private $firstname;
/**
* @column("name"=>"lastname","nullable"=>false,"dbType"=>"varchar(65)")
* @validator("length","constraints"=>array("max"=>65,"notNull"=>true))
**/
private $lastname;
/**
* @column("name"=>"email","nullable"=>false,"dbType"=>"varchar(255)")
* @validator("email","constraints"=>array("notNull"=>true))
* @validator("length","constraints"=>array("max"=>255))
**/
private $email;
/**
* @column("name"=>"password","nullable"=>true,"dbType"=>"varchar(255)")
* @validator("length","constraints"=>array("max"=>255))
**/
private $password;
/**
* @column("name"=>"suspended","nullable"=>true,"dbType"=>"tinyint(1)")
* @validator("isBool")
**/
private $suspended;
}
|
These validators can then be modified.
Modifications must always be folowed by a re-initialization of the model cache.
Ubiquity init-cache -t=models
Models validation informations can be displayed with devtools :
Ubiquity info:validation -m=User

Gets validators on email field:
Ubiquity info:validation email -m=User

Validation informations are also accessible from the models part of the webtools:

Validator types¶
Basic¶
Validator | Roles | Constraints | Accepted values |
---|---|---|---|
isBool | Check if value is a boolean | true,false,0,1 | |
isEmpty | Check if value is empty | ‘’,null | |
isFalse | Check if value is false | false,’false’,0,’0’ | |
isNull | Check if value is null | null | |
isTrue | Check if value is true | true,’true’,1,’1’ | |
notEmpty | Check if value is not empty | !null && !’’ | |
notNull | Check if value is not null | !null | |
type | Check if value is of type {type} | {type} |
Comparison¶
Dates¶
Multiples¶
Strings¶
Transformers¶
Note
The Transformers module uses the static class TransformersManager to manage data transformations.
Transformers are used to transform datas after loading from the database, or before displaying in a view.
Adding transformers¶
Either the Author class that we want to use in our application :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | namespace models;
use Ubiquity\attributes\items\Transformer;
class Author {
#[Transformer('upper')]
private $name;
public function getName(){
return $this->name;
}
public function setName($name){
$this->name=$name;
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | namespace models;
class Author {
/**
* @var string
* @transformer("upper")
*/
private $name;
public function getName(){
return $this->name;
}
public function setName($name){
$this->name=$name;
}
}
|
We added a transformer on the name member with the @transformer annotation, in order to capitalize the name in the views.
Generating cache¶
Run this command in console mode to create the cache data of the Author class :
Ubiquity init-cache -t=models
transformer cache is generated with model metadatas in app/cache/models/Author.cache.php
.
Transformers informations can be displayed with devtools :
Ubiquity info:model -m=Author -f=#transformers

Using transformers¶
Start the TransformersManager in the file app/config/services.php:
\Ubiquity\contents\transformation\TransformersManager::startProd();
You can test the result in the administration interface:

or by creating a controller:
1 2 3 4 5 6 7 8 9 10 11 | namespace controllers;
class Authors {
public function index(){
DAO::transformersOp='toView';
$authors=DAO::getAll(Author::class);
$this->loadDefaultView(['authors'=>$authors]);
}
}
|
<ul>
{% for author in authors %}
<li>{{ author.name }}</li>
{% endfor %}
</ul>
Transformer types¶
transform¶
The transform type is based on the TransformerInterface interface. It is used when the transformed data must be converted into an object.
The DateTime transformer is a good example of such a transformer:
- When loading the data, the Transformer converts the date from the database into an instance of php DateTime.
- Its reverse method performs the reverse operation (php date to database compatible date).
toView¶
The toView type is based on the TransformerViewInterface interface. It is used when the transformed data must be displayed in a view.
toForm¶
The toForm type is based on the TransformerFormInterface interface. It is used when the transformed data must be used in a form.
Transformers usage¶
Transform on data loading¶
If ommited, default transformerOp is transform
$authors=DAO::getAll(Author::class);
Set transformerOp to toView
DAO::transformersOp='toView';
$authors=DAO::getAll(Author::class);
Transform after loading¶
Return the transformed member value:
TransformersManager::transform($author, 'name','toView');
Return a transformed value:
TransformersManager::applyTransformer($author, 'name','john doe','toView');
Transform an instance by applying all defined transformers:
TransformersManager::transformInstance($author,'toView');
Existing transformers¶
Transformer | Type(s) | Description |
datetime | transform, toView, toForm | Transform a database datetime to a php DateTime object |
upper | toView | Make the member value uppercase |
lower | toView | Make the member value lowercase |
firstUpper | toView | Make the member value first character uppercase |
password | toView | Mask the member characters |
md5 | toView | Hash the value with md5 |
Create your own¶
Creation¶
Create a transformer to display a user name as a local email address:
1 2 3 4 5 6 7 8 9 10 11 12 | namespace transformers;
use Ubiquity\contents\transformation\TransformerViewInterface;
class ToLocalEmail implements TransformerViewInterface{
public static function toView($value) {
if($value!=null) {
return sprintf('%s@mydomain.local',strtolower($value));
}
}
}
|
Registration¶
Register the transformer by executing the following script:
TransformersManager::registerClassAndSave('localEmail',\transformers\ToLocalEmail::class);
Usage¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | namespace models;
use Ubiquity\attributes\items\Transformer;
class User {
#[Transformer('localEmail')]
private $name;
public function getName(){
return $this->name;
}
public function setName($name){
$this->name=$name;
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | namespace models;
class User {
/**
* @var string
* @transformer("localEmail")
*/
private $name;
public function getName(){
return $this->name;
}
public function setName($name){
$this->name=$name;
}
}
|
DAO::transformersOp='toView';
$user=DAO::getOne(User::class,"name='Smith'");
echo $user->getName();
Smith user name will be displayed as smith@mydomain.local.
Translation module¶
Note
The Translation module uses the static class TranslatorManager to manage translations.
Module structure¶
Translations are grouped by domain, within a locale :
In the translation root directory (default app/translations):
- Each locale corresponds to a subfolder.
- For each locale, in a subfolder, a domain corresponds to a php file.
translations
├ en_EN
│ ├ messages.php
│ └ blog.php
└ fr_FR
├ messages.php
└ blog.php
- each domain file contains an associative array of translations key-> translation value
- Each key can be associated with
- a translation
- a translation containing variables (between % and %)
- an array of translations for handle pluralization
return [
'okayBtn'=>'Okay',
'cancelBtn'=>'Cancel',
'deleteMessage'=>['No message to delete!','1 message to delete.','%count% messages to delete.']
];
Starting the module¶
Module startup is logically done in the services.php file.
1 2 | Ubiquity\cache\CacheManager::startProd($config);
Ubiquity\translation\TranslatorManager::start();
|
With no parameters, the call of the start method uses the locale en_EN, without fallbacklocale.
Important
The translations module must be started after the cache has started.
Defining the root translations dir¶
If the rootDir parameter is missing, the default directory used is app/translations
.
1 2 | Ubiquity\cache\CacheManager::startProd($config);
Ubiquity\translation\TranslatorManager::start('fr_FR','en_EN','myTranslations');
|
Make a translation¶
With php¶
Translation of the okayBtn key into the default locale (specified when starting the manager):
$okBtnCaption=TranslatorManager::trans('okayBtn');
With no parameters, the call of the trans method uses the default locale, the domain messages.
Translation of the message key using a variable:
$okBtnCaption=TranslatorManager::trans('message',['user'=>$user]);
In this case, the translation file must contain a reference to the user variable for the key message:
['message'=>'Hello %user%!',...];
In twig views:¶
Translation of the okayBtn key into the default locale (specified when starting the manager):
{{ t('okayBtn') }}
Translation of the message key using a variable:
{{ t('message',parameters) }}
Security¶
Guiding principles¶
Forms validation¶
Client-side validation¶
It is preferable to perform an initial client-side validation to avoid submitting invalid data to the server.
Example of the creation of a form in the action of a controller (this part could be located in a dedicated service for a better separation of layers):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public function index(){
$frm=$this->jquery->semantic()->dataForm('frm-user',new User());
$frm->setFields(['login','password','connection']);
$frm->fieldAsInput('login',
['rules'=>'empty']
);
$frm->fieldAsInput('password',
[
'inputType'=>'password',
'rules'=>['empty','minLength[6]']
]
);
$frm->setValidationParams(['on'=>'blur','inline'=>true]);
$frm->fieldAsSubmit('connection','fluid green','/submit','#response');
$this->jquery->renderDefaultView();
}
|
The Associated View:
{{ q['frm-user'] | raw }}
{{ script_foot | raw }}
<div id="response"></div>

Note
The CRUD controllers automatically integrate this client-side validation using the Validators attached to the members of the models.
#[Column(name: "password",nullable: true,dbType: "varchar(255)")]
#[Validator(type: "length",constraints: ["max"=>20,"min"=>6])]
#[Transformer(name: "password")]
private $password;
Server-side validation¶
It is preferable to restrict the URLs allowed to modify data.
Beforehand, by specifying the Http method in the routes, and by testing the request :
#[Post(path: "/submit")]
public function submitUser(){
if(!URequest::isCrossSite() && URequest::isAjax()){
$datas=URequest::getPost();//post with htmlEntities
//Do something with $datas
}
}
Note
The Ubiquity-security module offers additional control to avoid cross-site requests.
After modifying an object, it is possible to check its validity, given the validators attached to the members of the associated Model:
#[Post(path: "/submit")]
public function submitUser(){
if(!URequest::isCrossSite()){
$datas=URequest::getPost();//post with htmlEntities
$user=new User();
URequest::setValuesToObject($user,$datas);
$violations=ValidatorsManager::validate($user);
if(\count($violations)==0){
//do something with this valid user
} else {
//Display violations...
}
}
}
DAO operations¶
- It is always recommended to use parameterized queries, regardless of the operations performed on the data:
- To avoid SQL injections.
- To allow the use of prepared queries, speeding up processing.
$googleUsers=DAO::getAll(User::class,'email like ?',false,['%@gmail.com']);
$countActiveUsers=DAO::count(User::class,'active= ?',[true]);
Note
DAO operations that take objects as parameters use this mechanism by default.
DAO::save($user);
Passwords management¶
The Password
Transformer allows a field to be of the password type when displayed in an automatically generated CRUD form.
#[Transformer(name: "password")]
private $password;
After submission from a form, it is possible to encrypt a password from the URequest class:
$encryptedPassword=URequest::password_hash('password');
$user->setPassword($encryptedPassword);
DAO::save($user);
The algorithm used in this case is defined by the php PASSWORD_DEFAULT
.
It is also possible to check a password entered by a user in the same way, to compare it to a hash:
if(URequest::password_verify('password', $existingPasswordHash)){
//password is ok
}
Important
Set up Https to avoid sending passwords in clear text.
Security module/ ACL management¶
- In addition to these few rules, you can install if necessary:
Security module¶
Installation¶
Install the Ubiquity-security module from the command prompt or from the Webtools (Composer part).
composer require phpmv/ubiquity-security
Then activate the display of the Security part in the Webtools:

Session CSRF¶
The session is by default protected against CSRF attacks via the VerifyCsrfToken
class (even without the Ubiquity-security module).
A token instance (CSRFToken
) is generated at the session startup. The validity of the token is then checked via a cookie at each request.

This protection can be customized by creating a class implementing the VerifySessionCsrfInterface
.
class MyCsrfProtection implements VerifySessionCsrfInterface {
private AbstractSession $sessionInstance;
public function __construct(AbstractSession $sessionInstance) {
$this->sessionInstance = $sessionInstance;
}
public function init() {
//TODO when the session starts
}
public function clear() {
//TODO when the session ends
}
public function start() {
//TODO When the session starts or is resumed
}
public static function getLevel() {
return 1; //An integer to appreciate the level of security
}
}
Starting the custom protection in services:
use Ubiquity\utils\http\session\PhpSession;
use Ubiquity\controllers\Startup;
use app\session\MyCsrfProtection;
Startup::setSessionInstance(new PhpSession(new MyCsrfProtection()));
Deactivating the protection¶
If you do not need to protect your session against Csrf attacks, start the session with the NoCsrfProtection
class.
use Ubiquity\utils\http\session\PhpSession;
use Ubiquity\controllers\Startup;
use Ubiquity\utils\http\session\protection\NoCsrfProtection;
Startup::setSessionInstance(new PhpSession(new NoCsrfProtection()));
CSRF manager¶
The CsrfManager service can be started directly from the webtools interface.
Its role is to provide tools to protect sensitive routes from Csrf attacks (the ones that allow the validation of forms for example).

- The service is started in the
services.php
file.
\Ubiquity\security\csrf\CsrfManager::start();
Example of form protection:¶
The form view:
<form id="frm-bar" action='/submit' method='post'>
{{ csrf('frm-bar') }}
<input type='text' id='sensitiveData' name='sensitiveData'>
</form>
The csrf
method generates a token for the form (By adding a hidden field in the form corresponding to the token.).
The form submitting in a controller:
use Ubiquity\security\csrf\UCsrfHttp;
#[Post('/submit')]
public function submit(){
if(UCsrfHttp::isValidPost('frm-bar')){
//Token is valid! => do something with post datas
}
}
Note
It is also possible to manage this protection via cookie.
Example of protection with ajax:¶
The meta field csrf-token
is generated on all pages.
abstract class ControllerBase extends Controller{
protected $headerView = "@activeTheme/main/vHeader.html";
protected $footerView = "@activeTheme/main/vFooter.html";
public function initialize() {
if (! URequest::isAjax ()) {
$meta=UCsrfHttp::getTokenMeta('postAjax');
$this->loadView ( $this->headerView,['meta'=>$meta] );
}
}
}
This field is added in the headerView:
{% block header %}
<base href="{{config["siteUrl"]}}">
<meta charset="UTF-8">
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
{{meta | raw}}
<title>Tests</title>
{% endblock %}
Example with a button posting data via ajax.
The parameter csrf
is set to true. So when the request is posted, the csrf-token
is sent in the request headers.
#[Get(path: "/ajax")]
public function ajax(){
$this->jquery->postOnClick('#bt','/postAjax','{id:55}','#myResponse',['csrf'=>true]);
$this->jquery->renderDefaultView();
}
The submitting route can check the presence and validity of the token:
#[Post(path: "postAjax")]
public function postAjax(){
if(UCsrfHttp::isValidMeta('postAjax')){
var_dump($_POST);
}else{
echo 'invalid or absent meta csrf-token';
}
}
Encryption manager¶
The EncryptionManager service can be started directly from the webtools interface.
- In this case, a key is generated in the configuration file
app/config/config.php
. - The service is started in the
services.php
file.
\Ubiquity\security\data\EncryptionManager::start($config);
Note
By default, encryption is performed in AES-128
.

Changing the cipher:¶
Upgrade to AES-256:
\Ubiquity\security\data\EncryptionManager::startProd($config, Encryption::AES256);
Generate a new key:
Ubiquity new:key 256
The new key is generated in the app/config/config.php
file.
Cookie encryption¶
Cookies can be encrypted by default, by adding this in services.php
:
use Ubiquity\utils\http\UCookie;
use Ubiquity\contents\transformation\transformers\Crypt;
UCookie::setTransformer(new Crypt());

Model data encryption¶
The Crypt
transformer can also be used on the members of a model:
class Foo{
#[Transformer(name: "crypt")]
private $secret;
...
}
Usage:
$o=new Foo();
$o->setSecret('bar');
TransformersManager::transformInstance($o);// secret member is encrypted
Generic Data encryption¶
Strings encryption:
$encryptedBar=EncryptionManager::encryptString('bar');
To then decrypt it:
echo EncryptionManager::decryptString($encryptedBar);
It is possible to encrypt any type of data:
$encryptedUser=EncryptionManager::encrypt($user);
To then decrypt it, with possible serialisation/deserialisation if it is an object:
$user=EncryptionManager::decrypt($encryptedUser);
Content Security Policies manager¶
The ContentSecurityManager service can be started directly from the webtools interface.
- The service is started in the
services.php
file.
\Ubiquity\security\csp\ContentSecurityManager::start(reportOnly: true,onNonce: function($name,$value){
if($name==='jsUtils') {
\Ubiquity\security\csp\ContentSecurityManager::defaultUbiquityDebug()->addNonce($value, \Ubiquity\security\csp\CspDirectives::SCRIPT_SRC)->addHeaderToResponse();
}
});
Note
With this default configuration, a nonce is added to jquery scripts generated with phpmv-ui. CSP control is done in Report-only mode..

Adding a nonce¶
Example of adding nonce on the header and footer pages:
Updating the base controller¶
namespace controllers;
use Ubiquity\controllers\Controller;
use Ubiquity\security\csp\ContentSecurityManager;
use Ubiquity\utils\http\URequest;
/**
* controllers$ControllerBase
*/
abstract class ControllerBase extends Controller {
protected $headerView = "@activeTheme/main/vHeader.html";
protected $footerView = "@activeTheme/main/vFooter.html";
protected $nonce;
public function initialize() {
$this->nonce=ContentSecurityManager::getNonce('jsUtils');
if (! URequest::isAjax()) {
$this->loadView($this->headerView,['nonce'=>$this->nonce]);
}
}
public function finalize() {
if (! URequest::isAjax()) {
$this->loadView($this->footerView,['nonce'=>$this->nonce]);
}
}
}
Password management¶
Users token¶
ACL management¶
Installation¶
Install the Ubiquity-acl module from the command prompt or from the Webtools (Composer part).
composer require phpmv/ubiquity-acl
Then activate the display of the Acl part in the Webtools:

ACL interface in webtools:

Acl Rules¶
ACLs are used to define access to an Ubiquity application. They are defined according to the following principles:
- An Ubiquity application is composed of :
- Resources (possibly controllers, or actions of these controllers)
- Roles, possibly assigned to users. Each Role can inherit parent roles.
- Permissions, which correspond to a right to do. Each permission has a level (represented by an integer value).
- Additional rules:
- An AclElement (Allow) grants Permission to a Role on a Resource.
- Each role inherits authorisations from its parents, in addition to its own.
- If a role has a certain level of access permission on a resource, it will also have all the permissions of a lower level on that resource.
- The association of a resource and a permission to a controller or a controller action defines a map element.

- Naming tips:
- Role, in capital letters, beginning with an arobase (@USER, @ADMIN, @ALL…).
- Permissions, in upper case, named using a verb (READ, WRITE, OPEN…).
- Resource, capitalized on the first letter (Products, Customers…)
ACL Starting¶
The AclManager service can be started directly from the webtools interface, in the Security part.
- The service is started in the
services.php
file.
\Ubiquity\security\acl\AclManager::startWithCacheProvider();
ACLCacheProvider¶
This default provider allows you to manage ACLs defined through attributes or annotations.
AclController¶
An AclController enables automatic access management based on ACLs to its own resources.
It is possible to create them automatically from webtools.

But it is just a basic controller, using the AclControllerTrait feature.
This controller just goes to redefine the _getRole
method, so that it returns the role of the active user, for example.
<?php
namespace controllers;
use Ubiquity\controllers\Controller;
use Ubiquity\security\acl\controllers\AclControllerTrait;
use Ubiquity\attributes\items\acl\Allow;
class BaseAclController extends Controller {
use AclControllerTrait;
#[Allow('@ME')]
public function index() {
$this->loadView("BaseAclController/index.html");
}
public function _getRole() {
$_GET['role']??'@ME';//Just for testing: logically, this is the active user's role
}
/**
* {@inheritdoc}
* @see \Ubiquity\controllers\Controller::onInvalidControl()
*/
public function onInvalidControl() {
echo $this->_getRole() . ' is not allowed!';
}
}
- Authorisation has been granted for the resource:
- Without specifying the resource, the controller’s actions are defined as a resource.
- Without specifying the permission, the
ALL
permission is used.

And this association is present in the Acls map:

AclController with authentication¶
Note
The use of both WithAuthTrait
and AclControllerTrait
requires to remove the ambiguity about the isValid
method.
class BaseAclController extends Controller {
use AclControllerTrait,WithAuthTrait{
WithAuthTrait::isValid insteadof AclControllerTrait;
AclControllerTrait::isValid as isValidAcl;
}
public function isValid($action){
return parent::isValid($action)&& $this->isValidAcl($action);
}
}
Allow with Role, resource and permission¶
Allow without prior creation:
@USER
is allowed to access to Foo
resource with READ
permission.
use Ubiquity\attributes\items\acl\Allow;
class BaseAclController extends Controller {
use AclControllerTrait;
...
#[Allow('@USER','Foo', 'READ')]
public function foo(){
echo 'foo page allowed for @USER and @ME';
}
}
Note
The role, resource and permission are automatically created as soon as they are invoked with Allow
.
Allow with explicit creation:
use Ubiquity\attributes\items\acl\Allow;
use Ubiquity\attributes\items\acl\Permission;
class BaseAclController extends Controller {
use AclControllerTrait;
...
#[Permission('READ',500)]
#[Allow('@USER','Foo', 'READ')]
public function foo(){
echo 'foo page allowed for @USER and @ME';
}
}
Adding ACL at runtime¶
Whether in a controller or in a service, it is possible to add Roles, Resources, Permissions and Authorizations at runtime:
For example :\
Adding a Role @USER
inheriting from @GUEST
.
use Ubiquity\security\acl\AclManager;
AclManager::addRole('@GUEST');
AclManager::addRole('@USER',['@GUEST']);
Defining ACLs with Database¶
The ACLs defined in the database are additional to the ACLs defined via annotations or attributes.
Initializing¶
The initialization allows to create the tables associated to the ACLs (Role, Resource, Permission, AclElement). It needs to be done only once, and in dev mode only.
To place for example in app/config/bootstrap.php
file:
use Ubiquity\controllers\Startup;
use Ubiquity\security\acl\AclManager;
$config=Startup::$config;
AclManager::initializeDAOProvider($config, 'default');
Starting¶
In app/config/services.php
file :
use Ubiquity\security\acl\AclManager;
use Ubiquity\security\acl\persistence\AclCacheProvider;
use Ubiquity\security\acl\persistence\AclDAOProvider;
use Ubiquity\orm\DAO;
DAO::start();//Optional, to use only if dbOffset is not default
AclManager::start();
AclManager::initFromProviders([
new AclCacheProvider(), new AclDAOProvider($config)
]);
Strategies for defining ACLs¶
With few resources:¶
Defining authorisations for each controller’s action or action group:
Resources logically correspond to controllers, and permissions to actions. But this rule may not be respected, and an action may be defined as a resource, as required.
The only mandatory rule is that a Controller/action pair can only correspond to one Resource/permission pair (not necessarily unique).
namespace controllers;
use Ubiquity\controllers\Controller;
use Ubiquity\security\acl\controllers\AclControllerTrait;
use Ubiquity\attributes\items\acl\Permission;
use Ubiquity\attributes\items\acl\Resource;
#[Resource('Foo')]
#[Allow('@ADMIN')]
class FooController extends Controller {
use AclControllerTrait;
#[Allow('@NONE')]
public function index() {
echo 'index';
}
#[Allow('@USER')]
public function read() {
echo 'read';
}
#[Allow('@USER')]
public function write() {
echo 'write';
}
public function admin() {
echo 'admin';
}
public function _getRole() {
return $_GET['role']??'@NONE';
}
/**
* {@inheritdoc}
* @see \Ubiquity\controllers\Controller::onInvalidControl()
*/
public function onInvalidControl() {
echo $this->_getRole() . ' is not allowed!';
}
}
With more resources:¶
namespace controllers;
use Ubiquity\controllers\Controller;
use Ubiquity\security\acl\controllers\AclControllerTrait;
use Ubiquity\attributes\items\acl\Permission;
use Ubiquity\attributes\items\acl\Resource;
#[Resource('Foo')]
class FooController extends Controller {
use AclControllerTrait;
#[Permission('INDEX',1)]
public function index() {
echo 'index';
}
#[Permission('READ',2)]
public function read() {
echo 'read';
}
#[Permission('WRITE',3)]
public function write() {
echo 'write';
}
#[Permission('ADMIN',10)]
public function admin() {
echo 'admin';
}
public function _getRole() {
return $_GET['role']??'NONE';
}
/**
* {@inheritdoc}
* @see \Ubiquity\controllers\Controller::onInvalidControl()
*/
public function onInvalidControl() {
echo $this->_getRole() . ' is not allowed!';
}
}
Rest¶
The REST module implements a basic CRUD,
with an authentication system, directly testable in the administration part.
REST and routing¶
The router is essential to the REST module, since REST (Respresentation State Transfer) is based on URLs and HTTP methods.
Note
For performance reasons, REST routes are cached independently of other routes.
It is therefore necessary to start the router in a particular way to activate the REST routes and not to obtain a recurring 404 error.
The router is started in services.php
.
Without activation of REST routes:
...
Router::start();
To enable REST routes in an application that also has a non-REST part:
...
Router::startAll();
To activate only Rest routes:
Router::startRest();
It is possible to start routing conditionally (this method will only be more efficient if the number of routes is large in either part):
...
if($config['isRest']()){
Router::startRest();
}else{
Router::start();
}
Resource REST¶
A REST controller can be directly associated with a model.
Note
If you do not have a mysql database on hand, you can download this one: messagerie.sql
Creation¶
With devtools:
Ubiquity rest RestUsersController -r=User -p=/rest/users
Or with webtools:
Go to the REST section and choose Add a new resource:

The created controller :
1 2 3 4 5 6 7 8 9 10 | namespace controllers;
/**
* Rest Controller RestUsersController
* @route("/rest/users","inherited"=>true,"automated"=>true)
* @rest("resource"=>"models\\User")
*/
class RestUsersController extends \Ubiquity\controllers\rest\RestController {
}
|
Since the attributes automated and inherited of the route are set to true, the controller has the default routes of the parent class.
Test interface¶
Webtools provide an interface for querying datas:

Getting an instance¶
A user instance can be accessed by its primary key (id):

Inclusion of associated members: the organization of the user

Inclusion of associated members: organization, connections and groups of the user

Getting multiple instances¶
Getting all instances:

Setting a condition:

Including associated members:

Adding an instance¶
The datas are sent by the POST method, with a content type defined at application/x-www-form-urlencoded
:
Add name and domain parameters by clicking on the parameters button:

The addition requires an authentication, so an error is generated, with the status 401:

The administration interface allows you to simulate the default authentication and obtain a token, by requesting the connect method:

The token is then automatically sent in the following requests.
The record can then be inserted.

Updating an instance¶
The update follows the same scheme as the insertion.
Deleting an instance¶

Customizing¶
Routes¶
It is of course possible to customize and simplify the routes.
In this case, it is preferable to use inheritance from the RestBaseController class, and not to enable automatic routes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | namespace controllers;
use models\Organization;
/**
* Rest Controller for organizations
*
* @route("/orgas")
* @rest
*/
class RestOrgas extends \Ubiquity\controllers\rest\RestBaseController {
public function initialize() {
$this->model = Organization::class;
parent::initialize();
}
/**
*
* @get
*/
public function index() {
$this->_get();
}
/**
*
* @get("{keyValues}")
*/
public function get($keyValues) {
$this->_getOne($keyValues);
}
/**
*
* @post("/")
*/
public function add() {
$this->_add();
}
/**
*
* @patch("{keyValues}")
*/
public function update(...$keyValues) {
$this->_update(...$keyValues);
}
/**
*
* @delete("{keyValues}")
*/
public function delete(...$keyValues) {
$this->_delete(...$keyValues);
}
}
|
After re-initializing the cache, the test interface shows the accessible routes:

Modification of sent data¶
By overriding¶
It is possible to modify the data sent to the update and add methods, in order to add, modify or delete the value of fields before sending.
Either by overdefining the method getDatas:
...
protected function getDatas() {
$datas = parent::getDatas();
unset($datas['aliases']);// Remove aliases field
return $datas;
}
With events¶
Either in a more global way by acting on the rest events:
use Ubiquity\events\EventsManager;
use Ubiquity\events\RestEvents;
use Ubiquity\controllers\rest\RestBaseController;
...
EventsManager::addListener(RestEvents::BEFORE_INSERT, function ($o, array &$datas, RestBaseController $resource) {
unset($datas['aliases']);// Remove aliases field
});
Authentification¶
Ubiquity REST implements an Oauth2 authentication with Bearer tokens.
Only methods with @authorization
annotation require the authentication, these are the modification methods (add, update & delete).
/**
* Update an instance of $model selected by the primary key $keyValues
* Require members values in $_POST array
* Requires an authorization with access token
*
* @param array $keyValues
* @authorization
* @route("methods"=>["patch"])
*/
public function update(...$keyValues) {
$this->_update ( ...$keyValues );
}
The connect method of a REST controller establishes the connection and returns a new token.
It is up to the developer to override this method to manage a possible authentication with login and password.

Simulation of a connection with login¶
In this example, the connection consists simply in sending a user variable by the post method.
If the user is provided, the connect
method of $server
instance returns a valid token that is stored in session (the session acts as a database here).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | namespace controllers;
use Ubiquity\utils\http\URequest;
use Ubiquity\utils\http\USession;
/**
* Rest Controller RestOrgas
* @route("/rest/orgas","inherited"=>true,"automated"=>true)
* @rest("resource"=>"models\\Organization")
*/
class RestOrgas extends \Ubiquity\controllers\rest\RestController {
/**
* This method simulate a connection.
* Send a <b>user</b> variable with <b>POST</b> method to retreive a valid access token
* @route("methods"=>["post"])
*/
public function connect(){
if(!URequest::isCrossSite()){
if(URequest::isPost()){
$user=URequest::post("user");
if(isset($user)){
$tokenInfos=$this->server->connect ();
USession::set($tokenInfos['access_token'], $user);
$tokenInfos['user']=$user;
echo $this->_format($tokenInfos);
return;
}
}
}
throw new \Exception('Unauthorized',401);
}
}
|
For each request with authentication, it is possible to retrieve the connected user (it is added here in the response headers) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | namespace controllers;
use Ubiquity\utils\http\URequest;
use Ubiquity\utils\http\USession;
/**
* Rest Controller RestOrgas
* @route("/rest/orgas","inherited"=>true,"automated"=>true)
* @rest("resource"=>"models\\Organization")
*/
class RestOrgas extends \Ubiquity\controllers\rest\RestController {
...
public function isValid($action){
$result=parent::isValid($action);
if($this->requireAuth($action)){
$key=$this->server->_getHeaderToken();
$user=USession::get($key);
$this->server->_header('active-user',$user,true);
}
return $result;
}
}
|
Use the webtools interface to test the connection:

Customizing¶
Api tokens¶
It is possible to customize the token generation, by overriding the getRestServer
method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | namespace controllers;
use Ubiquity\controllers\rest\RestServer;
class RestOrgas extends \Ubiquity\controllers\rest\RestController {
...
protected function getRestServer(): RestServer {
$srv= new RestServer($this->config);
$srv->setTokenLength(32);
$srv->setTokenDuration(4800);
return $srv;
}
}
|
Allowed origins and CORS¶
Cross-Origin Resource Sharing (CORS)¶
If you access your api from another site, it is necessary to set up CORS.
In this case, for requests of type PATCH
, PUT
, DELETE
, your api must define a route allowing CORS to carry out its control pre-request using the OPTIONS
method.
1 2 3 4 5 6 7 8 9 | class RestOrgas extends \Ubiquity\controllers\rest\RestController {
...
/**
* @options('{url}')
*/
public function options($url='') {}
}
|
Allowed origins¶
Allowed origins allow to define the clients that can access the resource in case of a cross domain request by defining The Access-Control-Allow-Origin response header.
This header field is returned by the OPTIONS
method.
1 2 3 4 5 6 7 8 9 10 | class RestOrgas extends \Ubiquity\controllers\rest\RestController {
...
protected function getRestServer(): RestServer {
$srv= new RestServer($this->config);
$srv->setAllowOrigin('http://mydomain/');
return $srv;
}
}
|
It is possible to authorize several origins:
1 2 3 4 5 6 7 8 9 10 | class RestOrgas extends \Ubiquity\controllers\rest\RestController {
...
protected function getRestServer(): RestServer {
$srv= new RestServer($this->config);
$srv->setAllowOrigins(['http://mydomain1/','http://mydomain2/']);
return $srv;
}
}
|
Response¶
To change the response format, it is necessary to create a class inheriting from ResponseFormatter
.
We will take inspiration from HAL, and change the format of the responses by:
- adding a link to self for each resource
- adding an
_embedded
attribute for collections - removing the
data
attribute for unique resources
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | namespace controllers\rest;
use Ubiquity\controllers\rest\ResponseFormatter;
use Ubiquity\orm\OrmUtils;
class MyResponseFormatter extends ResponseFormatter {
public function cleanRestObject($o, &$classname = null) {
$pk = OrmUtils::getFirstKeyValue ( $o );
$r=parent::cleanRestObject($o);
$r["links"]=["self"=>"/rest/orgas/get/".$pk];
return $r;
}
public function getOne($datas) {
return $this->format ( $this->cleanRestObject ( $datas ) );
}
public function get($datas, $pages = null) {
$datas = $this->getDatas ( $datas );
return $this->format ( [ "_embedded" => $datas,"count" => \sizeof ( $datas ) ] );
}
}
|
Then assign MyResponseFormatter
to the REST controller by overriding the getResponseFormatter
method:
1 2 3 4 5 6 7 8 | class RestOrgas extends \Ubiquity\controllers\rest\RestController {
...
protected function getResponseFormatter(): ResponseFormatter {
return new MyResponseFormatter();
}
}
|
Test the results with the getOne and get methods:


APIs¶
Unlike REST resources, APIs controllers are multi-resources.
SimpleRestAPI¶
JsonApi¶
Ubiquity implements the jsonApi specification with the class JsonApiRestController
.
JsonApi is used by EmberJS and others.
see https://jsonapi.org/ for more.
Creation¶
With devtools:
Ubiquity restapi JsonApiTest -p=/jsonapi
Or with webtools:
Go to the REST section and choose Add a new resource:

Test the api in webtools:

Getting an array of objects¶
By default, all associated members are included:

Including associated members¶
you need to use the include parameter of the request:
URL | Description |
---|---|
/jsonapi/user?include=false |
No associated members are included |
/jsonapi/user?include=organization |
Include the organization |
/jsonapi/user?include=organization,connections |
Include the organization and the connections |
/jsonapi/user?include=groupes.organization |
Include the groups and their organization |
Filtering instances¶
you need to use the filter parameter of the request,
filter parameter corresponds to the where part of an SQL statement:
URL | Description |
---|---|
/jsonapi/user?1=1 |
No filtering |
/jsonapi/user?firstname='Benjamin' |
Returns all users named Benjamin |
/jsonapi/user?filter=firstname like 'B*' |
Returns all users whose first name begins with a B |
/jsonapi/user?filter=suspended=0 and lastname like 'ca*' |
Returns all suspended users whose lastname begins with ca |
Pagination¶
you need to use the page[number] and page[size] parameters of the request:
URL | Description |
---|---|
/jsonapi/user |
No pagination |
/jsonapi/user?page[number]=1 |
Display the first page (page size is 1) |
/jsonapi/user?page[number]=1&&page[size]=10 |
Display the first page (page size is 10) |
Adding an instance¶
The datas, contained in data[attributes]
, are sent by the POST method, with a content type defined at application/json; charset=utf-8
.
Add your parameters by clicking on the parameters button:

The addition requires an authentication, so an error is generated, with the status 401 if the token is absent or expired.

Deleting an instance¶
Deletion requires the DELETE method, and the use of the id of the object to be deleted:

Webtools¶
Note
Webtools allow you to manage an Ubiquity application via a web interface. Since Ubiquity 2.2.0, webtools are in a separate repository.
Starting¶
Start the embedded web server, from the project folder:
Ubiquity serve
go to the address: http://127.0.0.1:8090/Admin

Webtools modules¶
Routes¶

Displays default (non REST) routes.
Operations:
- Filter routes
- Test routes (GET, POST…)
- Initialize router cache
Controllers¶

Displays non REST controllers.
Operations:
- Create a controller (and optionally the view associated to the default index action)
- Create an action in a controller (optionally the associated view, the associated route)
- Create a special controller (CRUD or Auth)
- Test an action (GET, POST…)
Models¶

Displays the metadatas of the models, allows to browse the entities.
Operations:
- Create models from database
- Generate models cache
- Generate database script from existing models
- Performs CRUD operations on models
Rest¶

Displays an manage REST services.
Operations:
- Re-initialize Rest cache and routes
- Create a new Service (using an api)
- Create a new resource (associated to a model)
- Test and query a web service using http methods
- Performs CRUD operations on models
Cache¶

Displays cache files.
Operations:
- Delete or re-initialize models cache
- Delete or re-initialize controllers cache
- Delete other cache files
Maintenance¶

Allows to manage maintenance modes.
Operations:
- Create or update a maintenance mode
- De/Activate a maintenance mode
- Delete a maintenance mode
Git¶

Synchronizes the project using git.
Operations:
- Configuration with external repositories
- Commit
- Push
- Pull
![]()
Manages Css themes.
Operations:
- Install an existing theme
- Activate a theme
- Create a new theme (eventually base on an existing theme)
Contributing¶
System requirements¶
Before working on Ubiquity, setup your environment with the following software:
- Git
- PHP version 7.1 or above.
Get Ubiquity source code¶
On Ubiquity github repository :
- Click Fork Ubiquity project
- Clone your fork locally:
git clone git@github.com:USERNAME/ubiquity.git
Work on your Patch¶
Note
Before you start, you must know that all the patches you are going to submit must be released under the Apache 2.0 license, unless explicitly specified in your commits.
Create a Topic Branch¶
Note
Use a descriptive name for your branch:
- issue_xxx where xxx is the issue number is a good convention for bug fixes
- feature_name is a good convention for new features
git checkout -b NEW_BRANCH_NAME master
Work on your Patch¶
Work on your code and commit as much as you want, and keep in mind the following:
Read about the Ubiquity coding standards;
Add unit, fonctional or acceptance tests to prove that the bug is fixed or that the new feature actually works;
Do atomic and logically separate commits (use git rebase to have a clean and logical history);
Write good commit messages (see the tip below).
Increase the version numbers in any modified files, respecting semver rules:
Given a version number
MAJOR.MINOR.PATCH
, increment the:MAJOR
version when you make incompatible API changes,MINOR
version when you add functionality in a backwards-compatible manner, andPATCH
version when you make backwards-compatible bug fixes.
Submit your Patch¶
Update the [Unrelease] part of the CHANGELOG.md file by integrating your changes into the appropriate parts:
- Added
- Changed
- Removed
- Fixed
Eventualy rebase your Patch
Before submitting, update your branch (needed if it takes you a while to finish your changes):
git checkout master
git fetch upstream
git merge upstream/master
git checkout NEW_BRANCH_NAME
git rebase master
Make a Pull Request¶
You can now make a pull request on Ubiquity github repository .
Coding guide¶
Note
Although the framework is very recent, please note some early Ubiquity classes do not fully follow this guide and have not been modified for backward compatibility reasons.
However all new codes must follow this guide.
Design choices¶
Fetching and using Services¶
Dependency injections¶
Avoid using dependency injection for all parts of the framework, internally.
Dependency injection is a resource-intensive mechanism:
- it needs to identify the element to instantiate ;
- then to proceed to its instantiation ;
- to finally assign it to a variable.
Getting services from a container¶
Also avoid public access to services registered in a service container.
This type of access involves manipulating objects whose return type is unknown, not easy to handle for the developer.
For example,
It’s hard to manipulate the untyped return of $this->serviceContainer->get('translator')
, as some frameworks allow,
and know which methods to call on it.
When possible, and when it does not reduce flexibility too much, the use of static classes is suggested:
For a developer, the TranslatorManager
class is accessible from an entire project without any object instantiation.
It exposes its public interface and allows code completion:
- The translator does not need to be injected to be used;
- It does not need to be retrieved from a service container.
The use of static classes inevitably creates a strong dependency and affects flexibility.
But to come back to the Translator example, there is no reason to change it if it is satisfying.
It is not desirable to want to provide flexibility at all costs when it is not necessary, and then for the user to see that its application is a little slow.
Optimization¶
Execution of each line of code can have significant performance implications.
Compare and benchmark implementation solutions, especially if the code is repeatedly called:
- Identify these repetitive and expensive calls with php profiling tools (Blackfire profiler , Tideways …)
- Benchmark your different implementation solutions with phpMyBenchmarks
Code quality¶
Ubiquity use Scrutinizer-CI for code quality.
- For classes and methods :
- A or B evaluations are good
- C is acceptable, but to avoid if possible
- The lower notes are to be prohibited
Code complexity¶
- Complex methods must be split into several, to facilitate maintenance and allow reuse;
- For complex classes , do an extract-class or extract-subclass refactoring and split them using Traits;
Code duplications¶
Absolutely avoid duplication of code, except if duplication is minimal, and is justified by performance.
Bugs¶
Try to solve all the bugs reported as you go, without letting them accumulate.
Tests¶
Any bugfix that doesn’t include a test proving the existence of the bug being fixed, may be suspect.
Ditto for new features that can’t prove they actually work.
It is also important to maintain an acceptable coverage, which may drop if a new feature is not tested.
Code Documentation¶
The current code is not yet fully documented, feel free to contribute in order to fill this gap.
Coding standards¶
Ubiquity coding standards are mainly based on the PSR-1 , PSR-2 and PSR-4 standards, so you may already know most of them.
The few intentional exceptions to the standards are normally reported in this guide.
Naming Conventions¶
- Use camelCase for PHP variables, members, function and method names, arguments (e.g. $modelsCacheDirectory, isStarted());
- Use namespaces for all PHP classes and UpperCamelCase for their names (e.g. CacheManager);
- Prefix all abstract classes with Abstract except PHPUnit BaseTests;
- Suffix interfaces with
Interface
; - Suffix traits with
Trait
; - Suffix exceptions with
Exception
; - Suffix core classes manager with
Manager
(e.g. CacheManager, TranslatorManager); - Prefix Utility classes with
U
(e.g. UString, URequest); - Use UpperCamelCase for naming PHP files (e.g. CacheManager.php);
- Use uppercase for constants (e.g. const SESSION_NAME=’Ubiquity’).
Indentation, tabs, braces¶
- Use Tabs, not spaces; (!PSR-2)
- Use brace always on the same line; (!PSR-2)
- Use braces to indicate control structure body regardless of the number of statements it contains;
Classes¶
- Define one class per file;
- Declare the class inheritance and all the implemented interfaces on the same line as the class name;
- Declare class properties before methods;
- Declare private methods first, then protected ones and finally public ones;
- Declare all the arguments on the same line as the method/function name, no matter how many arguments there are;
- Use parentheses when instantiating classes regardless of the number of arguments the constructor has;
- Add a use statement for every class that is not part of the global namespace;
Operators¶
- Use identical comparison and equal when you need type juggling;
Example
<?php
namespace Ubiquity\namespace;
use Ubiquity\othernamespace\Foo;
/**
* Class description.
* Ubiquity\namespace$Example
* This class is part of Ubiquity
*
* @author authorName <authorMail>
* @version 1.0.0
* @since Ubiquity x.x.x
*/
class Example {
/**
* @var int
*
*/
private $theInt = 1;
/**
* Does something from **a** and **b**
*
* @param int $a The a
* @param int $b The b
*/
function foo($a, $b) {
switch ($a) {
case 0 :
$Other->doFoo ();
break;
default :
$Other->doBaz ();
}
}
/**
* Adds some values
*
* @param param V $v The v object
*/
function bar($v) {
for($i = 0; $i < 10; $i ++) {
$v->add ( $i );
}
}
}
Documenting guide¶
Ubiquity has two main sets of documentation:
- the guides, which help you learn about manipulations or concepts ;
- and the API, which serves as a reference for coding.
You can help improve the Ubiquity guides by making them more coherent, consistent, or readable, adding missing information, correcting factual errors, fixing typos, or bringing them up to date with the latest Ubiquity version.
To do so, make changes to Ubiquity guides source files (located here on GitHub). Then open a pull request to apply your changes to the master branch.
When working with documentation, please take into account the guidelines.
Servers configuration¶
Important
Since version 2.4.5, for security and simplification reasons, the root of an Ubiquity application is located in the public folder.
Apache2¶
mod_php/PHP-CGI¶
Apache 2.2¶
<VirtualHost *:80>
ServerName mydomain.tld
DocumentRoot /var/www/project/public
DirectoryIndex /index.php
<Directory /var/www/project/public>
# enable the .htaccess rewrites
AllowOverride All
Order Allow,Deny
Allow from All
</Directory>
ErrorLog /var/log/apache2/project_error.log
CustomLog /var/log/apache2/project_access.log combined
</VirtualHost>
<VirtualHost *:80>
ServerName mydomain.tld
DocumentRoot /var/www/project/public
DirectoryIndex /index.php
<Directory /var/www/project/public>
AllowOverride None
# Copy .htaccess contents here
</Directory>
ErrorLog /var/log/apache2/project_error.log
CustomLog /var/log/apache2/project_access.log combined
</VirtualHost>
Apache 2.4¶
In Apache 2.4, Order Allow,Deny
has been replaced by Require all granted
.
<VirtualHost *:80>
ServerName mydomain.tld
DocumentRoot /var/www/project/public
DirectoryIndex /index.php
<Directory /var/www/project/public>
# enable the .htaccess rewrites
AllowOverride All
Require all granted
</Directory>
ErrorLog /var/log/apache2/project_error.log
CustomLog /var/log/apache2/project_access.log combined
</VirtualHost>
index.php relocation in public folder¶
If you created your project with a version prior to 2.4.5, you have to modify index.php
and move the index.php
and .htaccess
files to the public
folder.
<?php
define('DS', DIRECTORY_SEPARATOR);
//Updated with index.php in public folder
define('ROOT', __DIR__ . DS . '../app' . DS);
$config = include_once ROOT . 'config/config.php';
require_once ROOT . './../vendor/autoload.php';
require_once ROOT . 'config/services.php';
\Ubiquity\controllers\Startup::run($config);
PHP-FPM¶
Make sure the libapache2-mod-fastcgi and php7.x-fpm packages are installed (replace x with php version number).
php-pm configuration:
;;;;;;;;;;;;;;;;;;;;
; Pool Definitions ;
;;;;;;;;;;;;;;;;;;;;
; Start a new pool named 'www'.
; the variable $pool can be used in any directive and will be replaced by the
; pool name ('www' here)
[www]
user = www-data
group = www-data
; use a unix domain socket
listen = /var/run/php/php7.4-fpm.sock
; or listen on a TCP socket
listen = 127.0.0.1:9000
Apache 2.4 configuration:
<VirtualHost *:80>
...
<FilesMatch \.php$>
SetHandler proxy:fcgi://127.0.0.1:9000
# for Unix sockets, Apache 2.4.10 or higher
# SetHandler proxy:unix:/path/to/fpm.sock|fcgi://localhost/var/www/
</FilesMatch>
</VirtualHost>
nginX¶
nginX configuration:
upstream fastcgi_backend {
server unix:/var/run/php/php7.4-fpm.sock;
keepalive 50;
}
server {
server_name mydomain.tld www.mydomain.tld;
root /var/www/project/public;
index index.php;
listen 8080;
location / {
# try to serve file directly, fallback to index.php
try_files $uri @rewrites;
}
location @rewrites {
rewrite ^/(.*)$ /index.php?c=$1 last;
}
location = /index.php{
fastcgi_pass fastcgi_backend;
fastcgi_keep_conn on;
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
include /etc/nginx/fastcgi_params;
}
# return 404 for all other php files not matching the front controller
# this prevents access to other php files you don't want to be accessible.
location ~ \.php$ {
return 404;
}
error_log /var/log/nginx/project_error.log;
access_log /var/log/nginx/project_access.log;
}
Swoole¶
Swoole configuration:
<?php
return array(
"host" => "0.0.0.0",
"port" => 8080,
"options"=>[
"worker_num" => \swoole_cpu_num() * 2,
"reactor_num" => \swoole_cpu_num() * 2
]
);
Workerman¶
Workerman configuration:
<?php
return array(
"host" => "0.0.0.0",
"port" => 8080,
"socket"=>[
"count" => 4,
"reuseport" =>true
]
);
RoadRunner¶
RoadRunner configuration:
http:
address: ":8090"
workers.command: "php-cgi ./.ubiquity/rr-worker.php"
workers:
pool:
# Set numWorkers to 1 while debugging
numWorkers: 10
maxJobs: 1000
# static file serving. remove this section to disable static file serving.
static:
# root directory for static file (http would not serve .php and .htaccess files).
dir: "."
# list of extensions for forbid for serving.
forbid: [".php", ".htaccess", ".yml"]
always: [".ico", ".html", ".css", ".js"]
Ubiquity optimization¶
Ubiquity is fast, but can be even faster by optimizing a few elements.
Note
The integrated test server (accessible by Ubiquity serve) uses its own configuration and launch files (in the .ubiquity folder of your project).
It should therefore not be used to assess the results of the changes made.
Test your pages using a software and hardware configuration similar to the one used in production.
Use a benchmark tool to assess your changes as they happen (Apache bench for example).
Cache¶
System¶
Choose and test among the different cache systems (ArrayCache, PhpFastCache, MemCached).
The cache system is defined in the configuration file:
"cache" => [
"directory" => "cache/",
"system" => "Ubiquity\\cache\\system\\ArrayCache",
"params" => []
]
default ArrayCache is often the most optimized solution.
Generation¶
Generate the router and ORM cache (Think that annotations are never used at runtime):
Ubiquity init-cache
Static contents¶
If your application has pages that are being generated by PHP but that actually rarely change, you can cache:
- The query results (using DAO methods)
- The route response (with @route annotation)
index file¶
Remove the line defining error reporting at runtime, and make sure that error display is disabled in php.ini.
error_reporting(\E_ALL);//To be removed
Config optimization¶
The configuration is accessible from the app/config/config.php
file.
Keep only those items that are essential to your application.
key | role | Optimization |
---|---|---|
siteUrl | Used by Ajax methods, and by Twig’s url and path functions |
To be removed if these functions are not used |
database | Used by Ubiquity ORM | To be removed if the ORM is not used |
sessionName | If assigned, starts or retrieves the php session for each request | To be removed if the session is useless |
templateEngine | If assigned, instanciates a new Engine object for each request | To be removed if the views are not used |
templateEngineOptions | Options assigned to the template engine instance | set the cache option to true if Twig is used |
test | To remove (deprecated) | |
debug | Enables or disables logs | Set to false in production |
logger | Defines the logger instance | To remove in production |
di | Defines the services to be injected | Only the @exec key is read at runtime |
cache | Defines the cache path and base class of the cache, used by models, router, dependency injection | |
mvcNS | Defines the paths or namespaces used by Rest controllers, models and controllers | |
isRest | Defines the condition to detect if a path corresponds to a controller Rest | To be removed if you do not explicitly use this condition in your code |
Example of configuration without session, and without dependency injection:
1 2 3 4 5 6 7 8 | <?php
return array(
"templateEngine"=>'Ubiquity\\views\\engine\\Twig',
"templateEngineOptions"=>array("cache"=>true),
"debug"=>false,
"cache"=>["directory"=>"cache/","system"=>"Ubiquity\\cache\\system\\ArrayCache","params"=>[]],
"mvcNS"=>["models"=>"models","controllers"=>"controllers","rest"=>""]
);
|
Services optimization¶
The loaded services are accessibles from the app/config/services.php
file.
As for the configuration file, keep only those items that are essential to your application.
Lines | Role |
---|---|
\Ubiquity\cache\CacheManager::startProd($config) | Starts the cache for ORM, database, router, dependency injection |
UbiquityormDAO::start() | To be used only with multiple databases |
Router::start() | To be used only if the routes are defined with annotations |
Router::addRoute(“_default”, “controllers\IndexController”) | Defines the default route (to remove in production) |
\Ubiquity\assets\AssetsManager::start($config) | Assigns the variable siteUrl to the ThemeManager, to be used only if the css and js functions of twig are used |
Example of a Services file with a database and starting the router :
1 2 3 | <?php
\Ubiquity\cache\CacheManager::startProd($config);
\Ubiquity\controllers\Router::start();
|
Autoloader optimization¶
In production, remove dependencies used only in development, and generate the optimized class map file:
composer install --no-dev --classmap-authoritative
If the dependencies used have already been removed and you only want to update the map file (after adding or removing a class):
composer dump-autoload -o --classmap-authoritative
Note
The --no-dev
parameter removes the ubiquity-dev
dependency required by webtools.
If you use webtools in production, add the phpmv/ubiquity-dev
dependency:
composer require phpmv/ubiquity-dev
PHP optimization¶
Please note that other applications can use the modified values on the same server.
OP-Cache¶
OPcache improves PHP performance by storing precompiled script bytecode in shared memory, thereby removing the need for PHP to load and parse scripts on each request.
[opcache]
; Determines if Zend OPCache is enabled
opcache.enable=1
; The OPcache shared memory storage size.
opcache.memory_consumption=256
; The maximum number of keys (scripts) in the OPcache hash table.
; Only numbers between 200 and 1000000 are allowed.
opcache.max_accelerated_files=10000
; When disabled, you must reset the OPcache manually or restart the
; webserver for changes to the filesystem to take effect.
opcache.validate_timestamps=0
; Allow file existence override (file_exists, etc.) performance feature.
opcache.enable_file_override=1
; Enables or disables copying of PHP code (text segment) into HUGE PAGES.
; This should improve performance, but requires appropriate OS configuration.
opcache.huge_code_pages=1
If you use ubiquity-swoole web server:
; Determines if Zend OPCache is enabled for the CLI version of PHP
opcache.enable_cli=1
To complete¶
Remember that the framework used does not do everything. You also need to optimize your own code.
Ubiquity commands¶
Note
This part is accessible from the webtools, so if you created your project with the -a option or with the create-project command..
Commands¶
From the webtools, activate the commands part,

or go directly to http://127.0.0.1:8090/Admin/commands
.
Command info¶
It is possible to get help on a command (which produces a result equivalent to Ubiquity help cmdName
).

Command execution¶
Clicking on the run button of a command displays a form to enter the parameters (or executes it directly if it takes none).

After entering the parameters, the execution produces a result.

Commands suite¶
Return to My commands tab: It is possible to save a sequence of commands (with stored parameters), and then execute the same sequence:
Suite creation¶
Click on the add command suite

Add the desired commands and modify the parameters:

The validation generates the suite:

Commands suite execution¶
Clicking on the run button of the suite executes the list of commands it contains:

Custom command creation¶
Click on the Create devtools command button.

Enter the characteristics of the new command:
- The command name
- The command value: name of the main argument
- The command parameters: In case of multiple parameters, use comma as separator
- The command description
- The command aliases: In case of multiple aliases, use comma as separator

Note
Custom commands are created in the app/commands folder of the project.

The generated class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | namespace commands;
use Ubiquity\devtools\cmd\commands\AbstractCustomCommand;
use Ubiquity\devtools\cmd\ConsoleFormatter;
use Ubiquity\devtools\cmd\Parameter;
class CreateArray extends AbstractCustomCommand {
protected function getValue(): string {
return 'jsonValue';
}
protected function getAliases(): array {
return array("createarray","arrayFromJson");
}
protected function getName(): string {
return 'createArray';
}
protected function getParameters(): array {
return ['f' => Parameter::create('fLongName', 'The f description.', [])];
}
protected function getExamples(): array {
return ['Sample use of createArray'=>'Ubiquity createArray jsonValue'];
}
protected function getDescription(): string {
return 'Creates an array from JSON and save to file';
}
public function run($config, $options, $what, ...$otherArgs) {
//TODO implement command behavior
echo ConsoleFormatter::showInfo('Run createArray command');
}
}
|
The CreateArray command implemented:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | namespace commands;
use Ubiquity\devtools\cmd\commands\AbstractCustomCommand;
use Ubiquity\devtools\cmd\ConsoleFormatter;
use Ubiquity\devtools\cmd\Parameter;
use Ubiquity\utils\base\UFileSystem;
class CreateArray extends AbstractCustomCommand {
protected function getValue(): string {
return 'jsonValue';
}
protected function getAliases(): array {
return array(
"createarray",
"arrayFromJson"
);
}
protected function getName(): string {
return 'createArray';
}
protected function getParameters(): array {
return [
'f' => Parameter::create('filename', 'The filename to create.', [])
];
}
protected function getExamples(): array {
return [
'Save an array in test.php' => "Ubiquity createArray \"{\\\"created\\\":true}\" -f=test.php"
];
}
protected function getDescription(): string {
return 'Creates an array from JSON and save to file';
}
public function run($config, $options, $what, ...$otherArgs) {
echo ConsoleFormatter::showInfo('Run createArray command');
$array = \json_decode($what, true);
$error = \json_last_error();
if ($error != 0) {
echo ConsoleFormatter::showMessage(\json_last_error_msg(), 'error');
} else {
$filename = self::getOption($options, 'f', 'filename');
if ($filename != null) {
UFileSystem::save($filename, "<?php\nreturn " . var_export($array, true) . ";\n");
echo ConsoleFormatter::showMessage("$filename succefully created!", 'success', 'CreateArray');
} else {
echo ConsoleFormatter::showMessage("Filename must have a value!", 'error');
}
}
}
}
|
Custom command execution¶
The new command is accessible from the devtools, as long as it is in the project:
Ubiquity help createArray

Ubiquity createArray "{\"b\":true,\"i\":5,\"s\":\"string\"}" -f=test.php

Composer management¶
Note
This part is accessible from the webtools, so if you created your project with the -a option or with the create-project command..
Access¶
From the webtools, activate the composer part,

or go directly to http://127.0.0.1:8090/Admin/composer
.
Dependencies list¶
The interface displays the list of already installed dependencies, and those that are directly installable.

Dependency installation¶
Among the listed dependencies:¶
Click on the add button of the dependencies you want to add.

Then click on the Generate composer update button:

The validation generates the update.
For non listed dependencies:¶
Click on the Add dependency button :

- Enter a vendor name (provider) ;
- Select a package in the list ;
- Select eventually a version (if none, the last stable version will be installed).
Dependency removal¶
Click on the remove button of the dependencies you want to add.

Then click on the Generate composer update button, and validate the update.
Note
It is possible to perform several addition or deletion operations and validate them simultaneously.
Composer optimization¶
Click on the Optimize autoloader button.
This optimize composer autoloading with an authoritative classmap.
Ubiquity Caching¶
Ubiquity dependencies¶
php >=7.4
phpmv/ubiquity
=> Ubiquity core
In production¶
Templating¶
Twig is required if it is used as a template engine, which is not a requirement.
twig/twig
=> Template engine
Security¶
phpmv/ubiquity-security
=> Csrf, Csp…phpmv/ubiquity-acl
=> Access Control List management
In development¶
Webtools¶
phpmv/ubiquity-dev
=> dev classes for webtools and devtools since v2.3.0phpmv/php-mv-ui
=> Front librarymindplay/annotations
=> Annotations library, required for generating models, cache…monolog/monolog
=> Loggingczproject/git-php
=> Git operations (+ require git console)
Devtools¶
phpmv/ubiquity-devtools
=> Cli consolephpmv/ubiquity-dev
=> dev classes for webtools and devtools since v2.3.0mindplay/annotations
=> Annotations library, required for generating models, cache…
Testing¶
codeception/codeception
=> Testscodeception/c3
=> C3 integrationphpmv/ubiquity-codeception
=> Codeception for Ubiquity
OAuth2 client module¶
Note
This part is accessible from the webtools, so if you created your project with the -a option or with the create-project command. The OAuth module is not installed by default. It uses HybridAuth library.
Installation¶
In the root of your project:
composer require phpmv/ubiquity-oauth
Note
It is also possible to add the ubiquity-oauth dependency using the Composer part of the administration module.

OAuth configuration¶
Global configuration¶

Click on the Global configuration button, and modify the callback URL, which corresponds to the local callback url after a successful connection.

OAuth controller¶
Click on the Create Oauth controller button and assign to the route the value previously given to the callback:

Validate and reset the router cache:

Providers¶
Note
For an OAuth authentication, it is necessary to create an application at the provider beforehand, and to take note of the keys of the application (id and secret).
Click on the Add provider button and select Google:

Check the connection by clicking on the Check button:

Post Login Information:

OAuthController customization¶
The controller created is the following:
namespace controllers;
use Hybridauth\Adapter\AdapterInterface;
/**
* Controller OAuthTest
*/
class OAuthTest extends \Ubiquity\controllers\auth\AbstractOAuthController{
public function index(){
}
/**
* @get("oauth/{name}")
*/
public function _oauth(string $name):void {
parent::_oauth($name);
}
protected function onConnect(string $name,AdapterInterface $provider){
//TODO
}
}
- The _oauth method corresponds to the callback url
- The onConnect method is triggered on connection and can be overridden.
Example :
- Possible retrieval of an associated user in the database
- or creation of a new user
- Adding the logged-in user and redirection
protected function onConnect(string $name, AdapterInterface $provider) {
$userProfile = $provider->getUserProfile();
$key = md5($name . $userProfile->identifier);
$user = DAO::getOne(User::class, 'oauth= ?', false, [
$key
]);
if (! isset($user)) {
$user = new User();
$user->setOauth($key);
$user->setLogin($userProfile->displayName);
DAO::save($user);
}
USession::set('activeUser', $user);
\header('location:/');
}
Async platforms¶
Note
Ubiquity supports multiple platforms : Swoole, Workerman, RoadRunner, PHP-PM, ngx_php.
Swoole¶
Install the Swoole extension on your system (linux) or in your Docker image :
#!/bin/bash
pecl install swoole
Run Ubiquity Swoole (for the first time, ubiquity-swoole package will be installed):
Ubiquity serve -t=swoole
Server configuration¶
<?php
return array(
"host" => "0.0.0.0",
"port" => 8080,
"options"=>[
"worker_num" => \swoole_cpu_num() * 2,
"reactor_num" => \swoole_cpu_num() * 2
]
);
The port can also be changed at server startup:
Ubiquity serve -t=swoole -p=8999
Services optimization¶
Startup of services will be done only once, at server startup.
\Ubiquity\cache\CacheManager::startProd($config);
\Ubiquity\orm\DAO::setModelsDatabases([
'models\\Foo' => 'default',
'models\\Bar' => 'default'
]);
\Ubiquity\cache\CacheManager::warmUpControllers([
\controllers\IndexController::class,
\controllers\FooController::class
]);
$swooleServer->on('workerStart', function ($srv) use (&$config) {
\Ubiquity\orm\DAO::startDatabase($config, 'default');
\controllers\IndexController::warmup();
\controllers\FooController::warmup();
});
- The warmUpControllers method:
- instantiates the controllers
- performs dependency injection
- prepares the call of the initialize and finalize methods (initialization of call constants)
At the start of each Worker, the warmup method of the controllers can for example initialize prepared DAO queries:
public static function warmup() {
self::$oneFooDao = new DAOPreparedQueryById('models\\Foo');
self::$allFooDao = new DAOPreparedQueryAll('models\\Foo');
}
Workerman¶
Workerman does not require any special installation (except for libevent to be used in production for performance reasons).
Run Ubiquity Workerman (for the first time, ubiquity-workerman package will be installed):
Ubiquity serve -t=workerman
Server configuration¶
<?php
return array(
"host" => "0.0.0.0",
"port" => 8080,
"socket"=>[
"count" => 4,
"reuseport" =>true
]
);
The port can also be changed at server startup:
Ubiquity serve -t=workerman -p=8999
Services optimization¶
Startup of services will be done only once, at server startup.
\Ubiquity\cache\CacheManager::startProd($config);
\Ubiquity\orm\DAO::setModelsDatabases([
'models\\Foo' => 'default',
'models\\Bar' => 'default'
]);
\Ubiquity\cache\CacheManager::warmUpControllers([
\controllers\IndexController::class,
\controllers\FooController::class
]);
$workerServer->onWorkerStart = function () use ($config) {
\Ubiquity\orm\DAO::startDatabase($config, 'default');
\controllers\IndexController::warmup();
\controllers\FooController::warmup();
});
ngx_php¶
//TODO
Roadrunner¶
//TODO