看完這篇文章,就知道給 Laravel 開啟 Swoole還可以不用三方包
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 - 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技術交流群