Ubiquity Guía del usuario

Inicio rápido con consola

Nota

Si no te gusta el modo consola, puedes cambiar a inicio rápido con web tools (UbiquityMyAdmin).

Instalar Composer

ubiquity utiliza Composer para gestionar sus dependencias. Por lo tanto, antes de usar, usted tendrá que asegurarse de que tiene Composer instalado en su máquina.

Instalar Ubiquity-devtools

Descargue el instalador de Ubiquity-devtools utilizando Composer.

composer global require phpmv/ubiquity-devtools

Pruebe su reciente instalación haciendo:

Ubiquity version
_images/ubi-version.png

Puedes obtener en todo momento ayuda con un comando tecleando: Ubiquity help seguido de lo que buscas.

Ejemplo :

Ubiquity help project

Creación de proyectos

Crear el proyecto **quick-start

Ubiquity new quick-start

Estructura del directorio

El proyecto creado en la carpeta quick-start tiene una estructura sencilla y legible:

la carpeta app contiene el código de tu futura aplicación:

app
  cache
  config
  controllers
  models
  views

Start-up

Vaya a la carpeta recién creada quick-start e inicie el servidor php incorporado:

Ubiquity serve

Compruebe el correcto funcionamiento en la dirección http://127.0.0.1:8090:

_images/quick-start-main.png

Nota

Si el puerto 8090 está ocupado, puede iniciar el servidor en otro puerto utilizando la opción -p.

Ubiquity serve -p=8095

Controlador

La aplicación de consola dev-tools ahorra tiempo en operaciones repetitivas. Pasamos por ella para crear un controlador.

Ubiquity controller DefaultController
_images/controller-creation.png

Podemos entonces editar el archivo app/controllers/DefaultController en nuestro IDE favorito:

app/controllers/DefaultController.php
1namespace controllers;
2 /**
3  * Controller DefaultController
4  */
5class DefaultController extends ControllerBase{
6     public function index(){}
7}

Añada el mensaje tradicional y pruebe su página en http://127.0.0.1:8090/DefaultController.

app/controllers/DefaultController.php
class DefaultController extends ControllerBase{

    public function index(){
        echo 'Hello world!';
    }

}

Por ahora, no hemos definido rutas, por lo que el acceso a la aplicación se realiza según el siguiente esquema:
controllerName/actionName/param

La acción por defecto es el método index, no necesitamos especificarlo en la url.

Route

Importante

El enrutamiento se define con el atributo Route (con php>8) o la anotación @route y no se hace en un fichero de configuración:
es una elección de diseño.

El parámetro automated puesto a true permite definir los métodos de nuestra clase como sub-rutas de la ruta principal /hello.

Con anotaciones:

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

Con atributos (php>8):

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

Router cache

Importante

Ningún cambio en las rutas es efectivo sin inicializar la caché.
Las anotaciones nunca se leen en tiempo de ejecución. Esto también es una elección de diseño.

Podemos utilizar la consola para la reinicialización de la caché:

Ubiquity init-cache
_images/init-cache.png

Comprobemos que la ruta existe:

Ubiquity info:routes
_images/info-routes1.png

Ahora podemos probar la página en http://127.0.0.1:8090/hello.

Acción y ruta con parámetros

Ahora crearemos una acción (sayHello) con un parámetro (name), y la ruta asociada (to):
La ruta utilizará el parámetro name de la acción:

Ubiquity action DefaultController.sayHello -p=name -r=to/{name}/
_images/action-creation.png

Tras reinicializar la caché (comando init-cache), debería aparecer el comando info:routes:

_images/2-routes.png

Cambia el código en tu IDE: la acción debe decir Hola a alguien…

app/controllers/DefaultController.php
     /**
      * @route("to/{name}/")
      */
     public function sayHello($name){
             echo 'Hello '.$name.'!';
     }

y prueba la página en http://127.0.0.1:8090/hello/to/Mr SMITH

Acción, parámetros de ruta y vista

Ahora crearemos una acción (information) con dos parámetros (title and message), la ruta asociada (info) y una vista para mostrar el mensaje: |br|La ruta utilizará los dos parámetros de la acción.

Ubiquity action DefaultController.information -p=title,message='nothing' -r=info/{title}/{message} -v

Nota

El parámetro -v (–view) se utiliza para crear la vista asociada a la acción.

Tras reinicializar la caché, ahora tenemos 3 rutas:

_images/3-routes.png

Volvamos a nuestro entorno de desarrollo y veamos el código generado:

app/controllers/DefaultController.php
     /**
      * @route("info/{title}/{message}")
      */
     public function information($title,$message='nothing'){
             $this->loadView('DefaultController/information.html');
     }

Necesitamos pasar las 2 variables a la vista:

/**
 * @route("info/{title}/{message}")
 */
public function information($title,$message='nothing'){
        $this->loadView('DefaultController/information.html',compact('title','message'));
}

Y utilizamos nuestras 2 variables en la vista twig asociada:

app/views/DefaultController/information.html
     <h1>{{title}}</h1>
     <div>{{message | raw}}</div>

Podemos probar nuestra página en http://127.0.0.1:8090/hello/info/Quick start/Ubiquity is quiet simple
Es obvio

_images/quiet-simple.png

Inicio rápido con Webtools

Instalar Composer

ubiquity utiliza Composer para gestionar sus dependencias. Por lo tanto, antes de usar, usted tendrá que asegurarse de que tiene Composer instalado en su máquina.

Instalar Ubiquity-devtools

Descargue el instalador de Ubiquity-devtools utilizando Composer.

composer global require phpmv/ubiquity-devtools

Pruebe su reciente instalación haciendo:

Ubiquity version
_images/ubi-version.png

Puedes obtener en todo momento ayuda con un comando tecleando: Ubiquity help seguido de lo que buscas.

Ejemplo :

Ubiquity help project

Creación de proyectos

Crear el proyecto quick-start con la interfaz Webtools (la opción -a)

Ubiquity new quick-start -a

Estructura del directorio

El proyecto creado en la carpeta quick-start tiene una estructura sencilla y legible:

la carpeta app contiene el código de tu futura aplicación:

app
  cache
  config
  controllers
  models
  views

Start-up

Vaya a la carpeta recién creada quick-start e inicie el servidor php incorporado:

Ubiquity serve

Compruebe el correcto funcionamiento en la dirección http://127.0.0.1:8090:

_images/quick-start-main.png

Nota

Si el puerto 8090 está ocupado, puede iniciar el servidor en otro puerto utilizando la opción -p.

Ubiquity serve -p=8095

Controlador

Vaya a la interfaz de administración haciendo clic en el botón Webtools:

_images/ubi-my-admin-btn.png

Selecciona las herramientas que necesitas:

_images/ubi-my-admin-interface-0.png

La aplicación web Webtools ahorra tiempo en operaciones repetitivas.

_images/ubi-my-admin-interface.png

Vamos a través de él para crear un controlador.

Vaya a la parte controllers, introduzca DefaultController en el campo nombreControlador y cree el controlador:

_images/create-controller-btn.png

Se crea el controlador DefaultController:

_images/controller-created.png

Podemos entonces editar el archivo app/controllers/DefaultController en nuestro IDE favorito:

app/controllers/DefaultController.php
1namespace controllers;
2 /**
3 * Controller DefaultController
4 **/
5class DefaultController extends ControllerBase{
6     public function index(){}
7}

Añada el mensaje tradicional y pruebe su página en http://127.0.0.1:8090/DefaultController.

app/controllers/DefaultController.php
     class DefaultController extends ControllerBase{

             public function index(){
                     echo 'Hello world!';
             }

     }

Por ahora, no hemos definido rutas, por lo que el acceso a la aplicación se realiza según el siguiente esquema:
controllerName/actionName/param

La acción por defecto es el método index, no necesitamos especificarlo en la url.

Route

Importante

El enrutamiento se define con la anotación @route y no se hace en un fichero de configuración: |br|es una elección de diseño.

El parámetro automated puesto a true permite definir los métodos de nuestra clase como sub-rutas de la ruta principal /hello.

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

Router cache

Importante

Ningún cambio en las rutas es efectivo sin inicializar la caché. |br|Las anotaciones nunca se leen en tiempo de ejecución. Esto también es una elección de diseño.

Podemos utilizar las web tools para la reinicialización de la caché:

Vaya a la sección Routes y haga clic en el botón re-init cache.

_images/re-init-cache-btn.png

La ruta aparece ahora en la interfaz:

_images/1-route.png

Ahora podemos probar la página pulsando el botón GET o yendo a la dirección http://127.0.0.1:8090/hello.

Acción y ruta con parámetros

Ahora crearemos una acción (sayHello) con un parámetro (name), y la ruta asociada (to):
La ruta utilizará el parámetro name de la acción:

Vaya a la sección Controllers:

  • haga clic en el botón + asociado a DefaultController,

  • a continuación, seleccione Add new action in.. elemento.

_images/create-action-btn.png

Introduzca la información de la acción en el siguiente formulario:

_images/create-action.png

Tras reiniciar la caché con el botón naranja, podemos ver la nueva ruta hello/to/{name}:

_images/router-re-init-1.png

Compruebe la creación de la ruta accediendo a la sección Rutas:

_images/router-re-init-2.png

Ahora podemos probar la página pulsando el botón GET:

_images/test-action.png

Podemos ver el resultado:

_images/test-action-result.png

Podríamos ir directamente a la dirección http://127.0.0.1:8090/hello/to/Mr SMITH para probar

Acción, parámetros de ruta y vista

Ahora crearemos una acción (information) con dos parámetros (title and message), la ruta asociada (info) y una vista para mostrar el mensaje: |br|La ruta utilizará los dos parámetros de la acción.

En la sección Controllers, cree otra acción en DefaultController:

_images/create-action-btn.png

Introduzca la información de la acción en el siguiente formulario:

_images/create-action-view.png

Nota

La casilla de verificación de la vista se utiliza para crear la vista asociada a la acción.

Tras reinicializar la caché, ahora tenemos 3 rutas:

_images/create-action-view-result.png

Volvamos a nuestro entorno de desarrollo y veamos el código generado:

app/controllers/DefaultController.php
     /**
      *@route("info/{title}/{message}")
     **/
     public function information($title,$message='nothing'){
             $this->loadView('DefaultController/information.html');
     }

Necesitamos pasar las 2 variables a la vista:

/**
 *@route("info/{title}/{message}")
**/
public function information($title,$message='nothing'){
        $this->loadView('DefaultController/information.html',compact('title','message'));
}

Y utilizamos nuestras 2 variables en la vista twig asociada:

app/views/DefaultController/information.html
     <h1>{{title}}</h1>
     <div>{{message | raw}}</div>

Podemos probar nuestra página en http://127.0.0.1:8090/hello/info/Quick start/Ubiquity is quiet simple
Es obvio

_images/quiet-simple.png

Instalación de Ubiquity-devtools

Instalar Composer

ubiquity utiliza Composer para gestionar sus dependencias. Por lo tanto, antes de usar, usted tendrá que asegurarse de que tiene Composer instalado en su máquina.

Instalar Ubiquity-devtools

Descargue el instalador de Ubiquity-devtools utilizando Composer.

composer global require phpmv/ubiquity-devtools

Asegúrese de colocar el directorio ~/.composer/vendor/bin en su PATH para que el ejecutable Ubiquity pueda ser localizado por su sistema.

Una vez instalado, el simple comando Ubiquity new creará una nueva instalación de Ubiquity en el directorio que especifique. Por ejemplo, Ubiquity new blog crearía un directorio llamado blog que contendría un proyecto Ubiquity:

Ubiquity new blog

La opción semántica añade Semantic-UI para el front-end.

Puede ver más opciones sobre la instalación leyendo la sección Creación de proyectos.

Creación de proyectos

Después de instalar Instalación de Ubiquity-devtools, en su terminal, llame al comando new en la carpeta raíz de su servidor web :

Muestras

Un proyecto sencillo

Ubiquity new projectName

Un proyecto con interfaz UbiquityMyAdmin

Ubiquity new projectName -a

Un proyecto con los temas bootstrap y semantic-ui instalados

Ubiquity new projectName --themes=bootstrap,semantic

Argumentos del instalador

nombre corto

nombre

rol

default

Valores permitidos

Desde devtools

b

dbName

Establece el nombre de la base de datos.

s

serverName

Define la dirección del servidor db.

127.0.0.1

p

port

Define el puerto del servidor db.

3306

u

user

Define el usuario del servidor db.

root

w

password

Defines the db server password.

“”

h

themes

Instalar temas.

semantic,bootstrap,foundation

m

all-models

Crea todos los modelos a partir de la base de datos.

false

a

admin

Añade la interfaz UbiquityMyAdmin.

false

i

siteUrl

Define la URL del sitio.

http://127.0.0.1/{projectname}

1.2.6

e

rewriteBase

Establece la base para la reescritura.

/{projectname}/

1.2.6

Uso de argumentos

nombres cortos

Ejemplo de creación del proyecto blog, conectado a la base de datos blogDb, con generación de todos los modelos

Ubiquity new blog -b=blogDb -m=true

nombres largos

Ejemplo de creación del proyecto blog, conectado a la base de datos bogDb, con generación de todos los modelos e integración del tema semántico

Ubiquity new blog --dbName=blogDb --all-models=true --themes=semantic

Ejecutar

Para iniciar el servidor web incrustado y probar tus páginas, ejecútalo desde la carpeta raíz de la aplicación:

Ubiquity serve

El servidor web se inicia en 127.0.0.1:8090.

Configuración del proyecto

Normalmente, el instalador limita las modificaciones a realizar en los ficheros de configuración y su aplicación queda operativa tras la instalación

_images/firstProject.png

Configuración principal

La configuración principal de un proyecto se localiza en el archivo app/conf/config.php.

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

Configuración de los servicios

Los servicios que se cargan al iniciarse se configuran en el archivo app/conf/services.php.

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 bonitas

Apache

El framework incluye un archivo .htaccess que se utiliza para permitir URLs sin index.php. Si utiliza Apache para servir su aplicación Ubiquity, asegúrese de habilitar el módulo mod_rewrite.

.htaccess
AddDefaultCharset UTF-8
<IfModule mod_rewrite.c>
     RewriteEngine On
     RewriteBase /blog/
     RewriteCond %{REQUEST_FILENAME} !-f
     RewriteCond %{HTTP_ACCEPT} !(.*images.*)
     RewriteRule ^(.*)$ index.php?c=$1 [L,QSA]
</IfModule>

Véase Apache configuration para saber más.

Nginx

En Nginx, la siguiente directiva en la configuración de su sitio permitirá URLs «bonitas»:

location /{
      rewrite ^/(.*)$ /index.php?c=$1 last;
}

Véase NginX configuration para saber más.

Laravel Valet Driver

Valet es un entorno de desarrollo php para Mac minimalistas. No Vagrant, no /etc/hosts file. Incluso puedes compartir tus sitios públicamente usando túneles locales.

Laravel Valet configura tu Mac para ejecutar siempre Nginx en segundo plano cuando se inicia tu máquina. Entonces, usando DnsMasq, Valet proxy todas las peticiones en el dominio *.test para que apunten a sitios instalados en tu máquina local.

Más información sobre Laravel Valet

Crea UbiquityValetDriver.php en ~/.config/valet/Drivers/ añade el siguiente código php y guárdalo.

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

Uso de Devtools

Creación de proyectos

Consulte Creación de proyectos para crear un proyecto.

Truco

Para todos los demás comandos, debes estar en la carpeta de tu proyecto o en una de sus subcarpetas.

Importante

La carpeta .ubiquity creada automáticamente con el proyecto permite a las devtools encontrar la carpeta raíz del proyecto.
Si se ha eliminado o ya no está presente, debe volver a crear esta carpeta vacía.

Creación de controladores

Especificaciones

  • comando : controller

  • Argumento : controller-name

  • alias : create-controller

Parámetros

nombre corto

nombre

rol

default

Valores permitidos

v

view

Crea el índice de la vista asociada.

true

true, false

Muestras:

Crea la clase de controlador controllers\ClientController en app/controllers/ClientController.php:

Ubiquity controller ClientController

Crea la clase de controlador controllersClientController en app/controllers/ClientController.php y la vista asociada en app/views/ClientController/index.html:

Ubiquity controller ClientController -v

Creación de acciones

Especificaciones

  • comando : action

  • Argumento : controller-name.action-name

  • aliases : new-action

Parámetros

nombre corto

nombre

rol

default

Valores permitidos

p

params

Los parámetros (o argumentos) de la acción.

a,b=5 or $a,$b,$c

r

route

La ruta asociada.

/path/to/route

v

create-view

Crea la vista asociada.

false

true,false

Muestras:

Añade la acción all en el controlador Users:

Ubiquity action Users.all

resultado del código:

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

Añade la acción display en el controlador Users con un parámetro:

Ubiquity action Users.display -p=idUser

resultado del código:

app/controllers/Users.php
1class Users extends ControllerBase{
2
3   public function index(){}
4
5   public function display($idUser){
6
7   }
8}

Añade la acción display con una ruta asociada:

Ubiquity action Users.display -p=idUser -r=/users/display/{idUser}

resultado del código:

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

Añade la acción buscar con múltiples parámetros:

Ubiquity action Users.search -p=name,address=''

resultado del código:

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

Añade la acción search y crea la vista asociada:

Ubiquity action Users.search -p=name,address -v

Creación de modelos

Nota

Opcionalmente, compruebe la configuración de la conexión a la base de datos en el archivo app/config/config.php antes de ejecutar estos comandos.

Generar un modelo correspondiente a la tabla user de la base de datos:

Ubiquity model user

Creación de todos los modelos

Para generar todos los modelos a partir de la base de datos:

Ubiquity all-models

Inicialización de la caché

Para inicializar la caché de enrutamiento (basado en anotaciones en controladores) y orm (basado en anotaciones en modelos) :

Ubiquity init-cache

URLs

como muchos otros frameworks, si estás usando el router con su comportamiento por defecto, hay una relación uno a uno entre una cadena URL y su correspondiente clase/método controlador. Los segmentos en una URI normalmente siguen este patrón:

example.com/controller/method/param
example.com/controller/method/param1/param2

Método por defecto

Cuando la URL se compone de una sola parte, correspondiente al nombre de un controlador, se llama automáticamente al método index del controlador :

URL :

example.com/Products
example.com/Products/index

Controller :

app/controllers/Products.php
1class Products extends ControllerBase{
2    public function index(){
3        //Default action
4    }
5}

Parámetros requeridos

Si el método solicitado requiere parámetros, deben pasarse en la URL:

Controller :

app/controllers/Products.php
1class Products extends ControllerBase{
2    public function display($id){}
3}

Valid Urls :

example.com/Products/display/1
example.com/Products/display/10/
example.com/Products/display/ECS

Parámetros opcionales

El método llamado puede aceptar parámetros opcionales.

Si un parámetro no está presente en la URL, se utiliza el valor por defecto del parámetro.

Controller :

app/controllers/Products.php
class Products extends ControllerBase{
    public function sort($field, $order='ASC'){}
}

Valid Urls :

example.com/Products/sort/name (uses "ASC" for the second parameter)
example.com/Products/sort/name/DESC
example.com/Products/sort/name/ASC

Se distingue entre mayúsculas y minúsculas

