lunes, 25 de agosto de 2014
Symfony - Día 13: El usuario
Ayer estuvo lleno de una gran información. Con muy pocas líneas de código PHP, el generador de administración de Symfony permitió al desarrollador crear interfaces backend en cuestión de minutos.
Hoy, vamos a descubrir cómo Symfony gestiona persistentemente datos entre las peticiones HTTP. Como se puede saber, el protocolo HTTP no tiene memoria, lo que significa que cada petición es independiente de lo anterior o de la subsiguiente. Los sitios web modernos necesitan una manera de mantener los datos entre las peticiones para mejorar la experiencia del usuario.
Una sesión de usuario puede ser identificada mediante una cookie. En Symfony, el desarrollador no tiene que manipular directamente las sesiones, sino que usa el objeto
sfUser
, que representa al usuario final de la aplicación.Mensajes Flashes del Usuario
Ya hemos visto el objeto de usuario en acción con los mensajes flashes. Un flash es un efímero mensaje almacenado en la sesión de usuario que se eliminará automáticamente después de la próxima petición. Es muy útil cuando se necesita mostrar un mensaje al usuario después de un redireccionamiento. El generador de administración utiliza flashes para mostrar información al usuario cuando se guarda un puesto de trabajo, se borra o extiende su validez.
Un flash se establece utilizando el método
setFlash()
de sfUser
:// apps/frontend/modules/job/actions/actions.class.php public function executeExtend(sfWebRequest $request) { $request->checkCSRFProtection(); $job = $this->getRoute()->getObject(); $this->forward404Unless($job->extend()); $this->getUser()->setFlash('notice', sprintf('Your job validity has been extended until %s.', $job->getDateTimeObject('expires_at')->format('m/d/Y'))); $this->redirect($this->generateUrl('job_show_user', $job)); }
El primer argumento es el identificador del flash y el segundo es el mensaje a mostrar. Se puede definir cualquier flashes que quieras, pero
notice
y error
son dos de los más comunes (que son utilizadas intensivamente por el generador de administración).
Corresponde a los desarrolladores incluír el mensaje flash en las plantillas. Para Jobeet, se muestran en el
layout.php
:// apps/frontend/templates/layout.php <?php if ($sf_user->hasFlash('notice')): ?> <div class="flash_notice"><?php echo $sf_user->getFlash('notice') ?></div> <?php endif ?> <?php if ($sf_user->hasFlash('error')): ?> <div class="flash_error"><?php echo $sf_user->getFlash('error') ?></div> <?php endif ?>
En una plantilla, el usuario es accesible a través de la variable especial
$sf_user
.
Algunos objetos symfony siempre son accesibles en las plantillas, sin la necesidad de explicitamente pasarlos desde la acción:
$sf_request
, $sf_user
, y$sf_response
.Atributos de Usuario
Lamentablemente, los casos de uso de Jobeet no tienen ningún requisito sobre el almacenamiento de algo en la sesión de usuario. Así que vamos a añadir un nuevo requisito: para facilitar la navegación de los puestos de trabajo, los últimos tres vistos por el usuario se deben mostrar en un menú con enlaces para volver a la página de esos puestos de trabajo más tarde.
Cuando un usuario accede a una página de puestos de trabajo, el objeto job mostrado necesita ser agregado en el historial del usuario y almacenado en la sesión:
// apps/frontend/modules/job/actions/actions.class.php class jobActions extends sfActions { public function executeShow(sfWebRequest $request) { $this->job = $this->getRoute()->getObject(); // fetch jobs already stored in the job history $jobs = $this->getUser()->getAttribute('job_history', array()); // add the current job at the beginning of the array array_unshift($jobs, $this->job->getId()); // store the new job history back into the session $this->getUser()->setAttribute('job_history', $jobs); } // ... }
Podríamos haber almacenado los objetos
JobeetJob
directamente en la sesión. Esto está totalmente desaconsejada ya que las variables de sesión son serializadas (almacenadas) entre las peticiones. Y cuando la sesión se ha cargado, los objetosJobeetJob
son de-serializados y pueden quedar "estancos" aun si han sido modificados o borrados en el ínterin.
Los métodos getAttribute()
, setAttribute()
Dado un identificador, el método
sfUser::getAttribute()
obtiene los valores de la sesión de usuario. Por el contrario, el método setAttribute()
almacena cualquier variable PHP en la sesión, para un determinado identificador.
El método
getAttribute()
también tiene un valor predeterminado opcional a devolver si el identificador no está todavía definido.
El valor por defecto tomado por el método
getAttribute()
es un acceso directo para:if (!$value = $this->getAttribute('job_history')) { $value = array(); }
La Clase myUser
Lo mejor respeto a la separación de las capas, es mover el código a la clase
myUser
. La clasemyUser
sobreescribe la clase por defecto base symfony sfUser
con comportamientos específicos de la aplicación:// apps/frontend/modules/job/actions/actions.class.php class jobActions extends sfActions { public function executeShow(sfWebRequest $request) { $this->job = $this->getRoute()->getObject(); $this->getUser()->addJobToHistory($this->job); } // ... } // apps/frontend/lib/myUser.class.php class myUser extends sfBasicSecurityUser { public function addJobToHistory(JobeetJob $job) { $ids = $this->getAttribute('job_history', array()); if (!in_array($job->getId(), $ids)) { array_unshift($ids, $job->getId()); $this->setAttribute('job_history', array_slice($ids, 0, 3)); } } }
El código también ha sido modificado para tener en cuenta todos los requisitos:
!in_array($job->getId(), $ids)
: Un job no se puede almacenar dos veces en el historialarray_slice($ids, 0, 3)
: Sólo los tres últimos puestos de trabajo vistos por el usuario se muestran
En el layout, agrega el código siguiente antes de que la variable
$sf_content
se muestre:// apps/frontend/templates/layout.php <div id="job_history"> Recent viewed jobs: <ul> <?php foreach ($sf_user->getJobHistory() as $job): ?> <li> <?php echo link_to($job->getPosition().' - '.$job->getCompany(), 'job_show_user', $job) ?> </li> <?php endforeach ?> </ul> </div> <div class="content"> <?php echo $sf_content ?> </div>
El layout usa un nuevo método
getJobHistory()
para obtener el historial job:// apps/frontend/lib/myUser.class.php class myUser extends sfBasicSecurityUser { public function getJobHistory() { $ids = $this->getAttribute('job_history', array()); if (!empty($ids)) { return Doctrine_Core::getTable('JobeetJob') ->createQuery('a') ->whereIn('a.id', $ids) ->execute() ; } return array(); } // ... }
El sfParameterHolder
Para completar el API del historial de job, vamos a añadir un método para resetear el historial:
// apps/frontend/lib/myUser.class.php class myUser extends sfBasicSecurityUser { public function resetJobHistory() { $this->getAttributeHolder()->remove('job_history'); } // ... }
Los atributos del usuario son gestionados por un objeto de la clase
sfParameterHolder
. Los métodos getAttribute()
y setAttribute()
son métodos proxy para getParameterHolder()->get()
y getParameterHolder()->set()
. Como el método remove()
no tiene método proxy ensfUser
, necesitas usar el objeto del contenedor de parámetros directamente.
La clase
sfParameterHolder
es también utilizada por sfRequest
para almacenar sus parámetros.Seguridad de la Aplicación
Autenticación
Al igual que muchas otras características, la seguridad es manejada por un archivo YAML,
security.yml
. Por ejemplo, puedes encontrar la configuración por defecto para la aplicación backend en el directorio config/
:# apps/backend/config/security.yml default: is_secure: false
Si cambia el
is_secure
a true
, toda la aplicación backend necesitará que el usuario deba autenticarse.
En un archivo YAML, un Booleano puede expresarse con la cadena
true
o false
.
Si echas un vistazo a los registros en la barra web de depuración, se puede observar que el método
executeLogin()
de la clase defaultActions
se llama por cada página que intentas acceder.
Cuando un usuario no-autenticado intenta acceder a una acción segura, Symfony remite la peticióna la acción
login
configurada en settings.yml
:all: .actions: login_module: default login_action: login
No es posible asegurar a la acción login para evitar una recursión infinita.
Como vimos durante el día 4, el mismo archivo de configuración se pueden definir en varios lugares. Este es también el caso de
security.yml
. Para sólo asegurar o desproteger una única acción o un módulo, crea un security.yml
en the directorioconfig/
del módulo:index: is_secure: false all: is_secure: true
De forma predeterminada, la clase
myUser
hereda de sfBasicSecurityUser
, y no de sfUser
.sfBasicSecurityUser
proporciona métodos para gestionar la autenticación de usuario y autorización.
Para gestionar la autenticación de usuario, utiliza los métodos
isAuthenticated()
ysetAuthenticated()
:if (!$this->getUser()->isAuthenticated()) { $this->getUser()->setAuthenticated(true); }
Autorización
Cuando un usuario está autenticado, el acceso a algunas acciones pueden ser aún más restringida por la definición de credenciales. Un usuario debe tener las credenciales para acceder a la página:
default: is_secure: false credentials: admin
El sistema de credenciales de Symfony es bastante simple y poderoso. Una credencial puede representar todo lo que sea necesario para describir la seguridad al modelo de la aplicación (como grupos o permisos).
Credenciales Complejas
El item
credentials
de security.yml
soporta operadores Booleanos para describir las necesidades complejas en credenciales.
Si un usuario debe tener credenciales A y B, hay que envolver las credenciales con corchetes:
index: credentials: [A, B]
Si un usuario debe tener credenciales A o B, hay que envolver las credenciales con dos pares de corchetes:
index: credentials: [[A, B]]
Incluso puedes mezclar y combinar entre paréntesis para describir cualquier tipo de Expresión booleana con cualquier número de credenciales.
Para gestionar las credenciales de usuario,
sfBasicSecurityUser
da varios métodos:// Add one or more credentials $user->addCredential('foo'); $user->addCredentials('foo', 'bar'); // Check if the user has a credential echo $user->hasCredential('foo'); => true // Check if the user has both credentials echo $user->hasCredential(array('foo', 'bar')); => true // Check if the user has one of the credentials echo $user->hasCredential(array('foo', 'bar'), false); => true // Remove a credential $user->removeCredential('foo'); echo $user->hasCredential('foo'); => false // Remove all credentials (useful in the logout process) $user->clearCredentials(); echo $user->hasCredential('bar'); => false
Por el Backend de Jobeet, no vamos a usar ninguna credencial ya que sólo tenemos un perfil: el administrador.
Plugins
Como no nos gusta reinventar la rueda, no vamos a desarrollar la acción de acceso a partir de cero. En lugar de ello, se instalará un plugin symfony.
Uno de los grandes puntos fuertes del framework Symfony son los plugins. Como veremos en los próximos días, es muy fácil crear un plugin. También es bastante potente, ya que un plugin puede contener cualquier cosa, desde la configuración de los módulos hasta los recursos web.
Hoy, vamos a instalar
sfDoctrineGuardPlugin
para garantizar el acceso al backend$ php symfony plugin:install sfDoctrineGuardPlugin
La tarea
plugin:install
instala un plugin por nombre. Todos los plugins son almacenados bajo el directorio plugins/
y cada uno tiene su propio directorio con el nombre del nombre del plugin.
PEAR debe estar instalado para que la tarea
plugin:install
funcione.
Cuando se instala un plugin con la tarea
plugin:install
, Symfony instala la última versión estable del mismo. Para instalar una versión específica de un plugin, pasa la opción --release
.
La web de los plugins lista todas las versiones disponibles agrupados por la versión de Symfony.
Como un plugin es auto-contenido en un directorio, también puedes descargar el paquete desde el sitio web de Symfony y descomprimirlo, o alternativamente, haces un enlace
svn:externals
a su Subversion repository.Seguridad del Backend
Cada plugin tiene un archivo README que explica cómo configurarlo.
Vamos a ver cómo configurar el nuevo plugin. Como el plugin ofrece varias nuevas clases al modelo para la gestión de usuarios, grupos y permisos, necesitas reconstruir tu modelo:
$ php symfony doctrine:build --all --and-load --no-confirmation
Recuerde que la tarea
doctrine:build --all --and-load
elimina todas las tablas existentes antes de volver a crearlas. Para evitar esto, puedes construir los modelos, formularios y filtros y, a continuación, crear las nuevas tablas ejecutando el SQL generado y almacenado en data/sql/
.
Como
sfDoctrineGuardPlugin
agrega varios métodos a la clase de usuario, es necesario cambiar la clase base de myUser
a sfGuardSecurityUser
:// apps/backend/lib/myUser.class.php class myUser extends sfGuardSecurityUser { }
sfDoctrineGuardPlugin
proporciona una acción signin
en el modulo sfGuardAuth
para autenticar a los usuarios:
Edita el archivo
settings.yml
para cambiar la acción por defecto empleada por la página de login:# apps/backend/config/settings.yml all: .settings: enabled_modules: [default, sfGuardAuth] # ... .actions: login_module: sfGuardAuth login_action: signin # ...
Como los plugins son compartidos entre todas las aplicaciones de un proyecto, tiene que permitir explícitamente los módulos que desea utilizar agregando el item
enabled_modules
.
El último paso es crear un usuario administrador:
$ php symfony guard:create-user fabien SecretPass
$ php symfony guard:promote fabien
El
sfGuardPlugin
proporciona funciones para la gestión de usuarios, grupos y permisos desde la línea de comandos. Utiliza la tarea list
para listar todas las tareas pertenecientes al espacio de nombres guard
:$ php symfony list guard
Y cuando el usuario no está autenticado, tenemos que ocultar la barra de menú:
// apps/backend/templates/layout.php <?php if ($sf_user->isAuthenticated()): ?> <div id="menu"> <ul> <li><?php echo link_to('Jobs', 'jobeet_job') ?></li> <li><?php echo link_to('Categories', 'jobeet_category') ?></li> </ul> </div> <?php endif ?>
Cuando el usuario es autenticado, tenemos que añadir un enlace de logout en el menú:
// apps/backend/templates/layout.php <li><?php echo link_to('Logout', 'sf_guard_signout') ?></li>
Para una lista de todas las rutas previstas por
sfGuardPlugin
, usa la tareaapp:routes
.
Para pulir el backend aún más, vamos a añadir un nuevo módulo para la gestión del administrador de usuarios. Afortunadamente,
sfGuardPlugin
proporciona un módulo de este tipo. Como el módulo sfGuardAuth
, que necesitarás habilitarlo en settings.yml
:// apps/backend/config/settings.yml all: .settings: enabled_modules: [default, sfGuardAuth, sfGuardUser]
Añadir un enlace en el menú:
// apps/backend/templates/layout.php <li><?php echo link_to('Users', 'sf_guard_user') ?></li>
Terminamos!
Probando al Usuario
El tutorial de hoy no termina ya que no hemos aún hablado de las pruebas al usuario. Como el symfony browser simula las cookies, es bastante fácil de probar el comportamiento del usuario mediante el uso del tester
sfTesterUser
.
Vamos a actualizar la pruebas funcionales para el menú que hemos añadido hoy. Agrega el código siguiente al final del módulo de pruebas funcionales
job
:// test/functional/frontend/jobActionsTest.php $browser-> info('4 - User job history')-> loadData()-> restart()-> info(' 4.1 - When the user access a job, it is added to its history')-> get('/')-> click('Web Developer', array(), array('position' => 1))-> get('/')-> with('user')->begin()-> isAttribute('job_history', array($browser->getMostRecentProgrammingJob()->getId()))-> end()-> info(' 4.2 - A job is not added twice in the history')-> click('Web Developer', array(), array('position' => 1))-> get('/')-> with('user')->begin()-> isAttribute('job_history', array($browser->getMostRecentProgrammingJob()->getId()))-> end() ;
Para facilitar las pruebas, primero hacemos la recarga datos y reiniciamos el navegador para empezar con una sesión.
El método
isAttribute()
controla un atributo de usuario dado.
El tester
sfTesterUser
también proporciona métodos isAuthenticated()
andhasCredential()
para poner a prueba la autenticación de usuario y autorizaciones.
Suscribirse a:
Enviar comentarios
(
Atom
)
Sígueme en las Redes Sociales
Donaciones
Datos personales
Entradas populares
-
En este apartado vamos a explicar como ejercutar archivos PHP a través del terminal de Ubuntu. Lo primero que tendríamos que hacer es inst...
-
En este blog voy a comentar un tema que se utilizan en casi todas las páginas web que existen, y es el tema de la paginación. La paginaci...
-
Este post trata de la integración de la librería PHPExcel en Codeigniter, aunque se podría aplicar a cualquier librería, como por ejemplo mP...
-
Ejemplo para añadir o sumar un número determinado de hora/s, minuto/s, segundo/s a una fecha en php. Con la función strtotime se puede ...
-
Este tema es uno de los temas primordiales sobre el framework Codeigniter, ya que en alguna ocación nos hemos visto obligados a recoger dato...
© Espacio Daycry - Espacio de programación 2013 . Powered by Bootstrap , Blogger templates and RWD Testing Tool
No hay comentarios :
Publicar un comentario