Crear un framework en PHP5 desde cero (2ª Parte)

guru logo framework suenyos.com

 

Ya vimos en la primera parte, que necesitamos y como se ha de configurar nuestro servidor para hacer funcionar nuestro propio framework. Ahora vamos a avanzar un poquito más en la creación de nuestro propio framework, que como mínimo nos ha de servir para entender más y mejor como funcionan los frameworks existentes en el mercado y familiarizarnos con la arquitectura MVC.

Controlador de Base de Datos

En los tiempos que corren, una página web estática está más que fuera de lugar y más todavía con la cantidad de CMS y Frameworks disponibles en el mercado libres y gratuitos que con apenas algo de experiencia se puede instalar y configurar sin esfuerzo alguno. Además, si contamos lo sencillo que puede resultar crear una web enlazada a datos con PHP y MySQL que por ejemplo sea capaz de mostrar y administrar datos de productos, estamos si seguimos teniendo una web estática en la Edad de Piedra con respecto a nuestro competidores directos. Así que evidentemente nuestro framework tendrá, como no, un controlador de base de datos que nos ayudará en la gestión de los mismos. Pero ¿qué debe hacer nuestro controlador de base de datos?

  • Administrar las conexiones a la base de datos
  • Intentar proporcionar un cierto nivel de abstracción a nuestra base de datos (para que podamos reutilizar código independientemente de la tabla a la que ataquemos)
  • Guardar las consultas en caché para reutilizarlas de nuevo
  • Realizar operaciones en la base de datos de manera mucho más sencilla

He aquí nuestro controlador del oráculo:

<?php

/**
 * Manipulación de base de datos y acceso
 * Nivel básico de abstracción
 * Database.Class.php ver. 0.1
 *
 * GURU FRAMEWORK Suenyos.COM
 * @autor Alex Romero
 * @basedOnWork Michael Peacock
 */
class database {

	/**
	 * Permite múltiples conexiones a la base de datos
	 * Es posible que nunca se utilice, pero por ofrecer la opción
	 */
	private $connections = array();

	/**
	 * Conexión activa a la base de datos
	 * Permite cambiar la conexión activa: setActiveConnection($id)
	 */
	private $activeConnection = 0;

	/**
	 * Consultas que se han ejecutado y que se guardan
	 * en caché para volver a ejecutarlas
	 * si fuese necesario
	 */
	private $queryCache = array();

	/**
	 * Los datos que se han recuperado, también se cachean para
	 * su posterior uso
	 */
	private $dataCache = array();

	/**
	 * Registro de la última consulta
	 */
	private $last;

	/**
	 * Constructor de la clase
	 */
    public function __construct()
    {

    }

    /**
     * Crea una nueva conexión a base de datos
     * @param String SERVIDOR
     * @param String USUARIO
     * @param String CONTRASEÑA
     * @param String BASE DE DATOS
     * @return int El identificador de la conexión
     */
    public function newConnection( $host, $user, $password, $database )
    {
    	$this->connections[] = new mysqli( $host, $user, $password, $database );
    	$connection_id = count( $this->connections )-1;
    	if( mysqli_connect_errno() )
    	{
    		trigger_error('Error al conectar con el servidor. '.$this->connections[$connection_id]->error, E_USER_ERROR);
		} 	

    	return $connection_id;
    }

    /**
     * Cierra la conexión activa
     * @return void
     */
    public function closeConnection()
    {
    	$this->connections[$this->activeConnection]->close();
    }

    /**
     * Cambia la conexión activa
     * @param int El identificador de la nueva conexión
     * @return void
     */
    public function setActiveConnection( int $new )
    {
    	$this->activeConnection = $new;
    }

    /**
     * Almacena una consulta en caché para su posterior uso
     * @param String the query string
     * @return the pointed to the query in the cache
     */
    public function cacheQuery( $queryStr )
    {
    	if( !$result = $this->connections[$this->activeConnection]->query( $queryStr ) )
    	{
		    trigger_error('Error al ejecutar y cachear a la consulta: '.$this->connections[$this->activeConnection]->error, E_USER_ERROR);
		    return -1;
		}
		else
		{
			$this->queryCache[] = $result;
			return count($this->queryCache)-1;
		}
    }

    /**
     * Obtiene el número de filas de la caché
     * @param int El puntero de la consulta en caché
     * @return int the number of rows
     */
    public function numRowsFromCache( $cache_id )
    {
    	return $this->queryCache[$cache_id]->num_rows;
    }

    /**
     * Recibe las filas de una consulta en la caché
     * @param int El puntero de la consulta en caché
     * @return array the row
     */
    public function resultsFromCache( $cache_id )
    {
    	return $this->queryCache[$cache_id]->fetch_array(MYSQLI_ASSOC);
    }

    /**
     * Guardar los datos en caché para su posterior uso
     * @param array Los datos
     * @return int El total de registros almacenados en la caché
     */
    public function cacheData( $data )
    {
    	$this->dataCache[] = $data;
    	return count( $this->dataCache )-1;
    }

    /**
     * Obtiene datos de la caché
     * @param int índice de la caché
     * @return array Los datos
     */
    public function dataFromCache( $cache_id )
    {
    	return $this->dataCache[$cache_id];
    }

