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
_images/ubi-version.png

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

Ubiquity new quick-start

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:

_images/quick-start-main.png

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
_images/controller-creation.png

We can then edit app/controllers/DefaultController file in our favorite IDE:

app/controllers/DefaultController.php
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

app/controllers/DefaultController.php
     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.

app/controllers/DefaultController.php
 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 console for the cache re-initialization:

Ubiquity init-cache
_images/init-cache.png

Let’s check that the route exists:

Ubiquity info:routes
_images/info-routes.png

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}/
_images/action-creation.png

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

_images/2-routes.png

Change the code in your IDE: the action must say Hello to somebody…

app/controllers/DefaultController.php
     /**
      *@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:

_images/3-routes.png

Let’s go back to our development environment and see the generated code:

app/controllers/DefaultController.php
     /**
      *@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:

app/views/DefaultController/information.html
     <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

_images/quiet-simple.png

Quick start with web tools

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
_images/ubi-version.png

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 UbiquityMyAdmin 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:

_images/quick-start-main.png

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 UbiquityMyAdmin:

_images/ubi-my-admin-btn.png

The web application UbiquityMyAdmin saves time in repetitive operations.

_images/ubi-my-admin-interface.png

We go through it to create a controller.

Go to the controllers part, enter DefaultController in the controllerName field and create the controller:

_images/create-controller-btn.png

The controller DefaultController is created:

_images/controller-created.png

We can then edit app/controllers/DefaultController file in our favorite IDE:

app/controllers/DefaultController.php
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

app/controllers/DefaultController.php
     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.

app/controllers/DefaultController.php
 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

_images/re-init-cache-btn.png

The route now appears in the interface:

_images/1-route.png

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.
_images/create-action-btn.png

Enter the action information in the following form:

_images/create-action.png

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

_images/router-re-init-1.png

Check the route creation by going to the Routes section:

_images/router-re-init-2.png

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

_images/test-action.png

We can see the result:

_images/test-action-result.png

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:

_images/create-action-btn.png

Enter the action information in the following form:

_images/create-action-view.png

Note

The view checkbox is used to create the view associated with the action.

After re-initializing the cache, we now have 3 routes:

_images/create-action-view-result.png

Let’s go back to our development environment and see the generated code:

app/controllers/DefaultController.php
     /**
      *@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:

app/views/DefaultController/information.html
     <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

_images/quiet-simple.png

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

_images/firstProject.png

Main configuration

The main configuration of a project is localised in the app/conf/config.php file.

app/conf/config.php
 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',
             "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.

app/conf/services.php
 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.

.htaccess
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){
                $_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:

app/controllers/Users.php
 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:

app/controllers/Users.php
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:

app/controllers/Users.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
     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:

app/controllers/Users.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
     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

All models creation

For generating all models from the database:

Ubiquity all-models

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 :

app/controllers/Products.php
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 :

app/controllers/Products.php
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 :

app/controllers/Products.php
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 :

app/controllers/Products.php
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)

Routing customization

The Router and annotations of controller classes allow you to customize URLs.

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.

app/config/services.php
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:

app/config/services.php
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 UbiquitycontrollersController or one of its subclasses, and must have an index method:

app/controllers/FooController.php
1
2
3
4
5
6
7
8
     namespace controllers;

     class FooController extends ControllerBase{

             public function index(){
                     echo 'Hello from foo';
             }
     }

Default route

The default route matches the path /.
It can be defined using the reserved path _default

app/config/services.php
1
2
3
     use Ubiquity\controllers\Router;

     Router::addRoute("_default", \controllers\FooController::class,'bar');

Static routes

Static routes are defined using the @route annotation on controller methods.

Note

These annotations are never read at runtime.
It is necessary to reset the router cache to take into account the changes made on the routes.

Creation

app/controllers/ProductsController.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
namespace controllers;
 /**
 * Controller ProductsController
 */
class ProductsController extends ControllerBase{

     /**
     * @route("products")
     */
     public function index(){}

}

