Simple async access to packagist.org's API, like listing project details, number of downloads etc., built on top of ReactPHP.
This is an async version of KnpLab's excellent packagist-api
,
but built upon ReactPHP's non-blocking event-loop
.
It uses the async HTTP client library clue/reactphp-buzz
to process
any number of requests in parallel.
In a nutshell, it allows you to issue multiple requests to the packagist API in parallel and process them out of order
whenever their results arrive - while trying to hide all the nifty details of async processing.
On top of that it provides a very easy to use API, very much similar to the original packagist-api
,
enriched with the comfort of ReactPHP's Promises.
Table of Contents
Once installed, you can use the following code to fetch package information from packagist.org:
$loop = React\EventLoop\Factory::create();
$browser = new Clue\React\Buzz\Browser($loop);
$client = new Client($browser);
$client->get('clue/phar-composer')->then(function (Package $package) {
var_dump($package->getName(), $package->getDescription());
});
$loop->run();
See also the examples.
The Client
is responsible for assembling and sending HTTP requests to the remote Packagist API.
It requires a Browser
object
bound to the main EventLoop
in order to handle async requests:
$loop = React\EventLoop\Factory::create();
$browser = new Clue\React\Buzz\Browser($loop);
$client = new Client($browser);
If you need custom connector settings (DNS resolution, TLS parameters, timeouts,
proxy servers etc.), you can explicitly pass a custom instance of the
ConnectorInterface
to the Browser
instance:
$connector = new \React\Socket\Connector($loop, array(
'dns' => '127.0.0.1',
'tcp' => array(
'bindto' => '192.168.10.1:0'
),
'tls' => array(
'verify_peer' => false,
'verify_peer_name' => false
)
));
$browser = new Browser($loop, $connector);
$client = new Client($browser);
All public methods on the Client
resemble the API provided by KnpLab's packagist-api
,
except for an async shift in their return values:
Sending requests is async (non-blocking), so you can actually send multiple requests in parallel.
Packagist will respond to each request with a response message, the order is not guaranteed.
Sending requests uses a Promise-based interface that makes it easy to react to when a request is fulfilled (i.e. either successfully resolved or rejected with an error).
$client->get('clue/graph-composer')->then(
function ($result) {
// result received for get() function
},
function (Exception $e) {
// an error occured while executing the request
}
});
The returned Promise is implemented in such a way that it can be cancelled when it is still pending. Cancelling a pending promise will reject its value with an Exception and clean up any underlying resources.
$promise = $client->get('clue/graph-composer');
$loop->addTimer(2.0, function () use ($promise) {
$promise->cancel();
});
This library uses a very efficient HTTP implementation, so most API requests
should usually be completed in mere milliseconds. However, when sending API
requests over an unreliable network (the internet), there are a number of things
that can go wrong and may cause the request to fail after a time. As such,
timeouts are handled by the underlying HTTP library and this library respects
PHP's default_socket_timeout
setting (default 60s) as a timeout for sending the
outgoing API request and waiting for a successful response and will otherwise
cancel the pending request and reject its value with an Exception.
Note that this timeout value covers creating the underlying transport connection,
sending the API request, waiting for the Packagist service to process the request
and receiving the full API response. To pass a custom timeout value, you can
assign the underlying timeout
option
like this:
$browser = new Browser($loop);
$browser = $browser->withOptions(array(
'timeout' => 10.0
));
$client = new Client($browser);
$client->get('clue/graph-composer')->then(function ($result) {
// result received within 10 seconds maximum
var_dump($result);
});
Similarly, you can use a negative timeout value to not apply a timeout at all
or use a null
value to restore the default handling. Note that the underlying
connection may still impose a different timeout value. See also the underlying
timeout
option for more details.
The search(string $query, array $filters = array()): PromiseInterface<Package[],Exception>
method can be used to
search packages matching the given query string and optionally matching the given filter parameter.
It resolves with an array containing zero or more Package
objects
on success or rejects with an Exception
on error.
$client->search('packagist')->then(function (array $packages) {
foreach ($packages as $package) {
echo $package->getName() . PHP_EOL;
}
});
Note that this method follows Packagist's paginated search results which may contain a large number of matches depending on your search. Accordingly, this method sends one API request for each page which may take a while for the whole search to be completed. It is not uncommon to take around 5-10 seconds to fetch search results for 1000 matches.
The get(string $name): PromiseInterface<Package,Exception>
method can be used to
get package details for the given package name.
It resolves with a single Package
object
on success or rejects with an Exception
on error.
$client->get('clue/packagist-api-react')->then(function (Package $package) {
echo $package->getDescription();
});
The all(array $filters = array()): PromiseInterface<string[],Exception>
method an be used to
list all package names, optionally matching the given filter parameter.
It resolves with an array of package names
on success or rejects with an Exception
on error.
$client->all(array('vendor' => 'clue'))->then(function (array $names) {
// array containing (among others) "clue/packagist-api-react"
});
The Package
class represents information about a given composer package.
This class is part of the underlying KnpLab/packagist-api,
its full name is actually Packagist\Api\Result\Package
.
See its class outline for all available methods.
The getName()
method can be used to get the package name.
The getDescription()
method can be used to the package description.
The recommended way to install this library is through Composer. New to Composer?
This project follows SemVer. This will install the latest supported version:
$ composer require clue/packagist-api-react:^1.3
See also the CHANGELOG for details about version upgrades.
This project aims to run on any platform and thus does not require any PHP extensions and supports running on legacy PHP 5.3 through current PHP 8+ and HHVM. It's highly recommended to use PHP 7+ for this project.
To run the test suite, you first need to clone this repo and then install all dependencies through Composer:
$ composer install
To run the test suite, go to the project root and run:
$ php vendor/bin/phpunit
This project is released under the permissive MIT license.
Did you know that I offer custom development services and issuing invoices for sponsorships of releases and for contributions? Contact me (@clue) for details.