Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a4c0e77
Init
sahilsaha7773 Jun 22, 2023
6b3025c
Delete .DS_Store
sahilsaha7773 Jun 22, 2023
f9983aa
Delete .DS_Store
sahilsaha7773 Jun 22, 2023
3a11910
Updated composer.json
sahilsaha7773 Jul 3, 2023
795a6be
Improved code quality
sahilsaha7773 Jul 3, 2023
35813d2
Removed index.php
sahilsaha7773 Jul 10, 2023
f8eff6c
Removed name from authors
sahilsaha7773 Jul 10, 2023
2f42a4e
Added config file for phpstan
sahilsaha7773 Jul 10, 2023
98685a4
Updated the scripts
sahilsaha7773 Jul 10, 2023
272ab12
Removed type from Response
sahilsaha7773 Jul 10, 2023
1e98f86
Removed array() and updated json() and the tests accordingly
sahilsaha7773 Jul 10, 2023
e2eb93e
Updated tests.yml
sahilsaha7773 Jul 10, 2023
b53d232
Added default in match and updated phpstan config
sahilsaha7773 Jul 10, 2023
582e357
Added README.md
sahilsaha7773 Jul 11, 2023
d586ef6
Added more usage examples
sahilsaha7773 Jul 12, 2023
fe26fc6
Added request headers check in tests
sahilsaha7773 Jul 12, 2023
47592c3
Fixed Typo
sahilsaha7773 Jul 30, 2023
20c69a4
Updated tests so that isOk is removed
sahilsaha7773 Aug 1, 2023
de325e9
Updated Client.php
sahilsaha7773 Aug 1, 2023
b27bcd0
Added valid method check and updated default parameters
sahilsaha7773 Aug 1, 2023
2e7ba9b
Fixed code style
sahilsaha7773 Aug 1, 2023
834ee48
Removed url and method from Response
sahilsaha7773 Aug 1, 2023
c36fcc2
Removed default header content-type
sahilsaha7773 Aug 1, 2023
d2509ab
Fixed tests for sending file
sahilsaha7773 Aug 1, 2023
3836ee7
Changed response body to mixed type
sahilsaha7773 Aug 1, 2023
e72b9eb
Added tests for sending more file types and redirects
sahilsaha7773 Aug 1, 2023
35df8d5
Added tests for getting a file as a response
sahilsaha7773 Aug 2, 2023
050edef
rtrim url if it contains ? already before adding query string
sahilsaha7773 Aug 2, 2023
6fa6820
Code quality fixes
sahilsaha7773 Aug 2, 2023
12a7996
Removed unnecessary code and updated variable names
sahilsaha7773 Aug 3, 2023
bcfdc53
Made timeout configurable
sahilsaha7773 Aug 3, 2023
99aeb4f
Updated README.md
sahilsaha7773 Aug 4, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: "CodeQL"

on: [ pull_request ]
jobs:
lint:
name: CodeQL
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 2

- run: git checkout HEAD^2

- name: Install dependencies
run: composer install --profile --ignore-platform-reqs

- name: Run CodeQL
run: composer check
21 changes: 21 additions & 0 deletions .github/workflows/linter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: "Linter"

on: [ pull_request ]
jobs:
lint:
name: Linter
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 2

- run: git checkout HEAD^2

- name: Install dependencies
run: composer install --profile --ignore-platform-reqs

- name: Run Linter
run: composer lint
23 changes: 23 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: "Tests"

on: [ pull_request ]
jobs:
lint:
name: Tests
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 2

- run: git checkout HEAD^2

- name: Install dependencies
run: composer install --profile --ignore-platform-reqs

- name: Run Tests
run: php -S localhost:8000 tests/router.php &
composer test

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
vendor
*.cache
composer.lock
113 changes: 113 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Utopia Fetch
Lite & fast micro PHP library that provides a convenient and flexible way to perform HTTP requests in PHP applications.

# Usage
The library provides a static method `Client::fetch()` that returns a `Response` object.

