Add options to configure repository priorities
parent
59c831c2f8
commit
b6bad4eef6
@ -0,0 +1,94 @@
|
||||
<!--
|
||||
tagline: Configure which packages are found in which repositories
|
||||
-->
|
||||
|
||||
# Repository priorities
|
||||
|
||||
## Canonical repositories
|
||||
|
||||
When Composer resolves dependencies it will look up a given package in the
|
||||
topmost repository. If that repository does not contain the package, it
|
||||
goes on to the next one, until one repository contains it and the process ends.
|
||||
|
||||
Canonical repositories are better for a few reasons:
|
||||
|
||||
- Performance wise, it is more efficient to stop looking for a package once it
|
||||
has been found somewhere. It also avoids loading duplicate packages in case
|
||||
the same package is present in several of your repositories.
|
||||
- Security wise, it is safer to treat them canonically as it means that your most
|
||||
important repositories will return the packages you expect them to always. Let's
|
||||
say you have a private repository which is not canonical, and you require your
|
||||
private package `foo/bar ^2.0` for example. Now if someone publishes
|
||||
`foo/bar 2.999` to packagist.org, suddenly Composer will pick that package as it
|
||||
has a higher version than your latest release (say 2.4.3), and you end up install
|
||||
something you may not have meant to. If the private repository is canonical
|
||||
however, that 2.999 version from packagist.org will not be considered at all.
|
||||
|
||||
There are however a few cases where you may want to specifically load some packages
|
||||
from a given repository, but not all. Or you may want a given repository to not be
|
||||
canonical, and to be only preferred if it has higher package versions than the
|
||||
repositories defined below.
|
||||
|
||||
## Default behavior
|
||||
|
||||
By default in Composer 2.x all repositories are canonical. Composer 1.x treated
|
||||
all repositories as non-canonical.
|
||||
|
||||
Another default is that the packagist.org repository is always added implicitly
|
||||
as the last repository, unless you [disable it](../05-repositories.md#disabling-packagist-org).
|
||||
|
||||
## Making repositories non-canonical
|
||||
|
||||
You can add the canonical option to any repository to disable this default behavior
|
||||
and make sure Composer keeps looking in other repositories, even if that repository
|
||||
contains a given package.
|
||||
|
||||
```json
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://example.org",
|
||||
"canonical": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Filtering packages
|
||||
|
||||
You can also filter packages which a repository will be able to load, either by
|
||||
selecting which you want, or by excluding those you do not want.
|
||||
|
||||
For example here we want to pick only the `foo/bar` and all the packages from
|
||||
`some-vendor/` from this composer repository.
|
||||
|
||||
```json
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://example.org",
|
||||
"only": ["foo/bar", "some-vendor/*"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
And in this other example we exclude `toy/package` from a path repository, which
|
||||
we may not want to load in this project.
|
||||
|
||||
```json
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://example.org",
|
||||
"exclude": ["toy/package"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Both `only` and `exclude` should be array of package names, which can also
|
||||
contain wildcards (`*`) which will match any characters.
|
@ -0,0 +1,193 @@
|
||||
<?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;
|
||||
use Composer\Package\BasePackage;
|
||||
|
||||
/**
|
||||
* Filters which packages are seen as canonical on this repo by loadPackages
|
||||
*
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class FilterRepository implements RepositoryInterface
|
||||
{
|
||||
private $only = array();
|
||||
private $exclude = array();
|
||||
private $canonical = true;
|
||||
private $repo;
|
||||
|
||||
public function __construct(RepositoryInterface $repo, array $options)
|
||||
{
|
||||
if (isset($options['only'])) {
|
||||
if (!is_array($options['only'])) {
|
||||
throw new \InvalidArgumentException('"only" key for repository '.$repo->getRepoName().' should be an array');
|
||||
}
|
||||
$this->only = '{^'.implode('|', array_map(function ($val) {
|
||||
return BasePackage::packageNameToRegexp($val, '%s');
|
||||
}, $options['only'])) .'$}iD';
|
||||
}
|
||||
if (isset($options['exclude'])) {
|
||||
if (!is_array($options['exclude'])) {
|
||||
throw new \InvalidArgumentException('"exclude" key for repository '.$repo->getRepoName().' should be an array');
|
||||
}
|
||||
$this->exclude = '{^'.implode('|', array_map(function ($val) {
|
||||
return BasePackage::packageNameToRegexp($val, '%s');
|
||||
}, $options['exclude'])) .'$}iD';
|
||||
}
|
||||
if ($this->exclude && $this->only) {
|
||||
throw new \InvalidArgumentException('Only one of "only" and "exclude" can be specified for repository '.$repo->getRepoName());
|
||||
}
|
||||
if (isset($options['canonical'])) {
|
||||
if (!is_bool($options['canonical'])) {
|
||||
throw new \InvalidArgumentException('"canonical" key for repository '.$repo->getRepoName().' should be a boolean');
|
||||
}
|
||||
$this->canonical = $options['canonical'];
|
||||
}
|
||||
|
||||
$this->repo = $repo;
|
||||
}
|
||||
|
||||
public function getRepoName()
|
||||
{
|
||||
return $this->repo->getRepoName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the wrapped repositories
|
||||
*
|
||||
* @return RepositoryInterface
|
||||
*/
|
||||
public function getRepository()
|
||||
{
|
||||
return $this->repo;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasPackage(PackageInterface $package)
|
||||
{
|
||||
return $this->repo->hasPackage($package);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function findPackage($name, $constraint)
|
||||
{
|
||||
if (!$this->isAllowed($name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->repo->findPackage($name, $constraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function findPackages($name, $constraint = null)
|
||||
{
|
||||
if (!$this->isAllowed($name)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return $this->repo->findPackages($name, $constraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function loadPackages(array $packageMap, array $acceptableStabilities, array $stabilityFlags)
|
||||
{
|
||||
foreach ($packageMap as $name => $constraint) {
|
||||
if (!$this->isAllowed($name)) {
|
||||
unset($packageMap[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
$result = $this->repo->loadPackages($packageMap, $acceptableStabilities, $stabilityFlags);
|
||||
if (!$this->canonical) {
|
||||
$result['namesFound'] = array();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function search($query, $mode = 0, $type = null)
|
||||
{
|
||||
return $this->repo->search($query, $mode, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPackages()
|
||||
{
|
||||
$result = array();
|
||||
foreach ($this->repo->getPackages() as $package) {
|
||||
if ($this->isAllowed($package->getName())) {
|
||||
$result[] = $package;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProviders($packageName)
|
||||
{
|
||||
$result = array();
|
||||
foreach ($this->repo->getProviders($packageName) as $provider) {
|
||||
if ($this->isAllowed($provider['name'])) {
|
||||
$result[] = $provider;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removePackage(PackageInterface $package)
|
||||
{
|
||||
return $this->repo->removePackage($package);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return $this->repo->count();
|
||||
}
|
||||
|
||||
private function isAllowed($name)
|
||||
{
|
||||
if (!$this->only && !$this->exclude) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->only) {
|
||||
return (bool) preg_match($this->only, $name);
|
||||
}
|
||||
|
||||
return !preg_match($this->exclude, $name);
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
--TEST--
|
||||
Test that filter repositories apply correctly
|
||||
--COMPOSER--
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "package",
|
||||
"package": [
|
||||
{ "name": "foo/a", "version": "1.0.0" }
|
||||
],
|
||||
"canonical": false
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": [
|
||||
{ "name": "foo/a", "version": "1.0.0" },
|
||||
{ "name": "foo/b", "version": "1.0.0" }
|
||||
],
|
||||
"only": ["foo/b"]
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": [
|
||||
{ "name": "foo/a", "version": "1.2.0" },
|
||||
{ "name": "foo/c", "version": "1.2.0" }
|
||||
],
|
||||
"exclude": ["foo/c"]
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": [
|
||||
{ "name": "foo/a", "version": "1.1.0" },
|
||||
{ "name": "foo/b", "version": "1.1.0" },
|
||||
{ "name": "foo/c", "version": "1.1.0" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"foo/a": "1.*",
|
||||
"foo/b": "1.*",
|
||||
"foo/c": "1.*"
|
||||
}
|
||||
}
|
||||
--RUN--
|
||||
update
|
||||
--EXPECT--
|
||||
Installing foo/a (1.2.0)
|
||||
Installing foo/b (1.0.0)
|
||||
Installing foo/c (1.1.0)
|
@ -0,0 +1,69 @@
|
||||
<?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\Test\Repository;
|
||||
|
||||
use Composer\Test\TestCase;
|
||||
use Composer\Repository\FilterRepository;
|
||||
use Composer\Repository\ArrayRepository;
|
||||
use Composer\Semver\Constraint\EmptyConstraint;
|
||||
use Composer\Package\BasePackage;
|
||||
|
||||
class FilterRepositoryTest extends TestCase
|
||||
{
|
||||
private $arrayRepo;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->arrayRepo = new ArrayRepository();
|
||||
$this->arrayRepo->addPackage($this->getPackage('foo/aaa', '1.0.0'));
|
||||
$this->arrayRepo->addPackage($this->getPackage('foo/bbb', '1.0.0'));
|
||||
$this->arrayRepo->addPackage($this->getPackage('bar/xxx', '1.0.0'));
|
||||
$this->arrayRepo->addPackage($this->getPackage('baz/yyy', '1.0.0'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider repoMatchingTests
|
||||
*/
|
||||
public function testRepoMatching($expected, $config)
|
||||
{
|
||||
$repo = new FilterRepository($this->arrayRepo, $config);
|
||||
$packages = $repo->getPackages();
|
||||
|
||||
$this->assertSame($expected, array_map(function ($p) { return $p->getName(); }, $packages));
|
||||
}
|
||||
|
||||
public static function repoMatchingTests()
|
||||
{
|
||||
return array(
|
||||
array(array('foo/aaa', 'foo/bbb'), array('only' => array('foo/*'))),
|
||||
array(array('foo/aaa', 'baz/yyy'), array('only' => array('foo/aaa', 'baz/yyy'))),
|
||||
array(array('bar/xxx'), array('exclude' => array('foo/*', 'baz/yyy'))),
|
||||
);
|
||||
}
|
||||
|
||||
public function testCanonicalDefaultTrue()
|
||||
{
|
||||
$repo = new FilterRepository($this->arrayRepo, array());
|
||||
$result = $repo->loadPackages(array('foo/aaa' => new EmptyConstraint), BasePackage::$stabilities, array());
|
||||
$this->assertCount(1, $result['packages']);
|
||||
$this->assertCount(1, $result['namesFound']);
|
||||
}
|
||||
|
||||
public function testNonCanonical()
|
||||
{
|
||||
$repo = new FilterRepository($this->arrayRepo, array('canonical' => false));
|
||||
$result = $repo->loadPackages(array('foo/aaa' => new EmptyConstraint), BasePackage::$stabilities, array());
|
||||
$this->assertCount(1, $result['packages']);
|
||||
$this->assertCount(0, $result['namesFound']);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue