![]() |
|
The Askeet TutorialDía dieciocho del calendario de symfony: Filtros |
WARNING: The SVN source code found in the release_day tags is outdated. Please refer to the current version until each day code is updated.
You are currently reading "The Askeet Tutorial" which is licensed under the Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License license.
![]() |
This work is licensed under a
Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License.
Translation of this work into another language is explicitly allowed. |
Ayer vimos cómo utilizar el servicio de askeet vía una API XML. El programa de hoy se centra en los filtros, y mostraremos su uso con la creación de subdominios para askeet. Por ejemplo, 'php.askket.com' mostrará sólo preguntas con la etiqueta PHP, y todas las preguntas nuevas entradas en este dominio serán etiquetadas con 'php'.. Vamos a llamar esta nueva funcionalidad 'universo askeet' y vamos a desarrollarla enseguida.
Primero, esta nueva funcionalidad tiene que ser opcional. Se supone que askeet es un software que se puede instalar con cualquier configuración, y quizá uno no quiera permitir subdominios en, por ejemplo, una Intranet corporativa.
Por lo tanto vamos a añadir un nuevo parámetro en la configuración
de la aplicación. Para activar la funcionalidad universo, deberá tener
el valor on
. Para añadir un parámetro personalizado, abre el fichero askeet/apps/frontend/config/app.yml
y añade:
all: .global: universe: on
Este parámetro es ahora disponible para todas las acciones de la aplicación. Para obtener su valor, utiliza la función sfConfig::get('app_universe')
.
Encontrarás más información acerca de los parámetros personalizados en el capítulo de configuración del libro de symfony.
Un filtro es un trozo de código ejecutado antes de cada acción. Tenemos que examinar el nombre de host para todas las acciones en busca de un nombre de etiqueta en el domino.
Los filtros tiene que ser declarados en un fichero de configuración especial para ser ejecutados, el askeet/apps/frontend/config/filters.yml
. Este fichero es creado por defecto cuando se inicializa una aplicación y además está vació. Abrelo y añádele:
myTagFilter: class: myTagFilter
Eso declara un nuevo filtro llamado myTagFilter
. Vamos a crear un fichero de clase llamado myTagFilter.class.php
en el directorio askeet/apps/frontend/lib/
para hacerla disponible a toda la aplicación frontend
[php] isFirstCall()) { // hacer algo } // ejecuta siguiente filtro $filterChain->execute(); } } ?>
Ésta es la estructura general de un filtro. Si el parámetro app_universe
no tiene el valor on
,
el filtro no se va a ejecutar. Como queremos que se ejecute sólo una
vez por petición (aunque probablemente haya más de una acción por
llamada, porque usamos forwards
), comprobamos el método ->isFirstCall()
. Si es verdadero el filtro sólo se ejecutará una vez en una llamada.
Una cosa acerca del objeto filterChain
: Todos los pasos
de ejecución de una petición (configuración, controlador frontal,
acción, vista) son una cadena de filtros. Un filtro personalizado entra
pronto en esta cadena (antes de la ejecución de una acción), y no tiene
que romper la ejecución de los otros pasos de la cadena de filtros. Es
por eso que un filtro personalizado siempre tiene que acabar con el
método $filterChain->execute();
.
La clase
sfFilter
tiene un métodoinitialize()
, ejecutado cuando un objeto filtro es creado. Puedes sobreescribirlo en tu filtro personalizado si necesitas tratar con tus propios parámetros de filtrado.
Queremos inspeccionar el nombre del host para ver si contiene un
subdominio que pueda ser una etiqueta. Etiquetas como 'www' o 'askeet'
tendrán que ser ignoradas. Además, queremos que nos sea posible
modificar las reglas de los subdominios a ignorar, por si usamos
técnicas de distribución de carga con nombres de dominio alternativos
como 'www1', 'www2', etc. Para eso hemos decidido poner las reglas de
los universos a ignorar (expresiones regulares) en una parámetro del
fichero de configuración filters.yml
:
myTagFilter:
class: myTagFilter
param:
host_exclude_regex: /^(www|askeet)/
Ahora es el momento de mirar el contenido de la acción del filtro execute()
(reemplazando el comentario // hacer algo
):
[php] // hay alguna etiqueta en el nombre de host? $hostname = $this->getContext()->getRequest()->getHost(); if (!preg_match($this->getParameter('host_exclude_regex'), $hostname) && $pos = strpos($hostname, '.')) { $tag = Tag::normalize(substr($hostname, 0, $pos));
// añadir una etiqueta permanente a un parámetro de configuración personalizado sfConfig::set('app_permanent_tag', $tag);
// añadir hoja de estilo personalizada $this->getContext()->getResponse()->addStylesheet($tag); } El filtro mira si encuentra una etiqueta permanente en el URI. Si se encuentra, se añade como parámetro personalizado y una hoja de estilos personalizada se añade a la vista. Por tanto:
// acceder a esta URI para mostrar el universo PHP http://php.askeet.com
// crear constante sfConfig::set('app_permanent_tag', 'php');
// añadir hoja de estilo personalizada en la vista
Como la ejecución de un filtro personalizado ocurre muy pronto en la cadena de filtrado, y aún más pronto en el
parsing
de la configuración de la vista, la hoja de estilos personalizada aparecerá en el fichero HTML resultante antes que las otras hojas de estilo. Por lo tanto si tienes que sobreescribir algún valor de un estilo de la hoja de estilos principal de askeet, estos valores se tienen que declarar!important
.
Ahora tenemos que modificar los métodos de las acciones y el modelo
para tengan en cuenta las etiquetas permanentes. Como queremos mantener
la lógica del modelo en la capa del Modelo y porque además
'refactorizar' es realmente necesario, nos ayudamos de los cambios de
las etiquetas permanentes para sacar las peticiones a Propel de las
acciones y ponerlas en el modelo. Si hechas un vistazo a la lista de
modificaciones de la entrega de hoy en el trac de askeet, verás que se han creado unos cuantos métodos nuevos del modelo y que las acciones llaman a esos métodos en vez de hacer doSelect()
por ellas mismas:
[php] Answer->getRecent() Question->getPopularAnswers() QuestionPeer::getPopular() QuestionPeer::getRecent() QuestionTagPeer::getForUserLike()
Cuando una lista de preguntas, etiquetas o respuestas son mostradas
en un universo askeet, todas las peticiones tienen que tener en cuenta
un nuevo parámetro de búsqueda. En symfony, los parámetros de búsqueda
son llamadas a los métodos ->add()
del objeto Criteria
.
Por lo tanto añade el siguiente método a las clases QuestionPeer
y AnswerPeer
:
[php] private static function addPermanentTagToCriteria($criteria) { if (sfConfig::get('app_permanent_tag')) { $criteria->addJoin(self::ID, QuestionTagPeer::QUESTION_ID, Criteria::LEFT_JOIN); $criteria->add(QuestionTagPeer::NORMALIZED_TAG, sfConfig::get('app_permanent_tag')); $criteria->setDistinct(); } return $criteria; }
Ahora necesitamos comprobar todos los métodos del modelo que
devuelven una lista que tiene que estar filtrada en un universo y
añadir en la definición del Criteria
la siguiente línea:
[php] $c = self::addPermanentTagToCriteria($c);
Por ejemplo, la QuestionPeer:getHomepagePager()
tiene que parecerse a:
[php] public static function getHomepagePager($page) { $pager = new sfPropelPager('Question', sfConfig::get('app_pager_homepage_max')); $c = new Criteria(); $c->addDescendingOrderByColumn(self::INTERESTED_USERS); // añadir esta línea $c = self::addPermanentTagToCriteria($c); $pager->setCriteria($c); $pager->setPage($page); $pager->setPeerMethod('doSelectJoinUser'); $pager->init(); return $pager; } La misma modificación tiene que repetirse unas cuantas veces en los siguientes métodos:
[php] QuestionPeer::getHomepagePager() QuestionPeer::getPopular() QuestionPeer::getPopular() QuestionPeer::getRecentPager() QuestionPeer::getRecent() AnswerPeer::getPager() AnswerPeer::getRecentPager() AnswerPeer::getRecent()
Para peticiones complejas que no usan el objeto Criteria
necesitamos añadir etiquetas permanentes como condiciones WHERE
en el código SQL. Mira como lo hicimos
con los métodos QuestionTagPeer::getPopularTags()
y QuestionTagPeer:getPopularTagsFor()
en el trac de askeet.
Todas las preguntas del universo 'PHP' son etiquetadas con 'php'.
Pero si un usuario está navegando por las preguntas del universo 'PHP',
la etiqueta 'php' no debe de mostrarse en la lista de etiquetas ya que
se presupone. Cuando se muestra una lista de etiquetas para una
pregunta o un usuario en un universo, la etiqueta permanente debe de
ser omitida. Eso puede hacerse fácilmente haciéndolas pasar por un
bucle, como por ejemplo en el método Question-getTags()
:
[php] public function getTags() { $c = new Criteria(); $c->add(QuestionTagPeer::QUESTION_ID, $this->getId()); $c->addGroupByColumn(QuestionTagPeer::NORMALIZED_TAG); $c->setDistinct(); $c->addAscendingOrderByColumn(QuestionTagPeer::NORMALIZED_TAG); $tags = array(); foreach (QuestionTagPeer::doSelect($c) as $tag) { if (sfConfig::get('app_permanent_tag') == $tag) { continue; } $tags[] = $tag->getNormalizedTag(); } return $tags; }
La misma técnica es usada en los siguientes métodos:
[php] Question->getTags() Question->getPopularTags() User->getTagsFor() User->getPopularTags()
Cuando una pregunta es creada en un universo askeet, debe de
etiquetarse con la etiqueta permanente además de las etiquetas elegidas
por el usuario. Como recordatorio, en el método question/add
, el método Question->addTagsForUser()
es llamado:
[php] $question->addTagsForUser($this->getRequestParameter('tag'), $sf_user->getId());
...dónde el parámetro pedido, tag
, contiene las
etiquetas introducidas por el usuario separadas por espacios en blanco
(a eso se le llama 'frase'). Por lo tanto sólo tenemos que añadir la
etiqueta permanente a la frase en la primera línea del método addTagsForUser
:
[php] public function addTagsForUser($phrase, $userId) { // separar la frase en etiquetas individuales $tags = Tag::splitPhrase($phrase.(sfConfig::get('app_permanent_tag') ? ' '.sfConfig::get('app_permanent_tag') : '')); // añadir etiquetas foreach ($tags as $tag) { $questionTag = new QuestionTag(); $questionTag->setQuestionId($this->getId()); $questionTag->setUserId($userId); $questionTag->setTag($tag); $questionTag->save(); } }
Eso es: si el usuario no ha añadido la etiqueta permanente, se añade a la lista de etiquetas para la nueva pregunta.
Para conseguir hacer disponibles los nuevos dominios, tienes que modificar la configuración de tu servidor web.
En local, i.e. si no tienes control de la entrada DNS de tu sitio
askeet, añade un nuevo host por cada universo que quieras añadir (en el
fichero /etc/hosts
en Linux, o en el fichero C:\WINDOWS\system32\drivers\etc\hosts
en Windows):
127.0.0.1 php.askeet 127.0.0.1 senseoflife.askeet 127.0.0.1 women.askeet
Necesitas permisos de administrador para hacer esto
En todos los casos, necesitas añadir un servidor alias en la configuración del virtual host
(en el fichero de configuración de Apache httpd.conf
):
Después de reiniciar el servidor web, puedes probar si los universos funcionan, por ejemplo:
http://php.askeet/
Los filtros tienen mucha potencia y pueden ser usados para todo tipo de cosas. Las etiquetas nos han permitido personalizar el contenido según un tema específico. Combinar etiquetas y filtros nos ha ayudado a dividir askeet en varios universos, y las posibilidades de tener sitios askeet especializados (por ejemplo music.askeet.com, programming.askeet.com o hazlotumismo.askeet.com) son interminables. Como todos los sitios pueden tener un aspecto distinto, y además el contenido de los sitios especializados también saldrá en el sito global de askeet, se obtiene lo mejor de las aplicaciones web basadas en las comunidades de usuarios.
Mañana nos concentraremos en el rendimiento y veremos como la cache HTML puede acelerar el tiempo de entrega de páginas complejas. En tres días sabremos la misteriosa funcionalidad, todavía tienes tiempo para votar la mejor idea. Puedes visitar el fórum de askeet y ver cómo el sitio web de askeet se comporta online.
If you find a typo or an error, please register and open a ticket.
If you need support or have a technical question, please post to the user mailing-list or to the forum.