jueves, 21 de agosto de 2014
Symfony - Día 7: Jugando con la Página de Categoría
Ayer expandiste tus conocimiento de symfony sobre un montón de áreas diferentes: consultas con Doctrine, archivos de datos, enrutamiento, depuración, y personalizar la configuración. Y terminamos con un pequeño desafío para hoy.
Espero que hayas trabajado en la Página de Categoría de Jobeet así el tutorial de hoy será mucho más productivo para ti.
¿Listo? Vamos a hablar acerca de una posible implementación.
La Ruta Category
Primero, necesitamos agregar una ruta para definir una URL amigable para la página de la categoría. Agrégalo al inicio del archivo de enrutamiento:
# apps/frontend/config/routing.yml category: url: /category/:slug class: sfDoctrineRoute param: { module: category, action: show } options: { model: JobeetCategory, type: object }
Si vas a comenzar la implementación de una nueva funcionalidad, es una buena práctica primero pensar acerca de la URL y crear la ruta asociada. Y esto es obligatorio si quitas las reglas de enrutamiento por defecto.
Como
slug
no es una columna de la tabla category
, necesitamos para agregar un método virtual en JobeetCategory
para que la ruta funcione:
Un ruta puede usar cualquier columna de su objeto asociado como parámetro. También puede usa cualquier otro valor si hay un método asociado definido en la clase del objeto. Debido a que el parámetro
slug
no tiene una columna correspondiente en la tabla category
, necesitamos agregar un método de acceso virtual en JobeetCategory
para que la ruta funcione:// lib/model/doctrine/JobeetCategory.class.php public function getSlug() { return Jobeet::slugify($this->getName()); }
El Enlace Categoría
Ahora, edita la plantilla
indexSuccess.php
del módulo job
para agregar el enlace a la página de la categoría:<!-- some HTML code --> <h1> <?php echo link_to($category, 'category', $category) ?> </h1> <!-- some HTML code --> </table> <?php if (($count = $category->countActiveJobs() - sfConfig::get('app_max_jobs_on_homepage')) > 0): ?> <div class="more_jobs"> and <?php echo link_to($count, 'category', $category) ?> more... </div> <?php endif; ?> </div> <?php endforeach; ?> </div>
Solo agregamos el enlace si hay más de 10 puestos de trabajo a mostrar para la categoría actual. El enlace tiene el número de puestos de trabajo no mostrados. Para que esta plantilla funcione, necesitamos agregar el método
countActiveJobs()
a JobeetCategory
:// lib/model/doctrine/JobeetCategory.class.php public function countActiveJobs() { $q = Doctrine_Query::create() ->from('JobeetJob j') ->where('j.category_id = ?', $this->getId()); return Doctrine_Core::getTable('JobeetJob')->countActiveJobs($q); }
El método
countActiveJobs()
emplea un método countActiveJobs()
que aún no existe enJobeetJobTable
. Reemplaza el contenido del archivo JobeetJobTable.php
con el siguiente código: [php] // lib/model/doctrine/JobeetJobTable.class.php class JobeetJobTable extends Doctrine_Table { public function retrieveActiveJob(Doctrine_Query $q) { return $this->addActiveJobsQuery($q)->fetchOne(); } public function getActiveJobs(Doctrine_Query $q = null)
{
return $this->addActiveJobsQuery($q)->execute();
}
public function countActiveJobs(Doctrine_Query $q = null)
{
return $this->addActiveJobsQuery($q)->count();
}
public function addActiveJobsQuery(Doctrine_Query $q = null)
{
if (is_null($q))
{
$q = Doctrine_Query::create()
->from('JobeetJob j');
}
$alias = $q->getRootAlias();
$q->andWhere($alias . '.expires_at > ?', date('Y-m-d h:i:s', time()))
->addOrderBy($alias . '.expires_at DESC');
return $q;
}
}
Como puedes ver por ti mismo, tenemos que refactorizar todo el código de
JobeetJobTable
para introducir un nuevo método compartido addActiveJobsQuery()
para hacer el código más DRY (Don't Repeat Yourself).
La primera vez, un trozo de código es re-usado, copiando el código puede ser suficiente. Pero si encuentras otra función para él necesitas refactorizar para reutilizar la función o método, como hemos hecho aquí.
En el método
countActiveJobs()
, en vez de usar execute()
y recién contar el número de resultados, usamos el método mas rápido count()
.
Hemos cambiado un montón de archivos, recién para esta simple funcionalidad. Pero cada vez que debas agregar algún código, tenemos que tratar de ponerlo en la capa correcta de la aplicación y también tratar de hace el código más reusable. En el proceso, tenemos que rectorizar algún código existente. Ese es el típico workflow cuando trabajamos en un proyecto symfony.
Creación del Módulo Category
Es hora de crear el módulo
category
:$ php symfony generate:module frontend category
Si has creado un módulo, probablemente has utilizado el
doctrine:generate-module
. Eso está bien, pero como no es necesario el 90% del código generado, usa generate:module
para crear un módulo vacío.
¿Por qué no añadir una acción
category
al módulo job
? Podríamos, pero como el tema principal de la página de categoría es una categoría, se siente más natural crear un módulo dedicado category
.
Al acceder a la página de categoría, la ruta
category
tendrá que encontrar la categoría asociada con la variable slug
de la petición. Pero como slug no se almacena en la base de datos, y porque no podemos deducir el nombre de la categoría del slug, no hay forma de encontrar la categoría asociada con el slug.Actualizar la Base de Datos
Tenemos que añadir una columna
slug
para la tabla category
:
Esta columna
slug
puede ser tomada con cuidado por un comportamiento Doctrine llamadoSluggable
. Simplemente necesitamos habilitar el comportamiento sobre nuestro modeloJobeetCategory
y este se encargará de todo por tí.# config/doctrine/schema.yml JobeetCategory: actAs: Timestampable: ~ Sluggable: fields: [name] columns: name: type: string(255) notnull: true
Ahora que
slug
es una columna real, es necesario eliminar el método getSlug()
deJobeetCategory
.
La configuración de la columna slug es tomada automáticamente cuando guardas un registro. El slug es armado usando el valor del campo
name
y se lo da al objeto.
Usa la tarea
doctrine:build --all --and-load
para actualizar las tablas de la base de datos, y llenar la base de datos con nuestros datos:$ php symfony doctrine:build --all --and-load --no-confirmation
Tenemos ahora todo en su lugar para crear el método
executeShow()
. Reemplaza el contenido del archivo de acciones category
con el siguiente código:// apps/frontend/modules/category/actions/actions.class.php class categoryActions extends sfActions { public function executeShow(sfWebRequest $request) { $this->category = $this->getRoute()->getObject(); } }
Debido a que quitamos el método generado
executeIndex()
, también puedes quitar la platilla automáticamente generada indexSuccess.php
(apps/frontend/modules/category/templates/indexSuccess.php
).
El último paso es crear la plantilla
showSuccess.php
:// apps/frontend/modules/category/templates/showSuccess.php <?php use_stylesheet('jobs.css') ?> <?php slot('title', sprintf('Jobs in the %s category', $category->getName())) ?> <div class="category"> <div class="feed"> <a href="">Feed</a> </div> <h1><?php echo $category ?></h1> </div> <table class="jobs"> <?php foreach ($category->getActiveJobs() as $i => $job): ?> <tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>"> <td class="location"> <?php echo $job->getLocation() ?> </td> <td class="position"> <?php echo link_to($job->getPosition(), 'job_show_user', $job) ?> </td> <td class="company"> <?php echo $job->getCompany() ?> </td> </tr> <?php endforeach; ?> </table>
Elementos Parciales o Partials
Nota del Traductor Los Partials o Parciales, son elementos que se usan en las plantillas, haciendo uso del helper
include_partial()
, es por eso que su traducción literal no es muy amigable, ya que debemos pensar no en parcial sino en porción (snippet de código de la capa Vista)
Observa que hemos copiado y pegado la etiqueta
<table>
que crear una lista de puestos de trabajo en la plantilla indexSuccess.php
. Eso esta mal. Es tiempo para aprender un nuevo truco. Cuando necesites volver a utilizar una parte de una plantilla, lo que necesitas es crear unpartial. Un partial es un snippet de código de plantilla que puede ser compartido entre varias plantillas. Un partial es sólo otra plantilla que comienza con un guión bajo (_
).
Crea el archivo
_list.php
:// apps/frontend/modules/job/templates/_list.php <table class="jobs"> <?php foreach ($jobs as $i => $job): ?> <tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>"> <td class="location"> <?php echo $job->getLocation() ?> </td> <td class="position"> <?php echo link_to($job->getPosition(), 'job_show_user', $job) ?> </td> <td class="company"> <?php echo $job->getCompany() ?> </td> </tr> <?php endforeach; ?> </table>
Puedes incluir un partial utilizando el helper
include_partial()
:<?php include_partial('job/list', array('jobs' => $jobs)) ?>
El primer argumento de
include_partial()
es el nombre del partial (hecho del nombre del módulo, una /
, y el nombre del partial sin el _
). El segundo argumento es un array de las variables a pasar al partial.
¿Por qué no utilizar el método
include()
incluído en PHP en lugar del helperinclude_partial()
? La principal diferencia entre los dos es el soporte de cache incluído del helper include_partial()
.
Reemplaza el HTML
<table>
de ambas plantillas con la llamada a include_partial()
:// in apps/frontend/modules/job/templates/indexSuccess.php <?php include_partial('job/list', array('jobs' => $category->getActiveJobs(sfConfig::get('app_max_jobs_on_homepage')))) ?> // in apps/frontend/modules/category/templates/showSuccess.php <?php include_partial('job/list', array('jobs' => $category->getActiveJobs())) ?>
Lista Paginada
De los requisitos del día 2:
"La lista es paginada, con 20 puestos de trabajo por página."
Para paginar una lista de un Objetos Doctrine, symfony proporciona una clase dedicada a ello:
sfDoctrinePager
. En la acción category
en lugar de pasar los objetos (jobs) de los puestos de trabajo a la plantilla, pasamos un paginador:// apps/frontend/modules/category/actions/actions.class.php public function executeShow(sfWebRequest $request) { $this->category = $this->getRoute()->getObject(); $this->pager = new sfDoctrinePager( 'JobeetJob', sfConfig::get('app_max_jobs_on_category') ); $this->pager->setQuery($this->category->getActiveJobsQuery()); $this->pager->setPage($request->getParameter('page', 1)); $this->pager->init(); }
El método
sfRequest::getParameter()
toma un valor por defecto como segundo argumento. En la acción anterior, si el parámetro de la petición page
no existe, entonces getParameter()
devolverá 1
.
El constructor de
sfDoctrinePager
tiene una clase del modelo y el número máximo de elementos a regresar por página. Añade este último valor a tu archivo de configuración:# apps/frontend/config/app.yml all: active_days: 30 max_jobs_on_homepage: 10 max_jobs_on_category: 20
El método
sfDoctrinePager::setQuery()
toma un objeto Doctrine_Query
para utilizarlo a la hora de seleccionar los elementos de la base de datos.
Agrega el método
getActiveJobsCriteria()
:// lib/model/doctrine/JobeetCategory.class.php public function getActiveJobsQuery() { $q = Doctrine_Query::create() ->from('JobeetJob j') ->where('j.category_id = ?', $this->getId()); return Doctrine_Core::getTable('JobeetJob')->addActiveJobsQuery($q); }
Ahora que tenemos el método
getActiveJobsQuery()
, podemos refactorizar otros métodos deJobeetCategory
para usarlos:// lib/model/doctrine/JobeetCategory.class.php public function getActiveJobs($max = 10) { $q = $this->getActiveJobsQuery() ->limit($max); return $q->execute(); } public function countActiveJobs() { return $this->getActiveJobsQuery()->count(); }
Por último, vamos a actualizar la plantilla:
<!-- apps/frontend/modules/category/templates/showSuccess.php --> <?php use_stylesheet('jobs.css') ?> <?php slot('title', sprintf('Jobs in the %s category', $category->getName())) ?> <div class="category"> <div class="feed"> <a href="">Feed</a> </div> <h1><?php echo $category ?></h1> </div> <?php include_partial('job/list', array('jobs' => $pager->getResults())) ?> <?php if ($pager->haveToPaginate()): ?> <div class="pagination"> <a href="<?php echo url_for('category', $category) ?>?page=1"> <img src="/legacy/images/first.png" alt="First page" title="First page" /> </a> <a href="<?php echo url_for('category', $category) ?>?page=<?php echo $pager->getPreviousPage() ?>"> <img src="/legacy/images/previous.png" alt="Previous page" title="Previous page" /> </a> <?php foreach ($pager->getLinks() as $page): ?> <?php if ($page == $pager->getPage()): ?> <?php echo $page ?> <?php else: ?> <a href="<?php echo url_for('category', $category) ?>?page=<?php echo $page ?>"><?php echo $page ?></a> <?php endif; ?> <?php endforeach; ?> <a href="<?php echo url_for('category', $category) ?>?page=<?php echo $pager->getNextPage() ?>"> <img src="/legacy/images/next.png" alt="Next page" title="Next page" /> </a> <a href="<?php echo url_for('category', $category) ?>?page=<?php echo $pager->getLastPage() ?>"> <img src="/legacy/images/last.png" alt="Last page" title="Last page" /> </a> </div> <?php endif; ?> <div class="pagination_desc"> <strong><?php echo $pager->getNbResults() ?></strong> jobs in this category <?php if ($pager->haveToPaginate()): ?> - page <strong><?php echo $pager->getPage() ?>/<?php echo $pager->getLastPage() ?></strong> <?php endif; ?> </div>
La mayor parte de este código se refiere a los enlaces a otras páginas. Aquí está la lista de métodos
sfDoctrinePager
usados en esta plantilla:getResults()
: Devuelve un array objetos Doctrine para la página actualgetNbResults()
: Devuelve el número total de resultadoshaveToPaginate()
: Devuelvetrue
si hay más de una páginagetLinks()
: Devuelve una lista de los enlaces de la página a mostrargetPage()
: Devuelve el número de la página actualgetPreviousPage()
: Devuelve el número de la página anteriorgetNextPage()
: Devuelve el número de la siguiente páginagetLastPage()
: Devuelve el número de la última página
Referencia: http://symfony.com/legacy/doc/jobeet/1_4/es/07?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