diff --git a/src/Illuminate/Foundation/ComposerScripts.php b/src/Illuminate/Foundation/ComposerScripts.php index 8cf568409138..8a8b16d96479 100644 --- a/src/Illuminate/Foundation/ComposerScripts.php +++ b/src/Illuminate/Foundation/ComposerScripts.php @@ -2,7 +2,9 @@ namespace Illuminate\Foundation; +use Composer\Installer\PackageEvent; use Composer\Script\Event; +use Illuminate\Contracts\Console\Kernel; class ComposerScripts { @@ -45,6 +47,36 @@ public static function postAutoloadDump(Event $event) static::clearCompiled(); } + /** + * Handle the pre-package-uninstall Composer event. + * + * @param \Composer\Installer\PackageEvent $event + * @return void + */ + public static function prePackageUninstall(PackageEvent $event) + { + $bootstrapFile = dirname($vendorDir = $event->getComposer()->getConfig()->get('vendor-dir')).'/bootstrap/app.php'; + + if (! file_exists($bootstrapFile)) { + return; + } + + require_once $vendorDir.'/autoload.php'; + + define('LARAVEL_START', microtime(true)); + + /** @var Application $app */ + $app = require_once $bootstrapFile; + + $app->make(Kernel::class)->bootstrap(); + + /** @var \Composer\DependencyResolver\Operation\UninstallOperation $uninstallOperation */ + $uninstallOperation = $event->getOperation()->getPackage(); + + $app['events']->dispatch('composer_package.'.$uninstallOperation->getName().':pre_uninstall'); + } + + /** * Clear the cached Laravel bootstrapping files. * diff --git a/src/Illuminate/Support/ServiceProvider.php b/src/Illuminate/Support/ServiceProvider.php index ce2b526d2be1..7b11a0423115 100755 --- a/src/Illuminate/Support/ServiceProvider.php +++ b/src/Illuminate/Support/ServiceProvider.php @@ -572,6 +572,51 @@ public static function addProviderToBootstrapFile(string $provider, ?string $pat $content = 'getBootstrapProvidersPath(); + + if (! file_exists($path)) { + return false; + } + + if (function_exists('opcache_invalidate')) { + opcache_invalidate($path, true); + } + + $providersToRemove = Arr::wrap($providersToRemove); + + $providers = (new Collection(require $path)) + ->unique() + ->sort() + ->values() + ->when( + $strict, + static fn (Collection $providerCollection) => $providerCollection->reject(fn (string $p) => in_array($p, $providersToRemove, true)), + static fn (Collection $providerCollection) => $providerCollection->reject(fn (string $p) => Str::contains($p, $providersToRemove)) + ) + ->map(fn ($p) => ' '.$p.'::class,') + ->implode(PHP_EOL); + + $content = 'tempFile) && file_exists($this->tempFile)) { + @unlink($this->tempFile); + } } public function testPublishableServiceProviders() @@ -191,6 +195,48 @@ public function testLoadTranslationsFromWithNamespace() $provider = new ServiceProviderForTestingOne($this->app); $provider->loadTranslationsFrom(__DIR__.'/translations', 'namespace'); } + + public function test_can_remove_provider() + { + $this->tempFile = __DIR__.'/providers.php'; + file_put_contents($this->tempFile, $contents = <<< PHP +tempFile, true); + + // Should have deleted nothing + $this->assertStringEqualsStringIgnoringLineEndings($contents, trim(file_get_contents($this->tempFile))); + + // Should delete the telescope provider + ServiceProvider::removeProviderFromBootstrapFile('App\Providers\TelescopeServiceProvider', $this->tempFile, true); + + $this->assertStringEqualsStringIgnoringLineEndings(<<< PHP +tempFile))); + + // Should fuzzily delete the App\Providers\AppServiceProvider class + ServiceProvider::removeProviderFromBootstrapFile('AppServiceProvider', $this->tempFile); + + $this->assertStringEqualsStringIgnoringLineEndings(<<< 'PHP' +tempFile))); + } } class ServiceProviderForTestingOne extends ServiceProvider