1
0
Fork 0

Add system trigger

pull/257/head
François Pluchino 2012-01-24 18:08:41 +01:00
parent e90a8dc4c9
commit 2f6a3ce8b9
5 changed files with 442 additions and 0 deletions

70
doc/faqs/triggers.md Normal file
View File

@ -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.

View File

@ -12,6 +12,10 @@
namespace Composer\Command; namespace Composer\Command;
use Composer\Trigger\TriggerEvents;
use Composer\Trigger\TriggerDispatcher;
use Composer\Autoload\AutoloadGenerator; use Composer\Autoload\AutoloadGenerator;
use Composer\DependencyResolver; use Composer\DependencyResolver;
use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Pool;
@ -65,6 +69,7 @@ EOT
$dryRun = (Boolean) $input->getOption('dry-run'); $dryRun = (Boolean) $input->getOption('dry-run');
$verbose = $dryRun || $input->getOption('verbose'); $verbose = $dryRun || $input->getOption('verbose');
$composer = $this->getComposer(); $composer = $this->getComposer();
$dispatcher = new TriggerDispatcher($this->getApplication());
if ($preferSource) { if ($preferSource) {
$composer->getDownloadManager()->setPreferSource(true); $composer->getDownloadManager()->setPreferSource(true);
@ -82,6 +87,12 @@ EOT
$pool->addRepository($repository); $pool->addRepository($repository);
} }
// dispatch pre event
if (!$dryRun) {
$eventName = $update ? TriggerEvents::PRE_UPDATE : TriggerEvents::PRE_INSTALL;
$dispatcher->dispatch($eventName);
}
// creating requirements request // creating requirements request
$request = new Request($pool); $request = new Request($pool);
if ($update) { if ($update) {
@ -177,6 +188,10 @@ EOT
$output->writeln('<info>Generating autoload files</info>'); $output->writeln('<info>Generating autoload files</info>');
$generator = new AutoloadGenerator; $generator = new AutoloadGenerator;
$generator->dump($localRepo, $composer->getPackage(), $installationManager, $installationManager->getVendorPath().'/.composer'); $generator->dump($localRepo, $composer->getPackage(), $installationManager, $installationManager->getVendorPath().'/.composer');
// dispatch post event
$eventName = $update ? TriggerEvents::POST_UPDATE : TriggerEvents::POST_INSTALL;
$dispatcher->dispatch($eventName);
} }
} }

View File

@ -0,0 +1,108 @@
<?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\Console\Application;
/**
* The Trigger Event.
*
* @author François Pluchino <francois.pluchino@opendisplay.com>
*/
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();
}
}

View File

@ -0,0 +1,184 @@
<?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\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 <francois.pluchino@opendisplay.com>
*/
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;
}
}

View File

@ -0,0 +1,65 @@
<?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';
}