Guide d’utilisation Ubiquity
Quick start avec la console
Note
Si vous n’aimez pas le mode console, vous pouvez passer au quick-start avec les webtools (UbiquityMyAdmin).
Composer installation
ubiquity utilise Composer pour gérer ses dépendances. Vous devrez donc vous assurer que vous avez Composer installé sur votre machine.
Installation d’Ubiquity devtools
Télécharger l’installeur Ubiquity-devtools avec Composer.
composer global require phpmv/ubiquity-devtools
Testez votre installation en faisant :
Ubiquity version

Vous pouvez obtenir à tout moment de l’aide sur une commande en tapant : Ubiquity help
suivi de ce que vous cherchez.
Exemple :
Ubiquity help project
Création de projet
Créer le projet quick-start
Ubiquity new quick-start
Structure des dossiers
Le projet créé dans le dossier quick-start a une structure simple et lisible :
Le dossier app contient le code de votre future application :
app
├ cache
├ config
├ controllers
├ models
└ views
Démarrage
Allez dans le dossier nouvellement créé quick-start et démarrez le serveur php intégré :
Ubiquity serve
Vérifier l’opération à l’adresse http://127.0.0.1:8090 :

Note
Si le port 8090 est occupé, vous pouvez démarrer le serveur en utilisant un autre port en utilisant l’option -p.
Ubiquity serve -p=8095
Contrôleur
L’application en mode console devtools permet de gagner du temps dans les opérations répétitives. Utilisons la pour créer un contrôleur.
Ubiquity controller DefaultController

Vous pouvez éditer le fichier app/controllers/DefaultController
dans votre IDE :
1namespace controllers;
2 /**
3 * Controller DefaultController
4 */
5class DefaultController extends ControllerBase{
6 public function index(){}
7}
Ajouter le message traditionnel, et tester votre page à l’adresse http://127.0.0.1:8090/DefaultController
class DefaultController extends ControllerBase{
public function index(){
echo 'Hello world!';
}
}
Pour l’instant, nous n’avons pas défini de routes,
L’accès à l’application se fait donc selon le schéma suivant :
controllerName/actionName/param
L’action par défaut est la méthode index, elle n’a pas besoin d’être spécifiée dans l’url.
Route
Important
Le routage est défini avec l’attribut Route
(avec php>8) ou l’annotation @route
et n’est pas fait dans un fichier de configuration :
c’est un choix de conception.
Le paramètre automated mis à true permet aux méthodes de notre classe d’être définies comme des sous routes de la route principale /hello
.
Avec annotations :
1namespace controllers;
2/**
3 * Controller DefaultController
4 * @route("/hello","automated"=>true)
5 */
6class DefaultController extends ControllerBase{
7
8 public function index(){
9 echo 'Hello world!';
10 }
11
12}
Avec attributs (php>8) :
1namespace controllers;
2use Ubiquity\attributes\items\router\Route;
3
4#[Route('/hello', automated: true)]
5class DefaultController extends ControllerBase{
6
7 public function index(){
8 echo 'Hello world!';
9 }
10
11}
Cache du routeur
Important
Aucune modification sur les routes n’est effective sans initialiser le cache.
Les annotations ne sont jamais lues au moment de l’exécution. C’est également un choix de conception.
Nous pouvons utiliser la console pour ré-initialiser le cache :
Ubiquity init-cache

Vérifions que la route existe :
Ubiquity info:routes

Nous pouvons maintenant tester la page http://127.0.0.1:8090/hello
Action et route avec paramètres
Nous allons maintenant créer une action (sayHello) avec un paramètre (name), et la route associée (to) :
La route utilisera le paramètre name de l’action :
Ubiquity action DefaultController.sayHello -p=name -r=to/{name}/

Après ré-initialisation du cache (commande init-cache), la commande info:routes devrait afficher :

Changer le code dans votre IDE : l’action doit dire hello à quelqu’un (somebody)…
/**
* @route("to/{name}/")
*/
public function sayHello($name){
echo 'Hello '.$name.'!';
}
et tester la page http://127.0.0.1:8090/hello/to/Mr SMITH
Action, route, paramètres et vue
Nous allons maintenant créer une action (information) avec deux paramètres (titre et message), la route associée (info), et une vue pour afficher le message :
La route utilisera les deux paramètres de l’action.
Ubiquity action DefaultController.information -p=title,message='nothing' -r=info/{title}/{message} -v
Note
Le paramètre -v (–view) est utiliser pour créer la vue associée à l’action.
Après ré-initialisation du cache, nous devrions avoir 3 routes :

Retournons à notre environnement de développement pour voir le code généré :
/**
* @route("info/{title}/{message}")
*/
public function information($title,$message='nothing'){
$this->loadView('DefaultController/information.html');
}
Nous devons passer les 2 variables à la vue :
/**
* @route("info/{title}/{message}")
*/
public function information($title,$message='nothing'){
$this->loadView('DefaultController/information.html',compact('title','message'));
}
Et utiliser ces 2 variables dans la vue twig associée :
<h1>{{title}}</h1>
<div>{{message | raw}}</div>
Nous pouvons tester votre page à l’addresse http://127.0.0.1:8090/hello/info/Quick start/Ubiquity est super simple
une évidence.

Quick start avec les Webtools
Installation de Composer
ubiquity utilise Composer pour gérer ses dépendances. Vous devrez donc vous assurer que vous avez Composer installé sur votre machine.
Installation d’Ubiquity-devtools
Télécharger l’installeur Ubiquity-devtools avec Composer.
composer global require phpmv/ubiquity-devtools
Testez votre installation en faisant :
Ubiquity version

Vous pouvez obtenir à tout moment de l’aide sur une commande en tapant : Ubiquity help
suivi de ce que vous cherchez.
Exemple :
Ubiquity help project
Création de projet
Créer le projet quick-start incluant les Webtools (avec l’option -a)
Ubiquity new quick-start -a
Structure des dossiers
Le projet créé dans le dossier quick-start a une structure simple et lisible :
Le dossier app contient le code de votre future application :
app
├ cache
├ config
├ controllers
├ models
└ views
Démarrage
Allez dans le dossier nouvellement créé quick-start et démarrez le serveur php intégré :
Ubiquity serve
Vérifier l’opération à l’adresse http://127.0.0.1:8090 :

Note
Si le port 8090 est occupé, vous pouvez démarrer le serveur en utilisant un autre port en utilisant l’option -p.
Ubiquity serve -p=8095
Contrôleur
Naviguer vers l’interface d’administration en cliquant sur le bouton Webtools :

Sélectionner les outils dont vous avez besoin :

L’application web Webtools permet de gagner du temps sur les opérations répétitives.

Nous allons les utiliser pour créer un contrôleur.
Aller dans la partie Controllers, saisir DefaultController dans la zone controllerName et créer le contrôleur :

Le contrôleur DefaultController est créé :

Vous pouvez éditer le fichier app/controllers/DefaultController
dans votre IDE :
1namespace controllers;
2 /**
3 * Controller DefaultController
4 **/
5class DefaultController extends ControllerBase{
6 public function index(){}
7}
Ajouter le traditionnel Hello world, et tester votre page à l’adresse http://127.0.0.1:8090/DefaultController
class DefaultController extends ControllerBase{
public function index(){
echo 'Hello world!';
}
}
Pour l’instant, nous n’avons pas défini de routes,
L’accès à l’application se fait donc selon le schéma suivant :
controllerName/actionName/param
L’action par défaut est la méthode index, elle n’a pas besoin d’être spécifiée dans l’url.
Route
Important
Le routage est défini avec l’attribut Route
(avec php>8) ou l’annotation @route
et n’est pas fait dans un fichier de configuration :
c’est un choix de conception.
Le paramètre automated mis à true permet aux méthodes de notre classe d’être définies comme des sous routes de la route principale /hello
.
1 namespace controllers;
2 /**
3 * Controller DefaultController
4 * @route("/hello","automated"=>true)
5 **/
6 class DefaultController extends ControllerBase{
7
8 public function index(){
9 echo 'Hello world!';
10 }
11
12 }
Cache du routeur
Important
Aucune modification sur les routes n’est effective sans initialiser le cache.
Les annotations ne sont jamais lues au moment de l’exécution. C’est également un choix de conception.
Nous pouvons utiliser les webtools pour ré-initialiser le cache :
Allez dans la partie Routes et cliquer sur le bouton re-init cache

La route est maintenant listée dans l’interface :

Nous pouvons maintenant tester la page en cliquant sur le bouton GET ou en allant à l’adresse http://127.0.0.1:8090/hello
.
Action & route avec paramètres
Nous allons maintenant créer une action (sayHello) avec un paramètre (name), et la route associée (to) :
La route utilisera le paramètre name de l’action :
Aller à la section Controllers :
cliquer sur le bouton + associé à DefaultController,
puis sélectionner l’élément Add new action in..,

Saisir les caractéristiques de l’action dans le formulaire suivant :

Après avoir réinitialisé le cache avec le bouton orange, nous pouvons voir la nouvelle route hello/to/{name} :

Vérifiez la création de la route en allant dans la section Routes :

Nous pouvons maintenant tester la page en cliquant sur le bouton GET :

Nous pouvons voir le résultat :

Aller directement à l’adresse « http://127.0.0.1:8090/hello/to/Mr SMITH » pour tester
Action, paramètres de route & vue
Nous allons maintenant créer une action (information) avec deux paramètres (titre et message), la route associée (info), et une vue pour afficher le message :
La route utilisera les deux paramètres de l’action.
Dans la section Contrôleurs, créer une autre action dans DefaultController :

Saisir les caractéristiques de l’action dans le formulaire suivant :

Note
La case à cocher view d’activer la création de la vue associée à l’action.
Après avoir réinitialisé le cache, nous avons maintenant 3 routes :

Retournons dans notre environnement de développement et voyons le code généré :
/**
*@route("info/{title}/{message}")
**/
public function information($title,$message='nothing'){
$this->loadView('DefaultController/information.html');
}
Nous devons passer les 2 variables à la vue :
/**
*@route("info/{title}/{message}")
**/
public function information($title,$message='nothing'){
$this->loadView('DefaultController/information.html',compact('title','message'));
}
Et nous utilisons nos 2 variables dans la vue twig associée :
<h1>{{title}}</h1>
<div>{{message | raw}}</div>
Nous pouvons tester notre page à l’adresse http://127.0.0.1:8090/hello/info/Quick start/Ubiquity is quiet simple
;-)

Installation d’ubiquity-devtools
Composer installation
ubiquity utilise Composer pour gérer ses dépendances. Vous devrez donc vous assurer que vous avez Composer installé sur votre machine.
Installer Ubiquity-devtools
Téléchargez l’installateur Ubiquity-devtools en utilisant Composer.
composer global require phpmv/ubiquity-devtools
Assurez-vous de placer le répertoire ~/.composer/vendor/bin
dans votre PATH afin que l’exécutable Ubiquity puisse être localisé par votre système.
Une fois installé, la commande Ubiquity new
crée une nouvelle installation d’Ubiquity dans le répertoire que vous avez spécifié. Par exemple, Ubiquity new blog
crée un répertoire nommé blog contenant un projet Ubiquity :
Ubiquity new blog
L’option semantic ajoute Semantic-UI pour le front-end.
Vous pouvez voir plus d’options sur l’installation en lisant la section Création de projet.
Création de projet
Après avoir installé Installation d’ubiquity-devtools, dans votre terminal, utilisez la commande new depuis le dossier racine de votre serveur web :
Exemples
Un simple projet
Ubiquity new projectName
Un projet avec les webtools
Ubiquity new projectName -a
Un projet avec les thèmes Bootstrap et Semantic-ui installés
Ubiquity new projectName --themes=bootstrap,semantic
Arguments de l’installeur
nom court |
nom |
rôle |
valeur par défaut |
valeurs autorisées |
Depuis les devtools |
---|---|---|---|---|---|
b |
dbName |
Définit le nom de la base de données |
|||
s |
serverName |
Définit l’adresse du serveur de base de données |
127.0.0.1 |
||
p |
port |
Définit le port de la base de données |
3306 |
||
u |
user |
Définit l’utilisateur de la base de données |
root |
||
w |
password |
Définit le mot de passe d’accès à la base de données |
“” |
||
h |
themes |
Installe les thèmes. |
semantic,bootstrap,foundation |
||
m |
all-models |
Crée l’ensemble des modèles depuis la base de données. |
false |
||
a |
admin |
Ajoute les webtools. |
false |
||
i |
siteUrl |
Définit l’URL du site. |
http://127.0.0.1/{projectname} |
1.2.6 |
|
e |
rewriteBase |
Définit la base de la ré-écriture d’urls. |
/{projectname}/ |
1.2.6 |
Usage des arguments
noms courts
Exemple de création du projet blog, connecté à la base de données blogDb, avec génération de tous les modèles
Ubiquity new blog -b=blogDb -m=true
noms longs
Exemple de création du projet blog, connecté à la base de données bogDb, avec génération de tous les modèles et intégration du thème sémantic
Ubiquity new blog --dbName=blogDb --all-models=true --themes=semantic
Lancement
Pour démarrer le serveur web intégré et tester vos pages, exécutez à partir du dossier racine de l’application :
Ubiquity serve
Le serveur web est démarré à l’adresse 127.0.0.1:8090
Configuration de projet
Normalement, le programme d’installation limite les modifications à apporter aux fichiers de configuration et votre application est opérationnelle après l’installation.

Configuration principale
La configuration principale d’un projet est localisée dans le fichier app/conf/config.php
.
1return array(
2 "siteUrl"=>"%siteUrl%",
3 "database"=>[
4 "dbName"=>"%dbName%",
5 "serverName"=>"%serverName%",
6 "port"=>"%port%",
7 "user"=>"%user%",
8 "password"=>"%password%"
9 ],
10 "namespaces"=>[],
11 "templateEngine"=>'Ubiquity\views\engine\twig\Twig',
12 "templateEngineOptions"=>array("cache"=>false),
13 "test"=>false,
14 "debug"=>false,
15 "di"=>[%injections%],
16 "cacheDirectory"=>"cache/",
17 "mvcNS"=>["models"=>"models","controllers"=>"controllers"]
18);
Configuration des services
Les services chargés au démarrage sont configurés dans le fichier app/conf/services.php
.
1 use Ubiquity\controllers\Router;
2
3 try{
4 \Ubiquity\cache\CacheManager::startProd($config);
5 }catch(Exception $e){
6 //Do something
7 }
8 \Ubiquity\orm\DAO::startDatabase($config);
9 Router::start();
10 Router::addRoute("_default", "controllers\\IndexController");
URLs sympas
Apache
Le framework est livré avec un fichier .htaccess qui est utilisé pour autoriser les URLs sans index.php. Si vous utilisez Apache comme serveur pour votre application Ubiquity, assurez-vous d’activer le module mod_rewrite.
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>
Voir Apache configuration pour des détails supplémentaires.
Nginx
Avec Nginx, la directive suivante dans la configuration de votre site autorisera les URL « sympas » :
location /{
rewrite ^/(.*)$ /index.php?c=$1 last;
}
Voir NginX configuration pour des informations supplémentaires.
Driver Laravel Valet
Valet est un environnement de développement php pour les adeptes Mac. Pas de Vagrant
, pas de fichier /etc/hosts
. Vous pouvez même partager vos sites publiquement en utilisant des tunnels locaux.
Laravel Valet configure votre Mac pour qu’il exécute toujours Nginx
en arrière-plan au démarrage de votre machine. Ensuite, en utilisant DnsMasq
, Valet renvoie toutes les requêtes sur le domaine *.test
pour pointer vers des sites installés sur votre machine locale.
Plus d’informations sur Laravel Valet
Créez UbiquityValetDriver.php
sous ~/.config/valet/Drivers/
ajoutez le code php ci-dessous et sauvegardez-le.
<?php
class UbiquityValetDriver extends BasicValetDriver{
/**
* Determine if the driver serves the request.
*
* @param string $sitePath
* @param string $siteName
* @param string $uri
* @return bool
*/
public function serves($sitePath, $siteName, $uri){
if(is_dir($sitePath . DIRECTORY_SEPARATOR . '.ubiquity')) {
return true;
}
return false;
}
public function isStaticFile($sitePath, $siteName, $uri){
if(is_file($sitePath . $uri)) {
return $sitePath . $uri;
}
return false;
}
/**
* Get the fully resolved path to the application's front controller.
*
* @param string $sitePath
* @param string $siteName
* @param string $uri
* @return string
*/
public function frontControllerPath($sitePath, $siteName, $uri){
$sitePath.='/public';
$_SERVER['DOCUMENT_ROOT'] = $sitePath;
$_SERVER['SCRIPT_NAME'] = '/index.php';
$_SERVER['SCRIPT_FILENAME'] = $sitePath . '/index.php';
$_SERVER['DOCUMENT_URI'] = $sitePath . '/index.php';
$_SERVER['PHP_SELF'] = '/index.php';
$_GET['c'] = '';
if($uri) {
$_GET['c'] = ltrim($uri, '/');
$_SERVER['PHP_SELF'] = $_SERVER['PHP_SELF']. $uri;
$_SERVER['PATH_INFO'] = $uri;
}
$indexPath = $sitePath . '/index.php';
if(file_exists($indexPath)) {
return $indexPath;
}
}
}
Utilisation des devtools
Création de projet
Voir Création de projet pour créer un projet.
Astuce
Pour toutes les autres commandes, vous devez vous trouver dans le dossier de votre projet ou dans l’un de ses sous-dossiers.
Important
Le dossier .ubiquity
créé automatiquement avec le projet permet aux devtools de trouver le dossier racine du projet.
S’il a été supprimé ou n’est plus présent, vous devez recréer ce dossier vide.
Création de contrôleur
Spécifications
commande :
controller
Argument :
controller-name
aliases :
create-controller
Paramètres
nom court |
nom |
rôle |
valeur par défaut |
valeurs autorisées |
---|---|---|---|---|
v |
view |
Crée la vue index associée. |
true |
true, false |
Exemples :
Crée la classe contrôleur controllers\ClientController
dans app/controllers/ClientController.php
:
Ubiquity controller ClientController
Crée la classe contrôleur controllers\ClientController
dans app/controllers/ClientController.php
et la vue associée dans app/views/ClientController/index.html
:
Ubiquity controller ClientController -v
Création d’action
Spécifications
commande :
action
Argument :
controller-name.action-name
aliases :
new-action
Paramètres
nom court |
nom |
rôle |
valeur par défaut |
valeurs autorisées |
---|---|---|---|---|
p |
params |
Les paramètres (ou arguments) de l’action. |
a,b=5 or $a,$b,$c |
|
r |
route |
Le path de route associé |
/path/to/route |
|
v |
create-view |
Crée la vue associée. |
false |
true,false |
Exemples :
Ajoute l’action « all » dans le contrôleur « Users » :
Ubiquity action Users.all
Résultat :
1namespace controllers;
2/**
3 * Controller Users
4 */
5class Users extends ControllerBase{
6
7 public function index(){}
8
9 public function all(){
10
11 }
12
13}
Ajoute l’action display
dans le contrôleur Users
avec un paramètre :
Ubiquity action Users.display -p=idUser
Résultat :
1class Users extends ControllerBase{
2
3 public function index(){}
4
5 public function display($idUser){
6
7 }
8}
Ajoute l’action « display » avec une route associée :
Ubiquity action Users.display -p=idUser -r=/users/display/{idUser}
Résultat :
1namespace controllers;
2
3use Ubiquity\attributes\items\router\Route;
4
5class Users extends ControllerBase{
6
7 public function index(){}
8
9 #[Route('/users/display/{idUser}')]
10 public function display($idUser){
11
12 }
13}
1namespace controllers;
2
3class Users extends ControllerBase{
4
5 public function index(){}
6
7 /**
8 *@route("/users/display/{idUser}")
9 */
10 public function display($idUser){
11
12 }
13}
Ajoute l’action search
avec plusieurs paramètres :
Ubiquity action Users.search -p=name,address=''
Résultat :
1namespace controllers;
2
3use Ubiquity\attributes\items\router\Route;
4
5class Users extends ControllerBase{
6
7 public function index(){}
8
9 #[Route('/users/display/{idUser}')]
10 public function display($idUser){
11
12 }
13
14 public function search($name,$address=''){
15
16 }
17}
1namespace controllers;
2
3class Users extends ControllerBase{
4
5 public function index(){}
6
7 /**
8 * @route("/users/display/{idUser}")
9 */
10 public function display($idUser){
11
12 }
13
14 public function search($name,$address=''){
15
16 }
17}
Ajoute l’action search
et crée la vue associée :
Ubiquity action Users.search -p=name,address -v
Création de modèle
Note
Vous pouvez vérifier les paramètres de connexion à la base de données dans le fichier app/config/config.php avant d’exécuter ces commandes.
Pour générer un modèle correspondant à la table user dans la base de données :
Ubiquity model user
Création de tous les modèles
Pour générer tous les modèles à partir de la base de données :
Ubiquity all-models
Initialisation du cache
Pour initialiser le cache du routeur (basé sur les annotations dans les contrôleurs) et de l’ORM (basé sur les annotations dans les modèles) :
Ubiquity init-cache
URLs
Comme de nombreux autres frameworks, si vous utilisez le routeur avec son comportement par défaut, il existe une relation biunivoque entre une chaîne d’URL et la classe/méthode de contrôleur correspondante. Les segments d’une URI suivent normalement ce modèle : : :
example.com/controller/method/param
example.com/controller/method/param1/param2
Méthode par défaut
Lorsque l’URL est composée d’une seule partie, correspondant au nom d’un contrôleur, la méthode index du contrôleur est automatiquement appelée :
URL :
example.com/Products
example.com/Products/index
Controller :
1class Products extends ControllerBase{
2 public function index(){
3 //Default action
4 }
5}
Paramètres requis
Si la méthode sollicitée a des paramètres obligatoires, ils doivent être passés dans l’URL :
Controller :
1class Products extends ControllerBase{
2 public function display($id){}
3}
Urls valides :
example.com/Products/display/1
example.com/Products/display/10/
example.com/Products/display/ECS
Paramètres optionnels
La méthode appelée peut avoir des paramètres optionnels.
Si un paramètre n’est pas présent dans l’URL, la valeur par défaut du paramètre est utilisée.
Controller :
class Products extends ControllerBase{
public function sort($field, $order='ASC'){}
}
Urls valides :
example.com/Products/sort/name (uses "ASC" for the second parameter)
example.com/Products/sort/name/DESC
example.com/Products/sort/name/ASC
Sensibilité à la casse
Sur les systèmes Unix, le nom des contrôleurs est sensible à la casse.
Controller :
class Products extends ControllerBase{
public function caseInsensitive(){}
}
Urls :
example.com/Products/caseInsensitive (valid)
example.com/Products/caseinsensitive (valid because the method names are case insensitive)
example.com/products/caseInsensitive (invalid since the products controller does not exist)
Personnalisation du routage
Le Routeur et les attributs ou annotations dans les classes contrôleur permettent de personnaliser les URLs.
Routeur
Le routage peut être utilisé en plus du mécanisme par défaut qui associe controller/action/{paramètres}
à une url.
Routes dynamiques
Les routes dynamiques sont définies à l’exécution.
Il est possible de les déclarer dans le fichier app/config/services.php.
Important
Les routes dynamiques ne devraient être utilisées que si la situation l’exige :
Dans le cas d’une micro-application
Si une route doit être définie dynamiquement
Dans tous les autres cas, il est conseillé de déclarer les routes avec des annotations ou attributs, afin de bénéficier du cache.
Callback routes
Les routes Ubiquity les plus basiques sont définies à partir d’une Closure.
Dans le contexte des micro-applications, cette méthode évite de devoir créer un contrôleur.
1 use Ubiquity\controllers\Router;
2
3 Router::get("foo", function(){
4 echo 'Hello world!';
5 });
Les routes de type callback peuvent être définies pour toutes les méthodes http :
Router::post
Router::put
Router::delete
Router::patch
Router::options
Controller routes
Les routes peuvent aussi être définies de manière plus conventionnelle à partir de l’action d’un contrôleur :
1 use Ubiquity\controllers\Router;
2
3 Router::addRoute('bar', \controllers\FooController::class,'index');
La méthode FooController::index()
sera accessible via l’url /bar
.
Dans ce cas, le contrôleur FooController doit hériter de Ubiquity\controllers\Controller ou l’un de ces dérivés, et doit implémenter la méthode index :
1 namespace controllers;
2
3 class FooController extends ControllerBase{
4
5 public function index(){
6 echo 'Hello from foo';
7 }
8 }
Route par défaut
La route par défaut correspond au path /.
Elle peut être définie en utilisant le path réservé _default
1 use Ubiquity\controllers\Router;
2
3 Router::addRoute("_default", \controllers\FooController::class,'bar');
Routes statiques
Les routes statiques sont définies via annotations ou en utilisant les attribut php natifs depuis Ubiquity 2.4.0
.
Note
Ces annotations ou attributs ne sont jamais lus à l’exécution.
Il est nécessaire de ré-initialiser le cache du router pour prendre en compte les changements effectués sur les routes.
Création
1namespace controllers;
2
3use Ubiquity\attributes\items\router\Route;
4
5class ProductsController extends ControllerBase{
6
7 #[Route('products')]
8 public function index(){}
9
10}
1namespace controllers;
2
3class ProductsController extends ControllerBase{
4
5 /**
6 * @route("products")
7 */
8 public function index(){}
9
10}
La méthode Products::index()
sera accessible par l’url /products
.
Note
- Les slash initial ou terminal sont ignorés dans le path. Les routes suivantes sont donc équivalentes :
#[Route('products')]
#[Route('/products')]
#[Route('/products/')]
Paramètres de routes
Une route peut avoir des paramètres :
1namespace controllers;
2
3use Ubiquity\attributes\items\router\Route;
4
5class ProductsController extends ControllerBase{
6...
7 #[Route('products/{value}')]
8 public function search($value){
9 // $value will equal the dynamic part of the URL
10 // e.g. at /products/brocolis, then $value='brocolis'
11 // ...
12 }
13}
1namespace controllers;
2
3class ProductsController extends ControllerBase{
4...
5 /**
6 * @route("products/{value}")
7 */
8 public function search($value){
9 // $value will equal the dynamic part of the URL
10 // e.g. at /products/brocolis, then $value='brocolis'
11 // ...
12 }
13}
Paramètres optionnels des routes
Une route peut définir des paramètres optionnels, si la méthode associée a des arguments optionnels :
1namespace controllers;
2
3use Ubiquity\attributes\items\router\Route;
4
5class ProductsController extends ControllerBase{
6 ...
7 #[Route('products/all/{pageNum}/{countPerPage}')]
8 public function list($pageNum,$countPerPage=50){
9 // ...
10 }
11}
1namespace controllers;
2
3class ProductsController extends ControllerBase{
4 ...
5 /**
6 * @route("products/all/{pageNum}/{countPerPage}")
7 */
8 public function list($pageNum,$countPerPage=50){
9 // ...
10 }
11}
Route requirements
Il est possible d’ajouter des spécifications sur les variables passées dans l’url via l’attribut requirements.
1namespace controllers;
2
3use Ubiquity\attributes\items\router\Route;
4
5class ProductsController extends ControllerBase{
6 ...
7 #[Route('products/all/{pageNum}/{countPerPage}',requirements: ["pageNum"=>"\d+","countPerPage"=>"\d?"])]
8 public function list($pageNum,$countPerPage=50){
9 // ...
10 }
11}
1namespace controllers;
2
3class ProductsController extends ControllerBase{
4 ...
5 /**
6 * @route("products/all/{pageNum}/{countPerPage}","requirements"=>["pageNum"=>"\d+","countPerPage"=>"\d?"])
7 */
8 public function list($pageNum,$countPerPage=50){
9 // ...
10 }
11}
- La route définie correspond à ces urls :
products/all/1/20
products/all/5/
- mais pas avec celle-ci :
products/all/test
Types des paramètres
La déclaration de route prend en compte les types de données passés à l’action, ce qui évite d’ajouter des requirements pour les types simples (int, bool, float).
1namespace controllers;
2
3use Ubiquity\attributes\items\router\Route;
4
5class ProductsController extends ControllerBase{
6 ...
7 #[Route('products/{productNumber}')]
8 public function one(int $productNumber){
9 // ...
10 }
11}
1namespace controllers;
2
3class ProductsController extends ControllerBase{
4 ...
5 /**
6 * @route("products/{productNumber}")
7 */
8 public function one(int $productNumber){
9 // ...
10 }
11}
- La route définie correspond à ces urls :
products/1
products/20
- mais pas avec celle-ci :
products/test
- Valeurs possibles par type de données :
int
:1
…bool
:0
or1
float
:1
1.0
…
Méthodes http des routes
Il est possible de spécifier la ou les méthodes http associées à une route :
1namespace controllers;
2
3use Ubiquity\attributes\items\router\Route;
4
5class ProductsController extends ControllerBase{
6
7 #[Route('products',methods: ['get','post'])]
8 public function index(){}
9
10}
1namespace controllers;
2
3class ProductsController extends ControllerBase{
4
5 /**
6 * @route("products","methods"=>["get","post"])
7 */
8 public function index(){}
9
10}
L’attribut methods peut accepter plusieurs méthodes :
@route("testMethods","methods"=>["get","post","delete"])
#[Route('testMethods', methods: ['get','post','delete'])]
L’annotation @route ou l’attribut Route correspondent à l’ensemble des méthodes http.
Une annotation spécifique existe pour chaque méthode http :
@get => Get
@post => Post
@put => Put
@patch => Patch
@delete => Delete
@head => Head
@options => Options
1namespace controllers;
2
3use Ubiquity\attributes\items\router\Get;
4
5class ProductsController extends ControllerBase{
6
7 #[Get('products')]
8 public function index(){}
9
10}
1namespace controllers;
2
3class ProductsController extends ControllerBase{
4
5 /**
6 * @get("products")
7 */
8 public function index(){}
9
10}
Nom de route
Il est possible de spécifier le nom name d’une route pour faciliter l’accès à l’url associée.
Si l’attribut name n’est pas spécifié, chaque route a un nom par défaut, basé sur le pattern controllerName.methodName.
1namespace controllers;
2
3use Ubiquity\attributes\items\router\Route;
4
5class ProductsController extends ControllerBase{
6
7 #[Route('products',name: 'products.index')]
8 public function index(){}
9
10}
1namespace controllers;
2
3class ProductsController extends ControllerBase{
4
5 /**
6 * @route("products","name"=>"products.index")
7 */
8 public function index(){}
9
10}
Génération d’URL
Les routes names peuvent être utilisés pour générer les URLs.
Liens vers une route en Twig
<a href="{{ path('products.index') }}">Products</a>
Route globale
L’annotation @route peut être utilisée sur une classe contrôleur :
1namespace controllers;
2
3use Ubiquity\attributes\items\router\Route;
4
5#[Route('products')]
6class ProductsController extends ControllerBase{
7 ...
8 #[Route('/all')]
9 public function display(){}
10
11}
1namespace controllers;
2/**
3 * @route("/product")
4 */
5class ProductsController extends ControllerBase{
6
7 ...
8 /**
9 * @route("/all")
10 */
11 public function display(){}
12
13}
Dans ce cas, la route définie sur le contrôleur est utilisée en tant que préfixe pour toutes les routes du contrôleur :
La route générée pour l’action display est /product/all
Routes automatiques
Si une route globale est définie, il est possible d’ajouter toutes les actions du contrôleur en tant que routes (en utilisant le préfixe global), en spécifiant le paramètre automated :
1namespace controllers;
2
3use Ubiquity\attributes\items\router\Route;
4
5#[Route('/products',automated: true)]
6class ProductsController extends ControllerBase{
7
8 public function index(){}
9
10 public function generate(){}
11
12 public function display($id){}
13
14}
1namespace controllers;
2/**
3 * @route("/product","automated"=>true)
4 */
5class ProductsController extends ControllerBase{
6
7 public function index(){}
8
9 public function generate(){}
10
11 public function display($id){}
12
13}
- L’attribut automated définit 3 routes depuis ProductsController :
/product/(index/)?
/product/generate
/product/display/{id}
Routes héritées
Avec l’attribut inherited, il est possible de générer les routes déclarées dans la classe de base, ou de générer automatiquement les routes associées aux actions de la classe de base, si l’attribut automated est mis à true en même temps.
Classe de base :
1 namespace controllers;
2
3 use Ubiquity\attributes\items\router\Route;
4
5 abstract class ProductsBase extends ControllerBase{
6
7 #[Route('(index/)?')]
8 public function index(){}
9
10 #[Route('sort/{name}')]
11 public function sortBy($name){}
12
13 }
1namespace controllers;
2
3abstract class ProductsBase extends ControllerBase{
4
5 /**
6 *@route("(index/)?")
7 */
8 public function index(){}
9
10 /**
11 * @route("sort/{name}")
12 */
13 public function sortBy($name){}
14
15}
La classe dérivée utilisant les membres hérités :
1namespace controllers;
2
3use Ubiquity\attributes\items\router\Route;
4
5#[Route('/product',inherited: true)]
6class ProductsController extends ProductsBase{
7
8 public function display(){}
9
10}
1namespace controllers;
2/**
3 * @route("/product","inherited"=>true)
4 */
5class ProductsController extends ProductsBase{
6
7 public function display(){}
8
9}
- L’attribut inherited définit les 2 routes de ProductsBase:
/products/(index/)?
/products/sort/{name}
Si les attributs automated et inherited sont utilisés en conjonction, les actions de la classe de base sont également ajoutées aux routes.
Paramètres globaux de routes
La partie globale d’une route peut définir des paramètres, qui seront passés dans toutes les routes générées.
Ces paramètres peuvent être récupérés par le biais d’un membre de données public :
1namespace controllers;
2
3use Ubiquity\attributes\items\router\Route;
4
5#[Route('/foo/{bar}',automated: true)]
6class FooController {
7
8 public string $bar;
9
10 public function display(){
11 echo $this->bar;
12 }
13
14}
1namespace controllers;
2
3/**
4 * @route("/foo/{bar}","automated"=>true)
5 */
6class FooController {
7
8 public string $bar;
9
10 public function display(){
11 echo $this->bar;
12 }
13
14}
L’accès à l’url /foo/bar/display
affiche le contenu du membre bar.
Routes avec préfixe global
Si la route globale est définie sur un contrôleur, toutes les routes générées dans ce contrôleur sont précédées du préfixe.
Il est possible d’introduire explicitement des exceptions sur certaines routes, en utilisant le préfixe #/
.
1namespace controllers;
2
3use Ubiquity\attributes\items\router\Route;
4
5#[Route('/foo',automated: true)]
6class FooController {
7
8 #[Route('#/noRoot')]
9 public function noRoot(){}
10
11}
1namespace controllers;
2
3/**
4 * @route("/foo","automated"=>true)
5 */
6class FooController {
7
8 /**
9 * @route("#/noRoot")
10 */
11 public function noRoot(){}
12
13}
Le contrôleur définit l’url /noRoot
url, qui n’est pas préfixée par /foo
.
Priorité des routes
Le paramètre prority d’une route permet à celle ci d’être résolue avec une plus ou moins grande priorité.
Plus le paramètre de priorité est élevé, plus la route sera définie au début de la pile des routes en cache.
Dans l’exemple ci-dessous, la route produits/all sera définie avant la route /produits.
1namespace controllers;
2
3use Ubiquity\attributes\items\router\Route;
4
5class ProductsController extends ControllerBase{
6
7 #[Route('products', priority: 1)]
8 public function index(){}
9
10 #[Route('products/all', priority: 10)]
11 public function all(){}
12
13}
1namespace controllers;
2
3class ProductsController extends ControllerBase{
4
5 /**
6 * @route("products","priority"=>1)
7 */
8 public function index(){}
9
10 /**
11 * @route("products/all","priority"=>10)
12 */
13 public function all(){}
14
15}
La valeur par défaut pour priority est 0
.
Mise en cache de réponse d’une route
Il est possible de mettre en cache la réponse produite par une route :
Dans ce cas, la réponse est en cache et n’est plus générée dynamiquement.
#[Route('products/all', cache: true)]
public function all(){}
/**
* @route("products/all","cache"=>true)
*/
public function all(){}
Durée du cache
La duration est exprimée en secondes, si elle est omise, la validité du cache est infinie.
#[Route('products/all', cache: true, duration: 3600)]
public function all(){}
/**
* @route("products/all","cache"=>true,"duration"=>3600)
*/
public function all(){}
Expiration du cache
Il est possible de forcer le rechargement de la réponse en supprimant le cache associé.
Router::setExpired("products/all");
Mise en cache des routes dynamiques
Les routes dynamiques peuvent également être mises en cache.
Important
Cette possibilité n’a d’intérêt que si cette mise en cache n’est pas faite en production, mais au moment de l’initialisation du cache.
Router::get("foo", function(){
echo 'Hello world!';
});
Router::addRoute("string", \controllers\Main::class,"index");
CacheManager::storeDynamicRoutes(false);
Vérification des routes avec les devtools :
Ubiquity info:routes

Gestion des erreurs (404 & 500)
Routage par défaut
Avec le système de routage par défaut (le couple contrôleur+action définissant une route), un gestionnaire d’erreurs peut être redéfini pour personnaliser la gestion des erreurs.
Dans le fichier de configuration app/config/config.php, ajoutez la clé onError, associée à un callback définissant les messages d’erreur :
"onError"=>function ($code, $message = null,$controller=null){
switch($code){
case 404:
$init=($controller==null);
\Ubiquity\controllers\Startup::forward('IndexController/p404',$init,$init);
break;
}
}
Implémente l’action sollicitée p404 dans IndexController :
...
public function p404(){
echo "<div class='ui error message'><div class='header'>404</div>The page you are looking for doesn't exist!</div>";
}
Routage avec annotations
Il suffit dans ce cas d’ajouter une dernière route désactivant le système de routage par défaut, et correspondant à la gestion de l’erreur 404 :
...
#[Route('{url}', priority: -1000)]
public function p404($url){
echo "<div class='ui error message'><div class='header'>404</div>The page `$url` you are looking for doesn't exist!</div>";
}
...
/**
* @route("{url}","priority"=>-1000)
*/
public function p404($url){
echo "<div class='ui error message'><div class='header'>404</div>The page `$url` you are looking for doesn't exist!</div>";
}
Contrôleurs
Un contrôleur est une classe PHP héritant de Ubiquity\controllers\Controller
et fournissant un point d’entrée dans l’application.
Les contrôleurs et leurs méthodes définissent les URLs accessibles.
Création de contrôleur
La méthode la plus facile pour créer un contrôleur est de le faire depuis les devtools.
A partir de l’invite de commande, aller dans le dossier du projet.
Pour créer le contrôleur Products, utiliser la commande :
Ubiquity controller Products
Le contrôleur Products.php
créé dans le dossier app/controllers
du projet.
1namespace controllers;
2/**
3 * Controller Products
4 */
5class Products extends ControllerBase{
6
7public function index(){}
8
9}
Il est maintenant possible d’accéder à l’URL (la méthode index
est sollicitée par défaut) :
example.com/Products
example.com/Products/index
Note
Un contrôleur peut être créé manuellement. Dans ce cas, il doit respecter les règles suivantes :
La classe doit être définie dans le dossier app/controllers
Le nom de la classe doit correspondre au nom du fichier php
La classe doit hériter de ControllerBase et doit être définie dans le namespace controllers
Elle doit surdéfinir la méthode abstraite index
Méthodes
public
Le second segment de l’URL détermine la méthode publique du contrôleur sollicitée.
La méthode « index » est appelée par défaut, si le second segment est vide.
1namespace controllers;
2class First extends ControllerBase{
3
4 public function hello(){
5 echo "Hello world!";
6 }
7
8}
La méthode hello
du contrôleur First
met à disposition les URL suivantes :
example.com/First/hello
Arguments de méthode
Les arguments d’une méthode doivent être passés dans l’url, excepté s’ils sont optionnels.
namespace controllers;
class First extends ControllerBase{
public function says($what,$who='world') {
echo $what.' '.$who;
}
}
La méthode hello
du contrôleur First
met à disposition les URL suivantes :
example.com/First/says/hello (says hello world)
example.com/First/says/Hi/everyone (says Hi everyone)
private
Les méthodes privées ou protégées ne sont pas accessibles depuis l’URL.
Contrôleur par défaut
Le contrôleur par défaut peut être défini par le biais du Router, dans le fichier services.php
Router::start();
Router::addRoute("_default", "controllers\First");
Dans ce cas, l’accès à l’URL « exemple.com/ » charge le contrôleur First et appelle la méthode par défaut index.
Chargement des vues
chargement
Les vues sont stockées dans le dossier app/views
. Elles sont chargées à partir des méthodes du contrôleur.
Par défaut, il est possible de créer des vues en php, ou avec twig.
Twig est le moteur de template par défaut pour les fichiers html.
Chargement des vues php
Si l’extension du fichier n’est pas spécifiée, la méthode loadView charge un fichier php.
namespace controllers;
class First extends ControllerBase{
public function displayPHP(){
//loads the view app/views/index.php
$this->loadView('index');
}
}
Chargement des vues twig
Si l’extension du fichier est html, la méthode loadView charge un fichier twig html.
namespace controllers;
class First extends ControllerBase{
public function displayTwig(){
//loads the view app/views/index.html
$this->loadView("index.html");
}
}
Chargement par défaut de vue
Si vous utilisez la méthode de dénomination des vues par défaut :
La vue par défaut associée à une action dans un contrôleur est située dans le dossier views/controller-name/action-name
:
views
│
└ Users
└ info.html
1 namespace controllers;
2
3 class Users extends BaseController{
4 ...
5 public function info(){
6 $this->loadDefaultView();
7 }
8}
Variables associées à une vue
Une des missions du contrôleur est de passer des variables à la vue.
Ceci peut être fait au chargement de la vue, à partir d’un tableau associatif :
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]);
}
}
Les clés du tableau associatif créent des variables du même nom dans la vue.
Utilisation de ces variables dans Twig :
<h1>{{message}} {{recipient}}</h1>
Les variables peuvent également être passées avant le chargement de la vue :
//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');
retour de l’affichage d’une vue dans une chaîne
Il est possible de charger une vue, et de retourner le résultat dans une chaîne, en mettant à true le 3ème paramètre de la méthode loadview :
$viewResult=$this->loadView("First/index.html",[],true);
echo $viewResult;
chargement de plusieurs vues
Une action peut charger plusieurs vues
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
Une vue est souvent partielle. Il est donc important de ne pas intégrer systématiquement les balises html et body définissant une page html complète.
organisation des vues
Il est conseillé d’organiser les vues en dossiers. La méthode la plus recommandée est de créer un dossier par contrôleur, et d’y stocker les vues associées.
Pour charger la vue index.html
, stockée dans app/views/First
:
$this->loadView("First/index.html");
initialize et finalize
La méthode initialize est automatiquement appelée avant chaque action sollicitée, la méthode finalize après chaque action.
Exemple d’utilisation des méthodes initialize et finalize créées automatiquement au sein d’un nouveau projet :
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 );
}
}
}
Contrôle d’accès
Le contrôle d’accès à un contrôleur peut être effectué manuellement, à l’aide des méthodes isValid et onInvalidControl.
La méthode isValid doit retourner un booléen permettant de définir si l’action sollicitée passée en paramètre est accessible.
Dans l’exemple suivant, l’accès aux actions du contrôleur IndexController n’est possible que si une variable de session activeUser existe :
class IndexController extends ControllerBase{
...
public function isValid($action){
return USession::exists('activeUser');
}
}
Si la variable activeUser n’existe pas, une erreur unauthorized 401 est renvoyée.
La méthode onInvalidControl permet de personnalisé le comportement de l’accès non autorisé :
class IndexController extends ControllerBase{
...
public function isValid($action){
return USession::exists('activeUser');
}
public function onInvalidControl(){
$this->initialize();
$this->loadView('unauthorized.html');
$this->finalize();
}
}
<div class="ui container">
<div class="ui brown icon message">
<i class="ui ban icon"></i>
<div class="content">
<div class="header">
Error 401
</div>
<p>You are not authorized to access to <b>{{app.getController() ~ "::" ~ app.getAction()}}</b>.</p>
</div>
</div>
</div>
Il est aussi possible de gérer le contrôle d’accès à partir des AuthControllers
Redirection
Une redirection n’est pas un simple appel à une action d’un contrôleur.
La redirection sollicite également les méthodes initialize et finalize, ainsi que le contrôle d’accès.
La méthode forward peut être invoquée sans la sollicitation des méthodes initialize et finalize.
Il est possible d’effectuer une redirection vers une route par son nom.
Injection de dépendances
Voir Dependency injection
namespaces
Le namespace des contrôleurs est défini par défaut à controllers dans le fichier app/config/config.php.
Classe de base
L’héritage peut être utiliser pour factoriser le comportement des contrôleurs.
La classe BaseController est créée par défaut au sein des nouveaux projets dans cette logique.
Classe de base contrôleur spécifiques
Classe contrôleur |
rôle |
---|---|
Contrôleur |
Classe de base pour tous les contrôleurs |
SimpleViewController |
Classe de base associée à un moteur de template php basique (pour utilisation avec des micro-services) |
SimpleViewAsyncController |
Classe de base associée avec un moteur de template php pour les serveurs asynchrones |
Evènements
Note
Le module Events utilise la classe statique EventsManager pour gérer les évènements.
Framework core events
Ubiquity émet des événements lors des différentes phases de soumission d’une requête.
Ces événements sont relativement peu nombreux, afin de limiter leur impact sur les performances.
Partie |
Nom d’évènement |
Paramètres |
Se produit quand |
---|---|---|---|
ViewEvents |
BEFORE_RENDER |
viewname, parameters |
Avant d’afficher une vue |
ViewEvents |
AFTER_RENDER |
viewname, parameters |
Après l’affichage d’une vue |
DAOEvents |
GET_ALL |
objects, classname |
Après le chargement d’un ensemble d’objets |
DAOEvents |
GET_ONE |
object, classname |
Après le chargement d’un objet |
DAOEvents |
BEFORE_UPDATE |
instance |
Avant la mise à jour d’un objet |
DAOEvents |
AFTER_UPDATE |
instance, result |
Après mise à jour d’un objet |
DAOEvents |
BEFORE_INSERT |
instance |
Avant l’insertion d’un objet |
DAOEvents |
AFTER_INSERT |
instance, result |
Après l’insertion d’un objet |
RestEvents |
BEFORE_INSERT |
instance |
Avant l’insertion d’un objet |
RestEvents |
BEFORE_UPDATE |
instance |
Avant la mise à jour d’un objet |
Note
Il n’y a pas d’événement BeforeAction et AfterAction, puisque les méthodes initialize et finalize de la classe contrôleur effectuent ces opérations.
Ecouter un évènement
Exemple 1 :
Ajout d’une propriété _updated sur les instances modifiées dans la base de données :
1use Ubiquity\events\EventsManager;
2use Ubiquity\events\DAOEvents;
3
4...
5
6 EventsManager::addListener(DAOEvents::AFTER_UPDATE, function($instance,$result){
7 if($result==1){
8 $instance->_updated=true;
9 }
10 });
Note
Les paramètres passés à la fonction de callback varient en fonction de l’évènement écouté.
Exemple 2 :
Modification de l’affichage de la vue
1use Ubiquity\events\EventsManager;
2use Ubiquity\events\ViewEvents;
3
4...
5
6 EventsManager::addListener(ViewEvents::AFTER_RENDER,function(&$render,$viewname,$datas){
7 $render='<h1>'.$viewname.'</h1>'.$render;
8 });
Créer ses propres évènements
Exemple :
Création d’un évènement pour compter et mémoriser le nombre d’affichages par action :
1 namespace eventListener;
2
3 use Ubiquity\events\EventListenerInterface;
4 use Ubiquity\utils\base\UArray;
5
6 class TracePageEventListener implements EventListenerInterface {
7 const EVENT_NAME = 'tracePage';
8
9 public function on(&...$params) {
10 $filename = \ROOT . \DS . 'config\stats.php';
11 $stats = [ ];
12 if (file_exists ( $filename )) {
13 $stats = include $filename;
14 }
15 $page = $params [0] . '::' . $params [1];
16 $value = $stats [$page] ?? 0;
17 $value ++;
18 $stats [$page] = $value;
19 UArray::save ( $stats, $filename );
20 }
21 }
Enregistrement d’évènements
Enregistrement de l’évènement TracePageEventListener dans services.php
:
1 use Ubiquity\events\EventsManager;
2 use eventListener\TracePageEventListener;
3
4 ...
5
6 EventsManager::addListener(TracePageEventListener::EVENT_NAME, TracePageEventListener::class);
Déclencher des évènements
Un évènement peut être déclenché depuis n’importe quel contexte, mais il est plus logique de le faire depuis la méthode initialize du contrôleur de base.
1 namespace controllers;
2
3 use Ubiquity\controllers\Controller;
4 use Ubiquity\utils\http\URequest;
5 use Ubiquity\events\EventsManager;
6 use eventListener\TracePageEventListener;
7 use Ubiquity\controllers\Startup;
8
9 /**
10 * ControllerBase.
11 **/
12 abstract class ControllerBase extends Controller{
13 protected $headerView = "@activeTheme/main/vHeader.html";
14 protected $footerView = "@activeTheme/main/vFooter.html";
15 public function initialize() {
16 $controller=Startup::getController();
17 $action=Startup::getAction();
18 EventsManager::trigger(TracePageEventListener::EVENT_NAME, $controller,$action);
19 if (! URequest::isAjax ()) {
20 $this->loadView ( $this->headerView );
21 }
22 }
23 public function finalize() {
24 if (! URequest::isAjax ()) {
25 $this->loadView ( $this->footerView );
26 }
27 }
28 }
Le résultat dans app/config/stats.php :
return array(
"controllers\\IndexController::index"=>5,
"controllers\\IndexController::ct"=>1,
"controllers\\NewController::index"=>1,
"controllers\\TestUCookieController::index"=>1
);
Optimisation de l’enregistrement d’évènements
Il est préférable de mettre en cache l’enregistrement des écouteurs, afin d’optimiser leur temps de chargement :
Créer un script client, ou une action contrôleur (non accessible en mode production) :
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();
}
Après exécution, le cache est généré dans le fichier app/cache/events/events.cache.php
.
Une fois le cache généré, le fichier services.php
doit juste inclure cette ligne :
\Ubiquity\events\EventsManager::start();
Injection de dépendances
Note
Pour des raisons de performances, l’injection de dépendances n’est pas utilisée dans le cœur du framework.
L’injection de dépendances (DI) est un modèle de conception utilisé pour mettre en œuvre l’IoC.
Il permet de créer des objets dépendants en dehors d’une classe et de fournir ces objets à une classe de différentes manières. En utilisant l’injection de dépendances, nous déplaçons la création et la liaison des objets dépendants en dehors de la classe qui en dépend.
Note
Ubiquity ne supporte que l’injection de propriétés, afin de ne pas recourir à l’introspection à l’exécution.
Seuls les contrôleurs supportent l’injection de dépendances.
Autowiring de service
Création de service
Créer un service
1namespace services;
2
3 class Service{
4 public function __construct($ctrl){
5 echo 'Service instanciation in '.get_class($ctrl);
6 }
7
8 public function do($someThink=""){
9 echo 'do '.$someThink ."in service";
10 }
11 }
Autowiring dans un contrôleur
Créer un contrôleur utilisant le service
1 namespace controllers;
2
3 /**
4 * Controller Client
5 **/
6 class ClientController extends ControllerBase{
7
8 /**
9 * @autowired
10 * @var services\Service
11 */
12 private $service;
13
14 public function index(){}
15
16 /**
17 * @param \services\Service $service
18 */
19 public function setService($service) {
20 $this->service = $service;
21 }
22 }
Dans l’exemple ci-dessus, Ubiquity recherche et injecte $service lorsque ClientController est créé.
- L’annotation @autowired nécessite que :
Le type à instancier soit déclaré avec l’annotation @var
La propriété $service ait un setteur, ou soit déclarée publique
Etant donné que les annotations ne sont jamais lues à l’exécution, il est nécessaire de générer le cache des contrôleurs :
Ubiquity init-cache -t=controllers
Reste à vérifier que le service est bien injecté, en allant à l’adresse /ClientController
.
Injection de service
Service
Créons maintenant un deuxième service, nécessitant une initialisation spéciale.
1 class ServiceWithInit{
2 private $init;
3
4 public function init(){
5 $this->init=true;
6 }
7
8 public function do(){
9 if($this->init){
10 echo 'init well initialized!';
11 }else{
12 echo 'Service not initialized';
13 }
14 }
15 }
Injection dans le contrôleur
1namespace controllers;
2
3 /**
4 * Controller Client
5 **/
6 class ClientController extends ControllerBase{
7
8 /**
9 * @autowired
10 * @var \services\Service
11 */
12 private $service;
13
14 /**
15 * @injected
16 */
17 private $serviceToInit;
18
19 public function index(){
20 $this->serviceToInit->do();
21 }
22
23 /**
24 * @param \services\Service $service
25 */
26 public function setService($service) {
27 $this->service = $service;
28 }
29
30 /**
31 * @param mixed $serviceToInit
32 */
33 public function setServiceToInit($serviceToInit) {
34 $this->serviceToInit = $serviceToInit;
35 }
36
37 }
Di déclaration
Dans app/config/config.php
, créez une nouvelle clé pour la propriété serviceToInit à injecter dans la partie di.
"di"=>["ClientController.serviceToInit"=>function(){
$service=new \services\ServiceWithInit();
$service->init();
return $service;
}
]
générer le cache des contrôleurs :
Ubiquity init-cache -t=controllers
Vérifier que le service est bien injecté en allant à l’adresse /ClientController
.
Note
Si le même service doit être utilisé dans plusieurs contrôleurs, utilisez la notation avec joker :
"di"=>["*.serviceToInit"=>function(){
$service=new \services\ServiceWithInit();
$service->init();
return $service;
}
]
Injection avec un nom
Si le nom du service à injecter est différent de la clé du tableau di, il est possible d’utiliser l’attribut name de l’annotation @injected.
Dans app/config/config.php
, créez une nouvelle clé pour la propriété serviceToInit à injecter dans la partie di.
"di"=>["*.service"=>function(){
$service=new \services\ServiceWithInit();
$service->init();
return $service;
}
]
/**
* @injected("service")
*/
private $serviceToInit;
Injection de service à l’exécution
Il est possible d’injecter des services à l’exécution, sans qu’ils aient été précédemment déclarés dans une classe contrôleur.
1namespace services;
2
3 class RuntimeService{
4 public function __construct($ctrl){
5 echo 'Service instanciation in '.get_class($ctrl);
6 }
7 }
Dans app/config/config.php
, ajouter la clé @exec dans la partie di.
"di"=>["@exec"=>"rService"=>function($ctrl){
return new \services\RuntimeService($ctrl);
}
]
Avec cette déclaration, le membre $rService, instance de RuntimeService, est injecté dans tous les contrôleurs.
Il est donc conseillé d’utiliser les commentaires de javadoc pour déclarer $rService dans les contrôleurs qui l’utilisent (pour obtenir la complétion de code sur $rService dans votre IDE).
1namespace controllers;
2
3 /**
4 * Controller Client
5 * property services\RuntimeService $rService
6 **/
7 class MyController extends ControllerBase{
8
9 public function index(){
10 $this->rService->do();
11 }
12 }
Contrôleurs CRUD
- Les contrôleurs CRUD vous permettent d’effectuer des opérations de base sur une classe Modèle :
Create
Read
Update
Delete
…
Note
Depuis la version 2.4.6, deux types de CrudController existent :
ResourceCrudController associé à un modèle
MultiResourceCRUDController, affichant un index et permettant de naviguer entre les modèles.
ResourceCrudController
Création
Dans l’interface d’administration (webtools), activez la partie Controllers, et choisissez Resource Crud controller :

