domingo, 21 de diciembre de 2014

Arreglar el problema al repetir en Live HTTP Headers

Buenas, queria comentarles hoy por la tarde estaba intentando repetir unas cabeceras que habia enviado a una pagina, y sucede que no se enviaba por mas que lo hacia, asi que entonces como utilizo mucho esta extension y vi que el problema era por que tengo un firefox una version mas reciente que deshabilito HTTP Cache V1.



La extension de Live HTTP Headers tiene dos lineas donde utiliza el cache:


cacheService.evictEntries(Components.interfaces.nsICache.STORE_ON_DISK);
cacheService.evictEntries(Components.interfaces.nsICache.STORE_IN_MEMORY);
Estas dos lineas deben de ser comentadas!

Se encuentran dentro del archivo:

livehttpheaders/content/LiveHTTPHeaders.js
Para bajar la version correcta, esta disponible en:


sábado, 6 de diciembre de 2014

[Parte 3] Programacion orientada a objetos con PHP

POO Mas Avanzado

En la parte anterior vimos algunos conceptos de POO Avanzado, con un enfasis particular relacionado a herencia. 

Los temas de este capitulo llegan a ser mas esoterico, y no siempre es necesario una aplicacion web, pero estos conceptos deben estar en su radar a medida que se sienta mas comodo con la programacion orientada a objetos como un enfoque de programacion. 

Clases abstractas y Metodos

En algunas situaciones en la que se aplica herencia, nunca seria apropiado crear una instancia de una clase padre. Por ejemplo, en el ejemplo de Mascota, la intencion seria la creacion de una subclase especifica para cada tipo de Mascota y nunca realmente crear un objeto de tipo de Mascota. En tales casos, seria mas apropiado definir una clase base abstracta, en lugar de una clase base estandar. 

Las clases abstractas son versiones de la plantilla de una clase padre. Al definir una clase abstracta, puede indicar el comportamiento general de que las subclases deben tener. Dicho de otra manera, una clase abstracta define las interfaces: como las clases derivadas de este tipo de base van a ser utilizadas. Las subclases son responsables de definir las implementaciones reales de esas interfaces.

Las clases abstractas son diferentes de las clases normales en que el intento de crear un objeto de resultados de tipo de una clase abstracta resulta en un error fatal (Ver la imagen de abajo). En cambio, las clases abstractas estan destinadas a ser extendidas, y luego se crea una instancia de esa clase extendida. 



Este enfoque comienza con la palabra clave abstract:

abstract class NombreClase{
       
    }
Las clases abstractas suelen tener metodos abstractos. Estos se definen asi: 

abstract function nombreMetodo();
abstract function nombreMetodo($var1,$var2);
Eso es todo!, aun no se define la funcionalidad del metodo; en cambio, la funcionalidad sera determinada por la clase extendida de la clase abstracta. Si desea agregar visibilidad a la definicion, solo tiene que añadir la palabra clave correspondiente despues de la palabra abstract: 

abstract public function nombreMetodo();
Asi es como la parte de Mascota puede lucir:

    abstract class Mascota {
        protected $_name;
        abstract public function obtenerNombre();
    }


Entonces Gato contendria:


class Gato extends Mascota {
        function obtenerNombre(){
            return $this->_name;
        }
    }

Tenga en cuenta que la implementacion del metodo abstracto en el de la clase extendida Gato::obtenerNombre(); por ejemplo- tiene que cumplir con la misma visibilidad o mas debil. Si la funcion abstracta es publica, la version extendida tambien debe ser publica. Si la funcion de abstracta esta protegida, a continuacion, la version extendida solo puede ser protegida o publica. Nunca crearia un metodo abstracto privado, ya que un metodo privado no se puede heredar. 

En todos los casos, la version implementada del metodo tambien debe tener el mismo numero de argumentos que la definicion abstracta (es decir, la misma firma).


Tenga en cuenta que si una clase tiene incluso un metodo abstracto, la clase en si debe ser abstracta.
Sin embargo una clase abstracta, puede tener metodos no abstractos, asi como atributos, todos los cuales tambien seran heredados por la clase derivada. 
Para poner esto en accion, vamos a volver a los ejemplos de geometria, como Rectangulo. 
Esa clase podria ser una extension de una clase de forma mas generica. Vamos a instruir la clase abstracta Figura y una clase Hijo, Triangulo.


La clase abstracta Figura puede ser el padre de muchos tipos de Figuras (bidimensionales).



 <?php
 /*
 * Esta pagina define la clase abstracta Figura
 * Esta clase no contiene atributos
 * Esta clase  contiene dos metodos abstractos:
 * - obtenerArea()
 * - obtenerPerimetro()
 */

 abstract class Figura {
  // Sin atributos declarados
  // Sin constructores o destructores definidos aqui

  // Metodo para calcular y devolver el area
  abstract protected function obtenerArea();

  // Metodo para calcular y devolver el perimetro.
  abstract protected function obtenerPerimetro();
 } // Terminamos la clase Figura.

 
?>

1.- Definimos la clase figura: abstract class Figura {
2.- Definimos el primer metodo abstracto: abstract protected function obtenerArea();
Esta linea dice que cualquier clase que se extiende de la clase Figura necesita definir un metodo obtenerArea(). Ademas, este metodo no deberia tener ningun argumento y la visibilidad tiene que ser publica o protegida (la misma visibilidad o mas debil).
3.- Definimos el segundo metodo abstracto: abstract protected function obtenerPerimetro();
4.- Completamos la clase: } 


 <?php
 /*
 * Esta pagina define la clase Triangulo
 * Esta clase contiene dos atributos:
 * - private $_lados (array)
 * - private $_perimetro (number)
 * La clase contiene 3 metodos:
 * - __construct()
 * - obtenerArea()
 * - obtenerPerimetro()
 */

 class Triangulo extends Figura {
  // Declaramos los atributos:
  private $_lados = array();
  private $_perimetro = NULL;

  // Constructor:
  function __construct($s0 = 0, $s1 = 0, $s2 = 0){
   // Almacenamos los valores en un Array:
   $this->_lados[] = $s0;
   $this->_lados[] = $s1;
   $this->_lados[] = $s2;

  // Calculamos el perimetro:
  $this->_perimetro = array_sum($this->_lados);
  } // Fin del constructor

  // Metodo para calcular y devolver el area:
  public function obtenerArea(){
   // Calculamos y devolvemos el area:
   return (sqrt(
   ($this->_perimetro/2) *
   (($this->_perimetro/2) - $this->_lados[0]) *
   (($this->_perimetro/2) - $this->_lados[1]) *
   (($this->_perimetro/2) - $this->_lados[2]) 
   ));
  } // Fin del metodo obtenerArea().

  // Metodo para devolver el perimetro:
  public function obtenerPerimetro(){
   return $this->_perimetro;
  } // Fin del metodo obtenerPerimetro().
 } // Fin de la clase Triangulo.
?>


1.- Comenzamos declarando la clase Triangulo: class Triangulo extends Figura {

2.- Declaramos los atributos:
private $_lados = array();
private $_perimetro = NULL;
 

El primer atributo almacenara el tamaño de los tres lados (como alternativa puede hacer tres variables independientes), la segunda variable almacenara el perimetro. 
Solo estoy añadiendo este atributo por que el perimetro sera utilizado en el calculo del area, asi que es bueno tener una variable en lugar de recuperarla a traves de un metodo. Todos los atributos son privados, ya que no deben ser accedidos fuera de cualquier clase y no puedo imaginar como una clase Triangulo sera heredada (en cuyo caso pueden estar protegidos).


3.- Definimos el constructor:
function __construct($s0 = 0, $s1 = 0, $s2 = 0){
            // Almacenamos los valores en un Array:
            $this->_lados[] = $s0;
            $this->_lados[] = $s1;
            $this->_lados[] = $s2;

            // Calculamos el perimetro:
            $this->_perimetro = array_sum($this->_lados);
        } // Fin del constructor




El constructor toma tres argumentos para los tres lados del triangulo. Esos valores se colocan en el array $_lados, y luego se calcula el perimetro. La funcion array_sum() suma todos los elementos del array.


4.- Creamos un metodo obtenerArea():

public function obtenerArea(){
            // Calculamos y devolvemos el area:
            return (sqrt(
            ($this->_perimetro/2) *
            (($this->_perimetro/2) - $this->_lados[0]) *
            (($this->_perimetro/2) - $this->_lados[1]) *
            (($this->_perimetro/2) - $this->_lados[2])
            ))
        } // Fin del metodo obtenerArea().


Si recuerda la geometria, sabe que el area del triangulo es igual a la mitad de la base por altura. 




Por supuesto, para hacer este calculo la base tendria que determinar la base (el lado mas largo, no es un problema) y la altura (que requiere trigonometria). En lugar de eso utilizaremos la Formula de Heron's. Para implementarlo en el codigo PHP.


Ejemplo: ¿Cuál es el área de un triángulo en el que cada lado es de 5?

Paso 1: s = (5 + 5 + 5) / 2 = 7,5 (perimetro)
Paso 2: A = (7,5 × 2,5 × 2,5 × 2,5) = (117,1875) = 10.825 ...


5.- Creamos el metodo obtenerPerimetro():
 
public function obtenerPerimetro(){
            return $this->_perimetro;
        } // Fin del metodo obtenerPerimetro().


Este es el segundo metodo abstracto en Figura que debe ser implementado aqui. Para este ejemplo, simplemente retorna el atributo perimetro. No tiene que crear el atributo perimetro, este metodo fue devuelto por $this->_perimetro = array_sum($this->_lados);

6.- Completamos la clase: } // Fin de la clase Triangulo.   

Para usar la clase Triangulo:


 <!doctype html>
<html lang="es">
<head>
 <meta charset="utf-8">
 <title>Triangulo</title>
</head>
<body>
<?php
 // Leemos las definiciones de las clases:
 require('triangulo.php');

 // Establecemos los lados del triangulo:
 $s1 = 5;
 $s2 = 10;
 $s3 = 13;

 // Imprimimos una introduccion y creamos un nuevo triangulo
 echo "<h2>Con los lados de $s1, $s2, y $s3...</h2>";
 $t = new Triangulo($s1,$s2,$s3);

 // Imprimimos el area:
 echo '<p>El area de un triangulo es '. $t->obtenerArea().'</p>';
 // Imprimimos el perimetro:
 echo '<p>El perimetro de el triangulo es '.$t->obtenerPerimetro().'</p>';

 // Completamos la pagina:
 unset($t);
