常见问题

Class swoole does not exist

  • LaravelS中,Swoole是以cli模式启动的Http Server,替代了FPM
  • 投递任务、触发异步事件都会调用app('swoole'),从Laravel容器中获取Swoole\http\server实例。只有在LaravelS启动时,才会注入这个实例到容器中。
  • 所以,一旦脱离LaravelS,由于跨进程,以下情况,你将无法成功调用app('swoole')
    • 以各种命令行方式运行的代码,例如Artisan命令行、PHP脚本命令行;
    • 运行在FPM/Apache PHP Module下的代码,查看SAPI Log::info('PHP SAPI', [php_sapi_name()]);

使用包 encore/laravel-admin

修改config/laravels.php,在cleaners中增加LaravelAdminCleaner

  1. 'cleaners' => [
  2. Hhxsv5\LaravelS\Illuminate\Cleaners\LaravelAdminCleaner::class,
  3. ],

使用包 jenssegers/agent

监听系统事件

  1. // 重置Agent
  2. \Event::listen('laravels.received_request', function (\Illuminate\Http\Request $req, $app) {
  3. $app->agent->setHttpHeaders($req->server->all());
  4. $app->agent->setUserAgent();
  5. });

使用包 barryvdh/laravel-debugbar

官方不支持cli模式,需通过修改环境变量APP_RUNNING_IN_CONSOLE为非cli,但启用后不排除会有其他问题。

.env中增加环境变量APP_RUNNING_IN_CONSOLE=false

使用包 the-control-group/voyager

voyager依赖包arrilot/laravel-widgets,而其中WidgetGroupCollection是单例,追加Widget会造成它们重复展示,通过重新注册ServiceProvider来重置此单例。

  1. // config/laravels.php
  2. 'register_providers' => [
  3. Arrilot\Widgets\ServiceProvider::class,
  4. ],

使用包 overtrue/wechat

easywechat包会出现异步通知回调失败的问题,原因是$app['request']->getContent()是空的,给其赋值即可。

  1. //回调通知
  2. public function notify(Request $request)
  3. {
  4. $app = $this->getPayment();//获取支付实例
  5. $app['request'] = $request;//在原有代码添加这一行,将当前Request赋值给$app['request']
  6. $response = $app->handlePaidNotify(function ($message, $fail) use($id) {
  7. //...
  8. });
  9. return $response;
  10. }

使用包 laracasts/flash

常驻内存后,每次调用flash()会追加消息提醒,导致叠加展示消息提醒。有以下两个方案。

1.通过中间件在每次请求处理前处理后重置$messages app('flash')->clear();

2.每次请求处理后重新注册FlashServiceProvider,配置register_providers

使用包 laravel/telescope

因Swoole运行在cli模式,导致RequestWatcher不能正常识别忽略的路由。

解决方案:

1..env中增加环境变量APP_RUNNING_IN_CONSOLE=false

2.修改代码。

  1. // 修改`app/Providers/EventServiceProvider.php`, 添加下面监听代码到boot方法中
  2. // use Laravel\Telescope\Telescope;
  3. // use Illuminate\Support\Facades\Event;
  4. Event::listen('laravels.received_request', function ($request, $app) {
  5. $reflection = new \ReflectionClass(Telescope::class);
  6. $handlingApprovedRequest = $reflection->getMethod('handlingApprovedRequest');
  7. $handlingApprovedRequest->setAccessible(true);
  8. $handlingApprovedRequest->invoke(null, $app) ? Telescope::startRecording() : Telescope::stopRecording();
  9. });

单例控制器

  • Laravel 5.3+ 控制器是被绑定在Router下的Route中,而Router是单例,控制器只会被构造一次,所以不能在构造方法中初始化请求级数据,下面展示错误的用法
  1. namespace App\Http\Controllers;
  2. class TestController extends Controller
  3. {
  4. protected $userId;
  5. public function __construct()
  6. {
  7. // 错误的用法:因控制器只被构造一次,然后常驻于内存,所以$userId只会被赋值一次,后续请求会误读取之前请求$userId
  8. $this->userId = session('userId');
  9. }
  10. public function testAction()
  11. {
  12. // 读取$this->userId;
  13. }
  14. }
  • 两种解决方法(二选一)

