diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..f05dac3
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,64 @@
+name: CI
+
+on:
+ push:
+ branches: ["main", "master"]
+ pull_request:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ci-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ strategy:
+ fail-fast: false
+ matrix:
+ php: ['8.3', '8.4', '8.5']
+ dependency-version: ['prefer-stable', 'prefer-lowest']
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ coverage: none
+ tools: composer:v2
+ extensions: mbstring, json
+
+ - name: Validate composer.json
+ run: composer validate --strict
+
+ - name: Get Composer cache directory
+ id: composer-cache
+ run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+
+ - name: Cache Composer dependencies
+ uses: actions/cache@v4
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ matrix.dependency-version }}-${{ hashFiles('**/composer.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-composer-${{ matrix.php }}-
+
+ - name: Install dependencies
+ run: |
+ composer update --${{ matrix.dependency-version }} \
+ --no-interaction \
+ --prefer-dist \
+ --no-progress
+
+ - name: Run PHPUnit
+ run: composer test
+
+ - name: Run PHPStan
+ if: matrix.dependency-version == 'prefer-stable'
+ run: composer stan
diff --git a/.gitignore b/.gitignore
index 9c006cf..e0b9a47 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,8 @@
/bin/
composer.lock
composer.phar
+.phpunit.cache/
+.phpunit.result.cache
+coverage/
+*.cache
+.DS_Store
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 5c82401..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-language: php
-
-php:
- - '7.0'
- - '7.1'
- - '7.2'
-
-before_script:
- - composer install -n --dev --prefer-source
-
-script: vendor/bin/phpcs -p --standard=PSR2 src/ && vendor/bin/phpunit
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 71e19e5..c1e3e20 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# CHANGE LOG
+### v2.1.0 - 2026-02-20
+
+- Added production-readiness test coverage for:
+ - `IpstackClient` requester and bulk edge cases
+ - `IpstackFactory` configuration failure and success paths
+ - `Psr18Transport` request building, HTTP error handling, invalid JSON handling, and transport exception wrapping
+- Expanded CI quality gates to run `rector:check` in matrix builds.
+
### v1.0.0 - 2018-07-06
- New Release
diff --git a/README.md b/README.md
index 64d14f0..4d41fb6 100644
--- a/README.md
+++ b/README.md
@@ -1,87 +1,334 @@
-## A Simple Package for IP to Location implementation with PHP using real-time API service through https://ipstack.com.
+# ipstack PHP Client
-[](https://travis-ci.com/sudiptpa/ipstack)
-[](https://packagist.org/packages/sudiptpa/ipstack)
-[](https://packagist.org/packages/sudiptpa/ipstack)
-[](https://packagist.org/packages/sudiptpa/ipstack)
+A modern, PSR-based PHP client for the [ipstack](https://ipstack.com) API.
-https://ipstack.com provides a public HTTP API for software developers to search the geolocation of IP addresses. It uses a database of IP addresses that are associated to cities along with other relevant information like time zone, latitude and longitude.
+## Highlights
-You're allowed up to 10,000 queries per month by default. Once this limit is reached, all of your requests will result in HTTP 403, forbidden, until your quota is cleared.
+- PSR-18 transport architecture
+- Factory-based client construction
+- Typed result models (`IpstackResult`, `Country`, `Region`, etc.)
+- Bulk lookup support (up to 50 IPs per request)
+- Domain exceptions mapped from ipstack API error codes
-The ipstack is an API service which enable you to locate and identify website visitors at a stage before any data is entered into your system. The data received from the API can be used to enhance user experiences based on location data and assess risks and potential threats to your web application in time.
+## Requirements
-### Installation
+- PHP `8.3` to `8.5` (`>=8.3 <8.6`)
+- `psr/http-client`
+- `psr/http-factory`
+- `psr/http-message`
+- A concrete PSR-18 client + PSR-17 factory implementation (for example `symfony/http-client` + `nyholm/psr7`)
-You can install the package via composer: [Composer](http://getcomposer.org/).
+## Installation
-```
+```bash
composer require sudiptpa/ipstack
```
-And run composer to update your dependencies:
+Optional (for the PSR-18 quick-start adapter stack shown below):
+
+```bash
+composer require symfony/http-client nyholm/psr7
+```
+
+## Architecture
+
+Core layers:
+
+- `Ipstack::factory()` configures and builds the client
+- `IpstackClient` runs lookup operations
+- `TransportInterface` abstracts HTTP transport (`Psr18Transport` included)
+- `IpstackResultMapper` maps API payloads to typed models
+
+Main entry points:
+
+- `Ipstack\\Ipstack`
+- `Ipstack\\Factory\\IpstackFactory`
+- `Ipstack\\Client\\IpstackClient`
+- `Ipstack\\Client\\Options`
+
+## Quick Start (PSR-18)
+
+```php
+withAccessKey('YOUR_ACCESS_KEY')
+ ->withPsr18(new Psr18Client(), new Psr17Factory())
+ ->build();
+
+$result = $client->lookup('8.8.8.8');
+
+echo $result->formatted();
+```
+
+## Full Usage Examples
+
+### 1) Reusable bootstrap
+
+```php
+use Ipstack\Client\IpstackClient;
+use Ipstack\Ipstack;
+use Nyholm\Psr7\Factory\Psr17Factory;
+use Symfony\Component\HttpClient\Psr18Client;
+
+function ipstackClient(string $accessKey): IpstackClient
+{
+ return Ipstack::factory()
+ ->withAccessKey($accessKey)
+ ->withPsr18(new Psr18Client(), new Psr17Factory())
+ ->build();
+}
+```
+
+### 2) Override base URL (advanced)
+
+```php
+$client = Ipstack::factory()
+ ->withAccessKey('YOUR_ACCESS_KEY')
+ ->withBaseUrl('https://api.ipstack.com')
+ ->withPsr18(new Psr18Client(), new Psr17Factory())
+ ->build();
+```
+
+### 3) Single IP lookup + options
- $ curl -s http://getcomposer.org/installer | php
- $ php composer.phar update
+```php
+use Ipstack\Client\Options;
-### Usage
+$options = Options::create()
+ ->fields(['ip', 'country_name', 'region_name', 'city', 'zip'])
+ ->language('en')
+ ->security(true)
+ ->hostname(true);
-This package only supports `json` format for now.
+$result = $client->lookup('8.8.8.8', $options);
-Here are a few examples on how you can use the package:
+echo $result->ip . PHP_EOL;
+echo $result->formatted() . PHP_EOL;
+echo ($result->country->name ?? 'unknown') . PHP_EOL;
+```
-#### Free
+### 4) Requester IP (`/check`)
```php
- $ipstack = new Sujip\Ipstack\Ipstack($ip);
+$result = $client->lookupRequester();
+
+echo $result->ip . PHP_EOL;
+```
+
+### 5) Bulk lookup
- $ipstack->country();
+```php
+$results = $client->lookupBulk(['8.8.8.8', '1.1.1.1']);
- $ipstack->city();
+echo 'count=' . count($results) . PHP_EOL;
- $ipstack->region();
+foreach ($results as $row) {
+ echo $row->ip . ' => ' . $row->formatted() . PHP_EOL;
+}
```
-#### Using API Key
+Notes:
+
+- Empty list returns an empty `Ipstack\\Model\\IpstackCollection`
+- More than 50 IPs throws `Ipstack\\Exception\\IpstackException`
+
+### 6) Access raw payload and JSON output
```php
- $ipstack = new Sujip\Ipstack\Ipstack($ip, $api_key);
+$result = $client->lookup('8.8.8.8');
+
+$raw = $result->raw();
+$json = json_encode($result, JSON_PRETTY_PRINT);
- $ipstack->formatted();
+echo $json . PHP_EOL;
```
-#### Premium Membership
+### 7) Custom transport (local smoke test)
-If you have a paid membership with https://ipstack.com and want to make API call with HTTPS mode, you can call ->secure() method.
+```php
+use Ipstack\Ipstack;
+use Ipstack\Transport\TransportInterface;
+
+final class DemoTransport implements TransportInterface
+{
+ public function get(string $url, array $query): array
+ {
+ return [
+ 'ip' => '8.8.8.8',
+ 'country_name' => 'United States',
+ 'country_code' => 'US',
+ 'region_name' => 'California',
+ 'region_code' => 'CA',
+ 'city' => 'Mountain View',
+ 'zip' => '94035',
+ ];
+ }
+}
+
+$client = Ipstack::factory()
+ ->withAccessKey('DUMMY_KEY')
+ ->withTransport(new DemoTransport())
+ ->build();
+
+echo $client->lookup('8.8.8.8')->formatted();
+```
+
+### 8) Typed exception handling
```php
- $ipstack = (new Sujip\Ipstack\Ipstack($ip, $api_key))->secure();
+use Ipstack\Exception\ApiErrorException;
+use Ipstack\Exception\BatchNotSupportedException;
+use Ipstack\Exception\InvalidFieldsException;
+use Ipstack\Exception\InvalidResponseException;
+use Ipstack\Exception\IpstackException;
+use Ipstack\Exception\RateLimitException;
+use Ipstack\Exception\TooManyIpsException;
+use Ipstack\Exception\TransportException;
+
+try {
+ $result = $client->lookup('8.8.8.8');
+} catch (RateLimitException $e) {
+ // 104
+} catch (InvalidFieldsException $e) {
+ // 301
+} catch (TooManyIpsException $e) {
+ // 302
+} catch (BatchNotSupportedException $e) {
+ // 303
+} catch (TransportException | InvalidResponseException $e) {
+ // network/HTTP/JSON transport failure
+} catch (ApiErrorException $e) {
+ // other API-side errors
+} catch (IpstackException $e) {
+ // any other library-level exception
+}
+```
+
+## Models
+
+`lookup*()` returns `Ipstack\\Model\\IpstackResult` with nested typed objects:
+
+- `country` (`Country`)
+- `region` (`Region`)
+- `location` (`Location|null`)
+- `connection` (`Connection|null`)
+- `security` (`Security|null`)
+- `routing` (`Routing|null`)
+
+Helpers:
+
+- `$result->formatted()` returns a readable location string
+- `$result->raw()` returns original decoded payload
+- `json_encode($result)` serializes raw payload (`JsonSerializable`)
+
+## Error Model
+
+All package exceptions extend `Ipstack\\Exception\\IpstackException`.
+
+Transport/response exceptions:
- $ipstack->formatted();
+- `TransportException`
+- `InvalidResponseException`
+
+API exceptions:
+
+- `ApiErrorException` (base)
+- `RateLimitException` for code `104`
+- `InvalidFieldsException` for code `301`
+- `TooManyIpsException` for code `302`
+- `BatchNotSupportedException` for code `303`
+
+## Real API Smoke Test
+
+Use an environment variable and run this one-liner:
+
+```bash
+IPSTACK_ACCESS_KEY=your_key php -r 'require "vendor/autoload.php"; $c=Ipstack\Ipstack::factory()->withAccessKey(getenv("IPSTACK_ACCESS_KEY"))->withPsr18(new Symfony\Component\HttpClient\Psr18Client(), new Nyholm\Psr7\Factory\Psr17Factory())->build(); echo $c->lookup("8.8.8.8")->formatted(), PHP_EOL;'
```
-Also have a look in the [source code of `Sujip\Ipstack\Ipstack`](https://github.com/sudiptpa/ipstack/blob/master/src/Ipstack.php) to discover the methods you can use.
+## Troubleshooting
+
+- `InvalidArgumentException: Access key is required.`
+ - Call `->withAccessKey('...')` before `->build()`.
+- `InvalidArgumentException: No transport configured...`
+ - Configure either `->withPsr18(...)` or `->withTransport(...)`.
+- `TransportException: Unexpected HTTP status ...`
+ - Check network access, endpoint, and ipstack key validity.
+- `InvalidResponseException: Invalid JSON response`
+ - Usually an upstream/proxy response issue; inspect raw HTTP response.
+- `RateLimitException` (`104`)
+ - Monthly/request quota reached on your ipstack account.
+
+## API Plan Notes
+
+Some ipstack fields/features may depend on plan tier (for example `security`, `hostname`, and parts of connection/routing data). If a field is unavailable on your plan, ipstack may omit it or return an API error.
-### Changelog
+## Breaking Changes
-Please see [CHANGELOG](https://github.com/sudiptpa/ipstack/blob/master/CHANGELOG.md) for more information what has changed recently.
+Compared to the legacy API style:
-### Contributing
+- `Sujip\\Ipstack\\Ipstack` is replaced by `Ipstack\\Ipstack`
+- Constructor-style usage is replaced by factory-style configuration
+- Legacy root convenience accessors are replaced by typed result objects from lookup methods
+- `secure()` is no longer the documented toggle pattern
+- Transport wiring is explicit and PSR-based
+- Bulk lookup is now `lookupBulk(array $ips)` with a strict max of 50 IPs/request
-Contributions are **welcome** and will be fully **credited**.
+## Migration Guide (v1 -> v2)
-Contributions can be made via a Pull Request on [Github](https://github.com/sudiptpa/ipstack).
+Use this quick mapping to migrate legacy usage to the current API.
+| v1 (legacy) | v2 (current) |
+| --- | --- |
+| `new Sujip\\Ipstack\\Ipstack($ip)` | Build once, then call lookup: `Ipstack::factory()->withAccessKey(...)->withPsr18(...)->build()->lookup($ip)` |
+| `new Sujip\\Ipstack\\Ipstack($ip, $apiKey)` | `Ipstack::factory()->withAccessKey($apiKey)->withPsr18(...)->build()` |
+| `$ipstack->country()` | `$client->lookup($ip)->country->name` |
+| `$ipstack->region()` | `$client->lookup($ip)->region->name` |
+| `$ipstack->city()` | `$client->lookup($ip)->city` |
+| `$ipstack->formatted()` | `$client->lookup($ip)->formatted()` |
+| `$ipstack->secure()` | Use configured base URL + transport: `withBaseUrl(...)` + `withPsr18(...)` |
+| N/A (or ad-hoc loops) | Native bulk call: `$client->lookupBulk([$ip1, $ip2])` |
+| Generic runtime/API failures | Typed exception model (`RateLimitException`, `InvalidFieldsException`, `TransportException`, etc.) |
-### Testing
+### Suggested migration steps
+1. Replace direct constructor usage with factory-based client construction.
+2. Create one reusable `IpstackClient` service and inject it where needed.
+3. Replace legacy convenience getters with `lookup()` result model access.
+4. Add typed exception handling around lookup calls.
+5. Add a smoke test (local fake transport + real API env test) before release.
+
+## Quality Gates
+
+CI runs the following checks across PHP `8.3`, `8.4`, and `8.5`:
+
+- `composer test`
+- `composer stan` (on `prefer-stable` matrix jobs)
+
+## Development
+
+```bash
+composer test
+composer stan
+composer rector
+composer rector:check
```
- $ composer test
- ```
-### Support
+## Changelog
+
+See `CHANGELOG.md` for release notes (latest hardening release: `v2.1.0`).
+
+## Author
+
+- Sujip Thapa (`sudiptpa@gmail.com`)
-If you are having general issues with the package, feel free to drop me and email [sudiptpa@gmail.com](mailto:sudiptpa@gmail.com)
+## License
-If you believe you have found a bug, please report it using the [GitHub issue tracker](https://github.com/sudiptpa/ipstack/issues),
-or better yet, fork the library and submit a pull request.
+MIT. See `LICENSE`.
diff --git a/composer.json b/composer.json
index b14e49a..2c535ee 100644
--- a/composer.json
+++ b/composer.json
@@ -1,42 +1,53 @@
{
- "name":"sudiptpa/ipstack",
- "type":"library",
- "description":"A simple package for IP to Location implementation with PHP.",
- "keywords":[
+ "name": "sudiptpa/ipstack",
+ "type": "library",
+ "description": "A simple package for IP to Location implementation with PHP.",
+ "keywords": [
"geoip",
"ipstack",
"ip to location"
],
- "license":"MIT",
- "authors":[
+ "license": "MIT",
+ "authors": [
{
- "name":"Sujip Thapa",
- "email":"sudiptpa@gmail.com"
+ "name": "Sujip Thapa",
+ "email": "sudiptpa@gmail.com"
}
],
- "require":{
- "php": "~7.0"
+ "require": {
+ "php": ">=8.3 <8.6",
+ "psr/http-client": "^1.0",
+ "psr/http-factory": "^1.1",
+ "psr/http-message": "^2.0"
},
- "require-dev":{
- "mockery/mockery": "^0.9.4",
- "phpunit/phpunit": "~6.0",
- "squizlabs/php_codesniffer": "^3",
- "phpro/grumphp": "^0.14.0"
+ "require-dev": {
+ "phpunit/phpunit": "^11.0",
+ "phpstan/phpstan": "^1.11",
+ "symfony/http-client": "^7.0 || ^8.0",
+ "nyholm/psr7": "^1.8",
+ "rector/rector": "^1.2"
},
- "extra":{
- "branch-alias":{
- "dev-master":"1.0.x-dev"
+ "autoload": {
+ "psr-4": {
+ "Ipstack\\": "src/"
}
},
- "autoload":{
- "psr-4":{
- "Sujip\\Ipstack\\":"src/"
+ "autoload-dev": {
+ "psr-4": {
+ "Ipstack\\Tests\\": "tests/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
}
},
"scripts": {
- "test": "vendor/bin/phpunit",
- "check-style": "phpcs -p --standard=PSR2 src/",
- "fix-style": "phpcbf -p --standard=PSR2 src/"
+ "test": "phpunit",
+ "stan": "phpstan analyse -c phpstan.neon --debug",
+ "rector": "@php vendor/bin/rector process --config=rector.php",
+ "rector:check": "@php vendor/bin/rector process --config=rector.php --dry-run"
},
+ "minimum-stability": "stable",
"prefer-stable": true
}
diff --git a/composer.lock b/composer.lock
deleted file mode 100755
index fd18a7c..0000000
--- a/composer.lock
+++ /dev/null
@@ -1,3196 +0,0 @@
-{
- "_readme": [
- "This file locks the dependencies of your project to a known state",
- "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
- "This file is @generated automatically"
- ],
- "content-hash": "7d1da35e8f2d8380766e0d8bea6b84d6",
- "packages": [],
- "packages-dev": [
- {
- "name": "composer/ca-bundle",
- "version": "1.1.1",
- "source": {
- "type": "git",
- "url": "https://github.com/composer/ca-bundle.git",
- "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/composer/ca-bundle/zipball/d2c0a83b7533d6912e8d516756ebd34f893e9169",
- "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169",
- "shasum": ""
- },
- "require": {
- "ext-openssl": "*",
- "ext-pcre": "*",
- "php": "^5.3.2 || ^7.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5",
- "psr/log": "^1.0",
- "symfony/process": "^2.5 || ^3.0 || ^4.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Composer\\CaBundle\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Jordi Boggiano",
- "email": "j.boggiano@seld.be",
- "homepage": "http://seld.be"
- }
- ],
- "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.",
- "keywords": [
- "cabundle",
- "cacert",
- "certificate",
- "ssl",
- "tls"
- ],
- "time": "2018-03-29T19:57:20+00:00"
- },
- {
- "name": "composer/composer",
- "version": "1.6.5",
- "source": {
- "type": "git",
- "url": "https://github.com/composer/composer.git",
- "reference": "b184a92419cc9a9c4c6a09db555a94d441cb11c9"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/composer/composer/zipball/b184a92419cc9a9c4c6a09db555a94d441cb11c9",
- "reference": "b184a92419cc9a9c4c6a09db555a94d441cb11c9",
- "shasum": ""
- },
- "require": {
- "composer/ca-bundle": "^1.0",
- "composer/semver": "^1.0",
- "composer/spdx-licenses": "^1.2",
- "justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0",
- "php": "^5.3.2 || ^7.0",
- "psr/log": "^1.0",
- "seld/cli-prompt": "^1.0",
- "seld/jsonlint": "^1.4",
- "seld/phar-utils": "^1.0",
- "symfony/console": "^2.7 || ^3.0 || ^4.0",
- "symfony/filesystem": "^2.7 || ^3.0 || ^4.0",
- "symfony/finder": "^2.7 || ^3.0 || ^4.0",
- "symfony/process": "^2.7 || ^3.0 || ^4.0"
- },
- "conflict": {
- "symfony/console": "2.8.38"
- },
- "require-dev": {
- "phpunit/phpunit": "^4.8.35 || ^5.7",
- "phpunit/phpunit-mock-objects": "^2.3 || ^3.0"
- },
- "suggest": {
- "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages",
- "ext-zip": "Enabling the zip extension allows you to unzip archives",
- "ext-zlib": "Allow gzip compression of HTTP requests"
- },
- "bin": [
- "bin/composer"
- ],
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.6-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Composer\\": "src/Composer"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nils Adermann",
- "email": "naderman@naderman.de",
- "homepage": "http://www.naderman.de"
- },
- {
- "name": "Jordi Boggiano",
- "email": "j.boggiano@seld.be",
- "homepage": "http://seld.be"
- }
- ],
- "description": "Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.",
- "homepage": "https://getcomposer.org/",
- "keywords": [
- "autoload",
- "dependency",
- "package"
- ],
- "time": "2018-05-04T09:44:59+00:00"
- },
- {
- "name": "composer/semver",
- "version": "1.4.2",
- "source": {
- "type": "git",
- "url": "https://github.com/composer/semver.git",
- "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573",
- "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573",
- "shasum": ""
- },
- "require": {
- "php": "^5.3.2 || ^7.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^4.5 || ^5.0.5",
- "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Composer\\Semver\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nils Adermann",
- "email": "naderman@naderman.de",
- "homepage": "http://www.naderman.de"
- },
- {
- "name": "Jordi Boggiano",
- "email": "j.boggiano@seld.be",
- "homepage": "http://seld.be"
- },
- {
- "name": "Rob Bast",
- "email": "rob.bast@gmail.com",
- "homepage": "http://robbast.nl"
- }
- ],
- "description": "Semver library that offers utilities, version constraint parsing and validation.",
- "keywords": [
- "semantic",
- "semver",
- "validation",
- "versioning"
- ],
- "time": "2016-08-30T16:08:34+00:00"
- },
- {
- "name": "composer/spdx-licenses",
- "version": "1.4.0",
- "source": {
- "type": "git",
- "url": "https://github.com/composer/spdx-licenses.git",
- "reference": "cb17687e9f936acd7e7245ad3890f953770dec1b"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/cb17687e9f936acd7e7245ad3890f953770dec1b",
- "reference": "cb17687e9f936acd7e7245ad3890f953770dec1b",
- "shasum": ""
- },
- "require": {
- "php": "^5.3.2 || ^7.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5",
- "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Composer\\Spdx\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nils Adermann",
- "email": "naderman@naderman.de",
- "homepage": "http://www.naderman.de"
- },
- {
- "name": "Jordi Boggiano",
- "email": "j.boggiano@seld.be",
- "homepage": "http://seld.be"
- },
- {
- "name": "Rob Bast",
- "email": "rob.bast@gmail.com",
- "homepage": "http://robbast.nl"
- }
- ],
- "description": "SPDX licenses list and validation library.",
- "keywords": [
- "license",
- "spdx",
- "validator"
- ],
- "time": "2018-04-30T10:33:04+00:00"
- },
- {
- "name": "doctrine/collections",
- "version": "v1.4.0",
- "source": {
- "type": "git",
- "url": "https://github.com/doctrine/collections.git",
- "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba",
- "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba",
- "shasum": ""
- },
- "require": {
- "php": "^5.6 || ^7.0"
- },
- "require-dev": {
- "doctrine/coding-standard": "~0.1@dev",
- "phpunit/phpunit": "^5.7"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.3.x-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Doctrine\\Common\\Collections\\": "lib/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Roman Borschel",
- "email": "roman@code-factory.org"
- },
- {
- "name": "Benjamin Eberlei",
- "email": "kontakt@beberlei.de"
- },
- {
- "name": "Guilherme Blanco",
- "email": "guilhermeblanco@gmail.com"
- },
- {
- "name": "Jonathan Wage",
- "email": "jonwage@gmail.com"
- },
- {
- "name": "Johannes Schmitt",
- "email": "schmittjoh@gmail.com"
- }
- ],
- "description": "Collections Abstraction library",
- "homepage": "http://www.doctrine-project.org",
- "keywords": [
- "array",
- "collections",
- "iterator"
- ],
- "time": "2017-01-03T10:49:41+00:00"
- },
- {
- "name": "doctrine/instantiator",
- "version": "1.0.5",
- "source": {
- "type": "git",
- "url": "https://github.com/doctrine/instantiator.git",
- "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
- "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3,<8.0-DEV"
- },
- "require-dev": {
- "athletic/athletic": "~0.1.8",
- "ext-pdo": "*",
- "ext-phar": "*",
- "phpunit/phpunit": "~4.0",
- "squizlabs/php_codesniffer": "~2.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Marco Pivetta",
- "email": "ocramius@gmail.com",
- "homepage": "http://ocramius.github.com/"
- }
- ],
- "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
- "homepage": "https://github.com/doctrine/instantiator",
- "keywords": [
- "constructor",
- "instantiate"
- ],
- "time": "2015-06-14T21:17:01+00:00"
- },
- {
- "name": "gitonomy/gitlib",
- "version": "v1.0.4",
- "source": {
- "type": "git",
- "url": "https://github.com/gitonomy/gitlib.git",
- "reference": "932a960221ae3484a3e82553b3be478e56beb68d"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/gitonomy/gitlib/zipball/932a960221ae3484a3e82553b3be478e56beb68d",
- "reference": "932a960221ae3484a3e82553b3be478e56beb68d",
- "shasum": ""
- },
- "require": {
- "php": "^5.3 || ^7.0",
- "symfony/process": "^2.3|^3.0|^4.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^4.8.35|^5.7",
- "psr/log": "^1.0"
- },
- "suggest": {
- "psr/log": "Add some log"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Gitonomy\\Git\\": "src/Gitonomy/Git/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Alexandre Salomé",
- "email": "alexandre.salome@gmail.com",
- "homepage": "http://alexandre-salome.fr"
- },
- {
- "name": "Julien DIDIER",
- "email": "genzo.wm@gmail.com",
- "homepage": "http://www.jdidier.net"
- }
- ],
- "description": "Library for accessing git",
- "homepage": "http://gitonomy.com",
- "time": "2018-04-22T19:55:36+00:00"
- },
- {
- "name": "hamcrest/hamcrest-php",
- "version": "v1.2.2",
- "source": {
- "type": "git",
- "url": "https://github.com/hamcrest/hamcrest-php.git",
- "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b37020aa976fa52d3de9aa904aa2522dc518f79c",
- "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.2"
- },
- "replace": {
- "cordoval/hamcrest-php": "*",
- "davedevelopment/hamcrest-php": "*",
- "kodova/hamcrest-php": "*"
- },
- "require-dev": {
- "phpunit/php-file-iterator": "1.3.3",
- "satooshi/php-coveralls": "dev-master"
- },
- "type": "library",
- "autoload": {
- "classmap": [
- "hamcrest"
- ],
- "files": [
- "hamcrest/Hamcrest.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD"
- ],
- "description": "This is the PHP port of Hamcrest Matchers",
- "keywords": [
- "test"
- ],
- "time": "2015-05-11T14:41:42+00:00"
- },
- {
- "name": "justinrainbow/json-schema",
- "version": "5.2.7",
- "source": {
- "type": "git",
- "url": "https://github.com/justinrainbow/json-schema.git",
- "reference": "8560d4314577199ba51bf2032f02cd1315587c23"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/8560d4314577199ba51bf2032f02cd1315587c23",
- "reference": "8560d4314577199ba51bf2032f02cd1315587c23",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.3"
- },
- "require-dev": {
- "friendsofphp/php-cs-fixer": "^2.1",
- "json-schema/json-schema-test-suite": "1.2.0",
- "phpunit/phpunit": "^4.8.35"
- },
- "bin": [
- "bin/validate-json"
- ],
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "5.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "JsonSchema\\": "src/JsonSchema/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Bruno Prieto Reis",
- "email": "bruno.p.reis@gmail.com"
- },
- {
- "name": "Justin Rainbow",
- "email": "justin.rainbow@gmail.com"
- },
- {
- "name": "Igor Wiedler",
- "email": "igor@wiedler.ch"
- },
- {
- "name": "Robert Schönthal",
- "email": "seroscho@googlemail.com"
- }
- ],
- "description": "A library to validate a json schema.",
- "homepage": "https://github.com/justinrainbow/json-schema",
- "keywords": [
- "json",
- "schema"
- ],
- "time": "2018-02-14T22:26:30+00:00"
- },
- {
- "name": "mockery/mockery",
- "version": "0.9.9",
- "source": {
- "type": "git",
- "url": "https://github.com/mockery/mockery.git",
- "reference": "6fdb61243844dc924071d3404bb23994ea0b6856"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/mockery/mockery/zipball/6fdb61243844dc924071d3404bb23994ea0b6856",
- "reference": "6fdb61243844dc924071d3404bb23994ea0b6856",
- "shasum": ""
- },
- "require": {
- "hamcrest/hamcrest-php": "~1.1",
- "lib-pcre": ">=7.0",
- "php": ">=5.3.2"
- },
- "require-dev": {
- "phpunit/phpunit": "~4.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "0.9.x-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Mockery": "library/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Pádraic Brady",
- "email": "padraic.brady@gmail.com",
- "homepage": "http://blog.astrumfutura.com"
- },
- {
- "name": "Dave Marshall",
- "email": "dave.marshall@atstsolutions.co.uk",
- "homepage": "http://davedevelopment.co.uk"
- }
- ],
- "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.",
- "homepage": "http://github.com/padraic/mockery",
- "keywords": [
- "BDD",
- "TDD",
- "library",
- "mock",
- "mock objects",
- "mockery",
- "stub",
- "test",
- "test double",
- "testing"
- ],
- "time": "2017-02-28T12:52:32+00:00"
- },
- {
- "name": "monolog/monolog",
- "version": "1.23.0",
- "source": {
- "type": "git",
- "url": "https://github.com/Seldaek/monolog.git",
- "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4",
- "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.0",
- "psr/log": "~1.0"
- },
- "provide": {
- "psr/log-implementation": "1.0.0"
- },
- "require-dev": {
- "aws/aws-sdk-php": "^2.4.9 || ^3.0",
- "doctrine/couchdb": "~1.0@dev",
- "graylog2/gelf-php": "~1.0",
- "jakub-onderka/php-parallel-lint": "0.9",
- "php-amqplib/php-amqplib": "~2.4",
- "php-console/php-console": "^3.1.3",
- "phpunit/phpunit": "~4.5",
- "phpunit/phpunit-mock-objects": "2.3.0",
- "ruflin/elastica": ">=0.90 <3.0",
- "sentry/sentry": "^0.13",
- "swiftmailer/swiftmailer": "^5.3|^6.0"
- },
- "suggest": {
- "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
- "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
- "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
- "ext-mongo": "Allow sending log messages to a MongoDB server",
- "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
- "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
- "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
- "php-console/php-console": "Allow sending log messages to Google Chrome",
- "rollbar/rollbar": "Allow sending log messages to Rollbar",
- "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
- "sentry/sentry": "Allow sending log messages to a Sentry server"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Monolog\\": "src/Monolog"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Jordi Boggiano",
- "email": "j.boggiano@seld.be",
- "homepage": "http://seld.be"
- }
- ],
- "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
- "homepage": "http://github.com/Seldaek/monolog",
- "keywords": [
- "log",
- "logging",
- "psr-3"
- ],
- "time": "2017-06-19T01:22:40+00:00"
- },
- {
- "name": "myclabs/deep-copy",
- "version": "1.7.0",
- "source": {
- "type": "git",
- "url": "https://github.com/myclabs/DeepCopy.git",
- "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
- "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
- "shasum": ""
- },
- "require": {
- "php": "^5.6 || ^7.0"
- },
- "require-dev": {
- "doctrine/collections": "^1.0",
- "doctrine/common": "^2.6",
- "phpunit/phpunit": "^4.1"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "DeepCopy\\": "src/DeepCopy/"
- },
- "files": [
- "src/DeepCopy/deep_copy.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "description": "Create deep copies (clones) of your objects",
- "keywords": [
- "clone",
- "copy",
- "duplicate",
- "object",
- "object graph"
- ],
- "time": "2017-10-19T19:58:43+00:00"
- },
- {
- "name": "phar-io/manifest",
- "version": "1.0.1",
- "source": {
- "type": "git",
- "url": "https://github.com/phar-io/manifest.git",
- "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0",
- "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0",
- "shasum": ""
- },
- "require": {
- "ext-dom": "*",
- "ext-phar": "*",
- "phar-io/version": "^1.0.1",
- "php": "^5.6 || ^7.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Arne Blankerts",
- "email": "arne@blankerts.de",
- "role": "Developer"
- },
- {
- "name": "Sebastian Heuer",
- "email": "sebastian@phpeople.de",
- "role": "Developer"
- },
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "Developer"
- }
- ],
- "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
- "time": "2017-03-05T18:14:27+00:00"
- },
- {
- "name": "phar-io/version",
- "version": "1.0.1",
- "source": {
- "type": "git",
- "url": "https://github.com/phar-io/version.git",
- "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df",
- "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df",
- "shasum": ""
- },
- "require": {
- "php": "^5.6 || ^7.0"
- },
- "type": "library",
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Arne Blankerts",
- "email": "arne@blankerts.de",
- "role": "Developer"
- },
- {
- "name": "Sebastian Heuer",
- "email": "sebastian@phpeople.de",
- "role": "Developer"
- },
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "Developer"
- }
- ],
- "description": "Library for handling version information and constraints",
- "time": "2017-03-05T17:38:23+00:00"
- },
- {
- "name": "phpdocumentor/reflection-common",
- "version": "1.0.1",
- "source": {
- "type": "git",
- "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
- "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
- "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
- "shasum": ""
- },
- "require": {
- "php": ">=5.5"
- },
- "require-dev": {
- "phpunit/phpunit": "^4.6"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "phpDocumentor\\Reflection\\": [
- "src"
- ]
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Jaap van Otterdijk",
- "email": "opensource@ijaap.nl"
- }
- ],
- "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
- "homepage": "http://www.phpdoc.org",
- "keywords": [
- "FQSEN",
- "phpDocumentor",
- "phpdoc",
- "reflection",
- "static analysis"
- ],
- "time": "2017-09-11T18:02:19+00:00"
- },
- {
- "name": "phpdocumentor/reflection-docblock",
- "version": "4.3.0",
- "source": {
- "type": "git",
- "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
- "reference": "94fd0001232e47129dd3504189fa1c7225010d08"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08",
- "reference": "94fd0001232e47129dd3504189fa1c7225010d08",
- "shasum": ""
- },
- "require": {
- "php": "^7.0",
- "phpdocumentor/reflection-common": "^1.0.0",
- "phpdocumentor/type-resolver": "^0.4.0",
- "webmozart/assert": "^1.0"
- },
- "require-dev": {
- "doctrine/instantiator": "~1.0.5",
- "mockery/mockery": "^1.0",
- "phpunit/phpunit": "^6.4"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "phpDocumentor\\Reflection\\": [
- "src/"
- ]
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Mike van Riel",
- "email": "me@mikevanriel.com"
- }
- ],
- "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
- "time": "2017-11-30T07:14:17+00:00"
- },
- {
- "name": "phpdocumentor/type-resolver",
- "version": "0.4.0",
- "source": {
- "type": "git",
- "url": "https://github.com/phpDocumentor/TypeResolver.git",
- "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7",
- "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7",
- "shasum": ""
- },
- "require": {
- "php": "^5.5 || ^7.0",
- "phpdocumentor/reflection-common": "^1.0"
- },
- "require-dev": {
- "mockery/mockery": "^0.9.4",
- "phpunit/phpunit": "^5.2||^4.8.24"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "phpDocumentor\\Reflection\\": [
- "src/"
- ]
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Mike van Riel",
- "email": "me@mikevanriel.com"
- }
- ],
- "time": "2017-07-14T14:27:02+00:00"
- },
- {
- "name": "phpro/grumphp",
- "version": "v0.14.1",
- "source": {
- "type": "git",
- "url": "https://github.com/phpro/grumphp.git",
- "reference": "73288e15bfd7d5aba8e73a19a45f5eab411c3595"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpro/grumphp/zipball/73288e15bfd7d5aba8e73a19a45f5eab411c3595",
- "reference": "73288e15bfd7d5aba8e73a19a45f5eab411c3595",
- "shasum": ""
- },
- "require": {
- "composer-plugin-api": "~1.0",
- "composer/composer": "^1.0",
- "doctrine/collections": "~1.2",
- "gitonomy/gitlib": "^1.0.3",
- "monolog/monolog": "~1.16",
- "php": ">=5.6.0",
- "seld/jsonlint": "~1.1",
- "symfony/config": "~2.7|~3.0|~4.0",
- "symfony/console": "~2.7|~3.0|~4.0",
- "symfony/dependency-injection": "~2.7|~3.0|~4.0",
- "symfony/event-dispatcher": "~2.7|~3.0|~4.0",
- "symfony/filesystem": "~2.7|~3.0|~4.0",
- "symfony/finder": "~2.7|~3.0|~4.0",
- "symfony/options-resolver": "~2.7|~3.0|~4.0",
- "symfony/process": "~2.7|~3.0|~4.0",
- "symfony/yaml": "~2.7|~3.0|~4.0"
- },
- "require-dev": {
- "friendsofphp/php-cs-fixer": "~1|~2",
- "jakub-onderka/php-parallel-lint": "^0.9.2",
- "nikic/php-parser": "~2.1",
- "phpspec/phpspec": "^3.2.2",
- "phpspec/prophecy": "^1.6.2",
- "phpunit/phpunit": "^5.7.27|^6.4.4",
- "sebastian/comparator": "^1.2.4",
- "sensiolabs/security-checker": "^4.0",
- "squizlabs/php_codesniffer": "~2.4"
- },
- "suggest": {
- "atoum/atoum": "Lets GrumPHP run your unit tests.",
- "behat/behat": "Lets GrumPHP validate your project features.",
- "codeception/codeception": "Lets GrumPHP run your project's full stack tests",
- "codegyre/robo": "Lets GrumPHP run your automated PHP tasks.",
- "doctrine/orm": "Lets GrumPHP validate your Doctrine mapping files.",
- "etsy/phan": "Lets GrumPHP unleash a static analyzer on your code",
- "friendsofphp/php-cs-fixer": "Lets GrumPHP automatically fix your codestyle.",
- "infection/infection": "Lets GrumPHP evaluate the quality your unit tests",
- "jakub-onderka/php-parallel-lint": "Lets GrumPHP quickly lint your entire code base.",
- "maglnet/composer-require-checker": "Lets GrumPHP analyze composer dependencies.",
- "malukenho/kawaii-gherkin": "Lets GrumPHP lint your Gherkin files.",
- "nikic/php-parser": "Lets GrumPHP run static analyses through your PHP files.",
- "phing/phing": "Lets GrumPHP run your automated PHP tasks.",
- "phpmd/phpmd": "Lets GrumPHP sort out the mess in your code",
- "phpspec/phpspec": "Lets GrumPHP spec your code.",
- "phpstan/phpstan": "Lets GrumPHP discover bugs in your code without running it.",
- "phpunit/phpunit": "Lets GrumPHP run your unit tests.",
- "povils/phpmnd": "Lets GrumPHP help you detect magic numbers in PHP code.",
- "roave/security-advisories": "Lets GrumPHP be sure that there are no known security issues.",
- "sebastian/phpcpd": "Lets GrumPHP find duplicated code.",
- "sensiolabs/security-checker": "Lets GrumPHP be sure that there are no known security issues.",
- "squizlabs/php_codesniffer": "Lets GrumPHP sniff on your code.",
- "sstalle/php7cc": "Lets GrumPHP check PHP 5.3 - 5.6 code compatibility with PHP 7."
- },
- "bin": [
- "bin/grumphp"
- ],
- "type": "composer-plugin",
- "extra": {
- "class": "GrumPHP\\Composer\\GrumPHPPlugin"
- },
- "autoload": {
- "psr-4": {
- "GrumPHP\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Toon Verwerft",
- "email": "toon.verwerft@phpro.be"
- },
- {
- "name": "Community",
- "homepage": "https://github.com/phpro/grumphp/graphs/contributors"
- }
- ],
- "description": "A composer plugin that enables source code quality checks.",
- "time": "2018-05-25T12:19:26+00:00"
- },
- {
- "name": "phpspec/prophecy",
- "version": "1.7.6",
- "source": {
- "type": "git",
- "url": "https://github.com/phpspec/prophecy.git",
- "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712",
- "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712",
- "shasum": ""
- },
- "require": {
- "doctrine/instantiator": "^1.0.2",
- "php": "^5.3|^7.0",
- "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
- "sebastian/comparator": "^1.1|^2.0|^3.0",
- "sebastian/recursion-context": "^1.0|^2.0|^3.0"
- },
- "require-dev": {
- "phpspec/phpspec": "^2.5|^3.2",
- "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.7.x-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Prophecy\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Konstantin Kudryashov",
- "email": "ever.zet@gmail.com",
- "homepage": "http://everzet.com"
- },
- {
- "name": "Marcello Duarte",
- "email": "marcello.duarte@gmail.com"
- }
- ],
- "description": "Highly opinionated mocking framework for PHP 5.3+",
- "homepage": "https://github.com/phpspec/prophecy",
- "keywords": [
- "Double",
- "Dummy",
- "fake",
- "mock",
- "spy",
- "stub"
- ],
- "time": "2018-04-18T13:57:24+00:00"
- },
- {
- "name": "phpunit/php-code-coverage",
- "version": "5.3.2",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "c89677919c5dd6d3b3852f230a663118762218ac"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac",
- "reference": "c89677919c5dd6d3b3852f230a663118762218ac",
- "shasum": ""
- },
- "require": {
- "ext-dom": "*",
- "ext-xmlwriter": "*",
- "php": "^7.0",
- "phpunit/php-file-iterator": "^1.4.2",
- "phpunit/php-text-template": "^1.2.1",
- "phpunit/php-token-stream": "^2.0.1",
- "sebastian/code-unit-reverse-lookup": "^1.0.1",
- "sebastian/environment": "^3.0",
- "sebastian/version": "^2.0.1",
- "theseer/tokenizer": "^1.1"
- },
- "require-dev": {
- "phpunit/phpunit": "^6.0"
- },
- "suggest": {
- "ext-xdebug": "^2.5.5"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "5.3.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
- }
- ],
- "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
- "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
- "keywords": [
- "coverage",
- "testing",
- "xunit"
- ],
- "time": "2018-04-06T15:36:58+00:00"
- },
- {
- "name": "phpunit/php-file-iterator",
- "version": "1.4.5",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
- "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4",
- "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.3"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.4.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sb@sebastian-bergmann.de",
- "role": "lead"
- }
- ],
- "description": "FilterIterator implementation that filters files based on a list of suffixes.",
- "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
- "keywords": [
- "filesystem",
- "iterator"
- ],
- "time": "2017-11-27T13:52:08+00:00"
- },
- {
- "name": "phpunit/php-text-template",
- "version": "1.2.1",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/php-text-template.git",
- "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
- "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.3"
- },
- "type": "library",
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
- }
- ],
- "description": "Simple template engine.",
- "homepage": "https://github.com/sebastianbergmann/php-text-template/",
- "keywords": [
- "template"
- ],
- "time": "2015-06-21T13:50:34+00:00"
- },
- {
- "name": "phpunit/php-timer",
- "version": "1.0.9",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/php-timer.git",
- "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
- "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
- "shasum": ""
- },
- "require": {
- "php": "^5.3.3 || ^7.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sb@sebastian-bergmann.de",
- "role": "lead"
- }
- ],
- "description": "Utility class for timing",
- "homepage": "https://github.com/sebastianbergmann/php-timer/",
- "keywords": [
- "timer"
- ],
- "time": "2017-02-26T11:10:40+00:00"
- },
- {
- "name": "phpunit/php-token-stream",
- "version": "2.0.2",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/php-token-stream.git",
- "reference": "791198a2c6254db10131eecfe8c06670700904db"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db",
- "reference": "791198a2c6254db10131eecfe8c06670700904db",
- "shasum": ""
- },
- "require": {
- "ext-tokenizer": "*",
- "php": "^7.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^6.2.4"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.0-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- }
- ],
- "description": "Wrapper around PHP's tokenizer extension.",
- "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
- "keywords": [
- "tokenizer"
- ],
- "time": "2017-11-27T05:48:46+00:00"
- },
- {
- "name": "phpunit/phpunit",
- "version": "6.5.9",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "093ca5508174cd8ab8efe44fd1dde447adfdec8f"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/093ca5508174cd8ab8efe44fd1dde447adfdec8f",
- "reference": "093ca5508174cd8ab8efe44fd1dde447adfdec8f",
- "shasum": ""
- },
- "require": {
- "ext-dom": "*",
- "ext-json": "*",
- "ext-libxml": "*",
- "ext-mbstring": "*",
- "ext-xml": "*",
- "myclabs/deep-copy": "^1.6.1",
- "phar-io/manifest": "^1.0.1",
- "phar-io/version": "^1.0",
- "php": "^7.0",
- "phpspec/prophecy": "^1.7",
- "phpunit/php-code-coverage": "^5.3",
- "phpunit/php-file-iterator": "^1.4.3",
- "phpunit/php-text-template": "^1.2.1",
- "phpunit/php-timer": "^1.0.9",
- "phpunit/phpunit-mock-objects": "^5.0.5",
- "sebastian/comparator": "^2.1",
- "sebastian/diff": "^2.0",
- "sebastian/environment": "^3.1",
- "sebastian/exporter": "^3.1",
- "sebastian/global-state": "^2.0",
- "sebastian/object-enumerator": "^3.0.3",
- "sebastian/resource-operations": "^1.0",
- "sebastian/version": "^2.0.1"
- },
- "conflict": {
- "phpdocumentor/reflection-docblock": "3.0.2",
- "phpunit/dbunit": "<3.0"
- },
- "require-dev": {
- "ext-pdo": "*"
- },
- "suggest": {
- "ext-xdebug": "*",
- "phpunit/php-invoker": "^1.1"
- },
- "bin": [
- "phpunit"
- ],
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "6.5.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
- }
- ],
- "description": "The PHP Unit Testing framework.",
- "homepage": "https://phpunit.de/",
- "keywords": [
- "phpunit",
- "testing",
- "xunit"
- ],
- "time": "2018-07-03T06:40:40+00:00"
- },
- {
- "name": "phpunit/phpunit-mock-objects",
- "version": "5.0.7",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
- "reference": "3eaf040f20154d27d6da59ca2c6e28ac8fd56dce"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/3eaf040f20154d27d6da59ca2c6e28ac8fd56dce",
- "reference": "3eaf040f20154d27d6da59ca2c6e28ac8fd56dce",
- "shasum": ""
- },
- "require": {
- "doctrine/instantiator": "^1.0.5",
- "php": "^7.0",
- "phpunit/php-text-template": "^1.2.1",
- "sebastian/exporter": "^3.1"
- },
- "conflict": {
- "phpunit/phpunit": "<6.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^6.5"
- },
- "suggest": {
- "ext-soap": "*"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "5.0.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
- }
- ],
- "description": "Mock Object library for PHPUnit",
- "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
- "keywords": [
- "mock",
- "xunit"
- ],
- "time": "2018-05-29T13:50:43+00:00"
- },
- {
- "name": "psr/container",
- "version": "1.0.0",
- "source": {
- "type": "git",
- "url": "https://github.com/php-fig/container.git",
- "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
- "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Psr\\Container\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
- }
- ],
- "description": "Common Container Interface (PHP FIG PSR-11)",
- "homepage": "https://github.com/php-fig/container",
- "keywords": [
- "PSR-11",
- "container",
- "container-interface",
- "container-interop",
- "psr"
- ],
- "time": "2017-02-14T16:28:37+00:00"
- },
- {
- "name": "psr/log",
- "version": "1.0.2",
- "source": {
- "type": "git",
- "url": "https://github.com/php-fig/log.git",
- "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
- "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Psr\\Log\\": "Psr/Log/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
- }
- ],
- "description": "Common interface for logging libraries",
- "homepage": "https://github.com/php-fig/log",
- "keywords": [
- "log",
- "psr",
- "psr-3"
- ],
- "time": "2016-10-10T12:19:37+00:00"
- },
- {
- "name": "sebastian/code-unit-reverse-lookup",
- "version": "1.0.1",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
- "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
- "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
- "shasum": ""
- },
- "require": {
- "php": "^5.6 || ^7.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^5.7 || ^6.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- }
- ],
- "description": "Looks up which function or method a line of code belongs to",
- "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
- "time": "2017-03-04T06:30:41+00:00"
- },
- {
- "name": "sebastian/comparator",
- "version": "2.1.3",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/comparator.git",
- "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9",
- "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9",
- "shasum": ""
- },
- "require": {
- "php": "^7.0",
- "sebastian/diff": "^2.0 || ^3.0",
- "sebastian/exporter": "^3.1"
- },
- "require-dev": {
- "phpunit/phpunit": "^6.4"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.1.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Jeff Welch",
- "email": "whatthejeff@gmail.com"
- },
- {
- "name": "Volker Dusch",
- "email": "github@wallbash.com"
- },
- {
- "name": "Bernhard Schussek",
- "email": "bschussek@2bepublished.at"
- },
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- }
- ],
- "description": "Provides the functionality to compare PHP values for equality",
- "homepage": "https://github.com/sebastianbergmann/comparator",
- "keywords": [
- "comparator",
- "compare",
- "equality"
- ],
- "time": "2018-02-01T13:46:46+00:00"
- },
- {
- "name": "sebastian/diff",
- "version": "2.0.1",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/diff.git",
- "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd",
- "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd",
- "shasum": ""
- },
- "require": {
- "php": "^7.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^6.2"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.0-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Kore Nordmann",
- "email": "mail@kore-nordmann.de"
- },
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- }
- ],
- "description": "Diff implementation",
- "homepage": "https://github.com/sebastianbergmann/diff",
- "keywords": [
- "diff"
- ],
- "time": "2017-08-03T08:09:46+00:00"
- },
- {
- "name": "sebastian/environment",
- "version": "3.1.0",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/environment.git",
- "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5",
- "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5",
- "shasum": ""
- },
- "require": {
- "php": "^7.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^6.1"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.1.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- }
- ],
- "description": "Provides functionality to handle HHVM/PHP environments",
- "homepage": "http://www.github.com/sebastianbergmann/environment",
- "keywords": [
- "Xdebug",
- "environment",
- "hhvm"
- ],
- "time": "2017-07-01T08:51:00+00:00"
- },
- {
- "name": "sebastian/exporter",
- "version": "3.1.0",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/exporter.git",
- "reference": "234199f4528de6d12aaa58b612e98f7d36adb937"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937",
- "reference": "234199f4528de6d12aaa58b612e98f7d36adb937",
- "shasum": ""
- },
- "require": {
- "php": "^7.0",
- "sebastian/recursion-context": "^3.0"
- },
- "require-dev": {
- "ext-mbstring": "*",
- "phpunit/phpunit": "^6.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.1.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Jeff Welch",
- "email": "whatthejeff@gmail.com"
- },
- {
- "name": "Volker Dusch",
- "email": "github@wallbash.com"
- },
- {
- "name": "Bernhard Schussek",
- "email": "bschussek@2bepublished.at"
- },
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- },
- {
- "name": "Adam Harvey",
- "email": "aharvey@php.net"
- }
- ],
- "description": "Provides the functionality to export PHP variables for visualization",
- "homepage": "http://www.github.com/sebastianbergmann/exporter",
- "keywords": [
- "export",
- "exporter"
- ],
- "time": "2017-04-03T13:19:02+00:00"
- },
- {
- "name": "sebastian/global-state",
- "version": "2.0.0",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/global-state.git",
- "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
- "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
- "shasum": ""
- },
- "require": {
- "php": "^7.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^6.0"
- },
- "suggest": {
- "ext-uopz": "*"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.0-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- }
- ],
- "description": "Snapshotting of global state",
- "homepage": "http://www.github.com/sebastianbergmann/global-state",
- "keywords": [
- "global state"
- ],
- "time": "2017-04-27T15:39:26+00:00"
- },
- {
- "name": "sebastian/object-enumerator",
- "version": "3.0.3",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/object-enumerator.git",
- "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5",
- "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5",
- "shasum": ""
- },
- "require": {
- "php": "^7.0",
- "sebastian/object-reflector": "^1.1.1",
- "sebastian/recursion-context": "^3.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^6.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.0.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- }
- ],
- "description": "Traverses array structures and object graphs to enumerate all referenced objects",
- "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
- "time": "2017-08-03T12:35:26+00:00"
- },
- {
- "name": "sebastian/object-reflector",
- "version": "1.1.1",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/object-reflector.git",
- "reference": "773f97c67f28de00d397be301821b06708fca0be"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be",
- "reference": "773f97c67f28de00d397be301821b06708fca0be",
- "shasum": ""
- },
- "require": {
- "php": "^7.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^6.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.1-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- }
- ],
- "description": "Allows reflection of object attributes, including inherited and non-public ones",
- "homepage": "https://github.com/sebastianbergmann/object-reflector/",
- "time": "2017-03-29T09:07:27+00:00"
- },
- {
- "name": "sebastian/recursion-context",
- "version": "3.0.0",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/recursion-context.git",
- "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8",
- "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8",
- "shasum": ""
- },
- "require": {
- "php": "^7.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^6.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.0.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Jeff Welch",
- "email": "whatthejeff@gmail.com"
- },
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- },
- {
- "name": "Adam Harvey",
- "email": "aharvey@php.net"
- }
- ],
- "description": "Provides functionality to recursively process PHP variables",
- "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
- "time": "2017-03-03T06:23:57+00:00"
- },
- {
- "name": "sebastian/resource-operations",
- "version": "1.0.0",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/resource-operations.git",
- "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
- "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
- "shasum": ""
- },
- "require": {
- "php": ">=5.6.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- }
- ],
- "description": "Provides a list of PHP built-in functions that operate on resources",
- "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
- "time": "2015-07-28T20:34:47+00:00"
- },
- {
- "name": "sebastian/version",
- "version": "2.0.1",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/version.git",
- "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
- "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
- "shasum": ""
- },
- "require": {
- "php": ">=5.6"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.0.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
- }
- ],
- "description": "Library that helps with managing the version number of Git-hosted PHP projects",
- "homepage": "https://github.com/sebastianbergmann/version",
- "time": "2016-10-03T07:35:21+00:00"
- },
- {
- "name": "seld/cli-prompt",
- "version": "1.0.3",
- "source": {
- "type": "git",
- "url": "https://github.com/Seldaek/cli-prompt.git",
- "reference": "a19a7376a4689d4d94cab66ab4f3c816019ba8dd"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/Seldaek/cli-prompt/zipball/a19a7376a4689d4d94cab66ab4f3c816019ba8dd",
- "reference": "a19a7376a4689d4d94cab66ab4f3c816019ba8dd",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Seld\\CliPrompt\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Jordi Boggiano",
- "email": "j.boggiano@seld.be"
- }
- ],
- "description": "Allows you to prompt for user input on the command line, and optionally hide the characters they type",
- "keywords": [
- "cli",
- "console",
- "hidden",
- "input",
- "prompt"
- ],
- "time": "2017-03-18T11:32:45+00:00"
- },
- {
- "name": "seld/jsonlint",
- "version": "1.7.1",
- "source": {
- "type": "git",
- "url": "https://github.com/Seldaek/jsonlint.git",
- "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/d15f59a67ff805a44c50ea0516d2341740f81a38",
- "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38",
- "shasum": ""
- },
- "require": {
- "php": "^5.3 || ^7.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
- },
- "bin": [
- "bin/jsonlint"
- ],
- "type": "library",
- "autoload": {
- "psr-4": {
- "Seld\\JsonLint\\": "src/Seld/JsonLint/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Jordi Boggiano",
- "email": "j.boggiano@seld.be",
- "homepage": "http://seld.be"
- }
- ],
- "description": "JSON Linter",
- "keywords": [
- "json",
- "linter",
- "parser",
- "validator"
- ],
- "time": "2018-01-24T12:46:19+00:00"
- },
- {
- "name": "seld/phar-utils",
- "version": "1.0.1",
- "source": {
- "type": "git",
- "url": "https://github.com/Seldaek/phar-utils.git",
- "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/7009b5139491975ef6486545a39f3e6dad5ac30a",
- "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Seld\\PharUtils\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Jordi Boggiano",
- "email": "j.boggiano@seld.be"
- }
- ],
- "description": "PHAR file format utilities, for when PHP phars you up",
- "keywords": [
- "phra"
- ],
- "time": "2015-10-13T18:44:15+00:00"
- },
- {
- "name": "squizlabs/php_codesniffer",
- "version": "3.3.0",
- "source": {
- "type": "git",
- "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
- "reference": "d86873af43b4aa9d1f39a3601cc0cfcf02b25266"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d86873af43b4aa9d1f39a3601cc0cfcf02b25266",
- "reference": "d86873af43b4aa9d1f39a3601cc0cfcf02b25266",
- "shasum": ""
- },
- "require": {
- "ext-simplexml": "*",
- "ext-tokenizer": "*",
- "ext-xmlwriter": "*",
- "php": ">=5.4.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
- },
- "bin": [
- "bin/phpcs",
- "bin/phpcbf"
- ],
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.x-dev"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Greg Sherwood",
- "role": "lead"
- }
- ],
- "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
- "homepage": "http://www.squizlabs.com/php-codesniffer",
- "keywords": [
- "phpcs",
- "standards"
- ],
- "time": "2018-06-06T23:58:19+00:00"
- },
- {
- "name": "symfony/config",
- "version": "v3.4.12",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/config.git",
- "reference": "1fffdeb349ff36a25184e5564c25289b1dbfc402"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/config/zipball/1fffdeb349ff36a25184e5564c25289b1dbfc402",
- "reference": "1fffdeb349ff36a25184e5564c25289b1dbfc402",
- "shasum": ""
- },
- "require": {
- "php": "^5.5.9|>=7.0.8",
- "symfony/filesystem": "~2.8|~3.0|~4.0",
- "symfony/polyfill-ctype": "~1.8"
- },
- "conflict": {
- "symfony/dependency-injection": "<3.3",
- "symfony/finder": "<3.3"
- },
- "require-dev": {
- "symfony/dependency-injection": "~3.3|~4.0",
- "symfony/event-dispatcher": "~3.3|~4.0",
- "symfony/finder": "~3.3|~4.0",
- "symfony/yaml": "~3.0|~4.0"
- },
- "suggest": {
- "symfony/yaml": "To use the yaml reference dumper"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.4-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\Config\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony Config Component",
- "homepage": "https://symfony.com",
- "time": "2018-06-19T14:02:58+00:00"
- },
- {
- "name": "symfony/console",
- "version": "v3.4.12",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/console.git",
- "reference": "1b97071a26d028c9bd4588264e101e14f6e7cd00"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/1b97071a26d028c9bd4588264e101e14f6e7cd00",
- "reference": "1b97071a26d028c9bd4588264e101e14f6e7cd00",
- "shasum": ""
- },
- "require": {
- "php": "^5.5.9|>=7.0.8",
- "symfony/debug": "~2.8|~3.0|~4.0",
- "symfony/polyfill-mbstring": "~1.0"
- },
- "conflict": {
- "symfony/dependency-injection": "<3.4",
- "symfony/process": "<3.3"
- },
- "require-dev": {
- "psr/log": "~1.0",
- "symfony/config": "~3.3|~4.0",
- "symfony/dependency-injection": "~3.4|~4.0",
- "symfony/event-dispatcher": "~2.8|~3.0|~4.0",
- "symfony/lock": "~3.4|~4.0",
- "symfony/process": "~3.3|~4.0"
- },
- "suggest": {
- "psr/log-implementation": "For using the console logger",
- "symfony/event-dispatcher": "",
- "symfony/lock": "",
- "symfony/process": ""
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.4-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\Console\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony Console Component",
- "homepage": "https://symfony.com",
- "time": "2018-05-23T05:02:55+00:00"
- },
- {
- "name": "symfony/debug",
- "version": "v3.4.12",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/debug.git",
- "reference": "47e6788c5b151cf0cfdf3329116bf33800632d75"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/debug/zipball/47e6788c5b151cf0cfdf3329116bf33800632d75",
- "reference": "47e6788c5b151cf0cfdf3329116bf33800632d75",
- "shasum": ""
- },
- "require": {
- "php": "^5.5.9|>=7.0.8",
- "psr/log": "~1.0"
- },
- "conflict": {
- "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
- },
- "require-dev": {
- "symfony/http-kernel": "~2.8|~3.0|~4.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.4-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\Debug\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony Debug Component",
- "homepage": "https://symfony.com",
- "time": "2018-06-25T11:10:40+00:00"
- },
- {
- "name": "symfony/dependency-injection",
- "version": "v3.4.12",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/dependency-injection.git",
- "reference": "a0be80e3f8c11aca506e250c00bb100c04c35d10"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/a0be80e3f8c11aca506e250c00bb100c04c35d10",
- "reference": "a0be80e3f8c11aca506e250c00bb100c04c35d10",
- "shasum": ""
- },
- "require": {
- "php": "^5.5.9|>=7.0.8",
- "psr/container": "^1.0"
- },
- "conflict": {
- "symfony/config": "<3.3.7",
- "symfony/finder": "<3.3",
- "symfony/proxy-manager-bridge": "<3.4",
- "symfony/yaml": "<3.4"
- },
- "provide": {
- "psr/container-implementation": "1.0"
- },
- "require-dev": {
- "symfony/config": "~3.3|~4.0",
- "symfony/expression-language": "~2.8|~3.0|~4.0",
- "symfony/yaml": "~3.4|~4.0"
- },
- "suggest": {
- "symfony/config": "",
- "symfony/expression-language": "For using expressions in service container configuration",
- "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required",
- "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them",
- "symfony/yaml": ""
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.4-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\DependencyInjection\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony DependencyInjection Component",
- "homepage": "https://symfony.com",
- "time": "2018-06-25T08:36:56+00:00"
- },
- {
- "name": "symfony/event-dispatcher",
- "version": "v3.4.12",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "fdd5abcebd1061ec647089c6c41a07ed60af09f8"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/fdd5abcebd1061ec647089c6c41a07ed60af09f8",
- "reference": "fdd5abcebd1061ec647089c6c41a07ed60af09f8",
- "shasum": ""
- },
- "require": {
- "php": "^5.5.9|>=7.0.8"
- },
- "conflict": {
- "symfony/dependency-injection": "<3.3"
- },
- "require-dev": {
- "psr/log": "~1.0",
- "symfony/config": "~2.8|~3.0|~4.0",
- "symfony/dependency-injection": "~3.3|~4.0",
- "symfony/expression-language": "~2.8|~3.0|~4.0",
- "symfony/stopwatch": "~2.8|~3.0|~4.0"
- },
- "suggest": {
- "symfony/dependency-injection": "",
- "symfony/http-kernel": ""
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.4-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\EventDispatcher\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony EventDispatcher Component",
- "homepage": "https://symfony.com",
- "time": "2018-04-06T07:35:25+00:00"
- },
- {
- "name": "symfony/filesystem",
- "version": "v3.4.12",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/filesystem.git",
- "reference": "8a721a5f2553c6c3482b1c5b22ed60fe94dd63ed"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/8a721a5f2553c6c3482b1c5b22ed60fe94dd63ed",
- "reference": "8a721a5f2553c6c3482b1c5b22ed60fe94dd63ed",
- "shasum": ""
- },
- "require": {
- "php": "^5.5.9|>=7.0.8",
- "symfony/polyfill-ctype": "~1.8"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.4-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\Filesystem\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony Filesystem Component",
- "homepage": "https://symfony.com",
- "time": "2018-06-21T11:10:19+00:00"
- },
- {
- "name": "symfony/finder",
- "version": "v3.4.12",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/finder.git",
- "reference": "3a8c3de91d2b2c68cd2d665cf9d00f7ef9eaa394"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/3a8c3de91d2b2c68cd2d665cf9d00f7ef9eaa394",
- "reference": "3a8c3de91d2b2c68cd2d665cf9d00f7ef9eaa394",
- "shasum": ""
- },
- "require": {
- "php": "^5.5.9|>=7.0.8"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.4-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\Finder\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony Finder Component",
- "homepage": "https://symfony.com",
- "time": "2018-06-19T20:52:10+00:00"
- },
- {
- "name": "symfony/options-resolver",
- "version": "v3.4.12",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/options-resolver.git",
- "reference": "cc5e98ed91688a22a7162a8800096356f9076b1d"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/options-resolver/zipball/cc5e98ed91688a22a7162a8800096356f9076b1d",
- "reference": "cc5e98ed91688a22a7162a8800096356f9076b1d",
- "shasum": ""
- },
- "require": {
- "php": "^5.5.9|>=7.0.8"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.4-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\OptionsResolver\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony OptionsResolver Component",
- "homepage": "https://symfony.com",
- "keywords": [
- "config",
- "configuration",
- "options"
- ],
- "time": "2018-05-30T04:26:49+00:00"
- },
- {
- "name": "symfony/polyfill-ctype",
- "version": "v1.8.0",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/polyfill-ctype.git",
- "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/7cc359f1b7b80fc25ed7796be7d96adc9b354bae",
- "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.3"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.8-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Polyfill\\Ctype\\": ""
- },
- "files": [
- "bootstrap.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- },
- {
- "name": "Gert de Pagter",
- "email": "BackEndTea@gmail.com"
- }
- ],
- "description": "Symfony polyfill for ctype functions",
- "homepage": "https://symfony.com",
- "keywords": [
- "compatibility",
- "ctype",
- "polyfill",
- "portable"
- ],
- "time": "2018-04-30T19:57:29+00:00"
- },
- {
- "name": "symfony/polyfill-mbstring",
- "version": "v1.8.0",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "3296adf6a6454a050679cde90f95350ad604b171"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171",
- "reference": "3296adf6a6454a050679cde90f95350ad604b171",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.3"
- },
- "suggest": {
- "ext-mbstring": "For best performance"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.8-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Polyfill\\Mbstring\\": ""
- },
- "files": [
- "bootstrap.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony polyfill for the Mbstring extension",
- "homepage": "https://symfony.com",
- "keywords": [
- "compatibility",
- "mbstring",
- "polyfill",
- "portable",
- "shim"
- ],
- "time": "2018-04-26T10:06:28+00:00"
- },
- {
- "name": "symfony/process",
- "version": "v3.4.12",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/process.git",
- "reference": "acc5a37c706ace827962851b69705b24e71ca17c"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/acc5a37c706ace827962851b69705b24e71ca17c",
- "reference": "acc5a37c706ace827962851b69705b24e71ca17c",
- "shasum": ""
- },
- "require": {
- "php": "^5.5.9|>=7.0.8"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.4-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\Process\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony Process Component",
- "homepage": "https://symfony.com",
- "time": "2018-05-30T04:24:30+00:00"
- },
- {
- "name": "symfony/yaml",
- "version": "v3.4.12",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/yaml.git",
- "reference": "c5010cc1692ce1fa328b1fb666961eb3d4a85bb0"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/c5010cc1692ce1fa328b1fb666961eb3d4a85bb0",
- "reference": "c5010cc1692ce1fa328b1fb666961eb3d4a85bb0",
- "shasum": ""
- },
- "require": {
- "php": "^5.5.9|>=7.0.8",
- "symfony/polyfill-ctype": "~1.8"
- },
- "conflict": {
- "symfony/console": "<3.4"
- },
- "require-dev": {
- "symfony/console": "~3.4|~4.0"
- },
- "suggest": {
- "symfony/console": "For validating YAML files using the lint command"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.4-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\Yaml\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony Yaml Component",
- "homepage": "https://symfony.com",
- "time": "2018-05-03T23:18:14+00:00"
- },
- {
- "name": "theseer/tokenizer",
- "version": "1.1.0",
- "source": {
- "type": "git",
- "url": "https://github.com/theseer/tokenizer.git",
- "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b",
- "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b",
- "shasum": ""
- },
- "require": {
- "ext-dom": "*",
- "ext-tokenizer": "*",
- "ext-xmlwriter": "*",
- "php": "^7.0"
- },
- "type": "library",
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Arne Blankerts",
- "email": "arne@blankerts.de",
- "role": "Developer"
- }
- ],
- "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
- "time": "2017-04-07T12:08:54+00:00"
- },
- {
- "name": "webmozart/assert",
- "version": "1.3.0",
- "source": {
- "type": "git",
- "url": "https://github.com/webmozart/assert.git",
- "reference": "0df1908962e7a3071564e857d86874dad1ef204a"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a",
- "reference": "0df1908962e7a3071564e857d86874dad1ef204a",
- "shasum": ""
- },
- "require": {
- "php": "^5.3.3 || ^7.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^4.6",
- "sebastian/version": "^1.0.1"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.3-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Webmozart\\Assert\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Bernhard Schussek",
- "email": "bschussek@gmail.com"
- }
- ],
- "description": "Assertions to validate method input/output with nice error messages.",
- "keywords": [
- "assert",
- "check",
- "validate"
- ],
- "time": "2018-01-29T19:49:41+00:00"
- }
- ],
- "aliases": [],
- "minimum-stability": "stable",
- "stability-flags": [],
- "prefer-stable": true,
- "prefer-lowest": false,
- "platform": {
- "php": "~7.0"
- },
- "platform-dev": []
-}
diff --git a/grumphp.yml b/grumphp.yml
deleted file mode 100644
index bf81fa5..0000000
--- a/grumphp.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-parameters:
- git_dir: .
- bin_dir: vendor/bin
- tasks:
- phpunit:
- config_file: ~
- testsuite: ~
- group: []
- always_execute: false
- phpcs:
- standard: PSR2
- warning_severity: ~
- ignore_patterns:
- - tests/
- triggered_by: [php]
diff --git a/phpstan.neon b/phpstan.neon
new file mode 100644
index 0000000..d3be392
--- /dev/null
+++ b/phpstan.neon
@@ -0,0 +1,7 @@
+parameters:
+ level: 7
+ paths:
+ - src
+ - tests
+ parallel:
+ maximumNumberOfProcesses: 1
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..1fc2c3f
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,8 @@
+
+
+
+
+ tests
+
+
+
\ No newline at end of file
diff --git a/rector.php b/rector.php
new file mode 100644
index 0000000..ae96a82
--- /dev/null
+++ b/rector.php
@@ -0,0 +1,21 @@
+withPaths([
+ __DIR__ . '/src',
+ __DIR__ . '/tests',
+ ])
+ ->withPhpVersion(PhpVersion::PHP_83)
+ ->withSets([
+ SetList::CODE_QUALITY,
+ SetList::TYPE_DECLARATION,
+ SetList::DEAD_CODE,
+ LevelSetList::UP_TO_PHP_83,
+ ]);
diff --git a/src/Client/Config.php b/src/Client/Config.php
new file mode 100644
index 0000000..bef700d
--- /dev/null
+++ b/src/Client/Config.php
@@ -0,0 +1,14 @@
+baseUrl, '/') . '/' . rawurlencode($ip);
+ }
+
+ /** @param list $ips */
+ public function bulk(array $ips): string
+ {
+ return rtrim($this->baseUrl, '/') . '/' . implode(',', array_map('rawurlencode', $ips));
+ }
+
+ public function requester(): string
+ {
+ return rtrim($this->baseUrl, '/') . '/check';
+ }
+}
diff --git a/src/Client/IpstackClient.php b/src/Client/IpstackClient.php
new file mode 100644
index 0000000..77dc15d
--- /dev/null
+++ b/src/Client/IpstackClient.php
@@ -0,0 +1,123 @@
+transport->get(
+ $this->endpoint->standard($ip),
+ $this->buildQuery($options)
+ );
+ $this->throwIfApiError($data);
+ return $this->mapper->map($data);
+ }
+
+ public function lookupRequester(?Options $options = null): IpstackResult
+ {
+ $data = $this->transport->get(
+ $this->endpoint->requester(),
+ $this->buildQuery($options)
+ );
+ $this->throwIfApiError($data);
+ return $this->mapper->map($data);
+ }
+
+ /**
+ * @param list $ips
+ */
+ public function lookupBulk(array $ips, ?Options $options = null): IpstackCollection
+ {
+ if ($ips === []) {
+ return new IpstackCollection([]);
+ }
+
+ if (count($ips) > 50) {
+ throw new IpstackException('Bulk lookup supports up to 50 IPs per request.');
+ }
+
+ /** @var array $data */
+ $data = $this->transport->get(
+ $this->endpoint->bulk($ips),
+ $this->buildQuery($options)
+ );
+
+ // If API error wrapper returned (assoc), throw
+ if (!array_is_list($data)) {
+ /** @var array $assoc */
+ $assoc = $data;
+ $this->throwIfApiError($assoc);
+ return new IpstackCollection([$this->mapper->map($assoc)]);
+ }
+
+ $out = [];
+ foreach ($data as $row) {
+ if (!is_array($row)) {
+ continue;
+ }
+
+ /** @var array $rowAssoc */
+ $rowAssoc = $row;
+
+ $this->throwIfApiError($rowAssoc);
+ $out[] = $this->mapper->map($rowAssoc);
+ }
+
+ return new IpstackCollection($out);
+ }
+
+ /**
+ * @return array
+ */
+ private function buildQuery(?Options $options): array
+ {
+ return array_merge(
+ ['access_key' => $this->config->accessKey],
+ $options?->toQuery() ?? []
+ );
+ }
+
+ /** @param array $data */
+ private function throwIfApiError(array $data): void
+ {
+ if (!isset($data['error']) || !is_array($data['error'])) return;
+
+ $code = $data['error']['code'] ?? null;
+ $info = (string)($data['error']['info'] ?? 'Unknown API error');
+
+ // Map known ipstack error codes to domain exceptions
+ switch ((int)$code) {
+ case 104:
+ throw new RateLimitException($code, $info);
+ case 301:
+ throw new InvalidFieldsException($code, $info);
+ case 302:
+ throw new TooManyIpsException($code, $info);
+ case 303:
+ throw new BatchNotSupportedException($code, $info);
+ default:
+ throw new ApiErrorException($code, $info);
+ }
+ }
+}
diff --git a/src/Client/Options.php b/src/Client/Options.php
new file mode 100644
index 0000000..e8900f1
--- /dev/null
+++ b/src/Client/Options.php
@@ -0,0 +1,47 @@
+ */
+ private array $query = [];
+
+ public static function create(): self
+ {
+ return new self();
+ }
+
+ /** @param list|string $fields */
+ public function fields(array|string $fields): self
+ {
+ $this->query['fields'] = is_array($fields) ? implode(',', $fields) : $fields;
+ return $this;
+ }
+
+ public function language(string $lang): self
+ {
+ $this->query['language'] = $lang;
+ return $this;
+ }
+
+ public function security(bool $enabled = true): self
+ {
+ $this->query['security'] = $enabled ? 1 : 0;
+ return $this;
+ }
+
+ public function hostname(bool $enabled = true): self
+ {
+ $this->query['hostname'] = $enabled ? 1 : 0;
+ return $this;
+ }
+
+ /** @return array */
+ public function toQuery(): array
+ {
+ return $this->query;
+ }
+}
diff --git a/src/Exception/ApiErrorException.php b/src/Exception/ApiErrorException.php
new file mode 100644
index 0000000..b73d3bd
--- /dev/null
+++ b/src/Exception/ApiErrorException.php
@@ -0,0 +1,15 @@
+accessKey = $key;
+
+ return $this;
+ }
+
+ public function withBaseUrl(string $baseUrl): self
+ {
+ $this->baseUrl = $baseUrl;
+
+ return $this;
+ }
+
+ public function withTransport(TransportInterface $transport): self
+ {
+ $this->transport = $transport;
+
+ return $this;
+ }
+
+ public function withPsr18(
+ ClientInterface $client,
+ RequestFactoryInterface $requestFactory
+ ): self {
+ $this->transport = new Psr18Transport($client, $requestFactory);
+
+ return $this;
+ }
+
+ /**
+ * If you prefer PSR-18 wiring here, pass an adapter transport you create externally.
+ * (This factory stays PSR-only and dependency-light.)
+ */
+ public function build(): IpstackClient
+ {
+ if ($this->accessKey === null || $this->accessKey === '') {
+ throw new \InvalidArgumentException('Access key is required.');
+ }
+
+ $config = new Config($this->accessKey, $this->baseUrl);
+ $endpoint = new Endpoint($this->baseUrl);
+ $mapper = new IpstackResultMapper();
+
+ if ($this->transport === null) {
+ throw new \InvalidArgumentException(
+ 'No transport configured. Provide a PSR-18 based transport via withTransport().'
+ );
+ }
+
+ return new IpstackClient($config, $endpoint, $this->transport, $mapper);
+ }
+}
diff --git a/src/Http/Request.php b/src/Http/Request.php
deleted file mode 100644
index ff7c611..0000000
--- a/src/Http/Request.php
+++ /dev/null
@@ -1,77 +0,0 @@
-
- */
-class Request
-{
- /**
- * @var string
- */
- protected $param = [];
-
- /**
- * Create new instance.
- *
- * @param $param
- */
- public function __construct($param)
- {
- $this->param = $param;
- }
-
- /**
- * @return mixed
- */
- public function getEndPoint()
- {
- $url = sprintf('https://ipstack.com/ipstack_api.php?ip=%s', $this->param['ip']);
-
- if ($this->param['api_key']) {
- $protocol = $this->param['secure'] ? 'https' : 'http';
-
- $url = sprintf(
- '%s://api.ipstack.com/%s?access_key=%s',
- $protocol,
- $this->param['ip'],
- $this->param['api_key']
- );
- }
-
- return $url;
- }
-
- /**
- * @return \Sujip\Ipstack\Http\Response
- */
- public function make()
- {
- if (empty($this->param['ip'])) {
- $this->throwException('Error: No IP specified', 403);
- }
-
- try {
- $response = file_get_contents($this->getEndPoint());
- } catch (Exception $e) {
- $this->throwException('Forbidden', 403);
- }
-
- return new Response($response);
- }
-
- /**
- * @param $message
- * @param $code
- *
- * @return \Sujip\Ipstack\Exception\Forbidden
- */
- public function throwException($message, $code = 400)
- {
- throw new Forbidden($message, $code);
- }
-}
diff --git a/src/Http/Response.php b/src/Http/Response.php
deleted file mode 100644
index 130605a..0000000
--- a/src/Http/Response.php
+++ /dev/null
@@ -1,30 +0,0 @@
-response = $response;
- }
-
- /**
- * @return mixed
- */
- public function getBody()
- {
- return json_decode($this->response, true);
- }
-}
diff --git a/src/Ipstack.php b/src/Ipstack.php
index 64c65ee..8ece543 100644
--- a/src/Ipstack.php
+++ b/src/Ipstack.php
@@ -1,275 +1,15 @@
ip = $ip;
- $this->api_key = $api_key;
- }
-
- /**
- * Set HTTPS mode on for API call.
- *
- * @return boolean
- */
- public function secure()
- {
- $this->secure = true;
-
- return $this;
- }
-
- /**
- * Make an API call with IP
- *
- * @return \Sujip\Ipstack\Http\Response
- */
- public function call()
- {
- try {
- $request = new Request([
- 'ip' => $this->ip,
- 'api_key' => $this->api_key,
- 'secure' => $this->secure,
- ]);
-
- $response = $request->make();
- } catch (Forbidden $e) {
- throw new Forbidden('Error: No IP specified', 403);
- }
-
- return $response->getBody();
- }
-
- /**
- * Resolve if the API returns the coulumn.
- *
- * @param $key
- * @param $default
- */
- public function resolve($key, $default = null)
- {
- if (!sizeof($this->items)) {
- $this->items = $this->call();
- }
+use Ipstack\Factory\IpstackFactory;
- if (isset($this->items[$key])) {
- return $this->items[$key];
- }
-
- return $default;
- }
-
- /**
- * Get formatted address by IP
- * eg: Kathmandu, Central Region, Nepal
- *
- * @return string
- */
- public function formatted()
- {
- $address = array_filter([
- $this->city(),
- $this->zip(),
- $this->region(),
- $this->country(),
- ]);
-
- return implode(', ', $address);
- }
-
- /**
- * Get IP address, eg: 27.34.19.106
- *
- * @return string
- */
- public function ip()
- {
- return $this->resolve('ip');
- }
-
- /**
- * Get continent eg: Asia
- *
- * @return string
- */
- public function continent()
- {
- return $this->resolve('continent_name');
- }
-
- /**
- * Get continent_code eg AS
- *
- * @return string
- */
- public function continentCode()
- {
- return $this->resolve('continent_code');
- }
-
- /**
- * Get country code, eg:NP
- *
- * @return string
- */
- public function countryCode()
- {
- return $this->resolve('country_code');
- }
-
- /**
- * Get country name, eg: Nepal
- *
- * @return string
- */
- public function country()
- {
- return $this->resolve('country_name');
- }
-
- /**
- * Get your region eg: Central Region
- *
- * @return string
- */
- public function region()
- {
- return $this->resolve('region_name');
- }
-
- /**
- * Gey your region_code: 1
- *
- * @return string
- */
- public function regionCode()
- {
- return $this->resolve('region_code');
- }
-
- /**
- * Get city, eg: Kathmandu
- *
- * @return string
- */
- public function city()
- {
- return $this->resolve('city');
- }
-
- /**
- * Get zip code, eg: 33700
- *
- * @return string
- */
- public function zip()
- {
- return $this->resolve('zip');
- }
-
- /**
- * Get your capital eg: Kathmandu
- *
- * @return string
- */
- public function capital()
- {
- return $this->resolve('location')['capital'] ?? null;
- }
-
- /**
- * Get latitude eg: 27.6667
- *
- * @return string
- */
- public function latitude()
- {
- return $this->resolve('latitude');
- }
-
- /**
- * Get longitude eg: 85.3167
- *
- * @return string
- */
- public function longitude()
- {
- return $this->resolve('longitude');
- }
-
- /**
- * Get timezone eg: Asia/Kathmandu
- *
- * @return string
- */
- public function timezone()
- {
- return $this->resolve('time_zone')['id'] ?? null;
- }
-
- /**
- * Get connection, isp eg: WorldLink Communications Pvt Ltd
- *
- * @return string
- */
- public function isp()
- {
- return $this->resolve('connection')['isp'] ?? null;
- }
-
- /**
- * Get curreny code eg: NPR
- *
- * @return string
- */
- public function currency()
- {
- return $this->resolve('currency')['code'] ?? null;
- }
-
- /**
- * Get the currency name eg: Nepalese Rupee
- *
- * @return string
- */
- public function currencyName()
+final class Ipstack
+{
+ public static function factory(): IpstackFactory
{
- return $this->resolve('currency')['name'] ?? null;
+ return new IpstackFactory();
}
}
diff --git a/src/Mapper/IpstackResultMapper.php b/src/Mapper/IpstackResultMapper.php
new file mode 100644
index 0000000..13c6d6d
--- /dev/null
+++ b/src/Mapper/IpstackResultMapper.php
@@ -0,0 +1,125 @@
+ $data */
+ public function map(array $data): IpstackResult
+ {
+ /** @var Country $country */
+ $country = $this->hydrate(Country::class, $data);
+
+ /** @var Region $region */
+ $region = $this->hydrate(Region::class, $data);
+
+ $location = null;
+ if (isset($data['location']) && is_array($data['location'])) {
+ $location = new Location(
+ capital: $data['location']['capital'] ?? null,
+ languages: is_array($data['location']['languages'] ?? null) ? $data['location']['languages'] : [],
+ );
+ }
+
+ $connection = null;
+ if (isset($data['connection']) && is_array($data['connection'])) {
+ $c = $data['connection'];
+ $connection = new Connection(
+ asn: isset($c['asn']) ? (int)$c['asn'] : null,
+ isp: $c['isp'] ?? null,
+ sld: $c['sld'] ?? null,
+ tld: $c['tld'] ?? null,
+ carrier: $c['carrier'] ?? null,
+ home: isset($c['home']) ? (bool)$c['home'] : null,
+ organizationType: $c['organization_type'] ?? null,
+ isicCode: $c['isic_code'] ?? null,
+ naicsCode: $c['naics_code'] ?? null,
+ );
+ }
+
+ $security = null;
+ if (isset($data['security']) && is_array($data['security'])) {
+ $s = $data['security'];
+ $security = new Security(
+ isProxy: isset($s['is_proxy']) ? (bool)$s['is_proxy'] : null,
+ isVpn: isset($s['is_vpn']) ? (bool)$s['is_vpn'] : null,
+ isTor: isset($s['is_tor']) ? (bool)$s['is_tor'] : null,
+ proxyLastDetected: $s['proxy_last_detected'] ?? null,
+ proxyLevel: $s['proxy_level'] ?? null,
+ vpnService: $s['vpn_service'] ?? null,
+ anonymizerStatus: $s['anonymizer_status'] ?? null,
+ hostingFacility: $s['hosting_facility'] ?? null,
+ );
+ }
+
+ // Routing fields are top-level in the response (per changelog additions)
+ $routing = new Routing(
+ msa: $data['msa'] ?? null,
+ dma: $data['dma'] ?? null,
+ radius: isset($data['radius']) ? (float)$data['radius'] : null,
+ ipRoutingTyp: $data['ip_routing_typ'] ?? null,
+ connectionType: $data['connection_type'] ?? null,
+ );
+
+ return new IpstackResult(
+ ip: (string)($data['ip'] ?? ''),
+ type: $data['type'] ?? null,
+ city: $data['city'] ?? null,
+ zip: $data['zip'] ?? null,
+ latitude: isset($data['latitude']) ? (float)$data['latitude'] : null,
+ longitude: isset($data['longitude']) ? (float)$data['longitude'] : null,
+
+ country: $country,
+ region: $region,
+ location: $location,
+ connection: $connection,
+ security: $security,
+ routing: $routing,
+
+ raw: $data
+ );
+ }
+
+ /**
+ * @template T of object
+ * @param class-string $class
+ * @param array $data
+ * @return T
+ */
+ private function hydrate(string $class, array $data): object
+ {
+ $rc = new ReflectionClass($class);
+ $args = [];
+
+ foreach ($rc->getConstructor()?->getParameters() ?? [] as $p) {
+ $name = $p->getName();
+ $prop = $rc->hasProperty($name) ? $rc->getProperty($name) : null;
+
+ $key = $name;
+ if ($prop) {
+ $attrs = $prop->getAttributes(MapFrom::class);
+ if ($attrs) {
+ $key = $attrs[0]->newInstance()->key;
+ }
+ }
+
+ $args[$name] = $data[$key] ?? $p->getDefaultValue();
+ }
+
+ return $rc->newInstanceArgs($args);
+ }
+}
diff --git a/src/Model/Attributes/MapFrom.php b/src/Model/Attributes/MapFrom.php
new file mode 100644
index 0000000..52eca67
--- /dev/null
+++ b/src/Model/Attributes/MapFrom.php
@@ -0,0 +1,11 @@
+ */
+final class IpstackCollection implements IteratorAggregate, Countable
+{
+ /** @param list $items */
+ public function __construct(private readonly array $items) {}
+
+ /**
+ * @return ArrayIterator
+ */
+ public function getIterator(): ArrayIterator
+ {
+ return new ArrayIterator($this->items);
+ }
+
+ public function count(): int
+ {
+ return count($this->items);
+ }
+
+ /** @return list */
+ public function all(): array
+ {
+ return $this->items;
+ }
+}
diff --git a/src/Model/IpstackResult.php b/src/Model/IpstackResult.php
new file mode 100644
index 0000000..9df8997
--- /dev/null
+++ b/src/Model/IpstackResult.php
@@ -0,0 +1,51 @@
+ */
+ private readonly array $raw = [],
+ ) {}
+
+ public function formatted(): string
+ {
+ $parts = array_filter([$this->city, $this->zip, $this->region->name, $this->country->name]);
+
+ return implode(', ', $parts);
+ }
+
+ /** @return array */
+ public function raw(): array
+ {
+ return $this->raw;
+ }
+
+ /**
+ * @return array
+ */
+ public function jsonSerialize(): array
+ {
+ return $this->raw;
+ }
+}
diff --git a/src/Model/Location.php b/src/Model/Location.php
new file mode 100644
index 0000000..536ea92
--- /dev/null
+++ b/src/Model/Location.php
@@ -0,0 +1,16 @@
+ $languages
+ */
+ public function __construct(
+ public readonly ?string $capital = null,
+ public readonly array $languages = [],
+ ) {}
+}
diff --git a/src/Model/Region.php b/src/Model/Region.php
new file mode 100644
index 0000000..bcbc501
--- /dev/null
+++ b/src/Model/Region.php
@@ -0,0 +1,15 @@
+ $query
+ * @return array
+ */
+ public function get(string $url, array $query): array
+ {
+ $full = $url . (str_contains($url, '?') ? '&' : '?') . http_build_query($query);
+ $req = $this->requests->createRequest('GET', $full);
+
+ try {
+ $res = $this->http->sendRequest($req);
+ } catch (\Throwable $e) {
+ throw new TransportException('HTTP request failed: ' . $e->getMessage(), 0, $e);
+ }
+
+ $code = $res->getStatusCode();
+
+ if ($code < 200 || $code >= 300) {
+ throw new TransportException("Unexpected HTTP status: {$code}");
+ }
+
+ $body = (string)$res->getBody();
+ $data = json_decode($body, true);
+
+ if (!is_array($data)) {
+ throw new InvalidResponseException('Invalid JSON response');
+ }
+
+ return $data;
+ }
+}
diff --git a/src/Transport/TransportInterface.php b/src/Transport/TransportInterface.php
new file mode 100644
index 0000000..ad55626
--- /dev/null
+++ b/src/Transport/TransportInterface.php
@@ -0,0 +1,14 @@
+ $query
+ * @return array
+ */
+ public function get(string $url, array $query): array;
+}
diff --git a/tests/Fakes/FakeTransport.php b/tests/Fakes/FakeTransport.php
new file mode 100644
index 0000000..4a4738b
--- /dev/null
+++ b/tests/Fakes/FakeTransport.php
@@ -0,0 +1,28 @@
+ */
+ private array $next;
+
+ /** @param array $next */
+ public function __construct(array $next)
+ {
+ $this->next = $next;
+ }
+
+ /**
+ * @param array $query
+ * @return array
+ */
+ public function get(string $url, array $query): array
+ {
+ return $this->next;
+ }
+}
diff --git a/tests/IpstackClientEdgeCaseTest.php b/tests/IpstackClientEdgeCaseTest.php
new file mode 100644
index 0000000..44d680b
--- /dev/null
+++ b/tests/IpstackClientEdgeCaseTest.php
@@ -0,0 +1,130 @@
+ '127.0.0.1',
+ 'country_name' => 'Local',
+ 'region_name' => 'Loopback',
+ ]),
+ new IpstackResultMapper()
+ );
+
+ $result = $client->lookupRequester();
+
+ $this->assertSame('127.0.0.1', $result->ip);
+ $this->assertSame('Local', $result->country->name);
+ }
+
+ public function testLookupBulkReturnsEmptyCollectionForEmptyInput(): void
+ {
+ $client = new IpstackClient(
+ new Config('KEY'),
+ new Endpoint('https://api.ipstack.com'),
+ new FakeTransport(['ip' => 'unused']),
+ new IpstackResultMapper()
+ );
+
+ $results = $client->lookupBulk([]);
+
+ $this->assertCount(0, $results);
+ }
+
+ public function testLookupBulkThrowsWhenMoreThanFiftyIpsAreProvided(): void
+ {
+ $client = new IpstackClient(
+ new Config('KEY'),
+ new Endpoint('https://api.ipstack.com'),
+ new FakeTransport(['ip' => 'unused']),
+ new IpstackResultMapper()
+ );
+
+ $ips = [];
+ for ($i = 0; $i < 51; $i++) {
+ $ips[] = "203.0.113.{$i}";
+ }
+
+ $this->expectException(IpstackException::class);
+ $this->expectExceptionMessage('Bulk lookup supports up to 50 IPs per request.');
+
+ $client->lookupBulk($ips);
+ }
+
+ public function testLookupBulkSkipsNonArrayRowsAndMapsArrayRows(): void
+ {
+ $client = new IpstackClient(
+ new Config('KEY'),
+ new Endpoint('https://api.ipstack.com'),
+ new FakeTransport([
+ [
+ 'ip' => '8.8.8.8',
+ 'country_name' => 'United States',
+ ],
+ 'ignore-me',
+ [
+ 'ip' => '1.1.1.1',
+ 'country_name' => 'Australia',
+ ],
+ ]),
+ new IpstackResultMapper()
+ );
+
+ $results = $client->lookupBulk(['8.8.8.8', '1.1.1.1']);
+
+ $this->assertCount(2, $results);
+ $this->assertSame('8.8.8.8', $results->all()[0]->ip);
+ $this->assertSame('1.1.1.1', $results->all()[1]->ip);
+ }
+
+ /**
+ * @dataProvider apiErrorInBulkProvider
+ * @param class-string<\Throwable> $class
+ */
+ public function testLookupBulkMapsTypedApiErrors(int $code, string $class): void
+ {
+ $client = new IpstackClient(
+ new Config('KEY'),
+ new Endpoint('https://api.ipstack.com'),
+ new FakeTransport([
+ 'error' => ['code' => $code, 'info' => 'error'],
+ ]),
+ new IpstackResultMapper()
+ );
+
+ $this->expectException($class);
+
+ $client->lookupBulk(['8.8.8.8']);
+ }
+
+ /**
+ * @return array}>
+ */
+ public static function apiErrorInBulkProvider(): array
+ {
+ return [
+ 'invalid-fields' => [301, InvalidFieldsException::class],
+ 'too-many-ips' => [302, TooManyIpsException::class],
+ 'batch-not-supported' => [303, BatchNotSupportedException::class],
+ ];
+ }
+}
diff --git a/tests/IpstackClientTest.php b/tests/IpstackClientTest.php
new file mode 100644
index 0000000..9253ee2
--- /dev/null
+++ b/tests/IpstackClientTest.php
@@ -0,0 +1,90 @@
+ '8.8.8.8',
+ 'country_name' => 'United States',
+ 'country_code' => 'US',
+ 'region_name' => 'California',
+ 'region_code' => 'CA',
+ 'city' => 'Mountain View',
+ 'zip' => '94035',
+ 'security' => [
+ 'is_proxy' => false,
+ 'proxy_level' => 'none'
+ ],
+ 'connection' => [
+ 'asn' => 15169,
+ 'isp' => 'Google LLC',
+ 'tld' => 'com'
+ ],
+ 'msa' => 'San Francisco-Oakland-Berkeley'
+ ]);
+
+ $client = new IpstackClient(
+ new Config('KEY'),
+ new Endpoint('https://api.ipstack.com'),
+ $fake,
+ new IpstackResultMapper()
+ );
+
+ $result = $client->lookup('8.8.8.8', Options::create()->security());
+
+ $this->assertSame('United States', $result->country->name);
+ $this->assertSame('California', $result->region->name);
+ $this->assertSame('Mountain View, 94035, California, United States', $result->formatted());
+ $this->assertSame(15169, $result->connection?->asn);
+ $this->assertSame('none', $result->security?->proxyLevel);
+ }
+
+ public function testApiErrorThrows(): void
+ {
+ $fake = new FakeTransport([
+ 'error' => ['code' => 101, 'info' => 'Invalid access key']
+ ]);
+
+ $client = new IpstackClient(
+ new Config('BAD'),
+ new Endpoint('https://api.ipstack.com'),
+ $fake,
+ new IpstackResultMapper()
+ );
+
+ $this->expectException(ApiErrorException::class);
+ $client->lookup('8.8.8.8');
+ }
+
+ public function testRateLimitMapsToTypedException(): void
+ {
+ $fake = new FakeTransport([
+ 'error' => ['code' => 104, 'info' => 'Usage limit reached']
+ ]);
+
+ $client = new IpstackClient(
+ new Config('KEY'),
+ new Endpoint('https://api.ipstack.com'),
+ $fake,
+ new IpstackResultMapper()
+ );
+
+ $this->expectException(RateLimitException::class);
+ $client->lookup('8.8.8.8');
+ }
+}
diff --git a/tests/IpstackFactoryTest.php b/tests/IpstackFactoryTest.php
new file mode 100644
index 0000000..3927084
--- /dev/null
+++ b/tests/IpstackFactoryTest.php
@@ -0,0 +1,50 @@
+withTransport(new FakeTransport(['ip' => '8.8.8.8']));
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Access key is required.');
+
+ $factory->build();
+ }
+
+ public function testBuildThrowsWhenTransportIsMissing(): void
+ {
+ $factory = (new IpstackFactory())
+ ->withAccessKey('KEY');
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('No transport configured.');
+
+ $factory->build();
+ }
+
+ public function testBuildCreatesClientWhenConfigured(): void
+ {
+ $factory = (new IpstackFactory())
+ ->withAccessKey('KEY')
+ ->withTransport(new FakeTransport([
+ 'ip' => '8.8.8.8',
+ 'country_name' => 'United States',
+ ]));
+
+ $client = $factory->build();
+ $result = $client->lookup('8.8.8.8');
+
+ $this->assertSame('8.8.8.8', $result->ip);
+ $this->assertSame('United States', $result->country->name);
+ }
+}
diff --git a/tests/IpstackTest.php b/tests/IpstackTest.php
deleted file mode 100644
index c29ec43..0000000
--- a/tests/IpstackTest.php
+++ /dev/null
@@ -1,49 +0,0 @@
-payload = file_get_contents(__DIR__ . '/Mock/Response/response.json');
- }
-
- public function tearDown()
- {
- $this->payload = null;
- }
-
- public function testShouldThrowExceptionForEmptyIpHost()
- {
- $this->expectException(Forbidden::class);
- $this->expectExceptionMessage('Error: No IP specified');
- $this->expectExceptionCode(403);
-
- $geo = (new Ipstack(''))->call();
- }
-
- public function testShouldReturnValidResponse()
- {
- $body = $this->payload;
-
- $result = (new Response($body))->getBody();
-
- $body = json_decode($body, true);
-
- $this->assertEquals($body, $result);
- }
-}
diff --git a/tests/Mock/Response/response.json b/tests/Mock/Response/response.json
deleted file mode 100644
index 05dcc45..0000000
--- a/tests/Mock/Response/response.json
+++ /dev/null
@@ -1,59 +0,0 @@
-{
- "ip":"27.34.19.106",
- "hostname":"27.34.19.106",
- "type":"ipv4",
- "continent_code":"AS",
- "continent_name":"Asia",
- "country_code":"NP",
- "country_name":"Nepal",
- "region_code":"1",
- "region_name":"Central Region",
- "city":"Jawalakhel",
- "zip":null,
- "latitude":27.6667,
- "longitude":85.3167,
- "location":{
- "geoname_id":1283312,
- "capital":"Kathmandu",
- "languages":[
- {
- "code":"ne",
- "name":"Nepali",
- "native":"\u0928\u0947\u092a\u093e\u0932\u0940"
- }
- ],
- "country_flag":"http:\/\/assets.ipstack.com\/flags\/np.svg",
- "country_flag_emoji":"\ud83c\uddf3\ud83c\uddf5",
- "country_flag_emoji_unicode":"U+1F1F3 U+1F1F5",
- "calling_code":"977",
- "is_eu":false
- },
- "time_zone":{
- "id":"Asia\/Kathmandu",
- "current_time":"2018-07-06T14:16:47+05:45",
- "gmt_offset":20700,
- "code":"+0545",
- "is_daylight_saving":false
- },
- "currency":{
- "code":"NPR",
- "name":"Nepalese Rupee",
- "plural":"Nepalese rupees",
- "symbol":"NPRs",
- "symbol_native":"\u0928\u0947\u0930\u0942"
- },
- "connection":{
- "asn":17501,
- "isp":"WorldLink Communications Pvt Ltd"
- },
- "security":{
- "is_proxy":false,
- "proxy_type":null,
- "is_crawler":false,
- "crawler_name":null,
- "crawler_type":null,
- "is_tor":false,
- "threat_level":"low",
- "threat_types":null
- }
-}
diff --git a/tests/Psr18TransportTest.php b/tests/Psr18TransportTest.php
new file mode 100644
index 0000000..82e85bf
--- /dev/null
+++ b/tests/Psr18TransportTest.php
@@ -0,0 +1,110 @@
+createMock(RequestInterface::class);
+ $requests = $this->createMock(RequestFactoryInterface::class);
+ $http = $this->createMock(ClientInterface::class);
+ $response = $this->createMock(ResponseInterface::class);
+ $stream = $this->createMock(StreamInterface::class);
+
+ $requests->expects($this->once())
+ ->method('createRequest')
+ ->with(
+ 'GET',
+ 'https://api.ipstack.com/8.8.8.8?access_key=KEY&language=en'
+ )
+ ->willReturn($request);
+
+ $http->expects($this->once())
+ ->method('sendRequest')
+ ->with($request)
+ ->willReturn($response);
+
+ $response->method('getStatusCode')->willReturn(200);
+ $response->method('getBody')->willReturn($stream);
+ $stream->method('__toString')->willReturn('{"ip":"8.8.8.8"}');
+
+ $transport = new Psr18Transport($http, $requests);
+ $data = $transport->get('https://api.ipstack.com/8.8.8.8', [
+ 'access_key' => 'KEY',
+ 'language' => 'en',
+ ]);
+
+ $this->assertSame('8.8.8.8', $data['ip']);
+ }
+
+ public function testGetThrowsTransportExceptionOnNonSuccessfulStatus(): void
+ {
+ $request = $this->createMock(RequestInterface::class);
+ $requests = $this->createMock(RequestFactoryInterface::class);
+ $http = $this->createMock(ClientInterface::class);
+ $response = $this->createMock(ResponseInterface::class);
+
+ $requests->method('createRequest')->willReturn($request);
+ $http->method('sendRequest')->willReturn($response);
+ $response->method('getStatusCode')->willReturn(403);
+
+ $transport = new Psr18Transport($http, $requests);
+
+ $this->expectException(TransportException::class);
+ $this->expectExceptionMessage('Unexpected HTTP status: 403');
+
+ $transport->get('https://api.ipstack.com/8.8.8.8', ['access_key' => 'KEY']);
+ }
+
+ public function testGetThrowsInvalidResponseExceptionWhenJsonIsInvalid(): void
+ {
+ $request = $this->createMock(RequestInterface::class);
+ $requests = $this->createMock(RequestFactoryInterface::class);
+ $http = $this->createMock(ClientInterface::class);
+ $response = $this->createMock(ResponseInterface::class);
+ $stream = $this->createMock(StreamInterface::class);
+
+ $requests->method('createRequest')->willReturn($request);
+ $http->method('sendRequest')->willReturn($response);
+ $response->method('getStatusCode')->willReturn(200);
+ $response->method('getBody')->willReturn($stream);
+ $stream->method('__toString')->willReturn('not-json');
+
+ $transport = new Psr18Transport($http, $requests);
+
+ $this->expectException(InvalidResponseException::class);
+ $this->expectExceptionMessage('Invalid JSON response');
+
+ $transport->get('https://api.ipstack.com/8.8.8.8', ['access_key' => 'KEY']);
+ }
+
+ public function testGetWrapsUnderlyingClientThrowable(): void
+ {
+ $request = $this->createMock(RequestInterface::class);
+ $requests = $this->createMock(RequestFactoryInterface::class);
+ $http = $this->createMock(ClientInterface::class);
+
+ $requests->method('createRequest')->willReturn($request);
+ $http->method('sendRequest')->willThrowException(new \RuntimeException('network down'));
+
+ $transport = new Psr18Transport($http, $requests);
+
+ $this->expectException(TransportException::class);
+ $this->expectExceptionMessage('HTTP request failed: network down');
+
+ $transport->get('https://api.ipstack.com/8.8.8.8', ['access_key' => 'KEY']);
+ }
+}