Crear un marco PHP5 - Parte 2

Con la estructura básica de nuestro Marco en su lugar, es hora de comenzar a agregarle funcionalidad. En este tutorial, crearemos un administrador de plantillas y un controlador de base de datos, que nos acercará un paso más hacia un potente Framework adecuado para casi cualquier proyecto. Si aún no lo ha hecho, asegúrese de revisar la Parte 1 de esta serie primero!




MVC: Ajustar la estructura

En la primera parte de este tutorial, creamos una carpeta llamada controladores Almacenar la lógica de negocio para nuestras aplicaciones. Como señaló Daok en un comentario, este no es el mejor lugar para toda la lógica de negocios, y ese modelo debe utilizarse para almacenar esta lógica. Anteriormente, siempre he usado la base de datos como el modelo en la mayoría de mis aplicaciones, sin embargo, separar esto un poco más hará que nuestro marco sea aún más poderoso y más fácil de extender..

Entonces que es MVC? MVC es un patrón de diseño (como lo fueron los patrones Singleton y Registry que vimos en la parte 1), y significa Model View Controller, y el objetivo de este patrón es separar la lógica de negocios, las acciones de la interfaz de usuario y la interfaz de usuario de unos y otros. Aunque todavía no vamos a hacer nada con nuestros modelos y controladores, actualicemos nuestra estructura de carpetas de marcos para incluir la carpeta "modelos". El modelo contendrá la lógica de negocio principal, y el controlador tratará la interacción del usuario (por ejemplo, el envío de datos, como un comentario). NB: Nuestra función __autoload no necesita ser cambiada.

Manejador de base de datos

La mayoría de los sitios web y aplicaciones web que utilizan PHP también utilizan un motor de base de datos, como MySQL. Si mantenemos todas nuestras funciones relacionadas con la base de datos en el mismo lugar, entonces podemos (en teoría) cambiar fácilmente el motor de base de datos que utilizamos. También podemos facilitar ciertas operaciones, como insertar registros, actualizar registros o eliminar registros de la base de datos. También puede hacerlo más fácil cuando se trata de conexiones de base de datos múltiples.

Entonces ... ¿qué debería nuestro manejador de base de datos hacer:

  • Gestionar conexiones a la base de datos.
  • Trate de proporcionar algún nivel de abstracción de la base de datos
  • Consultas en caché para que podamos usarlas más tarde.
  • Facilita las operaciones comunes de la base de datos.

