iT邦幫忙

DAY 25
9

逐步提昇PHP技術能力系列 第 25

逐步提昇PHP技術能力 - 逐步改善軟體架構 - 建立簡單的View

從昨天的例子,可以看出要使用一個樣板引擎大致上會有怎樣的操作。接下來的工作,就是包裝一下樣板引擎,這樣就可以在需要時抽換。

不過程式碼本身也還需要進一步整理,抽出共用的參數以及共用的bootstrap過程。
先來找出一些共用的參數,通常至少會有資料庫連接的參數,之後再視需要加入更多的參數。不過為了讓其他程式不用require太多東西,我們只require一支bootstrap.php,載入參數以及初始話的過程,就都在bootstrap中完成。

先來看一下之前論壇的首頁程式,然後再看看怎樣調整。

<?php
session_start();
include 'vendor/autoload.php';
if(isset($_SESSION['user']['account'])) {
	$member = true;
	$name = $_SESSION['user']['name'];
} else $member = false;
$conn = mysql_connect('localhost', 'root', '');
if(!$conn)
	die('mysql connection error.');

mysql_select_db('myforum');
if(isset($_SESSION['msg'])) {
	$message = mysql_real_escape_string($_SESSION['msg']);
	unset($_SESSION['msg']);
}
$sql = "SELECT f.*,count(a.forums_id) AS count  FROM forums f LEFT JOIN articles a ON a.forums_id = f.id GROUP BY a.forums_id ORDER BY f.id";
$result = mysql_query($sql, $conn);
$count = 0;
$data = array();
while($row = mysql_fetch_array($result)) {
	if($count%2==0) {
		$style = "#EEFFEE";
	} else {
		$style = "#FFFFFF";
	}
	$sql = "SELECT * FROM articles WHERE forums_id=".$row['id']." ORDER BY id DESC LIMIT 1";
	$result1 = mysql_query($sql, $conn);
	$row1 = mysql_fetch_array($result1);
	$data[] = array('id'=>$row['id'], 'name'=>$row['name'], 'count'=>$row['count'], 'title'=>$row1['title'], 'style'=>$style);
	$count++;
}
#include 'views/index.php';
Twig_Autoloader::register();
$loader = new Twig_Loader_Filesystem('views');
$twig = new Twig_Environment($loader, array('cache'=>'cache'));
$template = $twig->loadTemplate('index.html');
echo $template->render(array(
	'member'=>$member,
	'data'=>$data,
	'name'=>$name,
	'message'=>$message
));
?>

因為頁面都移出去了,所以程式就沒那麼長,而且容易修改,這也是要做程式與頁面分離的原因。由於後續會再加入更種形式的檔案,並且建立簡單的view class,所以還是先做基本的目錄規劃。目前已經有的目錄:

  1. vendor,這是composer套件安裝的位置,不會列入版本管理
  2. views,這是之前將頁面邏輯切出以及套用twig時,頁面的樣板存放的地方。
  3. cache,這是編譯過的twig樣板放置的地方

為了後續管理方便,先訂兩個目錄:

  1. asset:存放靜態的資源檔案,包含js、css、圖檔(不過目前都沒用上)
  2. class:所有的類別就集中放在這裡,這樣做autoload會比較方便

然後把一些目前可以想到的共用參數,拉出來,放到config.php

<?php
define('DB_HOST','localhost');
define('DB_USER','root');
define('DB_PASSWORD', '');
define('DB_NAME', 'myforum');
define('CLASS_DIR', 'class/');

把一些初始化的動作,放進bootstrap.php。這個檔除了會引入config.php之外,還會載入autoloader,並且預先連接資料庫。

接下來是bootstrap.php:

<?php
session_start();
include 'config.php';
//register autoloader
include 'vendor/autoload.php';
set_include_path(get_include_path().PATH_SEPARATOR.CLASS_DIR);
spl_autoload_extensions(".php");
spl_autoload_register();
spl_autoload_register(function($class) {
  $class = str_replace('_', '/', $class);
  $class = str_replace('\\', '/', $class);
  include $class.".php";
}, false);
//connect db
$conn = mysql_connect(DB_HOST, DB_USER, DB_PASSWORD);
if(!$conn)
	die('mysql connection error.'.mysql_error());
mysql_select_db(DB_NAME);

最後index.php就只要引用bootstrap.php就可以:

<?php
require 'bootstrap.php';
if(isset($_SESSION['user']['account'])) {
	$member = true;
	$name = $_SESSION['user']['name'];
} else $member = false;
if(isset($_SESSION['msg'])) {
	$message = mysql_real_escape_string($_SESSION['msg']);
	unset($_SESSION['msg']);
}
$sql = "SELECT f.*,count(a.forums_id) AS count  FROM forums f LEFT JOIN articles a ON a.forums_id = f.id GROUP BY a.forums_id ORDER BY f.id";
$result = mysql_query($sql, $conn);
$count = 0;
$data = array();
while($row = mysql_fetch_array($result)) {
	if($count%2==0) {
		$style = "#EEFFEE";
	} else {
		$style = "#FFFFFF";
	}
	$sql = "SELECT * FROM articles WHERE forums_id=".$row['id']." ORDER BY id DESC LIMIT 1";
	$result1 = mysql_query($sql, $conn);
	$row1 = mysql_fetch_array($result1);
	$data[] = array('id'=>$row['id'], 'name'=>$row['name'], 'count'=>$row['count'], 'title'=>$row1['title'], 'style'=>$style);
	$count++;
}
#include 'views/index.php';
Twig_Autoloader::register();
$loader = new Twig_Loader_Filesystem('views');
$twig = new Twig_Environment($loader, array('cache'=>'cache'));
$template = $twig->loadTemplate('index.html');
echo $template->render(array(
	'member'=>$member,
	'data'=>$data,
	'name'=>$name,
	'message'=>$message
));

