tag:blogger.com,1999:blog-36344859829773134522024-03-06T04:58:05.263+01:00ZonaDevBlog de programación, desarrollo web y administración de sistemas en sistemas operativos GNU/Linux.jacanaleshttp://www.blogger.com/profile/12451684109673089591noreply@blogger.comBlogger14125tag:blogger.com,1999:blog-3634485982977313452.post-74198449853405916902012-05-09T01:47:00.001+02:002015-09-01T22:05:40.631+02:00Patrones de diseño - Factory<br />
<a href="http://www.zonadev.es/2012/05/patrones-de-diseno.html">Patrones de Diseño.</a><br />
<br />
El <i>patrón Factory</i>, nos permitirá centralizar en una clase la creación de objetos de un mismo tipo. Definiremos una interfaz o una clase abstracta para crear un objeto, pero serán las subclases quienes decidan qué clase se va a instanciar, permitiendo que <b>una clase delegue a sus subclases la creación de objetos</b>. A continuación vamos a explicarlo más a fondo:<br />
<h3>
Clasificación</h3>
Creacional<br />
<h3>
Intención</h3>
Centraliza en una clase constructora la creación de objetos de un mismo subtipo de un tipo determinado.<br />
<h3>
Motivación</h3>
El <i>patrónFactory</i> es bastante útil cuando no se sabe de antemano que tipo de objeto de una familia se va a instanciar, sino que durante el proceso se decidirá que tipo hacerlo. Por ejemplo, procesar un fichero de configuración json, xml, yui, txt y es una variable la que nos indica que fichero se parseará.<br />
<h3>
Aplicabilidad</h3>
<ul>
<li>Una clase no puede prever la clase de objetos que debe crear</li>
<li>Centralizar la creación de objetos. Una clase quiere que sean sus subclases quienes especifiquen los objetos que ésta crea.</li>
<li>Delegar la responsabilidad en una de entre varias clases auxiliares, permitiendo crear una familia de objetos.</li>
</ul>
<h3>
Estructura</h3>
<div class="separator" style="clear: both;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMufrZwth9w5oMUt-Xh9nh0Lf10jZAoXTfVpFUwUBu-AFKd0Y60dGp8zUiRWCTlo4EDdQfxMCsMdDuT_y1NjFmvFYk4_NHXcCcfRg-VZfVy7TNStVFD_Zw94I6WjcEG-7tpnzXTFWrhZGM/s1600/factory.png">
<img alt="Patrón Factory" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMufrZwth9w5oMUt-Xh9nh0Lf10jZAoXTfVpFUwUBu-AFKd0Y60dGp8zUiRWCTlo4EDdQfxMCsMdDuT_y1NjFmvFYk4_NHXcCcfRg-VZfVy7TNStVFD_Zw94I6WjcEG-7tpnzXTFWrhZGM/s650/factory.png" title="Patrón Factory" />
</a>
</div>
<h3>
Consecuencias</h3>
Proporciona más flexibilidad a la hora de crear objetos de una misma familia de objetos dentro de una clase.<br />
<h3>
Participantes</h3>
<b>· Producto</b>
Define la interfaz de los objetos que crea el método de fabricación.<br />
<b>· Producto Concreto</b>
Implementa la interfaz del Producto.<br />
<b>· Creador</b>
Declara el método de fabricación que devolverá un objeto del tipo Producto. Puede definir una implementación predeterminada del método de fabricación.<br />
<b>· Creador Concreto</b>
Redefine el método de fabricación para devolver una instancia del Producto Concreto.<br />
<h3>
Implementación</h3>
La clase UserFactory tiene un método estático Create() que recibe como parámetro 2 argumentos. Según el valor de estos se decide que subclase se va a instanciar.
<br />
<h3>
Código de ejemplo</h3>
Aunque existen bastantes variaciones del patrón Factory, las dos más utilizadas són:
<br />
- El creador es clase concreta que implementa una interfaz predeterminada para el método de creación.<br />
- El creador es abstracto y no provee implementación par el método de creación.<br />
<h4>
Creador: es una Clase abstracta o Interfaz</h4>
Ahora vamos a mostrar varios ejemplos. En el primero, la Factory es una interfaz que crea las instancias GuestUser, ActiveUser y AdminUser, las cuales son denominadas <i>productos </i>en este tipo de patrón.<br />
<h5>
Clases User y subtipos: user.php</h5>
<pre class="brush: php"> /**
* Clase abstracta User. De esta clase extenderá la familia
* de objetos de tipos de usuarios
*/
abstract class User {
protected $username = null;
public function __construct($username) {
$this->username = $username;
}
public function getPermissions() {
return false;
}
public function getSuperPrivileges() {
return false;
}
}
// Clase GuestUser: User
class GuestUser extends User {}
// Clase ActiveUser: User
class ActiveUser extends User {
public getPermissions() {
return true;
}
}
// Clase AdminUser: User
class AdminUser extends User {
public getPermissions() {
return true;
}
public function getSuperPrivileges() {
return true;
}
}
</pre>
<h5>
Patrón Factory: user_factory.php</h5>
<pre class="brush: php">
/**
* Creador sin implementación. Puede ser una inferface o
* una clase abstracta donde no se definan la implementación
* de los métodos de creación.
*/
interface FactoryInterface {
/**
* Método de creación.
*
* @static
* @param String $username Nombre de usuario
* @param String $privilege Tipo de usuario.
* @access public
*/
static public function Create($username, $privilege);
}
// UserFactory: FactoryInterface
/**
* Creador concreto que implementa la interfaz de creación.
*/
class UserFactory implements FactoryInterface {
// Datos "hardcoded" para realizar demostración
private static $users = array(
'jose' => 'Guest',
'jesus' => 'Active',
'zonadev' => 'Admin'
);
/**
* Método de creación. Retorna una instancia del objeto. Se obtiene que objeto
* instanciar según el valor de $privilege.
*
* @static
* @param String $username Nombre de usuario
* @access public
*/
static public function Create($username) {
if(isset(self::$users[$username])) {
$privilege = self::$users[$username];
$fry = ucfirst($privilege) . 'User';
if(class_exists($fry)) {
return new {$fry}($username);
} else {
throw new Exception('El tipo de usuario indicado no es correcto.');
}
} else {
return new GuestUser($username);
}
}
}
</pre>
<h5>
Ejemplo de implementación: privileges.php</h5>
<pre class="brush: php"> include_once 'user.php';
include_once 'user_factory.php';
function checkPermissions(User $o) {
return $o->getPermissions();
}
function checkSuperPriivleges(User $o) {
return $o->getSuperPrivileges();
}
echo checkPermissions(UserFactory::Create('zonadev'));
echo checkSuperPrivileges(UserFactory::Create('zonadev'));
</pre>
Próximamente añadiré más ejemplos con las diferentes formas de implementar la creación de objetos<br />
<h3>
Patrones relacionados</h3>
<ul>
<li>Patrón Abstract Factory</li>
</ul>
<h3>
Referencias</h3>
<ul>
<li><a href="http://msdn.microsoft.com/es-es/library/bb972258.aspx#M11" target="_blank">Biblioteca Microsoft</a></li>
<li><a href="http://desarrolladorsenior.blogspot.com.es/2009/09/patron-simple-factory-en-php5.html" target="_blank">Dessarrollador Senior</a></li>
</ul>
<br />jacanaleshttp://www.blogger.com/profile/12451684109673089591noreply@blogger.com0tag:blogger.com,1999:blog-3634485982977313452.post-44046744315305259932012-05-07T12:48:00.000+02:002015-09-01T21:26:57.870+02:00Patrones de diseño<h2>
Que són los Patrones de diseño del Software</h2>
En el mundo de la programación escuchamos bastante a menudo hablar sobre <b>"Patrones de diseño"</b> y la primera vez, las preguntas que nos hemos hecho todos son las mismas. <b>¿Qué son y para qué sirven?</b><br />
Podemos definir un <i>patrón de diseño</i>, como una base para la <i>búsqueda de soluciones a problemas</i> que encontramos comúnmente durante el desarrollo de software. Para que una solución sea considerada un <i>patrón</i>, debe haberse <b>demostrado en otras ocasiones su efectividad</b> y que esa misma solución <b>es reutilizable</b> para cualquier situación similar.<br />
Podemos encontrar 3 tipos fundamentales de patrones: de creación, estructurales y de comportamiento, además de un cuarto tipo que utilizaremos para aplicaciones concurrentes. Paso a listar los patrones más conocidos.<br />
En futuros posts, iré explicando los patrones uno por uno, con ejemplos de implementación y explicando cuando y porqué los utilizaremos. No lo hago en este post directamente porque se haría demasiado largo y pesado de leer, así que he preferido ir paso a paso y hacer primero una primera introducción.<br />
<h3>
Relación de Patrones:</h3>
<ul>
<li>1 Patrones de creación</li>
<ul>
<li><a href="http://www.zonadev.es/2012/05/patrones-de-diseno-factory.html">1.1 Patrón Factory (Factoría)</a></li>
<li>1.2 Patrón Abstract Factory (Factoría Abstracta)</li>
<li>1.3 Patrón Builder (Constructor)</li>
<li>1.4 Patrón Singleton (Instancia </li>
<ul>
<li>Shallow copy</li>
<li>Deep Copy</li>
</ul>
<li>1.5 Patrón Prototype (Prototipo)</li>
<li>1.6 Patrón Lazy initialization</li>
<li>1.7 Patrón Multiton</li>
<li>1.8 Patrón Object Pool</li>
</ul>
<li>2 Patrones estructurales</li>
<ul>
<li>2.1 Patrón Adapter (Adaptador)</li>
<li>2.2 Patrón Bridge (Puente)</li>
<li>2.3 Patrón Composite (Composición)</li>
<li>2.4 Patrón Decorator (Decorador)</li>
<li>2.5 Patrón Facade (Fachada)</li>
<li>2.6 Patrón Front Controller</li>
<li>2.7 Patrón FlyWeight</li>
<li>2.8 Patrón Proxy</li>
<li>2.9 Patrón Module</li>
</ul>
<li>3 Patrones de comportamiento</li>
<ul>
<li>3.1 Patrón Blackboard</li>
<li>3.2 Patrón Chain of responsibility (Cadena de Responsabilidad)</li>
<li>3.3 Patrón Command (Comando)</li>
<li>3.4 Patrón Interpreter (Intérprete)</li>
<li>3.5 Patrón Iterator (Iterador)</li>
<li>3.6 Patrón Mediator (Mediador)</li>
<li>3.7 Patrón Memento (Recuerdo)</li>
<li>3.8 Patrón Null Object</li>
<li>3.9 Patrón Observer (Observador)</li>
<li>3.10 Patrón Servant</li>
<li>3.11 Patrón Specification</li>
<li>3.12 Patrón State (Estado)</li>
<li>3.13 Patrón Strategy (Estrategia)</li>
<li>3.14 Patrón Template (Plantilla)</li>
<li>3.15 Patrón Visitor (Visitante)</li>
</ul>
<li>4 Patrones para aplicaciones concurrentes</li>
<ul>
<li>4.1 Patrón Active Object</li>
<li>4.2 Patrón Balking</li>
<li>4.3 Patrón Binding properties</li>
<li>4.4 Patrón Messaging design pattern (MDP)</li>
<li>4.5 Patrón Double-checked locking</li>
<li>4.6 Patrón Event-based asynchronous</li>
<li>4.7 Patrón Guarded suspension</li>
<li>4.8 Patrón Lock</li>
<li>4.9 Patrón Monitor object</li>
<li>4.10 Patrón Reactor</li>
<li>4.11 Patrón Read-write lock</li>
<li>4.12 Patrón Scheduler</li>
<li>4.13 Patrón Thread pool</li>
<li>4.14 Patrón Thread-specific storage</li>
<li>4.15 Patrón Unit of Work</li>
</ul>
</ul>
<h5>
Referencias:</h5>
Para estudiar todo el tema de patrones, estoy utilizando la información disponible en los siguientes enlaces, los cuales os recomiendo si queréis meteros a fondo en el estudio de patrones de diseño:<br />
<ul>
<li><a href="http://www.info-ab.uclm.es/asignaturas/42579/cap4/Estructural.htm" target="_blank">Universidad de Castilla-La Mancha</a></li>
<li><a href="http://es.wikipedia.org/wiki/Patr%C3%B3n_de_dise%C3%B1o" target="_blank">Wikipedia</a></li>
<li><a href="http://es.wikipedia.org/wiki/Patrones_de_arquitectura" target="_blank">Wikipedia - Patrones de arquitectura</a></li>
<li><a href="http://desarrolladorsenior.blogspot.com.es/search/label/patrones%20de%20dise%C3%B1o" target="_blank">Desarrollador Senior</a></li>
<li><a href="http://programacionsolida.blogspot.com.es/" target="_blank">Programación SOLIDa</a></li>
<li><a href="http://patronesdediseno.net16.net/" target="_blank">Patrones de diseño</a></li>
<li><a href="http://trevinca.ei.uvigo.es/~formella/doc/cd03/condis.html" target="_blank">Concurrencia y distribución</a></li>
<li><a href="http://mit.ocw.universia.net/6.170/6.170/f01/pdf/lecture-12.pdf" target="_blank">Universia.net</a></li>
</ul>
jacanaleshttp://www.blogger.com/profile/12451684109673089591noreply@blogger.com2tag:blogger.com,1999:blog-3634485982977313452.post-63312184310538436912012-04-29T19:45:00.000+02:002012-04-29T19:45:17.821+02:00Interfaces y clases abstractas en PHP<p>En este post explicaremos que son las <b>interfaces </b>y las <b>clases abstractas</b>, y para que utilizaremos cada una de ellas.</p>
<h3>Interfaces</h3>
<p>Las interfaces de objetos permiten crear código con el cual especificamos <b>qué métodos deben ser implementados por una clase</b>, sin tener que definir cómo estos métodos son manipulados.
Las interfaces son definidas utilizando la palabra clave <b>interface</b>, de la misma forma que con clases estándar, pero sin métodos que tengan su contenido definido.
Todos los métodos declarados en una interfaz deben ser public, ya que ésta es la naturaleza de una interfaz.</p>
<p>Por lo general, utilizaremos las interfaces cuando queramos asegurarnos de que una clase implemente una serie de métodos. Veamos un ejemplo:</p>
<pre class="brush: php">
// Declarar la interfaz 'iTemplate'
interface iTemplate {
public function setVariable($name, $var);
public function getHtml($template);
}
// Implementar la interfaz
// Ésto funcionará
class Template implements iTemplate {
private $vars = array();
public function setVariable($name, $var) {
$this->vars[$name] = $var;
}
public function getHtml($template) {
foreach($this->vars as $name => $value) {
$template = str_replace('{' . $name . '}', $value, $template);
}
return $template;
}
}
// Ésto no funcionará
// Error fatal: La Clase BadTemplate contiene un método abstracto
// y por lo tanto debe declararse como abstracta (iTemplate::getHtml)
class BadTemplate implements iTemplate {
private $vars = array();
public function setVariable($name, $var) {
$this->vars[$name] = $var;
}
}
</pre>
<h3>Clases abstractas</h3>
<p>Mientra que las <i>interfaces</i> sólo nos permiten <b>compartir comportamientos entre objetos no relacionados</b>, <b>las clases abstractas permiten limitar y/o definir con precisión las capacidades de cada objeto</b>. No se puede instanciar directamente a una <i>clase abstract</i>a, sino que habrá que crear otra clase que herede de esta (usando <b>extends</b>) y desde esta se podrán realizar las operaciones sobre los métodos o parámetros de la clase abstracta. En una clase abstracta se pueden definir <b>métodos públicos</b>, que serán accesibles desde las clases heredadas sin necesidad de sobreescribirlas.</p>
<p>Veamos un ejemplo de abstracción de clases:</p>
<pre class="brush: php">
abstract class AbstractClass
{
// Forzando la extensión de clase para definir este método
abstract protected function getValue();
abstract protected function prefixValue($prefix);
// Método común
public function printOut() {
print $this->getValue() . "\n";
}
}
class ConcreteClass1 extends AbstractClass
{
protected function getValue() {
return "ConcreteClass1";
}
public function prefixValue($prefix) {
return "{$prefix}ConcreteClass1";
}
}
class ConcreteClass2 extends AbstractClass
{
public function getValue() {
return "ConcreteClass2";
}
public function prefixValue($prefix) {
return "{$prefix}ConcreteClass2";
}
}
$class1 = new ConcreteClass1;
$class1->printOut();
echo $class1->prefixValue('FOO_') ."\n";
$class2 = new ConcreteClass2;
$class2->printOut();
echo $class2->prefixValue('FOO_') ."\n";
</pre>
<p>En este ejemplo podemos ver como las clases <i>ConcreteClass1</i> y <i>ConcreteClass2</i> extienden de <i>AbstractClass</i> y se llama al método abstracto <i>printOut()</i> para mostrar el resultado.</p>
<h5>Referencias</h5>
<ul>
<li><a href="http://php.net/manual/es/language.oop5.interfaces.php">PHP.net - Interfaces de objetos</a></li>
<li><a href="http://www.php.net/manual/es/language.oop5.abstract.php">PHP.net - Abstracción de clases</a></li>
</ul>jacanaleshttp://www.blogger.com/profile/12451684109673089591noreply@blogger.com0tag:blogger.com,1999:blog-3634485982977313452.post-24338740782765051332012-04-29T16:59:00.001+02:002012-04-29T17:00:38.185+02:00Lambda functions y closures en PHP<p>Con la versión de PHP 5.3, aparte de los <a href="http://www.zonadev.es/2012/04/namespaces-en-php.html">namespaces</a>, otra de las funcionalidades más importantes que se añadieron fue el soporte a las <b>funciones anónimas</b> y los <b>closures</b>. En este post voy a explicar un poco que es cada cosa y como podemos utilizarlo.</p>
<h3>Lambda Functions</h3>
<p>Las <b>funciones Lambda</b>, (o funciones anónimas), simplemente son aquellas funciones que no tienen nombre. Son muy utilizadas en lenguajes como Javascript. La forma de declarar una función anónima sería la siguiente:</p>
<pre class="brush: php">
$c = function($a, $b) {
return sqrt(pow(a, 2) + pow(b, 2));
}
echo $c;
</pre>
<p>De esta manera, vemos que estamos guardando en $c una referencia a la función anónima. Ahora la pregunta, ¿para que utilizaremos las funciones lambda?. Este tipo de funciones son sobretodo útiles para utilizar <i>callbacks</i> en nuestros métodos. Podemos crear algo parecido a esto:</p>
<pre class="brush: php">
function greet($value, $callback) {
echo $callback($value);
}
greet(’ZonaDev’, function($name) {
return "Hello, $name";
});
</pre>
<p>En este ejemplo, primero definimos la función greet, con dos parámetros, de los cuales el segundo será un callback (una función anónima). En el momento de llamar a la función greet, vemos como le pasamos el nombre y una función anónima que se ejecutará cuando llamamos a <i>echo $callback($value);</i></p>
<h3>Closures</h3>
<p>Vamos a ir ahora con las closures, que no son más que funciones anónimas. La única diferencia, es que estas necesitan conocer el valor de alguna variable que se encuentra fuera de la función anónima para realizar sus procesos. Para poder pasarle a una closure una variable externa que utilizará, vamos a utilizar <b>use</b>, vayamos con un ejemplo:</p>
<pre class="brush:php">
//Simple math class
class Math
{
function __construct($a) {
$this->a =$a;
}
//Returns a closure
function mul()
{
//We can't directly use $this inside closure
$self = $this;
return function($n) use($self) {
return $n*$self->a;
};
}
}
$math = new Math(5);
$mul = $math->mul();
echo $mul(4); //Output : 20
</pre>
<p>En este caso, vemos como dentro de mul creamos una closure y le pasamos una instancia de $this.</p>
<p>Hasta aquí una explicación breve de lo que son las funciones anónimas y como podemos utilizarlas, pronto empezaremos con los <b>patrones de diseño</b>.</p>
<h5>Referencias</h5>
<ul>
<li><a target="_blank" href="http://static.zend.com/topics/slides.pdf">Zend.com</a></li>
<li><a target="_blank" href="http://shameerc.com/2010/12/php-53-practical-look-into-lambda.html">Shameer</a></li>
<li>Libro: Desarrollo web ágil con Symfony2 de <i><a target="_blank" href="https://twitter.com/#!/javiereguiluz">Javier Equiluz</a></i></li>
</ul>jacanaleshttp://www.blogger.com/profile/12451684109673089591noreply@blogger.com0tag:blogger.com,1999:blog-3634485982977313452.post-54309529172015743772012-04-29T03:45:00.000+02:002012-04-29T04:39:34.017+02:00Namespaces en PHP<p>Para empezar, un poco de teoría sobre que son los <b>namespaces </b>(o nombres de espacio). Con la salida de la versión de <b>PHP 5.3</b>, una de las novedades más importantes que se incorporaron fué la posibilidad de utlizar namespaces, como ya se podían hacer en otros lenguajes de programación como eran <i>C, C++ y Java</i>, por ejemplo.</p>
<p>Para entender que es un namespace, podemos recurrir a la definición que encontramos en la propia <i>Wikipedia</i>: Un <i>namespace </i>es "<i>un contenedor abstracto que agrupa de forma lógica varios símbolos
e identificadores</i>" y los utilizaremos básicamente, para estructurar mejor nuestro código fuente.</p>
<p>Antes de que salieran los namespaces, en PHP todas las clases, constantes y funciones cargadas se ubican en un espacio global.</p>
<p>Para explicar el uso de namespace, crearé un supuesto modelo desde el cual accederemos después mediante el controlador:</p>
<pre class="brush: php">
namespace Users\Model;
class UserModel {
private String $nick = '';
public function getNick() {
return $this->nick;
}
public function setNick($p_nick) {
$this->nick = $p_nick;
}
}
</pre>
<p>En este ejemplo, vemos como <b>un namespace puede tener sub-namespaces</b>. "Model", es un sub-namespace dentro de "Users"</p>
<p>Una vez definida una clase en el namespace "Users\Model", podremos acceder a ella mediante otra clase, en este caso un controlador. Hay que recordar que, <b>en caso de ser necesario hacer un include o un require, tendremos que hacerlo SIEMPRE después de la declaración del namespace</b>.</p>
<pre class="brush: php">
namespace Users\Controller;
require_once 'users_model.php';
$bd = new \Users\Model\UserModel();
$bd->setNick('ZonaDev');
var_dump($bd->getNick());
</pre>
<p>En el caso de que la clase controlador y la clase modelo se encontraran en el mismo nombre de espacio, no haría falta añadir la ruta del nombre de espacio en la declaración.</p>
<p>También es interesante saber, que si vamos a utilizar mucho los objetos ubicados en un mismo namespace, podemos ahorrarnos bastante trabajo haciendo uso de la sentencia <b><i>use</i></b>, que además nos permite <b>definir un alias</b> para las clases. Aquí van algunos ejemplos:</p>
<pre class="brush: php">
namespace Users\Controller;
use Users\Model;
require_once 'users_model.php';
$bd = new UserModel();
$bd->setNick('ZonaDev');
var_dump($bd->getNick());
</pre>
<p>Usando alias:</p>
<pre class="brush: php">
namespace Users\Controller;
use Users\Model\UserModel as User;
require_once 'users_model.php';
$bd = new User();
$bd->setNick('ZonaDev');
var_dump($bd->getNick());
</pre>
<p>Con esta pequeña introducción a los namespaces, podremos entender mucho mejor el funcionamiento de los nuevos frameworks MVC que están saliendo y hacen uso de ellos, como es el ejemplo de <i>Symfony2 </i>y el inminente <i>Zend Framework 2</i>, además de motivarnos a utilizarlos para todo tipo de proyecto desarrollado en php.</p>
<p>Si quieres aprender más sobre el uso de namespaces, te recomiendo mirarte la documentación oficial de PHP en los siguientes enlaces:</p>
<p>
<a target="_blank" href="http://es.php.net/manual/es/language.namespaces.php">Espacios de nombres (namespaces)</a><br />
<a target="_blank" href="http://es.php.net/manual/es/language.namespaces.basics.php">Usar espacios de nombres: Lo básico</a><br />
<a target="_blank" href="http://es.php.net/manual/es/language.namespaces.faq.php">FAQ: Cosas que se necesitan saber sobre los espacios de nombres</a></p>
<p><b>NOTA:</b> Os recomiendo que leais el libro "<i>Desarrollo web ágil con Symfony2</i>" de <i>Javier Equiluz</i>, en el explica bastante bien que son los namespaces y como debemos utilizarnos (aplicando al entorno de Symfony2, claro).</p>jacanaleshttp://www.blogger.com/profile/12451684109673089591noreply@blogger.com0tag:blogger.com,1999:blog-3634485982977313452.post-40204570519300939072012-03-22T13:35:00.000+01:002012-04-29T03:47:39.470+02:00Conferencia deSymfony 2012Como muchos ya sabreis, otros posiblemente no, durante las fechas del<b> 15 y 16 de juni</b>o, se realiza en Castellón una de las citas más importantes en el panorama de las TI.<br />
<div>
<br /></div>
<div>
La comunidad de<b> Symfony</b> española, vuelve a organizar la conferencia "<b>deSymfony</b>", esta vez de una duración de dos días y en la que el propio<i> Fabien Potencier</i>, creador del Framework Symfony acudirá en persona para hacer una ponencia.</div>
<div>
<br /></div>
<div>
Aunque las ponencias están basadas en el framework que le da nombre (<b>Symfony2</b>), se hablarán de todo tipo de consejos, trucos, tutoriales sobre como manejar los complementos que lo componen, así como de buenas técnicas de uso y de como optimizar al máximo su rendimiento.</div>
<div>
<br /></div>
<div>
Se tratarán temas como el uso de <b>Twig</b>, <b>Varnish</b>, el uso de <b>MongoDB</b> como sistema de base de datos, internacionalición y otros componentes y servicios.</div>
<div>
<br /></div>
<div>
Entre los ponentes, podremos contar con la presencia de <i><b>Javier Eguiluz</b></i>, administrador de la comunidad española de Symfony (www.symfony.es), <i><b>Nacho Martín</b></i>, que ya fué ponente en el 2011 (al que tengo que agradecerle personalmente que me ayudara con la configuración de VIM) y como ya se ha mencionado, el propio<i><b> Fabien Potencier</b></i>.</div>
<div>
<br /></div>
<div>
La lista entera de ponentes la podreis encontrar en la web del evento <a href="http://desymfony.com/">http://desymfony.com/</a>.</div>
<div>
<br /></div>
<div>
La entrada se compra a través de esa misma web a un precio de <u><b>55€</b></u> que incluye las comidas (no las cenas) de los 2 días de ponencia.</div>
<div>
<br /></div>
<div>
Me gustaría hacer un incapié especial a la oferta de tren para los que asistan al evento. Descargándo una cédula de viaje desde esta web: <a href="http://desymfony.com/noticias/grandes-descuentos-asistentes-viajan-tren">http://desymfony.com/noticias/grandes-descuentos-asistentes-viajan-tren</a>, los asistentes que viajen en tren disfrutarán de un <b>30% de descuento</b>.</div>
<div>
<br /></div>
<div>
Poco más puedo extenderme, ya que sería repetir información que podreis encontrar en la web oficial, os invito a que la visiteis. Un saludo, nos vemos en Castellón.</div>
<div>
<br /></div>
<div>
Fuente oficial: <a href="http://desymfony.com/">http://desymfony.com/</a></div>jacanaleshttp://www.blogger.com/profile/12451684109673089591noreply@blogger.com5tag:blogger.com,1999:blog-3634485982977313452.post-71678472200198455072012-03-14T13:34:00.000+01:002012-04-29T03:48:37.327+02:00CakePHP: Pros y contras (según mi opinión)Actualmente llevo ya más de 2 años trabajando con el Framework de CakePHP 1.2. No elegí yo trabajar con este framework, pero se me impuso en su día y al principio tengo que reconocer que me pareció una buena idea. Hasta ahora, que lo conozco bien y conozco sus mayores defectos (y seguro el de la mayoría de frameworks), por lo que en el futuro procuraré evitar cometer los mismos errores.<br />
<br />
Voy a comentar mi punto de vista sobre el Framework, aunque tampoco me extenderé mucho. Empezaré por las ventajas.<br />
<br />
<b>PROS</b>
<ol>
<li><u><b>Fácil de aprender</b></u>: Hay que reconocer que desarrollar con CakePHP es fácil y sencillo una vez te has documentado un poco y conoces la estructura de ficheros. Sin conocerlo de nada, en 2 días ya estaba creando mis primeras pantallas con el.</li>
<li><u><b>Implementación</b> rápida</u>: Gracias a su estructura y los métodos propios del framework, desarrollar una aplicación puede realizarse en poco tiempo, eso si, en relación a si hubiera que hacerlo de 0, o si la aplicación no necesita modificaciones personalizadas.</li>
<li><u><b>Migración</b> <b>entre entornos</b></u>: Instalar una aplicación desarrollada con CakePHP es muy fácil, ya que si tienes bien configurado el servidor en el nuevo entorno, bastará con copiar y pegar el código en el nuevo entorno. No necesita muchas librerías especiales, pero repito, depende de la aplicacuón y los componentes o plugins que se le añadan.</li>
<li><u><b>Plugins</b></u>: Mencionados en el punto anterior, se pueden encontrar por internet plugins que realizan una tarea específica sin tener que programar nada. Basta con bajarse el código y añadirlo a la carpeta de plugins de CakePHP. Lo mismo pasa con algunos "componentes" y "helpers".</li>
</ol>
<div>
Hasta aquí las ventajas más significativas que voy a comentar de CakePHP. Ahora pasaré a lo que para mi, es motivo suficiente como para no volver a utilizarlo en futuros proyectos:</div>
<div>
<br /></div>
<div>
<b>CONTRAS</b></div>
<div>
<ol>
<li><b><u>Malos hábitos</u>: </b>En su documentación oficial, en algunos puntos te motivan a realizar algunos malos hábitos que deberían evitarse al trabajar con cualquier tipo de Framework MVC. Por ejemplo, con el método "find" de los modelos. Realizas la llamada desde el controlador, construyendo allí mismo la consulta, pero con el formato de CakePHP, enviando arrays. En mi opinión, todas las consultas find deben hacerse en el propio modelo, creando métodos allí que son los que realmente deben llamarse desde el controlador. Solo me pareció ver una referencia a esta forma de trabajar en la documentación de CakePHP, en uno de los últimos puntos de "recomendaciones", aunque ahora mismo tengo dudas. Además, con este sistema es mucho más sencillo poder cachear las consultas y queda el código mucho más limpio y ordenado. Ejemplo de llamada desde el controlador:
<pre class="brush: php">$specificallyThisOne = $this->Article->find(
'first',
array(
'conditions' => array('Article.id' => 1)
)
);
</pre>
</li>
<li><b><u>Exceso de consultas</u>: </b>En proyectos en los que el número de tablas es muy elevado y hay muchas relaciones, el ORM de CakePHP puede llegar a realizar excesivas consultas. En proyectos pequeños con pocas tablas esto no suele suceder o no suele ser tan importante, pero cuando hay más de 10 tablas esto puede ser un gran problema. La facilidad de CakePHP se compensa con su mal rendimiento en proyectos grandes. En CakePHP 2.0 dicen haber mejorado bastante el rendimiento, pero aún así el sistema de consultas es idéntico y sigue existiendo este mismo problema.</li>
<li><b><u>Tamaño de ficheros</u>:</b> Algunos de los ficheros del propio Framework son demasiado pesados. Esto, añadiendo que hay demasiados ficheros del framework a los que se accede prácticamente en cada petición (request), conlleva a un elevado número de accesos al disco que penalizan el rendimiento del servidor, haciendo que el tiempo de carga de la web se eleve considerablemente. La única solución para esto utilizar un servidor que compile los ficheros PHP para agilizar esta tarea (Por ejemplo: Apache + APC).</li>
</ol>
</div>
<div>
<b>Consejo</b></div>
<div>
Antes de utilizar un framework para realizar una aplicación importante, de gran envergadura, documentate bien sobre los pros y los contras que tendrá ese Framework y si cumplirá tus expectativas a medio y largo plazo (a corto casi siempre lo hacen). Puedes verte obligado a tener que reescribir todo tu código si tomas una mala decisión o te pasas los pasos principales de todo desarrollo.</div>
<div>
I+D, Análisis, desarrollo, guía de uso. Esto nunca debería faltar.</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<b>Reflexión personal.</b></div>
<div>
<b><u>¿Programadores?</u>: </b>Esto ya es un problema más general de la informática en general, pero que lo aplicaré a CakePHP en este caso. En este mundillo, hay mucha gente que ha trabajado duro para realizar algunas aplicaciones, frameworks, o cms para facilitar las tareas a los que no son informáticos. El problema de esto es que muchos usuarios finales que no saben más que utilizar estas "facilidades" empiezan a creerse gurús. Cuidado con este tipo de gente, son muy peligrosos y están sueltos.</div>jacanaleshttp://www.blogger.com/profile/12451684109673089591noreply@blogger.com4tag:blogger.com,1999:blog-3634485982977313452.post-70002085077037627622011-09-14T17:05:00.002+02:002012-04-29T03:50:35.598+02:00Terminator - Consola multiventana<div class="separator" style="clear: both; text-align: left;">
Hoy os traigo información sobre una herramienta que para mi es indispensable: Terminator.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Para mi que trabajo con varias máquinas a la vez (hasta 4 simultaneas, de momento), a veces es necesario tener abiertas varias terminales. Con Terminator tengo la posibilidad de tener 4 terminales en una misma ventana, al estilo de Konsole de KDE, pero con la particularidad añadida de que Terminator te permite crear grupos de terminales, con lo cual lo que escribas en una ventana del grupo puedes enviarlo a todas al mismo tiempo. Útil si tienes que realizar una misma tarea en varias máquinas, ya que escribiendo solo una vez la realizas en todas las máquinas a la vez.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisbim7-HCsrOWXH0imPimUhEVNUXLzD_e-88FW0FMfp6WyyRbrmaen8o4w-53vpVArAOsZ1PNcmwaS0NPx0JDDmm7OLyfcA0LFe4Bkj7FBoL4oQ-rk8mLdG1NWQRZF4ud4w2n9zMlsY_Ya/s1600/terminator.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="211" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisbim7-HCsrOWXH0imPimUhEVNUXLzD_e-88FW0FMfp6WyyRbrmaen8o4w-53vpVArAOsZ1PNcmwaS0NPx0JDDmm7OLyfcA0LFe4Bkj7FBoL4oQ-rk8mLdG1NWQRZF4ud4w2n9zMlsY_Ya/s400/terminator.png" width="400" /></a></div>
<br />
Personalmente, desde que uso Terminator, trabajo más cómodo, espero que a mis lectores también os ayude.<br />
<br />
Para instalarlo solo debeis instalarlo desde aptitude, si usais una distribución basada en Debian (Debian, Ubuntu...):<br />
<br />
<pre class="brush: bash">$ aptitude install terminator</pre>
<i><br /></i><br />
¡Hasta la próxima!jacanaleshttp://www.blogger.com/profile/12451684109673089591noreply@blogger.com0tag:blogger.com,1999:blog-3634485982977313452.post-35067858918952115872011-09-13T13:02:00.001+02:002012-04-29T03:54:04.534+02:00Conectar a Wifi desde el terminalBuenas, después de un tiempo sin escribir (por falta de tiempo), traigo un nuevo artículo, corto pero útil.<br />
<br />
Si instalamos un servidor Linux, por poner un ejemplo, es posible que necesitemos conectarnos a través de wifi a nuestra red local. Hoy os pondré los pasos necesarios para hacerlo. Según vaya configurando mi portátil del trabajo, iré escribiendo documentos sobre la instalación y configuraciones más habituales y/o útiles para tener un buen entorno de desarrollo configurado.<br />
<br />
Vayamos al lío,<br />
<br />
<b>1º Empezaremos buscando la información de la red WiFi a la que queremos conectar. Haremos iwconfig si no sabemos el nombre de la interfaz lo miraremos con "<i>iwconfig</i>". Suponemos que es wlan0:</b><br />
<pre class="brush: bash">
$ iwlist wlan0 scanning
</pre>
<br />
<b>2º Buscaremos la información ESSID de la red y la dirección MAC del router y los configuramos en nuestra interfaz:</b><br />
<pre class="brush: bash">$ iwconfig wlan0 essid $_ESSID
$ iwconfig wlan0 ap 40:4A:03:XX:XX:XX</pre>
<br />
<b>3º Se configura la clave de la WiFi (suponiendo que tienes una especificada). Si es una clave ASCII (normalmente lo es), deberás añadir "s:" delante de la clave:</b><br />
<pre class="brush: bash">$ iwconfig wlan0 key $_KEY
$ iwconfig wlan0 key s:$_KEY</pre>
<br />
<b>4º Se obtiene una ip desde el router. Antes de esto, podemos comprobar si hemos introducido bien los datos con "<i>iwconfig</i>"</b><br />
<pre class="brush: bash">$ dhclient wlan0</pre>jacanaleshttp://www.blogger.com/profile/12451684109673089591noreply@blogger.com0tag:blogger.com,1999:blog-3634485982977313452.post-74230885940037308582011-08-14T20:58:00.000+02:002012-04-29T03:55:37.268+02:00Cambiar nombre del equipo desde terminalEn algunas ocasiones, es posible que necesitemos cambiar el nombre de nuestro equipo (más posiblemente, un servidor remoto que hayamos adquirido de algún proveedor).<br />
<br />
Para realizar este cambio deberemos realizar los siguientes pasos:<br />
<br />
<b>1.- </b>Editaremos el fichero <i>/etc/hosts</i> y cambiaremos el nombre antiguo por el nuevo.<br />
<b>2.- </b>Cambiaremos el nombre del equipo en el fichero <i>/etc/hostname</i><br />
<b>3.- </b>Lanzaremos el script<i> /etc/init.d/hostname.sh</i> para que se apliquen los cambios sin reiniciar.<br />
<br />
<pre class="brush: bash">sh /etc/init.d/hostname.sh</pre>
<br />
También podemos cambiar el nombre del equipo temporalmente ejecutando la sentencia:
<pre class="brush: bash">/bin/hostname nuevo-nombre</pre>jacanaleshttp://www.blogger.com/profile/12451684109673089591noreply@blogger.com0tag:blogger.com,1999:blog-3634485982977313452.post-11175245080013053882011-08-14T20:40:00.000+02:002012-04-29T03:58:44.568+02:00Conectar a servidor SSH sin contraseñaPara los que trabajamos continuamente con varios servidores SSH, puede llegar a ser algo extresante tener que escribir siempre la contraseña. Y si además tienes que memorizar varias contraseñas de diferentes servicios, servidores, etc, como es mi caso, puede darse el caso de que cueste recordarlas.<br />
<br />
En cualquier caso, por un motivo u otro, puede que cualquier programador o administrador de sistemas prefiera poder conectarse a su servidor SSH sin necesidad de escribir continuamente la contraseña, pero, ¿como hacer esto sin vulnerar la seguridad del servidor?.<br />
<br />
La respuesta está en las claves RSA y el fichero authorized_keys de SSH. La cuestión es crear una clave rsa, que almacenaremos en el servidor al que deseamos conectar. A continuación explico los pasos para realizar el procedimiento.<br />
<br />
<b>1. Creamos la clave RSA</b><br />
Para empezar, debemos crear en nuestro cliente la una clave rsa para identificar la máquina. Abriremos una ventana de terminal y ejecutaremos la siguiente sentencia:<br />
<pre class="brush: bash">
ssh-keygen -t rsa
</pre>
Nos pedirá una contraseña común, que podremos utilizar en caso de querer utilizar esta misma para conectar a diferentes servidores (en caso de que lo subamos a varias máquinas). Es una medida de seguridad más, aunque yo siempre dejo este campo vacío.<br />
<br />
Una vez ejecutada la esta sentencia, se creará automáticamente en la carpeta .ssh de nuestro usuario ($HOME/.ssh) un fichero id_rsa.pub que utilizaremos para conectar a nuestros servidores.<br />
<br />
<b>2. Enviar la clave al servidor/es</b><br />
El siguiente paso es enviar la clave al servidor. Con un sencillo comando que nos pedirá la contraseña, ya conseguiremos hacerlo:<br />
<pre class="brush: bash">
ssh-copy-id -i ~/.ssh/id_rsa.pub $USER@$HOST
</pre>
Donde <u>$USER</u> es el usuario con el que conectamos al servidor y <u>$HOST</u> la dirección. Con esto ya estaremos listos para conectar al servidor sin necesidad de introducir la contraseña.<br />
<br />
<br />
<b>CONFIGURACIÓN ADICIONAL</b><br />
Para los más maniáticos, o aquellos que quieran añadir un nivel más de seguridad al servidor, se puede añadir la opción de que no se pueda conectar al servidor SSH si el equipo no está en el archivo authorized_keys del servidor. Es decir, que solo aquellos que han realizado el paso anterior puedan conectarse.<br />
<br />
Para ello se debe editar el fichero <i>/etc/ssh/ssh_config</i> del servidor y añadir las siguientes lineas de configuración:<br />
<pre class="brush: bash">
PasswordAuthentication no
RSAAuthentication yes
PubkeyAuthentication yes
</pre>
Reiniciamos el servidor:<br />
<pre class="brush: bash">sudo /etc/init.d/ssh restart</pre>
Y probamos de conectaros mirando los mensajes de conexión (para comprobar que funciona correctamente):<br />
<pre class="brush: bash">
ssh -v $USER@$HOST</pre>
Para realizar esta configuración en varios servidores, basta repetir solo el paso 2 en cada uno de los servidores, ya que la clave RSA será única para el cliente.<br />jacanaleshttp://www.blogger.com/profile/12451684109673089591noreply@blogger.com0tag:blogger.com,1999:blog-3634485982977313452.post-55643633043154850752011-08-03T17:32:00.001+02:002012-04-29T04:02:19.813+02:00Evitar desconexión SSH<p>A muchos de vosotros os pasará que debeis conectar a un servidor mediante SSH y realizar varias tareas, pero entre estas puede pasar un cierto tiempo. Por defecto, la conexión SSH se desconecta automáticamente cuando el servidor lleva un rato sin recibir ninguna orden, por lo que se debe volver a reconectar al servidor (e incluso en algunos casos, bloquea la terminal y hay que cerrarla y volver a abrirla).</p>
<p>Esta mañana he encontrado la solución en un <a href="http://gomix.fedora-ve.org/projects/fedobetatest/wiki/SSH">blog venezonalo de Fedora</a>.</p>
<p>La mejor manera de mantener la sesión abierta, es hacer que el servidor SSH envíe peticiones cada cierto tiempo para comprobar el estado de la conexión. Esto hace que al haber actividad, la conexión no se cierre. Para hacerlo deberemos aplicar la siguiente configuración:</p>
<p>
<b><u>Cliente</u></b><br />
Abriremos el fichero <i>/etc/ssh/ssh_config </i>con nuestro editor favorito y añadiremos las siguientes 2 líneas:
<pre class="brush: bash">
ServerAliveInterval 30
AliveCountMax 4
</pre>
<b><u>Servidor</u></b><br />
En nuestro servidor SSH abriremos el mismo fichero <i>/etc/ssh/ssh_config</i> para añadir estas otras 2 líneas:
<pre class="brush: bash">
ClientAliveInterval 30
ClientAliveCountMax 4
</pre>
</p>
<p>Esta configuración hace que cada una de las máquinas haga consultas a la otra cada 30 segundos y en caso de recibir 4 errores NOK (errores de respuesta), la conexión se cerrará. De lo contrario se mantendrá abierta.</p>
<p>Disfrutad de una conexión SSH sin cortes.</p>jacanaleshttp://www.blogger.com/profile/12451684109673089591noreply@blogger.com4tag:blogger.com,1999:blog-3634485982977313452.post-66935478239612578362011-07-29T10:44:00.014+02:002012-04-29T04:06:34.904+02:00Botón de +1 con jQuery<p>Para los amantes de <b>Google+</b> y <b>jQuery</b>, traigo una nueva alternativa a la integración del <b>botón +1</b> que publicó<i> Ricardo Galli</i> en su <a href="https://plus.google.com/117939449396284436490">perfil de Google+</a>. Con esta pequeña modificación del código, se consigue que no se cargue el botón hasta que se haya cargado completamente el DOM de la página, evitando posibles errores.</p>
<p>El primer paso para integrarlo es incrustar este código en el header (entre los tags de tu código). Debes recordar que este fragmento de código se debe colocar después de haber cargado la librería de jQuery</p>
<pre class="brush: html">
<script type="text/javascript">
window.___gcfg = {lang: 'es'};
$(function () {
$.getScript("<a class="ot-anchor" href="https://apis.google.com/js/plusone.js" style="color: #3366cc; cursor: pointer; text-decoration: none;">https://apis.google.com/js/plusone.js<wbr></wbr></a>");
});
</script>
</pre>
<p>Una vez cargada la api de Google, solo nos falta incrustar el botón en el lugar de la web que hayamos elegido. Para ello debes introducir el siguiente código:</p>
<pre class="brush: javascript">
<g:plusone count="false" size="small">
</pre>
<p>Para más información sobre el botón de +1, ver otros parámetros posibles o dudas, podeis consultar la web oficial de <a href="http://www.google.com/webmasters/+1/button/index.html">Google Webmasters</a> donde encontrareis toda la información oficial.</p>
<p>También podréis ver un ejemplo en la sección de noticias de la LVP, al entrar al detalle de una. Por ejemplo: <a href="http://www.lvp.es/news/view/148">http://www.lvp.es/news/view/148</a></p>jacanaleshttp://www.blogger.com/profile/12451684109673089591noreply@blogger.com0tag:blogger.com,1999:blog-3634485982977313452.post-22424932726322592002011-07-29T02:40:00.000+02:002011-07-29T02:40:34.365+02:00Apertura de blog provisionalBuenas noches.<br />
<br />
Desde este mismo instante, abre este blog de forma provisional hasta que pueda tener el diseño de mi propio blog donde iré publicando noticias sobre programación y administración de sistemas, así como ejemplos de código de diferentes frameworks y lenguajes.<br />
<br />
Se acepta la colaboración de gente, así que si alguien está interesado en publicar, puede contactar conmigo a través de mi correo personal: tanque.tm@gmail.com o a través de mi cuenta de twitter @tanque_tm.<br />
<br />
Muchos de los que entreis y veais esta "chapuza" pensareis, "en casa de herrero, cuchillo de palo", pero lo peor de todo es que tendreis razón, pero a continuación detallo los motivos por los que he decidido abrir un blog en blogger:<br />
<br />
<br />
<ul>
<li>Tengo pensado abrir un blog con wordpress, ya que tiene plugins bastante interesantes y la gestión de noticias y usuarios es bastante buena, pero mientras permanezco a la espera del diseño del logotipo y me decante por un diseño por el blog, o pueda diseñar el mío propio, he decidido abrir uno temporal en blogger, ya que después se puede realizar una exportación rápida y sencilla.</li>
<li>Mientras no tenga una solución definitiva, aprovecho el servicio gratuito de blogger, que me permite redireccionar mi propio dominio.</li>
<li>Últimamente ando excaso de tiempo para ponerme a trabajar en un nuevo diseño, programación de blog, además de estar esperando mi nuevo equipo que ya configuraré como un buen entorno de programación.</li>
<li>Porque a pesar de haber podido esperar a tener todo lo anterior, quería reservar ya el dominio y montar algo para empezar a dar a conocer la idea, buscar posibles colaboradores, e ir insertando contenidos.</li>
<li>Porque me gustan los retos, y lo de ponerme la meta de ir publicando noticias es uno de ellos, así como la de hacer una migración correcta de blogger a wordpress, ya que esto me dará un nuevo contenido con un tutorial de como hacerlo, en el momento en el que lo haga.</li>
<li>Porqué así podré hacer tutoriales sobre la gestión de contenidos en blogger y wordpress y hacer una comparativa en el futuro.</li>
<li>Y en resumen y para terminar, porque me ha salido de los santos procesadores, porque hace años que quería hacer algo y esto es solo un primer paso. Pequeño, sencillo pero un paso. Espero que de aquí surja un gran proyecto, o almenos un pequeño proyecto donde pueda acudir yo a consultar recopilaciones mías cuando las necesite, en lugar de volver a buscar de nuevo por la red.</li>
</ul>
<div>
A pesar de que la idea principal de la web es abarcar noticias de programación y sistemas, también puede que publique temas de diseño, aunque al no estar metido en el tema serán minoritarios. Los principales lenguajes de programación (y no tan lenguajes) que intentaré abarcar serán los siguientes:</div>
<div>
<ul>
<li>Java</li>
<li>PHP</li>
<li>C++</li>
<li>Python</li>
<li>Perl</li>
<li>Bash script</li>
<li>Javascript</li>
<li>CSS2.1 / CSS3</li>
<li>XHTML1.0 / XHTML1.1 / HTML5</li>
<li>XML, XSL, XSLT</li>
<li>Otros lenguajes</li>
</ul>
<div>
<br /></div>
</div>
<div>
Ale, ahora a criticar que he elegido una plantilla feísima, que blogger es una porquería, y demás. Pero es lo que hay, almenos de momento.</div>
<div>
<br /></div>
<div>
Un abrazo y gracias a todos :).</div>
jacanaleshttp://www.blogger.com/profile/12451684109673089591noreply@blogger.com3