?>
</body>
</html>
 
 


En UML, una clase se marca como abstracto ya sea por su nombre en cursiva, o colocando llaves junto a su nombre {abstracta}.

El metodo __toString()

Si se define un metodo __toString() en una clase, PHP invoca ese metodo automaticamente, cuando un objeto de este tipo es usado como cadena. Por ejemplo, seria llamado si trato de hacer esto:


$a = new AlgunaClase();
echo $a;


Los objetos son por definicion, tipos de variables complejos, no destinados a ser usados como si fueran variables escalares. Sin embargo, es bastante comun para los desarrolladores agregar metodos __toString(), por lo menos como una herramienta sencilla y util de depuracion. 

Como desarrollador, mediante la definicion de un metodo __toString() para una clase, tiene que decidir exactamente lo que sucede podria un objeto de esa clase ser utilizado como Cadena, en lugar de limitarse a que PHP genere un error. 




Interfaces

Una interfaz es similar a la clase abstracta. Interfaces, como clases abstractas, identifican la funcionalidad (es decir, metodos) que debe ser definido por una clase especifica. Para crear una interfaz utilice la palabra clave interface.

Luego dentro de las llaves, se definen las firmas de metodos, no su implementacion real:

interface iAlgo {
        public function algunaFuncion($var);
    }


(Convencionalmente, los nombres de interfaces a menudo comienzan con i minuscula, pero no es requerido). Tenga en cuenta que los metodos de una interfaz deben ser publicos. Ademas, las interfaces solo identifican metodos; nunca incluyen atributos. Para asociar una clase con una interfaz, utilice el operador implements en la definicion de la clase.

class AlgunaClase implements iAlgo{}

La clase entonces debe definir todos los metodos que aparecen en la interfaz o un error fatal se producira:



El error fatal es creado por tener una clase implementada de una interfaz sin implementar todos los metodos.


Clase abstracta vs Interfaz

La diferencia entre una interfaz y una clase abstracta puede parecer sutil. Recuerde que una clase abstracta tiene la intencion de ser extendida por una clase mas especifica, de la que probablemente se cree una instancia de objeto. Como ya has visto, una clase abstracta puede definir un objeto generico, como una Figura. A la inversa, la interfaz no es heredado por una clase, por lo que no se debe pensar en una interfaz como una forma de definir libremente un objeto completo. En cambio, una interfaz establece un contrato para la funcionalidad que una clase debe tener, independientemente del tipo de clase. 
Otra forma de distinguir entre clases abstractas e interfaces es que las clases abstractas siguen teniendo "es una" relacion con la clase derivada. Las interfaces no tienen "es una" relacion con clases derivadas, aunque se podria decir que la clase derivada tiene un "tiene los mismos comportamientos como" relacion con una interfaz.
En el siguiente ejemplo, vamos a crear una interfaz para la funcionalidad CRUD estandar. La sigla CRUD se refiere a la capacidad de CREAR, LEER, ACTUALIZAR y ELIMINAR datos - las cuatro acciones basicas que se requieren para muchos tipos diferentes de contenido utilizado en sitios y aplicaciones.





 <!doctype html>
<html lang="es">
<head>
 <meta charset="utf-8">
 <title>Interface</title>
</head>
<body>
<?php
 // Esta pagina define y usa la interface iCrud
 
 /*
 * La interface iCrud
 * La interface identifica cuatro metodos:
 * - crear()
 * - leer()
 * - actualizar()
 * - eliminar()
 */

 interface iCrud {
  public function crear($datos);
  public function leer();
  public function actualizar($datos);
  public function eliminar();
 }

 /* El usuario implementa la clase interface iCrud.
 * La clase contiene dos atributos:
 * - private $_usuarioId;
 * - private $_usuario;
 * La clase contiene los cuatro metodos de la interface, mas un constructor.
 */

 class Usuario implements iCrud {

  private $_usuarioId;
  private $_usuario;

  // El constructor toma un array de datos:
  function __construct($datos){
   $this->_usuarioId = uniqid();
   $this->_usuario = $datos['usuario'];
  } // Fin del constructor

  // Este metodo tambien toma un array de datos:
  function crear($datos){
   self::__construct($datos);
  }

  // Esta funcion retorna la informacion acerca del objeto actual:
  function leer(){
   return array('usuarioId' => $this->_usuarioId, 'usuario' => $this->_usuario);
  }

  // Funcion para actualizar el objeto actual:
  function actualizar($datos){
   $this->_usuario = $datos['usuario'];
  }

  // Funcion para deshacerse del objeto actual:
  public function eliminar(){
   $this->_usuario = NULL;
   $this->_usuarioId = NULL;
  } 
 } // Terminamos la clase Usuario


 // Identificamos la informacion del usuario:
 $usuario = array('usuario' => 'arthusu');

 // Imprimimos una pequeña introduccion:
 echo "<h2>Creando un nuevo usuario</h2>";

 // Crear un nuevo usuario:
 $yo = new Usuario($usuario);

 // Obtenermos el ID de usuario:
 $info = $yo->leer();
 echo "<p>El ID de usuario es {$info['usuarioId']}.</p>";

 // Cambiamos el nombre de usuario:
 $yo->actualizar(array('usuario'=>'arthusuxD'));

 // Confirmamos que el usuario ha sido actualizado:
 $info = $yo->leer();
 echo "<p>El nombre de usuario ahora es {$info['usuario']}.</p>";

 // Eliminamos los registros:
 $yo->eliminar();

 // Eliminamos el objeto:
 unset($yo);
?>
</body>
</html>


1.- Declaramos la interface iCrud:
interface iCrud {
        public function crear($datos);
        public function leer();
        public function actualizar($datos);
        public function eliminar();
    }
La interface iCrud identifica cuatro metodos necesarios. Dos de los metodos esperan tomar los datos como argumento. Los otros dos metodos sin argumentos. Usted vera como funciona este sistema en la interface. Todos los metodos requeridos son publicos.

2.-  Definimos la clase Usuario:
class Usuario implements iCrud {

        private $_usuarioId;
        private $_usuario;
La clase Usuario implementa iCrud, significa que debe definir los cuatro metodos identificados en la interface. Para demostrar este concepto, sin abrumar con codigo, voy a definir dos atributos de la clase, ambos seran privados.


3.- Definimos el constructor:

function __construct($datos){
            $this->_usuarioId = uniqid();
            $this->_usuario = $datos['usuario'];
        } // Fin del constructor


El constructor se va tomar un array de datos como su unico argumento. Utilizara estos datos para asignar valores a las variables privadas internas. Logicamente tambien se puede añadir alguna validacion para los datos que se proveen. El constructor tambien crea un valor usuarioId unico invocando la funcion uniqid()
En una aplicacion real, el constructor podria crear un nuevo usuario registrarlo en la base de datos y asignar una clave primaria generando automaticamente el valor al atributo interno.

4.- Definimos el metodo crear():

function crear($datos){
            self::__construct($datos);
        }


Mediante la implementacion de la interface iCrud, esta clase esta obligada a tener un metodo crear() con un solo argumento. Sin embargo, el constructor ya lo hace lo requerido para crear un nuevo objeto de este tipo, entonces este metodo puede llamar al constructor (utilizando la palabra clave self, que se refiere a la clase actual, añadiendo el operador de resolucion), pasamos la cantidad de datos que le provimos. 
Este metodo podria ser usado en situaciones cuando un nuevo objeto Usuario es creado (quizas de este modo creamos un nuevo registro en la base de datos).

5.- Definimos el metodo leer():

function leer(){
            return array('usuarioId' => $this->_usuarioId, 'usuario' => $this->_usuario);
        }


El metodo leer() no toma ningun argumento y devuelve un array de informacion. En un ejemplo hipotetico, la informacion es representada por las variables internas.
En una aplicacion real, el metodo leer() deberia poder asociar la informacion con la base de datos, usando el interno, valor ID que conocemos para devolver.

6.- Definimos el metodo actualizar():

function actualizar($datos){
            $this->_usuario = $datos['usuario'];
        }



Presumiblemente, el Id de usuario no puede ser actualizado, entonces el metodo actualizar() solo escribe en el atributo interno. De nuevo utilizas una validacion apropiada $datos['usuario'] si existe.

7.- Definimos el metodo eliminar() y completamos la clase:

 public function eliminar(){
            $this->_usuario = NULL;
            $this->_usuarioId = NULL;
        }
    } // Terminamos la clase Usuario


El metodo eliminar() limpia los atributos. En el mundo real, se borraria su correspondiente usuario de la base de datos. 

8.- Creamos un nuevo objeto Usuario:

$usuario = array('usuario' => 'arthusu');

    // Imprimimos una pequeña introduccion:
    echo "<h2>Creando un nuevo usuario</h2>";

    // Crear un nuevo usuario:
    $yo = new Usuario($usuario);



Para crear una interface mas util, hay dos metodos que esperan recibir un solo argumento, con lo que podria ser un array de datos. Si la clase Usuario tambien almacena un correo y contraseña, estos pueden ser representados en el array tambien.

9.- Obtenemos el ID de Usuario:

$info = $yo->leer();
echo "<p>El ID de usuario es {$info['usuarioId']}.</p>";

10.- Cambiamos el nombre de usuario y lo leemos:

$yo->actualizar(array('usuario'=>'arthusuxD'));

    // Confirmamos que el usuario ha sido actualizado:
    $info = $yo->leer();
    echo "<p>El nombre de usuario ahora es {$info['usuario']}.</p>";


11.- Eliminamos el registro:

$yo->eliminar();

Nota que esta linea no elimina el objeto Usuario. Esta solo limpia los valores de guardados internamente.

12.- completamos la pagina:

unset($yo);
?>
</body>
</html>



Interface es una palabra con multiples significados. Genericamente interface se refiere a los tipos de informacion sobre las clases y metodos reflejado por un diagrama UML. En otras palabras una interface, explica como una clase o metodo se utiliza. Alternativamente, la interface puede referirse a un contrato que se puede enlazar con una clase. 

