Espacio Daycry - Espacio de programación

  • Inicio
  • Categorias
    • - Codeigniter
    • - Symfony
    • - HTML5
    • - Linux / Ubuntu
    • - PHP
    • - Jquery
  • PortFolio - Proyectos Codeiniter
    • - Encuestas Online
    • - Estadísticas - GLPI
    • - Gestión de colas
    • - Web Service - REST

martes, 2 de septiembre de 2014

Symfony - Día 15: Servicios Web

Posted by daycry at 9:36 Labels: symfony
Con agregar feeds a Jobeet, los solicitantes de puestos de trabajo pueden ahora ser informados de los nuevos puestos de trabajo en tiempo real.
En el otro lado de la valla, esta cuando se envía un puesto de empleo, y deseas tener la mayor exposición/publicidad posible. Si tu trabajo es sindicado en una gran cantidad de pequeños sitios web, tendrás una mejor oportunidad de encontrar a la persona adecuada. Ese es el poder de la Larga Cola o long tail. Los afiliados podrán publicar los puestos de trabajos más recientes en sus sitios web gracias a los servicios web que se desarrollarán hoy.

Los Afiliados

Según los requisitos del día 2:
"Caso de Uso F7: Un afiliado recupera la lista de puestos de trabajos activos"

Los Datos

Vamos a crear un nuevo archivo de datos para los afiliados:
# data/fixtures/affiliates.yml
JobeetAffiliate:
  sensio_labs:
    url:       http://www.sensio-labs.com/
    email:     fabien.potencier@example.com
    is_active: true
    token:     sensio_labs
    JobeetCategories: [programming]
 
  symfony:
    url:       /
    email:     fabien.potencier@example.org
    is_active: false
    token:     symfony
    JobeetCategories: [design, programming]
La creación de registros para la tabla intermedia de una relación muchos-a-muchos es tan simple como definir un array cuya clave sea el nombre de la relación. El contenido del array es el nombre de los objetos definidos en los archivos de datos. Puede enlazar objetos desde diferentes archivos, pero los nombres deben haber sido definidos antes.
En el archivo de datos, los tokens están hardcodeados para simplificar las pruebas, pero cuando un usuario real solicita una cuenta, el token tendrán que ser generado:
// lib/model/doctrine/JobeetAffiliate.class.php
class JobeetAffiliate extends BaseJobeetAffiliate
{
  public function save(Doctrine_Connection $conn = null)
  {
    if (!$this->getToken())
    {
      $this->setToken(sha1($this->getEmail().rand(11111, 99999)));
    }
 
    return parent::save($conn);
  }
 
  // ...
}
Ahora puedes recargar los datos:
$ php symfony doctrine:data-load

El Servicio Web de los Puestos de Trabajo

Como siempre, cuando se crea un nuevo recurso, es un buen hábito primero definir la dirección URL:
# apps/frontend/config/routing.yml
api_jobs:
  url:     /api/:token/jobs.:sf_format
  class:   sfDoctrineRoute
  param:   { module: api, action: list }
  options: { model: JobeetJob, type: list, method: getForToken }
  requirements:
    sf_format: (?:xml|json|yaml)
Por esta ruta, la variable especial sf_format termina la dirección URL y los valores válidos sonxml, json, o yaml.
El método getForToken() es llamado cuando la acción recupera la colección de objetos relacionados con la ruta. Como tenemos que comprobar que el afiliado esta activado, tenemos que sobreescribir el comportamiento predeterminado de la ruta:
// lib/model/doctrine/JobeetJobTable.class.php
class JobeetJobTable extends Doctrine_Table
{
  public function getForToken(array $parameters)
  {
    $affiliate = Doctrine_Core::getTable('JobeetAffiliate') ->findOneByToken($parameters['token']);
    if (!$affiliate || !$affiliate->getIsActive())
    {
      throw new sfError404Exception(sprintf('Affiliate with token "%s" does not exist or is not activated.', $parameters['token']));
    }
 
    return $affiliate->getActiveJobs();
  }
 
