nolikein/curl-client

A centralized way to perform curl requests

Installs: 44

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Forks: 0

Type:librairy

1.0.0 2024-11-03 23:52 UTC

This package is auto-updated.

Last update: 2024-11-19 17:32:58 UTC


README

Packagist Version Pipeline Status Gitlab Code Coverage

Presentation

Here is a client to use Curl not only easiely, but also customized. You can create multiple instances of it from the Manager ! The Manager is perfect to be used with a Framework by retrieving data from configuration files then injecting result in a Service Container.

Installation

You need to use composer to install the librairy.

composer require nolikein/curl-client ^1.0

General use

The librairy has 3 main classes.

Curl which allows to manage curl native php functions but with POO, basically it's a low level management. Curl Client which allows to perform requests by using a "Curl" instance, basically it's a high level management. Curl Client Manager which allows to register instances of "Curl Client" (that have themselves instances of "Curl").

Using Curl instance

Here is the most basic way of use (with some secure things)

use Nolikein\CurlClient\Curl;
use Nolikein\CurlClient\Exception\CurlError;

try {
    $curl = new Curl('https://custom-url.org');

    /** @var \Nolikein\CurlClient\Response\Response $response */
    $response = $curl->request();
    echo $response->getContent() ?? '';
} catch(CurlError $e) {
    // report($e);
}

Now listen. Curl instance is just a wrapper of the php native \CurlHandle instance. In other words, it is what you get from using curl_init() So... what could we do with this instance? Many things !

use Nolikein\CurlClient\Curl;
use Nolikein\CurlClient\Exception\CurlError;
use Nolikein\CurlClient\Enums\HttpMethod;
use Nolikein\CurlClient\Dtos\Certificate;
use Nolikein\CurlClient\Dtos\Proxy;

$curl = new Curl();
$curl
    // Change url, nothing weird here
    ->withUrl('https://custom-url.org')
    // Set \CurlHandle headers
    ->withHeaders([
        'content-type' => ['text/html'],
    ])

    /**
     * Since the default http method is GET, select the http method
     * and give body arguments to the request.
     */
    // Set method as POST
    ->usePost()
    // With arguments
    ->usePost(data: ['a' => 'b'])

    // Set method as PATCH
    ->useRequestWithPostFields(
        method: HttpMethod::PATCH,
        data: ['a' => 'b']
    )

    // Set method as DELETE
    ->useRequest(HttpMethod::DELETE)

    /**
     * Added cool things
     */
    // Set a certificate (CURLOPT_SSLCERT)
    ->withCertificate(
        Certificate::createFromPath(__DIR__ . '/my/custom/path/cert.crt')
    )
    // Set a proxy (CURLOPT_PROXY and CURLOPT_HTTPPROXYTUNNEL)
    ->withproxy(
        new Proxy(
        address: 'http://address.org',
        tunnel: false // Using proxy tunneling. This is an optional argument
    )
    )
    // Attach file (example for POST http method)
    ->usePost([
        'my_file' => Curl::createFile(
            filepath: __DIR__ . 'here.txt',
            mime: 'text/txt',
            finalName: 'dummy.txt'
        )
    ])

    /**
     * \CurlHandle configuration and management
     */
    ->withConfiguration([
        CURLOPT_HEADER => true,
    ])
    // If you do not find what you're looking for, use manage() to handle the \CurlHandle instance.
    // Return statement if optional in the callback, manage() return the current instance.
    ->manage(function (CurlHandle $handle): CurlHandle {
        return $handle;
    })
    
    /**
     * Other methods
     */
    // Check the stream of the \CurlHandle instance is open
    ->isOpen()
    // Close the stream of the \CurlHandle instance
    ->close()
    // Perform a request then close the stream of the \CurlHandle instance.
    // Note, the Curl Client down there DO NOT use it. It is because of the nature of a "Service Container"
    // on recent Frameworks, we should keep an open stream otherwise consecutive requests won't be possible.
    ->requestAndClose()
;

Using Curl Client

Here is the most basic way of use (with some secure things)

use Nolikein\CurlClient\CurlClient;
use Nolikein\CurlClient\Exception\CurlException;

try {
    $client = new CurlClient();

    /** @var \Nolikein\CurlClient\Response\Response $response */
    $response = $client->get('https://custom-url.org');
    echo $response->getContent() ?? '';
} catch(CurlException $e) {
    // report($e);
}

Here is a list of cool other things you can do:

use Nolikein\CurlClient\Curl;
use Nolikein\CurlClient\CurlClient;
use Nolikein\CurlClient\Dtos\AuthToken;
use Nolikein\CurlClient\Dtos\Certificate;
use Nolikein\CurlClient\Dtos\Proxy;
use Nolikein\CurlClient\Enums\HttpMethod;

$client = new CurlClient();
$client
    /**
     * Use different http methods
     */
    ->get(url: '', headers: [], curlConfig: [])
    ->post(url: '', data: [], headers: [], curlConfig: [])
    ->put(url: '', data: [], headers: [], curlConfig: [])
    ->patch(url: '', data: [], headers: [], curlConfig: [])
    ->delete(url: '', headers: [], curlConfig: [])
    // You can choose your http method
    ->request(url: '', method: 'GET', data: [], headers: [], curlConfig: [])
    // You can also use the enumeration
    ->request(url: '', method: HttpMethod::GET, data: [], headers: [], curlConfig: [])

    /**
     * Header
     * They are just appended
     */
    // Set one
    ->withHeader('name', 'value1')
    ->withHeader('name', 'value2')
    // Set many
    ->withHeaders(['name' => 'value1'])
    ->withHeaders(['name' => ['value1', 'value2']])
    // Remove one
    ->withoutHeader('name')
    // Check existance
    ->hasHeader('name') //: bool
    // Get one
    ->getHeader('name') //: array<int, string>
    // Get all
    ->getHeaders() //: array<string, array<int, string>>

    /**
     * Files.
     * Files are a POST argument, a file postArgumentKey overide a passed post argument to request().
     */
    ->withFile(postArgumentKey: 'file', filepath: '/path/to/file.txt', mime: 'text/txt', finalName: 'destinationName.txt')
    ->hasFile('file') //: bool
    ->getFile('file') //: \CURLFile

    /**
     * Proxy
     */
    ->withProxy(new Proxy(
        address: 'http://address.org',
        tunnel: false // Using proxy tunneling. This is an optional argument
    ))
    ->isUsingProxy() //: bool

    /**
     * Auth token
     */
    ->withAuthToken(new AuthToken(type: 'Bearer', value: 'value'))
    ->isUsingAuthToken() //: bool

    /**
     * Certificate
     */
    ->withCertificate(Certificate::createFromPath(__DIR__ . '/my/custom/path/cert.crt'))
    ->isUsingCertificate() //: bool

    /**
     * Manage the curl instance
     */
    // Note: this also mean you can manage the \CurlHandle instance from the $curl->manage() method.
    ->manageCurl(function (Curl $curl): Curl {
        return $curl->manage(function (CurlHandle $handle): CurlHandle {
            return $handle;
        });
    })
;

Using Curl Manager

Here is how to use the manager. We add how much client instance we need, then we retrieve the client instance that interrest us.

use Nolikein\CurlClient\Curl;
use Nolikein\CurlClient\CurlClient;
use Nolikein\CurlClient\CurlClientManager;

$manager = new CurlClientManager();
$manager->setClient(
    identifier: 'base',
    client: $manager->makeClientFromConfiguration(
        key: 'base', // It is a name in case of error message
        clientData: [
            'curl' => Curl::class,
            'client' => CurlClient::class,
        ],
    )
);

// Get the client
$client = $manager->getClient('base');
// Do the things you want...
echo $client->get('http://address.org')->getContent();

But set a client is not just selecting a curl and a client, you can add certificate, proxy, auth token and even manage your curl client instance from it:

use Nolikein\CurlClient\CurlClientManager;