1.避免在构造函数中初始化请求级的数据,应在具体Action中读取,这样编码风格更合理,建议这样写。

  1. # 列出你的路由中所有关联的控制器的所有属性
  2. php artisan laravels:list-properties
  1. namespace App\Http\Controllers;
  2. class TestController extends Controller
  3. {
  4. protected function getUserId()
  5. {
  6. return session('userId');
  7. }
  8. public function testAction()
  9. {
  10. // 通过调用$this->getUserId()读取$userId
  11. }
  12. }

2.使用LaravelS提供的自动销毁控制器机制。

  1. // config/laravels.php
  2. // 将enable置为true、excluded_list置为[],则表示自动销毁所有控制器
  3. 'destroy_controllers' => [
  4. 'enable' => true, // 启用自动销毁控制器
  5. 'excluded_list' => [
  6. //\App\Http\Controllers\TestController::class, // 排除销毁的控制器类列表
  7. ],
  8. ],

不能使用这些函数

  • flush/ob_flush/ob_end_flush/ob_implicit_flushswoole_http_response不支持flush

  • dd()/exit()/die(): 将导致Worker/Task/Process进程立即退出,建议通过抛异常跳出函数调用栈,Swoole文档

  • header()/setcookie()/http_response_code():HTTP响应只能通过Laravel/Lumen的Response对象。

不能使用的全局变量

  • $_GET、$_POST、$_FILES、$_COOKIE、$_REQUEST、$_SESSION、$GLOBALS,$_ENV是可读的,$_SERVER是部分可读的。

大小限制

  • Swoole限制了GET请求头的最大尺寸为8KB,建议Cookie的不要太大,不然Cookie可能解析失败。

  • POST数据或文件上传的大小受Swoole配置package_max_length限制,默认上限2M

  • 文件上传的大小受到了memory_limit的限制,默认128M

Inotify监听文件数达到上限

Warning: inotify_add_watch(): The user limit on the total number of inotify watches was reached

  • LinuxInotify监听文件数默认上限一般是8192,实际项目的文件数+目录树很可能超过此上限,进而导致后续的监听失败。

  • 增加此上限到524288echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p,注意Docker时需设置启用privileged

注意include/require与(include/require)_once

看看鸟哥这篇文章再一次, 不要使用(include/require)_once

  • 引入接口trait函数时使用(include/require)_once,其他情况使用include/require。

  • 在多进程模式下,子进程会继承父进程资源,一旦父进程引入了某个需要被执行的文件,子进程再次require_once()时会直接返回true,导致该文件执行失败。此时,你应该使用include/require。

对于Swoole < 1.9.17的环境

开启handle_static后,静态资源文件将由LaravelS组件处理。由于PHP环境的原因,可能会导致MimeTypeGuesser无法正确识别MimeType,比如会Javascript与CSS文件会被识别为text/plain

解决方案:

1.升级Swoole到1.9.17+

2.注册自定义MIME猜测器

  1. // MyGuessMimeType.php
  2. use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface;
  3. class MyGuessMimeType implements MimeTypeGuesserInterface
  4. {
  5. protected static $map = [
  6. 'js' => 'application/javascript',
  7. 'css' => 'text/css',
  8. ];
  9. public function guess($path)
  10. {
  11. $ext = pathinfo($path, PATHINFO_EXTENSION);
  12. if (strlen($ext) > 0) {
  13. return Arr::get(self::$map, $ext);
  14. } else {
  15. return null;
  16. }
  17. }
  18. }
  1. // AppServiceProvider.php
  2. use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;
  3. public function boot()
  4. {
  5. MimeTypeGuesser::getInstance()->register(new MyGuessMimeType());
  6. }