logo

额外区块类型 (EBT) - 全新的布局构建器体验❗

额外区块类型 (EBT) - 样式化、可定制的区块类型:幻灯片、标签页、卡片、手风琴等更多类型。内置背景、DOM Box、JavaScript 插件的设置。立即体验布局构建的未来。

演示 EBT 模块 下载 EBT 模块

❗额外段落类型 (EPT) - 全新的 Paragraphs 体验

额外段落类型 (EPT) - 类似的基于 Paragraph 的模块集合。

演示 EPT 模块 滚动

滚动

1.5. 引入用于操作数据库和模板的类

07/10/2025, by Ivan

我们已经为框架创建了结构,现在是时候考虑如何存储数据,例如新闻、商品等。用于操作数据库的对象需要具备以下功能:

  • 管理数据库连接
  • 提供对数据库的轻度抽象
  • 缓存查询结果
  • 简化常用数据库操作

为此,我们创建一个文件 Registry/objects/db.class.php

<?php
/**
 * 数据库管理类
 * 提供对数据库的轻度抽象
 */
class database {

  private $connections = array();
  private $activeConnection = 0;
  private $queryCache = array();
  private $dataCache = array();
  private $last;

  public function __construct() {}

  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('连接数据库出错: '.$this->connections[$connection_id]->error, E_USER_ERROR);
    }
    return $connection_id;
  }

  public function closeConnection()
  {
    $this->connections[$this->activeConnection]->close();
  }

  public function setActiveConnection( int $new )
  {
    $this->activeConnection = $new;
  }

  public function cacheQuery( $queryStr )
  {
    if( !$result = $this->connections[$this->activeConnection]->query( $queryStr ) )
    {
      trigger_error('执行并缓存查询出错: '.$this->connections[$this->activeConnection]->error, E_USER_ERROR);
      return -1;
    }
    else
    {
      $this->queryCache[] = $result;
      return count($this->queryCache)-1;
    }
  }

  public function numRowsFromCache( $cache_id )
  {
    return $this->queryCache[$cache_id]->num_rows;
  }

  public function resultsFromCache( $cache_id )
  {
    return $this->queryCache[$cache_id]->fetch_array(MYSQLI_ASSOC);
  }

  public function cacheData( $data )
  {
    $this->dataCache[] = $data;
    return count( $this->dataCache )-1;
  }

  public function dataFromCache( $cache_id )
  {
    return $this->dataCache[$cache_id];
  }

  public function deleteRecords( $table, $condition, $limit )
  {
    $limit = ( $limit == '' ) ? '' : ' LIMIT ' . $limit;
    $delete = "DELETE FROM {$table} WHERE {$condition} {$limit}";
    $this->executeQuery( $delete );
  }

  public function updateRecords( $table, $changes, $condition )
  {
    $update = "UPDATE " . $table . " SET ";
    foreach( $changes as $field => $value )
    {
      $update .= "`" . $field . "`='{$value}',";
    }
    $update = substr($update, 0, -1);
    if( $condition != '' )
    {
      $update .= "WHERE " . $condition;
    }
    $this->executeQuery( $update );
    return true;
  }

  public function insertRecords( $table, $data )
  {
    $fields  = "";
    $values = "";
    foreach ($data as $f => $v)
    {
      $fields  .= "`$f`,";
      $values .= ( is_numeric( $v ) && ( intval( $v ) == $v ) ) ? $v."," : "'$v',";
    }
    $fields = substr($fields, 0, -1);
    $values = substr($values, 0, -1);
    $insert = "INSERT INTO $table ({$fields}) VALUES({$values})";
    $this->executeQuery( $insert );
    return true;
  }

  public function executeQuery( $queryStr )
  {
    if( !$result = $this->connections[$this->activeConnection]->query( $queryStr ) )
    {
      trigger_error('执行查询出错: '.$this->connections[$this->activeConnection]->error, E_USER_ERROR);
    }
    else
    {
      $this->last = $result;
    }
  }

  public function getRows()
  {
    return $this->last->fetch_array(MYSQLI_ASSOC);
  }

  public function affectedRows()
  {
    return $this->connections[$this->activeConnection]->affected_rows;
  }

  public function sanitizeData( $data )
  {
    return $this->connections[$this->activeConnection]->real_escape_string( $data );
  }

  public function __deconstruct()
  {
    foreach( $this->connections as $connection )
    {
      $connection->close();
    }
  }
}
?>

在连接数据库之前,我们先看一下这个类的功能。我们可以通过类方法轻松地执行增删改操作:

// 插入数据
$registry->getObject('db')->insertRecords( 'products', array('name'=>'杯子' ) );
// 更新数据
$registry->getObject('db')->updateRecords( 'products', array('name'=>'红色杯子' ), 'ID=2' );
// 删除数据
$registry->getObject('db')->deleteRecords( 'products', "name='红色杯子'", 5 );

此外,该类还支持查询缓存。

接着我们添加一个用于模板管理的类 Registry/objects/template.class.php

<?php
if ( ! defined( 'FW' ) )
{
  echo '此文件只能从 index.php 调用,不能直接访问';
  exit();
}

/**
 * 模板处理类
 */
class template {
  private $page;

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

  public function addTemplateBit( $tag, $bit )
  {
    if( strpos( $bit, 'Views/' ) === false )
    {
      $bit = 'Views/Templates/' . $bit;
    }
    $this->page->addTemplateBit( $tag, $bit );
  }

  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 );
    }
  }

  private function replaceTags()
  {
    $tags = $this->page->getTags();
    foreach( $tags as $tag => $data )
    {
      if( is_array( $data ) )
      {
        if( $data[0] == 'SQL' )
        {
          $this->replaceDBTags( $tag, $data[1] );
        }
        elseif( $data[0] == 'DATA' )
        {
          $this->replaceDataTags( $tag, $data[1] );
        }
      }
      else
      {
        $newContent = str_replace( '{' . $tag . '}', $data, $this->page->getContent() );
        $this->page->setContent( $newContent );
      }
    }
  }

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

  public function parseOutput()
  {
    $this->replaceBits();
    $this->replaceTags();
    $this->parseTitle();
  }

  public function getPage()
  {
    return $this->page;
  }
}
?>

模板类引用了 Page 对象,因此我们需要在 Registry/objects/page.class.php 中定义它:

<?php
/**
 * 页面类
 * 可扩展为支持 JS/CSS、密码保护页面等功能
 */
class page {

  private $title = '';
  private $tags = array();
  private $bits = array();
  private $content = "";

  public function getTitle() { return $this->title; }
  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 addTemplateBit( $tag, $bit )
  {
    $this->bits[ $tag ] = $bit;
  }

  public function getBits() { return $this->bits; }

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

现在我们有了数据库类和模板类,需要在注册器中注册它们。在 Registry/registry.class.php 中添加方法:

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

然后我们创建用户表 users,包含字段 id、name、email,并编写主页面模板 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>

接下来修改 index.php 以加载模板和数据库连接:

<?php
session_start();
error_reporting(E_ALL);

define( "APP_PATH", dirname( __FILE__ ) ."/" );
define( "FW", true );

function __autoload( $class_name )
{
    require_once('Controllers/' . $class_name . '/' . $class_name . '.php' );
}

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

$registry->storeCoreObjects();
$registry->getObject('db')->newConnection('localhost', 'root', '', 'framework');

$registry->getObject('template')->buildFromTemplates('main.tpl.php');

$cache = $registry->getObject('db')->cacheQuery('SELECT * FROM users');
$registry->getObject('template')->getPage()->addTag('users', array('SQL', $cache) );
$registry->getObject('template')->getPage()->setTitle('Our users');
$registry->getObject('template')->parseOutput();

print $registry->getObject('template')->getPage()->getContent();
print $registry->getFrameworkName();
exit();
?>

如果一切正常并且数据库中有用户数据,页面将显示如下:

Our users

如果出现错误,可以参考 GitHub 上的可运行代码。以下是修复示例:

  • 修改数据库类名为 db
  • 修正模板类中标题替换的 bug

通过这些修改,我们的数据库与模板系统就已成功连接。