From 5c4f524d6aeb3237bddfcb35bc2ef1880e3c4724 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Thu, 28 Nov 2019 22:34:29 +0100 Subject: [PATCH] Add funding field to composer.json You can specify a list of funding options each with a type and URL. The type is used to specify the kind of funding or the platform through which funding is possible. --- doc/04-schema.md | 32 +++++++++++++++++++ res/composer-schema.json | 18 +++++++++++ src/Composer/Package/AliasPackage.php | 5 +++ src/Composer/Package/CompletePackage.php | 19 +++++++++++ .../Package/CompletePackageInterface.php | 9 ++++++ src/Composer/Package/Dumper/ArrayDumper.php | 1 + src/Composer/Package/Loader/ArrayLoader.php | 4 +++ .../Package/Loader/ValidatingArrayLoader.php | 26 +++++++++++++++ .../Composer/Test/Json/Fixtures/composer.json | 6 ++++ .../Test/Package/Dumper/ArrayDumperTest.php | 4 +++ .../Test/Package/Loader/ArrayLoaderTest.php | 3 ++ .../Loader/ValidatingArrayLoaderTest.php | 9 ++++++ 12 files changed, 136 insertions(+) diff --git a/doc/04-schema.md b/doc/04-schema.md index 6e273c1a0..e2c5369de 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -258,6 +258,38 @@ An example: Optional. +### funding + +A list of URLs to provide funding to the package authors for maintenance and +development of new functionality. + +Each entry consists of the following + +* **type:** The type of funding or the platform through which funding can be provided, e.g. patreon, opencollective, tidelift or github. +* **url:** URL to a website with details and a way to fund the package. + +An example: + +```json +{ + "funding": [ + { + "type": "patreon", + "url": "https://www.patreon.com/phpdoctrine" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/packagist-doctrine_doctrine-bundle" + }, + { + "type": "other", + "url": "https://www.doctrine-project.org/sponsorship.html" + } +} +``` + +Optional. + ### Package links All of the following take an object which maps package names to diff --git a/res/composer-schema.json b/res/composer-schema.json index c83109151..a74819baa 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -522,6 +522,24 @@ } } }, + "funding": { + "type": "array", + "description": "A list of options to fund the development and maintenance of the package.", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Type of funding or platform through which funding is possible." + }, + "url": { + "type": "string", + "description": "URL to a website with details on funding and a way to fund the package.", + "format": "uri" + } + } + } + }, "non-feature-branches": { "type": ["array"], "description": "A set of string or regex patterns for non-numeric branch names that will not be handled as feature branches.", diff --git a/src/Composer/Package/AliasPackage.php b/src/Composer/Package/AliasPackage.php index 89f197856..ee93ec497 100644 --- a/src/Composer/Package/AliasPackage.php +++ b/src/Composer/Package/AliasPackage.php @@ -377,6 +377,11 @@ class AliasPackage extends BasePackage implements CompletePackageInterface return $this->aliasOf->getSupport(); } + public function getFunding() + { + return $this->aliasOf->getFunding(); + } + public function getNotificationUrl() { return $this->aliasOf->getNotificationUrl(); diff --git a/src/Composer/Package/CompletePackage.php b/src/Composer/Package/CompletePackage.php index 5dbdb82e3..785d5817c 100644 --- a/src/Composer/Package/CompletePackage.php +++ b/src/Composer/Package/CompletePackage.php @@ -27,6 +27,7 @@ class CompletePackage extends Package implements CompletePackageInterface protected $homepage; protected $scripts = array(); protected $support = array(); + protected $funding = array(); protected $abandoned = false; /** @@ -171,6 +172,24 @@ class CompletePackage extends Package implements CompletePackageInterface return $this->support; } + /** + * Set the funding + * + * @param array $funding + */ + public function setFunding(array $funding) + { + $this->funding = $funding; + } + + /** + * {@inheritDoc} + */ + public function getFunding() + { + return $this->funding; + } + /** * @return bool */ diff --git a/src/Composer/Package/CompletePackageInterface.php b/src/Composer/Package/CompletePackageInterface.php index 4036b3cec..7782886d3 100644 --- a/src/Composer/Package/CompletePackageInterface.php +++ b/src/Composer/Package/CompletePackageInterface.php @@ -79,6 +79,15 @@ interface CompletePackageInterface extends PackageInterface */ public function getSupport(); + /** + * Returns an array of funding options for the package + * + * Each item will contain type and url keys + * + * @return array + */ + public function getFunding(); + /** * Returns if the package is abandoned or not * diff --git a/src/Composer/Package/Dumper/ArrayDumper.php b/src/Composer/Package/Dumper/ArrayDumper.php index b1e20dbf5..dece598f1 100644 --- a/src/Composer/Package/Dumper/ArrayDumper.php +++ b/src/Composer/Package/Dumper/ArrayDumper.php @@ -104,6 +104,7 @@ class ArrayDumper 'keywords', 'repositories', 'support', + 'funding', ); $data = $this->dumpValues($package, $keys, $data); diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index c269afa22..228632b42 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -198,6 +198,10 @@ class ArrayLoader implements LoaderInterface $package->setSupport($config['support']); } + if (!empty($config['funding']) && is_array($config['funding'])) { + $package->setFunding($config['funding']); + } + if (isset($config['abandoned'])) { $package->setAbandoned($config['abandoned']); } diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index 43f23236b..f02f6b165 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -193,6 +193,32 @@ class ValidatingArrayLoader implements LoaderInterface } } + if ($this->validateArray('funding') && !empty($this->config['funding'])) { + foreach ($this->config['funding'] as $key => $fundingOption) { + if (!is_array($fundingOption)) { + $this->errors[] = 'funding.'.$key.' : should be an array, '.gettype($fundingOption).' given'; + unset($this->config['funding'][$key]); + continue; + } + foreach (array('type', 'url') as $fundingData) { + if (isset($fundingOption[$fundingData]) && !is_string($fundingOption[$fundingData])) { + $this->errors[] = 'funding.'.$key.'.'.$fundingData.' : invalid value, must be a string'; + unset($this->config['funding'][$key][$fundingData]); + } + } + if (isset($fundingOption['url']) && !$this->filterUrl($fundingOption['url'])) { + $this->warnings[] = 'funding.'.$key.'.url : invalid value ('.$fundingOption['url'].'), must be an http/https URL'; + unset($this->config['funding'][$key]['url']); + } + if (empty($this->config['funding'][$key])) { + unset($this->config['funding'][$key]); + } + } + if (empty($this->config['funding'])) { + unset($this->config['funding']); + } + } + $unboundConstraint = new Constraint('=', $this->versionParser->normalize('dev-master')); $stableConstraint = new Constraint('=', '1.0.0'); diff --git a/tests/Composer/Test/Json/Fixtures/composer.json b/tests/Composer/Test/Json/Fixtures/composer.json index 66e35a7e7..ec6df7f18 100644 --- a/tests/Composer/Test/Json/Fixtures/composer.json +++ b/tests/Composer/Test/Json/Fixtures/composer.json @@ -21,6 +21,12 @@ "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/composer/issues" }, + "funding": [ + { + "type": "service-subscription", + "url": "https://packagist.com" + } + ], "require": { "php": ">=5.3.2", "justinrainbow/json-schema": "~1.4", diff --git a/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php b/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php index 81d94bdc1..ca7d60902 100644 --- a/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php +++ b/tests/Composer/Test/Package/Dumper/ArrayDumperTest.php @@ -191,6 +191,10 @@ class ArrayDumperTest extends TestCase 'support', array('foo' => 'bar'), ), + array( + 'funding', + array('type' => 'foo', 'url' => 'https://example.com'), + ), array( 'require', array(new Link('foo', 'foo/bar', new Constraint('=', '1.0.0.0'), 'requires', '1.0.0'), new Link('bar', 'bar/baz', new Constraint('=', '1.0.0.0'), 'requires', '1.0.0')), diff --git a/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php index 5ccaa038a..1d4f8661b 100644 --- a/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php @@ -97,6 +97,9 @@ class ArrayLoaderTest extends TestCase 'authors' => array( array('name' => 'Bob', 'email' => 'bob@example.org', 'homepage' => 'example.org', 'role' => 'Developer'), ), + 'funding' => array( + array('type' => 'example', 'url' => 'https://example.org/fund'), + ), 'require' => array( 'foo/bar' => '1.0', ), diff --git a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php index 69936f4bf..2cde001ac 100644 --- a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php @@ -73,6 +73,15 @@ class ValidatingArrayLoaderTest extends TestCase 'rss' => 'http://example.org/rss', 'chat' => 'http://example.org/chat', ), + 'funding' => array( + array( + 'type' => 'example', + 'url' => 'https://example.org/fund' + ), + array( + 'url' => 'https://example.org/fund' + ), + ), 'require' => array( 'a/b' => '1.*', 'b/c' => '~2',