Veamos el código de nuestro controlador de base de datos, luego lo discutiremos..

 conexiones [] = nuevo mysqli ($ host, $ usuario, $ contraseña, $ base de datos); $ connection_id = count ($ this-> connections) -1; if (mysqli_connect_errno ()) trigger_error ('Error al conectar con el host.'. $ this-> connections [$ connection_id] -> error, E_USER_ERROR);  devuelve $ connection_id;  / ** * Cerrar la conexión activa * @return void * / public function closeConnection () $ this-> connections [$ this-> activeConnection] -> close ();  / ** * Cambiar qué conexión de base de datos se usa activamente para la siguiente operación * @param int el nuevo ID de conexión * @return void * / public function setActiveConnection (int $ new) $ this-> activeConnection = $ new;  / ** * Almacenar una consulta en el caché de consulta para procesarla más tarde * @param Cadena de la cadena de consulta * @ devolver el apuntado a la consulta en el caché * / función pública cacheQuery ($ queryStr) si (! $ Resultado = $ this-> connections [$ this-> activeConnection] -> query ($ queryStr)) trigger_error ('Error al ejecutar y almacenar en caché la consulta:'. $ this-> connections [$ this-> activeConnection] -> error, E_USER_ERROR); devuelve -1;  else $ this-> queryCache [] = $ result; recuento de devoluciones ($ this-> queryCache) -1;  / ** * Obtenga el número de filas del caché * @param int el puntero del caché de consulta * @return int el número de filas * / función pública numRowsFromCache ($ cache_id) return $ this-> queryCache [$ cache_id] -> num_rows;  / ** * Obtenga las filas de una consulta en caché * @param int el puntero de la caché de consultas * @return array la fila * / public function resultsFromCache ($ cache_id) return $ this-> queryCache [$ cache_id] -> fetch_array ( MYSQLI_ASSOC);  / ** * Almacenar algunos datos en un caché para más tarde * @param array the data * @return int el apuntado al array en el caché de datos * / public function cacheData ($ data) $ this-> dataCache [] = $ datos; recuento de devoluciones ($ this-> dataCache) -1;  / ** * Obtenga datos de la caché de datos * @param int data caché apuntó * @return array los datos * / función pública dataFromCache ($ cache_id) return $ this-> dataCache [$ cache_id];  / ** * Eliminar registros de la base de datos * @param Cadena de la tabla para eliminar filas de * @param Cadena de la condición para la cual se eliminarán las filas * @param int el número de filas que se eliminarán * @return void * / función pública deleteRecords ($ table, $ condition, $ limit) $ limit = ($ limit == ")?": 'LIMIT'. límite de $ $ delete = "ELIMINAR DE $ tabla DONDE $ condición $ límite"; $ this-> executeQuery ($ delete);  / ** * Actualizar registros en la base de datos * @param Cadena de la tabla * @param matriz de campo de cambios => valor * @param Cadena de la condición * @return bool * / public function updateRecords ($ tabla, $ cambios, $ condición ) $ update = "ACTUALIZACIÓN". $ mesa "SET"; foreach ($ cambia como $ field => $ value) $ update. = "'". $ campo. "'=' $ value ',";  // elimina nuestro final, $ update = substr ($ update, 0, -1); if ($ condition! = ") $ update. =" WHERE ". $ condition; $ this-> executeQuery ($ update); return true; / ** * Insertar registros en la base de datos * @param Cadena de la base de datos table * @param array data para insertar field => value * @return bool * / public function insertRecords ($ table, $ data) // configura algunas variables para campos y valores $ fields = ""; $ values ​​= ""; // rellénelos para cada persona ($ datos como $ f => $ v) $ fields. = "'$ f',"; $ valores. = (is_numeric ($ v) && (intval ($ v) == $ v ))? $ v. ",": "'$ v',"; // eliminamos nuestro final, $ fields = substr ($ fields, 0, -1); // eliminamos nuestro final, $ values ​​= substr ( $ valores, 0, -1); $ insert = "INSERT INTO $ table ($ fields) VALUES ($ valores)"; $ this-> executeQuery ($ insert); return true; / ** * Ejecutar una cadena de consulta * @param Cadena de 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 [$ t his-> activeConnection] -> error, E_USER_ERROR);  else $ this-> last = $ result;  / ** * Obtenga las filas de la última consulta ejecutada, excluyendo las consultas en caché * @return array * / public function getRows () return $ this-> last-> fetch_array (MYSQLI_ASSOC);  / ** * Obtiene el número de filas afectadas de la consulta anterior * @return int el número de filas afectadas * / public functionfectRows () return $ this -> $ this-> connections [$ this-> activeConnection] - > flecos afectados;  / ** * Sanitize data * @param String los datos a sanear * @return String los datos saneados * / public function sanitizeData ($ data) return $ this-> connections [$ this-> activeConnection] -> real_escape_string $ datos);  / ** * Deconstruye el objeto * cierra todas las conexiones de la base de datos * / public function __deconstruct () foreach ($ this-> connections as $ connection) $ connection-> close (); ?>

Antes de discutir esto con más detalle, debo señalar que este controlador de base de datos es muy básico. Podríamos proporcionar una abstracción completa no ejecutando consultas directamente, sino construyendo consultas basadas en parámetros para una función de consulta y luego ejecutándolas.

Nuestros métodos de borrado, inserción y actualización de registros facilitan la realización de algunas tareas comunes (como mencioné anteriormente, podríamos extender esto para hacer mucho más), al proporcionar solo información como el nombre de la tabla, una matriz de campos y valores correspondientes. Valores y condiciones límite. Las consultas también se pueden "almacenar en caché" para que podamos hacer cosas con ellas más adelante. Considero que esta característica (así como la capacidad de "almacenar en caché" arreglos de datos) es muy útil cuando se combina con un administrador de plantillas, ya que podemos iterar fácilmente a través de filas de datos y rellenarlas en nuestras plantillas con poco problema, como lo harás Vemos cuando miramos el gestor de plantillas..

 // inserte el registro $ registry-> getObject ('db') -> insertRecords ('testTable', array ('name' => 'Michael')); // actualizar un registro $ registry-> getObject ('db') -> updateRecords ('testTable', array ('name' => 'MichaelP'), 'ID = 2'); // eliminar un registro (bueno, hasta 5 en este caso) $ registry-> getObject ('db') -> deleteRecords ('testTable', "name = 'MichaelP'", 5);

También podemos trabajar con múltiples conexiones de base de datos con relativa facilidad, siempre y cuando cambiemos entre las conexiones apropiadas cuando sea necesario (aunque esto no funcionará cuando las consultas en caché y las recuperemos a través de nuestro administrador de plantillas sin más trabajo), por ejemplo, El siguiente fragmento de código nos permitiría eliminar registros de dos bases de datos.

 // nuestra segunda conexión de base de datos (supongamos que ya tenemos una conexión con nuestra base de datos principal) $ newConnection = $ registry-> getObject ('db') -> newConnection ('localhost', 'root', 'password', 'secondDB '); // eliminar de la conexión db primaria $ registry-> getObject ('db') -> deleteRecords ('testTable', "name = 'MichaelP'", 5); // cambiar nuestra conexión activa de base de datos, para permitir futuras consultas en la segunda conexión $ registry-> getObject ('db') -> setActiveConnection ($ newConnection); // eliminar de la conexión db secundaria $ registry-> getObject ('db') -> deleteRecords ('testTable', "name = 'MichaelP'", 5); // revertir la conexión activa para que las consultas futuras estén en la conexión principal de la base de datos $ registry-> getObject ('db') -> setActiveConnection (0);

¿Cómo podríamos extender esta clase??

  • Abstracción completa
  • Haga uso de la herencia, cree una interfaz y haga heredar las clases de la base de datos, cada una para los diferentes motores de base de datos.
  • Almacene los identificadores de conexión junto con la consulta al almacenar en caché las consultas
  • Mejorar la desinfección de datos, según el tipo de datos que deseamos sanear.

Administrador de plantillas

El administrador de plantillas manejará toda la salida, debe poder trabajar con varios archivos de plantillas diferentes, reemplazar los marcadores de posición (los llamo etiquetas) con datos e iterar a través de partes de la plantilla con múltiples filas de datos de la base de datos.

Para facilitar las cosas, utilizaremos una clase de página para contener el contenido relacionado con la página, esto también nos facilita ampliarlo y agregarle funciones más adelante. El gestor de plantillas gestionará este objeto..

 page = new Page ();  / ** * Agrega un bit de plantilla a nuestra página * @param String $ etiqueta la etiqueta donde insertamos la plantilla, por ejemplo. hello * @param String $ bit el bit de la plantilla (ruta al archivo, o simplemente el nombre del archivo) * @return void * / public function addTemplateBit ($ tag, $ bit) if (strpos ($ bit, 'skins /' ) === falso) $ bit = 'pieles /'. PCARegistry :: getSetting ('skin'). '/ templates /'. $ bit;  $ this-> page-> addTemplateBit ($ tag, $ bit);  / ** * Ponga los bits de la plantilla en el contenido de nuestra página * Actualiza el contenido de las páginas * @return void * / private function replaceBits () $ bits = $ this-> page-> getBits (); foreach ($ bits as $ tag => $ template) $ templateContent = file_get_contents ($ bit); $ newContent = str_replace (''. $ tag. '', $ templateContent, $ this-> page-> getContent ()); $ this-> page-> setContent ($ newContent);  / ** * Reemplazar etiquetas en nuestra página con contenido * @return void * / private function replaceTags () // obtener las etiquetas $ tags = $ this-> page-> getTags (); // ir a través de todos ellos foreach ($ tags as $ tag => $ data) if (is_array ($ data)) if ($ data [0] == 'SQL') // es una consulta en caché ... Reemplace las etiquetas DB $ this-> replaceDBTags ($ tag, $ data [1]);  elseif ($ data [0] == 'DATA') // son algunos datos almacenados en caché ... reemplazar las etiquetas de datos $ this-> replaceDataTags ($ tag, $ data [1]);  else else // sustituye el contenido $ newContent = str_replace (''. $ tag. '', $ data, $ this-> page-> getContent ()); // actualizar el contenido de las páginas $ this-> page-> setContent ($ newContent);  / ** * Reemplace el contenido de la página con datos del DB * @param String $ etiquete la etiqueta que define el área de contenido * @param int $ cacheId el ID de las consultas en el caché de consultas * @return void * / private function replaceDBTags ($ tag, $ cacheId) $ block = "; $ blockOld = $ this-> page-> getBlock ($ tag); // registro de cada registro relacionado con la consulta ... while ($ tags = PCARegistry :: getObject ( 'db') -> resultsFromCache ($ cacheId)) $ blockNew = $ blockOld; // crea un nuevo bloque de contenido con los resultados reemplazados en foreach ($ tags como $ ntag => $ data) $ blockNew = str_replace ("". $ ntag. "", $ data, $ blockNew); $ block. = $ blockNew; $ pageContent = $ this-> page-> getContent (); // elimina el separador en la plantilla , limpiador HTML $ newContent = str_replace (''. $ blockOld. '', $ block, $ pageContent); // actualizar el contenido de la página $ this-> page-> setContent ($ newContent);  / ** * Reemplazar el contenido en la página con datos del caché * @param String $ etiquetar la etiqueta que define el área de contenido * @param int $ cacheId el ID de datos en el caché de datos * @return void * / private function replaceDataTags ($ tag, $ cacheId) $ block = $ this-> page-> getBlock ($ tag); $ blockOld = $ block; while ($ tags = PCARegistry :: getObject ('db') -> dataFromCache ($ cacheId)) foreach ($ tags como $ tag => $ data) $ blockNew = $ blockOld; $ blockNew = str_replace ("". $ tag. "", $ data, $ blockNew);  $ bloque. = $ bloqueNuevo;  $ pageContent = $ this-> page-> getContent (); $ newContent = str_replace ($ blockOld, $ block, $ pageContent); $ this-> page-> setContent ($ newContent);  / ** * Obtener el objeto de la página * @return Object * / public function getPage () return $ this-> page;  / ** * Configure el contenido de la página en función de una serie de plantillas * pase las ubicaciones de los archivos de la plantilla como argumentos individuales * @return void * / public function buildFromTemplates () $ bits = func_get_args (); $ content = ""; foreach ($ bits como $ bit) if (strpos ($ bit, 'skins /') === false) $ bit = 'skins /'. PCARegistry :: getSetting ('skin'). '/ templates /'. $ bit;  if (file_exists ($ bit) == true) $ content. = file_get_contents ($ bit);  $ this-> page-> setContent ($ content);  / ** * Convertir una matriz de datos (es decir, ¿una fila db?) A algunas etiquetas * @param array the data * @param string un prefijo que se agrega al nombre del campo para crear la etiqueta de nombre * @return void * / public function dataToTags ($ data, $ prefix) foreach ($ data como $ key => $ content) $ this-> page-> addTag ($ key. $ prefix, $ content);  función pública parseTitle () $ newContent = str_replace ('','<title>'. $ page-> getTitle (), $ this-> page-> getContent ()); $ this-> page-> setContent ($ newContent);  / ** * Analizar el objeto de la página en alguna salida * @return void * / public function parseOutput () $ this-> replaceBits (); $ this-> replaceTags (); $ this-> parseTitle (); ?></pre> <p> <em>Entonces, ¿qué es exactamente lo que hace esta clase? </em></p> <p> <strong>Crea nuestro objeto de página y lo basa a partir de archivos de plantilla.</strong>, el objeto de la página contiene el contenido y la información que se necesita para componer el HTML de la página. Luego, creamosFromTemplate ('templatefile.tpl.php', 'templatefile2.tpl.php') para obtener el contenido inicial de nuestra página, este método toma cualquier número de archivos de plantilla como sus argumentos, y los unimos en orden, útil para encabezado, contenido y plantillas de pie de página.</p> <p> <strong>Gestiona el contenido asociado a la página.</strong> ayudando al objeto de la página a mantener un registro de los datos que se van a reemplazar en la página, y también los bits de plantilla adicionales que deben incorporarse a la página (addTemplateBit ('barra de usuario', 'usertoolsbar.tpl.php')).</p> <p> <strong>Agrega datos y contenido a la página.</strong> realizando varias operaciones de reemplazo en el contenido de la página, incluida la recuperación de resultados de una consulta en caché y su adición a la página.</p> <p>El archivo de la plantilla debe marcarse dentro de sí mismo cuando sea necesario recuperar una consulta en caché y reemplazar los datos de la consulta. Cuando el administrador de plantillas encuentra una etiqueta para reemplazar la que es una consulta, obtiene la porción de la página donde debe iterar llamando a getBlock ('bloque') en el objeto de la página. Esta porción de contenido luego se copia para cada registro en la consulta, y sus etiquetas se reemplazan con los resultados de la consulta. Veremos cómo se ve esto en la plantilla más adelante en este tutorial..</p> <h3>Administrador de plantillas: Página</h3> <p>El administrador de plantillas gestiona el objeto de la página y se utiliza para contener todos los detalles relacionados con la página. Esto permite que el administrador de plantillas sea de administración libre, mientras nos facilita la extensión de la funcionalidad en una fecha posterior..</p> <pre> <?php /** * This is our page object * It is a seperate object to allow some interesting extra functionality to be added * Some ideas: passwording pages, adding page specific css/js files, etc */ class page  // room to grow later? private $css = array(); private $js = array(); private $bodyTag ="; private $bodyTagInsert ="; // future functionality? private $authorised = true; private $password ="; // page elements private $title ="; private $tags = array(); private $postParseTags = array(); private $bits = array(); private $content = ""; /** * Constructor… */ function __construct()   public function getTitle()  return $this->título;  función pública setPassword ($ contraseña) $ this-> contraseña = $ contraseña;  función pública setTitle ($ title) $ this-> title = $ title;  función pública setContent ($ contenido) $ esto-> contenido = $ contenido;  función pública addTag ($ key, $ data) $ this-> tags [$ key] = $ data;  función pública getTags () return $ this-> tags;  public function addPPTag ($ key, $ data) $ this-> postParseTags [$ key] = $ data;  / ** * Obtener las etiquetas que se analizarán después de haber analizado el primer lote * @return array * / función pública getPPTags () return $ this-> postParseTags;  / ** * Agregar un bit de plantilla a la página, en realidad no agrega el contenido todavía * @param Cadena de la etiqueta donde se agrega la plantilla * @param Cadena del nombre del archivo de plantilla * @return void * / función pública addTemplateBit ($ etiqueta, $ bit) $ esto-> bits [$ etiqueta] = $ bit;  / ** * Obtenga los bits de plantilla que se ingresarán en la página * @return array la matriz de etiquetas de plantilla y nombres de archivo de plantilla * / función pública getBits () return $ this-> bits;  / ** * Obtiene una parte del contenido de la página * @param Cadena la etiqueta que encierra el bloque ( <!-- START tag --> bloquear <!-- END tag --> ) * @return String el bloque de contenido * / public function getBlock ($ tag) preg_match ('#<!-- START '. $tag . ' -->(. +?)<!-- END '. $tag . ' -->#si ', $ this-> content, $ tor); $ tor = str_replace ('<!-- START '. $tag . ' -->', "", $ tor [0]); $ tor = str_replace ('<!-- END ' . $tag . ' -->', "", $ tor); devuelve $ tor;  public function getContent () return $ this-> content; ?></pre> <p>¿Cómo se puede ampliar y mejorar esta clase??</p> <ul> <li>PostParseTags: es posible que desee reemplazar las etiquetas <em>después</em> la mayor parte de la página se ha analizado, tal vez el contenido de la base de datos contiene etiquetas que deben analizarse.</li> <li>Páginas con contraseña: asigne una contraseña a una página, verifique si el usuario tiene la contraseña en una cookie o una sesión para que puedan ver la página.</li> <li>Páginas restringidas (¡aunque primero necesitamos nuestros componentes de autenticación!)</li> <li>Alterando el </li> <li>Agregar dinámicamente referencias a archivos javascript y css basados ​​en la página o la aplicación.</li> </ul> <h3>Cargar objetos centrales</h3> <p>Ahora que tenemos algunos objetos que nuestro registro va a almacenar para nosotros, necesitamos decirle al registro qué objetos son. He creado un método en el objeto PCARegistry llamado loadCoreObjects que (como dice) carga los objetos principales. Esto significa que puede simplemente llamar a esto desde nuestro archivo index.php para cargar el registro con estos objetos.</p> <pre> función pública storeCoreObjects () $ this-> storeObject ('database', 'db'); $ this-> storeObject ('template', 'template'); </pre> <p>Este método se puede modificar más adelante para incorporar los otros objetos centrales que debe cargar el registro, por supuesto, puede haber objetos que queremos que nuestro registro administre, pero solo dependiendo de la aplicación para la cual se use el marco. Estos objetos serían cargados fuera de este método..</p> <h3>Algunos datos</h3> <p>Para poder demostrar las nuevas características agregadas a nuestro marco, necesitamos una base de datos para hacer uso del controlador de la base de datos y algunas de las funciones de administración de plantillas (donde reemplazamos un bloque de contenido con las filas en la base de datos).</p> <p>El sitio de demostración que haremos con nuestro marco al final de esta serie de tutoriales es un sitio web con un directorio de miembros, así que hagamos una tabla de base de datos muy básica para los perfiles de los miembros, que contenga un ID, un nombre y una dirección de correo electrónico..</p> <img src="//accentsconagua.com/img/images_27_8/create-a-php5-framework-part-2_2.png"> <p>Obviamente, necesitamos algunas filas de datos en esta tabla!</p> <h3>Una plantilla rapida</h3> <p>Para que se muestre cualquier cosa, necesitamos una plantilla básica, donde listaremos los datos de nuestra tabla de miembros.</p> <pre> <html> <head> <title> Desarrollado por PCA Framework   

Nuestros miembros

A continuación se muestra una lista de nuestros miembros:

  • nombre Correo Electronico

Los miembros de START y los comentarios HTML de los miembros de END denotan el bloque de miembros (que se obtiene a través del método getBlock () en la página), donde el administrador de plantillas recorrerá los registros de la base de datos y los mostrará..

Marco en uso

Ahora, necesitamos unir todo esto con nuestro archivo index.php:

 // requiere nuestro registro require_once ('PCARegistry / pcaregistry.class.php'); $ registry = PCARegistry :: singleton (); // almacena esos objetos centrales $ registry-> storeCoreObjects (); // crear una conexión a la base de datos $ registry-> getObject ('db') -> newConnection ('localhost', 'root', ", 'pcaframework'); // establecer la configuración del skin por defecto (las almacenaremos en la base de datos más adelante…) $ registry-> storeSetting ('default', 'skin'); // llenar nuestro objeto de página desde un archivo de plantilla $ registry-> getObject ('template') -> buildFromTemplates ('main.tpl.php') ; // cachear una consulta de nuestros miembros tabla $ cache = $ registry-> getObject ('db') -> cacheQuery ('SELECT * FROM members'); // asignar esto a la etiqueta de miembros $ registry-> getObject (' template ') -> getPage () -> addTag (' members ', array (' SQL ', $ cache)) // establece el título de la página $ registry-> getObject (' template ') -> getPage () -> setTitle ('Nuestros miembros'); // lo analiza todo y lo escupe $ registry-> getObject ('template') -> parseOutput (); print $ registry-> getObject ('template') -> getPage () -> getContent ();

Si ahora vemos esta página en nuestro navegador web, los resultados de la consulta se muestran en la página:

Viniendo en la parte 3 ...

En la tercera parte nos desviaremos un poco del lado de desarrollo de nuestro Marco, y veremos cómo diseñar con nuestro marco en mente y cómo dividir las plantillas HTML para que sean adecuadas para nuestro marco. Cuando empecemos a construir nuestra primera aplicación con nuestro marco, veremos con más detalle algunos de los trabajos de estas clases. Finalmente, gracias por tus comentarios la última vez.!

  • Suscríbase a la fuente RSS de NETTUTS para obtener más artículos y artículos de desarrollo web diarios..