从零实现一个 PHP 微框架 - 前言
前言
前不久为了准备用 PHP(原本打算是用 Spring,但是还不太会 233) 写一个博客项目,因为不打算使用任何框架,于是便打算自己写一个应用模板来完成博客这个坑。由于之前用过 Laravel 并且很喜欢 Laravel 接口的风格,一开始打算是弄一个接口与 Laravel 类似的模板,所以就没有考虑到 PSR 相关的标准。后来由于博客项目暂时咕掉了 ?,而且 PHP 模板也逐渐完善便打算将其作为一个独立的项目来进行开发。
在开发的期间学习了许多有趣的功能和设计模式,看了不少 Laravel 的文章和源码(XK-PHP 有部分代码是基于 Laravel 缩水而来,当然也有不少添加了一些功能 ?)。
最近 XK-PHP 已经趋于完善,于是就打算把开发过程遇到的坑和学到的知识写成文章,做下记录顺便分享给有想了解框架如何实现的同学们。
本系列文章(没错,我要水好几篇文章 ?)主要是围绕着 XK-PHP 的实现过程展开,同时也会提及 Laravel 和 Swoft 等 PHP 框架的代码或问题。(预计可能会写好几个月
目录
- 从零实现一个 PHP 微框架 - 前言
- 从零实现一个 PHP 微框架 – PSR & Composer
- 从零实现一个 PHP 微框架 – IoC 容器
- 从零实现一个 PHP 微框架 – Bootstrap 启动加载
- 从零实现一个 PHP 微框架 – 服务提供者
Github 地址
目前 XK-PHP 大部分的功能均已完成,如果不想看文章的话也可以直接到 Github 上查看代码。
主要功能
- IoC 容器,兼容 PSR-11
- 中间件,兼容 PSR-15
- 请求和响应,兼容 PSR-7
- 注解
- Aop
- MVC
- Facade
- ReactJS 集成,类似于 Laravel Mix
- 简单事件系统
- 简单任务队列,类似于协程,但是是同步阻塞的,只是可以主动让出
- PHPUnit 单元测试,HTTP 测试
- 响应异常处理
- 日志系统
- 模板系统,类似于 Yii 的视图
- …
流程图

框架成果
_195<?php_195_195namespace App\Controllers;_195_195use ..._195_195class 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许可协议
发布于
2020-05-07
本文作者
Otstar Lin
转载或引用本文时请遵守许可协议,注明出处、不得用于商业用途!