The `Client::fetch()` method accepts the following parameters:
- `url` - A **String** containing the URL to which the request is sent.
- `method` - A **String** containing the HTTP method for the request. The default method is `GET`.
- `headers` - An **associative array** of HTTP headers to send.
- `body` - An **associative array** of data to send as the body of the request.
- `query` - An **associative array** of query parameters.
- `timeout` - An **integer** representing the maximum number of seconds to allow cURL functions to execute, the default is `15`.

The `Response` object has the following methods:
- `isOk()` - Returns **true** if the response status code is in the range 200-299, **false** otherwise.
- `getBody()` - Returns the response body as a **String**.
- `getHeaders()` - Returns an **associative array** of response headers.
- `getStatusCode()` - Returns the response status code as an **integer**.
- `getMethod()` - Returns the request method as a **String**.
- `getUrl()` - Returns the request URL as a **String**.
- `text()` - Returns the response body as a **String**.
- `json()` - Returns the response body as an **associative array**.
- `blob()` - Converts the response body to blob and return it as a **String**.

## Examples
### GET request
```php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Utopia\Fetch\Client;

$url = 'https://httpbin.org/get';
$method = 'GET';
$query = [
'foo' => 'bar'
];

$resp = Client::fetch(
url: $url,
method: $method,
query: $query
);

if($resp->isOk()) {
print("Status Code: " . $resp->getStatusCode() . "\n");
print("Response Headers:\n");
print_r($resp->getHeaders());
print("Response Body:\n");
print($resp->getBody());
}
else {
print("Error: " . $resp->getStatusCode() . "\n");
print("Response Headers:\n");
print_r($resp->getHeaders());
print("Response Body:\n");
print($resp->getBody());
}
```
### POST request
```php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Utopia\Fetch\Client;

$url = 'https://httpbin.org/post';
$method = 'POST';
$headers = [
'Content-Type' => 'application/json',
];
$body = [
'name' => 'John Doe',
];
$query = [
'foo' => 'bar'
];

$resp = Client::fetch(
url: $url,
method: $method,
headers: $headers,
body: $body,
query: $query
);

print_r($resp->json());
```
### Send a file
```php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Utopia\Fetch\Client;

$url = 'http://localhost:8000';
$method = 'POST';
$headers = [
'Content-Type' => 'application/json',
];
$filePath = strval(realpath(__DIR__ . '/tests/resources/logo.png')); // Absolute path to the file

$body = [
'file' => new \CURLFile($filePath, 'image/png', 'logo.png')
];

$resp = Client::fetch(
url: $url,
method: $method,
headers: $headers,
body: $body
);

print_r($resp->json());
```
26 changes: 26 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "utopia-php/fetch",
"description": "A simple library that provides an interface for making HTTP Requests.",
"type": "library",
"license": "MIT",
"require": {
"php": ">=8.0"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"laravel/pint": "^1.5.0",
"phpstan/phpstan": "^1.10"
},
"scripts": {
"lint": "./vendor/bin/pint --test --config pint.json",
"format": "./vendor/bin/pint --config pint.json",
"check": "./vendor/bin/phpstan analyse -c phpstan.neon",
"test": "./vendor/bin/phpunit --configuration phpunit.xml --debug"
},
"autoload": {
"psr-4": {
"Utopia\\Fetch\\": "src/"
}
},
"authors": []
}
5 changes: 5 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
parameters:
level: 8
paths:
- src
- tests
16 changes: 16 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
>
<testsuites>
<testsuite name="Application Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
</phpunit>
3 changes: 3 additions & 0 deletions pint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"preset": "psr12"
}
130 changes: 130 additions & 0 deletions src/Client.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php

namespace Utopia\Fetch;

