Catch plugin's bootstrapper exceptions
This commit is contained in:
parent
016e11b40b
commit
c44ff192ba
16
app/Events/PluginBootFailed.php
Normal file
16
app/Events/PluginBootFailed.php
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Services\Plugin;
|
||||
|
||||
class PluginBootFailed extends Event
|
||||
{
|
||||
/** @var Plugin */
|
||||
public $plugin;
|
||||
|
||||
public function __construct(Plugin $plugin)
|
||||
{
|
||||
$this->plugin = $plugin;
|
||||
}
|
||||
}
|
||||
25
app/Listeners/NotifyFailedPlugin.php
Normal file
25
app/Listeners/NotifyFailedPlugin.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use Event;
|
||||
|
||||
class NotifyFailedPlugin
|
||||
{
|
||||
public function handle($event)
|
||||
{
|
||||
$plugin = $event->plugin;
|
||||
$user = auth()->user();
|
||||
if ($user && $user->isAdmin()) {
|
||||
Event::listen(\App\Events\RenderingFooter::class, function ($event) use ($plugin) {
|
||||
$options = json_encode([
|
||||
'type' => 'warning',
|
||||
'title' => trans('errors.plugins.boot.title'),
|
||||
'message' => trans('errors.plugins.boot.message', ['plugin' => trans($plugin->title)]),
|
||||
'duration' => 0,
|
||||
]);
|
||||
$event->addContent('<script>blessing.ui.notify('.$options.')</script>');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,9 @@ class EventServiceProvider extends ServiceProvider
|
|||
'App\Events\PluginVersionChanged' => [
|
||||
'App\Listeners\CopyPluginAssets',
|
||||
],
|
||||
'App\Events\PluginBootFailed' => [
|
||||
'App\Listeners\NotifyFailedPlugin',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -233,7 +233,18 @@ class PluginManager
|
|||
{
|
||||
$path = $plugin->getPath().'/bootstrap.php';
|
||||
if ($this->filesystem->exists($path)) {
|
||||
$this->app->call($this->filesystem->getRequire($path), ['plugin' => $plugin]);
|
||||
try {
|
||||
$this->app->call($this->filesystem->getRequire($path), ['plugin' => $plugin]);
|
||||
} catch (\Throwable $th) {
|
||||
report($th);
|
||||
if (is_a($th, \Exception::class)) {
|
||||
$handler = $this->app->make(\App\Exceptions\Handler::class);
|
||||
if (! $handler->shouldReport($th)) {
|
||||
throw $th;
|
||||
}
|
||||
}
|
||||
$this->dispatcher->dispatch(new Events\PluginBootFailed($plugin));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -179,8 +179,8 @@ return [
|
|||
*/
|
||||
App\Providers\RuntimeCheckServiceProvider::class,
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\PluginServiceProvider::class,
|
||||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\PluginServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
App\Providers\ResponseMacroServiceProvider::class,
|
||||
App\Providers\TranslationServiceProvider::class,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import Vue from 'vue'
|
||||
import {
|
||||
Button, Input, Message, MessageBox, Switch,
|
||||
Button, Input, Message, MessageBox, Switch, Notification
|
||||
} from 'element-ui'
|
||||
|
||||
Vue.use(Button)
|
||||
|
|
@ -21,4 +21,5 @@ blessing.ui = {
|
|||
alert: MessageBox.alert,
|
||||
confirm: MessageBox.confirm,
|
||||
prompt: MessageBox.prompt,
|
||||
notify: Notification,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@
|
|||
@import '~element-theme-chalk/src/input.scss';
|
||||
@import '~element-theme-chalk/src/message.scss';
|
||||
@import '~element-theme-chalk/src/message-box.scss';
|
||||
@import '~element-theme-chalk/src/notification.scss';
|
||||
@import '~element-theme-chalk/src/switch.scss';
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ exception:
|
|||
plugins:
|
||||
duplicate: The plugin [:dir1] has a duplicated plugin name definition which is same to plugin [:dir2]. Please check your plugins directory, remove one of them or use another name definition.
|
||||
directory: We can't approach the path for loading plugins specified by the PLUGINS_DIR in .env file. Please check your configuration. Error :msg
|
||||
boot:
|
||||
title: Failed to boot plugin.
|
||||
message: There is something wrong with plugin ":plugin". Please check logs for detail.
|
||||
|
||||
cipher:
|
||||
unsupported: Unsupported password hashing method `:cipher`, please check your `.env` configuration
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ exception:
|
|||
plugins:
|
||||
duplicate: 【插件定义重复】:dir1 目录下的插件与 :dir2 目录下的插件使用了相同的 name 定义并造成了冲突。请检查你的插件目录,移除其中一个插件或者使用不同的 name 属性。
|
||||
directory: 配置文件 .env 中指定的插件加载目录(PLUGINS_DIR)不存在或无法打开,请检查你的配置。错误信息::msg
|
||||
boot:
|
||||
title: 插件加载失败
|
||||
message: 「:plugin」插件存在错误,无法加载。详细信息请见日志。
|
||||
|
||||
cipher:
|
||||
unsupported: 不支持的密码加密方式 `:cipher`,请检查你的 .env 配置文件
|
||||
|
|
|
|||
30
tests/ListenersTest/NotifyFailedPluginTest.php
Normal file
30
tests/ListenersTest/NotifyFailedPluginTest.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Tests;
|
||||
|
||||
use App\Events;
|
||||
use App\Services\Plugin;
|
||||
|
||||
class NotifyFailedPluginTest extends TestCase
|
||||
{
|
||||
public function testHandle()
|
||||
{
|
||||
$content = [];
|
||||
$plugin = new Plugin('', ['title' => 'ff']);
|
||||
|
||||
event(new Events\PluginBootFailed($plugin));
|
||||
event(new Events\RenderingFooter($content));
|
||||
$this->assertCount(0, $content);
|
||||
|
||||
$this->actAs('normal');
|
||||
event(new Events\PluginBootFailed($plugin));
|
||||
event(new Events\RenderingFooter($content));
|
||||
$this->assertCount(0, $content);
|
||||
|
||||
$this->actAs('admin');
|
||||
event(new Events\PluginBootFailed($plugin));
|
||||
event(new Events\RenderingFooter($content));
|
||||
$this->assertCount(1, $content);
|
||||
$this->assertStringContainsString('blessing.ui.notify', $content[0]);
|
||||
}
|
||||
}
|
||||
|
|
@ -323,6 +323,59 @@ class PluginManagerTest extends TestCase
|
|||
resolve(PluginManager::class)->boot();
|
||||
}
|
||||
|
||||
public function testHandleBootstrapperExceptions()
|
||||
{
|
||||
Event::fake();
|
||||
$this->mock(Option::class, function ($mock) {
|
||||
$mock->shouldReceive('get')
|
||||
->with('plugins_enabled', '[]')
|
||||
->andReturn(json_encode([['name' => 'mayaka', 'version' => '0.0.0']]));
|
||||
});
|
||||
$this->mock(Filesystem::class, function ($mock) {
|
||||
$mock->shouldReceive('directories')
|
||||
->with(base_path('plugins'))
|
||||
->andReturn(collect(['/mayaka']));
|
||||
|
||||
$mock->shouldReceive('exists')
|
||||
->with('/mayaka'.DIRECTORY_SEPARATOR.'package.json')
|
||||
->andReturn(true);
|
||||
|
||||
$mock->shouldReceive('get')
|
||||
->with('/mayaka'.DIRECTORY_SEPARATOR.'package.json')
|
||||
->andReturn(json_encode([
|
||||
'name' => 'mayaka',
|
||||
'version' => '0.0.0',
|
||||
]));
|
||||
|
||||
$mock->shouldReceive('exists')
|
||||
->with('/mayaka/vendor/autoload.php')
|
||||
->andReturn(false);
|
||||
|
||||
$mock->shouldReceive('exists')
|
||||
->with('/mayaka/bootstrap.php')
|
||||
->andReturn(true);
|
||||
|
||||
$mock->shouldReceive('getRequire')
|
||||
->with('/mayaka/bootstrap.php')
|
||||
->andReturn(function () {
|
||||
throw new \Exception();
|
||||
}, function () {
|
||||
abort(500);
|
||||
});
|
||||
});
|
||||
|
||||
app()->forgetInstance(PluginManager::class);
|
||||
resolve(PluginManager::class)->boot();
|
||||
Event::assertDispatched(Events\PluginBootFailed::class, function ($event) {
|
||||
$this->assertEquals('mayaka', $event->plugin->name);
|
||||
return true;
|
||||
});
|
||||
|
||||
app()->forgetInstance(PluginManager::class);
|
||||
$this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class);
|
||||
resolve(PluginManager::class)->boot();
|
||||
}
|
||||
|
||||
public function testLifecycleHooks()
|
||||
{
|
||||
$this->mock(Option::class, function ($mock) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user