Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
9 changes: 3 additions & 6 deletions .github/workflows/phpunit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
pull_request:
push:
branches:
- "master"
- "master"

jobs:
phpunit:
Expand All @@ -17,9 +17,6 @@ jobs:
dependencies:
- "highest"
php-version:
- "7.2"
- "7.3"
- "7.4"
- "8.0"
- "8.1"
- "8.2"
Expand All @@ -28,7 +25,7 @@ jobs:
- "8.5"

include:
- php-version: '7.2'
- php-version: '8.0'
dependencies: "lowest"

steps:
Expand Down Expand Up @@ -57,4 +54,4 @@ jobs:

- name: "Tests (PHPUnit 10+)"
if: ${{ matrix.php-version >= '8.1' }}
run: "vendor/bin/phpunit"
run: "vendor/bin/phpunit"
9 changes: 5 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,21 @@
"classmap": ["tests/OmnipayTest.php"]
},
"require": {
"php": "^7.2|^8",
"php": "^8",
"php-http/client-implementation": "^1",
Comment on lines +45 to 46

Copilot AI Jan 29, 2026

Copy link

Choose a reason for hiding this comment

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

The PR description suggests this is not a big BC break, but composer.json now requires PHP ^8 (dropping ^7.2|^8). If this is intentional, it should be called out explicitly as a BC break (and aligned with the project’s release/UPGRADE notes). If not intentional, restore the previous PHP constraint.

Copilot uses AI. Check for mistakes.
"php-http/message": "^1.5",
"php-http/message-factory": "^1.1",
"php-http/discovery": "^1.14",
"symfony/http-foundation": "^2.1|^3|^4|^5|^6|^7|^8",
"moneyphp/money": "^3.1|^4.0.3"
"moneyphp/money": "^3.1|^4.0.3",
"psr/http-factory": "^1"
},
"require-dev": {
"omnipay/tests": "^4.1",
"php-http/mock-client": "^1.6",
"php-http/guzzle7-adapter": "^1",
"squizlabs/php_codesniffer": "^3.8.1",
"http-interop/http-factory-guzzle": "^1.1"
"http-interop/http-factory-guzzle": "^1.1",
"php-http/message-factory": "^1.1"
},
Comment on lines 53 to 60

Copilot AI Jan 29, 2026

Copy link

Choose a reason for hiding this comment

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

composer.json moves php-http/message-factory to require-dev, but production code (e.g., src/Common/Http/AbstractClient.php and src/Common/Http/Client.php) still references Http\Message\RequestFactory in imports and type declarations. If users install without require-dev, PHP will fatal when loading these classes. Either keep php-http/message-factory in require, or remove the hard type dependency (e.g., accept an untyped legacy factory and only interact with it behind an interface_exists/class_exists check).

