miércoles 31 de octubre de 2007

FrontController

Saludos a todos!, antes que nada una disculpa por ya no haber puesto mas temas pero el trabajo me estuvo comiendo.

Vuelvo con este tema que es la segunda parte del patrón MVC, y es el FrontController, en el tema pasado vimos como es posible separar la capa de datos/proceso/vista, es una forma ideal para separar nuestro código y hacerlo mucho mas legible.

Ahora vamos a ver como centralizar todo y controlar las instancias. En un ambiente como Java o C, siempre hemos tenido dos puntos únicos de entrada, en estos puntos de entrada podemos incluir las clases necesarias, realizar labores de levantamiento, etc.

En un ambiente en Web es igual de necesario tener un punto de entrada único en nuestra aplicación, así podemos controlar y definir la configuración de nuestra base de datos, que templates vamos a usar, logs, etc.

Aquí podemos ver la utilidad de manejar un punto central de carga, ahora en un ambiente web luego es complicado llegar a esta automatización, y otros lenguajes proveen algunas herramientas para facilitar esto (por ejemplo en ASP tenemos el global.asa, etc.).

Un FrontController es como la operadora de teléfonos que esta en la central que se encargaba en tiempos antiguos de conectar las llamadas entre extensiones, para recordarnos les dejo esta foto:


Esa precisamente es la tarea del FrontController, escucha las peticiones que vienen desde un URL, y se encarga de hacer un dispatch al controlador especifico que mandemos llamar, posteriormente llama a la acción deseada de nuestro controlador, en pocas palabras es un Router de requests, y tiene la facilidad de que al todo ser centralizado podemos hacer tareas de levantamiento.

Un ejemplo sencillo de aplicar este patrón seria el siguiente:

1 2 class FrontController {
3 public static function
Main() {
4
// Realizamos tareas antes de iniciar el proceso, aqui podemos iniciar lo que sea relativamente...
5
$config = new ConfigBag();
6
$config['db'] = new ConfigBag();
7
$config['db']['host'] = "localhost";
8
$config['db']['user'] = "usuarioDB";
9
$config['db']['password'] = "pass";
10
$config['db']['database'] = "proyecto";
11
12
DB::setDefaultConfig( $config['db'] );
13
ActiveTable::setDefaultDBAdapter( DB::getInstance() );
14
15
$logFile = "/path/a/log/log.log";
16
Logger::setDefaultLogger( new FileLogger( $logFile ) );
17
18
// Definimos nuestro directorio de Controllers
19
$controllerDir = "/path/a/los/controllers/";
20
// Obtenemos el controlador y la accion
21
$controller = $_GET['controller'];
22
$accion = $_GET['accion'];
23
24 if( empty(
$controller ) ) { // Comprobamos si esta vacia, si asi es definimos que por defecto cargue Index
25
$controller = "index";
26 }
27 if( empty(
$accion ) ) { // Comprobamos tambien..
28
$accion = "index";
29 }
30
31
$controllerFile = $controllerDir . $controller . ".php";
32 if( !
file_exists( $controllerFile ) ) { // Si no existe el archivo lanzamos una excepcion
33
throw new FrontControllerException( "No se encontro el archivo especificado" );
34 }
35
36
$controllerClass = $controller . "Controller";
37 if(
class_exists( $controllerClass, false ) ) { // Si existe el archivo pero no esta la clase lanzamos otra excepcion
38
throw new FrontControllerException( "El controlador fue cargado pero no se encontro la clase" );
39 }
40
41
$controllerInst = new $controllerClass();
42
43 if( !
is_callable( array( $controllerInst, $accion ) ) ) { // Comprobamos si la accion es posible llamarla
44
throw new FrontControllerException( "El controlador no tiene definida la accion $accion" );
45 } else {
46
$controllerInst->$accion(); // Llamamos a la accion y dejamos el proceso al controlador
47
}
48 }
49 }
50
51 class
FrontControllerException extends Exception {}
52
53
// Iniciamos todo
54
FrontController::Main();
55
?>



Como podemos ver el FrontController es un Router que se encarga de enrutar las peticiones, para que esto sea centralizado tenemos que forzosamente llamar al router ej:

http://host/router.php?controller=automoviles&accion=listaAutomoviles

Ahora con la facilidad que nos da mod_rewrite podemos crear reglas para que nos transforme ese url en algo mas amigable:

http://host/automoviles/listaAutomoviles/

Asi podemos pasar los parametros de forma oculta, un ejemplo de esta regla seria:

RewriteRule ^(.+)/(.+)/$ router.php?controller=$1&action=$2

Las demas acciones que levanta el FrontController son de ejemplo, asi que podemos ver como centralizar la carga de nuestras clases, en un solo archivo, los includes los podemos realizar de igual manera en nuestros controladores especificando el path completo.

Es todo por hoy y espero les sea claro.

Saludos.

martes 29 de mayo de 2007

Patron MVC

El patrón MVC es un patrón de diseño de software en el cual todo el proceso está dividido en 3 capas, típicamente estas capas son el Modelo, la Vista y el Controlador.

El Modelo incorpora la capa del dominio y persistencia, es la encargada de guardar los datos en un medio persistente (ya sea una base de datos, un archivo de texto, XML, registro, etc.). En el modelo es donde se hace el levantamiento de todos los objetos que tu sistema debe de utilizar, es el proveedor de tus recursos, es decir, el Modelo Automóviles te tiene que dar objetos Automóvil. Es muy típico que las clases del modelo incorporen otro patrón de diseño como Active Record, ya que así es más fácil guardar en el momento en la base de datos.

