利用 Composer 完善本身的 PHP 框架(一)——視圖裝載

原文發表在個人我的網站:利用 Composer 完善本身的 PHP 框架(一)——視圖裝載php


本教程示例代碼見 https://github.com/johnlui/My-First-Framework-based-on-Composerhtml


回顧

通過了上一個 系列教程 《利用 Composer 一步一步構建本身的 PHP 框架》,咱們組建了一個具備 路由MVC 架構ORM 功能的基礎框架 MFFC。接下來咱們繼續完善這個項目。前端

咱們先從目前用着 最不爽 的地方——視圖裝載下手。咱們將封裝一個視圖裝載類,讓它來幫咱們裝載視圖,並把變量傳遞進視圖。這個類將只暴露出來幾個簡單的接口,讓咱們在控制器裏面用的爽,讓咱們一邊寫代碼一邊笑。真的笑,笑出聲。:-Dlaravel


正文

構思

視圖裝載類要作的工做其實很簡單:git

  1. 根據視圖名稱找到視圖文件,支持文件夾
  2. 更加方便,更加優雅地把變量的值傳遞進視圖

本文中咱們將不會不引入模板引擎,只作裝載文件和傳遞變量的功能。github

基礎準備

咱們要引入視圖裝載器,這就正式打開了組件化的大門,因此咱們須要作一些準備工做。json

1. 啓動流程組件化bootstrap

public/index.php 裏面的代碼分離一部分到啓動器(bootstrap),新建 MFFC/bootstrap.php 文件:後端

<?php
use Illuminate\Database\Capsule\Manager as Capsule;

// 定義 BASE_PATH
define('BASE_PATH', __DIR__);

// Autoload 自動載入
require BASE_PATH.'/vendor/autoload.php';

// Eloquent ORM
$capsule = new Capsule;
$capsule->addConnection(require BASE_PATH.'/config/database.php');
$capsule->bootEloquent();

修改 public/index.php 爲:瀏覽器

<?php

// 定義 PUBLIC_PATH
define('PUBLIC_PATH', __DIR__);

// 啓動器
require PUBLIC_PATH.'/../bootstrap.php';

// 路由配置、開始處理
require BASE_PATH.'/config/routes.php';

這時候咱們就完成了 入口文件 和 啓動器 的分離,並定義了兩個全局常量 BASE_PATHPUBLIC_PATH


在這裏咱們須要特別注意一點:「引入路由配置文件」 這一步並不僅是簡單地引入了一個配置文件,路由文件的最後一行 Macaw::dispatch(); 纔是 真正執行某個控制器中某個 function 的地方,全部準備條件都應該在載入路由文件以前完成,例如 Eloquent 的初始化,還有之後咱們要使用的 Composer 包的初始化等等。


2. 引入錯誤頁面提示組件

咱們選擇 filp/whoops 做爲咱們錯誤提示組件包。

修改 composer.json

"require": {
  "codingbean/macaw": "dev-master",
  "illuminate/database": "*",
  "filp/whoops": "*"
},

運行 composer update,而後在 bootstrap.php 的最後添加:

// whoops 錯誤提示
$whoops = new \Whoops\Run;
$whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler);
$whoops->register();

刷新 http://127.0.0.1:81 ,你獲得的應該仍是這個頁面:

pic

下面咱們將增長路由配置中 無匹配項 的錯誤頁面,修改 config/routes.php

<?php

use NoahBuscher\Macaw\Macaw;

Macaw::get('', 'HomeController@home');

Macaw::get('fuck', function() {
  echo "成功!";
});

Macaw::$error_callback = function() {
  throw new Exception("路由無匹配項 404 Not Found");
};

Macaw::dispatch();

如今訪問一個隨意輸入的 URL,例如 http://127.0.0.1:81/asd ,咱們會看到如下畫面:

pic

是否是有一種很熟悉的感受!

很不幸,這個錯誤提示包正是 Laravel 採用的那個,因此,咱們可愛的 MFFC 框架在長大之後仍是成了 Laravel 的樣子。%>_<%

實現裝載器

完成基礎準備之後咱們正式開始製造視圖裝載器。

視圖裝載器是一個可插拔組件,咱們應該把全部可插拔組件所有歸到一處,在 MFFC 中建議放在 MFFC/services 下。

