Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions config/plugins.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,14 @@
|
*/
'namespace' => 'Plugins',

/*
|--------------------------------------------------------------------------
| App Environment
|--------------------------------------------------------------------------
|
| This value is used to determine if debug logs are shown
|
*/
'environment' => env('APP_ENV', 'production'),
];
30 changes: 16 additions & 14 deletions src/Services/PluginManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;
use WhileSmart\LaravelPluginEngine\Traits\Loggable;

class PluginManager
{
use Loggable;

protected Application $app;

protected string $pluginsPath;
Expand Down Expand Up @@ -85,7 +87,7 @@ protected function setPluginEnabled(string $pluginId, bool $enabled): bool

return true;
} catch (\Exception $e) {
Log::error('Failed to update plugin manifest: '.$e->getMessage(), [
$this->error('Failed to update plugin manifest: '.$e->getMessage(), [
'plugin' => $pluginId,
'path' => $manifestPath,
'exception' => $e,
Expand Down Expand Up @@ -123,15 +125,15 @@ public function findPlugin(string $pluginId): ?array
public function discover(): Collection
{
if (! empty($this->plugins)) {
Log::debug('Returning cached plugins');
$this->debug('Returning cached plugins');

return collect($this->plugins);
}

Log::debug('Starting plugin discovery', ['path' => $this->pluginsPath]);
$this->debug('Starting plugin discovery', ['path' => $this->pluginsPath]);

if (! is_dir($this->pluginsPath)) {
Log::warning("Plugins directory not found: {$this->pluginsPath}");
$this->warning("Plugins directory not found: {$this->pluginsPath}");

return collect();
}
Expand All @@ -144,17 +146,17 @@ public function discover(): Collection
$foundDirs[] = $directory->getBasename();

if (! $directory->isDir() || $directory->isDot()) {
Log::debug('Skipping non-directory or dot file', ['path' => $directory->getPathname()]);
$this->debug('Skipping non-directory or dot file', ['path' => $directory->getPathname()]);

continue;
}

$pluginPath = $directory->getPathname();
$manifestPath = $pluginPath.'/plugin.json';
Log::debug('Checking plugin directory', ['path' => $pluginPath]);
$this->debug('Checking plugin directory', ['path' => $pluginPath]);

if (! file_exists($manifestPath)) {
Log::debug("Plugin manifest not found in: {$pluginPath}");
$this->debug("Plugin manifest not found in: {$pluginPath}");
// Include in results with error
$plugins[] = [
'path' => $pluginPath,
Expand All @@ -165,7 +167,7 @@ public function discover(): Collection
continue;
}

Log::debug('Found plugin manifest', ['path' => $manifestPath]);
$this->debug('Found plugin manifest', ['path' => $manifestPath]);
$plugin = $this->loadPlugin($pluginPath);

// Always include the plugin in the results, even if there was an error
Expand All @@ -180,7 +182,7 @@ public function discover(): Collection
$actualDirName,
$expectedDirName
);
Log::warning($warning);
$this->warning($warning);
$plugin['warning'] = $warning;
}

Expand All @@ -202,7 +204,7 @@ protected function loadPlugin(string $pluginPath): ?array

if (! isset($manifest['id'])) {
$error = "Plugin manifest missing required 'id' field";
Log::error($error, ['path' => $manifestPath]);
$this->error($error, ['path' => $manifestPath]);

return [
'path' => $pluginPath,
Expand All @@ -219,7 +221,7 @@ protected function loadPlugin(string $pluginPath): ?array
return $plugin;
} catch (\JsonException $e) {
$error = 'Invalid JSON in plugin manifest: '.$e->getMessage();
Log::error($error, [
$this->error($error, [
'path' => $manifestPath,
'exception' => $e,
]);
Expand Down Expand Up @@ -290,13 +292,13 @@ public function registerPlugins()
if (class_exists($plugin['provider'])) {
$this->app->register($plugin['provider']);
} else {
Log::error("Plugin service provider class not found: {$plugin['provider']}", [
$this->error("Plugin service provider class not found: {$plugin['provider']}", [
'plugin' => $plugin['id'] ?? 'unknown',
'path' => $plugin['path'] ?? null,
]);
}
} catch (\Exception $e) {
Log::error('Failed to register plugin: '.$e->getMessage(), [
$this->error('Failed to register plugin: '.$e->getMessage(), [
'plugin' => $plugin['id'] ?? 'unknown',
'exception' => $e,
]);
Expand Down
63 changes: 63 additions & 0 deletions src/Traits/Loggable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace WhileSmart\LaravelPluginEngine\Traits;

trait Loggable
{
private function info(string $message, array $context = []): void
{
$class = get_class($this);
logger()->info("$class: $message", $context);
}

private function error(string $message, array $context = []): void
{
$class = get_class($this);
logger()->error("$class: $message", $context);
}

private function warning(string $message, array $context = []): void
{
$class = get_class($this);
logger()->warning("$class: $message", $context);
}

private function debug(string $message, array $context = []): void
{
// Show debug logs only on development environments
if (config('plugins.environment', 'production') != 'production') {
$class = get_class($this);
logger()->debug("$class: $message", $context);
}
}

private function critical(string $message, array $context = []): void
{
$class = get_class($this);
logger()->critical("$class: $message", $context);
}

private function alert(string $message, array $context = []): void
{
$class = get_class($this);
logger()->alert("$class: $message", $context);
}

private function emergency(string $message, array $context = []): void
{
$class = get_class($this);
logger()->emergency("$class: $message", $context);
}

private function notice(string $message, array $context = []): void
{
$class = get_class($this);
logger()->notice("$class: $message", $context);
}

private function log($level, string $message, array $context = []): void
{
$class = get_class($this);
logger()->log($level, "$class: $message", $context);
}
}
24 changes: 8 additions & 16 deletions tests/Feature/Plugin/PluginCommandsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,24 +51,21 @@ protected function resetExamplePluginState(): void
}
}

/** @test */
public function it_lists_plugins()
public function test_it_lists_plugins()
{
$this->artisan('plugin:list')
->assertExitCode(0)
->expectsOutputToContain('Example Plugin');
}

/** @test */
public function it_shows_plugin_info()
public function test_it_shows_plugin_info()
{
$this->artisan('plugin:info example')
->assertExitCode(0)
->expectsOutputToContain('Example Plugin');
}

/** @test */
public function it_enables_plugins()
public function test_it_enables_plugins()
{
$this->artisan('plugin:disable example')
->assertExitCode(0);
Expand All @@ -81,32 +78,28 @@ public function it_enables_plugins()
->assertExitCode(0);
}

/** @test */
public function it_disables_plugins()
public function test_it_disables_plugins()
{
$this->artisan('plugin:disable example')
->assertExitCode(0)
->expectsOutputToContain('disabled successfully');
}

/** @test */
public function it_handles_nonexistent_plugin()
public function test_it_handles_nonexistent_plugin()
{
$this->artisan('plugin:info nonexistent')
->expectsOutputToContain('not found')
->assertExitCode(1);
}

/** @test */
public function it_suggests_similar_plugin_names()
public function test_it_suggests_similar_plugin_names()
{
$this->artisan('plugin:info examp')
->expectsOutputToContain('Did you mean one of these?')
->assertExitCode(1);
}

/** @test */
public function it_handles_invalid_plugin_manifest()
public function test_it_handles_invalid_plugin_manifest()
{
$pluginPath = $this->createTestPlugin('invalid_plugin', [
'name' => 'Invalid Plugin',
Expand All @@ -123,8 +116,7 @@ public function it_handles_invalid_plugin_manifest()
->expectsOutputToContain("Plugin 'invalid_plugin' has errors: Invalid JSON in plugin manifest: Syntax error");
}

/** @test */
public function it_handles_missing_id_in_manifest()
public function test_it_handles_missing_id_in_manifest()
{
$pluginId = 'missing_id_plugin';
$pluginPath = "{$this->pluginsPath}/{$pluginId}";
Expand Down
24 changes: 8 additions & 16 deletions tests/Feature/Plugin/PluginManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,7 @@ public function boot() {}
return $pluginPath;
}

/** @test */
public function it_can_discover_plugins()
public function test_it_can_discover_plugins()
{
$plugins = $this->pluginManager->discover();

Expand All @@ -106,25 +105,22 @@ public function it_can_discover_plugins()
$this->assertNull($plugin, 'Should return null for non-existent plugin');
}

/** @test */
public function it_can_find_a_plugin_by_id_case_insensitive()
public function test_it_can_find_a_plugin_by_id_case_insensitive()
{
$plugin = $this->pluginManager->findPlugin('EXAMPLE');

$this->assertNotNull($plugin, 'Should find plugin by ID case insensitive');
$this->assertEquals('example', $plugin['id']);
}

/** @test */
public function it_returns_null_for_nonexistent_plugin()
public function test_it_returns_null_for_nonexistent_plugin()
{
$plugin = $this->pluginManager->findPlugin('nonexistent');

$this->assertNull($plugin, 'Should return null for non-existent plugin');
}

/** @test */
public function it_validates_plugin_manifest()
public function test_it_validates_plugin_manifest()
{
$tempPath = storage_path('framework/testing/temp_plugin');
File::ensureDirectoryExists($tempPath);
Expand All @@ -147,8 +143,7 @@ public function it_validates_plugin_manifest()
$this->pluginManager = new PluginManager($this->app);
}

/** @test */
public function it_requires_plugin_id_to_match_directory_name()
public function test_it_requires_plugin_id_to_match_directory_name()
{
$tempPath = storage_path('framework/testing/mismatched_plugin');
File::ensureDirectoryExists($tempPath);
Expand Down Expand Up @@ -179,8 +174,7 @@ public function it_requires_plugin_id_to_match_directory_name()
$this->pluginManager = new PluginManager($this->app);
}

/** @test */
public function it_can_enable_and_disable_plugins()
public function test_it_can_enable_and_disable_plugins()
{
$this->assertTrue($this->pluginManager->isPluginEnabled('example'));
$this->assertTrue($this->pluginManager->enablePlugin('example'));
Expand All @@ -189,8 +183,7 @@ public function it_can_enable_and_disable_plugins()
$this->assertTrue($this->pluginManager->disablePlugin('example'));
}

/** @test */
public function it_allows_access_to_protected_route_when_authenticated()
public function test_it_allows_access_to_protected_route_when_authenticated()
{
$this->pluginManager->enablePlugin('example');
$user = User::factory()->create();
Expand All @@ -211,8 +204,7 @@ public function it_allows_access_to_protected_route_when_authenticated()
]);
}

/** @test */
public function it_prevents_access_to_disabled_plugin_routes()
public function test_it_prevents_access_to_disabled_plugin_routes()
{
$this->pluginManager->enablePlugin('example');

Expand Down