La Vista se encarga de presentar la interfaz al usuario, en sistemas web, esto es típicamente HTML, aunque pueden existir otro tipo de vistas. En la vista solo se deben de hacer operaciones simples, como ifs, ciclos, formateo, etc.

El Controlador es el que escucha los cambios en la vista y se los envía al modelo, el cual le regresa los datos a la vista, es un ciclo donde cada acción del usuario causa que se inicie de nuevo un nuevo ciclo.

La forma más sencilla de implementar este patrón es pensando en capas, como regla, los accesos a la base de datos se hacen en el modelo, la vista y el controlador no deben de saber si se usa o no una base de datos. El controlador es el que decide que vista se debe de imprimir y que información es la que se envía.

Un ejemplo sencillo implementando MVC es el que sigue, un listado de Automóviles (utilizando el ejemplo de Active Table):

Controlador (AutomovilesController.php):
1  
<?php
2  
class AutomovilesController extends Controller {
3       public function 
__construct() {
4       
5       }
6       
7       public function 
listaAutomoviles() {
8            
$automoviles = new Automoviles();
9            
10            
$data = array();
11            
$data['Autos'] = $automoviles->findAll();
12            
13            
$view = new View();
14            
$view->setData$data );
15            
$view->setTemplate'tpl/listado.php' );
16            
$view->setLayout'tpl/mainLayout.php' );
17            
18            
$view->render();
19       }
20       
21  }
22  
?>



Vista (listado.php):
1  
<table class="listadoAutos">
2       <tr>
3            <th>ID</th>
4            <th>Nombre</th>
5            <th>Color</th>
6       </tr>
7       <?php foreach( $Autos as $automovil ) { ?>
8  
     <tr>
9            <td><?php echo $automovil['id']; ?></td>
10            <td><?php echo $automovil['nombre']; ?></td>
11            <td><?php echo $automovil['color']; ?></td>
12       </tr>
13       <?php ?>
14  
</table>



Modelo: (Automoviles.php)
1  
<?php
2  
class Automoviles extends ActiveTable {
3       public function 
__construct() {
4            
parent::__construct();
5            
6            
$this->resultObject "Automovil";
7       }
8  }
9  
10  class 
Automovil extends ActiveRecord {}
11  
?>



Dispatcher: (este es el unico archivo que no es OOP)
1  
<?php
2  
include( 'ActiveTable.php' );
3  include( 
'ActiveRecord.php' );
4  include( 
'View.php' );
5  include( 
'Controller.php' );
6  include( 
'AutomovilesController.php' );
7  include( 
'Automoviles.php' );
8  
9  
$controller = new AutomovilesController();
10  
$controller->listaAutomoviles();
11  
?>



Como podemos ver la vista (que no es nada más que otro PHP) se encarga de imprimir la tabla y dejarla lista para su visualización, el modelo solo recibe lo que el controlador le envía y es de donde la vista obtiene su fuente de información.

Otro ejemplo seria agregar un nuevo automóvil a nuestro catalogo de automóviles:

Controlador (AutomovilesController.php):
1  
<?php
2  
class AutomovilesController extends Controller {
3       public function 
__construct() {
4       
5       }
6       
7       public function 
nuevoAuto() {
8            
$data = array();
9            if( 
strtoupper$_SERVER['HTTP_REQUEST_METHOD'] ) == "POST" ) {
10                 
$nombre $_POST['nombre'];
11                 
$color $_POST['color'];
12                 
13                 
$errors = array();
14                 if( empty( 
$nombre ) ) {
15                      
$errors['nombre'] = "Debe de escribir un nombre";
16                 }
17                 
18                 if( empty( 
$color ) ) {
19                      
$errors['color'] = "Debe de seleccionar un color";
20                 }
21                 
22                 
$noErrors = ( count$errors ) > false true );
23                 
24                 if( 
$noErrors ) {
25                      
$automoviles = new Automoviles();
26                      
$auto $automoviles->add();
27                      
28                      
$auto['nombre'] = $nombre;
29                      
$auto['color'] = $color;
30                      
31                      try {
32                           
$auto->save();
33                           
$id $auto['id'];
34                           
35                           
$data['Mensaje'] = "Automovil $id Insertado exitosamente";
36                      } catch( 
Exception $e ) {
37                           
$data['Mensaje'] = $e->getMessage();
38                      }
39                 }
40            }
41            
42            
$view = new View();
43            if( isset( 
$errors ) && !$noErrors ) {
44                 
$data['errors'] = $errors;
45            }
46            
47            
$view->setData$data );
48            
$view->setTemplate'tpl/nuevoAuto.php' );
49            
$view->setLayout'tpl/mainLayout.php' );
50            
51            
$view->render();
52       }
53  }
54  
?>