CI 框架提供的基礎組件庫叫 helpers,Laravel 使用 illuminate/support 包提供一些可重用的系統函數。實際上 「illuminate/support」 這個包已經被咱們的 ORM 包 「illuminate/database」 依賴了,如今 MFFC 框架裏面已經能夠直接使用。,這個包的中文文檔見:http://laravel-china.org/docs/helpers

咱們並無像 CI 框架那樣把視圖裝載器放到系統核心,有如下兩個緣由:

  1. 基於命名空間與自動加載的調用方式更加節省資源
  2. 在移動互聯網和大前端愈演愈烈的時代,後端愈來愈 API 化、 json 化。不少時候都不到視圖,沒有必要再增長無畏的消耗。

下面開始着手實現視圖裝載器。

新建 MFFC/services 文件夾,並修改 composer.json 把這個文件夾下的全部類自動納入根命名空間:

"autoload": {
  "classmap": [
    "app/controllers",
    "app/models",
    "services"
  ]
}

新建 services/View.php 文件,內容以下:

<?php
/**
* \View
*/
class View
{
  const VIEW_BASE_PATH = '/app/views/';

  public $view;
  public $data;

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

  public static function make($viewName = null)
  {
    if ( ! $viewName ) {
      throw new InvalidArgumentException("視圖名稱不能爲空!");
    } else {

      $viewFilePath = self::getFilePath($viewName);
      if ( is_file($viewFilePath) ) {
        return new View($viewFilePath);
      } else {
        throw new UnexpectedValueException("視圖文件不存在!");
      }
    }
  }

  public function with($key, $value = null)
  {
    $this->data[$key] = $value;
    return $this;
  }

  private static function getFilePath($viewName)
  {
    $filePath = str_replace('.', '/', $viewName);
    return BASE_PATH.self::VIEW_BASE_PATH.$filePath.'.php';
  }

  public function __call($method, $parameters)
  {
    if (starts_with($method, 'with'))
    {
      return $this->with(snake_case(substr($method, 4)), $parameters[0]);
    }

    throw new BadMethodCallException("方法 [$method] 不存在!.");
  }
}

運行 composer dump-autoload,完成之後,咱們就能夠在控制器中直接調用這個類了。

修改 controllers/HomeController.php

<?php
/**
* \HomeController
*/
class HomeController extends BaseController
{

  public function home()
  {
    $this->view = View::make('home')->with('article',Article::first())
                                    ->withTitle('MFFC :-D')
                                    ->withFuckMe('OK!');
  }
}

修改 controllers/BaseController.php

<?php
/**
* \BaseController
*/
class BaseController
{

  protected $view;

  public function __construct()
  {
  }

  public function __destruct()
  {
    $view = $this->view;
    if ( $view instanceof View ) {
      extract($view->data);
      require $view->view;
    }
  }
}

修改 app/views/home.php

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title><?php echo $title ?></title>
</head>
<body>
  <div class="article">
    <h1><?php echo $article['title'] ?></h1>
    <div class="content">
      <?php echo $article['content'] ?>
    </div>
  </div>
  <ul class="fuckme">
    <li>Fuck Me !</li>
    <li>
      <?php echo $fuck_me ?>
    </li>
  </ul>
</body>
</html>

刷新,你將看到如下頁面:

pic

至此,視圖裝載器實現完成。


下面我大體說一下設計視圖裝載器的基本思路:

  1. 這個視圖裝載器類模仿了 Laravel 的 View 類,它實現了一個靜態方法 make,接受視圖名稱做爲參數,以 . 做爲目錄的間隔符。
  2. make 靜態方法會檢查視圖名稱是否爲空,檢查視圖文件是否存在,並給出相應的異常。這就是咱們引入異常處理包的緣由。
  3. 視圖名稱合法且文件存在時,實例化一個 View 類的對象,返回。
  4. 使用 with('key', $value) 或者優雅的 withKey($value) 來給這個 View 對象插入要在視圖裏調用的變量。withFuckMe($value) 將採用蛇形命名法被轉化成 $fuck_me 供視圖使用。
  5. 最終組裝好的 View 對象會被賦給 HomeController 的成員變量 $view,這個變量是從 BaseController 中繼承得來。
  6. 父類 BaseController 中的析構函數 __destruct() 將在 function home() 執行完成後處理這個成員變量:extract 出視圖要用到的變量,require 視圖文件,將最終運算結果發送給瀏覽器,流程結束。

下一步:利用 Composer 完善本身的 PHP 框架(二)——發送郵件

相關文章
相關標籤/搜索