logo

Paleta - Deixe colorido🎨

Palette — Construtor visual de páginas. Não precisa ser designer.

Demonstração ao vivo Baixar Palette

Scroll

1.5. Conectando classes para trabalhar com banco de dados e templates

08/12/2019, by Ivan

Criamos a estrutura do nosso framework, agora é hora de pensar em armazenar dados: notícias, produtos. Um objeto para trabalhar com banco de dados deve ser capaz de:

  • Gerenciar conexão com banco de dados
  • Fornecer uma pequena abstração do banco de dados
  • Cachear consultas
  • Tornar operações comuns de banco de dados mais simples

Para isso, criaremos um objeto Registry/objects/db.class.php:

<?php

/**
 * Gerenciamento de banco de dados
 * Fornece uma pequena abstração do banco de dados
 */
class database {

  /**
   * Permite múltiplas conexões com banco de dados
   * raramente usado, mas às vezes é útil
   */
  private $connections = array();

  /**
   * Informa sobre a conexão ativa
   * setActiveConnection($id) permite alterar a conexão ativa
   */
  private $activeConnection = 0;

  /**
   * Consultas que foram executadas e salvas para o futuro
   */
  private $queryCache = array();

  /**
   * Dados que foram extraídos e salvos para o futuro
   */
  private $dataCache = array();

  /**
   * Registro da última consulta
   */
  private $last;


  /**
   * Construtor
   */
  public function __construct()
  {

  }

  /**
   * Criar nova conexão
   * @param String database hostname
   * @param String database username
   * @param String database password
   * @param String database we are using
   * @return int the id of the new connection
   */
  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 connecting to host. '.$this->connections[$connection_id]->error, E_USER_ERROR);
    }

    return $connection_id;
  }

  /**
   * Fechar conexão ativa
   * @return void
   */
  public function closeConnection()
  {
    $this->connections[$this->activeConnection]->close();
  }

  /**
   * Alterar conexão ativa
   * @param int the new connection id
   * @return void
   */
  public function setActiveConnection( int $new )
  {
    $this->activeConnection = $new;
  }

  /**
   * Salvar consulta em cache
   * @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 executing and caching query: '.$this->connections[$this->activeConnection]->error, E_USER_ERROR);
      return -1;
    }
    else
    {
      $this->queryCache[] = $result;
      return count($this->queryCache)-1;
    }
  }

  /**
   * Obter quantidade de linhas em cache
   * @param int the query cache pointer
   * @return int the number of rows
   */
  public function numRowsFromCache( $cache_id )
  {
    return $this->queryCache[$cache_id]->num_rows;
  }

  /**
   * Obter linhas do cache
   * @param int the query cache pointer
   * @return array the row
   */
  public function resultsFromCache( $cache_id )
  {
    return $this->queryCache[$cache_id]->fetch_array(MYSQLI_ASSOC);
  }

  /**
   * Salvar cache
   * @param array the data
   * @return int the pointed to the array in the data cache
   */
  public function cacheData( $data )
  {
    $this->dataCache[] = $data;
    return count( $this->dataCache )-1;
  }

  /**
   * Obter dados do cache
   * @param int data cache pointed
   * @return array the data
   */
  public function dataFromCache( $cache_id )
  {
    return $this->dataCache[$cache_id];
  }

  /**
   * Remover registro da tabela
   * @param String the table to remove rows from
   * @param String the condition for which rows are to be removed
   * @param int the number of rows to be removed
   * @return void
   */
  public function deleteRecords( $table, $condition, $limit )
  {
    $limit = ( $limit == '' ) ? '' : ' LIMIT ' . $limit;
    $delete = "DELETE FROM {$table} WHERE {$condition} {$limit}";
    $this->executeQuery( $delete );
  }

  /**
   * Atualizar registro na tabela
   * @param String the table
   * @param array of changes field => value
   * @param String the condition
   * @return bool
   */
  public function updateRecords( $table, $changes, $condition )
  {
    $update = "UPDATE " . $table . " SET ";
    foreach( $changes as $field => $value )
    {
      $update .= "`" . $field . "`='{$value}',";
    }

    // remover vírgula final
    $update = substr($update, 0, -1);
    if( $condition != '' )
    {
      $update .= "WHERE " . $condition;
    }

    $this->executeQuery( $update );

    return true;

  }

  /**
   * Inserir registro na tabela
   * @param String the database table
   * @param array data to insert field => value
   * @return bool
   */
  public function insertRecords( $table, $data )
  {
    // configurar algumas variáveis para campos e valores
    $fields  = "";
    $values = "";

    // preenchê-las
    foreach ($data as $f => $v)
    {

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

    }

    // remover vírgula final
    $fields = substr($fields, 0, -1);
    // remover vírgula final
    $values = substr($values, 0, -1);

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

  /**
   * Executar consulta no banco de dados
   * @param String the query
   * @return void
   */
  public function executeQuery( $queryStr )
  {
    if( !$result = $this->connections[$this->activeConnection]->query( $queryStr ) )
    {
      trigger_error('Error executing query: '.$this->connections[$this->activeConnection]->error, E_USER_ERROR);
    }
    else
    {
      $this->last = $result;
    }

  }

  /**
   * Obter linhas da última consulta, excluindo consultas do cache
   * @return array
   */
  public function getRows()
  {
    return $this->last->fetch_array(MYSQLI_ASSOC);
  }

  /**
   * Obter número de linhas da última consulta
   * @return int the number of affected rows
   */
  public function affectedRows()
  {
    return $this->$this->connections[$this->activeConnection]->affected_rows;
  }

  /**
   * Verificar segurança de dados
   * @param String the data to be sanitized
   * @return String the sanitized data
   */
  public function sanitizeData( $data )
  {
    return $this->connections[$this->activeConnection]->real_escape_string( $data );
  }

  /**
   * Destrutor, fecha conexão
   * fechar todas as conexões de banco de dados
   */
  public function __deconstruct()
  {
    foreach( $this->connections as $connection )
    {
      $connection->close();
    }
  }
}
?>

Antes de passar para a conexão com o banco de dados, vamos ver o que nossa classe faz. Poderemos fazer operações simples de adição, atualização, exclusão através dos métodos da classe:

// Inserção
$registry->getObject('db')->insertRecords( 'products', array('name'=>'Caneca' ) );
// Atualização
$registry->getObject('db')->updateRecords( 'products', array('name'=>'Caneca vermelha' ), 'ID=2' );
// Exclusão
$registry->getObject('db')->deleteRecords( 'products', "name='Caneca vermelha'", 5 );

Também nossa classe suporta cache.

Agora vamos adicionar outro objeto de gerenciamento de templates Registry/objects/template.class.php

<?php

// Constante definida em index.php para evitar chamar a classe de outro lugar
if ( ! defined( 'FW' ) )
{
  echo 'Este arquivo pode ser chamado apenas de index.php e não diretamente';
  exit();
}

/**
 * Classe para trabalhar com templates
 */
class template {

  private $page;

  /**
   * Construtor
   */
  public function __construct()
  {
    // Em seguida, adicionaremos esta classe de página
    include( APP_PATH . '/Registry/objects/page.class.php');
    $this->page = new Page();

  }

  /**
   * Adiciona tag na página
   * @param String $tag tag onde inserimos o template, por exemplo {hello}
   * @param String $bit caminho para o template
   * @return void
   */
  public function addTemplateBit( $tag, $bit )
  {
    if( strpos( $bit, 'Views/' ) === false )
    {
      $bit = 'Views/Templates/' . $bit;
    }
    $this->page->addTemplateBit( $tag, $bit );
  }

  /**
   * Incluir templates na página
   * Atualizar conteúdo da página
   * @return void
   */
  private function replaceBits()
  {
    $bits = $this->page->getBits();
    foreach( $bits as $tag => $template )
    {
      $templateContent = file_get_contents( $template );
      $newContent = str_replace( '{' . $tag . '}', $templateContent, $this->page->getContent() );
      $this->page->setContent( $newContent );
    }
  }

  /**
   * Substituir tags por novo conteúdo
   * @return void
   */
  private function replaceTags()
  {
    // obter tags
    $tags = $this->page->getTags();
    // iterar sobre tags
    foreach( $tags as $tag => $data )
    {
      if( is_array( $data ) )
      {

        if( $data[0] == 'SQL' )
        {
          // Substituir tags de consulta em cache
          $this->replaceDBTags( $tag, $data[1] );
        }
        elseif( $data[0] == 'DATA' )
        {
          // Substituir tags de dados em cache
          $this->replaceDataTags( $tag, $data[1] );
        }
      }
      else
      {
        // substituir tags por conteúdo
        $newContent = str_replace( '{' . $tag . '}', $data, $this->page->getContent() );
        // atualizar conteúdo da página
        $this->page->setContent( $newContent );
      }
    }
  }

