Эх сурвалжийг харах

Merge remote-tracking branch 'francoispluchino/master'

Jordi Boggiano 13 жил өмнө
parent
commit
c2aac6a37c

+ 70 - 0
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
+<?php
+namespace MyVendor\MyRootPackage;
+
+use Composer\Trigger\TriggerEvent;
+
+class MyClass
+{
+    public static function myStaticMethod(TriggerEvent $event)
+    {
+        // code...
+    }
+    
+    public static function myStaticMethod2(TriggerEvent $event)
+    {
+        // code...
+    }
+}
+```
+
+## Informations:
+
+A declared trigger with non existent file or without registered namespace will be ignored.

+ 14 - 0
src/Composer/Command/InstallCommand.php

@@ -12,6 +12,8 @@
 
 namespace Composer\Command;
 
+use Composer\Trigger\TriggerEvents;
+use Composer\Trigger\TriggerDispatcher;
 use Composer\Autoload\AutoloadGenerator;
 use Composer\DependencyResolver;
 use Composer\DependencyResolver\Pool;
@@ -65,6 +67,8 @@ EOT
         $dryRun = (Boolean) $input->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('<info>Generating autoload files</info>');
             $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);
         }
     }
 

+ 147 - 0
src/Composer/Trigger/TriggerDispatcher.php

@@ -0,0 +1,147 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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 <francois.pluchino@opendisplay.com>
+ */
+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;
+    }
+}

+ 83 - 0
src/Composer/Trigger/TriggerEvent.php

@@ -0,0 +1,83 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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 <francois.pluchino@opendisplay.com>
+ */
+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;
+    }
+}

+ 89 - 0
src/Composer/Trigger/TriggerEvents.php

@@ -0,0 +1,89 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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 <francois.pluchino@opendisplay.com>
+ */
+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
+}