En los sistemas Unix, el nombre de los controladores distingue entre mayúsculas y minúsculas.

Controller :

app/controllers/Products.php
class Products extends ControllerBase{
    public function caseInsensitive(){}
}

Urls :

example.com/Products/caseInsensitive (valid)
example.com/Products/caseinsensitive (valid because the method names are case insensitive)
example.com/products/caseInsensitive (invalid since the products controller does not exist)

Personalización de rutas

El Router (Rutas) y las anotaciones/atributos en las clases de controlador permiten personalizar las URL.

Router (Rutas)

El enrutamiento se puede utilizar además del mecanismo por defecto que asocia controller/action/{parameters} con una url.

Rutas dinámicas

Las rutas dinámicas se definen en tiempo de ejecución.
Es posible definir estas rutas en el archivo app/config/services.php.

Importante

Las rutas dinámicas sólo deben utilizarse si la situación lo requiere:

  • en el caso de una microaplicación

  • si una ruta debe definirse dinámicamente

En todos los demás casos, es aconsejable declarar las rutas con anotaciones, para beneficiarse del almacenamiento en caché.

Rutas de devolución de llamadas (callback)

Las rutas más básicas de Ubiquity aceptan un Closure.
En el contexto de las microaplicaciones, este método evita tener que crear un controlador.

app/config/services.php
1     use Ubiquity\controllers\Router;
2
3     Router::get("foo", function(){
4             echo 'Hello world!';
5     });

Se pueden definir rutas de devolución de llamada para todos los métodos http con:

  • Router::post

  • Router::put

  • Router::delete

  • Router::patch

  • Router::options

Rutas de controlador

Las rutas también pueden asociarse de forma más convencional a una acción de un controlador:

app/config/services.php
1     use Ubiquity\controllers\Router;
2
3     Router::addRoute('bar', \controllers\FooController::class,'index');

El método FooController::index() será accesible a través de la url /bar.

En este caso, el FooController debe ser una clase que herede de Ubiquitycontrollers\Controller o una de sus subclases, y debe tener un método index:

app/controllers/FooController.php
1     namespace controllers;
2
3     class FooController extends ControllerBase{
4
5             public function index(){
6                     echo 'Hello from foo';
7             }
8     }

Ruta por defecto

La ruta por defecto coincide con la ruta /.
Puede definirse utilizando la ruta reservada _default.

app/config/services.php
1     use Ubiquity\controllers\Router;
2
3     Router::addRoute("_default", \controllers\FooController::class,'bar');

Rutas estáticas

Las rutas estáticas se definen mediante anotaciones o con atributos nativos de php desde Ubiquity 2.4.0.

Nota

Estas anotaciones o atributos nunca se leen en tiempo de ejecución.
Es necesario reiniciar la caché del enrutador para tener en cuenta los cambios realizados en las rutas.

Creación

app/controllers/ProductsController.php
 1namespace controllers;
 2
 3use Ubiquity\attributes\items\router\Route;
 4
 5class ProductsController extends ControllerBase{
 6
 7    #[Route('products')]
 8    public function index(){}
 9
10}

El método Products::index() será accesible a través de la url /products.

Nota

La barra inicial o terminal se ignora en la ruta. Por lo tanto, las siguientes rutas son equivalentes:
  • #[Route('products')]

  • #[Route('/products')]

  • #[Route('/products/')]

Parámetros de ruta

Una ruta puede tener parámetros:

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

Parámetros opcionales de ruta

Una ruta puede definir parámetros opcionales, si el método asociado tiene argumentos opcionales:

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

Requisitos de ruta

Es posible añadir especificaciones sobre las variables pasadas en la url mediante el atributo requirements.

app/controllers/ProductsController.php
 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}
La ruta definida coincide con estas urls:
  • products/all/1/20

  • products/all/5/

pero no con ese:
  • products/all/test

Tipificación de parámetros

La declaración de ruta tiene en cuenta los tipos de datos pasados a la acción, lo que evita añadir requisitos para tipos simples (int, bool, float).

app/controllers/ProductsController.php
 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}
La ruta definida coincide con estas urls:
  • products/1

  • products/20

pero no con ese:
  • products/test

Valores correctos por tipo de datos:
  • int: 1

  • bool: 0 or 1

  • float: 1 1.0

Métodos http de enrutado

Es posible especificar el método o métodos http asociados a una ruta:

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

El atributo methods puede aceptar varios métodos:
@route("testMethods", "methods"=>["get", "post", "delete"])
#[Route('testMethods', methods: ['get','post','delete'])]

La anotación @route o atributo Route se aplica por defecto a todos los métodos HTTP.
Existe una anotación específica para cada uno de los métodos HTTP existentes:

  • @get => Get

  • @post => Post

  • @put => Put

  • @patch => Patch

  • @delete => Delete

  • @head => Head

  • @options => Options

app/controllers/ProductsController.php
 1namespace controllers;
 2
 3use Ubiquity\attributes\items\router\Get;
 4
 5class ProductsController extends ControllerBase{
 6
 7   #[Get('products')]
 8   public function index(){}
 9
10}

Nombre de ruta

Es posible especificar el name de una ruta, este nombre facilita entonces el acceso a la url asociada.
Si no se especifica el atributo name, cada ruta tiene un nombre por defecto, basado en el patrón controllerName_methodName.

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

Generación de URL o rutas

Los nombres de ruta pueden utilizarse para generar URL o rutas.

Enlaces a páginas en Twig

<a href="{{ path('products.index') }}">Products</a>

Ruta global

La anotación @route puede utilizarse en una clase de controlador :

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

En este caso, la ruta definida en el controlador se utiliza como prefijo para todas las rutas del controlador :
La ruta generada para la acción display es /product/all.

rutas automatizadas

Si se define una ruta global, es posible añadir todas las acciones del controlador como rutas (utilizando el prefijo global), estableciendo el parámetro automated :

app/controllers/ProductsController.php
 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}
El atributo automated define las 3 rutas contenidas en ProductsController:
  • /product/(index/)?

  • /product/generate

  • /product/display/{id}

rutas heredadas

Con el atributo inherited, también es posible generar las rutas declaradas en las clases base, o generar rutas asociadas a acciones de clases base si el atributo automated se establece a true al mismo tiempo.

La clase base:

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

La clase derivada que utiliza miembros heredados:

app/controllers/ProductsController.php
 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}
El atributo inherited define las 2 rutas definidas en ProductsBase:
  • /products/(index/)?

  • /products/sort/{name}

Si se combinan los atributos automated y inherited, las acciones de la clase base también se añaden a las rutas.

Parámetros de ruta globales

La parte global de una ruta puede definir parámetros, que serán pasados en todas las rutas generadas.
Estos parámetros se pueden recuperar a través de un miembro de datos público:

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

Accediendo a la url /foo/bar/display se muestra el contenido del miembro bar.

Ruta sin prefijo global

Si la ruta global se define en un controlador, todas las rutas generadas en este controlador irán precedidas del prefijo.
Es posible introducir explícitamente excepciones en algunas rutas, utilizando el prefijo #/.

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

El controlador define la url /noRoot, que no está prefijada con la parte /foo.

Prioridad de ruta

El parámetro prority de una ruta permite resolver esta ruta en un orden de prioridad.

Cuanto mayor sea el parámetro de prioridad, más se definirá la ruta al principio de la pila de rutas en la caché.

En el ejemplo siguiente, la ruta products/all se definirá antes que la ruta products.

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

El valor de prioridad por defecto es 0.

Caché de respuesta de rutas

Es posible almacenar en caché la respuesta producida por una ruta:

En este caso, la respuesta se almacena en caché y deja de ser dinámica.

#[Route('products/all', cache: true)]
public function all(){}

Duración de la caché

La duración se expresa en segundos, si se omite, la duración de la caché es infinita.

#[Route('products/all', cache: true, duration: 3600)]
public function all(){}

Expiración de la caché

Es posible forzar la recarga de la respuesta borrando la caché asociada.

Router::setExpired("products/all");

Cacheo dinámico de rutas

Las rutas dinámicas también pueden almacenarse en caché.

Importante

Esta posibilidad sólo es útil si esta caché no se realiza en producción, sino en el momento de inicializar la caché.

Router::get("foo", function(){
   echo 'Hello world!';
});

Router::addRoute("string", \controllers\Main::class,"index");
CacheManager::storeDynamicRoutes(false);

Comprobación de rutas con devtools :

Ubiquity info:routes
_images/info-routes.png

Gestión de errores (errores 404 y 500)

Sistema de enrutamiento por defecto

Con el sistema de enrutamiento por defecto (la pareja controlador+acción definiendo una ruta), el manejador de errores puede ser redefinido para personalizar la gestión de errores.

En el fichero de configuración app/config/config.php, añade la clave onError, asociada a un devolucion de llamada (callback) que defina los mensajes de error:

"onError"=>function ($code, $message = null,$controller=null){
   switch($code){
      case 404:
         $init=($controller==null);
         \Ubiquity\controllers\Startup::forward('IndexController/p404',$init,$init);
         break;
   }
}

Implementar la acción solicitada p404 en el IndexController:

app/controllers/IndexController.php
...

public function p404(){
   echo "<div class='ui error message'><div class='header'>404</div>The page you are looking for doesn't exist!</div>";
}

Enrutado con anotaciones

Basta en este caso con añadir una última ruta deshabilitando el sistema de enrutamiento por defecto, y correspondiente a la gestión del error 404:

app/controllers/IndexController.php
...

#[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>";
}

Controladores

Un controlador es una clase PHP que hereda de Ubiquity\controllers\Controller, proporcionando un punto de entrada en la aplicación.
Los controladores y sus métodos definen URLs accesibles.

Creación de controladores

La forma más sencilla de crear un controlador es hacerlo desde las devtools.

Desde el símbolo del sistema, vaya a la carpeta del proyecto.
Para crear el controlador Products, utilice el comando:

Ubiquity controller Products

El controlador Products.php se crea en la carpeta app/controllers del proyecto.

app/controllers/Products.php
1namespace controllers;
2/**
3 * Controller Products
4 */
5class Products extends ControllerBase{
6
7public function index(){}
8
9}

Ahora es posible acceder a URLs (por defecto se solicita el método index):

example.com/Products
example.com/Products/index

Nota

Un controlador puede crearse manualmente. En este caso, debe respetar las siguientes reglas:

  • La clase debe estar en la carpeta app/controllers.

  • El nombre de la clase debe coincidir con el nombre del archivo php

  • La clase debe heredar de ControllerBase y estar definida en el espacio de nombres controllers.

  • y debe anular el método abstracto index.

Métodos

public

El segundo segmento del URI determina a qué método público del controlador se llama.
El método «index» siempre se carga por defecto si el segundo segmento del URI está vacío.

app/controllers/First.php
1namespace controllers;
2class First extends ControllerBase{
3
4   public function hello(){
5      echo "Hello world!";
6   }
7
8}

El método hello del controlador First pone a disposición la siguiente URL:

example.com/First/hello

argumentos del método

los argumentos de un método deben pasarse en la url, excepto si son opcionales.

app/controllers/First.php
namespace controllers;
class First extends ControllerBase{

public function says($what,$who='world') {
   echo $what.' '.$who;
}

}

El método hello del controlador First pone a disposición la siguiente URL:

example.com/First/says/hello (says hello world)
example.com/First/says/Hi/everyone (says Hi everyone)

private

Los métodos privados o protegidos no son accesibles desde la URL.

Controlador por defecto

El controlador por defecto se puede establecer con el Router, en el archivo services.php.

app/config/services.php
Router::start();
Router::addRoute("_default", "controllers\First");

En este caso, el acceso a la URL example.com/ carga el controlador First y llama al método index por defecto.

carga de vistas

cargando

Las vistas se almacenan en la carpeta app/views. Se cargan desde los métodos del controlador.
Por defecto, es posible crear vistas en php, o con twig.
Twig es el motor de plantillas por defecto para archivos html.

carga de vistas php

Si no se especifica la extensión del archivo, el método loadView carga un archivo php.

app/controllers/First.php
namespace controllers;
class First extends ControllerBase{
   public function displayPHP(){
      //loads the view app/views/index.php
      $this->loadView('index');
   }
}
carga de la vista twig

Si la extensión del archivo es html, el método loadView carga un archivo html twig.

app/controllers/First.php
namespace controllers;
class First extends ControllerBase{
   public function displayTwig(){
      //loads the view app/views/index.html
      $this->loadView("index.html");
   }
}
Carga de la vista por defecto

Si se utiliza el método de nomenclatura de vistas por defecto :
La vista por defecto asociada a una acción en un controlador se encuentra en la carpeta views/nombre-controlador/nombre-acción:

views
           Users
          info.html
app/controllers/Users.php
1 namespace controllers;
2
3 class Users extends BaseController{
4   ...
5   public function info(){
6      $this->loadDefaultView();
7   }
8}

ver parámetros

Una de las misiones del controlador es pasar variables a la vista.
Esto se puede hacer en la carga de la vista, con un array asociativo:

app/controllers/First.php
class First extends ControllerBase{
   public function displayTwigWithVar($name){
      $message="hello";
      //loads the view app/views/index.html
      $this->loadView('index.html', ['recipient'=>$name, 'message'=>$message]);
   }
}

Las claves del array asociativo crean variables del mismo nombre en la vista.
Uso de estas variables en Twig:

app/views/index.html
<h1>{{message}} {{recipient}}</h1>

También se pueden pasar variables antes de cargar la vista:

//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');

ver resultado como cadena

Es posible cargar una vista, y devolver el resultado en una cadena, asignando true al 3er parámetro del método loadview :

$viewResult=$this->loadView("First/index.html",[],true);
echo $viewResult;

carga de vistas múltiples

Un controlador puede cargar varias vistas:

app/controllers/Products.php
namespace controllers;
class Products extends ControllerBase{
   public function all(){
      $this->loadView('Main/header.html', ['title'=>'Products']);
      $this->loadView('Products/index.html',['products'=>$this->products]);
      $this->loadView('Main/footer.html');
   }
}

Importante

Una vista suele ser parcial. Por lo tanto, es importante no integrar sistemáticamente las etiquetas html y body definiendo una página html completa.

organización de vistas

Es aconsejable organizar las vistas en carpetas. El método más recomendado es crear una carpeta por controlador, y almacenar allí las vistas asociadas.
Para cargar la vista index.html, almacenada en app/views/First:

$this->loadView("First/index.html");

initialize y finalize

El método initialize se llama automáticamente antes de cada acción solicitada, el método finalize después de cada acción.

Ejemplo de uso de los métodos initialize y finalize con la clase base creada automáticamente con un nuevo proyecto:

app/controllers/ControllerBase.php
namespace controllers;

use Ubiquity\controllers\Controller;
use Ubiquity\utils\http\URequest;

/**
 * ControllerBase.
 */
abstract class ControllerBase extends Controller{
   protected $headerView = "@activeTheme/main/vHeader.html";
   protected $footerView = "@activeTheme/main/vFooter.html";

   public function initialize() {
      if (! URequest::isAjax ()) {
         $this->loadView ( $this->headerView );
      }
   }

   public function finalize() {
      if (! URequest::isAjax ()) {
         $this->loadView ( $this->footerView );
      }
   }
}

Control de acceso

El control de acceso a un controlador puede realizarse manualmente, utilizando los métodos isValid y onInvalidControl.

El método isValid debe devolver un booleano que determine si es posible acceder a action pasada como parámetro:

En el siguiente ejemplo, el acceso a las acciones del controlador IndexController sólo es posible si existe una variable de sesión activeUser:

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

Si la variable activeUser no existe, se devuelve un error unauthorized 401.

El método onInvalidControl permite personalizar el acceso no autorizado:

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

   public function onInvalidControl(){
      $this->initialize();
      $this->loadView('unauthorized.html');
      $this->finalize();
   }
}
app/views/unauthorized.html
<div class="ui container">
   <div class="ui brown icon message">
      <i class="ui ban icon"></i>
      <div class="content">
         <div class="header">
            Error 401
         </div>
         <p>You are not authorized to access to <b>{{app.getController() ~ "::" ~ app.getAction()}}</b>.</p>
      </div>
   </div>
</div>

También es posible generar automáticamente el control de acceso a partir de AuthControllers

Reenvío

Una redirección no es una simple llamada a una acción de un controlador.
La redirección implica los métodos initialize y finalize, así como el control de acceso.

El método forward puede invocarse sin utilizar los métodos initialize y finalize:

Es posible redirigir a una ruta por su nombre:

Inyección de dependencia

Ver Dependency injection

namespaces

El namespace del controlador se define por defecto en controllers en el archivo app/config/config.php.

Superclase

La herencia se puede utilizar para factorizar el comportamiento del controlador.
La clase BaseController creada con un nuevo proyecto está presente para este propósito.

Clases base específicas del controlador

Clase de controlador

role

Controller

Clase base para todos los controladores

SimpleViewController

Clase base asociada a un motor de plantillas php (para usar con microservicios)

SimpleViewAsyncController

Clase base asociada a un motor de plantillas php para servidores asíncronos

Eventos

Nota

El módulo Eventos utiliza la clase estática EventsManager para gestionar los eventos.

Eventos principales del marco

Ubiquity emite eventos durante las diferentes fases del envío de una solicitud.
Estos eventos son relativamente pocos, para limitar su impacto en el rendimiento.

Parte

Nombre del evento

Parámetros

Ocurren cuando

ViewEvents

BEFORE_RENDER

viewname, parameters

Antes de renderizar una vista

ViewEvents

AFTER_RENDER

viewname, parameters

Después de renderizar una vista

DAOEvents

GET_ALL

objects, classname

Después de cargar varios objetos

DAOEvents

GET_ONE

object, classname

Después de cargar un objeto

DAOEvents

BEFORE_UPDATE

instance

Antes de actualizar un objeto

DAOEvents

AFTER_UPDATE

instance, result

Después de actualizar un objeto

DAOEvents

BEFORE_INSERT

instance

Antes de insertar un objeto

DAOEvents

AFTER_INSERT

instance, result

Después de insertar un objeto

RestEvents

BEFORE_INSERT

instance

Antes de insertar un objeto

RestEvents

BEFORE_UPDATE

instance

Antes de actualizar un objeto

Nota

No hay eventos BeforeAction y AfterAction, ya que los métodos initialize y finalize de la clase del controlador realizan esta operación.

Escuchar un evento

Example 1 :

Añadir una propiedad _updated en las instancias modificadas en la base de datos :

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

Nota

Los parámetros que se pasan a la función callback varían según el evento que se esté escuchando.

Example 2 :

Modificación de la representación de la vista

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

Crear tus propios eventos

Example :

Crear un evento para contar y almacenar el número de visualizaciones por acción :

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

Registro de eventos

Registrando el evento TracePageEventListener en services.php :

app/config/services.php
1     use Ubiquity\events\EventsManager;
2     use eventListener\TracePageEventListener;
3
4     ...
5
6     EventsManager::addListener(TracePageEventListener::EVENT_NAME, TracePageEventListener::class);

Disparo de eventos

Un evento puede ser disparado desde cualquier lugar, pero tiene más sentido hacerlo aquí en el método initialize del controlador base :

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

El resultado en app/config/stats.php :

app/config/stats.php
return array(
             "controllers\\IndexController::index"=>5,
             "controllers\\IndexController::ct"=>1,
             "controllers\\NewController::index"=>1,
             "controllers\\TestUCookieController::index"=>1
     );

Eventos registro optimización

Es preferible almacenar en caché el registro de los listeners, para optimizar su tiempo de carga :

Crear un script de cliente, o una acción de controlador (no accesible en modo de producción) :

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

Después de la ejecución, el archivo de caché se genera en app/cache/events/events.cache.php.

Una vez creada la caché, el archivo services.php sólo necesita tener la línea :

\Ubiquity\events\EventsManager::start();

Inyección de dependencia

Nota

Por razones de rendimiento, la inyección de dependencias no se utiliza en la parte central del framework.

La inyección de dependencia (DI) es un patrón de diseño utilizado para implementar IoC.
Permite la creación de objetos dependientes fuera de una clase y proporciona esos objetos a una clase a través de diferentes formas. Usando DI, movemos la creación y vinculación de los objetos dependientes fuera de la clase que depende de ella.

Nota

Ubiquity sólo soporta inyección de propiedades, para no requerir introspección en la ejecución.
Sólo los controladores soportan inyección de dependencias.

Servicio de autowiring

Creación de servicios

Crear un servicio

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

Crear un controlador que requiere el servicio

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

En el ejemplo anterior, Ubiquity busca e inyecta $service cuando se crea ClientController.

La anotación @autowired requiere que:
  • el tipo que se va a instanciar se declara con la anotación @var.

  • La propiedad $service tiene un setter, o si se declara pública

Como las anotaciones nunca se leen en tiempo de ejecución, es necesario generar la caché de los controladores:

Ubiquity init-cache -t=controllers

Queda por comprobar que el servicio se inyecta yendo a la dirección /ClientController.

Inyección de servicio

Servicio

Creemos ahora un segundo servicio, que requiere una inicialización especial.

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

Inyección en el controlador

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

Declaración Di

En app/config/config.php, crea una nueva clave para la propiedad serviceToInit para inyectar en la parte di.

"di"=>["ClientController.serviceToInit"=>function(){
                        $service=new \services\ServiceWithInit();
                        $service->init();
                        return $service;
                }
        ]

generar la caché de los controladores:

Ubiquity init-cache -t=controllers

Comprueba que el servicio está inyectado yendo a la dirección /ClientController.

Nota

Si se va a utilizar el mismo servicio en varios controladores, utilice la notación comodín :

"di"=>["*.serviceToInit"=>function(){
                        $service=new \services\ServiceWithInit();
                        $service->init();
                        return $service;
                }
        ]

Inyección con un nombre calificador

Si el nombre del servicio que se va a inyectar es diferente de la clave de la matriz di, es posible utilizar el atributo name de la anotación @injected.

En app/config/config.php, crea una nueva clave para la propiedad serviceToInit para inyectar en la parte di.

"di"=>["*.service"=>function(){
                        $service=new \services\ServiceWithInit();
                        $service->init();
                        return $service;
                }
        ]
/**
 * @injected("service")
 */
private $serviceToInit;

Inyección de servicios en tiempo de ejecución

Es posible inyectar servicios en tiempo de ejecución, sin que éstos hayan sido declarados previamente en las clases del controlador.

app/services/RuntimeService.php
1namespace services;
2
3     class RuntimeService{
4         public function __construct($ctrl){
5             echo 'Service instanciation in '.get_class($ctrl);
6         }
7     }

En app/config/config.php, crea la clave @exec en la parte di.

"di"=>["@exec"=>"rService"=>function($ctrl){
                        return new \services\RuntimeService($ctrl);
                }
        ]

Con esta declaración, el miembro $rService, instancia de RuntimeService, se inyecta en todos los controladores.
Es aconsejable utilizar los comentarios javadoc para declarar $rService en los controladores que lo utilizan (para obtener la finalización de código en $rService en su IDE).

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

Controladores CRUD

Los controladores CRUD permiten realizar operaciones básicas sobre una clase Modelo:
  • Create

  • Read

  • Update

  • Delete

Nota

Desde la versión 2.4.6, existen dos tipos de CrudController:

  • ResourceCrudController asociado a un modelo

  • MultiResourceCRUDController, mostrando un índice y permitiendo navegar entre modelos.

ResourceCrudController

Creación

En la interfaz de administración (web-tools), active la parte Controllers, y elija crear Resource Crud controller:

_images/speControllerBtn.png
A continuación, rellene el formulario:
  • Introduzca el nombre del controlador

  • Seleccione el modelo asociado

  • A continuación, haga clic en el botón de validación

_images/createCrudForm1.png

Descripción de las características

El controlador generado:

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

Pruebe el controlador creado haciendo clic en el botón get situado delante de la acción index:

_images/getBtn.png
Read (acción índice)
_images/usersControllerIndex1.png

Al hacer clic en una fila de la dataTable (instance) se muestran los objetos asociados a la instancia (acción details):

_images/usersControllerIndex1-details.png

Utilizar el área de búsqueda:

_images/usersControllerSearch1.png
Create (acción newModel)

Es posible crear una instancia pulsando el botón añadir

_images/addNewModelBtn.png

El formulario por defecto para añadir una instancia de Usuario:

_images/usersControllerNew1.png
Update (acción de actualización)

El botón de edición de cada fila permite editar una instancia

_images/editModelBtn.png

El formulario por defecto para añadir una instancia de Usuario:

_images/usersControllerEdit1.png
Delete (acción eliminar)

El botón de eliminación de cada fila permite editar una instancia

_images/deleteModelBtn.png

Visualización del mensaje de confirmación antes de la eliminación:

_images/usersControllerDelete1.png

Personalización

Cree de nuevo un ResourceCrudController desde la interfaz de administración:

_images/createCrudForm2.png

Ahora es posible personalizar el módulo utilizando overriding.

Visión general
_images/crud-schema.png
Clases de anulación
Métodos de ResourceCRUDController a sustituir

Método

Significado

Retorno por defecto

rutas

index()

Página por defecto: lista de todos los objetos

edit($modal=»no», $ids=»»)

Edita una instancia

newModel($modal=»no»)

Crea una nueva instancia

display($modal=»no»,$ids=»»)

Muestra una instancia

delete($ids)

Elimina una instancia

update()

Muestra el resultado de la actualización de una instancia

showDetail($ids)

Muestra los miembros asociados con claves externas

refresh_()