The method Products::index() will be accessible via the url /products.

Route parameters

A route can have parameters:

app/controllers/ProductsController.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
namespace controllers;
 /**
 * Controller ProductsController
 */
class ProductsController extends ControllerBase{
     ...
     /**
     * Matches products/*
     *
     * @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:

app/controllers/ProductsController.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
namespace controllers;
 /**
 * Controller ProductsController
 */
class ProductsController extends ControllerBase{
     ...
     /**
     * Matches products/all/(.*?)/(.*?)
     *
     * @route("products/all/{pageNum}/{countPerPage}")
     */
     public function list($pageNum,$countPerPage=50){
             // ...
     }
}

Route requirements

php being an untyped language, it is possible to add specifications on the variables passed in the url via the attribute requirements.

app/controllers/ProductsController.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
namespace controllers;
 /**
 * Controller ProductsController
 */
class ProductsController extends ControllerBase{
     ...
     /**
     * Matches products/all/(\d+)/(\d?)
     *
     * @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

Route http methods

It is possible to specify the http method or methods associated with a route:

app/controllers/ProductsController.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
namespace controllers;
 /**
 * Controller ProductsController
 */
class ProductsController extends ControllerBase{

 /**
 * @route("products","methods"=>["get"])
 */
     public function index(){}

}

The methods attribute can accept several methods:
@route("testMethods","methods"=>["get","post","delete"])

It is also possible to use specific annotations @get, @post
@get("products")

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.

app/controllers/ProductsController.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
namespace controllers;
 /**
 * Controller ProductsController
 **/
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 :

app/controllers/ProductsController.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
namespace controllers;
 /**
 * @route("/product")
 * Controller ProductsController
 */
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 :

app/controllers/ProductsController.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
namespace controllers;
 /**
 * @route("/product","automated"=>true)
 * Controller ProductsController
 */
class ProductsController extends ControllerBase{

     public function generate(){}

     public function display(){}

}
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:

app/controllers/ProductsBase.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
namespace controllers;
 /**
 * Controller ProductsBase
 */
abstract class ProductsBase extends ControllerBase{

     /**
     *@route("(index/)?")
     */
     public function index(){}

     /**
     *@route("sort/{name}")
     */
     public function sortBy($name){}

}

The derived class using inherited attribute:

app/controllers/ProductsController.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
namespace controllers;
 /**
 * @route("/product","inherited"=>true)
 * Controller ProductsController
 */
class ProductsController extends ProductsBase{

     public function display(){}

}
The inherited attribute defines the 2 routes contained 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.

Route priority

The prority parameter of a route allows this route to be resolved more quickly.

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.

app/controllers/ProductsController.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
namespace controllers;
 /**
 * Controller ProductsController
 */
class ProductsController extends ControllerBase{

     /**
     * @route("products","priority"=>1)
     */
     public function index(){}

     /**
     * @route("products/all","priority"=>10)
     */
     public function all(){}

}

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(){}

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(){}

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 possiblity 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
_images/ubi-version.png

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.

app/controllers/Products.php
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.

app/controllers/First.php
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.

app/controllers/First.php
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

app/config/services.php
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.

app/controllers/First.php
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.

app/controllers/First.php
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
app/controllers/Users.php
1
2
3
4
5
6
7
8
9
 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:

app/controllers/First.php
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:

app/views/index.html
<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:

app/controllers/Products.php
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:

app/controllers/ControllerBase.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 );
                     }
             }
     }

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:

app/controllers/IndexController.php
     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:

app/controllers/IndexController.php
     class IndexController extends ControllerBase{
             ...
             public function isValid($action){
                     return USession::exists('activeUser');
             }

             public function onInvalidControl(){
                     $this->initialize();
                     $this->loadView("unauthorized.html");
                     $this->finalize();
             }
     }
app/views/unauthorized.html
     <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

See 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 UPDATE instance, result After updating an object
DAOEvents INSERT instance, result After inserting 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 :

app/config/services.php
 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

app/config/services.php
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 :

app/eventListener/TracePageEventListener.php
 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 :

app/config/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 :

app/controllers/ControllerBase.php
 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 :

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

app/services/Service.php
 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

app/services/Service.php
 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.

app/services/ServiceWithInit.php
 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

app/controllers/ClientController.php
 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.

app/services/RuntimeService.php
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).

app/controllers/MyController.php
 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

Creation

In the admin interface (web-tools), activate the Controllers part, and choose create Crud controller:
_images/speControllerBtn.png
Then fill in the form:
  • Enter the controller name
  • Select the associated model
  • Then click on the validate button
_images/createCrudForm1.png

Description of the features

The generated controller:

app/controllers/Products.php
 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";
     }

     public function _getBaseRoute() {
             return 'UsersController';
     }
}

Test the created controller by clicking on the get button in front of the index action:

_images/getBtn.png

Read (index action)

_images/usersControllerIndex1.png

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

_images/usersControllerIndex1-details.png

Using the search area:

_images/usersControllerSearch1.png

Create (newModel action)

It is possible to create an instance by clicking on the add button

_images/addNewModelBtn.png

The default form for adding an instance of User:

_images/usersControllerNew1.png

Update (update action)

The edit button on each row allows you to edit an instance

_images/editModelBtn.png

The default form for adding an instance of User:

_images/usersControllerEdit1.png

Delete (delete action)

The delete button on each row allows you to edit an instance

_images/deleteModelBtn.png

Display of the confirmation message before deletion:

_images/usersControllerDelete1.png

Customization

Create again a CrudController from the admin interface:

_images/createCrudForm2.png

It is now possible to customize the module using overriding.

Overview

_images/crud-schema.png

Classes overriding

CRUDController 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) 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
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
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 “”

Twig Templates structure

index.html
_images/template_index.png
form.html

Displayed in frm block

_images/template_form.png
display.html

Displayed in frm block

_images/template_display.png

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:
_images/speControllerBtn.png
Then fill in the form:
  • Enter the controller name (BaseAuthController in this case)
_images/createAuthForm1.png

The generated controller:

app/controllers/BaseAuthController.php
 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) {
             return USession::exists($this->_getUserSessionKey());
     }

     public function _getBaseRoute() {
             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:

_images/model-user.png

BaseAuthController modification

app/controllers/BaseAuthController.php
 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) {
             return USession::exists($this->_getUserSessionKey());
     }

     public function _getBaseRoute() {
             return 'BaseAuthController';
     }
     /**
      * {@inheritDoc}
      * @see \Ubiquity\controllers\auth\AuthController::_getLoginInputName()
      */
     public function _getLoginInputName() {
             return "email";
     }
}

Admin controller modification

Modify the Admin Controller to use BaseAuthController:

app/controllers/Admin.php
1
2
3
4
5
6
class Admin extends UbiquityMyAdminBaseController{
     use WithAuthTrait;
     protected function getAuthController(): AuthController {
             return new BaseAuthController();
     }
}

Test the administration interface at /admin:

_images/adminForbidden.png

After clicking on login:

_images/formLogin.png

If the authentication data entered is invalid:

_images/invalidCreditentials.png

If the authentication data entered is valid:

_images/adminWithAuth.png

Attaching the zone info-user

Modify the BaseAuthController controller:

app/controllers/BaseAuthController.php
1
2
3
4
5
6
7
8
9
 /**
 * Auth Controller BaseAuthController
 **/
class BaseAuthController extends \Ubiquity\controllers\auth\AuthController{
...
     public function _displayInfoAsString() {
             return true;
     }
}

The _userInfo area is now present on every page of the administration:

_images/infoUserZone.png

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:

_images/template_authIndex.png

Example with the _userInfo aera:

Create a new AuthController named PersoAuthController:

_images/createAuthForm2.png

Edit the template app/views/PersoAuthController/info.html

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:

app/controllers/Admin.php
1
2
3
4
5
6
class Admin extends UbiquityMyAdminBaseController{
     use WithAuthTrait;
     protected function getAuthController(): AuthController {
             return new PersoAuthController();
     }
}
_images/adminWithAuth2.png

Customizing messages

app/controllers/PersoAuthController.php
 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

app/controllers/PersoAuthController.php
 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

app/controllers/PersoAuthController.php
 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() {
     return 3;
 }
...
}

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
_images/db-config.png

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:

_images/add-new-co-btn.png

Define the connection configuration parameters:

_images/new-co.png

Generate models for the new connection:
The generated models include the @database annotation mentioning their link to the connection.

<?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.

Models generation

From existing database

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 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
app/models/User.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
 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.

app/models/User.php
 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.

app/models/User.php
 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:

_images/manyToOne.png
app/models/User.php
 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 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:

_images/oneToMany.png
app/models/Organization.php
 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.
_images/manyToMany.png
app/models/User.php
 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;

 }
app/models/Group.php
 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.

app/models/Group.php
 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
@table Defines the associated table name.

Annotations for members

@annotation role properties role
@id Defines the primary key(s).
@column Specify the associated field caracteristics. 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;

$user=DAO::getById(models\User::class, 5);

Loading an instance using a condition:

use Ubiquity\orm\DAO;

