From 2709d09823dc2da1c3f23c92f601db83ab122315 Mon Sep 17 00:00:00 2001 From: Pig Fang Date: Sun, 11 Aug 2019 18:00:00 +0800 Subject: [PATCH] Refactor booting plugins (part 1) --- app/Services/PluginManager.php | 69 ++++++++++++++++++++ storage/mocks/fake-plugin/package.json | 5 ++ storage/mocks/fake-plugin/src/Faker.php | 7 ++ tests/ServicesTest/PluginManagerTest.php | 83 ++++++++++++++++++++++++ 4 files changed, 164 insertions(+) create mode 100644 storage/mocks/fake-plugin/package.json create mode 100644 storage/mocks/fake-plugin/src/Faker.php create mode 100644 tests/ServicesTest/PluginManagerTest.php diff --git a/app/Services/PluginManager.php b/app/Services/PluginManager.php index d8b0c194..bc83066d 100644 --- a/app/Services/PluginManager.php +++ b/app/Services/PluginManager.php @@ -3,9 +3,11 @@ namespace App\Services; use Storage; +use Exception; use App\Events; use Composer\Semver\Semver; use Illuminate\Support\Arr; +use Illuminate\Support\Str; use Composer\Semver\Comparator; use Illuminate\Support\Collection; use Illuminate\Filesystem\Filesystem; @@ -15,6 +17,11 @@ use Illuminate\Contracts\Foundation\Application; class PluginManager { + /** + * @var bool + */ + protected $booted = false; + /** * @var Application */ @@ -57,6 +64,68 @@ class PluginManager $this->filesystem = $filesystem; } + /** + * Boot all enabled plugins. + */ + public function boot() + { + if ($this->booted) { + return; + } + + $this->enabled = collect(json_decode($this->option->get('plugins_enabled', '[]'), true)); + $plugins = collect(); + + collect($this->filesystem->directories($this->getPluginsDir())) + ->filter(function ($directory) { + return $this->filesystem->exists($directory.DIRECTORY_SEPARATOR.'package.json'); + }) + ->each(function ($directory) use (&$plugins) { + $manifest = json_decode( + $this->filesystem->get($directory.DIRECTORY_SEPARATOR.'package.json'), + true + ); + + $name = $manifest['name']; + if ($plugins->has($name)) { + throw new PrettyPageException(trans('errors.plugins.duplicate', [ + 'dir1' => $plugins->get($name)->getPath(), + 'dir2' => $directory, + ]), 5); + } + + $plugins->put($name, new Plugin($directory, $manifest)); + }); + + // disable unsatisfied here + + $this->registerAutoload($plugins->mapWithKeys(function ($plugin) { + return [$plugin->namespace => $plugin->getPath().'/src']; + })); + + $this->booted = true; + } + + /** + * @param Collection $paths + */ + protected function registerAutoload($paths) + { + spl_autoload_register(function ($class) use ($paths) { + $paths->each(function ($path, $namespace) use ($class) { + if ($namespace != '' && mb_strpos($class, $namespace) === 0) { + // Parse real file path + $path = $path.Str::replaceFirst($namespace, '', $class).'.php'; + $path = str_replace('\\', '/', $path); + + if ($this->filesystem->exists($path)) { + $this->filesystem->getRequire($path); + } + } + }); + }); + } + /** * @return Collection */ diff --git a/storage/mocks/fake-plugin/package.json b/storage/mocks/fake-plugin/package.json new file mode 100644 index 00000000..c7540d8c --- /dev/null +++ b/storage/mocks/fake-plugin/package.json @@ -0,0 +1,5 @@ +{ + "name": "fake", + "version": "0.0.0", + "namespace": "Fake" +} diff --git a/storage/mocks/fake-plugin/src/Faker.php b/storage/mocks/fake-plugin/src/Faker.php new file mode 100644 index 00000000..6d667569 --- /dev/null +++ b/storage/mocks/fake-plugin/src/Faker.php @@ -0,0 +1,7 @@ +getProperty('booted'); + $property->setAccessible(true); + $property->setValue($manager, false); + + $manager->boot(); + return $manager; + } + + public function testPreventBootAgain() + { + // TODO: modify asserting 0 times here + $this->mock(Filesystem::class, function ($mock) { + $mock->shouldReceive('directories')->times(1); + }); + app('plugins')->boot(); + app('plugins')->boot(); + } + + public function testRegisterAutoload() + { + config(['plugins.directory' => storage_path('mocks')]); + $this->assertFalse(class_exists('Fake\Faker')); + $manager = $this->rebootPluginManager(app('plugins')); + $this->assertTrue(class_exists('Fake\Faker')); + + config(['plugins.directory' => env('PLUGINS_DIR')]); + } + + public function testReportDuplicatedPlugins() + { + $this->mock(Filesystem::class, function ($mock) { + $mock->shouldReceive('directories') + ->with(config('plugins.directory')) + ->once() + ->andReturn(collect(['/nano', '/yuko'])); + + $mock->shouldReceive('exists') + ->with('/nano/package.json') + ->once() + ->andReturn(true); + + $mock->shouldReceive('get') + ->with('/nano/package.json') + ->once() + ->andReturn(json_encode([ + 'name' => 'fake', + 'version' => '0.0.0', + ])); + + $mock->shouldReceive('exists') + ->with('/yuko/package.json') + ->once() + ->andReturn(true); + + $mock->shouldReceive('get') + ->with('/yuko/package.json') + ->once() + ->andReturn(json_encode([ + 'name' => 'fake', + 'version' => '0.0.0', + ])); + }); + + $this->expectExceptionMessage(trans('errors.plugins.duplicate', [ + 'dir1' => '/nano', + 'dir2' => '/yuko', + ])); + $manager = $this->rebootPluginManager(app('plugins')); + } +}