Refresca el área correspondiente a la DataTable (#lv)

refreshTable($id=null)

//TO COMMENT

Métodos de ModelViewer a sustituir

Método

Significado

Retorno por defecto

ruta index

getModelDataTable($instances, $model,$totalCount,$page=1)

Crea la dataTable y añade su comportamiento

DataTable

getDataTableInstance($instances,$model,$totalCount,$page=1)

Crea la dataTable

DataTable

recordsPerPage($model,$totalCount=0)

Devuelve el recuento de filas a mostrar (si es null no hay paginación)

null or 6

getGroupByFields()

Devuelve una matriz de miembros sobre la que realizar una agrupación

[]

getDataTableRowButtons()

Devuelve un array de botones a mostrar para cada fila [«edit»,»delete»,»display»]

[«edit»,»delete»]

onDataTableRowButton(HtmlButton $bt, ?string $name)

Para modificar los botones de fila de dataTable

getCaptions($captions, $className)

Devuelve los títulos de las cabeceras de las columnas

todos los nombres de los miembros

ruta detalle

showDetailsOnDataTableClick()

Anular para asegurarse de que se muestra o no el detalle de un objeto sobre el que se ha hecho clic

true

onDisplayFkElementListDetails($element,$member,$className,$object)

Modificar para mostrar cada elemento de un componente de lista de objetos foraneos

getFkHeaderElementDetails($member, $className, $object)

Devuelve la cabecera de un único objeto foraneo (issue from ManyToOne)

HtmlHeader

getFkElementDetails($member, $className, $object)

Devuelve un componente para mostrar un único objeto foraneo (relación manyToOne)

HtmlLabel

getFkHeaderListDetails($member, $className, $list)

Devuelve la cabecera de una lista de objetos foraneos (oneToMany o ManyToMany)

HtmlHeader

getFkListDetails($member, $className, $list)

Devuelve un componente de lista para mostrar una colección de objetos foraneos (muchos)

HtmlList

rutas edit y newModel.

getForm($identifier, $instance)

Devuelve el formulario para añadir o modificar un objeto

HtmlForm

formHasMessage()

Determina si el formulario tiene un título de mensaje

true

getFormModalTitle($instance)

Devuelve el título del modal del formulario

clase de instancia

onFormModalButtons($btOkay, $btCancel)

Hook para actualizar los botones modales

getFormTitle($form,$instance)

Devuelve una matriz asociativa que define el título del mensaje de formulario con las claves «icon»,»message»,»subMessage»

HtmlForm

setFormFieldsComponent(DataForm $form,$fieldTypes)

Establece los componentes de cada campo

onGenerateFormField($field)

Para hacer algo cuando $field se genera en el formulario

isModal($objects, $model)

Condición para determinar si el formulario de edición o adición es modal para los objetos $model

count($objects)>5

getFormCaptions($captions, $className, $instance)

Devuelve las leyendas de los campos de formulario

todos los nombres de los miembros

ruta display

getModelDataElement($instance,$model,$modal)

Devuelve un objeto DataElement para mostrar la instancia

DataElement

getElementCaptions($captions, $className, $instance)

Devuelve los subtítulos de los campos DataElement

todos los nombres de los miembros

ruta delete

onConfirmButtons(HtmlButton $confirmBtn,HtmlButton $cancelBtn)

Para anular la modificación de los botones de confirmación de borrado

Métodos CRUDDatas a sustituir

Método

Significado

Retorno por defecto

ruta index

_getInstancesFilter($model)

Añade una condición para filtrar las instancias mostradas en dataTable

1=1

getFieldNames($model)

Devuelve los campos a mostrar en la acción index para $model

todos los nombres de los miembros

getSearchFieldNames($model)

Devuelve los campos que se utilizarán en las consultas de búsqueda

todos los nombres de los miembros

rutas edit y newModel.

getFormFieldNames($model,$instance)

Devuelve los campos a actualizar en las acciones edit y newModel para $model

todos los nombres de los miembros

getManyToOneDatas($fkClass,$instance,$member)

Devuelve una lista (filtrada) de objetos $fkClass para mostrar en una lista html

todas las instancias $fkClass

getOneToManyDatas($fkClass,$instance,$member)

Devuelve una lista (filtrada) de objetos $fkClass para mostrar en una lista html

todas las instancias $fkClass

getManyToManyDatas($fkClass,$instance,$member)

Devuelve una lista (filtrada) de objetos $fkClass para mostrar en una lista html

todas las instancias $fkClass

ruta display

getElementFieldNames($model)

Devuelve los campos a mostrar en la acción display para $model

todos los nombres de los miembros

Métodos CRUDEvents a sustituir

Método

Significado

Retorno por defecto

ruta index

onConfDeleteMessage(CRUDMessage $message,$instance)

Devuelve el mensaje de confirmación que aparece antes de eliminar una instancia

CRUDMessage

onSuccessDeleteMessage(CRUDMessage $message,$instance)

Devuelve el mensaje mostrado tras un borrado

CRUDMessage

onErrorDeleteMessage(CRUDMessage $message,$instance)

Devuelve el mensaje mostrado cuando se ha producido un error al borrar

CRUDMessage

rutas edit y newModel.

onSuccessUpdateMessage(CRUDMessage $message)

Devuelve el mensaje que aparece cuando se añade o inserta una instancia

CRUDMessage

onErrorUpdateMessage(CRUDMessage $message)

Devuelve el mensaje que aparece cuando se produce un error al actualizar o insertar

CRUDMessage

onNewInstance(object $instance)

Se activa tras la creación de una nueva instancia

onBeforeUpdate(object $instance, bool $isNew)

Se activa antes de la actualización de la instancia

todas las rutas

onNotFoundMessage(CRUDMessage $message,$ids)

Devuelve el mensaje que aparece cuando una instancia no existe

onDisplayElements($dataTable,$objects,$refresh)

Se activa después de mostrar objetos en dataTable

Métodos de CRUDFiles que deben sustituirse

Método

Significado

Retorno por defecto

archivos de plantilla

getViewBaseTemplate()

Devuelve la plantilla base para todas las acciones Crud si getBaseTemplate devuelve un nombre de archivo de plantilla base

@framework/crud/baseTemplate.html

getViewIndex()

Devuelve la plantilla de la ruta index.

@framework/crud/index.html

getViewForm()

Devuelve la plantilla de las rutas edit y newInstance.

@framework/crud/form.html

getViewDisplay()

Devuelve la plantilla de la ruta display.

@framework/crud/display.html

Urls

getRouteRefresh()

Devuelve la ruta para refrescar la ruta índice

/refresh_

getRouteDetails()

Devuelve la ruta para la ruta de detalle, cuando el usuario hace clic en una fila dataTable

/showDetail

getRouteDelete()

Devuelve la ruta para eliminar una instancia

/delete

getRouteEdit()

Returns the route for editing an instance

/edit

getRouteDisplay()

Devuelve la ruta para mostrar una instancia

/display

getRouteRefreshTable()

Devuelve la ruta para refrescar la dataTable

/refreshTable

getDetailClickURL($model)

Devuelve la ruta asociada a una instancia de clave externa en la lista

«»

Estructura de plantillas Twig
index.html
_images/template_index.png
form.html

Se muestra en el bloque frm

_images/template_form.png
display.html

Se muestra en el bloque frm

_images/template_display.png

MultiResourceCrudController

Nota

El MultiResourceCRUDController muestra un índice que permite navegar entre los CRUDs de los modelos.

Creación

En la interfaz de administración (web-tools), active la parte Controllers, y elija crear Index Crud controller:

_images/speControllerBtn.png
A continuación, rellene el formulario:
  • Introduzca el nombre del controlador

  • La ruta de acceso (que debe contener la parte variable {resource})

  • A continuación, haga clic en el botón de validación

_images/createIndexCrudForm1.png

Descripción de las características

El controlador generado:

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

Prueba el controlador creado en la url /home/crud:

_images/indexCrudController.png

Personalización

Crea de nuevo un MultiResourceCrudController desde la interfaz de administración:

_images/createIndexCrudForm2.png

Ahora es posible personalizar el módulo utilizando overriding como el ResourceCRUDControllers.

Clases específicas para anular
Métodos de MultiResourceCRUDController a sustituir

Método

Significado

Retorno por defecto

rutas

home ()

Página de inicio : lista de todos los modelos

Todas las rutas de CRUDController

Eventos

onRenderView(array &$data)

Antes de la presentación de la página de inicio

Configuración

hasNavegación()

Devuelve True para mostrar el menú desplegable de navegación

True

getIndexModels()

Devuelve la lista de modelos disponibles para mostrar

modelos de la base de datos por defecto

getIndexModelsDetails()

Devuelve una matriz asociativa (title, icon url) para cada modelo

[]

getIndexDefaultIcon(string $resource)

Devuelve el icono de un modelo

Un animal al azar

getIndexDefaultTitle(string $resource)

Devuelve el título de un modelo

El nombre del recurso

getIndexDefaultDesc(string $modelClass)

Devuelve la descripción de un modelo

El nombre completo de la clase

getIndexDefaultUrl(string $resource)

Devuelve la url asociada a un modelo

La ruta

getIndexDefaultMeta(string $modelClass)

Devuelve la meta de un modelo

getIndexType()

Define las clases css del componente index

tarjetas

getModelName()

Devuelve el nombre completo del modelo para $this->resource

Del modelo por defecto NS

Métodos de CRUDFiles que deben sustituirse

Método

Significado

Retorno por defecto

archivos de plantilla

getViewHome()

Devuelve la plantilla base para la vista de inicio

@framework/crud/home.html

getViewItemHome()

Devuelve la plantilla de un elemento de la ruta de inicio

@framework/crud/itemHome.html

getViewNav()

Devuelve la plantilla para mostrar modelos en un desplegable

@framework/crud/nav.html

Nota

Todos los demás métodos de las clases CRUDController, CRUDFiles, CRUDEvents y CRUDDatas pueden ser sobreescritos como en el caso de ResourceCRUDController.

Auth Controllers

The Auth controllers allow you to perform basic authentification with:
  • login with an account

  • account creation

  • logout

  • controllers with required authentication

Creation

In the admin interface (web-tools), activate the Controllers part, and choose create Auth controller:

_images/speControllerBtn.png
Then fill in the form:
  • Enter the controller name (BaseAuthController in this case)

_images/createAuthForm1.png

The generated controller:

app/controllers/BaseAuthController.php
 1 /**
 2 * 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}

Implementation of the authentification

Example of implementation with the administration interface : We will add an authentication check on the admin interface.

Authentication is based on verification of the email/password pair of a model User:

_images/model-user.png

BaseAuthController modification

app/controllers/BaseAuthController.php
 1 /**
 2 * 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}

Admin controller modification

Modify the Admin Controller to use BaseAuthController:

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

Test the administration interface at /admin:

_images/adminForbidden.png

After clicking on login:

_images/formLogin.png

If the authentication data entered is invalid:

_images/invalidCreditentials.png

If the authentication data entered is valid:

_images/adminWithAuth.png

Attaching the zone info-user

Modify the BaseAuthController controller:

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

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

_images/infoUserZone.png

It can be displayed in any twig template:

{{ _userInfo | raw }}

Description of the features

Customizing templates

index.html template

The index.html template manages the connection:

_images/template_authIndex.png

Example with the _userInfo area:

Create a new AuthController named PersoAuthController:

_images/createAuthForm2.png

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

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

Change the AuthController Admin controller:

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

Customizing messages

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

Self-check connection

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

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

Account recovery

account recovery is used to reset the account password.
A password reset email is sent, to an email address corresponding to an active account.

_images/recoveryInit.png
app/controllers/PersoAuthController.php
 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}
_images/recoveryForm.png

Nota

By default, the link can only be used on the same machine, within a predetermined period of time (which can be modified by overriding the accountRecoveryDuration method).

Activation of MFA/2FA

Multi-factor authentication can be enabled conditionally, based on the pre-logged-in user’s information.

Nota

Phase 2 of the authentication is done in the example below by sending a random code by email. The AuthMailerClass class is available in the Ubiquity-mailer package.

app/controllers/PersoAuthController.php
 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}
_images/2fa-code.png

Nota

It is possible to customize the creation of the generated code, as well as the prefix used. The sample below is implemented with robthree/twofactorauth library.

protected function generate2FACode():string{
        $tfa=new TwoFactorAuth();
        return $tfa->createSecret();
}

protected function towFACodePrefix():string{
        return 'U-';
}

Account creation

The activation of the account creation is also optional:

_images/account-creation-available.png
app/controllers/PersoAuthController.php
1class PersoAuthController extends \controllers\BaseAuth{
2...
3 protected function hasAccountCreation():bool{
4     return true;
5 }
6...
7}
_images/account-creation.png

In this case, the _create method must be overridden in order to create the account:

protected function _create(string $login, string $password): ?bool {
        if(!DAO::exists(User::class,'login= ?',[$login])){
                $user=new User();
                $user->setLogin($login);
                $user->setPassword($password);
                URequest::setValuesToObject($user);//for the others params in the POST.
                return DAO::insert($user);
        }
        return false;
}

You can check the validity/availability of the login before validating the account creation form:

protected function newAccountCreationRule(string $accountName): ?bool {
        return !DAO::exists(User::class,'login= ?',[$accountName]);
}
_images/account-creation-error.png

A confirmation action (email verification) may be requested from the user:

protected function hasEmailValidation(): bool {
        return true;
}

protected function _sendEmailValidation(string $email,string $validationURL,string $expire):void {
        MailerManager::start();
        $mail=new AuthEmailValidationMail();
        $mail->to($connected->getEmail());
        $mail->setUrl($validationURL);
        $mail->setExpire($expire);
        MailerManager::send($mail);
}

Nota

It is possible to customize these parts by overriding the associated methods, or by modifying the interfaces in the concerned templates.

Base de datos

La clase DAO es responsable de las operaciones de carga y persistencia de los modelos :

Conexión a la base de datos

Compruebe que los parámetros de conexión a la base de datos se han introducido correctamente en el archivo de configuración:

Ubiquity config -f=database
_images/db-config.png

Conexión transparente

Desde Ubiquity 2.3.0, La conexión a la base de datos se realiza automáticamente la primera vez que se solicita:

use Ubiquity\orm\DAO;

$firstUser=DAO::getById(User::class,1);//Automatically start the database

Este es el caso de todos los métodos de la clase DAO utilizados para realizar operaciones CRUD.

Conexión explícita

En algunos casos, sin embargo, puede ser útil establecer una conexión explícita con la base de datos, especialmente para comprobar la conexión.

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

Conexiones múltiples

Añadir una nueva conexión

Ubiquity permite gestionar varias conexiones a bases de datos.

Con Webtools

En la parte Models, seleccione el botón Add new connection:

_images/add-new-co-btn.png

Define los parámetros de configuración de la conexión:

_images/new-co.png

Generar modelos para la nueva conexión:
Los modelos generados incluyen la anotación @database o el atributo Database mencionando su enlace a la conexión.

<?php
namespace models\tests;
use Ubiquity\attributes\items\Database;
use Ubiquity\attributes\items\Table;

#[Database('tests')]
#[Table('groupe')]
class Groupe{
    ...
}

Los modelos se generan en una subcarpeta de models.

Con varias conexiones, no olvide añadir la siguiente línea al archivo services.php:

\Ubiquity\orm\DAO::start();

El método start realiza la correspondencia entre cada modelo y su conexión asociada.

Generación de modelos

A partir de la base de datos existente

Desde cero

ORM

Nota

si desea generar automáticamente los modelos, consulte la parte generating models .

Una clase modelo es simplemente un objeto php sin herencia. |br|Los modelos se encuentran por defecto en la carpeta app\models. |br|El mapeo relacional de objetos (ORM) se basa en anotaciones de miembros o atributos (desde PHP8) en la clase modelo.

Definición de modelos

Un modelo básico

  • Un modelo debe definir su clave primaria utilizando la anotación @id en los miembros correspondientes

  • Los miembros serializados deben tener getters y setters

  • Sin ninguna otra anotación, una clase corresponde a una tabla con el mismo nombre en la base de datos, cada miembro corresponde a un campo de esta tabla

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

Mapeo

Table->Class

Si el nombre de la tabla es diferente del nombre de la clase, la anotación @table permite especificar el nombre de la tabla.

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

Si el nombre de un campo es diferente del nombre de un miembro de la clase, la anotación @column permite especificar un nombre de campo diferente.

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

Asociaciones

Nota

Convención de nombres
Los nombres de los campos de clave foránea consisten en el nombre de la clave primaria de la tabla referenciada seguido del nombre de la tabla referenciada cuya primera letra es mayúscula. |br|Ejemplo: idUsuario para la tabla usuario cuya clave primaria es id.

ManyToOne

Un usuario pertenece a una organización:

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

La anotación @joinColumn o el atributo JoinColumn especifica que:

  • El miembro $organization es una instancia de modelsOrganization.

  • La tabla user tiene una clave externa idOrganization que hace referencia a la clave primaria de la organización

  • Esta clave externa no es null => un usuario siempre tendrá una organización

OneToMany

Una organización tiene muchos usuarios:

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

En este caso, la asociación es bidireccional. |br|La anotación @oneToMany sólo debe especificar:

  • La clase de cada usuario en la matriz de usuarios : modelsUser

  • el valor de @mappedBy es el nombre del atributo association-mapping en el lado propietario: $organization en la clase User.

ManyToMany
  • Un usuario puede pertenecer a grupos.

  • Un grupo está formado por varios usuarios.

_images/manyToMany.png
app/models/User.php
 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}
app/models/Group.php
 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}

Si no se respetan las convenciones de nomenclatura para las claves externas, es posible especificar los campos relacionados.

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

Anotaciones ORM

Anotaciones para las clases

@annotation

rol

propiedades

rol

@database

Define el desplazamiento de la base de datos asociada (definido en el archivo de configuración)

@table

Define el nombre de la tabla asociada.

Anotaciones para los miembros

@annotation

rol

propiedades

rol

@id

Define la(s) clave(s) primaria(s).

@column

Especifique las características del campo asociado.

name

Nombre del campo asociado

nullable

true si el valor puede ser null

dbType

Tipo de campo en la base de datos

@transient

Especifica que el campo no es persistente.

Asociaciones

@annotation (extends)

rol

propiedades [opcional]

rol

@manyToOne

Define una asociación de un solo valor a otra clase de entidad.

@joinColumn (@column)

Indica la clave externa en la asociación manyToOne.

className

Clase del miembro

[referencedColumnName]

Nombre de la columna asociada

@oneToMany

Define una asociación multivaluada (multi-valued) a otra clase de entidad.

className

Clase de los objetos en miembro

[mappedBy]

Nombre del atributo de asignación de asociaciones (association-mapping) en el lado propietario

@manyToMany

Define una asociación multivaluada (many-valued) con multiplicidad de muchos a muchos (many-to-many).

targetEntity

Clase de los objetos en miembro

[inversedBy]

Nombre del miembro de la asociación (association-member) en el lado inverso (inverse-side)

[mappedBy]

Nombre de la asociación miembro ( association-member) de la parte propietaria

@joinTable

Define la tabla de asociación para la multiplicidad muchos a muchos (many-to-many)

name

Nombre de la tabla de asociación

[joinColumns]

@column => nombre y referencedColumnName para este lado

[inverseJoinColumns]

@column => nombre y referencedColumnName para el otro lado

DAO

La clase DAO es responsable de las operaciones de carga y persistencia de los modelos :

Conexión a la base de datos

Compruebe que los parámetros de conexión a la base de datos se han introducido correctamente en el archivo de configuración:

Ubiquity config -f=database

Desde la versión 2.3.0

El inicio de la base de datos con DAO::startDatabase($config) en el archivo services.php es inútil, no es necesario iniciar la base de datos, la conexión se realiza automáticamente en la primera petición. Usa DAO::start() en el archivo app/config/services.php cuando uses varias bases de datos (con la característica multi db)

Carga de datos

Cargar una instancia

Cargando una instancia de la clase models\User con id 5.

use Ubiquity\orm\DAO;
use models\User;

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

Cargar una instancia utilizando una condición:

use Ubiquity\orm\DAO;
use models\User;

DAO::getOne(User::class, 'name= ?',false,['DOE']);
Carga de BelongsTo

Por defecto, los miembros definidos por una relación belongsTo se cargan automáticamente

Cada usuario pertenece a una sola categoría:

$user=DAO::getById(User::class,5);
echo $user->getCategory()->getName();

Es posible evitar esta carga por defecto; el tercer parámetro permite cargar o no los miembros belongsTo:

$user=DAO::getOne(User::class,5, false);
echo $user->getCategory();// NULL
Carga de HasMany

La carga de miembros hasMany debe ser siempre explícita; el tercer parámetro permite la carga explícita de miembros.

Cada usuario tiene muchos grupos:

$user=DAO::getOne(User::class,5,['groupes']);
foreach($user->getGroupes() as $groupe){
    echo $groupe->getName().'<br>';
}
Clave primaria compuesta

O bien el modelo ProductDetail correspondiente a un producto pedido en una orden y cuya clave primaria es compuesta:

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

El segundo parámetro $keyValues puede ser un array si la clave primaria es compuesta:

$productDetail=DAO::getOne(ProductDetail::class,[18,'BF327']);
echo 'Command:'.$productDetail->getCommande().'<br>';
echo 'Product:'.$productDetail->getProduct().'<br>';

Carga multiple de objetos

Carga de instancias de la clase User:

$users=DAO::getAll(User::class);
foreach($users as $user){
    echo $user->getName()."<br>";
}

Consulta mediante condiciones

Consultas sencillas

El parámetro condition equivale a la parte WHERE de una sentencia SQL:

$users=DAO::getAll(User::class,'firstName like "bren%" and not suspended',false);

Para evitar inyecciones SQL y beneficiarse de la preparación de sentencias, es preferible realizar una consulta parametrizada:

$users=DAO::getAll(User::class,'firstName like ? and suspended= ?',false,['bren%',false]);
UQueries

El uso de U-queries permite establecer condiciones sobre los miembros asociados:

Selección de usuarios cuya organización tiene el dominio lecnam.net:

$users=DAO::uGetAll(User::class,'organization.domain= ?',false,['lecnam.net']);

Es posible ver la solicitud generada en los registros (si el registro está activado):

_images/uquery-users-log.png

El resultado puede verificarse seleccionando a todos los usuarios de esta organización:

$organization=DAO::getOne(Organization::class,'domain= ?',['users'],['lecnam.net']);
$users=$organization->getUsers();

Los registros correspondientes:

_images/uquery-users-orga-log.png

Contando

Pruebas de existencia
if(DAO::exists(User::class,'lastname like ?',['SMITH'])){
    //there's a Mr SMITH
}
Contando

Contar las instancias, lo que no hay que hacer, si los usuarios no están ya cargados:

$users=DAO::getAll(User::class);
echo "there are ". \count($users) ." users";

Lo que hay que hacer:

$count=DAO::count(User::class);
echo "there are $count users";

Con una condición:

$notSuspendedCount=DAO::count(User::class, 'suspended = ?', [false]);

con una condición sobre los objetos asociados:

Número de usuarios pertenecientes a la organización denominada OTAN.

$count=DAO::uCount(User::class,'organization.name= ?',['OTAN']);

Modificación de datos

Añadir una instancia

Añadir una organización:

$orga=new Organization();
$orga->setName('Foo');
$orga->setDomain('foo.net');
if(DAO::save($orga)){
  echo $orga.' added in database';
}

Añadir una instancia de Usuario, en una organización:

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

Actualización de una instancia

En primer lugar, debe cargarse la instancia:

$orga=DAO::getOne(Organization::class,'domain= ?',false,['foo.net']);
$orga->setAliases('foo.org');
if(DAO::save($orga)){
  echo $orga.' updated in database';
}

Borrar una instancia

Si la instancia se carga desde la base de datos:

$orga=DAO::getById(Organization::class,5,false);
if(DAO::remove($orga)){
  echo $orga.' deleted from database';
}

Si la instancia no está cargada, es más apropiado utilizar el método delete:

if(DAO::delete(Organization::class,5)){
  echo 'Organization deleted from database';
}

Eliminar varias instancias

Eliminar varias instancias sin carga previa:

if($res=DAO::deleteAll(models\User::class, 'id in (?,?,?)',[1,2,3])){
    echo "$res elements deleted";
}

Consultas masivas

Las consultas masivas permiten realizar varias operaciones (inserción, modificación o supresión) en una sola consulta, lo que contribuye a mejorar el rendimiento.

Inserciones masivas

Ejemplo de inserciones:

$u = new User();
$u->setName('Martin1');
DAO::toInsert($u);
$u = new User();
$u->setName('Martin2');
DAO::toInsert($u);
//Perform inserts
DAO::flushInserts();

Actualizaciones masivas

Ejemplo de actualizaciones:

$users = DAO::getAll(User::class, 'name like ?', false, [
   'Martin%'
]);
foreach ($users as $user) {
   $user->setName(\strtoupper($user->getName()));
   DAO::toUpdate($user);
}
DAO::flushUpdates();

Borrado masivo

Ejemplo de eliminación

$users = DAO::getAll(User::class, 'name like ?', false, [
     'BULK%'
]);
DAO::toDeletes($users);
DAO::flushDeletes();

El método DAO::flush() puede ser llamado si hay inserciones, actualizaciones o borrados pendientes.

Transacciones

Transacciones explícitas

Todas las operaciones DAO se pueden insertar en una transacción, de forma que se pueda atomizar una serie de cambios:

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 caso de múltiples bases de datos definidas en la configuración, los métodos relacionados con transacciones pueden tomar el offset de base de datos definido en parámetro.

DAO::beginTransaction('db-messagerie');
//some DAO operations on messagerie models
DAO::commit('db-messagerie');

Transacciones implícitas

Algunos métodos DAO utilizan implícitamente transacciones para agrupar operaciones de inserción, actualización o eliminación.

$users=DAO::getAll(User::class);
foreach ($users as $user){
    $user->setSuspended(true);
    DAO::toUpdate($user);
}
DAO::updateGroups();//Perform updates in a transaction

Clase SDAO

La clase SDAO acelera las operaciones CRUD para las clases de negocio sin relaciones.

En este caso, los modelos deben declarar únicamente los miembros públicos y no respetar la encapsulación habitual.

app/models/Product.php
 1 namespace models;
 2 class Product{
 3   /**
 4    * @id
 5    */
 6   public $id;
 7
 8   public $name;
 9
10   ...
11 }

La clase SDAO hereda de DAO y tiene los mismos métodos para realizar operaciones CRUD.

use Ubiquity\orm\DAO;

$product=DAO::getById(Product::class, 5);

Consultas DAO preparadas

La preparación de ciertas peticiones puede mejorar el rendimiento con los servidores Swoole, Workerman o Roadrunner.
Esta preparación inicializa los objetos que luego se utilizarán para ejecutar la consulta.
Esta inicialización se realiza al inicio del servidor, o al inicio de cada worker, si existe tal evento.

Ejemplo swoole

Preparación
app/config/swooleServices.php
$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 ?');
});
Utilización
app/controllers/UsersController.php
public function displayUser($idUser){
   $user=DAO::executePrepared('user',[1]);
   echo $user->getName();
}

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

Peticiones (Request)

Nota

Para todas las funciones Http, Ubiquity utiliza clases técnicas que contienen métodos estáticos. Se trata de una elección de diseño para evitar la inyección de dependencias que degradaría el rendimiento.

La clase URequest proporciona funcionalidad adicional para manipular más fácilmente las matrices nativas $_POST y $_GET de php.

Recuperación de datos

Del método get

El método get devuelve el valor null si la clave name no existe en las variables get.

use Ubiquity\utils\http\URequest;

$name=URequest::get("name");

El método get puede ser llamado con el segundo parámetro opcional devolviendo un valor si la clave no existe en las variables get.

$name=URequest::get("name",1);

Del método post

El método post devuelve el valor null si la clave nombre no existe en las variables post.

use Ubiquity\utils\http\URequest;

$name=URequest::post("name");

El método post puede ser llamado con el segundo parámetro opcional devolviendo un valor si la clave no existe en las variables post.

$name=URequest::post("name",1);

El método getPost aplica un callback a los elementos del array $_POST y los devuelve (callback por defecto : htmlEntities) :

$protectedValues=URequest::getPost();

Recuperación y asignación de datos múltiples

Es habitual asignar los valores de un array asociativo a los miembros de un objeto. |br|Este es el caso, por ejemplo, de la validación de un formulario de modificación de objetos.

El método setValuesToObject realiza esta operación :

Consideremos una clase 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;
     }
}

Consideremos un formulario para modificar un usuario:

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

La acción update del controlador Users debe actualizar la instancia de usuario a partir de valores POST.
Utilizando el método setPostValuesToObject se evita la asignación de variables posteadas una a una a los miembros del objeto.
También es posible utilizar setGetValuesToObject para el método get, o setValuesToObject para asignar los valores de cualquier array asociativo a un objeto.

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

Nota

Los métodos SetValuesToObject utilizan setters para modificar los miembros de un objeto. Por lo tanto, la clase en cuestión debe implementar setters para todos los miembros modificables.

Probar la solicitud

isPost

El método isPost devuelve true si la petición fue enviada a través del método POST: |br|En el caso de abajo, el método initialize sólo carga la vista vHeader.html si la petición no es una petición Ajax.

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

El método isAjax devuelve true si la consulta es una consulta Ajax:

app/controllers/Users.php
1 ...
2     public function initialize(){
3             if(!URequest::isAjax()){
4                     $this->loadView("main/vHeader.html");
5             }
6     }
7     ...

isCrossSite

El método isCrossSite verifica que la consulta no es cross-site.

Respuesta (Response)

Nota

Para todas las funciones Http, Ubiquity utiliza clases técnicas que contienen métodos estáticos. Se trata de una elección de diseño para evitar la inyección de dependencias que degradaría el rendimiento.

La clase UResponse maneja sólo las cabeceras, no el cuerpo de la respuesta, que convencionalmente es proporcionado por el contenido mostrado por las llamadas utilizadas para dar salida a los datos (echo, print …).

La clase UResponse proporciona funcionalidad adicional para manipular más fácilmente las cabeceras de respuesta.

Añadir o modificar cabeceras

use Ubiquity\utils\http\UResponse;
$animal='camel';
UResponse::header('Animal',$animal);

Forzar cabecera múltiple del mismo tipo:

UResponse::header('Animal','monkey',false);

Fuerza el código de respuesta HTTP al valor especificado:

UResponse::header('Messages',$message,false,500);

Definición de cabeceras específicas

content-type

Establecer el tipo de contenido de la respuesta a application/json:

UResponse::asJSON();

Establecer el tipo de contenido de la respuesta a text/html:

UResponse::asHtml();

Establecer el tipo de contenido de la respuesta a plain/text:

UResponse::asText();

Establecer el tipo de contenido de la respuesta a application/xml:

UResponse::asXml();

Definición de una codificación específica (el valor por defecto es siempre utf-8):

UResponse::asHtml('iso-8859-1');

Cache

Forzar la desactivación de la caché del navegador:

UResponse::noCache();

Accept

Define qué tipos de contenido, expresados como tipos MIME, puede entender el cliente.
Ver Aceptar valores por defecto

UResponse::setAccept('text/html');

Cabeceras de respuesta CORS

Cross-Origin Resource Sharing (CORS) es un mecanismo que utiliza cabeceras HTTP adicionales para indicar a un navegador que permita a su aplicación web que se ejecuta en un origen (dominio) tener permiso para acceder a recursos seleccionados de un servidor en un origen diferente.

Access-Control-Allow-Origin

Ajuste de origen permitido:

UResponse::setAccessControlOrigin('http://myDomain/');

Access-Control-Allow-methods

Definición de métodos permitidos:

UResponse::setAccessControlMethods('GET, POST, PUT, DELETE, PATCH, OPTIONS');

Access-Control-Allow-headers

Definición de las cabeceras permitidas:

UResponse::setAccessControlHeaders('X-Requested-With, Content-Type, Accept, Origin, Authorization');

Activación global de CORS

habilitar CORS para un dominio con valores por defecto:

  • métodos permitidos: GET, POST, PUT, DELETE, PATCH, OPTIONS

  • cabeceras permitidas: X-Requested-With, Content-Type, Accept, Origin, Authorization