Vista (nuevoAuto.php):
1  
<?php if( !empty( $Mensaje ) ) { ?>
2  
<h1>
3  <?php echo $Mensaje?>
4  
</h1>
5  <?php ?>
6  
<form action="nuevoAuto.php" method="post">
7       <table class="nuevoAuto">
8            <tr>
9                 <td>Nombre:</td>
10                 <td><input type="text" name="nombre" value="" /><?php if( !empty( $errors['nombre'] ) ) { echo $errors['nombre']; } ?></td>
11            </tr>
12            <tr>
13                 <td>Color:</td>
14                 <td><input type="text" name="color" value="" /><?php if( !empty( $errors['color'] ) ) { echo $errors['color']; } ?></td>
15            </tr>
16            <tr>
17                 <td colspan="2">
18                      <input type="submit" name="agregar" value="Agregar" />
19                 </td>
20            </tr>
21       </table>
22  </form>



El Modelo es el mismo que el anterior.

Dispatcher: (este es el unico archivo que no es OOP)
1  
<?php
2  
include( 'ActiveTable.php' );
3  include( 
'ActiveRecord.php' );
4  include( 
'View.php' );
5  include( 
'Controller.php' );
6  include( 
'AutomovilesController.php' );
7  include( 
'Automoviles.php' );
8  include( 
'Automovil.php' );
9  
10  
$controller = new AutomovilesController();
11  
$controller->nuevoAuto();
12  
?>



Este ejemplo es muy básico, y al patrón MVC se le han agregado muchas cosas, como puede ser un Router que se encargue de decidir que controlador utilizar dependiendo de la entrada de información.

Otra adición son los smart-urls estos se encargan de que los urls sean más amigables, aunque esto no depende enteramente de PHP, pero es una adición que muchos ocupan a la hora de utilizar este patrón de diseño.

Básicamente el patrón MVC se puede implementar de una manera sencilla, no requiere de grandes cosas y es un beneficio ya que se utiliza mucha OOP, en el próximo artículo pondré un ejemplo de un router sencillo con smart urls para anexar a este patrón de diseño.

A continuación les dejo las clases que se implementaron para el patrón:

Controller.php:
1  
<?php
2  
class ControllerException extends Exception {}
3  class 
Controller {
4       public function 
__call() {
5            throw new 
ControllerException('Error fatal, se llamo a una funcion que no esta definida');
6       }
7  }
8  
?>



View.php:
1  
<?php
2  
class ViewException extends Exception {}
3  class 
View {
4       private 
$data;
5       private 
$template;
6       private 
$layout;
7       
8       public function 
__construct() {}
9       
10       public function 
setData$data ) {
11            if( !
is_array$data ) ) {
12                 throw new 
ViewException('$data se esperaba fuera un arreglo, se envio un ' gettype$data ));
13            }
14            
$this->data $data;
15       }
16       
17       public function 
setLayout$layout ) {
18            if( !
file_exists$layout ) ) {
19                 throw new 
ViewException('$layout  no es un archivo existente');
20            }
21            
22            
$this->layout $layout;
23       }
24       
25       public function 
setTemplate($template) {
26            if( !
file_exists$template ) ) {
27                 throw new 
ViewException('$template no es un archivo existente');
28            }
29            
30            
$this->template $template;
31       }
32       
33       public function 
render() {
34            
$content $this->renderTemplate();
35            
36            include( 
$this->layout );
37       }
38       
39       private function 
renderTemplate() {
40            
ob_start();
41            @
extract$this->dataEXTR_OVERWRITE );
42            include( 
$this->template );
43            
$content ob_get_clean();
44            
45            return 
$content;
46       }
47  }
48  
?>



mainLayout.php:
1  
<html>
2       <head>
3            <title>Sistema de Autos</title>
4       </head>
5       <body>
6            <?php echo $content?>
7  
     </body>
8  </html>



AutomovilesController.php:
1  
<?php
2  
class AutomovilesController extends Controller {
3       public function 
__construct() {
4       
5       }
6       
7       public function 
listaAutomoviles() {
8            
$automoviles = new Automoviles();
9            
10            
$data = array();
11            
$data['Autos'] = $automoviles->findAll();
12            
13            
$view = new View();
14            
$view->setData$data );
15            
$view->setTemplate'tpl/listado.php' );
16            
$view->setLayout'tpl/mainLayout.php' );
17            
18            
$view->render();
19       }
20       
21       public function 
nuevoAuto() {
22            
$data = array();
23            if( 
strtoupper$_SERVER['HTTP_REQUEST_METHOD'] ) == "POST" ) {
24                 
$nombre $_POST['nombre'];
25                 
$color $_POST['color'];
26                 
27                 
$errors = array();
28                 if( empty( 
$nombre ) ) {
29                      
$errors['nombre'] = "Debe de escribir un nombre";
30                 }
31                 
32                 if( empty( 
$color ) ) {
33                      
$errors['color'] = "Debe de seleccionar un color";
34                 }
35                 
36                 
$noErrors = ( count$errors ) > false true );
37                 
38                 if( 
$noErrors ) {
39                      
$automoviles = new Automoviles();
40                      
$auto $automoviles->add();
41                      
42                      
$auto['nombre'] = $nombre;
43                      
$auto['color'] = $color;
44                      
45                      try {
46                           
$auto->save();
47                           
$id $auto['id'];
48                           
49                           
$data['Mensaje'] = "Automovil $id Insertado exitosamente";
50                      } catch( 
Exception $e ) {
51                           
$data['Mensaje'] = $e->getMessage();
52                      }
53                 }
54            }
55            
56            
$view = new View();
57            if( isset( 
$errors ) && !$noErrors ) {
58                 
$data['errors'] = $errors;
59            }
60            
61            
$view->setData$data );
62            
$view->setTemplate'tpl/nuevoAuto.php' );
63            
$view->setLayout'tpl/mainLayout.php' );
64            
65            
$view->render();
66       }
67  }
68  
?>



Espero les ayude a comprender mejor como se utiliza el patrón MVC y como hace que las cosas sean más flexibles.

Saludos.

viernes 18 de mayo de 2007

Publicar codigo PHP en Blogger