    /**
     * Elimina un registro en la base de datos
     * @param String La tabla de la que se desea eliminar un registro
     * @param String La condición que se ha de cumplir para ser eliminado el registro
     * @param int El número de registros que se eliminarán
     * @return void
     */
    public function deleteRecords( $table, $condition, $limit )
    {
    	$limit = ( $limit == '' ) ? '' : ' LIMIT ' . $limit;
    	$delete = "DELETE FROM {$table} WHERE {$condition} {$limit}";
    	$this->executeQuery( $delete );
    }

    /**
     * Actualizar datos
     * @param String La tabla
     * @param array de cambios campo => valor
     * @param String La condición que se ha de cumplir para ser actualizado
     * @return bool
     */
    public function updateRecords( $table, $changes, $condition )
    {
    	$update = "UPDATE " . $table . " SET ";
    	foreach( $changes as $field => $value )
    	{
    		$update .= "`" . $field . "`='{$value}',";
    	}

    	// remove our trailing ,
    	$update = substr($update, 0, -1);
    	if( $condition != '' )
    	{
    		$update .= "WHERE " . $condition;
    	}

    	$this->executeQuery( $update );

    	return true;

    }

    /**
     * Inserta registros
     * @param String La tabla
     * @param array datos para insertar campo=> valor
     * @return bool
     */
    public function insertRecords( $table, $data )
    {
    	// Configuración de variables para campo y valor
    	$fields  = "";
		$values = "";

		// Rellena las variables con los campos y sus valores
		foreach ($data as $f => $v)
		{

			$fields  .= "`$f`,";
			$values .= ( is_numeric( $v ) && ( intval( $v ) == $v ) ) ? $v."," : "'$v',";

		}

		// Quitamos la coma del final
    	$fields = substr($fields, 0, -1);
    	// Quitamos la coma del final
    	$values = substr($values, 0, -1);

		$insert = "INSERT INTO $table ({$fields}) VALUES({$values})";
		$this->executeQuery( $insert );
		return true;
    }

    /**
     * Ejecuta una consulta
     * @param String La consulta
     * @return void
     */
    public function executeQuery( $queryStr )
    {
    	if( !$result = $this->connections[$this->activeConnection]->query( $queryStr ) )
    	{
		    trigger_error('Error al ejecutar la consulta: '.$this->connections[$this->activeConnection]->error, E_USER_ERROR);
		}
		else
		{
			$this->last = $result;
		}

    }

    /**
     * Obtener las filas de la consulta ejecutada más recientemente, con exclusión de las consultas cacheadas
     * @return array
     */
    public function getRows()
    {
    	return $this->last->fetch_array(MYSQLI_ASSOC);
    }

    /**
     * Obtiene el número de las filas afectadas en la última consulta realizada
     * @return int the number of affected rows
     */
    public function affectedRows()
    {
    	return $this->$this->connections[$this->activeConnection]->affected_rows;
    }

    /**
     * Desinfecta los datos
     * @param String Datos a desinfectar
     * @return String Los datos desinfectados
     */
    public function sanitizeData( $data )
    {
    	return $this->connections[$this->activeConnection]->real_escape_string( $data );
    }

    /**
     * Destruir el objeto
     * Cierra todas las conexiones a la base de datos
     */
    public function __deconstruct()
    {
    	foreach( $this->connections as $connection )
    	{
    		$connection->close();
    	}
    }
}
?>

Es un controlador de base de datos muy básico, pero para nuestro ejemplo va mucho mejor que bien. El código está completamente comentado y dudo que surjan demasiadas dudas. El nivel de abstracción es algo bajo, podríamos incluso subir la abstracción de nuestra clase para obtener resultados mucho más profesionales e incluso funcionales, pero como decía, para el ejemplo en cuestión nos sobra y nos basta. Nos deja borrar, insertar y actualizar registros en una base de datos. Podemos modificar o incluso añadir nuevas funciones a nuestra clase de base de datos. Además las consultas se almacenan en caché, por lo que podemos utilizar dichos resultados y valores más tarde, optimizando el tiempo de ejecución. Si queréis podemos ver un ejemplo de como podríamos utilizar esta clase:

// Insertar Registro
$registry->getObject('db')->insertRecords( 'TablaEjemplo', array('name'=>'Alex' ) );
// Actualizar Registro
$registry->getObject('db')->updateRecords( 'TablaEjemplo', array('name'=>'Alex Romero' ), 'ID=3' );
// Eliminar 5 registros (si se da el caso)
$registry->getObject('db')->deleteRecords( 'TablaEjemplo', "name='Alex Romero'", 5 );

También podemos trabajar con diferentes conexiones a base de datos, ¿queréis un ejemplo? Ahí va:

    // Segunda Conexión a Base de datos (suponiendo que ya tenemos una abierta)
    $newConnection = $registry->getObject('db')->newConnection('localhost', 'root', 'pass', 'segundaBD');
    // Eliminar utilizando la primera conexión
    $registry->getObject('db')->deleteRecords( 'TablaEjemplo', "name='Alex Romero'", 5 );
    // Cambiamos la conexión activa a la segunda conexión
    $registry->getObject('db')->setActiveConnection( $newConnection );
    // Como hemos cambiado la conexión activa ahora eliminamos en la segunda conexión creada
    $registry->getObject('db')->deleteRecords( 'TablaEjemplo', "name='Alex Romero'", 5 );
    // Volvemos a establecer la primera conexión como activa
    $registry->getObject('db')->setActiveConnection( 0 );
    // Si ahora realizáramos algún ataque a la BD terminaría atacando a la primera conexión

¿Os seguís preguntando cómo extender esta clase? Pues bien, tenéis que tener en cuenta que es una clase muy simple y habría que considerar:

  1. Abstracción absoluta.
  2. Hacer uso de la herencia, crear una interfaz y que el resto de clases de base de datos hereden de ella para los diferentes tipos de bases de datos.
  3. Almacenar el identificador de la conexión junto con la consulta cuando la cacheamos
  4. Ampliar la desinfección de datos para saber el tipo de datos que se desea y la longitud máxima y mínima del mismo o longitud fija para evitar errores en los tipos de datos.

Más adelante mostraremos más características sobre la clase de base de datos, creando una capa mucho más abstracta que incluso nos librará de las cadenas del proveedor de base de datos en el servidor, así si realizamos una migración, podremos seguir trabajando tranquilamente, con tan solo cambiar un parámetro indicando si el servidor es MySQL o SQL Server, por ejemplo. Además realizaremos algunos cambios más en la clase creada hasta ahora como control de base de datos para que sea mucho más útil y genérica (abstracta). Ahora vamos a dejar las bases de datos durante un rato porque poco podemos hacer si no podemos mostrar los datos por pantalla, así que vamos a ver el control de plantillas.

Administrador de plantillas

El administrador de plantillas será el encargado de coger un fichero en HTML e inyectarle los datos pertinentes allá donde existan marcas o en nuestro caso, ya que el framework se llama Gurú, estigmas. Como por ejemplo {title}, que cuando nuestro administrador de plantillas se encuentre con este estigma lo cambiará por el título de la página en cuestión. Ha de poder trabajar con diferentes plantillas a la vez y reemplazar tantos estigmas como sea necesario. Para ello nos crearemos una clase de página para controlar este tipo de eventos. Pero basta de cháchara y vamos al lío.

 

<?php

/**
 * Guru
 * Framework CLASS TEMPLATE
 *
 * @version 0.1
 * @autor Alex Romero
 * @company Suneyos.Com
 * @basedOnWork Michael Peacock
 */

// Evitamos que se llame directamente desde otra ubicación ajena al framework
if ( ! defined( 'GURUFW' ) ) 
{
	echo 'Solo se puede llamar desde el propio framework (index.php)';
	exit();
}

/**
 * Clase administradora de plantillas
 */
class template {

	private $page;

	/**
	 * Constructor
	 */
    public function __construct() 
    {
	    include( APP_PATH . '/core/objects/page.class.php');
	    $this->page = new Page();

    }

    /**
     * Añade un bloque de contenidos en la web
     * @param String $estigma es un tag identificativo a reemplazar en una plantilla
     * @param String $bit es un bloque 
     * @return void
     */
    public function addTemplateBit( $estigma, $bit )
    {
		if( strpos( $bit, 'skins/' ) === false )
		{
		    $bit = 'skins/' . Guru::getSetting('skin') . '/templates/' . $bit;
		}
		$this->page->addTemplateBit( $estigma, $bit );
    }

    /**
     * Reemplaza cada estigma con el contenido adecuado
     * Actualiza el contenido de la página
     * @return void
     */
    private function replaceBits()
    {
	    $bits = $this->page->getBits();
	    foreach( $bits as $estigma => $template )
	    {
		    $templateContent = file_get_contents( $bit );
		    $newContent = str_replace( '{' . $estigma . '}', $templateContent, $this->page->getContent() );
		    $this->page->setContent( $newContent );
	    }
    }

    /**
     * Rellena con contenido de la base de datos los estigmas
     * @return void
     */
    private function replaceTags()
    {
	    // get the tags
	    $estigmas = $this->page->getTags();
	    // go through them all
	    foreach( $estigmas as $estigma => $data )
	    {
		    if( is_array( $data ) )
		    {

			    if( $data[0] == 'SQL' )
			    {
				    // Si se trata de datos en la BD
				    $this->replaceDBTags( $estigma, $data[1] );
			    }
			    elseif( $data[0] == 'DATA' )
			    {
				     // si se trata de datos en caché
				    $this->replaceDataEstigmas( $estigma, $data[1] );
			    }
	    	}
	    	else
	    	{	
		    	// reemplaza el contenido del estigma	    	
		    	$newContent = str_replace( '{' . $estigma . '}', $data, $this->page->getContent() );
		    	// actualiza el contenido
		    	$this->page->setContent( $newContent );
	    	}
	    }
    }

    /**
     * Reemplace el contenido de la página con los datos de la base de datos
     * @param String $estigma la etiqueta a reemplazar
     * @param int $cacheId el ID de la consulta en caché
     * @return void
     */
    private function replaceDBTags( $estigma, $cacheId )
    {
	    $block = '';
		$blockOld = $this->page->getBlock( $estigma );

		// Recorremos todos los valores posibles recogidos en la caché
		while ($estigmas = Guru::getObject('db')->resultsFromCache( $cacheId ) )
		{
			$blockNew = $blockOld;
			// Se crea un bloque de contenido nuevo por cada registro recuperado
			foreach ($estigmas as $ntag => $data) 
	       	{
	        	$blockNew = str_replace("{" . $ntag . "}", $data, $blockNew); 
	        }
	        $block .= $blockNew;
		}
		$pageContent = $this->page->getContent();
		// Elimina los separadores de contenido en la plantilla
		$newContent = str_replace( '<!-- START ' . $estigma . ' -->' . $blockOld . '<!-- END ' . $estigma . ' -->', $block, $pageContent );
		// Actualiza el contenido de la página
		$this->page->setContent( $newContent );
	}

	/**
     * Reemplaza el contenido de la página con los datos de la caché
     * @param String $estigma la etiqueta a reemplazar
     * @param int $cacheId el ID de la consulta en caché
     * @return void
     */
    private function replaceDataEstigmas( $estigma, $cacheId )
    {
	    $block = $this->page->getBlock( $estigma );
		$blockOld = $block;
		while ($estigmas = Guru::getObject('db')->dataFromCache( $cacheId ) )
		{
			foreach ($estigmas as $estigma => $data) 
	       	{
		       	$blockNew = $blockOld;
	        	$blockNew = str_replace("{" . $estigma . "}", $data, $blockNew); 
	        }
	        $block .= $blockNew;
		}
		$pageContent = $this->page->getContent();
		$newContent = str_replace( $blockOld, $block, $pageContent );
		$this->page->setContent( $newContent );
    }

    /**
     * Obtiene el objeto página
     * @return Object 
     */
    public function getPage()
    {
	    return $this->page;
    }

    /**
     * Establece el contenido de la página de acuerdo a una serie de plantillas
     * @return void
     */
    public function buildFromTemplates()
    {
	    $bits = func_get_args();
	    $content = "";
	    foreach( $bits as $bit )
	    {

		    if( strpos( $bit, 'skins/' ) === false )
		    {
			    $bit = 'skins/' . Guru::getSetting('skin') . '/templates/' . $bit;
		    }
		    if( file_exists( $bit ) == true )
		    {
			    $content .= file_get_contents( $bit );
		    }

	    }
	    $this->page->setContent( $content );
    }

    /**
     * Convierte en un array de datos todos los estigmas (etiquetas)
     * @param array los datos
     * @param string el nombre de la etiqueta
     * @return void
     */
    public function dataToEstigmas( $data, $prefix )
    {
	    foreach( $data as $key => $content )
	    {
		    $this->page->addEstigma( $key.$prefix, $content);
	    }
    }

    public function parseTitle()
    {
	    $newContent = str_replace('<title>', '<title>'. $this->page->getTitle(), $this->page->getContent() );
	    $this->page->setContent( $newContent );
    }

    /**
     * Reemplaza los estigmas por datos
     * @return void
     */
    public function parseOutput()
    {
	    $this->replaceBits();
	    $this->replaceTags();
	    $this->parseTitle();
    }

}
?>

Como podéis comprobar lo que hace esta clase es en primer lugar localizar los estigmas, es decir, las etiquetas dinámicas del propio framework para reemplazarlas por el valor deseado recuperado de la base de datos. En breve veremos un ejemplo de una plantilla muy simple que nos ayudará a comprender aun mejor esta clase, pero básicamente busca una etiqueta y la reemplaza por un valor de la base de datos. Como decía anteriormente, supongamos que dentro del HTML de la plantilla encontramos {UserName} por ejemplo, pues esta clase adoptaría ese nombre como propio de un campo de la base de datos y lo reemplaza por su valor.

 

Administrador de páginas

Esta clase está administrada desde el administrador de plantillas y está pensada para contener todos los detalles de una página web. Libera al administrador de plantillas de algunas funciones más específicas de una página y nos sirve para poder ayudar a crecer nuestro pequeño framework.

<?php

/**
 * Guru
 * Framework CLASS PAGE
 *
 * @version 0.1
 * @autor Alex Romero
 * @basedOnWork Michael Peacock
 */

class page {

	// Posible opciones a implementar en un futuro
	private $css = array();
	private $js = array();
	private $bodyTag = '';
	private $bodyTagInsert = '';

	// Nos servirá en un futuro para loguear usuarios
	private $authorised = true;
	private $password = '';

	// Elementos de la página
	private $title = '';
	private $estigmas = array();
	private $postParseTags = array();
	private $bits = array();
	private $content = "";

	/**
	 * Constructor de la clase
	 */
    function __construct() { }

    public function getTitle()
    {
    	return $this->title;
    }

    public function setPassword( $password )
    {
    	$this->password = $password;
    } 

    public function setTitle( $title )
    {
	    $this->title = $title;
    }

    public function setContent( $content )
    {
	    $this->content = $content;
    }

    public function addEstigma( $key, $data )
    {
	    $this->tags[$key] = $data;
    }

    public function getTags()
    {
	    return $this->tags;
    }

    public function addPPTag( $key, $data )
    {
	    $this->postParseTags[$key] = $data;
    }

    /**
     * Obtiene las etiqutas (estigmas) que se hallan parseado
     * @return array
     */
    public function getPPTags()
    {
	    return $this->postParseTags;
    }

    /**
     * Añade un bloque en la página (de momento no es funcional)
     * @param String el estigma a reemplazar
     * @param String el bloque a rellenar
     * @return void
     */
    public function addTemplateBit( $estigma, $bit )
    {
	    $this->bits[ $estigma ] = $bit;
    }

    /**
     * Obtener los bloques de la plantilla que se introducirán en la página
     * @return array Un array con todos los estigmas de la página y con los nombres de las plantillas
     */
    public function getBits()
    {
	    return $this->bits;
    }

    /**
     * Obtiene un fragmento de la página
     * @param String La etiqueta envoltorio del bloque ( <!-- START estigma --> block <!-- END estigma --> )
     * @return String El bloque de contenido
     */
    public function getBlock( $estigma )
    {
		preg_match ('#<!-- START '. $estigma . ' -->(.+?)<!-- END '. $estigma . ' -->#si', $this->content, $tor);

		$tor = str_replace ('<!-- START '. $estigma . ' -->', "", $tor[0]);
		$tor = str_replace ('<!-- END '  . $estigma . ' -->', "", $tor);

		return $tor;
    }

    public function getContent()
    {
	    return $this->content;
    }

}
?>

¿Como podría ampliarse y mejorarse esta clase?

  • PostParseTags: Quizás es interesante que podamos volver a comprobar las etiquetas recién reemplazadas para obtener nuevos resultados de la base de datos. Imagina que quieres que aparezca debajo de una supuesta puntuación el nombre de otra persona con la misma puntuación en tu web, obtenida quizás por participación, pues gracias a esta funcionabilidad, se podría leer los tags (estigmas) anteriormente parseados y modificar el contenido del mismo.
  • PasswordedPages: Como no, en muchas webs es necesario de un registro de usuario para poder realizar ciertas acciones, ver diferentes secciones de la web o incluso simplemente entrar para ver los contenidos. Pues bien, esta sería una opción muy interesante el poder comprobar mediante coockies o un formulario de acceso si el usuario en cuestión tiene o no privilegios para poder acceder a la página.
  • Includes Dinámicos: Poder incluir CSS o JS o cualquier otro elemento de forma dinámica dependiendo en la página en la que nos encontremos.

Estas serían las opciones siguientes a tener en cuenta para completar nuestro framework un poquito más, porque sin estas opciones el framework no sería nada de nada. Una cosa es que sea un framework pequeño y otra muy distinta es que sea un trasto inútil que no sirva para nada. De momento poco más se puede ver o quizás entender, para aquellos más alejados del mundo de los frameworks, pero vamos a avanzar un poquito más para ir comprendiendo. Esto va sobre la marcha, poco a poco. Si seguís todos los pasos conseguiréis entender mucho mejor el funcionamiento de un framework, claro a muy pequeña escala, pero algo es algo, incluso como decía en el primer tutorial, si lo hacemos bien, nos puede servir incluso para sacar pequeños proyectos adelante.

Modificaciones del código

Llegados a este punto y para poder realizar un ejemplo completo que utilice plantilla de diseño, ataque a base de datos y montaje de la vista de salida, debemos modificar una función anteriormente implementada en nuestra clase registro. Además ahora hemos renombrado el fichero en vez de ser registro.class.php pasa a ser guru.class.php y dentro del mismo debemos añadir esta función:

public function storeCoreObjects()  
{  
    $this->storeObject('database', 'db' );  
    $this->storeObject('template', 'template' );  
}

De este modo nos creara un objeto database y otro objeto template. Que utilizaremos desde el index.php para atacar y mostrar datos.

Base de Datos

Bien, ahora viene el turno de los datos. Lo primero que tenemos que hacer es utilizar un cliente de base de datos, el que más nos guste, en nuestro caso utilizaremos PhpMyAdmin para administrar y gestionar nuestra base de datos. Lo segundo que tenemos que hacer es crear una base de datos que en nuestro caso la hemos llamado oracleofguru ¿A qué mola? Esta será la base de datos de nuestro framework. Al final de este proyecto tendrá unas cuantas muchas tablas, pero por ahora con crear una sola para ir viendo el funcionamiento de nuestro framework es suficiente. Así que creamos la base de datos:

crear base de datos MySQL

Crear base de datos MySQL

Una vez creada tenemos que crear una tabla, que como habéis podido observar, casi todo el código está en inglés, esto se debe a dos cosas, la primera y fundamental a que el autor del fuente en su mayoría es inglés y por tanto el código está en inglés, valga la redundancia, pero aun así también hay que tener en cuenta otra cosa, que aun a pesar de que se trate de un pequeño framework, mejor dejarlo todo en inglés por varias razones. La primera porque nos podemos olvidar de nombres que no podremos poner en español porque no podemos usar eñes, acentos y movidas de ese tipo. Y la segunda porque hacemos un código simple pero al menos es internacional. ¿No? Bien, pues como decía, ahora debemos de crear nuestra primera tabla, tabla que como va dividida del todo de nuestro framework, porque recordad que hemos utilizado una capa de abstracción que comunica con la base de datos, más adelante podremos modificar la tabla sin ningún problema, nuestra web seguirá funcionando igual. En este caso la tabla en cuestión es members en la que se almacenarán los miembros de la misma. Además hemos creído oportuno que esta tabla es la más adecuada para el tutorial, ya que en un futuro no muy lejano nos podría servir para continuar con nuestra clase page y ampliar su funcionalidad. Para crearla lo podemos hacer de muchas maneras. Lo mejor sería sin lugar a dudas en SQL. ¿Verdad? Pues ahí va este sencillísimo CREATE TABLE.

