8000 Improve performance of script handlers by Seldaek · Pull Request #12456 · composer/composer · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Improve performance of script handlers #12456

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 25, 2025
Merged
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
72 changes: 54 additions & 18 deletions src/Composer/EventDispatcher/EventDispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Composer\IO\ConsoleIO;
use Composer\IO\IOInterface;
use Composer\Composer;
use Composer\Package\PackageInterface;
use Composer\PartialComposer;
use Composer\Pcre\Preg;
use Composer\Plugin\CommandEvent;
Expand Down Expand Up @@ -69,6 +70,8 @@ class EventDispatcher
private $eventStack;
/** @var list<string> */
private $skipScripts;
/** @var string|null */
private $previousHash = null;

/**
* Constructor.
Expand Down Expand Up @@ -217,6 +220,15 @@ protected function doDispatch(Event $event)
}
$formattedEventNameWithArgs = $event->getName() . ($additionalArgs !== [] ? ' (' . implode(', ', $additionalArgs) . ')' : '');
if (!is_string($callable)) {
// try setting up autoloading if we do not have a known class as target
// or no valid callable (although in theory this case should not be salvageable by autoloading, it does not really hurt to try)
if (
8000 (is_array($callable) && is_string($callable[0]) && !class_exists($callable[0], false))
|| !is_callable($callable)
) {
$this->makeAutoloader($event);
}

if (!is_callable($callable)) {
$className = is_object($callable[0]) ? get_class($callable[0]) : $callable[0];

Expand Down Expand Up @@ -271,6 +283,11 @@ protected function doDispatch(Event $event)
$className = substr($callable, 0, strpos($callable, '::'));
$methodName = substr($callable, strpos($callable, '::') + 2);

// try to autoload it if the class is not known yet
if (!class_exists($className, false)) {
$this->makeAutoloader($event);
}

if (!class_exists($className)) {
$this->io->writeError('<warning>Class '.$className.' is not autoloadable, can not call '.$event->getName().' script</warning>', true, IOInterface::QUIET);
continue;
Expand All @@ -289,6 +306,10 @@ protected function doDispatch(Event $event)
}
} elseif ($this->isCommandClass($callable)) {
$className = $callable;
// try to autoload it if the class is not known yet
if (!class_exists($className, false)) {
$this->makeAutoloader($event);
}
if (!class_exists($className)) {
$this->io->writeError('<warning>Class '.$className.' is not autoloadable, can not call '.$event->getName().' script</warning>', true, IOInterface::QUIET);
continue;
Expand Down Expand Up @@ -607,23 +628,6 @@ protected function getScriptListeners(Event $event): array
return [];
}

assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer'));

if ($this->loader) {
$this->loader->unregister();
}

$generator = $this->composer->getAutoloadGenerator();
if ($event instanceof ScriptEvent) {
$generator->setDevMode($event->isDevMode());
}

$packages = $this->composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages();
$packageMap = $generator->buildPackageMap($this->composer->getInstallationManager(), $package, $packages);
$map = $generator->parseAutoloads($packageMap, $package);
$this->loader = $generator->createLoader($map, $this->composer->getConfig()->get('vendor-dir'));
$this->loader->register(false);

return $scripts[$event->getName()];
}

Expand All @@ -648,7 +652,7 @@ protected function isCommandClass(string $callable): bool
*/
protected function isComposerScript(string $callable): bool
{
return strpos($callable, '@') === 0 && strpos($callable, '@php ') !== 0 && strpos($callable, '@putenv ') !== 0;
return str_starts_with($callable, '@') && !str_starts_with($callable, '@php ') && !str_starts_with($callable, '@putenv ');
}

/**
Expand Down Expand Up @@ -714,4 +718,36 @@ private function getCallbackIdentifier($cb): string
// not great but also do not want to break everything here
return 'unsupported';
}

private function makeAutoloader(Event $event): void
{
assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer'));

$package = $this->composer->getPackage();
if ($this->loader !== null) {
$this->loader->unregister();
}

$packages = $this->composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages();
$generator = $this->composer->getAutoloadGenerator();
$hash = implode(',', array_map(function (PackageInterface $p) {
return $p->getName().'/'.$p->getVersion();
}, $packages));
if ($event instanceof ScriptEvent) {
$generator->setDevMode($event->isDevMode());
$hash .= $event->isDevMode() ? '/dev' : '';
}
$hash = hash('sha256', $hash);

if ($this->previousHash === $hash) {
return;
}

$this->previousHash = $hash;

$packageMap = $generator->buildPackageMap($this->composer->getInstallationManager(), $package, $packages);
$map = $generator->parseAutoloads($packageMap, $package);
$this->loader = $generator->createLoader($map, $this->composer->getConfig()->get('vendor-dir'));
$this->loader->register(false);
}
}
4 changes: 2 additions & 2 deletions tests/Composer/Test/EventDispatcher/EventDispatcherTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public function testDispatcherPassDevModeToAutoloadGeneratorForScriptEvents(bool
$composer->setAutoloadGenerator($generator);

$package = $this->getMockBuilder('Composer\Package\RootPackageInterface')->getMock();
$package->method('getScripts')->will($this->returnValue(['scriptName' => ['scriptName']]));
$package->method('getScripts')->will($this->returnValue(['scriptName' => ['ClassName::testMethod']]));
$composer->setPackage($package);

$composer->setRepositoryManager($this->getRepositoryManagerMockForDevModePassingTest());
Expand All @@ -114,7 +114,7 @@ public function testDispatcherPassDevModeToAutoloadGeneratorForScriptEvents(bool
->method('isDevMode')
->will($this->returnValue($devMode));

$dispatcher->hasEventListeners($event);
$dispatcher->dispatch('scriptName', $event);
}

public static function provideDevModes(): array
Expand Down
0