Muchas veces nos preguntamos como publicar código fuente, en varios blogs hay herramientas para tal efecto, pero en blogger particularmente no hay forma tan sencilla, eso si tenemos todo el poder para poder publicar código HTML directamente.

Para eso desarrolle un pequeño script en PHP, cuya finalidad es esa, formatear el codigo PHP y dejarlo listo para que se pueda publicar, con colores y números de lineas.

La uncia desventaja es que tienes que copiar el código fuente directamente cuando lo genera el script (delimitado vía comentarios). El porque de esto es porque si lo pones en una etiqueta PRE, el browser interpreta el código, al igual que si estuviera en un textarea.

La única forma de que se quedara formateado y listo era copiando directamente del codigo fuente, así que para utilizar este script, lo que haces es enviar tu código y presionas Enviar, al recargarse la pagina, debes de seleccionar ver código fuente y copiarlo directamente de ahí.

Espero les sea de ayuda, ya que funciona para publicar cualquier código, pero con colores solo con PHP, aunque se puede expandir para utilizar un sistema mayor de publicación de código y poder publicar casi cualquier tipo de código en Blogger.

Aquí les dejo el código (formateado con el mismo script):


1  
<?php
2  
if( !empty( $_POST['codigo'] ) ) {
3       
$codigo $_POST['codigo'];
4       
$codigo str_replace"\t""     "$codigo );
5       
$codigo highlight_stringstripslashes$codigo ), true );
6       
7       
$line 1;
8       
$buffer = array();
9       
$cod explode"<br />"$codigo );
10       foreach( 
$cod as $codLine ) {
11            
$buffer[] = "<b>$line</b>&nbsp;&nbsp;" $codLine;
12            
$line++;
13       }
14       
15       
$codigo implode"\n"$buffer );
16       
// HACK:
17       
$codigo str_replace"<b>1</b>&nbsp;&nbsp;<code>""<code><b>1</b>&nbsp;&nbsp;"$codigo );
18  }
19  
?>
20  
<html>
21  <head>
22       <title>Format Code</title>
23  </head>
24  <body>
25  
26  <!-- COPY FROM HERE
27  
28  <?php echo $codigo?>
29  

30  TO HERE -->
31  
32  
33  <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
34       <textarea rows="20" cols="80" name="codigo"></textarea><br />
35       <input type="submit" name="enviar" value="Enviar" />
36  </form>
37  </body>
38  </html>

miércoles 16 de mayo de 2007

Patron ActiveTable y ActiveRecord

Patrón ActiveTable y ActiveRecord

Cuando empezamos a utilizar OOP al 100% empezamos a usar clases para representar objetos que estamos utilizando en el diseño de nuestro sistema.

Pensemos en un ejemplo básico, vamos a hacer un catalogo de automóviles, para representar a nuestro automóvil vamos a utilizar la siguiente clase:



1  <?php
2  
class Automovil {
3       private 
$nombre;
4       private 
$color;
5       private 
$puertas;
6       private 
$maxVelocidad;
7       
8       public function 
__construct() {}
9       
10       
/** Setters **/
11       
public function setNombre$nombre ) {
12            
$this->nombre $nombre;
13       }
14       
15       public function 
setColor$color ) {
16            
$this->color $color;
17       }
18       
19       public function 
setPuertas$puertas ) {
20            
$this->puertas $puertas;
21       }
22       
23       public function 
setMaxVelocidad$velocidad ) {
24            
$this->maxVelocidad $velocidad;
25       }
26       
27       
/** Getters **/
28       
public function getNombre() {
29            return 
$this->nombre;
30       }
31       
32       public function 
getColor() {
33            return 
$this->color;
34       }
35       
36       public function 
getPuertas() {
37            return 
$this->puertas;
38       }
39       
40       public function 
getMaxVelocidad() {
41            return 
$this->maxVelocidad;
42       }
43  }
44  
?>



Como podemos ver nuestro automóvil tiene propiedades básicas, puede ser más complejo pero para este tutorial lo dejaremos así. Podemos utilizar nuestro nuevo objeto Automóvil de la siguiente manera:



1  <?php
2  $auto 
= new Automovil();
3  
$auto->setNombre("BMW M3");
4  
$auto->setColor("Negro");
5  
$auto->setPuertas);
6  
$auto->setMaxVelocidad"250 km/h" );
7  
?>



Es muy sencillo, pero se complica cuando queremos que este automóvil sea persistente, o sea que este guardado en un medio no volátil, como una base de datos, XML, archivo de texto, etc. Esto es fácil de implementar, por ejemplo usando serialize y guardándolo en esos medios, pero ¿cómo lo recuperamos?, ¿Cómo podemos hacer búsqueda por cierta característica (ej: el color)? Aquí es donde se complica todo, ya que si queremos tener una tabla en nuestra base de datos donde cada propiedad de nuestra clase represente una columna de nuestra base de datos, y cada instancia de la clase represente una fila de nuestra tabla necesitamos de cierta forma hacer que se traduzca la clase en un objeto que se pueda insertar en nuestra base de datos.

La forma de hacer esto: ActiveRecord, este es un patrón de diseño, con el cual podemos implementar de manera correcta que cada objeto nos represente una fila de nuestra base de datos.

¿Qué es el ActiveTable?

El Active Table es un objeto que puede persistir otros objetos en medios no volátiles, como bases de datos. La función de una clase que implementa este patrón es de comunicarse con la base de datos y regresarnos objetos como los tenemos definidos para nuestra aplicación. Veamos el siguiente ejemplo:



1  <?php
2  $autos 
= new Automoviles();
3  
$unAuto $autos->findByPk(1); // Recuperamos el Auto con ID=1
4  
echo $unAuto['Nombre'];
5  
$unAuto['Puertas'] = 3// Cambiamos el numero de puertas
6  
$unAuto->save(); // Guardamos la modificacion.
7  
?>



Como podemos ver, nuestro ActiveTable Automóviles nos trae un objeto automóvil, sobre el cual podemos trabajar e incluso guardar directamente en la base de datos, el objeto es lo suficientemente inteligente para saber guardarse o crear uno nuevo.

¿Cómo implementamos este patrón?

Hay varias formas de implementarlo, muchos Frameworks lo hacen a su manera, yo en lo personal me gusta la siguiente forma, en la que heredando la clase directamente, podemos saber el nombre de la tabla y usamos comandos simples para obtener los datos que nos interesan (como la clave primaria).

Una vez creado nuestro objeto, a la hora de pedirle que nos regrese algo (con alguna de las funciones find*), la clase debe de crear y regresarnos un objeto que podamos utilizar (en nuestro caso un objeto Automóvil). Este objeto nos sirve para trabajar como lo vimos en el primer ejemplo, pero también a la hora de implementar ActiveRecord es posible que el objeto se sepa guardar en la base de datos solos.

Veamos el siguiente ejemplo de ActiveTable:



1  <?php
2  
class ActiveTable {
3       protected 
$table;
4       protected 
$keyField;
5       protected 
$_db;
6       protected 
$resultObject;
7       private 
$_columns;
8       
9       public function 
__construct() {
10            
$this->_db     DB::getInstance();
11            
$this->setup();
12       }
13       
14       public function 
__destruct() {}
15       
16       protected function 
setup() {
17            if( !
$this->table $this->table get_class($this);
18            
19            
$query "SHOW FIELDS FROM ?";
20            
$command $this->_db->prepare$query );
21            
$command->execute( array( $this->table ) );
22            
23            
$fields = array();
24            
$primary '';
25            while( 
$row $command->fetch() ) {
26                 
$fields[$field['Field']] = array(
27                      
"name" => $field['Field'],
28                      
"type" => $field['Type'],
29                      
"defaultValue" => $field['Default'],
30                      
"key" => $field['Key'],
31                 );
32                 
33                 if( 
$field['Key'] === "PRI" ) {
34                      
$primary $field['Field'];
35                 }
36            }
37            
38            
$this->_columns $fields;
39            
40            if( empty( 
$primary ) ) {
41                 throw new 
Exception"Primary Column not found for Table: " $this->table );
42            }
43            
44            
$this->keyField $primary;
45       }
46       
47       public function 
info() {
48            return array(
49                 
"name" => $this->table,
50                 
"columns" => $this->_columns,
51                 
"primary" => $this->keyField,
52            );
53       }
54       
55       public function 
getPrimaryKey() {
56            return 
$this->keyField;
57       }
58       
59       public function 
newRecord() {
60            
$obj $this->resultObject;
61            
62            
$aObj = new $obj(null$this);
63            return 
$aObj;
64       }
65       
66       public function 
findByPk$id ) {
67            
$sql "SELECT * FROM `%s` WHERE `%s`='%s' LIMIT 1";
68            
$sql sprintf($sql$this->table$this->keyField$id);
69            
70            
$result $this->_db->query$sql );
71            
$data $result->fetch();
72            
73            
$obj $this->resultObject;
74            
75            
$aObj = new $obj($data$this);
76            
77            return 
$aObj;
78       }
79       
80       public function 
add$fields$function_fields = array() ) {
81            
$qvalues = array();
82            
$qfields = array();
83            foreach( 
$fields as $field_name => $field_value ) {
84                 
$qfields[] = "`$field_name`";
85                 if( !empty( 
$function_fields[$field_name] ) ) {
86                      
$qvalues[] = $function_fields[$field_name] . "('$field_value')";
87                 } else {
88                      
$qvalues[] = "'" addslashes$field_value ) . "'";
89                 }
90            }
91            
92            
$query "INSERT INTO `" $this->table "` (" implode', '$qfields ) . ") VALUES ( " implode', '$qvalues ) . ")";
93            
94            
$affRows $this->_db->exec$query );
95            
$id $this->_db->lastInsertId();
96            
97            if( 
$affRows <= ) {
98                 throw new 
Exception"SQL Error, Insert Failed" );
99            }
100            
101            return 
$id;
102       }
103       
104       public function 
save$fields$strict true$function_fields = array() ) {
105            if( ( isset( 
$fields[$this->keyField] ) ) && ( !empty( $fields[$this->keyField] ) ) && $this->keyExists$fields[$this->keyField] ) ) {
106                 
$result $this->update$fields$strict$function_fields );
107            } else {
108                 
$result $this->add$fields$function_fields );
109            }
110            
111            return 
$result;
112       }
113       
114       public function 
update$fields$strict true$function_fields = array() ) {
115            if( !isset( 
$fields[$this->keyField] ) || empty( $fields[$this->keyField] ) ) {
116                 throw new 
Exception"Primary Key Missing, update failed" );
117            }
118            
119            
$key $fields[$this->keyField];
120            unset( 
$fields[$this->keyField] );
121            
122            if( 
$strict ) {
123                 
$rst $this->_db->querysprintf"SELECT * FROM `%s` WHERE `%s`='%s' LIMIT 1"$this->table$this->keyField$key ) );
124                 
$data $rst->fetch();
125            } else {
126                 
$data = array();
127            }
128            
129            
$toupdate = array();
130            foreach( 
$fields as $field=>$value ) {
131                 if( 
$strict ) {
132                      if( ( 
$data[$field] != $value ) ) {
133                           if( !empty( 
$function_fields[$field] ) ) {
134                                
$toupdate[] = sprintf"`%s`=%s('%s')"$field$function_fields[$field], $value );
135                           } else {
136                                
$toupdate[] = sprintf"`%s`='%s'"$fieldaddslashes$value ) );
137                           }
138                      }
139                 } else {
140                      if( !empty( 
$function_fields[$field] ) ) {
141                           
$toupdate[] = sprintf"`%s`=%s('%s')"$field$function_fields[$field], $value );
142                      } else {
143                           
$toupdate[] = sprintf"`%s`='%s'"$fieldaddslashes$value ) );
144                      }
145                 }
146            }
147            if( 
count$toupdate ) == ) { 
148                 return 
0;
149            }
150            
151            
$query sprintf"UPDATE `%s` SET %s WHERE `%s`='%s' LIMIT 1"$this->tableimplode', '$toupdate  ), $this->keyField$key );
152            
153            
$updated $this->_db->exec$query );
154            
155            if( 
$updated <= ) { // if true, then some wierd database error ocurred
156                 
return 0;
157            }
158            
159            return 
$updated;
160       }
161       
162       public function 
delete$records ) {
163            if( empty( 
$records ) ) return 0;
164            
165            if( 
is_array$records ) ) { 
166                 
$escaped_keys = array();
167                 
168                 foreach( 
$records as $key ) {
169                      
$escaped_keys[] = "'{$key}'";
170                 }
171                 
$keys implode(","$escaped_keys);
172                 
$total count$escaped_keys );
173                 
174                 
$query sprintf"DELETE FROM `%s` WHERE `%s` IN (%s) LIMIT %s"$this->table$this->keyField$keys$total );
175            } else {
176                 
$total 1;
177                 
$query sprintf"DELETE FROM `%s` WHERE `%s`='%s' LIMIT %s"$this->table$this->keyField$records$total );
178            }
179            
180            
$deleted $this->_db->exec$query );
181            
182            return 
$deleted;
183       }
184  }
185  
?>