- Remplissez ensuite le formulaire :
Entrez le nom du contrôleur
Sélectionnez le modèle associé
Cliquez ensuite sur le bouton de validation

Description des caractéristiques
Le contrôleur généré :
1<?php
2namespace controllers;
3
4 /**
5 * CRUD Controller UsersController
6 **/
7class UsersController extends \Ubiquity\controllers\crud\CRUDController{
8
9 public function __construct(){
10 parent::__construct();
11 $this->model= models\User::class;
12 }
13
14 public function _getBaseRoute():string {
15 return 'UsersController';
16 }
17}
Testez le contrôleur créé en cliquant sur le bouton « get » devant l’action index :

Lecture (action index)

En cliquant sur une ligne de la dataTable (instance), on affiche les objets associés à l’instance (action details) :

Utilisation de la zone de recherche :

Création (action newModel)
Il est possible de créer une instance en cliquant sur le bouton « add ».

Le formulaire par défaut pour ajouter une instance de User :

Mise à jour (action update)
Le bouton d’édition sur chaque ligne vous permet d’éditer une instance.

Le formulaire par défaut pour ajouter une instance de User :

Suppression (action delete)
Le bouton de suppression sur chaque ligne vous permet de supprimer une instance.

Affichage du message de confirmation avant la suppression :

Personnalisation
Créez à nouveau un ResourceCrudController à partir de l’interface d’administration :

Il est désormais possible de personnaliser le module en utilisant la surdéfinition.
Aperçu

Surdéfinition des classes
Méthodes de ResourceCRUDController à surdéfinir
Méthode |
Signification |
Retour par défaut |
---|---|---|
routes |
||
index() |
Default page : liste de toutes les instances |
|
edit($modal= »no », $ids= » ») |
Edite une instance |
|
newModel($modal= »no ») |
Crée une instance |
|
display($modal= »no »,$ids= » ») |
Affiche une instance |
|
delete($ids) |
Supprime une instance |
|
update() |
Affiche le résultat de la mise à jour d’une instance |
|
showDetail($ids) |
Affiche les membres associés avec des clés étrangères |
|
refresh_() |
Rafraîchit la zone correspondant à la DataTable (#lv) |
|
refreshTable($id=null) |
//TO COMMENT |
Méthodes de ModelViewer à surdéfinir
Méthode |
Signification |
Retour par défaut |
---|---|---|
route index |
||
getModelDataTable($instances, $model,$totalCount,$page=1) |
Crée la dataTable et ajoute son comportement |
DataTable |
getDataTableInstance($instances,$model,$totalCount,$page=1) |
Crée la dataTable |
DataTable |
recordsPerPage($model,$totalCount=0) |
Retourne le nombre de lignes à afficher (si null, il n’y a pas de pagination). |
null ou 6 |
getGroupByFields() |
Renvoie un tableau de membres sur lequel on peut effectuer un regroupement. |
[] |
getDataTableRowButtons() |
Retourne un tableau de boutons à afficher pour chaque ligne [« edit », »delete », »display »] |
[« edit », »delete »] |
onDataTableRowButton(HtmlButton $bt, ?string $name) |
À surdéfinir pour modifier les boutons des lignes de la table de données. |
|
getCaptions($captions, $className) |
Retourne les légendes des en-têtes de colonne |
tous les noms des membres |
route detail |
||
showDetailsOnDataTableClick() |
À remplacer pour s’assurer que le détail d’un objet cliqué est affiché ou non. |
true |
onDisplayFkElementListDetails($element,$member,$className,$object) |
A modifier pour l’affichage de chaque élément dans un composant liste d’objets étrangers |
|
getFkHeaderElementDetails($member, $className, $object) |
Renvoie l’en-tête d’un objet étranger (issue de ManyToOne) |
HtmlHeader |
getFkElementDetails($member, $className, $object) |
Renvoie un composant permettant d’afficher un objet étranger (relation manyToOne). |
HtmlLabel |
getFkHeaderListDetails($member, $className, $list) |
Renvoie l’en-tête d’une liste d’objets étrangers (oneToMany ou ManyToMany). |
HtmlHeader |
getFkListDetails($member, $className, $list) |
Renvoie un composant liste permettant d’afficher une collection d’objets étrangers (many). |
HtmlList |
routes edit et newModel |
||
getForm($identifier, $instance) |
Renvoie le formulaire d’ajout ou de modification d’un objet |
HtmlForm |
formHasMessage() |
Détermine si le formulaire a un titre de type message |
true |
getFormModalTitle($instance) |
Renvoie le titre de la modale du formulaire |
instance class |
onFormModalButtons($btOkay, $btCancel) |
Hook pour modifier les boutons modaux |
|
getFormTitle($form,$instance) |
Renvoie un tableau associatif définissant le titre du message du formulaire avec les clés « icon », « message », « subMessage ». |
HtmlForm |
setFormFieldsComponent(DataForm $form,$fieldTypes) |
Définit le composant pour chaque champ |
|
onGenerateFormField($field) |
Pour faire quelque chose lorsque $field est généré dans le formulaire |
|
isModal($objects, $model) |
Condition pour déterminer si le formulaire de modification ou d’ajout est modal pour les objets $model |
count($objects)>5 |
getFormCaptions($captions, $className, $instance) |
Renvoie les légendes des champs de formulaire |
tous les noms des membres |
route display |
||
getModelDataElement($instance,$model,$modal) |
Renvoie un objet DataElement pour l’affichage de l’instance. |
DataElement |
getElementCaptions($captions, $className, $instance) |
Renvoie les légendes des champs du DataElement |
tous les noms des membres |
route delete |
||
onConfirmButtons(HtmlButton $confirmBtn,HtmlButton $cancelBtn) |
A surdéfinir pour modifier les boutons de confirmation de suppression |
Méthodes CRUDDatas à surdéfinir
Méthode |
Signification |
Retour par défaut |
---|---|---|
route index |
||
_getInstancesFilter($model) |
Ajoute une condition pour filtrer les instances affichées dans dataTable |
1=1 |
getFieldNames($model) |
Retourne les champs à afficher dans l’action index pour $model |
tous les noms des membres |
getSearchFieldNames($model) |
Renvoie les champs à utiliser dans les requêtes de recherche. |
tous les noms des membres |
routes edit et newModel |
||
getFormFieldNames($model,$instance) |
Renvoie les champs à mettre à jour dans les actions edit et newModel pour $model. |
tous les noms des membres |
getManyToOneDatas($fkClass,$instance,$member) |
Renvoie une liste (filtrée) d’objets $fkClass à afficher dans une liste html |
toutes les instances de $fkClass |
getOneToManyDatas($fkClass,$instance,$member) |
Renvoie une liste (filtrée) d’objets $fkClass à afficher dans une liste html |
toutes les instances de $fkClass |
getManyToManyDatas($fkClass,$instance,$member) |
Renvoie une liste (filtrée) d’objets $fkClass à afficher dans une liste html |
toutes les instances de $fkClass |
route display |
||
getElementFieldNames($model) |
Retourne les champs à afficher dans l’action display pour $model |
tous les noms des membres |
Méthodes CRUDEvents à surdéfinir
Méthode |
Signification |
Retour par défaut |
---|---|---|
route index |
||
onConfDeleteMessage(CRUDMessage $message,$instance) |
Renvoie le message de confirmation affiché avant la suppression d’une instance. |
CRUDMessage |
onSuccessDeleteMessage(CRUDMessage $message,$instance) |
Renvoie le message affiché après une suppression |
CRUDMessage |
onErrorDeleteMessage(CRUDMessage $message,$instance) |
Renvoie le message affiché lorsqu’une erreur s’est produite lors de la suppression. |
CRUDMessage |
routes edit et newModel |
||
onSuccessUpdateMessage(CRUDMessage $message) |
Renvoie le message affiché lorsqu’une instance est ajoutée ou insérée. |
CRUDMessage |
onErrorUpdateMessage(CRUDMessage $message) |
Renvoie le message affiché lorsqu’une erreur s’est produite lors de la mise à jour ou de l’insertion. |
CRUDMessage |
onNewInstance(object $instance) |
Déclenché après la création d’une nouvelle instance |
|
onBeforeUpdate(object $instance, bool $isNew) |
Déclenché avant la mise à jour de l’instance |
|
Toutes les routes |
||
onNotFoundMessage(CRUDMessage $message,$ids) |
Renvoie le message affiché lorsqu’une instance n’existe pas. |
|
onDisplayElements($dataTable,$objects,$refresh) |
Déclenché après l’affichage des objets dans le dataTable |
Méthodes CRUDFiles à surdéfinir
Méthode |
Signification |
Retour par défaut |
|
---|---|---|---|
Fichiers template |
|||
getViewBaseTemplate() |
Renvoie le template de base pour toutes les actions Crud si getBaseTemplate renvoie un nom de fichier de template de base. |
@framework/crud/baseTemplate.html |
|
getViewIndex() |
Retourne le template pour la route index. |
@framework/crud/index.html |
|
getViewForm() |
Retourne le modèle pour les routes edit et newInstance. |
@framework/crud/form.html |
|
getViewDisplay() |
Retourne le modèle pour la route display. |
@framework/crud/display.html |
|
Urls |
|||
getRouteRefresh() |
Retourne la route pour rafraîchir la route index. |
||
getRouteDetails() |
Renvoie la route de la route detail, lorsque l’utilisateur clique sur une ligne du dataTable. |
/showDetail |
|
getRouteDelete() |
Retourne la route pour la suppression d’une instance. |
/delete |
|
getRouteEdit() |
Retourne la route pour éditer une instance |
/edit |
|
getRouteDisplay() |
Retourne la route pour l’affichage d’une instance |
/display |
|
getRouteRefreshTable() |
Renvoie la route pour rafraîchir le dataTable. |
/refreshTable |
|
getDetailClickURL($model) |
Retourne la route associée à une instance de clé étrangère dans la liste |
« « |
Structure des templates Twig
index.html

form.html
Affiché dans le bloc frm

display.html
Affiché dans le bloc frm

MultiResourceCrudController
Note
MultiResourceCRUDController affiche un index permettant de naviguer entre les CRUDs des modèles.
Création
Dans l’interface d’administration (webtools), activez la partie Controllers, et choisissez Index Crud controller :

- Remplissez ensuite le formulaire :
Entrez le nom du contrôleur
Le chemin de la route (qui doit contenir la partie variable {resource})
Cliquez ensuite sur le bouton de validation

Description des caractéristiques
Le contrôleur généré :
1<?php
2namespace controllers;
3use Ubiquity\attributes\items\router\Route;
4
5#[Route(path: "/{resource}/crud",inherited: true,automated: true)]
6class CrudIndex extends \Ubiquity\controllers\crud\MultiResourceCRUDController{
7
8 #[Route(name: "crud.index",priority: -1)]
9 public function index() {
10 parent::index();
11 }
12
13 #[Route(path: "#//home/crud",name: "crud.home",priority: 100)]
14 public function home(){
15 parent::home();
16 }
17
18 protected function getIndexType():array {
19 return ['four link cards','card'];
20 }
21
22 public function _getBaseRoute():string {
23 return "/".$this->resource."/crud";
24 }
25
26}
Testez le contrôleur créé à l’url /home/crud :

Personnalisation
Créez à nouveau un MultiResourceCrudController à partir de l’interface d’administration :

Il est maintenant possible de personnaliser le module en utilisant la surdéfinition comme pour les ResourceCRUDControllers.
Classes spécifiques à surdéfinir
Méthodes de MultiResourceCRUDController à surdéfinir
Méthode |
Signification |
Retour par défaut |
---|---|---|
routes |
||
home () |
Page d’accueil : liste des modèles |
|
Toutes les routes de « CRUDController ». |
||
Evènements |
||
onRenderView(array &$data) |
Avant le rendu de la page d’accueil |
|
Configuration |
||
hasNavigation() |
Renvoie la valeur True pour l’affichage du menu déroulant de navigation. |
True |
getIndexModels() |
Renvoie la liste des modèles disponibles à afficher |
modèles à partir de la base de données par défaut |
getIndexModelsDetails() |
Retourne un tableau associatif (titre, icon, url) pour chaque modèle. |
[] |
getIndexDefaultIcon(string $resource) |
Renvoie l’icône d’un modèle |
Un animal au hasard |
getIndexDefaultTitle(string $resource) |
Retourne le titre d’un modèle |
Le nom de la ressource |
getIndexDefaultDesc(string $modelClass) |
Renvoie la description d’un modèle |
Le nom complet de la classe |
getIndexDefaultUrl(string $resource) |
Retourne l’url associée à un modèle |
Le chemin de la route |
getIndexDefaultMeta(string $modelClass) |
Retourne la partie méta pour un modèle |
|
getIndexType() |
Définit les classes css du composant de l’index |
cards |
getModelName() |
Renvoie le nom complet du modèle pour $this->resource |
A partir du NS par défaut des modèles |
Méthodes CRUDFiles à surdéfinir
Méthode |
Signification |
Retour par défaut |
|
---|---|---|---|
Fichiers template |
|||
getViewHome() |
Retourne le template de base pour la vue d’accueil |
@framework/crud/home.html |
|
getViewItemHome() |
Renvoie le template d’un élément de la route home. |
@framework/crud/itemHome.html |
|
getViewNav() |
Renvoie le template pour l’affichage des modèles dans une liste déroulante. |
@framework/crud/nav.html |
Note
Toutes les autres méthodes des classes CRUDController
, CRUDFiles
, CRUDEvents
et CRUDDatas
peuvent être surdéfinies comme pour le ResourceCRUDController
.
Auth Contrôleurs
- Les contrôleurs Auth vous permettent d’effectuer une authentification de base avec :
se connecter avec un compte
création de compte
déconnexion
contrôleurs avec l’authentification requise
Création
Dans l’interface d’administration (webtools), activez la partie Controllers, et choisissez de créer un Auth controller :

- Remplissez ensuite le formulaire :
Entrez le nom du contrôleur (BaseAuthController dans ce cas)

Le contrôleur généré :
1 /**
2 * Auth Controller BaseAuthController
3 **/
4class BaseAuthController extends \Ubiquity\controllers\auth\AuthController{
5
6 protected function onConnect($connected) {
7 $urlParts=$this->getOriginalURL();
8 USession::set($this->_getUserSessionKey(), $connected);
9 if(isset($urlParts)){
10 Startup::forward(implode("/",$urlParts));
11 }else{
12 //TODO
13 //Forwarding to the default controller/action
14 }
15 }
16
17 protected function _connect() {
18 if(URequest::isPost()){
19 $email=URequest::post($this->_getLoginInputName());
20 $password=URequest::post($this->_getPasswordInputName());
21 //TODO
22 //Loading from the database the user corresponding to the parameters
23 //Checking user creditentials
24 //Returning the user
25 }
26 return;
27 }
28
29 /**
30 * {@inheritDoc}
31 * @see \Ubiquity\controllers\auth\AuthController::isValidUser()
32 */
33 public function _isValidUser($action=null): bool {
34 return USession::exists($this->_getUserSessionKey());
35 }
36
37 public function _getBaseRoute(): string {
38 return 'BaseAuthController';
39 }
40}
Implémentation de l’authentification
Exemple d’implémentation avec l’interface d’administration : Nous allons ajouter un contrôle d’authentification sur l’interface d’administration.
L’authentification est basée sur la vérification du couple email/password sur le modèle User :

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

Après avoir cliqué sur login :

Si les données d’authentification sont invalides :

Si les données d’authentification sont valides :

Attachement de la zone info-user
Modifier le contrôleur BaseAuthController :
1 /**
2 * Auth Controller BaseAuthController
3 **/
4class BaseAuthController extends \Ubiquity\controllers\auth\AuthController{
5...
6 public function _displayInfoAsString(): bool {
7 return true;
8 }
9}
La zone _userInfo est désormais présente sur toutes les pages de l’administration :

Elle peut être affichée dans n’importe quelle vue twig :
{{ _userInfo | raw }}
Description des caractéristiques
Personnalisation des templates
template index.html
Le template index.html gère la connexion :

Exemple avec la zone _userInfo :
Créez un nouveau AuthController nommé PersoAuthController :

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

Personnalisation des messages
1class PersoAuthController extends \controllers\BaseAuth{
2...
3 /**
4 * {@inheritDoc}
5 * @see \Ubiquity\controllers\auth\AuthController::badLoginMessage()
6 */
7 protected function badLoginMessage(\Ubiquity\utils\flash\FlashMessage $fMessage) {
8 $fMessage->setTitle("Erreur d'authentification");
9 $fMessage->setContent("Login ou mot de passe incorrects !");
10 $this->_setLoginCaption("Essayer à nouveau");
11
12 }
13...
14}
Auto-vérification
1class PersoAuthController extends \controllers\BaseAuth{
2...
3 /**
4 * {@inheritDoc}
5 * @see \Ubiquity\controllers\auth\AuthController::_checkConnectionTimeout()
6 */
7 public function _checkConnectionTimeout() {
8 return 10000;
9 }
10...
11}
Limitation du nombre de tentatives
1class PersoAuthController extends \controllers\BaseAuth{
2...
3 /**
4 * {@inheritDoc}
5 * @see \Ubiquity\controllers\auth\AuthController::attemptsNumber()
6 */
7 protected function attemptsNumber(): int {
8 return 3;
9 }
10...
11}
Récupération de compte
La récupération de compte se fait en réinitialisant le mot de passe du compte.
Un e-mail de réinitialisation du mot de passe est envoyé, à une adresse e-mail correspondant à un compte actif.

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

Note
Par défaut, le lien ne peut être utilisé que sur la même machine, dans une période de temps prédéterminée (qui peut être modifiée en surchargeant la méthode accountRecoveryDuration
).
Activation de MFA/2FA
L’authentification multi-facteurs peut être activée de manière conditionnelle, sur la base des informations de l’utilisateur préalablement connecté.
Note
La phase 2 de l’authentification est réalisée dans l’exemple ci-dessous en envoyant un code aléatoire par email. La classe AuthMailerClass est disponible dans le paquet Ubiquity-mailer
.
1class PersoAuthController extends \controllers\BaseAuth{
2...
3 /**
4 * {@inheritDoc}
5 * @see \Ubiquity\controllers\auth\AuthController::has2FA()
6 */
7 protected function has2FA($accountValue=null):bool{
8 return true;
9 }
10
11 protected function _send2FACode(string $code, $connected):void {
12 MailerManager::start();
13 $mail=new AuthMailerClass();
14 $mail->to($connected->getEmail());
15 $mail->setCode($code);
16 MailerManager::send($mail);
17 }
18...
19}

Note
Il est possible de personnaliser la création du code généré, ainsi que le préfixe utilisé. L’exemple ci-dessous est implémenté avec la bibliothèque robthree/twofactorauth
.
protected function generate2FACode():string{
$tfa=new TwoFactorAuth();
return $tfa->createSecret();
}
protected function towFACodePrefix():string{
return 'U-';
}
Création de compte
L’activation de la création du compte est également optionnelle :

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

Dans ce cas, la méthode _create doit être surchargée afin de créer le compte :
protected function _create(string $login, string $password): ?bool {
if(!DAO::exists(User::class,'login= ?',[$login])){
$user=new User();
$user->setLogin($login);
$user->setPassword($password);
URequest::setValuesToObject($user);//for the others params in the POST.
return DAO::insert($user);
}
return false;
}
Vous pouvez vérifier la validité/disponibilité du login avant de valider le formulaire de création de compte :
protected function newAccountCreationRule(string $accountName): ?bool {
return !DAO::exists(User::class,'login= ?',[$accountName]);
}

Une action de confirmation (vérification par courriel) peut être demandée à l’utilisateur :
protected function hasEmailValidation(): bool {
return true;
}
protected function _sendEmailValidation(string $email,string $validationURL,string $expire):void {
MailerManager::start();
$mail=new AuthEmailValidationMail();
$mail->to($connected->getEmail());
$mail->setUrl($validationURL);
$mail->setExpire($expire);
MailerManager::send($mail);
}
Note
Il est possible de personnaliser ces parties en surchargeant les méthodes associées, ou en modifiant les interfaces dans les modèles concernés.
Base de données
La classe DAO est en charge des opérations de chargement et de persistance des modèles :
Connexion à la base de données
Vérifiez que les paramètres de connexion à la base de données sont correctement renseignés dans le fichier de configuration :
Ubiquity config -f=database

Connexion transparente
Depuis Ubiquity 2.3.0, la connexion à la base de données se fait automatiquement à la première requête :
use Ubiquity\orm\DAO;
$firstUser=DAO::getById(User::class,1);//Automatically start the database
C’est le cas de toutes les méthodes de la classe DAO utilisées pour effectuer des opérations CRUD.
Connexion explicite
Dans certains cas, cependant, il peut être utile d’établir une connexion explicite à la base de données, notamment pour vérifier la connexion.
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();
}
Connexions multiples
Ajout d’une nouvelle connexion
Ubiquity vous permet de gérer plusieurs connexions à des bases de données.
Avec les webtools
Dans la partie Modèles, choisissez le bouton Add new connection :

Définir les paramètres de configuration de la connexion :

Générer des modèles pour la nouvelle connexion:
Les modèles générés incluent l’annotation @database
ou l’attribut Database
mentionnant leur lien avec la connexion.
<?php
namespace models\tests;
use Ubiquity\attributes\items\Database;
use Ubiquity\attributes\items\Table;
#[Database('tests')]
#[Table('groupe')]
class Groupe{
...
}
<?php
namespace models\tests;
/**
* @database('tests')
* @table('groupe')
*/
class Groupe{
...
}
Les modèles sont générés dans un sous-dossier de models
.
Avec plusieurs connexions, n’oubliez pas d’ajouter la ligne suivante au fichier services.php
:
\Ubiquity\orm\DAO::start();
La méthode start
effectue la correspondance entre chaque modèle et la connexion associée.
Génération des modèles
A partir d’une base de données existante
A partir de zéro
ORM
Note
si vous voulez générer automatiquement les modèles, consultez la partie génération des modèles .
Une classe de type modèle est juste un bon vieil objet php sans héritage.
Les modèles sont situés par défaut dans le dossier appmodels.
Le mappage relationnel d’objets (ORM) repose sur les annotations ou les attributs des membres (depuis PHP8) dans la classe du modèle.
Définition de modèles
Un modèle basique
Un modèle doit définir sa clé primaire en utilisant l’annotation @id sur les membres concernés.
Les membres sérialisés doivent avoir des getters et setters.
Sans autre annotation, une classe correspond à une table du même nom dans la base de données, chaque membre correspond à un champ de cette table.
1namespace models;
2
3use Ubiquity\attributes\items\Id;
4
5class User{
6
7 #[Id]
8 private $id;
9
10 private $firstname;
11
12 public function getFirstname(){
13 return $this->firstname;
14 }
15 public function setFirstname($firstname){
16 $this->firstname=$firstname;
17 }
18}
1 namespace models;
2
3 class User{
4 /**
5 * @id
6 */
7 private $id;
8
9 private $firstname;
10
11 public function getFirstname(){
12 return $this->firstname;
13 }
14 public function setFirstname($firstname){
15 $this->firstname=$firstname;
16 }
17 }
Mappage
Table->Classe
Si le nom de la table est différent du nom de la classe, l’annotation @table permet de préciser le nom de la table.
1namespace models;
2
3use Ubiquity\attributes\items\Table;
4use Ubiquity\attributes\items\Id;
5
6#[Table('user')]
7class User{
8
9 #[Id]
10 private $id;
11
12 private $firstname;
13
14 public function getFirstname(){
15 return $this->firstname;
16 }
17 public function setFirstname($firstname){
18 $this->firstname=$firstname;
19 }
20}
1namespace models;
2
3/**
4 * @table("name"=>"user")
5 */
6class User{
7 /**
8 * @id
9 */
10 private $id;
11
12 private $firstname;
13
14 public function getFirstname(){
15 return $this->firstname;
16 }
17 public function setFirstname($firstname){
18 $this->firstname=$firstname;
19 }
20}
Champ->Membre
Si le nom d’un champ est différent du nom du membre de la classe associé, l’annotation @column permet de spécifier un nom de champ différent.
1namespace models;
2
3use Ubiquity\attributes\items\Table;
4use Ubiquity\attributes\items\Id;
5use Ubiquity\attributes\items\Column;
6
7#[Table('user')
8class User{
9
10 #[Id]
11 private $id;
12
13 #[Column('column_name')]
14 private $firstname;
15
16 public function getFirstname(){
17 return $this->firstname;
18 }
19 public function setFirstname($firstname){
20 $this->firstname=$firstname;
21 }
22}
1namespace models;
2
3/**
4 * @table("user")
5 */
6class User{
7 /**
8 * @id
9 */
10 private $id;
11
12 /**
13 * column("user_name")
14 */
15 private $firstname;
16
17 public function getFirstname(){
18 return $this->firstname;
19 }
20 public function setFirstname($firstname){
21 $this->firstname=$firstname;
22 }
23}
Associations
Note
Convention de nommage
Les noms des champs des clés étrangères sont constitués du nom de la clé primaire de la table référencée suivi du nom de la table référencée dont la première lettre est en majuscule.
Exemple : idUser
pour la table user
dont la clé primaire est id
.
ManyToOne
Un utilisateur appartient à une organisation :

1 namespace models;
2
3use Ubiquity\attributes\items\ManyToOne;
4use Ubiquity\attributes\items\Id;
5use Ubiquity\attributes\items\JoinColumn;
6
7 class User{
8
9 #[Id]
10 private $id;
11
12 private $firstname;
13
14 #[ManyToOne]
15 #[JoinColumn(className: \models\Organization::class, name: 'idOrganization', nullable: false)]
16 private $organization;
17
18 public function getOrganization(){
19 return $this->organization;
20 }
21
22 public function setOrganization($organization){
23 $this->organization=$organization;
24 }
25}
1 namespace models;
2
3 class User{
4 /**
5 * @id
6 */
7 private $id;
8
9 private $firstname;
10
11 /**
12 * @manyToOne
13 * @joinColumn("className"=>"models\\Organization","name"=>"idOrganization","nullable"=>false)
14 */
15 private $organization;
16
17 public function getOrganization(){
18 return $this->organization;
19 }
20
21 public function setOrganization($organization){
22 $this->organization=$organization;
23 }
24}
L’annotation @joinColumn ou l’attribut JoinColumn le spécifie :
Le membre $organisation est une instance de modelsOrganization.
La table user possède une clé étrangère idOrganization faisant référence à la clé primaire de l’organisation.
Cette clé étrangère n’est pas nulle => un utilisateur aura toujours une organisation.
OneToMany
Une organisation a de nombreux utilisateurs :

1namespace models;
2
3use Ubiquity\attributes\items\OneToMany;
4use Ubiquity\attributes\items\Id;
5
6class Organization{
7
8 #[Id]
9 private $id;
10
11 private $name;
12
13 #[OneToMany(mappedBy: 'organization', className: \models\User::class)]
14 private $users;
15}
1namespace models;
2
3class Organization{
4 /**
5 * @id
6 */
7 private $id;
8
9 private $name;
10
11 /**
12 * @oneToMany("mappedBy"=>"organization","className"=>"models\\User")
13 */
14 private $users;
15}
Dans ce cas, l’association est bidirectionnelle.
L’annotation @oneToMany doit juste préciser :
La classe de chaque utilisateur dans le tableau users : modelsUser.
la valeur de @mappedBy est le nom de l’attribut association-mapping du côté propriétaire : $organisation dans la classe User.
ManyToMany
Un utilisateur peut appartenir à des groupes.
Un groupe se compose de plusieurs utilisateurs.

1namespace models;
2
3use Ubiquity\attributes\items\ManyToMany;
4use Ubiquity\attributes\items\Id;
5use Ubiquity\attributes\items\JoinTable;
6
7class User{
8
9 #[Id]
10 private $id;
11
12 private $firstname;
13
14 #[ManyToMany(targetEntity: \models\Group::class, inversedBy: 'users')]
15 #[JoinTable(name: 'groupusers')]
16 private $groups;
17
18}
1namespace models;
2
3class User{
4 /**
5 * @id
6 */
7 private $id;
8
9 private $firstname;
10
11 /**
12 * @manyToMany("targetEntity"=>"models\\Group","inversedBy"=>"users")
13 * @joinTable("name"=>"groupusers")
14 */
15 private $groups;
16
17}
1namespace models;
2
3use Ubiquity\attributes\items\ManyToMany;
4use Ubiquity\attributes\items\Id;
5use Ubiquity\attributes\items\JoinTable;
6
7class Group{
8
9 #[Id]
10 private $id;
11
12 private $name;
13
14 #[ManyToMany(targetEntity: \models\User::class, inversedBy: 'groups')]
15 #[JoinTable(name: 'groupusers')]
16 private $users;
17
18}
1namespace models;
2
3class Group{
4 /**
5 * @id
6 */
7 private $id;
8
9 private $name;
10
11 /**
12 * @manyToMany("targetEntity"=>"models\\User","inversedBy"=>"groups")
13 * @joinTable("name"=>"groupusers")
14 */
15 private $users;
16
17}
Si les conventions de nommage ne sont pas respectées pour les clés étrangères,
il est possible de spécifier les champs associés.
1namespace models;
2
3use Ubiquity\attributes\items\ManyToMany;
4use Ubiquity\attributes\items\Id;
5use Ubiquity\attributes\items\JoinTable;
6
7class Group{
8
9 #[Id]
10 private $id;
11
12 private $name;
13
14 #[ManyToMany(targetEntity: \models\User::class, inversedBy: 'groupes')]
15 #[JoinTable(name: 'groupeusers',
16 joinColumns: ['name'=>'id_groupe','referencedColumnName'=>'id'],
17 inverseJoinColumns: ['name'=>'id_user','referencedColumnName'=>'id'])]
18 private $users;
19
20}
1namespace models;
2
3class Group{
4 /**
5 * @id
6 */
7 private $id;
8
9 private $name;
10
11 /**
12 * @manyToMany("targetEntity"=>"models\\User","inversedBy"=>"groupes")
13 * @joinTable("name"=>"groupeusers",
14 * "joinColumns"=>["name"=>"id_groupe","referencedColumnName"=>"id"],
15 * "inverseJoinColumns"=>["name"=>"id_user","referencedColumnName"=>"id"])
16 */
17 private $users;
18
19}
Annotations ORM
Annotations pour les classes
@annotation |
rôle |
propriétés |
rôle |
---|---|---|---|
@database |
Définit l’offset de la base de données associée (défini dans le fichier de configuration) |
||
@table |
Définit le nom de la table associée. |
Annotations pour les membres
@annotation |
rôle |
propriétés |
rôle |
---|---|---|---|
@id |
Définit la ou les clés primaires. |
||
@column |
Spécifie les caractéristiques du champ associé. |
name |
Nom du champ associé |
nullable |
true si la valeur peut être nulle |
||
dbType |
Type du champ dans la base de données |
||
@transient |
Indique que le champ n’est pas persistant. |
Associations
@annotation (extends) |
rôle |
properties [optional] |
rôle |
---|---|---|---|
@manyToOne |
Définit une association à valeur unique avec une autre classe d’entité. |
||
@joinColumn (@column) |
Indique la clé étrangère dans l’association manyToOne. |
className |
Classe du membre |
[referencedColumnName] |
Nom de la colonne associée |
||
@oneToMany |
Définit une association à valeurs multiples avec une autre classe d’entité. |
className |
Classe des objets du membre |
[mappedBy] |
Nom de l’attribut de mappage d’association du côté propriétaire. |
||
@manyToMany |
Définit une association à valeurs multiples avec une multiplicité de plusieurs à plusieurs. |
targetEntity |
Classe des objets du membre |
[inversedBy] |
Nom du membre associé du côté inverse |
||
[mappedBy] |
Nom du membre associé du côté propriétaire |
||
@joinTable |
Définit la table d’association pour l’association many-to-many. |
name |
Le nom de la table d’association |
[joinColumns] |
@column => name et referencedColumnName pour ce côté |
||
[inverseJoinColumns] |
@column => name et referencedColumnName pour l’autre côté |
DAO
La classe DAO est en charge des opérations de chargement et de persistance des modèles :
Connexion à la base de données
Vérifiez que les paramètres de connexion à la base de données sont correctement renseignés dans le fichier de configuration :
Ubiquity config -f=database
Depuis la version 2.3.0
Le démarrage de la base de données avec DAO::startDatabase($config)
dans le fichier services.php est inutile, nul besoin de démarrer la base de données, la connexion est faite automatiquement à la première requête. Utilisez DAO::start()
dans le fichier app/config/services.php lorsque vous utilisez plusieurs bases de données (avec la fonctionnalité multi db).
Chargement de données
Chargement d’une instance
Chargement d’une instance de la classe modelsUser avec l’identifiant 5.
use Ubiquity\orm\DAO;
use models\User;
$user=DAO::getById(User::class, 5);
Chargement d’une instance en utilisant une condition :
use Ubiquity\orm\DAO;
use models\User;
DAO::getOne(User::class, 'name= ?',false,['DOE']);
Chargement de BelongsTo
Par défaut, les membres définis par une relation belongsTo sont automatiquement chargés.
Chaque utilisateur n’appartient qu’à une seule catégorie :
$user=DAO::getById(User::class,5);
echo $user->getCategory()->getName();
Il est possible d’empêcher ce chargement par défaut ; le troisième paramètre permet de charger ou non les membres belongsTo :
$user=DAO::getOne(User::class,5, false);
echo $user->getCategory();// NULL
Chargement de HasMany
Le chargement des membres hasMany doit toujours être explicite ; le troisième paramètre permet le chargement explicite des membres.
Chaque utilisateur fait partie de plusieurs groupes :
$user=DAO::getOne(User::class,5,['groupes']);
foreach($user->getGroupes() as $groupe){
echo $groupe->getName().'<br>';
}
Clé primaire composite
Soit le modèle ProductDetail correspondant à un produit commandé sur une commande et dont la clé primaire est composite :
1 namespace models;
2
3use Ubiquity\attributes\items\Id;
4
5 class ProductDetail{
6
7 #[Id]
8 private $idProduct;
9
10 #[Id]
11 private $idCommand;
12
13 ...
14 }
1 namespace models;
2
3 class ProductDetail{
4 /**
5 * @id
6 */
7 private $idProduct;
8
9 /**
10 * @id
11 */
12 private $idCommand;
13
14 ...
15 }
Le deuxième paramètre $keyValues peut être un tableau si la clé primaire est composite :
$productDetail=DAO::getOne(ProductDetail::class,[18,'BF327']);
echo 'Command:'.$productDetail->getCommande().'<br>';
echo 'Product:'.$productDetail->getProduct().'<br>';
Chargement de plusieurs objets
Chargement d’instances de la classe User :
$users=DAO::getAll(User::class);
foreach($users as $user){
echo $user->getName()."<br>";
}
Requêtage utilisant des conditions
Requêtes simples
Le paramètre condition est équivalent à la partie WHERE d’une instruction SQL :
$users=DAO::getAll(User::class,'firstName like "bren%" and not suspended',false);
Pour éviter les injections SQL et bénéficier de la préparation des statements, il est préférable d’effectuer une requête paramétrée :
$users=DAO::getAll(User::class,'firstName like ? and suspended= ?',false,['bren%',false]);
UQueries
L’utilisation de U-queries permet de poser des conditions sur les membres associés :
Sélection des utilisateurs dont l’organisation possède le domaine lecnam.net :
$users=DAO::uGetAll(User::class,'organization.domain= ?',false,['lecnam.net']);
Il est possible de visualiser la requête générée dans les logs (si le logging est activé) :

Le résultat peut être vérifié en sélectionnant tous les utilisateurs de cette organisation :
$organization=DAO::getOne(Organization::class,'domain= ?',['users'],['lecnam.net']);
$users=$organization->getUsers();
Les logs correspondants :

