Skip to content

Commit 8047bcd

Browse files
Merge pull request #72 from JellyBellyDev/feat/composer2-metadata
feat: add methods to fetch package info from new composer 2 api
2 parents ca668d4 + 179847d commit 8047bcd

File tree

8 files changed

+148
-40
lines changed

8 files changed

+148
-40
lines changed

examples/getComposer.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
require __DIR__.'/../vendor/autoload.php';
4+
5+
$client = new Packagist\Api\Client();
6+
$package = $client->getComposer('sylius/sylius');
7+
8+
var_export($package);

examples/getComposerLite.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
require __DIR__.'/../vendor/autoload.php';
4+
5+
$client = new Packagist\Api\Client();
6+
$package = $client->getComposerLite('sylius/sylius');
7+
8+
var_export($package);

spec/Packagist/Api/ClientSpec.php

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use GuzzleHttp\Client as HttpClient;
88
use GuzzleHttp\Psr7\Response;
9+
use GuzzleHttp\Psr7\Stream;
910
use Packagist\Api\Client;
1011
use Packagist\Api\Result\Factory;
1112
use PhpSpec\ObjectBehavior;
@@ -17,15 +18,16 @@ public function let(HttpClient $client, Factory $factory)
1718
$this->beConstructedWith($client, $factory);
1819
}
1920

20-
public function it_is_initializable()
21+
public function it_is_initializable(): void
2122
{
2223
$this->shouldHaveType(Client::class);
2324
}
2425

25-
public function it_search_for_packages(HttpClient $client, Factory $factory, Response $response)
26+
public function it_search_for_packages(HttpClient $client, Factory $factory, Response $response, Stream $body): void
2627
{
2728
$data = file_get_contents('spec/Packagist/Api/Fixture/search.json');
28-
$response->getBody()->shouldBeCalled()->willReturn($data);
29+
$response->getBody()->shouldBeCalled()->willReturn($body);
30+
$body->getContents()->shouldBeCalled()->willReturn($data);
2931

3032
$client->request('GET', 'https://packagist.org/search.json?q=sylius')
3133
->shouldBeCalled()
@@ -35,21 +37,23 @@ public function it_search_for_packages(HttpClient $client, Factory $factory, Res
3537
$this->search('sylius');
3638
}
3739

38-
public function it_search_for_packages_with_limit(HttpClient $client, Factory $factory, Response $response)
40+
public function it_search_for_packages_with_limit(HttpClient $client, Factory $factory, Response $response, Stream $body): void
3941
{
4042
$data = file_get_contents('spec/Packagist/Api/Fixture/search.json');
41-
$response->getBody()->shouldBeCalled()->willReturn($data);
43+
$response->getBody()->shouldBeCalled()->willReturn($body);
44+
$body->getContents()->shouldBeCalled()->willReturn($data);
4245

4346
$client->request('GET', 'https://packagist.org/search.json?q=sylius')->shouldBeCalled()->willReturn($response);
4447
$factory->create(json_decode($data, true))->shouldBeCalled()->willReturn(array());
4548

4649
$this->search('sylius', [], 2);
4750
}
4851

49-
public function it_searches_for_packages_with_filters(HttpClient $client, Factory $factory, Response $response)
52+
public function it_searches_for_packages_with_filters(HttpClient $client, Factory $factory, Response $response, Stream $body): void
5053
{
5154
$data = file_get_contents('spec/Packagist/Api/Fixture/search.json');
52-
$response->getBody()->shouldBeCalled()->willReturn($data);
55+
$response->getBody()->shouldBeCalled()->willReturn($body);
56+
$body->getContents()->shouldBeCalled()->willReturn($data);
5357

5458
$client->request('GET', 'https://packagist.org/search.json?tag=storage&q=sylius')
5559
->shouldBeCalled()
@@ -60,10 +64,11 @@ public function it_searches_for_packages_with_filters(HttpClient $client, Factor
6064
$this->search('sylius', ['tag' => 'storage']);
6165
}
6266

63-
public function it_gets_popular_packages(HttpClient $client, Factory $factory, Response $response)
67+
public function it_gets_popular_packages(HttpClient $client, Factory $factory, Response $response, Stream $body): void
6468
{
6569
$data = file_get_contents('spec/Packagist/Api/Fixture/popular.json');
66-
$response->getBody()->shouldBeCalled()->willReturn($data);
70+
$response->getBody()->shouldBeCalled()->willReturn($body);
71+
$body->getContents()->shouldBeCalled()->willReturn($data);
6772

6873
$client->request('GET', 'https://packagist.org/explore/popular.json?page=1')
6974
->shouldBeCalled()
@@ -76,10 +81,11 @@ public function it_gets_popular_packages(HttpClient $client, Factory $factory, R
7681
$this->popular(2)->shouldHaveCount(2);
7782
}
7883

79-
public function it_gets_package_details(HttpClient $client, Factory $factory, Response $response)
84+
public function it_gets_package_details(HttpClient $client, Factory $factory, Response $response, Stream $body): void
8085
{
8186
$data = file_get_contents('spec/Packagist/Api/Fixture/get.json');
82-
$response->getBody()->shouldBeCalled()->willReturn($data);
87+
$response->getBody()->shouldBeCalled()->willReturn($body);
88+
$body->getContents()->shouldBeCalled()->willReturn($data);
8389

8490
$client->request('GET', 'https://packagist.org/packages/sylius/sylius.json')
8591
->shouldBeCalled()
@@ -90,10 +96,11 @@ public function it_gets_package_details(HttpClient $client, Factory $factory, Re
9096
$this->get('sylius/sylius');
9197
}
9298

93-
public function it_gets_composer_package_details(HttpClient $client, Factory $factory, Response $response)
99+
public function it_gets_composer_package_details(HttpClient $client, Factory $factory, Response $response, Stream $body): void
94100
{
95101
$data = file_get_contents('spec/Packagist/Api/Fixture/get_composer.json');
96-
$response->getBody()->shouldBeCalled()->willReturn($data);
102+
$response->getBody()->shouldBeCalled()->willReturn($body);
103+
$body->getContents()->shouldBeCalled()->willReturn($data);
97104

98105
$client->request('GET', 'https://packagist.org/p/sylius/sylius.json')
99106
->shouldBeCalled()
@@ -104,10 +111,26 @@ public function it_gets_composer_package_details(HttpClient $client, Factory $fa
104111
$this->getComposer('sylius/sylius');
105112
}
106113

107-
public function it_lists_all_package_names(HttpClient $client, Factory $factory, Response $response)
114+
public function it_gets_composer_lite_package_details(HttpClient $client, Factory $factory, Response $response, Stream $body): void
115+
{
116+
$data = file_get_contents('spec/Packagist/Api/Fixture/get_composer_lite.json');
117+
$response->getBody()->shouldBeCalled()->willReturn($body);
118+
$body->getContents()->shouldBeCalled()->willReturn($data);
119+
120+
$client->request('GET', 'https://packagist.org/p/sylius/sylius.json')
121+
->shouldBeCalled()
122+
->willReturn($response);
123+
124+
$factory->create(json_decode($data, true))->shouldBeCalled();
125+
126+
$this->getComposerLite('sylius/sylius');
127+
}
128+
129+
public function it_lists_all_package_names(HttpClient $client, Factory $factory, Response $response, Stream $body): void
108130
{
109131
$data = file_get_contents('spec/Packagist/Api/Fixture/all.json');
110-
$response->getBody()->shouldBeCalled()->willReturn($data);
132+
$response->getBody()->shouldBeCalled()->willReturn($body);
133+
$body->getContents()->shouldBeCalled()->willReturn($data);
111134

112135
$client->request('GET', 'https://packagist.org/packages/list.json')
113136
->shouldBeCalled()
@@ -118,10 +141,11 @@ public function it_lists_all_package_names(HttpClient $client, Factory $factory,
118141
$this->all();
119142
}
120143

121-
public function it_filters_package_names_by_type(HttpClient $client, Factory $factory, Response $response)
144+
public function it_filters_package_names_by_type(HttpClient $client, Factory $factory, Response $response, Stream $body): void
122145
{
123146
$data = file_get_contents('spec/Packagist/Api/Fixture/all.json');
124-
$response->getBody()->shouldBeCalled()->willReturn($data);
147+
$response->getBody()->shouldBeCalled()->willReturn($body);
148+
$body->getContents()->shouldBeCalled()->willReturn($data);
125149

126150
$client->request('GET', 'https://packagist.org/packages/list.json?type=library')
127151
->shouldBeCalled()
@@ -132,10 +156,11 @@ public function it_filters_package_names_by_type(HttpClient $client, Factory $fa
132156
$this->all(['type' => 'library']);
133157
}
134158

135-
public function it_filters_package_names_by_vendor(HttpClient $client, Factory $factory, Response $response)
159+
public function it_filters_package_names_by_vendor(HttpClient $client, Factory $factory, Response $response, Stream $body): void
136160
{
137161
$data = file_get_contents('spec/Packagist/Api/Fixture/all.json');
138-
$response->getBody()->shouldBeCalled()->willReturn($data);
162+
$response->getBody()->shouldBeCalled()->willReturn($body);
163+
$body->getContents()->shouldBeCalled()->willReturn($data);
139164

140165
$client->request('GET', 'https://packagist.org/packages/list.json?vendor=sylius')
141166
->shouldBeCalled()
@@ -145,4 +170,5 @@ public function it_filters_package_names_by_vendor(HttpClient $client, Factory $
145170

146171
$this->all(['vendor' => 'sylius']);
147172
}
173+
148174
}

spec/Packagist/Api/Fixture/get_composer.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

spec/Packagist/Api/Fixture/get_composer_lite.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

spec/Packagist/Api/Result/FactorySpec.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,22 @@ public function it_creates_composer_packages()
6363
}
6464
}
6565

66+
public function it_creates_composer_lite_packages(): void
67+
{
68+
$data = json_decode(file_get_contents('spec/Packagist/Api/Fixture/get_composer_lite.json'), true);
69+
70+
$results = $this->create($data);
71+
$results->shouldHaveCount(1);
72+
$results->shouldBeArray();
73+
foreach ($results as $result) {
74+
$result->shouldBeAnInstanceOf('Packagist\Api\Result\Package');
75+
76+
foreach ($result->getVersions() as $version) {
77+
$version->shouldBeAnInstanceOf('Packagist\Api\Result\Version');
78+
}
79+
}
80+
}
81+
6682
public function it_creates_package_names()
6783
{
6884
$data = json_decode(file_get_contents('spec/Packagist/Api/Fixture/all.json'), true);
@@ -115,4 +131,5 @@ public function getMatchers(): array
115131
}
116132
);
117133
}
134+
118135
}

src/Packagist/Api/Client.php

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use GuzzleHttp\Client as HttpClient;
88
use GuzzleHttp\ClientInterface;
9+
use GuzzleHttp\Exception\GuzzleException;
910
use Packagist\Api\Result\Factory;
1011
use Packagist\Api\Result\Package;
1112
use Psr\Http\Message\StreamInterface;
@@ -55,9 +56,9 @@ public function __construct(
5556
* * type: type of package (type in composer.json)
5657
* * tags: tags of package (keywords in composer.json)
5758
*
58-
* @param string $query Name of package
59-
* @param array $filters An array of filters
60-
* @param int $limit Pages to limit results (0 = all pages)
59+
* @param string $query Name of package
60+
* @param array $filters An array of filters
61+
* @param int $limit Pages to limit results (0 = all pages)
6162
*
6263
* @return array The results
6364
*/
@@ -98,23 +99,25 @@ public function get(string $package)
9899
}
99100

100101
/**
101-
* Similar to {@link get()}, but uses composer metadata which is Packagist's preferred
102-
* way of retrieving details, since responses are cached efficiently as static files
103-
* by the Packagist service. The response lacks some metadata that is provided
104-
* by {@link get()}, see https://packagist.org/apidoc for details.
105-
*
106-
* Caution: Returns an array of packages, you need to select the correct one
107-
* from the indexed array.
108-
*
109-
* @since 1.6
110-
*
102+
* @see https://packagist.org/apidoc#get-package-data
103+
* Contains tagged releases and dev versions
111104
* @param string $package Full qualified name ex : myname/mypackage
112-
*
113-
* @return \Packagist\Api\Result\Package[] An array of packages, including the requested one.
105+
* @return Package[] An array of packages, including the requested one.
106+
*/
107+
public function getComposer(string $package): array
108+
{
109+
return $this->respond(sprintf($this->url('/p2/%s~dev.json'), $package));
110+
}
111+
112+
/**
113+
* @see https://packagist.org/apidoc#get-package-data
114+
* Contains only tagged releases
115+
* @param string $package Full qualified name ex : myname/mypackage
116+
* @return Package[] An array of packages, including the requested one.
114117
*/
115-
public function getComposer($package)
118+
public function getComposerLite(string $package): array
116119
{
117-
return $this->respond(sprintf($this->url('/p/%s.json'), $package));
120+
return $this->respond(sprintf($this->url('/p2/%s.json'), $package));
118121
}
119122

120123
/**
@@ -197,9 +200,14 @@ protected function respond(string $url)
197200
*/
198201
protected function request(string $url): string
199202
{
200-
return (string) $this->httpClient
201-
->request('GET', $url)
202-
->getBody();
203+
try {
204+
return $this->httpClient
205+
->request('GET', $url)
206+
->getBody()
207+
->getContents();
208+
} catch (GuzzleException $e) {
209+
return json_encode([]);
210+
}
203211
}
204212

205213
/**

src/Packagist/Api/Result/Factory.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ public function create(array $data)
3838
// Used for /explore/popular.json
3939
return $this->createSearchResults($data['packages']);
4040
}
41+
if (isset($data['minified']) && 'composer/2.0' === $data['minified']) {
42+
// Used for /p2/<package>.json
43+
return $this->createComposer2PackagesResults($data['packages']);
44+
}
4145
// Used for /p/<package>.json
4246
return $this->createComposerPackagesResults($data['packages']);
4347
}
@@ -127,6 +131,19 @@ public function createPackageResults(array $package): Package
127131
$version['name'] ??= '';
128132
$version['type'] ??= '';
129133

134+
if (isset($version['conflict']) && !is_array($version['conflict'])) {
135+
unset($version['conflict']);
136+
}
137+
if (isset($version['suggest']) && !is_array($version['suggest'])) {
138+
unset($version['suggest']);
139+
}
140+
if (isset($version['replace']) && !is_array($version['replace'])) {
141+
unset($version['replace']);
142+
}
143+
if (isset($version['provide']) && !is_array($version['provide'])) {
144+
unset($version['provide']);
145+
}
146+
130147
if (isset($version['authors']) && $version['authors']) {
131148
foreach ($version['authors'] as $key => $author) {
132149
// Cast some potentially null properties to empty strings
@@ -171,4 +188,27 @@ protected function createResult(string $class, array $data): AbstractResult
171188

172189
return $result;
173190
}
191+
192+
/**
193+
* @param array $packages
194+
* @return Package[]
195+
*/
196+
public function createComposer2PackagesResults(array $packages): array
197+
{
198+
$created = [];
199+
200+
foreach ($packages as $name => $package) {
201+
// Create an empty package, only contains versions
202+
$createdPackage = array(
203+
'versions' => [],
204+
);
205+
foreach ($package as $version) {
206+
$createdPackage['versions'][$version['version']] = $version;
207+
}
208+
209+
$created[$name] = $this->createPackageResults($createdPackage);
210+
}
211+
212+
return $created;
213+
}
174214
}

0 commit comments

Comments
 (0)