Otro beneficio que tiene utilizar interfaces ante las clases abstractas y herencia es que las clases en PHP no se extienden desde multiples padres. Las clases, sin embargo, pueden ser implementadas multiples interfaces separando por coma:

class AlgunaClase implements iA, iB{  

El operador instanceof tambien puede ser utilizado para probar si una clase implementa una interface.



En UML, una interface es indicado por llevar <<interface>> en su nombre (en este caso interface).

En UML, para indicar que una clase implementa una interface, se utiliza una flecha discontinua desde la clase a la interface (de modo que la flecha esta apuntando a la interface).

Traits

Nuevo en PHP 5.4 es el soporte para traits. Traits son usados para resolver problemas en lenguajes de programacion orientado a objetos como PHP que solo permiten herencia simple. Por ejemplo, digamos que estas diseñando un sitio web que tiene varias clases: Usuario, Pagina, Formulario de contacto, etc. Mientras estas desarrollando el sitio web, ayudaria tener una herramienta de depuracion que pueda imprimir la informacion acerca de un objeto dado, independientemente de su tipo.

function arrojarObjeto(){
        // Imprimir la informacion
    }


Se podria añadir esta definicion para cada clase, pero eso seria innecesariamente redundante (y un obstaculo el cual superar en caso de cambiar la definicion). Normalmente, cuando se tiene un metodo que seria necesario en multiples clases, la herencia es la solucion. 
Sin embargo, en PHP cada clase solo se puede heredar desde una sola clase padre, y no hay una clase padre en comun para cada una de ellas. La solucion es, entonces, los traits. Traits le permiten agregar esta funcionalidad para la clase sin usar herencia.

Para crear un trait, usamos la palabra clave trait, seguido por el nombre y la definicion:

    trait tAlgunTrait {
        // Atributos
        function algunaFuncion(){
            // Hacer algo
        }
    }


(Estilisticamente, usted podria iniciar su Trait con "t" minuscula, pero esto no es necesario).

Al igual que una clase abstracta y una interface, los traits no pueden ser instanciados (es decir, no se puede crear un objeto desde un trait). En su lugar tu agregas un Trait a traves de una clase usando la palabra clave use dentro de la definicion de la clase:

    class AlgunaClase {
        use tAlgunTrait;
        // Resto de la clase.
    }


Asi como incluyes un script PHP externo puedes crear un script con codigo usable en el mismo, añadiendo la declaracion use NombredelTrait el codigo estara disponible en la clase.

Ahora, cuando tu creas un objeto de una AlgunaClase escrita, este metodo tiene una metodo algunaFuncion():

    $obj = new AlgunaClase();
    $obj->algunaFuncion();


En el siguiente ejemplo, vamos a implementar la depuracion del ejemplo trait y utilizarlo con alguna clase. Al hacer esto, voy a utilizar tres funciones de PHP no mencionadas anteriormente pero son practicamente autoexplicativas.

 

 <?php
 // Esta pagina define el trait tDebug.

 /* El trait tDebug.
 * El trait define un metodo:
 * - arrojarObjeto():
 */

 trait tDebug {
  // Este metodo arroja todos los datos acerca del objeto actual:

  public function arrojarObjeto(){

   // Obtenemos el nombre de la clase:
   $clase = get_class($this);

   // Obtenemos todos los atributos:
   $atributos = get_object_vars($this);

   // Obtenemos los metodos:
   $metodos = get_class_methods($this);

   // Imprimimos una cabecera:
   echo "<h2>Informacion acerca del objeto $clase</h2>";

   // Imprimimos los atributos:
   echo '<h3>Atributos</h3><ul>';
   foreach($atributos as $c => $v){
    echo "<li>$c : $v</li>";
   }
   echo '</ul>';

   // Imprimimos los metodos:
   echo '<h3>Metodos</h3><ul>';
   foreach($metodos as $c => $v){
    echo "<li>$c : $v</li>";
   }
   echo '</ul>';
  } // Fin del metodo arrojarObjeto().
 } // Fin del trait tDebug.
?>


Clase Rectangulo modificada usando el trait tDebug:



 <?php
 // Esta pagina define el trait tDebug.

 /* El trait tDebug.
 * El trait define un metodo:
 * - arrojarObjeto():
 */

 trait tDebug {
  // Este metodo arroja todos los datos acerca del objeto actual:

  public function arrojarObjeto(){

   // Obtenemos el nombre de la clase:
   $clase = get_class($this);

   // Obtenemos todos los atributos:
   $atributos = get_object_vars($this);

   // Obtenemos los metodos:
   $metodos = get_class_methods($this);

   // Imprimimos una cabecera:
   echo "<h2>Informacion acerca del objeto $clase</h2>";

   // Imprimimos los atributos:
   echo '<h3>Atributos</h3><ul>';
   foreach($atributos as $c => $v){
    echo "<li>$c : $v</li>";
   }
   echo '</li></ul>';

   // Imprimimos los metodos:
   echo '<h3>Metodos</h3><ul>';
   foreach($metodos as $c => $v){
    echo "<li>$c : $v</li>";
   }
   echo '</li></ul>';
  } // Fin del metodo arrojarObjeto().
 } // Fin del trait tDebug.
?>

Usando el trait tDebug:


 <!DOCTYPE html>
<html lang="es">
<head>
 <meta charset="utf-8">
 <title>Trait</title>
</head>
<body>
<?php
 // Esta pagina usa el trait tDebug a traves del objeto Rectangulo.

 // Incluimos la definion del trait:
 require('tDebug.php');

 // Incluimos la definicion de la clase:
 require('Rectangulo.php');

 // Creamos un nuevo objeto:
 $r = new Rectangulo(42,37);

 // Arrojamos la informacion:
 $r->arrojarObjeto();

 // Eliminamos el objeto:
 unset($r);
?>
</body>
</html>
 
 
Esta es la salida generada por el metodo arrojarObjeto().

Si obtiene un error de analisis cuando ejecuta el script, podria ser por que no esta usando la version de PHP 5.4 o una superior o no reconoce la palabra clave trait.

Para incorporar multiples traits en una clase, debemos separar cada trait por una coma:

use tTrait1, tTrait2;

Los traits pueden tener metodos abstractos que luego debe ser implementado por cualquier clase que utilice el trait.

Una vez mas, usted podria añadir tDebug dentro de la clase Rectangulo, en lugar de un script principal para el mismo.

Copiando y clonando objetos

En PHP cuando se crea una copia de un objeto, PHP en realidad crea una nueva referencia a ese objeto, no enteramente un nuevo objeto. En otras palabras, ambas variables apuntaran a la misma cosa, y los cambios realizados a traves de un objeto puede ser reflejado por otro:

    $a = new AlgunaClase();
    $a->val = 1;
    $b = $a;
    $b->val = 2;
    echo $a->val; // 2


Mas formalmente puesto, esto significa que PHP asigna objetos por referencia, no por valor. PHP hace esto por razones de rendimiento, como tener multiples copias de objetos enteros, cuando no es necesario, es caro.

Si usted realmente quiere dos separados, objetos individuales, es necesario crear un clon:

    $a = new AlgunaClase();
    $a->val = 1;
    $b = clone $a; // Objetos separados!
    $b->val = 2;
    echo $a->val; // 1


Cuando el operador clone es usado, PHP llevara a cabo lo que se llama una "superficial copia". Si quieres cambiar la manera en que un objeto clon esta hecho, se puede definir un metodo __clone() dentro de la clase. Este metodo se llama siempre que un clon se hace, y se ocuparia de la clonacion como mejor le parezca. Para mas detalles consultar el manual de PHP.


Precedencia de un trait

Si un trait es utilizado como un metodo en una clase que tiene un metodo con el mismo nombre dentro de la clase, PHP decide que metodo toma precedencia (es decir, cual sera ejecutado cuando el metodo sea llamado). Si el metodo es definido en la clase, esta version tendra precedencia sobre el metodo del trait. Si el metodo definido en la clase es actualmente heredado de otra clase, entonces el trait tomara precedencia.


Type Hinting

Type hinting es en la programacion un acto de indicar que tipo de valor es esperado. Por ejemplo, que tipo de valor una funcion espera recibir como parametro. Type hinting no juega un gran papel en el codigo PHP procedural por que no se puede sugerir tipos simples (ejemplo: enteros o cadenas). Pero usted puede hacer alusion a los objetos, que es mas util. Para llevar a cabo el type hinting, debemos anteponer al nombre de la variable el tipo de clase esperada:

class AlgunaClase {
        function hacerEsto(OtraClase $var){
           
        }
    }



Si el argumento pasado a los metodos hacerEsto() no es de tipo OtraClase, o de una clase subderivada. PHP generara un error Fatal.

    class OtraClase{ }
    $algo = new OtraClase();
    $otra = new OtraClase();
    $algo->hacerEsto($otra);
    $algo->hacerEsto($algo);


Para ver un ejemplo de type hinting, este proximo script definira una clase Departamento con un metodo agregarEmpleado(). Ese metodo se utiliza para añadir un nuevo empleado a la lista de los empleados del departamento, el metodo solo acepta un parametro de tipo Empleado.



 <!DOCTYPE html>
<html lang="es">
<head>
 <meta charset="utf-8">
 <title>Type Hinting</title>
</head>
<body>
<?php
 // Esta pagina define el uso de las clases Departamento y Empleado.

 # *** CLASES *** #
 
 /* Clase Departamento.
 *  Esta clase contiene dos atributos:
 * nombre y empleados[].
 * Esta clase contiene dos metodos:
 * - __construct()
 * - agregarEmpleado()
 */

 class Departamento {
  private $_nombre;
  private $_empleados;

  function __construct($nombre){
   $this->_nombre = $nombre;
   $this->_empleados = array();
  }

  function agregarEmpleado(Empleado $e) {
   $this->_empleados[] = $e;
   echo "<p>{$e->obtenerNombre()} ha sido agregado al departamento {$this->_nombre}.</p>";
  }
 } // Fin de la clase Departamento

 /* Clase Empleado
 * La clase contiene un atributo:
 * nombre
 * La clase contiene dos metodos:
 * - __construct()
 * - obtenerNombre()
 */

