Add PSR HTTP Message Interfaces and Dependencies

- Implemented StreamInterface, UploadedFileInterface, and UriInterface as per PSR standards.
- Added getallheaders function to retrieve HTTP headers in a compatible manner.
- Included LICENSE files for ralouphie/getallheaders and symfony/deprecation-contracts.
- Introduced function for triggering deprecation notices in Symfony.
This commit is contained in:
2025-12-28 12:44:00 +01:00
parent cf600ae727
commit cd264483f8
410 changed files with 60841 additions and 16 deletions

View File

@@ -0,0 +1,33 @@
<?php
/**
* Interface ATFPP\AI_Translate\Services\HTTP\Contracts\Stream_Request_Handler
*
* @since 0.6.0
* @package ai-services
*/
namespace ATFPP\AI_Translate\Services\HTTP\Contracts;
use ATFPP\AI_Translate\Services\HTTP\Stream_Response;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Request;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Exception\Request_Exception;
/**
* Interface for a request handler that can stream responses.
*
* @since 0.6.0
*/
interface Stream_Request_Handler {
/**
* Sends an HTTP request and streams the response.
*
* @since 0.6.0
*
* @param Request $request The request to send.
* @return Stream_Response The stream response.
*
* @throws Request_Exception Thrown if the request fails.
*/
public function request_stream( Request $request ): Stream_Response;
}

View File

@@ -0,0 +1,28 @@
<?php
/**
* Interface ATFPP\AI_Translate\Services\HTTP\Contracts\With_Stream
*
* @since 0.3.0
* @package ai-services
*/
namespace ATFPP\AI_Translate\Services\HTTP\Contracts;
use Generator;
/**
* Interface for a class that contains a readable stream.
*
* @since 0.3.0
*/
interface With_Stream {
/**
* Returns a generator that reads individual chunks of decoded JSON data from the streamed response body.
*
* @since 0.3.0
*
* @return Generator The generator for the response stream.
*/
public function read_stream(): Generator;
}

View File

@@ -0,0 +1,104 @@
<?php
/**
* Class ATFPP\AI_Translate\Services\HTTP\HTTP_With_Streams
*
* @since 0.3.0
* @package ai-services
*/
namespace ATFPP\AI_Translate\Services\HTTP;
use ATFPP\AI_Translate\Services\HTTP\Contracts\Stream_Request_Handler;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Request;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Exception\Request_Exception;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\HTTP;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
/**
* Extended HTTP class with support for streaming responses.
*
* @since 0.3.0
*/
final class HTTP_With_Streams extends HTTP implements Stream_Request_Handler {
/**
* Guzzle client instance.
*
* Used for streaming requests, as WordPress Core's Requests API does not support this.
*
* @since 0.3.0
* @var Client
*/
private $guzzle;
/**
* Constructor.
*
* @since 0.3.0
*
* @param array<string, mixed> $default_options Optional. Default options to use for all requests. Default empty
* array.
*/
public function __construct( array $default_options = array() ) {
parent::__construct( $default_options );
$this->guzzle = new Client();
}
/**
* Sends an HTTP request and streams the response.
*
* @since 0.3.0
*
* @param Request $request The request to send.
* @return Stream_Response The stream response.
*
* @throws Request_Exception Thrown if the request fails.
*/
public function request_stream( Request $request ): Stream_Response {
$request_args = $this->build_request_args( $request );
$request_options = array(
'allow_redirects' => $request_args['options']['redirection'] > 0 ? array( 'max' => $request_args['options']['redirection'] ) : false,
'timeout' => (float) $request_args['options']['timeout'],
'stream' => true,
);
if ( isset( $request_args['data'] ) ) {
if ( in_array( $request_args['type'], array( Request::HEAD, Request::GET, Request::DELETE ), true ) ) {
$request_options['query'] = $request_args['data'];
} else {
if ( ! is_string( $request_args['data'] ) ) {
$request_args['data'] = http_build_query( $request_args['data'], '', '&' );
}
$request_options['body'] = $request_args['data'];
}
}
if ( isset( $request_args['headers'] ) ) {
if ( ! isset( $request_args['headers']['User-Agent'] ) ) {
$request_args['headers']['User-Agent'] = $request_args['options']['user-agent'];
}
} else {
$request_args['headers'] = array(
'User-Agent' => $request_args['options']['user-agent'],
);
}
$request_options['headers'] = $request_args['headers'];
try {
$response = $this->guzzle->request(
$request_args['type'],
$request_args['url'],
$request_options
);
} catch ( ClientException $e ) {
throw new Request_Exception(
htmlspecialchars( $e->getMessage() ) // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
);
}
$headers = $this->sanitize_headers( $response->getHeaders() );
return new Stream_Response( $response->getStatusCode(), $response->getBody(), $headers );
}
}

View File

@@ -0,0 +1,106 @@
<?php
/**
* Class ATFPP\AI_Translate\Services\HTTP\Stream_Response
*
* @since 0.3.0
* @package ai-services
*/
namespace ATFPP\AI_Translate\Services\HTTP;
use ATFPP\AI_Translate\Services\HTTP\Contracts\With_Stream;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Generic_Response;
use ATFPP\AI_Translate_Dependencies\Psr\Http\Message\StreamInterface;
use Generator;
use InvalidArgumentException;
use IteratorAggregate;
/**
* Class for a HTTP response that uses streaming.
*
* @since 0.3.0
*
* @implements IteratorAggregate<Generator>
*/
class Stream_Response extends Generic_Response implements With_Stream, IteratorAggregate {
/**
* The stream to read from.
*
* @since 0.3.0
* @var StreamInterface
*/
private $stream;
/**
* Constructor.
*
* @since 0.3.0
*
* @param int $status The HTTP status code received with the response.
* @param StreamInterface $stream The response body stream to read from.
* @param array<string, string> $headers The headers received with the response.
*
* @throws InvalidArgumentException Thrown if the $stream parameter has an invalid type.
*/
public function __construct( int $status, StreamInterface $stream, array $headers ) {
parent::__construct( $status, '', $headers );
$this->stream = $stream;
}
/**
* Returns a generator that reads individual chunks of decoded JSON data from the streamed response body.
*
* @since 0.3.0
*
* @return Generator The generator for the response stream.
*/
public function read_stream(): Generator {
while ( ! $this->stream->eof() ) {
$line = $this->read_line( $this->stream );
$data = json_decode( $line, true );
if ( ! $data ) {
continue;
}
yield $data;
}
}
/**
* Retrieves an iterator reading individual chunks of decoded JSON data from the streamed response body.
*
* @since 0.3.0
*
* @return Generator The iterator for the response stream.
*/
public function getIterator(): Generator {
return $this->read_stream();
}
/**
* Reads a line from the stream.
*
* @since 0.3.0
*
* @param StreamInterface $stream The stream to read from.
* @return string The line read from the stream.
*/
private function read_line( $stream ): string {
$buffer = '';
while ( ! $stream->eof() ) {
$buffer .= $stream->read( 1 );
if ( strlen( $buffer ) === 1 && '{' !== $buffer ) {
$buffer = '';
}
if ( json_decode( $buffer ) !== null ) {
return $buffer;
}
}
return rtrim( $buffer, ']' );
}
}