  /**
   * Substituir tags por dados do banco de dados
   * @param String $tag tag (token)
   * @param int $cacheId ID das consultas
   * @return void
   */
  private function replaceDBTags( $tag, $cacheId )
  {
    $block = '';
    $blockOld = $this->page->getBlock( $tag );

    // Verificar cache para cada uma das consultas...
    while ($tags = Registry::getObject('db')->resultsFromCache( $cacheId ) )
    {
      $blockNew = $blockOld;
      // criar novo bloco e inseri-lo no lugar da tag
      foreach ($tags as $ntag => $data)
      {
        $blockNew = str_replace("{" . $ntag . "}", $data, $blockNew);
      }
      $block .= $blockNew;
    }
    $pageContent = $this->page->getContent();
    // remover delimitadores do template, limpar HTML
    $newContent = str_replace( '<!-- START ' . $tag . ' -->' . $blockOld . '<!-- END ' . $tag . ' -->', $block, $pageContent );
    // atualizar conteúdo da página
    $this->page->setContent( $newContent );
  }

  /**
   * Substituir conteúdo da página no lugar de tags
   * @param String $tag tag
   * @param int $cacheId ID de dados do cache
   * @return void
   */
  private function replaceDataTags( $tag, $cacheId )
  {
    $block = $this->page->getBlock( $tag );
    $blockOld = $block;
    while ($tags = Registry::getObject('db')->dataFromCache( $cacheId ) )
    {
      foreach ($tags as $tag => $data)
      {
        $blockNew = $blockOld;
        $blockNew = str_replace("{" . $tag . "}", $data, $blockNew);
      }
      $block .= $blockNew;
    }
    $pageContent = $this->page->getContent();
    $newContent = str_replace( $blockOld, $block, $pageContent );
    $this->page->setContent( $newContent );
  }

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

  /**
   * Definir conteúdo da página dependendo da quantidade de templates
   * passar caminhos para templates
   * @return void
   */
  public function buildFromTemplates()
  {
    $bits = func_get_args();
    $content = "";
    foreach( $bits as $bit )
    {

      if( strpos( $bit, 'skins/' ) === false )
      {
        $bit = 'Views/Templates/' . $bit;
      }
      if( file_exists( $bit ) == true )
      {
        $content .= file_get_contents( $bit );
      }

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

  /**
   * Convert an array of data (i.e. a db row?) to some tags
   * @param array the data
   * @param string a prefix which is added to field name to create the tag name
   * @return void
   */
  public function dataToTags( $data, $prefix )
  {
    foreach( $data as $key => $content )
    {
      $this->page->addTag( $key.$prefix, $content);
    }
  }

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

  /**
   * Substituir tags e tokens, cabeçalhos
   * @return void
   */
  public function parseOutput()
  {
    $this->replaceBits();
    $this->replaceTags();
    $this->parseTitle();
  }

}
?>

Também definimos chama o objeto Page no templating, então precisamos defini-lo Registry/objects/page.class.php:

<?php

/**
 * Nossa classe para página
 * Esta classe permite adicionar várias coisas que precisamos
 * Por exemplo: páginas com senha, adição de arquivos js/css, etc.
 */
class page {

  private $css = array();
  private $js = array();
  private $bodyTag = '';
  private $bodyTagInsert = '';

  // funcionalidade futura
  private $authorised = true;
  private $password = '';

  // elementos da página
  private $title = '';
  private $tags = array();
  private $postParseTags = array();
  private $bits = array();
  private $content = "";

  /**
   * Constructor...
   */
  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 addTag( $key, $data )
  {
    $this->tags[$key] = $data;
  }

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

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

  /**
   * Analisar tags
   * @return array
   */
  public function getPPTags()
  {
    return $this->postParseTags;
  }

  /**
   * Adicionar tag
   * @param String the tag where the template is added
   * @param String the template file name
   * @return void
   */
  public function addTemplateBit( $tag, $bit )
  {
    $this->bits[ $tag ] = $bit;
  }

  /**
   * Obter todas as tags
   * @return array the array of template tags and template file names
   */
  public function getBits()
  {
    return $this->bits;
  }

  /**
   * Procurar todos os blocos na página
   * @param String the tag wrapping the block ( <!-- START tag --> block <!-- END tag --> )
   * @return String the block of content
   */
  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);

    return $tor;
  }

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

}
?>

Agora que criamos classes para trabalhar com banco de dados e templates, vamos conectar essas classes.

Vamos criar o método storeCoreObjects() em Registry/registry.class.php:

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

Nele escreveremos quais classes serão conectadas.

Vamos preencher um pouco mais de dados, especificamente criar a tabela de usuários. Nesta tabela haverá três campos id, name, email. Vou adicionar ao git um arquivo sql com o banco de dados para o exemplo.

Agora vamos exibir a página principal, para isso precisamos criar o template Views/Templates/main.tpl.php:

<html>
<head>
    <title> Powered by PCA Framework</title>
</head>
<body>
<h1>Our Members</h1>
<p>Below is a list of our members:</p>
<ul>
<!-- START members -->
<li>{name} {email}</li>
<!-- END members -->
</ul>
</body>
</html>

Como você pode ver, definimos a exibição da tag de membros e tokens {name}, {email}. Acho que em um dos artigos analisaremos detalhadamente o funcionamento do templating. Agora vamos voltar para index.php e conectar o template e o banco de dados.

Agora nosso index.php se parece com isto:

<?php
/**
 * Framework
 * Framework loader - ponto de entrada para nosso framework
 *
 */

// iniciar a sessão
session_start();

error_reporting(E_ALL);
// definir algumas constantes
// Defina a raiz do framework para obter facilmente em qualquer script
define( "APP_PATH", dirname( __FILE__ ) ."/" );
// Usaremos isso para evitar chamar scripts fora do nosso framework
define( "FW", true );

/**
 * Função mágica de carregamento automático
 * permite chamar o -controller- necessário quando necessário
 * @param String the name of the class
 */
function __autoload( $class_name )
{
    require_once('Controllers/' . $class_name . '/' . $class_name . '.php' );
}

// conectar nosso registro
require_once('Registry/registry.class.php');
$registry = Registry::singleton();


// armazenamos uma lista de todos os objetos na classe de registro
$registry->storeCoreObjects();

// aqui devem estar suas credenciais de banco de dados
$registry->getObject('db')->newConnection('localhost', 'root', '', 'framework');

// Conectar o template da página principal
$registry->getObject('template')->buildFromTemplates('main.tpl.php');

// Fazer consulta na tabela de usuários
$cache = $registry->getObject('db')->cacheQuery('SELECT * FROM users');

// Adicionar tag de usuários para chamá-la no template,
// nesta tag os campos da tabela estarão disponíveis através dos tokens {name}, {email}
$registry->getObject('template')->getPage()->addTag('users', array('SQL', $cache) );

// Definir o título da página
$registry->getObject('template')->getPage()->setTitle('Our users');

// Analisar a página em busca de tags e tokens e exibir a página
$registry->getObject('template')->parseOutput();
print $registry->getObject('template')->getPage()->getContent();

// exibir o nome do framework para verificar se tudo funciona
print $registry->getFrameworkName();

exit();

?>

Se tudo correr bem e houver usuários no banco de dados, você deve ter algo como:

Our users

Se algo deu errado e surgiram erros, é possível que eu ainda não tenha corrigido o código nos artigos anteriores, você pode ver o código funcionando no github.

Aqui estão os erros que encontrei ao escrever o artigo.

Alterar o nome da classe de trabalho com banco de dados Registry/objects/db.class.php:

Index: Registry/objects/db.class.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- Registry/objects/db.class.php	(revision b1ffa3bbfce4e95ace7ed735e9412e9332e17d50)
+++ Registry/objects/db.class.php	(revision )
@@ -4,7 +4,7 @@
  * Gerenciamento de banco de dados
  * Fornece uma pequena abstração do banco de dados
  */
-class database {
+class db {

   /**
    * Permite múltiplas conexões com banco de dados
\ No newline at end of file

Defini classes estáticas onde era necessário, renomeei a classe de trabalho com banco de dados Registry/registry.class.php:

Index: Registry/registry.class.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- Registry/registry.class.php	(revision b1ffa3bbfce4e95ace7ed735e9412e9332e17d50)
+++ Registry/registry.class.php	(revision )
@@ -69,15 +69,15 @@
      * @param String $key the key for the array
      * @return void
      */
-    public function storeObject( $object, $key )
+    public static function storeObject( $object, $key )
      {
-        require_once('objects/' . $object . '.class.php');
+        require_once('Registry/objects/' . $object . '.class.php');
          self::$objects[ $key ] = new $object( self::$instance );
      }

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

@@ -86,7 +86,7 @@
       * @param String $key the array key
       * @return object
       */
-    public function getObject( $key )
+    public static function getObject( $key )
      {
          if( is_object ( self::$objects[ $key ] ) )
          {
\ No newline at end of file

Era necessário criar um controlador db com db.php

Controllers/db/
Controllers/db/db.php

 Corrigi o erro no templating Registry/objects/template.class.php:

Index: Registry/objects/template.class.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- Registry/objects/template.class.php	(revision b1ffa3bbfce4e95ace7ed735e9412e9332e17d50)
+++ Registry/objects/template.class.php	(revision )
@@ -194,7 +194,7 @@

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

\ No newline at end of file