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 Enrutador (Router) y las anotaciones/atributos en las clases de controlador permiten personalizar las URL.

Enrutador (Router)

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.

Controladores de autenticación (Auth Controllers)

Los controladores Auth permiten realizar una autentificación básica con:
  • iniciar sesión con una cuenta

  • creación de cuenta

  • cierre de sesión

  • controladores con autenticación requerida

Creación

En la interfaz de administración (web-toolsb), active la parte Controllers y seleccione crear Auth controller:

_images/speControllerBtn.png
A continuación, rellene el formulario:
  • Introduzca el nombre del controlador (BaseAuthController en este caso)

_images/createAuthForm1.png

El controlador generado:

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}

Aplicación de la autentificación

Ejemplo de implementación con la interfaz de administración : Añadiremos una comprobación de autenticación en la interfaz de administración.

La autenticación se basa en la verificación del par correo electrónico/contraseña de un modelo User:

_images/model-user.png

Modificación de BaseAuthController

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}

Modificación del controlador de administración

Modificar el controlador de administración para utilizar 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}

Pruebe la interfaz de administración en /admin:

_images/adminForbidden.png

Después de hacer clic en login:

_images/formLogin.png

Si los datos de autenticación introducidos no son válidos:

_images/invalidCreditentials.png

Si los datos de autenticación introducidos son válidos:

_images/adminWithAuth.png

Adjuntar la zona info-user

Modificar el controlador BaseAuthController:

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}

El área _userInfo está ahora presente en todas las páginas de la administración:

_images/infoUserZone.png

Puede mostrarse en cualquier plantilla twig:

{{ _userInfo | raw }}

Descripción de las características

Personalización de plantillas

plantilla index.html

La plantilla index.html gestiona la conexión:

_images/template_authIndex.png

Ejemplo con el área _userInfo:

Cree un nuevo AuthController llamado PersoAuthController:

_images/createAuthForm2.png

Editar la plantilla 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 %}

Cambiar el controlador AuthController Admin:

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

Personalización de los mensajes

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}

Conexión de autocomprobación

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}

Limitación de los intentos de conexión

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}

Recuperación de cuenta

La recuperación de cuenta se utiliza para restablecer la contraseña de la cuenta.
Se envía un correo electrónico de restablecimiento de contraseña, a una dirección de correo electrónico correspondiente a una cuenta activa.

_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

Por defecto, el enlace sólo puede utilizarse en la misma máquina, dentro de un periodo de tiempo predeterminado (que puede modificarse anulando el método accountRecoveryDuration).

Activación de MFA/2FA

La autenticación multifactor puede activarse condicionalmente, en función de la información del usuario previamente registrado.

Nota

La fase 2 de la autenticación se realiza en el siguiente ejemplo enviando un código aleatorio por correo electrónico. La clase AuthMailerClass está disponible en el paquete Ubiquity-mailer.

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

Es posible personalizar la creación del código generado, así como el prefijo utilizado. El ejemplo de abajo está implementado con la librería robthree/twofactorauth.

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

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

Creación de cuenta

La activación de la creación de la cuenta también es opcional:

_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

En este caso, debe sobrescribirse el método _create para crear la cuenta:

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

Puede comprobar la validez/disponibilidad del login antes de validar el formulario de creación de cuenta:

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

Se puede solicitar al usuario una acción de confirmación (verificación por correo electrónico):

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

Es posible personalizar estas partes anulando los métodos asociados o modificando las interfaces en las plantillas correspondientes.

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

Vistas

Ubiquity utiliza Twig como motor de plantillas por defecto (ver Documentación de Twig).
Las vistas se encuentran en la carpeta app/views. Deben tener la extensión .html para ser interpretadas por Twig.

Ubiquity también se puede utilizar con un sistema de vistas PHP, para obtener un mejor rendimiento, o simplemente para permitir el uso de php en las vistas.

Carga

Las vistas se cargan desde los controladores:

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 }

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

Carga y paso de variables

Las variables se pasan a la vista con una matriz asociativa. Cada clave crea una variable del mismo nombre en la vista.

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 }

En este caso, es útil llamar a Compact para crear un array que contenga variables y sus valores :

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 }

Mostrar en vista

A continuación, la vista puede mostrar las variables:

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

Las variables también pueden tener atributos o elementos a los que se puede acceder.

Puede utilizar un punto (.) para acceder a los atributos de una variable (métodos o propiedades de un objeto PHP, o elementos de un array PHP), o la llamada sintaxis de «subíndice» ([]):

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

Funciones adicionales de Ubiquity

La variable global app proporciona acceso a funciones predefinidas de Ubiquity Twig:

  • app es una instancia de Framework y proporciona acceso a los métodos públicos de esta clase.

Obtener la versión instalada del framework:

{{ app.version() }}

Devuelve el controlador activo y los nombres de las acciones:

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

Devolver clases envolventes globales :

Para request:

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

Para sesión

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

véase Framework class in API para más información.

Carga de vistas PHP

Desactive si es necesario Twig en el archivo de configuración borrando la clave templateEngine.

Luego crea un controlador que herede de SimpleViewController, o SimpleViewAsyncController si usas Swoole o 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

En este caso, las funciones para cargar assets y temas no son compatibles.

Assets

Los Assets corresponden a archivos javascript, hojas de estilo, fuentes, imágenes para incluir en tu aplicación.
Se ubican desde la carpeta public/assets.
Es preferible separar los recursos en subcarpetas por tipo.

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

Integración de archivos css o js :

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

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

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

CDN con parámetros adicionales:

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

Themes

Nota

Los temas son totalmente inútiles si sólo tienes una presentación que aplicar.

Ubiquity soporta temas que pueden tener sus propios activos y vistas de acuerdo a la plantilla del tema para ser renderizados por el controlador. Cada acción del controlador puede renderizar un tema específico, o pueden utilizar el tema por defecto configurado en el archivo config.php en templateEngineOptions => array("activeTheme" => "semantic").

Ubiquity se entrega con 3 temas por defecto : Bootstrap, Foundation** y Semantic-UI**.

Instalar un tema

Con devtools, ejecute :

Ubiquity install-theme bootstrap

El tema instalado es uno de bootstrap, foundation o semantic.

Con webtools, puede hacer lo mismo, siempre que las devtools estén instaladas y accesibles (carpeta Ubiquity añadida en la ruta del sistema) :

_images/themesManager-install-theme.png

Crear un nuevo tema

Con devtools, ejecute :

Ubiquity create-theme myTheme

Creación de un nuevo tema a partir de Bootstrap, Semantic…

Con devtools, ejecute :

Ubiquity create-theme myBootstrap -x=bootstrap

Con webtools :

_images/themesManager-create-theme.png

Funcionamiento y estructura del tema

Estructura

Theme view folder

Las vistas de un tema se encuentran en la carpeta app/views/themes/theme-name.

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

La clase base controlador se encarga de cargar las vistas para definir la cabecera y el pie de cada página :

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

Los assets de un tema se crean dentro de la carpeta public/assets/theme-name.

La estructura de la carpeta de assets suele ser la siguiente :

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

Cambio del tema activo

Cambio persistente

activeTheme se define en app/config/config.php con templateEngineOptions => array("activeTheme" => "semantic")

El tema activo puede cambiarse con devtools :

Ubiquity config:set --templateEngineOptions.activeTheme=bootstrap

También puede hacerse desde la página de inicio, o con webtools :

Desde la página de inicio:

_images/change-theme-home.png

Desde Webtools

_images/change-theme-webtools.png

Este cambio también puede hacerse en tiempo de ejecución :

Desde un controlador :

ThemeManager::saveActiveTheme('bootstrap');

Cambio local no persistente

