function EventDispatcher::doDispatch
Triggers the listeners of an event.
Parameters
Event $event The event object to pass to the event handlers/listeners.:
Return value
int return code of the executed script if any, for php scripts a false return value is changed to 1, anything else to 0
Throws
\RuntimeException|\Exception
4 calls to EventDispatcher::doDispatch()
- EventDispatcher::dispatch in vendor/
composer/ composer/ src/ Composer/ EventDispatcher/ EventDispatcher.php - Dispatch an event
- EventDispatcher::dispatchInstallerEvent in vendor/
composer/ composer/ src/ Composer/ EventDispatcher/ EventDispatcher.php - Dispatch a installer event.
- EventDispatcher::dispatchPackageEvent in vendor/
composer/ composer/ src/ Composer/ EventDispatcher/ EventDispatcher.php - Dispatch a package event.
- EventDispatcher::dispatchScript in vendor/
composer/ composer/ src/ Composer/ EventDispatcher/ EventDispatcher.php - Dispatch a script event.
File
-
vendor/
composer/ composer/ src/ Composer/ EventDispatcher/ EventDispatcher.php, line 179
Class
- EventDispatcher
- The Event Dispatcher.
Namespace
Composer\EventDispatcherCode
protected function doDispatch(Event $event) {
if (Platform::getEnv('COMPOSER_DEBUG_EVENTS')) {
$details = null;
if ($event instanceof PackageEvent) {
$details = (string) $event->getOperation();
}
elseif ($event instanceof CommandEvent) {
$details = $event->getCommandName();
}
elseif ($event instanceof PreCommandRunEvent) {
$details = $event->getCommand();
}
$this->io
->writeError('Dispatching <info>' . $event->getName() . '</info>' . ($details ? ' (' . $details . ')' : '') . ' event');
}
$listeners = $this->getListeners($event);
$this->pushEvent($event);
$autoloadersBefore = spl_autoload_functions();
try {
$returnMax = 0;
foreach ($listeners as $callable) {
$return = 0;
$this->ensureBinDirIsInPath();
$additionalArgs = $event->getArguments();
if (is_string($callable) && str_contains($callable, '@no_additional_args')) {
$callable = Preg::replace('{ ?@no_additional_args}', '', $callable);
$additionalArgs = [];
}
$formattedEventNameWithArgs = $event->getName() . ($additionalArgs !== [] ? ' (' . implode(', ', $additionalArgs) . ')' : '');
if (!is_string($callable)) {
if (!is_callable($callable)) {
$className = is_object($callable[0]) ? get_class($callable[0]) : $callable[0];
throw new \RuntimeException('Subscriber ' . $className . '::' . $callable[1] . ' for event ' . $event->getName() . ' is not callable, make sure the function is defined and public');
}
if (is_array($callable) && (is_string($callable[0]) || is_object($callable[0])) && is_string($callable[1])) {
$this->io
->writeError(sprintf('> %s: %s', $formattedEventNameWithArgs, (is_object($callable[0]) ? get_class($callable[0]) : $callable[0]) . '->' . $callable[1]), true, IOInterface::VERBOSE);
}
$return = false === $callable($event) ? 1 : 0;
}
elseif ($this->isComposerScript($callable)) {
$this->io
->writeError(sprintf('> %s: %s', $formattedEventNameWithArgs, $callable), true, IOInterface::VERBOSE);
$script = explode(' ', substr($callable, 1));
$scriptName = $script[0];
unset($script[0]);
$index = array_search('@additional_args', $script, true);
if ($index !== false) {
$args = array_splice($script, $index, 0, $additionalArgs);
}
else {
$args = array_merge($script, $additionalArgs);
}
$flags = $event->getFlags();
if (isset($flags['script-alias-input'])) {
$argsString = implode(' ', array_map(static function ($arg) {
return ProcessExecutor::escape($arg);
}, $script));
$flags['script-alias-input'] = $argsString . ' ' . $flags['script-alias-input'];
unset($argsString);
}
if (strpos($callable, '@composer ') === 0) {
$exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(Platform::getEnv('COMPOSER_BINARY')) . ' ' . implode(' ', $args);
if (0 !== ($exitCode = $this->executeTty($exec))) {
$this->io
->writeError(sprintf('<error>Script %s handling the %s event returned with error code ' . $exitCode . '</error>', $callable, $event->getName()), true, IOInterface::QUIET);
throw new ScriptExecutionException('Error Output: ' . $this->process
->getErrorOutput(), $exitCode);
}
}
else {
if (!$this->getListeners(new Event($scriptName))) {
$this->io
->writeError(sprintf('<warning>You made a reference to a non-existent script %s</warning>', $callable), true, IOInterface::QUIET);
}
try {
/** @var InstallerEvent $event */
$scriptEvent = new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags);
$scriptEvent->setOriginatingEvent($event);
$return = $this->dispatch($scriptName, $scriptEvent);
} catch (ScriptExecutionException $e) {
$this->io
->writeError(sprintf('<error>Script %s was called via %s</error>', $callable, $event->getName()), true, IOInterface::QUIET);
throw $e;
}
}
}
elseif ($this->isPhpScript($callable)) {
$className = substr($callable, 0, strpos($callable, '::'));
$methodName = substr($callable, strpos($callable, '::') + 2);
if (!class_exists($className)) {
$this->io
->writeError('<warning>Class ' . $className . ' is not autoloadable, can not call ' . $event->getName() . ' script</warning>', true, IOInterface::QUIET);
continue;
}
if (!is_callable($callable)) {
$this->io
->writeError('<warning>Method ' . $callable . ' is not callable, can not call ' . $event->getName() . ' script</warning>', true, IOInterface::QUIET);
continue;
}
try {
$return = false === $this->executeEventPhpScript($className, $methodName, $event) ? 1 : 0;
} catch (\Exception $e) {
$message = "Script %s handling the %s event terminated with an exception";
$this->io
->writeError('<error>' . sprintf($message, $callable, $event->getName()) . '</error>', true, IOInterface::QUIET);
throw $e;
}
}
elseif ($this->isCommandClass($callable)) {
$className = $callable;
if (!class_exists($className)) {
$this->io
->writeError('<warning>Class ' . $className . ' is not autoloadable, can not call ' . $event->getName() . ' script</warning>', true, IOInterface::QUIET);
continue;
}
if (!is_a($className, Command::class, true)) {
$this->io
->writeError('<warning>Class ' . $className . ' does not extend ' . Command::class . ', can not call ' . $event->getName() . ' script</warning>', true, IOInterface::QUIET);
continue;
}
if (defined('Composer\\Script\\ScriptEvents::' . str_replace('-', '_', strtoupper($event->getName())))) {
$this->io
->writeError('<warning>You cannot bind ' . $event->getName() . ' to a Command class, use a non-reserved name</warning>', true, IOInterface::QUIET);
continue;
}
$app = new Application();
$app->setCatchExceptions(false);
if (method_exists($app, 'setCatchErrors')) {
$app->setCatchErrors(false);
}
$app->setAutoExit(false);
$cmd = new $className($event->getName());
$app->add($cmd);
$app->setDefaultCommand((string) $cmd->getName(), true);
try {
$args = implode(' ', array_map(static function ($arg) {
return ProcessExecutor::escape($arg);
}, $additionalArgs));
// reusing the output from $this->io is mostly needed for tests, but generally speaking
// it does not hurt to keep the same stream as the current Application
if ($this->io instanceof ConsoleIO) {
$reflProp = new \ReflectionProperty($this->io, 'output');
if (\PHP_VERSION_ID < 80100) {
$reflProp->setAccessible(true);
}
$output = $reflProp->getValue($this->io);
}
else {
$output = new ConsoleOutput();
}
$return = $app->run(new StringInput($event->getFlags()['script-alias-input'] ?? $args), $output);
} catch (\Exception $e) {
$message = "Script %s handling the %s event terminated with an exception";
$this->io
->writeError('<error>' . sprintf($message, $callable, $event->getName()) . '</error>', true, IOInterface::QUIET);
throw $e;
}
}
else {
$args = implode(' ', array_map([
'Composer\\Util\\ProcessExecutor',
'escape',
], $additionalArgs));
// @putenv does not receive arguments
if (strpos($callable, '@putenv ') === 0) {
$exec = $callable;
}
else {
if (str_contains($callable, '@additional_args')) {
$exec = str_replace('@additional_args', $args, $callable);
}
else {
$exec = $callable . ($args === '' ? '' : ' ' . $args);
}
}
if ($this->io
->isVerbose()) {
$this->io
->writeError(sprintf('> %s: %s', $event->getName(), $exec));
}
elseif ($event->getName() !== '__exec_command') {
// do not output the command being run when using `composer exec` as it is fairly obvious the user is running it
$this->io
->writeError(sprintf('> %s', $exec));
}
$possibleLocalBinaries = $this->composer
->getPackage()
->getBinaries();
if (count($possibleLocalBinaries) > 0) {
foreach ($possibleLocalBinaries as $localExec) {
if (Preg::isMatch('{\\b' . preg_quote($callable) . '$}', $localExec)) {
$caller = BinaryInstaller::determineBinaryCaller($localExec);
$exec = Preg::replace('{^' . preg_quote($callable) . '}', $caller . ' ' . $localExec, $exec);
break;
}
}
}
if (strpos($exec, '@putenv ') === 0) {
if (false === strpos($exec, '=')) {
Platform::clearEnv(substr($exec, 8));
}
else {
[
$var,
$value,
] = explode('=', substr($exec, 8), 2);
Platform::putEnv($var, $value);
}
continue;
}
if (strpos($exec, '@php ') === 0) {
$pathAndArgs = substr($exec, 5);
if (Platform::isWindows()) {
$pathAndArgs = Preg::replaceCallback('{^\\S+}', static function ($path) {
return str_replace('/', '\\', $path[0]);
}, $pathAndArgs);
}
// match somename (not in quote, and not a qualified path) and if it is not a valid path from CWD then try to find it
// in $PATH. This allows support for `@php foo` where foo is a binary name found in PATH but not an actual relative path
$matched = Preg::isMatchStrictGroups('{^[^\'"\\s/\\\\]+}', $pathAndArgs, $match);
if ($matched && !file_exists($match[0])) {
$finder = new ExecutableFinder();
if ($pathToExec = $finder->find($match[0])) {
if (Platform::isWindows()) {
$execWithoutExt = Preg::replace('{\\.(exe|bat|cmd|com)$}i', '', $pathToExec);
// prefer non-extension file if it exists when executing with PHP
if (file_exists($execWithoutExt)) {
$pathToExec = $execWithoutExt;
}
unset($execWithoutExt);
}
$pathAndArgs = $pathToExec . substr($pathAndArgs, strlen($match[0]));
}
}
$exec = $this->getPhpExecCommand() . ' ' . $pathAndArgs;
}
else {
$finder = new PhpExecutableFinder();
$phpPath = $finder->find(false);
if ($phpPath) {
Platform::putEnv('PHP_BINARY', $phpPath);
}
if (Platform::isWindows()) {
$exec = Preg::replaceCallback('{^\\S+}', static function ($path) {
return str_replace('/', '\\', $path[0]);
}, $exec);
}
}
// if composer is being executed, make sure it runs the expected composer from current path
// resolution, even if bin-dir contains composer too because the project requires composer/composer
// see https://github.com/composer/composer/issues/8748
if (strpos($exec, 'composer ') === 0) {
$exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(Platform::getEnv('COMPOSER_BINARY')) . substr($exec, 8);
}
if (0 !== ($exitCode = $this->executeTty($exec))) {
$this->io
->writeError(sprintf('<error>Script %s handling the %s event returned with error code ' . $exitCode . '</error>', $callable, $event->getName()), true, IOInterface::QUIET);
throw new ScriptExecutionException('Error Output: ' . $this->process
->getErrorOutput(), $exitCode);
}
}
$returnMax = max($returnMax, $return);
if ($event->isPropagationStopped()) {
break;
}
}
} finally {
$this->popEvent();
$knownIdentifiers = [];
foreach ($autoloadersBefore as $key => $cb) {
$knownIdentifiers[$this->getCallbackIdentifier($cb)] = [
'key' => $key,
'callback' => $cb,
];
}
foreach (spl_autoload_functions() as $cb) {
// once we get to the first known autoloader, we can leave any appended autoloader without problems
if (isset($knownIdentifiers[$this->getCallbackIdentifier($cb)]) && $knownIdentifiers[$this->getCallbackIdentifier($cb)]['key'] === 0) {
break;
}
// other newly appeared prepended autoloaders should be appended instead to ensure Composer loads its classes first
if ($cb instanceof ClassLoader) {
$cb->unregister();
$cb->register(false);
}
else {
spl_autoload_unregister($cb);
spl_autoload_register($cb);
}
}
}
return $returnMax;
}