DAO::getOne(models\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(models\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(models\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(models\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:

app/models/ProductDetail.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 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(models\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(models\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):

_images/uquery-users-log.png

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:

_images/uquery-users-orga-log.png

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::getOne(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.

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.

app/models/Product.php
 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(models\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

Preparation
app/config/swooleServices.php
$swooleServer->on('workerStart', function ($srv) use (&$config) {
     \Ubiquity\orm\DAO::startDatabase($config);
     \Ubiquity\orm\DAO::prepareGetById('user', models\User::class);
     \Ubiquity\orm\DAO::prepareGetAll('productsByName', models\Product::class,'name like ?');
});
Usage
app/controllers/UsersController.php
public function displayUser($idUser){
     $user=DAO::executePrepared('user',[1]);
     echo $user->getName();
}

public function displayProducts($name){
     $products=DAO::executePrepared('productsByName',[$name]);
     ...
}

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.

app/controllers/Users.php
 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.

app/controllers/Users.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 namespace controllers;

 use Ubiquity\orm\DAO;
 use Uniquity\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:

app/controllers/Users.php
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');

Cache

Forcing the disabling of the browser cache:

UResponse::noCache();

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
}

Deleting variables

The delete method remove a session variable:

USession::delete("name");

Explicit closing of the session

The terminate method closes the session correctly and deletes all session variables created:

USession::terminate();

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:

app/controllers/Users.php
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
app/controllers/Users.php
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.

app/controllers/Users.php
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 :

app/controllers/Users.php
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:

users/display.html
 <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 of Framework 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:

app/controllers/Users.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 namespace controllers;

 use Ubiquiy\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

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) :

_images/themesManager-install-theme.png

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 :

_images/themesManager-create-theme.png

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 :

app/controllers/ControllerBase.php
 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 :

_images/change-theme-home.png

From the webtools :

_images/change-theme-webtools.png

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 :

app/controllers/Users.php
 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 :

app/controllers/Users.php
 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

app/config/services.php
 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.

Global mobile detection (from services.php)
app/config/services.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use Ubiquity\themes\ThemesManager;

...

ThemesManager::onBeforeRender(function () {
     $mb = new \Mobile_Detect();
     if ($mb->isMobile()) {
             ThemesManager::setActiveTheme('mobile');
     }
});
Locale detection (from a controller)
app/controllers/FooController.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use Ubiquity\themes\ThemesManager;

...

     public function initialize() {
             $mb = new \Mobile_Detect();
             if ($mb->isMobile()) {
                     ThemesManager::setActiveTheme('mobile');
             }
             parent::initialize();
     }

View and assets loading

Views

For loading a view from the activeTheme folder, you can use the @activeTheme namespace :

app/controllers/Users.php
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.

app/controllers/Users.php
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') }}

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:

app/config.php
...
"di"=>array(
             "@exec"=>array(
                             "jquery"=>function ($controller){
                                     return \Ubiquity\core\Framework::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:

app/controllers/FooController.php
 /**
 * 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:

app/controllers/FooController.php
 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:

app/views/FooController/index.html
     <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)

app/controllers/FooController.php
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");
     }
     ...
}
app/views/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:

app/views/FooController/index.html
<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:

app/controllers/FooController.php
namespace controllers;

class FooController extends ControllerBase {
     ...
     /**
      *@get("c","name"=>"action.c")
      */
     public function cAction() {
             echo \rand(0, 1000);
     }
}

The associated view:

app/views/FooController/index.html
<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 }}
_images/fooController.png
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.
app/controllers/FooController.php
 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
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `user` (`id`, `firstname`, `lastname`) VALUES
(1, 'You', 'Evan'),
(2, 'Potencier', 'Fabien'),
(3, 'Otwell', 'Taylor');

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:

_images/UsersJqueryControllerStructure.png
Index action

The index action must display a button to obtain the list of users, loaded via an ajax request:

app/controllers/UsersJqueryController.php
 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:

app/views/UsersJqueryController/index.html
<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:

app/controllers/UsersJqueryController.php
 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:

app/views/UsersJqueryController/displayUsers.html
<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
app/controllers/UsersJqueryController.php
 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:

app/views/UsersJqueryController/displayUsers.html
<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

//todo HtmlButton sample +++++++++++++++++

//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 :

app/models/Author.php
 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
app/models/Author.php
 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
_images/info-validation-devtools.png

Gets validators on email field:

Ubiquity info:validation email -m=User
_images/info-validation-email-devtools.png

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

_images/info-validation-webtools.png

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 :

app/models/Author.php
 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
_images/trans-info.png

Using transformers

Start the TransformersManager in the file app/config/services.php:

app/config/services.php
\Ubiquity\contents\transformation\TransformersManager::startProd();

You can test the result in the administration interface:

_images/trans-upper.png

or by creating a controller:

app/controllers/Authors.php
 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]);
             }

     }
app/views/Authors/index.html
     <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:

app/transformers/toLocalEmail.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
     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

app/models/User.php
 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
app/translations/en_EN/messages.php
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.

app/config/services.php
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.

Setting the locale

Changing the locale when the manager starts:

app/config/services.php
1
2
Ubiquity\cache\CacheManager::startProd($config);
Ubiquity\translation\TranslatorManager::start('fr_FR');

Changing the locale after loading the manager:

TranslatorManager::setLocale('fr_FR');

Setting the fallbackLocale

The en_EN locale will be used if fr_FR is not found:

app/config/services.php
1
2
Ubiquity\cache\CacheManager::startProd($config);
Ubiquity\translation\TranslatorManager::start('fr_FR','en_EN');

Defining the root translations dir

If the rootDir parameter is missing, the default directory used is app/translations.

app/config/services.php
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:

app/translations/en_EN/messages.php
['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) }}

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:

app/config/services.php
...
Router::start();

To enable REST routes in an application that also has a non-REST part:

app/config/services.php
...
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):

app/config/services.php
...
     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:

_images/addNewResource.png

The created controller :

app/controllers/RestUsersController.php
 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:

_images/createdResource.png
Getting an instance

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

_images/getOneResource.png

Inclusion of associated members: the organization of the user

_images/getOneResourceInclude.png

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

_images/getOneResourceIncludeAll.png
Getting multiple instances

Getting all instances:

_images/getAllOrgas.png

Setting a condition:

_images/condition-orgas.png

Including associated members:

_images/include-orgas.png
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:

_images/post-parameters.png

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

_images/unauthorized-post.png

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

_images/connect.png

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

_images/added.png
Updating an instance

The update follows the same scheme as the insertion.

Deleting an instance
_images/delete-instance.png

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.

_images/token.png

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).

app/controllers/RestOrgas.php
 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) :

app/controllers/RestOrgas.php
 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:

_images/connected-user.png

Customizing

Api tokens

It is possible to customize the token generation, by overriding the getRestServer method:

app/controllers/RestOrgas.php
 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

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.

app/controllers/RestOrgas.php
 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:

app/controllers/RestOrgas.php
 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
app/controllers/RestOrgas.php
 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:

app/controllers/RestOrgas.php
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:

_images/getOneFormatted.png _images/getFormatted.png

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:

_images/jsonapi-creation.png

Test the api in webtools:

_images/jsonapi-admin.png
Getting an array of objects

By default, all associated members are included:

_images/getAll.png
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
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:

_images/add-parameters.png

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

_images/add-response.png
Deleting an instance

Deletion requires the DELETE method, and the use of the id of the object to be deleted:

_images/delete-response.png

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.

Installation

Update the devtools if necessary to get started:

composer global update

At the project creation

Create a projet with webtools (-a option)

Ubiquity new quick-start -a

In an existing project

In a console, go to the project folder and execute:

Ubiquity admin

Starting

Start the embedded web server, from the project folder:

Ubiquity serve

go to the address: http://127.0.0.1:8090/Admin

_images/interface.png

Customizing

Click on customize to display only the tools you use:

_images/customizing.png _images/customized.png

Webtools modules

Routes

_images/routes.png

Displays default (non REST) routes.

Operations:

  • Filter routes
  • Test routes (GET, POST…)
  • Initialize router cache

Controllers

_images/controllers.png

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

_images/models.png

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

_images/rest.png

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

_images/cache.png

Displays cache files.

Operations:

  • Delete or re-initialize models cache
  • Delete or re-initialize controllers cache
  • Delete other cache files

Maintenance

_images/maintenance.png

Allows to manage maintenance modes.

Operations:

  • Create or update a maintenance mode
  • De/Activate a maintenance mode
  • Delete a maintenance mode

Config

_images/config.png

Allows the display and modification of the app configuration.

Git

_images/git.png

Synchronizes the project using git.

Operations:

  • Configuration with external repositories
  • Commit
  • Push
  • Pull
_images/themes.png

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, and
    • PATCH 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:

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 );
                }
        }
}

Important

You can import this standardization files that integrates all these rules in your IDE:

If your preferred IDE is not listed, you can submit the associated standardization file by creating a new PR.

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.

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,

_images/composer-elm.png

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.

_images/composer-dependencies.png

Dependency installation

Among the listed dependencies:

Click on the add button of the dependencies you want to add.

_images/composer-add-1.png

Then click on the Generate composer update button:

_images/composer-add-2.png

The validation generates the update.

For non listed dependencies:

Click on the Add dependency button :

_images/composer-add-dependency.png
  • 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.

_images/composer-remove-1.png

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

In development

Webtools

  • phpmv/ubiquity-dev => dev classes for webtools and devtools since v2.3.0
  • phpmv/php-mv-ui => Front library
  • mindplay/annotations => Annotations library, required for generating models, cache…
  • monolog/monolog => Logging
  • czproject/git-php => Git operations (+ require git console)

Devtools

  • phpmv/ubiquity-devtools => Cli console
  • phpmv/ubiquity-dev => dev classes for webtools and devtools since v2.3.0
  • mindplay/annotations => Annotations library, required for generating models, cache…

Testing

  • codeception/codeception => Tests
  • codeception/c3 => C3 integration
  • phpmv/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.

_images/composer-add-1.png

OAuth configuration

Global configuration

_images/oauth-part-0.png

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

_images/oauth-part-callback.png

OAuth controller

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

_images/create-oauth-controller.png

Validate and reset the router cache:

_images/create-oauth-controller-created.png

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:

_images/provider-config.png

Check the connection by clicking on the Check button:

_images/google-check.png

Post Login Information:

_images/google-check-infos.png

OAuthController customization

The controller created is the following:

app/controllers/OAuthTest.php
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
app/controllers/OAuthTest.php
     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:/');
     }

Indices and tables