Excedesoft Blog
Sharing knowledge …
Sharing knowledge …
Sep 1st
Hace un tiempo me toco la “sencilla” tarea de embeber dos formularios para completar el form de resgistración.
La función utilizada para dicha tarea es embedForm ($this->embedForm(“nombre_form”, $objeto_del_form);), para ello lo veremos en un sencillo ejemplo, en el cual debemos mostrar un formulario UserInformationForm, el cual tiene embebido a un companyForm. Siempre partiendo de la premisa de la correcta construccion del schema.yml y de la existencia de los dos Formularios.
El userForm:
classUserInformationForm extends UserForm{ public function configure() { unset($this['name']); // Bookmark creation form $newcompanyForm = new companyForm(); $this->embedForm('company', $newcompanyForm); // Existing bookmark forms $this->embedRelation('Bookmarks'); $this->widgetSchema->setNameFormat('user_bookmarks[%s]'); $this->useFields(array( 'username', 'email_address', 'password', 'password_again', 'company')); $this->widgetSchema['username'] = new sfWidgetFormInputText(); $this->widgetSchema['email_address']= new sfWidgetFormInputText(); $this->widgetSchema['password'] = new sfWidgetFormInputPassword(); $this->widgetSchema['password_again'] = new sfWidgetFormInputPassword(); $this->validatorSchema['username'] = new sfValidatorString(); $this->validatorSchema['email_address']= new sfValidatorEmail(); $this->validatorSchema['email_address']= new sfValidatorEmail(); $this->validatorSchema['password_again'] = clone $this->validatorSchema['password']; } }
<formname="<?php echo $form->getName()?>"method="post"action="<?php echo url_for('direction') ?>"id="formRegister"><?php echo $form->renderGlobalErrors() ?>User Name:<br /><div><?php echo $form['username']?></div><?php echo $form['username']->renderError() ?>Password:<br /><div><?php echo $form['password']?></div><?php echo $form['password']->renderError() ?>Repeat Password:<br /><div><?php echo $form['password_again']?></div><?php echo $form['password_again']->renderError() ?><br>Email:<br /><div><?php echo $form['email_address']?></div><br /><?php echo $form['email_address']->renderError() ?><br>Company:<br /><div><?php echo $form['company']['name'] ?></div><br /><?php echo $form['company']['name']->renderError() ?><br><!-- p>If you would like us to call you directly* optional </p --><p>How many employees do you have?: </p><div><?php echo $form['company']['employees']?></div><br /><?php echo $form['company']['employees']->renderError() ?><br></form>
Aug 2nd
Hola a todos, aquí les dejo un documento de mi experiencia con apache y balanceo de carga.
Fue una buena experiencia dado que no es tan comun que a uno se le presente este escenario.Como veran use 3 servidores apache.

Primer apache (balancer): utilizado para balancear las peticiones redireccionando al webserver correpondiente.
===========configuracion del balanceador (httpd.conf) =============
LoadModule proxy_module /usr/lib/apache2/modules/mod_proxy.so
LoadModule proxy_balancer_module /usr/lib/apache2/modules/mod_proxy_balancer.so
LoadModule proxy_http_module /usr/lib/apache2/modules/mod_proxy_http.so
ProxyRequests Off
ServerName excedesoft.com
DocumentRoot /var/www/
ProxyRequests Off
Order deny,allow
Allow from all
ProxyPass /balancer-manager !
ProxyPass / balancer://mycluster/ stickysession=BALANCEID nofailover=Off
ProxyPassReverse / http://192.168.2.197
ProxyPassReverse / http://192.168.2.198
BalancerMember http://192.168.2.197 route=http1
BalancerMember http://192.168.2.198 route=http2
ProxySet lbmethod=byrequests
SetHandler balancer-manager
Order deny,allow
Allow from all
============fin configuracion balanceador===============
Primer webserver apache: este tiene la aplicacion , es un simple webserver, hay dos cosas que lo diferencian de su compañero (segundo webserver), cuando el cliente lo accede le graba una cookie con su propio nombre (http1), este dato sera leido las proximas veces por el balanceador, es un pequeño rastro dejado en cookie para que el balanceador lo redirecciones siempre al mismo.
====config .htacces server 1 ====
RewriteEngine on
RewriteRule .* – [CO=BALANCEID:balancer.http1:.excedesoft.com]
===========================
Segundo webserver apache: solo cambie la cookie por medio de .htacces
====config .htacces ===========
RewriteEngine on
RewriteRule .* – [CO=BALANCEID:balancer.http2:.excedesoft.com]
===========================
mysql y sesiones: las aplicaciones de los dos apaches (http1 y http2) guardan las sesiones en mysql, esto posibilita que ante algun problema en uno de los webserver, las sesiones puedan ser recuperadas por otro webserver del cluster.
Para ello se debe especificar en php.ini esto:
setear a user
=======php.ini========
session.save_handler = user
====================
————————————————
Uso de session_set_save_handler (funcion).
aqui se debe respetar el orden de las funciones: ejemplo
session_set_save_handler(“open”, “close”, “read”, “write”, “destroy”, “gc”);
no importa el nombre, solo el orden, la que se especifique primero siempre sera para abrir sesion (open).
Ejemplo:
==========================================================
class Session {
public function __construct(){
session_set_save_handler(array(&$this, ‘open’),array(&$this, ‘close’),array(&$this, ‘read’),array(&$this, ‘write’),array(&$this, ‘destroy’),array(&$this, ‘clean’));
if(!session_id()){
session_start();
}
}
public function open() {
$this->mysql = mysql_connect(’192.168.2.100′, ‘root’, ’123′);
$bSeldb = mysql_select_db(‘excedesoft’, $this->mysql);
if (!$bSeldb) {
die (‘Can\’t use Database : ‘ . mysql_error());
}
}
public function write($id, $data) {
$access = time();
$sql = “REPLACE INTO sessions VALUES (‘$id’, ‘$access’, ‘$data’)”;
return mysql_query($sql, $this->mysql) or die(mysql_error());
}
public function read($id) {
$sql = “SELECT data FROM sessions WHERE id = ‘$id’”;
if ($result = mysql_query($sql, $this->mysql)) {
if (mysql_num_rows($result)) {
$record = mysql_fetch_assoc($result);
return $record['data'];
}
}
return ”;
}
public function destroy($id) {
$sql = “DELETE FROM sessions WHERE id = ‘$id’”;
return mysql_query($sql, $this->mysql);
}
public function clean($max) {
$old = time() – $max;
$sql = “DELETE FROM sessions WHERE access mysql);
}
public function close() {
mysql_close($this->mysql);
}
}
$oSession = new Session();
===========================================
Uso
Pues solo queda incluir este archivo en el header de tu aplicacion (para que se ejecute en todas las paginas) y nada mas.
Testing
si entras a http://balanceador_ip/balancer-manager veras el funcionamiento y podras detener algun apache, tambien por ejemplo la forma que se hace el balanceo, pero para es hace falta otro post.
Aqui puedes bajar el apache 1 y veras como trabaja la aplicacion en tu apache 2. lo que deberias hacer es poner un pequeno texto en cada header . server 1 y server 2 por ejemplo asi sabes cual estas viendo.
si borras las cookie “BALANCEID” veras que el balanceador te pasara al otro server en la proxima peticion, asi podras tambien testear.
saludos.
Aug 2nd
Recientemente ha sido lanzado el proyecto PHP for Android, el cual nos permite crear aplicaciones para el sistema operativo para móviles de Google con uno de los lenguajes de scripting más populares del mundo (PHP). Con este proyecto los desarrolladores de PHP seremos capaces de correr aplicaciones PHP en cualquier dispositivo basado en Android.
El proyecto phpforandroid.net se llevo a cabo sobre el ASE (Android Scripting Environment) ahora renombrado como SL4A (Scripting Layer for Android), el cual le permite a los desarrolladores correr lenguajes de scripting tales como Phyton ó Lua.
El proyecto esta en sus comienzos y seguramente evolucionará con el tiempo y la ayuda de los desarrolladores PHP interesados en generar aplicaciones en el mundo de Android. Lo hemos probado y hemos estado haciendo algunas pruebas con teléfonos celulares basado en Android y funciona perfectamente; solo es cuestión de pensar en alguna necesidad a cubrir (ó generarla) para desarrollar alguna aplicación interesante en PHP para Android.
Otra cuestión muy interesante acerca de este proyecto es que se esta generando una múy útil documentación, y lo más importante es que no necesitas tener un teléfono basado en Android para desarrollar, ya que en la página de desarrolladores de Android nos provee de un emulador para testear nuestros desarrollos.
Aca pueden ver un video de como funciona phpforandroid como instalarlo y probarlo:
Jul 19th
Investigando, encontré una presentación de Nicolas Perriault en donde, basado en su experiencia, da 30 consejos a tener en cuenta a la hora de desarrollar en symfony. Como el mismo lo dice, son practicas discutibles lo que se busca con esto es mejorar la comunicación entre desarrolladores a través de la normalización y mejorar de la seguridad, mantenibilidad, portabilidad y la interoperabilidad mediante el aumento de código calidad.
Me pareció bastante interesante, por lo cual deseo compartirlas con ustedes en este post.
Tip 0: Siempre escribe “symfony”
Tip 1: Maneja la vista dentro de la vista. No manejes nunca la vista Desde el controlador o el modelo. El uso de slot puede ayudar bastante.
Tip 2: Siempre usa la protección CSRF (viene por defecto desde la versión 1.3)
Tip 3: Siempre usa redirect después de manejar datos con POST.
(Puedes mostrar mensajes flash)
*Por propósitos de seguridad.
*Por ergonomía.
*Para evitar duplicados en la base de datos.
Tip 4: Sentencias SQL, instancias de Propel Criteria o Doctrine_Query siempre en el modelo.
Tip 5: No modificar nunca el núcleo de symfony. Si quieres añadir, cambiar o quitar cosas, extiéndelo (mediante plugins, herencia%85)
*Afinando el archivo factories.yml y la captura de eventos nativos de
Symfony puede ayudar mucho lograr este objetivo.
Tip 6: No meter etiquetas HTML dentro de PHP.
Tip 7: Si usas caché, recuerda configurarlo adecuadamente y activarlo. Además no te olvides de limpiarlo cada vez que modifiques un archivo .yml.
*Mejor: evitar el uso de caché. No es broma.
Tip 8: Usar nombres de rutas en vez de direcciones manuales en url_for, link_to y redirect.
*Además, las rutas por defecto debería estar desactivada
por defecto (al menos en apps que no funcionan en modulos
admin-generator 1.0)
Tip 9: Escribir la documentación en inglés, incluyendo los comentarios del código.
Tip 10: Utiliza las excepciones. Muestra mensajes útiles al usuario final.
Tip 11: No despliegues en producción ficheros de desarrollo (como los *_dev.php)
Tip 12: Una clase de control no debe tener más de 15 métodos, y un método no debería tener más de 30 líneas.
Tip 13: Usa siempre el mismo formato para el código (mejor usando los “symfonys coding standards”)
Tip 14: Todo lo relacionado con el usuario y su sesión debería almacenarse en sfUser.
Tip 15: Nunca serializes objetos en la sesión.
Tip 16: Personaliza las páginas de error (404, 500..).
Tip 17: No deberías usar sfContext directamente en el modelo.
* Porque la instancia de sfContext puede difierir mucho en relación con el env
utilizado (CLI, prueba,dev, prod …)
*Sería hacer que tu código fuera bastante incontrastables
* Sí, es duro.
Tip 18: Evita la creación de una clase genérica con un montón de métodos estáticos. es mejor escribir clases especializadas.
*Es como reinventar procedimiento de programación o el uso de funciones.
*Escribe clases especializadas
Tip 19: Usar sfLogger para hacer debug, apoyado de FirePHP o FireSymfony.
Tip 20: Si se usa gestor de versiones para el código (svn, cvs), nunca subas ficheros con contraseñas o rutas absolutas.
* Ejemplo típico: el archivo databases.yml
* Distribuir configuración depende de la plataforma archivos.
Tip 21: Escribe y usa las pruebas funcionales.
* Probar Funcionalmente la interfaz de usuario
(por ejemplo, 404/403/401 HTTP códigos, la seguridad y el usuario
autenticación, transacciones y formularios, etc)
Tip 22: Nunca escribas paths absolutos en el código.
* Haga su proyecto symfony portátiles.
Tip 23: No representes los objetos con su clave primaria en las URLs.
* Por razones de seguridad: PKs numéricos son fácilmente
adivinar, mientras que los slugs no lo son.
* Para la usabilidad y SEO URL:
es más sexy /article/symfony-rulez.html
que /article/123.html
Tip 24: Todas las variables de configuración deberían definirse en app.yml.
* Puede compartir todo el proyecto (cross-
applications) ajustes poniendo app.yml
archivo de configuración dentro de la raíz o carpeta de la
proyecto.
Tip 25: Intenta no versionar las clases base creadas por symfony, ya que dificultarías el trabajo para futuras extensiones.
* Tal vez la gente va a querer ampliar o añadir
comportamientos de las clases del modelo.
Tip 26: Usa el sistema de tareas de symfony para scripts de línea de comando (como tareas de administración del proyecto)
Tip 27: Altera la petición y la respuesta mediante filtros.
Tip 28: Siempre crea ficheros README, INSTALL y CHANGELOG cuando vayas a distribuir la aplicación.
* Plugin Symfony! = Sistema Operativo.
Tip 29: Puedes reusar funcionalidad y módulos entre aplicaciones escribiendo plugins.
Tip 30: Estudia el código de otros frameworks para ver qué hace bien cada uno y usarlo.
* Puede ser que tengan buenas ideas, incluso mejor
para resolver el problema que tiene.
* Incluso los marcos escrito en otros
idiomas que PHP (Django, Rails,Primavera …)
Jul 19th
| What is OpenInviter? |
| Open source OpenInviterTM (Open InviterTM) is a free import contacts (addressbook) script from email providers like Live/Hotmail, FastMail, Inbox.com, AOL, Care2, Popstarmail, Grafitti, Bigstring, Rambler, Wp.pt, Apropo, Evite, Meta, Aussiemail, Xing, MSN, Bordermail, Pochta, Gawab, Hushmail, Canoe, Mail.in, Yahoo!, IndiaTimes, YouTube, Mail.com, Walla, LinkedIn, Lycos, GMX.net, Libero, Techemail, 5Fm, Virgilio, OperaMail, Yandex, Kids, Web.de, Sapo.pt, Azet, Interia, Terra, Plaxo, KataMail, Mynet.com, Clevergo, Mail2World, Rediff, Doramail, Inet, Freemail, Atlas, Mail.ru, O2, Netaddress, Abv, India, GMail, Uk2, Nz11, Zapakmail or social portals like Faces, Last.fm, MySpace, Tagged, Vimeo, Perfspot, Flingr, Brazencareerist, Xuqa, Ning, Motortopia, Twitter, Mydogspace, Xanga, Orkut, Multiply, Plurk, Friendster, Koolro, Meinvz, Vkontakte, Eons, Lovento, Flickr, NetLog, Cyworld, Livejournal, Mevio, Hyves, Fdcareer, Plazes, Skyrock, Kincafe, Bebo, Badoo, Mycatspace, Famiva, Konnects, Bookcrossing, Hi5, Flixster, Facebook, Friendfeed. This contacts importer script is integrating with content management systems (aka CMS) like Vwebmail, Social Engine, SimpleMachines Forum (SMF), Atmail5, Boonex Dolphin, Joomla, nowFire, jamit job, PunBB, Dating Pro, Joomla1.0, myBB, phpFoX, vBulletin, RoundCube, PHPMELODY, joovili, Drupal, Buddy Zone, WordPress, PhpBB, symfony, phpizabi, JamRoom. Open Inviter is written in PHP 5 (no database required but cURL or wget required) and running on any webserver(tested on Apache) offering advanced tell a friend features. OpenInviterTM is a free self hosted solution that does not use a third party gateway (or API) to import contacts. |
Put the files in a sub folder inside your web folder in your symfony project. Follow the steps to install openinviter. Copy all the files from that sub folder after the installation to your lib folder (in a sub folder example: /lib/api/openinviter).
Now you can call the class in one of your actions like this:
$inviter= new openinviter();
With the object $inviter now you can call the methods.
For documentation see the link: http://openinviter.com/documentation/index.html
Jul 19th
Siempre dije que cuando uno busca una cosa por segunda vez lo mas probable es que tenga que buscarla por tercera ves. Eso me paso con ese script que encontre hace un tiempo dando vueltas por internet.
Pero mejor vamos al punto de este post: vamos emular a svn como si fuese un servicio.
Las ventajas de esto es que tenemos una sintaxis clara para levantarlo, pararlo y reseterlo.
En primer lugar creamos el archivo ‘subversion’ en /etc/init.d/.
Archivo “/etc/init.d/subversion”:
#!/bin/bash
#
# /etc/rc.d/init.d/subversion
#
# Starts the Subversion Daemon
#
# chkconfig: 2345 90 10
# description: Subversion Daemon
# processname: svnserve
source /etc/rc.d/init.d/functions
[ -x /usr/bin/svnserve ] || exit 1
### Default variables
SYSCONFIG="/etc/sysconfig/subversion"
### Read configuration
[ -r "$SYSCONFIG" ] && source "$SYSCONFIG"
RETVAL=0
prog="svnserve"
desc="Subversion Daemon"
start() {
echo -n $"Starting $desc ($prog): "
daemon $prog -d $OPTIONS
RETVAL=$?
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/$prog
echo
}
stop() {
echo -n $"Shutting down $desc ($prog): "
killproc $prog
RETVAL=$?
[ $RETVAL -eq 0 ] && success || failure
echo
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$prog
return $RETVAL
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
RETVAL=$?
;;
condrestart)
[ -e /var/lock/subsys/$prog ] && restart
RETVAL=$?
;;
*)
echo $"Usage: $0 {start|stop|restart|condrestart}"
RETVAL=1
esac
exit $RETVAL
Ya con esto tenemos declaradas las funciones básicas de un servicio.
Ahora creamos un fichero de configuración en sysconfig para parametros adicionales.
Archivo “/etc/sysconfig/subversion”:
# Configuration file for the Subversion service
#
# To pass additional options (for instace, -r root of directory to server) to
# the svnserve binary at startup, set OPTIONS here.
#
#OPTIONS=
OPTIONS="--threads --root /var/svn"
Por ultimo debemos hacer un cambio de permisos en este archivo y agregar a subvrsion como servicio (a estos comandos correrlos como root):
$>chmod +x '/etc/init.d/subversion
$>chkconfig --add subversion && chkconfig subversion on
$>service subversion start
Fuente: http://www.svnforum.org/2017/viewtopic.php?t=281
Jul 19th
En la tarea de internacionalizar una pagina, son muchos los puntos que solo alguien que tubo practica con las librerías de internacionalización nos puede contar. En mi corta experiencia con symfony he encontrado varios de esos tips y consejos prácticos para tener en cuenta a la hora de trabajar. Para comenzar un breve resumen de como realizar la correcta internacionalización. La Cultura del Usuario: Las características i18n y l10n de symfony se basan en la cultura del usuario. La cultura es la combinación del lenguaje y el país del usuario. Puedes manejar la cultura por el usuario llamando a los métodos setCulture() y getCulture() del objeto User: $this->getUser()->setCulture("cultura"); $this->getUser()->getCulture(); La cultura del usuario es la configurada en el archivo de configuración settings.yml: # apps/frontend/config/settings.yml all: .settings: default_culture: it_IT Como una dirección URL sólo puede representar a un único recurso, la cultura debe estar integrada en la URL. Para ello, abre el archivo routing.yml, y agrega la variable especial :sf_culture para todas las rutas, pero no para api_jobs y homepage. Para simples rutas, agrega /:sf_culture al principio de la url. Para colección de rutas, agrega una opción prefix_path que comience con /:sf_culture.: # apps/frontend/config/routing.yml prefix_path: /:sf_culture/job para mas información: http://www.symfony-project.org/jobeet/1_2/Doctrine/es/19 ahora una serie de tips y ayudas a tener en cuenta: * El div que se debería agregar en el layout si deseamos tener dos link a los cuales seleccionar y cambiar de ese modo la cultura: <!--Language switch --> <div id="language"> <a href="<?php echo url_for('language/change?lang=es')?>" title="Español">Español</a> | <a href="<?php echo url_for('language/change?lang=en') ?>" title="English">English</a> </div> * Para q funcione esto debemos agregar un modulo "language" y agregar el siguiente contenido: <?php class languageActions extends sfActions { public function executeChange() { $lang = $this->getRequestParameter("lang"); $this->getUser()->setCulture($lang); $this->redirect("@homepage"); $this->redirect($redirect); } public function executeUri(){ $uri = $this->getRequest()->getUri(); return $uri; } } * crear en el frontend la carpeta i18n y en ella crear el archivo messages.es.xml (si se desea realizar una internacionalización a otra cultura que no sea la española se deberá cambia el sufijo es por la correspondiente). Agregar en él el siguiente contenido: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE xliff PUBLIC "-//XLIFF//DTD XLIFF//EN" "http://www.oasis-open.org/committees/xliff/documents/xliff.dtd"> <!-- SPANISH --> <xliff version="1.0"> <file source-language="EN" target-language="es" datatype="plaintext" original="messages" date="2010-05-14T12:11:22Z" product-name="messages"> <header/> <body> <trans-unit id="1"> <source>palabraOfraceEnIngles</source> <target>palabraOfraceEnEspañol</target> </trans-unit> <trans-unit id="2"> <source>Example</source> <target>Ejemplo</target> </trans-unit> </body> </file> </xliff> * Al momento de traducir una palabra debemos llamar a la función __() la cual recibe como parámetro la palábra en ingles para traducir (en este caso del ingles al español). cabe aclarar q se debe escribir exactamente la palabra en ingles como esta en el archivo xml, para q este la traduzca, si al ingresar al archivo xml no la encuentra la dejara tal cual estaba antes de llamar a la función __() Ejemplo: <?php echo __('Example') ?> Muchas veces nos vemos obligados a internacionalizar hasta las imágenes he inclusive los CSS para dejar la pagina totalmente internacionalada. Pero no es para preocuparse por esto, simplemente internacionalizamos el nombre de las imágenes ( siempre es conveniente dejar un distintivo y estandarizar, de esta manera los nombres, en este caso será el sufijo es o en. Ejemplo nombre_en, para las imágenes en ingles y nombre_es para las imágenes en español) ejemplo: <img src="/images/'.__("imagen_en").'.jpg">' Por supuesto debemos tener en ya agregado en nuestro archivo xml la imagen en ingles y en español: <trans-unit id="3"> <source>imagen_en</source> <target>imagen_es</target> </trans-unit> y con el tema de los CSS lo mismo, por lo general conviene tener dos archivos CSS, uno por idioma. En cuanto al manejo de flash-messages, error messages, etc., es conveniente y a veces la única forma de internacionalizarlos, no a donde se produce el error sino donde se muestra, ya que nos aseguramos de q ningún error o mensaje se muestre sin pasar por la función __() y si vemos que se nos paso una frase o palabra por traducir, lo único que tenemos que hacer es ir y agregarla en nuestro archivo xml. Y por ultimo pero menos importante, el manejo de las fechas, para ello he creado una simple función, la cual debe ser agregada en lib/service/ServiceI18n . La cual es la siguiente: public static function internationalizationDate($format, $date){ $dateFomated = date(__($format), $date); $day = date('l', $date); $retorno = str_replace($day, __($day), $dateFomated); $month = date('F', $date); $retorno = str_replace($month, __($month), $retorno); $month = date('M', $date); $retorno = str_replace($month, __($month), $retorno); return $retorno; } la misma recibe como parámetros el formato en el que queremos la fecha, y la fecha de tipo strtotime. Para que la función funcione, debemos agregar en el archivo xml todos los días, los meces, los formatos posibles y todo lo que creamos que nos podría servir para internacionalizar las fechas. A continuación esta el contenido del archivo xml que utiliza esta función: <!-- DATE --> <!-- month date --> <trans-unit id="2001"> <source>January</source> <target>Enero</target> </trans-unit> <trans-unit id="2002"> <source>February</source> <target>Febrero</target> </trans-unit> <trans-unit id="2003"> <source>March</source> <target>Marzo</target> </trans-unit> <trans-unit id="2004"> <source>April</source> <target>Abril</target> </trans-unit> <trans-unit id="2005"> <source>May</source> <target>Mayo</target> </trans-unit> <trans-unit id="2006"> <source>June</source> <target>Junio</target> </trans-unit> <trans-unit id="2007"> <source>July</source> <target>Julio</target> </trans-unit> <trans-unit id="2008"> <source>August</source> <target>Agosto</target> </trans-unit> <trans-unit id="2009"> <source>September</source> <target>Septiembre</target> </trans-unit> <trans-unit id="2010"> <source>October</source> <target>Octubre</target> </trans-unit> <trans-unit id="2011"> <source>November</source> <target>Noviembre</target> </trans-unit> <trans-unit id="2012"> <source>Dicember</source> <target>Diciembre</target> </trans-unit> <!-- month date --> <trans-unit id="2013"> <source>Jan</source> <target>Ene</target> </trans-unit> <trans-unit id="2014"> <source>Feb</source> <target>Feb</target> </trans-unit> <trans-unit id="2015"> <source>Mar</source> <target>Mar</target> </trans-unit> <trans-unit id="2016"> <source>Apr</source> <target>Abr</target> </trans-unit> <trans-unit id="2017"> <source>May</source> <target>May</target> </trans-unit> <trans-unit id="2018"> <source>Jun</source> <target>Jun</target> </trans-unit> <trans-unit id="2019"> <source>Jul</source> <target>Jul</target> </trans-unit> <trans-unit id="2020"> <source>Aug</source> <target>Ago</target> </trans-unit> <trans-unit id="2021"> <source>Sep</source> <target>Sep</target> </trans-unit> <trans-unit id="2022"> <source>Oct</source> <target>Oct</target> </trans-unit> <trans-unit id="2023"> <source>Novr</source> <target>Nov</target> </trans-unit> <trans-unit id="2024"> <source>Dic</source> <target>Dic</target> </trans-unit> <!-- day date --> <trans-unit id="2025"> <source>Monday</source> <target>Lunes</target> </trans-unit> <trans-unit id="2026"> <source>Tuesday</source> <target>Martes</target> </trans-unit> <trans-unit id="2027"> <source>Wendesday</source> <target>Miercoles</target> </trans-unit> <trans-unit id="2028"> <source>Thursday</source> <target>Jueves</target> </trans-unit> <trans-unit id="2029"> <source>Friday</source> <target>Viernes</target> </trans-unit> <trans-unit id="2030"> <source>Saturday</source> <target>Sabado</target> </trans-unit> <trans-unit id="2031"> <source>Sunday</source> <target>Domingo</target> </trans-unit> <trans-unit id="2032"> <source></source> <target></target> </trans-unit> <!-- format date --> <trans-unit id="2033"> <source>F d, Y</source> <target>d F, Y</target> </trans-unit> <trans-unit id="2034"> <source>l, F j, Y</source> <target>l, j F, Y</target> </trans-unit> <trans-unit id="2035"> <source>F d, Y</source> <target>d F, Y</target> </trans-unit> <trans-unit id="2036"> <source>F j, Y</source> <target>j F, Y</target> </trans-unit> <trans-unit id="2037"> <source>M d, Y</source> <target>d M, Y</target> </trans-unit>
Jul 19th
Una de las tareas más comunes en una aplicación web es el tema de este post “envio de emails”. Por suerte a partir de Symfony 1.3 el envío de emails ah sido simplificado completamente gracias a la integración con Swift Mailer, una librería muy completa para esta funcionalidad.
El objeto mailer está disponible en las tareas de Symfony mediante el método sfCommandApplicationTask::getMailer(). Acceder al mailer y enviar emails es sumamente sencillo como se muestra en el siguiente ejemplo:
public function execute($arguments = array(), $options = array())
{
$mailer = $this->getMailer();
$mailer->composeAndSend($remitente, $destinatario, $asunto, $contenido);
}
Nota
Como la configuración del mailer se obtiene a partir de la configuración de la aplicación, para utilizar el mailer es obligatorio que la tarea utilice una opción llamada
application.
Nota
Si utilizas la estrategia de envío
spool, los emails sólo se envían cuando se ejecuta la tareaproject:send-emails.
Normalmente el contenido del email no se encuentra en una variable mágica llamada $contenido esperando a que lo envíes, sino que debes generarlo de alguna forma antes de enviarlo. En Symfony no existe una forma estándar de generar el contenido de los emails, pero puedes seguir los consejos mostrados en las próximas secciones para facilitar tu trabajo.
Se puede crear en la tarea un método protegido que devuelva el contenido del email que se va a enviar:
public function execute($arguments = array(), $options = array())
{
$this->getMailer()->composeAndsend($remitente, $destinatario, $asunto, $this->getContenido());
}
protected function getContenido()
{
return 'Hola Mundo';
}
Swift Mailer incluye un plugin llamado Decorator que básicamente es un sistema de plantillas muy simple pero eficiente. Este plugin recibe los datos que dependen del destinatario y los sustituye en el contenido del email preparado.
Puedes leer la documentación de Swift Mailer para obtener más información.
Integrar una librería externa de plantillas es muy sencillo. Se puede utilizar por ejemplo el nuevo componente de plantillas que forma parte del proyecto Symfony Components. Descarga el código del componente, copialo por ejemplo en el directorio lib/vendor/templating/ y utiliza el siguiente código en la plantilla:
protected function getContenido($template, $vars = array())
{
$engine = $this->getTemplateEngine();
return $engine->render($template, $vars);
}
protected function getTemplateEngine()
{
if (is_null($this->templateEngine))
{
$loader = new sfTemplateLoaderFilesystem(sfConfig::get('sf_app_dir').'/templates/emails/%s.php');
$this->templateEngine = new sfTemplateEngine($loader);
}
return $this->templateEngine;
}
Todavía existe otra forma de hacerlo. El plugin Decorator de Swift Mailer es genial porque se encarga de realizar las sustituciones que dependen del destinatario del mensaje. Esto significa que en el contenido del mensaje se definen los elementos que dependen del destinatario y Swift Mailer se encarga de reemplazar los tokens con el valor correcto en función del destinatario del email que se envía. El siguiente código muestra cómo integrarlo con el componente de las plantillas:
public function execute($arguments = array(), $options = array())
{
$message = Swift_Message::newInstance();
// obtiene la lista de usuarios
foreach($users as $user)
{
$replacements[$user->getEmail()] = array(
'{username}' => $user->getEmail(),
'{specific_data}' => $user->getSomeUserSpecificData(),
);
$message->addTo($user->getEmail());
}
$this->registerDecorator($replacements);
$message
->setSubject('User specific data for {username}!')
->setBody($this->getMessageBody('user_specific_data'));
$this->getMailer()->send($message);
}
protected function registerDecorator($replacements)
{
$this->getMailer()->registerPlugin(new Swift_Plugins_DecoratorPlugin($replacements));
}
protected function getMessageBody($template, $vars = array())
{
$engine = $this->getTemplateEngine();
return $engine->render($template, $vars);
}
protected function getTemplateEngine($replacements = array())
{
if (is_null($this->templateEngine))
{
$loader = new sfTemplateLoaderFilesystem(sfConfig::get('sf_app_template_dir').'/emails/%s.php');
$this->templateEngine = new sfTemplateEngine($loader);
}
return $this->templateEngine;
}
El archivo apps/frontend/templates/emails/user_specific_data.php contiene el siguiente código:
Hi {username}!
We just wanted to let you know your specific data:
{specific_data}
¡Y eso es todo! Ahora ya dispones un completo sistema de plantillas para crear el contenido de tus emails.
Jul 19th
En este post veremos como establecer el charset y collation de nuestra base de datos a UTF8, lo cual en determinadas ocasiones es necesario, por ejemplo, cuando queremos utilizar una base de datos que nos sirva para internacionalización (i18N).
Para realizar esto tenemos tres posibilidades, veamos las tres:
La primera que veremos es la opción en la que editaremos el archivo config/ProjectConfiguration.class.php
<?phpclass ProjectConfiguration extends sfProjectConfiguration
{
public function setup()
{
$this->enablePlugins('sfDoctrinePlugin');
}
public function configureDoctrine(Doctrine_Manager $manager)
{
$manager->setCollate('utf8_unicode_ci');
$manager->setCharset('utf8');
}
}
?>
Otra manera de setear a utf8 es configurando la base de datos en el archivo config/databases.yml
all:
doctrine:
class: sfDoctrineDatabase
param:
dsn: mysql:host=localhost;dbname=myDatabse
username: myUser
password: mySecret
attributes:
default_table_type: InnoDB
default_table_collate: utf8_unicode_ci
default_table_charset: utf8
Y la última alternativa es seteando las opciones en nuestro schema en config/doctrine/schema.yml
options: collate: utf8_unicode_ci charset: utf8 type: InnoDB
Espero que les sea de utilidad.
Jul 17th
Watir, pronounced water, is an open-source (BSD) library for automating web browsers. It allows you to write tests that are easy to read and maintain. It is simple and flexible.
Watir drives browsers the same way people do. It clicks links, fills in forms, presses buttons. Watir also checks results, such as whether expected text appears on the page.
Watir is a family of Ruby libraries but it supports your app no matter what technology it is developed in. They support Internet Explorer on Windows, Firefox on Windows, Mac and Linux, Safari on Mac, Chrome on Windows and Flash testing with Firefox.
Like other programming languages, Ruby gives you the power to connect to databases, read data files and spreadsheets, export XML, and structure your code as reusable libraries. Unlike other programming languages, Ruby is concise and often a joy to read.
“Watir Rocks! Truly awesome!!”—Shashank Date
“Watir is the most compelling alternative [to Fit] for filling the automated acceptance testing need.”—Ward Cunningham
“I have limited scripting experience and none with Ruby, but I was able to follow your examples and be productive in short order.”—Kevin Sheehy
“I’ve been trying to find the Holy Grail of Automated Web UI Testing… And the one I’m currently enamored with is Watir.”—Scott Hanselman
“I wanted to run around my office dancing and celebrating.”—Beth Ferguson
Some Watir users include (click image for more!)