Skip to content

Jules refactoring from Google #31

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
129 changes: 113 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
Scraper
=======
# Scraper

![Packagist version](https://badgen.net/packagist/v/rem42/scraper)
![Packagist download](https://badgen.net/packagist/dt/rem42/scraper)
Expand All @@ -10,26 +9,124 @@ Scraper
![Codeclimate lines of code](https://badgen.net/codeclimate/loc/rem42/scraper)
![Codeclimate maintainability](https://badgen.net/codeclimate/maintainability/rem42/scraper)

Scraper can handle multiple request type and transform them into object in order to create some API.
Scraper is a PHP library designed to simplify making HTTP requests and processing responses, particularly for interacting with APIs or scraping web content. It provides a structured way to define different types of requests and map responses to PHP objects.

Installation
------------
## Installation

````bash
```bash
composer require rem42/scraper "^2.0"
````
```

Requirement
-----------
## Requirements

- PHP: 7.4
- PHP: ^7.4 || ^8.0

Usage
-----
## Usage

TODO
The core of the library is the `Scraper\Scraper\Client` class, which sends `Scraper\Scraper\Request\ScraperRequest` objects.

List of supported Scraper
-------------------------
### Basic Example

- WIP
Here's a conceptual example of how to make a request:

```php
<?php

require 'vendor/autoload.php';

use Scraper\Scraper\Client;
use Scraper\Scraper\Request\ScraperRequest; // Base request class
use Scraper\Scraper\Annotation\Method; // For annotation-based request definition
use Scraper\Scraper\Annotation\Path; // For annotation-based request definition
use Symfony\Component\HttpClient\HttpClient; // Example PSR-18 HTTP client

// 1. Create an HTTP client instance (PSR-18 compatible)
// You can use any client that implements Psr\Http\Client\ClientInterface
$httpClient = HttpClient::create();

// 2. Create the Scraper Client instance
$client = new Client($httpClient);

// 3. Define your custom Request class
// Requests are typically defined by extending ScraperRequest and using annotations
// or by implementing specific interfaces for request features.

/**
* @Method("GET")
* @Path("/users/{userId}")
*/
class GetUserRequest extends ScraperRequest
{
/**
* @var string
*/
public string $userId;

public function __construct(string $userId)
{
$this->userId = $userId;
}
}

// 4. Create an instance of your request
$request = new GetUserRequest('123');

// 5. Send the request
try {
// The response type depends on how the corresponding Api class (e.g., GetUserApi)
// processes the response. It could be an object, an array, or a string.
$response = $client->send($request);

// Process your response
var_dump($response);

} catch (\Scraper\Scraper\Exception\ScraperException $e) {
// Handle exceptions specific to the Scraper library
echo "Scraper Error: " . $e->getMessage() . "\n";
} catch (\Symfony\Contracts\HttpClient\Exception\ExceptionInterface $e) {
// Handle exceptions from the underlying HTTP client
echo "HTTP Client Error: " . $e->getMessage() . "\n";
}

?>
```

**Note:** For the above example to be fully functional, you would also need to define a corresponding `Api` class (e.g., `GetUserApi extends AbstractApi`) that handles the transformation of the HTTP response into the desired PHP object or data structure. The library uses reflection to find an `Api` class that matches the `Request` class name (e.g., `MyRequest` -> `MyApi`).

## Request Features

The library allows you to define various aspects of your HTTP request by having your `ScraperRequest` subclass implement specific interfaces:

* **`Scraper\Scraper\Request\RequestAuthBasic`**: Implement this to add Basic HTTP Authentication.
* Your request class will need a `getLogin()` and `getPassword()` method.
* The `Client` will use these to set the `auth_basic` option.
* **`Scraper\Scraper\Request\RequestAuthBearer`**: Implement this for Bearer Token Authentication.
* Your request class will need a `getBearer()` method.
* The `Client` will use this to set the `auth_bearer` option.
* **`Scraper\Scraper\Request\RequestBody`**: For sending a raw request body (e.g., form data as a string or resource).
* Your request class will need a `getBody()` method.
* The `Client` will use this to set the `body` option.
* **`Scraper\Scraper\Request\RequestBodyJson`**: For sending a JSON request body.
* Your request class will need a `getJson()` method (returning an array).
* The `Client` will use this to set the `json` option.
* **`Scraper\Scraper\Request\RequestHeaders`**: For adding custom request headers.
* Your request class will need a `getHeaders()` method (returning an associative array of headers).
* The `Client` will use this to set the `headers` option.
* **`Scraper\Scraper\Request\RequestQuery`**: For adding URL query parameters.
* Your request class will need a `getQuery()` method (returning an associative array of query parameters).
* The `Client` will use this to set the `query` option.
* **`Scraper\Scraper\Request\RequestException`**: Implement this to control whether an exception should be thrown on HTTP errors (4xx-5xx).
* Your request class will need an `isThrow()` method returning a boolean.

These interfaces are now classes that your custom request class should extend if you want to use their functionality. For example, `class MyRequest extends RequestAuthBasic { ... }`.
The `Client`'s `buildOptions` method checks if your request object is an instance of these classes and calls the relevant `getOptions()` method defined in the `App\Request\RequestOptionProvider` interface (which these classes implement) to construct the final HTTP client options.

## Contributing

Contributions are welcome! Please feel free to submit pull requests or open issues.
(TODO: Add more specific contribution guidelines if necessary)

## License

This library is licensed under the MIT License. (Assuming MIT based on common practice and `badgen.net/github/license/rem42/scraper` badge, but this should be confirmed with a `LICENSE` file).
```
50 changes: 29 additions & 21 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ public function send(ScraperRequest $request)
$annotation = ExtractAnnotation::extract($this->request);
$options = $this->buildOptions();

return $this->executeApiCall($annotation, $options);
}

/**
* @param ExtractAnnotation $annotation
* @param array<string, array<int|string,mixed>|resource|string> $options
* @return array<object>|bool|object|string
*/
private function executeApiCall(ExtractAnnotation $annotation, array $options)
{
$throw = $this->isThrow();

try {
Expand Down Expand Up @@ -80,36 +90,34 @@ private function getApiReflectionClass(): \ReflectionClass
return new \ReflectionClass($apiClass);
}

use App\Request\RequestOptionProvider;

/**
* @return array<string, array<int|string,mixed>|resource|string>
*/
private function buildOptions(): array
{
$options = [];

if ($this->request instanceof RequestAuthBearer) {
$options['auth_bearer'] = $this->request->getBearer();
}

if ($this->request instanceof RequestAuthBasic && false !== $this->request->isAuthBasic()) {
$options['auth_basic'] = $this->request->getAuthBasic();
}

if ($this->request instanceof RequestHeaders) {
$options['headers'] = $this->request->getHeaders();
}

if ($this->request instanceof RequestQuery) {
$options['query'] = $this->request->getQuery();
}

if ($this->request instanceof RequestBody) {
$options['body'] = $this->request->getBody();
// Define the list of RequestOptionProvider implementers
$optionProviders = [
RequestAuthBasic::class,
RequestAuthBearer::class,
RequestBody::class,
RequestBodyJson::class,
RequestHeaders::class,
RequestQuery::class,
];

foreach ($optionProviders as $providerClass) {
if ($this->request instanceof $providerClass) {
// Ensure $this->request is treated as RequestOptionProvider
if ($this->request instanceof RequestOptionProvider) {
$options = array_merge($options, $this->request->getOptions());
}
}
}

if ($this->request instanceof RequestBodyJson) {
$options['json'] = $this->request->getJson();
}
return $options;
}

Expand Down
46 changes: 44 additions & 2 deletions src/Request/RequestAuthBasic.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,50 @@
<?php

declare(strict_types=1);

namespace Scraper\Scraper\Request;

interface RequestAuthBasic
use App\Request\RequestOptionProvider;

class RequestAuthBasic implements RequestOptionProvider
{
public function getAuthBasic(): string;
public function __construct(
private readonly string $login,
private readonly string $password,
) {
}

public function getLogin(): string
{
return $this->login;
}

public function getPassword(): string
{
return $this->password;
}

// This method was used in the original buildOptions method.
// We need to ensure its logic is preserved.
// Assuming it should always return true if this object exists.
public function isAuthBasic(): bool
{
return true;
}

public function getAuthBasic(): array
{
return [$this->login, $this->password];
}

public function getOptions(): array
{
// Check isAuthBasic to maintain compatibility with original logic
if (false === $this->isAuthBasic()) {
return [];
}
return [
'auth_basic' => $this->getAuthBasic(),
];
}
}
23 changes: 21 additions & 2 deletions src/Request/RequestAuthBearer.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,27 @@
<?php

declare(strict_types=1);

namespace Scraper\Scraper\Request;

interface RequestAuthBearer
use App\Request\RequestOptionProvider;

class RequestAuthBearer implements RequestOptionProvider
{
public function getBearer(): string;
public function __construct(
private readonly string $bearerToken,
) {
}

public function getBearer(): string
{
return $this->bearerToken;
}

public function getOptions(): array
{
return [
'auth_bearer' => $this->getBearer(),
];
}
}
31 changes: 29 additions & 2 deletions src/Request/RequestBody.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,38 @@
<?php

declare(strict_types=1);

namespace Scraper\Scraper\Request;

interface RequestBody
use App\Request\RequestOptionProvider;

class RequestBody implements RequestOptionProvider
{
/**
* @var array<string, string>|resource|string
*/
private $body;

/**
* @param array<string, string>|resource|string $body
*/
public function __construct($body)
{
$this->body = $body;
}

/**
* @return array<string, string>|resource|string
*/
public function getBody();
public function getBody()
{
return $this->body;
}

public function getOptions(): array
{
return [
'body' => $this->getBody(),
];
}
}
31 changes: 29 additions & 2 deletions src/Request/RequestBodyJson.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,38 @@
<?php

declare(strict_types=1);

namespace Scraper\Scraper\Request;

interface RequestBodyJson
use App\Request\RequestOptionProvider;

class RequestBodyJson implements RequestOptionProvider
{
/**
* @var array<int|string, mixed>
*/
private array $json;

/**
* @param array<int|string, mixed> $json
*/
public function __construct(array $json)
{
$this->json = $json;
}

/**
* @return array<int|string, mixed>
*/
public function getJson(): array;
public function getJson(): array
{
return $this->json;
}

public function getOptions(): array
{
return [
'json' => $this->getJson(),
];
}
}
Loading
Loading