$manager = new CurlClientManager();
$manager->setClient('base', $manager->makeClientFromConfiguration(
    'base',
    [
        'curl' => Curl::class,
        'client' => CurlClient::class,
        'authentication' => [
            // Supported type: Bearer
            'type' => 'Bearer',
            'value' => 'false-token',
        ],
        'certificate' => [
            'path' => 'path/to/certificate.crt',
        ],
        'proxy' => [
            'address' => 'http://address.org',
            'tunnel' => true, // Using proxy tunneling. This is an optional argument
        ],
        // You can perform actions that apply for all your future requests with this client.
        'manage' => function (CurlClient $client): CurlClient {
            return $client;
        },
    ])
);

// Remove client
$manager->unsetClient('base');
// Unexisting client return null
$client = $manager->getClient('base'); //: null

Customization

Having custom requests

You can extends the Nolikein\CurlClient\CurlClient class to create your own client with your own requests.

use Nolikein\CurlClient\CurlClient;
use Nolikein\CurlClient\Response\Response;

class CustomClient extends CurlClient
{
    /**
     * Perform a ping request to my favorite api
     */
    public function ping(): Response
    {
        return $this->get('http://my-api.org/ping');
    }
}

// Instanciate the custom client
$client = new CustomClient();
$response = $client->ping();
// ...

The constructor might also be overide to add custom properties. Just do not forget to use parent::__construct() and pass the $curl argument.

You can also recreate your whole implementation of the "Curl Client" by implementing the Nolikein\CurlClient\CurlClientInterface interface which is compatible with the "Curl Manager".

Having custom instance of Curl

You can extends the Nolikein\CurlClient\Curl class to create your own client with your own requests.

use Nolikein\CurlClient\Curl;
use Nolikein\CurlClient\CurlClient;
use Nolikein\CurlClient\Response\Response;

class CustomCurl extends Curl
{
    /**
     * Perform a ping request to my favorite api
     */
    public function setSomething(): self
    {
        curl_setopt($this->handle, CURLOPT_ACCEPT_ENCODING, 'my accepted encoding');

        return $this;
    }
}

// Instanciate the client with our custom curl instance
$client = new CurlClient(
    curl: new CustomCurl()
);
$response = $client->get('http://test.org');
// ...

Testing

Run tests with coverage

displayed as html in tests/Coverage directory

docker compose run php ./vendor/bin/pest --coverage-html tests/Coverage
sudo chown $USER:$USER -R tests/Coverage

Creating your own tests

Everything is testable and for that, Mockable. You can be inspirated from existing content foundable in the gitlab repository.

We will take an example. Look about the CustomClient we created before. We will test the ping() method, but this means we should trigger some methods of the Curl instance. This is why we Mock it.

The following example use the Pest testing Framework:

use Nolikein\CurlClient\Curl;
use Nolikein\CurlClient\Response\Response;

it('can perform a ping request', function(): void {
    // We create the response that the Curl instance should return when request() is called.
    $fakedResponse = Response::make(
        content: json_encode(['services_available' => true]),
        status: 200,
        reason: 'OK',
        headers: [
            'content-type' => 'application/json'
        ],
    );

    // Create a Mock of the Curl instance that will fake the request
    /** @var \Mockery\MockInterface&Curl $curl */
    $curl = \Mockery::mock(Curl::class);
    $curl->shouldReceive('withUrl', 'withHeaders', 'withConfiguration')
        ->once()
        ->andReturnSelf()
    ;
    $curl->shouldReceive('when')->times(2)->andReturnSelf();
    $curl->shouldReceive('request')->andReturn($fakedResponse);

    // Instanciate the custom client with our mocked instance
    $client = new CustomClient(
        curl: $curl
    );
    $response = $client->ping();

    // We check the response correspondance
    expect($response->getContent())->toBe($fakedResponse->getContent());
    expect($response->getStatusCode())->toBe($fakedResponse->getStatusCode());
});

Licence

MIT

Contributing

You can open a ticket, make a pull request or contact me.