/**
* Client class
* @package Utopia\Fetch
*/
class Client
{
public const METHOD_GET = 'GET';
public const METHOD_POST = 'POST';
public const METHOD_PUT = 'PUT';
public const METHOD_PATCH = 'PATCH';
public const METHOD_DELETE = 'DELETE';
public const METHOD_HEAD = 'HEAD';
public const METHOD_OPTIONS = 'OPTIONS';
public const METHOD_CONNECT = 'CONNECT';
public const METHOD_TRACE = 'TRACE';

public const CONTENT_TYPE_APPLICATION_JSON = 'application/json';
public const CONTENT_TYPE_APPLICATION_FORM_URLENCODED = 'application/x-www-form-urlencoded';
public const CONTENT_TYPE_MULTIPART_FORM_DATA = 'multipart/form-data';
public const CONTENT_TYPE_GRAPHQL = 'application/graphql';

/**
* Flatten request body array to PHP multiple format
*
* @param array<mixed> $data
* @param string $prefix
* @return array<mixed>
*/
private static function flatten(array $data, string $prefix = ''): array
{
$output = [];
foreach ($data as $key => $value) {
$finalKey = $prefix ? "{$prefix}[{$key}]" : $key;

if (is_array($value)) {
$output += self::flatten($value, $finalKey); // @todo: handle name collision here if needed
} else {
$output[$finalKey] = $value;
}
}

return $output;
}
/**
* This method is used to make a request to the server
* @param string $url
* @param array<string, string> $headers
* @param string $method
* @param array<string>|array<string, mixed> $body
* @param array<string, mixed> $query
* @param int $timeout
* @return Response
*/
public static function fetch(
string $url,
array $headers = [],
string $method = self::METHOD_GET,
array $body = [],
array $query = [],
int $timeout = 15
): Response {
// Process the data before making the request
if (!in_array($method, [self::METHOD_PATCH, self::METHOD_GET, self::METHOD_CONNECT, self::METHOD_DELETE, self::METHOD_POST, self::METHOD_HEAD, self::METHOD_OPTIONS, self::METHOD_PUT, self::METHOD_TRACE ])) { // If the method is not supported
throw new FetchException("Unsupported HTTP method");
}
if(isset($headers['content-type'])) {
match ($headers['content-type']) { // Convert the body to the appropriate format
self::CONTENT_TYPE_APPLICATION_JSON => $body = json_encode($body),
self::CONTENT_TYPE_APPLICATION_FORM_URLENCODED, self::CONTENT_TYPE_MULTIPART_FORM_DATA => $body = self::flatten($body),
self::CONTENT_TYPE_GRAPHQL => $body = $body[0],
default => $body = $body,
};
}
$headers = array_map(function ($i, $header) { // convert headers to appropriate format
return $i . ':' . $header;
}, array_keys($headers), $headers);
if($query) { // if the request has a query string, append it to the request URI
$url = rtrim($url, '?');
$url .= '?' . http_build_query($query);
}
$responseHeaders = [];
// Initialize the curl session
$ch = curl_init();
// Set the request URI
curl_setopt($ch, CURLOPT_URL, $url);
// Set the request headers
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
// Set the request method
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
// Set the request body
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
// Save the response headers
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) {
$len = strlen($header);
$header = explode(':', $header, 2);

if (count($header) < 2) { // ignore invalid headers
return $len;
}

$responseHeaders[strtolower(trim($header[0]))] = trim($header[1]);
return $len;
});
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 60);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$responseBody = curl_exec($ch); // Execute the curl session
$responseStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if (curl_errno($ch)) {
$errorMsg = curl_error($ch);
}
curl_close($ch);

if (isset($errorMsg)) {
throw new FetchException($errorMsg);
}
$response = new Response(
statusCode: $responseStatusCode,
headers: $responseHeaders,
body: $responseBody
);
return $response;
}
}
19 changes: 19 additions & 0 deletions src/FetchException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Utopia\Fetch;

class FetchException extends \Exception
{
/**
* Constructor
* @param string $message
*/
public function __construct($message)
{
parent::__construct($message); // Call the parent constructor
}
public function __toString()
{
return __CLASS__ . " {$this->message}\n"; // Return the class name, code and message
}
}
Loading