Este es un ejemplo sencillo desde el cual ustedes pueden seguir y completarlo e implementar ya cosas más complejas.

¿Qué es el ActiveRecord?

El Active Record es un patrón de diseño donde a la hora de implementarlo debe de ser inteligente para saber guardarse en la base de datos con solo llamar a su función save.

¿Cómo implementamos este patrón?

Una forma sencilla y rápida es usando ArrayAccess y heredando directamente de ActiveRecord, una implementación sencilla pero efectiva puede ser la siguiente:



1  <?php
2  
class ActiveRecord implements ArrayAccess {
3       private 
$record = array();
4       private 
$keyField '';
5       private 
$table null;
6       private 
$isNew false;
7       
8       public function 
__construct$data$table ) {
9            if( 
$data === null ) {
10                 
$this->record = array();
11                 
$this->isNew true;
12            } else {
13                 
$this->record $data;
14            }
15            
16            if( !( 
$table instanceof ActiveTable ) ) {
17                 throw new 
Exception'$table is expected to be a GeckoDBTable instance' );
18            }
19            
20            
$this->table $table;
21            
$this->keyField $table->getPrimaryKey();
22       }
23       
24       public function 
refresh() {
25            if( 
$this->isNew ) return; // new Records can't be refreshed
26            
27            
$newData $this->table->findByPk$this->record[$this->keyField] );
28            
$this->record $newData->record;
29            
$newData null;
30       }
31       
32       public function 
save() {
33            if( 
$this->isNew ) {
34                 
$id $this->table->add$this->record );
35                 
$this->record[$this->keyField] = $id;
36                 
$this->isNew false;
37                 
38                 return 
$id;
39            } else {
40                 return 
$this->table->update$this->record );
41            }
42       }
43       
44       public function 
getNew() {
45            return new 
selfnull$this->table );
46       }
47       
48       public function 
offsetExists$offset ) {
49            return (isset( 
$this->record[$offset] ) );
50       }
51       
52       public function 
offsetGet$offset ) {
53            return 
$this->record[$offset];
54       }
55       
56       public function 
offsetSet$offset$value ) {
57            if( 
$offset == $this->keyField ) {
58                 throw new 
Exception"Primary Key can't be set or changed" );
59            }
60            
61            
$this->record[$offset] = $value;
62       }
63       
64       public function 
offsetUnset$offset ) {
65            if( isset( 
$this->record[$offset ) ) {
66                 unset( 
$this->record[$offset] );
67            }
68       }
69  }
70  
?>



Ahora ya teniendo nuestros patrones y nuestras clases listas, ¿cómo lo utilizamos?, veamos el siguiente ejemplo completo:



1  <?php
2  
include( 'ActiveTable.php' );
3  include( 
'ActiveRecord.php' );
4  
5  class 
Automoviles extends ActiveTable {
6       public function 
__construct() {
7            
parent::__construct();
8            
9            
$this->resultObject "Automovil";
10       }
11  }
12  
13  class 
Automovil extends ActiveRecord {}
14  
15  
$autos = new Automoviles();
16  
$unAuto $autos->findByPk(1); // Recuperamos el Auto con ID=1
17  
echo $unAuto['Nombre'];
18  
$unAuto['Puertas'] = 3// Cambiamos el numero de puertas
19  
$unAuto->save(); // Guardamos la modificacion.
20  
?>