  // ...
}
Si el token no existe en la base de datos, arrojamos una excepción sfError404Exception. Esta clase excepción se convierten automáticamente en una respuesta 404. Esta es la forma más sencilla de generar una página 404 de una clase del modelo.
El método getForToken() utiliza un nuevo método llamado getActiveJobs() y devuelve la lista de puestos de trabajo activos actualmente:
// lib/model/doctrine/JobeetAffiliate.class.php
class JobeetAffiliate extends BaseJobeetAffiliate
{
  public function getActiveJobs()
  {
    $q = Doctrine_Query::create()
      ->select('j.*')
      ->from('JobeetJob j')
      ->leftJoin('j.JobeetCategory c')
      ->leftJoin('c.JobeetAffiliates a')
      ->where('a.id = ?', $this->getId());
 
    $q = Doctrine_Core::getTable('JobeetJob')->addActiveJobsQuery($q);
 
    return $q->execute();
  }
 
  // ...
}
El último paso es crear la acción y plantillas para la api. Inicializa el módulo con la tareagenerate:module:
$ php symfony generate:module frontend api
Como no vamos a utilizar la acción predeterminada index, puedes eliminarla de la clase action, y eliminar la plantilla asociada indexSucess.php.

La Acción

Todos los formatos comparten la misma acción list:
// apps/frontend/modules/api/actions/actions.class.php
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());
  }
}
En lugar de pasar un array de objetos JobeetJob a las plantillas, se pasa un array de cadenas. Como tenemos tres modelos diferentes para la misma acción, la lógica de proceso de los valores ha sido refactorizada en el método JobeetJob::asArray():
// lib/model/doctrine/JobeetJob.class.php
class JobeetJob extends BaseJobeetJob
{
  public function asArray($host)
  {
    return array(
      'category'     => $this->getJobeetCategory()->getName(),
      'type'         => $this->getType(),
      'company'      => $this->getCompany(),
      'logo'         => $this->getLogo() ? 'http://'.$host.'/uploads/jobs/'.$this->getLogo() : null,
      'url'          => $this->getUrl(),
      'position'     => $this->getPosition(),
      'location'     => $this->getLocation(),
      'description'  => $this->getDescription(),
      'how_to_apply' => $this->getHowToApply(),
      'expires_at'   => $this->getCreatedAt(),
    );
  }
 
  // ...
}

El Formato xml

Soportar el formato xml es tan simple como crear una plantilla:
<!-- apps/frontend/modules/api/templates/listSuccess.xml.php -->
<?xml version="1.0" encoding="utf-8"?>
<jobs>
<?php foreach ($jobs as $url => $job): ?>
  <job url="<?php echo $url ?>">
<?php foreach ($job as $key => $value): ?>
    <<?php echo $key ?>><?php echo $value ?></<?php echo $key ?>>
<?php endforeach ?>
  </job>
<?php endforeach ?>
</jobs>

El Formato json

Soportar el formato JSON es similar:
<!-- apps/frontend/modules/api/templates/listSuccess.json.php -->
[
<?php $nb = count($jobs); $i = 0; foreach ($jobs as $url => $job): ++$i ?>
{
  "url": "<?php echo $url ?>",
<?php $nb1 = count($job); $j = 0; foreach ($job as $key => $value): ++$j ?>
  "<?php echo $key ?>": <?php echo json_encode($value).($nb1 == $j ? '' : ',') ?>
 
<?php endforeach ?>
}<?php echo $nb == $i ? '' : ',' ?>
 
<?php endforeach ?>
]

El Formato yaml

