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)
{
return $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 ?: $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
$res->end($response->getContent());
$this->app->forgetInstance('request');
$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 symfony/symfony...
- Throttle 的 IP 獲取設定默認會產生問題
更多學習內容請訪問:
騰訊T3-T4標準精品PHP架構師教程目錄大全,只要你看完保證薪資上升一個臺階(持續更新)
以上內容希望幫助到大家,很多PHPer在進階的時候總會遇到一些問題和瓶頸,業務代碼寫多了沒有方向感,不知道該從那裏入手去提升,對此我整理了一些資料,包括但不限於:分佈式架構、高可擴展、高性能、高併發、服務器性能調優、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql優化、shell腳本、Docker、微服務、Nginx等多個知識點高級進階乾貨需要的可以免費分享給大家,需要的可以加入我的官方羣點擊此處。