 class Empleado {
  private $_nombre;

  function __construct($nombre) {
   $this->_nombre = $nombre;
  } 

  function obtenerNombre(){
   return $this->_nombre;
  }
 } // Fin de la clase Empleado.

 # *** Fin de las clases *** #

 // Creamos un departamento:
 $rh = new Departamento('Recursos Humanos');

 // Creamos empleados:
 $e1 = new Empleado('Jane Doe');
 $e2 = new Empleado('John Doe');

 // Agregamos empleados al departamento:
 $rh->agregarEmpleado($e1);
 $rh->agregarEmpleado($e2);

 // Eliminamos los objetos:
 unset($rh, $e1, $e2);

?>
</body>
</html>

1.- Declaramos la clase Departamento:

class Departamento {
        private $_nombre;
        private $_empleados;

        function __construct($nombre){
            $this->_nombre = $nombre;
            $this->_empleados = array();
        }



La clase Departamento tiene dos atributos privados: uno almacena el nombre del departamento y el otro almacena un array de empleados en el departamento. El constructor asigna el valor a la variable privada $_nombre y crea $_empleados como un array vacio.


2.- Añadimos el metodo AgregarEmpleado() para completar la clase:

function agregarEmpleado(Empleado $e) {
            $this->_empleados[] = $e;
            echo "<p>{$e->obtenerNombre()} ha sido agregado al departamento {$this->_nombre}.</p>";
        }
    } // Fin de la clase Departamento


Este metodo debe ser llamado para agregar un empleado al departamento del objeto actual. Este toma un argumento, que gracias al type hinting, debe ser Empleado. Si un argumento apropiado es recibido, puede ser agregado internamente, al array $_empleados. Para propositos de confirmacion (y para tener que el script finalmente arroje algo), la verificacion del empleado agregado es mostrada.

Nota que $e debe ser un objeto de tipo Empleado, para poder invocar cualquier tipo de metodos de empleados, tal como obtenerNombre().

3.- Definimos la clase Empleado:

class Empleado {
        private $_nombre;

        function __construct($nombre) {
            $this->_nombre = $nombre;
        }

        function obtenerNombre(){
            return $this->_nombre;
        }
    } // Fin de la clase Empleado.


Estamos definiendo una clase Empleado de una manera minima. El constructor toma un nombre de empleado como argumento, y este es asignado a un atributo interno. El metodo obtenerNombre() es publico, y es la forma correcta de buscar el nombre del empleado.

Los errores se arrojan cuando el tipo de argumento no son enviados. Son excepciones lo que significa que pueden ser "atrapados", es una manera mas elegante de manejar este problema.

Type hinting puede ser utilizado en funciones tambien (es decir, en los no metodos, las funciones que estan fuera de una clase).

Namespaces

Agregados en la version de PHP 5.3 tiene soporte para namespaces. Los namespaces proporcionan una solucion para un problema comun en POO: como comenzar a utilizar mas y mas clases, incluidas las definidas por otros desarrolladores y en las bibliotecas de terceros, los conflictos pueden ocurrir si multiples clases tienen el mismo nombre. Los namespaces previenen estos conflictos por lo que permite organizar el codigo en grupos. Esto tiene el efecto para permitir que para utilizar con seguridad nombres descriptivos sin preocupacion por conflictos.

Una analogia adecuada (que el manual de PHP tambien usa) es comparar los namespaces para la creacion de una estructura de directorios en su computadora. No pueden colocar dos archivos llamadas funciones.php en la misma carpeta. Sin embargo, usted puede colocar uno en la carpeta misUtilidades/ y otro en la carpeta tusUtilidades/, con lo que ambas versiones de funciones.php estan disponibles.
Hay limites en lo que se puede colocar en un namespace, en concreto:

* Clases
* Interfaces
* Funciones
* Constantes

No se podria, por ejemplo, crear un namespace solo para mantener algunas variables. Para definir un namespace, querras crear un nuevo archivo que solo almacenara el codigo de namespace. Esto es un tanto virtual requisito de PHP y una mejor practica de diseño. Dentro de ese archivo, se crea un namespace utilizando la palabra clave namespace, seguido por su identificador:

namespace AlgunNamespace;

Tenga en cuenta que esta debe ser la primera linea de un codigo PHP en un archivo, y que el archivo no puede incluso tener algun codigo HTML antes de el codigo (aunque, desde una perspectiva de diseño, usted no quiere mezclar HTML y namespaces de todas maneras). Usted puede tener codigo PHP comentado antes de esa linea. Sin embargo, cualquier codigo que siga de esa linea sera automaticamente colocado dentro de ese namespace:

    namespace AlgunNamespace;

    class AlgunaClase { }


Los namespaces pueden tener subespacios, al igual que usted tendria niveles de directorios en su ordenador. Para ello, indicamos un subespacio de nombre utilizando la barra invertida:

    namespace MisUtilidades\ManejoUsuario;

    class Login { }


Una vez que haya definido un namespace, puede hacer referencia a ella mediante el uso de barra invertida de nuevo. Primero, sin embargo, tienes necesidad de incluir el archivo que define el namespace. 

require('AlgunNameSpace.php');

A continuacion, utilizar barras invertidas para indicar que un namespace se esta utilizando:

$obj = new \AlgunNamespace\AlgunaClase();

O:

require('MisUtilidades\ManejoUsuario\Usuario.php');
$obj = new \MisUtilidades\ManejoUsuario\Login();

Como una opcion estilistica, una sugerencia es utilizar su nombre u organizacion como nivel superior en el namespace:

namespace MiNombre\Utilerias\Usuario;

Otra sugerencia es organizar los archivos de la misma forma que colocas los namespaces.

Como ejemplo practico de esto, vamos a colocar las clases Departamento y Empleado dentro del namespace MiNamespace\Compania.



 <?php
 // Este script define un namespace Compañia, con dos clases.

 // declaramos el namespace:
 namespace MiNamespace\Compania;

  # *** CLASES *** #
 
 /* Clase Departamento.
 *  Esta clase contiene dos atributos:
 * nombre y empleados[].
 * Esta clase contiene dos metodos:
 * - __construct()
 * - agregarEmpleado()
 */

 class Departamento {
  private $_nombre;
  private $_empleados;

  function __construct($nombre){
   $this->_nombre = $nombre;
   $this->_empleados = array();
  }

  function agregarEmpleado(Empleado $e) {
   $this->_empleados[] = $e;
   echo "<p>{$e->obtenerNombre()} ha sido agregado al departamento {$this->_nombre}.</p>";
  }
 } // Fin de la clase Departamento

 /* Clase Empleado
 * La clase contiene un atributo:
 * nombre
 * La clase contiene dos metodos:
 * - __construct()
 * - obtenerNombre()
 */

 class Empleado {
  private $_nombre;

  function __construct($nombre) {
   $this->_nombre = $nombre;
  } 

  function obtenerNombre(){
   return $this->_nombre;
  }
 } // Fin de la clase Empleado.

 # *** Fin de las clases *** #

?>


1.- Declaramos el namespace MiNamespace\Compania:

namespace MiNamespace\Compania;

La premisa aqui es que todo su codigo reusable entraria al namespace Minamespace, Cuyo nombre lo haria mas unico. Dentro de toda esa biblioteca, todas las clases y codigos relacionados con la creacion de proyectos basados en la empresa iria en el namespace de la empresa, declarado aqui.

2.- Definimos la clase Departamento:

class Departamento {
        private $_nombre;
        private $_empleados;

        function __construct($nombre){
            $this->_nombre = $nombre;
            $this->_empleados = array();
        }

        function agregarEmpleado(Empleado $e) {
            $this->_empleados[] = $e;
            echo "<p>{$e->obtenerNombre()} ha sido agregado al departamento {$this->_nombre}.</p>";
        }
    } // Fin de la clase Departamento



Este es el mismo codigo que ya se explico anteriormente en type hinting.

3.- Declaramos la clase Empleado:

class Empleado {
        private $_nombre;

        function __construct($nombre) {
            $this->_nombre = $nombre;
        }

        function obtenerNombre(){
            return $this->_nombre;
        }
    } // Fin de la clase Empleado.


De nuevo, nada cambia aqui.

4.- Guardamos el archivo, y luego lo colocamos en el directorio web dentro de MiNamespace\Compania.

Usar la clase namespace:


 
 <!DOCTYPE html>
<html>
<head>
 <meta charset="utf-8">
 <title>Namespace</title>
</head>
<body>
<?php
 // Esta pagina define el uso de las clases Departamento y Empleado.

 // Incluimos los script PHP:
 require('compania/company.php');

 // Creamos un departamento:
 $rh = new MiNamespace\Compania\Departamento('Contabilidad');

 // Creamos empleados:
 $e1 = new MiNamespace\Compania\Empleado('Holden Caulfield');
 $e2 = new MiNamespace\Compania\Empleado('Jane Gallagher');

 // Agregamos los empleados al departamento:
 $rh->agregarEmpleado($e1);
 $rh->agregarEmpleado($e2);

 // Eliminar los objetos:
 unset($rh, $e1, $e2);
 
?>
</body>
</html>


1.- Incluimos el archivo de namespace:

require('compania/company.php');


2.- Creamos el objeto Departamento:


$rh = new MiNamespace\Compania\Departamento('Contabilidad');


Este es el mismo codigo usado anteriormente, solo que en este caso estamos utilizando namespace.


3.- Creamos dos empleados:

$e1 = new MiNamespace\Compania\Empleado('Holden Caulfield');
$e2 = new MiNamespace\Compania\Empleado('Jane Gallagher');


4.- Agregamos los empleados al departamento:


$rh->agregarEmpleado($e1);
$rh->agregarEmpleado($e2);

5.- Completamos la pagina:


unset($rh, $e1, $e2);



La salida es mas o menos la misma que el script anterior, aunque el codigo subyacente ahora esta mejor organizado gracias a namespaces.


La constante __NAMESPACE__ representa el namespace actual.

Fin de esta parte de POO en PHP.

domingo, 30 de noviembre de 2014

[PHP] E-Commerce Basico

En esta parte de PHP desarrollaremos un comercio electronico (e-commerce) que vende impresiones de arte. Por desgracia, escribir y explicar una aplicacion completa requeriria mas de las partes que se van a realizar, y algunos aspectos e-commerce como la forma de manejar dinero son particulares en cada sitio individual. Con estas limitaciones en mente, el objetivo en este capitulo es sobre la funcionalidad principal de un e-commerce: el diseño de la base de datos, llenar un catalogo como administrador, mostrar productos al publico, creacion de un carrito de compra y almacenamiento de las ordenes en la base de datos. Esto se basa en un ecommerce de impresiones de arte mas que nada diseñado por la base de datos, pero dentro de los articulos sera algo que no tendra nada que ver con ello.

Creando la base de datos

Con cualquier tipo de aplicacion de comercio electronico hay tres tipos de datos que se almacenan: la informacion del producto (lo que se vende), la informacion de los clientes (que esta haciendo compras), y la informacion del pedido (lo que se compro y por quien). Todo esto no lo explicaré, ya que esta mas que explicado en muchas de las partes de PHP aqui en este mismo BLOG.

1.- Iniciamos sesion en MySQL y creamos nuestra base de datos:

mysql> CREATE DATABASE ecommerce;
Query OK, 1 row affected (0.00 sec)

mysql> USE ecommerce;
Database changed
 

En estos casos tu puedes utilizar la linea de comandos para MySQL o simplemente utilizar un script en PHP tal como PHPMyAdmin.

2.- Creamos la tabla artistas:

mysql> CREATE TABLE artistas (
    -> artist_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    -> first_name VARCHAR(20) DEFAULT NULL,
    -> middle_name VARCHAR(20) DEFAULT NULL,
    -> last_name VARCHAR(40) NOT NULL,
    -> PRIMARY KEY (artist_id),
    -> UNIQUE full_name (last_name,first_name,middle_name)
    -> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.09 sec)

 

3.- Creamos la tabla prints:

mysql> CREATE TABLE prints (
    -> print_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    -> artist_id INT UNSIGNED NOT NULL,
    -> print_name VARCHAR(60) NOT NULL,
    -> price DECIMAL(6,2) UNSIGNED NOT NULL,
    -> size VARCHAR(60) DEFAULT NULL,
    -> description VARCHAR(255) DEFAULT NULL,
    -> image_name VARCHAR(60) NOT NULL,
    -> PRIMARY KEY (print_id),
    -> INDEX (artist_id),
    -> INDEX (print_name),
    -> INDEX (price)
    -> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.07 sec)


4.- Creamos la tabla clientes:

mysql> CREATE TABLE clientes (
    -> customer_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    -> email VARCHAR(60) NOT NULL,
    -> pass CHAR(40) NOT NULL,
    -> PRIMARY KEY (customer_id),
    -> UNIQUE (email),
    -> INDEX login (email,pass)
    -> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.07 sec)


5.- Crear la tabla Pedidos:

mysql> CREATE TABLE pedidos (
    -> order_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    -> customer_id INT UNSIGNED NOT NULL,
    -> total DECIMAL(10,2) UNSIGNED NOT NULL,
    -> order_date TIMESTAMP,
    -> PRIMARY KEY (order_id),
    -> INDEX (customer_id),
    -> INDEX (order_date)
    -> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.85 sec)


6.- Creamos la tabla contenido_pedidos:

mysql> CREATE TABLE contenido_pedidos (
    -> oc_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    -> order_id INT UNSIGNED NOT NULL,
    -> print_id INT UNSIGNED NOT NULL,
    -> quantity TINYINT UNSIGNED NOT NULL DEFAULT 1,
    -> price DECIMAL(6,2) UNSIGNED NOT NULL,
    -> ship_date DATETIME DEFAULT NULL,
    -> PRIMARY KEY (oc_id),
    -> INDEX (order_id),
    -> INDEX (print_id),
    -> INDEX (ship_date)
    -> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.79 sec)


Gracias a los indices es mas facil encontrar los datos mas rapidos (el uso de INDEX y PRIMARY KEY son un ejemplo claro). 

DECIMAL(m,n), donde m es el numero maximo de cifras en total (incluyendo la parte decimal) y n es el numero de cifras despues de la coma.  Tomando como ejemplo decimal(6,2):

123456
1234.56
123.456
12345.6
12.3456   

Los diferentes ENGINES del motor de base de datos, estamos utilizando MyISAM e InnoDB, ¿que hay de diferencia? MyISAM es mas rapido pero a diferencia de ello InnoDB permite utilizar transacciones.

Las transacciones son mas usadas para realizar consultas tipo INSERT, UPDATE, DELETE pero no son realizadas hasta que una instruccion COMMIT es ejecutada, esto es utilizado en sistemas ecommerce en caso de que una transaccion salga mal, existe una instruccion ROLLBACK que deshace todas las consultas en caso de error, de esta manera la base de datos no sera modificada.

Para iniciar una transaccion utilizamos:

START TRANSACTION;

Podemos realizar una consulta:

UPDATE cuenta SET dinero='10000000' WHERE id=2;

En caso de que por error ejecutemos esa consulta, y no nos acordamos cual era la cantidad que tenia, o haya ocurrido un error, es tan facil devolver todo a la normalidad, todo a como estaba antes utilizando:

ROLLBACK;

Si en otro caso si queremos que los cambios se guarden usamos:

COMMIT;

Podemos utilizar MySQL para que se ponga en modo de auto asignacion:

SET AUTOCOMMIT=0;

Con esto no es necesario que tu pongas START TRANSACTION y las consultas no seran permanentes hasta que tu pongas COMMIT.

Cuando se crea un Ecommerce siempre es mejor utilizar SSL (Secure Socket Layer) y realizar los pagos desde fuera de tu sitio web, confiandoles a otros sitios como Paypal por ejemplo las transacciones.

La parte administrativa

La parte administrativa de una aplicacion ecommerce, se compone principalmente por tres partes:

* productos (los articulos que se venden)
* clientes
* ordenes  

En este apartado creamos dos scripts para agregar productos al catalogo, como los productos que se venden son obras de arte, un script sera para rellenar la tabla artistas, y el otro para rellenar la tabla prints (que tiene una clave foranea a los artistas)

Debido a las limitaciones (constraints) de espacio, y el hecho de que sin un sistema de pago, este no sera un ejemplo completo de ecommerce de todas maneras. 





El primer script simplemente añade registros a la tabla artistas. El script presenta un formulario con tres entradas. Las entradas se validan y se añade el registro a la base de datos.

mysqli_connect.php

 <?php
 // Este archivo contiene informacion para acceder a la base de datos
 // Este archivo tambien establece una conexion a MySQL
 // selecciona la base de datos, y establece un encoding.

 // Establecemos la iformacion para acceder a la base de datos como constantes:
 define('DB_USER', 'tuusuario');
 define('DB_PASSWORD', 'tupass');
 define('DB_HOST', 'localhost');
 define('DB_NAME', 'ecommerce');

 // Creamos una conexion: 
 $dbc = @mysqli_connect(DB_HOST,DB_USER,DB_PASSWORD,DB_NAME) OR die ('No pudo conectarse a MySQL: '.mysqli_connect_error());

 // Establecemos el encoding...
 mysqli_set_charset($dbc,'utf8');
?>


agregar_artista.php

 <!DOCTYPE html>
<html lang="es">
<head>
 <meta charset="utf-8">
 <title>Agregar un artista</title>
</head>
<body>
<?php
 // Esta pagina permite al administrador agregar un artista
 if($_SERVER['REQUEST_METHOD'] == 'POST'){
  // Manejamos el formulario

  // Validamos el nombre y el nombre en medio (ninguno es requerido).
  $fn = (!empty($_POST['first_name'])) ? trim($_POST['first_name']) : NULL;
  $mn = (!empty($_POST['middle_name'])) ? trim($_POST['middle_name']) : NULL;

  // Verificamos por el apellido...
  if (!empty($_POST['last_name'])) {
   $ln = trim($_POST['last_name']);

   // Agregamos un artista a la base de datos:
   require('./mysqli_connect.php');
   $q = 'INSERT INTO artistas (first_name, middle_name, last_name) VALUES (?,?,?)';
   $stmt = mysqli_prepare($dbc,$q);
   mysqli_stmt_bind_param($stmt,'sss',$fn,$mn,$ln);
   mysqli_stmt_execute($stmt);
   // Checamos los resultados...
   if(mysqli_stmt_affected_rows($stmt) == 1){
    echo '<p>Los artistas han sido agregados.</p>';
    $_POST = array();
   }else{ // Error!
    $error  = 'El artista no pudo ser agregado a la base de datos!.';
   }

   // Cerramos la conexion de esta consulta preparada_
   mysqli_stmt_close($stmt);
   mysqli_close($dbc);
  }else{ // No hay apellido 
   $error = 'Por favor introduzca el nombre del artista!';
  }
 } // Aqui termina el IF de envio

 // Checamos por los errores e imprimimos:
 if(isset($error)){
  echo '<h1>Error!</h1>
  <p style="font-weight: bold; color: #C00">'.$error. ' Por favor intente de nuevo</p>';
 }

 // Mostramos el formulario...
?>
<h1>Agregar un artista</h1>
<form action="" method="post">
 <fieldset><legend>Llena el formulario para agregar un artista:</legend>
 <p><b>Primer nombre:</b><input type="text" name="first_name" size="10" maxlength="20" value="<?php if(isset($_POST['first_name'])) echo $_POST['first_name']; ?>"></p>
 <p><b>Segundo nombre:</b><input type="text" name="middle_name" size="10" maxlength="20" value="<?php if(isset($_POST['middle_name'])) echo $_POST['middle_name']; ?>"></p>
 <p><b>Apellido:</b><input type="text" name="last_name" size="10" maxlength="40" value="<?php if(isset($_POST['last_name'])) echo $_POST['last_name']; ?>"></p>
 </fieldset>
 <div align="center"><input type="submit" name="submit" value="Enviar" /></div>
</form>
</body>
</html>


Aunque aqui no se realiza, seria recomendable utilizar usuarios de MySQL para darles los privilegios que necesitan, por ejemplo: SELECT, INSERT, UPDATE y DELETE. Mientras que un usuario publico solo necesita: SELECT, INSERT y UPDATE.

Las paginas administrativas siempre deben estar lo mas seguras posibles, esto indica alguna autentificacion HTTP usando apache, utilizar cookies o sesiones, etc.

Agregando impresiones




 
El segundo script a escribir es para agregar un nuevo producto (en concreto una impresion) a la base de datos. La pagina permitira al administrador seleccionar al artista de la base de datos, cargar una imagen, y poner los detalles de la impresion. La imagen se almacena en el servidor y el registro de la impresion se almacena en la base de datos. 

agregar_impresion.php

 <!DOCTYPE html>
<html lang="es">
<head>
 <meta charset="utf-8">
 <title>Agregar impresion</title>
</head>
<body>
<?php
 // Esta pagina permite al administrador agregar una impresion (del producto).
 require('./mysqli_connect.php');

 if($_SERVER['REQUEST_METHOD'] == 'POST'){
  // Manejamos el formulario.

  // Validamos los datos entrantes...
  $errors = array();

  // Verificamos por el nombre a imprimir:
  if(!empty($_POST['print_name'])){
   $pn = trim($_POST['print_name']);
  }else{
   $errors[] = 'Por favor introduzca el nombre a imprimir!'; 
  }

  // Verificamos para la imagen:
  if(is_uploaded_file($_FILES['image']['tmp_name'])){
   // Creamos un archivo temporal:
   $temp = 'uploads/'.md5($_FILES['image']['name']);

   //  moveos el archivo
   if (move_uploaded_file($_FILES['image']['tmp_name'], $temp)) {
    echo '<p>El archivo ha sido subido!</p>';

    // Establecemos una variable $i para el nombre de la imagen:
    $i = $_FILES['image']['name'];
   }else{
    // No pudo moverse el archivo.
    $errors[] = 'El archivo no pudo moverse!';
    $temp = $_FILES['image']['tmp_name'];
   }
  }else{
   // El archivo no se subio.
   $errors[] = 'El archivo no pudo subirse!';
   $temp = NULL;
  }
  // Verificamos el tamaño (no es requerido):
  $s = (!empty($_POST['size'])) ? trim($_POST['size']) : NULL;
  // Verficiamos por el precio:
  if(is_numeric($_POST['price']) && ($_POST['price'])>0){
   $p = (float) $_POST['price'];
  }else{
   $errors[] = 'Por favor introduzca el precio a imprimir!';
  }
  // Verificamos por la descripcion (no es requerido):
  $d = (!empty($_POST['description'])) ? trim($_POST['description']) : NULL;
  // Validamos el artista...
  if(isset($_POST['artist']) && filter_var($_POST['artist'],FILTER_VALIDATE_INT,array('min_range'=>1))){
   $a = $_POST['artist'];
  }else{
   // Ninguna artista fue seleccionado
   $errors[] = 'Por favor seleccione un artista a imprimir';
  }

  if(empty($errors)){  // si todo esta bien.
   // Agregamos lo que vamos a imprimir a la base de datos
   $q = 'INSERT INTO prints (artist_id, print_name, price, size, description, image_name) VALUES (?, ?, ?, ?, ?, ?)';
   $stmt = mysqli_prepare($dbc,$q);
   mysqli_stmt_bind_param($stmt,'isdsss', $a, $pn, $p, $s, $d, $i);
   mysqli_stmt_execute($stmt);

  // Verificamos los resultados...
  if(mysqli_stmt_affected_rows($stmt) == 1){
   // Imprimir un mensaje:
   echo '<p>La impresion ha sido agregada.</p>';

   // Renombramos la imagen:
   $id = mysqli_stmt_insert_id($stmt); // Obetenemos el id de la impresion
   rename($temp,"uploads/$id");

   // limpiamos $_POST:
   $_POST = array();
  }else{ // Error!
   echo '<p style="font-weight:bold;color:#C00">Tu envio no pudo ser procesado debido a un error del sistema.</p>';
  }
  mysqli_stmt_close($stmt);
 } // Fin de $errors IF.
 // Eliminamos el archivo subido si existe:
  if(isset($temp) && file_exists($temp) && is_file($temp)){
   unlink($temp);
  }
 } // Fin del IF de envio.

 // Verificamos por cualquier error y los imprimimos:
 if(!empty($errors) && is_array($errors)){
  echo '<h1>Error!</h1>
  <p style="font-weight:bold;color:#C00">Los siguientes errores han ocurrido:<br />';
  foreach($errors as $msg){
   echo " - $msg<br />\n";
  }
  echo 'Por favor reseleccione la imagen a imprimir e intente de nuevo.</p>';
 }
 // Mostramos el formulario...
?>
<h1>Agregar una impresion</h1>
<form enctype="multipart/form-data" action="agregar_impresion.php" method="post">
 <input type="hidden" name="MAX_FILE_SIZE" value="524288" />
 <fieldset><legend>Llena el formulario para agregar una impresion al catalogo:</legend>
 <p><b>Nombre de la impresion:</b> <input type="text" name="print_name" size="30" maxlength="60" value="<?php if(isset($_POST['print_name'])) echo htmlspecialchars($_POST['print_name']); ?>"></p>
 <p><b>Imagen:</b><input type="file" name="image" /></p>
 <p><b>Artista</b>
 <select name="artist">
  <option>Selecciona uno</option>
  <?php
   // Devolvemos todos los artistas y agregamos al menu desplegable
   $q = "SELECT artist_id,CONCAT_WS(' ',first_name,middle_name,last_name) FROM artistas ORDER BY last_name,first_name ASC";
   $r = mysqli_query($dbc,$q);
   if (mysqli_num_rows($r) > 0) {
    while($row = mysqli_fetch_array($r,MYSQLI_NUM)){
     echo "<option value=\"$row[0]\"";
     // Verificamos por pegajosidad:
     if(isset($_POST['existing']) && ($_POST['existing']) == $row[0]) echo 'selected="selected"';
     echo ">$row[1]</option>\n";
    }
   }else{
    echo '<option>Por favor agrega un nuevo artista primero.</option>';
   }
   mysqli_close($dbc); // Cerramos la conexion a la base de datos.
  ?>
 </select>
 </p>
 <p><b>Precio</b><input type="text" name="price" size="10" maxlength="10" value="<?php if(isset($_POST['price'])) echo $_POST['price']; ?>"><small>No incluya el signo dolar o comas.</small></p>
 <p><b>Tamaño:</b><input type="text" name="size" size="30" maxlength="60" value="<?php if(isset($_POST['size'])) echo htmlspecialchars($_POST['size']); ?>"></p>
 <p><b>Descripcion:</b><textarea name="description" cols="40" rows="5"><?php if(isset($_POST['description'])) echo $_POST['description']; ?></textarea></p>
 </fieldset>
 <div align="center"><input type="submit" name="submit" value="Enviar!" /></div>
</form>
</body>
</html>


Creando una plantilla 

Antes de llegar al corazon de la parte publica, se deben crear los archivos de encabezado y pie de pagina HTML necesarios. Vamos a hacer esto rapidamente.

header.html


 <?php
 // Esta pagina crea una sesion, en la pagina HTML.
 session_start(); // Iniciamos una sesion.
?>
<!doctype html>
<html lang="es">
<head>
 <meta charset="utf-8">
 <style type="text/css">
  body{
   text-align: center;
  }
  h1{
   font-size: 15;
   font-family: "Comic Sans MS";
   color:green;
  }
  nav{
   margin-left: auto;
   margin-right: auto;
   width: 400px;
   background-color:black;
   border-radius: 5px;
  }
  nav a{
   text-decoration: none;
   color:white;
  }
  nav a:hover{
   text-decoration: underline;
  }
  nav ul li {
   list-style-type: none;
  }
  nav ul li{
   display: inline;
   padding-left: 5px;
  }
  footer{
   background-color: black;
   color: white;
   border-radius: 5px;
   text-align: center;
  }

 </style>
 <title><?php echo (isset($page_title)) ? $page_title : 'Bienvenido'; ?></title>
</head>
<body>
 <header>
  <h1>Crea una impresion!</h1>
 </header>
  <nav>
   <ul>
    <li><a href="index.php">Inicio</a></li>
    <li><a href="browse_prints.php">Ver impresiones</a></li>
    <li><a href="view_cart.php">Carro de compra</a></li>
   </ul>
  </nav>


footer.html



  <footer>
  <p>Copyright 2014</p>
 </footer>
</body>
</html>


index.php



 <?php
 // Esta es el menu para la pagina web
 
 // Establecemos el titulo del sitio e incluimos el header (cabecera):
 $page_title = 'Crea una impresion';
 include('header.html');
?>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab cumque nam illum dicta! Ut esse accusamus ullam saepe non, at maxime voluptatem ea velit quis nesciunt ab numquam id repellendus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Totam libero aliquid est temporibus. A nemo quisquam quos consectetur cupiditate, iusto error itaque, porro, ad eveniet facilis expedita ipsa incidunt assumenda!</p>
<?php
 include('footer.html');
?>



El catalogo de productos

Para que los clientes puedan comprar productos, necesitaran verlos primero. Para este fin, dos scripts representaran el catalogo productos. El primero browse_prints.php se mostrara una lista de las impresiones disponibles.Si un artista en particular ha sido seleccionado solo ese artista sera mostrado, de lo contrario se incluiran todas las impresiones.

El segundo script, view_print.php, se utiliza para mostrar la informacion de una sola impresion, incluyendo la imagen. En esta pagina los clientes encontraran un enlace Añadir al carrito, por lo que la impresion puede ser añadido a una cesta de compra.



browse_prints.php 


 <?php
 // Esta pagina muestra todas las impresiones (productos) disponibles

 // Establecemos el tituloe incluimos el header HTML:
 $page_title = "Productos disponibles";
 include('header.html');
 require('mysqli_connect.php');
 // Consulta por defecto para esta pagina:
 $q = "SELECT artistas.artist_id, CONCAT_WS(' ',first_name, middle_name, last_name) AS artist, print_name, price, description, print_id FROM artistas, prints WHERE artistas.artist_id = prints.artist_id ORDER BY artistas.last_name ASC, prints.print_name ASC";

 // ¿Estamos ante un artista en particular?
 if(isset($_GET['aid']) && filter_var($_GET['aid'],FILTER_VALIDATE_INT,array('min_range'=>1))){
  // Sobreescribimos la consulta:
  $q = "SELECT artistas.artist_id, CONCAT_WS(' ',first_name, middle_name, last_name) AS artist, print_name, price, description, print_id FROM artistas, prints WHERE artistas.artist_id = prints.artist_id AND prints.artist_id={$_GET['aid']} ORDER BY prints.print_name";

 }
 // Creamos el encabezado de la tabla
 echo '<table border="0" width="90%" cellspacing="3" align="center">
   <tr>
    <td align="left" width="20%"><b>Artista</b></td>
    <td align="left" width="20%"><b>Nombre del producto</b></td>
    <td align="left" width="40%"><b>Descripcion</b></td>
    <td align="right" width="20%"><b>Precio</b></td>
   </tr>';
 // Mostramos todos los productos, linkeados con URL:
 $r = mysqli_query($dbc,$q);
 while($row = mysqli_fetch_array($r,MYSQLI_ASSOC)){
  // Mostramos cada registro
  echo "\t<tr>
     <td align=\"left\"><a href=\"browse_prints.php?aid={$row['artist_id']}\">{$row['artist']}</a></td>
     <td align=\"left\"><a href=\"view_print.php?pid={$row['print_id']}\">{$row['print_name']}</a></td>
     <td align=\"left\">{$row['description']}</a></td>
     <td align=\"right\">{$row['price']}</a></td>
    </tr>\n";
 } // Terminamos el bucle while
 echo '</table>';
 mysqli_close($dbc);
 include('footer.html');
?>

Lo unico que hay que explicar aqui es la consulta SQL, donde utilizamos un JOIN utilizando una coma:

mysql> SELECT artistas.artist_id, CONCAT_WS(' ',first_name, middle_name, last_na
me) AS artist, print_name, price, description, print_id FROM artistas, prints WH
ERE artistas.artist_id = prints.artist_id ORDER BY artistas.last_name ASC, print
s.print_name ASC;
+-----------+----------------------+------------+--------+-------------------+----------+
| artist_id | artist               | print_name | price  | description       | print_id |
+-----------+----------------------+------------+--------+-------------------+----------+
|         1 | arthusu xd arthusuxd | Test       |  40.00 | This is a test    |  2       |
|         1 | arthusu xd arthusuxd | Watching   | 300.00 | i'm watching you! |  1       |
+-----------+----------------------+------------+--------+-------------------+----------+
2 rows in set (0.00 sec)


Y

mysql> SELECT artistas.artist_id, CONCAT_WS(' ',first_name, middle_name, last_na
me) AS artist, print_name, price, description, print_id FROM artistas JOIN prints WH
ERE artistas.artist_id = prints.artist_id ORDER BY artistas.last_name ASC, print
s.print_name ASC;
+-----------+----------------------+------------+--------+-------------------+----------+
| artist_id | artist               | print_name | price  | description       | print_id |
+-----------+----------------------+------------+--------+-------------------+----------+
|         1 | arthusu xd arthusuxd | Test       |  40.00 | This is a test    |  2       |
|         1 | arthusu xd arthusuxd | Watching   | 300.00 | i'm watching you! |  1       |
+-----------+----------------------+------------+--------+-------------------+----------+
2 rows in set (0.00 sec)

 


Devuelven el mismo resultado.

Para saber mas sobre JOINS puedes ver: http://arthusu.blogspot.mx/2013/03/mysql-minitutorial-parte-3.html

Otra accion que realiza este script es que si estamos en un artista en particular, muestra solo las impresiones (productos) de ese artista:



view_print.php

 <?php
 // Esta pagina muestra los detalles para un producto en particular
 
 $row = FALSE; // Asume que no hay nada!

 if(isset($_GET['pid']) && filter_var($_GET['pid'],FILTER_VALIDATE_INT,array('min_range'=>1))){
  // Estamos seguros de que hay un ID de impresion
  $pid = $_GET['pid'];

  // Obtenemos la informacion del producto:
  require('mysqli_connect.php');
  $q = "SELECT CONCAT_WS(' ',first_name,middle_name,last_name) AS artist, print_name, price, description, size, image_name FROM artistas, prints WHERE artistas.artist_id = prints.artist_id AND prints.print_id=$pid";
  $r = mysqli_query($dbc,$q);
  if(mysqli_num_rows($r) == 1){
   // Bueno vamos a ello!

   // Devolvemos toda la informacion:
   $row = mysqli_fetch_array($r,MYSQLI_ASSOC);

   // Comenzamos la pagina html:
   $page_title = $row['print_name'];
   include('header.html');

   // Mostramos la cabecera:
   echo "<div align=\"center\"><b>{$row['print_name']}</b> by {$row['artist']}<br />";

   // Imprimimos el tamaño o un mensaje predeterminado:
   echo (is_null($row['size'])) ? '(No hay informacion disponible sobre el tamaño)' : $row['size'];

   echo "<br />\${$row['price']}
   <a href=\"add_cart.php?pid=$pid\">Agregar a la cesta</a>
   </div><br />
   ";

   // Obtenems informacion de la imagen y la imprimimos:
   if($image = @getimagesize("uploads/$pid")){
    echo "<div align=\"center\"><img src=\"show_image.php?image=$pid&name=".urlencode($row['image_name'])."\" $image[3] alt=\"{$row['print_name']}\" /></div>\n";
   }else{
    echo "<div align=\"center\">La imagen no esta disponible</div>\n";
   }

   // Agregamos una descripcion o un mensaje por defecto:
   echo '<p align="center">'. ((is_null($row['description'])) ? '(No hay ninguna descripcion disponible)' : $row['description']) . '</p>'; 
  } // Terminamos el condicional de  mysqli_num_rows()

  mysqli_close($dbc);
 } // Fin del condicional $_GET['pid']

 if(!$row){ // Mostramos un mensaje de error.
  $page_title = 'Error';
  include('header.html');
  echo '<div align="center">Esta pagina ha sido provocado un error!</div>';
 }

 // Completamos la pagina:
 include('footer.html');
?>


En esta pagina hacemos una peticion para mostrar todos los datos que queremos de un solo producto:


En caso de que en la url, entren si que exista un solo articulo esto producira un error:



Si te das cuenta al leer el script view_print.php no se encuentra el enlace a la imagen que seria show_image.php, este será otro script con el cual mostraremos la imagen de nuestro producto.

Tambien se podria agregar un apartado para saber si el producto en particular se encuentra en STOCK y puede ser comprado, de otra manera mostrar un mensaje por defecto...


show_image.php



 <?php
 // Esta pagina devuelve y muestra una imagen

 // Variables banderas:
 $image = FALSE;
 $name = (!empty($_GET['name'])) ? $_GET['name'] : 'Imprimir imagen';

 // Verificamos por una imagen el valor en la URL:
 if(isset($_GET['image']) && filter_var($_GET['image'],FILTER_VALIDATE_INT,array('min_range'=>1))){
  // Ruta completa de la imagen:
  $image = 'uploads/'.$_GET['image'];

  // Verificamos que la imagen exista y que es un archivo:
  if(!file_exists($image) || (!is_file($image))){
   $image = FALSE;
  }
 } // Fin de el condicional $_GET['image']

 // Si ocurre un problema, usamos imagenes por defecto:
 if(!$image){
  $image = 'img_nodisponible.gif';
  $name = 'img_nodisponible.gif';
 }

 // Obtenemos la informacion sobre la imagen:
 $info = getimagesize($image);
 $fs = filesize($image);

 // Enviamos la informacion al navegador:
 header("Content-Type: {$info['mime']}\n");
 header("Content-Disposition: inline; filename=\"$name\"\n");
 header("Content-Length: $fs\n");

 // Enviamos el archivo:
 readfile($image);
?>

Con esto nos mostraria la imagen de la impresion:





En caso de que la imagen no se encuentre, mostrara una imagen por defecto para las imagenes que no existan:



Al momento de tu bajar la imagen va tener el nombre que le des en la url.


Si deseará buscar entre los productos, la estructura de la base  de datos crea una busqueda bastante facil. Como esta actualmente solo hay tres campos por buscar Nombre del producto, Descripcion, Apellido. Para ello podriamos utilizar una consulta con la siguiente sintaxis:

SELECT ... WHERE prints.description LIKE %palabraclave% OR prints.print_name LIKE %palabraclave%...

Se podria crear tambien una busqueda avanzada similar a IMDB


La cesta de compras

Una vez que haya creado el catalogo de productos, como los scripts anteriores, el script de cesta de la compra es realmente simple. El metodo elegido para este script es utilizar ID de producto, nombre, precio y cantidad en una sesion. Estos dos ejemplos siguientes proporcionaran toda la funcionalidad necesaria para la cesta de compra. El primer script, add_cart.php, se utilizara para agregar elementos a la cesta de compra. El segundo script, view_cart.php, tanto muestra el contenido de la cesta y permite al cliente paa actualizar la compra.


add_cart.php


 <?php
 // Esta pagina agrega las impresiones (productos) a la cesta de compras

 // Establecemos el titulo de la pagina e incluimos la cabecera:
 $page_title = 'Agregar a la cesta';
 include('header.html');

 if(isset($_GET['pid']) && filter_var($_GET['pid'],FILTER_VALIDATE_INT,array('min_range'=>1)) ){
  // Verificamos el id a imprimir.
  $pid = $_GET['pid'];

  // Verificamos si ya existe en la cesta este producto;
  // Si es asi incrementamos la cantidad:

  if(isset($_SESSION['cart'][$pid])){
   $_SESSION['cart'][$pid]['quantity']++; // Agregamos otra
   // Mostramos un mensaje:
   echo '<p>Otra copia de este producto ha sido agregado a tu cesta de compra.</p>';
  }else{
   // Un nuevo producto se agregara a la cesta.
   // Obtenemos el precio de la base de datos:
   require('mysqli_connect.php');
   // Conectamos a la base de datos.
   $q = "SELECT price FROM prints WHERE print_id=$pid";
   $r = mysqli_query($dbc,$q);
   if(mysqli_num_rows($r) == 1){
    // ID de Producto valido

    // Devolvemos la informacion.
    list($price) = mysqli_fetch_array($r,MYSQLI_NUM);

    // Agregamos a la cesta:
    $_SESSION['cart'][$pid] = array('quantity'=>1,'price'=>$price);

    // Mostramos un mensaje:
    echo '<p>El producto ha sido agregado a tu cesta de compra.</p>';
   }else{
    // ID de Producto no valido
    echo '<div align="center">Esta pagina no pudo ser accedida por un error!</div>';
   }
   mysqli_close($dbc);
  } // Fin del condicional isset($_SESSION['cart'][$pid])
 }else{
  // No se puede imprimir el ID del producto
  echo '<div align="center">Esta pagina no pudo ser accedida por un error!</div>; ';
 }