Comptage
Test de l’existance
if(DAO::exists(User::class,'lastname like ?',['SMITH'])){
//there's a Mr SMITH
}
Comptage
Pour compter les instances, ce qu’il ne faut pas faire, si les utilisateurs ne sont pas déjà chargés :
$users=DAO::getAll(User::class);
echo "there are ". \count($users) ." users";
Ce qui doit être fait :
$count=DAO::count(User::class);
echo "there are $count users";
Avec une condition :
$notSuspendedCount=DAO::count(User::class, 'suspended = ?', [false]);
Avec une condition sur les objets associés :
Nombre d’utilisateurs appartenant à l’organisation nommée OTAN.
$count=DAO::uCount(User::class,'organization.name= ?',['OTAN']);
Modification de données
Ajout d’une instance
Ajout d’une organisation :
$orga=new Organization();
$orga->setName('Foo');
$orga->setDomain('foo.net');
if(DAO::save($orga)){
echo $orga.' added in database';
}
Ajout d’une instance d’utilisateur, dans une organisation :
$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;
}
Mise à jour d’une instance
Dans un premier temps, l’instance doit être chargée :
$orga=DAO::getOne(Organization::class,'domain= ?',false,['foo.net']);
$orga->setAliases('foo.org');
if(DAO::save($orga)){
echo $orga.' updated in database';
}
Suppression d’une instance
Si l’instance est déjà chargée depuis la base de données :
$orga=DAO::getById(Organization::class,5,false);
if(DAO::remove($orga)){
echo $orga.' deleted from database';
}
Si l’instance n’est pas chargée, il est plus approprié d’utiliser la méthode delete :
if(DAO::delete(Organization::class,5)){
echo 'Organization deleted from database';
}
Suppression de plusieurs instances
Suppression de plusieurs instances sans chargement préalable :
if($res=DAO::deleteAll(models\User::class, 'id in (?,?,?)',[1,2,3])){
echo "$res elements deleted";
}
Requêtes en masse (bulk)
Les requêtes en masse permettent d’effectuer plusieurs opérations (insertion, modification ou suppression) en une seule requête, ce qui contribue à améliorer les performances.
Insertions en masse
Exemple d’insertion :
$u = new User();
$u->setName('Martin1');
DAO::toInsert($u);
$u = new User();
$u->setName('Martin2');
DAO::toInsert($u);
//Perform inserts
DAO::flushInserts();
Mises à jour en masse
Exemple de mise à jour :
$users = DAO::getAll(User::class, 'name like ?', false, [
'Martin%'
]);
foreach ($users as $user) {
$user->setName(\strtoupper($user->getName()));
DAO::toUpdate($user);
}
DAO::flushUpdates();
Suppressions en masse
Exemple de suppression :
$users = DAO::getAll(User::class, 'name like ?', false, [
'BULK%'
]);
DAO::toDeletes($users);
DAO::flushDeletes();
La méthode DAO::flush() peut être appelée si des insertions, des mises à jour ou des suppressions sont en attente.
Transactions
Transactions explicites
Toutes les opérations DAO peuvent être insérées dans une transaction, ce qui permet d’atomiser une série de changements :
try{
DAO::beginTransaction();
$orga=new Organization();
$orga->setName('Foo');
DAO::save($orga);
$user=new User();
$user->setFirstname('DOE');
$user->setOrganization($orga);
DAO::save($user);
DAO::commit();
}catch (\Exception $e){
DAO::rollBack();
}
En cas de bases de données multiples définies dans la configuration, les méthodes liées aux transactions peuvent prendre l’offset de base de données défini en paramètre.
DAO::beginTransaction('db-messagerie');
//some DAO operations on messagerie models
DAO::commit('db-messagerie');
Transactions implicites
Certaines méthodes DAO utilisent implicitement les transactions pour regrouper les opérations d’insertion, de mise à jour ou de suppression.
$users=DAO::getAll(User::class);
foreach ($users as $user){
$user->setSuspended(true);
DAO::toUpdate($user);
}
DAO::updateGroups();//Perform updates in a transaction
Classe SDAO
La classe SDAO accélère les opérations CRUD pour les classes métier sans relations.
Les modèles doivent dans ce cas déclarer uniquement des membres publics, et ne pas respecter l’encapsulation habituelle.
1 namespace models;
2 class Product{
3 /**
4 * @id
5 */
6 public $id;
7
8 public $name;
9
10 ...
11 }
La classe SDAO hérite de DAO et possède les mêmes méthodes pour effectuer des opérations CRUD.
use Ubiquity\orm\DAO;
$product=DAO::getById(Product::class, 5);
Requêtes DAO préparées
La préparation de certaines requêtes peut améliorer les performances avec les serveurs Swoole, Workerman ou Roadrunner.
Cette préparation initialise les objets qui seront ensuite utilisés pour exécuter la requête.
Cette initialisation est effectuée au démarrage du serveur, ou au démarrage de chaque worker, si un tel événement existe.
Exemple Swoole
Préparation
$swooleServer->on('workerStart', function ($srv) use (&$config) {
\Ubiquity\orm\DAO::startDatabase($config);
\Ubiquity\orm\DAO::prepareGetById('user', User::class);
\Ubiquity\orm\DAO::prepareGetAll('productsByName', Product::class,'name like ?');
});
Utilisation
public function displayUser($idUser){
$user=DAO::executePrepared('user',[1]);
echo $user->getName();
}
public function displayProducts($name){
$products=DAO::executePrepared('productsByName',[$name]);
...
}
Requête
Note
Pour toutes les fonctionnalités Http, Ubiquity utilise des classes techniques contenant des méthodes statiques. C’est un choix de conception pour éviter l’injection de dépendances qui dégraderait les performances.
La classe URequest offre des fonctionnalités supplémentaires permettant de manipuler plus facilement les tableaux php natifs $_POST et $_GET.
Récupération de données
A partir de la méthode get
La méthode get renvoie la valeur null si la clé name n’existe pas dans les variables get.
use Ubiquity\utils\http\URequest;
$name=URequest::get("name");
La méthode get peut être appelée avec le second paramètre facultatif qui renvoie une valeur si la clé n’existe pas dans les variables get.
$name=URequest::get("name",1);
A partir de la méthode post
La méthode post renvoie la valeur null si la clé name n’existe pas dans les variables post.
use Ubiquity\utils\http\URequest;
$name=URequest::post("name");
La méthode post peut être appelée avec le second paramètre facultatif qui renvoie une valeur si la clé n’existe pas dans les variables post.
$name=URequest::post("name",1);
La méthode getPost applique un callback aux éléments du tableau $_POST et les retourne (callback par défaut : htmlEntities) :
$protectedValues=URequest::getPost();
Récupération et affectation de données multiples
Il est courant d’affecter les valeurs d’un tableau associatif aux membres d’un objet.
C’est le cas par exemple lors de la validation d’un formulaire de modification d’objet.
La méthode setValuesToObject effectue cette opération :
Considérons une classe User :
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;
}
}
Considérons un formulaire pour modifier un utilisateur :
<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>
L’action update du contrôleur Users doit mettre à jour l’instance utilisateur à partir des valeurs POST.
L’utilisation de la méthode setPostValuesToObject permet d’éviter l’affectation des variables postées une à une aux membres de l’objet.
Il est également possible d’utiliser la méthode setGetValuesToObject pour la méthode get, ou setValuesToObject pour affecter les valeurs de tout tableau associatif à un objet.
1 namespace controllers;
2
3 use Ubiquity\orm\DAO;
4 use Uniquity\utils\http\URequest;
5
6 class Users extends BaseController{
7 ...
8 public function update(){
9 $user=DAO::getOne("models\User",URequest::post("id"));
10 URequest::setPostValuesToObject($user);
11 DAO::update($user);
12 }
13 }
Note
Les méthodes SetValuesToObject utilisent des setters pour modifier les membres d’un objet. La classe concernée doit donc implémenter des setters pour tous les membres modifiables.
Test de la requête
isPost
La méthode isPost renvoie true si la requête a été soumise via la méthode POST :
Dans le cas ci-dessous, la méthode initialize ne charge la vue vHeader.html que si la requête n’est pas une requête Ajax.
1 namespace controllers;
2
3 use Ubiquity\orm\DAO;
4 use Ubiquity\utils\http\URequest;
5
6 class Users extends BaseController{
7 ...
8 public function update(){
9 if(URequest::isPost()){
10 $user=DAO::getOne("models\User",URequest::post("id"));
11 URequest::setPostValuesToObject($user);
12 DAO::update($user);
13 }
14 }
15 }
isAjax
La méthode isAjax renvoie true si la requête est une requête Ajax :
1 ...
2 public function initialize(){
3 if(!URequest::isAjax()){
4 $this->loadView("main/vHeader.html");
5 }
6 }
7 ...
isCrossSite
La méthode isCrossSite vérifie que la requête n’est pas cross-site.
Réponse
Note
Pour toutes les fonctionnalités Http, Ubiquity utilise des classes techniques contenant des méthodes statiques. C’est un choix de conception pour éviter l’injection de dépendances qui dégraderait les performances.
La classe UResponse ne gère que les en-têtes, pas le corps de la réponse, qui est conventionnellement fourni par le contenu affiché par les appels utilisés pour écrire des données (echo, print…).
La classe UResponse offre des fonctionnalités supplémentaires permettant de manipuler plus facilement les en-têtes de réponse.
Ajout ou modification d’en-têtes
use Ubiquity\utils\http\UResponse;
$animal='camel';
UResponse::header('Animal',$animal);
Forcer plusieurs en-têtes du même type :
UResponse::header('Animal','monkey',false);
Force le code de réponse HTTP à la valeur spécifiée :
UResponse::header('Messages',$message,false,500);
Définition d’en-têtes spécifiques
content-type
Définir le type de contenu de la réponse à application/json :
UResponse::asJSON();
Définir le type de contenu de la réponse à text/html :
UResponse::asHtml();
Définir le type de contenu de la réponse à plain/text :
UResponse::asText();
Définir le type de contenu de la réponse à application/xml :
UResponse::asXml();
Définition d’un encodage spécifique (la valeur par défaut est toujours utf-8) :
UResponse::asHtml('iso-8859-1');
Cache
Forcer la désactivation du cache du navigateur :
UResponse::noCache();
Accept
Définit les types de contenu, exprimés sous forme de types MIME, que le client est en mesure de comprendre.
Voir Acceptation des valeurs par défaut.
UResponse::setAccept('text/html');
En-têtes de réponse CORS
Cross-Origin Resource Sharing (CORS) est un mécanisme qui utilise des en-têtes HTTP supplémentaires pour indiquer au navigateur que votre application Web exécutée à une origine (domaine) a la permission d’accéder à des ressources sélectionnées sur un serveur à une origine différente.
Access-Control-Allow-Origin
Réglage de l’origine autorisée :
UResponse::setAccessControlOrigin('http://myDomain/');
Access-Control-Allow-methods
Définir les méthodes autorisées :
UResponse::setAccessControlMethods('GET, POST, PUT, DELETE, PATCH, OPTIONS');
Access-Control-Allow-headers
Définir les en-têtes autorisés :
UResponse::setAccessControlHeaders('X-Requested-With, Content-Type, Accept, Origin, Authorization');
Activation globale de CORS
activer CORS pour un domaine avec des valeurs par défaut :
Méthodes autorisées : « GET, POST, PUT, DELETE, PATCH, OPTIONS ».
En-têtes autorisés : « X-Requested-With, Content-Type, Accept, Origin, Authorization ».
UResponse::enableCors('http://myDomain/');
Test des en-têtes de réponse
Vérifier si les en-têtes ont été envoyés :
if(!UResponse::isSent()){
//do something if headers are not send
}
Test si le type de contenu de la réponse est application/json :
Important
Cette méthode ne fonctionne que si vous avez utilisé la classe UResponse pour définir les en-têtes.
if(UResponse::isJSON()){
//do something if response is a JSON response
}
Session
Note
Pour toutes les fonctionnalités Http, Ubiquity utilise des classes techniques contenant des méthodes statiques. C’est un choix de conception pour éviter l’injection de dépendances qui dégraderait les performances.
La classe USession fournit des fonctionnalités supplémentaires pour manipuler plus facilement le tableau natif $_SESSION de php.
Démarrer la session
La session Http est lancée automatiquement si la clé sessionName est renseignée dans le fichier de configuration app/config.php :
<?php
return array(
...
"sessionName"=>"key-for-app",
...
);
Si la clé sessionName n’est pas renseignée, il est nécessaire de démarrer la session explicitement pour l’utiliser :
use Ubiquity\utils\http\USession;
...
USession::start("key-for-app");
Note
Le paramètre name est facultatif mais recommandé pour éviter les conflits de variables.
Création ou modification d’une variable de session
use Ubiquity\utils\http\USession;
USession::set("name","SMITH");
USession::set("activeUser",$user);
Récupération de données
La méthode get renvoie la valeur null si la clé name n’existe pas dans les variables de session.
use Ubiquity\utils\http\USession;
$name=USession::get("name");
La méthode get peut être appelée avec le second paramètre facultatif qui renvoie une valeur si la clé n’existe pas dans les variables de session.
$name=USession::get("page",1);
Note
La méthode session est un alias de la méthode get.
La méthode getAll renvoie toutes les variables de la session :
$sessionVars=USession::getAll();
Test
La méthode exists teste l’existence d’une variable en session.
if(USession::exists("name")){
//do something when name key exists in session
}
La méthode isStarted vérifie que la session est démarrée.
if(USession::isStarted()){
//do something if the session is started
}
Suppression de variables
La méthode delete permet de supprimer une variable de session :
USession::delete("name");
Clôture explicite de la session
La méthode terminate ferme correctement la session et supprime toutes les variables de session créées :
USession::terminate();
Vues
Ubiquity utilise Twig comme moteur de template par défaut (voir Documentation Twig).
Les vues sont situées dans le dossier app/views. Elles doivent avoir l’extension .html pour être interprétées par Twig.
Ubiquity peut également être utilisé avec un système de vues PHP, pour obtenir de meilleures performances, ou simplement pour permettre l’utilisation de php dans les vues.
Chargement
Les vues sont chargées depuis les contrôleurs :
1 namespace controllers;
2
3 class Users extends BaseController{
4 ...
5 public function index(){
6 $this->loadView("index.html");
7 }
8 }
9 }
Chargement de la vue par défaut
Si vous utilisez la méthode de dénomination des vues par défaut :
La vue par défaut associée à une action dans un contrôleur est située dans le dossier views/controller-name/action-name
:
views
│
└ Users
└ info.html
1 namespace controllers;
2
3 class Users extends BaseController{
4 ...
5 public function info(){
6 $this->loadDefaultView();
7 }
8 }
9 }
Chargement et passage de variables
Les variables sont transmises à la vue dans un tableau associatif. Chaque clé crée une variable du même nom dans la vue.
1 namespace controllers;
2
3 class Users extends BaseController{
4 ...
5 public function display($message,$type){
6 $this->loadView("users/display.html",["message"=>$message,"type"=>$type]);
7 }
8 }
9 }
Dans ce cas, il est utile d’appeler compact
pour créer un tableau contenant des variables et leurs valeurs :
1 namespace controllers;
2
3 class Users extends BaseController{
4 ...
5 public function display($message,$type){
6 $this->loadView("users/display.html",compact("message","type"));
7 }
8 }
9 }
Affichage dans une vue
La vue peut alors afficher les variables :
<h2>{{type}}</h2>
<div>{{message}}</div>
Les variables peuvent également avoir des attributs ou des éléments auxquels vous pouvez accéder.
Vous pouvez utiliser un point (.) pour accéder aux attributs d’une variable (méthodes ou propriétés d’un objet PHP, ou éléments d’un tableau PHP), ou la syntaxe dite « d’indice » ([]) :
{{ foo.bar }}
{{ foo['bar'] }}
Fonctions supplémentaires Ubiquity
La variable globale app
donne accès à des fonctionnalités prédéfinies d’Ubiquity Twig :
app
est une instance de la classeFramework
et donne accès aux méthodes publiques de cette classe.
Obtenir la version installée du framework :
{{ app.version() }}
Renvoie les noms du contrôleur actif et de l’action :
{{ app.getController() }}
{{ app.getAction() }}
Retourne les wrapper classes globales :
Pour la requête :
{{ app.getRequest().isAjax() }}
Pour la session :
{{ app.getSession().get('homePage','index') }}
voir Classeframework dans API pour plus d’informations.
Chargement des vue PHP
Désactivez si nécessaire Twig dans le fichier de configuration en supprimant la clé templateEngine.
Créez ensuite un contrôleur qui hérite de « SimpleViewController », ou de « SimpleViewAsyncController » si vous utilisez Swoole ou Workerman :
1 namespace controllers;
2
3 use Ubiquity\controllers\SimpleViewController;
4
5 class Users extends SimpleViewController{
6 ...
7 public function display($message,$type){
8 $this->loadView("users/display.php",compact("message","type"));
9 }
10 }
11 }
Note
Dans ce cas, les fonctions de chargement des assets et des thèmes ne sont pas prises en charge.
Assets
Les assets correspondent aux fichiers javascript, feuilles de style, polices de caractères, images à inclure dans votre application.
Ils se trouvent dans le dossier public/assets.
Il est préférable de séparer les ressources en sous-dossiers par type.
public/assets
├ css
│ ├ style.css
│ └ semantic.min.css
└ js
└ jquery.min.js
Intégration de fichiers css ou js :
{{ 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 avec des paramètres supplémentaires :
{{ css('https://cdn.jsdelivr.net/npm/foundation-sites@6.5.3/dist/css/foundation.min.css',{crossorigin: 'anonymous',integrity: 'sha256-/PFxCnsMh+...'}) }}
Thèmes
Note
Les thèmes sont totalement inutiles si vous n’avez qu’une seule présentation à appliquer.
Ubiquity supporte des thèmes qui peuvent avoir leurs propres assets et vues selon le modèle du thème à afficher par le contrôleur. Chaque action du contrôleur peut utiliser un thème spécifique, ou peut utiliser le thème par défaut configuré dans le fichier config.php dans templateEngineOptions => array("activeTheme" => "semantic")
.
Ubiquity est livré avec 3 thèmes par défaut : Bootstrap, Foundation et Semantic-UI.
Installation d’un thème
Avec les devtools, exécuter :
Ubiquity install-theme bootstrap
Le thème installé est l’un des suivants : bootstrap, foundation ou semantic.
Avec webtools, vous pouvez faire de même, à condition que les devtools soient installés et accessibles (dossier Ubiquity ajouté dans le chemin du système) :

Création d’un nouveau thème
Avec les devtools, exécuter :
Ubiquity create-theme myTheme
Création d’un nouveau thème à partir de Bootstrap, Semantic…
Avec les devtools, exécuter :
Ubiquity create-theme myBootstrap -x=bootstrap
Avec les webtools :

Structure et fonctionnement des thèmes
Structure
Dossier vues d’un thème
Les vues d’un thème sont situées dans le dossier app/views/themes/theme-name.
app/views
└ themes
├ bootstrap
│ └ main
│ ├ vHeader.html
│ └ vFooter.html
└ semantic
└ main
├ vHeader.html
└ vFooter.html
La classe de base contrôleur est responsable du chargement des vues pour définir l’en-tête et le pied de page de chaque page :
1 <?php
2 namespace controllers;
3
4 use Ubiquity\controllers\Controller;
5 use Ubiquity\utils\http\URequest;
6
7 /**
8 * ControllerBase.
9 **/
10 abstract class ControllerBase extends Controller{
11 protected $headerView = "@activeTheme/main/vHeader.html";
12 protected $footerView = "@activeTheme/main/vFooter.html";
13
14 public function initialize() {
15 if (! URequest::isAjax ()) {
16 $this->loadView ( $this->headerView );
17 }
18 }
19 public function finalize() {
20 if (! URequest::isAjax ()) {
21 $this->loadView ( $this->footerView );
22 }
23 }
24 }
Dossier assets d’un thème
Les assets d’un thème sont créés dans le dossier public/assets/theme-name
.
La structure du dossier des assets est souvent la suivante :
public/assets/bootstrap
├ css
│ ├ style.css
│ └ all.min.css
├ scss
│ ├ myVariables.scss
│ └ app.scss
├ webfonts
│
└ img
Changement du thème actif
Changement persistant
activeTheme est défini dans app/config/config.php
avec templateEngineOptions => array("activeTheme" => "semantic")
Le thème actif peut être modifié avec les devtools :
Ubiquity config:set --templateEngineOptions.activeTheme=bootstrap
Il est également possible de le faire à partir de la page d’accueil, ou avec les webtools :
Depuis la page d’accueil :

Depuis les webtools :

Cette modification peut également être effectuée au moment de l’exécution :
Depuis un contrôleur :
ThemeManager::saveActiveTheme('bootstrap');
Changement local non persistant
Pour définir un thème spécifique pour toutes les actions d’un contrôleur, la méthode la plus simple consiste à surdéfinir la méthode initialize du contrôleur :
1 namespace controllers;
2
3 use \Ubiquity\themes\ThemesManager;
4
5 class Users extends BaseController{
6
7 public function initialize(){
8 parent::intialize();
9 ThemesManager::setActiveTheme('bootstrap');
10 }
11 }
Ou si le changement ne doit concerner qu’une seule action :
1 namespace controllers;
2
3 use \Ubiquity\themes\ThemesManager;
4
5 class Users extends BaseController{
6
7 public function doStuff(){
8 ThemesManager::setActiveTheme('bootstrap');
9 ...
10 }
11 }
Changement de thème conditionnel, indépendamment du contrôleur :
Exemple avec une modification du thème en fonction d’une variable passée dans l’URL
1use Ubiquity\themes\ThemesManager;
2use Ubiquity\utils\http\URequest;
3
4...
5
6ThemesManager::onBeforeRender(function(){
7 if(URequest::get("th")=='bootstrap'){
8 ThemesManager::setActiveTheme("bootstrap");
9 }
10 });
Support pour appareils mobiles
Ajouter un outil de détection des appareils mobiles.
Installer MobileDetect :
composer require mobiledetect/mobiledetectlib
Il est généralement plus facile de créer des vues différentes par appareil.
Créez un thème spécifique pour la partie mobile (en créant un dossier views/themes/mobile
et en y plaçant les vues spécifiques aux appareils mobiles).
Il est important dans ce cas d’utiliser les mêmes noms de fichiers pour la partie mobile et la partie non-mobile.
Il est également conseillé dans ce cas que tous les chargements de vues utilisent l’espace de noms @activeTheme :
$this->loadView("@activeTheme/index.html");
index.html doit être disponible dans ce cas dans les dossiers views
et views/themes/mobile
.
Détection globale des mobiles (à partir de services.php)
1use Ubiquity\themes\ThemesManager;
2
3...
4
5ThemesManager::onBeforeRender(function () {
6 $mb = new \Mobile_Detect();
7 if ($mb->isMobile()) {
8 ThemesManager::setActiveTheme('mobile');
9 }
10});
Détection locale (depuis un contrôleur)
1use Ubiquity\themes\ThemesManager;
2
3...
4
5 public function initialize() {
6 $mb = new \Mobile_Detect();
7 if ($mb->isMobile()) {
8 ThemesManager::setActiveTheme('mobile');
9 }
10 parent::initialize();
11 }
Chargement de vues et d’assets
Vues
Pour charger une vue à partir du dossier activeTheme, vous pouvez utiliser l’espace de nom @activeTheme :
1 namespace controllers;
2
3 class Users extends BaseController{
4
5 public function action(){
6 $this->loadView('@activeTheme/action.html');
7 ...
8 }
9 }
Si le activeTheme est bootstrap, la vue chargée est app/views/themes/bootstrap/action.html
.
Vue par défaut
Si vous suivez les conventions de nommage des vues Ubiquity, la vue par défaut chargée pour une action dans un contrôleur lorsqu’un thème est actif est : app/views/themes/theme-name/controller-name/action-name.html
.
Par exemple, si le thème actif est bootstrap, la vue par défaut pour l’action display dans le contrôleur Users doit se trouver dans le fichier app/views/themes/bootstrap/Users/display.html
.
1 namespace controllers;
2
3 class Users extends BaseController{
4
5 public function display(){
6 $this->loadDefaultView();
7 ...
8 }
9 }
Note
Les commandes devtools pour créer un contrôleur ou une action et leur vue associée utilisent le dossier @activeTheme si un thème est actif.
Ubiquity controller Users -v
Ubiquity action Users.display -v
Chargement des assets
Le mécanisme est le même que pour les vues : l’espace de nom @activeTheme
fait référence au dossier public/assets/theme-name/
.
{{ css('@activeTheme/css/style.css') }}
{{ js('@activeTheme/js/scripts.js') }}
{{ img('@activeTheme/img/image-name.png', {alt: 'Image Alt Name', class: 'css-class'}) }}
Si le thème bootstrap est actif, le dossier des assets est public/assets/bootstrap/
.
Compilation Css
Pour Bootstrap ou foundation, installez sass :
npm install -g sass
Ensuite, exécutez depuis le dossier racine du projet :
Pour bootstrap:
ssass public/assets/bootstrap/scss/app.scss public/assets/bootstrap/css/style.css --load-path=vendor
Pour foundation:
ssass public/assets/foundation/scss/app.scss public/assets/foundation/css/style.css --load-path=vendor
jQuery Semantic-UI
Par défaut, Ubiquity utilise la bibliothèque phpMv-UI pour la partie UI.
PhpMv-UI permet de créer des composants basés sur Semantic-UI ou Bootstrap et de générer des scripts jQuery en PHP.
Cette bibliothèque est utilisée pour l’interface d’administration webtools.
Intégration
Par défaut, une variable $jquery est injectée dans les contrôleurs au moment de l’exécution.
Cette opération se fait par injection de dépendance, dans app/config.php
:
...
"di"=>array(
"@exec"=>array(
"jquery"=>function ($controller){
return \Ajax\php\ubiquity\JsUtils::diSemantic($controller);
}
)
)
...
Il n’y a donc rien à faire,
mais pour faciliter son utilisation et permettre la complétion de code dans un contrôleur, il est recommandé d’ajouter la documentation de code suivante :
/**
* Controller FooController
* @property \Ajax\php\ubiquity\JsUtils $jquery
**/
class FooController extends ControllerBase{
public function index(){}
}
jQuery
Href en requêtes ajax
Créez un nouveau contrôleur et sa vue associée, puis définissez les routes suivantes :
1namespace controllers;
2
3class FooController extends ControllerBase {
4
5 public function index() {
6 $this->loadview("FooController/index.html");
7 }
8
9 /**
10 *
11 *@get("a","name"=>"action.a")
12 */
13 public function aAction() {
14 echo "a";
15 }
16
17 /**
18 *
19 *@get("b","name"=>"action.b")
20 */
21 public function bAction() {
22 echo "b";
23 }
24}
La vue associée :
<a href="{{path('action.a')}}">Action a</a>
<a href="{{path('action.b')}}">Action b</a>
Initialiser le cache du routeur :
Ubiquity init:cache -t=controllers
Testez cette page dans votre navigateur à l’adresse http://127.0.0.1:8090/FooController
.
Transformation des requêtes en requêtes Ajax
Le résultat de chaque requête ajax doit être affiché dans une zone de la page définie par son sélecteur jQuery (.result span
).
namespace controllers;
/**
* @property \Ajax\php\ubiquity\JsUtils $jquery
*/
class FooController extends ControllerBase {
public function index() {
$this->jquery->getHref('a','.result span');
$this->jquery->renderView("FooController/index.html");
}
...
}
<a href="{{path('action.a')}}">Action a</a>
<a href="{{path('action.b')}}">Action b</a>
<div class='result'>
Selected action:
<span>No One</span>
</div>
{{ script_foot | raw }}
Note
La variable script_foot
contient le script jquery généré par la méthode renderView. Le filtre raw marque la valeur comme étant « sûre », ce qui signifie que dans un environnement où l’échappement automatique est activé, cette variable ne sera pas échappée.
Ajoutons un peu de css pour le rendre plus professionnel :
<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 }}
Si nous voulons ajouter un nouveau lien dont le résultat doit être affiché dans une autre zone, il est possible de le spécifier via l’attribut data-target.
La nouvelle action
namespace controllers;
class FooController extends ControllerBase {
...
/**
*@get("c","name"=>"action.c")
*/
public function cAction() {
echo \rand(0, 1000);
}
}
La vue associée :
<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 }}

Définition des attributs de la requête ajax :
Dans l’exemple suivant, les paramètres passés à la variable attributs de la méthode getHref
:
supprimer l’historique de la navigation,
rendre le loader ajax interne au bouton cliqué.
1namespace controllers;
2
3/**
4 * @property \Ajax\php\ubiquity\JsUtils $jquery
5 */
6class FooController extends ControllerBase {
7
8 public function index() {
9 $this->jquery->getHref('a','.result span', [
10 'hasLoader' => 'internal',
11 'historize' => false
12 ]);
13 $this->jquery->renderView("FooController/index.html");
14 }
15 ...
16}
Note
Il est possible d’utiliser la méthode postHref
pour utiliser le POST http.
Requêtes ajax classiques
Pour cet exemple, créez la base de données suivante :
CREATE DATABASE `uguide` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE `uguide`;
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`firstname` varchar(30) NOT NULL,
`lastname` varchar(30) NOT NULL,
`password` varchar(30) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `user` (`id`, `firstname`, `lastname`) VALUES
(1, 'You', 'Evan'),
(2, 'Potencier', 'Fabien'),
(3, 'Otwell', 'Taylor');
ALTER TABLE `user` ADD PRIMARY KEY (`id`);
ALTER TABLE `user`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
Connectez l’application à la base de données, et générez la classe User :
Avec les devtools :
Ubiquity config:set --database.dbName=uguide
Ubiquity all-models
Créer un nouveau contrôleur UsersJqueryController.
Ubiquity controller UsersJqueryController -v
Créez les actions suivantes dans UsersJqueryController :

Action index
L’action index doit afficher un bouton pour obtenir la liste des utilisateurs, chargée via une requête ajax :
1namespace controllers;
2
3/**
4 * Controller UsersJqueryController
5 *
6 * @property \Ajax\php\ubiquity\JsUtils $jquery
7 * @route("users")
8 */
9class UsersJqueryController extends ControllerBase {
10
11 /**
12 *
13 * {@inheritdoc}
14 * @see \Ubiquity\controllers\Controller::index()
15 * @get
16 */
17 public function index() {
18 $this->jquery->getOnClick('#users-bt', Router::path('display.users'), '#users', [
19 'hasLoader' => 'internal'
20 ]);
21 $this->jquery->renderDefaultView();
22 }
23}
La vue par défaut associée à l’action index :
<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 }}
Action displayUsers
Tous les utilisateurs sont affichés, et un clic sur un utilisateur doit afficher les détails de l’utilisateur via une requête ajax postée :
1namespace controllers;
2
3/**
4 * Controller UsersJqueryController
5 *
6 * @property \Ajax\php\ubiquity\JsUtils $jquery
7 * @route("users")
8 */
9class UsersJqueryController extends ControllerBase {
10...
11 /**
12 *
13 * @get("all","name"=>"display.users","cache"=>true)
14 */
15 public function displayUsers() {
16 $users = DAO::getAll(User::class);
17 $this->jquery->click('#close-bt', '$("#users").html("");');
18 $this->jquery->postOnClick('li[data-ajax]', Router::path('display.one.user', [
19 ""
20 ]), '{}', '#user-detail', [
21 'attr' => 'data-ajax',
22 'hasLoader' => false
23 ]);
24 $this->jquery->renderDefaultView([
25 'users' => $users
26 ]);
27 }
La vue associée à l’action displayUsers :
<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 }}
Action displayOneUser
1namespace controllers;
2
3/**
4 * Controller UsersJqueryController
5 *
6 * @property \Ajax\php\ubiquity\JsUtils $jquery
7 * @route("users")
8 */
9class UsersJqueryController extends ControllerBase {
10...
11 /**
12 *
13 * @post("{userId}","name"=>"display.one.user","cache"=>true,"duration"=>3600)
14 */
15 public function displayOneUser($userId) {
16 $user = DAO::getById(User::class, $userId);
17 $this->jquery->hide('#users-content', '', '', true);
18 $this->jquery->click('#close-user-bt', '$("#user-detail").html("");$("#users-content").show();');
19 $this->jquery->renderDefaultView([
20 'user' => $user
21 ]);
22 }
La vue associée à l’action displayOneUser :
<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 }}
Composants Semantic
Nous allons ensuite réaliser un contrôleur implémentant les mêmes fonctionnalités que précédemment, mais en utilisant des composants PhpMv-UI (partie sémantique).
Normalizers
Note
Le module Normalizer utilise la classe statique NormalizersManager pour gérer la normalisation.
Validateurs
Note
Le module Validators utilise la classe statique ValidatorsManager pour gérer la validation.
La validation permet de vérifier que les données membres d’un objet respectent certaines contraintes.
Ajout de validators
Soit la classe Author que nous souhaitons utiliser dans notre application :
1 namespace models;
2
3 class Author {
4 /**
5 * @var string
6 * @validator("notEmpty")
7 */
8 private $name;
9
10 public function getName(){
11 return $this->name;
12 }
13
14 public function setName($name){
15 $this->name=$name;
16 }
17 }
Une contrainte de validation a été ajoutée sur le membre name avec l’annotation @validator, de façon à rendre sa saisie obligatoire.
Génération du cache
Exécuter cette commande dans la console pour générer le cache de la classe Author :
Ubiquity init-cache -t=models
Le cache du validateur est généré dans le fichier app/cache/contents/validators/models/Author.cache.php
.
Validation d’instances
Une 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!';
}
}
Si le name de l’instance de author est vide, l’action devrait afficher :
name : This value should not be empty
La méthode validate retroune un tableau d’instances de ConstraintViolation .
Plusieurs instances
public function testValidateAuthors(){
$authors=DAO::getAll(Author::class);
$violations=ValidatorsManager::validateInstances($author);
foreach($violations as $violation){
echo $violation.'<br>';
}
}
Génération des models avec validateurs par défaut
Lorsque les classes sont générées automatiquement à partir de la base de données, des validateurs par défaut sont associés aux membres, basés su les méta-données des champs.
Ubiquity create-model User
1 namespace models;
2 class User{
3 /**
4 * @id
5 * @column("name"=>"id","nullable"=>false,"dbType"=>"int(11)")
6 * @validator("id","constraints"=>array("autoinc"=>true))
7 **/
8 private $id;
9
10 /**
11 * @column("name"=>"firstname","nullable"=>false,"dbType"=>"varchar(65)")
12 * @validator("length","constraints"=>array("max"=>65,"notNull"=>true))
13 **/
14 private $firstname;
15
16 /**
17 * @column("name"=>"lastname","nullable"=>false,"dbType"=>"varchar(65)")
18 * @validator("length","constraints"=>array("max"=>65,"notNull"=>true))
19 **/
20 private $lastname;
21
22 /**
23 * @column("name"=>"email","nullable"=>false,"dbType"=>"varchar(255)")
24 * @validator("email","constraints"=>array("notNull"=>true))
25 * @validator("length","constraints"=>array("max"=>255))
26 **/
27 private $email;
28
29 /**
30 * @column("name"=>"password","nullable"=>true,"dbType"=>"varchar(255)")
31 * @validator("length","constraints"=>array("max"=>255))
32 **/
33 private $password;
34
35 /**
36 * @column("name"=>"suspended","nullable"=>true,"dbType"=>"tinyint(1)")
37 * @validator("isBool")
38 **/
39 private $suspended;
40 }
Ces validateurs peuvent être modifiés.
Les modifications doivent toujours être suivies d’une ré-initialisation du cache des models.
Ubiquity init-cache -t=models
Les informations relatives aux validateurs existants sur les models peuvent être affichées avec les devtools :
Ubiquity info:validation -m=User

Retourne les validateurs associés au champ email :
Ubiquity info:validation email -m=User

Les informations de validation sont également accessibles depuis la partie models des webtools :

Types de validators
Basic
Validator |
Rôles |
Contraintes |
Valeurs acceptées |
---|---|---|---|
isBool |
Vérifie si la valeur est un booléen |
true,false,0,1 |
|
isEmpty |
Vérifie si la valeur n’est pas vide |
“”,null |
|
isFalse |
Vérifie si la valeur est false |
false,”false”,0,”0” |
|
isNull |
Vérifie si la valeur est nulle |
null |
|
isTrue |
Vérifie si la valeur est true |
true,”true”,1,”1” |
|
notEmpty |
Vérifie si la valeur n’est pas vide |
!null && !”” |
|
notNull |
Vérifie si la valeur n’est pas nulle |
!null |
|
type |
Vérifie que la valeur est du type {type} |
{type} |
Comparison
Dates
Multiples
Strings
Transformers
Note
Le module Transformers utilise la classe statique TransformersManager pour gérer les transformations.
Les Transformers sont utilisés pour transformer les données après leur chargement depuis une base de données, ou avant leur affichage dans une vue.
Ajouter des transformers
Soit la classe Author que nous souhaitons utiliser dans notre application :
1namespace models;
2
3use Ubiquity\attributes\items\Transformer;
4
5class Author {
6
7 #[Transformer('upper')]
8 private $name;
9
10 public function getName(){
11 return $this->name;
12 }
13
14 public function setName($name){
15 $this->name=$name;
16 }
17}
1namespace models;
2
3class Author {
4 /**
5 * @var string
6 * @transformer("upper")
7 */
8 private $name;
9
10 public function getName(){
11 return $this->name;
12 }
13
14 public function setName($name){
15 $this->name=$name;
16 }
17}
Un transformer a été ajouté sur le membre name avec l’annotation @transformer , de façon à mettre en majuscules le nom dans les vues.
Génération du cache
Exécuter la commande suivante dans la console pour générer le cache de la classe Author :
Ubiquity init-cache -t=models
Le cache des transformers est généré avec les méta-données des models dans le dossier app/cache/models/Author.cache.php
.
Les informations relatives aux transormers peuvent être affichées à partir des devtools :
Ubiquity info:model -m=Author -f=#transformers

Utilisation des transformers
Démarrer le manager TransformersManager depuis le fichier app/config/services.php :
\Ubiquity\contents\transformation\TransformersManager::startProd();
Le résultat peut être testé depuis l’interface d’administration :

ou en créant un contrôleur
1namespace controllers;
2
3class Authors {
4
5 public function index(){
6 DAO::transformersOp='toView';
7 $authors=DAO::getAll(Author::class);
8 $this->loadDefaultView(['authors'=>$authors]);
9 }
10
11}
<ul>
{% for author in authors %}
<li>{{ author.name }}</li>
{% endfor %}
</ul>
Types de transformers
transform
Le type transform est basé sur l’interface TransformerInterface. Il est utilisé lorsque les données doivent être transformées en objet.
Le transormer DateTime est un bon exemple d’un tel transformer :
Au chargement des données, le transformer convertit les données de la base en une instance de php DateTime.
La méthode reverse effectue l’opération inverse ( php date vers date compatible avec la base de données).
toView
Le type toView est basé sur l’interface TransformerViewInterface. Il est utilisé dans le cas où les données transformées doivent être affichées dans une vue.
toForm
Le type toForm est basé sur l’interface TransformerFormInterface. Il est utilisé lorsque les données doivent être affichées dans un formulaire.
Utilisation des transformers
Transformation sur chargement des données
En cas d’omission, l’opération par défaut transformerOp est transform
$authors=DAO::getAll(Author::class);
Définit transformerOp à toView
DAO::transformersOp='toView';
$authors=DAO::getAll(Author::class);
Transformation après chargement
Retourne la valeur du membre transformée
TransformersManager::transform($author, 'name','toView');
Retourne une valeur transformée :
TransformersManager::applyTransformer($author, 'name','john doe','toView');
Transforme une instance en lui appliquant tous les transformers définis :
TransformersManager::transformInstance($author,'toView');
Transformers existants
Transformer |
Type(s) |
Description |
datetime |
transform, toView, toForm |
Transforme une date issue de la base de données en une instance de DateTime php |
upper |
toView |
Met la valeur du membre en majuscule |
lower |
toView |
Met la valeur du membre en minuscule |
firstUpper |
toView |
Met une majuscule sur la première lettre de la valeur du membre |
password |
toView |
Masque les caractères de la valeur du membre |
md5 |
toView |
Hash la valeur en md5 |
Créer votre propre transformer
Création
Crée un transformer pour afficher un nom d’utilisateur comme une adresse email locale :
1namespace transformers;
2use Ubiquity\contents\transformation\TransformerViewInterface;
3
4class ToLocalEmail implements TransformerViewInterface{
5
6 public static function toView($value) {
7 if($value!=null) {
8 return sprintf('%s@mydomain.local',strtolower($value));
9 }
10 }
11
12}
Enregistrement
Enregistrer le transformer en exécutant le script suivant :
TransformersManager::registerClassAndSave('localEmail',\transformers\ToLocalEmail::class);
Utilisation
1namespace models;
2
3use Ubiquity\attributes\items\Transformer;
4
5class User {
6
7 #[Transformer('localEmail')]
8 private $name;
9
10 public function getName(){
11 return $this->name;
12 }
13
14 public function setName($name){
15 $this->name=$name;
16 }
17}
1namespace models;
2
3class User {
4 /**
5 * @var string
6 * @transformer("localEmail")
7 */
8 private $name;
9
10 public function getName(){
11 return $this->name;
12 }
13
14 public function setName($name){
15 $this->name=$name;
16 }
17}
DAO::transformersOp='toView';
$user=DAO::getOne(User::class,"name='Smith'");
echo $user->getName();
Smith user name will be displayed as smith@mydomain.local.
Module de traduction
Note
Le module de traduction utilise la classe statique TranslatorManager pour gérer les traductions.
Structure du module
Les traductions sont regroupées par domaine, dans une locale :
Dans le répertoire racine des traductions (par défaut app/translations) :
Chaque locale correspond à un sous-dossier.
Pour chaque locale, dans un sous-dossier, un domaine correspond à un fichier php.
translations
├ en_EN
│ ├ messages.php
│ └ blog.php
└ fr_FR
├ messages.php
└ blog.php
chaque fichier de domaine contient un tableau associatif de traductions key-> valeur de la traduction.
- Chaque clé peut être associée à
une traduction
une traduction contenant des variables (entre % et %)
un tableau de traductions pour la pluralisation
return [
'okayBtn'=>'Okay',
'cancelBtn'=>'Cancel',
'deleteMessage'=>['No message to delete!','1 message to delete.','%count% messages to delete.']
];
Démarrage du module
Le démarrage du module se fait logiquement dans le fichier services.php.
1Ubiquity\cache\CacheManager::startProd($config);
2Ubiquity\translation\TranslatorManager::start();
En l’absence de paramètres, l’appel de la méthode start utilise la locale en_EN, sans fallbacklocale.
Important
Le module de traduction doit être lancé après le démarrage du cache.
Définition de la locale
Changement de la locale au démarrage du gestionnaire :
1Ubiquity\cache\CacheManager::startProd($config);
2Ubiquity\translation\TranslatorManager::start('fr_FR');
Changement de la locale après le chargement du gestionnaire :
TranslatorManager::setLocale('fr_FR');
Définition de la fallbackLocale
La locale fr_EN sera utilisée si fr_FR n’est pas trouvée :
1Ubiquity\cache\CacheManager::startProd($config);
2Ubiquity\translation\TranslatorManager::start('fr_FR','en_EN');
Définition du répertoire racine des traductions
Si le paramètre rootDir est absent, le répertoire utilisé par défaut est « app/translations ».
1Ubiquity\cache\CacheManager::startProd($config);
2Ubiquity\translation\TranslatorManager::start('fr_FR','en_EN','myTranslations');
Faire une traduction
Avec php
Traduction de la clé okayBtn dans la locale par défaut (spécifiée lors du démarrage du gestionnaire) :
$okBtnCaption=TranslatorManager::trans('okayBtn');
Sans paramètres, l’appel de la méthode trans utilise la locale par défaut, le domaine messages.
Traduction de la clé message utilisant une variable :
$okBtnCaption=TranslatorManager::trans('message',['user'=>$user]);
Dans ce cas, le fichier de traduction doit contenir une référence à la variable user pour la clé message :
['message'=>'Hello %user%!',...];
Dans les vues twig :
Traduction de la clé okayBtn dans la locale par défaut (spécifiée lors du démarrage du gestionnaire) :
{{ t('okayBtn') }}
Traduction de la clé message utilisant une variable :
{{ t('message',parameters) }}
Sécurité
Principes directeurs
Validation des formulaires
Validation côté client
Il est préférable d’effectuer une validation initiale côté client pour éviter de soumettre des données invalides au serveur.
Exemple de la création d’un formulaire dans l’action d’un contrôleur (cette partie pourrait être située dans un service dédié pour une meilleure séparation des couches) :
1 public function index(){
2 $frm=$this->jquery->semantic()->dataForm('frm-user',new User());
3 $frm->setFields(['login','password','connection']);
4 $frm->fieldAsInput('login',
5 ['rules'=>'empty']
6 );
7 $frm->fieldAsInput('password',
8 [
9 'inputType'=>'password',
10 'rules'=>['empty','minLength[6]']
11 ]
12 );
13 $frm->setValidationParams(['on'=>'blur','inline'=>true]);
14 $frm->fieldAsSubmit('connection','fluid green','/submit','#response');
15 $this->jquery->renderDefaultView();
16 }
Ma vue associée :
{{ q['frm-user'] | raw }}
{{ script_foot | raw }}
<div id="response"></div>

Note
Les contrôleurs CRUD intègrent automatiquement cette validation côté client en utilisant les validateurs attachés aux membres des modèles.
#[Column(name: "password",nullable: true,dbType: "varchar(255)")]
#[Validator(type: "length",constraints: ["max"=>20,"min"=>6])]
#[Transformer(name: "password")]
private $password;
Validation côté serveur
Il est préférable de restreindre les URLs autorisées à modifier les données.
Au préalable, en spécifiant la méthode Http dans les routes, et en testant la requête :
#[Post(path: "/submit")]
public function submitUser(){
if(!URequest::isCrossSite() && URequest::isAjax()){
$datas=URequest::getPost();//post with htmlEntities
//Do something with $datas
}
}
Note
Le module Ubiquity-security offre un contrôle supplémentaire pour éviter les requêtes intersites.
Après avoir modifié un objet, il est possible de vérifier sa validité, grâce aux validateurs attachés aux membres du Modèle associé :
#[Post(path: "/submit")]
public function submitUser(){
if(!URequest::isCrossSite()){
$datas=URequest::getPost();//post with htmlEntities
$user=new User();
URequest::setValuesToObject($user,$datas);
$violations=ValidatorsManager::validate($user);
if(\count($violations)==0){
//do something with this valid user
} else {
//Display violations...
}
}
}
Opérations DAO
- Il est toujours recommandé d’utiliser des requêtes paramétrées, quelles que soient les opérations effectuées sur les données :
Pour éviter les injections SQL.
Pour permettre l’utilisation de requêtes préparées, accélérant le traitement.
$googleUsers=DAO::getAll(User::class,'email like ?',false,['%@gmail.com']);
$countActiveUsers=DAO::count(User::class,'active= ?',[true]);
Note
Les opérations DAO qui prennent des objets comme paramètres utilisent ce mécanisme par défaut.
DAO::save($user);
Gestion des mots de passe
Le transformateur Password
permet à un champ d’être du type mot de passe lorsqu’il est affiché dans un formulaire CRUD généré automatiquement.
#[Transformer(name: "password")]
private $password;
Après soumission d’un formulaire, il est possible de crypter un mot de passe à partir de la classe URequest :
$encryptedPassword=URequest::password_hash('password');
$user->setPassword($encryptedPassword);
DAO::save($user);
L’algorithme utilisé dans ce cas est défini par le paramètre php PASSWORD_DEFAULT
.
Il est également possible de vérifier de la même manière un mot de passe saisi par un utilisateur, en le comparant à un hachage :
if(URequest::password_verify('password', $existingPasswordHash)){
//password is ok
}
Important
Configurez Https pour éviter d’envoyer les mots de passe en clair.
Module de sécurité/ Gestion des ACL
- En plus de ces quelques règles, vous pouvez procéder à des installations si nécessaire :
Module sécurité
Installation
Installer le module Ubiquity-security à partir de l’invite de commande ou des Webtools (partie Composer).
composer require phpmv/ubiquity-security
Activez ensuite l’affichage de la partie Sécurité dans les Webtools :

Session CSRF
La session est par défaut protégée contre les attaques CSRF via la classe VerifyCsrfToken
(même sans le module Ubiquity-security). |Une instance de jeton (CSRFToken
) est générée au démarrage de la session. La validité du jeton est ensuite vérifiée via un cookie à chaque requête.

Cette protection peut être personnalisée en créant une classe implémentant VerifySessionCsrfInterface
.
class MyCsrfProtection implements VerifySessionCsrfInterface {
private AbstractSession $sessionInstance;
public function __construct(AbstractSession $sessionInstance) {
$this->sessionInstance = $sessionInstance;
}
public function init() {
//TODO when the session starts
}
public function clear() {
//TODO when the session ends
}
public function start() {
//TODO When the session starts or is resumed
}
public static function getLevel() {
return 1; //An integer to appreciate the level of security
}
}
Démarrer la protection personnalisée dans les services :
use Ubiquity\utils\http\session\PhpSession;
use Ubiquity\controllers\Startup;
use app\session\MyCsrfProtection;
Startup::setSessionInstance(new PhpSession(new MyCsrfProtection()));
Désactiver la protection
Si vous n’avez pas besoin de protéger votre session contre les attaques Csrf, démarrez la session avec la classe « NoCsrfProtection ».
use Ubiquity\utils\http\session\PhpSession;
use Ubiquity\controllers\Startup;
use Ubiquity\utils\http\session\protection\NoCsrfProtection;
Startup::setSessionInstance(new PhpSession(new NoCsrfProtection()));
Gestion CSRF
Le service CsrfManager peut être démarré directement depuis l’interface webtools. |Son rôle est de fournir des outils pour protéger les routes sensibles des attaques Csrf (celles qui permettent la validation des formulaires par exemple).

Le service est démarré dans le fichier
services.php
.
\Ubiquity\security\csrf\CsrfManager::start();
Exemple de protection de formulaire :
La vue du formulaire :
<form id="frm-bar" action='/submit' method='post'>
{{ csrf('frm-bar') }}
<input type='text' id='sensitiveData' name='sensitiveData'>
</form>
La méthode csrf
génère un jeton pour le formulaire (En ajoutant un champ caché dans le formulaire correspondant au jeton.).
La soumission du formulaire dans un contrôleur :
use Ubiquity\security\csrf\UCsrfHttp;
#[Post('/submit')]
public function submit(){
if(UCsrfHttp::isValidPost('frm-bar')){
//Token is valid! => do something with post datas
}
}
Note
Il est également possible de gérer cette protection via un cookie.
Exemple de protection avec ajax :
Le champ méta csrf-token
est généré sur toutes les pages.
abstract class ControllerBase extends Controller{
protected $headerView = "@activeTheme/main/vHeader.html";
protected $footerView = "@activeTheme/main/vFooter.html";
public function initialize() {
if (! URequest::isAjax ()) {
$meta=UCsrfHttp::getTokenMeta('postAjax');
$this->loadView ( $this->headerView,['meta'=>$meta] );
}
}
}
Ce champ est ajouté dans la headerView :
{% block header %}
<base href="{{config["siteUrl"]}}">
<meta charset="UTF-8">
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
{{meta | raw}}
<title>Tests</title>
{% endblock %}
Exemple avec un bouton qui envoie des données via ajax. Le paramètre csrf
est mis à true. Donc quand la requête est postée, le csrf-token
est envoyé dans les headers de la requête.
#[Get(path: "/ajax")]
public function ajax(){
$this->jquery->postOnClick('#bt','/postAjax','{id:55}','#myResponse',['csrf'=>true]);
$this->jquery->renderDefaultView();
}
La route de soumission peut vérifier la présence et la validité du jeton :
#[Post(path: "postAjax")]
public function postAjax(){
if(UCsrfHttp::isValidMeta('postAjax')){
var_dump($_POST);
}else{
echo 'invalid or absent meta csrf-token';
}
}
Gestionnaire de cryptage
Le service EncryptionManager peut être lancé directement à partir de l’interface webtools.
Dans ce cas, une clé est générée dans le fichier de configuration « app/config/config.php ».
Le service est démarré dans le fichier
services.php
.
\Ubiquity\security\data\EncryptionManager::start($config);
Note
Par défaut, le cryptage est effectué en AES-128
.

Changement du cypher code :
Mise à niveau vers AES-256 :
\Ubiquity\security\data\EncryptionManager::startProd($config, Encryption::AES256);
Générer une nouvelle clé :
Ubiquity new:key 256
La nouvelle clé est générée dans le fichier app/config/config.php
.
Cryptage des données des modèles
Le transformateur Crypt
peut aussi être utilisé sur les membres d’un modèle :
class Foo{
#[Transformer(name: "crypt")]
private $secret;
...
}
Usage:
$o=new Foo();
$o->setSecret('bar');
TransformersManager::transformInstance($o);// secret member is encrypted
Cryptage des données génériques
Cryptage des chaînes :
$encryptedBar=EncryptionManager::encryptString('bar');
Pour ensuite le décrypter :
echo EncryptionManager::decryptString($encryptedBar);
Il est possible de crypter tout type de données :
$encryptedUser=EncryptionManager::encrypt($user);
Pour ensuite le décrypter, avec éventuellement sérialisation/désérialisation s’il s’agit d’un objet :
$user=EncryptionManager::decrypt($encryptedUser);
Gestionnaire des politiques de sécurité du contenu
Le service ContentSecurityManager peut être lancé directement à partir de l’interface webtools.
Le service est démarré dans le fichier
services.php
.
\Ubiquity\security\csp\ContentSecurityManager::start(reportOnly: true,onNonce: function($name,$value){
if($name==='jsUtils') {
\Ubiquity\security\csp\ContentSecurityManager::defaultUbiquityDebug()->addNonce($value, \Ubiquity\security\csp\CspDirectives::SCRIPT_SRC)->addHeaderToResponse();
}
});
Note
Avec cette configuration par défaut, un nonce est ajouté aux scripts jquery générés avec phpmv-ui. Le contrôle du CSP se fait en mode Report-only .

Ajout d’un nonce
Exemple d’ajout de nonce sur les pages d’en-tête et de pied de page :
Mise à jour du contrôleur de base
namespace controllers;
use Ubiquity\controllers\Controller;
use Ubiquity\security\csp\ContentSecurityManager;
use Ubiquity\utils\http\URequest;
/**
* controllers$ControllerBase
*/
abstract class ControllerBase extends Controller {
protected $headerView = "@activeTheme/main/vHeader.html";
protected $footerView = "@activeTheme/main/vFooter.html";
protected $nonce;
public function initialize() {
$this->nonce=ContentSecurityManager::getNonce('jsUtils');
if (! URequest::isAjax()) {
$this->loadView($this->headerView,['nonce'=>$this->nonce]);
}
}
public function finalize() {
if (! URequest::isAjax()) {
$this->loadView($this->footerView,['nonce'=>$this->nonce]);
}
}
}
Gestion des mots de passe
Jeton d’utilisateurs
Gestion des ACL
Installation
Installez le module Ubiquity-acl à partir de l’invite de commande ou des Webtools (partie Composer).
composer require phpmv/ubiquity-acl
Activez ensuite l’affichage de la partie Acl dans les Webtools :

Interface ACL dans les webtools :

Règles Acl
Les ACLs sont utilisées pour définir l’accès à une application Ubiquity. Elles sont définies selon les principes suivants :
- Une application Ubiquity est composée de :
Ressources (éventuellement des contrôleurs, ou des actions de ces contrôleurs)
Rôles, éventuellement attribués à des utilisateurs. Chaque Rôle peut hériter de rôles parents.
Permissions, qui correspondent à un droit de faire. Chaque permission a un niveau (représenté par une valeur entière).
- Règles supplémentaires :
Un AclElement (Allow) accorde une permission à un rôle sur une ressource.
Chaque rôle hérite des autorisations de ses parents, en plus des siennes.
Si un rôle a un certain niveau d’autorisation d’accès à une ressource, il aura également toutes les autorisations d’un niveau inférieur sur cette ressource.
L’association d’une ressource et d’une permission à un contrôleur ou à une action de contrôleur définit un élément map.

- Règles de nommage :
Rôle, en lettres capitales, commençant par une arobase (@USER, @ADMIN, @ALL…).
Permissions, en majuscules, nommées à l’aide d’un verbe (READ, WRITE, OPEN…).
Ressource, majuscule à la première lettre (Produits, Clients…)
Démarrage des ACLs
Le service AclManager peut être démarré directement depuis l’interface webtools, dans la partie Security.
Le service est démarré dans le fichier
services.php
.
\Ubiquity\security\acl\AclManager::startWithCacheProvider();
ACLCacheProvider
Ce fournisseur par défaut vous permet de gérer les ACL définies par des attributs ou des annotations.
AclController
Un AclController permet de gérer automatiquement les accès à ses propres ressources en se basant sur des ACL.
Il est possible de les créer automatiquement à partir des webtools.

Mais il ne s’agit que d’un contrôleur de base, utilisant le trait AclControllerTrait.
Ce contrôleur va juste redéfinir la méthode _getRole
, pour qu’elle renvoie le rôle de l’utilisateur actif, par exemple.
<?php
namespace controllers;
use Ubiquity\controllers\Controller;
use Ubiquity\security\acl\controllers\AclControllerTrait;
use Ubiquity\attributes\items\acl\Allow;
class BaseAclController extends Controller {
use AclControllerTrait;
#[Allow('@ME')]
public function index() {
$this->loadView("BaseAclController/index.html");
}
public function _getRole() {
$_GET['role']??'@ME';//Just for testing: logically, this is the active user's role
}
/**
* {@inheritdoc}
* @see \Ubiquity\controllers\Controller::onInvalidControl()
*/
public function onInvalidControl() {
echo $this->_getRole() . ' is not allowed!';
}
}
- L’autorisation a été accordée pour la ressource :
Sans spécifier la ressource, chaque action du contrôleur est définie comme une ressource.
Sans spécifier la permission, la permission « ALL » est utilisée.

Et cette association est présente dans la map des Acls :