Como podemos ver, no usamos 1 sola sentencia SQL directamente, esto nos permite tener un nivel de abstracción muchísimo mayor e inclusive a la hora de programar es mucho más rápido.

De aquí faltan mucho más cosas, como por ejemplo hace unos meses en ForosDelWeb alguien decía que esto no era efectivo porque cuando tenemos claves foráneas, teníamos que crear dos instancias y las clases hacían múltiples consultas a la base de datos, esto es cierto, pero no totalmente, podemos modificar nuestra clase para que sea lo suficientemente inteligente y pueda resolver las claves foráneas, y crear Joins, esto es más complicado y no se va a cubrir en este tutorial, pero es posible al 100%, y de manera eficiente.

Espero que con este tutorial les haya quedado como implementar estos patrones de diseño y puedan realizar sus aplicaciones más limpias.



Aqui les dejo un link para que vean como implementar ArrayAccess y como nos permite accesar a las propiedades de un objeto como si fuera un Arreglo.

viernes 27 de abril de 2007

Accesando a nuestra base de datos con PDO

PHP y MySQL, se llevan de la mano, es muy sencillo y casi todos los paquetes "todo en uno" implementan PHP y MySQL, esto le dio a PHP una ventaja muy grande y un crecimiento muy grande.

El problema viene que muchos programadores inexpertos se pusieron a programar, y a hacer micro sistemas, o scripts, que en si sirven para su función, pero pueden llegar a ser inseguros y los hackers empiezan a buscar agujeros para explotar sus funciones y poder entrar a la base de datos y borrar, o cambiar todo.

Este es un problema grave (investiguen el caso de PHP-Nuke o PHPBB en sus primeras versiones), ya que programando webs inseguras, ocurren varias cosas, nos vemos mal como programadores, y damos mala fama a todos los programadores web.

Para evitar esto hay que ser extremadamente cuidadosos en las variables que enviamos para entrar a la base de datos, dichas variables SIEMPRE tienen que ser verificadas que son los datos que esperamos, en pocas palabras existe una regla de oro:

Nunca confíes en el contenido que te envía el usuario

¿Como podemos remediarlo? Puede ser un dolor de cabeza, pero en cada variable que envíes tienes que checar si esta activado o no magic_quotes (otro error en PHP que por defecto viene activado), luego escapar todos tus strings con addslashes y mysql_real_escape_string, luego checar tus querys y ver que no permitas ningún agujero en tu código.

Esto es un dolor de cabeza pero es necesario, ahora, que pasa si derrepente te cambian tu base de datos, que pasa si tu cliente en lugar de mysql, decide que quiere MSSQL o PostgreSQL. Tendrías ahora mas dolores de cabeza, para remediar esto, y para evitar programar mal desde la version 5.1 de PHP viene por defecto (y activado) PDO.

PDO por sus siglas en Ingles (PHP Data Objects) Objetos de Datos de PHP. PDO es un driver de acceso a varias bases de datos por lo que es ideal que cuando empieces a programar lo hagas sobre este driver.

Este pequeño mini tutorial te dirá como entrar, seleccionar, insertar y actualizar datos en tu base de datos favorita: MySQL, si requieres cambiar a otra base de datos, por lo general solo cambiaras una linea de tu código.

Como conectar a MySQL

Para iniciar una conexión a MySQL usamos el constructor PDO:

try {
$db = new PDO('mysql:dbname=testdb;host=localhost', $user, $pass);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// Cerramos la conexion
$db = null;
}
catch( PDOException $e ) {
echo
"Error de conexion: " . $e->getMessage();
}



Facil, no utilizamos ni mysql_connect, ni mysql_close, el constructor PDO nos crea una instancia del driver sobre el que podemos trabajar.

Toda instancia de PDO si le pasamos como atributo que PDO::ERRMODE_EXCEPTION lanzara una excepcion en cuanto tenga un error, esto es muy util ya que nos concentraremos en realizar el trabajo, y si de paso estamos usando transacciones, si hay una excepcion automaticamente son regresadas garantizando que nuestros datos sean consistentes.

Ahora enviar querys, es sencillo, ya que automáticamente las variables son protegidas contra ataques de SQL Inyection. Aquí si cambia un poco la forma, ya que usamos lo que se le conoce como Prepared Statements o Comandos Preparados, es igual de sencillo, cuando preparamos un query (ya sea Insert, Select, Update, Call, etc.).

La forma es sencillo:

try {
$db = new PDO('mysql:dbname=testdb;host=localhost', $user, $pass);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// Preparamos un Query
$comando = $db->prepare("SELECT * FROM Clientes WHERE Cliente LIKE ?");
// Obtenemos el comando desde GET
$cliente = '%' . $_GET['Cliente'] . '%';

$comando->execute( array( $cliente ) );

while(
$row = $comando->fetch() ) {
var_dump( $row ); // Imprimimos resultados
}

// Cerramos la conexion
$comando = null;
$db = null;
}
catch( PDOException $e ) {
echo
"Error de conexión: " . $e->getMessage();
}

Como podemos ver, podemos usar directamente el valor de nuestro $_GET sin temor a que sea una cadena que pueda perjudicarnos, ya que el driver automáticamente se encarga de analizar y enviar la cadena como un string.

Si vemos el query hay algo diferente y es el uso de lo que se le conoce como marcadores o "placeholders", esto es el signo de interrogación en el WHERE "?". Estos marcadores es lo que se va a reemplazar por el valor que tu le asignes cuando uses execute(). Para actualizar solo debes de cambiar tu Query:

UPDATE Clientes SET Nombre=? WHERE idCliente=?

Y enviar tu query:

$comando->execute( array( "Juan", 1 );

Como puedes ver, no es necesario escapar las variables, ni mucho menos, esto te da la facilidad, y te quita una preocupación.

Otra ventaja de usar PDO en lugar de las funciones de mysql normales, es que podemos extenderlas y crear nuestra propia clase de bases de datos, agregarle el patron Singleton y tener ya lista una clase para trabajar con nuestro sistema.

Un rapido ejemplo:
class DB extends PDO {
private static $_instance = null;

private function __construct() {
// Obtenemos el Config desde un array, clase config etc:
$config = Config::getInstance();
$db = $config->DBName;
$user = $config->UserName;
$pwd = $config->Password;

$attrs = array();
$dsn = 'mysql:host=localhost;dbname=' . $db;

parent::__construct( $dsn, $user, $pwd, $attrs );
$this->setAtribute( PDO::ATTR_ERRMODE, PDO::ERR_MODE_EXCEPTION );
}

public static function getInstance() {
if(
self::$_instance == null ) {
self::$_instance = new self();
}

return
self::$_instance;
}
}


Espero que con este tutorial te puedas dar una idea de las ventajas de usar PDO en lugar de las funciones de mysql normales, y de paso abrirte camino a la abstracción de datos.

Suerte.

domingo 22 de abril de 2007

JSON: Transferir objetos entre PHP y Javascript

En la semana estuve buscando varias formas de establecer una comunicación entre PHP y una pagina HTML, casi 100% JavaScript. La idea era reutilizar ya varios componentes que existen, entre ellos prototype una librería que implementa JSON de una forma casi nativa (desde su versión 1.5).

Me encontré con varias complicaciones, y que seguramente te puedes encontrar, una de ellas es enviar directamente el objeto JSON y recibirlo en Javascript de forma nativa, el problema de esto es que para hacerlo debe de estar enviado en el header como X-JSON, y como contenido el objeto JSON.

La limitante de esto es que hay un limite (para Internet Explorer) en la cantidad de datos que puede enviar por el header X-JSON, asi que si vas a enviar un objeto muy largo te recomiendo que lo envíes directamente en el body de tu contenido.

Ahora al tutorial, desde la version 5.2, PHP incorpora una extension nativa para codificiar objetos, arrays, variables, etc. a JSON, es mas recomendable usar esta funcion (pues esta programada en C y su velocidad es mucho mayor a la clase en PHP).

Para empezar creamos nuestro objeto en PHP, que vamos a enviar a Javascript, es algo sencillo:


class Persona {
public $nombre;
public $apellido;
public $edad;

public function __construct($nombre, $apellido, $edad ) {
$this->nombre = $nombre;
$this->apellido = $apellido;
$this->edad = $edad;
}
}


Este es un objeto básico, pero eficiente, los atributos públicos son los que se van a enviar y codificar como JSON, si no deseas tener atributos públicos, puedes implementar tu propio método que te regrese las variables privadas ya codificadas. Esto lo mostrare en un tutorial mas adelante. Este objeto nos sirve para almacenar el nombre, el apellido y la edad de una persona. Bonito, ahora como lo podemos enviar a JavaScript y mantener que sea un objeto de tal forma que lo podamos leer así:


function verPersona( persona ) {
var nombre = persona.nombre;
var apellido = persona.apellido;
var edad = persona.edad;
}



La solucion es usar AJAX y Protototype, primero creamos nuestro script en PHP:


require( "Persona.php" );
// Hacemos un request a una base de datos, etc.
$persona = new Persona($nombre, $apellido, $edad);

header("Content-type: application/json");
echo "(" . json_encode( $persona ) . ")";


Si quieres enviarlo en el header para que prototype lo cree automático haces esto:

header( "X-JSON: (" . json_encode($persona) . ")");


Es importante enviarlo entre paréntesis para que en JavaScript no tengas problemas, para recibirlo haces un request:


function getPersona() {
var ajax = new Ajax.Request( '/myscript.php', { method: 'get', onComplete: checkResponse } );
}

function checkResponse( transport, json ) {
// Si enviaste como el header X-JSON, tu objeto ya esta en json de lo contrario haz esto:
json = tranport.responseText.evalJSON(true);

alert( Object.inspect(json) );
}


Ya en tu variable json tienes un objeto que puedes utilizar para sacar tus propiedades.

Espero que con este tutorial puedas enviar y recibir objetos de forma nativa desde PHP y hacia Javascript, las ventajas de usar JSON sobre AJAX es que JSON es mucho mas liviano para JavaScript. No es necesario parsear XML y el ancho de banda usado es menor. Ahora a codificar aplicaciones Web2.0 mucho mas sencillo.

domingo 15 de abril de 2007

Bienvenida

Bienvenidos a mi blog, la idea de este blog es poner trucos y tips para realizar aplicaciones Web2.0 usando PHP5 y Javascript.

Mucho del potencial de este blog se basara en tips de como hacer las cosas de la forma mas correcta, orientarnos a resolver el problema y aprender a resolver problemas similares en un futuro.

También aprovechare para ir poniendo partes del código de mi framework sobre el que se basaran algunas de las soluciones.

Aunque no todo sera programación, también se publicaran cosas chistosas y noticias en general, espero puedan tener una lectura de su agrado, no se olviden de comentar si no les parece algo, todos tienen su derecho de replica.

Saludos a todos