Para Formatos nativos, Symfony hace algunas configuraciones en el fondo, como cambiar el content type, y desactivar el layout.
Como el Formato YAML no esta en la lista de los formatos nativos, la respuesta y su content type se puede cambiar y el layout desactivado en la acción:
class apiActions extends sfActions
{
  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 acción, el método setLayout() cambia el layout por defecto o se desactiva cuando se establece en false.
La plantilla de YAML dice lo siguiente:
<!-- apps/frontend/modules/api/templates/listSuccess.yaml.php -->
<?php foreach ($jobs as $url => $job): ?>
-
  url: <?php echo $url ?>
 
<?php foreach ($job as $key => $value): ?>
  <?php echo $key ?>: <?php echo sfYaml::dump($value) ?>
 
<?php endforeach ?>
<?php endforeach ?>
Si intentas llamar a los servicios web con un token no-válido, tendrás una página XML 404 para el formato XML, y una página JSON 404 para el formato JSON. Pero para el formato YAML, Symfony no sabe qué mostrar.
Cuando creas un formato, una plantilla personalizada de error debe ser creada. La plantilla se utilizará para páginas 404, y todas las demás excepciones.
Dado que la excepción debe ser diferente según sea un entorno de desarrollo o producción, dos archivos son necesarios (config/error/exception.yaml.php para depuración, yconfig/error/error.yaml.php para producción):
// config/error/exception.yaml.php
<?php echo sfYaml::dump(array(
  'error'       => array(
    'code'      => $code,
    'message'   => $message,
    'debug'     => array(
      'name'    => $name,
      'message' => $message,
      'traces'  => $traces,
    ),
)), 4) ?>
 
// config/error/error.yaml.php
<?php echo sfYaml::dump(array(
  'error'       => array(
    'code'      => $code,
    'message'   => $message,
))) ?>
Antes de probarlo, debes crear un layout para el formato YAML:
// apps/frontend/templates/layout.yaml.php
<?php echo $sf_content ?>
404
Sobreescribiendo las plantillas por defecto de error 404 y de excepción es tan simple como crear los archivos en cuestión en el directorio config/error/.

Probando los Servicios Web

Para probar el servicio web, copia los datos de los afiliados de data/fixtures/ atest/fixtures/ y sustituye el contenido del archivo autogenerado apiActionsTest.php con el siguiente contenido:
include(dirname(__FILE__).'/../../bootstrap/functional.php');
 
$browser = new JobeetTestFunctional(new sfBrowser());
$browser->loadData();
 
$browser->
  info('1 - Web service security')->
 
  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 the affiliate')->
  get('/api/sensio_labs/jobs.xml')->
  with('request')->isFormat('xml')->
  with('response')->begin()->
    isValid()->
    checkElement('job', 32)->
  end()->
 