UResponse::enableCors('http://myDomain/');

Comprobación de las cabeceras de respuesta

Comprobación de si se han enviado las cabeceras:

if(!UResponse::isSent()){
     //do something if headers are not send
}

Comprobando si el tipo de contenido de la respuesta es application/json:

Importante

Este método sólo funciona si ha utilizado la clase UResponse para establecer las cabeceras.

if(UResponse::isJSON()){
     //do something if response is a JSON response
}

Sesión (Session)

Nota

Para todas las funciones Http, Ubiquity utiliza clases técnicas que contienen métodos estáticos. Se trata de una elección de diseño para evitar la inyección de dependencias que degradaría el rendimiento.

La clase USession proporciona funcionalidad adicional para manipular más fácilmente el array nativo $_SESSION php.

Inicio de sesión

La sesión Http se inicia automáticamente si se rellena la clave sessionName en el fichero de configuración app/config.php:

<?php
return array(
             ...
             "sessionName"=>"key-for-app",
             ...
 );

Si la clave sessionName no está rellenada, es necesario iniciar la sesión explícitamente para utilizarla:

use Ubiquity\utils\http\USession;
...
USession::start("key-for-app");

Nota

El parámetro nombre es opcional pero se recomienda para evitar variables conflictivas.

Crear o editar una variable de sesión

use Ubiquity\utils\http\USession;

USession::set("name","SMITH");
USession::set("activeUser",$user);

Recuperación de datos

El método get devuelve el valor null si la clave name no existe en las variables de sesión.

use Ubiquity\utils\http\USession;

$name=USession::get("name");

El método get puede ser llamado con el segundo parámetro opcional devolviendo un valor si la clave no existe en las variables de sesión.

$name=USession::get("page",1);

Nota

El método session es un alias del método get.

El método getAll devuelve todas las variables de sesión:

$sessionVars=USession::getAll();

Pruebas

El método exists comprueba la existencia de una variable en sesión.

if(USession::exists("name")){
     //do something when name key exists in session
}

El método isStarted comprueba el inicio de sesión

if(USession::isStarted()){
     //do something if the session is started
}

Borrar variables

El método delete elimina una variable de sesión:

USession::delete("name");

Cierre explícito de la sesión

El método terminar cierra la sesión correctamente y borra todas las variables de sesión creadas:

USession::terminate();

Views

Ubiquity uses Twig as the default template engine (see Twig documentation).
The views are located in the app/views folder. They must have the .html extension for being interpreted by Twig.

Ubiquity can also be used with a PHP view system, to get better performance, or simply to allow the use of php in the views.

Loading

Views are loaded from controllers:

app/controllers/Users.php
1 namespace controllers;
2
3 class Users extends BaseController{
4     ...
5     public function index(){
6                     $this->loadView("index.html");
7             }
8     }
9 }

Default view loading

If you use the default view naming method :
The default view associated to an action in a controller is located in views/controller-name/action-name folder:

views
           Users
          info.html
app/controllers/Users.php
1 namespace controllers;
2
3 class Users extends BaseController{
4     ...
5     public function info(){
6                     $this->loadDefaultView();
7             }
8     }
9 }

Loading and passing variables

Variables are passed to the view with an associative array. Each key creates a variable of the same name in the view.

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

In this case, it is usefull to call Compact for creating an array containing variables and their values :

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

Displaying in view

The view can then display the variables:

users/display.html
 <h2>{{type}}</h2>
 <div>{{message}}</div>

Variables may have attributes or elements you can access, too.

You can use a dot (.) to access attributes of a variable (methods or properties of a PHP object, or items of a PHP array), or the so-called «subscript» syntax ([]):

{{ foo.bar }}
{{ foo['bar'] }}

Ubiquity extra functions

Global app variable provides access to predefined Ubiquity Twig features:

  • app is an instance of Framework and provides access to public methods of this class.

Get framework installed version:

{{ app.version() }}

Return the active controller and action names:

{{ app.getController() }}
{{ app.getAction() }}

Return global wrapper classes :

For request:

{{ app.getRequest().isAjax() }}

For session :

{{ app.getSession().get('homePage','index') }}

see Framework class in API for more.

PHP view loading

Disable if necessary Twig in the configuration file by deleting the templateEngine key.

Then create a controller that inherits from SimpleViewController, or SimpleViewAsyncController if you use Swoole or Workerman:

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

Nota

In this case, the functions for loading assets and themes are not supported.

Assets

Assets correspond to javascript files, style sheets, fonts, images to include in your application.
They are located from the public/assets folder.
It is preferable to separate resources into sub-folders by type.

public/assets
      css
         style.css
         semantic.min.css
      js
          jquery.min.js

Integration of css or js files :

{{ css('css/style.css') }}
{{ css('css/semantic.min.css') }}

{{ js('js/jquery.min.js') }}
{{ css('https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css') }}

{{ js('https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.js') }}

CDN with extra parameters:

{{ css('https://cdn.jsdelivr.net/npm/foundation-sites@6.5.3/dist/css/foundation.min.css',{crossorigin: 'anonymous',integrity: 'sha256-/PFxCnsMh+...'}) }}

Themes

Nota

The themes are totally useless if you only have one presentation to apply.

Ubiquity support themes wich can have it’s own assets and views according to theme template to be rendered by controller. Each controller action can render a specific theme, or they can use the default theme configured at config.php file in templateEngineOptions => array("activeTheme" => "semantic").

Ubiquity is shipped with 3 default themes : Bootstrap, Foundation and Semantic-UI.

Installing a theme

With devtools, run :

Ubiquity install-theme bootstrap

The installed theme is one of bootstrap, foundation or semantic.

With webtools, you can do the same, provided that the devtools are installed and accessible (Ubiquity folder added in the system path) :

_images/themesManager-install-theme.png

Creating a new theme

With devtools, run :

Ubiquity create-theme myTheme

Creating a new theme from Bootstrap, Semantic…

With devtools, run :

Ubiquity create-theme myBootstrap -x=bootstrap

With webtools :

_images/themesManager-create-theme.png

Theme functioning and structure

Structure

Theme view folder

The views of a theme are located from the app/views/themes/theme-name folder

app/views
         themes
                bootstrap
                         main
                              vHeader.html
                              vFooter.html
                semantic
                         main
                              vHeader.html
                              vFooter.html

The controller base class is responsible for loading views to define the header and footer of each page :

app/controllers/ControllerBase.php
 1     <?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     }

Theme assets folder

The assets of a theme are created inside public/assets/theme-name folder.

The structure of the assets folder is often as follows :

public/assets/bootstrap
                                 css
                                    style.css
                                    all.min.css
                                 scss
                                    myVariables.scss
                                    app.scss
                                 webfonts
                                                                 img

Change of the active theme

Persistent change

activeTheme is defined in app/config/config.php with templateEngineOptions => array("activeTheme" => "semantic")

The active theme can be changed with devtools :

Ubiquity config:set --templateEngineOptions.activeTheme=bootstrap

It can also be done from the home page, or with webtools :

From the home page :

_images/change-theme-home.png

From the webtools :

_images/change-theme-webtools.png

This change can also be made at runtime :

From a controller :

ThemeManager::saveActiveTheme('bootstrap');

Non-persistent local change

To set a specific theme for all actions within a controller, the simplest method is to override the controller’s initialize method :

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

Or if the change should only concern one action :

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

Conditional theme change, regardless of the controller :

Example with a modification of the theme according to a variable passed in the URL

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

Mobile device support

Add a mobile device detection tool.
Installing MobileDetect:

composer require mobiledetect/mobiledetectlib

It is generally easier to create different views per device.

Create a specific theme for the mobile part (by creating a folder views/themes/mobile and putting the views specific to mobile devices in it).
It is important in this case to use the same file names for the mobile and non-mobile part.

It is also advisable in this case that all view loadings use the @activeTheme namespace:

$this->loadView("@activeTheme/index.html");

index.html must be available in this case in the folders views and views/themes/mobile.

Global mobile detection (from services.php)
app/config/services.php
 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});
Locale detection (from a controller)
app/controllers/FooController.php
 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     }

View and assets loading

Views

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

app/controllers/Users.php
1 namespace controllers;
2
3 class Users extends BaseController{
4
5         public function action(){
6             $this->loadView('@activeTheme/action.html');
7             ...
8         }
9     }

If the activeTheme is bootstrap, the loaded view is app/views/themes/bootstrap/action.html.

DefaultView

If you follow the Ubiquity view naming model, the default view loaded for an action in a controller when a theme is active is : app/views/themes/theme-name/controller-name/action-name.html.

For example, if the activeTheme is bootstrap, the default view for the action display in the Users controller must be loacated in app/views/themes/bootstrap/Users/display.html.

app/controllers/Users.php
1 namespace controllers;
2
3 class Users extends BaseController{
4
5         public function display(){
6             $this->loadDefaultView();
7             ...
8         }
9     }

Nota

The devtools commands to create a controller or an action and their associated view use the @activeTheme folder if a theme is active.

Ubiquity controller Users -v

Ubiquity action Users.display -v

Assets loading

The mechanism is the same as for the views : @activeTheme namespace refers to the public/assets/theme-name/ folder

{{ css('@activeTheme/css/style.css') }}

{{ js('@activeTheme/js/scripts.js') }}

{{ img('@activeTheme/img/image-name.png', {alt: 'Image Alt Name', class: 'css-class'}) }}

If the bootstrap theme is active,
the assets folder is public/assets/bootstrap/.

Css compilation

For Bootstrap or foundation, install sass:

npm install -g sass

Then run from the project root folder:

For bootstrap:

ssass public/assets/bootstrap/scss/app.scss public/assets/bootstrap/css/style.css --load-path=vendor

For foundation:

ssass public/assets/foundation/scss/app.scss public/assets/foundation/css/style.css --load-path=vendor

jQuery Semantic-UI

Por defecto, Ubiquity utiliza la librería phpMv-UI para la parte experiencia de usuario.
PhpMv-UI permite crear componentes basados en Semantic-UI o Bootstrap y generar scripts jQuery en PHP.

Esta biblioteca se utiliza para la interfaz de administración de webtools.

Integración

Por defecto, se inyecta una variable $jquery en los controladores en tiempo de ejecución.

Esta operación se realiza mediante inyección de dependencias, en app/config.php:

app/config.php
...
"di"=>array(
             "@exec"=>array(
                             "jquery"=>function ($controller){
                                     return \Ajax\php\ubiquity\JsUtils::diSemantic($controller);
                                     }
                             )
             )
...

Así que no hay nada que hacer,
pero para facilitar su uso y permitir la finalización de código en un controlador, se recomienda añadir la siguiente documentación de código:

app/controllers/FooController.php
 /**
 * Controller FooController
 * @property \Ajax\php\ubiquity\JsUtils $jquery
 **/
class FooController extends ControllerBase{

     public function index(){}
}

jQuery

Referencias (Href) a solicitudes ajax

Cree un nuevo Controlador y su vista asociada, luego defina las siguientes rutas:

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

app/views/FooController/index.html
     <a href="{{path('action.a')}}">Action a</a>
     <a href="{{path('action.b')}}">Action b</a>

Inicializar la caché del router:

Ubiquity init:cache -t=controllers

Pruebe esta página en su navegador en http://127.0.0.1:8090/FooController.

Transformación de peticiones en peticiones Ajax

El resultado de cada petición ajax debe mostrarse en un área de la página definida por su selector jQuery (.result span)

app/controllers/FooController.php
namespace controllers;

/**
 * @property \Ajax\php\ubiquity\JsUtils $jquery
 */
class FooController extends ControllerBase {

     public function index() {
             $this->jquery->getHref('a','.result span');
             $this->jquery->renderView("FooController/index.html");
     }
     ...
}
app/views/FooController/index.html
     <a href="{{path('action.a')}}">Action a</a>
     <a href="{{path('action.b')}}">Action b</a>
<div class='result'>
     Selected action:
     <span>No One</span>
</div>
{{ script_foot | raw }}

Nota

La variable script_foot contiene el script jquery generado por el método renderView. El filtro raw marca el valor como «safe», lo que significa que en un entorno con escape automático activado esta variable no será escapada.

Añadamos un poco de css para hacerlo más profesional:

app/views/FooController/index.html
<div class="ui buttons">
     <a class="ui button" href="{{path('action.a')}}">Action a</a>
     <a class="ui button" href="{{path('action.b')}}">Action b</a>
</div>
<div class='ui segment result'>
     Selected action:
     <span class="ui label">No One</span>
</div>
{{ script_foot | raw }}

Si queremos añadir un nuevo enlace cuyo resultado deba mostrarse en otra zona, es posible especificarlo mediante el atributo data-target.

La nueva acción:

app/controllers/FooController.php
namespace controllers;

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

La vista asociada:

app/views/FooController/index.html
<div class="ui buttons">
     <a class="ui button" href="{{path('action.a')}}">Action a</a>
     <a class="ui button" href="{{path('action.b')}}">Action b</a>
     <a class="ui button" href="{{path('action.c')}}" data-target=".result p">Action c</a>
</div>
<div class='ui segment result'>
     Selected action:
     <span class="ui label">No One</span>
     <p></p>
</div>
{{ script_foot | raw }}
_images/fooController.png
Definición de los atributos de la petición ajax:

En el siguiente ejemplo, los parámetros pasados a la variable attributes del método getHref:

  • eliminar el historial de navegación,

  • hacer que el cargador ajax sea interno al botón pulsado.

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

Nota

Es posible utilizar el método postHref para utilizar el método http POST.

Peticiones ajax clásicas

Para este ejemplo, cree la siguiente base de datos:

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;

Conectar la aplicación a la base de datos, y generar la clase User:

Con devtools:

Ubiquity config:set --database.dbName=uguide
Ubiquity all-models

Crear un nuevo controlador UsersJqueryController.

Ubiquity controller UsersJqueryController -v

Crea las siguientes acciones en UsersJqueryController:

_images/UsersJqueryControllerStructure.png
Acción index

La acción index debe mostrar un botón para obtener la lista de usuarios, cargada mediante una petición ajax:

app/controllers/UsersJqueryController.php
 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 vista por defecto asociada a la acción index:

app/views/UsersJqueryController/index.html
<div class="ui container">
     <div id="users-bt" class="ui button">
             <i class="ui users icon"></i>
             Display <b>users</b>
     </div>
     <p></p>
     <div id="users">
     </div>
</div>
{{ script_foot | raw }}
acción displayUsers

Se muestran todos los usuarios, y un clic en un usuario debe mostrar los detalles del usuario a través de una solicitud ajax publicada:

app/controllers/UsersJqueryController.php
 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 vista asociada a la acción displayUsers:

app/views/UsersJqueryController/displayUsers.html
<div class="ui top attached header">
     <i class="users circular icon"></i>
     <div class="content">Users</div>
</div>
<div class="ui attached segment">
     <ul id='users-content'>
     {% for user in users %}
             <li data-ajax="{{user.id}}">{{user.firstname }} {{user.lastname}}</li>
     {% endfor %}
     </ul>
     <div id='user-detail'></div>
</div>
<div class="ui bottom attached inverted segment">
<div id="close-bt" class="ui inverted button">Close</div>
</div>
{{ script_foot | raw }}
acción displayOneUser
app/controllers/UsersJqueryController.php
 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 vista asociada a la acción displayOneUser:

app/views/UsersJqueryController/displayUsers.html
<div class="ui label">
     <i class="ui user icon"></i>
     Id
     <div class="detail">{{user.id}}</div>
</div>
<div class="ui label">
     Firstname
     <div class="detail">{{user.firstname}}</div>
</div>
<div class="ui label">
     Lastname
     <div class="detail">{{user.lastname}}</div>
</div>
<p></p>
<div id="close-user-bt" class="ui black button">
     <i class="ui users icon"></i>
     Return to users
</div>
{{ script_foot | raw }}

Componentes semantic

A continuación, vamos a hacer un controlador implementando las mismas funcionalidades que antes, pero utilizando componentes PhpMv-UI (parte semántica).

HtmlButton ejemplo

Crear un nuevo controlador UsersJqueryController.

Ubiquity controller UsersCompoController -v
app/controllers/UsersJqueryController.php
 1namespace controllers;
 2
 3use Ubiquity\controllers\Router;
 4
 5/**
 6 * Controller UsersCompoController
 7 *
 8 * @property \Ajax\php\ubiquity\JsUtils $jquery
 9 * @route("users-compo")
10 */
11class UsersCompoController extends ControllerBase {
12
13     private function semantic() {
14             return $this->jquery->semantic();
15     }
16
17     /**
18      *
19      * @get
20      */
21     public function index() {
22             $bt = $this->semantic()->htmlButton('users-bt', 'Display users');
23             $bt->addIcon('users');
24             $bt->getOnClick(Router::path('display.compo.users'), '#users', [
25                     'hasLoader' => 'internal'
26             ]);
27             $this->jquery->renderDefaultView();
28     }

Nota

Al llamar a renderView o renderDefaultView sobre el objeto JQuery se realiza la compilación del componente, y se genera el HTML y JS correspondientes.

La vista asociada integra el componente de botón con la matriz q disponible en la vista :

app/views/UsersCompoController/index.html
<div class="ui container">
     {{ q['users-bt'] | raw }}
     <p></p>
     <div id="users">
     </div>
</div>
{{ script_foot | raw }}

//todo DataTable sample +++++++++++++++++

Normalizers (Normalizadores)

Nota

El módulo Normalizer utiliza la clase estática NormalizersManager para gestionar la normalización.

Validators (Validadores)

Nota

El módulo Validators utiliza la clase estática ValidatorsManager para gestionar la validación.

Los validadores se utilizan para comprobar que los datos de los miembros de un objeto cumplen ciertas restricciones.

Añadir validadores

O bien la clase Author que queremos utilizar en nuestra aplicación :

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

Hemos añadido una restricción de validación en el miembro name con la anotación @validator, para que no esté vacío.

Generar caché

Ejecute este comando en modo consola para crear los datos de caché de la clase Author :

Ubiquity init-cache -t=models

La caché del validador se genera en app/cache/contents/validators/models/Author.cache.php.

Validación de instancias

una instancia

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 el nombre del autor está vacío, esta acción debe mostrarse:

name : This value should not be empty

El método validate devuelve una matriz de instancias ConstraintViolation.

instancias multiples

public function testValidateAuthors(){
        $authors=DAO::getAll(Author::class);
        $violations=ValidatorsManager::validateInstances($author);
        foreach($violations as $violation){
                echo $violation.'<br>';
        }
}

Generación de modelos con validadores por defecto

Cuando las clases se generan automáticamente a partir de la base de datos, se asocian validadores por defecto a los miembros, en función de los metadatos de los campos.

Ubiquity create-model User
app/models/Author.php
 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     }

Estos validadores pueden ser modificados.
Las modificaciones siempre deben ir seguidas de una reinicialización de la caché del modelo.

Ubiquity init-cache -t=models

La información sobre la validación de los modelos puede visualizarse con devtools :

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

Obtener validadores en campo email:

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

También se puede acceder a la información de validación desde la parte modelos de las webtools:

_images/info-validation-webtools.png

Tipos de validadores

Basic

Validador

Roles

Restricciones

