diff --git a/doc/faqs/triggers.md b/doc/faqs/triggers.md
new file mode 100644
index 000000000..14bccbef5
--- /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
+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 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 project:
+
+```json
+{
+ "extra": {
+ "triggers": {
+ "post_install": [
+ "MyVendor\\MyRootPackage\\MyClass::myStaticMethod"
+ ],
+ "post_update": [
+ "MyVendor\\MyRootPackage\\MyClass::myStaticMethod2"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "MyVendor\\MyRootPackage": "my/folder/path/that/contains/triggers/from/the/root/project"
+ }
+ }
+}
+```
+
+Trigger Example:
+
+```php
+getOption('dry-run');
$verbose = $dryRun || $input->getOption('verbose');
$composer = $this->getComposer();
+ $io = $this->getApplication()->getIO();
+ $dispatcher = new TriggerDispatcher($this->getComposer(), $io);
if ($preferSource) {
$composer->getDownloadManager()->setPreferSource(true);
@@ -82,6 +86,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 +187,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/TriggerDispatcher.php b/src/Composer/Trigger/TriggerDispatcher.php
new file mode 100644
index 000000000..e63410da5
--- /dev/null
+++ b/src/Composer/Trigger/TriggerDispatcher.php
@@ -0,0 +1,147 @@
+
+ * 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\IO\IOInterface;
+use Composer\Composer;
+
+/**
+ * The Trigger Dispatcher.
+ *
+ * Example in command:
+ * $dispatcher = new TriggerDispatcher($this->getComposer(), $this->getApplication()->getIO());
+ * // ...
+ * $dispatcher->dispatch(TriggerEvents::POST_INSTALL);
+ * // ...
+ *
+ * @author François Pluchino
+ */
+class TriggerDispatcher
+{
+ protected $composer;
+ protected $io;
+ protected $loader;
+
+ /**
+ * Constructor.
+ *
+ * @param Composer $composer The composer instance
+ * @param IOInterface $io The IOInterface instance
+ */
+ public function __construct(Composer $composer, IOInterface $io)
+ {
+ $this->composer = $composer;
+ $this->io = $io;
+ $this->loader = new ClassLoader();
+ }
+
+ /**
+ * Dispatch the event.
+ *
+ * @param string $eventName The constant in TriggerEvents
+ */
+ public function dispatch($eventName)
+ {
+ $event = new TriggerEvent($eventName, $this->composer, $this->io);
+
+ $this->doDispatch($event);
+ }
+
+ /**
+ * Triggers the listeners of an event.
+ *
+ * @param TriggerEvent $event The event object to pass to the event handlers/listeners.
+ */
+ protected function doDispatch(TriggerEvent $event)
+ {
+ $listeners = $this->getListeners($event);
+
+ foreach ($listeners as $method) {
+ $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 TriggerEvent $event The event object
+ *
+ * @return array The listener classes with event type
+ */
+ 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();
+ $listeners = array();
+ $namespaces = array();
+
+ // get classes
+ if (isset($ex['triggers'][$event->getName()])) {
+ foreach ($ex['triggers'][$event->getName()] as $method) {
+ $searchListeners[] = $method;
+ }
+ }
+
+ // 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) {
+ 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 ($namespaces as $ns => $path) {
+ foreach ($searchListeners as $method) {
+ if (0 === strpos($method, $ns)) {
+ $listeners[] = $method;
+ }
+ }
+ }
+
+ return $listeners;
+ }
+}
diff --git a/src/Composer/Trigger/TriggerEvent.php b/src/Composer/Trigger/TriggerEvent.php
new file mode 100644
index 000000000..dce3fa549
--- /dev/null
+++ b/src/Composer/Trigger/TriggerEvent.php
@@ -0,0 +1,83 @@
+
+ * 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;
+
+ /**
+ * 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.
+ *
+ * @return string The event name
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Returns the composer instance.
+ *
+ * @return Composer
+ */
+ public function getComposer()
+ {
+ return $this->composer;
+ }
+
+ /**
+ * Returns the IO instance.
+ *
+ * @return IOInterface
+ */
+ public function getIO()
+ {
+ return $this->io;
+ }
+}
diff --git a/src/Composer/Trigger/TriggerEvents.php b/src/Composer/Trigger/TriggerEvents.php
new file mode 100644
index 000000000..61392405f
--- /dev/null
+++ b/src/Composer/Trigger/TriggerEvents.php
@@ -0,0 +1,89 @@
+
+ * 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';
+
+ /**
+ * 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
+}