symfony tutorial de jobeet

Upload: jorge-sandal

Post on 11-Feb-2018

235 views

Category:

Documents


0 download

TRANSCRIPT

  • 7/23/2019 Symfony Tutorial de Jobeet

    1/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    2/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    3/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    4/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    5/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    6/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    7/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    8/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    9/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    10/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    11/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    12/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    13/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    14/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    15/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    16/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    17/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    18/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    19/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    20/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    21/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    22/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    23/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    24/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    25/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    26/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    27/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    28/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    29/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    30/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    31/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    32/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    33/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    34/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    35/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    36/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    37/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    38/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    39/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    40/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    41/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    42/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    43/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    44/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    45/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    46/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    47/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    48/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    49/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    50/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    51/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    52/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    53/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    54/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    55/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    56/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    57/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    58/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    59/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    60/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    61/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    62/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    63/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    64/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    65/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    66/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    67/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    68/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    69/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    70/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    71/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    72/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    73/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    74/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    75/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    76/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    77/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    78/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    79/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    80/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    81/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    82/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    83/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    84/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    85/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    86/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    87/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    88/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    89/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    90/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    91/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    92/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    93/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    94/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    95/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    96/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    97/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    98/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    99/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    100/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    101/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    102/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    103/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    104/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    105/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    106/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    107/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    108/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    109/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    110/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    111/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    112/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    113/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    114/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    115/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    116/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    117/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    118/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    119/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    120/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    121/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    122/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    123/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    124/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    125/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    126/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    127/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    128/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    129/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    130/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    131/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    132/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    133/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    134/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    135/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    136/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    137/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    138/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    139/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    140/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    141/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    142/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    143/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    144/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    145/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    146/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    147/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    148/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    149/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    150/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    151/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    152/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    153/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    154/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    155/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    156/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    157/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    158/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    159/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    160/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    161/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    162/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    163/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    164/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    165/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    166/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    167/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    168/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    169/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    170/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    171/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    172/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    173/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    174/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    175/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    176/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    177/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    178/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    179/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    180/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    181/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    182/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    183/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    184/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    185/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    186/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    187/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    188/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    189/309

    Captulo 13. El usuarioAyer fue un da muy intenso y lleno de informacin. El generador de la parte deadministracin de Symfony nos permiti crear interfaces de administracin completasen muy pocos minutos y con slo unas pocas lneas de cdigo PHP.

    Hoy vamos a ver cmo gestiona Symfony la informacin que debe ser persistente entrelas diferentes peticiones HTTP. Como ya sabes, HTTP es un protocolo sin estado, lo quesignifica que cada peticin HTTP se considera independiente de cualquier otra peticin.Por otra parte, los sitios web modernos requieren de un mecanismo para almacenarinformacin persistente entre peticiones de forma que se pueda mejorar la experienciade usuario.

    Las sesiones de usuario se pueden identificar de forma nica gracias a las cookies. EnSymfony no es necesario que los programadores manipulen directamente las sesiones,ya que se puede utilizar el objeto sfUser que representa al usuario final de la aplicacin.

    13.1. Mensajes flash

    En los tutoriales de los das anteriores ya hemos visto el uso del objeto sfUser en las

    acciones para establecer mensajes flash. Un mensaje flash es un mensaje temporal quese almacena en la sesin del usuario y que se borra automticamente despus de lasiguiente peticin.

    Estos mensajes son muy tiles para mostrar informacin al usuario despus de unaredireccin. El propio generador de la parte de administracin utiliza mucho losmensajes flash para mostrar al usuario informacin sobre el resultado de las acciones,como por ejemplo cuando se crea, borra o guarda una oferta de trabajo.

    Jobeet Captulo 13. El usuario

    www.librosweb.es 189

  • 7/23/2019 Symfony Tutorial de Jobeet

    190/309

    Figura 13.1. Ejemplo de mensajes flash

    Los mensajes flash se crean con el mtodo setFlash() del objeto sfUser :

    // apps/frontend/modules/job/actions/actions.class.phppublic function executeExtend(sfWebRequest $request)

    {$request->checkCSRFProtection();

    $job = $this->getRoute()->getObject();$this->forward404Unless($job->extend());

    $this->getUser()->setFlash('notice', sprintf('Your job validity has beenextend until %s.', $job->getExpiresAt('m/d/Y')));

    $this->redirect($this->generateUrl('job_show_user', $job));}

    El primer argumento de setFlash() es el identificador del mensaje y el segundoargumento es el contenido del mensaje flash. Puedes definir cualquier tipo de mensajeflash, pero los tipos notice y error son los ms comunes (y son los que utilizacontinuamente el generador de la parte de administracin).

    La accin slo crea los mensajes flash, por lo que si se quieren mostrar en la plantilla sedeben incluir explcitamente. En la aplicacin Jobeet, los mensajes flash se muestran enlayout.php :

    // apps/frontend/templates/layout.php

    La plantilla puede acceder a la informacin del usuario directamente a travs de unavariable especial llamada sf_user .

    Nota

    Algunos objetos propios de Symfony siempre estn disponibles en las plantillas, sin necesidad depasarlos de forma explcita desde la accin: sf_request , sf_user y sf_response .

    13.2. Atributos del usuario

    En los escenarios que describimos en el turorial del segundo da no incluimos ningnrequisito para almacenar informacin en la sesin de usuario. Por tanto, a continuacinvamos a definir un nuevo requerimiento: "para facilitar la navegacin por las ofertas detrabajo, en el men se muestran los enlaces a las tres ltimas ofertas de trabajo vistas por

    el usuario" .

    Jobeet Captulo 13. El usuario

    www.librosweb.es 190

  • 7/23/2019 Symfony Tutorial de Jobeet

    191/309

    Cuando el usuario visita la pgina de una oferta de trabajo, debemos incluir en elhistorial del usuario el objeto que representa a esa oferta y debemos guardar el historialen la sesin del usuario:

    // 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 arrayarray_unshift($jobs, $this->job->getId());

    // store the new job history back into the session$this->getUser()->setAttribute('job_history', $jobs);

    }

    // ...}

    Nota

    En el cdigo anterior podramos haber guardado directamente los objetos JobeetJob en lasesin. No te aconsejamos que lo hagas porque las variables de sesin se serializan entre unapeticin y otra. Si guardramos los objetos, al cargar la sesin se deserializaran los objetos

    JobeetJob y se podran producir problemas si los objetos se han modificado o borrado desdeque se guardaron en la sesin.

    13.2.1. Los mtodos getAttribute() y setAttribute()

    El mtodo sfUser::getAttribute() devuelve los valores de la sesin asociados alidentificador que se indica. De la misma forma, el mtodo setAttribute() guarda

    cualquier variable de PHP en la sesin del usuario y la asocia con el identificadorproporcionado.

    El mtodo getAttribute() tambin permite indicar un segundo argumento opcionalque es el valor que devuelve el mtodo cuando el identificador proporcionado no estdefinido en la sesin del usuario.

    Nota

    El valor por defecto que se puede indicar en el mtodo getAttribute() es simplemente unatajo de:

    if ( ! $value = $this -> getAttribute('job_history')){

    $value = array() ;

    }

    Jobeet Captulo 13. El usuario

    www.librosweb.es 191

  • 7/23/2019 Symfony Tutorial de Jobeet

    192/309

    13.2.2. La clase myUser

    Para mantener la separacin del cdigo en capas, vamos a mover el cdigo a la clasemyUser. La clase myUser redefine la clase sfUser (http://www.symfony-project.org/api/1_2/sfUser) que incluye por defecto de Symfony y permite aadir caractersticas propiasde la aplicacin:

    // apps/frontend/modules/job/actions/actions.class.phpclass jobActions extends sfActions{

    public function executeShow(sfWebRequest $request){

    $this->job = $this->getRoute()->getObject();

    $this->getUser()->addJobToHistory($this->job);}

    // ...}

    // apps/frontend/lib/myUser.class.phpclass 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 cdigo anterior tambin se ha modificado para tener en cuenta todos losrequerimientos definidos:

    !in_array($job->getId(), $ids) : una misma oferta de trabajo no se puedeguardar dos veces en el historial.

    array_slice($ids, 0, 3) : slo se muestran las tres ltimas ofertas de trabajo

    vistas por el usuario.

    En el layout, aade el siguiente cdigo antes de la instruccin que muestra el contenidode la variable $sf_content :

    // apps/frontend/templates/layout.php

    Recent viewed jobs:

  • 7/23/2019 Symfony Tutorial de Jobeet

    193/309

    'job_show_user', $job) ?>

    El layout anterior utiliza un nuevo mtodo llamado getJobHistory() para obtener el

    historial de ofertas de trabajo visitadas:

    // apps/frontend/lib/myUser.class.phpclass myUser extends sfBasicSecurityUser{

    public function getJobHistory(){

    $ids = $this->getAttribute('job_history', array());

    return JobeetJobPeer::retrieveByPKs($ids);}

    // ...}

    El mtodo getJobHistory() utiliza el mtodo retrieveByPKs() de Propel para obtenervarios objetos de tipo JobeetJob mediante una nica llamada.

    Figura 13.2. Historial de ofertas de trabajo visitadas

    13.2.3. La clase sfParameterHolder

    Para completar la nueva funcionalidad del historial de ofertas de trabajo, aade elsiguiente mtodo para borrar el historial:

    // apps/frontend/lib/myUser.class.phpclass myUser extends sfBasicSecurityUser{

    public function resetJobHistory(){

    Jobeet Captulo 13. El usuario

    www.librosweb.es 193

  • 7/23/2019 Symfony Tutorial de Jobeet

    194/309

    $this->getAttributeHolder()->remove('job_history');}

    // ...}

    Los atributos del usuario se gestionan a travs de un objeto de la clasesfParameterHolder . Los mtodos getAttribute() y setAttribute() de sfUser son enrealidad atajos de los mtodos getParameterHolder()->get() ygetParameterHolder()->set() . Como el mtodo remove() no dispone de un atajo en laclase sfUser , tenemos que utilizar directamente el objeto que representa al contenedor

    de parmetros.

    Nota

    La clase sfRequest tambin guarda sus parmetros en un objeto de la clase sfParameterHolder(http://www.symfony-project.org/api/1_2/sfParameterHolder) .

    13.3. La seguridad de la aplicacin

    13.3.1. Autenticacin

    La seguridad de las aplicaciones Symfony se controla mediante un archivo en formatoYAML llamado security.yml . Si quieres ver la configuracin por defecto de la seguridadde la aplicacin backend , puedes acceder al archivo config/security.yml de la

    aplicacin:

    # apps/backend/config/security.ymldefault:

    is_secure: off

    Si cambias el valor de la opcin is_secure a on, la aplicacin backend requerir a partir

    de ese momento que los usuarios estn autenticados.

    Figura 13.3. Pantalla que muestra que el usuario debe estar autenticado

    Jobeet Captulo 13. El usuario

    www.librosweb.es 194

    http://www.symfony-project.org/api/1_2/sfParameterHolderhttp://www.symfony-project.org/api/1_2/sfParameterHolderhttp://www.symfony-project.org/api/1_2/sfParameterHolderhttp://www.symfony-project.org/api/1_2/sfParameterHolder
  • 7/23/2019 Symfony Tutorial de Jobeet

    195/309

    Sugerencia

    En los archivos YAML, los valores booleanos se pueden indicar con las cadenas de texto true yfalse o con los valores on y off .

    Si echas un vistazo a los mensajes de log de la barra de depuracin web, vers que cadavez que intentas acceder a una pgina de la aplicacin backend se ejecuta el mtodoexecuteLogin() de la clase defaultActions .

    Figura 13.4. Mensajes de la barra de depuracin web relacionados con el login

    Cuando un usuario que no ha sido autenticado intenta acceder a una accin restringida,Symfony reenva la peticin a la accin de login configurada en el archivosettings.yml :

    all:.actions:

    login_module: defaultlogin_action: login

    Nota

    No es posible restrigir la seguridad de la accin login para evitar recursiones infinitas.

    Sugerencia

    Como vimos en el tutorial del da 4, un mismo archivo de configuracin se puede definir endiferentes directorios. Este tambin es el caso del archivo security.yml . Si slo quieresrestringir o permitir el acceso a una accin o a un mdulo, crea un archivo llamadosecurity.yml en el directorio config/ de ese mdulo:

    index:is_secure: off

    all:is_secure: on

    La clase myUser hereda por defecto de sfBasicSecurityUser(http://www.symfony-project.org/api/1_2/sfBasicSecurityUser) y no de sfUser . Laclase sfBasicSecurityUser incluye mtodos adicionales para gestionar la autenticacin

    y autorizacin de usuarios.

    Si quieres controlar la autenticacin de los usuarios, puedes utilizar los mtodosisAuthenticated() y setAuthenticated() :

    if (!$this->getUser()->isAuthenticated()){

    $this->getUser()->setAuthenticated(true);}

    Jobeet Captulo 13. El usuario

    www.librosweb.es 195

    http://www.symfony-project.org/api/1_2/sfBasicSecurityUserhttp://www.symfony-project.org/api/1_2/sfBasicSecurityUserhttp://www.symfony-project.org/api/1_2/sfBasicSecurityUserhttp://www.symfony-project.org/api/1_2/sfBasicSecurityUser
  • 7/23/2019 Symfony Tutorial de Jobeet

    196/309

    13.3.2. Autorizacin

    Adems de la autenticacin de los usuarios, se puede restringir todava ms el acceso aalgunas acciones mediante la definicin de credenciales . Para acceder a una pginadeterminada, el usuario debe contar con ciertas credenciales:

    default:is_secure: offcredentials: admin

    El sistema de credenciales de Symfony es bastante sencillo pero muy poderoso. Cadacredencial puede representar cualquier cosa que requiera el modelo de seguridad de tuaplicacin (como por ejemplo grupos o permisos).

    Credenciales avanzadas

    La opcin credentials del archivo de configuracin security.yml permite el uso de

    operaciones booleanas para describir los requerimientos de un sistema avanzado decredenciales.

    Si un usuario debe disponer de dos credenciales, se indican entre corchetes. En el siguienteejemplo, el usuario debe disponer tanto de la credencial Acomo de la credencial B:

    index:credentials: [ A, B ]

    Si un usuario debe disponer de al menos una de las dos credenciales, se indican con dos pares decorchetes. En el siguiente ejemplo, el usuario debe disponer o de la credencial A o de lacredencial B:

    index:credentials: [[ A, B ]]

    Tambin puedes combinar varios corchetes entre s para describir cualquier tipo de expresinbooleana compleja que utilice cualquier nmero de credenciales.

    La clase sfBasicSecurityUser incluye varios mtodos para gestionar las credencialesde los usuarios:

    // Add one or more credentials$user->addCredential('foo');

    $user->addCredentials('foo', 'bar');

    // Check if the user has a credentialecho $user->hasCredential('foo'); => true

    // Check if the user has both credentialsecho $user->hasCredential(array('foo', 'bar')); => true

    // Check if the user has one of the credentialsecho $user->hasCredential(array('foo', 'bar'), false); => true

    // Remove a credential

    $user->removeCredential('foo');echo $user->hasCredential('foo'); => false

    Jobeet Captulo 13. El usuario

    www.librosweb.es 196

  • 7/23/2019 Symfony Tutorial de Jobeet

    197/309

    // Remove all credentials (useful in the logout process)$user->clearCredentials();echo $user->hasCredential('bar'); => false

    En la parte de administracin de Jobeet no vamos a utilizar credenciales porque slotenemos un perfil de usuario: el administrador.

    13.4. Plugins

    Como no nos gusta reinventar la rueda cada vez que tenemos que aadir unafuncionalidad en la aplicacin, no vamos a desarrollar un completo sistema de login, sinoque vamos a instalar un plugin de Symfony .

    Uno de los puntos fuertes del framework Symfony es su ecosistema de plugins(http://www.symfony-project.org/plugins/) . Como veremos en los prximos das, es

    muy sencillo crear un plugin. Adems, los plugins son muy poderosos, ya que puedencontener desde configuracin hasta mdulos enteros y archivos.

    Hoy vamos a instalar el plugin sfGuardPlugin (http://www.symfony-project.org/plugins/sfGuardPlugin) para restringir el acceso a la aplicacin backend :

    $ php symfony plugin:install sfGuardPlugin

    La tarea plugin:install instala el plugin cuyo nombre se pasa como parmetro. Todos

    los plugins se guardan en el directorio plugins/ y cada plugin dispone de su propiodirectorio llamado igual que el plugin.

    Nota

    Debes tener PEAR correctamente instalado y configurado en tu sistema para que funcione latarea plugin:install .

    Cuando se instalar un plugin con la tarea plugin:install , Symfony siempre instala sultima versin estable. Para instalar una versin especfica del plugin, puedes utilizar laopcin --release . La pgina de cada plugin, como por ejemplo la pgina del pluginsfGuardPlugin (http://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_all_releases) , muestra un listado de todas las versionesdisponibles para cada versin de Symfony.

    Como cada plugin se instala en su propio directorio, tambin puedes descargarsfGuardPlugin como archivo comprimido (http://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_installation) y descomprimirlo en el directoriocorrespondiente. Tambin puedes establecer un enlace con svn:externals al

    repositorio Subversion de sfGuardPlugin (http://svn.symfony-project.com/plugins/sfGuardPlugin) .

    Jobeet Captulo 13. El usuario

    www.librosweb.es 197

    http://www.symfony-project.org/plugins/http://www.symfony-project.org/plugins/http://www.symfony-project.org/plugins/sfGuardPluginhttp://www.symfony-project.org/plugins/sfGuardPluginhttp://www.symfony-project.org/plugins/sfGuardPluginhttp://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_all_releaseshttp://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_all_releaseshttp://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_all_releaseshttp://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_all_releaseshttp://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_installationhttp://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_installationhttp://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_installationhttp://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_installationhttp://svn.symfony-project.com/plugins/sfGuardPluginhttp://svn.symfony-project.com/plugins/sfGuardPluginhttp://svn.symfony-project.com/plugins/sfGuardPluginhttp://svn.symfony-project.com/plugins/sfGuardPluginhttp://svn.symfony-project.com/plugins/sfGuardPluginhttp://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_installationhttp://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_installationhttp://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_installationhttp://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_all_releaseshttp://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_all_releaseshttp://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_all_releaseshttp://www.symfony-project.org/plugins/sfGuardPluginhttp://www.symfony-project.org/plugins/sfGuardPluginhttp://www.symfony-project.org/plugins/http://www.symfony-project.org/plugins/
  • 7/23/2019 Symfony Tutorial de Jobeet

    198/309

    13.5. La seguridad de la aplicacin backend

    Cada plugin dispone de su propio archivo README (http://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_readme) donde se explica cmo se configura. A

    continuacin se muestra cmo configurar el pluginsfGuardPlugin

    . Como se trata de unplugin que incluye varias clases de su propio modelo de datos para gestionar usuarios,grupos y permisos, lo primero que debemos hacer es volver a generar todas las clasesdel modelo:

    $ php symfony propel:build-all-load --no-confirmation

    Sugerencia

    Recuerda que la tarea propel:build-all-load borra todas las tablas de la base de datos antesde volver a crearlas. Si no quieres borrar las tablas, puedes generar los modelos, formularios yfiltros y despus, puedes crear las nuevas tablas ejecutando las sentencias SQL generadas en el

    directorio data/sql .

    Como siempre que se crean nuevas clases, no te olvides de borrar la cache de Symfony:

    $ php symfony cc

    Como el plugin sfGuardPlugin aade varios mtodos a la clase del usuario, tienes quemodificar la clase de la que hereda myUser a sfGuardSecurityUser :

    // apps/backend/lib/myUser.class.phpclass myUser extends sfGuardSecurityUser{}

    El plugin sfGuardPlugin incluye una accin llamada signin en el mdulo sfGuardAuth

    para autenticar a los usuarios:

    Modifica el archivo settings.yml para cambiar la accin utilizada por defecto en lapgina de login:

    # apps/backend/config/settings.ymlall:

    .settings:enabled_modules: [default, sfGuardAuth]

    # ...

    .actions:login_module: sfGuardAuthlogin_action: signin

    # ...

    Como los plugins estn disponibles en todas las aplicaciones del proyecto, tienes queactivar de forma explcita los mdulos que quieres utilizar mediante la opcin

    enabled_modules .

    Jobeet Captulo 13. El usuario

    www.librosweb.es 198

    http://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_readmehttp://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_readmehttp://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_readmehttp://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_readmehttp://www.symfony-project.org/plugins/sfGuardPlugin?tab=plugin_readme
  • 7/23/2019 Symfony Tutorial de Jobeet

    199/309

    Figura 13.5. Pantalla de login del plugin sfGuardPlugin

    Por ltimo, crea el usuario de tipo administrador:

    $ php symfony guard:create-user fabien ConTraSenA$ php symfony guard:promote fabien

    Sugerencia

    El plugin sfGuardPlugin incluye tareas para gestionar usuarios, grupos y permisos directamentedesde la lnea de comandos. Si quieres ver todas las tareas disponibles para el namespace guard ,puedes utilizar la tarea list :

    $ php symfony list guard

    El siguiente paso consiste en no mostrar la barra del men si el usuario no estautenticado:

    // apps/backend/templates/layout.php

    Por otra parte, cuando el usuario est autenticado, tenemos que mostrar un enlace parala accin de desconectar que incluye el plugin sfGuardPlugin :

    // apps/backend/templates/layout.php

    Sugerencia

    Si quieres ver todas las rutas que define sfGuardPlugin , utiliza la tarea app:routes .

    Para completar la parte de administracin de Jobeet, vamos a aadir un mdulo paragestionar los usuarios de tipo administrador. Afortunadamente, el plugin sfGuardPlugin

    ya incluye un mdulo de este tipo. Para utilizarlo, debes activar el mdulo llamado

    sfGuardAuth en el archivo de configuracin settings.yml :

    Jobeet Captulo 13. El usuario

    www.librosweb.es 199

  • 7/23/2019 Symfony Tutorial de Jobeet

    200/309

    # apps/backend/config/settings.ymlall:

    .settings:enabled_modules: [default, sfGuardAuth, sfGuardUser]

    Y por ltimo, aade un enlace en el men:

    // apps/backend/templates/layout.php

    Figura 13.6. Men de la parte de administracin

    Y eso es todo lo que tenemos que hacer para disponer de una completa gestin deusuarios, grupos y permisos.

    13.6. Probando a los usuarios

    El tutorial de hoy todava no se ha acabado porque todava no hemos hablado de cmoprobar la parte de los usuarios. Como el navegador que incluye Symfony tambin simulael comportamiento de las cookies, es muy sencillo crear pruebas para la parte de losusuarios utilizando el tester sfTesterUser (http://symfony-project.org/api/1_2/sfTesterUser) .

    A continuacin vamos a actualizar las pruebas funcionales para las opciones del menque hemos aadido durante el da de hoy. Aade el siguiente cdigo al final de laspruebas funcionales del mdulo 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')->

    Jobeet Captulo 13. El usuario

    www.librosweb.es 200

    http://symfony-project.org/api/1_2/sfTesterUserhttp://symfony-project.org/api/1_2/sfTesterUserhttp://symfony-project.org/api/1_2/sfTesterUserhttp://symfony-project.org/api/1_2/sfTesterUserhttp://symfony-project.org/api/1_2/sfTesterUser
  • 7/23/2019 Symfony Tutorial de Jobeet

    201/309

    click('Web Developer', array(), array('position' => 1))->get('/')->with('user')->begin()->

    isAttribute('job_history',array($browser->getMostRecentProgrammingJob()->getId()))->

    end();

    Para que las pruebas sean ms sencillas, en primer lugar volvemos a cargar los datos deprueba y reiniciamos el navegador para comenzar con una sesin de usuario limpia.

    El mtodo isAttribute() comprueba el atributo de usuario que se indica.

    Nota

    El tester sfTesterUser tambin incluye los mtodos isAuthenticated() y hasCredential()para poder probar respectivamente la autenticacin y la autorizacin del usuario.

    13.7. Nos vemos maana

    Las clases de usuario de Symfony son una buena forma de abstraerse de la gestin desesiones de PHP. Si a ello unimos el sistema de plugins de Symfony y sobre todo, elplugin sfGuardPlugin , podemos restringir la seguridad de la parte de administracin deJobeet en pocos minutos. Adems, gracias a los mdulos que incluye el plugin, hemospodido aadir un gestor de usuarios de tipo administrador.

    Jobeet Captulo 13. El usuario

    www.librosweb.es 201

  • 7/23/2019 Symfony Tutorial de Jobeet

    202/309

    Captulo 14. El da de descansoDespus de la explicacin ayer de las clases relacionadas con los usuarios, ya hemoscompletado el recorrido por todas las caractersticas fundamentales de Symfony.Aunque todava te quedan muchas cosas por aprender, ya deberas ser capaz de crearpor tu cuenta proyectos Symfony sencillos.

    Para celebrar este hito, hoy vamos a hacer un descanso. En realidad, slo vamos adescansar nosotros, porque hoy no vamos a publicar ningn tutorial. No obstante,vamos a darte unas pistas sobre lo que podras hacer hoy para mejorar tus habilidadescon Symfony.

    14.1. Aprendiendo con la prctica

    El framework Symfony, como cualquier otra aplicacin, tiene su propia curva deaprendizaje. El primer paso en el proceso de aprendizaje consiste en utilizar ejemplosprcticos, tutoriales o libros como el que ests leyendo. El segundo paso consiste enpracticar , que es algo que jams se podr reemplazar.

    Esto es precisamente lo que puedes empezar a hacer hoy mismo. Piensa en cualquierproyecto web sencillo que pueda aportar valor: una lista de tareas, un blog sencillo, unconversor de divisas, etc. Selecciona un proyecto y empieza a desarrollarlo con todo loque ya sabes.

    Haz uso de los mensajes de ayuda de las tareas para aprender cada una de sus opciones,investiga el cdigo generado automticamente por Symfony, utiliza un editor de textosque tenga autocompletado de PHP como Eclipse (http://www.eclipse.org/) , lee ladocumentacin de la API (http://www.symfony-project.org/api/1_2/) para descubrirnuevos mtodos, pregunta todas las dudas que tengas en el grupo de usuarios de Google(http://groups.google.com/group/symfony-es/) , conctate al chat en el canal #symfonydel IRC de freenode (irc://irc.freenode.net/symfony) .

    Y sobre todo, disfruta de la gran cantidad de material gratuito relacionado con Symfonyque tienes a tu disposicin.

    Jobeet Captulo 14. El da de descanso

    www.librosweb.es 202

    http://www.eclipse.org/http://www.eclipse.org/http://www.symfony-project.org/api/1_2/http://www.symfony-project.org/api/1_2/http://groups.google.com/group/symfony-es/http://groups.google.com/group/symfony-es/http://e/javier/proyectos_sf/librosweb/data/books/jobeet/temp/screen/irc:/irc.freenode.net/symfonyhttp://e/javier/proyectos_sf/librosweb/data/books/jobeet/temp/screen/irc:/irc.freenode.net/symfonyhttp://e/javier/proyectos_sf/librosweb/data/books/jobeet/temp/screen/irc:/irc.freenode.net/symfonyhttp://e/javier/proyectos_sf/librosweb/data/books/jobeet/temp/screen/irc:/irc.freenode.net/symfonyhttp://e/javier/proyectos_sf/librosweb/data/books/jobeet/temp/screen/irc:/irc.freenode.net/symfonyhttp://groups.google.com/group/symfony-es/http://groups.google.com/group/symfony-es/http://www.symfony-project.org/api/1_2/http://www.eclipse.org/
  • 7/23/2019 Symfony Tutorial de Jobeet

    203/309

    Captulo 15. Canales AtomSi seguiste nuestra recomendacin, ayer empezaste a desarrollar tu propio proyecto deSymfony. No abandones ese proyecto y contina aadiendo caractersticas a tuaplicacin a medida que aprendas ms conceptos avanzados de Symfony. Adems,puedes colgar tu proyecto en cualquier servidor pblico de Internet para compartirlocon la comunidad.

    Sin embargo, nosotros hoy nos vamos a dedicar a algo completamente diferente.

    Si ests buscando trabajo, seguramente te interesa enterarte lo antes posible de lasofertas de trabajo que se publican. Como evidentemente no es lgico estar entrando enel sitio web cada poco tiempo para comprobar si se han publicado nuevas ofertas, hoyvamos a aadir varios canales Atom a la aplicacin para mantener actualizados a losusuarios de Jobeet.

    15.1. Formatos

    El framework Symfony incluye soporte de muchos formatos y tipos MIME. Esto significaque la parte del modelo y del controlador pueden utilizar diferentes plantillas en funcindel formato en el que se realiza la peticin. El formato por defecto es HTML, peroSymfony soporta muchos otros formatos como txt , js , css , json , xml, rdf y atom .

    El formato se puede establecer con el mtodo setRequestFormat() del objeto de lapeticin:

    $request->setRequestFormat('xml');

    No obstante, el formato se incluye casi siempre en la propia URL. En este caso, Symfonyestablece automticamente el formato si en la ruta correspondiente se utiliza unavariable especial llamada sf_format . La URL del listado de ofertas de trabajo es:

    http://jobeet.localhost/frontend_dev.php/job

    La URL anterior es equivalente a:

    http://jobeet.localhost/frontend_dev.php/job.html

    Las dos URL anteriores son equivalentes porque las rutas generadas por la clasesfPropelRouteCollection incluyen la variable sf_format como extensin del archivo yporque html es el valor por defecto. Si quieres comprobarlo tu mismo, puedes utilizar la

    tarea app:routes que muestra todas las rutas de la aplicacin:

    Jobeet Captulo 15. Canales Atom

    www.librosweb.es 203

  • 7/23/2019 Symfony Tutorial de Jobeet

    204/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    205/309

    Por qu aadimos el sufijo Success al nombre de todas las plantillas? Las acciones de Symfonypueden devolver un valor que indica la plantilla que se debe utilizar. Si la accin no devuelvenada, se considera que es equivalente al siguiente cdigo:

    return sfView:: SUCCESS; // == 'Success'

    Si quieres modificar el sufijo del nombre de la plantilla, simplemente devuelve cualquier otrovalor:

    return sfView:: ERROR; // == 'Error'

    return 'Foo' ;

    Tambin puedes modificar el nombre de la plantilla utilizando el mtodo setTemplate() :

    $this -> setTemplate('foo') ;

    Symfony modifica el valor del Content-Type de la respuesta en funcin del formatoutilizado y adems, deshabilita el layout para cualquier formato que no sea HTML. En elcaso del canal Atom, Symfony cambia el valor del Content-Type a application/atom+xml; charset=utf-8

    A continuacin, actualiza en el pie de pgina del layout de Jobeet el enlace al nuevocanal:

  • 7/23/2019 Symfony Tutorial de Jobeet

    206/309

    Jobeet

    Si te fijas en el cdigo anterior, vers que hemos utilizado la letra Ucomo argumento delmtodo getCreatedAt() para obtener la fecha en forma de timestamp. Si quieres

    obtener la fecha de la ltima oferta de trabajo, crea un mtodo llamadogetLatestPost() :

    // lib/model/JobeetJobPeer.phpclass JobeetJobPeer extends BaseJobeetJobPeer{

    static public function getLatestPost(){

    $criteria = new Criteria();self::addActiveJobsCriteria($criteria);

    return JobeetJobPeer::doSelectOne($criteria);}

    // ...}

    Una vez terminada la cabecera, el cuerpo del canal Atom se puede generar con elsiguiente cdigo:

    ()

  • 7/23/2019 Symfony Tutorial de Jobeet

    207/309

    How to apply?

    El mtodo getHost() del objeto de la peticin ( $sf_request ) devuelve el host o servidor

    actual, lo que resulta muy til para crear el enlace absoluto de la imagen del logotipo dela empresa.

    Figura 15.2. Canal Atom tal y como se muestra en el navegador

    SugerenciaCuando desarrollas canales RSS o Atom, es mucho ms fcil depurarlos si utilizas herramientasde la lnea de comandos como curl (http://curl.haxx.se/) o wget (http://www.gnu.org/software/wget/) , ya que te permiten ver directamente el contenido real del canal.

    15.2.2. Canal de las ltimas ofertas de trabajo de una categora

    Uno de los objetivos de Jobeet es ayudar a la gente a encontrar puestos de trabajo muyespecficos. Por tanto, es imprescindible que incluyamos canales en cada categora.

    En primer lugar, actualiza la ruta category para aadir el soporte de varios formatos:

    Jobeet Captulo 15. Canales Atom

    www.librosweb.es 207

    http://curl.haxx.se/http://curl.haxx.se/http://www.gnu.org/software/wget/http://www.gnu.org/software/wget/http://www.gnu.org/software/wget/http://www.gnu.org/software/wget/http://www.gnu.org/software/wget/http://curl.haxx.se/
  • 7/23/2019 Symfony Tutorial de Jobeet

    208/309

    # apps/frontend/config/routing.ymlcategory:

    url: /category/:slug.:sf_formatclass: sfPropelRouteparam: { module: category, action: show, sf_format: html }options: { model: JobeetCategory, type: object }requirements:

    sf_format: (?:html|atom)

    Ahora la ruta category ya es capaz de reconocer los formatos html y atom . El siguientepaso consiste en actualizar en la plantilla los enlaces a los canales de cada categora:

    )

  • 7/23/2019 Symfony Tutorial de Jobeet

    209/309

    How to apply?

    Utilizando este elemento parcial _list.atom.php se puede simplificar mucho la plantilladel canal que hemos creado en la seccin anterior y que muestra las ltimas ofertas detrabajo de todo el sitio:

    JobeetLatest Jobs

    Jobeet ()Latest Jobs

  • 7/23/2019 Symfony Tutorial de Jobeet

    210/309

    Al igual que para el canal principal del sitio, tenemos que calcular la fecha de la ltimaoferta de trabajo de cada categora:

    // lib/model/JobeetCategory.phpclass JobeetCategory extends BaseJobeetCategory{

    public function getLatestPost(){

    $jobs = $this->getActiveJobs(1);

    return $jobs[0];}

    // ...}

    Figura 15.3. Canal Atom de cada categora

    15.3. Nos vemos maana

    Como sucede con otras muchas caractersticas de Symfony, el soporte nativo deformatos y tipos MIME permite crear canales Atom de forma sencilla y sin esfuerzo.

    Hoy hemos mejorado la experiencia de usuario de los que buscan trabajo. Maanamejoraremos la experiencia de usuario de los que publican las ofertas de trabajomediante la creacin de servicios web.

    Jobeet Captulo 15. Canales Atom

    www.librosweb.es 210

  • 7/23/2019 Symfony Tutorial de Jobeet

    211/309

    Captulo 16. Servicios webAyer aadimos canales Atom a la aplicacin, de forma que los usuarios que buscantrabajo con Jobeet pueden estar informados casi en tiempo real de las nuevas ofertasque se publican.

    Si se considera el otro lado del proceso, cuando un usuario publica una oferta de trabajo,seguramente quiere que esa oferta sea vista por la mayor cantidad de personas. Si laoferta de trabajo se publica de forma simultnea en muchos sitios web, es ms probableque puedas encontrar a la persona adecuada para el puesto. Este fenmeno se conocecomo el long tail (http://es.wikipedia.org/wiki/Larga_Cola) . Hoy vamos a desarrollarlos servicios web que van a permitir a los afiliados publicar las ltimas ofertas de trabajoen sus propios sitios web.

    16.1. Los afiliados

    En los escenarios del tutorial del da 2 establecimos que "un usuario afiliado obtiene lalista de ofertas de trabajo activas" .

    16.1.1. Los archivos de datos

    A continuacin vamos a crear un nuevo archivo de datos para la informacin de losafiliados:

    # data/fixtures/030_affiliates.ymlJobeetAffiliate:

    sensio_labs:url: http://www.sensio-labs.com/email: [email protected]_active: truetoken: sensio_labsjobeet_category_affiliates: [programming]

    symfony:url: http://www.symfony-project.org/email: [email protected]_active: falsetoken: symfonyjobeet_category_affiliates: [design, programming]

    Cuando se establecen relaciones muchos-a-muchos, crear los registros de la tablaintermedia es tan sencillo como definir un array cuya clave sea el nombre de la tablaintermedia seguido de una letra s . El contenido del array est formado por los nombresde los objetos que se han definido en los archivos de datos. Puedes utilizar objetosdefinidos en otros archivos de datos, pero con la condicin de que los objetos hayan sidodefinidos antes de utilizarlos (el orden en el que se cargan los archivos YAML es

    importante).

    Jobeet Captulo 16. Servicios web

    www.librosweb.es 211

    http://es.wikipedia.org/wiki/Larga_Colahttp://es.wikipedia.org/wiki/Larga_Colahttp://es.wikipedia.org/wiki/Larga_Cola
  • 7/23/2019 Symfony Tutorial de Jobeet

    212/309

    El archivo de datos anterior ya incluye el valor del token de cada afiliado para que laspruebas sean ms fciles. En cualquier caso, cuando un usuario real solicita una cuenta,el token se debe generar automticamente:

    // lib/model/JobeetAffiliate.php

    class JobeetAffiliate extends BaseJobeetAffiliate{public function save(PropelPDO $con = null){

    if (!$this->getToken()){

    $this->setToken(sha1($this->getEmail().rand(11111, 99999)));}

    return parent::save($con);}

    // ...}

    Despus de crear el archivo de datos, ya puedes volver a cargar todos los datos deprueba:

    $ php symfony propel:data-load

    16.1.2. El servicio web de las ofertas de trabajo

    Como ya hemos explicado varias veces, siempre que vayas a aadir alguna nuevafuncionalidad a la aplicacin, es mejor pensar primero en su URL:

    # apps/frontend/config/routing.ymlapi_jobs:

    url: /api/:token/jobs.:sf_formatclass: sfPropelRouteparam: { module: api, action: list }options: { model: JobeetJob, type: list, method: getForToken }requirements:

    sf_format: (?:xml|json|yaml)

    En la ruta anterior, la variable especial sf_format es el ltimo elemento que forma laURL y sus posibles valores son xml, json o yaml .

    El mtodo getForToken() se invoca cuando la accin obtiene la coleccin de objetosrelacionados con la ruta. Como es necesario comprobar que el afiliado se encuentraactivado, debemos redefinir el comportamiento por defecto de la ruta:

    // lib/model/JobeetJobPeer.phpclass JobeetJobPeer extends BaseJobeetJobPeer{

    static public function getForToken(array $parameters){

    $affiliate = JobeetAffiliatePeer::getByToken($parameters['token']);

    if (!$affiliate || !$affiliate->getIsActive()){

    Jobeet Captulo 16. Servicios web

    www.librosweb.es 212

  • 7/23/2019 Symfony Tutorial de Jobeet

    213/309

    throw new sfError404Exception(sprintf('Affiliate with token "%s" does notexist or is not activated.', $parameters['token']));

    }

    return $affiliate->getActiveJobs();}

    // ...}

    Si el token no existe en la base de datos, se lanza una excepcin de tiposfError404Exception . Despus, esta clase se convierte automticamente en unarespuesta de error de tipo 404. Esta es por tanto la forma ms sencilla de generar una

    pgina de error 404 desde una clase del modelo.

    El mtodo getForToken() utiliza, a su vez, otros dos nuevos mtodos que vamos a creara continuacin.

    En primer lugar tenemos que crear el mtodo getByToken() para obtener los datos deun afiliado a partir del token que se indica:

    // lib/model/JobeetAffiliatePeer.phpclass JobeetAffiliatePeer extends BaseJobeetAffiliatePeer{

    static public function getByToken($token){

    $criteria = new Criteria();$criteria->add(self::TOKEN, $token);

    return self::doSelectOne($criteria);}

    }

    En segundo lugar, el mtodo getActiveJobs() devuelve el listado de las actuales ofertas

    de trabajo activas para las categoras seleccionadas por el afiliado:

    // lib/model/JobeetAffiliate.phpclass JobeetAffiliate extends BaseJobeetAffiliate{

    public function getActiveJobs(){

    $cas = $this->getJobeetCategoryAffiliates();$categories = array();foreach ($cas as $ca){

    $categories[] = $ca->getCategoryId();}

    $criteria = new Criteria();$criteria->add(JobeetJobPeer::CATEGORY_ID, $categories, Criteria::IN);JobeetJobPeer::addActiveJobsCriteria($criteria);

    return JobeetJobPeer::doSelect($criteria);}

    Jobeet Captulo 16. Servicios web

    www.librosweb.es 213

  • 7/23/2019 Symfony Tutorial de Jobeet

    214/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    215/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    216/309

    public function executeList(sfWebRequest $request){

    $this->jobs = array();foreach ($this->getRoute()->getObjects() as $job){

    $this->jobs[$this->generateUrl('job_show_user', $job, true)] =$job->asArray($request->getHost());

    }

    switch ($request->getRequestFormat()){

    case 'yaml':$this->setLayout(false);$this->getResponse()->setContentType('text/yaml');break;

    }}

    }

    En una accin, el mtodo setLayout() modifica el layout utilizado por defecto y tambinpermite deshabilitarlo si utilizas el valor false .

    A continuacin se muestra la plantilla resultante para el formato YAML:

    -

    url:

    :

    Si realizas una llamada a este servicio web con un token invlido, vers una pgina deerror 404 en formato XML si la peticin la realizas en XML y una pgina de error 404 enformato JSON si tu peticin estaba en el formato JSON. Sin embargo, si se produce unerror con una peticin en formato YAML, symfony no sabe lo que debe mostrar.

    Cada vez que creas un nuevo formato, debes crear una plantilla de error asociada. Estaplantilla se utiliza para las pginas del error 404 pero tambin para todas las dems

    excepciones.

    Como las excepciones deben ser diferentes en el entorno de produccin y en el dedesarrollo, debes crear dos archivos diferentes: config/error/exception.yaml.php

    para el entorno de desarrollo y config/error/error.yaml.php para el de produccin:

    // config/error/exception.yaml.php

  • 7/23/2019 Symfony Tutorial de Jobeet

    217/309

    'name' => $name,'message' => $message,'traces' => $traces,

    ),)), 4) ?>

    // config/error/error.yaml.php

    Por ltimo, antes de probar estas pginas no te olvides de crear un layout para elformato YAML:

    // apps/frontend/templates/layout.yaml.php

    Figura 16.1. Pgina de error 404

    Sugerencia

    Si quieres redefinir las plantillas que incluye Symfony por defecto para el error 404 y lasexcepciones, tan slo debes crear los archivos correspondientes en el directorio config/error/ .

    16.2. Probando los servicios web

    Si quieres probar el nuevo servicio web que acabamos de crear, copia el archivo dedatos de los afiliados del directorio data/fixtures/ al directorio test/fixtures/ y

    reemplaza el contenido del archivo apiActionsTest.php generado automticamentepor el siguiente cdigo:

    // test/functional/frontend/apiActionsTest.phpinclude(dirname(__FILE__).'/../../bootstrap/functional.php');

    $browser = new JobeetTestFunctional(new sfBrowser());$browser->loadData();

    $browser->info('1 - Web service security')->

    Jobeet Captulo 16. Servicios web

    www.librosweb.es 217

  • 7/23/2019 Symfony Tutorial de Jobeet

    218/309

    info(' 1.1 - A token is needed to access the service')->get('/api/foo/jobs.xml')->with('response')->isStatusCode(404)->

    info(' 1.2 - An inactive account cannot access the web service')->get('/api/symfony/jobs.xml')->with('response')->isStatusCode(404)->

    info('2 - The jobs returned are limited to the categories configured for theaffiliate')->

    get('/api/sensio_labs/jobs.xml')->with('request')->isFormat('xml')->with('response')->checkElement('job', 32)->

    info('3 - The web service supports the JSON format')->get('/api/sensio_labs/jobs.json')->with('request')->isFormat('json')->with('response')->contains('"category": "Programming"')->

    info('4 - The web service supports the YAML format')->get('/api/sensio_labs/jobs.yaml')->with('response')->begin()->

    isHeader('content-type', 'text/yaml; charset=utf-8')->contains('category: Programming')->

    end();

    En el cdigo anterior se utilizan por primera vez dos mtodos que te pueden resultartiles:

    isFormat() : comprueba el formato de la respuesta

    contains() : para el contenido que no sea HTML comprueba si la respuestacontiene el trozo de texto que se indica

    16.3. El formulario para darse de alta como afiliado

    Despus de haber preparado el servicio web, el siguiente paso consiste en crear elformulario con el que los afiliados se van a dar de alta. Una vez ms, vamos a describirpaso a paso cmo aadir una nueva caracterstica a la aplicacin.

    16.3.1. Sistema de enrutamiento

    Como ya habrs adivinado, lo primero que hacemos es pensar en la URL de la nuevafuncionalidad:

    # apps/frontend/config/routing.ymlaffiliate:

    class: sfPropelRouteCollectionoptions:

    model: JobeetAffiliateactions: [new, create]object_actions: { wait: get }

    Jobeet Captulo 16. Servicios web

    www.librosweb.es 218

  • 7/23/2019 Symfony Tutorial de Jobeet

    219/309

    La ruta anterior es una coleccin de rutas de Propel que utiliza una nueva opcinllamada actions . Como en este caso no necesitamos las siete acciones que define estetipo de ruta, la opcin actions permite indicar las acciones para las que esta ruta debe

    funcionar (en el ejemplo anterior, slo las acciones new y create ). La ruta wait adicional

    se va a emplear para informar al afiliado sobre el estado de su cuenta.

    16.3.2. Inicializacin

    A continuacin, se genera automticamente el mdulo llamado affiliate :

    $ php symfony propel:generate-module frontend affiliate JobeetAffiliate--non-verbose-templates

    16.3.3. Plantillas

    La tarea propel:generate-module genera las acciones y plantillas de las siete accionesclsicas de las colecciones de rutas de Propel. Por tanto, entra en el directoriotemplates/ del mdulo y elimina todos los archivos salvo _form.php y newSuccess.php .

    En estos dos archivos, reemplaza su contenido por el siguiente cdigo:

    Become an Affiliate

    A continuacin, crea la plantilla waitSuccess.php para la accin wait adicional:

    Your affiliate account has been created

    Thank you!You will receive an email with your affiliate token

    Jobeet Captulo 16. Servicios web

    www.librosweb.es 219

  • 7/23/2019 Symfony Tutorial de Jobeet

    220/309

    as soon as your account will be activated.

    Por ltimo, modifica el enlace del pie de pgina para que apunte al nuevo mduloaffiliate :

    // apps/frontend/templates/layout.php

  • 7/23/2019 Symfony Tutorial de Jobeet

    221/309

    $this->validatorSchema['email'] = new sfValidatorEmail(array('required' =>true));

    }}

    El framework de formularios soporta las relaciones muchos-a-muchos. Por defecto, estetipo de relaciones se muestran en forma de lista desplegable mediante el widget sfWidgetFormChoice . Como ya vimos durante el tutorial del da 10, hemos cambiado laforma en la que se muestra este widget mediante la opcin expanded .

    Como los campos en los que se escriben emails y URL suelen ser ms largos que eltamao por defecto de la etiqueta , hemos establecido nuevos atributos HTMLcon el mtodo setAttribute() .

    Figura 16.2. El formulario de los afiliados

    16.3.5. Pruebas

    Como siempre que aadimos una nueva caracterstica a la aplicacin, no te olvides de

    crear las pruebas funcionales correspondientes.

    Reemplaza el contenido de las pruebas generadas automticamente para el mduloaffiliate por el siguiente cdigo:

    // test/functional/frontend/affiliateActionsTest.phpinclude(dirname(__FILE__).'/../../bootstrap/functional.php');

    $browser = new JobeetTestFunctional(new sfBrowser());$browser->loadData();

    $browser->

    info('1 - An affiliate can create an account')->

    Jobeet Captulo 16. Servicios web

    www.librosweb.es 221

  • 7/23/2019 Symfony Tutorial de Jobeet

    222/309

    get('/affiliate/new')->click('Submit', array('jobeet_affiliate' => array(

    'url' => 'http://www.example.com/','email' => '[email protected]','jobeet_category_affiliate_list' =>

    array($browser->getProgrammingCategory()->getId()),)))->isRedirected()->followRedirect()->with('response')->checkElement('#content h1', 'Your affiliate account has

    been created')->

    info('2 - An affiliate must at leat select one category')->

    get('/affiliate/new')->click('Submit', array('jobeet_affiliate' => array(

    'url' => 'http://www.example.com/','email' => '[email protected]',

    )))->with('form')->isError('jobeet_category_affiliate_list')

    ;

    Para simular la seleccin de elementos de tipo checkbox , se pasa un array con losidentificadores de los elementos que se quieren seleccionar. Para simplificar un pocoms la tarea, hemos creado un mtodo llamado getProgrammingCategory() en la claseJobeetTestFunctional :

    // lib/model/JobeetTestFunctional.class.phpclass JobeetTestFunctional extends sfTestFunctional

    {public function getProgrammingCategory(){

    $criteria = new Criteria();$criteria->add(JobeetCategoryPeer::SLUG, 'programming');

    return JobeetCategoryPeer::doSelectOne($criteria);}

    // ...}

    No obstante, quizs recuerdes que ya tenemos este mismo cdigo en el mtodogetMostRecentProgrammingJob() , por lo que vamos a refactorizar ese cdigo en un

    nuevo mtodo llamado getForSlug() en la clase JobeetCategoryPeer :

    // lib/model/JobeetCategoryPeer.phpstatic public function getForSlug($slug){

    $criteria = new Criteria();$criteria->add(self::SLUG, $slug);

    return self::doSelectOne($criteria);}

    Jobeet Captulo 16. Servicios web

    www.librosweb.es 222

  • 7/23/2019 Symfony Tutorial de Jobeet

    223/309

    No te olvides de modificar en la clase JobeetTestFunctional las dos veces que apareceel cdigo anterior.

    16.4. Administrando los afiliados

    Como el administrador debe activar a cada afiliado, tenemos que crear en la aplicacinbackend un nuevo mdulo llamado affiliate :

    $ php symfony propel:generate-admin backend JobeetAffiliate --module=affiliate

    Para que el administrador pueda acceder al nuevo mdulo, aade un enlace en el menprincipal que indique el nmero de afiliados que estn pendientes de activar:

  • 7/23/2019 Symfony Tutorial de Jobeet

    224/309

    actions: {}filter:

    display: [url, email, is_active]

    Si quieres mejorar la productividad de los administradores, modifica los filtros pordefecto para que muestren slo los afiliados pendientes de activar:

    // apps/backend/modules/affiliate/lib/affiliateGeneratorConfiguration.class.phpclass affiliateGeneratorConfiguration extendsBaseAffiliateGeneratorConfiguration{

    public function getFilterDefaults(){

    return array('is_active' => '0');}

    }

    El nico cdigo que tienes que escribir es el correspondiente a las acciones activate ydeactivate :

    // apps/backend/modules/affiliate/actions/actions.class.phpclass affiliateActions extends autoAffiliateActions{

    public function executeListActivate(){

    $this->getRoute()->getObject()->activate();

    $this->redirect('@jobeet_affiliate');}

    public function executeListDeactivate(){

    $this->getRoute()->getObject()->deactivate();

    $this->redirect('@jobeet_affiliate');}

    public function executeBatchActivate(sfWebRequest $request){

    $affiliates =JobeetAffiliatePeer::retrieveByPks($request->getParameter('ids'));

    foreach ($affiliates as $affiliate){

    $affiliate->activate();}

    $this->redirect('@jobeet_affiliate');}

    public function executeBatchDeactivate(sfWebRequest $request){

    $affiliates =

    JobeetAffiliatePeer::retrieveByPks($request->getParameter('ids'));

    Jobeet Captulo 16. Servicios web

    www.librosweb.es 224

  • 7/23/2019 Symfony Tutorial de Jobeet

    225/309

    foreach ($affiliates as $affiliate){

    $affiliate->deactivate();}

    $this->redirect('@jobeet_affiliate');}

    } // lib/model/JobeetAffiliate.php

    class JobeetAffiliate extends BaseJobeetAffiliate{

    public function activate(){

    $this->setIsActive(true);

    return $this->save();}

    public function deactivate(){

    $this->setIsActive(false);

    return $this->save();}

    // ...}

    Figura 16.3. La parte de administracin de los afiliados

    16.5. Enviando emails

    Cuando el administrador activa la cuenta de un afiliado, se debe mandar un email a eseusuario confirmndole su suscripcin e indicndole cul es su token.

    PHP dispone de muchas libreras buenas para mandar emails, como por ejemploSwiftMailer (http://www.swiftmailer.org/) , Zend_Mail (http://framework.zend.com/)

    Jobeet Captulo 16. Servicios web

    www.librosweb.es 225

    http://www.swiftmailer.org/http://www.swiftmailer.org/http://framework.zend.com/http://framework.zend.com/http://framework.zend.com/http://www.swiftmailer.org/
  • 7/23/2019 Symfony Tutorial de Jobeet

    226/309

    y ezcMail (http://ezcomponents.org/docs/tutorials/Mail) . Como en los tutoriales de losprximos das haremos uso de algunos componentes del Zend Framework, vamos autilizar Zend_Mail para enviar los emails.

    16.5.1. Instalacin y configuracin del Zend Framework

    La librera Zend_Mail forma parte del Zend Framework. Como no queremos utilizartodos los componentes de este framework, vamos a instalar solamente los componentesnecesarios en el directorio lib/vendor/ , el mismo en el que instalamos Symfony.

    En primer lugar, descarga el Zend Framework (http://framework.zend.com/download/overview) y descomprime sus archivos en el directorio lib/vendor/Zend/ . Acontinuacin, elimina todos los archivos y directorios salvo los siguientes, que son losque vamos a utilizar para enviar emails:

    Exception.php Loader/

    Loader.php

    Mail/

    Mail.php

    Mime/

    Mime.php

    Search/

    Nota

    El directorio Search/ no lo necesitamos para enviar emails pero s para el tutorial de maana.

    Despus, aade el siguiente cdigo en la clase ProjectConfiguration de tu proyecto

    para registrar el cargador automtico de clases de Zend:

    // config/ProjectConfiguration.class.phpclass ProjectConfiguration extends sfProjectConfiguration

    { static protected $zendLoaded = false;

    static public function registerZend(){

    if (self::$zendLoaded){

    return;}

    set_include_path(sfConfig::get('sf_lib_dir').'/vendor'.PATH_SEPARATOR.get_include_path());

    require_once sfConfig::get('sf_lib_dir').'/vendor/Zend/Loader.php';Zend_Loader::registerAutoload();

    Jobeet Captulo 16. Servicios web

    www.librosweb.es 226

    http://ezcomponents.org/docs/tutorials/Mailhttp://ezcomponents.org/docs/tutorials/Mailhttp://framework.zend.com/download/overviewhttp://framework.zend.com/download/overviewhttp://framework.zend.com/download/overviewhttp://framework.zend.com/download/overviewhttp://framework.zend.com/download/overviewhttp://ezcomponents.org/docs/tutorials/Mail
  • 7/23/2019 Symfony Tutorial de Jobeet

    227/309

    self::$zendLoaded = true;}

    // ...}

    16.5.2. Enviando emails

    Modifica la accin activate para enviar un email cuando el administrador valida unafiliado:

    // apps/backend/modules/affiliate/actions/actions.class.phpclass affiliateActions extends autoAffiliateActions{

    public function executeListActivate(){

    $affiliate = $this->getRoute()->getObject();

    $affiliate->activate();

    // send an email to the affiliateProjectConfiguration::registerZend();$mail = new Zend_Mail();$mail->setBodyText(addTo($affiliate->getEmail());$mail->setSubject('Jobeet affiliate token');$mail->send();

    $this->redirect('@jobeet_affiliate');}

    // ...}

    Para que el cdigo anterior funcione correctamente, modifica [email protected] poruna direccin de email vlida.

    Nota

    El sitio web del Zend Framework incluye un completo tutorial sobre la librera Zend_Mail(http://framework.zend.com/manual/en/zend.mail.html) .

    16.6. Nos vemos maana

    Gracias a la arquitectura REST de Symfony, es muy sencillo incluir servicios web en tusproyectos. Aunque en este tutorial slo hemos creado un servicio web de consulta de

    Jobeet Captulo 16. Servicios web

    www.librosweb.es 227

    http://framework.zend.com/manual/en/zend.mail.htmlhttp://framework.zend.com/manual/en/zend.mail.htmlhttp://framework.zend.com/manual/en/zend.mail.htmlhttp://framework.zend.com/manual/en/zend.mail.html
  • 7/23/2019 Symfony Tutorial de Jobeet

    228/309

    datos, ya tienes suficientes conocimientos de Symfony como para crear un servicio webde consulta y/o modificacin de datos.

    Como ya conoces el proceso de aadir nuevas funcionalidades en un proyecto, hoy hasido realmente sencillo crear el formulario para que los afiliados se den de alta y el

    correspondiente gestor de usuarios afiliados.

    Si recuerdas los requisitos que establecimos durante el da 2: "los afiliados tambin pueden limitar el nmero de ofertas de trabajo del listado y pueden especificar unacategora para refinar la bsqueda" .

    Como este requisito es realmente sencillo, vamos a dejar que seas tu mismo el que loimplemente.

    En el tutorial de maana aadiremos un buscador, que ser la ltima funcionalidad delsitio web de Jobeet.

    Jobeet Captulo 16. Servicios web

    www.librosweb.es 228

  • 7/23/2019 Symfony Tutorial de Jobeet

    229/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    230/309

    puedes consultar la documentacin sobre Zend Lucene (http://framework.zend.com/manual/en/zend.search.lucene.html) disponible en el sitio web del Zend Framework.

    Si seguiste el tutorial de ayer, ya tienes instalada la librera Zend Lucene como parte de

    la instalacin de Zend Framework que realizamos ayer para enviar emails.

    17.2. Indices

    El buscador de Jobeet debe encontrar todas las ofertas de trabajo que coincidan dealguna manera con las palabras clave introducidas por los usuarios. Por ello, antes depoder realizar cualquier bsqueda, es necesario crear los ndices con la informacin delas ofertas de trabajo. En el caso de Jobeet, los ndices generados los vamos a guardar enel directorio data/

    Zend Lucene incluye dos mtodos para obtener un ndice dependiendo de si ese ndiceya existe o no. Vamos a crear un helper en la clase JobeetJobPeer que devuelve o crea

    un ndice en funcin de si ya exista o no:

    // lib/model/JobeetJobPeer.phpstatic public function getLuceneIndex(){

    ProjectConfiguration::registerZend();

    if (file_exists($index = self::getLuceneIndexFile())){

    return Zend_Search_Lucene::open($index);

    }else{

    return Zend_Search_Lucene::create($index);}

    }

    static public function getLuceneIndexFile(){

    returnsfConfig::get('sf_data_dir').'/job.'.sfConfig::get('sf_environment').'.index';}

    17.2.1. El mtodo save()

    Cada vez que creamos, modificamos o borramos una oferta de trabajo, debemosactualizar el ndice. Modifica la clase JobeetJob para que se actualice el ndice cada vezque guardamos una oferta de trabajo en la base de datos:

    // lib/model/JobeetJob.phppublic function save(PropelPDO $con = null){

    // ...

    $ret = parent::save($con);

    Jobeet Captulo 17. El buscador

    www.librosweb.es 230

    http://framework.zend.com/manual/en/zend.search.lucene.htmlhttp://framework.zend.com/manual/en/zend.search.lucene.htmlhttp://framework.zend.com/manual/en/zend.search.lucene.htmlhttp://framework.zend.com/manual/en/zend.search.lucene.htmlhttp://framework.zend.com/manual/en/zend.search.lucene.html
  • 7/23/2019 Symfony Tutorial de Jobeet

    231/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    232/309

    contenido de estas columnas se indexa pero no se guarda porque al mostrar losresultados de bsqueda utilizaremos los objetos reales.

    17.2.2. Transacciones Propel

    Qu sucede si surge un problema al indexar una oferta de trabajo o si la oferta no seguarda correctamente en la base de datos? En este caso, tanto Propel como Zend Lucenelanzan una excepcin. No obstante, puede suceder que hayamos guardado una oferta detrabajo en la base de datos pero su informacin no se encuentre en el ndice. Para evitarque esto ocurra, vamos a encerrar las dos actualizaciones de datos en una transaccinque podremos anular en caso de error:

    // lib/model/JobeetJob.phppublic function save(PropelPDO $con = null){

    // ...

    if (is_null($con)){

    $con = Propel::getConnection(JobeetJobPeer::DATABASE_NAME,Propel::CONNECTION_WRITE);

    }

    $con->beginTransaction();try{

    $ret = parent::save($con);

    $this->updateLuceneIndex();

    $con->commit();

    return $ret;}catch (Exception $e){

    $con->rollBack();throw $e;

    }}

    17.2.3. El mtodo delete()

    Adems de modificar el mtodo save() , tambin tenemos que redefinir el mtododelete() para eliminar del ndice el registro de la oferta de trabajo borrada:

    // lib/model/JobeetJob.phppublic function delete(PropelPDO $con = null){

    $index = JobeetJobPeer::getLuceneIndex();

    if ($hit = $index->find('pk:'.$this->getId())){

    Jobeet Captulo 17. El buscador

    www.librosweb.es 232

  • 7/23/2019 Symfony Tutorial de Jobeet

    233/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    234/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    235/309

    // apps/frontend/templates/layout.phpAsk for a job

  • 7/23/2019 Symfony Tutorial de Jobeet

    236/309

    17.5. Tareas

    Tarde o temprano tendremos que crear una tarea que se encargue de limpiar el ndiceborrando las ofertas de trabajo expiradas y optimizando peridicamente el ndice. Como

    ya disponemos de una tarea que se encarga de la limpieza de la base de datos, podemosactualizarla para que tambin se encargue del mantenimiento del ndice:

    // lib/task/JobeetCleanupTask.class.phpprotected function execute($arguments = array(), $options = array()){

    $databaseManager = new sfDatabaseManager($this->configuration);

    // cleanup Lucene index $index = JobeetJobPeer::getLuceneIndex();

    $criteria = new Criteria();

    $criteria->add(JobeetJobPeer::EXPIRES_AT, time(), Criteria::LESS_THAN);$jobs = JobeetJobPeer::doSelect($criteria);foreach ($jobs as $job){

    if ($hit = $index->find('pk:'.$job->getId())){

    $hit->delete();}

    }

    $index->optimize();

    $this->logSection('lucene', 'Cleaned up and optimized the job index');

    // Remove stale jobs$nb = JobeetJobPeer::cleanup($options['days']);

    $this->logSection('propel', sprintf('Removed %d stale jobs', $nb));}

    La tarea anterior ahora tambin elimina del ndice todas las ofertas de trabajo expiradasy optimiza el ndice gracias al mtodo optimize() incluido en Zend Lucene.

    17.6. Nos vemos maanaHoy hemos creado un completo buscador con muchas funcionalidades en menos de unahora. El tutorial de hoy tambin nos ha servido para explicar que cada vez que quieresaadir una nueva caracterstica a tu aplicacin, deberas comprobar que otros no lahayan resuelto anteriormente. Primero deberas comprobar si esa nueva caractersticano es algo que ya est incluido en la API de Symfony 1.2(http://www.symfony-project.org/api/1_2/) .

    Despus, deberas comprobar que la nueva funcionalidad tampoco la resuelve ningunode los plugins de Symfony (http://www.symfony-project.org/plugins/) . Por ltimo, note olvides de comprobar las libreras del Zend Framework

    Jobeet Captulo 17. El buscador

    www.librosweb.es 236

    http://www.symfony-project.org/api/1_2/http://www.symfony-project.org/api/1_2/http://www.symfony-project.org/plugins/http://www.symfony-project.org/plugins/http://framework.zend.com/manual/en/http://framework.zend.com/manual/en/http://www.symfony-project.org/plugins/http://www.symfony-project.org/api/1_2/http://www.symfony-project.org/api/1_2/
  • 7/23/2019 Symfony Tutorial de Jobeet

    237/309

    (http://framework.zend.com/manual/en/) y las libreras de ezComponent (http://ezcomponents.org/docs) .

    Maana aadiremos cdigo JavaScript no intrusivo para mejorar el tiempo de respuestadel buscador actualizando los resultados en tiempo real a medida que el usuario escribe

    en el cuadro de bsqueda. Por tanto, maana tambin hablaremos de cmo utilizar AJAXcon Symfony.

    Jobeet Captulo 17. El buscador

    www.librosweb.es 237

    http://framework.zend.com/manual/en/http://ezcomponents.org/docshttp://ezcomponents.org/docshttp://ezcomponents.org/docshttp://ezcomponents.org/docshttp://framework.zend.com/manual/en/
  • 7/23/2019 Symfony Tutorial de Jobeet

    238/309

    Captulo 18. AJAXAyer implementamos un buscador completo para Jobeet gracias a la librera ZendLucene. Hoy vamos a mejorar el tiempo de respuesta del buscador mediante AJAX(http://es.wikipedia.org/wiki/AJAX) para convertir un buscador normal en un buscadoren tiempo real.

    Como el formulario de bsqueda debe funcionar tanto si se activa como si se desactivaJavaScript, vamos a incluir el buscador en tiempo real mediante JavaScript no intrusivo(http://es.wikipedia.org/wiki/JavaScript_no_obstructivo) . Adems, utilizar JavaScript no intrusivo garantiza una mejor separacin entre el cdigo HTML, CSS y JavaScript de laparte de cliente de la aplicacin.

    18.1. Instalando jQueryComo no queremos reinventar la rueda y perder el tiempo intentando solucionar lasdiferencias de comportamientos de JavaScript en cada navegador, vamos a utilizar unalibrera de JavaScript llamada jQuery (http://jquery.com/) . El framework Symfony no teobliga a utilizar ninguna librera concreta, ya que funciona con cualquier librera deJavaScript.

    Accede al sitio web de jQuery (http://jquery.com/) , descarga su ltima versin y guardael archivo JavaScript descargado en el directorio web/js/

    18.2. Incluyendo jQuery

    Como vamos a hacer uso de jQuery en todas las pginas, actualiza el layout para enlazarel archivo JavaScipt en la seccin . Ten en cuenta que debes insertar la funcinuse_javascript() antes que la llamada a include_javascripts() :

    Aunque podramos haber enlazado el archivo de jQuery directamente con una etiqueta , el uso del helper use_javascript() nos asegura que no incluimos en la

    pgina dos veces el mismo archivo de JavaScript.

    Nota

    Si quieres mejorar el rendimiento, puedes colocar el helper include_javascripts() justo antesde la etiqueta , tal y como explican las reglas sobre rendimiento de aplicaciones web(http://developer.yahoo.com/performance/rules.html#js_bottom) elaboradas por Yahoo.

    Jobeet Captulo 18. AJAX

    www.librosweb.es 238

    http://es.wikipedia.org/wiki/AJAXhttp://es.wikipedia.org/wiki/AJAXhttp://es.wikipedia.org/wiki/JavaScript_no_obstructivohttp://es.wikipedia.org/wiki/JavaScript_no_obstructivohttp://jquery.com/http://jquery.com/http://jquery.com/http://jquery.com/http://developer.yahoo.com/performance/rules.html#js_bottomhttp://developer.yahoo.com/performance/rules.html#js_bottomhttp://developer.yahoo.com/performance/rules.html#js_bottomhttp://developer.yahoo.com/performance/rules.html#js_bottomhttp://jquery.com/http://jquery.com/http://es.wikipedia.org/wiki/JavaScript_no_obstructivohttp://es.wikipedia.org/wiki/JavaScript_no_obstructivohttp://es.wikipedia.org/wiki/AJAXhttp://es.wikipedia.org/wiki/AJAX
  • 7/23/2019 Symfony Tutorial de Jobeet

    239/309

    18.3. Aadiendo los comportamientos

    Crear un buscador en tiempo real significa que cada vez que el usuario escribe uncarcter en el cuadro de bsqueda debemos realizar una llamada al servidor.

    Posteriormente, el servidor devuelve la informacin necesaria para poder actualizar laszonas de la pgina donde se muestran los resultados sin tener que recargarcompletamente la pgina.

    Aunque tradicionalmente los comportamientos de JavaScript se han incluido mediantelos atributos on*() de HTML, el principio bsico de funcionamiento de jQuery consiste

    en aadir los comportamientos de cada elemento despus de que la pgina se hacargado por completo. De esta forma, si deshabilitas JavaScript en el navegador, no seaade ningn comportamiento y el formulario sigue funcionando como un formularionormal.

    En primer lugar, creamos una funcin para responder al evento que se produce cada vezque el usuario pulsa una tecla en el cuadro de bsqueda:

    $('#search_keywords').keyup(function(key){

    if (this.value.length >= 3 || this.value == ''){

    // do something}

    });

    Nota

    No aadas todava el cdigo de JavaScript porque lo vamos a modificar muchas veces. En laprxima seccin vamos a incluir el cdigo JavaScript definitivo en el layout.

    Cada vez que el usuario pulsa una tecla, jQuery ejecuta la funcin annima definida en elcdigo anterior. En nuestro caso, slo realizamos una consulta al servidor si el usuarioha escrito ms de tres caracteres o si el usuario ha borrado completamente el contenidodel cuadro de bsqueda.

    Realizar la llamada al servidor mediante AJAX es tan sencillo como utilizar el mtodoload() sobre el elemento DOM que queremos actualizar:

    $('#search_keywords').keyup(function(key){

    if (this.value.length >= 3 || this.value == ''){

    $('#jobs').load($(this).parents('form').attr('action'), { query: this.value + '*' } }

    );}

    });

    Jobeet Captulo 18. AJAX

    www.librosweb.es 239

  • 7/23/2019 Symfony Tutorial de Jobeet

    240/309

    La parte de servidor que se encarga de responder a la peticin AJAX es la misma accinque se ejecuta cuando se realizan peticiones normales . En la siguiente seccinmostraremos los cambios necesarios en esa accin.

    Por ltimo, si JavaScript se encuentra activado, ocultamos el botn del formulario de

    bsqueda:

    $('.search input[type="submit"]').hide();

    18.4. Informando al usuario

    Cuando se realizan peticiones AJAX, las pginas no se actualizan instantneamente. Elnavegador espera la respuesta del servidor antes de poder actualizar los contenidos dela pgina. Por tanto, durante ese periodo de tiempo debemos mostrar algn tipo deindicacin visual para informar al usuario de que ya se ha realizado la peticin.

    Una prctica muy extendida consiste en mostrar durante la peticin AJAX un pequeoicono en movimiento. Por tanto, aade en el layout la imagen del icono y ocultala pordefecto:

    Ask for a job

  • 7/23/2019 Symfony Tutorial de Jobeet

    241/309

    {$('#loader').show();$('#jobs').load(

    $(this).parents('form').attr('action'),{ query: this.value + '*' },function() { $('#loader').hide(); }

    );}

    });});

    Tambin debes actualizar el layout para incluir este nuevo archivo JavaScript:

    JavaScript como accin

    Aunque el cdigo JavaScript que hemos utilizado para el buscador es esttico, en ocasiones losarchivos JavaScript deben ser dinmicos para poder incluir algo de cdigo PHP (como porejemplo para utilizar el helper url_for() ).

    JavaScript no es ms que otro formato y, como vimos hace algunos das, Symfony te permitetrabajar con los formatos de forma sencilla. Como el archivo JavaScript contiene elcomportamiento dinmico de una pgina, puedes utilizar la misma URL tanto para la pginacomo para el archivo JavaScript (utilizando en este ltimo caso la extensin .js ). Si por ejemploquieres crear un archivo JavaScript para definir el comportamiento del buscador, puedesmodificar la ruta job_search de la siguiente forma y puedes crear una plantilla llamadasearchSuccess.js.php :

    job_search:url: /search.:sf_formatparam: { module: job, action: search, sf_format: html }requirements:

    sf_format: ( ?:html|js )

    18.5. AJAX en las acciones

    Cuando JavaScript est activado, jQuery intercepta todas las teclas pulsadas por elusuario en el cuadro de bsqueda y realiza la llamada a la accin search . Si JavaScript no

    se encuentra activado, se ejecuta la misma accin search cuando el usuario enva elformulario pulsando la tecla ENTERo pulsando el botn Search .

    Por tanto, la accin search necesita conocer si la peticin se realiza mediante AJAX o no.

    Cuando una peticin se realiza con AJAX, el mtodo isXmlHttpRequest() del objeto de lapeticin devuelve true .

    Nota

    El mtodo isXmlHttpRequest() funciona con todas las principales libreras de JavaScript, comopor ejemplo Prototype, Mootools y jQuery.

    Jobeet Captulo 18. AJAX

    www.librosweb.es 241

  • 7/23/2019 Symfony Tutorial de Jobeet

    242/309

    // apps/frontend/modules/job/actions/actions.class.phppublic function executeSearch(sfWebRequest $request){

    if (!$query = $request->getParameter('query')){

    return $this->forward('job', 'index');}

    $this->jobs = JobeetJobPeer::getForLuceneQuery($query);

    if ($request->isXmlHttpRequest()){

    return $this->renderPartial('job/list', array('jobs' => $this->jobs));}

    }

    Como jQuery no recarga la pgina y slo reemplaza el contenido del elemento #jobs del

    DOM con el contenido de la respuesta del servidor, la pgina devuelta no debera estardecorada por el layout. Como este caso es el habitual, Symfony deshabilita por defecto ellayout cuando la peticin se realiza con AJAX.

    Adems, en vez de devolver la plantilla completa, slo tenemos que devolver elcontenido del elemento parcial job/list . El mtodo renderPartial() de la accin

    anterior devuelve como respuesta el contenido del elemento parcial y no la plantillacompleta.

    Si el usuario borra todos los caracteres del cuadro de bsqueda o si la bsqueda nodevuelve ningn resultado, vamos a mostrar un mensaje adecuado en lugar de la

    pantalla vaca que se muestra actualmente. Para que la accin devuelva una simplecadena de texto, podemos utilizar el mtodo renderText() :

    // apps/frontend/modules/job/actions/actions.class.phppublic function executeSearch(sfWebRequest $request){

    if (!$query = $request->getParameter('query')){

    return $this->forward('job', 'index');}

    $this->jobs = JobeetJobPeer::getForLuceneQuery($query);

    if ($request->isXmlHttpRequest()){

    if ('*' == $query || !$this->jobs){

    return $this->renderText('No results.');}else{

    return $this->renderPartial('job/list', array('jobs' => $this->jobs));}

    }}

    Jobeet Captulo 18. AJAX

    www.librosweb.es 242

  • 7/23/2019 Symfony Tutorial de Jobeet

    243/309

  • 7/23/2019 Symfony Tutorial de Jobeet

    244/309

    Captulo 19. Internacionalizacin y

    localizacinAyer terminamos de incluir el buscador en nuestra aplicacin hacindolo msinteresante gracias a AJAX. Hoy vamos a hablar sobre la internacionalizacin (palabraque se suele abreviar por i18n ) y la localizacin (abreviada como l10n ).

    Segn la definicin de la Wikipedia (http://es.wikipedia.org/wiki/Internacionalizacin_(computacin)) :

    "La internacionalizacin es el proceso de disear aplicaciones de software que puedan ser adaptadas a distintos idiomas y regiones sin necesidad de realizar cambios en su

    ingeniera."

    "La localizacin es el proceso de adaptar el software para una regin o idioma especficosmediante la inclusin de componentes especficos de esa regin y mediante la traduccindel texto."

    Como siempre, Symfony no trata de reinventar la rueda y el soporte de i18n y l10n sebasa en el estndar ICU (http://www.icu-project.org/) .

    19.1. El usuario

    La internacionalizacin no tiene ningn sentido sin los usuarios. Cuando un sitio webest disponible en varios idiomas o adaptado a varias regiones del mundo, el usuario esel responsable de seleccionar el idioma o regin que ms le guste.

    Nota

    Durante el tutorial del da 13 ya hablamos en detalle sobre la clase sfUser de Symfony.

    19.1.1. La cultura del usuario

    Las caractersticas de i18n y l10n de Symfony se basan en la cultura del usuario . Lacultura es la combinacin del idioma y el pas/regin del usuario. La cultura de unusuario que por ejemplo habla francs es fr , mi