Para establecer un tema específico para todas las acciones dentro de un controlador, el método más sencillo es anular el método initialize del controlador :

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     }

O si el cambio sólo debe afectar a una acción :

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     }

Cambio de tema condicional, independientemente del controlador :

Ejemplo con una modificación del tema en función de una variable pasada en la 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     });

Compatibilidad con dispositivos móviles

Añadir una herramienta de detección de dispositivos móviles.
|nstalación de MobileDetect:

composer require mobiledetect/mobiledetectlib

En general, es más fácil crear vistas diferentes por dispositivo.

Crear un tema específico para la parte móvil (creando una carpeta views/themes/mobile y poniendo en ella las vistas específicas para dispositivos móviles).
Es importante en este caso utilizar los mismos nombres de archivo para la parte móvil y la parte no móvil.

También es aconsejable en este caso que todas las cargas de vistas utilicen el espacio de nombres @activeTheme:

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

index.html debe estar disponible en este caso en las carpetas views y views/themes/mobile.

Detección global de móviles (desde 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});
Detección de configuración regional (desde un controlador)
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     }

Vista y carga de assets

Vistas

Para cargar una vista desde la carpeta activeTheme, puede utilizar el espacio de nombres @activeTheme :

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     }

Si el activeTheme es bootstrap, la vista cargada es app/views/themes/bootstrap/action.html.

DefaultView

Si sigues el modelo de nomenclatura de vistas de Ubiquity, la vista cargada por defecto para una acción en un controlador cuando un tema está activo es : app/views/themes/theme-name/controller-name/action-name.html.

Por ejemplo, si el activeTheme es bootstrap, la vista por defecto para la acción display en el controlador Users debe estar localizada en 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

Los comandos devtools para crear un controlador o una acción y su vista asociada utilizan la carpeta @activeTheme si hay un tema activo.

Ubiquity controller Users -v

Ubiquity action Users.display -v

Carga de assets

El mecanismo es el mismo que para las vistas: el espacio de nombres @activeTheme hace referencia a la carpeta public/assets/theme-name/.

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

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

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

Si el tema bootstrap está activo, la carpeta de activos es public/assets/bootstrap/.

Compilación css

Para Bootstrap o foundation, instala sass:

npm install -g sass

A continuación, ejecute desde la carpeta raíz del proyecto:

Para bootstrap:

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

Para la fundation:

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

Normalizadores (Normalizers)

Nota

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

Validadores (Validators)

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.

Módulo de traducción

Nota

El módulo Translation utiliza la clase estática TranslatorManager para gestionar las traducciones.

Estructura del módulo

Las traducciones se agrupan por dominio, dentro de una localidad :

En el directorio raíz de la traducción (por defecto app/translations):

  • Cada configuración regional corresponde a una subcarpeta.

  • Para cada configuración regional, en una subcarpeta, un dominio corresponde a un archivo php

translations
      en_EN
           messages.php
           blog.php
      fr_FR
            messages.php
            blog.php
  • cada archivo de dominio contiene una matriz asociativa de traducciones clave->valor de traducción

  • Cada clave puede asociarse a
    • una traducción

    • una traducción que contenga variables (entre % y %)

    • una matriz de traducciones para manejar la pluralización

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

Inicio del módulo

El arranque del módulo se realiza lógicamente en el archivo services.php.

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

Sin parámetros, la llamada al método start utiliza la configuración regional en_EN, sin configuración regional alternativa.

Importante

El módulo de traducciones debe iniciarse después de que se haya iniciado la caché.

Configuración regional

Cambiar la configuración regional cuando se inicia el administrador

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

Cambiar la configuración regional después de cargar el administrador:

TranslatorManager::setLocale('fr_FR');

Configuración regional alternativa

Se utilizará la configuración regional en_EN si no se encuentra es_ES:

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

Definición del directorio raíz de las traducciones

Si falta el parámetro rootDir, el directorio utilizado por defecto es app/translations.

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