AclController avec authentication
Note
L’utilisation à la fois de WithAuthTrait
et de ``AclControllerTrait`” nécessite de lever l’ambiguïté sur la méthode ``isValid`”.
class BaseAclController extends Controller {
use AclControllerTrait,WithAuthTrait{
WithAuthTrait::isValid insteadof AclControllerTrait;
AclControllerTrait::isValid as isValidAcl;
}
public function isValid($action){
return parent::isValid($action)&& $this->isValidAcl($action);
}
}
Allow avec Rôle, ressource et permission
Allow sans création préalable :
@USER
est autorisé à accéder à la ressource Foo
avec la permission READ
.
use Ubiquity\attributes\items\acl\Allow;
class BaseAclController extends Controller {
use AclControllerTrait;
...
#[Allow('@USER','Foo', 'READ')]
public function foo(){
echo 'foo page allowed for @USER and @ME';
}
}
Note
Le rôle, la ressource et la permission sont automatiquement créés dès qu’ils sont invoqués avec Allow
.
Allow avec création explicite :
use Ubiquity\attributes\items\acl\Allow;
use Ubiquity\attributes\items\acl\Permission;
class BaseAclController extends Controller {
use AclControllerTrait;
...
#[Permission('READ',500)]
#[Allow('@USER','Foo', 'READ')]
public function foo(){
echo 'foo page allowed for @USER and @ME';
}
}
Ajout d’ACL à l’exécution
Que ce soit dans un contrôleur ou dans un service, il est possible d’ajouter des rôles, des ressources, des permissions et des autorisations au moment de l’exécution :
Par exemple :\N- Ajouter un rôle @USER
héritant de @GUEST
.
use Ubiquity\security\acl\AclManager;
AclManager::addRole('@GUEST');
AclManager::addRole('@USER',['@GUEST']);
Définir les ACLs avec une base de données
Les ACL définies dans la base de données s’ajoutent aux ACL définies via les annotations ou les attributs.
Initialisation
L’initialisation permet de créer les tables associées aux ACLs (Role, Resource, Permission, AclElement). Elle ne doit être faite qu’une seule fois, et en mode dev uniquement.
A placer par exemple dans le fichier app/config/bootstrap.php
:
use Ubiquity\controllers\Startup;
use Ubiquity\security\acl\AclManager;
$config=Startup::$config;
AclManager::initializeDAOProvider($config, 'default');
Démarrage
Dans le fichier app/config/services.php
:
use Ubiquity\security\acl\AclManager;
use Ubiquity\security\acl\persistence\AclCacheProvider;
use Ubiquity\security\acl\persistence\AclDAOProvider;
use Ubiquity\orm\DAO;
DAO::start();//Optional, to use only if dbOffset is not default
AclManager::start();
AclManager::initFromProviders([
new AclCacheProvider(), new AclDAOProvider($config)
]);
Stratégies de définition des ACL
Avec peu de ressources :
Définir les autorisations pour chaque action du contrôleur ou chaque groupe d’actions :
Les ressources correspondent logiquement aux contrôleurs, et les permissions aux actions. Mais cette règle peut ne pas être respectée, et une action peut être définie comme une ressource, selon les besoins.
La seule règle obligatoire est qu’une paire contrôleur/action ne peut correspondre qu’à une seule paire ressource/permission (pas nécessairement unique).
namespace controllers;
use Ubiquity\controllers\Controller;
use Ubiquity\security\acl\controllers\AclControllerTrait;
use Ubiquity\attributes\items\acl\Permission;
use Ubiquity\attributes\items\acl\Resource;
#[Resource('Foo')]
#[Allow('@ADMIN')]
class FooController extends Controller {
use AclControllerTrait;
#[Allow('@NONE')]
public function index() {
echo 'index';
}
#[Allow('@USER')]
public function read() {
echo 'read';
}
#[Allow('@USER')]
public function write() {
echo 'write';
}
public function admin() {
echo 'admin';
}
public function _getRole() {
return $_GET['role']??'@NONE';
}
/**
* {@inheritdoc}
* @see \Ubiquity\controllers\Controller::onInvalidControl()
*/
public function onInvalidControl() {
echo $this->_getRole() . ' is not allowed!';
}
}
Avec plus de ressources :
namespace controllers;
use Ubiquity\controllers\Controller;
use Ubiquity\security\acl\controllers\AclControllerTrait;
use Ubiquity\attributes\items\acl\Permission;
use Ubiquity\attributes\items\acl\Resource;
#[Resource('Foo')]
class FooController extends Controller {
use AclControllerTrait;
#[Permission('INDEX',1)]
public function index() {
echo 'index';
}
#[Permission('READ',2)]
public function read() {
echo 'read';
}
#[Permission('WRITE',3)]
public function write() {
echo 'write';
}
#[Permission('ADMIN',10)]
public function admin() {
echo 'admin';
}
public function _getRole() {
return $_GET['role']??'NONE';
}
/**
* {@inheritdoc}
* @see \Ubiquity\controllers\Controller::onInvalidControl()
*/
public function onInvalidControl() {
echo $this->_getRole() . ' is not allowed!';
}
}
Rest
Le module REST implémente un CRUD de base,
avec un système d’authentification, directement testable dans la partie administration.
REST et routage
Le routeur est essentiel au module REST, puisque REST (Representation State Transfer) est basé sur les URL et les méthodes HTTP.
Note
Pour des raisons de performance, les routes REST sont mises en cache indépendamment des autres routes.
Il est donc nécessaire de démarrer le routeur d’une manière particulière pour activer les routes REST et ne pas obtenir une erreur 404 récurrente.
Le routeur est démarré dans le fichier services.php
.
Sans l’activation des routes REST :
...
Router::start();
Pour activer les routes REST dans une application qui a également une partie non-REST :
...
Router::startAll();
Pour activer uniquement les routes REST :
Router::startRest();
Il est possible de lancer le routage de manière conditionnelle (cette méthode ne sera efficace que si le nombre de routes est important dans l’une ou l’autre partie) :
...
if($config['isRest']()){
Router::startRest();
}else{
Router::start();
}
Ressource REST
Un contrôleur REST peut être directement associé à un modèle.
Note
Si vous n’avez pas de base de données Mysql sous la main, vous pouvez télécharger celle-ci : messagerie.sql
Création
Avec les devtools :
Ubiquity rest RestUsersController -r=User -p=/rest/users
Ou avec les webtools :
Allez dans la partie REST puis choisir Add a new resource :

Le contrôleur créé :
1 namespace controllers;
2
3 /**
4 * Rest Controller RestUsersController
5 * @route("/rest/users","inherited"=>true,"automated"=>true)
6 * @rest("resource"=>"models\\User")
7 */
8 class RestUsersController extends \Ubiquity\controllers\rest\RestController {
9
10 }
Comme les attributs automated et inherited de la route sont définis sur true, le contrôleur a les routes par défaut de la classe parente.
The base controller RestController is not standardized, it should be considered as an example for data interrogation.
Interface de test
Les webtools fournissent une interface pour l’interrogation des données :

Obtenir une instance
Une instance d’utilisateur est accessible par sa clé primaire (id) :

Inclusion des membres associés : l’organisation de l’utilisateur

Inclusion des membres associés : organisation, connexions et groupes de l’utilisateur.

Obtenir plusieurs instances
Récupérer toutes les instances :

Définir une condition :

Inclure les membres associés :

Ajout d’une instance
Les données sont envoyées par la méthode POST, avec un content-type défini à application/x-www-form-urlencoded
:
Ajoutez les paramètres du nom et du domaine en cliquant sur le bouton parameters :

L’ajout nécessite une authentification, donc une erreur est générée, avec le statut 401 :

L’interface d’administration vous permet de simuler l’authentification par défaut et d’obtenir un jeton, en sollicitant la méthode connect :

Le jeton est alors automatiquement envoyé dans les requêtes suivantes.
L’enregistrement peut alors être inséré.

Mise à jour d’une instance
La mise à jour suit le même schéma que l’insertion.
Suppression d’une instance

Personnalisation
Routes
Il est bien sûr possible de personnaliser et de simplifier les routes.
Dans ce cas, il est préférable d’utiliser l’héritage depuis la classe RestBaseController, et de ne pas activer les routes automatiques.
1namespace controllers;
2
3use models\Organization;
4
5/**
6 * Rest Controller for organizations
7 *
8 * @route("/orgas")
9 * @rest
10 */
11class RestOrgas extends \Ubiquity\controllers\rest\RestBaseController {
12
13 public function initialize() {
14 $this->model = Organization::class;
15 parent::initialize();
16 }
17
18 /**
19 *
20 * @get
21 */
22 public function index() {
23 $this->_get();
24 }
25
26 /**
27 *
28 * @get("{keyValues}")
29 */
30 public function get($keyValues) {
31 $this->_getOne($keyValues);
32 }
33
34 /**
35 *
36 * @post("/")
37 */
38 public function add() {
39 $this->_add();
40 }
41
42 /**
43 *
44 * @patch("{keyValues}")
45 */
46 public function update(...$keyValues) {
47 $this->_update(...$keyValues);
48 }
49
50 /**
51 *
52 * @delete("{keyValues}")
53 */
54 public function delete(...$keyValues) {
55 $this->_delete(...$keyValues);
56 }
57}
Après avoir réinitialisé le cache, l’interface de test montre les routes accessibles :

Modification des données envoyées
Par sur-définition
Il est possible de modifier les données envoyées aux méthodes update et add, afin d’ajouter, modifier ou supprimer la valeur des champs avant l’envoi.
Soit en surdéfinissant la méthode getDatas :
...
protected function getDatas() {
$datas = parent::getDatas();
unset($datas['aliases']);// Remove aliases field
return $datas;
}
Avec les events
Soit de manière plus globale en agissant sur les événements de repos :
use Ubiquity\events\EventsManager;
use Ubiquity\events\RestEvents;
use Ubiquity\controllers\rest\RestBaseController;
...
EventsManager::addListener(RestEvents::BEFORE_INSERT, function ($o, array &$datas, RestBaseController $resource) {
unset($datas['aliases']);// Remove aliases field
});
Authentification
Ubiquity REST implémente une authentification Oauth2 avec des jetons Bearer.
Seules les méthodes avec l’annotation @authorization
nécessitent l’authentification, ce sont les méthodes de modification (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 );
}
La méthode connect d’un contrôleur REST établit la connexion et renvoie un nouveau jeton.
Il appartient au développeur de surcharger cette méthode pour gérer une éventuelle authentification avec login et mot de passe.

Simulation d’une connexion avec login
Dans cet exemple, la connexion consiste simplement à envoyer une variable utilisateur par la méthode post.
Si l’utilisateur est fourni, la méthode connect
de l’instance $server
retourne un jeton valide qui est stocké dans la session (la session agit ici comme une base de données).
1 namespace controllers;
2
3 use Ubiquity\utils\http\URequest;
4 use Ubiquity\utils\http\USession;
5
6 /**
7 * Rest Controller RestOrgas
8 * @route("/rest/orgas","inherited"=>true,"automated"=>true)
9 * @rest("resource"=>"models\\Organization")
10 */
11 class RestOrgas extends \Ubiquity\controllers\rest\RestController {
12
13 /**
14 * This method simulate a connection.
15 * Send a <b>user</b> variable with <b>POST</b> method to retreive a valid access token
16 * @route("methods"=>["post"])
17 */
18 public function connect(){
19 if(!URequest::isCrossSite()){
20 if(URequest::isPost()){
21 $user=URequest::post("user");
22 if(isset($user)){
23 $tokenInfos=$this->server->connect ();
24 USession::set($tokenInfos['access_token'], $user);
25 $tokenInfos['user']=$user;
26 echo $this->_format($tokenInfos);
27 return;
28 }
29 }
30 }
31 throw new \Exception('Unauthorized',401);
32 }
33 }
Pour chaque requête avec authentification, il est possible de récupérer l’utilisateur connecté (il est ajouté ici dans les en-têtes de réponse) :
1 namespace controllers;
2
3 use Ubiquity\utils\http\URequest;
4 use Ubiquity\utils\http\USession;
5
6 /**
7 * Rest Controller RestOrgas
8 * @route("/rest/orgas","inherited"=>true,"automated"=>true)
9 * @rest("resource"=>"models\\Organization")
10 */
11 class RestOrgas extends \Ubiquity\controllers\rest\RestController {
12
13 ...
14
15 public function isValid($action){
16 $result=parent::isValid($action);
17 if($this->requireAuth($action)){
18 $key=$this->server->_getHeaderToken();
19 $user=USession::get($key);
20 $this->server->_header('active-user',$user,true);
21 }
22 return $result;
23 }
24 }
Utilisez l’interface webtools pour tester la connexion :

Personnalisation
Jetons d’api
Il est possible de personnaliser la génération de jetons, en surdéfinissant la méthode getRestServer
:
1 namespace controllers;
2
3 use Ubiquity\controllers\rest\RestServer;
4 class RestOrgas extends \Ubiquity\controllers\rest\RestController {
5
6 ...
7
8 protected function getRestServer(): RestServer {
9 $srv= new RestServer($this->config);
10 $srv->setTokenLength(32);
11 $srv->setTokenDuration(4800);
12 return $srv;
13 }
14 }
Origines autorisées et CORS
Cross-Origin Resource Sharing (CORS)
Si vous accédez à votre api depuis un autre site, il est nécessaire de configurer CORS.
Dans ce cas, pour les requêtes de type PATCH
, PUT
, DELETE
, votre api doit définir une route permettant à CORS d’effectuer son contrôle pré-requête en utilisant la méthode OPTIONS
.
1 class RestOrgas extends \Ubiquity\controllers\rest\RestController {
2
3 ...
4
5 /**
6 * @options('{url}')
7 */
8 public function options($url='') {}
9 }
Origines autorisées
Les origines autorisées permettent de définir les clients qui peuvent accéder à la ressource dans le cas d’une requête inter-domaine en définissant l’en-tête de réponse Access-Control-Allow-Origin.
Ce champ d’en-tête est renvoyé par la méthode OPTIONS
.
1 class RestOrgas extends \Ubiquity\controllers\rest\RestController {
2
3 ...
4
5 protected function getRestServer(): RestServer {
6 $srv= new RestServer($this->config);
7 $srv->setAllowedOrigin('http://mydomain/');
8 return $srv;
9 }
10 }
Il est possible d’autoriser plusieurs origines :
1 class RestOrgas extends \Ubiquity\controllers\rest\RestController {
2
3 ...
4
5 protected function getRestServer(): RestServer {
6 $srv= new RestServer($this->config);
7 $srv->setAllowedOrigins(['http://mydomain1/','http://mydomain2/']);
8 return $srv;
9 }
10 }
Réponse
Pour changer le format des réponses, il est nécessaire de créer une classe héritant de ResponseFormatter
.
Nous allons nous inspirer de HAL, et changer le format des réponses par :
ajout d’un lien vers self pour chaque ressource
ajout d’un attribut « _embedded » pour les collections
suppression de l’attribut « data » pour les ressources uniques
1 namespace controllers\rest;
2
3 use Ubiquity\controllers\rest\ResponseFormatter;
4 use Ubiquity\orm\OrmUtils;
5
6 class MyResponseFormatter extends ResponseFormatter {
7
8 public function cleanRestObject($o, &$classname = null) {
9 $pk = OrmUtils::getFirstKeyValue ( $o );
10 $r=parent::cleanRestObject($o);
11 $r["links"]=["self"=>"/rest/orgas/get/".$pk];
12 return $r;
13 }
14
15 public function getOne($datas) {
16 return $this->format ( $this->cleanRestObject ( $datas ) );
17 }
18
19 public function get($datas, $pages = null) {
20 $datas = $this->getDatas ( $datas );
21 return $this->format ( [ "_embedded" => $datas,"count" => \sizeof ( $datas ) ] );
22 }
23 }
Assignation ensuite de « MyResponseFormatter » au contrôleur REST en surchargeant la méthode « getResponseFormatter » :
1 class RestOrgas extends \Ubiquity\controllers\rest\RestController {
2
3 ...
4
5 protected function getResponseFormatter(): ResponseFormatter {
6 return new MyResponseFormatter();
7 }
8 }
Testez les résultats avec les méthodes getOne et get :


APIs
Contrairement aux ressources REST, les contrôleurs de type APIs sont multi-ressources.
SimpleRestAPI
JsonApi
Ubiquity implémente la spécification jsonApi avec la classe JsonApiRestController
.
JsonApi est utilisé par EmberJS et d’autres.
voir https://jsonapi.org/ pour en savoir plus.
Création
Avec les devtools :
Ubiquity restapi JsonApiTest -p=/jsonapi
Ou avec les webtools :
Allez dans la partie REST puis choisir Add a new resource :

Testez l’api avec les webtools :

Liens
La route links (méthode index) renvoie la liste des urls accessibles :

Récupérer un tableau d’objets
Par défaut, tous les membres associés sont inclus :

Inclusion des membres associés
vous devez utiliser le paramètre include de la requête :
URL |
Description |
---|---|
|
Aucun membre associé n’est inclus |
|
Inclusion de l’organisation |
|
Inclusion de l’organisation et des connexions |
|
Inclusion des groupes, et de leur organisation |
Filtrage d’instances
il est nécessaire d’utiliser le paramètre filter de la requête,
Le paramètre filter correspond à la partie where d’une instruction SQL :
URL |
Description |
---|---|
|
Sans filtrage |
|
Retourne tous les utilisateurs prénommés Benjamin |
|
Retourne tous les utilisateurs dont le nom commence par un B |
|
Retourne tous les utilisateurs suspendus dont le nom commence par « ca » |
Pagination
vous devez utiliser les paramètres page[number] et page[size] de la requête :
URL |
Description |
---|---|
|
Sans pagination |
|
Affiche la première page (page size vaut 1) |
|
Affiche la première page (page size vaut 10) |
Ajout d’une instance
Les données, contenues dans data[attributes]
, sont envoyées par la méthode POST, avec un type de contenu défini à application/json ; charset=utf-8
.
Ajoutez vos paramètres en cliquant sur le bouton paramètres :

L’ajout nécessite une authentification, une erreur est donc générée, avec le statut 401 si le jeton est absent ou expiré.

Suppression d’une instance
La suppression requiert la méthode DELETE, et l’utilisation de l”id de l’objet à supprimer :

Webtools
Note
Les webtools vous permettent de gérer une application Ubiquity via une interface web. Depuis Ubiquity 2.2.0, les webtools sont dans un repository séparé.
Installation
Mettez à jour les devtools si nécessaire pour commencer :
composer global update
Lors de la création d’un projet
Créer un projet avec les webtools (option -a
)
Ubiquity new quick-start -a
Dans un projet existant
Dans une console, aller à la racine du projet et exécuter :
Ubiquity admin
Démarrage
Démarrez le serveur web embarqué, à partir du dossier du projet :
Ubiquity serve
allez à l’adresse : http://127.0.0.1:8090/Admin

Personnalisation
Cliquez sur customize pour afficher uniquement les outils que vous utilisez :


Modules webtools
Routes

Affiche les routes par défaut (non REST).
Operations:
Filtrer les routes
Tester les routes (GET, POST…)
Initialiser le cache du routeur
Contrôleurs

Affiche les contrôleurs non REST.
Operations:
Créer un contrôleur (et éventuellement la vue associée à l’action index par défaut)
Créer une action dans un contrôleur (éventuellement la vue associée, la route associée)
Créer un contrôleur spécial (CRUD ou Auth)
Tester une action (GET, POST…)
Modèles

Affiche les métadonnées des modèles, permet de parcourir les entités.
Operations:
Créer des modèles à partir d’une base de données
Générer le cache des modèles
Générer un script de base de données à partir de modèles existants
Effectuer des opérations CRUD sur les modèles
Rest

Affiche et gère les services REST.
Operations:
Ré-initiliser le cache Rest et les routes
Créer un nouveau service (en utilisant une api)
Créer une nouvelle ressource (associée à un modèle)
Tester et interroger un service web à l’aide de méthodes http
Effectuer des opérations CRUD sur les modèles
Cache

Affiche les fichiers de cache.
Operations:
Supprimer ou réinitialiser le cache des modèles
Supprimer ou réinitialiser le cache des contrôleurs
Supprimer les autres fichiers de cache
Maintenance

Permet de gérer les modes de maintenance.
Operations:
Créer ou mettre à jour un mode de maintenance
Désactiver/activer un mode de maintenance
Supprimer un mode de maintenance
Config

Permet d’afficher et de modifier la configuration de l’application.
Git

Synchronise le projet en utilisant git.
Operations:
Configuration avec des repositories externes
Commit
Push
Pull
![]()
Gère les thèmes Css.
Operations:
Installer un thème existant
Activer un thème
Créer un nouveau thème (éventuellement basé sur un thème existant)
Contribuer
Exigences système
Avant de travailler sur Ubiquity, ajoutez à votre environnement la configuration suivante :
Git
PHP 7.1 ou plus.
Télécharger le code source Ubiquity
Sur le repository github Ubiquity :
Forkez le projet Ubiquity
Clonez votre fork localement
git clone git@github.com:USERNAME/ubiquity.git
Travaillez sur votre partie
Note
Avant de commencer, vous devez savoir que tous les correctifs que vous allez soumettre doivent être publiés sous la licence Apache 2.0, sauf si cela est explicitement spécifié dans vos commits.
Créez une branche dédiée
Note
Utilisez un nom descriptif pour la nommer :
issue_xxx où xxx est le numéro de l’issue est une bonne convention pour les corrections de bugs.
feature_name est une bonne convention pour les nouvelles fonctionnalités
git checkout -b NEW_BRANCH_NAME master
Travaillez sur votre partie
Travaillez sur votre code et commitez aussi souvent que nécessaire, en gardant à l’esprit les éléments suivants :
Lire la partie Ubiquity coding standards;
Ajoutez des tests unitaires, fonctionnels ou d’acceptation pour prouver que le bug est corrigé ou que la nouvelle fonctionnalité fonctionne réellement ;
Faites des commits atomiques et logiquement séparés (utilisez git rebase pour avoir un historique propre et logique) ;
Rédigez de bons messages de commit (voir le conseil ci-dessous).
Incrémentez les numéros de version dans tous les fichiers modifiés, en respectant les règles semver :
Étant donné un numéro de version
MAJOR.MINOR.PATCH
, incrémenter le :La partie MAJOR si des changements non rétro compatibles sont effectués sur l’API,
la partie
MINOR
lorsque vous ajoutez des fonctionnalités de manière rétrocompatible, etla partie
PATCH
lorsque vous effectuez des corrections de bugs ou des améliorations rétro compatibles.
Soumettez vos modifications
Mettez à jour la partie [Unrelease] du fichier CHANGELOG.md en intégrant vos modifications dans les parties appropriées :
Added
Changed
Removed
Fixed
Avant de soumettre votre patch, mettez à jour votre branche (nécessaire si vous mettez du temps à terminer vos modifications) :
git checkout master
git fetch upstream
git merge upstream/master
git checkout NEW_BRANCH_NAME
git rebase master
Faire une Pull Request
Vous pouvez maintenant faire une Pull Request sur le repository github Ubiquity .
Guide de développement
Note
Bien que le framework soit relativement récent, veuillez noter que certaines des premières classes d’Ubiquity ne suivent pas entièrement ce guide et n’ont pas été modifiées pour des raisons de rétrocompatibilité.
Néanmoins, tous les nouveaux codes doivent suivre ce guide.
Choix de conception
Sollicitation et utilisation de services
Injection de dépendances
Évitez d’utiliser l’injection de dépendances pour toutes les parties du framework, en interne.
L’injection de dépendances est un mécanisme gourmand en ressources :
Elle nécessite d’identifier l’élément à instancier ;
Puis de procéder à sont instanciation :
Pour enfin assigner le résultat à une variable.
Obtenir des services d’un conteneur
Evitez également les accès publics aux services enregistrés dans un conteneur de services.
Ce type d’accès implique la manipulation d’objets dont le type de retour est inconnu, et peu facile à manipuler pour le développeur.
Par exemple, il est difficile de manipuler le retour non typé de $this->serviceContainer->get('translator')
, comme le permettent certains frameworks, et de savoir quelles méthodes appeler dessus.
Lorsque cela est possible, et lorsque cela ne réduit pas trop la flexibilité, l’utilisation de classes statiques est conseillée :
Pour un développeur, la classe TranslatorManager
est accessible depuis un l’ensemble du projet sans aucune instanciation d’objet.
Elle expose son interface publique et permet la complétion de code :
Le translator n’a pas besoin d’être injecté pour être utilisé ;
Il n’a pas besoin d’être récupéré depuis un conteneur de services.
L’utilisation de classes statiques crée inévitablement une forte dépendance et affecte la flexibilité.
Mais pour revenir à l’exemple du Traducteur, il n’y a aucune raison de le modifier s’il est satisfaisant. |brl Il n’est pas souhaitable de vouloir à tout prix apporter de la flexibilité quand elle n’est pas nécessaire, pour qu’en conséquence, l’utilisateur constate ensuite que son application est un peu lente.
Optimisation
L’exécution de chaque ligne de code peut avoir des répercussions importantes sur les performances.
Comparez et évaluez les solutions de mise en œuvre, en particulier si le code est sollicité à plusieurs reprises :
Identifiez ces appels répétés et coûteux avec des outils de profilage php (Blackfire profiler , Tideways …)
Benchmarkez vos différentes implémentations avec phpMyBenchmarks
Qualité du code
Ubiquity utilise Scrutinizer-CI pour l’évaluation de la qualité du code.
Pour les classes et les méthodes :
Les évaluations A ou B sont satisfaisantes
C est acceptable, mais à éviter autant que possible
Les notes plus basses sont à prohiber
Complexité du code
Les méthodes complexes doivent être divisées en plusieurs, pour faciliter la maintenance et permettre la réutilisation ;
Pour les classes complexes, effectuer une refactorisation de type extract-class ou extract-subclass en utilisant les traits.
Duplications de code
Évitez absolument la duplication du code, sauf si la duplication est minime et est justifiée par les performances.
Bugs
Essayez de résoudre tous les bugs signalés au fur et à mesure, sans les laisser s’accumuler.
Tests
Toute correction de bug qui n’inclut pas un test prouvant l’existence du bug corrigé, peut être suspecte.
De même pour les nouvelles fonctionnalités dont on ne peut prouver qu’elles fonctionnent réellement.
Il est également important de maintenir un taux de couverture du code par les tests acceptable, qui peut baisser si une nouvelle fonctionnalité n’est pas testée.
Documentation du code
Le code actuel n’est pas encore entièrement documenté, n’hésitez pas à contribuer afin de combler cette lacune.
Standards de code
Les standards de code Ubiquity sont basés sur les standards PSR-1 , PSR-2 et PSR-4 il est donc préférable de les connaître.
Les quelques exceptions aux standards sont normalement reportées dans ce guide.
Conventions de nommage
Utiliser camelCase pour les variables php, les membres, les fonctions et noms de méthodes, les arguments (e.g. $modelsCacheDirectory, isStarted()) ;
Chaque classe php doit avoir un namespace (voir PSR-4) et utiliser la convention UpperCamelCase pour son nommage (e.g. CacheManager) ;
Préfixer toutes les classes abstraites avec Abstract, à l’exception des classes PHPUnit BaseTests :
Suffixer les interfaces avec
Interface
;Suffixer les traits avec
Trait
;Suffixer les exceptions avec
Exception
;Suffixer les classes principales de type manager avec
Manager
(e.g. CacheManager, TranslatorManager) ;Préfixer les classes utilitaires avec un
U
(e.g. UString, URequest) ;Utiliser UpperCamelCase pour le nommage des fichiers PHP (e.g. CacheManager.php) ;
Utiliser les majuscules pour les constantes (e.g. const SESSION_NAME=”Ubiquity”).
Indentation, tabulations, accolades
Utiliser des tabulations, et non des espaces ; (! PSR-2)
Utiliser les accolades sans passage à la ligne suivante ; (! PSR-2)
Utilisez des accolades pour indiquer le corps de la structure de contrôle, quel que soit le nombre d’instructions qu’elle contient ;
Classes
Définir une classe par fichier ;
Déclarer l’héritage de la classe et toutes les interfaces implémentées sur la même ligne que le nom de la classe ;
Déclarer les membres de données avant les méthodes ;
Déclarer les méthodes privées en premier, puis celles qui sont protégées pour finir par les méthodes publiques :
Déclarer tous les arguments sur la même ligne que le nom de la méthode/fonction, quel que soit le nombre d’arguments ;
Utiliser des parenthèses lors de l’instanciation d’une classe, même si le constructeur ne prend aucun argument ;
Ajouter le statement use pour chaque classe qui ne fait pas partie de l’espace de noms global ;
Opérateurs
Utiliser la comparaison identique ou égale lorsque vous le jugez nécessaire ;
Exemple
<?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 );
}
}
}
Guide de documentation
Ubiquity fourni deux espaces de documentation :
Le guide, qui aide à apprendre les manipulations et les concepts ;
La documentation API, qui sert de référence lors du codage.
Vous pouvez aider à améliorer les guides Ubiquity en les rendant plus cohérents, consistants ou lisibles, en ajoutant des informations manquantes, en corrigeant des erreurs factuelles, en corrigeant des fautes de frappe ou en les mettant à jour pour la dernière version d’Ubiquity.
Pour ce faire, apportez des modifications aux fichiers sources des guides Ubiquity (situés ici sur GitHub). Ensuite, ouvrez une Pull Request pour appliquer vos changements à la branche master.
Lorsque vous travaillez sur la documentation, pensez à prendre en compte les consignes.
Serveurs configuration
Important
Depuis la version 2.4.5, pour des raisons de sécurité et de simplification, la racine d’une application Ubiquity est localisée dans le dossier public.
Apache2
mod_php/PHP-CGI
Apache 2.2
<VirtualHost *:80>
ServerName mydomain.tld
DocumentRoot /var/www/project/public
DirectoryIndex /index.php
<Directory /var/www/project/public>
# enable the .htaccess rewrites
AllowOverride All
Order Allow,Deny
Allow from All
</Directory>
ErrorLog /var/log/apache2/project_error.log
CustomLog /var/log/apache2/project_access.log combined
</VirtualHost>
<VirtualHost *:80>
ServerName mydomain.tld
DocumentRoot /var/www/project/public
DirectoryIndex /index.php
<Directory /var/www/project/public>
AllowOverride None
# Copy .htaccess contents here
</Directory>
ErrorLog /var/log/apache2/project_error.log
CustomLog /var/log/apache2/project_access.log combined
</VirtualHost>
Apache 2.4
Avec Apache 2.4, Order Allow,Deny
a été remplacé par Require all granted
.
<VirtualHost *:80>
ServerName mydomain.tld
DocumentRoot /var/www/project/public
DirectoryIndex /index.php
<Directory /var/www/project/public>
# enable the .htaccess rewrites
AllowOverride All
Require all granted
</Directory>
ErrorLog /var/log/apache2/project_error.log
CustomLog /var/log/apache2/project_access.log combined
</VirtualHost>
déplacement de index.php dans le dossier public
Si vous avez créé votre projet avec une version antérieure à la 2.4.5, vous devez modifier index.php
et déplacer les fichiers index.php
et .htaccess
dans le dossier public
.
<?php
define('DS', DIRECTORY_SEPARATOR);
//Updated with index.php in public folder
define('ROOT', __DIR__ . DS . '../app' . DS);
$config = include_once ROOT . 'config/config.php';
require_once ROOT . './../vendor/autoload.php';
require_once ROOT . 'config/services.php';
\Ubiquity\controllers\Startup::run($config);
PHP-FPM
Assurez-vous que les packages libapache2-mod-fastcgi et php7.x-fpm sont installés (remplacer x par votre version de php).
Configuration php-pm :
;;;;;;;;;;;;;;;;;;;;
; Pool Definitions ;
;;;;;;;;;;;;;;;;;;;;
; Start a new pool named 'www'.
; the variable $pool can be used in any directive and will be replaced by the
; pool name ('www' here)
[www]
user = www-data
group = www-data
; use a unix domain socket
listen = /var/run/php/php7.4-fpm.sock
; or listen on a TCP socket
listen = 127.0.0.1:9000
Configuration Apache 2.4 :
<VirtualHost *:80>
...
<FilesMatch \.php$>
SetHandler proxy:fcgi://127.0.0.1:9000
# for Unix sockets, Apache 2.4.10 or higher
# SetHandler proxy:unix:/path/to/fpm.sock|fcgi://localhost/var/www/
</FilesMatch>
</VirtualHost>
nginX
Configuration nginX :
upstream fastcgi_backend {
server unix:/var/run/php/php7.4-fpm.sock;
keepalive 50;
}
server {
server_name mydomain.tld www.mydomain.tld;
root /var/www/project/public;
index index.php;
listen 8080;
location / {
# try to serve file directly, fallback to index.php
try_files $uri @rewrites;
}
location @rewrites {
rewrite ^/(.*)$ /index.php?c=$1 last;
}
location = /index.php{
fastcgi_pass fastcgi_backend;
fastcgi_keep_conn on;
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
include /etc/nginx/fastcgi_params;
}
# return 404 for all other php files not matching the front controller
# this prevents access to other php files you don't want to be accessible.
location ~ \.php$ {
return 404;
}
error_log /var/log/nginx/project_error.log;
access_log /var/log/nginx/project_access.log;
}
Swoole
Configuration Swoole :
<?php
return array(
"host" => "0.0.0.0",
"port" => 8080,
"options"=>[
"worker_num" => \swoole_cpu_num() * 2,
"reactor_num" => \swoole_cpu_num() * 2
]
);
Workerman
Configuration Workerman :
<?php
return array(
"host" => "0.0.0.0",
"port" => 8080,
"socket"=>[
"count" => 4,
"reuseport" =>true
]
);
RoadRunner
Configuration RoadRunner :
http:
address: ":8090"
workers.command: "php-cgi ./.ubiquity/rr-worker.php"
workers:
pool:
# Set numWorkers to 1 while debugging
numWorkers: 10
maxJobs: 1000
# static file serving. remove this section to disable static file serving.
static:
# root directory for static file (http would not serve .php and .htaccess files).
dir: "."
# list of extensions for forbid for serving.
forbid: [".php", ".htaccess", ".yml"]
always: [".ico", ".html", ".css", ".js"]
Optimisation Ubiquity
Ubiquity est rapide, mais peut l’être encore plus en optimisant quelques éléments.
Note
Le serveur de test intégré (accessible par Ubiquity serve) utilise ses propres fichiers de configuration et de lancement (dans le dossier .ubiquity de votre projet).
Il ne doit donc pas être utilisé pour évaluer les résultats des modifications apportées.
Testez vos pages en utilisant une configuration logicielle et matérielle similaire à celle utilisée en production.
Utilisez un outil de benchmark pour évaluer vos changements au fur et à mesure (Apache Bench par exemple).
Cache
Système
Choisissez et testez parmi les différents systèmes de cache (ArrayCache, PhpFastCache, MemCached).
Le système de cache est défini dans le fichier de configuration :
"cache" => [
"directory" => "cache/",
"system" => "Ubiquity\\cache\\system\\ArrayCache",
"params" => []
]
Le cache par défaut ArrayCache est souvent la solution la plus optimisée.
Génération
Générer le routeur et le cache ORM (pensez que les annotations ne sont jamais utilisées à l’exécution) :
Ubiquity init-cache
Contenus statiques
Si votre application comporte des pages qui sont générées par PHP mais qui changent rarement, vous pouvez les mettre en cache :
Le résultat de la requête (utilisant les méthodes DAO)
La réponse de la route (avec l’annotation @route)
fichier index
Supprimez la ligne définissant le signalement des erreurs au moment de l’exécution, et assurez-vous que l’affichage des erreurs est désactivé dans php.ini.
error_reporting(\E_ALL);//To be removed
Optimisation de la configuration
La configuration est accessible depuis le fichier app/config/config.php
.
Ne gardez que les éléments essentiels à votre application.
key |
rôle |
Optimisation |
---|---|---|
siteUrl |
Utilisé par les méthodes Ajax, et par les fonctions |
A supprimer si ces fonctions ne sont pas utilisées |
database |
Utilisé par l’ORM Ubiquity |
A supprimer si la partie ORM n’est pas utilisée |
sessionName |
Si elle est assignée, démarre ou récupère la session php pour chaque requête. |
A supprimer si la session est inutile |
templateEngine |
S’il est assigné, instancie un nouvel objet Moteur pour chaque requête. |
A supprimer si les vues ne sont pas utilisées |
templateEngineOptions |
Options attribuées à l’instance du moteur de templates |
mettre l’option cache à |
test |
A enlever (déprécié) |
|
debug |
Active ou désactive les logs |
Mettre à |
logger |
Définit l’instance de logger |
A enlever en production |
di |
Définit les services à injecter |
La seule clé lue à l’exécution est |
cache |
Définit le chemin du cache et la classe de base du cache, utilisés par les modèles, le routeur, l’injection de dépendance. |
|
mvcNS |
Définit les chemins ou les espaces de noms utilisés par les contrôleurs, les modèles et les contrôleurs Rest |
|
isRest |
Définit la condition pour détecter si un chemin correspond à un contrôleur Rest |
A supprimer si vous n’utilisez pas explicitement cette condition dans votre code |
Exemple de configuration sans session, et sans injection de dépendances:
1<?php
2return array(
3 "templateEngine"=>'Ubiquity\\views\\engine\\Twig',
4 "templateEngineOptions"=>array("cache"=>true),
5 "debug"=>false,
6 "cache"=>["directory"=>"cache/","system"=>"Ubiquity\\cache\\system\\ArrayCache","params"=>[]],
7 "mvcNS"=>["models"=>"models","controllers"=>"controllers","rest"=>""]
8);
Optimisation des services
Les services chargés sont accessibles depuis le fichier app/config/services.php
.
Comme pour le fichier de configuration, ne conservez que les éléments essentiels à votre application.
Lignes |
Rôle |
---|---|
\Ubiquity\cache\CacheManager::startProd($config) |
Démarre le cache pour l’ORM, la base de données, le routeur, l’injection de dépendances. |
UbiquityormDAO::start() |
A utiliser uniquement en la présence de plusieurs bases de données |
Router::start() |
A utiliser uniquement si les routes sont définies avec des annotations. |
Router::addRoute(« _default », « controllers\IndexController ») |
Définit la route par défaut (à supprimer en production) |
\Ubiquity\assets\AssetsManager::start($config) |
Affecte la variable siteUrl à partir du ThemeManager, à utiliser uniquement si les fonctions |
Exemple d’un fichier Services avec une base de données et le démarrage du routeur :
1<?php
2\Ubiquity\cache\CacheManager::startProd($config);
3\Ubiquity\controllers\Router::start();
Optimisation de l’autoloader
En production, supprimez les dépendances utilisées uniquement en développement, et générez le fichier map des classes optimisé :
composer install --no-dev --classmap-authoritative
Si les dépendances utilisées ont déjà été supprimées et que vous souhaitez uniquement mettre à jour le fichier map (après avoir ajouté ou supprimé une classe) :
composer dump-autoload -o --classmap-authoritative
Note
Le paramètre –no-dev` supprime la dépendance ubiquity-dev
requise par webtools. Si vous utilisez webtools en production, ajoutez la dépendance phpmv/ubiquity-dev
:
composer require phpmv/ubiquity-dev
Optimisation PHP
Veuillez noter que d’autres applications peuvent utiliser les valeurs modifiées sur le même serveur.
OP-Cache
OPcache améliore les performances de PHP en stockant le bytecode des scripts précompilés dans la mémoire partagée, ce qui évite à PHP de devoir charger et analyser les scripts à chaque requête.
[opcache]
; Determines if Zend OPCache is enabled
opcache.enable=1
; The OPcache shared memory storage size.
opcache.memory_consumption=256
; The maximum number of keys (scripts) in the OPcache hash table.
; Only numbers between 200 and 1000000 are allowed.
opcache.max_accelerated_files=10000
; When disabled, you must reset the OPcache manually or restart the
; webserver for changes to the filesystem to take effect.
opcache.validate_timestamps=0
; Allow file existence override (file_exists, etc.) performance feature.
opcache.enable_file_override=1
; Enables or disables copying of PHP code (text segment) into HUGE PAGES.
; This should improve performance, but requires appropriate OS configuration.
opcache.huge_code_pages=1
Si vous utilisez le serveur web ubiquity-swoole :
; Determines if Zend OPCache is enabled for the CLI version of PHP
opcache.enable_cli=1
Pour compléter
N’oubliez pas que le framework utilisé ne fait pas tout. Vous devez également optimiser votre propre code.
Ubiquity commandes
Note
Cette partie est accessible à partir des webtools, donc si vous avez créé votre projet avec l’option -a ou avec la commande create-project.
Commandes
A partir des webtools, aller dans la partie commands,

ou directement à l’adresse http://127.0.0.1:8090/Admin/commands
.
Liste des commandes
Activer l’onglet Commands pour obtenir la liste des commandes devtools.

Informations sur une commande
Il est possible d’obtenir de l’aide sur une commande (ce qui produit un résultat équivalent à Ubiquity help cmdName
).

Exécution de commandes
Un clic sur le bouton run d’une commande affiche un formulaire pour saisir ses paramètres (ou l’exécute directement si elle n’en prend aucun).

Après avoir saisi les paramètres, l’exécution produit un résultat.

Suite de commandes
Retourner à l’onglet My commands : Il est possible de sauvegarder une séquence de commandes (avec des paramètres enregistrés), puis d’exécuter cette même séquence :
Création de suite
Cliquer sur add command suite

Ajouter les commandes souhaitées et modifier les paramètres :

La validation génère la suite :

Exécution d’une suite de commandes
En cliquant sur le bouton « run » de la suite, on exécute la liste des commandes qu’elle contient :

Création d’une commande personnalisée
Cliquer sur le bouton Create devtools command.

Saisir les caractéristiques de la nouvelle commande :
Le nom de la commande
Sa valeur : nom de l’argument principal
Les paramètres de la commande : avec plusieurs paramètres, les séparer avec une virgule
La description de la commande
Les alias de la commande : En cas d’alias multiples, utilisez la virgule comme séparateur.

Note
Les commandes personnalisées sont créées dans le dossier app/commands du projet.

La classe générée :
1namespace commands;
2
3use Ubiquity\devtools\cmd\commands\AbstractCustomCommand;
4use Ubiquity\devtools\cmd\ConsoleFormatter;
5use Ubiquity\devtools\cmd\Parameter;
6
7class CreateArray extends AbstractCustomCommand {
8
9 protected function getValue(): string {
10 return 'jsonValue';
11 }
12
13 protected function getAliases(): array {
14 return array("createarray","arrayFromJson");
15 }
16
17 protected function getName(): string {
18 return 'createArray';
19 }
20
21 protected function getParameters(): array {
22 return ['f' => Parameter::create('fLongName', 'The f description.', [])];
23 }
24
25 protected function getExamples(): array {
26 return ['Sample use of createArray'=>'Ubiquity createArray jsonValue'];
27 }
28
29 protected function getDescription(): string {
30 return 'Creates an array from JSON and save to file';
31 }
32
33 public function run($config, $options, $what, ...$otherArgs) {
34 //TODO implement command behavior
35 echo ConsoleFormatter::showInfo('Run createArray command');
36 }
37}
La commande CreateArray implémentée :
1namespace commands;
2
3use Ubiquity\devtools\cmd\commands\AbstractCustomCommand;
4use Ubiquity\devtools\cmd\ConsoleFormatter;
5use Ubiquity\devtools\cmd\Parameter;
6use Ubiquity\utils\base\UFileSystem;
7
8class CreateArray extends AbstractCustomCommand {
9
10 protected function getValue(): string {
11 return 'jsonValue';
12 }
13
14 protected function getAliases(): array {
15 return array(
16 "createarray",
17 "arrayFromJson"
18 );
19 }
20
21 protected function getName(): string {
22 return 'createArray';
23 }
24
25 protected function getParameters(): array {
26 return [
27 'f' => Parameter::create('filename', 'The filename to create.', [])
28 ];
29 }
30
31 protected function getExamples(): array {
32 return [
33 'Save an array in test.php' => "Ubiquity createArray \"{\\\"created\\\":true}\" -f=test.php"
34 ];
35 }
36
37 protected function getDescription(): string {
38 return 'Creates an array from JSON and save to file';
39 }
40
41 public function run($config, $options, $what, ...$otherArgs) {
42 echo ConsoleFormatter::showInfo('Run createArray command');
43 $array = \json_decode($what, true);
44 $error = \json_last_error();
45 if ($error != 0) {
46 echo ConsoleFormatter::showMessage(\json_last_error_msg(), 'error');
47 } else {
48 $filename = self::getOption($options, 'f', 'filename');
49 if ($filename != null) {
50 UFileSystem::save($filename, "<?php\nreturn " . var_export($array, true) . ";\n");
51 echo ConsoleFormatter::showMessage("$filename succefully created!", 'success', 'CreateArray');
52 } else {
53 echo ConsoleFormatter::showMessage("Filename must have a value!", 'error');
54 }
55 }
56 }
57}
Exécution d’une commande personnalisée
La nouvelle commande est accessible depuis les devtools, à condition qu’elle soit bien présente dans le projet :
Ubiquity help createArray

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

Gestion Composer
Note
Cette partie est accessible depuis les webtools, donc si vous avez créé votre projet avec l’option -a ou avec la commande create-project.
Accès
Depuis les webtools, activer la patir composer.

ou aller directement à l’adresse http://127.0.0.1:8090/Admin/composer
.
Liste des dépendances
L’interface affiche la liste des dépendances déjà installées, et celles qui sont directement installables.

Installation de dépendance
Parmi les dépendances listées :
Cliquez sur le bouton add des dépendances que vous voulez ajouter.

Puis cliquer sur le bouton Generate composer update :

La validation génère la mise à jour.
Pour les dépendances non listées :
Cliquer sur le bouton Add dependency :

Saisir le nom du vendor (provider) ;
Sélectionner le package dans la liste :
Sélectionnez éventuellement une version (si aucune ne l’est, la dernière version stable sera installée).
Suppression de dépendance
Cliquez sur le bouton remove des dépendances que vous voulez supprimer.

Cliquer ensuite sur le bouton Generate composer update puis valider la mise à jour.
Note
Il est possible d’effectuer plusieurs opérations d’addition ou de suppression et de les valider ensemble.
Optimisation Composer
Cliquer sur le bouton Optimize autoloader.
L’optimisation de l’autoload se fait avec l’option authoritative classmap.
Mise en cache Ubiquity
Dépendances Ubiquity
php >=7.4
phpmv/ubiquity
=> Ubiquity core
En production
Moteur de template
Twig est nécessaire s’il est utilisé en tant que moteur de template, ce qui n’est pas une obligation.
twig/twig
=> Template engine
Sécurité
phpmv/ubiquity-security
=> Csrf, Csp…phpmv/ubiquity-acl
=> Management des contrôles d’accès
En développement
Webtools
phpmv/ubiquity-dev
=> dev classes pour les webtools et les devtools depuis la v2.3.0phpmv/php-mv-ui
=> Librairie frontmindplay/annotations
=> Librairie pour annotations , requis pour générer le cache, les modèles…monolog/monolog
=> Librairie pour les logsczproject/git-php
=> Opérations git (+ git console requise)
Devtools
phpmv/ubiquity-devtools
=> Cli consolephpmv/ubiquity-dev
=> dev classes pour les webtools et les devtools depuis la v2.3.0mindplay/annotations
=> Librairie pour annotations , requis pour générer le cache, les modèles…
Tests
codeception/codeception
=> Testscodeception/c3
=> C3 integrationphpmv/ubiquity-codeception
=> Codeception pour Ubiquity
Module client OAuht2
Note
Cette partie est accessible depuis les webtools, donc si vous avez créé votre projet avec l’option -a ou avec la commande create-project. Le module OAuth n’est pas installé par défaut. Il utilise la bibliothèque HybridAuth.
Installation
Depuis la racine du projet :
composer require phpmv/ubiquity-oauth
Note
Il est également possible d’ajouter la dépendance ubiquity-oauth en utilisant la partie Composer du module d’administration.

Configuration OAuth
Configuration globale

Cliquez sur le bouton Global configuration, et modifiez l’URL de rappel, qui correspond à l’url de rappel locale après une connexion réussie.

Contrôleur OAuth
Cliquez sur le bouton Create Oauth controller et attribuez à la route la valeur précédemment donnée au callback :

Valider et réinitialiser le cache du routeur :

Fournisseurs
Note
Pour une authentification OAuth, il est nécessaire de créer au préalable une application chez le fournisseur, et de prendre connaissance des clés de l’application (id et secret).
Cliquer sur le bouton Add provider et sélectionner Google:

Vérifiez la connexion en cliquant sur le bouton Check :

Information post connexion :

Personnalisation de OAuthController
Le contrôleur créé est le suivant :
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
}
}
La méthode _oauth correspond à l’url de callback
La méthode onConnect est déclenchée à la connexion et peut être surchargée.
Exemple :
Récupération possible d’un utilisateur associé dans la base de données
ou création d’un nouvel utilisateur
Ajout de l’utilisateur connecté et redirection
protected function onConnect(string $name, AdapterInterface $provider) {
$userProfile = $provider->getUserProfile();
$key = md5($name . $userProfile->identifier);
$user = DAO::getOne(User::class, 'oauth= ?', false, [
$key
]);
if (! isset($user)) {
$user = new User();
$user->setOauth($key);
$user->setLogin($userProfile->displayName);
DAO::save($user);
}
USession::set('activeUser', $user);
\header('location:/');
}
Plateformes asynchrones
Note
Ubiquity supporte plusieurs plateformes : Swoole, Workerman, RoadRunner, PHM-PM,ngx_php.
Swoole
Installer l’extension Swoole sur votre système (linux) ou dans votre image Docker :
#!/bin/bash
pecl install swoole
Lancer Ubiquity Swoole (la première fois, le paquet ubiquity-swoole sera installé) :
Ubiquity serve -t=swoole
Configuration du serveur
<?php
return array(
"host" => "0.0.0.0",
"port" => 8080,
"options"=>[
"worker_num" => \swoole_cpu_num() * 2,
"reactor_num" => \swoole_cpu_num() * 2
]
);
Le port peut aussi être changé au démarrage du serveur :
Ubiquity serve -t=swoole -p=8999
Optimisation des services
Le démarrage des services ne sera effectué qu’une seule fois, au démarrage du serveur.
\Ubiquity\cache\CacheManager::startProd($config);
\Ubiquity\orm\DAO::setModelsDatabases([
'models\\Foo' => 'default',
'models\\Bar' => 'default'
]);
\Ubiquity\cache\CacheManager::warmUpControllers([
\controllers\IndexController::class,
\controllers\FooController::class
]);
$swooleServer->on('workerStart', function ($srv) use (&$config) {
\Ubiquity\orm\DAO::startDatabase($config, 'default');
\controllers\IndexController::warmup();
\controllers\FooController::warmup();
});
- La méthode warmUpControllers :
Instancie les contrôleurs
Effectue l’injection de dépendances
prépare l’appel des méthodes initialize et finalize (initialisation des constantes d’appel)
Au démarrage de chaque Worker, la méthode warmup des contrôleurs peut par exemple initialiser les requêtes DAO préparées :
public static function warmup() {
self::$oneFooDao = new DAOPreparedQueryById('models\\Foo');
self::$allFooDao = new DAOPreparedQueryAll('models\\Foo');
}
Workerman
Workerman ne nécessite aucune installation particulière (sauf pour libevent qu’il est recommandé d’utiliser en production pour des raisons de performance).
Lancer Ubiquity Workerman (la première fois, le paquet ubiquity-workerman sera installé) :
Ubiquity serve -t=workerman
Configuration du serveur
<?php
return array(
"host" => "0.0.0.0",
"port" => 8080,
"socket"=>[
"count" => 4,
"reuseport" =>true
]
);
Le port peut aussi être changé au démarrage du serveur :
Ubiquity serve -t=workerman -p=8999
Optimisation des services
Le démarrage des services ne sera effectué qu’une seule fois, au démarrage du serveur.
\Ubiquity\cache\CacheManager::startProd($config);
\Ubiquity\orm\DAO::setModelsDatabases([
'models\\Foo' => 'default',
'models\\Bar' => 'default'
]);
\Ubiquity\cache\CacheManager::warmUpControllers([
\controllers\IndexController::class,
\controllers\FooController::class
]);
$workerServer->onWorkerStart = function () use ($config) {
\Ubiquity\orm\DAO::startDatabase($config, 'default');
\controllers\IndexController::warmup();
\controllers\FooController::warmup();
});
ngx_php
//TODO
Roadrunner
//TODO