Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. EventDispatcher.php

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\EventDispatcher

Code

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;
}

API Navigation

  • Drupal Core 11.1.x
  • Topics
  • Classes
  • Functions
  • Constants
  • Globals
  • Files
  • Namespaces
  • Deprecated
  • Services
RSS feed
Powered by Drupal