  info('3 - The web service supports the JSON format')->
  get('/api/sensio_labs/jobs.json')->
  with('request')->isFormat('json')->
  with('response')->matches('/"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')->
    matches('/category\: Programming/')->
  end()
;
En esta prueba, te darás cuenta de tres nuevos métodos:
  • isValid(): Checks whether or not the XML response is well formed
  • isFormat(): Pone a prueba el formato de un request
  • contains(): Para formatos no-HTML, comprueba si la respuesta contiene el fragmento de texto esperado

El Formulario de Afiliación

Ahora que el servicio web está listo para ser utilizado, vamos a crear el Formulario de Afiliación. Vamos a describir una vez más el clásico proceso de agregar una nueva funcionalidad a una aplicación.

Enrutamiento

Lo sabes. La ruta es la primera cosa a crear:
# apps/frontend/config/routing.yml
affiliate:
  class:   sfDoctrineRouteCollection
  options:
    model: JobeetAffiliate
    actions: [new, create]
    object_actions: { wait: get 
Se trata de una clásica colección de rutas Doctrine con una nueva opción de configuración:actions. Como no necesitamos todas las siete acciones definidas por defecto para la ruta, la opción actions instruye a la ruta para sólo coincidir con las acciones new y create . La ruta adicional wait se utilizarán para dar al inminente afiliado algunos comentarios acerca de su cuenta.

Inicialización

El segundo paso clásico es generar un módulo:
$ php symfony doctrine:generate-module frontend affiliate JobeetAffiliate --non-verbose-templates

Las Plantillas

La tarea doctrine:generate-module genera las clásicas siete acciones y sus correspondientes plantillas. En el directorio templates/, elimina todos los archivos, pero no _form.php ninewSuccess.php. Y para los archivos que mantenemos, sustituye sus contenidos con los siguientes:
<!-- apps/frontend/modules/affiliate/templates/newSuccess.php -->
<?php use_stylesheet('job.css') ?>
 
<h1>Become an Affiliate</h1>
 
<?php include_partial('form', array('form' => $form)) ?>
 
<!-- apps/frontend/modules/affiliate/templates/_form.php -->
<?php include_stylesheets_for_form($form) ?>
<?php include_javascripts_for_form($form) ?>
 
<?php echo form_tag_for($form, 'affiliate') ?>
  <table id="job_form">
    <tfoot>
      <tr>
        <td colspan="2">
          <input type="submit" value="Submit" />
        </td>
      </tr>
    </tfoot>
    <tbody>
      <?php echo $form ?>
    </tbody>
  </table>
</form>
Crea la plantilla waitSuccess.php:
<!-- apps/frontend/modules/affiliate/templates/waitSuccess.php -->
<h1>Your affiliate account has been created</h1>
 
<div style="padding: 20px">
  Thank you!
  You will receive an email with your affiliate token
  as soon as your account will be activated.
</div>
Por último, cambiar el enlace en el pie de página para que apunte al módulo affiliate:
// apps/frontend/templates/layout.php
<li class="last">
  <a href="<?php echo url_for('affiliate_new') ?>">Become an affiliate</a>
</li>

Las Acciones

Una vez más, ya que sólo se utiliza el formulario de creación, abre el archivoactions.class.php y elimina todos los métodos pero deja executeNew(), executeCreate(), yprocessForm().
Para la acción processForm(), cambiar la URL de redireccionamiento a la acción wait:
// apps/frontend/modules/affiliate/actions/actions.class.php
$this->redirect($this->generateUrl('affiliate_wait', $jobeet_affiliate));
La acción wait es simple que no hace falta pasarle nada a la plantilla:
// apps/frontend/modules/affiliate/actions/actions.class.php
public function executeWait(sfWebRequest $request)
{
}
El afiliado no puede elegir su token, ni puede activar su cuenta inmediatamente. Abre el archivoJobeetAffiliateForm para personalizar el formulario:
// lib/form/doctrine/JobeetAffiliateForm.class.php
class JobeetAffiliateForm extends BaseJobeetAffiliateForm
{
  public function configure()
  {
    $this->useFields(array(
      'url', 
      'email', 
      'jobeet_categories_list'
    ));
    $this->widgetSchema['jobeet_categories_list']->setOption('expanded', true);
    $this->widgetSchema['jobeet_categories_list']->setLabel('Categories');
 
    $this->validatorSchema['jobeet_categories_list']->setOption('required', true);
 
    $this->widgetSchema['url']->setLabel('Your website URL');
    $this->widgetSchema['url']->setAttribute('size', 50);
 
    $this->widgetSchema['email']->setAttribute('size', 50);
 
    $this->validatorSchema['email'] = new sfValidatorEmail(array('required' => true));
  }
}
The new sfForm::useFields() method allows to specify the white list of fields to keep. All non mentionned fields will be removed from the form.
El framework de formularios soporta relaciones muchos-a-muchos como cualquier otra columna. Por defecto, esta relación es mostrada como un cuadro desplegable gracias al widgetsfWidgetFormChoice. Como se ha visto durante el día 10, hemos cambiado lo mostrado mediante el uso de la opción expanded.
Como emails y URLs tienden a ser bastante más largo que el tamaño predeterminado de una etiqueta input, los atributos de HTML por defecto se puede configurar utilizando el métodosetAttribute().
fFormulario de Afiliado

Las Pruebas

El último paso es escribir algunas pruebas funcionales para la nueva función.
Sustituye las pruebas generadas para el módulo affiliate con el código siguiente:
// test/functional/frontend/affiliateActionsTest.php
include(dirname(__FILE__).'/../../bootstrap/functional.php');
 
$browser = new JobeetTestFunctional(new sfBrowser());
$browser->loadData();
 
$browser->
  info('1 - An affiliate can create an account')->
 
  get('/affiliate/new')->
  click('Submit', array('jobeet_affiliate' => array(
    'url'                            => 'http://www.example.com/',
    'email'                          => 'foo@example.com',
    'jobeet_categories_list'         => array(Doctrine_Core::getTable('JobeetCategory')->findOneBySlug('programming')->getId()),
  )))->
  with('response')->isRedirected()->
  followRedirect()->
  with('response')->checkElement('#content h1', 'Your affiliate account has been created')->
 
  info('2 - An affiliate must at least select one category')->
 
  get('/affiliate/new')->
  click('Submit', array('jobeet_affiliate' => array(
    'url'   => 'http://www.example.com/',
    'email' => 'foo@example.com',
  )))->
  with('form')->isError('jobeet_categories_list')
;

El Backend para Afiliados

Por el backend, un module affiliate debe ser creado para activar los afiliados por el administrador:
$ php symfony doctrine:generate-admin backend JobeetAffiliate --module=affiliate
Para acceder al nuevo módulo creado, añade un enlace en el menú principal con el número de afiliados que deben ser activados:
<!-- apps/backend/templates/layout.php -->
<li>
  <a href="<?php echo url_for('jobeet_affiliate_affiliate') ?>">
    Affiliates - <strong><?php echo Doctrine_Core::getTable('JobeetAffiliate')->countToBeActivated() ?></strong>
  </a>
</li>
 
// lib/model/doctrine/JobeetAffiliateTable.class.php
class JobeetAffiliateTable extends Doctrine_Table
{
  public function countToBeActivated()
  {
    $q = $this->createQuery('a')
      ->where('a.is_active = ?', 0);
 
    return $q->count();
  }
 
  // ...
 
}
Como la única acción necesaria en el backend es para activar o desactivar las cuentas, cambia la sección config del generador por defecto para simplificar la interfaz un poco y añade un enlace para activar directamente las cuentas en la vista list:
# apps/backend/modules/affiliate/config/generator.yml
config:
  fields:
    is_active: { label: Active? }
  list:
    title:   Affiliate Management
    display: [is_active, url, email, token]
    sort:    [is_active]
    object_actions:
      activate:   ~
      deactivate: ~
    batch_actions:
      activate:   ~
      deactivate: ~
    actions: {}
  filter:
    display: [url, email, is_active]
Para que los administradores sean más productivos, cambiar el filtro para mostrar únicamente los afiliados a ser activados:
// apps/backend/modules/affiliate/lib/affiliateGeneratorConfiguration.class.php
class affiliateGeneratorConfiguration extends BaseAffiliateGeneratorConfiguration
{
  public function getFilterDefaults()
  {
    return array('is_active' => '0');
  }
}
El único otro código a escribir es para las acciones activate, deactivate :
// apps/backend/modules/affiliate/actions/actions.class.php
class 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)
  {
    $q = Doctrine_Query::create()
      ->from('JobeetAffiliate a')
      ->whereIn('a.id', $request->getParameter('ids'));
 
