站点图标

从零实现一个 PHP 微框架 - 前言

2020-05-07折腾记录PHP / XK-PHP / 从零实现
本文最后更新于 607 天前,文中所描述的信息可能已发生改变

前言

前不久为了准备用 PHP(原本打算是用 Spring,但是还不太会 233) 写一个博客项目,因为不打算使用任何框架,于是便打算自己写一个应用模板来完成博客这个坑。由于之前用过 Laravel 并且很喜欢 Laravel 接口的风格,一开始打算是弄一个接口与 Laravel 类似的模板,所以就没有考虑到 PSR 相关的标准。后来由于博客项目暂时咕掉了 ?,而且 PHP 模板也逐渐完善便打算将其作为一个独立的项目来进行开发。

在开发的期间学习了许多有趣的功能和设计模式,看了不少 Laravel 的文章和源码(XK-PHP 有部分代码是基于 Laravel 缩水而来,当然也有不少添加了一些功能 ?)。

最近 XK-PHP 已经趋于完善,于是就打算把开发过程遇到的坑和学到的知识写成文章,做下记录顺便分享给有想了解框架如何实现的同学们。

本系列文章(没错,我要水好几篇文章 ?)主要是围绕着 XK-PHP 的实现过程展开,同时也会提及 Laravel 和 Swoft 等 PHP 框架的代码或问题。(预计可能会写好几个月

目录

  1. 从零实现一个 PHP 微框架 - 前言
  2. 从零实现一个 PHP 微框架 – PSR & Composer
  3. 从零实现一个 PHP 微框架 – IoC 容器
  4. 从零实现一个 PHP 微框架 – Bootstrap 启动加载
  5. 从零实现一个 PHP 微框架 – 服务提供者

Github 地址

目前 XK-PHP 大部分的功能均已完成,如果不想看文章的话也可以直接到 Github 上查看代码。

主要功能

  • IoC 容器,兼容 PSR-11
  • 中间件,兼容 PSR-15
  • 请求和响应,兼容 PSR-7
  • 注解
  • Aop
  • MVC
  • Facade
  • ReactJS 集成,类似于 Laravel Mix
  • 简单事件系统
  • 简单任务队列,类似于协程,但是是同步阻塞的,只是可以主动让出
  • PHPUnit 单元测试,HTTP 测试
  • 响应异常处理
  • 日志系统
  • 模板系统,类似于 Yii 的视图

流程图

e748d64a 60a6 4bd9 b36e 540586180c31

框架成果


_195
<?php
_195
_195
namespace App\Controllers;
_195
_195
use ...
_195
_195
class HomeController
_195
{
_195
/**
_195
* @var Request
_195
* @Autowired("App\Http\Request")
_195
*/
_195
public $request;
_195
_195
/**
_195
* @var Hash
_195
*/
_195
public $hash;
_195
_195
public function __construct(Hash $hash)
_195
{
_195
// 如果不使用注解注入类属性,则可以使用构造器注入
_195
$this->hash = $hash;
_195
}
_195
_195
/**
_195
* @param Request $request
_195
* @param AnnotationReader $reader
_195
* @return View
_195
*
_195
* @DI\Set({
_195
* @DI\Item(name="request", value="request")
_195
* })
_195
*/
_195
public function index($request, AnnotationReader $reader): View
_195
{
_195
return view('home')->filter(function (string $content) {
_195
return preg_replace('/>(\s*)</', '><', $content);
_195
});
_195
}
_195
_195
/**
_195
* @param Request $request
_195
* @return View
_195
* @Route\Get("/home/home")
_195
*/
_195
public function home(Request $request): string
_195
{
_195
return view('home');
_195
}
_195
_195
/**
_195
* @param Request $request
_195
* @return string
_195
*
_195
* @Route\Get("/jwt")
_195
*/
_195
public function jwt(Request $request): string
_195
{
_195
return JWT::decode($request->query('jwt'));
_195
}
_195
_195
/**
_195
* @param Request $request
_195
* @return bool
_195
*/
_195
public function get(Request $request): bool
_195
{
_195
return true;
_195
}
_195
_195
/**
_195
* @param Request $request
_195
* @return bool
_195
*
_195
* @Route\Get("/exce")
_195
*/
_195
public function exception(Request $request): bool
_195
{
_195
Log::info('Info', 'Info', ['Info']);
_195
Log::debug('Debug', 'Debug', ['Debug']);
_195
Log::warn('Warn', 'Warn', ['Warn']);
_195
Log::error(new MethodNotAllowedException('Error'));
_195
Log::fatal(new MethodNotAllowedException('Fatal'));
_195
report('info', 'Info Function');
_195
abort(403);
_195
return true;
_195
}
_195
_195
/**
_195
* @param int $path
_195
* @param int $query
_195
* @return string
_195
*
_195
* @Route\Get("/inject/{path}")
_195
*/
_195
public function inject(int $path, int $query): string
_195
{
_195
// IoC 容器会自动将参数名作为 key 在绑定的实例和 Request 中寻找匹配的字段,然后进行注入
_195
return $path . ',' . $query;
_195
}
_195
_195
/**
_195
* @return Response
_195
*
_195
* @Route\Get("/cookie")
_195
*/
_195
public function cookie(): Response
_195
{
_195
$response = \response('Cookie')->cookie('cookie1', 'value');
_195
Cookie::queue(\App\Http\Cookie::make('cookie2', 'value'));
_195
return $response;
_195
}
_195
_195
/**
_195
* @return string
_195
*
_195
* @Route\Get("/aspect")
_195
*/
_195
public function aspect(): string
_195
{
_195
report('debug', 'hash');
_195
$hash = \App\Facades\Hash::make('123');
_195
report('debug', 'encrypt');
_195
$encrypt = App::callWithAspect(
_195
[Crypt::class, 'encrypt'],
_195
[
_195
'value' => '123'
_195
]
_195
);
_195
report('debug', 'function');
_195
App::callWithAspect(
_195
function () {
_195
report('debug', 'function-in');
_195
},
_195
[],
_195
null,
_195
false,
_195
[LogAspect::class]
_195
);
_195
report('debug', App::make('path'));
_195
return '';
_195
}
_195
_195
/**
_195
* @return string
_195
*
_195
* @Route\Get("/event")
_195
*/
_195
public function event(): string
_195
{
_195
Event::dispatch('event.str_config');
_195
Event::listen(LogEvent::class, [LogListener::class, 'handle']);
_195
Event::subscribe(LogSubscriber::class);
_195
Event::dispatch(LogEvent::class);
_195
Event::listen('event.str', StrListener::class);
_195
Event::dispatch('event.str');
_195
return '';
_195
}
_195
_195
/**
_195
* @return string
_195
*
_195
* @Route\Get("/task")
_195
*/
_195
public function task(): string
_195
{
_195
$scheduler = new Scheduler();
_195
$req = function () {
_195
report('debug', 'task1-start');
_195
$ch = curl_init();
_195
curl_setopt($ch, CURLOPT_URL, "http://ixk.me");
_195
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
_195
$data = curl_exec($ch);
_195
curl_close($ch);
_195
report('debug', 'task1-end');
_195
return $data;
_195
};
_195
$task1 = function () use ($req) {
_195
for ($i = 0; $i < 5; $i++) {
_195
yield $req();
_195
}
_195
};
_195
$task2 = function () {
_195
for ($i = 0; $i < 5; $i++) {
_195
report('debug', 'task2-start');
_195
yield;
_195
}
_195
};
_195
$scheduler->add($task1);
_195
$scheduler->add($task2);
_195
$scheduler->then();
_195
return '';
_195
}
_195
}

由于框架已经实现,所以我们就先看看结果吧。可以看到使用的方式和 Laravel 类似,当我们需要某些对象的时候,只需要在方法参数声明即可,IoC 容器会自动注入到方法中,也可以在构造器中或使用注解的方式注入到类中,同时也支持切面,日志,事件等功能。

结语

XK-PHP 参考了以下的框架:

感谢这些框架为我提供了实现和学习的思路。

从零实现一个 PHP 微框架 - 前言

https://blog.ixk.me/post/implement-a-php-microframework-from-zero-1
  • 许可协议

    BY-NC-SA

  • 发布于

    2020-05-07

  • 本文作者

    Otstar Lin

转载或引用本文时请遵守许可协议,注明出处、不得用于商业用途!

从零实现一个 PHP 微框架 - PSR & ComposerMVVM 简单实现