Valores aceptados

isBool

Verifica si el valor es un booleano

true,false,0,1

isEmpty

Verifica si el valor está vacío

“”,null

isFalse

Verifica si el valor es falso

false,”false”,0,”0”

isNull

Verifica si el valor es nulo

null

isTrue

Verifica si el valor es verdadero

true,”true”,1,”1”

notEmpty

Verifica si el valor no está vacío

!null && !””

notNull

Verifica si el valor no es nulo

!null

tipo

Verifica si el valor es del tipo {type}

{type}

Comparación

Fechas

Múltiples

Cadenas

Transformers

Nota

El módulo Transformers utiliza la clase estática TransformersManager para gestionar las transformaciones de datos.

Los transformers se utilizan para transformar los datos después de cargarlos desde la base de datos o antes de mostrarlos en una vista.

Añadir transformers

O bien la clase Author que queremos utilizar en nuestra aplicación :

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

Añadimos un transformer en el miembro name con la anotación @transformer, para poner el nombre en mayúsculas en las vistas.

Generar caché

Ejecute este comando en modo consola para crear los datos de caché de la clase Author :

Ubiquity init-cache -t=models

La caché del transformer se genera con metadatos del modelo en app/cache/models/Author.cache.php.

La información de los transformers puede visualizarse con devtools :

Ubiquity info:model -m=Author -f=#transformers
_images/trans-info.png

Uso de transformers

Inicie el TransformersManager en el archivo app/config/services.php:

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

Puede comprobar el resultado en la interfaz de administración:

_images/trans-upper.png

o creando un controlador:

app/controllers/Authors.php
 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}
app/views/Authors/index.html
<ul>
   {% for author in authors %}
      <li>{{ author.name }}</li>
   {% endfor %}
</ul>

Tipos de transformers

transform

El tipo transform se basa en la interfaz TransformerInterface. Se utiliza cuando los datos transformados deben convertirse en un objeto. |brl El transformador DateTime es un buen ejemplo de este tipo de transformador:

  • Al cargar los datos, el Transformador convierte la fecha de la base de datos en una instancia de php DateTime.

  • Su método reverse realiza la operación inversa (fecha php a fecha compatible con la base de datos).

toView

El tipo toView se basa en la interfaz TransformerViewInterface. Se utiliza cuando los datos transformados deben mostrarse en una vista.

toForm

El tipo toForm se basa en la interfaz TransformerFormInterface. Se utiliza cuando los datos transformados deben utilizarse en un formulario.

Uso de transformers

Transformación en la carga de datos

Si se omite, transformerOp por defecto es transform.

$authors=DAO::getAll(Author::class);

Establecer transformerOp a toView

DAO::transformersOp='toView';
$authors=DAO::getAll(Author::class);

Transformación tras la carga

Devuelve el valor transformado del miembro:

TransformersManager::transform($author, 'name','toView');

Devuelve un valor transformado:

TransformersManager::applyTransformer($author, 'name','john doe','toView');

Transforma una instancia aplicando todos los transformers definidos:

TransformersManager::transformInstance($author,'toView');

Transformers existentes

Transformer

Tipo(s)

Descripción

datetime

transform, toView, toForm

Transformar una fecha y hora de una base de datos en un objeto DateTime de php

upper

toView

Poner el valor en mayúsculas

lower

toView

Poner el valor en minúsculas

firstUpper

toView

Poner en mayúsculas el primer carácter del valor

password

toView

Enmascarar los caracteres

md5

toView

Hashear el valor con md5

Cree personalizado

Creación

Crear un transformador para mostrar un nombre de usuario como una dirección de correo electrónico local:

app/transformers/toLocalEmail.php
 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}

Registro

Registre el transformador ejecutando el siguiente script:

TransformersManager::registerClassAndSave('localEmail',\transformers\ToLocalEmail::class);

Uso

app/models/User.php
 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}
DAO::transformersOp='toView';
$user=DAO::getOne(User::class,"name='Smith'");
echo $user->getName();

El nombre de usuario Smith aparecerá como smith@mydomain.local.

Translation module

Nota

The Translation module uses the static class TranslatorManager to manage translations.

Module structure

Translations are grouped by domain, within a locale :

In the translation root directory (default app/translations):

  • Each locale corresponds to a subfolder.

  • For each locale, in a subfolder, a domain corresponds to a php file.

translations
      en_EN
           messages.php
           blog.php
      fr_FR
            messages.php
            blog.php
  • each domain file contains an associative array of translations key-> translation value

  • Each key can be associated with
    • a translation

    • a translation containing variables (between % and %)

    • an array of translations for handle pluralization

app/translations/en_EN/messages.php
return [
     'okayBtn'=>'Okay',
     'cancelBtn'=>'Cancel',
     'deleteMessage'=>['No message to delete!','1 message to delete.','%count% messages to delete.']
];

Starting the module

Module startup is logically done in the services.php file.

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

With no parameters, the call of the start method uses the locale en_EN, without fallbacklocale.

Importante

The translations module must be started after the cache has started.

Setting the locale

Changing the locale when the manager starts:

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

Changing the locale after loading the manager:

TranslatorManager::setLocale('fr_FR');

Setting the fallbackLocale

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

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

Defining the root translations dir

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

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

Make a translation

With php

Translation of the okayBtn key into the default locale (specified when starting the manager):

$okBtnCaption=TranslatorManager::trans('okayBtn');

With no parameters, the call of the trans method uses the default locale, the domain messages.

Translation of the message key using a variable:

$okBtnCaption=TranslatorManager::trans('message',['user'=>$user]);

In this case, the translation file must contain a reference to the user variable for the key message:

app/translations/en_EN/messages.php
['message'=>'Hello %user%!',...];

In twig views:

Translation of the okayBtn key into the default locale (specified when starting the manager):

{{ t('okayBtn') }}

Translation of the message key using a variable:

{{ t('message',parameters) }}

Security

Guiding principles

Forms validation

Client-side validation

It is preferable to perform an initial client-side validation to avoid submitting invalid data to the server.

Example of the creation of a form in the action of a controller (this part could be located in a dedicated service for a better separation of layers):

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

The Associated View:

app/views/UsersManagement/index.html
 {{ q['frm-user'] | raw }}
 {{ script_foot | raw }}
 <div id="response"></div>
_images/frm-user.png

Nota

The CRUD controllers automatically integrate this client-side validation using the Validators attached to the members of the models.

#[Column(name: "password",nullable: true,dbType: "varchar(255)")]
#[Validator(type: "length",constraints: ["max"=>20,"min"=>6])]
#[Transformer(name: "password")]
private $password;
Server-side validation

It is preferable to restrict the URLs allowed to modify data.
Beforehand, by specifying the Http method in the routes, and by testing the request :

#[Post(path: "/submit")]
public function submitUser(){
   if(!URequest::isCrossSite() && URequest::isAjax()){
      $datas=URequest::getPost();//post with htmlEntities
      //Do something with $datas
   }
}

Nota

The Ubiquity-security module offers additional control to avoid cross-site requests.

After modifying an object, it is possible to check its validity, given the validators attached to the members of the associated Model:

#[Post(path: "/submit")]
public function submitUser(){
   if(!URequest::isCrossSite()){
      $datas=URequest::getPost();//post with htmlEntities
      $user=new User();
      URequest::setValuesToObject($user,$datas);

      $violations=ValidatorsManager::validate($user);
      if(\count($violations)==0){
         //do something with this valid user
      } else {
         //Display violations...
      }
   }
}

DAO operations

It is always recommended to use parameterized queries, regardless of the operations performed on the data:
  • To avoid SQL injections.

  • To allow the use of prepared queries, speeding up processing.

$googleUsers=DAO::getAll(User::class,'email like ?',false,['%@gmail.com']);
$countActiveUsers=DAO::count(User::class,'active= ?',[true]);

Nota

DAO operations that take objects as parameters use this mechanism by default.

DAO::save($user);

Passwords management

The Password Transformer allows a field to be of the password type when displayed in an automatically generated CRUD form.

#[Transformer(name: "password")]
private $password;

After submission from a form, it is possible to encrypt a password from the URequest class:

$encryptedPassword=URequest::password_hash('password');
$user->setPassword($encryptedPassword);
DAO::save($user);

The algorithm used in this case is defined by the php PASSWORD_DEFAULT.

It is also possible to check a password entered by a user in the same way, to compare it to a hash:

if(URequest::password_verify('password', $existingPasswordHash)){
   //password is ok
}

Importante

Set up Https to avoid sending passwords in clear text.

Security module/ ACL management

In addition to these few rules, you can install if necessary:

Security module

Installation

Install the Ubiquity-security module from the command prompt or from the Webtools (Composer part).

composer require phpmv/ubiquity-security

Then activate the display of the Security part in the Webtools:

_images/display-security.png

Session CSRF

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

_images/security-part.png

This protection can be customized by creating a class implementing the VerifySessionCsrfInterface.

app/session/MyCsrfProtection.php
class MyCsrfProtection implements VerifySessionCsrfInterface {
   private AbstractSession $sessionInstance;

   public function __construct(AbstractSession $sessionInstance) {
      $this->sessionInstance = $sessionInstance;
   }

   public function init() {
      //TODO when the session starts
   }

   public function clear() {
      //TODO when the session ends
   }

   public function start() {
      //TODO When the session starts or is resumed
   }

   public static function getLevel() {
      return 1; //An integer to appreciate the level of security
   }
}

Starting the custom protection in services:

app/config/services.php
use Ubiquity\utils\http\session\PhpSession;
use Ubiquity\controllers\Startup;
use app\session\MyCsrfProtection;

Startup::setSessionInstance(new PhpSession(new MyCsrfProtection()));

Deactivating the protection

If you do not need to protect your session against Csrf attacks, start the session with the NoCsrfProtection class.

app/config/services.php
use Ubiquity\utils\http\session\PhpSession;
use Ubiquity\controllers\Startup;
use Ubiquity\utils\http\session\protection\NoCsrfProtection;

Startup::setSessionInstance(new PhpSession(new NoCsrfProtection()));

CSRF manager

The CsrfManager service can be started directly from the webtools interface.
Its role is to provide tools to protect sensitive routes from Csrf attacks (the ones that allow the validation of forms for example).

_images/csrf-manager-started.png
  • The service is started in the services.php file.

app/config/services.php
 \Ubiquity\security\csrf\CsrfManager::start();

Example of form protection:

The form view:

<form id="frm-bar" action='/submit' method='post'>
   {{ csrf('frm-bar') }}
   <input type='text' id='sensitiveData' name='sensitiveData'>
</form>

The csrf method generates a token for the form (By adding a hidden field in the form corresponding to the token.).

The form submitting in a controller:

use Ubiquity\security\csrf\UCsrfHttp;

#[Post('/submit')]
public function submit(){
   if(UCsrfHttp::isValidPost('frm-bar')){
      //Token is valid! => do something with post datas
   }
}

Nota

It is also possible to manage this protection via cookie.

Example of protection with ajax:

The meta field csrf-token is generated on all pages.

app/controllers/BaseController.php
abstract class ControllerBase extends Controller{
   protected $headerView = "@activeTheme/main/vHeader.html";
   protected $footerView = "@activeTheme/main/vFooter.html";

   public function initialize() {
      if (! URequest::isAjax ()) {
         $meta=UCsrfHttp::getTokenMeta('postAjax');
         $this->loadView ( $this->headerView,['meta'=>$meta] );
      }
   }
}

This field is added in the headerView:

app/views/main/vHeader.html
{% block header %}
   <base href="{{config["siteUrl"]}}">
   <meta charset="UTF-8">
   <link rel="icon" href="data:;base64,iVBORw0KGgo=">
   {{meta | raw}}
   <title>Tests</title>
{% endblock %}

Example with a button posting data via ajax. The parameter csrf is set to true. So when the request is posted, the csrf-token is sent in the request headers.

#[Get(path: "/ajax")]
public function ajax(){
   $this->jquery->postOnClick('#bt','/postAjax','{id:55}','#myResponse',['csrf'=>true]);
   $this->jquery->renderDefaultView();
}

The submitting route can check the presence and validity of the token:

#[Post(path: "postAjax")]
public function postAjax(){
   if(UCsrfHttp::isValidMeta('postAjax')){
      var_dump($_POST);
   }else{
      echo 'invalid or absent meta csrf-token';
   }
}

Encryption manager

The EncryptionManager service can be started directly from the webtools interface.

  • In this case, a key is generated in the configuration file app/config/config.php.

  • The service is started in the services.php file.

app/config/services.php
 \Ubiquity\security\data\EncryptionManager::start($config);

Nota

By default, encryption is performed in AES-128.

_images/encryption-manager-started.png

Changing the cipher:

Upgrade to AES-256:

app/config/services.php
\Ubiquity\security\data\EncryptionManager::startProd($config, Encryption::AES256);

Generate a new key:

Ubiquity new:key 256

The new key is generated in the app/config/config.php file.

Model data encryption

The Crypt transformer can also be used on the members of a model:

app/models/User.php
 class Foo{
     #[Transformer(name: "crypt")]
     private $secret;
     ...
 }

Usage:

$o=new Foo();
$o->setSecret('bar');
TransformersManager::transformInstance($o);// secret member is encrypted
Generic Data encryption

Strings encryption:

$encryptedBar=EncryptionManager::encryptString('bar');

To then decrypt it:

echo EncryptionManager::decryptString($encryptedBar);

It is possible to encrypt any type of data:

$encryptedUser=EncryptionManager::encrypt($user);

To then decrypt it, with possible serialisation/deserialisation if it is an object:

$user=EncryptionManager::decrypt($encryptedUser);

Content Security Policies manager

The ContentSecurityManager service can be started directly from the webtools interface.

  • The service is started in the services.php file.

app/config/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();
     }
});

Nota

With this default configuration, a nonce is added to jquery scripts generated with phpmv-ui. CSP control is done in Report-only mode..

_images/csp-manager-started.png

Adding a nonce

Example of adding nonce on the header and footer pages:

Updating the base controller
app/controllers/ControllerBase.php
namespace controllers;

use Ubiquity\controllers\Controller;
use Ubiquity\security\csp\ContentSecurityManager;
use Ubiquity\utils\http\URequest;

/**
 * controllers$ControllerBase
 */
abstract class ControllerBase extends Controller {

     protected $headerView = "@activeTheme/main/vHeader.html";

     protected $footerView = "@activeTheme/main/vFooter.html";

     protected $nonce;

     public function initialize() {
             $this->nonce=ContentSecurityManager::getNonce('jsUtils');
             if (! URequest::isAjax()) {
                     $this->loadView($this->headerView,['nonce'=>$this->nonce]);
             }
     }

     public function finalize() {
             if (! URequest::isAjax()) {
                     $this->loadView($this->footerView,['nonce'=>$this->nonce]);
             }
     }
}

Password management

Users token

ACL management

Installation

Install the Ubiquity-acl module from the command prompt or from the Webtools (Composer part).

composer require phpmv/ubiquity-acl

Then activate the display of the Acl part in the Webtools:

_images/display-acl.png

ACL interface in webtools:

_images/acl-part.png

Acl Rules

ACLs are used to define access to an Ubiquity application. They are defined according to the following principles:

An Ubiquity application is composed of :
  • Resources (possibly controllers, or actions of these controllers)

  • Roles, possibly assigned to users. Each Role can inherit parent roles.

  • Permissions, which correspond to a right to do. Each permission has a level (represented by an integer value).

Additional rules:
  • An AclElement (Allow) grants Permission to a Role on a Resource.

  • Each role inherits authorisations from its parents, in addition to its own.

  • If a role has a certain level of access permission on a resource, it will also have all the permissions of a lower level on that resource.

  • The association of a resource and a permission to a controller or a controller action defines a map element.

_images/acl-diagram.png
Naming tips:
  • Role, in capital letters, beginning with an arobase (@USER, @ADMIN, @ALL…).

  • Permissions, in upper case, named using a verb (READ, WRITE, OPEN…).

  • Resource, capitalized on the first letter (Products, Customers…)

ACL Starting

The AclManager service can be started directly from the webtools interface, in the Security part.

  • The service is started in the services.php file.

app/config/services.php
 \Ubiquity\security\acl\AclManager::startWithCacheProvider();

ACLCacheProvider

This default provider allows you to manage ACLs defined through attributes or annotations.

AclController

An AclController enables automatic access management based on ACLs to its own resources.
It is possible to create them automatically from webtools.

_images/new-acl-controller.png

But it is just a basic controller, using the AclControllerTrait feature.

This controller just goes to redefine the _getRole method, so that it returns the role of the active user, for example.

app/controllers/BaseAclController.php
<?php
namespace controllers;

use Ubiquity\controllers\Controller;
use Ubiquity\security\acl\controllers\AclControllerTrait;
use Ubiquity\attributes\items\acl\Allow;

class BaseAclController extends Controller {
use AclControllerTrait;

   #[Allow('@ME')]
   public function index() {
      $this->loadView("BaseAclController/index.html");
   }

   public function _getRole() {
      $_GET['role']??'@ME';//Just for testing: logically, this is the active user's role
   }

   /**
    * {@inheritdoc}
    * @see \Ubiquity\controllers\Controller::onInvalidControl()
    */
   public function onInvalidControl() {
      echo $this->_getRole() . ' is not allowed!';
   }
}
Authorisation has been granted for the resource:
  • Without specifying the resource, the controller’s actions are defined as a resource.

  • Without specifying the permission, the ALL permission is used.

_images/me-allow.png

And this association is present in the Acls map:

_images/me-map.png
AclController with authentication

Nota

The use of both WithAuthTrait and AclControllerTrait requires to remove the ambiguity about the isValid method.

app/controllers/BaseAclController.php
class BaseAclController extends Controller {
   use AclControllerTrait,WithAuthTrait{
      WithAuthTrait::isValid insteadof AclControllerTrait;
      AclControllerTrait::isValid as isValidAcl;
   }

   public function isValid($action){
        return parent::isValid($action)&& $this->isValidAcl($action);
   }
}
Allow with Role, resource and permission

Allow without prior creation:

@USER is allowed to access to Foo resource with READ permission.

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

Nota

The role, resource and permission are automatically created as soon as they are invoked with Allow.

Allow with explicit creation:

app/controllers/BaseAclController.php
use Ubiquity\attributes\items\acl\Allow;
use Ubiquity\attributes\items\acl\Permission;

class BaseAclController extends Controller {
use AclControllerTrait;
   ...

   #[Permission('READ',500)]
   #[Allow('@USER','Foo', 'READ')]
   public function foo(){
      echo 'foo page allowed for @USER and @ME';
   }
}
Adding ACL at runtime

Whether in a controller or in a service, it is possible to add Roles, Resources, Permissions and Authorizations at runtime:

For example :\ Adding a Role @USER inheriting from @GUEST.

use Ubiquity\security\acl\AclManager;

AclManager::addRole('@GUEST');
AclManager::addRole('@USER',['@GUEST']);
Defining ACLs with Database

The ACLs defined in the database are additional to the ACLs defined via annotations or attributes.

Initializing

The initialization allows to create the tables associated to the ACLs (Role, Resource, Permission, AclElement). It needs to be done only once, and in dev mode only.

To place for example in app/config/bootstrap.php file:

use Ubiquity\controllers\Startup;
use Ubiquity\security\acl\AclManager;

