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.
-***
+[](https://github.com/rodrigomanara/News-API-php/actions/workflows/php.yml)
+[](https://packagist.org/packages/rmanara/news-api-php)
+[](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);
+ }
+}