CREATE TABLE `members` (
   `ID` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
   `name` VARCHAR(50) NOT NULL,
   `email` VARCHAR(50) NOT NULL,
   `pass` VARCHAR(25) NOT NULL
) ENGINE = INNODB COMMENT = 'Members of site.';

Además necesitaremos insertar algunos registros para realizar la prueba, si no, no nos mostrará nada en absoluto. Así que os dejo un par de inserts de ejemplo:

INSERT INTO `members` (name, email, pass)
VALUES ('Alex','alex@suenyos.com','1234');

INSERT INTO `members` (name, email, pass)
VALUES ('Carol','carol@suenyos.com','1234');

¿Por ahora todo bien? Bueno, vamos a continuar a ver que tal va eso.

Un fastermaster de diseño web

Vamos a crear una plantilla web de lo más simple y sencilla. Más adelante ya veremos como mejorar este aspecto. De momento estamos comprendiendo el funcionamiento de un framework, qué es lo que hace, como trabaja y además podemos experimentar con nuestro propio marco de desarrollo. (A saber que saldrá de aquí XD). Bueno, ahora a por lo siguiente. El diseño. Necesitamos una plantilla que nuestro framework pueda procesar. Para ello, ¿recordáis la arquitectura de nuestro framework? Bien pues dirigiros a la carpeta templates que se encuentra en ../guru/skins/default/templates Bien, ¿estamos ya ahí? Pues ahora creamos un documento html simple y llano con nombre por ejemplo main.tpl.htm De este modo sabemos que se trata de la página principal. Por el nombre, no por otra cosa ¿eh? No asustéis. Bien, continuamos. Debería tener un aspecto como el siguiente:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<title> Powered by GURU Framework</title>
</head>
<body>
<h1>Nuestros Miembros</h1>
<p>La lista de nuestros miembros:</p>
    <ul>
        <!-- START members -->
        <li>{name} {email}</li>
        <!-- END members -->
    </ul>
</body>
</html>

Bien, como se pude apreciar existen dos estigmas. ¿Qué son los estigmas? Si preguntas esto es que no estás leyendo ni siguiendo el post. Aun así te refrescaré la memoria. Hemos llamado estigmas a las etiquetas o tags que van entre llaves y que nuestro framework cambiará por datos de nuestra base de datos. Es decir, nuestro framework cogerá el HTML, lo parseará y reemplazará {name} {email} por Alex alex@suenyos.com y así sucesivamente con todos y cada uno de los registros que se encuentren en la tabla members que hemos creado anteriormente. Espero que ahora halla quedado algo más claro.

Qué ganas de probarlo

Bueno, llegó la hora de la verdad. A ver si conseguimos que funcione. Pero tenemos que modificar nuestro index.php y dejarlo de la siguiente manera:

<?php
/**
 * Guru
 * Framework loader - acts as a single point of access to the Framework
 *
 * @version 0.1
 * @autor Alex Romero
 * @basedOnWork Michael Peacock
 */

// first and foremost, start our sessions
session_start();

// setup some definitions
// The applications root path, so we can easily get this path from files located in other folders
define( "APP_PATH", dirname( __FILE__ ) ."/" );
// We will use this to ensure scripts are not called from outside of the framework
define( "GURUFW", true );

// @TODO make a setting
date_default_timezone_set("Europe/Madrid");

/** 
 * Magic autoload function
 * used to include the appropriate -controller- files when they are needed
 * @param String the name of the class
 */
function __autoload( $class_name )
{
	require_once('controllers/' . $class_name . '/' . $class_name . '.php' );
}

// require our registry
require_once('core/guru.class.php');
$registry = Guru::singleton();

// store those core objects
$registry->storeCoreObjects();

// create a database connection
$registry->getObject('db')->newConnection('localhost', 'root', 'clave', 'oracleofguru');

// set the default skin setting (we will store these in the database later...)
$registry->storeSetting('default', 'skin');

// populate our page object from a template file
$registry->getObject('tmp')->buildFromTemplates('main.tpl.htm');

// cache a query of our members table
$cache = $registry->getObject('db')->cacheQuery('SELECT * FROM members');

// assign this to the members tag
$registry->getObject('tmp')->getPage()->addEstigma('members', array('SQL', $cache) );

// set the page title
$registry->getObject('tmp')->getPage()->setTitle('Nuestros Miembros');

// parse it all, and spit it out
$registry->getObject('tmp')->parseOutput();
print $registry->getObject('tmp')->getPage()->getContent();

exit();

?>

Ahora si lo ejecutamos en nuestro servidor nos debería aparecer algo como esto:

Nuestros Miembros Powered by GURU Framework

Nuestros Miembros Powered by GURU Framework

 

¡Manténganse al tanto que muy pronto habrá más y mejor! Saludos compañeros.

