溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么通過PHP實現(xiàn)視圖模板引擎的解析和渲染

發(fā)布時間:2021-11-25 15:33:31 來源:億速云 閱讀:130 作者:iii 欄目:大數(shù)據(jù)

這篇文章主要講解了“怎么通過PHP實現(xiàn)視圖模板引擎的解析和渲染”,文中的講解內(nèi)容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“怎么通過PHP實現(xiàn)視圖模板引擎的解析和渲染”吧!

0、引言

上篇教程學院君給大家簡單介紹了什么是 MVC 設計模式,并演示了如何基于原生 PHP 代碼編寫簡單的 HTTP 控制器,控制器對應 MVC 模式中的 C(Controller),今天,我們一起來看下 MVC 模式中另一個模塊 —— 視圖(View,對應 MVC 模式中的 V),并且基于原生 PHP 代碼實現(xiàn)簡單的視圖模板引擎。

在此之前,我們的視圖渲染實現(xiàn)比較簡單粗暴,就是直接通過 include 語句引入對應的 PHP 視圖模板,然后在當前作用域內(nèi)有效的變量會在引入的視圖模板中生效,以博客應用首頁為例,對應的視圖引入代碼是這樣的(代碼位于 HomeController.php 中):

public function index()
{
    $albums = $this->connection->table('albums')->selectAll();
    $pageTitle = $siteName = $this->container->resolve('app.name');
    $siteUrl = $this->container->resolve('app.url');
    $siteDesc = $this->container->resolve('app.desc');
    include __DIR__  . "/../../../views/home.php";
}
 

當前控制器方法中設置的變量在 home.php 視圖模板中可以直接使用,因為 include 的本質(zhì)就是把對應的 PHP 腳本導入到當前位置。

在 PHP 中,之所以可以直接這樣渲染 HTML 視圖,得益于 PHP 腳本和 HTML 文檔可以混合編程,PHP 本身就看作是一種視圖模板引擎,而不需要像其他語言那樣(比如 Java、Go、Python),要引入額外的視圖模板語言才能在 HTML 文檔中動態(tài)引入變量進行渲染。

雖然 PHP 生態(tài)也提供了很多第三方擴展包作為獨立的視圖模板引擎,以便以工程化的方式構建更加復雜的應用,比如 Smarty、twig、Blade 等,不過這里為了簡化系統(tǒng),我們直接使用 PHP 本身作為 HTML 視圖的模板語言。

不過為了讓上述視圖渲染實現(xiàn)代碼更加優(yōu)雅、便于維護和擴展,我們以面向對象風格的代碼對其進行重構,并且將其調(diào)整為支持其他模板引擎。 

1、編寫 PHP 視圖引擎實現(xiàn)代碼

我們在 app 目錄下新建一個 view 子目錄,用于保存視圖模板解析和渲染相關代碼,然后在 view 目錄下新建 engine 子目錄,用來保存視圖模板引擎代碼。

engine 目錄下新建一個 ViewEngine 接口作為所有 PHP 模板引擎實現(xiàn)的契約:

<?php
namespace App\View\Engine;

interface ViewEngine
{
    public function extract($path, $data): string;
}
 

接下來,在同級目錄下新建一個實現(xiàn)了 ViewEngine 接口的 PhpEngine 類作為 PHP 原生視圖模板引擎的實現(xiàn):

<?php
namespace App\View\Engine;

class PhpEngine implements ViewEngine
{
    public function extract($path, $data): string
    {
        ob_start();

        extract($data, EXTR_SKIP);

        try {
            include $path;
        } catch (\Throwable $e) {
            throw new \Exception('解析視圖模板出錯:' . $e->getMessage());
        }

        return ltrim(ob_get_clean());
    }
}
 

PhpEngineextract 實現(xiàn)中,我們通過 PHP 自帶的輸出控制函數(shù) ob_start 打開輸出控制緩沖,然后調(diào)用 extract 函數(shù)將從外部傳入的數(shù)組變量導入當前符號表(即在當前作用域內(nèi)以數(shù)組鍵名作為變量名,以對應鍵值作為變量值),接下來調(diào)用 include 引入指定路徑的視圖文件到緩沖區(qū),這樣,從外部傳入的變量就可以在視圖文件中生效了,如果引入文件或者變量解析出錯,則拋出異常,最后,我們調(diào)用 ob_get_clean 函數(shù)將當前緩沖區(qū)內(nèi)執(zhí)行過 PHP 腳本代碼并完成變量渲染的視圖文件內(nèi)容(標準的 HTML 文檔)以字符串形式返回,后續(xù)這部分內(nèi)容將作為 HTTP 響應的響應實體返回給客戶端。 

2、編寫視圖管理器代碼

以上只是最底層視圖模板引擎解析 PHP 變量、返回 HTML 格式視圖文件內(nèi)容的實現(xiàn)代碼,如果你想要基于第三方 PHP 引擎擴展包構建更復雜的自定義模板引擎解析實現(xiàn),可以自行實現(xiàn) ViewEngine 接口并完成相應的視圖模板解析邏輯。

接下來,我們在 view 目錄下編寫上層的視圖模板引擎管理器和相應的服務提供者。前者用來管理不同的模板引擎實現(xiàn)類,根據(jù)應用配置獲取當前使用的模板引擎,并完成視圖響應的渲染,后者用來將這個視圖管理器實例注冊到服務容器中,以便在應用代碼中需要渲染視圖模板的時候從服務容器獲取并使用。

首先來編寫視圖管理器,在 view 目錄下新建 View.php 并初始化代碼如下:

<?php
namespace App\View;

use App\Http\Response;
use App\View\Engine\ViewEngine;

class View
{
    /**
     * @var ViewEngine
     */
    protected $engine;
    /**
     * @var string
     */
    protected $basePath;

    public function __construct(ViewEngine $engine, $basePath)
    {
        $this->engine = $engine;
        $this->basePath = $basePath;
    }

    public function render($path, $data)
    {
        $response = new Response();
        try {
            $content = $this->getContent($path, $data);
        } catch (\Throwable $e) {
            $response->setStatusCode(500);
            $response->setContent($e->getMessage());
            $response->send();
            return;
        }
        $response->setContent($content);
        $response->setStatusCode(200);
        $response->send();
    }

    protected function getContent($path, $data): string
    {
        $path = $this->basePath . $path;
        if (!file_exists($path)) {
            throw new \Exception('對應的視圖文件不存在!');
        }
        return $this->engine->extract($path, $data);
    }
}
 

在視圖管理器 View 類中,定義了兩個屬性,$engine 表示模板引擎對象,basePath 則表示視圖模板的根路徑,這兩個屬性都是在實例化 View 時從外部傳入的,我們馬上會看到實例化 View 的代碼。

重點看下 render 方法,該方法用于被上層代碼調(diào)用完成視圖模板的解析和渲染,在這個方法中,我們通過 getContent 方法調(diào)用系統(tǒng)當前使用的模板引擎實例 $engineextract 方法(比如當前使用的是 PhpEngine,則調(diào)用該對象的 extract 方法)完成視圖模板的解析和 PHP 變量替換,然后將其返回的字符串格式 HTML 文檔作為 Response 對象的響應實體隨著  $response->send() 方法一起發(fā)送給客戶端,完成視圖渲染的閉環(huán),如果解析視圖模板過程中出錯(比如視圖文件不存在,變量解析出錯),則返回 500 響應。 

3、編寫視圖服務提供者代碼

接下來,在 view 目錄下新建 ViewProvider.php,并編寫服務提供者實現(xiàn)代碼如下(其用途前面已經(jīng)提及):

<?php
namespace App\View;

use App\Core\Container;
use App\View\Engine\PhpEngine;
use App\View\Engine\ViewEngine;

class ViewProvider
{
    /**
     * @var Container
     */
    protected $container;

    public function __construct($container)
    {
        $this->container = $container;
    }

    public function register()
    {
        $this->container->bind('view', function () {
            $config = $this->container->resolve('view.engine');
            $method = 'register' . ucfirst($config) . 'Engine';
            if (!method_exists($this, $method)) {
                throw new \Exception('對應的視圖模板引擎暫不支持!');
            }
            $engine = call_user_func([$this, $method]);
            $basePath = $this->container->resolve('view.path');
            return new View($engine, $basePath);
        });
    }

    public function registerPhpEngine()
    {
        return new PhpEngine();
    }
}
 

我們在其 register 方法實現(xiàn)中將 View 對象實例綁定到全局服務容器中,在初始化 View 對象的時候,需要先初始化 ViewEngine 對象,這里,我們通過配置文件配置系統(tǒng)使用的模板引擎:

'view.engine' => 'php',  // 視圖模板引擎
 

目前只有 PhpEngine 一個實現(xiàn),所以我們將 view.engine 配置為 php,如果后續(xù)支持其他模板引擎,在實現(xiàn)了對應的引擎類 XxxEngine 后,還要在這里實現(xiàn)對應的注冊方法 registerXxxEngine,最后在配置文件中配置 view.engine 值為 xxx 才可以使其生效。

另外,我們還在 app/config/app.php 新增配置 view.path 作為視圖模板的根路徑:

'view.path' => __DIR__ . '/../../views/',  // 視圖模板根路徑 
 

有了模板引擎實例和視圖模板根路徑后,就可以將它們傳入視圖管理器 View 的構造函數(shù)對其進行初始化了。

代碼實現(xiàn)比較簡單,不再逐一解釋了。

最后,還要在 app/config/app.configproviders 中注冊視圖提供者:

'providers' => [
    \App\Store\StoreProvider::class,
    \App\Printer\PrinterProvider::class,
    \App\View\ViewProvider::class,
]
 

以便在應用啟動時調(diào)用其 register 方法注冊 View 實例。

另外,為了讓新增的 view.engineview.path 配置生效,需要在 app/bootstrap.phpinitConfig 方法中新增這兩個配置的注冊:

function initConfig(Container $container) {
    ...
    $container->bind('view.engine', $config['view.engine']);
    $container->bind('view.path', $config['view.path']);
}
   

4、重構配置文件及注冊邏輯

為了免于后續(xù)新增配置需要頻繁修改這里的代碼,還可以通過 foreach 循環(huán)來重構這段注冊代碼,為此,我們需要先調(diào)整 app/config/app.config

<?php

return [
    'app' => [
        'name' => '學院君的個人網(wǎng)站',
        'desc' => '讓學習與進取者不再孤獨',
        'url'  => 'https://xueyuanjun.com',
        'store' => [
            'default' => 'mysql',
            'drivers' => [
                'array' => [

                ],
                'mysql' => [
                    'host' => '127.0.0.1',
                    'port' => 3306,
                    'dbname' => 'blog',
                    'charset' => 'utf8mb4',
                    'user' => 'root',
                    'password' => 'root',
                ]
            ]
        ],
        'editor' => 'markdown',  // 支持html和markdown
        'providers' => [
            \App\Store\StoreProvider::class,
            \App\Printer\PrinterProvider::class,
            \App\View\ViewProvider::class,
        ]
    ],
    'view' => [
        'engine' => 'php',  // 視圖模板引擎
        'path' => __DIR__ . '/../../views/',  // 視圖模板根路徑
    ]
];
 

這樣一來,可讀性更好,而且隨著應用復雜度增高,配置項增多,也便于后期維護和拆分。

然后重構 bootstrap.php 中的 initConfig 方法實現(xiàn)如下:

function initConfig(Container $container) {
    $configs = require __DIR__ . '/config/app.php';
    foreach ($configs as $module => $config) {
        foreach ($config as $key => $val) {
            $container->bind($module . '.' . $key, $val);
        }
    }
}
   

5、在控制器中使用新的視圖渲染方法

最后,我們需要重構所有控制器方法代碼,使用新的視圖模板渲染方法返回視圖響應。

在此之前,先要在控制器基類 Controller 中新增一個 $view 屬性,然后在構造函數(shù)中對其進行初始化:

<?php
namespace App\Http\Controller;

use App\Core\Container;
use App\Http\Request;
use App\Store\StoreContract;
use App\View\View;

class Controller
{
    ...

    /**
     * @var View
     */
    protected $view;

    public function __construct()
    {
        ...
        $this->view = $this->container->resolve('view');
    }
}
 

接下來在各個控制器中重構視圖渲染代碼,將原來通過 include 語句引入視圖模板改為通過 $this->view->render($path, $data) 返回視圖響應:

// 首頁:HomeController
class HomeController extends Controller
{
    public function index()
    {
        ...
        $this->view->render('home.php', [
            'albums' => $albums,
            'pageTitle' => $pageTitle,
            'siteName' => $siteName,
            'siteDesc' => $siteDesc,
            'siteUrl' => $siteUrl
        ]);
    }
}

// 專輯頁:AlbumController
class AlbumController extends Controller
{
    public function list()
    {
        ...
        $this->view->render('album.php', compact('album', 'posts', 'pageTitle', 'siteName', 'siteUrl'));
    }
}

// 文章頁:‘
class PostController extends Controller
{
    public function show()
    {
        ...
        $this->view->render('post.php', compact('post', 'album', 'pageTitle', 'siteUrl'));
    }
}
 

可以看到 render 方法的第一個參數(shù)是視圖模板路徑,由于根路徑已經(jīng)通過配置文件設置并在底層生效,所以只需要傳入相對根路徑的相對路徑即可,第二個參數(shù)是數(shù)組格式的、需要傳入視圖模板的 PHP 變量,這些變量可以通過數(shù)組形式定義傳入,也可以通過 compact 函數(shù)組合當前作用域內(nèi)的變量傳入(以變量名作為鍵,變量值作為值構建關聯(lián)數(shù)組,組合結果和前一種形式完全一樣)。

6、驗證重構結果

至此,我們就完成了視圖模板引擎的編寫和所有代碼重構工作,運行 composer dump-auto 讓上述代碼修改引起的命名空間與目錄映射變更生效,在瀏覽器訪問應用所有頁面都正常,則表示代碼重構成功。

感謝各位的閱讀,以上就是“怎么通過PHP實現(xiàn)視圖模板引擎的解析和渲染”的內(nèi)容了,經(jīng)過本文的學習后,相信大家對怎么通過PHP實現(xiàn)視圖模板引擎的解析和渲染這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

php
AI