From 2f6a3ce8b9da42268b5124dad3621b6b94219b7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Pluchino?= Date: Tue, 24 Jan 2012 18:08:41 +0100 Subject: [PATCH 1/7] Add system trigger --- doc/faqs/triggers.md | 70 ++++++++ src/Composer/Command/InstallCommand.php | 15 ++ src/Composer/Trigger/GetTriggerEvent.php | 108 ++++++++++++ src/Composer/Trigger/TriggerDispatcher.php | 184 +++++++++++++++++++++ src/Composer/Trigger/TriggerEvents.php | 65 ++++++++ 5 files changed, 442 insertions(+) create mode 100644 doc/faqs/triggers.md create mode 100644 src/Composer/Trigger/GetTriggerEvent.php create mode 100644 src/Composer/Trigger/TriggerDispatcher.php create mode 100644 src/Composer/Trigger/TriggerEvents.php diff --git a/doc/faqs/triggers.md b/doc/faqs/triggers.md new file mode 100644 index 000000000..65fc3c846 --- /dev/null +++ b/doc/faqs/triggers.md @@ -0,0 +1,70 @@ +# Triggers + +## What is a trigger? + +A trigger is an event that runs a script in a static method, defined by a +package or project. This event is raised before and after each action (install, +update). + + +## Where are the event types defined? + +It is in the constant property in `Composer\Trigger\TriggerEvents` class. + + +## How is it defined? + +It is defined by adding the `triggers` key in the `extra` key to a project's +`composer.json` or package's `composer.json`. + +It is specified as an associative array of classes with her static method, +associated with the event's type. + +The PSR-0 must be defined, otherwise the trigger will not be triggered. + +For any given package: + +```json +{ + "extra": { + "triggers": { + "MyVendor\MyPackage\MyClass::myStaticMethod" : "post_install", + "MyVendor\MyPackage\MyClass::myStaticMethod2" : "post_update", + } + }, + "autoload": { + "psr-0": { + "MyVendor\MyPackage": "" + } + } +} +``` + +For any given project: +```json +{ + "extra": { + "triggers": { + "MyVendor\MyPackage2\MyClass2::myStaticMethod2" : "post_install", + "MyVendor\MyPackage2\MyClass2::myStaticMethod3" : "post_update", + } + }, + "autoload": { + "psr-0": { + "MyVendor\MyPackage": "my/folder/path/that/contains/triggers/from/the/root/project" + } + } +} +``` + +## Informations: + +The project's triggers are executed after the package's triggers. +A declared trigger with non existent file will be ignored. + +For example: +If you declare a trigger for a package pre install, as this trigger isn't +downloaded yet, it won't run. + +On the other hand, if you declare a pre-update package trigger, as the file +already exist, the actual vendor's version of the trigger will be run. diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index bf8bca726..c10b0cfb7 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -12,6 +12,10 @@ namespace Composer\Command; +use Composer\Trigger\TriggerEvents; + +use Composer\Trigger\TriggerDispatcher; + use Composer\Autoload\AutoloadGenerator; use Composer\DependencyResolver; use Composer\DependencyResolver\Pool; @@ -65,6 +69,7 @@ EOT $dryRun = (Boolean) $input->getOption('dry-run'); $verbose = $dryRun || $input->getOption('verbose'); $composer = $this->getComposer(); + $dispatcher = new TriggerDispatcher($this->getApplication()); if ($preferSource) { $composer->getDownloadManager()->setPreferSource(true); @@ -82,6 +87,12 @@ EOT $pool->addRepository($repository); } + // dispatch pre event + if (!$dryRun) { + $eventName = $update ? TriggerEvents::PRE_UPDATE : TriggerEvents::PRE_INSTALL; + $dispatcher->dispatch($eventName); + } + // creating requirements request $request = new Request($pool); if ($update) { @@ -177,6 +188,10 @@ EOT $output->writeln('Generating autoload files'); $generator = new AutoloadGenerator; $generator->dump($localRepo, $composer->getPackage(), $installationManager, $installationManager->getVendorPath().'/.composer'); + + // dispatch post event + $eventName = $update ? TriggerEvents::POST_UPDATE : TriggerEvents::POST_INSTALL; + $dispatcher->dispatch($eventName); } } diff --git a/src/Composer/Trigger/GetTriggerEvent.php b/src/Composer/Trigger/GetTriggerEvent.php new file mode 100644 index 000000000..940e0d0e0 --- /dev/null +++ b/src/Composer/Trigger/GetTriggerEvent.php @@ -0,0 +1,108 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Trigger; + +use Composer\Console\Application; + +/** + * The Trigger Event. + * + * @author François Pluchino + */ +class GetTriggerEvent +{ + /** + * @var TriggerDispatcher Dispatcher that dispatched this event + */ + private $dispatcher; + + /** + * @var string This event's name + */ + private $name; + + /** + * @var Application The application instance + */ + private $application; + + /** + * Returns the TriggerDispatcher that dispatches this Event + * + * @return TriggerDispatcher + */ + public function getDispatcher() + { + return $this->dispatcher; + } + + /** + * Stores the TriggerDispatcher that dispatches this Event + * + * @param TriggerDispatcher $dispatcher + */ + public function setDispatcher(TriggerDispatcher $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + /** + * Returns the event's name. + * + * @return string The event name + */ + public function getName() + { + return $this->name; + } + + /** + * Stores the event's name. + * + * @param string $name The event name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * Returns the application instance. + * + * @return Application + */ + public function getApplication() + { + return $this->application; + } + + /** + * Stores the application instance. + * + * @param Application $application + */ + public function setApplication(Application $application) + { + $this->application = $application; + } + + /** + * Returns the composer instance. + * + * @return Composer + */ + public function getComposer() + { + return $this->application->getComposer(); + } +} diff --git a/src/Composer/Trigger/TriggerDispatcher.php b/src/Composer/Trigger/TriggerDispatcher.php new file mode 100644 index 000000000..ad80d7bd3 --- /dev/null +++ b/src/Composer/Trigger/TriggerDispatcher.php @@ -0,0 +1,184 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Trigger; + +use Composer\Json\JsonFile; +use Composer\Repository\FilesystemRepository; +use Composer\Autoload\ClassLoader; +use Composer\Package\PackageInterface; +use Composer\Console\Application; +use Composer\Composer; + +/** + * The Trigger Dispatcher. + * + * Example in command: + * $dispatcher = new TriggerDispatcher($this->getApplication()); + * // ... + * $dispatcher->dispatch(TriggerEvents::PRE_INSTALL); + * // ... + * + * @author François Pluchino + */ +class TriggerDispatcher +{ + protected $application; + protected $loader; + + /** + * Constructor. + * + * @param Application $application + */ + public function __construct(Application $application) + { + $this->application = $application; + $this->loader = new ClassLoader(); + } + + /** + * Dispatch the event. + * + * @param string $eventName The constant in TriggerEvents + */ + public function dispatch($eventName) + { + $event = new GetTriggerEvent(); + + $event->setDispatcher($this); + $event->setName($eventName); + $event->setApplication($this->application); + + $this->doDispatch($event); + } + + /** + * Triggers the listeners of an event. + * + * @param GetTriggerEvent $event The event object to pass to the event handlers/listeners. + */ + protected function doDispatch(GetTriggerEvent $event) + { + $listeners = $this->getListeners($event); + + foreach ($listeners as $method => $eventType) { + if ($eventType === $event->getName()) { + $className = substr($method, 0, strpos($method, '::')); + $methodName = substr($method, strpos($method, '::') + 2); + + try { + $refMethod = new \ReflectionMethod($className, $methodName); + + // execute only if all conditions are validates + if ($refMethod->isPublic() + && $refMethod->isStatic() + && !$refMethod->isAbstract() + && 1 === $refMethod->getNumberOfParameters()) { + $className::$methodName($event); + } + + } catch (\ReflectionException $ex) {}//silent execpetion + } + } + } + + /** + * Register namespaces in ClassLoader. + * + * @param GetTriggerEvent $event The event object + * + * @return array The listener classes with event type + */ + protected function getListeners(GetTriggerEvent $event) + { + $listeners = array(); + $composer = $this->application->getComposer(); + $vendorDir = $composer->getInstallationManager()->getVendorPath(true); + $installedFile = $vendorDir . '/.composer/installed.json'; + + // get the list of package installed + // $composer->getRepositoryManager()->getLocalRepository() not used + // because the list is not refreshed for the post event + $fsr = new FilesystemRepository(new JsonFile($installedFile)); + $packages = $fsr->getPackages(); + + foreach ($packages as $package) { + $listeners = array_merge_recursive($listeners, $this->getListenerClasses($package)); + } + + // add root package + $listeners = array_merge_recursive($listeners, $this->getListenerClasses($composer->getPackage(), true)); + + return $listeners; + } + + /** + * Get listeners and register the namespace on Classloader. + * + * @param PackageInterface $package The package objet + * @param boolean $root For root composer + * + * @return array The listener classes with event type + */ + private function getListenerClasses(PackageInterface $package, $root = false) + { + $composer = $this->application->getComposer(); + $installDir = $composer->getInstallationManager()->getVendorPath(true) + . '/' . $package->getName(); + $ex = $package->getExtra(); + $al = $package->getAutoload(); + $searchListeners = array(); + $searchNamespaces = array(); + $listeners = array(); + $namespaces = array(); + + // get classes + if (isset($ex['triggers'])) { + foreach ($ex['triggers'] as $method => $event) { + $searchListeners[$method] = $event; + } + } + + // get namespaces + if (isset($al['psr-0'])) { + foreach ($al['psr-0'] as $ns => $path) { + $dir = $root ? realpath('.') : $installDir; + + $path = trim($dir . '/' . $path, '/'); + $searchNamespaces[$ns] = $path; + } + } + + // filter class::method have not a namespace registered + foreach ($searchNamespaces as $ns => $path) { + foreach ($searchListeners as $method => $event) { + if (0 === strpos($method, $ns)) { + $listeners[$method] = $event; + + if (!in_array($ns, array_keys($namespaces))) { + $namespaces[$ns] = $path; + } + } + } + } + + // register namespaces in class loader + foreach ($namespaces as $ns => $path) { + $this->loader->add($ns, $path); + } + + $this->loader->register(); + + return $listeners; + } +} diff --git a/src/Composer/Trigger/TriggerEvents.php b/src/Composer/Trigger/TriggerEvents.php new file mode 100644 index 000000000..19d542938 --- /dev/null +++ b/src/Composer/Trigger/TriggerEvents.php @@ -0,0 +1,65 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Trigger; + +/** + * The Trigger Events. + * + * @author François Pluchino + */ +class TriggerEvents +{ + /** + * The PRE_INSTALL event occurs at begging installation packages. + * + * This event allows you to execute a trigger before any other code in the + * composer is executed. The event listener method receives a + * Composer\Trigger\GetTriggerEvent instance. + * + * @var string + */ + const PRE_INSTALL = 'pre_install'; + + /** + * The POST_INSTALL event occurs at end installation packages. + * + * This event allows you to execute a trigger after any other code in the + * composer is executed. The event listener method receives a + * Composer\Trigger\GetTriggerEvent instance. + * + * @var string + */ + const POST_INSTALL = 'post_install'; + + /** + * The PRE_UPDATE event occurs at begging update packages. + * + * This event allows you to execute a trigger before any other code in the + * composer is executed. The event listener method receives a + * Composer\Trigger\GetTriggerEvent instance. + * + * @var string + */ + const PRE_UPDATE = 'pre_update'; + + /** + * The POST_UPDATE event occurs at end update packages. + * + * This event allows you to execute a trigger after any other code in the + * composer is executed. The event listener method receives a + * Composer\Trigger\GetTriggerEvent instance. + * + * @var string + */ + const POST_UPDATE = 'post_update'; +} From b147210ff4d8d301d048c0f093a60750611c5122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Pluchino?= Date: Tue, 24 Jan 2012 18:10:22 +0100 Subject: [PATCH 2/7] Adding access to input's options and arguments used by the triggers --- src/Composer/IO/ConsoleIO.php | 32 +++++++++++++++++++++++++++++++ src/Composer/IO/IOInterface.php | 34 ++++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/Composer/IO/ConsoleIO.php b/src/Composer/IO/ConsoleIO.php index 0169d2908..3ef514564 100644 --- a/src/Composer/IO/ConsoleIO.php +++ b/src/Composer/IO/ConsoleIO.php @@ -46,6 +46,38 @@ class ConsoleIO implements IOInterface $this->helperSet = $helperSet; } + /** + * {@inheritDoc} + */ + public function getArguments() + { + return $this->input->getArguments(); + } + + /** + * {@inheritDoc} + */ + public function getArgument($name) + { + return $this->input->getArgument($name); + } + + /** + * {@inheritDoc} + */ + public function getOptions() + { + return $this->input->getOptions(); + } + + /** + * {@inheritDoc} + */ + public function getOption($name) + { + return $this->input->getOption($name); + } + /** * {@inheritDoc} */ diff --git a/src/Composer/IO/IOInterface.php b/src/Composer/IO/IOInterface.php index 1b84daab7..cb9c74e24 100644 --- a/src/Composer/IO/IOInterface.php +++ b/src/Composer/IO/IOInterface.php @@ -18,7 +18,39 @@ namespace Composer\IO; * @author François Pluchino */ interface IOInterface -{ +{ + /** + * Returns all the given arguments merged with the default values. + * + * @return array + */ + function getArguments(); + + /** + * Gets argument by name. + * + * @param string $name The name of the argument + * + * @return mixed + */ + function getArgument($name); + + /** + * Returns all the given options merged with the default values. + * + * @return array + */ + function getOptions(); + + /** + * Gets an option by name. + * + * @param string $name The name of the option + * + * @return mixed + */ + function getOption($name); + /** * Is this input means interactive? * From f0170746558d13bbdbf449a4d94e3799653c8a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Pluchino?= Date: Wed, 25 Jan 2012 00:22:12 +0100 Subject: [PATCH 3/7] Remove package trigger Rename GetTriggerEvent on TriggerEvent Remove get/set Application in TriggerEvent Remove get/set Dispatcher in TriggerEvent Add get/set Composer in TriggerEvent Add get/set IO in TriggerEvent Restaure ConsoleIO and IOInterface Update Faq --- doc/faqs/triggers.md | 45 ++----- src/Composer/Command/InstallCommand.php | 5 +- src/Composer/IO/ConsoleIO.php | 32 ----- src/Composer/IO/IOInterface.php | 34 +----- src/Composer/Trigger/GetTriggerEvent.php | 108 ----------------- src/Composer/Trigger/TriggerDispatcher.php | 134 ++++++++------------- src/Composer/Trigger/TriggerEvent.php | 99 +++++++++++++++ src/Composer/Trigger/TriggerEvents.php | 26 +++- 8 files changed, 188 insertions(+), 295 deletions(-) delete mode 100644 src/Composer/Trigger/GetTriggerEvent.php create mode 100644 src/Composer/Trigger/TriggerEvent.php diff --git a/doc/faqs/triggers.md b/doc/faqs/triggers.md index 65fc3c846..237079e6e 100644 --- a/doc/faqs/triggers.md +++ b/doc/faqs/triggers.md @@ -3,13 +3,12 @@ ## What is a trigger? A trigger is an event that runs a script in a static method, defined by a -package or project. This event is raised before and after each action (install, -update). +project. This event is raised before and after each action (install, update). ## Where are the event types defined? -It is in the constant property in `Composer\Trigger\TriggerEvents` class. +It is in the constant property in `Composer\\Trigger\\TriggerEvents` class. ## How is it defined? @@ -17,41 +16,27 @@ It is in the constant property in `Composer\Trigger\TriggerEvents` class. It is defined by adding the `triggers` key in the `extra` key to a project's `composer.json` or package's `composer.json`. -It is specified as an associative array of classes with her static method, -associated with the event's type. +It is specified as an array of classes with her static method, +in associative array define the event's type. The PSR-0 must be defined, otherwise the trigger will not be triggered. -For any given package: - -```json -{ - "extra": { - "triggers": { - "MyVendor\MyPackage\MyClass::myStaticMethod" : "post_install", - "MyVendor\MyPackage\MyClass::myStaticMethod2" : "post_update", - } - }, - "autoload": { - "psr-0": { - "MyVendor\MyPackage": "" - } - } -} -``` - For any given project: ```json { "extra": { "triggers": { - "MyVendor\MyPackage2\MyClass2::myStaticMethod2" : "post_install", - "MyVendor\MyPackage2\MyClass2::myStaticMethod3" : "post_update", + "post_install": [ + "MyVendor\\MyRootPackage\\MyClass::myStaticMethod" + ], + "post_update": [ + "MyVendor\\MyRootPackage\\MyClass::myStaticMethod2" + ] } }, "autoload": { "psr-0": { - "MyVendor\MyPackage": "my/folder/path/that/contains/triggers/from/the/root/project" + "MyVendor\\MyRootPackage": "my/folder/path/that/contains/triggers/from/the/root/project" } } } @@ -59,12 +44,4 @@ For any given project: ## Informations: -The project's triggers are executed after the package's triggers. A declared trigger with non existent file will be ignored. - -For example: -If you declare a trigger for a package pre install, as this trigger isn't -downloaded yet, it won't run. - -On the other hand, if you declare a pre-update package trigger, as the file -already exist, the actual vendor's version of the trigger will be run. diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index c10b0cfb7..f6f2594b9 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -13,9 +13,7 @@ namespace Composer\Command; use Composer\Trigger\TriggerEvents; - use Composer\Trigger\TriggerDispatcher; - use Composer\Autoload\AutoloadGenerator; use Composer\DependencyResolver; use Composer\DependencyResolver\Pool; @@ -69,7 +67,8 @@ EOT $dryRun = (Boolean) $input->getOption('dry-run'); $verbose = $dryRun || $input->getOption('verbose'); $composer = $this->getComposer(); - $dispatcher = new TriggerDispatcher($this->getApplication()); + $io = $this->getApplication()->getIO(); + $dispatcher = new TriggerDispatcher($this->getComposer(), $io); if ($preferSource) { $composer->getDownloadManager()->setPreferSource(true); diff --git a/src/Composer/IO/ConsoleIO.php b/src/Composer/IO/ConsoleIO.php index 3ef514564..0169d2908 100644 --- a/src/Composer/IO/ConsoleIO.php +++ b/src/Composer/IO/ConsoleIO.php @@ -46,38 +46,6 @@ class ConsoleIO implements IOInterface $this->helperSet = $helperSet; } - /** - * {@inheritDoc} - */ - public function getArguments() - { - return $this->input->getArguments(); - } - - /** - * {@inheritDoc} - */ - public function getArgument($name) - { - return $this->input->getArgument($name); - } - - /** - * {@inheritDoc} - */ - public function getOptions() - { - return $this->input->getOptions(); - } - - /** - * {@inheritDoc} - */ - public function getOption($name) - { - return $this->input->getOption($name); - } - /** * {@inheritDoc} */ diff --git a/src/Composer/IO/IOInterface.php b/src/Composer/IO/IOInterface.php index cb9c74e24..1b84daab7 100644 --- a/src/Composer/IO/IOInterface.php +++ b/src/Composer/IO/IOInterface.php @@ -18,39 +18,7 @@ namespace Composer\IO; * @author François Pluchino */ interface IOInterface -{ - /** - * Returns all the given arguments merged with the default values. - * - * @return array - */ - function getArguments(); - - /** - * Gets argument by name. - * - * @param string $name The name of the argument - * - * @return mixed - */ - function getArgument($name); - - /** - * Returns all the given options merged with the default values. - * - * @return array - */ - function getOptions(); - - /** - * Gets an option by name. - * - * @param string $name The name of the option - * - * @return mixed - */ - function getOption($name); - +{ /** * Is this input means interactive? * diff --git a/src/Composer/Trigger/GetTriggerEvent.php b/src/Composer/Trigger/GetTriggerEvent.php deleted file mode 100644 index 940e0d0e0..000000000 --- a/src/Composer/Trigger/GetTriggerEvent.php +++ /dev/null @@ -1,108 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Trigger; - -use Composer\Console\Application; - -/** - * The Trigger Event. - * - * @author François Pluchino - */ -class GetTriggerEvent -{ - /** - * @var TriggerDispatcher Dispatcher that dispatched this event - */ - private $dispatcher; - - /** - * @var string This event's name - */ - private $name; - - /** - * @var Application The application instance - */ - private $application; - - /** - * Returns the TriggerDispatcher that dispatches this Event - * - * @return TriggerDispatcher - */ - public function getDispatcher() - { - return $this->dispatcher; - } - - /** - * Stores the TriggerDispatcher that dispatches this Event - * - * @param TriggerDispatcher $dispatcher - */ - public function setDispatcher(TriggerDispatcher $dispatcher) - { - $this->dispatcher = $dispatcher; - } - - /** - * Returns the event's name. - * - * @return string The event name - */ - public function getName() - { - return $this->name; - } - - /** - * Stores the event's name. - * - * @param string $name The event name - */ - public function setName($name) - { - $this->name = $name; - } - - /** - * Returns the application instance. - * - * @return Application - */ - public function getApplication() - { - return $this->application; - } - - /** - * Stores the application instance. - * - * @param Application $application - */ - public function setApplication(Application $application) - { - $this->application = $application; - } - - /** - * Returns the composer instance. - * - * @return Composer - */ - public function getComposer() - { - return $this->application->getComposer(); - } -} diff --git a/src/Composer/Trigger/TriggerDispatcher.php b/src/Composer/Trigger/TriggerDispatcher.php index ad80d7bd3..5bb9514a9 100644 --- a/src/Composer/Trigger/TriggerDispatcher.php +++ b/src/Composer/Trigger/TriggerDispatcher.php @@ -16,33 +16,36 @@ use Composer\Json\JsonFile; use Composer\Repository\FilesystemRepository; use Composer\Autoload\ClassLoader; use Composer\Package\PackageInterface; -use Composer\Console\Application; +use Composer\IO\IOInterface; use Composer\Composer; /** * The Trigger Dispatcher. * * Example in command: - * $dispatcher = new TriggerDispatcher($this->getApplication()); + * $dispatcher = new TriggerDispatcher($this->getComposer(), $this->getApplication()->getIO()); * // ... - * $dispatcher->dispatch(TriggerEvents::PRE_INSTALL); + * $dispatcher->dispatch(TriggerEvents::POST_INSTALL); * // ... * * @author François Pluchino */ class TriggerDispatcher { - protected $application; + protected $composer; + protected $io; protected $loader; /** * Constructor. * - * @param Application $application + * @param Composer $composer The composer instance + * @param IOInterface $io The IOInterface instance */ - public function __construct(Application $application) + public function __construct(Composer $composer, IOInterface $io) { - $this->application = $application; + $this->composer = $composer; + $this->io = $io; $this->loader = new ClassLoader(); } @@ -53,11 +56,11 @@ class TriggerDispatcher */ public function dispatch($eventName) { - $event = new GetTriggerEvent(); - - $event->setDispatcher($this); + $event = new TriggerEvent(); + $event->setName($eventName); - $event->setApplication($this->application); + $event->setComposer($this->composer); + $event->setIO($this->io); $this->doDispatch($event); } @@ -65,109 +68,72 @@ class TriggerDispatcher /** * Triggers the listeners of an event. * - * @param GetTriggerEvent $event The event object to pass to the event handlers/listeners. + * @param TriggerEvent $event The event object to pass to the event handlers/listeners. */ - protected function doDispatch(GetTriggerEvent $event) + protected function doDispatch(TriggerEvent $event) { $listeners = $this->getListeners($event); - foreach ($listeners as $method => $eventType) { - if ($eventType === $event->getName()) { - $className = substr($method, 0, strpos($method, '::')); - $methodName = substr($method, strpos($method, '::') + 2); + foreach ($listeners as $method) { + $className = substr($method, 0, strpos($method, '::')); + $methodName = substr($method, strpos($method, '::') + 2); - try { - $refMethod = new \ReflectionMethod($className, $methodName); + try { + $refMethod = new \ReflectionMethod($className, $methodName); - // execute only if all conditions are validates - if ($refMethod->isPublic() - && $refMethod->isStatic() - && !$refMethod->isAbstract() - && 1 === $refMethod->getNumberOfParameters()) { - $className::$methodName($event); - } + // execute only if all conditions are validates + if ($refMethod->isPublic() + && $refMethod->isStatic() + && !$refMethod->isAbstract() + && 1 === $refMethod->getNumberOfParameters()) { + $className::$methodName($event); + } - } catch (\ReflectionException $ex) {}//silent execpetion - } + } catch (\ReflectionException $ex) {}//silent execpetion } } /** * Register namespaces in ClassLoader. * - * @param GetTriggerEvent $event The event object + * @param TriggerEvent $event The event object * * @return array The listener classes with event type */ - protected function getListeners(GetTriggerEvent $event) + protected function getListeners(TriggerEvent $event) { - $listeners = array(); - $composer = $this->application->getComposer(); - $vendorDir = $composer->getInstallationManager()->getVendorPath(true); - $installedFile = $vendorDir . '/.composer/installed.json'; - - // get the list of package installed - // $composer->getRepositoryManager()->getLocalRepository() not used - // because the list is not refreshed for the post event - $fsr = new FilesystemRepository(new JsonFile($installedFile)); - $packages = $fsr->getPackages(); - - foreach ($packages as $package) { - $listeners = array_merge_recursive($listeners, $this->getListenerClasses($package)); - } - - // add root package - $listeners = array_merge_recursive($listeners, $this->getListenerClasses($composer->getPackage(), true)); - - return $listeners; - } - - /** - * Get listeners and register the namespace on Classloader. - * - * @param PackageInterface $package The package objet - * @param boolean $root For root composer - * - * @return array The listener classes with event type - */ - private function getListenerClasses(PackageInterface $package, $root = false) - { - $composer = $this->application->getComposer(); - $installDir = $composer->getInstallationManager()->getVendorPath(true) - . '/' . $package->getName(); - $ex = $package->getExtra(); - $al = $package->getAutoload(); - $searchListeners = array(); - $searchNamespaces = array(); - $listeners = array(); + $package = $this->composer->getPackage(); + $ex = $package->getExtra(); + $al = $package->getAutoload(); + $searchListeners = array(); + $searchNamespaces = array(); + $listeners = array(); $namespaces = array(); // get classes - if (isset($ex['triggers'])) { - foreach ($ex['triggers'] as $method => $event) { - $searchListeners[$method] = $event; + if (isset($ex['triggers'][$event->getName()])) { + foreach ($ex['triggers'][$event->getName()] as $method) { + $searchListeners[] = $method; } } // get namespaces if (isset($al['psr-0'])) { foreach ($al['psr-0'] as $ns => $path) { - $dir = $root ? realpath('.') : $installDir; - - $path = trim($dir . '/' . $path, '/'); + $path = trim(realpath('.') . '/' . $path, '/'); $searchNamespaces[$ns] = $path; } } - - // filter class::method have not a namespace registered - foreach ($searchNamespaces as $ns => $path) { - foreach ($searchListeners as $method => $event) { - if (0 === strpos($method, $ns)) { - $listeners[$method] = $event; - if (!in_array($ns, array_keys($namespaces))) { - $namespaces[$ns] = $path; - } + // filter class::method have not a namespace registered + foreach ($searchNamespaces as $ns => $path) { + foreach ($searchListeners as $method) { + if (0 === strpos($method, $ns)) { + $listeners[] = $method; + + if (!in_array($ns, array_keys($namespaces))) { + $namespaces[$ns] = $path; + } } } } diff --git a/src/Composer/Trigger/TriggerEvent.php b/src/Composer/Trigger/TriggerEvent.php new file mode 100644 index 000000000..1b1a968bb --- /dev/null +++ b/src/Composer/Trigger/TriggerEvent.php @@ -0,0 +1,99 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Trigger; + +use Composer\Composer; +use Composer\IO\IOInterface; + +/** + * The Trigger Event. + * + * @author François Pluchino + */ +class TriggerEvent +{ + /** + * @var string This event's name + */ + private $name; + + /** + * @var Composer The composer instance + */ + private $composer; + + /** + * @var IOInterface The IO instance + */ + private $io; + + /** + * Returns the event's name. + * + * @return string The event name + */ + public function getName() + { + return $this->name; + } + + /** + * Stores the event's name. + * + * @param string $name The event name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * Returns the composer instance. + * + * @return Composer + */ + public function getComposer() + { + return $this->composer; + } + + /** + * Stores the composer instance. + * + * @param Composer $composer + */ + public function setComposer(Composer $composer) + { + $this->composer = $composer; + } + + /** + * Returns the IO instance. + * + * @return IOInterface + */ + public function getIO() + { + return $this->io; + } + + /** + * Stores the IO instance. + * + * @param IOInterface $io + */ + public function setIO(IOInterface $io) + { + $this->io = $io; + } +} diff --git a/src/Composer/Trigger/TriggerEvents.php b/src/Composer/Trigger/TriggerEvents.php index 19d542938..61392405f 100644 --- a/src/Composer/Trigger/TriggerEvents.php +++ b/src/Composer/Trigger/TriggerEvents.php @@ -51,7 +51,7 @@ class TriggerEvents * @var string */ const PRE_UPDATE = 'pre_update'; - + /** * The POST_UPDATE event occurs at end update packages. * @@ -62,4 +62,28 @@ class TriggerEvents * @var string */ const POST_UPDATE = 'post_update'; + + /** + * The PRE_UNINSTALL event occurs at begging uninstallation packages. + * + * This event allows you to execute a trigger after any other code in the + * composer is executed. The event listener method receives a + * Composer\Trigger\TriggerEvent instance. + * + * @var string + */ + const PRE_UNINSTALL = 'pre_uninstall'; + //TODO add the dispatcher when the uninstall command will be doing + + /** + * The PRE_UNINSTALL event occurs at end uninstallation packages. + * + * This event allows you to execute a trigger after any other code in the + * composer is executed. The event listener method receives a + * Composer\Trigger\TriggerEvent instance. + * + * @var string + */ + const POST_UNINSTALL = 'post_uninstall'; + //TODO add the dispatcher when the uninstall command will be doing } From 0bcf3c26d9f5fa093e5d6fad8a0d1b18d6b6fcf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Pluchino?= Date: Wed, 25 Jan 2012 17:08:06 +0100 Subject: [PATCH 4/7] Add .composer/autoload.php in namespace loader --- src/Composer/Trigger/TriggerDispatcher.php | 35 +++++++++++----------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/Composer/Trigger/TriggerDispatcher.php b/src/Composer/Trigger/TriggerDispatcher.php index 5bb9514a9..a321bb5d4 100644 --- a/src/Composer/Trigger/TriggerDispatcher.php +++ b/src/Composer/Trigger/TriggerDispatcher.php @@ -103,11 +103,12 @@ class TriggerDispatcher protected function getListeners(TriggerEvent $event) { $package = $this->composer->getPackage(); + $vendorDir = $this->composer->getInstallationManager()->getVendorPath(true); + $autoloadFile = $vendorDir . '/.composer/autoload.php'; $ex = $package->getExtra(); $al = $package->getAutoload(); $searchListeners = array(); - $searchNamespaces = array(); - $listeners = array(); + $listeners = array(); $namespaces = array(); // get classes @@ -117,34 +118,34 @@ class TriggerDispatcher } } - // get namespaces + // get autoload namespaces + if (file_exists($autoloadFile)) { + $this->loader = require $autoloadFile; + } + + $namespaces = $this->loader->getPrefixes(); + + // get namespaces in composer.json project if (isset($al['psr-0'])) { foreach ($al['psr-0'] as $ns => $path) { - $path = trim(realpath('.') . '/' . $path, '/'); - $searchNamespaces[$ns] = $path; + if (!isset($namespaces[str_replace('\\', '\\\\', $ns)])) { + $this->loader->add($ns, trim(realpath('.').'/'.$path, '/')); + } } + + $this->loader->register(); + $namespaces = $this->loader->getPrefixes(); } // filter class::method have not a namespace registered - foreach ($searchNamespaces as $ns => $path) { + foreach ($namespaces as $ns => $path) { foreach ($searchListeners as $method) { if (0 === strpos($method, $ns)) { $listeners[] = $method; - - if (!in_array($ns, array_keys($namespaces))) { - $namespaces[$ns] = $path; - } } } } - // register namespaces in class loader - foreach ($namespaces as $ns => $path) { - $this->loader->add($ns, $path); - } - - $this->loader->register(); - return $listeners; } } From c7b898d10d8e4eba88a83f021ea0f4138f29ec84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Pluchino?= Date: Wed, 25 Jan 2012 17:31:46 +0100 Subject: [PATCH 5/7] Remove TriggerEvent setter and adding arguments to the constructor --- src/Composer/Trigger/TriggerDispatcher.php | 6 +-- src/Composer/Trigger/TriggerEvent.php | 44 +++++++--------------- 2 files changed, 15 insertions(+), 35 deletions(-) diff --git a/src/Composer/Trigger/TriggerDispatcher.php b/src/Composer/Trigger/TriggerDispatcher.php index a321bb5d4..e63410da5 100644 --- a/src/Composer/Trigger/TriggerDispatcher.php +++ b/src/Composer/Trigger/TriggerDispatcher.php @@ -56,11 +56,7 @@ class TriggerDispatcher */ public function dispatch($eventName) { - $event = new TriggerEvent(); - - $event->setName($eventName); - $event->setComposer($this->composer); - $event->setIO($this->io); + $event = new TriggerEvent($eventName, $this->composer, $this->io); $this->doDispatch($event); } diff --git a/src/Composer/Trigger/TriggerEvent.php b/src/Composer/Trigger/TriggerEvent.php index 1b1a968bb..dce3fa549 100644 --- a/src/Composer/Trigger/TriggerEvent.php +++ b/src/Composer/Trigger/TriggerEvent.php @@ -37,6 +37,20 @@ class TriggerEvent */ private $io; + /** + * Constructor. + * + * @param string $name The event name + * @param Composer $composer The composer objet + * @param IOInterface $io The IOInterface object + */ + public function __construct($name, Composer $composer, IOInterface $io) + { + $this->name = $name; + $this->composer = $composer; + $this->io = $io; + } + /** * Returns the event's name. * @@ -47,16 +61,6 @@ class TriggerEvent return $this->name; } - /** - * Stores the event's name. - * - * @param string $name The event name - */ - public function setName($name) - { - $this->name = $name; - } - /** * Returns the composer instance. * @@ -67,16 +71,6 @@ class TriggerEvent return $this->composer; } - /** - * Stores the composer instance. - * - * @param Composer $composer - */ - public function setComposer(Composer $composer) - { - $this->composer = $composer; - } - /** * Returns the IO instance. * @@ -86,14 +80,4 @@ class TriggerEvent { return $this->io; } - - /** - * Stores the IO instance. - * - * @param IOInterface $io - */ - public function setIO(IOInterface $io) - { - $this->io = $io; - } } From 1699b5c3386fb5dcb5f90a7f50da6019354b4d1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Pluchino?= Date: Wed, 25 Jan 2012 18:23:04 +0100 Subject: [PATCH 6/7] Update trigger faq --- doc/faqs/triggers.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/doc/faqs/triggers.md b/doc/faqs/triggers.md index 237079e6e..05083a40f 100644 --- a/doc/faqs/triggers.md +++ b/doc/faqs/triggers.md @@ -42,6 +42,27 @@ For any given project: } ``` +Trigger Example: +```php + Date: Wed, 25 Jan 2012 18:29:44 +0100 Subject: [PATCH 7/7] Update doc/faqs/triggers.md --- doc/faqs/triggers.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/faqs/triggers.md b/doc/faqs/triggers.md index 05083a40f..14bccbef5 100644 --- a/doc/faqs/triggers.md +++ b/doc/faqs/triggers.md @@ -8,7 +8,7 @@ project. This event is raised before and after each action (install, update). ## Where are the event types defined? -It is in the constant property in `Composer\\Trigger\\TriggerEvents` class. +It is in the constant property in `Composer\Trigger\TriggerEvents` class. ## How is it defined? @@ -22,6 +22,7 @@ in associative array define the event's type. The PSR-0 must be defined, otherwise the trigger will not be triggered. For any given project: + ```json { "extra": { @@ -43,6 +44,7 @@ For any given project: ``` Trigger Example: + ```php