20 thoughts on “Crear un framework en PHP5 desde cero (2ª Parte)

  • Hola, desde ya muy buena web, muy buen post… solo les quería hacer una pregunta….. estoy desarrollando mi propio framework y hay algo que no entiendo, cual es el motor del algoritmo que utiliza para hacer que se muestre el dato en la vista sin tener que usar script php en la vista.
    Por ejemplo yo lo que hice es: primero abro el archivo para lleerlo con file_get_content(); , luego lo que hago es crear un pequeño algoritmo que me toma datos de la base de datos y utilizo str_replace($parametro,$remplazador,$archivo); (los $parametros son los script que coloque en forma de comentario en la vista, $remplazador es el código que extraigo de la base de datos y $archivo es el archivo que leo con file_get_content();) para remplazar los datos que yo escribí en forma de comentario en la vista por los de la base de datos…me los muestra correctamente pero no me los incluye dentro de la plantilla… saludos y espero haberme explicado bien…Saludos desde Argentina

    • Veamos, lo suyo es que tengas un script que se encargue de recoger la petición del navegador para mostrar una vista u otra y sea, dicho script quien monte la plantilla adecuada con los datos pertinentes recuperados de la BD. Si te fijas, nosotros utilizamos un bootstrap sencillo, en el que tan solo busca “etiquetas” creadas por nosotros que servirán al motor para realizar el reemplazo de dicha etiqueta por la petición del cliente web. Recuerda al señor de los anillos, XD. Un script para dominarlos a todos, es decir, un script principal, nuestro motor del framework, cuyo algoritmo se encargue de recibir la petición del cliente web y monte la vista pertinente con los datos solicitados.

      Envíanos un poquito del código con algún comentario y quizás podamos servirte de más ayuda.

      • Hola Alex gracias por tu respuesta…
        Mira yo estoy tomando los datos de la base de datos desde un archivo llamado model.php y el código es el siguiente.

        class Model {

        private $loadData;

        public function __contruct() {

        $this->loadData = array();
        }

        public function loadDataGet($consult, $name) {

        $sql = $consult;

        $result = mysql_query($sql, Conection::connect());

        while ($row = mysql_fetch_assoc($result)) {

        $this->loadData[] = $row;
        }
        return $this->loadData;
        }

        }

        Y desde helpers llamo a este metodo y lo que hace la misma es cargar el dato que vien de la base, ya trasformado en array :

        public function loadDataDB($consult, $data) {

        $consulta = $this->model->loadDataGet($consult, $data);

        for ($i = 0; $i helper->loadDataDB($consult,$data) */

        $dato = $this->helper->loadDataDB($consult,$data);

        $remplazador = “{$dato}”;

        $filename = “skings/default/contents/”.$file.”.tpl.php”;

        $leer = file_get_contents($filename);

        //strstr encuentra una aguja en un pajar

        $remplazado = “{“.$data.”}”;

        if (!strstr($leer, $remplazado)) {

        echo “No se logro encontrar la expresion regular ” . $remplazado . “”;

        } else {

        $string = str_replace($remplazado, $remplazador, $leer);

        echo $string;

        }

        }

        Y por ultimo llamarlo desde controller de la siguiente manera :

        $this->singleton(new Tag())->reWriteFile(‘skings/default/contents’,’content’, “select * from prueba”, ‘Name’);

        este el contenido de la vista:



        {Name}

        Como explique antes, el dato se muestra correctamente, pero no dentro del div que deseo..
        Desde ya gracias por su ayuda y espero poder haberme explicado bien… Saludos muy buena web…

          • No te abre otro archivo desde el str_replace, eso no puede ser puesto que el str_replace tan solo toma una cadena de caracteres, busca un caracter o conjunto de caracteres y los reemplaza por lo que le indiques. Comprueba que tu variable $filename realmente esté asociada en el momento de la ejecución con la plantilla (view) deseada y que dicho tag propio de tu framework esté bien escrito, tanto en el *.tpl como en el script de reemplazo. Hazte una traza de tu programa y comprueba dichas variables. Quizás te ayude a saber que *.tpl se está cargando y qué es lo que realmente intentas reemplazar. Otro punto a tener en cuenta es… ¿Tu tag {Name} se ve o se consigue reemplazar por los datos extraídos? ¿Has comprobado que la “N” sea mayúscula en todas las referencias? Poco más se me ocurre, parece que el script está correcto. Cuéntanos si solucionaste el problema.

          • yo tambien ando con la misma situacion, si encontraron solucion, por favor me podrian comentar como lo solucionaron, el texto que cambia, presisamente es cargado en la pagina pero no dentro del div donde se encuentra el {nombre}, sino dentro del body..

  • hola, a mi me pasa algo similar, el contenido que reemplaza no lo pone en el mismo lugar donde esta {}, en vez de eso lo inserta fuera del div donde se encuentran las llaves {}..si tienen alguna solucion les estaria altamente agradecido

    • Lo tengo en cuenta, estas navidades los subiré para que le echéis un vistazo, pero algo no debéis estar haciendo bien. Quizás ayuda mucho más que escribáis vuestro código, para que podamos ver y aprender donde está el error.

      • mira, cargo el archivo en la variable

        $theme=file_get_contents(“themes/theme1/index.html”);

        reemplazo las {} y su reemplazo es el llamado a una funcion, dicha funcion imprimo con echo, por que requiero que me agregue como reemplazo una lista de datos de la base de datos

        $file_new=str_replace(“{sidebar_iz}”,self::getSections(), $theme);

        pero igualmente lo sustituye, pero no en el lugar que es, ojala puedas subir los arthivos de tu framework pronto..

  • Hola amigo, muy buen post, yo a penas estoy iniciandome en este mundillo de mvc, sería bueno que lo culminaras para estudiarlo mejor ya que hay cosas que no entiendo, por ejemplo tienes una clase registry y también mencionas un bootstrap en un comentario que hiciste, más en el ejemplo no aparecen ninguno de estos.
    Lo que más me gusta de todo esto es que no hay que incrustar código php en la plantilla html, eso es estupendo, yo hago el intento de trabajar con el patrón mvc, pero todabia estoy lejos de lograrlo jejeje, los ejemplos que he encontrado buscando en google siempre terminan incrustando php en html, pero eso no es lo que se quiere y los que no usan smarty, para eso hay que hacer pseudo lenguaje, muy engorroso no.
    Bueno de antemano agradézco muchisimo tu ayuda.

    • Buenas compañero!

      Por suerte o por desgracia, aunque más bien por la primera, estamos algo desbordados de trabajo y todos estos pequeños tutoriales que nos encanta tanto hacer aquí para ofrecer ideas y conceptos interesantes a las mentes más inquietas tienen que quedar en segundo lugar, ya que a penas tenemos tiempo para nosotros mismos. Lo bueno es que disfrutamos mucho trabajando, pero de verdad que tuvimos que dejar este pseudoproyecto en manos de la suerte has que podamos encontrar algo más de tiempo y continuarlo. Aún así, ya te informo que se quedará en un pequeñísimo framework cuya única finalidad será didáctica y para proyectos muy, pero que muy pequeños. Aunque quién sabe si no se anima alguien más a colaborar y termina siendo algo realmente útil. (Risas a carcajadas)

      En fin, lo dicho, que esperemos que de aquí a unas semanas podamos publicar algo más referente al tema, pero nuestra idea es dejar completamente de lado el PHP del HTML y que un tipo que se dedique simplemente a maquetar en HTML5, CSS3 y sus programas de edición gráfica, tan sólo conozca las etiquetas clave para mostrar un listado, un menú, una imagen en base de datos,… Vamos, hacerle la vida mucho más fácil, y que un desarrollador tenga medio camino hecho y tan sólo tenga que desarrollar pequeños plugins o addons, pequeñas porciones de código para dotar a dicho framework de nuevas herramientas para adecuarlo a su finalidad.

      Si quieres puedes compartir con nosotros qué es lo que andas haciendo, intentaremos ayudarte en la medida de lo que nos sea posible.
      Saludos y gracias por comentar!!! Te esperamos pronto!!!

      • Wow.., que lastima y que bueno que tengan bastante trabajo..!
        Con respecto a lo de mvc, es a lo que quiero llegar también, no tocar nunca más el html, por lo menos no con php ya que yo también hago mis propias plantillas, jejeje.

        En realidad lo que me falta saber hacer es mostrar en la plantilla el listado de registros que sean obtenidos de una tabla de x db y mostrarlos en una tabla html, sería bueno que dieras un link de descarga de ese mini proyecto, pero bueno, como dicen en mi país: sino se puede, no se puede.

        Seguiré intentando hasta lograrlo, soy de los que no se rinden.. de todas maneras…
        MUCHISISMAS GRACIAS POR RESPONDER Y POR TU APORTE PARA NOSOTROS LOS NOVATOS..! GRACIAS.

        • Si te fijas bien en este post, justamente se explica eso mismo, como mostrar un listado de registros de la tabla “members” reemplazando los estigmas (las etiquetas variables formadas por llaves {nombre_campo}). ¿Comprendes el texto? ¿Qué es lo que te falla?

          • lo voy a analizar mejor y a adaptar a mi forma de trabajar a ver como me va.. luego te comento y te envio el código para que le des un vistazo, aunque no soy un experto pero me esfuerzo en mejorar cada día, cuando vi tu código me dije a mi mismo, aún tengo mucho camino por recorrer jejeje.! Gracias.

          • Esperamos ansiosos tu porción de código. Así que ánimo y al toro. Nosotros aquí aprendemos cosas nuevas cada día, de seguro que no te sacamos tanta ventaja, pues una persona inquieta siempre es más valiosa que otra con más conocimientos. De veras que esperamos tu aportación!

            Un saludo y hasta pronto!

  • Excelente tutorial, es posible que el patron que se esta utilizando para implementar la clase database sea el ActiveRecords?
    Me gustaria que sigas con estos tutoriales sobre todo con la parte del manejo de bases de datos y todas las mejoras que se pueden hacer a esto, como ser, mayor abstraccion, seguridad, distintos patrones que se pueden adoptar, etc.

    Muchas gracias por compartir tus conocimientos con todos nosotros, excelente trabajo. Saludos desde Argentina. Matias

    Sugerencia, el captcha solicitado en el ingreso de comentarios es bastante complejo, cuesta bastante lograr sortearlo para enviar un mensaje, es solo una sugerencia.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *