From d7714983c32ebc8d062c80ee73c228095d2fca56 Mon Sep 17 00:00:00 2001 From: Beau Simensen Date: Thu, 19 Jan 2012 22:24:50 -0800 Subject: [PATCH 01/20] Fallback to include `vendor/.composer/autoload.php` for `bin/composer` When Composer is a dependency for a project the `vendor/bin/composer` script will not run as it is looking for `__DIR__.'/../vendor'` which likely will not exist. What I believe is intended is for the script to include the packages `vendor/.composer/autoload.php`. --- bin/composer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/composer b/bin/composer index 8062757f3..b997cff8f 100755 --- a/bin/composer +++ b/bin/composer @@ -1,7 +1,7 @@ #!/usr/bin/env php Date: Sun, 22 Jan 2012 10:37:28 -0800 Subject: [PATCH 02/20] Check for vendor differently for "installs as a dependency" case --- bin/composer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/composer b/bin/composer index b997cff8f..735e84152 100755 --- a/bin/composer +++ b/bin/composer @@ -1,7 +1,7 @@ #!/usr/bin/env php Date: Mon, 23 Jan 2012 15:19:05 +0100 Subject: [PATCH 03/20] fixed broken packages on filesystem --- src/Composer/Command/InstallCommand.php | 6 +- .../Operation/ReplaceOperation.php | 66 +++++++++++++++++++ .../Installer/InstallationManager.php | 12 ++++ src/Composer/Installer/InstallerInterface.php | 7 ++ src/Composer/Installer/LibraryInstaller.php | 19 +++++- src/Composer/Package/BasePackage.php | 11 ++++ .../installer-v1/Installer/Custom.php | 1 + .../installer-v2/Installer/Custom2.php | 1 + .../installer-v3/Installer/Custom2.php | 1 + .../Test/Installer/LibraryInstallerTest.php | 24 +++++++ 10 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 src/Composer/DependencyResolver/Operation/ReplaceOperation.php diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index bf8bca726..dbacf89d5 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -23,6 +23,7 @@ use Composer\Repository\PlatformRepository; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Composer\DependencyResolver\Operation\ReplaceOperation; /** * @author Jordi Boggiano @@ -132,7 +133,10 @@ EOT // TODO this belongs in the solver, but this will do for now to report top-level deps missing at least foreach ($request->getJobs() as $job) { if ('install' === $job['cmd']) { - foreach ($installedRepo->getPackages() as $package) { + foreach ($installedRepo->getPackages() as $package ) { + if ($installedRepo->hasPackage($package) && !$package->isPlatform() && !$installationManager->isPackageInstalled($package)) { + $operations[$job['packageName']] = new ReplaceOperation($package, \Composer\DependencyResolver\Solver::RULE_PACKAGE_NOT_EXIST); + } if (in_array($job['packageName'], $package->getNames())) { continue 2; } diff --git a/src/Composer/DependencyResolver/Operation/ReplaceOperation.php b/src/Composer/DependencyResolver/Operation/ReplaceOperation.php new file mode 100644 index 000000000..1944355ac --- /dev/null +++ b/src/Composer/DependencyResolver/Operation/ReplaceOperation.php @@ -0,0 +1,66 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\DependencyResolver\Operation; + +use Composer\Package\PackageInterface; + +/** + * Solver replace operation. + * + * @author Robert Schönthal + */ +class ReplaceOperation extends SolverOperation +{ + protected $package; + + /** + * Initializes operation. + * + * @param PackageInterface $package package instance + * @param string $reason operation reason + */ + public function __construct(PackageInterface $package, $reason = null) + { + parent::__construct($reason); + + $this->package = $package; + } + + /** + * Returns package instance. + * + * @return PackageInterface + */ + public function getPackage() + { + return $this->package; + } + + /** + * Returns job type. + * + * @return string + */ + public function getJobType() + { + return 'replace'; + } + + /** + * {@inheritDoc} + */ + public function __toString() + { + return 'Replacing '.$this->package->getPrettyName().' ('.$this->package->getPrettyVersion().')'; + } +} diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index 2acb09d5b..2e7404299 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -17,6 +17,7 @@ use Composer\DependencyResolver\Operation\OperationInterface; use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\DependencyResolver\Operation\UninstallOperation; +use Composer\DependencyResolver\Operation\ReplaceOperation; /** * Package operation manager. @@ -160,6 +161,17 @@ class InstallationManager $installer->uninstall($operation->getPackage()); } + /** + * Replaces package. + * + * @param ReplaceOperation $operation operation instance + */ + public function replace(ReplaceOperation $operation) + { + $installer = $this->getInstaller($operation->getPackage()->getType()); + $installer->replace($operation->getPackage()); + } + /** * Returns the installation path of a package * diff --git a/src/Composer/Installer/InstallerInterface.php b/src/Composer/Installer/InstallerInterface.php index 36c023b3b..9e8eafdf1 100644 --- a/src/Composer/Installer/InstallerInterface.php +++ b/src/Composer/Installer/InstallerInterface.php @@ -63,6 +63,13 @@ interface InstallerInterface */ function uninstall(PackageInterface $package); + /** + * Replaces specific package. + * + * @param PackageInterface $package package instance + */ + function replace(PackageInterface $package); + /** * Returns the installation path of a package * diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index 319ac9e98..33624cb47 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -72,7 +72,7 @@ class LibraryInstaller implements InstallerInterface */ public function isInstalled(PackageInterface $package) { - return $this->repository->hasPackage($package); + return $this->repository->hasPackage($package) && is_readable($this->getInstallPath($package)); } /** @@ -123,6 +123,23 @@ class LibraryInstaller implements InstallerInterface $this->repository->removePackage($package); } + /** + * {@inheritDoc} + */ + public function replace(PackageInterface $package) + { + if (!$this->repository->hasPackage($package)) { + throw new \InvalidArgumentException('Package is not installed: '.$package); + } + + $downloadPath = $this->getInstallPath($package); + + $this->removeBinaries($package); + + $this->downloadManager->download($package, $downloadPath); + $this->installBinaries($package); + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Package/BasePackage.php b/src/Composer/Package/BasePackage.php index 786c58ae5..d5a0e5230 100644 --- a/src/Composer/Package/BasePackage.php +++ b/src/Composer/Package/BasePackage.php @@ -15,6 +15,7 @@ namespace Composer\Package; use Composer\Package\LinkConstraint\LinkConstraintInterface; use Composer\Package\LinkConstraint\VersionConstraint; use Composer\Repository\RepositoryInterface; +use Composer\Repository\PlatformRepository; /** * Base class for packages providing name storage and default match implementation @@ -134,6 +135,16 @@ abstract class BasePackage implements PackageInterface $this->repository = $repository; } + /** + * checks if this package is a platform package + * + * @return boolean + */ + public function isPlatform() + { + return $this->getRepository() instanceof PlatformRepository; + } + /** * Returns package unique name, constructed from name, version and release type. * diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php b/tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php index 4bb58ded8..393261893 100644 --- a/tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php +++ b/tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php @@ -14,5 +14,6 @@ class Custom implements InstallerInterface public function install(PackageInterface $package) {} public function update(PackageInterface $initial, PackageInterface $target) {} public function uninstall(PackageInterface $package) {} + public function replace(PackageInterface $package) {} public function getInstallPath(PackageInterface $package) {} } diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v2/Installer/Custom2.php b/tests/Composer/Test/Installer/Fixtures/installer-v2/Installer/Custom2.php index edd264428..b454cbcfd 100644 --- a/tests/Composer/Test/Installer/Fixtures/installer-v2/Installer/Custom2.php +++ b/tests/Composer/Test/Installer/Fixtures/installer-v2/Installer/Custom2.php @@ -14,5 +14,6 @@ class Custom2 implements InstallerInterface public function install(PackageInterface $package) {} public function update(PackageInterface $initial, PackageInterface $target) {} public function uninstall(PackageInterface $package) {} + public function replace(PackageInterface $package) {} public function getInstallPath(PackageInterface $package) {} } diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v3/Installer/Custom2.php b/tests/Composer/Test/Installer/Fixtures/installer-v3/Installer/Custom2.php index db211bed5..8e58c436e 100644 --- a/tests/Composer/Test/Installer/Fixtures/installer-v3/Installer/Custom2.php +++ b/tests/Composer/Test/Installer/Fixtures/installer-v3/Installer/Custom2.php @@ -14,5 +14,6 @@ class Custom2 implements InstallerInterface public function install(PackageInterface $package) {} public function update(PackageInterface $initial, PackageInterface $target) {} public function uninstall(PackageInterface $package) {} + public function replace(PackageInterface $package) {} public function getInstallPath(PackageInterface $package) {} } diff --git a/tests/Composer/Test/Installer/LibraryInstallerTest.php b/tests/Composer/Test/Installer/LibraryInstallerTest.php index ed655f983..0f85c4b21 100644 --- a/tests/Composer/Test/Installer/LibraryInstallerTest.php +++ b/tests/Composer/Test/Installer/LibraryInstallerTest.php @@ -175,6 +175,30 @@ class LibraryInstallerTest extends \PHPUnit_Framework_TestCase $library->uninstall($package); } + public function testReplace() + { + $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository, $this->io); + $package = $this->createPackageMock(); + + $package + ->expects($this->once()) + ->method('getPrettyName') + ->will($this->returnValue('pkg')); + + $this->repository + ->expects($this->once()) + ->method('hasPackage') + ->with($package) + ->will($this->onConsecutiveCalls(true, false)); + + $this->dm + ->expects($this->once()) + ->method('download') + ->with($package, $this->vendorDir.'/pkg'); + + $library->replace($package); + } + public function testGetInstallPath() { $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository, $this->io); From 4d0fae688ea749717acfe51a4856a47df014a037 Mon Sep 17 00:00:00 2001 From: digitalkaoz Date: Mon, 23 Jan 2012 15:43:42 +0100 Subject: [PATCH 04/20] removed replace, reinstalling as install --- src/Composer/Command/InstallCommand.php | 4 +- .../Operation/ReplaceOperation.php | 66 ------------------- .../Installer/InstallationManager.php | 11 ---- src/Composer/Installer/InstallerInterface.php | 7 -- src/Composer/Installer/LibraryInstaller.php | 30 ++++----- .../installer-v1/Installer/Custom.php | 1 - .../installer-v2/Installer/Custom2.php | 1 - .../installer-v3/Installer/Custom2.php | 1 - .../Test/Installer/LibraryInstallerTest.php | 26 +------- 9 files changed, 14 insertions(+), 133 deletions(-) delete mode 100644 src/Composer/DependencyResolver/Operation/ReplaceOperation.php diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index dbacf89d5..10a54cd06 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -23,7 +23,7 @@ use Composer\Repository\PlatformRepository; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Composer\DependencyResolver\Operation\ReplaceOperation; +use Composer\DependencyResolver\Operation\InstallOperation; /** * @author Jordi Boggiano @@ -135,7 +135,7 @@ EOT if ('install' === $job['cmd']) { foreach ($installedRepo->getPackages() as $package ) { if ($installedRepo->hasPackage($package) && !$package->isPlatform() && !$installationManager->isPackageInstalled($package)) { - $operations[$job['packageName']] = new ReplaceOperation($package, \Composer\DependencyResolver\Solver::RULE_PACKAGE_NOT_EXIST); + $operations[$job['packageName']] = new InstallOperation($package, \Composer\DependencyResolver\Solver::RULE_PACKAGE_NOT_EXIST); } if (in_array($job['packageName'], $package->getNames())) { continue 2; diff --git a/src/Composer/DependencyResolver/Operation/ReplaceOperation.php b/src/Composer/DependencyResolver/Operation/ReplaceOperation.php deleted file mode 100644 index 1944355ac..000000000 --- a/src/Composer/DependencyResolver/Operation/ReplaceOperation.php +++ /dev/null @@ -1,66 +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\DependencyResolver\Operation; - -use Composer\Package\PackageInterface; - -/** - * Solver replace operation. - * - * @author Robert Schönthal - */ -class ReplaceOperation extends SolverOperation -{ - protected $package; - - /** - * Initializes operation. - * - * @param PackageInterface $package package instance - * @param string $reason operation reason - */ - public function __construct(PackageInterface $package, $reason = null) - { - parent::__construct($reason); - - $this->package = $package; - } - - /** - * Returns package instance. - * - * @return PackageInterface - */ - public function getPackage() - { - return $this->package; - } - - /** - * Returns job type. - * - * @return string - */ - public function getJobType() - { - return 'replace'; - } - - /** - * {@inheritDoc} - */ - public function __toString() - { - return 'Replacing '.$this->package->getPrettyName().' ('.$this->package->getPrettyVersion().')'; - } -} diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index 2e7404299..04f8b73ec 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -161,17 +161,6 @@ class InstallationManager $installer->uninstall($operation->getPackage()); } - /** - * Replaces package. - * - * @param ReplaceOperation $operation operation instance - */ - public function replace(ReplaceOperation $operation) - { - $installer = $this->getInstaller($operation->getPackage()->getType()); - $installer->replace($operation->getPackage()); - } - /** * Returns the installation path of a package * diff --git a/src/Composer/Installer/InstallerInterface.php b/src/Composer/Installer/InstallerInterface.php index 9e8eafdf1..36c023b3b 100644 --- a/src/Composer/Installer/InstallerInterface.php +++ b/src/Composer/Installer/InstallerInterface.php @@ -63,13 +63,6 @@ interface InstallerInterface */ function uninstall(PackageInterface $package); - /** - * Replaces specific package. - * - * @param PackageInterface $package package instance - */ - function replace(PackageInterface $package); - /** * Returns the installation path of a package * diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index 33624cb47..8aae018c1 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -80,11 +80,20 @@ class LibraryInstaller implements InstallerInterface */ public function install(PackageInterface $package) { - $downloadPath = $this->getInstallPath($package); + $broken = !is_readable($this->getInstallPath($package)); + //remove the binaries first if its missing on filesystem + if ($broken) { + $this->removeBinaries($package); + } + + $downloadPath = $this->getInstallPath($package); $this->downloadManager->download($package, $downloadPath); $this->installBinaries($package); - $this->repository->addPackage(clone $package); + + if($broken) { + $this->repository->addPackage(clone $package); + } } /** @@ -123,23 +132,6 @@ class LibraryInstaller implements InstallerInterface $this->repository->removePackage($package); } - /** - * {@inheritDoc} - */ - public function replace(PackageInterface $package) - { - if (!$this->repository->hasPackage($package)) { - throw new \InvalidArgumentException('Package is not installed: '.$package); - } - - $downloadPath = $this->getInstallPath($package); - - $this->removeBinaries($package); - - $this->downloadManager->download($package, $downloadPath); - $this->installBinaries($package); - } - /** * {@inheritDoc} */ diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php b/tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php index 393261893..4bb58ded8 100644 --- a/tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php +++ b/tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php @@ -14,6 +14,5 @@ class Custom implements InstallerInterface public function install(PackageInterface $package) {} public function update(PackageInterface $initial, PackageInterface $target) {} public function uninstall(PackageInterface $package) {} - public function replace(PackageInterface $package) {} public function getInstallPath(PackageInterface $package) {} } diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v2/Installer/Custom2.php b/tests/Composer/Test/Installer/Fixtures/installer-v2/Installer/Custom2.php index b454cbcfd..edd264428 100644 --- a/tests/Composer/Test/Installer/Fixtures/installer-v2/Installer/Custom2.php +++ b/tests/Composer/Test/Installer/Fixtures/installer-v2/Installer/Custom2.php @@ -14,6 +14,5 @@ class Custom2 implements InstallerInterface public function install(PackageInterface $package) {} public function update(PackageInterface $initial, PackageInterface $target) {} public function uninstall(PackageInterface $package) {} - public function replace(PackageInterface $package) {} public function getInstallPath(PackageInterface $package) {} } diff --git a/tests/Composer/Test/Installer/Fixtures/installer-v3/Installer/Custom2.php b/tests/Composer/Test/Installer/Fixtures/installer-v3/Installer/Custom2.php index 8e58c436e..db211bed5 100644 --- a/tests/Composer/Test/Installer/Fixtures/installer-v3/Installer/Custom2.php +++ b/tests/Composer/Test/Installer/Fixtures/installer-v3/Installer/Custom2.php @@ -14,6 +14,5 @@ class Custom2 implements InstallerInterface public function install(PackageInterface $package) {} public function update(PackageInterface $initial, PackageInterface $target) {} public function uninstall(PackageInterface $package) {} - public function replace(PackageInterface $package) {} public function getInstallPath(PackageInterface $package) {} } diff --git a/tests/Composer/Test/Installer/LibraryInstallerTest.php b/tests/Composer/Test/Installer/LibraryInstallerTest.php index 0f85c4b21..c5cb9d538 100644 --- a/tests/Composer/Test/Installer/LibraryInstallerTest.php +++ b/tests/Composer/Test/Installer/LibraryInstallerTest.php @@ -85,7 +85,7 @@ class LibraryInstallerTest extends \PHPUnit_Framework_TestCase $package = $this->createPackageMock(); $package - ->expects($this->once()) + ->expects($this->exactly(2)) ->method('getPrettyName') ->will($this->returnValue('some/package')); @@ -175,30 +175,6 @@ class LibraryInstallerTest extends \PHPUnit_Framework_TestCase $library->uninstall($package); } - public function testReplace() - { - $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository, $this->io); - $package = $this->createPackageMock(); - - $package - ->expects($this->once()) - ->method('getPrettyName') - ->will($this->returnValue('pkg')); - - $this->repository - ->expects($this->once()) - ->method('hasPackage') - ->with($package) - ->will($this->onConsecutiveCalls(true, false)); - - $this->dm - ->expects($this->once()) - ->method('download') - ->with($package, $this->vendorDir.'/pkg'); - - $library->replace($package); - } - public function testGetInstallPath() { $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository, $this->io); From 3d43bdce4526f9826ab92fb68bba2ba8636fedcc Mon Sep 17 00:00:00 2001 From: digitalkaoz Date: Mon, 23 Jan 2012 16:17:57 +0100 Subject: [PATCH 05/20] refactored install --- src/Composer/Command/InstallCommand.php | 3 ++- src/Composer/Installer/InstallationManager.php | 1 - src/Composer/Installer/LibraryInstaller.php | 6 ++---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index 10a54cd06..2a26f48c0 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -24,6 +24,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\DependencyResolver\Solver; /** * @author Jordi Boggiano @@ -135,7 +136,7 @@ EOT if ('install' === $job['cmd']) { foreach ($installedRepo->getPackages() as $package ) { if ($installedRepo->hasPackage($package) && !$package->isPlatform() && !$installationManager->isPackageInstalled($package)) { - $operations[$job['packageName']] = new InstallOperation($package, \Composer\DependencyResolver\Solver::RULE_PACKAGE_NOT_EXIST); + $operations[$job['packageName']] = new InstallOperation($package, Solver::RULE_PACKAGE_NOT_EXIST); } if (in_array($job['packageName'], $package->getNames())) { continue 2; diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index 04f8b73ec..2acb09d5b 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -17,7 +17,6 @@ use Composer\DependencyResolver\Operation\OperationInterface; use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\DependencyResolver\Operation\UninstallOperation; -use Composer\DependencyResolver\Operation\ReplaceOperation; /** * Package operation manager. diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index 8aae018c1..f9c4828fb 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -80,10 +80,8 @@ class LibraryInstaller implements InstallerInterface */ public function install(PackageInterface $package) { - $broken = !is_readable($this->getInstallPath($package)); - //remove the binaries first if its missing on filesystem - if ($broken) { + if (!is_readable($this->getInstallPath($package)) && $this->repository->hasPackage($package)) { $this->removeBinaries($package); } @@ -91,7 +89,7 @@ class LibraryInstaller implements InstallerInterface $this->downloadManager->download($package, $downloadPath); $this->installBinaries($package); - if($broken) { + if(!$this->repository->hasPackage($package)) { $this->repository->addPackage(clone $package); } } 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 06/20] 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 07/20] 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 08/20] 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 09/20] 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 10/20] 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 11/20] 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 12/20] 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 Date: Sun, 5 Feb 2012 16:14:25 +0100 Subject: [PATCH 13/20] Refactor scripts --- src/Composer/Command/InstallCommand.php | 16 +- src/Composer/Package/MemoryPackage.php | 17 ++ src/Composer/Package/PackageInterface.php | 7 + src/Composer/Script/CommandEvent.php | 26 +++ .../TriggerEvent.php => Script/Event.php} | 166 +++++++++--------- src/Composer/Script/EventDispatcher.php | 120 +++++++++++++ src/Composer/Script/PackageEvent.php | 54 ++++++ src/Composer/Script/ScriptEvents.php | 112 ++++++++++++ src/Composer/Trigger/TriggerDispatcher.php | 147 ---------------- src/Composer/Trigger/TriggerEvents.php | 89 ---------- 10 files changed, 428 insertions(+), 326 deletions(-) create mode 100644 src/Composer/Script/CommandEvent.php rename src/Composer/{Trigger/TriggerEvent.php => Script/Event.php} (90%) create mode 100644 src/Composer/Script/EventDispatcher.php create mode 100644 src/Composer/Script/PackageEvent.php create mode 100644 src/Composer/Script/ScriptEvents.php delete mode 100644 src/Composer/Trigger/TriggerDispatcher.php delete mode 100644 src/Composer/Trigger/TriggerEvents.php diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index f6f2594b9..5c4beba90 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -12,8 +12,8 @@ namespace Composer\Command; -use Composer\Trigger\TriggerEvents; -use Composer\Trigger\TriggerDispatcher; +use Composer\Script\ScriptEvents; +use Composer\Script\EventDispatcher; use Composer\Autoload\AutoloadGenerator; use Composer\DependencyResolver; use Composer\DependencyResolver\Pool; @@ -68,7 +68,7 @@ EOT $verbose = $dryRun || $input->getOption('verbose'); $composer = $this->getComposer(); $io = $this->getApplication()->getIO(); - $dispatcher = new TriggerDispatcher($this->getComposer(), $io); + $dispatcher = new EventDispatcher($this->getComposer(), $io); if ($preferSource) { $composer->getDownloadManager()->setPreferSource(true); @@ -88,8 +88,8 @@ EOT // dispatch pre event if (!$dryRun) { - $eventName = $update ? TriggerEvents::PRE_UPDATE : TriggerEvents::PRE_INSTALL; - $dispatcher->dispatch($eventName); + $eventName = $update ? ScriptEvents::PRE_UPDATE_CMD : ScriptEvents::PRE_INSTALL_CMD; + $dispatcher->dispatchCommandEvent($eventName); } // creating requirements request @@ -172,7 +172,9 @@ EOT $output->writeln((string) $operation); } if (!$dryRun) { + $dispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType())), $operation); $installationManager->execute($operation); + $dispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::POST_PACKAGE_'.strtoupper($operation->getJobType())), $operation); } } @@ -189,8 +191,8 @@ EOT $generator->dump($localRepo, $composer->getPackage(), $installationManager, $installationManager->getVendorPath().'/.composer'); // dispatch post event - $eventName = $update ? TriggerEvents::POST_UPDATE : TriggerEvents::POST_INSTALL; - $dispatcher->dispatch($eventName); + $eventName = $update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD; + $dispatcher->dispatchCommandEvent($eventName); } } diff --git a/src/Composer/Package/MemoryPackage.php b/src/Composer/Package/MemoryPackage.php index 9167fde90..02222e43b 100644 --- a/src/Composer/Package/MemoryPackage.php +++ b/src/Composer/Package/MemoryPackage.php @@ -40,6 +40,7 @@ class MemoryPackage extends BasePackage protected $homepage; protected $extra = array(); protected $binaries = array(); + protected $scripts = array(); protected $requires = array(); protected $conflicts = array(); @@ -128,6 +129,22 @@ class MemoryPackage extends BasePackage return $this->binaries; } + /** + * @param array $scripts + */ + public function setScripts(array $scripts) + { + $this->scripts = $scripts; + } + + /** + * {@inheritDoc} + */ + public function getScripts() + { + return $this->scripts; + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Package/PackageInterface.php b/src/Composer/Package/PackageInterface.php index 4616143b5..c8f92b581 100644 --- a/src/Composer/Package/PackageInterface.php +++ b/src/Composer/Package/PackageInterface.php @@ -152,6 +152,13 @@ interface PackageInterface */ function getDistSha1Checksum(); + /** + * Returns the scripts of this package + * + * @return array array('script name' => array('listeners')) + */ + function getScripts(); + /** * Returns the version of this package * diff --git a/src/Composer/Script/CommandEvent.php b/src/Composer/Script/CommandEvent.php new file mode 100644 index 000000000..2526a99d7 --- /dev/null +++ b/src/Composer/Script/CommandEvent.php @@ -0,0 +1,26 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Script; + +use Composer\Composer; +use Composer\IO\IOInterface; +use Composer\Package\PackageInterface; + +/** + * The Command Event. + * + * @author François Pluchino + */ +class CommandEvent extends Event +{ +} diff --git a/src/Composer/Trigger/TriggerEvent.php b/src/Composer/Script/Event.php similarity index 90% rename from src/Composer/Trigger/TriggerEvent.php rename to src/Composer/Script/Event.php index dce3fa549..239d494c8 100644 --- a/src/Composer/Trigger/TriggerEvent.php +++ b/src/Composer/Script/Event.php @@ -1,83 +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; - } -} + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Script; + +use Composer\Composer; +use Composer\IO\IOInterface; + +/** + * The base event class + * + * @author François Pluchino + */ +class Event +{ + /** + * @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/Script/EventDispatcher.php b/src/Composer/Script/EventDispatcher.php new file mode 100644 index 000000000..049762f7c --- /dev/null +++ b/src/Composer/Script/EventDispatcher.php @@ -0,0 +1,120 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Script; + +use Composer\Json\JsonFile; +use Composer\Repository\FilesystemRepository; +use Composer\Autoload\ClassLoader; +use Composer\Package\PackageInterface; +use Composer\IO\IOInterface; +use Composer\Composer; +use Composer\DependencyResolver\Operation\OperationInterface; + +/** + * The Event Dispatcher. + * + * Example in command: + * $dispatcher = new EventDispatcher($this->getComposer(), $this->getApplication()->getIO()); + * // ... + * $dispatcher->dispatch(ScriptEvents::POST_INSTALL_CMD); + * // ... + * + * @author François Pluchino + * @author Jordi Boggiano + */ +class EventDispatcher +{ + 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(); + $this->loader->register(); + } + + /** + * Dispatch a package event. + * + * @param string $eventName The constant in ScriptEvents + * @param OperationInterface $operation The package being installed/updated/removed + */ + public function dispatchPackageEvent($eventName, OperationInterface $operation) + { + $this->doDispatch(new PackageEvent($eventName, $this->composer, $this->io, $operation)); + } + + /** + * Dispatch a command event. + * + * @param string $eventName The constant in ScriptEvents + */ + public function dispatchCommandEvent($eventName) + { + $this->doDispatch(new CommandEvent($eventName, $this->composer, $this->io)); + } + + /** + * Triggers the listeners of an event. + * + * @param Event $event The event object to pass to the event handlers/listeners. + */ + protected function doDispatch(Event $event) + { + $listeners = $this->getListeners($event); + + foreach ($listeners as $callable) { + $className = substr($callable, 0, strpos($callable, '::')); + $methodName = substr($callable, strpos($callable, '::') + 2); + + if (!class_exists($className)) { + throw new \UnexpectedValueException('Class '.$className.' is not autoloadable, can not call '.$event->getName().' script'); + } + if (!is_callable($callable)) { + throw new \UnexpectedValueException('Method '.$callable.' is not callable, can not call '.$event->getName().' script'); + } + + $className::$methodName($event); + } + } + + /** + * @param Event $event Event object + * @return array Listeners + */ + protected function getListeners(Event $event) + { + $package = $this->composer->getPackage(); + $scripts = $package->getScripts(); + $autoload = $package->getAutoload(); + + // get namespaces in composer.json project + if (!$this->loader->getPrefixes() && isset($autoload['psr-0'])) { + krsort($autoload['psr-0']); + foreach ($autoload['psr-0'] as $ns => $path) { + $this->loader->add($ns, rtrim(getcwd().'/'.$path, '/')); + } + } + + return isset($scripts[$event->getName()]) ? $scripts[$event->getName()] : array(); + } +} diff --git a/src/Composer/Script/PackageEvent.php b/src/Composer/Script/PackageEvent.php new file mode 100644 index 000000000..7d0e39407 --- /dev/null +++ b/src/Composer/Script/PackageEvent.php @@ -0,0 +1,54 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Script; + +use Composer\Composer; +use Composer\IO\IOInterface; +use Composer\DependencyResolver\Operation\OperationInterface; + +/** + * The Package Event. + * + * @author Jordi Boggiano + */ +class PackageEvent extends Event +{ + /** + * @var OperationInterface The package instance + */ + private $operation; + + /** + * Constructor. + * + * @param string $name The event name + * @param Composer $composer The composer objet + * @param IOInterface $io The IOInterface object + * @param OperationInterface $operation The operation object + */ + public function __construct($name, Composer $composer, IOInterface $io, OperationInterface $operation) + { + parent::__construct($name, $composer, $io); + $this->operation = $operation; + } + + /** + * Returns the package instance. + * + * @return OperationInterface + */ + public function getOperation() + { + return $this->operation; + } +} diff --git a/src/Composer/Script/ScriptEvents.php b/src/Composer/Script/ScriptEvents.php new file mode 100644 index 000000000..9f5131345 --- /dev/null +++ b/src/Composer/Script/ScriptEvents.php @@ -0,0 +1,112 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Script; + +/** + * The Script Events. + * + * @author François Pluchino + * @author Jordi Boggiano + */ +class ScriptEvents +{ + /** + * The PRE_INSTALL_CMD event occurs before the install command is executed. + * + * The event listener method receives a Composer\Script\CommandEvent instance. + * + * @var string + */ + const PRE_INSTALL_CMD = 'pre-install-cmd'; + + /** + * The POST_INSTALL_CMD event occurs after the install command is executed. + * + * The event listener method receives a Composer\Script\CommandEvent instance. + * + * @var string + */ + const POST_INSTALL_CMD = 'post-install-cmd'; + + /** + * The PRE_UPDATE_CMD event occurs before the update command is executed. + * + * The event listener method receives a Composer\Script\CommandEvent instance. + * + * @var string + */ + const PRE_UPDATE_CMD = 'pre-update-cmd'; + + /** + * The POST_UPDATE_CMD event occurs after the update command is executed. + * + * The event listener method receives a Composer\Script\CommandEvent instance. + * + * @var string + */ + const POST_UPDATE_CMD = 'post-update-cmd'; + + /** + * The PRE_PACKAGE_INSTALL event occurs before a package is installed. + * + * The event listener method receives a Composer\Script\PackageEvent instance. + * + * @var string + */ + const PRE_PACKAGE_INSTALL = 'pre-package-install'; + + /** + * The POST_PACKAGE_INSTALL event occurs after a package is installed. + * + * The event listener method receives a Composer\Script\PackageEvent instance. + * + * @var string + */ + const POST_PACKAGE_INSTALL = 'post-package-install'; + + /** + * The PRE_PACKAGE_UPDATE event occurs before a package is updated. + * + * The event listener method receives a Composer\Script\PackageEvent instance. + * + * @var string + */ + const PRE_PACKAGE_UPDATE = 'pre-package-update'; + + /** + * The POST_PACKAGE_UPDATE event occurs after a package is updated. + * + * The event listener method receives a Composer\Script\PackageEvent instance. + * + * @var string + */ + const POST_PACKAGE_UPDATE = 'post-package-update'; + + /** + * The PRE_PACKAGE_UNINSTALL event occurs before a package has been uninstalled. + * + * The event listener method receives a Composer\Script\PackageEvent instance. + * + * @var string + */ + const PRE_PACKAGE_UNINSTALL = 'pre-package-uninstall'; + + /** + * The POST_PACKAGE_UNINSTALL event occurs after a package has been uninstalled. + * + * The event listener method receives a Composer\Script\PackageEvent instance. + * + * @var string + */ + const POST_PACKAGE_UNINSTALL = 'post-package-uninstall'; +} diff --git a/src/Composer/Trigger/TriggerDispatcher.php b/src/Composer/Trigger/TriggerDispatcher.php deleted file mode 100644 index e63410da5..000000000 --- a/src/Composer/Trigger/TriggerDispatcher.php +++ /dev/null @@ -1,147 +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\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/TriggerEvents.php b/src/Composer/Trigger/TriggerEvents.php deleted file mode 100644 index 61392405f..000000000 --- a/src/Composer/Trigger/TriggerEvents.php +++ /dev/null @@ -1,89 +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; - -/** - * 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 -} From d43dd336325527ba23eb1914c966d4ef4eaba860 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 5 Feb 2012 16:14:44 +0100 Subject: [PATCH 14/20] Add scripts loading --- src/Composer/Package/Loader/ArrayLoader.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index 183818826..6d6f4f15d 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -74,6 +74,13 @@ class ArrayLoader $package->setBinaries($config['bin']); } + if (isset($config['scripts']) && is_array($config['scripts'])) { + foreach ($config['scripts'] as $event => $listeners) { + $config['scripts'][$event]= (array) $listeners; + } + $package->setScripts($config['scripts']); + } + if (!empty($config['description']) && is_string($config['description'])) { $package->setDescription($config['description']); } From 39ab5017aa7339f26d3a748adfb511a9aec6121a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 5 Feb 2012 16:27:55 +0100 Subject: [PATCH 15/20] Update scripts docs --- doc/faqs/scripts.md | 66 +++++++++++++++++++++++++++++++++++++++++ doc/faqs/triggers.md | 70 -------------------------------------------- 2 files changed, 66 insertions(+), 70 deletions(-) create mode 100644 doc/faqs/scripts.md delete mode 100644 doc/faqs/triggers.md diff --git a/doc/faqs/scripts.md b/doc/faqs/scripts.md new file mode 100644 index 000000000..7b50ae78c --- /dev/null +++ b/doc/faqs/scripts.md @@ -0,0 +1,66 @@ +# Scripts + +## What is a script? + +A script is a callback (defined as a static method) that will be called +when the event it listens on is triggered. + +**Scripts are only executed on the root package, not on the dependencies +that are installed.** + + +## Event types + +- **pre-install-cmd**: occurs before the install command is executed. +- **post-install-cmd**: occurs after the install command is executed. +- **pre-update-cmd**: occurs before the update command is executed. +- **post-update-cmd**: occurs after the update command is executed. +- **pre-package-install**: occurs before a package is installed. +- **post-package-install**: occurs after a package is installed. +- **pre-package-update**: occurs before a package is updated. +- **post-package-update**: occurs after a package is updated. +- **pre-package-uninstall**: occurs before a package has been uninstalled. +- **post-package-uninstall**: occurs after a package has been uninstalled. + + +## Defining scripts + +Scripts are defined by adding the `scripts` key to a project's `composer.json`. + +They are specified as an array of classes and static method names. + +The classes used as scripts must be autoloadable via Composer's autoload +functionality. + +Script definition example: + +```json +{ + "scripts": { + "post-update-cmd": "MyVendor\\MyClass::postUpdate", + "post-package-install": ["MyVendor\\MyClass::postPackageInstall"] + } +} +``` + +Script listener example: + +```php +getOperation()->getPackage(); + // do stuff + } +} +``` diff --git a/doc/faqs/triggers.md b/doc/faqs/triggers.md deleted file mode 100644 index 14bccbef5..000000000 --- a/doc/faqs/triggers.md +++ /dev/null @@ -1,70 +0,0 @@ -# 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 - Date: Sun, 5 Feb 2012 20:21:06 +0100 Subject: [PATCH 16/20] Fix bug on authorization of file download for the private repositories --- src/Composer/Repository/Vcs/VcsDriver.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Composer/Repository/Vcs/VcsDriver.php b/src/Composer/Repository/Vcs/VcsDriver.php index cf7af1fa9..6addf26e7 100644 --- a/src/Composer/Repository/Vcs/VcsDriver.php +++ b/src/Composer/Repository/Vcs/VcsDriver.php @@ -79,6 +79,7 @@ abstract class VcsDriver } else if (null !== $this->io->getLastUsername()) { $authStr = base64_encode($this->io->getLastUsername() . ':' . $this->io->getLastPassword()); $params['http'] = array('header' => "Authorization: Basic $authStr\r\n"); + $this->io->setAuthorization($this->url, $this->io->getLastUsername(), $this->io->getLastPassword()); } $ctx = stream_context_create($params); From d35a1a4a9d25bf6a9f6d12edb8526a802e77e014 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 5 Feb 2012 21:08:43 +0100 Subject: [PATCH 17/20] Stylistic fixes to the previous merge --- src/Composer/Installer/LibraryInstaller.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index f9c4828fb..b1bdfa60d 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -80,16 +80,16 @@ class LibraryInstaller implements InstallerInterface */ public function install(PackageInterface $package) { - //remove the binaries first if its missing on filesystem - if (!is_readable($this->getInstallPath($package)) && $this->repository->hasPackage($package)) { + $downloadPath = $this->getInstallPath($package); + + // remove the binaries if it appears the package files are missing + if (!is_readable($downloadPath) && $this->repository->hasPackage($package)) { $this->removeBinaries($package); } - $downloadPath = $this->getInstallPath($package); $this->downloadManager->download($package, $downloadPath); $this->installBinaries($package); - - if(!$this->repository->hasPackage($package)) { + if (!$this->repository->hasPackage($package)) { $this->repository->addPackage(clone $package); } } From 2262a3f864eebaa404699c6186d9baff6f04b401 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 5 Feb 2012 21:13:16 +0100 Subject: [PATCH 18/20] Fix test --- tests/Composer/Test/Installer/LibraryInstallerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Composer/Test/Installer/LibraryInstallerTest.php b/tests/Composer/Test/Installer/LibraryInstallerTest.php index c5cb9d538..ed655f983 100644 --- a/tests/Composer/Test/Installer/LibraryInstallerTest.php +++ b/tests/Composer/Test/Installer/LibraryInstallerTest.php @@ -85,7 +85,7 @@ class LibraryInstallerTest extends \PHPUnit_Framework_TestCase $package = $this->createPackageMock(); $package - ->expects($this->exactly(2)) + ->expects($this->once()) ->method('getPrettyName') ->will($this->returnValue('some/package')); From 26e909a1a0260f0a2ab1cd5aa4c427dc8be4ff71 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 5 Feb 2012 21:23:52 +0100 Subject: [PATCH 19/20] Apply nested-autoload support to all relevant files --- bin/compile | 9 ++++----- bin/composer | 9 ++++----- tests/bootstrap.php | 6 ++++++ 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/bin/compile b/bin/compile index 62e111228..bd8426693 100755 --- a/bin/compile +++ b/bin/compile @@ -1,11 +1,10 @@ #!/usr/bin/env php add('Composer\Test', __DIR__); $loader->register(); From 385075cda3832f69425b9bb47d39eeca899021a2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 5 Feb 2012 22:38:42 +0100 Subject: [PATCH 20/20] Cosmetic fixes --- src/Composer/Command/SearchCommand.php | 2 +- src/Composer/Command/ShowCommand.php | 2 +- src/Composer/Command/ValidateCommand.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Composer/Command/SearchCommand.php b/src/Composer/Command/SearchCommand.php index 10df68e35..c7c6ebd3d 100644 --- a/src/Composer/Command/SearchCommand.php +++ b/src/Composer/Command/SearchCommand.php @@ -26,7 +26,7 @@ class SearchCommand extends Command { $this ->setName('search') - ->setDescription('search for packages') + ->setDescription('Search for packages') ->setDefinition(array( new InputArgument('tokens', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'tokens to search for'), )) diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 334cd8997..9de3ed2de 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -28,7 +28,7 @@ class ShowCommand extends Command { $this ->setName('show') - ->setDescription('show package details') + ->setDescription('Show package details') ->setDefinition(array( new InputArgument('package', InputArgument::REQUIRED, 'the package to inspect'), new InputArgument('version', InputArgument::OPTIONAL, 'the version'), diff --git a/src/Composer/Command/ValidateCommand.php b/src/Composer/Command/ValidateCommand.php index 5e244feff..692b6a323 100644 --- a/src/Composer/Command/ValidateCommand.php +++ b/src/Composer/Command/ValidateCommand.php @@ -28,7 +28,7 @@ class ValidateCommand extends Command { $this ->setName('validate') - ->setDescription('validates a composer.json') + ->setDescription('Validates a composer.json') ->setDefinition(array( new InputArgument('file', InputArgument::OPTIONAL, 'path to composer.json file', './composer.json') ))