 include('footer.html');
?>

La cosa mas importante a almacenar en la cesta de compras es el ID del producto unico y la cantidad (quantity) de ese articulo. Todo lo demas incluyendo, el precio, se puede recuperar de la base de datos. 

El carro de compras se almacena en $_SESSION['cart'], no solo $_SESSION. Es de suponer que otros datos, como el ID del usuario de la base de datos, tambien sera almacenada en $_SESSION.


Ver el carrito de compra

E script view_cart.php sera mas complicado que add_cart.php por que tiene dos propositos. En primer lugar, se mostrara el contenido de la cesta en detalle. En segundo lugar, se le dara al cliente la posibilidad de actualizar el carro cambiando las cantidades de los productos en la misma (o eliminar un elemento haciendo su cantidad 0). Para cumplir ambos roles, el contenido de la compra se mostrara en un formulario que consigue enviarse de nuevo a esta misma pagina. Por ultimo esta pagina, se vinculara a un script checkout.php que pretende ser el primer paso en el proceso de pago.

view_cart.php


 <?php
 // Esta pagina muestra el contenido de la cesta de compras.
 // Esta pagina tambien deja al usuario actualizar el contenido de la cesta de compras.

 // Establecemos el titulo e incluimos la cabecera:
 
 $page_title = 'Ver tu carrito de compras';
 include('header.html');

 // Verificamos si el formulario ha sido enviado (para actualizar la cesta):
 if($_SERVER['REQUEST_METHOD'] == 'POST'){
  // Cambiamos cualquier cantidad:
  foreach($_POST['qty'] as $k => $v){
   // deben ser enteros!
   $pid = (int) $k;
   $qty = (int) $v;

   if($qty == 0){
    // Eliminamos.
    unset($_SESSION['cart'][$pid]);
   }elseif($qty > 0 ){
    // Cambiamos la cantidad
    $_SESSION['cart'][$pid]['quantity'] = $qty;
   }
  } // Fin del foreach
 } // Fin del condicional de envio

 // Mostramos la cesta y verificamos que no este vacia...
 if(!empty($_SESSION['cart'])){
  // Devolvemos toda la informacion de los productos en la cesta:
  require('mysqli_connect.php');
  $q = "SELECT print_id, CONCAT_WS(' ',first_name,middle_name,last_name) AS artist,print_name FROM artistas,prints WHERE artistas.artist_id = prints.artist_id AND prints.print_id IN (";
  foreach($_SESSION['cart'] as $pid => $value){
   $q .= $pid . ',';
  }
  $q = substr($q, 0,-1) . ') ORDER BY artistas.last_name ASC';
  $r = mysqli_query($dbc,$q);

  // Creamos un formulario y una tabla:
  echo '<form action="view_cart.php" method="post">
  <table border="0" width="90%" cellspacing="3" cellpadding="3" align="center">
  <tr>
   <td align="left" width="30%"><b>Artista</b></td>
   <td align="left" width="30%"><b>Producto</b></td>
   <td align="right" width="30%"><b>Precio</b></td>
   <td align="center" width="30%"><b>Qty</b></td>
   <td align="right" width="30%"><b>Precio Total</b></td>
  </tr>
  ';

  // Imprimimos cada elemento...
  $total = 0; // Costo total de la orden.
  while($row = mysqli_fetch_array($r,MYSQLI_ASSOC)){
   // calculamos el total y subtotal.
   $subtotal = $_SESSION['cart'][$row['print_id']]['quantity'] * $_SESSION['cart'][$row['print_id']]['price'];
   $total += $subtotal;

   // Imprimimos la fila:
   echo "\t<tr>
   <td align=\"left\">{$row['artist']}</td>
   <td align=\"left\">{$row['print_name']}</td>
   <td align=\"right\">\${$_SESSION['cart'][$row['print_id']]['price']}</td>
   <td align=\"center\"><input type=\"text\" size=\"3\" name=\"qty[{$row['print_id']}]\" value=\"{$_SESSION['cart'][$row['print_id']]['quantity']}\" /></td>
   <td align=\"right\">$".number_format($subtotal,2)."</td>";
  } // Fin del bucle while
  mysqli_close($dbc); // Cerramos la conexion a la base de datos

  // Imprimimos el total, cerramos la tabla, y el formulario:
  echo '<tr>
   <td colspan="4" align="right">
   <b>Total</b></td>
   <td align="right">$'.number_format($total,2).'</td>
   </tr>
   </table>
   <div align="center"><input type="submit" name="submit" value="Actualizar mi Cesta" /></div></form>
   <p align="center">Introduzca la cantidad 0 para remover un elemento.<br /><br />
   <a href="checkout.php">Pagar la cuenta</a></p>';
 }else{
  echo '<p>Tu cesta esta actualmente vacia.</p>';
 }

 include('footer.html');
?>

Un aspecto seguro de una aplicacion de comercio electronico (ecommerce) es como esta enviando y utilizando los datos. Por ejemplo, seria mucho menos seguro colocar el precio del producto por la URL, donde facilmente podria ser cambiado.

Los cambios realizados en las cantidades (Qty) puedes actualizar el carrito de compra, cambiando el subtotal y total.


Para eliminar los elementos de la cesta basta con establecer todos en 0.


Almacenar las ordenes

Despues de mostrar todos los productos en el catalogo, y despues de que el usuario ha llenado el carrito de compras, hay tres pasos finales:

* Comprobacion del usuario
* Almacenar la orden en la base de datos
* El cumplimiento de la orden

Ironicamente, el parcial mas importante (tomar el dinero del cliente), no se podria mostrar adecuadamente en este ejemplo basico ya que necesita de mas partes y es individual para cada sitio.
En este caso vamos a almacenar la informacion del pedido e introducirlos correctamente en la tabla contenido_pedidos utilizando transacciones.

checkout.php



 <?php
 // Esta pagina inserta la orden y la informacion dentro de una tabla.
 // Esta pagina puede venir despues de rellenar toda la informacion del cliente.
 // Esta pagina asume que ya has rellenado la informacion del cliente (y el dinero ha sido tomado).
 
 // Establecemos el titulo de la pagina e incluimos la cabecera:
 $page_title = "Confirmacion de Orden";
 include('header.html');

 // Asumimos que el cliente esta con sesion iniciada y que tiene su ID (identificador):

 $cid = 1; // Temporalmente.

 // Asumimos que esta pagina recibe la orden total:
 $total = 178.93; // Temporalmente.

 require('mysqli_connect.php');  // Conectamos a la base de datos

 // Ponemos OFF autocommit
 mysqli_autocommit($dbc,FALSE);

 // Agregamos una orden en la tabla orders:
 $q = "INSERT INTO pedidos (customer_id,total) VALUES ($cid,$total)";
 $r = mysqli_query($dbc,$q);
 if(mysqli_affected_rows($dbc) == 1){
  // Necesitamos el ID del pedido:
  $oid = mysqli_insert_id($dbc);

  // Insertamos el pedido especifico dentro de la base de datos...

  // Preparamos una consulta:
  $q = "INSERT INTO contenido_pedidos (order_id, print_id, quantity, price) VALUES (?,?,?,?)";
  $stmt = mysqli_prepare($dbc,$q);
  mysqli_stmt_bind_param($stmt,'iiid',$oid,$pid,$qty,$price);

  // Ejecutamos cada consulta; contando el total de afectadas:
  $affected = 0;
  foreach($_SESSION['cart'] as $pid => $item){
   $qty = $item['quantity'];
   $price = $item['price'];
   mysqli_stmt_execute($stmt);
   $affected += mysqli_stmt_affected_rows($stmt);
  }

  // Cerramos la consulta preparada:
  mysqli_stmt_close($stmt);

  // Reportamos lo ocurrido...
  if($affected == count($_SESSION['cart'])){ // Whohoo!
   // Realizamos la transaccion:
   mysqli_commit($dbc);
   // Limpiamos la cesta:
   unset($_SESSION['cart']);
   // Dejamos un mensaje al cliente:
   echo '<p>Gracias por su pedido. Seras notificado cuando se haya comprado el producto.</p>';

   // Enviamos un email de lo que sea:

  }else{ // Rollback y reportamos el problema:
   mysqli_rollback($dbc);
   echo '<p>Tu orden no pudo ser procesada por un error en el sistema. Tu debes ponerte en contacto para solucionar el problema. Pedimos disculpas por cualquier inconveniencia.</p>';
   // Enviamos la informacion del pedido para el administrador.
  }
 }else{ // Rollback y reportamos el problema.
  mysqli_rollback($dbc);
  echo '<p>Tu orden no pudo ser procesada por un error en el sistema. Tu debes ponerte en contacto para solucionar el problema. Pedimos disculpas por cualquier inconveniencia.</p>';
  // Enviamos la informacion del pedido para el administrador.
 }

 mysqli_close($dbc);

 include('footer.html');

?>

En un sitio trabajando realmente, usted debe asignar las variables $cliente y $total para trabajar con este script. Tambien deberia asegurarse de que la cesta no esta vacia antes de realizar la orden.





La orden puede ser vista desde MySQL.


Esta aqui termina este minitutorial basico para un sitio ecommerce, lo visto aqui no es algo "completo" sino algo para saber como funciona un sitio de comercio electronico, tambien nos hizo falta estructurar adecuadamente los archivos en sus carpetas, agregar HTTPS, agregar una compra en algun otro sitio seguro de pago, y muchas cosas mas, pero espero depues de haber realizado esto tengan un conocimiento basico de como es que trabaja un sitio de comercio electronico.




Puedes descagar todo completo desde Mediafire