Tutorial - Vökuró

Vökuró
Vökuró es una aplicación de ejemplo, que muestra una aplicación web típica escrita en Phalcon. Esta aplicación se centra en: - Inicio de Sesión de Usuario (seguridad) - Registro de Usuario (seguridad) - Permisos de Usuario - Gestión de Usuarios
NOTA: Puede usar Vökuró como punto de partida para su aplicación y mejorarla aún más para cumplir con sus necesidades. No significa que ésta sea una aplicación perfecta y se ajuste a todas las necesidades.
NOTA: Este tutorial asume que está familiarizado con los conceptos del patrón de diseño Modelo Vista Controlador. (ver Referencias al final de este tutorial)
NOTA: Tenga en cuenta que el código siguiente se ha formateado para aumentar la legibilidad
Instalación
Descarga
Para poder instalar la aplicación, puede clonarla o descargarla desde GitHub. Puede visitar la página de GitHub, descargar la aplicación y luego descomprimirla a un directorio de su máquina. Alternativamente, puede usar git clone
:
git clone https://github.com/phalcon/vokuro
Extensiones
Hay algunos prerrequisitos para que Vökuró funcione. Necesitará tener instalado PHP >= 7.2 en su máquina y las siguientes extensiones: - ctype - curl - dom - json - iconv - mbstring - memcached - opcache - openssl - pdo - pdo_mysql - psr - session - simplexml - xml - xmlwriter
Se necesita instalar Phalcon. Diríjase a la página instalación si necesita ayuda con la instalación de Phalcon. Tenga en cuenta que Phalcon v4 requiere tener instalada la extensión PSR y cargada antes que Phalcon. Para instalar PSR puede consultar la página de GitHub php-psr.
Finalmente, también necesitará asegurarse que ha actualizado los paquetes de composer (ver sección más abajo).
Ejecutar
Si todos los requerimientos anteriores son satisfechos, puede ejecutar la aplicación usando el servidor web integrado de PHP ejecutando el siguiente comando en un terminal:
php -S localhost:8080 -t public/ .htrouter.php
El comando anterior empezará a servir el sitio para localhost
en el puerto 8080
. Puede cambiar estos ajustes para que cumplan sus necesidades. Alternativamente, puede configurar su sitio en Apache o nginX usando un virtual host. Por favor, consulte la documentación correspondiente de cómo configurar un virtual host para estos servidores web.
Docker
En la carpeta resources
encontrará un Dockerfile
que le permite configurar rápidamente el entorno y ejecutar la aplicación. Para usar el Dockerfile
necesitamos decidir el nombre de nuestra aplicación dockerizada. Para los propósitos de este tutorial, usaremos phalcon-tutorial-vokuro
.
Desde la raíz de la aplicación necesitamos compilar el proyecto (sólo necesita hacer esto una vez):
$ docker build -t phalcon-tutorial-vokuro -f docker/Dockerfile .
y ejecutarla
$ docker run -it --rm phalcon-tutorial-vokuro bash
Esto nos introducirá en el entorno dockerizado. Para comprobar la versión PHP:
root@c7b43060b115:/code $ php -v
PHP 7.3.9 (cli) (built: Sep 12 2019 10:08:33) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.9, Copyright (c) 1998-2018 Zend Technologies
with Zend OPcache v7.3.9, Copyright (c) 1999-2018, by Zend Technologies
y Phalcon:
root@c7b43060b115:/code $ php -r 'echo Phalcon\Version::get();'
4.0.0
Ahora tiene un entorno dockerizado con todo los componentes necesarios para ejecutar Vökuró.
Nanobox
En la carpeta resources
también encontrará un fichero boxfile.yml
que le permite usar nanobox para configurar el entorno rápidamente. Todo lo que tiene que hacer es copiar el fichero en la raíz de su directorio y ejecutar nanobox run php-server
. Una vez que la aplicación está configurada por primera vez, será capaz de navegar a la dirección IP presentada en pantalla y trabajar con la aplicación.
Para más información de cómo configurar nanobox, consulte la página [Entornos Nanobox][environments-nanobox] así como la página Nanobox Guides
NOTA: En este tutorial, asumimos que su aplicación se ha descargado o clonado en un directorio llamado vokuro
.
Estructura
Buscando en la estructura de la aplicación tenemos lo siguiente:
vokuro/
.ci
configs
db
migrations
seeds
public
resources
src
Controllers
Forms
Models
Phalcon
Plugins
Providers
tests
themes
vokuro
var
cache
acl
metaData
session
volt
logs
vendor
Directorio |
Descripción |
.ci |
Ficheros necesarios para configurar los servicios del CI |
configs |
Ficheros de configuración |
db |
Guarda las migraciones de la base de datos |
public |
Punto de entrada para la aplicación, css, js, imágenes |
resources |
Ficheros Docker/nanobox para configurar la aplicación |
src |
Donde reside la aplicación (controladores, formularios, etc.) |
src/Controllers |
Controladores |
src/Forms |
Formularios |
src/Models |
Modelos de la Base de Datos |
src/Plugins |
Plugins |
src/Providers |
Proveedores: configuración de servicios en el contenedor DI |
tests |
Pruebas |
themes |
Temas/vistas para una fácil personalización |
themes/vokuro |
Tema predeterminado para la aplicación |
var |
Varios ficheros de soporte |
var/cache |
Ficheros de caché |
var/logs |
Logs |
vendor |
Librerías basadas en vendor/composer |
Configuración
.env
Vökuró usa la librería popular Dotenv de Vance Lucas. La librería usa un fichero .env
ubicado en su carpeta raíz, que contiene parámetros de configuración como el nombre del servidor de base de datos, nombre de usuario, contraseña, etc. Hay un fichero .env.example
que viene con Vökuró que puede copiar y renombrar a .env
y luego editarlo para que coincida con su entorno. Necesita hacer esto primero para que su aplicación pueda funcionar correctamente.
Las opciones disponibles son:
Opción |
Descripción |
APP_CRYPT_SALT |
Cadena aleatoria y larga que se usa por el componente Phalcon\Crypt para producir contraseñas y cualquier otra característica de seguridad adicional |
APP_BASE_URI |
Usualmente / si su servidor web apunta directamente al directorio Vökuró. Si tiene instalado Vökuró en un subdirectorio, puede ajustar la URI base |
APP_PUBLIC_URL |
La URL pública de la aplicación. Se usa para los emails. |
DB_ADAPTER |
El adaptador de base de datos. Los adaptadores disponibles son: mysql , pgsql , sqlite . Por favor, asegúrese de que tiene instaladas en su sistema las extensiones correspondientes para la base de datos. |
DB_HOST |
El servidor de base de datos |
DB_PORT |
El puerto de la base de datos |
DB_USERNAME |
El nombre de usuario de la base de datos |
DB_PASSWORD |
La contraseña de la base de datos |
DB_NAME |
El nombre de la base de datos |
MAIL_FROM_NAME |
El nombre DE al enviar emails |
MAIL_FROM_EMAIL |
El email DE al enviar emails |
MAIL_SMTP_SERVER |
El servidor SMTP |
MAIL_SMTP_PORT |
El puerto SMTP |
MAIL_SMTP_SECURITY |
La seguridad SMTP (ej. tls ) |
MAIL_SMTP_USERNAME |
El nombre de usuario del SMTP |
MAIL_SMTP_PASSWORD |
La contraseña del SMTP |
CODECEPTION_URL |
El servidor Codeception para pruebas. Si ejecuta las pruebas localmente este debería ser 127.0.0.1 |
CODECEPTION_PORT |
El puerto Codeception |
Una vez que el fichero de configuración está en su lugar, al visitar la dirección IP se presentará en pantalla algo similar a esto:

Base de Datos
También necesita inicializar la base de datos. Vökuró usa la popular librería Phinx de Rob Morgan (ahora la Cake Foundation). La librería usa su propio fichero de configuración (phinx.php
), pero para Vökuró no necesita realizar ningún ajuste ya que phinx.php
lee el fichero .env
para recuperar los ajustes de configuración. Esto le permite establecer sus parámetros de configuración en un solo lugar.
Ahora necesitaremos ejecutar las migraciones. Para comprobar el estado de nuestra base de datos:
/app $ ./vendor/bin/phinx status
Verá la siguiente pantalla:

Para inicializar la base de datos necesita ejecutar las migraciones:
/app $ ./vendor/bin/phinx migrate
La pantalla mostrará la operación:

Y el comando status
ahora mostrará todo verde:

Configuración
acl.php
Buscando en la carpeta config/
, observará cuatro ficheros. No hay necesidad de cambiar estos ficheros para iniciar la aplicación, pero si desea personalizarla, éste es el lugar a visitar. El fichero acl.php
devuelve un vector de rutas que controlan qué rutas son visibles sólo para usuarios conectados.
La configuración actual requerirá que un usuario se conecte, si visita estas rutas:
users/index
users/search
users/edit
users/create
users/delete
users/changePassword
profiles/index
profiles/search
profiles/edit
profiles/create
profiles/delete
permissions/index
Si usa Vökuró como punto de partida para su propia aplicación, necesitará modificar este fichero para añadir o quitar rutas, para asegurarse de que sus rutas protegidas estén detrás del mecanismo de inicio de sesión.
NOTA: Mantener las rutas privadas en un vector es eficiente y fácil de mantener para una aplicación pequeña o mediana. Una vez su aplicación empieza a crecer, podría necesitar considerar una técnica diferente para mantener sus rutas privadas como la base de datos con un mecanismo de caché.
config.php
Este fichero mantiene todos los parámetros de configuración que necesita Vökuró. Normalmente no necesitará cambiar este fichero, ya que los elementos del vector están configurados en el fichero .env
y Dotenv. Sin embargo, podría querer cambiar la localización de sus logs u otras rutas, si decide cambiar la estructura de directorios.
Uno de los elementos que podría querer considerar cuando trabaje con Vökuró en su máquina local es useMail
y establecerlo a false
. Esto indicará a Vökuró que no intente conectar a un servidor de correo y enviar un email cuando un usuario se registre en el sitio.
providers.php
Este fichero contiene todos los proveedores que necesita Vökuró. Esto es una lista de clases de la aplicación, que registra la clase particular en el contenedor DI. Si necesita registrar nuevos componentes en su contenedor DI, puede añadirlos al vector de este fichero.
routes.php
Este fichero contiene las rutas que Vökuró entiende. El enrutador ya registra las rutas predeterminadas, por lo que cualquier ruta definida en routes.php
es específica. Puede añadir cualquier ruta no estándar que necesite, al personalizar Vökuró, en este fichero. Como recordatorio, las rutas predeterminadas son:
/:controller/:action/:parameters
Proveedores
Como se ha mencionado antes, Vökuró usa clases llamadas Proveedores para registrar servicios en el contenedor DI. Esto sólo es una forma de registrar servicios en el contenedor DI, nada le impide poner todos estos registros en un solo fichero.
Para Vökuró decidimos usar un fichero por servicio así como un providers.php
(ver arriba) como vector de configuración de registro para estos servicios. Esto nos permite tener trozos de código mucho más pequeños, organizados en ficheros separados por servicio, así como un vector que nos permite registrar o desregistrar/desactivar un servicio sin eliminar ficheros. Todo lo que necesitamos hacer es cambiar el vector providers.php
.
Las clases proveedor se encuentran en src/Providers
. Cada una de las clases proveedor implementa el interfaz Phalcon\Di\ServiceProviderInterface. Para más información, ver la sección de arranque a continuación.
Composer
Vökuró usa composer para descargar e instalar librerías PHP adicionales. Las librerías usadas son:
Mirando en composer.json
los paquetes requeridos son:
"require": {
"php": ">=7.2",
"ext-openssl": "*",
"ext-phalcon": "~4.0.0-beta.2",
"robmorgan/phinx": "^0.11.1",
"swiftmailer/swiftmailer": "^5.4",
"vlucas/phpdotenv": "^3.4"
}
Si es una instalación nueva puede ejecutar
o si desea actualizar las instalaciones existentes de los paquetes anteriores:
Para más información sobre composer, puede visitar su página de documentación.
Arranque
Entrada
El punto de entrada a nuestra aplicación es public/index.php
. Este fichero contiene el código necesario que inicia la aplicación y la ejecuta. También sirve como punto único de entrada de nuestra aplicación, haciendo las cosas mucho más fáciles para nosotros cuando queremos capturar errores, proteger ficheros, etc.
Veamos el código:
<?php
use Vokuro\Application as VokuroApplication;
error_reporting(E_ALL);
$rootPath = dirname(__DIR__);
try {
require_once $rootPath . '/vendor/autoload.php';
/**
* Load .env configurations
*/
Dotenv\Dotenv::create($rootPath)->load();
/**
* Run Vökuró!
*/
echo (new VokuroApplication($rootPath))->run();
} catch (Exception $e) {
echo $e->getMessage(), '<br>';
echo nl2br(htmlentities($e->getTraceAsString()));
}
En primer lugar, nos aseguramos que tenemos el reporte de errores al máximo. Por supuesto, puede cambiar esto si lo desea, o sustituir el código donde se controla el reporte de errores por una entrada en su fichero .env
.
Un bloque try
/catch
envuelve todas las operaciones. Esto asegura que todos los errores sean capturados y mostrados por pantalla.
NOTA Necesitará sustituir el código para mejorar la seguridad. Actualmente, si ocurre algún error con la base de datos, el código catch
mostrará en pantalla las credenciales de la base de datos con la excepción. Este código está pensado como un tutorial, no como una aplicación en producción a escala completa
Nos aseguramos que tenemos acceso a todas las librerías de soporte cargando el autocargador de composer. En composer.json
también tenemos definida la entrada autoload
, dirigiendo al autocargador a cargar cualquier clase con espacio de nombres Vokuro
desde la carpeta src
.
"autoload": {
"psr-4": {
"Vokuro\\": "app/"
},
"files": [
"app/Helpers.php"
]
}
Entonces cargaremos las variables de entorno definidas en nuestro fichero .env
llamando
Dotenv\Dotenv::create($rootPath)->load();
Finalmente, ejecutamos nuestra aplicación.
Aplicación
Toda la lógica de aplicación está envuelta en la clase Vokuro\Application
. Veamos como se hace esto:
<?php
declare(strict_types=1);
namespace Vokuro;
use Exception;
use Phalcon\Application\AbstractApplication;
use Phalcon\Di\DiInterface;
use Phalcon\Di\FactoryDefault;
use Phalcon\Di\ServiceProviderInterface;
use Phalcon\Mvc\Application as MvcApplication;
/**
* Vökuró Application
*/
class Application
{
const APPLICATION_PROVIDER = 'bootstrap';
/**
* @var AbstractApplication
*/
protected $app;
/**
* @var DiInterface
*/
protected $di;
/**
* Project root path
*
* @var string
*/
protected $rootPath;
/**
* @param string $rootPath
*
* @throws Exception
*/
public function __construct(string $rootPath)
{
$this->di = new FactoryDefault();
$this->app = $this->createApplication();
$this->rootPath = $rootPath;
$this->di->setShared(self::APPLICATION_PROVIDER, $this);
$this->initializeProviders();
}
/**
* Run Vökuró Application
*
* @return string
* @throws Exception
*/
public function run(): string
{
return (string) $this
->app
->handle($_SERVER['REQUEST_URI'])
->getContent()
;
}
/**
* Get Project root path
*
* @return string
*/
public function getRootPath(): string
{
return $this->rootPath;
}
/**
* @return AbstractApplication
*/
protected function createApplication(): AbstractApplication
{
return new MvcApplication($this->di);
}
/**
* @throws Exception
*/
protected function initializeProviders(): void
{
$filename = $this->rootPath
. '/configs/providers.php';
if (!file_exists($filename) || !is_readable($filename)) {
throw new Exception(
'File providers.php does not exist or is not readable.'
);
}
$providers = include_once $filename;
foreach ($providers as $providerClass) {
/** @var ServiceProviderInterface $provider */
$provider = new $providerClass;
$provider->register($this->di);
}
}
}
El constructor de la clase crea primero un nuevo contenedor DI y lo almacena en una propiedad local. Estamos usando Phalcon\Di\FactoryDefault, que tiene muchos servicios ya registrados por nosotros.
Entonces creamos un nuevo Phalcon\Mvc\Application y lo almacenamos también en una propiedad. También almacenamos la ruta raíz porque es útil a lo largo de la aplicación.
Luego registramos est clase (Vokuro\Application
) en el contenedor Di usando el nombre bootstrap
. Esto nos permite tener acceso a esta clase desde cualquier parte de nuestra aplicación a través del contenedor Di.
Lo último que hacemos es registrar todos los proveedores. Aunque el objeto Phalcon\Di\FactoryDefault tiene muchos servicios ya registrados para nosotros, todavía necesitamos registrar proveedores que cubran las necesidades de nuestra aplicación. Como hemos mencionado anteriormente, cada clase proveedor implementa el interfaz Phalcon\Di\ServiceProviderInterface, por lo que podemos cargar cada clase y llamar al método register()
con el contenedor Di para registrar cada servicio. Por lo tanto, primero cargamos el vector de configuración config/providers.php
y luego iteramos sobre las entradas y registramos cada proveedor por orden.
Los proveedores disponibles son:
Proveedor |
Descripción |
AclProvider |
Permisos |
AuthProvider |
Autenticación |
ConfigProvider |
Valores de configuración |
CryptProvider |
Encriptación |
DbProvider |
Acceso a base de datos |
DispatcherProvider |
Despachador - qué controlador llamar para qué URL |
FlashProvider |
Mensajes flash para retroalimentar al usuario |
LoggerProvider |
Registrador de errores y otra información |
MailProvider |
Soporte de email |
ModelsMetadataProvider |
Metadatos para modelos |
RouterProvider |
Rutas |
SecurityProvider |
Seguridad |
SessionBagProvider |
Datos de sesión |
SessionProvider |
Datos de sesión |
UrlProvider |
Gestión de URL |
ViewProvider |
Vistas y motor de vistas |
run()
ahora gestionará REQUEST_URI
, y devolverá el contenido de vuelta. Internamente, la aplicación calculará la ruta basándose en la petición, y despachará el controlador y vista correspondientes, antes de devolver el resultado de esta operación de vuelta al usuario como respuesta.
Base de Datos
Como hemos mencionado anteriormente, Vökuró se puede instalar con MariaDB/MySQL/Aurora, PostgreSql o SQLite como almacén de base de datos. Para los propósitos de este tutorial, usamos MariaDB. Las tablas que usa la aplicación son:
Tabla |
Descripción |
email_confirmations |
Confirmaciones de email de registro |
failed_logins |
Intentos de inicio de sesión fallidos |
password_changes |
Cuando cambia una contraseña y por quién |
permissions |
Vector de permisos |
phinxlog |
Tabla de migraciones de Phinx |
profiles |
Perfil de cada usuario |
remember_tokens |
Tokens de la funcionalidad Recuérdame |
reset_passwords |
Tabla de tokens de reseteo de contraseñas |
success_logins |
Intentos de inicio de sesión correctos |
users |
Usuarios |
Modelos
Siguiendo el patrón Modelo-Vista-Controlador, Vökuró tiene un modelo por tabla de base de datos (excluyendo phinxlog
). Los modelos nos permiten interactuar con las tablas de la base de datos de una forma orientada a objetos sencilla. Los modelos se localizan en el directorio /src/Models
, y cada modelo define los campos relevantes, tabla origen así como cualquier relación entre este modelo y los otros. Algunos modelos también implementan reglas de validación para asegurarse que los datos se almacenan correctamente en la base de datos.
<?php
declare(strict_types=1);
namespace Vokuro\Models;
use Phalcon\Mvc\Model;
/**
* SuccessLogins
*
* This model registers successfully logins registered users have made
*/
class SuccessLogins extends Model
{
/**
* @var integer
*/
public $id;
/**
* @var integer
*/
public $usersId;
/**
* @var string
*/
public $ipAddress;
/**
* @var string
*/
public $userAgent;
public function initialize()
{
$this->belongsTo(
'usersId',
Users::class,
'id',
[
'alias' => 'user',
]
);
}
}
En el modelo anterior, hemos definido todos los campos de la tabla como propiedades públicas para un acceso fácil:
echo $successLogin->ipAddress;
NOTA: Si se ha dado cuenta, los nombres de propiedad mapean exactamente las mayúsculas/minúsculas de los nombres de campo en la tabla correspondiente.
En el método initialize()
, también definimos las relaciones entre este modelo y el modelo Users
. Asignamos los campos (local/remoto) así como un alias
para esta relación. Por lo tanto, ahora podemos acceder al usuario relacionado con un registro de este modelo como sigue:
echo $successLogin->user->name;
NOTA: Es libre de abrir cada fichero de modelo e identificar las relaciones entre modelos. Consulta nuestra documentación para la diferencia entre los distintos tipos de relaciones
Controladores
Otra vez siguiendo el patrón Modelo-Vista-Controlador, Vökuro tiene un controlador para gestionar una ruta padre específica. Esto significa que AboutController
gestiona la ruta /about
. Todos los controladores se localizan en el directorio /src/Cotnrollers
.
El controlador predeterminado es IndexController
. Todas las clases controlador tienen el sufijo Controller
. Cada controlador tiene métodos con el sufijo Action
y la acción predeterminada es indexAction
. Por lo tanto, si visita el sitio con sólo la URL, se llamará IndexController
y se ejecutará indexAction
.
Después de eso, a no ser que tenga rutas específicas registradas, las rutas predeterminadas (automáticamente registradas) intentarán encajar:
a
/src/Controllers/ProfilesController.php -> searchAction
Los controladores, acciones y rutas disponibles para Vökuró son:
Controlador |
Acción |
Ruta |
Descripción |
About |
index |
/about |
Muestra la página about |
Índice |
index |
/ |
Acción predeterminada - página de inicio |
Permissions |
index |
/permissions |
Ver/cambiar permisos para un nivel perfil |
Privacy |
index |
/privacy |
Ver la página de privacidad |
Profiles |
index |
/profiles |
Ver página predeterminada de perfiles |
Profiles |
create |
/profiles/create |
Crear perfil |
Profiles |
delete |
/profiles/delete |
Eliminar perfil |
Profiles |
edit |
/profiles/edit |
Editar perfil |
Profiles |
search |
/profiles/search |
Buscar perfiles |
Session |
index |
/session |
Acción predeterminada de sesión |
Session |
forgotPassword |
/session/forgotPassword |
Ha olvidado la contraseña |
Session |
login |
/session/login |
Inicio de sesión |
Session |
logout |
/session/logout |
Cerrar sesión |
Session |
signup |
/session/signup |
Registro |
Terms |
index |
/terms |
Ver la página de términos |
UserControl |
confirmEmail |
/confirm |
Confirmar email |
UserControl |
resetPassword |
/reset-password |
Resetear contraseña |
Users |
index |
/users |
Pantalla predeterminada de usuarios |
Users |
changePassword |
/users/changePassword |
Cambiar contraseña de usuario |
Users |
create |
/users/create |
Crear usuario |
Users |
delete |
/users/delete |
Eliminar usuario |
Users |
edit |
/users/edit |
Editar usuario |
Vistas
El último elemento del patrón Modelo-Vista-Controlador son las vistas. Vökuró usa Volt como el motor de vista para sus vistas.
NOTA: Generalmente, uno esperaría ver una carpeta views
bajo la carpeta /src
. Vökuró usa un enfoque diferente, almacenando todas los ficheros de las vistas bajo /themes/vokuro
.
El directorio de vistas contiene directorios que mapean a cada controlador. Dentro de cada uno de esos directorios, se mapean ficheros .volt
a cada acción. Así por ejemplo la ruta:
mapea a:
ProfilesController -> createAction
y la vista se localiza en:
/themes/vokuro/profiles/create.volt
Las vistas disponibles son:
Controlador |
Acción |
Vistas |
Descripción |
About |
index |
/about/index.volt |
Muestra la página about |
Índice |
index |
/index/index.volt |
Acción predeterminada - página de inicio |
Permissions |
index |
/permissions/index.volt |
Ver/cambiar permisos para un nivel perfil |
Privacy |
index |
/privacy/index.volt |
Ver la página de privacidad |
Profiles |
index |
/profiles/index.volt |
Ver página predeterminada de perfiles |
Profiles |
create |
/profiles/create.volt |
Crear perfil |
Profiles |
delete |
/profiles/delete.volt |
Eliminar perfil |
Profiles |
edit |
/profiles/edit.volt |
Editar perfil |
Profiles |
search |
/profiles/search.volt |
Buscar perfiles |
Session |
index |
/session/index.volt |
Acción predeterminada de sesión |
Session |
forgotPassword |
/session/forgotPassword.volt |
Ha olvidado la contraseña |
Session |
login |
/session/login.volt |
Inicio de sesión |
Session |
logout |
/session/logout.volt |
Logout |
Session |
signup |
/session/signup.volt |
Registro |
Terms |
index |
/terms/index.volt |
Ver la página de términos |
Users |
index |
/users/index.volt |
Pantalla predeterminada de usuarios |
Users |
changePassword |
/users/changePassword.volt |
Cambiar contraseña de usuario |
Users |
create |
/users/create.volt |
Crear usuario |
Users |
delete |
/users/delete.volt |
Eliminar usuario |
Users |
edit |
/users/edit.volt |
Editar usuario |
El fichero /index.volt
contiene el diseño principal de la página, incluyendo hojas de estilo, referencias javascript, etc. El directorio /layouts
contiene diferentes diseños que se usan en la aplicación, por ejemplo uno public
si el usuario no está conectado, y uno private
para los usuarios conectados. Las vistas individuales se inyectan en los diseños y construyen la página final.
Componentes
Hay varios componentes que usamos en Vökuró, ofreciendo funcionalidad a lo largo de la aplicación. Todos estos componentes se localizan en el directorio /src/Plugins
.
Acl
Vokuro\Plugins\Acl\Acl
es un componente que implementa una Lista de Control de Acceso para nuestra aplicación. La ACL controla qué usuario tiene acceso a qué recurso. Puede leer más sobre ACL en nuestra página dedicada.
En este componente, definimos los recursos que son considerados privados. Estos se mantienen en un vector interno con el controlador como clave y la acción como el valor, e identificamos qué controlador/acción requiere autenticación. Eso también mantiene descripciones legibles para humanos en las acciones usadas a través de la aplicación.
El componente expone los siguientes métodos:
Método |
Devuelve |
Descripción |
getActionDescription($action) |
string |
Devuelve la descripción de la acción según su nombre simplificado |
getAcl() |
Objeto ACL |
Devuelve la lista ACL |
getPermissions(Profiles $profile) |
array |
Devuelve los permisos asignados a un perfil |
getResources() |
array |
Devuelve todos los recursos y sus acciones disponibles |
isAllowed($profile, $controller, $action) |
bool |
Comprueba si el perfil actual tiene permitido acceder a un recurso |
isPrivate($controllerName) |
bool |
Comprueba si un controlador es privado o no |
rebuild() |
Objeto ACL |
Reconstruye la lista de acceso en un fichero |
Auth
Vokuro\Plugins\Auth\Auth
es un componente que gestiona la autenticación y ofrece gestión de identidad en Vökuró.
El componente expone los siguientes métodos:
Método |
Descripción |
check($credentials) |
Comprueba las credenciales de usuario |
saveSuccessLogin($user) |
Crea la configuración del entorno recuérdame, las cookies relacionadas y genera tokens |
registerUserThrottling($userId) |
Implementa el bloqueo de acceso. Reduce la efectividad de ataques de fuerza bruta |
createRememberEnvironment(Users $user) |
Crea la configuración del entorno recuérdame, las cookies relacionadas y genera tokens |
hasRememberMe(): bool |
Comprueba si la sesión tiene una cookie recuérdame |
loginWithRememberMe(): Response |
Inicia sesión utilizando la información en las cookies |
checkUserFlags(Users $user) |
Comprueba si el usuario está baneado/inactivo/suspenddo |
getIdentity(): array / null |
Devuelve la identidad actual |
getName(): string |
Devuelve el nombre del usuario |
remove() |
Elimina la información de identidad del usuario de la sesión |
authUserById($id) |
Autentica al usuario por su id |
getUser(): Users |
Obtiene la entidad relacionada con el usuario de la identidad activa |
findFirstByToken($token): int / null |
Devuelve el usuario del token actual |
deleteToken(int $userId) |
Elimina el token del usuario actual de la sesión |
Mail
Vokuro\Plugins\Mail\Mail
es un envoltorio de Swift Mailer. Expone dos métodos send()
y getTemplate()
que le permite obtener una plantilla desde las vistas y rellenarla con datos. El HTML resultante se puede usar en el método send()
junto con el destinatario y otros parámetros para enviar el mensaje de email.
NOTA: Tenga en cuenta que este componente se usa sólo si useMail
está habilitado en su fichero .env
. También necesitará asegurarse de que el servidor SMTP y las credenciales son válidos.
Registro
Controlador
Para poder acceder a todas las áreas de Vökuró necesita tener una cuenta. Vökuró le permite registrarse en el sitio haciendo click en el botón Crear una Cuenta
.
Lo que esto hará es llevarle a la URL /session/signup
, que a su vez llamará a SessionController
y signupAction
. Echemos un vistazo a lo que está ocurriendo en signupAction
:
<?php
declare(strict_types=1);
namespace Vokuro\Controllers;
use Phalcon\Flash\Direct;
use Phalcon\Http\Request;
use Phalcon\Mvc\Dispatcher;
use Phalcon\Security;
use Phalcon\Mvc\View;
use Vokuro\Forms\SignUpForm;
use Vokuro\Models\Users;
/**
* @property Dispatcher $dispatcher
* @property Direct $flash
* @property Request $request
* @property Security $security
* @property View $view
*/
class SessionController extends ControllerBase
{
/**
* Allow a user to signup to the system
*/
public function signupAction()
{
$form = new SignUpForm();
// ....
$this->view->setVar('form', $form);
}
}
El flujo de trabajo de la aplicación es:
- Visita
/session/signup
- Crea formulario, envía formulario a la vista, renderiza el formulario
- Envía datos (no post)
- El formulario se muestra de nuevo, no ocurre nada más
- Envía datos (post)
- Errores
- Validadores del formulario tiene errores, envía el formulario a la vista, renderiza el formulario (se mostrarán los errores)
- Sin errores
- Se sanean los datos
- Se crea nuevo modelo
- Se guardan datos en la base de datos
- Error
- Se muestra mensaje en pantalla y actualiza el formulario
- Éxito
- Registro guardado
- Muestra confirmación en pantalla
- Envía email (si corresponde)
Para tener validación de los datos facilitados por el usuario, usamos las clases Phalcon\Forms\Form y Phalcon\Validation*. Estas clases nos permiten crear elementos HTML y adjuntarles validadores. El formulario se pasa entonces a la vista, donde los elementos HTML actuales se renderizan en la pantalla.
Cuando el usuario envía la información, envía los datos publicados de vuelta al formulario y los validadores correspondientes validan la entrada y devuelven cualquier posible mensaje de error.
NOTE: Todos los formularios de Vökuró se localizan en /src/Forms
Primero creamos un objeto SignUpForm
. En ese objeto definimos todos los elementos HTML que necesitamos con sus respectivos validadores:
<?php
declare(strict_types=1);
namespace Vokuro\Forms;
use Phalcon\Forms\Element\Check;
use Phalcon\Forms\Element\Hidden;
use Phalcon\Forms\Element\Password;
use Phalcon\Forms\Element\Submit;
use Phalcon\Forms\Element\Text;
use Phalcon\Forms\Form;
use Phalcon\Validation\Validator\Confirmation;
use Phalcon\Validation\Validator\Email;
use Phalcon\Validation\Validator\Identical;
use Phalcon\Validation\Validator\PresenceOf;
use Phalcon\Validation\Validator\StringLength;
class SignUpForm extends Form
{
/**
* @param string|null $entity
* @param array $options
*/
public function initialize(
string $entity = null,
array $options = []
) {
$name = new Text('name');
$name->setLabel('Name');
$name->addValidators(
[
new PresenceOf(
[
'message' => 'The name is required',
]
),
]
);
$this->add($name);
// Email
$email = new Text('email');
$email->setLabel('E-Mail');
$email->addValidators(
[
new PresenceOf(
[
'message' => 'The e-mail is required',
]
),
new Email(
[
'message' => 'The e-mail is not valid',
]
),
]
);
$this->add($email);
// Password
$password = new Password('password');
$password->setLabel('Password');
$password->addValidators(
[
new PresenceOf(
[
'message' => 'The password is required',
]
),
new StringLength(
[
'min' => 8,
'messageMinimum' => 'Password is too short. ' .
'Minimum 8 characters',
]
),
new Confirmation(
[
'message' => "Password doesn't match " .
"confirmation",
'with' => 'confirmPassword',
]
),
]
);
$this->add($password);
// Confirm Password
$confirmPassword = new Password('confirmPassword');
$confirmPassword->setLabel('Confirm Password');
$confirmPassword->addValidators(
[
new PresenceOf(
[
'message' => 'The confirmation password ' .
'is required',
]
),
]
);
$this->add($confirmPassword);
// Remember
$terms = new Check(
'terms',
[
'value' => 'yes',
]
);
$terms->setLabel('Accept terms and conditions');
$terms->addValidator(
new Identical(
[
'value' => 'yes',
'message' => 'Terms and conditions must be ' .
'accepted',
]
)
);
$this->add($terms);
// CSRF
$csrf = new Hidden('csrf');
$csrf->addValidator(
new Identical(
[
'value' => $this->security->getRequestToken(),
'message' => 'CSRF validation failed',
]
)
);
$csrf->clear();
$this->add($csrf);
// Sign Up
$this->add(
new Submit(
'Sign Up',
[
'class' => 'btn btn-success',
]
)
);
}
/**
* Prints messages for a specific element
*
* @param string $name
*
* @return string
*/
public function messages(string $name)
{
if ($this->hasMessagesFor($name)) {
foreach ($this->getMessagesFor($name) as $message) {
return $message;
}
}
return '';
}
}
En el método initialize
estamos configurando todos los elementos HTML que necesitamos. Estos elementos son:
Elemento |
Tipo |
Descripción |
name |
Text |
El nombre del usuario |
email |
Text |
El email de la cuenta |
password |
Password |
La contraseña para la cuenta |
confirmPassword |
Password |
Confirmación de la contraseña |
terms |
Check |
Acepta la casilla de términos |
csrf |
Hidden |
Elemento de protección CSRF |
Sign Up |
Submit |
Botón de enviar |
Añadir elementos es bastante sencillo:
<?php
declare(strict_types=1);
// Email
$email = new Text('email');
$email->setLabel('E-Mail');
$email->addValidators(
[
new PresenceOf(
[
'message' => 'The e-mail is required',
]
),
new Email(
[
'message' => 'The e-mail is not valid',
]
),
]
);
$this->add($email);
Primero creamos un objeto Text
y establecemos su nombre a email
. También establecemos la etiqueta del elemento a E-Mail
. Después de eso adjuntamos varios validadores al elemento. Estos se invocarán después de que el usuario envíe los datos, y los datos se pasen en el formulario.
Como vemos arriba, adjuntamos el validador PresenceOf
en el elemento email
con un mensaje The e-mail is required
. El validador comprobará si el usuario ha enviado los datos cuando hace click en el botón enviar y producirá el mensaje si el validador falla. El validador comprueba el vector pasado (normalmente $_POST
) y para este elemento particular comprobará $_POST['email']
.
También adjunta el validador Email
, que es responsable de comprobar si una dirección de email es válida. Como puede ver los validadores residen en un vector, por lo que fácilmente puede adjuntar tantos validadores como necesite.
Lo último que hacemos es añadir el elemento en el formulario.
Tenga en cuenta que el elemento terms
no tiene ningún validador adjunto a él, por lo que nuestro formulario no comprobará los contenidos del elemento.
Atención especial a los elementos password
and confirmPassword
. Notará que ambos elementos son del tipo Password
. La idea es que necesita escribir la contraseña dos veces, y las contraseñas deben coincidir para evitar errores.
El campo password
tiene dos validadores de contenido: PresenceOf
es decir, es requerido y StringLength
: necesitamos que la contraseña tenga más de 8 caracteres. También adjuntamos un tercer validador llamado Confirmation
. Este validador especial vincula el elemento password
con el elemento confirmPassword
. Cuando se activa para validarlos comprueba los contenidos de ambos elementos y si no son idénticos, aparecerá el mensaje de error, y la validación fallará.
Vistas
Ahora que tenemos todo configurado en nuestro formulario, pasamos el formulario a la vista:
$this->view->setVar('form', $form);
Nuestra vista ahora necesita renderizar los elementos:
{# ... #}
{%
set isEmailValidClass = form.messages('email') ?
'form-control is-invalid' :
'form-control'
%}
{# ... #}
<h1 class="mt-3">Sign Up</h1>
<form method="post">
{# ... #}
<div class="form-group row">
{{
form.label(
'email',
[
'class': 'col-sm-2 col-form-label'
]
)
}}
<div class="col-sm-10">
{{
form.render(
'email',
[
'class': isEmailValidClass,
'placeholder': 'Email'
]
)
}}
<div class="invalid-feedback">
{{ form.messages('email') }}
</div>
</div>
</div>
{# ... #}
<div class="form-group row">
<div class="col-sm-10">
{{
form.render(
'csrf',
[
'value': security.getToken()
]
)
}}
{{ form.messages('csrf') }}
{{ form.render('Sign Up') }}
</div>
</div>
</form>
<hr>
{{ link_to('session/login', "← Back to Login") }}
La variable que establecemos en nuestra vista para nuestro objeto SignUpForm
se llama form
. Por lo tanto, la usamos directamente y llamamos a sus métodos. La sintaxis en Volt es ligeramente diferente. En PHP usaríamos $form->render()
mientras que en Volt usaremos form.render()
.
Las vistas contienen un condicional en la parte superior, que comprueba si ha habido algún error en nuestro formulario, en cuyo caso, adjunta la clase CSS is-invalid
al elemento. Esta clase pone un bonito borde rojo en el elemento, resaltando el error y mostrando el mensaje.
Después de eso tenemos etiquetas HTML regulares con el estilo correspondiente. Para mostrar el código HTML de cada elemento necesitamos llamar render()
sobre form
con el nombre de elemento correspondiente. También tenga en cuenta que llamamos form.label()
con el mismo nombre de elemento, para poder crear las etiquetas <label>
correspondientes.
Al final de la vista renderizamos el campo oculto CSRF
así como el botón de enviar Sign Up
.
Post
Como mencionamos antes, una vez que el usuario rellena el formulario y hace click en el botón Sign Up
, el formulario se autopublicará, es decir, enviará los datos al mismo controlador y acción (en nuestro caso /session/signup
). La acción ahora necesita procesar los datos publicados:
<?php
declare(strict_types=1);
namespace Vokuro\Controllers;
use Phalcon\Flash\Direct;
use Phalcon\Http\Request;
use Phalcon\Mvc\Dispatcher;
use Phalcon\Security;
use Phalcon\Mvc\View;
use Vokuro\Forms\SignUpForm;
use Vokuro\Models\Users;
/**
* @property Dispatcher $dispatcher
* @property Direct $flash
* @property Request $request
* @property Security $security
* @property View $view
*/
class SessionController extends ControllerBase
{
/**
* Allow a user to signup to the system
*/
public function signupAction()
{
$form = new SignUpForm();
if (true === $this->request->isPost()) {
if (false !== $form->isValid($this->request->getPost())) {
$name = $this
->request
->getPost('name', 'striptags')
;
$email = $this
->request
->getPost('email')
;
$password = $this
->request
->getPost('password')
;
$password = $this
->security
->hash($password)
;
$user = new Users(
[
'name' => $name,
'email' => $email,
'password' => $password,
'profilesId' => 2,
]
);
if ($user->save()) {
return $this->dispatcher->forward([
'controller' => 'index',
'action' => 'index',
]);
}
foreach ($user->getMessages() as $message) {
$this->flash->error((string) $message);
}
}
}
$this->view->setVar('form', $form);
}
}
Si el usuario ha enviado datos, la siguiente línea evaluará y estaremos ejecutando código dentro de la sentencia if
:
if (true === $this->request->isPost()) {
Aquí estamos comprobando la petición que llegó del usuario, si es un POST
. Ahora que lo es, necesitamos usar los validadores del formulario y comprobar si tenemos algún error. El objeto Phalcon\Http\Request, nos permite obtener esos datos fácilmente usando:
$this->request->getPost()
Ahora necesitamos pasar estos datos publicados en el formulario y llamar isValid
. Esto disparará todos los validadores de cada elemento y si alguno de ellos falla, el formulario rellenará la colección de mensajes internos y devolverá false
if (false !== $form->isValid($this->request->getPost())) {
Si todo es correcto, usamos otra vez el objeto Phalcon\Http\Request para recuperar los datos publicados y además sanearlos. El siguiente ejemplo elimina las etiquetas de la cadena name
enviada:
$name = $this
->request
->getPost('name', 'striptags')
;
Tenga en cuenta que nunca almacenamos contraseñas en texto plano. En su lugar, usamos el componente Phalcon\Security y llamamos hash
sobre él, para transformar las contraseñas proporcionadas a un hash unidireccional y almacenarlo en su lugar. De esta forma, si alguien compromete nuestra base de datos, al menos no tendrá acceso a las contraseñas en texto plano.
$password = $this
->security
->hash($password)
;
Ahora necesitamos almacenar los datos proporcionados en la base de datos. Hacemos eso creando un nuevo modelo Users
, le pasamos los datos saneados y llamamos a save
:
$user = new Users(
[
'name' => $name,
'email' => $email,
'password' => $password,
'profilesId' => 2,
]
);
if ($user->save()) {
return $this
->dispatcher
->forward(
[
'controller' => 'index',
'action' => 'index',
]
);
}
Si $user->save()
devuelve true
, el usuario será enviado a la página de inicio (index/index
) y le aparecerá en pantalla un mensaje de éxito.
Modelo
Relaciones
Ahora necesitamos comprobar el modelo Users
, ya que hay alguna lógica que hemos aplicado aquí, en particular los eventos afterSave
y beforeValidationOnCreate
.
El método principal, la configuración si le gusta ocurre en el método initialize
. Ese es el lugar donde establecemos todas las relaciones del modelo. Para la clase Users
tenemos varias relaciones definidas. Podría preguntar, ¿porqué relaciones? Phalcon ofrece una forma fácil de recuperar datos relacionados con un modelo particular.
Si por ejemplo quiere comprobar todos los inicios de sesión correctos para un usuario particular, puede hacerlo con el siguiente fragmento de código:
<?php
declare(strict_types=1);
use Vokuro\Models\SuccessLogins;
use Vokuro\Models\Users;
$user = Users::findFirst(
[
'conditions' => 'id = :id:',
'bind' => [
'id' => 7,
]
]
);
$logins = SuccessLogin::find(
[
'conditions' => 'userId = :userId:',
'bind' => [
'userId' => 7,
]
]
);
El código anterior obtiene el usuario con id 7
y luego obtiene todos los inicios de sesión correctos de la tabla correspondiente para ese usuario.
Usando las relaciones podemos dejar que Phalcon haga todo el trabajo pesado por nosotros. Con lo que el código anterior sería:
<?php
declare(strict_types=1);
use Vokuro\Models\SuccessLogins;
use Vokuro\Models\Users;
$user = Users::findFirst(
[
'conditions' => 'id = :id:',
'bind' => [
'id' => 7,
]
]
);
$logins = $user->successLogins;
$logins = $user->getRelated('successLogins');
Las últimas dos líneas hacen exactamente lo mismo. Es una cuestión de preferencia qué sintaxis quiere usar. Phalcon consultará la tabla relacionada, filtrando la tabla relacionada con el id del usuario.
Para nuestra tabla Users
definimos las siguientes relaciones:
Nombre |
Campo origen |
Campo destino |
Modelo |
passwordChanges |
id |
usersId |
PasswordChanges |
profile |
profileId |
id |
Profiles |
resetPasswords |
id |
usersId |
ResetPasswords |
successLogins |
id |
usersId |
SuccessLogins |
<?php
declare(strict_types=1);
namespace Vokuro\Models;
use Phalcon\Mvc\Model;
use Phalcon\Validation;
use Phalcon\Validation\Validator\Uniqueness;
/**
* All the users registered in the application
*/
class Users extends Model
{
// ...
public function initialize()
{
$this->belongsTo(
'profilesId',
Profiles::class,
'id',
[
'alias' => 'profile',
'reusable' => true,
]
);
$this->hasMany(
'id',
SuccessLogins::class,
'usersId',
[
'alias' => 'successLogins',
'foreignKey' => [
'message' => 'User cannot be deleted because ' .
'he/she has activity in the system',
],
]
);
$this->hasMany(
'id',
PasswordChanges::class,
'usersId',
[
'alias' => 'passwordChanges',
'foreignKey' => [
'message' => 'User cannot be deleted because ' .
'he/she has activity in the system',
],
]
);
$this->hasMany(
'id',
ResetPasswords::class,
'usersId', [
'alias' => 'resetPasswords',
'foreignKey' => [
'message' => 'User cannot be deleted because ' .
'he/she has activity in the system',
],
]);
}
// ...
}
Como puede ver en las relaciones definidas, tenemos un belongsTo
y tres hasMany
. Todas las relaciones tienen un alias para que podamos acceder a ellas más fácilmente. La relación belongsTo
también tiene el parámetro reusable
activo. Esto significa que si la relación se llama más de una vez en la misma petición, Phalcon realizaría la consulta a la base de datos sólo la primera vez y cachearía el conjunto de resultados. Cualquier llamada posterior usará el conjunto de resultados cacheado.
También destacar que hemos definido mensajes específicos para claves ajenas. Si se infringe la relación particular, se generará el mensaje definido.
Eventos
Phalcon\Mvc\Model está diseñado para disparar eventos específicos. Estos métodos de eventos se pueden localizar en un oyente o en el propio modelo.
Para el modelo Users
, adjuntamos código a los eventos afterSave
y beforeValidationOnCreate
.
<?php
declare(strict_types=1);
namespace Vokuro\Models;
use Phalcon\Mvc\Model;
use Phalcon\Validation;
use Phalcon\Validation\Validator\Uniqueness;
/**
* All the users registered in the application
*/
class Users extends Model
{
public function beforeValidationOnCreate()
{
if (true === empty($this->password)) {
$tempPassword = preg_replace(
'/[^a-zA-Z0-9]/',
'',
base64_encode(openssl_random_pseudo_bytes(12))
);
$this->mustChangePassword = 'Y';
$this->password = $this->getDI()
->getSecurity()
->hash($tempPassword)
;
} else {
$this->mustChangePassword = 'N';
}
if ($this->getDI()->get('config')->useMail) {
$this->active = 'N';
} else {
$this->active = 'Y';
}
$this->suspended = 'N';
$this->banned = 'N';
}
}
beforeValidationOnCreate
disparará cada vez que tenemos un nuevo registro (Create
), antes de que ocurra alguna validación. Comprobamos si hemos definido una contraseña o no, generaremos una cadena aleatoria, luego haremos hash de esa cadena usando Phalcon\Security y la almacena en la propiedad password
. También activamos el parámetro para cambiar la contraseña.
Si la contraseña no está vacía, solo establecemos el campo mustChangePassword
a N
. Finalmente, establecemos algunos valores predeterminados sobre si el usuario está active
(activo), suspended
(suspendido) o banned
(baneado). Esto asegura que nuestro registro está listo antes de ser insertado en la base de datos.
<?php
declare(strict_types=1);
namespace Vokuro\Models;
use Phalcon\Mvc\Model;
use Phalcon\Validation;
use Phalcon\Validation\Validator\Uniqueness;
/**
* All the users registered in the application
*/
class Users extends Model
{
public function afterSave()
{
if ($this->getDI()->get('config')->useMail) {
if ($this->active == 'N') {
$emailConfirmation = new EmailConfirmations();
$emailConfirmation->usersId = $this->id;
if ($emailConfirmation->save()) {
$this->getDI()
->getFlash()
->notice(
'A confirmation mail has ' .
'been sent to ' . $this->email
)
;
}
}
}
}
}
El evento afterSave
se dispara justo después de guardar un registro en la base de datos. En este evento comprobamos si los emails están habilitados (ver ajuste useMail
del fichero .env
), y si están activos creamos un nuevo registro en la tabla EmailConfirmations
y guardamos el registro. Una vez que se ha hecho todo, aparecerá un aviso en pantalla.
NOTA: Tenga en cuenta que el modelo EmailConfirmations
también tiene un evento afterCreate
, que es responsable de enviar el email al usuario.
Validación
El modelo también tiene el método validate
que nos permite adjuntar un validador a cualquier número de campos de nuestro modelo. Para la tabla Users
, necesitamos que el email
sea único. Para lo cual, le adjuntamos el validador Uniqueness
. El validador se disparará justo antes de que se realice cualquier operación de guardado en el modelo y se devolverá el mensaje si la validación falla.
<?php
declare(strict_types=1);
namespace Vokuro\Models;
use Phalcon\Mvc\Model;
use Phalcon\Validation;
use Phalcon\Validation\Validator\Uniqueness;
/**
* All the users registered in the application
*/
class Users extends Model
{
public function validation()
{
$validator = new Validation();
$validator->add(
'email',
new Uniqueness(
[
"message" => "The email is already registered",
]
)
);
return $this->validate($validator);
}
}
Conclusión
Vökuró es una aplicación de ejemplo que usamos para demostrar algunas de las características que Phalcon ofrece. Definitivamente, no es una solución que cubra todas las necesidades. Sin embargo, puede usarla como punto de partida para desarrollar su aplicación.
Referencias