Skip to content

Commit

Permalink
NEW getComposer() for more efficient Packagist API usage
Browse files Browse the repository at this point in the history
From https://packagist.org/apidoc:

> This is the preferred way to access the data as it is always up to date, and dumped to static files so it is very efficient on our end.

I think it's important to include this method and nudge people towards using it,
as a courtesy towards Packagist who are providing this API for free :)

This particular API endpoint is a bit confusing, it is clearly designed to find a single package,
yet it can return other unrelated in the "packages" response key.
My theory is that it's used for internal performance reasons by composer ("sharding").
I've opted to leave this unaltered in the PHP API, rather than pick out the specific package.
This should make it easier to read Packagist's API docs and understand the PHP API of this project.

The new endpoint also overlaps with a different "packages" response signature in the popular.json endpoint,
so I've opted to detect the types based on available keys in the factory.

Note that there's hundreds of pre-existing phpcs failures which I didn't fix,
it's a bit hard to tell if I made it worse (hopefully not!)
  • Loading branch information
chillu authored and robbieaverill committed Dec 1, 2020
1 parent 2086ee8 commit 37a9b1e
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 6 deletions.
31 changes: 31 additions & 0 deletions README.md
Expand Up @@ -58,6 +58,11 @@ $client->search('sylius', [], 2) // get first 2 pages

#### Get package details:

Gets full package details, generated dynamically by the Packagist API.
Consider using [getComposer()](#get-composer-details)
instead to use the Packagist API more efficiently if you don't need all
the full metadata for a package.

```php
<?php

Expand All @@ -73,6 +78,32 @@ printf(
Package sylius/sylius. Modern ecommerce for Symfony2.
```

#### Get composer details: {#get-composer-details}

Similar to `get()`, but uses composer metadata which is Packagist's preferred
way of retrieving details, since responses are cached efficiently as static files
by the Packagist service. The response lacks some metadata that is provided
by `get()`, see [Packagist API documentation](https://packagist.org/apidoc)
for details. Returns multiple packages, you need to select the requested
one from the indexed array.

```php
<?php

$packages = $client->getComposer('sylius/sylius');
$package = $packages['sylius/sylius'];
$versions = $package->getVersions();

printf(
'Package %s. %s.',
$versions[0]->getName(),
$versions[0]->getDescription()
);

// Outputs:
Package sylius/sylius. Modern ecommerce for Symfony2.
```

#### List all packages:

```php
Expand Down
12 changes: 12 additions & 0 deletions spec/Packagist/Api/ClientSpec.php
Expand Up @@ -90,6 +90,18 @@ public function it_gets_package_details(HttpClient $client, Factory $factory, Re
$this->get('sylius/sylius');
}

public function it_gets_composer_package_details(HttpClient $client, Factory $factory, Response $response)
{
$data = file_get_contents('spec/Packagist/Api/Fixture/get_composer.json');
$response->getBody()->shouldBeCalled()->willReturn($data);

$client->request('get', 'https://packagist.org/p/sylius/sylius.json')->shouldBeCalled()->willReturn($response);

$factory->create(json_decode($data, true))->shouldBeCalled();

$this->getComposer('sylius/sylius');
}

public function it_lists_all_package_names(HttpClient $client, Factory $factory, Response $response)
{
$data = file_get_contents('spec/Packagist/Api/Fixture/all.json');
Expand Down
1 change: 1 addition & 0 deletions spec/Packagist/Api/Fixture/get_composer.json

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions spec/Packagist/Api/Result/FactorySpec.php
Expand Up @@ -47,6 +47,22 @@ public function it_creates_packages()
$this->create($data)->shouldHaveType(Package::class);
}

public function it_creates_composer_packages()
{
$data = json_decode(file_get_contents('spec/Packagist/Api/Fixture/get_composer.json'), true);

$results = $this->create($data);
$results->shouldHaveCount(1);
$results->shouldBeArray();
foreach ($results as $result) {
$result->shouldBeAnInstanceOf('Packagist\Api\Result\Package');

foreach ($result->getVersions() as $version) {
$version->shouldBeAnInstanceOf('Packagist\Api\Result\Version');
}
}
}

public function it_creates_package_names()
{
$data = json_decode(file_get_contents('spec/Packagist/Api/Fixture/all.json'), true);
Expand Down
24 changes: 23 additions & 1 deletion src/Packagist/Api/Client.php
Expand Up @@ -82,7 +82,9 @@ public function search(string $query, array $filters = [], int $limit = 0): arra
}

/**
* Retrieve full package information
* Retrieves full package details, generated dynamically by the Packagist API.
* Consider using {@link getComposer()} instead to use the Packagist API
* more efficiently if you don't need all the full metadata for a package.
*
* @param string $package Full qualified name ex : myname/mypackage
* @return array|Package A package instance or array of packages
Expand All @@ -92,6 +94,26 @@ public function get(string $package)
return $this->respond(sprintf($this->url('/packages/%s.json'), $package));
}

/**
* Similar to {@link get()}, but uses composer metadata which is Packagist's preferred
* way of retrieving details, since responses are cached efficiently as static files
* by the Packagist service. The response lacks some metadata that is provided
* by {@link get()}, see https://packagist.org/apidoc for details.
*
* Caution: Returns an array of packages, you need to select the correct one
* from the indexed array.
*
* @since 1.6
*
* @param string $package Full qualified name ex : myname/mypackage
*
* @return \Packagist\Api\Result\Package[] An array of packages, including the requested one.
*/
public function getComposer($package)
{
return $this->respond(sprintf($this->url('/p/%s.json'), $package));
}

/**
* Search packages
*
Expand Down
39 changes: 34 additions & 5 deletions src/Packagist/Api/Result/Factory.php
Expand Up @@ -25,15 +25,22 @@ class Factory
* @param array $data
* @throws InvalidArgumentException
*
* @return array|Package
* @return array|Package|Package[]|Result[]
*/
public function create(array $data)
{
if (isset($data['results'])) {
return $this->createSearchResults($data['results']);
}
if (isset($data['packages'])) {
return $this->createSearchResults($data['packages']);
$packageOrResult = $data['packages'][array_key_first($data['packages'])];
if (isset($packageOrResult['name'])) {
// Used for /explore/popular.json
return $this->createSearchResults($data['packages']);
} else {
// Used for /p/<package>.json
return $this->createComposerPackagesResults($data['packages']);
}
}
if (isset($data['package'])) {
return $this->createPackageResults($data['package']);
Expand All @@ -49,7 +56,8 @@ public function create(array $data)
* Create a collection of \Packagist\Api\Result\Result
*
* @param array $results
* @return array
*
* @return Result[]
*/
public function createSearchResults(array $results): array
{
Expand All @@ -61,8 +69,29 @@ public function createSearchResults(array $results): array
}

/**
* Parse array to \Packagist\Api\Result\Result
*
* @param array $packages
* @return Package[]
*/
public function createComposerPackagesResults(array $packages)
{
$created = array();

foreach ($packages as $name => $package) {
// Create an empty package, only contains versions
$createdPackage = array(
'versions' => [],
);
foreach ($package as $branch => $version) {
$createdPackage['versions'][$branch] = $version;
}

$created[$name] = $this->createPackageResults($createdPackage);
}

return $created;
}

/**
* @param array $package
* @return Package
*/
Expand Down

0 comments on commit 37a9b1e

Please sign in to comment.