自己动手搭建一个属于自己的MVC框架,实现极简主义设计。风格类似Sinatra。
本仓库是一个示例代码,如果有兴趣,可以关注作者公众号或者加群讨论。
说起composer,就要讲PSR规范,也就是FIG。FIG 最初由几位知名 PHP 框架开发者发起,在吸纳了许多优秀的大脑和强健的体魄后,提出了 PSR-0 到 PSR-4 五套 PHP 非官方规范:
- PSR-0 (Autoloading Standard) 自动加载标准
- PSR-1 (Basic Coding Standard) 基础编码标准
- PSR-2 (Coding Style Guide) 编码风格向导
- PSR-3 (Logger Interface) 日志接口
- PSR-4 (Improved Autoloading) 自动加载优化标准
Composer 利用 PSR-0 和 PSR-4 以及 PHP5.3 的命名空间构造了一个繁荣的 PHP 生态系统。类似 npm 和 RubyGems,给海量 PHP 包提供了一个异常方便的协作通道。 后期基于composer演化出来的框架,有 Laravel 和 Symfony。 本文也是基于composer创建一个自己的MVC框架。
使用composer初始化项目
composer init使用阿里云的composer仓库
# 在json文件最后添加
"repositories": {
"packagist": {
"type": "composer",
"url": "https://mirrors.aliyun.com/composer/"
}
}安装composer
composer install
开始构建我们自己的路由,路由的选择可以参考github的搜索结果
本教程我们选择简单易上手的noahbuscher/macaw,大家可以去官网上看看。
编辑composer.json文件
# 安装noahbuscher/macaw
require: {
"noahbuscher/macaw": "dev-master"
}
# 更新composer
composer update
使用插件
# 项目目录下新建public/index.php
# 代码如下
<?php
// 定义根目录
define("VINC_PATH", __DIR__);
//引入启动文件
require_once VINC_PATH. '/../vendor/autoload.php';
require_once VINC_PATH . '/../src/Route.php';
# 新建src/Route.php
# 代码如下
<?php
use \NoahBuscher\Macaw\Macaw;
Macaw::get('/', function () {
echo 'Hello world!';
});
# 重新加载下composer类
composer dump-autoload
# 启动php-server
cd public/
php -S 127.0.0.1:5000
打开浏览器,输入127.0.0.1:5000 ,看到Hello world!,表示你安装成功。 更多选项可以参考文档,github有详细使用。
原理梗概 当php引入composer的自动加载文件后,composer会在内存维护一个全量命名空间,类名到文件名的数组。这样我们在使用某个插件功能的时候,会在数组中找到它。 当Macaw::get,使用get的时候,会由Macaw的一个__callstatic() 接收,这个函数接受两个参数,$method 和 $params,前者是具体的 function 名称,在这里就是 get,后者是这次调用传递的参数,即 Macaw::get('/',function(){...}) 中的两个参数,一个是路径,一个是函数处理。 __callstatic() 做的事情就是分别将目标URL(即 ‘/’)、HTTP方法(即 GET)和回调代码压入 $routes、$methods 和 $callbacks 三个 Macaw 类的静态成员变量(数组)中。 最后由Macaw::dispatch()的方法处理。不能直接匹配到的将利用正则进行匹配。
终于我们的框架实现了第一步,已经能通过路由访问不同的页面了。 下面我们要实现的是搭建起来自己的MVC结构。 这就说到了PHP框架另外的价值:
- 确立开发规范以便于多人协作
- 使用 ORM、模板引擎 等工具以提高开发效率。
开始编码了~~
# 新建app文件夹在项目目录,添加controllers、models、views三个文件夹。
# controllers增加两个文件BaseController.php HomeController.php
# BaseController.php设置一些继承的属性
<?php
class BaseController
{
public function __construct()
{ }
}
# HomeController.php
<?php
class HomeController extends BaseController{
public function index(){
echo "this is Vinc!";
}
}
# 修改Route.php,增加路由
Macaw::get('/', 'HomeController@index');
# 修改composer.json,增加自动加载
"autoload": {
"classmap": [
"app/controllers",
"app/models"
],
#重新加载类
composer dump-autoload打开浏览器,输入网址 127.0.0.1:5000 ,你看到配置成功了。我们已经实现了控制器。
配置数据库,实现model文件
# 新建文件 Data.php,这就是操作数据库的model文件。以后介绍使用ORM操作数据库
# 编辑文件,请查看GitHub内容。
# 修改HomeController.php
<?php
class HomeController extends BaseController{
public function index(){
// echo "this is Vinc!";
$db = Data::getIntance();
$sql = "select * from articles limit 0,1";
$list = $db->getAll($sql);
$db->p($list);
}
}
#重新加载类
composer dump-autoload# 数据库的代码
# 创建你自己的数据库。修改你的配置,Data.php文件
CREATE TABLE `articles` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) DEFAULT NULL,
`content` longtext,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `articles` (`id`, `title`, `content`)
VALUES
(1,'标题1','<h3>内容111呀~~</h3><p>参与用户调研,有机会获得200元无门槛代金券!~ O(∩_∩)O</p>'),
(2,'标题2','<h3>内容222呀~~</h3><p>联系我有优惠!~ O(∩_∩)O</p>');
# 打开浏览器,输入网址 127.0.0.1:5000
# 看到打印内容,加载成功。现在已经和数据库连通了。现在MVC的M和C都已经实现了。我们现在实现view
# 添加view目录下的home.php
<?php
foreach ($list as $key => $value) {
echo "<h1>". $value['title'].'</h1>';
echo "<p>". $value['content'].'</p>';
}
# 引入文件到控制器
<?php
class HomeController extends BaseController
{
public function index()
{
// echo "this is Vinc!";
$db = Data::getIntance();
$sql = "select * from articles limit 0,1";
$list = $db->getAll($sql);
require_once VINC_PATH . '/../app/views/home.php';
}
}
# 刷新浏览器MVC框架本质是一种管理代码的格式。现在几乎所有人都是通过学习某个框架来了解 MVC 的。但是一旦脱离了框架,一个页面也写不出。我认为,框架再成熟,也离不开PHP的基本原理和哲学。不管哪种语言都是为了让人脑这样的超低 RAM 的计算机能够制造出远超人脑 RAM 的大型软件。一个框架驱动程序做的时区是这样的,入口文件通过路由调用控制器,控制器调用模型,模型和数据库交互,返回数据给控制器,控制器在调用视图,视图把数据装载进视图显示给用户,流程结束。
本篇集成一个 ORM Composer包 ORM 就是'Object Relational Mapping'=对象关系映射。ORM的出现是为了帮我们把对数据库的操作变得更加地方便。
# 编辑composer.json
"require": {
"php": ">=7.1.3",
"noahbuscher/macaw": "dev-master",
"illuminate/database": "*",
"filp/whoops": "*"
}
# illuminate/database这个是Laravel的ORM 包。我试用了几个著名的ORM,发现还是 Laravel 的 Eloquent 好用!filp/whoops 是我们的错误提示组件包。也是laravel正在使用的。
# 编辑view/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>欢迎使用!</li>
<li>
<?php echo $content ?>
</li>
</ul>
</body>
</html># 编辑BaseController.php
<?php
class BaseController
{
public function __construct()
{ }
public function __destruct()
{
$view = $this->view;
if ($view instanceof View) {
extract($view->data);
require $view->view;
}
}
}
# 编辑HomeController.php
<?php
class HomeController extends BaseController
{
public function index()
{
$this->view = View::make('home')->with('article', Article::first())->withTitle('一个优雅简单的PHP框架--vinc')->withContent('vinc框架');
}
}
# 编辑models/View.php
<?php
class View
{
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 VINC_PATH . '/../app/views/' . $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] 不存在!.");
}
}
# 编辑Article.php
<?php
class Article extends Illuminate\Database\Eloquent\Model
{
public $timestamps = false;
}
# 刷新类文件
composer dump-autoload启动php-server,刷新浏览器,看到内容正常显示。至此,视图装载器实现完成
上面的内容,我们已经搭建出来了自己的框架,有没有觉得很好玩,下面我们就更加进一步。扩充我们的邮件发送系统。
# 修改composer.json 添加
"require": {
"php": ">=7.1.3",
"noahbuscher/macaw": "dev-master",
"illuminate/database": "*",
"filp/whoops": "*",
"nette/mail": "*"
}
# 装载插件
composer update
# 引入插件,增加app/models/Mail.php
# 修改BaseController.php
$mail = $this->mail;
if ($mail instanceof Mail) {
$mailer = new Nette\Mail\SmtpMailer($mail->config);
$mailer->send($mail);
}
# 调用发送邮件,发送邮件需要邮箱开通smtp
$this->mail = Mail::to(['[email protected]', '[email protected]'])->from('yourname <[email protected]>')->title('this is your good news!')->content('<h1>Hello~~</h1>');Redis是一个高性能的 'key-value' 数据库,其'value'支持 'String'、'Map(Hash)'、'list'、'set' 和 'sorted sets',中文翻译为 字符串、字典(哈希,在'世界上最好的语言PHP' 中属于 '数组' 的一部分)、列表、集合和有序集合。我们可以用 Redis 作为高速缓存,存放系统经常需要访问的数据。相比使用文件作为缓存,Redis 拥有更高的性能、更好地可维护性和更强大的操作 API。
我们来扩展redis功能
# 修改composer.json 添加
"require": {
"php": ">=7.1.3",
"noahbuscher/macaw": "dev-master",
"illuminate/database": "*",
"filp/whoops": "*",
"nette/mail": "*",
"predis/predis": "*"
}
# 装载插件
composer update
# 引入插件,增加app/models/Redis.php
# 修改HomeController.php
Redis::set('key', 'value', 5, 's');
echo Redis::get('key');
# 运行一次后将上面一行注释掉,不断刷新看'value'是否会在设定的时间结束后从页面上消失。我们的MVC框架搭建到此基本完善,后续还有很多好的插件,我会在这里进行更新,相关的模块如何使用,我会开设专门的板块讲解,谢谢您的耐心查看和陪伴!
使用我们的框架做一个程序 ...待续
1.Pinatra/Pinatra 2.noahbuscher/macaw 3.Illuminate/Database 4.filp/whoops 5.nrk/predis 6.nette/mail