Skip to content
Merged
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
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@
"require": {
"php": ">=7.4",
"ext-json": "*",
"nyholm/psr7": "^1.8",
"php-http/discovery": "^1.0",
"php-http/httplug": "^2.0",
"psr/event-dispatcher": "^1.0",
Comment on lines 26 to +28
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super nit-pick, but since you're sorting this differently now, can you make it alphabetically?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are. 😄

"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"psr/event-dispatcher": "^1.0",
"psr/http-message": "^1.0 || ^2.0",
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
},
Expand Down
82 changes: 80 additions & 2 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/AiClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class AiClient
/**
* @var string The version of the AI Client.
*/
public const VERSION = '1.0.0';
public const VERSION = '1.1.0';

/**
* @var ProviderRegistry|null The default provider registry instance.
Expand Down
95 changes: 95 additions & 0 deletions src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

declare(strict_types=1);

namespace WordPress\AiClient\Providers\Http\Abstracts;

use Http\Discovery\Psr18ClientDiscovery;
use Http\Discovery\Strategy\DiscoveryStrategy;
use Nyholm\Psr7\Factory\Psr17Factory;
use Psr\Http\Client\ClientInterface;

/**
* Abstract discovery strategy for HTTP client implementations.
*
* Provides a base for registering custom HTTP client implementations
* with HTTPlug's discovery mechanism. Subclasses must implement
* the createClient() method to provide their specific PSR-18
* HTTP client instance using the provided Psr17Factory.
*
* @since 1.1.0
*/
abstract class AbstractClientDiscoveryStrategy implements DiscoveryStrategy
{
/**
* Initializes and registers the discovery strategy.
*
* @since 1.1.0
*
* @return void
*/
public static function init(): void
{
if (!class_exists('\Http\Discovery\Psr18ClientDiscovery')) {
return;
}

Psr18ClientDiscovery::prependStrategy(static::class);
}

/**
* {@inheritDoc}
*
* @since 1.1.0
*
* @param string $type The type of discovery.
* @return array<array<string, mixed>> The discovery candidates.
*/
public static function getCandidates($type)
{
if (ClientInterface::class === $type) {
return [
[
'class' => static function () {
$psr17Factory = new Psr17Factory();
return static::createClient($psr17Factory);
},
],
];
}

$psr17Factories = [
'Psr\Http\Message\RequestFactoryInterface',
'Psr\Http\Message\ResponseFactoryInterface',
'Psr\Http\Message\ServerRequestFactoryInterface',
'Psr\Http\Message\StreamFactoryInterface',
'Psr\Http\Message\UploadedFileFactoryInterface',
'Psr\Http\Message\UriFactoryInterface',
];

if (in_array($type, $psr17Factories, true)) {
return [
[
'class' => Psr17Factory::class,
],
];
}

return [];
}

/**
* Creates an instance of the HTTP client.
*
* Subclasses must implement this method to return their specific
* PSR-18 HTTP client instance. The provided Psr17Factory implements
* all PSR-17 interfaces (RequestFactory, ResponseFactory, StreamFactory,
* etc.) and can be used to satisfy client constructor dependencies.
*
* @since 1.1.0
*
* @param Psr17Factory $psr17Factory The PSR-17 factory for creating HTTP messages.
* @return ClientInterface The PSR-18 HTTP client.
*/
abstract protected static function createClient(Psr17Factory $psr17Factory): ClientInterface;
}
73 changes: 73 additions & 0 deletions tests/mocks/MockClientDiscoveryStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

namespace WordPress\AiClient\Tests\mocks;

use Nyholm\Psr7\Factory\Psr17Factory;
use Psr\Http\Client\ClientInterface;
use WordPress\AiClient\Providers\Http\Abstracts\AbstractClientDiscoveryStrategy;

/**
* Mock implementation of AbstractClientDiscoveryStrategy for testing.
*/
class MockClientDiscoveryStrategy extends AbstractClientDiscoveryStrategy
{
/**
* @var ClientInterface|null The client to return from createClient().
*/
private static ?ClientInterface $clientToReturn = null;

/**
* @var Psr17Factory|null The last Psr17Factory received by createClient().
*/
private static ?Psr17Factory $lastPsr17Factory = null;

/**
* {@inheritDoc}
*
* @param Psr17Factory $psr17Factory The PSR-17 factory for creating HTTP messages.
* @return ClientInterface The PSR-18 HTTP client.
*/
protected static function createClient(Psr17Factory $psr17Factory): ClientInterface
{
self::$lastPsr17Factory = $psr17Factory;

/** @var ClientInterface $client */
$client = self::$clientToReturn;

return $client;
}

/**
* Sets the client instance that createClient() will return.
*
* @param ClientInterface $client The client to return.
* @return void
*/
public static function setClientToReturn(ClientInterface $client): void
{
self::$clientToReturn = $client;
}

/**
* Returns the last Psr17Factory passed to createClient().
*
* @return Psr17Factory|null The last factory instance, or null if not called.
*/
public static function getLastPsr17Factory(): ?Psr17Factory
{
return self::$lastPsr17Factory;
}

/**
* Resets the static state for test isolation.
*
* @return void
*/
public static function reset(): void
{
self::$clientToReturn = null;
self::$lastPsr17Factory = null;
}
}
Loading