    $affiliates = $q->execute();
 
    foreach ($affiliates as $affiliate)
    {
      $affiliate->activate();
    }
 
    $this->redirect('jobeet_affiliate');
  }
 
  public function executeBatchDeactivate(sfWebRequest $request)
  {
    $q = Doctrine_Query::create()
      ->from('JobeetAffiliate a')
      ->whereIn('a.id', $request->getParameter('ids'));
 
    $affiliates = $q->execute();
 
    foreach ($affiliates as $affiliate)
    {
      $affiliate->deactivate();
    }
 
    $this->redirect('jobeet_affiliate');
  }
}
 
// lib/model/doctrine/JobeetAffiliate.class.php
class JobeetAffiliate extends BaseJobeetAffiliate
{
  public function activate()
  {
    $this->setIsActive(true);
 
    return $this->save();
  }
 
  public function deactivate()
  {
    $this->setIsActive(false);
 
    return $this->save();
  }
 
  // ...
}
Backend para Afiliados

Referencia: http://symfony.com/legacy/doc/jobeet/1_4/es/15?orm=Doctrine
Tweet

Related Posts

  • Symfony - Día 7: Jugando con la Página de Categoría
    Symfony - Día 7: Jugando con la Página de Categoría
  • Symfony - Día 6: Más acerca del Modelo
    Symfony - Día 6: Más acerca del Modelo
  • Symfony - Día 5: El Enrutamiento
    Symfony - Día 5: El Enrutamiento
  • Symfony - Día 4: El Controlador y la Vista
    Symfony - Día 4: El Controlador y la Vista

No hay comentarios :

Publicar un comentario

Entrada más reciente Entrada antigua Inicio
Suscribirse a: Enviar comentarios ( Atom )

Sígueme en las Redes Sociales



Follow @daycry9



Donaciones

Suscribirse a

Entradas
Atom
Entradas
Comentarios
Atom
Comentarios

Datos personales

daycry
Ver todo mi perfil

Entradas populares

  • Crear archivos PHP ejecutables por terminal UBUNTU
    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...
  • Pâginación PHP con Librería Zebra Pagination
    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...
  • PHPExcel - Codeigniter
    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...
  • PHP- Operaciones con fechas - Sumar Horas, minutos y segundos
    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 ...
  • Codeigniter - Múltiples conexiones a base de datos
    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