123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531 |
- <?php
- namespace Composer\EventDispatcher;
- use Composer\DependencyResolver\PolicyInterface;
- use Composer\DependencyResolver\Request;
- use Composer\Installer\InstallerEvent;
- use Composer\IO\IOInterface;
- use Composer\Composer;
- use Composer\DependencyResolver\Operation\OperationInterface;
- use Composer\Repository\CompositeRepository;
- use Composer\Repository\RepositorySet;
- use Composer\Script;
- use Composer\Installer\PackageEvent;
- use Composer\Installer\BinaryInstaller;
- use Composer\Util\ProcessExecutor;
- use Composer\Script\Event as ScriptEvent;
- use Symfony\Component\Process\PhpExecutableFinder;
- class EventDispatcher
- {
- protected $composer;
- protected $io;
- protected $loader;
- protected $process;
- protected $listeners;
- private $eventStack;
-
- public function __construct(Composer $composer, IOInterface $io, ProcessExecutor $process = null)
- {
- $this->composer = $composer;
- $this->io = $io;
- $this->process = $process ?: new ProcessExecutor($io);
- $this->eventStack = array();
- }
-
- public function dispatch($eventName, Event $event = null)
- {
- if (null === $event) {
- $event = new Event($eventName);
- }
- return $this->doDispatch($event);
- }
-
- public function dispatchScript($eventName, $devMode = false, $additionalArgs = array(), $flags = array())
- {
- return $this->doDispatch(new Script\Event($eventName, $this->composer, $this->io, $devMode, $additionalArgs, $flags));
- }
-
- public function dispatchPackageEvent($eventName, $devMode, PolicyInterface $policy, RepositorySet $repositorySet, CompositeRepository $installedRepo, Request $request, array $operations, OperationInterface $operation)
- {
- return $this->doDispatch(new PackageEvent($eventName, $this->composer, $this->io, $devMode, $policy, $repositorySet, $installedRepo, $request, $operations, $operation));
- }
-
- public function dispatchInstallerEvent($eventName, $devMode, PolicyInterface $policy, RepositorySet $repositorySet, CompositeRepository $installedRepo, Request $request, array $operations = array())
- {
- return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $devMode, $policy, $repositorySet, $installedRepo, $request, $operations));
- }
-
- protected function doDispatch(Event $event)
- {
- $pathStr = 'PATH';
- if (!isset($_SERVER[$pathStr]) && isset($_SERVER['Path'])) {
- $pathStr = 'Path';
- }
-
- $binDir = $this->composer->getConfig()->get('bin-dir');
- if (is_dir($binDir)) {
- $binDir = realpath($binDir);
- if (isset($_SERVER[$pathStr]) && !preg_match('{(^|'.PATH_SEPARATOR.')'.preg_quote($binDir).'($|'.PATH_SEPARATOR.')}', $_SERVER[$pathStr])) {
- $_SERVER[$pathStr] = $binDir.PATH_SEPARATOR.getenv($pathStr);
- putenv($pathStr.'='.$_SERVER[$pathStr]);
- }
- }
- $listeners = $this->getListeners($event);
- $this->pushEvent($event);
- $return = 0;
- foreach ($listeners as $callable) {
- 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');
- }
- $event = $this->checkListenerExpectedEvent($callable, $event);
- $return = false === call_user_func($callable, $event) ? 1 : 0;
- } elseif ($this->isComposerScript($callable)) {
- $this->io->writeError(sprintf('> %s: %s', $event->getName(), $callable), true, IOInterface::VERBOSE);
- $scriptName = substr($callable, 1);
- $args = $event->getArguments();
- $flags = $event->getFlags();
- if (substr($callable, 0, 10) === '@composer ') {
- $exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(getenv('COMPOSER_BINARY')) . substr($callable, 9);
- if (0 !== ($exitCode = $this->process->execute($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 {
- $return = $this->dispatch($scriptName, new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags));
- } 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;
- }
- } else {
- $args = implode(' ', array_map(array('Composer\Util\ProcessExecutor', 'escape'), $event->getArguments()));
- $exec = $callable . ($args === '' ? '' : ' '.$args);
- if ($this->io->isVerbose()) {
- $this->io->writeError(sprintf('> %s: %s', $event->getName(), $exec));
- } else {
- $this->io->writeError(sprintf('> %s', $exec));
- }
- $possibleLocalBinaries = $this->composer->getPackage()->getBinaries();
- if ($possibleLocalBinaries) {
- foreach ($possibleLocalBinaries as $localExec) {
- if (preg_match('{\b'.preg_quote($callable).'$}', $localExec)) {
- $caller = BinaryInstaller::determineBinaryCaller($localExec);
- $exec = preg_replace('{^'.preg_quote($callable).'}', $caller . ' ' . $localExec, $exec);
- break;
- }
- }
- }
- if (substr($exec, 0, 5) === '@php ') {
- $exec = $this->getPhpExecCommand() . ' ' . substr($exec, 5);
- }
- if (0 !== ($exitCode = $this->process->execute($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);
- }
- }
- if ($event->isPropagationStopped()) {
- break;
- }
- }
- $this->popEvent();
- return $return;
- }
- protected function getPhpExecCommand()
- {
- $finder = new PhpExecutableFinder();
- $phpPath = $finder->find();
- if (!$phpPath) {
- throw new \RuntimeException('Failed to locate PHP binary to execute '.$scriptName);
- }
- $allowUrlFOpenFlag = ' -d allow_url_fopen=' . ProcessExecutor::escape(ini_get('allow_url_fopen'));
- $disableFunctionsFlag = ' -d disable_functions=' . ProcessExecutor::escape(ini_get('disable_functions'));
- $memoryLimitFlag = ' -d memory_limit=' . ProcessExecutor::escape(ini_get('memory_limit'));
- return ProcessExecutor::escape($phpPath) . $allowUrlFOpenFlag . $disableFunctionsFlag . $memoryLimitFlag;
- }
-
- protected function executeEventPhpScript($className, $methodName, Event $event)
- {
- $event = $this->checkListenerExpectedEvent(array($className, $methodName), $event);
- if ($this->io->isVerbose()) {
- $this->io->writeError(sprintf('> %s: %s::%s', $event->getName(), $className, $methodName));
- } else {
- $this->io->writeError(sprintf('> %s::%s', $className, $methodName));
- }
- return $className::$methodName($event);
- }
-
- protected function checkListenerExpectedEvent($target, Event $event)
- {
- if (in_array($event->getName(), array(
- 'init',
- 'command',
- 'pre-file-download',
- ), true)) {
- return $event;
- }
- try {
- $reflected = new \ReflectionParameter($target, 0);
- } catch (\Exception $e) {
- return $event;
- }
- $typehint = $reflected->getClass();
- if (!$typehint instanceof \ReflectionClass) {
- return $event;
- }
- $expected = $typehint->getName();
-
- if (!$event instanceof $expected && $expected === 'Composer\Script\CommandEvent') {
- trigger_error('The callback '.$this->serializeCallback($target).' declared at '.$reflected->getDeclaringFunction()->getFileName().' accepts a '.$expected.' but '.$event->getName().' events use a '.get_class($event).' instance. Please adjust your type hint accordingly, see https://getcomposer.org/doc/articles/scripts.md#event-classes', E_USER_DEPRECATED);
- $event = new \Composer\Script\CommandEvent(
- $event->getName(),
- $event->getComposer(),
- $event->getIO(),
- $event->isDevMode(),
- $event->getArguments()
- );
- }
- if (!$event instanceof $expected && $expected === 'Composer\Script\PackageEvent') {
- trigger_error('The callback '.$this->serializeCallback($target).' declared at '.$reflected->getDeclaringFunction()->getFileName().' accepts a '.$expected.' but '.$event->getName().' events use a '.get_class($event).' instance. Please adjust your type hint accordingly, see https://getcomposer.org/doc/articles/scripts.md#event-classes', E_USER_DEPRECATED);
- $event = new \Composer\Script\PackageEvent(
- $event->getName(),
- $event->getComposer(),
- $event->getIO(),
- $event->isDevMode(),
- $event->getPolicy(),
- $event->getRepositorySet(),
- $event->getInstalledRepo(),
- $event->getRequest(),
- $event->getOperations(),
- $event->getOperation()
- );
- }
- if (!$event instanceof $expected && $expected === 'Composer\Script\Event') {
- trigger_error('The callback '.$this->serializeCallback($target).' declared at '.$reflected->getDeclaringFunction()->getFileName().' accepts a '.$expected.' but '.$event->getName().' events use a '.get_class($event).' instance. Please adjust your type hint accordingly, see https://getcomposer.org/doc/articles/scripts.md#event-classes', E_USER_DEPRECATED);
- $event = new \Composer\Script\Event(
- $event->getName(),
- $event->getComposer(),
- $event->getIO(),
- $event->isDevMode(),
- $event->getArguments(),
- $event->getFlags()
- );
- }
- return $event;
- }
- private function serializeCallback($cb)
- {
- if (is_array($cb) && count($cb) === 2) {
- if (is_object($cb[0])) {
- $cb[0] = get_class($cb[0]);
- }
- if (is_string($cb[0]) && is_string($cb[1])) {
- $cb = implode('::', $cb);
- }
- }
- if (is_string($cb)) {
- return $cb;
- }
- return var_export($cb, true);
- }
-
- public function addListener($eventName, $listener, $priority = 0)
- {
- $this->listeners[$eventName][$priority][] = $listener;
- }
-
- public function addSubscriber(EventSubscriberInterface $subscriber)
- {
- foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
- if (is_string($params)) {
- $this->addListener($eventName, array($subscriber, $params));
- } elseif (is_string($params[0])) {
- $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0);
- } else {
- foreach ($params as $listener) {
- $this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0);
- }
- }
- }
- }
-
- protected function getListeners(Event $event)
- {
- $scriptListeners = $this->getScriptListeners($event);
- if (!isset($this->listeners[$event->getName()][0])) {
- $this->listeners[$event->getName()][0] = array();
- }
- krsort($this->listeners[$event->getName()]);
- $listeners = $this->listeners;
- $listeners[$event->getName()][0] = array_merge($listeners[$event->getName()][0], $scriptListeners);
- return call_user_func_array('array_merge', $listeners[$event->getName()]);
- }
-
- public function hasEventListeners(Event $event)
- {
- $listeners = $this->getListeners($event);
- return count($listeners) > 0;
- }
-
- protected function getScriptListeners(Event $event)
- {
- $package = $this->composer->getPackage();
- $scripts = $package->getScripts();
- if (empty($scripts[$event->getName()])) {
- return array();
- }
- 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->loader->register();
- return $scripts[$event->getName()];
- }
-
- protected function isPhpScript($callable)
- {
- return false === strpos($callable, ' ') && false !== strpos($callable, '::');
- }
-
- protected function isComposerScript($callable)
- {
- return '@' === substr($callable, 0, 1) && '@php ' !== substr($callable, 0, 5);
- }
-
- protected function pushEvent(Event $event)
- {
- $eventName = $event->getName();
- if (in_array($eventName, $this->eventStack)) {
- throw new \RuntimeException(sprintf("Circular call to script handler '%s' detected", $eventName));
- }
- return array_push($this->eventStack, $eventName);
- }
-
- protected function popEvent()
- {
- return array_pop($this->eventStack);
- }
- }
|