$config=Startup::$config;
AclManager::initializeDAOProvider($config, 'default');

Starting

In app/config/services.php file :

use Ubiquity\security\acl\AclManager;
use Ubiquity\security\acl\persistence\AclCacheProvider;
use Ubiquity\security\acl\persistence\AclDAOProvider;
use Ubiquity\orm\DAO;

DAO::start();//Optional, to use only if dbOffset is not default

AclManager::start();
AclManager::initFromProviders([
    new AclCacheProvider(), new AclDAOProvider($config)
]);

Strategies for defining ACLs

With few resources:

Defining authorisations for each controller’s action or action group:

Resources logically correspond to controllers, and permissions to actions. But this rule may not be respected, and an action may be defined as a resource, as required.

The only mandatory rule is that a Controller/action pair can only correspond to one Resource/permission pair (not necessarily unique).

app/controllers/BaseAclController.php
namespace controllers;

use Ubiquity\controllers\Controller;
use Ubiquity\security\acl\controllers\AclControllerTrait;
use Ubiquity\attributes\items\acl\Permission;
use Ubiquity\attributes\items\acl\Resource;

#[Resource('Foo')]
#[Allow('@ADMIN')]
class FooController extends Controller {
   use AclControllerTrait;

   #[Allow('@NONE')]
   public function index() {
      echo 'index';
   }

   #[Allow('@USER')]
   public function read() {
      echo 'read';
   }

   #[Allow('@USER')]
   public function write() {
      echo 'write';
   }

   public function admin() {
      echo 'admin';
   }

   public function _getRole() {
      return $_GET['role']??'@NONE';
   }

   /**
    * {@inheritdoc}
    * @see \Ubiquity\controllers\Controller::onInvalidControl()
    */
   public function onInvalidControl() {
      echo $this->_getRole() . ' is not allowed!';
   }

}

With more resources:

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

El módulo REST implementa un CRUD básico,
con un sistema de autenticación, directamente comprobable en la parte de administración.

REST y enrutamiento

El enrutador es esencial para el módulo REST, ya que REST (Respresentation State Transfer) se basa en URL y métodos HTTP.

Nota

Por razones de rendimiento, las rutas REST se almacenan en caché independientemente de otras rutas.
Por lo tanto, es necesario iniciar el enrutador de una manera particular para activar las rutas REST y no obtener un error 404 recurrente.

El router se inicia en services.php.

Sin activación de rutas REST:

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

Para habilitar rutas REST en una aplicación que también tiene una parte no REST:

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

Para activar sólo las rutas Rest:

Router::startRest();

Es posible iniciar el enrutamiento condicionalmente (este método sólo será más eficiente si el número de rutas es grande en cualquiera de las partes):

app/config/services.php
...
     if($config['isRest']()){
             Router::startRest();
     }else{
             Router::start();
     }

Recursos REST

Un controlador REST puede asociarse directamente a un modelo.

Nota

Si no tiene a mano una base de datos mysql, puede descargar ésta: messagerie.sql

Creación

Con devtools:

Ubiquity rest RestUsersController -r=User -p=/rest/users

O con webtools:

Vaya a la sección REST y elija Añadir un nuevo recurso:

_images/addNewResource.png

El controlador creado :

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

Dado que los atributos automated y inherited de la ruta están en true, el controlador tiene las rutas por defecto de la clase padre.

Interfaz de prueba

Webtools ofrecen una interfaz para consultar datos:

_images/createdResource.png
Obtener una instancia

Se puede acceder a una instancia de usuario por su clave principal (id):

_images/getOneResource.png

Inclusión de miembros asociados: la organización del usuario

_images/getOneResourceInclude.png

Inclusión de miembros asociados: organización, conexiones y grupos del usuario

_images/getOneResourceIncludeAll.png
Obtener varias instancias

Obtener todas las instancias:

_images/getAllOrgas.png

Establecer una condición:

_images/condition-orgas.png

Incluidos los miembros asociados:

_images/include-orgas.png
Añadir una instancia

Los datos se envían mediante el método POST, con un tipo de contenido definido en application/x-www-form-urlencoded:

Añada los parámetros de nombre y dominio pulsando el botón parámetros:

_images/post-parameters.png

La adición requiere una autenticación, por lo que se genera un error, con el estado 401:

_images/unauthorized-post.png

La interfaz de administración permite simular la autenticación por defecto y obtener un token, solicitando el método connect:

_images/connect.png

El token se envía automáticamente en las siguientes solicitudes.
A continuación, se puede insertar el registro.

_images/added.png
Actualización de una instancia

La actualización sigue el mismo esquema que la inserción.

Borrar una instancia
_images/delete-instance.png

Personalización

Rutas

Por supuesto, es posible personalizar y simplificar las rutas.
En este caso, es preferible utilizar la herencia de la clase RestBaseController, y no habilitar las rutas automáticas.

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

Tras reinicializar la caché, la interfaz de prueba muestra las rutas accesibles:

_images/custom-orgas.png
Modificación de los datos enviados

By overriding

Es posible modificar los datos enviados a los métodos update y add, para añadir, modificar o borrar el valor de los campos antes de enviarlos.
Ya sea sobredefiniendo el método getDatas:

app/controllers/RestOrgas.php
...

     protected function getDatas() {
             $datas = parent::getDatas();
             unset($datas['aliases']);// Remove aliases field
             return $datas;
     }

Con eventos

O bien de forma más global actuando sobre los eventos de descanso:

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

Autentificación

Ubiquity REST implementa una autenticación Oauth2 con tokens Bearer. |br|Sólo los métodos con la anotación ``@authorization`` requieren la autenticación, estos son los métodos de modificación (añadir, actualizar y eliminar). |br|

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

El método connect de un controlador REST establece la conexión y devuelve un nuevo token.
Corresponde al desarrollador anular este método para gestionar una posible autenticación con nombre de usuario y contraseña.

_images/token.png

Simulación de una conexión con inicio de sesión

En este ejemplo, la conexión consiste simplemente en enviar una variable de usuario por el método post.
Si se proporciona el usuario, el método connect de la instancia $server devuelve un token válido que se almacena en sesión (la sesión actúa aquí como base de datos).

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

Para cada solicitud con autenticación, es posible recuperar el usuario conectado (se añade aquí en las cabeceras de respuesta) :

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

Utilice la interfaz webtools para probar la conexión:

_images/connected-user.png

Personalización

Api tokens

Es posible personalizar la generación de tokens anulando el método getRestServer:

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

Orígenes y CORS permitidos

Compartición de recursos entre orígenes (CORS)

Si accede a su api desde otro sitio, es necesario configurar CORS.

En este caso, para peticiones de tipo PATCH, PUT, DELETE, tu api debe definir una ruta que permita a CORS realizar su control pre-petición mediante el método OPTIONS.

app/controllers/RestOrgas.php
1     class RestOrgas extends \Ubiquity\controllers\rest\RestController {
2
3             ...
4
5             /**
6              * @options('{url}')
7              */
8             public function options($url='') {}
9     }
Orígenes permitidos

Los orígenes permitidos permiten definir los clientes que pueden acceder al recurso en caso de una petición entre dominios definiendo la cabecera de respuesta Access-Control-Allow-Origin.
Este campo de cabecera es devuelto por el método OPTIONS.

app/controllers/RestOrgas.php
 1     class RestOrgas extends \Ubiquity\controllers\rest\RestController {
 2
 3             ...
 4
 5             protected function getRestServer(): RestServer {
 6                     $srv= new RestServer($this->config);
 7                     $srv->setAllowOrigin('http://mydomain/');
 8                     return $srv;
 9             }
10     }

Es posible autorizar varios orígenes:

app/controllers/RestOrgas.php
 1     class RestOrgas extends \Ubiquity\controllers\rest\RestController {
 2
 3             ...
 4
 5             protected function getRestServer(): RestServer {
 6                     $srv= new RestServer($this->config);
 7                     $srv->setAllowOrigins(['http://mydomain1/','http://mydomain2/']);
 8                     return $srv;
 9             }
10     }

Respuesta

Para cambiar el formato de las respuestas, es necesario crear una clase que herede de ResponseFormatter.
Nos inspiraremos en HAL, y cambiaremos el formato de las respuestas por:

  • añadir un enlace a sí mismo para cada recurso

  • añadir un atributo _embedded para las colecciones

  • eliminación del atributo data para los recursos únicos

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

A continuación, asigna MyResponseFormatter al controlador REST anulando el método getResponseFormatter:

app/controllers/RestOrgas.php
1     class RestOrgas extends \Ubiquity\controllers\rest\RestController {
2
3             ...
4
5             protected function getResponseFormatter(): ResponseFormatter {
6                     return new MyResponseFormatter();
7             }
8     }

Comprueba los resultados con los métodos getOne y get:

_images/getOneFormatted.png _images/getFormatted.png

APIs

A diferencia de los recursos REST, los controladores API son multirrecursos.

SimpleRestAPI

JsonApi

Ubiquity implementa la especificación jsonApi con la clase JsonApiRestController.
JsonApi es utilizado por ``EmberJS <https://api.emberjs.com/ember-data/release/classes/DS.JSONAPIAdapter>`_and otros.
ver https://jsonapi.org/ para más.

Creación

Con devtools:

Ubiquity restapi JsonApiTest -p=/jsonapi

O con webtools:

Vaya a la sección REST y elija Añadir un nuevo recurso:

_images/jsonapi-creation.png

Prueba la api en webtools:

_images/jsonapi-admin.png
Obtener una matriz de objetos

Por defecto, se incluyen todos los miembros asociados:

_images/getAll.png
Incluidos los miembros asociados

debe utilizar el parámetro include de la solicitud:

URL

Descripción

/jsonapi/user?include=false

No se incluyen miembros asociados

/jsonapi/user?include=organization

Incluir la organización

/jsonapi/user?include=organization,connections

Incluir la organización y las conexiones

/jsonapi/user?include=groupes.organization

Incluir los grupos y su organización

Filtrado de instancias

debe utilizar el parámetro filter de la solicitud,
el parámetro filter corresponde a la parte where de una sentencia SQL:

URL

Descripción

/jsonapi/user?1=1

Sin filtro

/jsonapi/user?firstname='Benjamin'

Devuelve todos los usuarios llamados Benjamin

/jsonapi/user?filter=firstname like 'B*'

Devuelve todos los usuarios cuyo nombre empieza por B

/jsonapi/user?filter=suspended=0 and lastname like 'ca*'

Devuelve todos los usuarios suspendidos cuyo apellido empiece por ca

Paginación

debe utilizar los parámetros page[number] y page[size] de la solicitud:

URL

Descripción

/jsonapi/user

Sin paginación

/jsonapi/user?page[number]=1

Mostrar la primera página (el tamaño de página es 1)

/jsonapi/user?page[number]=1&&page[size]=10

Mostrar la primera página (el tamaño de página es 10)

Añadir una instancia

Los datos, contenidos en data[attributes], se envían mediante el método POST, con un tipo de contenido definido en application/json; charset=utf-8.

Añada sus parámetros haciendo clic en el botón parameters:

_images/add-parameters.png

La adición requiere una autenticación, por lo que se genera un error, con el estado 401 si el token está ausente o caducado.

_images/add-response.png
Borrar una instancia

El borrado requiere el método DELETE, y el uso del id del objeto a borrar:

_images/delete-response.png

Webtools

Nota

Webtools allow you to manage an Ubiquity application via a web interface. Since Ubiquity 2.2.0, webtools are in a separate repository.

Installation

Update the devtools if necessary to get started:

composer global update

At the project creation

Create a projet with webtools (-a option)

Ubiquity new quick-start -a

In an existing project

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

Ubiquity admin

Starting

Start the embedded web server, from the project folder:

Ubiquity serve

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

_images/interface.png

Customizing

Click on customize to display only the tools you use:

_images/customizing.png _images/customized.png

Webtools modules

Routes

_images/routes.png

Displays default (non REST) routes.

Operations:

  • Filter routes

  • Test routes (GET, POST…)

  • Initialize router cache

Controllers

_images/controllers.png

Displays non REST controllers.

Operations:

  • Create a controller (and optionally the view associated to the default index action)

  • Create an action in a controller (optionally the associated view, the associated route)

  • Create a special controller (CRUD or Auth)

  • Test an action (GET, POST…)

Models

_images/models.png

Displays the metadatas of the models, allows to browse the entities.

Operations:

  • Create models from database

  • Generate models cache

  • Generate database script from existing models

  • Performs CRUD operations on models

Rest

_images/rest.png

Displays an manage REST services.

Operations:

  • Re-initialize Rest cache and routes

  • Create a new Service (using an api)

  • Create a new resource (associated to a model)

  • Test and query a web service using http methods

  • Performs CRUD operations on models

Cache

_images/cache.png

Displays cache files.

Operations:

  • Delete or re-initialize models cache

  • Delete or re-initialize controllers cache

  • Delete other cache files

Maintenance

_images/maintenance.png

Allows to manage maintenance modes.

Operations:

  • Create or update a maintenance mode

  • De/Activate a maintenance mode

  • Delete a maintenance mode

Config

_images/config.png

Allows the display and modification of the app configuration.

Git

_images/git.png

Synchronizes the project using git.

Operations:

  • Configuration with external repositories

  • Commit

  • Push

  • Pull

_images/themes.png

Manages Css themes.

Operations:

  • Install an existing theme

  • Activate a theme

  • Create a new theme (eventually base on an existing theme)

Contribución

Requisitos del sistema

Antes de trabajar en Ubiquity, configure su entorno con el siguiente software:

  • Git

  • PHP versión 7.1 o superior.

Obtenga el código fuente de Ubiquity

En Ubiquity repositorio github :

  • Haga clic en Fork proyecto Ubiquity

  • Clona tu fork localmente:

git clone git@github.com:USERNAME/ubiquity.git

Trabaje en su parche

Nota

Antes de empezar, debes saber que todos los parches que vayas a enviar deben publicarse bajo la licencia Apache 2.0, a menos que se especifique explícitamente en tus commits.

Crear un branch temático

Nota

Utilice un nombre descriptivo para su branch:

  • issue_xxx donde xxx es el número de incidencia es una buena convención para las correcciones de errores

  • feature_name es una buena convención para las nuevas funciones

git checkout -b NEW_BRANCH_NAME master

Trabaje en su parche

Trabaja en tu código y haz todas las confirmaciones que quieras, y ten en cuenta lo siguiente:

  • Leer sobre las Normas de codificación de Ubiquity;

  • Añade pruebas unitarias, funcionales o de aceptación para demostrar que el error se ha corregido o que la nueva función funciona realmente;

  • Haga commits atómicos y lógicamente separados (use git rebase para tener un historial limpio y lógico);

  • Escriba buenos mensajes de confirmación (consulte el consejo siguiente).

  • Incrementa los números de versión en cualquier archivo modificado, respetando las reglas de semver:

    Dado un número de versión MAJOR.MINOR.PATCH, incrementa el:

    • versión MAJOR cuando realice cambios incompatibles en la API,

    • versión MINOR cuando añada funciones de forma compatible con versiones anteriores, y

    • versión PATCH cuando realice correcciones de errores compatibles con versiones anteriores.

Envíe su parche

Actualice la parte [Unrelease] del archivo CHANGELOG.md integrando sus cambios en las partes correspondientes:

  • Añadido

  • Cambiado

  • Cambiado

  • Arreglado

Eventualmente, vuelva a basar su parche
Antes de enviar, actualice su rama (necesario si tarda en terminar sus cambios):

git checkout master
git fetch upstream
git merge upstream/master
git checkout NEW_BRANCH_NAME
git rebase master

Hacer una Pull Request

Ahora puede hacer un pull request en Ubiquity repositorio github .

Guía de codificación

Nota

Aunque el framework es muy reciente, tenga en cuenta que algunas de las primeras clases de Ubiquity no siguen completamente esta guía y no han sido modificadas por razones de compatibilidad con versiones anteriores.
Sin embargo, todos los códigos nuevos deben seguir esta guía.

Opciones de diseño

Obtención y utilización de servicios

Inyecciones de dependencia

Evite utilizar la inyección de dependencias para todas las partes del framework, internamente.
La inyección de dependencias es un mecanismo que consume muchos recursos:

  • necesita identificar el elemento a instanciar ;

  • a continuación, proceder a su instanciación ;

  • para finalmente asignarlo a una variable.

Obtener servicios de un contenedor

Evite también el acceso público a servicios registrados en un contenedor de servicios.
Este tipo de acceso implica manipular objetos cuyo tipo de retorno se desconoce, lo que no es fácil de manejar para el desarrollador.

Por ejemplo, es difícil manipular el retorno no tipado de $this->serviceContainer->get('translator'), como permiten algunos frameworks, y saber qué métodos llamar en él.

Cuando sea posible, y cuando no reduzca demasiado la flexibilidad, se sugiere el uso de clases estáticas:

Para un desarrollador, la clase TranslatorManager es accesible desde todo un proyecto sin necesidad de instanciar ningún objeto.
Expone su interfaz pública y permite completar el código:

  • No es necesario inyectar el traductor para utilizarlo;

  • No es necesario recuperarlo de un contenedor de servicios.

El uso de clases estáticas crea inevitablemente una fuerte dependencia y afecta a la flexibilidad.
Pero volviendo al ejemplo del Traductor, no hay razón para cambiarlo si es satisfactorio.
No es deseable querer proporcionar flexibilidad a toda costa cuando no es necesario, y que luego el usuario vea que su aplicación es un poco lenta.

Optimización

La ejecución de cada línea de código puede tener importantes repercusiones en el rendimiento.
Compara y compara soluciones de implementación, especialmente si el código se llama repetidamente:

Calidad de código

Ubiquity utiliza Scrutinizer-CI para la calidad del código.

  • Para clases y métodos :

    • Las evaluaciones A o B son buenas

    • C es aceptable, pero debe evitarse si es posible

    • Las notas más bajas deben prohibirse

Complejidad del código

  • Los métodos complejos deben dividirse en varios, para facilitar su mantenimiento y permitir su reutilización;

  • Para clases complejas, haga una refactorización extract-class o extract-subclass y divídalas usando Traits;

Duplicaciones de código

Evite absolutamente la duplicación de código, excepto si la duplicación es mínima y está justificada por el rendimiento.

Errores

Intenta resolver sobre la marcha todos los errores que se reporten, sin dejar que se acumulen.

Pruebas

Cualquier corrección de errores que no incluya una prueba que demuestre la existencia del error corregido puede ser sospechosa.
Lo mismo ocurre con las nuevas funciones que no pueden demostrar que funcionan.

También es importante mantener una cobertura aceptable, que puede disminuir si no se prueba una nueva función.

Documentación de código

El código actual aún no está completamente documentado, siéntase libre de contribuir para llenar este vacío.

Normas de codificación

Las normas de codificación de Ubiquity se basan principalmente en las normas PSR-1 , PSR-2 y PSR-4 , por lo que es posible que ya conozca la mayoría de ellas.
Las pocas excepciones intencionadas a las normas se informan normalmente en esta guía.