Hacer una traducción

Con php

Traducción de la tecla okayBtn a la configuración regional por defecto (especificada al iniciar el gestor):

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

Sin parámetros, la llamada al método trans utiliza la configuración regional por defecto, el dominio messages.

Traducción de la clave message mediante una variable:

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

En este caso, el archivo de traducción debe contener una referencia a la variable user para la clave message:

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

En vistas de twig:

Traducción de la tecla okayBtn a la configuración regional por defecto (especificada al iniciar el gestor):

{{ t('okayBtn') }}

Traducción de la clave message mediante una variable:

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

Seguridad

Principios guía

Validación de formularios

Validación del cliente

Es preferible realizar una validación inicial en el lado del cliente para evitar enviar datos no válidos al servidor.

Ejemplo de creación de un formulario en la acción de un controlador (esta parte podría ubicarse en un servicio dedicado para una mejor separación de capas):

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 }

La Vista Asociada:

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

Nota

Los controladores CRUD integran automáticamente esta validación del lado del cliente utilizando los validadores adjuntos a los miembros de los modelos.

#[Column(name: "password",nullable: true,dbType: "varchar(255)")]
#[Validator(type: "length",constraints: ["max"=>20,"min"=>6])]
#[Transformer(name: "password")]
private $password;
Validación en el servidor

Es preferible restringir las URL autorizadas a modificar los datos.
Previamente, especificando el método Http en las rutas, y probando la petición :

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

Nota

El módulo Ubiquity-security ofrece un control adicional para evitar las peticiones cross-site.

Tras modificar un objeto, es posible comprobar su validez, dados los validadores adjuntos a los miembros del Modelo asociado:

#[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...
      }
   }
}

Operaciones DAO

Siempre se recomienda utilizar consultas parametrizadas, independientemente de las operaciones que se realicen con los datos:
  • Para evitar inyecciones SQL.

  • Permitir el uso de consultas preparadas, acelerando el procesamiento.

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

Nota

Las operaciones DAO que toman objetos como parámetros utilizan este mecanismo por defecto.

DAO::save($user);

Gestión de contraseñas

El transformador Contraseña permite que un campo sea de tipo contraseña cuando se muestra en un formulario CRUD generado automáticamente.

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

Tras el envío desde un formulario, es posible encriptar una contraseña desde la clase URequest:

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

El algoritmo utilizado en este caso está definido por el PASSWORD_DEFAULT de php.

También es posible comprobar una contraseña introducida por un usuario del mismo modo, para compararla con un hash:

if(URequest::password_verify('password', $existingPasswordHash)){
   //password is ok
}

Importante

Configure Https para evitar el envío de contraseñas en texto claro.

Módulo de seguridad/gestión ACL

Además de estas pocas reglas, puede instalar si es necesario:

Módulo de seguridad

Instalación

Instale el módulo Ubiquity-security desde la línea de comandos o desde Webtools (parte Composer).

composer require phpmv/ubiquity-security

A continuación, active la visualización de la parte Seguridad en el Webtools:

_images/display-security.png

CSRF de Sesión

La sesión está protegida por defecto contra ataques CSRF mediante la clase VerifyCsrfToken (incluso sin el módulo Ubiquity-security). |Se genera una instancia del token (CSRFToken) al iniciar la sesión. La validez del token se comprueba mediante una cookie en cada petición.

_images/security-part.png

Esta protección puede personalizarse creando una clase que implemente la 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
   }
}

Iniciar la protección personalizada en los servicios:

app/config/services.php
use Ubiquity\utils\http\session\PhpSession;
use Ubiquity\controllers\Startup;
use app\session\MyCsrfProtection;

Startup::setSessionInstance(new PhpSession(new MyCsrfProtection()));

Desactivar la protección

Si no necesita proteger su sesión contra ataques Csrf, inicie la sesión con la clase NoCsrfProtection.

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

Gestor de CSRF

El servicio CsrfManager puede iniciarse directamente desde la interfaz webtools.
Su papel es proporcionar herramientas para proteger las rutas sensibles de los ataques Csrf (los que permiten la validación de formularios por ejemplo).

_images/csrf-manager-started.png
  • El servicio se inicia en el archivo services.php.

app/config/services.php
 \Ubiquity\security\csrf\CsrfManager::start();

Ejemplo de protección de formularios:

La vista del formulario:

<form id="frm-bar" action='/submit' method='post'>
   {{ csrf('frm-bar') }}
   <input type='text' id='sensitiveData' name='sensitiveData'>
</form>

El método csrf genera un token para el formulario (Añadiendo un campo oculto en el formulario correspondiente al token).

El formulario de envío en un controlador:

use Ubiquity\security\csrf\UCsrfHttp;

#[Post('/submit')]
public function submit(){
   if(UCsrfHttp::isValidPost('frm-bar')){
      //Token is valid! => do something with post datas
   }
}

Nota

También es posible gestionar esta protección mediante cookies.

Ejemplo de protección con ajax:

El meta campo csrf-token se genera en todas las páginas.

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

Este campo se añade en el 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 %}

Ejemplo con un botón que envía datos vía ajax. El parámetro csrf se establece en true. Así que cuando se envía la solicitud, el csrf-token se envía en las cabeceras de la solicitud.

#[Get(path: "/ajax")]
public function ajax(){
   $this->jquery->postOnClick('#bt','/postAjax','{id:55}','#myResponse',['csrf'=>true]);
   $this->jquery->renderDefaultView();
}

La ruta de envío puede comprobar la presencia y validez del token:

#[Post(path: "postAjax")]
public function postAjax(){
   if(UCsrfHttp::isValidMeta('postAjax')){
      var_dump($_POST);
   }else{
      echo 'invalid or absent meta csrf-token';
   }
}

Gestor de cifrado

El servicio EncryptionManager puede iniciarse directamente desde la interfaz webtools.

  • En este caso, se genera una clave en el archivo de configuración app/config/config.php.

  • El servicio se inicia en el archivo services.php.

app/config/services.php
 \Ubiquity\security\data\EncryptionManager::start($config);

Nota

Por defecto, el cifrado se realiza en AES-128.

_images/encryption-manager-started.png

Cambiando el cifrado:

Actualización a AES-256:

app/config/services.php
\Ubiquity\security\data\EncryptionManager::startProd($config, Encryption::AES256);

Generar una nueva clave:

Ubiquity new:key 256

La nueva clave se genera en el archivo app/config/config.php.

Modelo de encriptación de datos

El transformador Crypt también puede utilizarse en los miembros de un modelo:

app/models/User.php
 class Foo{
     #[Transformer(name: "crypt")]
     private $secret;
     ...
 }

Uso:

$o=new Foo();
$o->setSecret('bar');
TransformersManager::transformInstance($o);// secret member is encrypted
Cifrado genérico de datos

Cifrado de cadenas:

$encryptedBar=EncryptionManager::encryptString('bar');

Para luego desencriptarlo:

echo EncryptionManager::decryptString($encryptedBar);

Es posible cifrar cualquier tipo de datos:

$encryptedUser=EncryptionManager::encrypt($user);

Para luego desencriptarlo, con posible serialización/deserialización si se trata de un objeto:

$user=EncryptionManager::decrypt($encryptedUser);

Gestor de políticas de seguridad de contenidos

El servicio ContentSecurityManager puede iniciarse directamente desde la interfaz webtools.

  • El servicio se inicia en el archivo services.php.

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

Con esta configuración por defecto, se añade un nonce a los scripts jquery generados con phpmv-ui. El control de CSP se realiza en el modo Report-only..

_images/csp-manager-started.png

Añadir un nonce

Ejemplo de adición de nonce en las páginas de cabecera y pie de página:

Actualización del controlador de base
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]);
             }
     }
}

Gestión de contraseñas

Tokens de usuario

Gestión de ACL

Instalación

Instale el módulo Ubiquity-acl desde el símbolo del sistema o desde Webtools (parte Composer).

composer require phpmv/ubiquity-acl

A continuación, active la visualización de la parte Acl en el Webtools:

_images/display-acl.png

Interfaz ACL en webtools:

_images/acl-part.png

Normas Acl

Las ACL se utilizan para definir el acceso a una aplicación Ubiquity. Se definen de acuerdo con los siguientes principios:

Una aplicación Ubiquity se compone de :
  • Recursos (posiblemente controladores, o acciones de estos controladores)

  • Roles, posiblemente asignados a usuarios. Cada Rol puede heredar roles padre.

  • Permisos, que corresponden a un derecho a hacer. Cada permiso tiene un nivel (representado por un valor entero).

Normas adicionales:
  • Un AclElement (Allow) concede Permiso a un Rol sobre un Recurso.

  • Cada rol hereda autorizaciones de sus padres, además de las suyas propias.

  • Si un rol tiene un determinado nivel de permiso de acceso sobre un recurso, también tendrá todos los permisos de un nivel inferior sobre ese recurso.

  • La asociación de un recurso y un permiso a un controlador o a una acción de controlador define un elemento map.

_images/acl-diagram.png
Consejos para poner nombres:
  • Rol, en mayúsculas, empezando por una arroba (@USER, @ADMIN, @ALL…).

  • Permisos, en mayúsculas, nombrados con un verbo (READ, WRITE, OPEN…).

  • Recurso, con mayúscula inicial (Products, Customers…)

Inicio de ACL

El servicio AclManager puede iniciarse directamente desde la interfaz webtools, en la parte Security.

  • El servicio se inicia en el archivo services.php.

app/config/services.php
 \Ubiquity\security\acl\AclManager::startWithCacheProvider();

ACLCacheProvider

Este proveedor predeterminado permite gestionar ACLs definidas mediante atributos o anotaciones.

AclController

Un AclController permite la gestión automática del acceso basado en ACLs a sus propios recursos.
Es posible crearlas automáticamente desde webtools.

_images/new-acl-controller.png

Pero es sólo un controlador básico, utilizando la característica AclControllerTrait.

Este controlador sólo va a redefinir el método _getRole, para que devuelva el rol del usuario activo, por ejemplo.

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!';
   }
}
Se ha concedido autorización para el recurso:
  • Sin especificar el recurso, las acciones del controlador se definen como un recurso.

  • Sin especificar el permiso, se utiliza el permiso ALL.

_images/me-allow.png

Y esta asociación está presente en el mapa de Acls:

_images/me-map.png
AclController con autenticación

Nota

El uso tanto de WithAuthTrait como de AclControllerTrait requiere eliminar la ambigüedad sobre el método isValid.

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);
   }
}
Permitir con función, recurso y permiso

Permitir sin creación previa:

@USER puede acceder al recurso Foo con permiso READ.

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

El rol, el recurso y el permiso se crean automáticamente en cuanto se invocan con Allow.

Permitir con creación explícita:

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';
   }
}
Añadir ACL en tiempo de ejecución

Ya sea en un controlador o en un servicio, es posible añadir Roles, Recursos, Permisos y Autorizaciones en tiempo de ejecución:

Por ejemplo: Añadir un rol @USER que herede de @GUEST.

use Ubiquity\security\acl\AclManager;

AclManager::addRole('@GUEST');
AclManager::addRole('@USER',['@GUEST']);
Definición de ACL con base de datos

Las ACLs definidas en la base de datos son adicionales a las ACLs definidas mediante anotaciones o atributos.

Inicialización

La inicialización permite crear las tablas asociadas a las ACLs (Role, Resource, Permission, AclElement). Debe realizarse una sola vez, y únicamente en modo dev.

Para colocar por ejemplo en el archivo app/config/bootstrap.php :

use Ubiquity\controllers\Startup;
use Ubiquity\security\acl\AclManager;

$config=Startup::$config;
AclManager::initializeDAOProvider($config, 'default');

Comenzando

En el archivo app/config/services.php :

use Ubiquity\security\acl\AclManager;
use Ubiquity\security\acl\persistence\AclCacheProvider;
use Ubiquity\security\acl\persistence\AclDAOProvider;
use Ubiquity\orm\DAO;

DAO::start();//Optional, to use only if dbOffset is not default

AclManager::start();
AclManager::initFromProviders([
    new AclCacheProvider(), new AclDAOProvider($config)
]);

Estrategias para definir ACLs

Con pocos recursos:

Definición de autorizaciones para cada acción o grupo de acciones del controlador:

Los recursos corresponden lógicamente a los controladores, y los permisos a las acciones. Pero esta regla puede no respetarse, y una acción puede definirse como un recurso, según sea necesario.

La única regla obligatoria es que un par Controlador/acción sólo puede corresponder a un par Recurso/permiso (no necesariamente único).

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!';
   }

}

Con más recursos:

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.

The base controller RestController is not standardized, it should be considered as an example for data interrogation.

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->setAllowedOrigin('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->setAllowedOrigins(['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 le permiten gestionar una aplicación Ubiquity a través de una interfaz web. Desde Ubiquity 2.2.0, las webtools están en un repositorio separado.

Instalación

Actualiza las devtools si es necesario para empezar:

composer global update

En la creación del proyecto

Crear un proyecto con webtools (opción -a)

Ubiquity new quick-start -a

En un proyecto existente

En una consola, vaya a la carpeta del proyecto y ejecútelo:

Ubiquity admin

Comenzando

Inicie el servidor web integrado, desde la carpeta del proyecto:

Ubiquity serve

diríjase a la dirección http://127.0.0.1:8090/Admin

_images/interface.png

Personalización

Haz clic en customize para mostrar sólo las herramientas que utilizas:

_images/customizing.png _images/customized.png

Módulos Webtools

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:

  • Crear un controlador (y opcionalmente la vista asociada a la acción index por defecto)

  • Crear una acción en un controlador (opcionalmente la vista asociada, la ruta asociada)

  • Crear un controlador especial (CRUD o Auth)

  • Probar una acción (GET, POST…)

Modelos

_images/models.png

Muestra los metadatos de los modelos, permite navegar por las entidades.

Operations:

  • Crear modelos a partir de la base de datos

  • Generar caché de modelos

  • Generar script de base de datos a partir de modelos existentes

  • Realiza operaciones CRUD en los modelos

Rest

_images/rest.png

Muestra y gestiona servicios REST.

Operations:

  • Reiniciar la caché Rest y las rutas

  • Crear un nuevo Servicio (utilizando una api)

  • Crear un nuevo recurso (asociado a un modelo)

  • Prueba y consulta de un servicio web mediante métodos http

  • Realiza operaciones CRUD en los modelos

Cache

_images/cache.png

Muestra los archivos de caché.

Operations:

  • Borrar o reinicializar la caché de modelos

  • Borrar o reinicializar la caché de los controladores

  • Borrar otros archivos de caché

Mantenimiento

_images/maintenance.png

Permite gestionar los modos de mantenimiento.

Operations:

  • Crear o actualizar un modo de mantenimiento

  • Desactivar/activar un modo de mantenimiento

  • Borrar un modo de mantenimiento

Config

_images/config.png

Permite visualizar y modificar la configuración de la aplicación.

Git

_images/git.png

Sincroniza el proyecto utilizando git.

Operations:

  • Configuración con repositorios externos

  • Commit

  • Push

  • Pull

_images/themes.png

Gestiona temas Css.

Operations:

  • Instalar un tema existente

  • Activar un tema

  • Crear un nuevo tema (eventualmente basado en un tema existente)

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