From a8f74a0983306306bfaac6c2d853bed15a3703f1 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 29 Nov 2012 09:24:28 +0100 Subject: [PATCH] Allow notification from locked installs, fixes #1368, fixes #1372, fixes #1369 --- src/Composer/Command/CreateProjectCommand.php | 26 ++++--- src/Composer/Factory.php | 7 +- .../Installer/InstallationManager.php | 69 +++++++++++++++++-- src/Composer/Package/AliasPackage.php | 4 ++ src/Composer/Package/Dumper/ArrayDumper.php | 1 + src/Composer/Package/Loader/ArrayLoader.php | 4 ++ src/Composer/Package/Package.php | 19 +++++ src/Composer/Package/PackageInterface.php | 7 ++ .../Repository/ComposerRepository.php | 60 +--------------- .../NotifiableRepositoryInterface.php | 28 -------- .../Installer/InstallationManagerTest.php | 40 +++++++++-- 11 files changed, 154 insertions(+), 111 deletions(-) delete mode 100644 src/Composer/Repository/NotifiableRepositoryInterface.php diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 5cc8d6fb7..1a3086ed8 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -16,14 +16,15 @@ use Composer\Config; use Composer\Factory; use Composer\Installer; use Composer\Installer\ProjectInstaller; +use Composer\Installer\InstallationManager; use Composer\IO\IOInterface; use Composer\Package\BasePackage; use Composer\Package\LinkConstraint\VersionConstraint; use Composer\DependencyResolver\Pool; +use Composer\DependencyResolver\Operation\InstallOperation; use Composer\Repository\ComposerRepository; use Composer\Repository\CompositeRepository; use Composer\Repository\FilesystemRepository; -use Composer\Repository\NotifiableRepositoryInterface; use Composer\Repository\InstalledFilesystemRepository; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -117,11 +118,6 @@ EOT throw new \InvalidArgumentException('Invalid stability provided ('.$stability.'), must be one of: '.implode(', ', array_keys(BasePackage::$stabilities))); } - $dm = $this->createDownloadManager($io, $config); - if ($preferSource) { - $dm->setPreferSource(true); - } - if (null === $repositoryUrl) { $sourceRepo = new CompositeRepository(Factory::createDefaultRepositories($io, $config)); } elseif ("json" === pathinfo($repositoryUrl, PATHINFO_EXTENSION)) { @@ -179,13 +175,16 @@ EOT $package->setSourceReference(substr($package->getPrettyVersion(), 4)); } + $dm = $this->createDownloadManager($io, $config); $dm->setPreferSource($preferSource) ->setPreferDist($preferDist); + $projectInstaller = new ProjectInstaller($directory, $dm); - $projectInstaller->install(new InstalledFilesystemRepository(new JsonFile('php://memory')), $package); - if ($package->getRepository() instanceof NotifiableRepositoryInterface) { - $package->getRepository()->notifyInstall($package); - } + $im = $this->createInstallationManager(); + $im->addInstaller($projectInstaller); + $im->install(new InstalledFilesystemRepository(new JsonFile('php://memory')), new InstallOperation($package)); + $im->notifyInstalls(); + $installedFromVcs = 'source' === $package->getInstallationSource(); $io->write('Created project in ' . $directory . ''); @@ -194,7 +193,7 @@ EOT putenv('COMPOSER_ROOT_VERSION='.$package->getPrettyVersion()); // clean up memory - unset($dm, $config, $projectInstaller, $sourceRepo, $package); + unset($dm, $im, $config, $projectInstaller, $sourceRepo, $package); // install dependencies of the created project $composer = Factory::create($io); @@ -248,4 +247,9 @@ EOT return $factory->createDownloadManager($io, $config); } + + protected function createInstallationManager() + { + return new InstallationManager(); + } } diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 911805dce..65f2dd526 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -223,7 +223,7 @@ class Factory $dm = $this->createDownloadManager($io, $config); // initialize installation manager - $im = $this->createInstallationManager($config); + $im = $this->createInstallationManager(); // initialize composer $composer = new Composer(); @@ -305,12 +305,11 @@ class Factory } /** - * @param Config $config * @return Installer\InstallationManager */ - protected function createInstallationManager(Config $config) + protected function createInstallationManager() { - return new Installer\InstallationManager($config->get('vendor-dir')); + return new Installer\InstallationManager(); } /** diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index 2a696ec21..5c239d0c6 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -15,7 +15,6 @@ namespace Composer\Installer; use Composer\Package\PackageInterface; use Composer\Package\AliasPackage; use Composer\Repository\RepositoryInterface; -use Composer\Repository\NotifiableRepositoryInterface; use Composer\Repository\InstalledRepositoryInterface; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\DependencyResolver\Operation\InstallOperation; @@ -23,6 +22,7 @@ use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\DependencyResolver\Operation\UninstallOperation; use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation; use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation; +use Composer\Util\StreamContextFactory; /** * Package operation manager. @@ -52,6 +52,19 @@ class InstallationManager $this->cache = array(); } + /** + * Removes installer + * + * @param InstallerInterface $installer installer instance + */ + public function removeInstaller(InstallerInterface $installer) + { + if (false !== ($key = array_search($installer, $this->installers, true))) { + array_splice($this->installers, $key, 1); + $this->cache = array(); + } + } + /** * Disables custom installers. * @@ -219,16 +232,60 @@ class InstallationManager public function notifyInstalls() { - foreach ($this->notifiablePackages as $packages) { - $repo = reset($packages)->getRepository(); - $repo->notifyInstalls($packages); + foreach ($this->notifiablePackages as $repoUrl => $packages) { + // non-batch API, deprecated + if (strpos($repoUrl, '%package%')) { + foreach ($packages as $package) { + $url = str_replace('%package%', $package->getPrettyName(), $repoUrl); + + $params = array( + 'version' => $package->getPrettyVersion(), + 'version_normalized' => $package->getVersion(), + ); + $opts = array('http' => + array( + 'method' => 'POST', + 'header' => 'Content-type: application/x-www-form-urlencoded', + 'content' => http_build_query($params, '', '&'), + 'timeout' => 3, + ) + ); + + $context = StreamContextFactory::getContext($opts); + @file_get_contents($url, false, $context); + } + + return; + } + + $postData = array('downloads' => array()); + foreach ($packages as $package) { + $postData['downloads'][] = array( + 'name' => $package->getPrettyName(), + 'version' => $package->getVersion(), + ); + } + + $opts = array('http' => + array( + 'method' => 'POST', + 'header' => 'Content-Type: application/json', + 'content' => json_encode($postData), + 'timeout' => 6, + ) + ); + + $context = StreamContextFactory::getContext($opts); + @file_get_contents($repoUrl, false, $context); } + + $this->reset(); } private function markForNotification(PackageInterface $package) { - if ($package->getRepository() instanceof NotifiableRepositoryInterface) { - $this->notifiablePackages[spl_object_hash($package->getRepository())][$package->getName()] = $package; + if ($package->getNotificationUrl()) { + $this->notifiablePackages[$package->getNotificationUrl()][$package->getName()] = $package; } } } diff --git a/src/Composer/Package/AliasPackage.php b/src/Composer/Package/AliasPackage.php index 965570807..e2f748092 100644 --- a/src/Composer/Package/AliasPackage.php +++ b/src/Composer/Package/AliasPackage.php @@ -307,6 +307,10 @@ class AliasPackage extends BasePackage implements CompletePackageInterface { return $this->aliasOf->getSupport(); } + public function getNotificationUrl() + { + return $this->aliasOf->getNotificationUrl(); + } public function __toString() { return parent::__toString().' (alias of '.$this->aliasOf->getVersion().')'; diff --git a/src/Composer/Package/Dumper/ArrayDumper.php b/src/Composer/Package/Dumper/ArrayDumper.php index bff9a0888..47167dd23 100644 --- a/src/Composer/Package/Dumper/ArrayDumper.php +++ b/src/Composer/Package/Dumper/ArrayDumper.php @@ -31,6 +31,7 @@ class ArrayDumper 'extra', 'installationSource' => 'installation-source', 'autoload', + 'notificationUrl' => 'notification-url', 'includePaths' => 'include-path', ); diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index a5464bd00..661013691 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -142,6 +142,10 @@ class ArrayLoader implements LoaderInterface } } + if (!empty($config['notification-url'])) { + $package->setNotificationUrl($config['notification-url']); + } + if ($package instanceof Package\CompletePackageInterface) { if (isset($config['scripts']) && is_array($config['scripts'])) { foreach ($config['scripts'] as $event => $listeners) { diff --git a/src/Composer/Package/Package.php b/src/Composer/Package/Package.php index cb985d2f4..ddd4b4ec5 100644 --- a/src/Composer/Package/Package.php +++ b/src/Composer/Package/Package.php @@ -41,6 +41,7 @@ class Package extends BasePackage protected $prettyAlias; protected $dev; protected $stability; + protected $notificationUrl; protected $requires = array(); protected $conflicts = array(); @@ -506,4 +507,22 @@ class Package extends BasePackage { return $this->includePaths; } + + /** + * Sets the notification URL + * + * @param string $notificationUrl + */ + public function setNotificationUrl($notificationUrl) + { + $this->notificationUrl = $notificationUrl; + } + + /** + * {@inheritDoc} + */ + public function getNotificationUrl() + { + return $this->notificationUrl; + } } diff --git a/src/Composer/Package/PackageInterface.php b/src/Composer/Package/PackageInterface.php index e034acba6..6c2b48b4f 100644 --- a/src/Composer/Package/PackageInterface.php +++ b/src/Composer/Package/PackageInterface.php @@ -288,6 +288,13 @@ interface PackageInterface */ public function getUniqueName(); + /** + * Returns the package notification url + * + * @return string + */ + public function getNotificationUrl(); + /** * Converts the package into a readable and unique string * diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 249dfd89e..fdb7fa5cb 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -22,12 +22,11 @@ use Composer\Cache; use Composer\Config; use Composer\IO\IOInterface; use Composer\Util\RemoteFilesystem; -use Composer\Util\StreamContextFactory; /** * @author Jordi Boggiano */ -class ComposerRepository extends ArrayRepository implements NotifiableRepositoryInterface, StreamableRepositoryInterface +class ComposerRepository extends ArrayRepository implements StreamableRepositoryInterface { protected $config; protected $options; @@ -77,61 +76,6 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository $this->loader = new ArrayLoader(); } - /** - * {@inheritDoc} - */ - public function notifyInstalls(array $packages) - { - if (!$this->notifyUrl || !$this->config->get('notify-on-install')) { - return; - } - - // non-batch API, deprecated - if (strpos($this->notifyUrl, '%package%')) { - foreach ($packages as $package) { - $url = str_replace('%package%', $package->getPrettyName(), $this->notifyUrl); - - $params = array( - 'version' => $package->getPrettyVersion(), - 'version_normalized' => $package->getVersion(), - ); - $opts = array('http' => - array( - 'method' => 'POST', - 'header' => 'Content-type: application/x-www-form-urlencoded', - 'content' => http_build_query($params, '', '&'), - 'timeout' => 3, - ) - ); - - $context = StreamContextFactory::getContext($opts); - @file_get_contents($url, false, $context); - } - - return; - } - - $postData = array('downloads' => array()); - foreach ($packages as $package) { - $postData['downloads'][] = array( - 'name' => $package->getPrettyName(), - 'version' => $package->getVersion(), - ); - } - - $opts = array('http' => - array( - 'method' => 'POST', - 'header' => 'Content-Type: application/json', - 'content' => json_encode($postData), - 'timeout' => 6, - ) - ); - - $context = StreamContextFactory::getContext($opts); - @file_get_contents($this->notifyUrl, false, $context); - } - public function setRootAliases(array $rootAliases) { $this->rootAliases = $rootAliases; @@ -459,6 +403,8 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository protected function createPackage(array $data, $class) { try { + $data['notification-url'] = $this->notifyUrl; + return $this->loader->load($data, 'Composer\Package\CompletePackage'); } catch (\Exception $e) { throw new \RuntimeException('Could not load package '.(isset($data['name']) ? $data['name'] : json_encode($data)).' in '.$this->url.': ['.get_class($e).'] '.$e->getMessage(), 0, $e); diff --git a/src/Composer/Repository/NotifiableRepositoryInterface.php b/src/Composer/Repository/NotifiableRepositoryInterface.php deleted file mode 100644 index e3a252ea0..000000000 --- a/src/Composer/Repository/NotifiableRepositoryInterface.php +++ /dev/null @@ -1,28 +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\Repository; - -use Composer\Package\PackageInterface; - -/** - * @author Jordi Boggiano - */ -interface NotifiableRepositoryInterface extends RepositoryInterface -{ - /** - * Notify this repository about the installation of a package - * - * @param PackageInterface[] $packages Packages that were installed - */ - public function notifyInstalls(array $packages); -} diff --git a/tests/Composer/Test/Installer/InstallationManagerTest.php b/tests/Composer/Test/Installer/InstallationManagerTest.php index 553a3e129..9f31d776c 100644 --- a/tests/Composer/Test/Installer/InstallationManagerTest.php +++ b/tests/Composer/Test/Installer/InstallationManagerTest.php @@ -35,7 +35,7 @@ class InstallationManagerTest extends \PHPUnit_Framework_TestCase return $arg === 'vendor'; })); - $manager = new InstallationManager('vendor'); + $manager = new InstallationManager(); $manager->addInstaller($installer); $this->assertSame($installer, $manager->getInstaller('vendor')); @@ -44,6 +44,36 @@ class InstallationManagerTest extends \PHPUnit_Framework_TestCase $manager->getInstaller('unregistered'); } + public function testAddRemoveInstaller() + { + $installer = $this->createInstallerMock(); + + $installer + ->expects($this->exactly(2)) + ->method('supports') + ->will($this->returnCallback(function ($arg) { + return $arg === 'vendor'; + })); + + $installer2 = $this->createInstallerMock(); + + $installer2 + ->expects($this->exactly(1)) + ->method('supports') + ->will($this->returnCallback(function ($arg) { + return $arg === 'vendor'; + })); + + $manager = new InstallationManager(); + + $manager->addInstaller($installer); + $this->assertSame($installer, $manager->getInstaller('vendor')); + $manager->addInstaller($installer2); + $this->assertSame($installer2, $manager->getInstaller('vendor')); + $manager->removeInstaller($installer2); + $this->assertSame($installer, $manager->getInstaller('vendor')); + } + public function testExecute() { $manager = $this->getMockBuilder('Composer\Installer\InstallationManager') @@ -77,7 +107,7 @@ class InstallationManagerTest extends \PHPUnit_Framework_TestCase public function testInstall() { $installer = $this->createInstallerMock(); - $manager = new InstallationManager('vendor'); + $manager = new InstallationManager(); $manager->addInstaller($installer); $package = $this->createPackageMock(); @@ -105,7 +135,7 @@ class InstallationManagerTest extends \PHPUnit_Framework_TestCase public function testUpdateWithEqualTypes() { $installer = $this->createInstallerMock(); - $manager = new InstallationManager('vendor'); + $manager = new InstallationManager(); $manager->addInstaller($installer); $initial = $this->createPackageMock(); @@ -139,7 +169,7 @@ class InstallationManagerTest extends \PHPUnit_Framework_TestCase { $libInstaller = $this->createInstallerMock(); $bundleInstaller = $this->createInstallerMock(); - $manager = new InstallationManager('vendor'); + $manager = new InstallationManager(); $manager->addInstaller($libInstaller); $manager->addInstaller($bundleInstaller); @@ -185,7 +215,7 @@ class InstallationManagerTest extends \PHPUnit_Framework_TestCase public function testUninstall() { $installer = $this->createInstallerMock(); - $manager = new InstallationManager('vendor'); + $manager = new InstallationManager(); $manager->addInstaller($installer); $package = $this->createPackageMock();