Commit aadd4f2e authored by thePanz's avatar thePanz

WIP2

parent 1a959cc3
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
"symfony/event-dispatcher": "^2.7 || ^3.0 || ^4.0" "symfony/event-dispatcher": "^2.7 || ^3.0 || ^4.0"
}, },
"require-dev": { "require-dev": {
"guzzlehttp/psr7": "^1.4",
"php-http/curl-client": "^1.7", "php-http/curl-client": "^1.7",
"phpunit/phpunit": "^6.5" "phpunit/phpunit": "^6.5"
}, },
......
<?php
namespace Solarium\Client;
use Http\Client\Common\HttpClientPool;
use Http\Client\Common\Plugin;
use Http\Client\Common\PluginClient;
use Http\Client\HttpClient;
use Http\Discovery\HttpClientDiscovery;
use Http\Discovery\UriFactoryDiscovery;
use Http\Message\RequestFactory;
use Http\Message\UriFactory;
use Psr\Http\Message\UriInterface;
class HttpClientConfigurator
{
/**
* @var UriInterface[]
*/
private $endpoints = [];
/**
* @var UriFactory
*/
private $uriFactory;
/**
* @var HttpClient
*/
private $httpClient;
/**
* @var Plugin[]
*/
private $prependPlugins = [];
/**
* @var Plugin[]
*/
private $appendPlugins = [];
/**
* @param HttpClient|null $httpClient
* @param UriFactory|null $uriFactory
* @param RequestFactory|null $requestFactory
*/
public function __construct(HttpClient $httpClient = null, UriFactory $uriFactory = null, RequestFactory $requestFactory = null)
{
$this->httpClient = $httpClient ?? HttpClientDiscovery::find();
$this->uriFactory = $uriFactory ?? UriFactoryDiscovery::find();
}
/**
* Add a Solr endpoint.
*
* @param string $endpoint The Solr endpoint
*
* @return self
*/
public function addEndpoint(string $endpoint): self
{
$this->endpoints[] = $this->uriFactory->createUri($endpoint);
return $this;
}
/**
* Return a configured HttpClient.
*
* @todo: use use Http\Client\Common\HttpClientRouter for master-slave routing?
*
* @return HttpClient
*/
public function createConfiguredClient(): HttpClient
{
if (empty($this->endpoints)) {
throw new \RuntimeException('No endpoints defined!');
}
if (1 === count($this->endpoints)) {
return $this->createCondifuredClientForEndpoint(current($this->endpoints));
}
$clientPool = $this->getClientPool();
foreach ($this->endpoints as $uri) {
$clientPool->addHttpClient($this->createCondifuredClientForEndpoint($uri));
}
return $clientPool;
}
private function createCondifuredClientForEndpoint(UriInterface $uri): HttpClient
{
$plugins = $this->prependPlugins;
$plugins[] = new Plugin\AddHostPlugin($uri);
$plugins[] = new Plugin\HeaderDefaultsPlugin([
'User-Agent' => 'PHP Solarium (https://github.com/solariumphp/solarium)',
]);
$plugins = array_merge($plugins, $this->appendPlugins);
return new PluginClient($this->httpClient, $plugins);
}
/**
* @param Plugin[] $plugin
*
* @return self
*/
public function appendPlugin(Plugin ...$plugin): self
{
foreach ($plugin as $p) {
$this->appendPlugins[] = $p;
}
return $this;
}
/**
* @param Plugin[] $plugin
*
* @return self
*/
public function prependPlugin(Plugin ...$plugin): self
{
$plugin = array_reverse($plugin);
foreach ($plugin as $p) {
array_unshift($this->prependPlugins, $p);
}
return $this;
}
/**
* Returns a client pool, by using the configured strategy.
*
* @return HttpClientPool
*/
private function getClientPool(): HttpClientPool
{
// @todo: allow the strategy to be defined by the external
// - random
// - round robin
// - zookeeper?
// - HttpClientRouter?
return new HttpClientPool\RoundRobinClientPool();
}
}
\ No newline at end of file
<?php
declare(strict_types=1);
namespace Solarium\Client;
use Http\Discovery\MessageFactoryDiscovery;
use Http\Message\MultipartStream\MultipartStreamBuilder;
use Http\Message\RequestFactory;
use Psr\Http\Message\RequestInterface;
class RequestBuilder
{
/**
* @var RequestFactory
*/
private $requestFactory;
/**
* @var MultipartStreamBuilder
*/
private $multipartStreamBuilder;
/**
* @param RequestFactory $requestFactory
* @param MultipartStreamBuilder $multipartStreamBuilder
*/
public function __construct(
RequestFactory $requestFactory = null,
MultipartStreamBuilder $multipartStreamBuilder = null
) {
$this->requestFactory = $requestFactory ?: MessageFactoryDiscovery::find();
$this->multipartStreamBuilder = $multipartStreamBuilder ?: new MultipartStreamBuilder();
}
/**
* Creates a new PSR-7 request.
*
* @param string $method
* @param string $uri
* @param array $headers
* @param array|string|null $body Request body. If body is an array we will send a as multipart stream request.
* If array, each array *item* MUST look like:
* array (
* 'content' => string|resource|StreamInterface,
* 'name' => string,
* 'filename'=> string (optional)
* 'headers' => array (optinal) ['header-name' => 'header-value']
* )
*
* @return RequestInterface
*/
public function create(string $method, string $uri, array $headers = [], $body = null): RequestInterface
{
if (!is_array($body)) {
return $this->requestFactory->createRequest($method, $uri, $headers, $body);
}
foreach ($body as $item) {
$name = $item['name'];
$content = $item['content'];
unset($item['name']);
unset($item['content']);
$this->multipartStreamBuilder->addResource($name, $content, $item);
}
$multipartStream = $this->multipartStreamBuilder->build();
$boundary = $this->multipartStreamBuilder->getBoundary();
$headers['Content-Type'] = 'multipart/form-data; boundary='.$boundary;
$this->multipartStreamBuilder->reset();
return $this->requestFactory->createRequest($method, $uri, $headers, $multipartStream);
}
}
<?php
namespace Solarium\Client;
use Http\Client\HttpClient;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Solarium\Core\Event\Events;
use Solarium\Core\Query\QueryInterface;
use Solarium\Core\Event;
use Solarium\Core\Query\Result\ResultInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class SolrClient
{
/**
* @var HttpClient
*/
private $httpClient;
/**
* @var RequestBuilder
*/
private $requestBuilder;
/**
* @var EventDispatcherInterface
*/
private $eventDispatcher;
/**
* @var string
*/
private $defaultCore = null;
public function __construct(HttpClient $httpClient, RequestBuilder $requestBuilder, EventDispatcherInterface $eventDispatcher)
{
$this->httpClient = $httpClient;
$this->eventDispatcher = $eventDispatcher;
$this->requestBuilder = $requestBuilder;
}
/**
* Execute a query.
*
* @param QueryInterface $query
* @param string|null $solrCore
*
* @return ResultInterface
*/
public function execute(QueryInterface $query, string $solrCore = null): ResultInterface
{
$event = new Event\PreExecute($query);
$this->eventDispatcher->dispatch(Events::PRE_EXECUTE, $event);
if (null !== $event->getResult()) {
return $event->getResult();
}
$request = $this->createRequest($query);
$response = $this->executeRequest($request, $endpoint);
$result = $this->createResult($query, $response);
$this->eventDispatcher->dispatch(
Events::POST_EXECUTE,
new PostExecuteEvent($query, $result)
);
return $result;
}
/**
* Execute a request and return the response.
*
* @param Request $request
* @param Endpoint|string|null $endpoint
*
* @return Response
*/
public function executeRequest($request, $endpoint = null)
{
// load endpoint by string or by using the default one in case of a null value
if (!($endpoint instanceof Endpoint)) {
$endpoint = $this->getEndpoint($endpoint);
}
$event = new Event\PreExecuteRequest($request, $endpoint);
$this->eventDispatcher->dispatch(Events::PRE_EXECUTE_REQUEST, $event);
if (null !== $event->getResponse()) {
$response = $event->getResponse(); //a plugin result overrules the standard execution result
} else {
$response = $this->getAdapter()->execute($request, $endpoint);
}
$this->eventDispatcher->dispatch(
Events::POST_EXECUTE_REQUEST,
new PostExecuteRequestEvent($request, $endpoint, $response)
);
return $response;
}
/**
* Creates a request based on a query instance.
*
*
* @param QueryInterface $query
*
* @throws UnexpectedValueException
*
* @return Request
*/
public function createRequest(QueryInterface $query)
{
$event = new PreCreateRequestEvent($query);
$this->eventDispatcher->dispatch(Events::PRE_CREATE_REQUEST, $event);
if (null !== $event->getRequest()) {
return $event->getRequest();
}
$requestBuilder = $query->getRequestBuilder();
if (!$requestBuilder || !($requestBuilder instanceof RequestBuilderInterface)) {
throw new UnexpectedValueException('No requestbuilder returned by querytype: '.$query->getType());
}
$request = $requestBuilder->build($query);
$this->eventDispatcher->dispatch(
Events::POST_CREATE_REQUEST,
new PostCreateRequestEvent($query, $request)
);
return $request;
}
/**
* Execute a request and return the response.
*
* @param RequestInterface $request
*
* @throws \Exception
* @throws \Http\Client\Exception
*
* @return ResponseInterface
*/
public function executeRequest(RequestInterface $request): ResponseInterface
{
return $this->httpClient->sendRequest($request);
}
}
\ No newline at end of file
<?php
namespace Solarium\Client;
use Http\Client\Common\Exception\HttpClientNotFoundException;
use Http\Client\Common\HttpClientPool;
class ZookeeperHttpClientPool extends HttpClientPool {
protected function chooseHttpClient()
{
throw new HttpClientNotFoundException();
}
public function addHttpClient($client)
{
throw new \RuntimeException('ZookeeperHttpClientPool does not accept clients!');
}
}
\ No newline at end of file
<?php
namespace Solarium\Core\Client\Adapter;
use Solarium\Core\Client\Endpoint;
use Solarium\Core\Client\Request;
use Solarium\Core\Client\Response;
use Solarium\Core\ConfigurableInterface;
/**
* Interface for client adapters.
*
* The goal of an adapter is to accept a query, execute it and return the right
* result object. This is actually quite a complex task as it involves the
* handling of all Solr communication.
*
* The adapter structure allows for varying implementations of this task.
*
* Most adapters will use some sort of HTTP client. In that case the
* query request builders and query response parsers can be used to simplify
* HTTP communication.
*
* However an adapter may also implement all logic by itself if needed.
*/
interface AdapterInterface extends ConfigurableInterface
{
/**
* Execute a request.
*
* @param Request $request
* @param Endpoint $endpoint
*
* @return Response
*/
public function execute($request, $endpoint);
}
<?php
namespace Solarium\Core\Client\Adapter;
use Solarium\Core\Client\Endpoint;
use Solarium\Core\Client\Request;
use Solarium\Core\Client\Response;
use Solarium\Core\Configurable;
use Solarium\Exception\HttpException;
use Solarium\Exception\InvalidArgumentException;
use Solarium\Exception\RuntimeException;
/**
* cURL HTTP adapter.
*
* @author Intervals <info@myintervals.com>
*/
class Curl extends Configurable implements AdapterInterface
{
/**
* Execute a Solr request using the cURL Http.
*
* @param Request $request
* @param Endpoint $endpoint
*
* @return Response
*/
public function execute($request, $endpoint)
{
return $this->getData($request, $endpoint);
}
/**
* Get the response for a curl handle.
*
* @param resource $handle
* @param string $httpResponse
*
* @return Response
*/
public function getResponse($handle, $httpResponse)
{
// @codeCoverageIgnoreStart
if (false !== $httpResponse && null !== $httpResponse) {
$data = $httpResponse;
$info = curl_getinfo($handle);
$headers = [];
$headers[] = 'HTTP/1.1 '.$info['http_code'].' OK';
} else {
$headers = [];
$data = '';
}
$this->check($data, $headers, $handle);
curl_close($handle);
return new Response($data, $headers);
// @codeCoverageIgnoreEnd
}
/**
* Create curl handle for a request.
*
*
* @param Request $request
* @param Endpoint $endpoint
*
* @throws InvalidArgumentException
*
* @return resource
*/
public function createHandle($request, $endpoint)
{
// @codeCoverageIgnoreStart
$uri = $endpoint->getBaseUri().$request->getUri();
$method = $request->getMethod();
$options = $this->createOptions($request, $endpoint);
$handler = curl_init();
curl_setopt($handler, CURLOPT_URL, $uri);
curl_setopt($handler, CURLOPT_RETURNTRANSFER, true);
if (!(function_exists('ini_get') && ini_get('open_basedir'))) {
curl_setopt($handler, CURLOPT_FOLLOWLOCATION, true);
}
curl_setopt($handler, CURLOPT_TIMEOUT, $options['timeout']);
curl_setopt($handler, CURLOPT_CONNECTTIMEOUT, $options['timeout']);
if (null !== ($proxy = $this->getOption('proxy'))) {
curl_setopt($handler, CURLOPT_PROXY, $proxy);
}
if (!isset($options['headers']['Content-Type'])) {
if (Request::METHOD_GET == $method) {
$options['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
} else {
$options['headers']['Content-Type'] = 'application/xml; charset=utf-8';
}
}
// 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'])) {
curl_setopt($handler, CURLOPT_USERPWD, $authData['username'].':'.$authData['password']);
curl_setopt($handler, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
}
if (count($options['headers'])) {
$headers = [];
foreach ($options['headers'] as $key => $value) {
$headers[] = $key.': '.$value;
}
curl_setopt($handler, CURLOPT_HTTPHEADER, $headers);
}
if (Request::METHOD_POST == $method) {
curl_setopt($handler, CURLOPT_POST, true);
if ($request->getFileUpload()) {
if (version_compare(PHP_VERSION, '5.5.0') >= 0) {
$curlFile = curl_file_create($request->getFileUpload());
curl_setopt($handler, CURLOPT_POSTFIELDS, ['content' => $curlFile]);
} else {
curl_setopt($handler, CURLOPT_POSTFIELDS, ['content' => '@'.$request->getFileUpload()]);
}
} else {
curl_setopt($handler, CURLOPT_POSTFIELDS, $request->getRawData());
}
} elseif (Request::METHOD_GET == $method) {
curl_setopt($handler, CURLOPT_HTTPGET, true);
} elseif (Request::METHOD_HEAD == $method) {
curl_setopt($handler, CURLOPT_CUSTOMREQUEST, 'HEAD');
} else {
throw new InvalidArgumentException("unsupported method: $method");
}
return $handler;
// @codeCoverageIgnoreEnd
}
/**
* Check result of a request.
*
*
* @param string $data
* @param array $headers
* @param resource $handle
*
* @throws HttpException
*/
public function check($data, $headers, $handle)
{
// if there is no data and there are no headers it's a total failure,
// a connection to the host was impossible.
if (empty($data) && 0 == count($headers)) {
throw new HttpException('HTTP request failed, '.curl_error($handle));
}
}
/**
* Execute request.
*
* @param Request $request
* @param Endpoint $endpoint
*
* @return Response
*/
protected function getData($request, $endpoint)
{
// @codeCoverageIgnoreStart
$handle = $this->createHandle($request, $endpoint);
$httpResponse = curl_exec($handle);
return $this->getResponse($handle, $httpResponse);
// @codeCoverageIgnoreEnd
}
/**
* Initialization hook.
*
* Checks the availability of Curl_http
*
* @throws RuntimeException
*/
protected function init()
{
// @codeCoverageIgnoreStart
if (!function_exists('curl_init')) {
throw new RuntimeException('cURL is not available, install it to use the CurlHttp adapter');
}
parent::init();
// @codeCoverageIgnoreEnd
}
/**
* Create http request options from request.
*
* @param Request $request
* @param Endpoint $endpoint
*
* @return array
*/
protected function createOptions($request, $endpoint)
{
// @codeCoverageIgnoreStart
$options = [
'timeout' => $endpoint->getTimeout(),
];
foreach ($request->getHeaders() as $headerLine) {
list($header, $value) = explode(':', $headerLine);
if ($header = trim($header)) {
$options['headers'][$header] = trim($value);
}
}
return $options;
// @codeCoverageIgnoreEnd
}
}
<?php
namespace Solarium\Core\Client\Adapter;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\RequestOptions;
use Solarium\Core\Client\Endpoint;
use Solarium\Core\Client\Request;
use Solarium\Core\Client\Response;
use Solarium\Core\Configurable;
use Solarium\Exception\HttpException;
/**
* Guzzle HTTP adapter.
*/
class Guzzle extends Configurable implements AdapterInterface
{
/**
* The Guzzle HTTP client instance.
*
* @var GuzzleClient
*/
private $guzzleClient;
/**
* Execute a Solr request using the cURL Http.
*
* @param Request $request the incoming Solr request
* @param Endpoint $endpoint the configured Solr endpoint
*
* @throws HttpException thrown if solr request connot be made
*
* @return Response
*
*
* @codingStandardsIgnoreStart AdapterInterface does not declare type-hints
*/
public function execute($request, $endpoint)
{
//@codingStandardsIgnoreEnd
$requestOptions = [
RequestOptions::HEADERS => $this->getRequestHeaders($request),
RequestOptions::BODY => $this->getRequestBody($request),
RequestOptions::TIMEOUT => $endpoint->getTimeout(),
RequestOptions::CONNECT_TIMEOUT => $endpoint->getTimeout(),
];
// 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'])) {
$requestOptions[RequestOptions::AUTH] = [$authData['username'], $authData['password']];
}
try {
$guzzleResponse = $this->getGuzzleClient()->request(
$request->getMethod(),
$endpoint->getBaseUri().$request->getUri(),
$requestOptions
);
$responseHeaders = [
"HTTP/{$guzzleResponse->getProtocolVersion()} {$guzzleResponse->getStatusCode()} "
.$guzzleResponse->getReasonPhrase(),
];
foreach ($guzzleResponse->getHeaders() as $key => $value) {
$responseHeaders[] = "{$key}: ".implode(', ', $value);
}
return new Response((string) $guzzleResponse->getBody(), $responseHeaders);
} catch (\GuzzleHttp\Exception\RequestException $e) {
$error = $e->getMessage();
throw new HttpException("HTTP request failed, {$error}");
}
}
/**
* Gets the Guzzle HTTP client instance.
*
* @return GuzzleClient
*/
public function getGuzzleClient()
{
if (null === $this->guzzleClient) {
$this->guzzleClient = new GuzzleClient($this->options);
}
return $this->guzzleClient;
}
/**
* Helper method to create a request body suitable for a guzzle 3 request.
*
* @param Request $request the incoming solarium request
*
* @return null|resource|string
*/
private function getRequestBody(Request $request)
{
if (Request::METHOD_POST !== $request->getMethod()) {
return null;
}
if ($request->getFileUpload()) {
return fopen($request->getFileUpload(), 'r');
}
return $request->getRawData();
}
/**
* Helper method to extract headers from the incoming solarium request and put them in a format
* suitable for a guzzle 3 request.
*
* @param Request $request the incoming solarium request
*
* @return array
*/
private function getRequestHeaders(Request $request)
{
$headers = [];
foreach ($request->getHeaders() as $headerLine) {
list($header, $value) = explode(':', $headerLine);
if ($header = trim($header)) {
$headers[$header] = trim($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';
}
}
return $headers;
}
}
<?php
namespace Solarium\Core\Client\Adapter;
use Guzzle\Http\Client as GuzzleClient;
use Solarium\Core\Client\Endpoint;
use Solarium\Core\Client\Request;
use Solarium\Core\Client\Response;
use Solarium\Core\Configurable;
use Solarium\Exception\HttpException;
/**
* Guzzle3 HTTP adapter.
*/
class Guzzle3 extends Configurable implements AdapterInterface
{
/**
* The Guzzle HTTP client instance.
*
* @var GuzzleClient
*/
private $guzzleClient;
/**
* Execute a Solr request using the cURL Http.
*
* @param Request $request
* @param Endpoint $endpoint
*
* @return Response
*/
public function execute($request, $endpoint)
{
$guzzleRequest = $this->getGuzzleClient()->createRequest(
$request->getMethod(),
$endpoint->getBaseUri().$request->getUri(),
$this->getRequestHeaders($request),
$this->getRequestBody($request),
[
'timeout' => $endpoint->getTimeout(),
'connect_timeout' => $endpoint->getTimeout(),
]
);
// 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'])) {
$guzzleRequest->setAuth($authData['username'], $authData['password']);
}
try {
$this->getGuzzleClient()->send($guzzleRequest);
$guzzleResponse = $guzzleRequest->getResponse();
$responseHeaders = array_merge(
["HTTP/1.1 {$guzzleResponse->getStatusCode()} {$guzzleResponse->getReasonPhrase()}"],
$guzzleResponse->getHeaderLines()
);
return new Response($guzzleResponse->getBody(true), $responseHeaders);
} catch (\Guzzle\Http\Exception\RequestException $e) {
$error = $e->getMessage();
if ($e instanceof \Guzzle\Http\Exception\CurlException) {
$error = $e->getError();
}
throw new HttpException("HTTP request failed, {$error}");
}
}
/**
* Gets the Guzzle HTTP client instance.
*
* @return GuzzleClient
*/
public function getGuzzleClient()
{
if (null === $this->guzzleClient) {
$this->guzzleClient = new GuzzleClient(null, $this->options);
}
return $this->guzzleClient;
}
/**
* Helper method to create a request body suitable for a guzzle 3 request.
*
* @param Request $request the incoming solarium request
*
* @return null|resource|string
*/
private function getRequestBody(Request $request)
{
if (Request::METHOD_POST !== $request->getMethod()) {
return null;
}
if ($request->getFileUpload()) {
return fopen($request->getFileUpload(), 'r');
}
return $request->getRawData();
}
/**
* Helper method to extract headers from the incoming solarium request and put them in a format
* suitable for a guzzle 3 request.
*
* @param Request $request the incoming solarium request
*
* @return array
*/
private function getRequestHeaders(Request $request)
{
$headers = [];
foreach ($request->getHeaders() as $headerLine) {
list($header, $value) = explode(':', $headerLine);
if ($header = trim($header)) {
$headers[$header] = trim($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';
}
}
return $headers;
}
}
<?php
namespace Solarium\Core\Client\Adapter;
use Solarium\Core\Client\Endpoint;
use Solarium\Core\Client\Request;
use Solarium\Core\Client\Response;
use Solarium\Core\Configurable;
use Solarium\Exception\HttpException;
/**
* Basic HTTP adapter using a stream.
*/
class Http extends Configurable implements AdapterInterface
{
/**
* Handle Solr communication.
*
*
* @param Request $request
* @param Endpoint $endpoint
*
* @throws HttpException
*
* @return Response
*/
public function execute($request, $endpoint)
{
$context = $this->createContext($request, $endpoint);
$uri = $endpoint->getBaseUri().$request->getUri();
list($data, $headers) = $this->getData($uri, $context);
$this->check($data, $headers);
return new Response($data, $headers);
}
/**
* Check result of a request.
*
*
* @param string $data
* @param array $headers
*
* @throws HttpException
*/
public function check($data, $headers)
{
// if there is no data and there are no headers it's a total failure,
// a connection to the host was impossible.
if (false === $data && 0 == count($headers)) {
throw new HttpException('HTTP request failed');
}
}
/**
* Create a stream context for a request.
*
* @param Request $request
* @param Endpoint $endpoint
*
* @return resource
*/
public function createContext($request, $endpoint)
{
$method = $request->getMethod();
$context = stream_context_create(
['http' => [
'method' => $method,
'timeout' => $endpoint->getTimeout(),
],
]
);
if (Request::METHOD_POST == $method) {
if ($request->getFileUpload()) {
$boundary = '----------'.md5(time());
$CRLF = "\r\n";
$file = $request->getFileUpload();
// Add the proper boundary to the Content-Type header
$headers = $request->getHeaders();
// Remove the Content-Type header, because we will replace it with something else.
if (false !== ($key = array_search('Content-Type: multipart/form-data', $headers, true))) {
unset($headers[$key]);
}
$request->setHeaders($headers);
$request->addHeader("Content-Type: multipart/form-data; boundary={$boundary}");
$data = "--{$boundary}".$CRLF;
$data .= 'Content-Disposition: form-data; name="upload"; filename='.$file.$CRLF;
$data .= 'Content-Type: application/octet-stream'.$CRLF.$CRLF;
$data .= file_get_contents($file).$CRLF;
$data .= '--'.$boundary.'--';
$content_length = strlen($data);
$request->addHeader("Content-Length: $content_length\r\n");
stream_context_set_option(
$context,
'http',
'content',
$data
);
} else {
$data = $request->getRawData();
if (null !== $data) {
stream_context_set_option(
$context,
'http',
'content',
$data
);
$request->addHeader('Content-Type: text/xml; charset=UTF-8');
}
}
}
// 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'])) {
$request->addHeader(
'Authorization: Basic '.base64_encode($authData['username'].':'.$authData['password'])
);
}
$headers = $request->getHeaders();
if (count($headers) > 0) {
stream_context_set_option(
$context,
'http',
'header',
implode("\r\n", $headers)
);
}
return $context;
}
/**
* Execute request.
*
* @param string $uri
* @param resource $context
*
* @return array
*/
protected function getData($uri, $context)
{
// @codeCoverageIgnoreStart
$data = @file_get_contents($uri, false, $context);
$headers = [];
if (isset($http_response_header)) {
$headers = $http_response_header;
}
return [$data, $headers];
// @codeCoverageIgnoreEnd
}
}
<?php
namespace Solarium\Core\Client\Adapter;
use Solarium\Core\Client\Endpoint;
use Solarium\Core\Client\Request;
use Solarium\Core\Client\Response;
use Solarium\Core\Configurable;
use Solarium\Exception\HttpException;
use Solarium\Exception\InvalidArgumentException;
use Solarium\Exception\RuntimeException;
/**
* Pecl HTTP adapter.
*
* @author Gasol Wu <gasol.wu@gmail.com>
*/
class PeclHttp extends Configurable implements AdapterInterface
{
/**
* Execute a Solr request using the Pecl Http.
*
*
* @param Request $request
* @param Endpoint $endpoint
*
* @throws HttpException
*
* @return Response
*/
public function execute($request, $endpoint)
{
$httpRequest = $this->toHttpRequest($request, $endpoint);
try {
$httpMessage = $httpRequest->send();
} catch (\Exception $e) {
throw new HttpException($e->getMessage());
}
return new Response(
$httpMessage->getBody(),
$this->toRawHeaders($httpMessage)
);
}
/**
* adapt Request to HttpRequest.
*
* {@link http://us.php.net/manual/en/http.constants.php
* HTTP Predefined Constant}
*
* {@link http://us.php.net/manual/en/http.request.options.php
* HttpRequest options}
*
*
* @param Request $request
* @param Endpoint $endpoint
*
* @throws InvalidArgumentException
*
* @return \HttpRequest
*/
public function toHttpRequest($request, $endpoint)
{
$url = $endpoint->getBaseUri().$request->getUri();
$httpRequest = new \HttpRequest($url);
$headers = [];
foreach ($request->getHeaders() as $headerLine) {
list($header, $value) = explode(':', $headerLine);
if ($header = trim($header)) {
$headers[$header] = trim($value);
}
}
// 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']);
}
switch ($request->getMethod()) {
case Request::METHOD_GET:
$method = HTTP_METH_GET;
break;
case Request::METHOD_POST:
$method = HTTP_METH_POST;
if ($request->getFileUpload()) {
$httpRequest->addPostFile(
'content',
$request->getFileUpload(),
'application/octet-stream; charset=binary'
);
} else {
$httpRequest->setBody($request->getRawData());
if (!isset($headers['Content-Type'])) {
$headers['Content-Type'] = 'text/xml; charset=utf-8';
}
}
break;
case Request::METHOD_HEAD:
$method = HTTP_METH_HEAD;
break;
default:
throw new InvalidArgumentException(
'Unsupported method: '.$request->getMethod()
);
}
$httpRequest->setMethod($method);
$httpRequest->setOptions(
[
'timeout' => $endpoint->getTimeout(),
'connecttimeout' => $endpoint->getTimeout(),
'dns_cache_timeout' => $endpoint->getTimeout(),
]
);
$httpRequest->setHeaders($headers);
return $httpRequest;
}
/**
* Initialization hook.
*
* Checks the availability of pecl_http
*
* @throws RuntimeException
*/
protected function init()
{
// @codeCoverageIgnoreStart
if (!class_exists('HttpRequest', false)) {
throw new RuntimeException('Pecl_http is not available, install it to use the PeclHttp adapter');
}
parent::init();
// @codeCoverageIgnoreEnd
}
/**
* Convert key/value pair header to raw header.
*
* <code>
* //before
* $headers['Content-Type'] = 'text/plain';
*
* ...
*
* //after
* $headers[0] = 'Content-Type: text/plain';
* </code>
*
* @param $message \HttpMessage
*
* @return array
*/
protected function toRawHeaders($message)
{
$headers[] = 'HTTP/'.$message->getHttpVersion().' '.$message->getResponseCode().' '.$message->getResponseStatus();
foreach ($message->getHeaders() as $header => $value) {
$headers[] = "$header: $value";
}
return $headers;
}
}
<?php
namespace Solarium\Core\Client\Adapter;
use Solarium\Core\Client;
use Solarium\Core\Client\Endpoint;
use Solarium\Core\Client\Request;
use Solarium\Core\Client\Response;
use Solarium\Core\Configurable;
use Solarium\Exception\HttpException;
use Solarium\Exception\OutOfBoundsException;
/**
* Adapter that uses a ZF2 Zend\Http\Client.
*
* The Zend Framework HTTP client has many great features and has lots of
* configuration options. For more info see the manual at
* {@link http://framework.zend.com/manual/en/zend.http.html}
*
* To use this adapter you need to have the Zend Framework available (autoloading)
*/
class Zend2Http extends Configurable implements AdapterInterface
{
/**
* Zend Http instance for communication with Solr.
*
* @var \Zend\Http\Client
*/
protected $zendHttp;
/**
* @var int
*/
protected $timeout;
/**
* Set options.
*
* Overrides any existing values.
*
* If the options array has an 'options' entry it is forwarded to the
* Zend\Http\Client. See the Zend\Http\Client docs for the many config
* options available.
*
* The $options param should be an array or an object that has a toArray
* method, like Zend_Config
*
* @param array|object $options
* @param bool $overwrite
*
* @return self Provides fluent interface
*/
public function setOptions($options, $overwrite = false)
{
parent::setOptions($options, $overwrite);
// forward options to zendHttp instance
if (null !== $this->zendHttp) {
// forward timeout setting
$adapterOptions = [];
// forward adapter options if available
if (isset($this->options['options'])) {
$adapterOptions = array_merge($adapterOptions, $this->options['options']);
}
$this->zendHttp->setOptions($adapterOptions);
}
return $this;
}
/**
* Set the Zend\Http\Client instance.
*
* This method is optional, if you don't set a client it will be created
* upon first use, using default and/or custom options (the most common use
* case)
*
* @param \Zend\Http\Client $zendHttp
*
* @return self Provides fluent interface
*/
public function setZendHttp($zendHttp)
{
$this->zendHttp = $zendHttp;
return $this;
}
/**
* Get the Zend\Http\Client instance.
*
* If no instance is available yet it will be created automatically based on
* options.
*
* You can use this method to get a reference to the client instance to set
* options, get the last response and use many other features offered by the
* Zend\Http\Client API.
*
* @return \Zend\Http\Client
*/
public function getZendHttp()
{
if (null === $this->zendHttp) {
$options = [];
// forward zendhttp options
if (isset($this->options['options'])) {
$options = array_merge(
$options,
$this->options['options']
);
}
$this->zendHttp = new \Zend\Http\Client(null, $options);
}
return $this->zendHttp;
}
/**
* Execute a Solr request using the Zend\Http\Client instance.
*
* @param Request $request
* @param Endpoint $endpoint
*
* @throws HttpException
* @throws OutOfBoundsException
*
* @return Response
*/
public function execute($request, $endpoint)
{
$client = $this->getZendHttp();
$client->resetParameters();
switch ($request->getMethod()) {
case Request::METHOD_GET:
$client->setMethod('GET');
$client->setParameterGet($request->getParams());
break;
case Request::METHOD_POST:
$client->setMethod('POST');
if ($request->getFileUpload()) {
$this->prepareFileUpload($client, $request);
} else {
$client->setParameterGet($request->getParams());
$client->setRawBody($request->getRawData());
$request->addHeader('Content-Type: text/xml; charset=UTF-8');
}
break;
case Request::METHOD_HEAD:
$client->setMethod('HEAD');
$client->setParameterGet($request->getParams());
break;
default:
throw new OutOfBoundsException('Unsupported method: '.$request->getMethod());
break;
}
$client->setUri($endpoint->getBaseUri().$request->getHandler());
$client->setHeaders($request->getHeaders());
$this->timeout = $endpoint->getTimeout();
$response = $client->send();
return $this->prepareResponse(
$request,
$response
);
}
/**
* Prepare a solarium response from the given request and client
* response.
*
* @param Request $request
* @param \Zend\Http\Response $response
*
* @throws HttpException
*
* @return Response
*/
protected function prepareResponse($request, $response)
{
if ($response->isClientError()) {
throw new HttpException(
$response->getReasonPhrase(),
$response->getStatusCode()
);
}
if (Request::METHOD_HEAD == $request->getMethod()) {
$data = '';
} else {
$data = $response->getBody();
}
// this is used because in ZF2 status line isn't in the headers anymore
$headers = [$response->renderStatusLine()];
return new Response($data, $headers);
}
/**
* Prepare the client to send the file and params in request.
*
* @param \Zend\Http\Client $client
* @param Request $request
*/
protected function prepareFileUpload($client, $request)
{
$filename = $request->getFileUpload();
$client->setFileUpload(
'content',
'content',
file_get_contents($filename),
'application/octet-stream; charset=binary'
);
}
}
<?php
namespace Solarium\Core\Client\Adapter;
use Solarium\Core\Client;
use Solarium\Core\Client\Endpoint;
use Solarium\Core\Client\Request;
use Solarium\Core\Client\Response;
use Solarium\Core\Configurable;
use Solarium\Exception\HttpException;
use Solarium\Exception\OutOfBoundsException;
/**
* Adapter that uses a Zend_Http_Client.
*
* The Zend Framework HTTP client has many great features and has lots of
* configuration options. For more info see the manual at
* {@link http://framework.zend.com/manual/en/zend.http.html}
*
* To use this adapter you need to have the Zend Framework available (autoloading)
*/
class ZendHttp extends Configurable implements AdapterInterface
{
/**
* Zend Http instance for communication with Solr.
*
* @var \Zend_Http_Client
*/
protected $zendHttp;
/**
* @var int
*/
protected $timeout;
/**
* Set options.
*
* Overrides any existing values.
*
* If the options array has an 'options' entry it is forwarded to the
* Zend_Http_Client. See the Zend_Http_Clientdocs for the many config
* options available.
*
* The $options param should be an array or an object that has a toArray
* method, like Zend_Config
*
* @param array|object $options
* @param bool $overwrite
*
* @return self Provides fluent interface
*/
public function setOptions($options, $overwrite = false)
{
parent::setOptions($options, $overwrite);
// forward options to zendHttp instance
if (null !== $this->zendHttp) {
// forward timeout setting
$adapterOptions = [];
// forward adapter options if available
if (isset($this->options['options'])) {
$adapterOptions = array_merge($adapterOptions, $this->options['options']);
}
$this->zendHttp->setConfig($adapterOptions);
}
return $this;
}
/**
* Set the Zend_Http_Client instance.
*
* This method is optional, if you don't set a client it will be created
* upon first use, using default and/or custom options (the most common use
* case)
*
* @param \Zend_Http_Client $zendHttp
*
* @return self Provides fluent interface
*/
public function setZendHttp($zendHttp)
{
$this->zendHttp = $zendHttp;
return $this;
}
/**
* Get the Zend_Http_Client instance.
*
* If no instance is available yet it will be created automatically based on
* options.
*
* You can use this method to get a reference to the client instance to set
* options, get the last response and use many other features offered by the
* Zend_Http_Client API.
*
* @return \Zend_Http_Client
*/
public function getZendHttp()
{
if (null === $this->zendHttp) {
$options = [];
// forward zendhttp options
if (isset($this->options['options'])) {
$options = array_merge(
$options,
$this->options['options']
);
}
$this->zendHttp = new \Zend_Http_Client(null, $options);
}
return $this->zendHttp;
}
/**
* Execute a Solr request using the Zend_Http_Client instance.
*
*
* @param Request $request
* @param Endpoint $endpoint
*
* @throws HttpException
* @throws OutOfBoundsException
*
* @return Response
*/
public function execute($request, $endpoint)
{
$client = $this->getZendHttp();
$client->resetParameters();
switch ($request->getMethod()) {
case Request::METHOD_GET:
$client->setMethod(\Zend_Http_Client::GET);
$client->setParameterGet($request->getParams());
break;
case Request::METHOD_POST:
$client->setMethod(\Zend_Http_Client::POST);
if ($request->getFileUpload()) {
$this->prepareFileUpload($client, $request);
} else {
$client->setParameterGet($request->getParams());
$client->setRawData($request->getRawData());
$request->addHeader('Content-Type: text/xml; charset=UTF-8');
}
break;
case Request::METHOD_HEAD:
$client->setMethod(\Zend_Http_Client::HEAD);
$client->setParameterGet($request->getParams());
break;
default:
throw new OutOfBoundsException('Unsupported method: '.$request->getMethod());
break;
}
$client->setUri($endpoint->getBaseUri().$request->getHandler());
$client->setHeaders($request->getHeaders());
$this->timeout = $endpoint->getTimeout();
$response = $client->request();
return $this->prepareResponse(
$request,
$response
);
}
/**
* Prepare a solarium response from the given request and client
* response.
*
*
* @param Request $request
* @param \Zend_Http_Response $response
*
* @throws HttpException
*
* @return Response
*/
protected function prepareResponse($request, $response)
{
if ($response->isError()) {
throw new HttpException(
$response->getMessage(),
$response->getStatus()
);
}
if (Request::METHOD_HEAD == $request->getMethod()) {
$data = '';
} else {
$data = $response->getBody();
}
// this is used because getHeaders doesn't return the HTTP header...
$headers = explode("\n", $response->getHeadersAsString());
return new Response($data, $headers);
}
/**
* Prepare the client to send the file and params in request.
*
* @param \Zend_Http_Client $client
* @param Request $request
*/
protected function prepareFileUpload($client, $request)
{
$filename = $request->getFileUpload();
$client->setFileUpload(
'content',
'content',
file_get_contents($filename),
'application/octet-stream; charset=binary'
);
}
}
<?php
namespace Solarium\Core\Client;
use Solarium\Core\Configurable;
/**
* Class for describing an endpoint.
*/
class Endpoint extends Configurable
{
/**
* Default options.
*
* The defaults match a standard Solr example instance as distributed by
* the Apache Lucene Solr project.
*
* @var array
*/
protected $options = [
'scheme' => 'http',
'host' => '127.0.0.1',
'port' => 8983,
'path' => '/solr',
'core' => null,
'timeout' => 5,
];
/**
* Magic method enables a object to be transformed to a string.
*
* Get a summary showing significant variables in the object
* note: uri resource is decoded for readability
*
* @return string
*/
public function __toString()
{
$output = __CLASS__.'::__toString'."\n".'base uri: '.$this->getBaseUri()."\n".'host: '.$this->getHost()."\n".'port: '.$this->getPort()."\n".'path: '.$this->getPath()."\n".'core: '.$this->getCore()."\n".'timeout: '.$this->getTimeout()."\n".'authentication: '.print_r($this->getAuthentication(), 1);
return $output;
}
/**
* Get key value.
*
* @return string
*/
public function getKey()
{
return $this->getOption('key');
}
/**
* Set key value.
*
* @param string $value
*
* @return self Provides fluent interface
*/
public function setKey($value)
{
return $this->setOption('key', $value);
}
/**
* Set host option.
*
* @param string $host This can be a hostname or an IP address
*
* @return self Provides fluent interface
*/
public function setHost($host)
{
return $this->setOption('host', $host);
}
/**
* Get host option.
*
* @return string
*/
public function getHost()
{
return $this->getOption('host');
}
/**
* Set port option.
*
* @param int $port Common values are 80, 8080 and 8983
*
* @return self Provides fluent interface
*/
public function setPort($port)
{
return $this->setOption('port', $port);
}
/**
* Get port option.
*
* @return int
*/
public function getPort()
{
return $this->getOption('port');
}
/**
* Set path option.
*
* If the path has a trailing slash it will be removed.
*
* @param string $path
*
* @return self Provides fluent interface
*/
public function setPath($path)
{
if ('/' == substr($path, -1)) {
$path = substr($path, 0, -1);
}
return $this->setOption('path', $path);
}
/**
* Get path option.
*
* @return string
*/
public function getPath()
{
return $this->getOption('path');
}
/**
* Set core option.
*
* @param string $core
*
* @return self Provides fluent interface
*/
public function setCore($core)
{
return $this->setOption('core', $core);
}
/**
* Get core option.
*
* @return string
*/
public function getCore()
{
return $this->getOption('core');
}
/**
* Set timeout option.
*
* @param int $timeout
*
* @return self Provides fluent interface
*/
public function setTimeout($timeout)
{
return $this->setOption('timeout', $timeout);
}
/**
* Get timeout option.
*
* @return string
*/
public function getTimeout()
{
return $this->getOption('timeout');
}
/**
* Set scheme option.
*
* @param string $scheme
*
* @return self Provides fluent interface
*/
public function setScheme($scheme)
{
return $this->setOption('scheme', $scheme);
}
/**
* Get scheme option.
*
* @return string
*/
public function getScheme()
{
return $this->getOption('scheme');
}
/**
* Get the base url for all requests.
*
* Based on host, path, port and core options.
*
* @return string
*/
public function getBaseUri()
{
$uri = $this->getScheme().'://'.$this->getHost().':'.$this->getPort().$this->getPath().'/';
$core = $this->getCore();
if (!empty($core)) {
$uri .= $core.'/';
}
return $uri;
}
/**
* Set HTTP basic auth settings.
*
* If one or both values are NULL authentication will be disabled
*
* @param string $username
* @param string $password
*
* @return self Provides fluent interface
*/
public function setAuthentication($username, $password)
{
$this->setOption('username', $username);
$this->setOption('password', $password);
return $this;
}
/**
* Get HTTP basic auth settings.
*
* @return array
*/
public function getAuthentication()
{
return [
'username' => $this->getOption('username'),
'password' => $this->getOption('password'),
];
}
/**
* Initialization hook.
*
* In this case the path needs to be cleaned of trailing slashes.
*
* @see setPath()
*/
protected function init()
{
foreach ($this->options as $name => $value) {
switch ($name) {
case 'path':
$this->setPath($value);
break;
}
}
}
}
<?php
namespace Solarium\Core\Client;
use Solarium\Core\Configurable;
use Solarium\Exception\RuntimeException;
/**
* Class for describing a request.
*/
class Request extends Configurable
{
/**
* Request GET method.
*/
const METHOD_GET = 'GET';
/**
* Request POST method.
*/
const METHOD_POST = 'POST';
/**
* Request HEAD method.
*/
const METHOD_HEAD = 'HEAD';
/**
* Default options.
*
* @var array
*/
protected $options = [
'method' => self::METHOD_GET,
];
/**
* Request headers.
*/
protected $headers = [];
/**
* Request params.
*
* Multivalue params are supported using a multidimensional array:
* 'fq' => array('cat:1','published:1')
*
* @var array
*/
protected $params = [];
/**
* Raw POST data.
*
* @var string
*/
protected $rawData;
/**
* Magic method enables a object to be transformed to a string.
*
* Get a summary showing significant variables in the object
* note: uri resource is decoded for readability
*
* @return string
*/
public function __toString()
{
$output = __CLASS__.'::__toString'."\n".'method: '.$this->getMethod()."\n".'header: '.print_r($this->getHeaders(), 1).'authentication: '.print_r($this->getAuthentication(), 1).'resource: '.$this->getUri()."\n".'resource urldecoded: '.urldecode($this->getUri())."\n".'raw data: '.$this->getRawData()."\n".'file upload: '.$this->getFileUpload()."\n";
return $output;
}
/**
* Set request handler.
*
* @param string $handler
*
* @return self Provides fluent interface
*/
public function setHandler($handler)
{
$this->setOption('handler', $handler);
return $this;
}
/**
* Get request handler.
*
* @return string
*/
public function getHandler()
{
return $this->getOption('handler');
}
/**
* Set request method.
*
* Use one of the constants as value
*
* @param string $method
*
* @return self Provides fluent interface
*/
public function setMethod($method)
{
$this->setOption('method', $method);
return $this;
}
/**
* Get request method.
*
* @return string
*/
public function getMethod()
{
return $this->getOption('method');
}
/**
* Get a param value.
*
* @param string $key
*
* @return string|array
*/
public function getParam($key)
{
if (isset($this->params[$key])) {
return $this->params[$key];
}
}
/**
* Get all params.
*
* @return array
*/
public function getParams()
{
return $this->params;
}
/**
* Set request params.
*
* @param array $params
*
* @return self Provides fluent interface
*/
public function setParams($params)
{
$this->clearParams();
$this->addParams($params);
return $this;
}
/**
* Add a request param.
*
* If you add a request param that already exists the param will be converted into a multivalue param,
* unless you set the overwrite param to true.
*
* Empty params are not added to the request. If you want to empty a param disable it you should use
* remove param instead.
*
* @param string $key
* @param string|array $value
* @param bool $overwrite
*
* @return self Provides fluent interface
*/
public function addParam($key, $value, $overwrite = false)
{
if (null !== $value) {
if (!$overwrite && isset($this->params[$key])) {
if (!is_array($this->params[$key])) {
$this->params[$key] = [$this->params[$key]];
}
$this->params[$key][] = $value;
} else {
// not all solr handlers support 0/1 as boolean values...
if (true === $value) {
$value = 'true';
} elseif (false === $value) {
$value = 'false';
}
$this->params[$key] = $value;
}
}
return $this;
}
/**
* Add multiple params to the request.
*
* @param array $params
* @param bool $overwrite
*
* @return self Provides fluent interface
*/
public function addParams($params, $overwrite = false)
{
foreach ($params as $key => $value) {
$this->addParam($key, $value, $overwrite);
}
return $this;
}
/**
* Remove a param by key.
*
* @param string $key
*
* @return self Provides fluent interface
*/
public function removeParam($key)
{
if (isset($this->params[$key])) {
unset($this->params[$key]);
}
return $this;
}
/**
* Clear all request params.
*
* @return self Provides fluent interface
*/
public function clearParams()
{
$this->params = [];
return $this;
}
/**
* Get raw POST data.
*
* @return string
*/
public function getRawData()
{
return $this->rawData;
}
/**
* Set raw POST data.
*
* This string must be safely encoded.
*
* @param string $data
*
* @return self Provides fluent interface
*/
public function setRawData($data)
{
$this->rawData = $data;
return $this;
}
/**
* Get the file to upload via "multipart/form-data" POST request.
*
* @return string|null
*/
public function getFileUpload()
{
return $this->getOption('file');
}
/**
* Set the file to upload via "multipart/form-data" POST request.
*
*
* @param string $filename Name of file to upload
*
* @throws RuntimeException
*
* @return self
*/
public function setFileUpload($filename)
{
if (!is_file($filename) || !is_readable($filename)) {
throw new RuntimeException("Unable to read file '{$filename}' for upload");
}
$this->setOption('file', $filename);
return $this;
}
/**
* Get all request headers.
*
* @return array
*/
public function getHeaders()
{
return $this->headers;
}
/**
* Set request headers.
*
* @param array $headers
*
* @return self Provides fluent interface
*/
public function setHeaders($headers)
{
$this->clearHeaders();
$this->addHeaders($headers);
return $this;
}
/**
* Add a request header.
*
* @param string|array $value
*
* @return self Provides fluent interface
*/
public function addHeader($value)
{
$this->headers[] = $value;
return $this;
}
/**
* Add multiple headers to the request.
*
* @param array $headers
*
* @return self Provides fluent interface
*/
public function addHeaders($headers)
{
foreach ($headers as $header) {
$this->addHeader($header);
}
return $this;
}
/**
* Clear all request headers.
*
* @return self Provides fluent interface
*/
public function clearHeaders()
{
$this->headers = [];
return $this;
}
/**
* Get an URI for this request.
*
* @return string
*/
public function getUri()
{
return $this->getHandler().'?'.$this->getQueryString();
}
/**
* Get the query string for this request.
*
* @return string
*/
public function getQueryString()
{
$queryString = '';
if (count($this->params) > 0) {
$queryString = http_build_query($this->params, null, '&');
$queryString = preg_replace(
'/%5B(?:[0-9]|[1-9][0-9]+)%5D=/',
'=',
$queryString
);
}
return $queryString;
}
/**
* Set HTTP basic auth settings.
*
* If one or both values are NULL authentication will be disabled
*
* @param string $username
* @param string $password
*
* @return self Provides fluent interface
*/
public function setAuthentication($username, $password)
{
$this->setOption('username', $username);
$this->setOption('password', $password);
return $this;
}
/**
* Get HTTP basic auth settings.
*
* @return array
*/
public function getAuthentication()
{
return [
'username' => $this->getOption('username'),
'password' => $this->getOption('password'),
];
}
/**
* Initialization hook.
*/
protected function init()
{
foreach ($this->options as $name => $value) {
switch ($name) {
case 'rawdata':
$this->setRawData($value);
break;
case 'file':
$this->setFileUpload($value);
break;
case 'param':
$this->setParams($value);
break;
case 'header':
$this->setHeaders($value);
break;
case 'authentication':
if (isset($value['username']) && isset($value['password'])) {
$this->setAuthentication($value['username'], $value['password']);
}
}
}
}
}
<?php
namespace Solarium\Core\Client;
use Solarium\Exception\HttpException;
/**
* Class for describing a response.
*/
class Response
{
/**
* Headers.
*
* @var array
*/
protected $headers;
/**
* Body.
*
* @var string
*/
protected $body;
/**
* HTTP response code.
*
* @var int
*/
protected $statusCode;
/**
* HTTP response message.
*
* @var string
*/
protected $statusMessage;
/**
* Constructor.
*
* @param string $body
* @param array $headers
*/
public function __construct($body, $headers = [])
{
$this->body = $body;
$this->headers = $headers;
$this->setHeaders($headers);
}
/**
* Get body data.
*
* @return string
*/
public function getBody()
{
return $this->body;
}
/**
* Get response headers.
*
* @return array
*/
public function getHeaders()
{
return $this->headers;
}
/**
* Get status code.
*
* @return int
*/
public function getStatusCode()
{
return $this->statusCode;
}
/**
* Get status message.
*
* @return string
*/
public function getStatusMessage()
{
return $this->statusMessage;
}
/**
* Set headers.
*
*
* @param array $headers
*
* @throws HttpException
*/
public function setHeaders($headers)
{
$this->headers = $headers;
// get the status header
$statusHeader = null;
foreach ($headers as $header) {
if ('HTTP' == substr($header, 0, 4)) {
$statusHeader = $header;
break;
}
}
if (null === $statusHeader) {
throw new HttpException('No HTTP status found');
}
// parse header like "$statusInfo[1]" into code and message
// $statusInfo[1] = the HTTP response code
// $statusInfo[2] = the response message
$statusInfo = explode(' ', $statusHeader, 3);
$this->statusCode = (int) $statusInfo[1];
$this->statusMessage = $statusInfo[2];
}
}
...@@ -92,19 +92,14 @@ class Configurable implements ConfigurableInterface ...@@ -92,19 +92,14 @@ class Configurable implements ConfigurableInterface
* *
* @return mixed * @return mixed
*/ */
public function getOption($name) public function getOption(string $name)
{ {
if (isset($this->options[$name])) { if (isset($this->options[$name])) {
return $this->options[$name]; return $this->options[$name];
} }
} }
/** public function getOptions(): array
* Get all options.
*
* @return array
*/
public function getOptions()
{ {
return $this->options; return $this->options;
} }
...@@ -133,9 +128,9 @@ class Configurable implements ConfigurableInterface ...@@ -133,9 +128,9 @@ class Configurable implements ConfigurableInterface
* @param string $name * @param string $name
* @param mixed $value * @param mixed $value
* *
* @return self Provides fluent interface * @return self
*/ */
protected function setOption($name, $value) protected function setOption($name, $value): self
{ {
$this->options[$name] = $value; $this->options[$name] = $value;
......
...@@ -21,13 +21,13 @@ interface ConfigurableInterface ...@@ -21,13 +21,13 @@ interface ConfigurableInterface
* Zend Framework, but can also easily be implemented in any other object. * Zend Framework, but can also easily be implemented in any other object.
* *
* *
* @param array|\Zend_Config $options * @param array $options
* @param bool $overwrite True for overwriting existing options, false * @param bool $overwrite True for overwriting existing options, false
* for merging (new values overwrite old ones if needed) * for merging (new values overwrite old ones if needed)
* *
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
public function setOptions($options, $overwrite = false); public function setOptions(array $options, bool $overwrite = false);
/** /**
* Get an option value by name. * Get an option value by name.
...@@ -38,12 +38,12 @@ interface ConfigurableInterface ...@@ -38,12 +38,12 @@ interface ConfigurableInterface
* *
* @return mixed * @return mixed
*/ */
public function getOption($name); public function getOption(string $name);
/** /**
* Get all options. * Get all options.
* *
* @return array * @return array
*/ */
public function getOptions(); public function getOptions(): array;
} }
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
namespace Solarium\Core\Query; namespace Solarium\Core\Query;
use Solarium\Core\Configurable; use Solarium\Core\Configurable;
use Solarium\QueryType\Select\Query\Query;
/** /**
* Base class for all query types, not intended for direct usage. * Base class for all query types, not intended for direct usage.
...@@ -27,53 +28,32 @@ abstract class AbstractQuery extends Configurable implements QueryInterface ...@@ -27,53 +28,32 @@ abstract class AbstractQuery extends Configurable implements QueryInterface
*/ */
protected $params = []; protected $params = [];
/** public function requiresSolrCore(): bool
* Set handler option.
*
* @param string $handler
*
* @return self Provides fluent interface
*/
public function setHandler($handler)
{ {
return $this->setOption('handler', $handler); return false;
} }
/**
* Get handler option. public function setHandler(string $handler): QueryInterface
* {
* @return string $this->setOption('handler', $handler);
*/
public function getHandler() return $this;
}
public function getHandler(): string
{ {
return $this->getOption('handler'); return $this->getOption('handler');
} }
/** public function setResultClass(string $className): QueryInterface
* Set resultclass option.
*
* If you set a custom result class it must be available through autoloading
* or a manual require before calling this method. This is your
* responsibility.
*
* Also you need to make sure it extends the orginal result class of the
* query or has an identical API.
*
* @param string $classname
*
* @return self Provides fluent interface
*/
public function setResultClass($classname)
{ {
return $this->setOption('resultclass', $classname); $this->setOption('resultclass', $className);
return $this;
} }
/** public function getResultClass(): string
* Get resultclass option.
*
* @return string
*/
public function getResultClass()
{ {
return $this->getOption('resultclass'); return $this->getOption('resultclass');
} }
...@@ -129,7 +109,7 @@ abstract class AbstractQuery extends Configurable implements QueryInterface ...@@ -129,7 +109,7 @@ abstract class AbstractQuery extends Configurable implements QueryInterface
* *
* @return Helper * @return Helper
*/ */
public function getHelper() public function getHelper(): Helper
{ {
if (null === $this->helper) { if (null === $this->helper) {
$this->helper = new Helper($this); $this->helper = new Helper($this);
...@@ -147,21 +127,16 @@ abstract class AbstractQuery extends Configurable implements QueryInterface ...@@ -147,21 +127,16 @@ abstract class AbstractQuery extends Configurable implements QueryInterface
* @param string $name * @param string $name
* @param string $value * @param string $value
* *
* @return self Provides fluent interface * @return self
*/ */
public function addParam($name, $value) public function addParam(string $name, string $value): QueryInterface
{ {
$this->params[$name] = $value; $this->params[$name] = $value;
return $this; return $this;
} }
/** public function getParams(): array
* Get extra params.
*
* @return array
*/
public function getParams()
{ {
return $this->params; return $this->params;
} }
......
...@@ -9,42 +9,47 @@ use Solarium\Core\ConfigurableInterface; ...@@ -9,42 +9,47 @@ use Solarium\Core\ConfigurableInterface;
*/ */
interface QueryInterface extends ConfigurableInterface interface QueryInterface extends ConfigurableInterface
{ {
/**
* @return bool
*/
public function requiresSolrCore(): bool;
/** /**
* Get type for this query. * Get type for this query.
* *
* @return string * @return string
*/ */
public function getType(); public function getType(): string;
/** /**
* Get the requestbuilder class for this query. * Get the requestbuilder class for this query.
* *
* @return RequestBuilderInterface * @return RequestBuilderInterface
*/ */
public function getRequestBuilder(); public function getRequestBuilder(): RequestBuilderInterface;
/** /**
* Get the response parser class for this query. * Get the response parser class for this query.
* *
* @return ResponseParserInterface * @return ResponseParserInterface
*/ */
public function getResponseParser(); public function getResponseParser(): ResponseParserInterface;
/** /**
* Set handler option. * Set handler option.
* *
* @param string $handler * @param string $handler
* *
* @return self Provides fluent interface * @return self
*/ */
public function setHandler($handler); public function setHandler(string $handler): self;
/** /**
* Get handler option. * Get handler option.
* *
* @return string * @return string
*/ */
public function getHandler(); public function getHandler(): string;
/** /**
* Set resultclass option. * Set resultclass option.
...@@ -55,25 +60,25 @@ interface QueryInterface extends ConfigurableInterface ...@@ -55,25 +60,25 @@ interface QueryInterface extends ConfigurableInterface
* *
* Also you need to make sure this class implements the ResultInterface * Also you need to make sure this class implements the ResultInterface
* *
* @param string $classname * @param string $className
* *
* @return self Provides fluent interface * @return self
*/ */
public function setResultClass($classname); public function setResultClass(string $className): self;
/** /**
* Get resultclass option. * Get resultclass option.
* *
* @return string * @return string
*/ */
public function getResultClass(); public function getResultClass(): string;
/** /**
* Get a helper instance. * Get a helper instance.
* *
* @return Helper * @return Helper
*/ */
public function getHelper(); //public function getHelper(): Helper;
/** /**
* Add extra params to the request. * Add extra params to the request.
...@@ -84,14 +89,14 @@ interface QueryInterface extends ConfigurableInterface ...@@ -84,14 +89,14 @@ interface QueryInterface extends ConfigurableInterface
* @param string $name * @param string $name
* @param string $value * @param string $value
* *
* @return self Provides fluent interface * @return self
*/ */
public function addParam($name, $value); public function addParam(string $name, string $value): self;
/** /**
* Get extra params. * Get extra params.
* *
* @return array * @return array
*/ */
public function getParams(); public function getParams(): array;
} }
...@@ -18,6 +18,8 @@ use Solarium\Component\QueryTraits\StatsTrait; ...@@ -18,6 +18,8 @@ use Solarium\Component\QueryTraits\StatsTrait;
use Solarium\Component\QueryTraits\SuggesterTrait; use Solarium\Component\QueryTraits\SuggesterTrait;
use Solarium\Core\Client\Client; use Solarium\Core\Client\Client;
use Solarium\Core\Query\AbstractQuery; use Solarium\Core\Query\AbstractQuery;
use Solarium\Core\Query\RequestBuilderInterface;
use Solarium\Core\Query\ResponseParserInterface;
use Solarium\Exception\InvalidArgumentException; use Solarium\Exception\InvalidArgumentException;
use Solarium\QueryType\Select\RequestBuilder; use Solarium\QueryType\Select\RequestBuilder;
use Solarium\QueryType\Select\ResponseParser; use Solarium\QueryType\Select\ResponseParser;
...@@ -129,32 +131,17 @@ class Query extends AbstractQuery implements ComponentAwareQueryInterface ...@@ -129,32 +131,17 @@ class Query extends AbstractQuery implements ComponentAwareQueryInterface
parent::__construct($options); parent::__construct($options);
} }
/** public function getType(): string
* Get type for this query.
*
* @return string
*/
public function getType()
{ {
return Client::QUERY_SELECT; return Client::QUERY_SELECT;
} }
/** public function getRequestBuilder(): RequestBuilderInterface
* Get a requestbuilder for this query.
*
* @return RequestBuilder
*/
public function getRequestBuilder()
{ {
return new RequestBuilder(); return new RequestBuilder();
} }
/** public function getResponseParser(): ResponseParserInterface
* Get a response parser for this query.
*
* @return ResponseParser
*/
public function getResponseParser()
{ {
return new ResponseParser(); return new ResponseParser();
} }
...@@ -261,30 +248,6 @@ class Query extends AbstractQuery implements ComponentAwareQueryInterface ...@@ -261,30 +248,6 @@ class Query extends AbstractQuery implements ComponentAwareQueryInterface
return $this->getOption('start'); return $this->getOption('start');
} }
/**
* Set a custom resultclass.
*
* @param string $value classname
*
* @return self Provides fluent interface
*/
public function setResultClass($value)
{
return $this->setOption('resultclass', $value);
}
/**
* Get the current resultclass option.
*
* The value is a classname, not an instance
*
* @return string
*/
public function getResultClass()
{
return $this->getOption('resultclass');
}
/** /**
* Set a custom document class. * Set a custom document class.
* *
......
...@@ -3,14 +3,14 @@ ...@@ -3,14 +3,14 @@
namespace Solarium\QueryType\Select; namespace Solarium\QueryType\Select;
use Solarium\Core\Client\Request; use Solarium\Core\Client\Request;
use Solarium\Core\Query\AbstractRequestBuilder as BaseRequestBuilder; use Solarium\Core\Query\AbstractRequestBuilder;
use Solarium\Core\Query\QueryInterface; use Solarium\Core\Query\QueryInterface;
use Solarium\QueryType\Select\Query\Query as SelectQuery; use Solarium\QueryType\Select\Query\Query as SelectQuery;
/** /**
* Build a select request. * Build a select request.
*/ */
class RequestBuilder extends BaseRequestBuilder class RequestBuilder extends AbstractRequestBuilder
{ {
/** /**
* Build request for a select query. * Build request for a select query.
......
<?php
require_once(__DIR__.'/../vendor/autoload.php');
$configurator = (new \Solarium\Client\HttpClientConfigurator())
->addEndpoint('http://localhost:8983/solr');
$requestBuilder = new \Solarium\Client\RequestBuilder();
$eventDispatcher = new \Symfony\Component\EventDispatcher\EventDispatcher();
$solrClient = new \Solarium\Client\SolrClient($configurator->createConfiguredClient(), $requestBuilder, $eventDispatcher);
$selectQuery = new Solarium\QueryType\Select\Query\Query();
$solrClient->execute($selectQuery);
\ No newline at end of file
<?php
namespace Solarium\Tests\Core\Client\Adapter;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Solarium\Core\Client\Adapter\Curl;
use Solarium\Core\Client\Endpoint;
use Solarium\Core\Client\Request;
use Solarium\Exception\HttpException;
class CurlTest extends TestCase
{
/**
* @var Curl
*/
protected $adapter;
public function setUp()
{
if (!function_exists('curl_init')) {
$this->markTestSkipped('Curl not available, skipping Curl adapter tests');
}
$this->adapter = new Curl();
}
public function testCheck()
{
$data = 'data';
$headers = ['X-dummy: data'];
$handler = curl_init();
// this should be ok, no exception
$this->adapter->check($data, $headers, $handler);
$data = '';
$headers = [];
$this->expectException(HttpException::class);
$this->adapter->check($data, $headers, $handler);
curl_close($handler);
}
public function testExecute()
{
$headers = ['HTTP/1.0 200 OK'];
$body = 'data';
$data = [$body, $headers];
$request = new Request();
$endpoint = new Endpoint();
/** @var Curl|MockObject $mock */
$mock = $this->getMockBuilder(Curl::class)
->setMethods(['getData'])
->getMock();
$mock->expects($this->once())
->method('getData')
->with($request, $endpoint)
->will($this->returnValue($data));
$response = $mock->execute($request, $endpoint);
$this->assertSame($data, $response);
}
}
<?php
namespace Solarium\Tests\Core\Client\Adapter;
use Guzzle\Http\Message\Response;
use Guzzle\Plugin\Mock\MockPlugin;
use PHPUnit\Framework\TestCase;
use Solarium\Core\Client\Adapter\Guzzle3 as GuzzleAdapter;
use Solarium\Core\Client\Endpoint;
use Solarium\Core\Client\Request;
use Solarium\Exception\HttpException;
/**
* @coversDefaultClass \Solarium\Core\Client\Adapter\Guzzle3
* @covers ::<private>
* @covers ::getGuzzleClient
*/
final class Guzzle3Test extends TestCase
{
/**
* @var GuzzleAdapter
*/
private $adapter;
/**
* Prepare each test.
*/
public function setUp()
{
if (!class_exists('\\Guzzle\\Http\\Client')) {
$this->markTestSkipped('Guzzle 3 not installed');
}
$this->adapter = new GuzzleAdapter();
}
/**
* Verify basic behavior of execute().
*
* @covers ::execute
*/
public function testExecuteGet()
{
$guzzleResponse = $this->getValidResponse();
$plugin = new MockPlugin();
$plugin->addResponse($guzzleResponse);
$this->adapter->getGuzzleClient()->addSubscriber($plugin);
$request = new Request();
$request->setMethod(Request::METHOD_GET);
$request->addHeader('X-PHPUnit: request value');
$endpoint = new Endpoint();
$endpoint->setTimeout(10);
$response = $this->adapter->execute($request, $endpoint);
$this->assertSame('OK', $response->getStatusMessage());
$this->assertSame(200, $response->getStatusCode());
$this->assertSame(
[
'HTTP/1.1 200 OK',
'Content-Type: application/json',
'X-PHPUnit: response value',
],
$response->getHeaders()
);
$this->assertSame($guzzleResponse->getBody(true), $response->getBody());
$receivedRequests = $plugin->getReceivedRequests();
$this->assertCount(1, $receivedRequests);
$this->assertSame('GET', $receivedRequests[0]->getMethod());
$this->assertSame(
'request value',
(string) $receivedRequests[0]->getHeader('X-PHPUnit')
);
}
/**
* Verify execute() with request containing file.
*
* @covers ::execute
*/
public function testExecutePostWithFile()
{
$guzzleResponse = $this->getValidResponse();
$plugin = new MockPlugin();
$plugin->addResponse($guzzleResponse);
$this->adapter->getGuzzleClient()->addSubscriber($plugin);
$request = new Request();
$request->setMethod(Request::METHOD_POST);
$request->addHeader('X-PHPUnit: request value');
$request->setFileUpload(__FILE__);
$endpoint = new Endpoint();
$endpoint->setTimeout(10);
$response = $this->adapter->execute($request, $endpoint);
$this->assertSame('OK', $response->getStatusMessage());
$this->assertSame(200, $response->getStatusCode());
$this->assertSame(
[
'HTTP/1.1 200 OK',
'Content-Type: application/json',
'X-PHPUnit: response value',
],
$response->getHeaders()
);
$this->assertSame($guzzleResponse->getBody(true), $response->getBody());
$receivedRequests = $plugin->getReceivedRequests();
$this->assertCount(1, $receivedRequests);
$this->assertSame('POST', $receivedRequests[0]->getMethod());
$this->assertStringEqualsFile(__FILE__, (string) $receivedRequests[0]->getBody());
$this->assertSame(
'request value',
(string) $receivedRequests[0]->getHeader('X-PHPUnit')
);
}
/**
* Verify execute() with request containing raw body.
*
* @covers ::execute
*/
public function testExecutePostWithRawBody()
{
$guzzleResponse = $this->getValidResponse();
$plugin = new MockPlugin();
$plugin->addResponse($guzzleResponse);
$this->adapter->getGuzzleClient()->addSubscriber($plugin);
$request = new Request();
$request->setMethod(Request::METHOD_POST);
$request->addHeader('X-PHPUnit: request value');
$xml = '<root><parent><child>some data</child></parent></root>';
$request->setRawData($xml);
$endpoint = new Endpoint();
$endpoint->setTimeout(10);
$response = $this->adapter->execute($request, $endpoint);
$this->assertSame('OK', $response->getStatusMessage());
$this->assertSame(200, $response->getStatusCode());
$this->assertSame(
[
'HTTP/1.1 200 OK',
'Content-Type: application/json',
'X-PHPUnit: response value',
],
$response->getHeaders()
);
$this->assertSame($guzzleResponse->getBody(true), $response->getBody());
$receivedRequests = $plugin->getReceivedRequests();
$this->assertCount(1, $receivedRequests);
$this->assertSame('POST', $receivedRequests[0]->getMethod());
$this->assertSame($xml, (string) $receivedRequests[0]->getBody());
$this->assertSame(
'request value',
(string) $receivedRequests[0]->getHeader('X-PHPUnit')
);
$this->assertSame(
'application/xml; charset=utf-8',
(string) $receivedRequests[0]->getHeader('Content-Type')
);
}
/**
* Verify execute() with GET request containing Authentication.
*
* @covers ::execute
*/
public function testExecuteGetWithAuthentication()
{
$guzzleResponse = $this->getValidResponse();
$plugin = new MockPlugin();
$plugin->addResponse($guzzleResponse);
$this->adapter->getGuzzleClient()->addSubscriber($plugin);
$request = new Request();
$request->setMethod(Request::METHOD_GET);
$request->addHeader('X-PHPUnit: request value');
$request->setAuthentication('username', 's3cr3t');
$endpoint = new Endpoint();
$endpoint->setTimeout(10);
$response = $this->adapter->execute($request, $endpoint);
$this->assertSame('OK', $response->getStatusMessage());
$this->assertSame(200, $response->getStatusCode());
$this->assertSame(
[
'HTTP/1.1 200 OK',
'Content-Type: application/json',
'X-PHPUnit: response value',
],
$response->getHeaders()
);
$this->assertSame($guzzleResponse->getBody(true), $response->getBody());
$receivedRequests = $plugin->getReceivedRequests();
$this->assertCount(1, $receivedRequests);
$this->assertSame('GET', $receivedRequests[0]->getMethod());
$this->assertSame(
'request value',
(string) $receivedRequests[0]->getHeader('X-PHPUnit')
);
$this->assertSame(
'Basic '.base64_encode('username:s3cr3t'),
(string) $receivedRequests[0]->getHeader('Authorization')
);
}
/**
* Verify execute() with GET when guzzle throws an exception.
*
* @covers ::execute
*/
public function testExecuteRequestException()
{
$request = new Request();
$request->setMethod(Request::METHOD_GET);
$endpoint = new Endpoint(
[
'scheme' => 'silly', //invalid protocol
]
);
$this->expectException(HttpException::class);
$this->expectExceptionMessage('HTTP request failed');
$this->adapter->execute($request, $endpoint);
}
/**
* Helper method to create a valid Guzzle response.
*
* @return Response
*/
private function getValidResponse()
{
$body = json_encode(
[
'response' => [
'numFound' => 10,
'start' => 0,
'docs' => [
[
'id' => '58339e95d5200',
'author' => 'Gambardella, Matthew',
'title' => "XML Developer's Guide",
'genre' => 'Computer',
'price' => 44.95,
'published' => 970372800,
'description' => 'An in-depth look at creating applications with XML.',
],
],
],
]
);
$headers = ['Content-Type' => 'application/json', 'X-PHPUnit' => 'response value'];
return new Response(200, $headers, $body);
}
}
<?php
namespace Solarium\Tests\Core\Client\Adapter;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Response;
use PHPUnit\Framework\TestCase;
use Solarium\Core\Client\Adapter\Guzzle as GuzzleAdapter;
use Solarium\Core\Client\Endpoint;
use Solarium\Core\Client\Request;
use Solarium\Core\Exception;
use Solarium\Exception\HttpException;
/**
* @coversDefaultClass \Solarium\Core\Client\Adapter\Guzzle
* @covers ::<private>
* @covers ::getGuzzleClient
*/
final class GuzzleTest extends TestCase
{
/**
* Prepare each test.
*/
public function setUp()
{
if (!class_exists('\\GuzzleHttp\\Client')) {
$this->markTestSkipped('Guzzle 6 not installed');
}
}
/**
* Verify basic behavior of execute().
*
* @covers ::execute
*/
public function testExecuteGet()
{
$guzzleResponse = $this->getValidResponse();
$mockHandler = new MockHandler([$guzzleResponse]);
$container = [];
$history = Middleware::history($container);
$stack = HandlerStack::create($mockHandler);
$stack->push($history);
$adapter = new GuzzleAdapter(['handler' => $stack]);
$request = new Request();
$request->setMethod(Request::METHOD_GET);
$request->addHeader('X-PHPUnit: request value');
$endpoint = new Endpoint();
$endpoint->setTimeout(10);
$response = $adapter->execute($request, $endpoint);
$this->assertSame('OK', $response->getStatusMessage());
$this->assertSame(200, $response->getStatusCode());
$this->assertSame(
[
'HTTP/1.1 200 OK',
'Content-Type: application/json',
'X-PHPUnit: response value',
],
$response->getHeaders()
);
$this->assertSame((string) $guzzleResponse->getBody(), $response->getBody());
$this->assertCount(1, $container);
$this->assertSame('GET', $container[0]['request']->getMethod());
$this->assertSame('request value', $container[0]['request']->getHeaderline('X-PHPUnit'));
}
/**
* Verify execute() with request containing file.
*
* @covers ::execute
*/
public function testExecutePostWithFile()
{
$guzzleResponse = $this->getValidResponse();
$mockHandler = new MockHandler([$guzzleResponse]);
$container = [];
$history = Middleware::history($container);
$stack = HandlerStack::create($mockHandler);
$stack->push($history);
$adapter = new GuzzleAdapter(['handler' => $stack]);
$request = new Request();
$request->setMethod(Request::METHOD_POST);
$request->addHeader('X-PHPUnit: request value');
$request->setFileUpload(__FILE__);
$endpoint = new Endpoint();
$endpoint->setTimeout(10);
$response = $adapter->execute($request, $endpoint);
$this->assertSame('OK', $response->getStatusMessage());
$this->assertSame(200, $response->getStatusCode());
$this->assertSame(
[
'HTTP/1.1 200 OK',
'Content-Type: application/json',
'X-PHPUnit: response value',
],
$response->getHeaders()
);
$this->assertSame((string) $guzzleResponse->getBody(), $response->getBody());
$this->assertCount(1, $container);
$this->assertSame('POST', $container[0]['request']->getMethod());
$this->assertSame('request value', $container[0]['request']->getHeaderline('X-PHPUnit'));
$this->assertStringEqualsFile(__FILE__, (string) $container[0]['request']->getBody());
}
/**
* Verify execute() with request containing raw body.
*
* @covers ::execute
*/
public function testExecutePostWithRawBody()
{
$guzzleResponse = $this->getValidResponse();
$mockHandler = new MockHandler([$guzzleResponse]);
$container = [];
$history = Middleware::history($container);
$stack = HandlerStack::create($mockHandler);
$stack->push($history);
$adapter = new GuzzleAdapter(['handler' => $stack]);
$request = new Request();
$request->setMethod(Request::METHOD_POST);
$request->addHeader('X-PHPUnit: request value');
$xml = '<root><parent><child>some data</child></parent></root>';
$request->setRawData($xml);
$endpoint = new Endpoint();
$endpoint->setTimeout(10);
$response = $adapter->execute($request, $endpoint);
$this->assertSame('OK', $response->getStatusMessage());
$this->assertSame(200, $response->getStatusCode());
$this->assertSame(
[
'HTTP/1.1 200 OK',
'Content-Type: application/json',
'X-PHPUnit: response value',
],
$response->getHeaders()
);
$this->assertSame((string) $guzzleResponse->getBody(), $response->getBody());
$this->assertCount(1, $container);
$this->assertSame('POST', $container[0]['request']->getMethod());
$this->assertSame('request value', $container[0]['request']->getHeaderline('X-PHPUnit'));
$this->assertSame('application/xml; charset=utf-8', $container[0]['request']->getHeaderline('Content-Type'));
$this->assertSame($xml, (string) $container[0]['request']->getBody());
}
/**
* Verify execute() with GET request containing Authentication.
*
* @test
* @covers ::execute
*/
public function executeGetWithAuthentication()
{
$guzzleResponse = $this->getValidResponse();
$mockHandler = new MockHandler([$guzzleResponse]);
$container = [];
$history = Middleware::history($container);
$stack = HandlerStack::create($mockHandler);
$stack->push($history);
$adapter = new GuzzleAdapter(['handler' => $stack]);
$request = new Request();
$request->setMethod(Request::METHOD_GET);
$request->addHeader('X-PHPUnit: request value');
$request->setAuthentication('username', 's3cr3t');
$endpoint = new Endpoint();
$endpoint->setTimeout(10);
$response = $adapter->execute($request, $endpoint);
$this->assertSame('OK', $response->getStatusMessage());
$this->assertSame(200, $response->getStatusCode());
$this->assertSame(
[
'HTTP/1.1 200 OK',
'Content-Type: application/json',
'X-PHPUnit: response value',
],
$response->getHeaders()
);
$this->assertSame((string) $guzzleResponse->getBody(), $response->getBody());
$this->assertCount(1, $container);
$this->assertSame('GET', $container[0]['request']->getMethod());
$this->assertSame('request value', $container[0]['request']->getHeaderline('X-PHPUnit'));
$this->assertSame(
'Basic '.base64_encode('username:s3cr3t'),
$container[0]['request']->getHeaderLine('Authorization')
);
}
/**
* Verify execute() with GET when guzzle throws an exception.
*
* @covers ::execute
*/
public function testExecuteRequestException()
{
$adapter = new GuzzleAdapter();
$request = new Request();
$request->setMethod(Request::METHOD_GET);
$endpoint = new Endpoint(
[
'scheme' => 'silly', //invalid protocol
]
);
$endpoint->setTimeout(10);
$this->expectException(HttpException::class);
$this->expectExceptionMessage('HTTP request failed');
$adapter->execute($request, $endpoint);
}
/**
* Helper method to create a valid Guzzle response.
*
* @return Response
*/
private function getValidResponse()
{
$body = json_encode(
[
'response' => [
'numFound' => 10,
'start' => 0,
'docs' => [
[
'id' => '58339e95d5200',
'author' => 'Gambardella, Matthew',
'title' => "XML Developer's Guide",
'genre' => 'Computer',
'price' => 44.95,
'published' => 970372800,
'description' => 'An in-depth look at creating applications with XML.',
],
],
],
]
);
$headers = ['Content-Type' => 'application/json', 'X-PHPUnit' => 'response value'];
return new Response(200, $headers, $body);
}
}
<?php
namespace Solarium\Tests\Core\Client\Adapter;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Solarium\Core\Client\Adapter\Http;
use Solarium\Core\Client\Endpoint;
use Solarium\Core\Client\Request;
use Solarium\Exception\HttpException;
class HttpTest extends TestCase
{
/**
* @var Http
*/
protected $adapter;
public function setUp()
{
$this->adapter = new Http();
}
public function testExecute()
{
$data = 'test123';
$request = new Request();
$request->setMethod(Request::METHOD_GET);
$endpoint = new Endpoint();
/** @var Http|MockObject $mock */
$mock = $this->getMockBuilder(Http::class)
->setMethods(['getData', 'check'])
->getMock();
$mock->expects($this->once())
->method('getData')
->with($this->equalTo('http://127.0.0.1:8983/solr/?'), $this->isType('resource'))
->will($this->returnValue([$data, ['HTTP 1.1 200 OK']]));
$mock->execute($request, $endpoint);
}
public function testExecuteErrorResponse()
{
$data = 'test123';
$request = new Request();
$endpoint = new Endpoint();
/** @var Http|MockObject $mock */
$mock = $this->getMockBuilder(Http::class)
->setMethods(['getData', 'check'])
->getMock();
$mock->expects($this->once())
->method('getData')
->with($this->equalTo('http://127.0.0.1:8983/solr/?'), $this->isType('resource'))
->will($this->returnValue([$data, ['HTTP 1.1 200 OK']]));
$mock->expects($this->once())
->method('check')
->will($this->throwException(new HttpException('HTTP request failed')));
$this->expectException(HttpException::class);
$mock->execute($request, $endpoint);
}
public function testCheckError()
{
$this->expectException(HttpException::class);
$this->adapter->check(false, []);
}
public function testCheckOk()
{
$value = $this->adapter->check('dummydata', ['HTTP 1.1 200 OK']);
$this->assertNull(
$value
);
}
public function testCreateContextGetRequest()
{
$timeout = 13;
$method = Request::METHOD_HEAD;
$request = new Request();
$request->setMethod($method);
$endpoint = new Endpoint();
$endpoint->setTimeout($timeout);
$context = $this->adapter->createContext($request, $endpoint);
$this->assertSame(
['http' => ['method' => $method, 'timeout' => $timeout]],
stream_context_get_options($context)
);
}
public function testCreateContextWithHeaders()
{
$timeout = 13;
$method = Request::METHOD_HEAD;
$header1 = 'Content-Type: text/xml; charset=UTF-8';
$header2 = 'X-MyHeader: dummyvalue';
$request = new Request();
$request->setMethod($method);
$request->addHeader($header1);
$request->addHeader($header2);
$endpoint = new Endpoint();
$endpoint->setTimeout($timeout);
$context = $this->adapter->createContext($request, $endpoint);
$this->assertSame(
['http' => ['method' => $method, 'timeout' => $timeout, 'header' => $header1."\r\n".$header2]],
stream_context_get_options($context)
);
}
public function testCreateContextPostRequest()
{
$timeout = 13;
$method = Request::METHOD_POST;
$data = 'test123';
$request = new Request();
$request->setMethod($method);
$request->setRawData($data);
$endpoint = new Endpoint();
$endpoint->setTimeout($timeout);
$context = $this->adapter->createContext($request, $endpoint);
$this->assertSame(
[
'http' => [
'method' => $method,
'timeout' => $timeout,
'content' => $data,
'header' => 'Content-Type: text/xml; charset=UTF-8',
],
],
stream_context_get_options($context)
);
}
public function testCreateContextPostFileRequest()
{
$timeout = 13;
$method = Request::METHOD_POST;
$data = 'test123';
$request = new Request();
$request->setMethod($method);
$request->setFileUpload(__FILE__);
$endpoint = new Endpoint();
$endpoint->setTimeout($timeout);
$context = $this->adapter->createContext($request, $endpoint);
// Remove content from comparison, since we can't determine the
// random boundary string.
$stream_context_get_options = stream_context_get_options($context);
unset($stream_context_get_options['http']['content'], $stream_context_get_options['http']['header']);
$this->assertSame(
[
'http' => [
'method' => $method,
'timeout' => $timeout,
],
],
$stream_context_get_options
);
}
public function testCreateContextWithAuthorization()
{
$timeout = 13;
$method = Request::METHOD_HEAD;
$request = new Request();
$request->setMethod($method);
$request->setAuthentication('someone', 'S0M3p455');
$endpoint = new Endpoint();
$endpoint->setTimeout($timeout);
$context = $this->adapter->createContext($request, $endpoint);
$this->assertSame(
[
'http' => [
'method' => $method,
'timeout' => $timeout,
'header' => 'Authorization: Basic c29tZW9uZTpTME0zcDQ1NQ==',
],
],
stream_context_get_options($context)
);
}
}
<?php
namespace Solarium\Tests\Core\Client\Adapter;
use HttpRequest;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Solarium\Core\Client\Adapter\PeclHttp;
use Solarium\Core\Client\Adapter\PeclHttp as PeclHttpAdapter;
use Solarium\Core\Client\Endpoint;
use Solarium\Core\Client\Request;
use Solarium\Exception\ExceptionInterface;
class PeclHttpTest extends TestCase
{
/**
* @var PeclHttpAdapter
*/
protected $adapter;
public function setUp()
{
if (!function_exists('http_get')) {
$this->markTestSkipped('Pecl_http not available, skipping PeclHttp adapter tests');
}
$this->adapter = new PeclHttpAdapter(['timeout' => 10]);
}
/**
* @dataProvider requestProvider
*
* @param mixed $request
* @param mixed $method
* @param mixed $support
*/
public function testToHttpRequestWithMethod($request, $method, $support)
{
$endpoint = new Endpoint();
try {
$httpRequest = $this->adapter->toHttpRequest($request, $endpoint);
$this->assertSame($httpRequest->getMethod(), $method);
} catch (ExceptionInterface $e) {
if ($support) {
$this->fail("Unsupport method: {$request->getMethod()}");
}
}
}
public function requestProvider()
{
// prevents undefined constants errors
if (function_exists('http_get')) {
$methods = [
Request::METHOD_GET => [
'method' => HTTP_METH_GET,
'support' => true,
],
Request::METHOD_POST => [
'method' => HTTP_METH_POST,
'support' => true,
],
Request::METHOD_HEAD => [
'method' => HTTP_METH_HEAD,
'support' => true,
],
'PUT' => [
'method' => HTTP_METH_PUT,
'support' => false,
],
'DELETE' => [
'method' => HTTP_METH_DELETE,
'support' => false,
],
];
$data = [];
foreach ($methods as $method => $options) {
$request = new Request();
$request->setMethod($method);
$data[] = array_merge([$request], $options);
}
return $data;
}
}
public function testToHttpRequestWithHeaders()
{
$request = new Request(
[
'header' => [
'Content-Type: application/json',
'User-Agent: Foo',
],
'authentication' => [
'username' => 'someone',
'password' => 'S0M3p455',
],
]
);
$endpoint = new Endpoint();
$endpoint->setTimeout(10);
$httpRequest = $this->adapter->toHttpRequest($request, $endpoint);
$this->assertSame(
[
'timeout' => 10,
'connecttimeout' => 10,
'dns_cache_timeout' => 10,
'headers' => [
'Content-Type' => 'application/json',
'User-Agent' => 'Foo',
'Authorization' => 'Basic c29tZW9uZTpTME0zcDQ1NQ==',
],
],
$httpRequest->getOptions()
);
}
public function testToHttpRequestWithFile()
{
$request = new Request();
$request->setMethod(Request::METHOD_POST);
$request->setFileUpload(__FILE__);
$endpoint = new Endpoint();
$endpoint->setTimeout(10);
$httpRequest = $this->adapter->toHttpRequest($request, $endpoint);
$this->assertSame(
[
[
'name' => 'content',
'type' => 'application/octet-stream; charset=binary',
'file' => __FILE__,
],
],
$httpRequest->getPostFiles()
);
}
public function testToHttpRequestWithDefaultContentType()
{
$request = new Request();
$request->setMethod(Request::METHOD_POST);
$endpoint = new Endpoint();
$endpoint->setTimeout(10);
$httpRequest = $this->adapter->toHttpRequest($request, $endpoint);
$this->assertSame(
[
'timeout' => 10,
'connecttimeout' => 10,
'dns_cache_timeout' => 10,
'headers' => [
'Content-Type' => 'text/xml; charset=utf-8',
],
],
$httpRequest->getOptions()
);
}
public function testExecute()
{
$statusCode = 200;
$statusMessage = 'OK';
$body = 'data';
$data = <<<EOF
HTTP/1.1 $statusCode $statusMessage
X-Foo: test
$body
EOF;
$request = new Request();
$endpoint = new Endpoint();
$mockHttpRequest = $this->createMock(HttpRequest::class);
$mockHttpRequest->expects($this->once())
->method('send')
->will($this->returnValue(\HttpMessage::factory($data)));
/** @var PeclHttp|MockObject $mock */
$mock = $this->getMockBuilder(PeclHttp::class)
->setMethods(['toHttpRequest'])
->getMock();
$mock->expects($this->once())
->method('toHttpRequest')
->with($request, $endpoint)
->will($this->returnValue($mockHttpRequest));
$response = $mock->execute($request, $endpoint);
$this->assertSame($body, $response->getBody());
$this->assertSame($statusCode, $response->getStatusCode());
$this->assertSame($statusMessage, $response->getStatusMessage());
}
public function testExecuteWithException()
{
$endpoint = new Endpoint();
$endpoint->setPort(-1); // this forces an error
$request = new Request();
$this->adapter->execute($request, $endpoint);
}
}
<?php
namespace Solarium\Tests\Core\Client\Adapter;
use PHPUnit\Framework\TestCase;
use Solarium\Core\Client\Adapter\ZendHttp as ZendHttpAdapter;
use Solarium\Core\Client\Endpoint;
use Solarium\Core\Client\Request;
class ZendHttpTest extends TestCase
{
/**
* @var ZendHttpAdapter
*/
protected $adapter;
public function setUp()
{
if (!class_exists('\Zend_Http_Client')) {
$this->markTestSkipped('Zend_Http_Client class not found! skipping test');
}
if (!class_exists('Zend_Loader_Autoloader') && !(@include_once 'Zend/Loader/Autoloader.php')) {
$this->markTestSkipped('ZF not in include_path, skipping ZendHttp adapter tests');
}
\Zend_Loader_Autoloader::getInstance();
$this->adapter = new ZendHttpAdapter();
}
public function testForwardingToZendHttpInSetOptions()
{
$options = ['optionZ' => 123, 'options' => ['optionX' => 'Y']];
$adapterOptions = ['optionX' => 'Y'];
$mock = $this->createMock(\Zend_Http_Client::class);
$mock->expects($this->once())
->method('setConfig')
->with($this->equalTo($adapterOptions));
$this->adapter->setZendHttp($mock);
$this->adapter->setOptions($options);
}
public function testSetAndGetZendHttp()
{
$dummy = new \stdClass();
$this->adapter->setZendHttp($dummy);
$this->assertSame(
$dummy,
$this->adapter->getZendHttp()
);
}
public function testGetZendHttpAutoload()
{
$options = ['optionZ' => 123, 'options' => ['adapter' => 'Zend_Http_Client_Adapter_Curl']];
$this->adapter->setOptions($options);
$zendHttp = $this->adapter->getZendHttp();
$this->assertThat($zendHttp, $this->isInstanceOf(\Zend_Http_Client::class));
}
public function testExecuteGet()
{
$method = Request::METHOD_GET;
$rawData = 'xyz';
$responseData = 'abc';
$handler = 'myhandler';
$headers = [
'X-test: 123',
];
$params = ['a' => 1, 'b' => 2];
$request = new Request();
$request->setMethod($method);
$request->setHandler($handler);
$request->setHeaders($headers);
$request->setRawData($rawData);
$request->setParams($params);
$endpoint = new Endpoint();
$response = new \Zend_Http_Response(200, ['status' => 'HTTP 1.1 200 OK'], $responseData);
$mock = $this->createMock(\Zend_Http_Client::class);
$mock->expects($this->once())
->method('setMethod')
->with($this->equalTo($method));
$mock->expects($this->once())
->method('setUri')
->with($this->equalTo('http://127.0.0.1:8983/solr/myhandler'));
$mock->expects($this->once())
->method('setHeaders')
->with($this->equalTo(['X-test: 123']));
$mock->expects($this->once())
->method('setParameterGet')
->with($this->equalTo($params));
$mock->expects($this->once())
->method('request')
->will($this->returnValue($response));
$this->adapter->setZendHttp($mock);
$adapterResponse = $this->adapter->execute($request, $endpoint);
$this->assertSame(
$responseData,
$adapterResponse->getBody()
);
}
public function testExecutePost()
{
$method = Request::METHOD_POST;
$rawData = 'xyz';
$responseData = 'abc';
$handler = 'myhandler';
$headers = [
'X-test: 123',
];
$params = ['a' => 1, 'b' => 2];
$request = new Request();
$request->setMethod($method);
$request->setHandler($handler);
$request->setHeaders($headers);
$request->setRawData($rawData);
$request->setParams($params);
$endpoint = new Endpoint();
$response = new \Zend_Http_Response(200, ['status' => 'HTTP 1.1 200 OK'], $responseData);
$mock = $this->createMock(\Zend_Http_Client::class);
$mock->expects($this->once())
->method('setMethod')
->with($this->equalTo($method));
$mock->expects($this->once())
->method('setUri')
->with($this->equalTo('http://127.0.0.1:8983/solr/myhandler'));
$mock->expects($this->once())
->method('setHeaders')
->with($this->equalTo(['X-test: 123', 'Content-Type: text/xml; charset=UTF-8']));
$mock->expects($this->once())
->method('setRawData')
->with($this->equalTo($rawData));
$mock->expects($this->once())
->method('setParameterGet')
->with($this->equalTo($params));
$mock->expects($this->once())
->method('request')
->will($this->returnValue($response));
$this->adapter->setZendHttp($mock);
$adapterResponse = $this->adapter->execute($request, $endpoint);
$this->assertSame(
$responseData,
$adapterResponse->getBody()
);
}
public function testExecuteErrorResponse()
{
$request = new Request();
$response = new \Zend_Http_Response(404, [], '');
$endpoint = new Endpoint();
$mock = $this->createMock(\Zend_Http_Client::class);
$mock->expects($this->once())
->method('request')
->will($this->returnValue($response));
$this->adapter->setZendHttp($mock);
$this->expectException('Solarium\Exception\HttpException');
$this->adapter->execute($request, $endpoint);
}
public function testExecuteHeadRequestReturnsNoData()
{
$request = new Request();
$request->setMethod(Request::METHOD_HEAD);
$response = new \Zend_Http_Response(200, ['status' => 'HTTP 1.1 200 OK'], 'data');
$endpoint = new Endpoint();
$mock = $this->createMock(\Zend_Http_Client::class);
$mock->expects($this->once())
->method('request')
->will($this->returnValue($response));
$this->adapter->setZendHttp($mock);
$response = $this->adapter->execute($request, $endpoint);
$this->assertSame(
'',
$response->getBody()
);
}
public function testExecuteWithInvalidMethod()
{
$request = new Request();
$request->setMethod('invalid');
$endpoint = new Endpoint();
$this->expectException('Solarium\Exception\OutOfBoundsException');
$this->adapter->execute($request, $endpoint);
}
public function testExecuteWithFileUpload()
{
$request = new Request();
$request->setMethod(Request::METHOD_POST);
$request->setFileUpload(__FILE__);
$endpoint = new Endpoint();
$response = new \Zend_Http_Response(200, ['status' => 'HTTP 1.1 200 OK'], 'dummy');
$mock = $this->createMock(\Zend_Http_Client::class);
$mock->expects($this->once())
->method('setFileUpload')
->with(
$this->equalTo('content'),
$this->equalTo('content'),
$this->equalTo(file_get_contents(__FILE__)),
$this->equalTo('application/octet-stream; charset=binary')
);
$mock->expects($this->once())
->method('request')
->will($this->returnValue($response));
$this->adapter->setZendHttp($mock);
$this->adapter->execute($request, $endpoint);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment