Swoole 是一款优秀的 PHP 扩展,利用其可以实现原生 PHP 很难做到的常驻服务和异步。正好我有个 Laravel 项目可以折腾,就研究了下。
Laravel 项目是基于 composer 的,所以我先帖下我的 composer.json 中的 require 声明:
{
"require": {
"php": "^7.1.3",
"cybercog/laravel-love": "^5.1",
"dingo/api": "~v2.0.0-alpha2",
"doctrine/dbal": "^2.8",
"fideloper/proxy": "^4.0",
"guzzlehttp/guzzle": "^6.3",
"infyomlabs/adminlte-templates": "5.6.x-dev",
"infyomlabs/laravel-generator": "5.6.x-dev",
"jeroennoten/laravel-adminlte": "^1.23",
"laravel/framework": "5.6.*",
"laravel/tinker": "^1.0",
"laravelcollective/html": "^5.6.0",
"lshorz/luocaptcha": "^1.0",
"overtrue/laravel-lang": "v3.0.08",
"overtrue/laravel-wechat": "^4.0",
"predis/predis": "^1.1",
"spatie/laravel-permission": "^2.17",
"tymon/jwt-auth": "~1.0.0-rc.2",
"yajra/laravel-datatables-buttons": "^4.0",
"yajra/laravel-datatables-oracle": "^8.7"
}
}
如果我们要开启 swoole,我们可选的包有这些:
但一般来说,项目中需要常驻容器的服务与每次均需重新构建的服务并不一样,所以我才剑走偏锋。
起步
我们需要将 public/index.php
替换成如下
<?php
use Illuminate\Http\Request;
use Illuminate\Http\Response;
define('LARAVEL_START', microtime(true));
require __DIR__ . '/../vendor/autoload.php';
$app = require_once __DIR__ . '/../bootstrap/app.php';
class Laravel
{
/**
* Illuminate\Foundation\Application
*
* @var \Illuminate\Foundation\Application
*/
public $app;
/**
* App\Http\Kernel
*
* @var \App\Http\Kernel
*/
public $kernel;
/**
* App\Http\Requests\Request
*
* @var \App\Http\Requests\Request
*/
public $request;
/**
* Illuminate\Http\JsonResponse
*
* @var \Illuminate\Http\JsonResponse
*/
public $response;
/**
* 构造
*
* @param \Illuminate\Foundation\Application $app
*/
public function __construct(\Illuminate\Foundation\Application $app)
{
$this->app = $app;
}
/**
* RUN
*
* @return void
*/
public function run()
{
\Swoole\Runtime::enableCoroutine(true);
$http = new swoole_http_server('127.0.0.1', '80');
$http->set([
'document_root' => public_path('/'),
'enable_static_handler' => true,
]);
$http->on('request', function ($req, $res) {
try {
$kernel = $this->app->make(Illuminate\Contracts\Http\Kernel::class);
$get = $req->get ?? [];
$post = $req->post ?? [];
$input = array_merge($get, $post);
$cookie = $req->cookie ?? [];
$files = $req->files ?? [];
$server = $req->server ?? [];
$request = Request::create($req->server['request_uri'], $req->server['request_method'], $input, $cookie, $files, $server);
if (isset($req->header['x-requested-with']) && $req->header['x-requested-with'] == "XMLHttpRequest") {
$request->headers->set('X-Requested-With', "XMLHttpRequest", true);
}
if (isset($req->header['accept']) && $req->header['accept']) {
$request->headers->set('Accept', $req->header['accept'], true);
}
$response = $kernel->handle($request);
$res->status($response->getStatusCode());
foreach ($response->headers->allPreserveCaseWithoutCookies() as $name => $values) {
foreach ($values as $value) {
$res->header($name, $value, false);
}
}
foreach ($response->headers->getCookies() as $cookie) {
$res->header('Set-Cookie', $cookie->getName() . strstr($cookie, '='), false);
}
dump(time());
$res->end($response->getContent());
$this->app->forgetInstance('request');
} catch (\throwable $e) {
echo $e->getMessage();
echo PHP_EOL;
echo $e->getFile();
echo PHP_EOL;
echo $e->getLine();
echo PHP_EOL;
}
});
$http->start();
}
}
(new Laravel($app))->run();
运行时发现大多数页面均没有问题,只有几个用了 infyomlabs/laravel-generator
产生的列表页,AJAX 拉取 JSON 时却返回了 HTML。
排查
在有问题页面的 controller 代码中,找到如下
/**
- Display a listing of the Star.
- @param StarDataTable $starDataTable
- @return Response
*/
public function index(StarDataTable starDataTable->render('stars.index');
}
定位 StarDataTable::render()
到了
/**
* Process dataTables needed render output.
*
* @param string $view
* @param array $data
* @param array $mergeData
* @return mixed
*/
public function render($view, $data = [], $mergeData = [])
{
if ($this->request()->ajax() && $this->request()->wantsJson()) {
return app()->call([$this, 'ajax']);
}
...
}
这是判断 $this->request()
是不是 XHR 请求,且 Accept
请求头声明了 application/json
而 $this->request()
实现如下
/**
- Get DataTables Request instance.
- @return \Yajra\DataTables\Utilities\Request
*/
public function request()
{
return this->request = resolve('datatables.request');
}
不难看出,如果第一次构建,会走到
$this->request = resolve('datatables.request');
而 resolve 的实现是啥?
if (! function_exists('resolve')) {
/**
* Resolve a service from the container.
*
* @param string $name
* @return mixed
*/
function resolve($name)
{
return app($name);
}
}
就是从容器中取出 datatables.request
的过程。
所以我们只需让每次请求结束,$app 容器忘掉 datatables.request
就好了
改进
增加遗忘 datatables.request
response->getContent());
this->app->forgetInstance('datatables.request');
$this->app->forgetInstance(\Dingo\Api\Http\Middleware\Request::class);
完整最终版:
<?php
use Illuminate\Http\Request;
use Illuminate\Http\Response;
define('LARAVEL_START', microtime(true));
require __DIR__ . '/../vendor/autoload.php';
$app = require_once __DIR__ . '/../bootstrap/app.php';
class Laravel
{
/**
* Illuminate\Foundation\Application
*
* @var \Illuminate\Foundation\Application
*/
public $app;
/**
* App\Http\Kernel
*
* @var \App\Http\Kernel
*/
public $kernel;
/**
* App\Http\Requests\Request
*
* @var \App\Http\Requests\Request
*/
public $request;
/**
* Illuminate\Http\JsonResponse
*
* @var \Illuminate\Http\JsonResponse
*/
public $response;
/**
* 构造
*
* @param \Illuminate\Foundation\Application $app
*/
public function __construct(\Illuminate\Foundation\Application $app)
{
$this->app = $app;
}
/**
* RUN
*
* @return void
*/
public function run()
{
\Swoole\Runtime::enableCoroutine(true);
$http = new swoole_http_server('127.0.0.1', '80');
$http->set([
'document_root' => public_path('/'),
'enable_static_handler' => true,
]);
$http->on('request', function ($req, $res) {
try {
$kernel = $this->app->make(Illuminate\Contracts\Http\Kernel::class);
$get = $req->get ?? [];
$post = $req->post ?? [];
$input = array_merge($get, $post);
$cookie = $req->cookie ?? [];
$files = $req->files ?? [];
$server = $req->server ?? [];
$request = Request::create($req->server['request_uri'], $req->server['request_method'], $input, $cookie, $files, $server);
if (isset($req->header['x-requested-with']) && $req->header['x-requested-with'] == "XMLHttpRequest") {
$request->headers->set('X-Requested-With', "XMLHttpRequest", true);
}
if (isset($req->header['accept']) && $req->header['accept']) {
$request->headers->set('Accept', $req->header['accept'], true);
}
$response = $kernel->handle($request);
$res->status($response->getStatusCode());
foreach ($response->headers->allPreserveCaseWithoutCookies() as $name => $values) {
foreach ($values as $value) {
$res->header($name, $value, false);
}
}
foreach ($response->headers->getCookies() as $cookie) {
$res->header('Set-Cookie', $cookie->getName() . strstr($cookie, '='), false);
}
dump(time());
$res->end($response->getContent());
$this->app->forgetInstance('request');
//$this->app->forgetInstance('session');
//$this->app->forgetInstance('session.store');
//$this->app->forgetInstance('cookie');
$this->app->forgetInstance('datatables.request');
$this->app->forgetInstance(\Dingo\Api\Http\Middleware\Request::class);
//$kernel->terminate($request, $response);
} catch (\throwable $e) {
echo $e->getMessage();
echo PHP_EOL;
echo $e->getFile();
echo PHP_EOL;
echo $e->getLine();
echo PHP_EOL;
}
});
$http->start();
}
}
(new Laravel($app))->run();
测试
比原生 laravel 确实快不少(这还有 4 句 SQL 查询)
在这里插入图片描述- 注,此处给出的代码可以借鉴,但未经长期验证。且不同项目实际用到的包不同,需要在调试过程中 debug 容器中的服务提供者,和追踪代码来调优。
已知问题
-
flash 闪存数据以及表单验证错误的展示有问题
-
PDO 会报 Cannot execute queries while other unbuffered queries are active - https://github.com/symfony/symfony/issues/6745
-
Throttle 的 IP 获取设定默认会产生问题
点关注,不迷路
好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是人才。之前说过,PHP方面的技术点很多,也是因为太多了,实在是写不过来,写过来了大家也不会看的太多,所以我这里把它整理成了PDF和文档,如果有需要的可以
在这里插入图片描述 在这里插入图片描述更多学习内容可以访问【对标大厂】精品PHP架构师教程目录大全,只要你能看完保证薪资上升一个台阶(持续更新)
以上内容希望帮助到大家,很多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货需要的可以免费分享给大家,需要的可以加入我的 PHP技术交流群
网友评论