整理完以後,先確定程式會動,然後再來進行下一步。這一步是把樣板引擎需要的動作進一步抽象化,寫出一個View類別,之後具體的樣板引擎可以抽換,而不需要修改主程式。

還是先來觀察一下使用樣板引擎時,必要的動作。其實歸納一下就知道,除了樣板引擎自身的設定,大體上還需要用幾個步驟來操作:

  1. 指定要使用的樣板
  2. 把要在view使用的資料丟給他。
  3. 輸出結果或是返回結果字串

另外,為了要能使用參數來抽換樣板引擎,直接在View類別寫一個工廠方法,讓他回傳實作的類別。為了管理方便,在開設計View時,也把namespace加進去了。首先來看一個簡單的View實作:

<?php
namespace Fillano\Core;

abstract class View
{
	public abstract function asign($datas = array());
	public abstract function render($template="", $ext);
	public abstract function fetch($template="", $ext);
	public static function getInstance($path) {
		if (!defined('TEMPLATE_ENGINE')) {
			die('Please specify a template engine in config.php');
		}
		$class = "Fillano\\Core\\".TEMPLATE_ENGINE;
		return new $class($path);
	}
}

要使用這個類別,還需要在設定檔中指定實作類別的名稱。其實這樣的依賴關係是比較差的設計,不過先讓他會動就好,反正還是有達到可抽換實作的目的。更好(?)的作法,可能要使用IoC/DI容器就是了...這個比較見仁見智XD

然後實作出一個TwigView類別,裡面包了Twig,用assign/render/fetch來操作:

<?php
namespace Fillano\Core;

class TwigView
{
	private $engine;
	private $datas;
	public function __construct($path)
	{
		$this->path = $path;
		\Twig_Autoloader::register();
		$loader = new \Twig_Loader_Filesystem($path);
		$this->engine = new \Twig_Environment($loader, array('cache'=>'cache'));
	}
	public function assign($datas) {
		$this->datas = $datas;
	}
	public function render($template, $ext) {
		echo $this->fetch($template, $ext);
	}
	public function fetch($template, $ext) {
		$file = implode('/', explode('_', $template)).'.'.$ext;
		$twig = $this->engine->loadTemplate($file);
		return $twig->render($this->datas);
	}
}

為了讓工廠方法找到實作類別,所以在config.php再加上一個設定:

<?php
define('DB_HOST','localhost');
define('DB_USER','root');
define('DB_PASSWORD', '');
define('DB_NAME', 'myforum');
define('CLASS_DIR', 'class/');
define('TEMPLATE_ENGINE', 'TwigView');

最後,把index.php中的程式碼改掉:

<?php
require 'bootstrap.php';
if(isset($_SESSION['user']['account'])) {
	$member = true;
	$name = $_SESSION['user']['name'];
} else $member = false;
if(isset($_SESSION['msg'])) {
	$message = mysql_real_escape_string($_SESSION['msg']);
	unset($_SESSION['msg']);
}
$sql = "SELECT f.*,count(a.forums_id) AS count  FROM forums f LEFT JOIN articles a ON a.forums_id = f.id GROUP BY a.forums_id ORDER BY f.id";
$result = mysql_query($sql, $conn);
$count = 0;
$data = array();
while($row = mysql_fetch_array($result)) {
	if($count%2==0) {
		$style = "#EEFFEE";
	} else {
		$style = "#FFFFFF";
	}
	$sql = "SELECT * FROM articles WHERE forums_id=".$row['id']." ORDER BY id DESC LIMIT 1";
	$result1 = mysql_query($sql, $conn);
	$row1 = mysql_fetch_array($result1);
	$data[] = array('id'=>$row['id'], 'name'=>$row['name'], 'count'=>$row['count'], 'title'=>$row1['title'], 'style'=>$style);
	$count++;
}
$view = Fillano\Core\View::getInstance('views');
$view->assign(array(
	'member'=>$member,
	'data'=>$data,
	'name'=>$name,
	'message'=>$message
));
$view->render('index', 'html');

就這樣,大概花了一個多小時,就把之前使用的Twig包裝成一個View類別,設計好統一的Facade介面來操作,如果將來要改用其他的樣板引擎,就照這樣的Facade把它包起來就可以使用,需要改的只有設定檔。

改完以後再測試一下,嗯...雖然中途跌倒幾次,不過總算可以操作,畫面也沒問題。

======

View的部份就先加工到這裡。一路上其實只改了index.php這一支程式,但是只要index.php可以這樣改,其他部分也可以這樣逐步改善。

不過即使把View整個從程式抽離,程式的邏輯還是混在一起,無法做單元測試來確保商業邏輯的正確性。所以,下一步還是先來從目前的程式中把Model抽出來。中途有時間的話,也考慮一下資料庫操作的抽象化。至於Controller...就擺在最後吧。


上一篇
逐步提昇PHP技術能力 - 逐步改善軟體架構 - 使用樣板引擎
下一篇
逐步提昇PHP技術能力 - 逐步改善軟體架構 - 用DAO初步分離資料邏輯
系列文
逐步提昇PHP技術能力30

尚未有邦友留言

立即登入留言