Copilot uses AI. Check for mistakes.
"extra": {
"branch-alias": {
Expand Down
4 changes: 2 additions & 2 deletions src/Common/AbstractGateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

namespace Omnipay\Common;

use Omnipay\Common\Http\Client;
use Omnipay\Common\Http\ClientInterface;
use Omnipay\Common\Http\PsrClient;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request as HttpRequest;

Expand Down Expand Up @@ -329,7 +329,7 @@ protected function createRequest($class, array $parameters)
*/
protected function getDefaultHttpClient()
{
return new Client();
return new PsrClient();
}

/**
Expand Down
109 changes: 109 additions & 0 deletions src/Common/Http/AbstractClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

namespace Omnipay\Common\Http;

use Http\Discovery\Psr17FactoryDiscovery;
use Http\Discovery\Psr18ClientDiscovery;
use Http\Message\RequestFactory;
use Omnipay\Common\Http\Exception\NetworkException;
use Omnipay\Common\Http\Exception\RequestException;
use Psr\Http\Client\ClientInterface as HttpClientInterface;
use Psr\Http\Client\NetworkExceptionInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;

abstract class AbstractClient implements ClientInterface
{
/** @var HttpClientInterface */
private $httpClient;

/** @var RequestFactoryInterface|RequestFactory */
private $requestFactory;

/** @var StreamFactoryInterface|null */
private $streamFactory;

public function __construct(
$httpClient = null,
RequestFactoryInterface|RequestFactory $requestFactory = null,

Copilot AI Jan 29, 2026

Copy link

Choose a reason for hiding this comment

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

AbstractClient has a hard type reference to Http\Message\RequestFactory in the union type. If php-http/message-factory is not a required dependency, this will fatal at load time. Consider removing the union type (accept an untyped legacy factory) and only using the legacy path when the interface exists, or keep php-http/message-factory as a required dependency.

Suggested change
RequestFactoryInterface|RequestFactory $requestFactory = null,
RequestFactoryInterface|object $requestFactory = null,

Copilot uses AI. Check for mistakes.
?StreamFactoryInterface $streamFactory = null
) {
$this->httpClient = $httpClient ?: Psr18ClientDiscovery::find();
$this->requestFactory = $requestFactory ?: Psr17FactoryDiscovery::findRequestFactory();
$this->streamFactory = $streamFactory;
}

/**
* {@inheritDoc}
*/
public function request(
$method,
$uri,
array $headers = [],
$body = null,
$protocolVersion = '1.1'
) {
$request = $this->createRequest($method, $uri, $headers, $body, $protocolVersion);

return $this->sendRequest($request);
}

protected function createRequest(
$method,
$uri,
array $headers = [],
$body = null,
$protocolVersion = '1.1'
): RequestInterface {
if ($this->requestFactory instanceof RequestFactory) {
return $this->requestFactory->createRequest($method, $uri, $headers, $body, $protocolVersion);
}

$request = $this->requestFactory->createRequest($method, $uri)->withProtocolVersion($protocolVersion);

foreach ($headers as $name => $value) {
$request = $request->withHeader($name, $value);
}

if ($body !== '' && $body !== null) {
if (is_resource($body)) {
$stream = $this->getStreamFactory()->createStreamFromResource($body);
} elseif ($body instanceof StreamInterface) {
$stream = $body;
} elseif (is_scalar($body) || (is_object($body) && method_exists($body, '__toString'))) {
$stream = $this->getStreamFactory()->createStream((string)$body);
} else {
throw new \InvalidArgumentException('Invalid body type: ' . gettype($body));
}
$request = $request->withBody($stream);
}

return $request;
}

/**
* @param RequestInterface $request
* @return ResponseInterface
* @throws \Http\Client\Exception
*/
private function sendRequest(RequestInterface $request)
{
try {
return $this->httpClient->sendRequest($request);
} catch (NetworkExceptionInterface $networkException) {
throw new NetworkException($networkException->getMessage(), $request, $networkException);
} catch (\Exception $exception) {
throw new RequestException($exception->getMessage(), $request, $exception);
}
Comment on lines +87 to +100

Copilot AI Jan 29, 2026

Copy link

Choose a reason for hiding this comment

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

sendRequest()’s docblock says it throws \Http\Client\Exception, but the implementation catches and wraps exceptions into Omnipay’s NetworkException/RequestException (and also depends on PSR-18 NetworkExceptionInterface). Update the docblock to reflect the actual thrown exceptions to avoid misleading implementers.

Copilot uses AI. Check for mistakes.
}

private function getStreamFactory(): StreamFactoryInterface
{
$this->streamFactory = $this->streamFactory ?: Psr17FactoryDiscovery::findStreamFactory();

return $this->streamFactory;
}
}
66 changes: 5 additions & 61 deletions src/Common/Http/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,71 +2,15 @@

namespace Omnipay\Common\Http;

use Http\Client\HttpClient;
use Http\Discovery\HttpClientDiscovery;
use Http\Discovery\MessageFactoryDiscovery;
use Http\Message\RequestFactory;
use Omnipay\Common\Http\Exception\NetworkException;
use Omnipay\Common\Http\Exception\RequestException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;

class Client implements ClientInterface
/**
* @deprecated use Psr18Client instead

Copilot AI Jan 29, 2026

Copy link

Choose a reason for hiding this comment

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

The @deprecated tag says 'use Psr18Client instead', but this PR introduces Omnipay\Common\Http\PsrClient (and there is no Psr18Client class in src/). Update the deprecation message to reference the actual replacement to avoid misleading users.

Suggested change
* @deprecated use Psr18Client instead
* @deprecated use PsrClient instead

Copilot uses AI. Check for mistakes.
*/
class Client extends AbstractClient
{
/**
* The Http Client which implements `public function sendRequest(RequestInterface $request)`
* Note: Will be changed to PSR-18 when released
*
* @var HttpClient
*/
private $httpClient;

/**
* @var RequestFactory
*/
private $requestFactory;

public function __construct($httpClient = null, ?RequestFactory $requestFactory = null)
{
$this->httpClient = $httpClient ?: HttpClientDiscovery::find();
$this->requestFactory = $requestFactory ?: MessageFactoryDiscovery::find();
}

/**
* @param $method
* @param $uri
* @param array $headers
* @param string|array|resource|StreamInterface|null $body
Comment thread
barryvdh marked this conversation as resolved.
* @param string $protocolVersion
* @return ResponseInterface
* @throws \Http\Client\Exception
*/
public function request(
$method,
$uri,
array $headers = [],
$body = null,
$protocolVersion = '1.1'
) {
$request = $this->requestFactory->createRequest($method, $uri, $headers, $body, $protocolVersion);

return $this->sendRequest($request);
}

/**
* @param RequestInterface $request
* @return ResponseInterface
* @throws \Http\Client\Exception
*/
private function sendRequest(RequestInterface $request)
{
try {
return $this->httpClient->sendRequest($request);
} catch (\Http\Client\Exception\NetworkException $networkException) {
throw new NetworkException($networkException->getMessage(), $request, $networkException);
} catch (\Exception $exception) {
throw new RequestException($exception->getMessage(), $request, $exception);
}
parent::__construct($httpClient, $requestFactory);
Comment on lines 8 to +14

Copilot AI Jan 29, 2026

Copy link

Choose a reason for hiding this comment

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

Client.php imports and typehints Http\Message\RequestFactory. If php-http/message-factory is no longer a required dependency, this hard reference can trigger fatal errors when loading the class. Either keep the dependency in require or remove/loosen this typehint to avoid a mandatory runtime dependency on the abandoned package.

Copilot uses AI. Check for mistakes.
}
}
18 changes: 18 additions & 0 deletions src/Common/Http/PsrClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Omnipay\Common\Http;

use Psr\Http\Client\ClientInterface as HttpClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;

class PsrClient extends AbstractClient
{
public function __construct(
?HttpClientInterface $httpClient = null,
?RequestFactoryInterface $requestFactory = null,
?StreamFactoryInterface $streamFactory = null
) {
parent::__construct($httpClient, $requestFactory, $streamFactory);
}
}
1 change: 0 additions & 1 deletion src/Common/Message/AbstractRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
use Omnipay\Common\Exception\InvalidRequestException;
use Omnipay\Common\Exception\RuntimeException;
use Omnipay\Common\Helper;
use Omnipay\Common\Http\Client;
use Omnipay\Common\Http\ClientInterface;
use Omnipay\Common\ItemBag;
use Omnipay\Common\ParametersTrait;
Expand Down
7 changes: 4 additions & 3 deletions tests/Common/AbstractGatewayTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
namespace Omnipay\Common;

use Mockery as m;
use Omnipay\Common\Http\Client;
use Omnipay\Common\Http\ClientInterface;
use Omnipay\Common\Http\PsrClient;
use Omnipay\Common\Message\AbstractRequest;
use Omnipay\Tests\TestCase;
use Symfony\Component\HttpFoundation\ParameterBag;

class AbstractGatewayTest extends TestCase
{
Expand All @@ -22,7 +22,8 @@ public function setUp() : void
public function testConstruct()
{
$this->gateway = new AbstractGatewayTest_MockAbstractGateway;
$this->assertInstanceOf(Client::class, $this->gateway->getProtectedHttpClient());
$this->assertInstanceOf(ClientInterface::class, $this->gateway->getProtectedHttpClient());
$this->assertInstanceOf(PsrClient::class, $this->gateway->getProtectedHttpClient());
$this->assertInstanceOf('\Symfony\Component\HttpFoundation\Request', $this->gateway->getProtectedHttpRequest());
$this->assertSame(array(), $this->gateway->getParameters());
}
Expand Down
Loading