Browse Source

Allow notification from locked installs, fixes #1368, fixes #1372, fixes #1369

Jordi Boggiano 12 years ago
parent
commit
a8f74a0983

+ 15 - 11
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('<info>Created project in ' . $directory . '</info>');
@@ -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();
+    }
 }

+ 3 - 4
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();
     }
 
     /**

+ 63 - 6
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;
         }
     }
 }

+ 4 - 0
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().')';

+ 1 - 0
src/Composer/Package/Dumper/ArrayDumper.php

@@ -31,6 +31,7 @@ class ArrayDumper
             'extra',
             'installationSource' => 'installation-source',
             'autoload',
+            'notificationUrl' => 'notification-url',
             'includePaths' => 'include-path',
         );
 

+ 4 - 0
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) {

+ 19 - 0
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;
+    }
 }

+ 7 - 0
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
      *

+ 3 - 57
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 <j.boggiano@seld.be>
  */
-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);

+ 0 - 28
src/Composer/Repository/NotifiableRepositoryInterface.php

@@ -1,28 +0,0 @@
-<?php
-
-/*
- * This file is part of Composer.
- *
- * (c) Nils Adermann <naderman@naderman.de>
- *     Jordi Boggiano <j.boggiano@seld.be>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Composer\Repository;
-
-use Composer\Package\PackageInterface;
-
-/**
- * @author Jordi Boggiano <j.boggiano@seld.be>
- */
-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);
-}

+ 35 - 5
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();