利用 Composer 完善自己的 PHP 框架(一)——视图装载
本教程示例代码见 https://github.com/johnlui/My-First-Framework-based-on-Composer
回顾
经过了上一个 系列教程 《利用 Composer 一步一步构建自己的 PHP 框架》 ,我们组建了一个具有 路由 、 MVC 架构 和 ORM 功能的基础框架 MFFC 。接下来我们继续完善这个项目。
我们先从目前用着 最不爽 的地方——视图装载下手。我们将封装一个视图装载类,让它来帮我们装载视图,并把变量传递进视图。这个类将只暴露出来几个简单的接口,让我们在控制器里面用的爽,让我们一边写代码一边笑。真的笑,笑出声。:-D
正文
构思
视图装载类要做的工作其实很简单:
1. 根据视图名称找到视图文件,支持文件夹
2. 更加方便,更加优雅地把变量的值传递进视图
本文中我们将不会不引入模板引擎,只做装载文件和传递变量的功能。
基础准备
我们要引入视图装载器,这就正式打开了组件化的大门,所以我们需要做一些准备工作。
启动流程组件化
将 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_PATH 和 PUBLIC_PATH 。
在这里我们需要特别注意一点:“引入路由配置文件” 这一步并不只是简单地引入了一个配置文件,路由文件的最后一行 Macaw::dispatch(); 才是 真正执行某个控制器中某个 function 的地方,所有准备条件都应该在载入路由文件之前完成,例如 Eloquent 的初始化,还有以后我们要使用的 Composer 包的初始化等等。
引入错误页面提示组件
我们选择 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 ,你得到的应该还是这个页面:

下面我们将增加路由配置中 无匹配项 的错误页面,修改 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 ,我们会看到以下画面:

是不是有一种很熟悉的感觉!
很不幸,这个错误提示包正是 Laravel 采用的那个,所以,我们可爱的 MFFC 框架在长大以后还是成了 Laravel 的样子。%>_<%
实现装载器
完成基础准备以后我们正式开始制造视图装载器。
视图装载器是一个可插拔组件,我们应该把所有可插拔组件全部归到一处,在 MFFC 中建议放在 MFFC/services 下。
CI 框架提供的基础组件库叫 helpers ,Laravel 使用 illuminate/support 包提供一些可重用的系统函数。实际上 “illuminate/support” 这个包已经被我们的 ORM 包 “illuminate/database” 依赖了,现在 MFFC 框架里面已经可以直接使用。,这个包的中文文档见:http://laravel-china.org/docs/helpers
我们并没有像 CI 框架那样把视图装载器放到系统核心,有以下两个原因:
- 基于命名空间与自动加载的调用方式更加节省资源
- 在移动互联网和大前端愈演愈烈的时代,后端越来越 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>
刷新,你将看到以下页面:

至此,视图装载器实现完成。
下面我大致说一下设计视图装载器的基本思路:
- 这个视图装载器类模仿了 Laravel 的 View 类,它实现了一个静态方法 make ,接受视图名称作为参数,以 . 作为目录的间隔符。
- make 静态方法会检查视图名称是否为空,检查视图文件是否存在,并给出相应的异常。这就是我们引入异常处理包的原因。
- 视图名称合法且文件存在时,实例化一个 View 类的对象,返回。
- 使用 with('key', $value) 或者优雅的 withKey($value) 来给这个 View 对象插入要在视图里调用的变量。 withFuckMe($value) 将采用蛇形命名法被转化成 $fuck_me 供视图使用。
- 最终组装好的 View 对象会被赋给 HomeController 的成员变量 $view ,这个变量是从 BaseController 中继承得来。
- 父类 BaseController 中的析构函数 __destruct() 将在 function home() 执行完成后处理这个成员变量: extract 出视图要用到的变量, require 视图文件,将最终运算结果发送给浏览器,流程结束。
利用 Composer 完善自己的 PHP 框架(一)——视图装载的更多相关文章
- 利用 Composer 完善自己的 PHP 框架(二)——发送邮件
本教程示例代码见 https://github.com/johnlui/My-First-Framework-based-on-Composer 回顾 上一篇文章中,我们手工建造了一个简易的视图加载器 ...
- 【完结】利用 Composer 完善自己的 PHP 框架(三)——Redis 缓存
本教程示例代码见 https://github.com/johnlui/My-First-Framework-based-on-Composer 回顾 上两篇文章中我们完成了 View 视图加载类和 ...
- 构建自己的PHP框架(视图装载)
完整项目地址:https://github.com/Evai/Aier 视图装载类要做的工作其实很简单: 1. 根据视图名称找到视图文件,支持文件夹 2. 更加方便,更加优雅地把变量的值传递进视图 本 ...
- 利用 Composer 一步一步构建自己的 PHP 框架(一)——基础准备
『Composer 一统天下的时代已经到来!』——白岩松 “一个时代结束了,另一个时代开始了.” Framework Interoperability Group(框架可互用性小组),简称 FIG,成 ...
- 利用jQuery扩展接口为jQuery框架定义了两个自定义函数,然后调用这两个函数
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- php 利用composer引用第三方类库构建项目
经常看到各种开源库推荐使用 composer 安装代码,却总是看不懂怎么用composer, 这几天静下心来学习了composer的使用,发现这可真是一个好东西,先贴上一个讲的很棒的视频教程: PHP ...
- 【实践】如何利用tensorflow的object_detection api开源框架训练基于自己数据集的模型(Windows10系统)
如何利用tensorflow的object_detection api开源框架训练基于自己数据集的模型(Windows10系统) 一.环境配置 1. Python3.7.x(注:我用的是3.7.3.安 ...
- laravel5.5框架中视图间如何共享数据?视图间共享数据的两种方法
laravel框架中视图间共享数据有两种,一种是用视图门面share()方法实现,另一种是用视图门面composer() 方法实现,那么,两种方法的实现究竟是怎样的呢?让我们来看一看接下来的文章内容. ...
- Laravel5.4框架中视图共享数据的方法详解
本文实例讲述了Laravel5.4框架中视图共享数据的方法.分享给大家供大家参考,具体如下: 每个人都会遇到这种情况:某些数据还在每个页面进行使用,比如用户信息,或者菜单数据,最基本的做法是在每个视图 ...
随机推荐
- CentsOS7 网络自动启动
虚拟机中安装完成CentOS7后,网络总是需要手工启动才可使用,设置为自动连接的方式如下: cd /etc/sysconfig/network-scripts/ls #找到类似的文件:ifcfg-et ...
- PHP命名规范【转】
[转]谭博的个人网站 [类] 1.类名与类文件名采用驼峰式且首字母大写 2.类私有属性和私有方法名称以下划线开头 3.方法名使用驼峰式 [变量] 变量名使用小写字母加下划线 [函数] 函数名使用小 ...
- 【Hadoop代码笔记】Hadoop作业提交之Child启动reduce任务
一.概要描述 在上篇博文描述了TaskTracker启动一个独立的java进程来执行Map任务.接上上篇文章,TaskRunner线程执行中,会构造一个java –D** Child address ...
- 并行开发——Parallel的使用 -摘自网络
随着多核时代的到来,并行开发越来越展示出它的强大威力,像我们这样的码农再也不用过多的关注底层线程的实现和手工控制, 要了解并行开发,需要先了解下两个概念:“硬件线程”和“软件线程”. 1. 硬件线程 ...
- POJ 3026 Borg Maze(bfs+最小生成树)
Borg Maze Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 6634 Accepted: 2240 Descrip ...
- HDU 1702 http://acm.hdu.edu.cn/showproblem.php?pid=1702
#include<stdio.h> #include<string.h> #include<queue> #include<stack> #define ...
- [iOS 多线程 & 网络 - 1.1] - 多线程NSThread
A.NSThread的基本使用 1.创建和启动线程 一个NSThread对象就代表一条线程创建.启动线程NSThread *thread = [[NSThread alloc] initWithTar ...
- HDU1398Square Coins(母函数)
母函数介绍见另一篇随笔HDU1028Ignatius and the Princess III(母函数) #include<iostream> #include<stdio.h> ...
- UVaLive 7363 A Rational Sequence (二叉树)
题意:给定一个二叉树,并对每一个进行编号和规定,现在给你一个值,问你是第几个. 析:这个题,我想了好久才想出来,这个真是数据结构练的太差了,不够扎实,这个题,应该从下向上推,如果分子大于分母,那么这个 ...
- SQL NULL Values
NULL代表缺失的.未知的数据.表的列值默认是NULL.如果某个表的某个列不是NOT NULL的,那么当我们插入新纪录.更新已存在的记录时,可以不用为此列赋值,这意味着那个列保存为NULL值. NUL ...