martes, 2 de septiembre de 2014
Symfony - Día 15: Servicios Web
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 ?>
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 formedisFormat()
: Pone a prueba el formato de un requestcontains()
: 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 archivo
actions.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 archivo
JobeetAffiliateForm
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 widget
sfWidgetFormChoice
. 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étodo
setAttribute()
.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(); } // ... }
Referencia: http://symfony.com/legacy/doc/jobeet/1_4/es/15?orm=Doctrine
Suscribirse a:
Enviar comentarios
(
Atom
)
Sígueme en las Redes Sociales
Donaciones
Datos personales
Entradas populares
-
En este apartado vamos a explicar como ejercutar archivos PHP a través del terminal de Ubuntu. Lo primero que tendríamos que hacer es inst...
-
En este blog voy a comentar un tema que se utilizan en casi todas las páginas web que existen, y es el tema de la paginación. La paginaci...
-
Este post trata de la integración de la librería PHPExcel en Codeigniter, aunque se podría aplicar a cualquier librería, como por ejemplo mP...
-
Ejemplo para añadir o sumar un número determinado de hora/s, minuto/s, segundo/s a una fecha en php. Con la función strtotime se puede ...
-
Este tema es uno de los temas primordiales sobre el framework Codeigniter, ya que en alguna ocación nos hemos visto obligados a recoger dato...
© Espacio Daycry - Espacio de programación 2013 . Powered by Bootstrap , Blogger templates and RWD Testing Tool
No hay comentarios :
Publicar un comentario