diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml new file mode 100644 index 0000000..8e856b8 --- /dev/null +++ b/.github/workflows/php.yml @@ -0,0 +1,23 @@ +name: PHP Composer + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + + - name: Validate composer.json and composer.lock + run: composer validate + + - name: Install dependencies + run: composer install --prefer-dist --no-progress --no-suggest + + # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" + # Docs: https://getcomposer.org/doc/articles/scripts.md + + # - name: Run test suite + # run: composer run-script test diff --git a/.gitignore b/.gitignore index a4854be..4f0b2c9 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,7 @@ Homestead.json # Rocketeer PHP task runner and deployment package. https://github.com/rocketeers/rocketeer .rocketeer/ +/nbproject/private/ +/nbproject/ + +.idea \ No newline at end of file diff --git a/.phpunit.result.cache b/.phpunit.result.cache new file mode 100644 index 0000000..b1eb6ee --- /dev/null +++ b/.phpunit.result.cache @@ -0,0 +1 @@ +{"version":1,"defects":{"testApi::testwithKeywithrequiredParametersApiCall":4,"testApi::testwithoutKeyApiCall":4},"times":{"testApi::testwithoutKeyApiCall":0.002,"testApi::testwithKeywithoutrequiredParametersApiCall":0.164,"testApi::testwithKeywithrequiredParametersApiCall":0.002,"testApi::testWithInvalidTypeReturnsError":0.002}} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..01ae7ac --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: php +php: + - '7.3' +sudo: required +before_script: + - composer install --prefer-source +notifications: + email: + - me@rodrigomanara.co.uk diff --git a/README.md b/README.md index dd6f7bf..47d96e5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,165 @@ # News API SDK for PHP -Coming soon... this is where our officially supported SDK for PHP is going to live. -*** +[![PHP Composer](https://github.com/rodrigomanara/News-API-php/actions/workflows/php.yml/badge.svg)](https://github.com/rodrigomanara/News-API-php/actions/workflows/php.yml) +[![Latest Stable Version](https://poser.pugx.org/rmanara/news-api-php/v/stable)](https://packagist.org/packages/rmanara/news-api-php) +[![License](https://poser.pugx.org/rmanara/news-api-php/license)](https://packagist.org/packages/rmanara/news-api-php) + +A lightweight PHP SDK for the [NewsAPI v2](https://newsapi.org/docs/) service. +Search through millions of articles from over 30,000 news sources and blogs — including breaking news and niche publications. + +--- + +## Requirements + +| Requirement | Version | +|---|---| +| PHP | `>= 7.4` | +| ext-curl | any | +| ext-json | any | + +--- + +## Installation + +```bash +composer require rmanara/news-api-php:^1.0 +``` + +--- + +## Quick start + +```php +require_once __DIR__ . '/vendor/autoload.php'; + +$api = new \NewsApi\Api('YOUR_API_KEY', ['q' => 'PHP', 'language' => 'en']); +$data = $api->getData(); + +// $data is a stdClass decoded from the JSON response. +echo $data->totalResults; +foreach ($data->articles as $article) { + echo $article->title . PHP_EOL; +} +``` + +> **Security note:** the API key is transmitted via the `X-Api-Key` request header +> and is never appended to the URL. This keeps it out of server access logs, +> browser history, and HTTP Referer headers. + +--- + +## Constructor + +```php +new \NewsApi\Api( + string $apiKey, // Required. Key from newsapi.org. + array $query = [], // Endpoint query parameters. + string $type = enumType::TOP_HEADLINE // Endpoint type constant. +) +``` + +Validation runs before any network request is made, in this order: + +1. `$apiKey` must be non-empty. +2. `$type` must be one of the `enumType` constants. +3. `$query` must contain at least one parameter. + +If any check fails, `getData()` returns a local error array and no HTTP call is made. + +--- + +## Endpoints + +Use the `\NewsApi\enumType` constants to select an endpoint: + +| Constant | Endpoint | Required query params | +|---|---|---| +| `enumType::TOP_HEADLINE` *(default)* | `top-headlines` | one of: `sources`, `q`, `language`, `country` | +| `enumType::EVERYTHING` | `everything` | one of: `q`, `sources`, `domains` | +| `enumType::SOURCES` | `sources` | none — all params optional | + +--- + +## Examples + +### Top headlines + +```php +$api = new \NewsApi\Api('YOUR_API_KEY', ['country' => 'gb']); +$data = $api->getData(); +``` + +### Search everything + +```php +use NewsApi\Api; +use NewsApi\enumType; + +$api = new Api('YOUR_API_KEY', ['q' => 'climate change', 'language' => 'en'], enumType::EVERYTHING); +$data = $api->getData(); +``` + +### Discover sources + +```php +use NewsApi\Api; +use NewsApi\enumType; + +$api = new Api('YOUR_API_KEY', ['language' => 'en', 'country' => 'us'], enumType::SOURCES); +$sources = $api->getData(); +``` + +--- + +## Error handling + +### Local validation errors + +When a validation guard fails before any request is made, `getData()` returns an +associative array: + +```php +// Missing or empty API key +$api = new \NewsApi\Api('', ['q' => 'test']); +$data = $api->getData(); +// ['error' => ['apikey' => 'missing apikey']] + +// Unsupported endpoint type +$api = new \NewsApi\Api('YOUR_API_KEY', ['q' => 'test'], 'bad-type'); +$data = $api->getData(); +// ['error' => ['type' => 'type is not correct']] + +// Empty query array +$api = new \NewsApi\Api('YOUR_API_KEY', []); +$data = $api->getData(); +// ['error' => ['query' => 'empty query']] +``` + +### Transport errors + +A `\RuntimeException` is thrown when cURL fails (e.g. DNS resolution error, +connection timeout): + +```php +try { + $api = new \NewsApi\Api('YOUR_API_KEY', ['q' => 'PHP']); + $data = $api->getData(); +} catch (\RuntimeException $e) { + // Handle transport failure + echo $e->getMessage(); +} +``` + +--- + +## Running the tests + +```bash +composer test +``` + +--- + +## License + +MIT © [Rodrigo Manara](https://github.com/rodrigomanara) diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..e397596 --- /dev/null +++ b/composer.json @@ -0,0 +1,60 @@ +{ + "name": "rmanara/news-api-php", + "description": "PHP SDK for the NewsAPI service", + "version": "1.0.0", + "type": "library", + "license": "MIT", + "homepage": "https://github.com/rodrigomanara/News-API-php", + "keywords": [ + "news", + "newsapi", + "sdk", + "api", + "php" + ], + "authors": [ + { + "name": "Rodrigo Manara", + "email": "me@rodrigomanara.co.uk" + } + ], + "support": { + "issues": "https://github.com/rodrigomanara/News-API-php/issues", + "source": "https://github.com/rodrigomanara/News-API-php" + }, + "minimum-stability": "stable", + "prefer-stable": true, + "require": { + "php": ">=7.4", + "ext-curl": "*", + "ext-json": "*" + }, + "autoload": { + "psr-4": { + "NewsApi\\": "src/" + } + }, + "autoload-dev": { + "classmap": [ + "test/" + ] + }, + "require-dev": { + "phpunit/phpunit": "^8.5.52" + }, + "scripts": { + "test": "vendor/bin/phpunit", + "test:coverage": "vendor/bin/phpunit --coverage-text" + }, + "config": { + "sort-packages": true, + "optimize-autoloader": true, + "preferred-install": "dist", + "allow-plugins": {} + }, + "extra": { + "branch-alias": { + "dev-main": "1.0.x-dev" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..340a5a2 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1500 @@ +{ + "_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": "e3fcbb06c7bbf2bfafbed3782ed24591", + "packages": [], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.30 || ^5.4" + }, + "type": "library", + "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": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-12-30T00:15:36+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "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": "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)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.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", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "7.0.17", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "40a4ed114a4aea5afd6df8d0f0c9cd3033097f66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/40a4ed114a4aea5afd6df8d0f0c9cd3033097f66", + "reference": "40a4ed114a4aea5afd6df8d0f0c9cd3033097f66", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": ">=7.2", + "phpunit/php-file-iterator": "^2.0.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^3.1.3 || ^4.0", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^4.2.2", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1.3" + }, + "require-dev": { + "phpunit/phpunit": "^8.2.2" + }, + "suggest": { + "ext-xdebug": "^2.7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.0-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" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.17" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:09:37+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "2.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "69deeb8664f611f156a924154985fbd4911eb36b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/69deeb8664f611f156a924154985fbd4911eb36b", + "reference": "69deeb8664f611f156a924154985fbd4911eb36b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "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": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T13:39:50+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" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" + }, + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "2.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "a691211e94ff39a34811abd521c31bd5b305b0bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/a691211e94ff39a34811abd521c31bd5b305b0bb", + "reference": "a691211e94ff39a34811abd521c31bd5b305b0bb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-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": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T13:42:41+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/a853a0e183b9db7eed023d7933a858fa1c8d25a3", + "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.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" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", + "source": "https://github.com/sebastianbergmann/php-token-stream/tree/master" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "abandoned": true, + "time": "2020-08-04T08:28:15+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "8.5.52", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "1015741814413c156abb0f53d7db7bbd03c6e858" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1015741814413c156abb0f53d7db7bbd03c6e858", + "reference": "1015741814413c156abb0f53d7db7bbd03c6e858", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.5.0", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=7.2", + "phpunit/php-code-coverage": "^7.0.17", + "phpunit/php-file-iterator": "^2.0.6", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^2.1.4", + "sebastian/comparator": "^3.0.7", + "sebastian/diff": "^3.0.6", + "sebastian/environment": "^4.2.5", + "sebastian/exporter": "^3.1.8", + "sebastian/global-state": "^3.0.6", + "sebastian/object-enumerator": "^3.0.5", + "sebastian/resource-operations": "^2.0.3", + "sebastian/type": "^1.1.5", + "sebastian/version": "^2.0.1" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage", + "phpunit/php-invoker": "To allow enforcing time limits" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.5-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" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.52" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2026-01-27T05:20:18+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", + "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "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/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T13:45:45+00:00" + }, + { + "name": "sebastian/comparator", + "version": "3.0.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "bc7d8ac2fe1cce229bff9b5fd4efe65918a1ff52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/bc7d8ac2fe1cce229bff9b5fd4efe65918a1ff52", + "reference": "bc7d8ac2fe1cce229bff9b5fd4efe65918a1ff52", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "sebastian/diff": "^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.7" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-01-24T09:20:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "98ff311ca519c3aa73ccd3de053bdb377171d7b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/98ff311ca519c3aa73ccd3de053bdb377171d7b6", + "reference": "98ff311ca519c3aa73ccd3de053bdb377171d7b6", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.0", + "symfony/process": "^2 || ^3.3 || ^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:16:36+00:00" + }, + { + "name": "sebastian/environment", + "version": "4.2.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "56932f6049a0482853056ffd617c91ffcc754205" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/56932f6049a0482853056ffd617c91ffcc754205", + "reference": "56932f6049a0482853056ffd617c91ffcc754205", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-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" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/4.2.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T13:49:59+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "64cfeaa341951ceb2019d7b98232399d57bb2296" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/64cfeaa341951ceb2019d7b98232399d57bb2296", + "reference": "64cfeaa341951ceb2019d7b98232399d57bb2296", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^8.5" + }, + "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" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T05:55:14+00:00" + }, + { + "name": "sebastian/global-state", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "800689427e3e8cf57a8fe38fcd1d4344c9b2f046" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/800689427e3e8cf57a8fe38fcd1d4344c9b2f046", + "reference": "800689427e3e8cf57a8fe38fcd1d4344c9b2f046", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^8.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.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" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" + } + ], + "time": "2025-08-10T05:40:12+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "ac5b293dba925751b808e02923399fb44ff0d541" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/ac5b293dba925751b808e02923399fb44ff0d541", + "reference": "ac5b293dba925751b808e02923399fb44ff0d541", + "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/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T13:54:02+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "1d439c229e61f244ff1f211e5c99737f90c67def" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/1d439c229e61f244ff1f211e5c99737f90c67def", + "reference": "1d439c229e61f244ff1f211e5c99737f90c67def", + "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/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T13:56:04+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "8fe7e75986a9d24b4cceae847314035df7703a5a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/8fe7e75986a9d24b4cceae847314035df7703a5a", + "reference": "8fe7e75986a9d24b4cceae847314035df7703a5a", + "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": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-10T05:25:53+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "72a7f7674d053d548003b16ff5a106e7e0e06eee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/72a7f7674d053d548003b16ff5a106e7e0e06eee", + "reference": "72a7f7674d053d548003b16ff5a106e7e0e06eee", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "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": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T13:59:09+00:00" + }, + { + "name": "sebastian/type", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "18f071c3a29892b037d35e6b20ddf3ea39b42874" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/18f071c3a29892b037d35e6b20ddf3ea39b42874", + "reference": "18f071c3a29892b037d35e6b20ddf3ea39b42874", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.2" + }, + "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", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/1.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T14:04:07+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", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/master" + }, + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.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", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-11-17T20:03:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": ">=7.4", + "ext-curl": "*", + "ext-json": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/demo.php b/demo.php new file mode 100644 index 0000000..66766f2 --- /dev/null +++ b/demo.php @@ -0,0 +1,18 @@ + 'Reino Unido', 'language' => 'pt']); +$data = $api->getData(); diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..ffff040 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,19 @@ + + + + + ./test/testApi.php + + + + diff --git a/src/AbstractApi.php b/src/AbstractApi.php new file mode 100644 index 0000000..f1d83f2 --- /dev/null +++ b/src/AbstractApi.php @@ -0,0 +1,206 @@ +|null + */ + private $data = null; + + /** + * Root URL shared by every NewsAPI v2 endpoint. + * + * The concrete endpoint segment and query string are appended at call time. + */ + protected const URL = 'https://newsapi.org/v2/'; + + // ------------------------------------------------------------------------- + // Transport + // ------------------------------------------------------------------------- + + /** + * Executes a GET request, authenticates it via the {@code X-Api-Key} request + * header, and stores the decoded JSON payload. + * + * Sending the key in a header rather than a query-string parameter prevents it + * from appearing in server access logs, browser history, and HTTP Referer + * headers — all common sources of unintentional credential exposure. + * + * @param string $url Fully-qualified NewsAPI endpoint URL (without the apiKey + * query parameter). + * @param string $apiKey Developer API key forwarded in the X-Api-Key header. + * + * @return void + * + * @throws \RuntimeException When a cURL handle cannot be initialised. + * @throws \RuntimeException When cURL reports a transport-level error. + */ + protected function call(string $url, string $apiKey = ''): void + { + $curl = curl_init(); + + if ($curl === false) { + throw new \RuntimeException('Failed to initialise a cURL handle.'); + } + + $dynamicAgent = $_SERVER['HTTP_USER_AGENT'] ?? 'Mozilla/5.0 (Default-Fallback)'; + + curl_setopt_array($curl, [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_ENCODING => 'UTF-8', + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 30, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => 'GET', + CURLOPT_SSL_VERIFYPEER => true, + CURLOPT_SSL_VERIFYHOST => 2, + CURLOPT_HTTPHEADER => [ + 'Cache-Control: no-cache', + 'X-Api-Key: ' . $apiKey, + ], + CURLOPT_USERAGENT => $dynamicAgent, + ]); + + $response = curl_exec($curl); + $err = curl_error($curl); + curl_close($curl); + + // Check for a transport error before attempting to store the response so + // that a failed request does not silently overwrite valid stored data. + if ($err !== '') { + throw new \RuntimeException('cURL transport error: ' . $err); + } + + $this->setData((string) $response); + } + + // ------------------------------------------------------------------------- + // Data accessors + // ------------------------------------------------------------------------- + + /** + * Returns the decoded response payload, a local validation-error array, or + * null when no request has been attempted yet. + * + * @return object|array|null + */ + public function getData() + { + return $this->data; + } + + /** + * Stores API response data on the instance. + * + * When {@code $decode} is true (the default) the value is treated as a raw JSON + * string and decoded before storage. Pass false together with an associative + * array to store a pre-built error payload directly without a JSON round-trip. + * + * @param string|array $data JSON response body or a pre-built + * error payload array. + * @param bool $decode When true, JSON-decode {@code $data} + * before storing it. + * + * @return void + */ + protected function setData($data, bool $decode = true): void + { + if ($decode) { + $this->data = json_decode((string) $data); + } else { + foreach ((array) $data as $key => $value) { + $this->data[$key] = $value; + } + } + } + + // ------------------------------------------------------------------------- + // Validation helpers + // ------------------------------------------------------------------------- + + /** + * Guards against a missing or blank API key. + * + * When the key is absent the method stores a local error payload and returns + * false so the caller can short-circuit before making any network request. + * + * @param string $apiKey The developer API key to validate. + * + * @return bool True when the key is non-empty; false otherwise. + */ + protected function validateApiKey(string $apiKey): bool + { + if (trim($apiKey) === '') { + $obj = ['error' => ['apikey' => 'missing apikey']]; + $this->setData($obj, false); + return false; + } + return true; + } + + /** + * Guards against an empty query-parameter array. + * + * Every NewsAPI endpoint requires at least one search criterion in addition to + * the API key, so an empty {@code $query} is rejected before any HTTP request + * is issued. + * + * @param array $query Request parameters (must contain at least + * one entry). + * + * @return bool True when the array is non-empty; false otherwise. + */ + protected function validateQuery(array $query): bool + { + if (empty($query)) { + $obj = ['error' => ['query' => 'empty query']]; + $this->setData($obj, false); + return false; + } + return true; + } + + /** + * Guards against an unsupported endpoint type. + * + * Accepted values are the string constants defined on {@see enumType}. + * + * @param string $type Endpoint segment to validate (e.g. {@code "top-headlines"}). + * + * @return bool True when {@code $type} matches a recognised NewsAPI endpoint; + * false otherwise. + */ + protected function validateType(string $type): bool + { + $allowed = [ + enumType::TOP_HEADLINE, + enumType::EVERYTHING, + enumType::SOURCES, + ]; + + if (!in_array($type, $allowed, true)) { + $obj = ['error' => ['type' => 'type is not correct']]; + $this->setData($obj, false); + return false; + } + return true; + } +} diff --git a/src/Api.php b/src/Api.php new file mode 100644 index 0000000..02409a3 --- /dev/null +++ b/src/Api.php @@ -0,0 +1,64 @@ + + * $api = new Api('your-api-key', ['q' => 'PHP', 'language' => 'en']); + * $data = $api->getData(); + * + * + * @package NewsApi + */ +class Api extends AbstractApi +{ + /** + * Validates the provided credentials and query, then immediately executes the request. + * + * Validation is performed in order: + * 1. The API key must be non-empty. + * 2. The endpoint type must be one of the values defined in {@see enumType}. + * 3. The query array must contain at least one search parameter. + * + * If any guard fails, {@see getData()} will return a local error payload and + * no HTTP request is made. + * + * The API key is transmitted via the {@code X-Api-Key} request header and is + * intentionally kept out of the URL to avoid credential exposure in logs. + * + * @param string $apiKey Developer API key issued by newsapi.org. + * @param array $query Endpoint-specific query parameters + * (e.g. {@code ['q' => 'PHP', 'language' => 'en']}). + * @param string $type Endpoint type; one of the {@see enumType} constants. + * Defaults to {@see enumType::TOP_HEADLINE}. + * + * @throws \RuntimeException When the underlying cURL transport fails. + */ + public function __construct( + string $apiKey, + array $query = [], + string $type = enumType::TOP_HEADLINE + ) { + if (!$this->validateApiKey($apiKey)) { + return; + } + + if (!$this->validateType($type)) { + return; + } + + if (!$this->validateQuery($query)) { + return; + } + + $uri = http_build_query($query); + $url = self::URL . $type . ($uri !== '' ? '?' . $uri : ''); + + $this->call($url, $apiKey); + } +} diff --git a/src/InterfaceApi.php b/src/InterfaceApi.php new file mode 100644 index 0000000..e495089 --- /dev/null +++ b/src/InterfaceApi.php @@ -0,0 +1,28 @@ +|null + */ + public function getData(); +} diff --git a/src/enumType.php b/src/enumType.php new file mode 100644 index 0000000..1e010c3 --- /dev/null +++ b/src/enumType.php @@ -0,0 +1,44 @@ + 'test']); + $data = $new->getData(); + + $this->assertEquals('missing apikey', $data['error']['apikey']); + } + + /** + * Confirms that passing an invalid endpoint type stores a local validation error + * without making any network request. + * + * @return void + */ + public function testWithInvalidTypeReturnsError(): void + { + $new = new Api('valid-key', ['q' => 'test'], 'invalid-endpoint'); + $data = $new->getData(); + + $this->assertEquals('type is not correct', $data['error']['type']); + } + + /** + * Confirms that a successful request can be consumed as a decoded response object. + * + * The HTTP call is replaced with a mock that injects a known JSON payload so that + * the test does not depend on network availability or a real API key. + * + * @return void + */ + public function testwithKeywithrequiredParametersApiCall(): void + { + $new = new class ('test-api-key', ['language' => 'pt']) extends Api { + /** + * Overrides the real HTTP transport with a stub that returns a static payload. + * + * @param string $url The request URL that would have been called. + * @param string $apiKey The API key that would have been forwarded. + * + * @return void + */ + protected function call(string $url, string $apiKey = ''): void + { + $this->setData(json_encode(['status' => 'ok'])); + } + }; + + $data = $new->getData(); + + $this->assertEquals('ok', $data->status); + } +}