Skip to content
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

add PSR-18 http adapter #754

Merged
merged 20 commits into from
Apr 1, 2020
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
9 changes: 7 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,20 @@
],
"require": {
"php": "^7.1.3",
"symfony/event-dispatcher": "^4.3 || ^5.0",
"ext-json": "*"
"ext-json": "*",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"symfony/event-dispatcher": "^4.3 || ^5.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.16",
"guzzlehttp/guzzle": "^3.8 || ^6.2",
"nyholm/psr7": "^1.2",
"php-coveralls/php-coveralls": "^2.1",
"php-http/guzzle6-adapter": "^2.0",
"phpunit/phpunit": "^8.0",
"squizlabs/php_codesniffer": "^3.4",
"symfony/phpunit-bridge": "^5.0",
"zendframework/zend-http": "^2.8"
},
"minimum-stability": "dev",
Expand Down
8 changes: 8 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
backupStaticAttributes="false"
colors="true"
>
<php>
<env name="SYMFONY_DEPRECATIONS_HELPER" value="max[self]=999999"/>
</php>

<testsuites>
<testsuite name="Solarium">
<directory suffix="Test.php">tests</directory>
Expand All @@ -24,4 +28,8 @@
</whitelist>
</filter>

<listeners>
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener"/>
</listeners>

</phpunit>
8 changes: 8 additions & 0 deletions phpunit.xml.travis
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
backupStaticAttributes="false"
colors="true"
>
<php>
<env name="SYMFONY_DEPRECATIONS_HELPER" value="max[self]=999999"/>
</php>

<testsuites>
<testsuite name="Solarium">
<directory suffix="Test.php">tests</directory>
Expand All @@ -22,4 +26,8 @@
</whitelist>
</filter>

<listeners>
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener"/>
</listeners>

</phpunit>
2 changes: 2 additions & 0 deletions src/Core/Client/Adapter/Guzzle.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

/**
* Guzzle HTTP adapter.
*
* @deprecated Deprecated since Solarium 5.2 and will be removed in Solarium 6. Use Psr18Adapter instead.
*/
class Guzzle extends Configurable implements AdapterInterface
{
Expand Down
2 changes: 2 additions & 0 deletions src/Core/Client/Adapter/Guzzle3.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

/**
* Guzzle3 HTTP adapter.
*
* @deprecated Deprecated since Solarium 5.2 and will be removed in Solarium 6. Use Psr18Adapter instead.
*/
class Guzzle3 extends Configurable implements AdapterInterface
{
Expand Down
2 changes: 1 addition & 1 deletion src/Core/Client/Adapter/Http.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function execute(Request $request, Endpoint $endpoint): Response
$context = $this->createContext($request, $endpoint);
$uri = AdapterHelper::buildUri($request, $endpoint);

list($data, $headers) = $this->getData($uri, $context);
[$data, $headers] = $this->getData($uri, $context);

$this->check($data, $headers);

Expand Down
144 changes: 144 additions & 0 deletions src/Core/Client/Adapter/Psr18Adapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php

namespace Solarium\Core\Client\Adapter;

use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Solarium\Core\Client\Endpoint;
use Solarium\Core\Client\Request;
use Solarium\Core\Client\Response;
use Solarium\Core\Configurable;
use Solarium\Exception\HttpException;

final class Psr18Adapter extends Configurable implements AdapterInterface
Copy link
Member

Choose a reason for hiding this comment

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

What is the reason for "final" (or the advantage)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we should not open this class for extension. Its does not make sense anyway as everything is private except public function execute(Request $request, Endpoint $endpoint): Response.

So we should not even encourage people to extend it. They should use composition if they need to customize stuff.

Or what do you think?

Copy link
Member

Choose a reason for hiding this comment

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

OK. Let's leave it like this and wait until someone complains ;-)

{
/**
* @var ClientInterface
*/
private $httpClient;

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

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

public function __construct(
ClientInterface $httpClient,
RequestFactoryInterface $requestFactory,
StreamFactoryInterface $streamFactory
) {
parent::__construct();
$this->httpClient = $httpClient;
$this->requestFactory = $requestFactory;
$this->streamFactory = $streamFactory;
}

public function execute(Request $request, Endpoint $endpoint): Response
{
try {
return $this->createResponse($this->httpClient->sendRequest($this->createPsr7Request($request, $endpoint)));
} catch (ClientExceptionInterface $clientException) {
throw new HttpException("HTTP request failed, {$clientException}");
}
}

private function createPsr7Request(Request $request, Endpoint $endpoint): RequestInterface
{
$uri = AdapterHelper::buildUri($request, $endpoint);

$psr7Request = $this->requestFactory->createRequest(
$request->getMethod(),
$uri
);

if (null !== $body = $this->getRequestBody($request)) {
$psr7Request = $psr7Request->withBody($this->streamFactory->createStream($body));
}

foreach ($this->getRequestHeaders($request, $endpoint) as $name => $values) {
foreach ($values as $value) {
$psr7Request = $psr7Request->withAddedHeader($name, $value);
}
}

return $psr7Request;
}

private function createResponse(ResponseInterface $psr7Response): Response
{
$headerLines = [
sprintf(
'HTTP/%s %s %s',
$psr7Response->getProtocolVersion(),
$psr7Response->getStatusCode(),
$psr7Response->getReasonPhrase()
),
];

foreach ($psr7Response->getHeaders() as $name => $values) {
$headerLines[] = sprintf('%s: %s', $name, implode(', ', $values));
}

return new Response((string) $psr7Response->getBody(), $headerLines);
}

private function getRequestBody(Request $request): ?string
{
if (Request::METHOD_PUT == $request->getMethod()) {
return $request->getRawData();
}

if (Request::METHOD_POST !== $request->getMethod()) {
return null;
}

if ($request->getFileUpload()) {
return AdapterHelper::buildUploadBodyFromRequest($request);
}

return $request->getRawData();
}

private function getRequestHeaders(Request $request, Endpoint $endpoint): array
{
$headers = [];

foreach ($request->getHeaders() as $headerLine) {
[$header, $value] = explode(':', $headerLine);
if ($header = trim($header)) {
$headers[$header][] = $value;
}
}

if (!isset($headers['Content-Type'])) {
if (Request::METHOD_GET == $request->getMethod()) {
$headers['Content-Type'] = ['application/x-www-form-urlencoded; charset=utf-8'];
} else {
$headers['Content-Type'] = ['application/xml; charset=utf-8'];
}
}

if (!isset($headers['Authorization'])) {
// Try endpoint authentication first, fallback to request for backwards compatibility
$authData = $endpoint->getAuthentication();
if (empty($authData['username'])) {
$authData = $request->getAuthentication();
}

if (!empty($authData['username']) && !empty($authData['password'])) {
$headers['Authorization'] = ['Basic '.base64_encode($authData['username'].':'.$authData['password'])];
}
}

return $headers;
}
}
2 changes: 2 additions & 0 deletions src/Core/Client/Adapter/Zend2Http.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
* {@link http://framework.zend.com/manual/en/zend.http.html}
*
* To use this adapter you need to have the Zend Framework available (autoloading)
*
* @deprecated Deprecated since Solarium 5.2 and will be removed in Solarium 6. Use Psr18Adapter instead.
*/
class Zend2Http extends Configurable implements AdapterInterface
{
Expand Down
67 changes: 64 additions & 3 deletions src/Core/Client/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Solarium\Core\Client\Adapter\AdapterInterface;
use Solarium\Core\Client\Adapter\Curl;
use Solarium\Core\Configurable;
use Solarium\Core\ConfigurableInterface;
use Solarium\Core\Event\Events;
use Solarium\Core\Event\PostCreateQuery as PostCreateQueryEvent;
use Solarium\Core\Event\PostCreateRequest as PostCreateRequestEvent;
Expand Down Expand Up @@ -277,13 +278,32 @@ class Client extends Configurable implements ClientInterface
* If options are passed they will be merged with {@link $options} using
* the {@link setOptions()} method.
*
* If an EventDispatcher instance is provided this will be used instead of creating a new instance
* Deprecated behavior: If an EventDispatcher instance is provided this will be used instead of creating a new instance
*
* @param array $options
* @param AdapterInterface $adapter
* @param EventDispatcherInterface $eventDispatcher
* @param array|null $options
*/
public function __construct(array $options = null, EventDispatcherInterface $eventDispatcher = null)
public function __construct($adapter = null, EventDispatcherInterface $eventDispatcher = null, array $options = null)
{
// BC layer for changed constructor signature
if (is_array($adapter)) {
$options = $adapter;
} elseif ($adapter instanceof AdapterInterface) {
$this->adapter = $adapter;
unset($this->options['adapter']);
} elseif (null !== $adapter) {
throw new \TypeError('first argument must be null, array or AdapterInterface');
}

if (null === $this->adapter) {
@trigger_error('Not passing an instance of AdapterInterface as the first constructor argument is deprecated in Solarium 5.2 and will cause an error in Solarium 6.', E_USER_DEPRECATED);
}

if (null === $eventDispatcher) {
@trigger_error('Not passing an instance of EventDispatcherInterface as the second constructor argument is deprecated in Solarium 5.2 and will cause an error in Solarium 6.', E_USER_DEPRECATED);
}

$this->eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher);

parent::__construct($options);
Expand Down Expand Up @@ -519,6 +539,7 @@ public function setAdapter($adapter): ClientInterface
if (\is_string($adapter)) {
$this->adapter = null;
$this->setOption('adapter', $adapter);
@trigger_error('Passing a string to Client::setAdapter is deprecated since Solarium 5.2 and will cause an error in Solarium 6. Pass an instance of AdapterInterface instead.', E_USER_DEPRECATED);
} elseif ($adapter instanceof AdapterInterface) {
// forward options
$adapter->setOptions($this->getOption('adapteroptions'));
Expand All @@ -543,6 +564,10 @@ public function setAdapter($adapter): ClientInterface
*/
public function getAdapter(bool $autoload = true): AdapterInterface
{
if (func_num_args() > 0) {
@trigger_error('Passing a boolean argument to Client::getAdapter is deprecated since Solarium 5.2. The argument and lazy loading logic will be removed in Solarium 6.');
}

if (null === $this->adapter && $autoload) {
$this->createAdapter();
}
Expand Down Expand Up @@ -1396,6 +1421,8 @@ protected function init()
* {@link getAdapter()}
*
* @throws InvalidArgumentException
*
* @deprecated Deprecated since Solarium 5.2 and will be removed in Solarium 6. Pass an instance of AdapterInterface to the constructor instead.
*/
protected function createAdapter()
{
Expand All @@ -1410,4 +1437,38 @@ protected function createAdapter()
$adapter->setOptions($this->getOption('adapteroptions'));
$this->adapter = $adapter;
}

/**
* {@inheritdoc}
*/
public function setOptions($options, bool $overwrite = false): ConfigurableInterface
{
if (is_array($options)) {
if (array_key_exists('adapter', $options)) {
@trigger_error('The Client option "adapter" is deprecated since Solarium 5.2 and will be removed in Solarium 6. Pass an instance of AdapterInterface to the constructor instead.', E_USER_DEPRECATED);
}

if (array_key_exists('adapteroptions', $options)) {
@trigger_error('The Client option "adapteroptions" is deprecated since Solarium 5.2 and will be removed in Solarium 6. Pass an instance of AdapterInterface to the constructor instead.', E_USER_DEPRECATED);
}
}

return parent::setOptions($options, $overwrite);
}

/**
* {@inheritdoc}
*/
protected function setOption(string $name, $value): Configurable
{
if ('adapter' === $name) {
@trigger_error('The Client option "adapter" is deprecated since Solarium 5.2 and will be removed in Solarium 6. Pass an instance of AdapterInterface to the constructor instead.', E_USER_DEPRECATED);
}

if ('adapteroptions' === $name) {
@trigger_error('The Client option "adapteroptions" is deprecated since Solarium 5.2 and will be removed in Solarium 6. Pass an instance of AdapterInterface to the constructor instead.', E_USER_DEPRECATED);
}

return parent::setOption($name, $value);
}
}
Loading