Convenciones de nombrado

  • Utilice camelCase para variables PHP, miembros, nombres de funciones y métodos, argumentos (por ejemplo, $modelsCacheDirectory, isStarted());

  • Utilice namespaces para todas las clases PHP y UpperCamelCase para sus nombres (por ejemplo, CacheManager);

  • Prefija todas las clases abstractas con Abstract excepto PHPUnit BaseTests;

  • Sufija las interfaces con Interface;

  • Sufija los traits con Trait;

  • Sufija las excepciones con Exception;

  • Sufijo gestor de clases principales con Manager (por ejemplo, CacheManager, TranslatorManager);

  • Prefije las clases de utilidad con U (por ejemplo, UString, URequest);

  • Utilice UpperCamelCase para nombrar los archivos PHP (por ejemplo, CacheManager.php);

  • Utilice mayúsculas para las constantes (por ejemplo, const SESSION_NAME=”Ubiquity”).

Sangría, tabulaciones, llaves

  • Utilizar tabuladores, no espacios; (!PSR-2)

  • Utilizar llaves siempre en la misma línea; (!PSR-2)

  • Utilice llaves para indicar el cuerpo de la estructura de control, independientemente del número de sentencias que contenga;

Clases

  • Defina una clase por archivo;

  • Declare la herencia de la clase y todas las interfaces implementadas en la misma línea que el nombre de la clase;

  • Declare las propiedades de la clase antes que los métodos;

  • Declare primero los métodos privados, luego los protegidos y finalmente los públicos;

  • Declare todos los argumentos en la misma línea que el nombre del método/función, sin importar el número de argumentos;

  • Utilice paréntesis al instanciar clases independientemente del número de argumentos que tenga el constructor;

  • Añade una declaración de uso para cada clase que no forme parte del espacio de nombres global;

Operadores

  • Utilice comparación idéntica e igual cuando necesite hacer manipulación de tipos;

Ejemplo

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

Importante

Puede importar estos archivos de estandarización que integran todas estas reglas en su IDE:

Si su IDE preferido no aparece en la lista, puede enviar el archivo de normalización asociado creando un nuevo PR.

Guía de documentación

Ubiquity tiene dos conjuntos principales de documentación:

  • las guías, que te ayudan a aprender sobre manipulaciones o conceptos ;

  • y la API, que sirve de referencia para la codificación.

Puede ayudar a mejorar las guías de Ubiquity haciéndolas más coherentes, consistentes o legibles, añadiendo información que falte, corrigiendo errores factuales, corrigiendo erratas o actualizándolas con la última versión de Ubiquity.

Para ello, realice cambios en los archivos fuente de las guías Ubiquity (ubicados aquí en GitHub). A continuación, abra una solicitud de extracción para aplicar los cambios al branch principal.

Cuando trabaje con documentación, tenga en cuenta las directrices.

Configuración de servidores

Importante

Desde la versión 2.4.5, por razones de seguridad y simplificación, la raíz de una aplicación Ubiquity se encuentra en la carpeta pública.

Apache2

mod_php/PHP-CGI

Apache 2.2
mydomain.conf
     <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>
mydomain.conf
     <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

En Apache 2.4, Order Allow,Deny ha sido reemplazado por Require all granted.

mydomain.conf
     <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>
reubicación de index.php en la carpeta pública

Si creaste el proyecto con una versión anterior a la 2.4.5, tienes que modificar index.php y mover los archivos index.php y .htaccess a la carpeta public.

public/index.php
<?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

Asegúrese de que los paquetes libapache2-mod-fastcgi y php7.x-fpm están instalados (sustituya x por el número de versión de php).

Configuración php-pm:

php-pm.conf
;;;;;;;;;;;;;;;;;;;;
; 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

Configuración de Apache 2.4:

mydomain.conf
<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

Configuración de nginX:

nginx.conf
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

Configuración Swoole:

.ubiquity/swoole-config.php
<?php
return array(
    "host" => "0.0.0.0",
    "port" => 8080,
    "options"=>[
        "worker_num" => \swoole_cpu_num() * 2,
            "reactor_num" => \swoole_cpu_num() * 2
        ]
);

Workerman

Configuración Workerman:

.ubiquity/workerman-config.php
<?php
return array(
    "host" => "0.0.0.0",
    "port" => 8080,
    "socket"=>[
        "count" => 4,
        "reuseport" =>true
    ]
);

RoadRunner

Configuración RoadRunner:

.ubiquity/.rr.yml
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"]

Optimización de Ubiquity

Ubiquity es rápido, pero puede serlo aún más optimizando algunos elementos.

Nota

El servidor de pruebas integrado (accesible mediante Ubiquity serve) utiliza sus propios archivos de configuración y lanzamiento (en la carpeta .ubiquity de su proyecto).
Por lo tanto, no debe utilizarse para evaluar los resultados de los cambios realizados.

Pruebe sus páginas utilizando una configuración de software y hardware similar a la utilizada en producción.
Utiliza una herramienta de benchmark para evaluar tus cambios a medida que se producen (Apache bench por ejemplo).

Cache

Sistema

Elija y pruebe entre los diferentes sistemas de caché (ArrayCache, PhpFastCache, MemCached).
El sistema de caché se define en el archivo de configuración:

app/config/config.php
     "cache" => [
             "directory" => "cache/",
             "system" => "Ubiquity\\cache\\system\\ArrayCache",
             "params" => []
     ]

por defecto ArrayCache es a menudo la solución más optimizada.

Generación

Genera el router y la caché ORM (Piensa que las anotaciones nunca se usan en tiempo de ejecución):

Ubiquity init-cache

Contenidos estáticos

Si su aplicación tiene páginas que están siendo generadas por PHP pero que en realidad raramente cambian, puede almacenarlas en caché:

  • Los resultados de la consulta (utilizando métodos DAO)

  • La respuesta de ruta (con la anotación @route)

archivo índice

Elimine la línea que define el informe de errores en tiempo de ejecución y asegúrese de que la visualización de errores está desactivada en php.ini.

index.php
error_reporting(\E_ALL);//To be removed

Optimización de la configuración

Se puede acceder a la configuración desde el archivo app/config/config.php.

Conserve sólo los elementos esenciales para su solicitud.

key

role

Optimización

siteUrl

Utilizado por los métodos Ajax y por las funciones url y path de Twig.

Debe suprimirse si no se utilizan estas funciones

base de datos

Utilizado por Ubiquity ORM

Debe eliminarse si no se utiliza el ORM

sessionName

Si se asigna, inicia o recupera la sesión php para cada petición

Se eliminará si la sesión es inútil

templateEngine

Si se asigna, instanciará un nuevo objeto Motor para cada solicitud.

Se eliminará si no se utilizan las vistas

templateEngineOptions

Opciones asignadas a la instancia del motor de plantillas

establecer la opción de caché en true si se utiliza Twig

test

Eliminar (obsoleto)

debug

Activa o desactiva los registros

Fijar en false en producción

logger

Define la instancia del logger

Para eliminar en producción

di

Define los servicios que deben inyectarse

Sólo se lee la clave @exec en tiempo de ejecución

cache

Define la ruta de la caché y la clase base de la caché, utilizada por modelos, enrutador, inyección de dependencia

mvcNS

Define las rutas o espacios de nombres utilizados por los controladores Rest, los modelos y los controladores

isRest

Define la condición para detectar si una ruta corresponde a un controlador Rest

Se eliminará si no utiliza explícitamente esta condición en su código

Ejemplo de configuración sin sesión, y sin inyección de dependencia:

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

Optimización de los servicios

Los servicios cargados son accesibles desde el archivo app/config/services.php.

En cuanto al archivo de configuración, conserve sólo los elementos esenciales para su aplicación.

Líneas

Rol

\Ubiquity\cache\CacheManager::startProd($config)

Inicia la caché para ORM, base de datos, enrutador, inyección de dependencia

UbiquityormDAO::start()

Sólo para bases de datos múltiples

Router::start()

Sólo debe utilizarse si las rutas se definen con anotaciones

Router::addRoute(«_default», «controllers\IndexController»)

Define la ruta por defecto (a eliminar en producción)

\Ubiquity\assets\AssetsManager::start($config)

Asigna la variable siteUrl al ThemeManager, que sólo se utilizará si se usan las funciones css y js de twig.

Ejemplo de un fichero de Servicios con una base de datos y arranque del router :

app/config/services.php
1<?php
2\Ubiquity\cache\CacheManager::startProd($config);
3\Ubiquity\controllers\Router::start();

Optimización del cargador automático

En producción, elimine las dependencias utilizadas únicamente en desarrollo y genere el archivo de mapa de clases optimizado:

composer install --no-dev --classmap-authoritative

Si las dependencias utilizadas ya se han eliminado y sólo desea actualizar el archivo de mapa (después de añadir o eliminar una clase):

composer dump-autoload -o  --classmap-authoritative

Nota

El parámetro --no-dev elimina la dependencia ubiquity-dev requerida por webtools. Si utiliza webtools en producción, añada la dependencia phpmv/ubiquity-dev:

composer require phpmv/ubiquity-dev

Optimización PHP

Tenga en cuenta que otras aplicaciones pueden utilizar los valores modificados en el mismo servidor.

OP-Cache

OPcache mejora el rendimiento de PHP almacenando el código de bytes de los scripts precompilados en memoria compartida, eliminando así la necesidad de que PHP cargue y analice los scripts en cada petición.

php.ini
[opcache]
; Determines if Zend OPCache is enabled
opcache.enable=1
php.ini
; 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 utiliza el servidor web ubiquity-swoole:

php.ini
; Determines if Zend OPCache is enabled for the CLI version of PHP
opcache.enable_cli=1

Para completar

Recuerda que el framework utilizado no lo hace todo. También tienes que optimizar tu propio código.

Comandos de Ubiquity

Nota

Esta parte es accesible desde las webtools, así que si creaste tu proyecto con la opción -a o con el comando create-project..

Comandos

Desde las webtools, activa la parte de comandos,

_images/commands-elm.png

o vaya directamente a http://127.0.0.1:8090/Admin/commands.

Lista de comandos

Active la pestaña Comandos para obtener la lista de comandos devtools existentes.

_images/commands-list.png

Info de comando

Es posible obtener ayuda sobre un comando (lo que produce un resultado equivalente a Ubiquity help cmdName).

_images/command-help.png

Ejecución de comandos

Al pulsar el botón de ejecución de un comando, aparece un formulario para introducir los parámetros (o lo ejecuta directamente si no necesita ninguno).

_images/command-run.png

Tras introducir los parámetros, la ejecución produce un resultado.

_images/command-exec.png

Grupo de comandos

Volver a la pestaña Mis comandos: Es posible guardar una secuencia de comandos (con parámetros almacenados), y luego ejecutar la misma secuencia:

Creación de grupos

Haga clic en **añadir grupo de comandos*.

_images/new-suite-btn.png

Añada los comandos deseados y modifique los parámetros:

_images/new-commands-suite.png

La validación genera el grupo:

_images/commands-suite-created.png

Ejecución del grupo de comandos

Al hacer clic en el botón de ejecución del grupo, se ejecuta la lista de comandos que contiene:

_images/commands-suite-exec.png

Creación de comandos personalizados

Haga clic en el botón Crear comando devtools.

_images/create-devtools-command-btn.png

Introduzca las características del nuevo comando:

  • Nombre del comando

  • El valor del comando: nombre del argumento principal

  • Los parámetros del comando: En caso de varios parámetros, utilice la coma como separador

  • Descripción del comando

  • Alias de los comandos: En caso de varios alias, utilice una coma como separador

_images/create-command.png

Nota

Los comandos personalizados se crean en la carpeta app/commands del proyecto.

_images/custom-command-exec.png

La clase generada:

app/commands/CreateArray.php
 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}

El comando CreateArray implementado:

app/commands/CreateArray.php
 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}

Ejecución de comandos personalizados

El nuevo comando es accesible desde las devtools, siempre que esté en el proyecto:

Ubiquity help createArray
_images/custom-command-help.png
Ubiquity createArray "{\"b\":true,\"i\":5,\"s\":\"string\"}" -f=test.php
_images/custom-command-devtools.png

Gestión de composer

Nota

Esta parte es accesible desde las webtools, así que si creaste tu proyecto con la opción -a o con el comando create-project..

Acceso

Desde las herramientas web, activa la parte composer,

_images/composer-elm.png

o vaya directamente a http://127.0.0.1:8090/Admin/composer.

Lista de dependencias

La interfaz muestra la lista de dependencias ya instaladas y las que se pueden instalar directamente.

_images/composer-dependencies.png

Instalación de dependencias

Lista de dependencias enumeradas:

Haga clic en el botón añadir de las dependencias que desee añadir.

_images/composer-add-1.png

A continuación, haga clic en el botón Generar actualización del compositor:

_images/composer-add-2.png

La validación genera la actualización.

Para las dependencias no enumeradas:

Pulse el botón Añadir dependencia :

_images/composer-add-dependency.png
  • Introduzca nombre del vendor (proveedor) ;

  • Seleccione un paquete de la lista ;

  • Seleccione eventualmente una versión (si no hay ninguna, se instalará la última versión estable).

Eliminación de la dependencia

Haga clic en el botón remove de las dependencias que desee añadir.

_images/composer-remove-1.png

A continuación, haga clic en el botón Generar actualización de composer y valide la actualización.

Nota

Es posible realizar varias operaciones de adición o supresión y validarlas simultáneamente.

Optimización de composer

Haga clic en el botón Optimize autoloader.

Esto optimiza la autocarga del composer con un mapa de clase autorizado.

Caché de Ubiquity

Dependencias en Ubiquity

  • php >=7.4

  • phpmv/ubiquity => Ubiquity core

En producción

Plantillas

Twig es necesario si se utiliza como motor de plantillas, que no es un requisito.

  • twig/twig => Template engine

Seguridad

  • phpmv/ubiquity-security => Csrf, Csp…

  • phpmv/ubiquity-acl => Gestión de listas de control de acceso

En desarrollo

Webtools

  • phpmv/ubiquity-dev => clases dev para webtools y devtools desde v2.3.0

  • phpmv/php-mv-ui => Front library

  • mindplay/annotations => Biblioteca de anotaciones, necesaria para generar modelos…

  • monolog/monolog => Logging

  • czproject/git-php => Operaciones Git (+ requiere consola git)

Devtools

  • phpmv/ubiquity-devtools => Consola Cli

  • phpmv/ubiquity-dev => clases dev para webtools y devtools desde v2.3.0

  • mindplay/annotations => Biblioteca de anotaciones, necesaria para generar modelos…

Pruebas

  • codeception/codeception => Pruebas

  • codeception/c3 => Integración C3

  • phpmv/ubiquity-codeception => Codeception for Ubiquity

Módulo cliente OAuth2

Nota

Esta parte es accesible desde las webtools, por lo que si creaste tu proyecto con la opción -a o con el comando create-project. El módulo OAuth no está instalado por defecto. Utiliza la librería HybridAuth.

Instalación

En la raíz de tu proyecto:

composer require phpmv/ubiquity-oauth

Nota

También es posible añadir la dependencia ubiquity-oauth utilizando la parte Composer del módulo de administración.

_images/composer-add-1.png

Configuración de OAuth

Configuración global

_images/oauth-part-0.png

Haga clic en el botón Global configuration y modifique la URL de devolución de llamada, que corresponde a la url de devolución de llamada local tras una conexión correcta.

_images/oauth-part-callback.png

Controlador OAuth

Haga clic en el botón Create Oauth controller y asigne a la ruta el valor dado previamente a la devolución de llamada:

_images/create-oauth-controller.png

Validar y restablecer la caché del router:

_images/create-oauth-controller-created.png

Proveedores

Nota

Para una autenticación OAuth, es necesario crear previamente una aplicación en el proveedor, y tomar nota de las claves de la aplicación (id y secret).

Haga clic en el botón Añadir proveedor y seleccione Google:

_images/provider-config.png

Compruebe la conexión pulsando el botón Check:

_images/google-check.png

Publicar información de inicio de sesión:

_images/google-check-infos.png

Personalización de OAuthController

El controlador creado es el siguiente:

app/controllers/OAuthTest.php
namespace controllers;
use Hybridauth\Adapter\AdapterInterface;
/**
 * Controller OAuthTest
 */
class OAuthTest extends \Ubiquity\controllers\auth\AbstractOAuthController{

   public function index(){
   }

   /**
    * @get("oauth/{name}")
    */
   public function _oauth(string $name):void {
      parent::_oauth($name);
   }

   protected function onConnect(string $name,AdapterInterface $provider){
      //TODO
   }
}
  • El método _oauth corresponde a la url de devolución de llamada

  • El método onConnect se activa en la conexión y puede ser anulado.

Ejemplo :

  • Posible recuperación de un usuario asociado en la base de datos

  • o creación de un nuevo usuario

  • Adición del usuario conectado y redirección

app/controllers/OAuthTest.php
   protected function onConnect(string $name, AdapterInterface $provider) {
      $userProfile = $provider->getUserProfile();
      $key = md5($name . $userProfile->identifier);
      $user = DAO::getOne(User::class, 'oauth= ?', false, [
         $key
      ]);
      if (! isset($user)) {
         $user = new User();
         $user->setOauth($key);
         $user->setLogin($userProfile->displayName);
         DAO::save($user);
      }
      USession::set('activeUser', $user);
      \header('location:/');
     }

Plataformas asíncronas

Nota

Ubiquity soporta múltiples plataformas : Swoole, Workerman, RoadRunner, PHP-PM, ngx_php.

Swoole

Instala la extensión Swoole en tu sistema (linux) o en tu imagen Docker :

#!/bin/bash
pecl install swoole

Ejecute Ubiquity Swoole (por primera vez, se instalará el paquete ubiquity-swoole):

Ubiquity serve -t=swoole

Configuración del servidor

.ubiquity/swoole-config.php
<?php
return array(
    "host" => "0.0.0.0",
    "port" => 8080,
    "options"=>[
        "worker_num" => \swoole_cpu_num() * 2,
            "reactor_num" => \swoole_cpu_num() * 2
        ]
);

El puerto también puede cambiarse al iniciar el servidor:

Ubiquity serve -t=swoole -p=8999

Optimización de los servicios

El inicio de los servicios se realizará una sola vez, al arrancar el servidor.

app/config/services.php
\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();
});
El método warmUpControllers:
  • instanciar los controladores

  • realiza la inyección de dependencias

  • prepara la llamada de los métodos initialize y finalize (inicialización de las constantes de llamada)

Al inicio de cada Worker, el método warmup de los controladores puede, por ejemplo, inicializar consultas DAO preparadas:

app/controllers/FooController.php
     public static function warmup() {
             self::$oneFooDao = new DAOPreparedQueryById('models\\Foo');
             self::$allFooDao = new DAOPreparedQueryAll('models\\Foo');
     }

Workerman

Workerman no requiere ninguna instalación especial (excepto libevent para ser utilizado en producción por razones de rendimiento).

Ejecute Ubiquity Workerman (por primera vez, se instalará el paquete ubiquity-workerman):

Ubiquity serve -t=workerman

Configuración del servidor

.ubiquity/workerman-config.php
<?php
return array(
    "host" => "0.0.0.0",
    "port" => 8080,
    "socket"=>[
        "count" => 4,
        "reuseport" =>true
    ]
);

El puerto también puede cambiarse al iniciar el servidor:

Ubiquity serve -t=workerman -p=8999

Optimización de los servicios

El inicio de los servicios se realizará una sola vez, al arrancar el servidor.

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

Índices y tablas