202 changed files with 38581 additions and 0 deletions
@ -0,0 +1,2 @@ |
|||
<?php |
|||
require dirname(__FILE__) . '/vendor/autoload.php'; |
|||
@ -0,0 +1,276 @@ |
|||
<?php |
|||
|
|||
namespace Qcloud\Cos; |
|||
|
|||
include("Common.php"); |
|||
|
|||
use Qcloud\Cos\Signature; |
|||
use GuzzleHttp\Client as HttpClient; |
|||
use GuzzleHttp\HandlerStack; |
|||
use Psr\Http\Message\RequestInterface; |
|||
use Psr\Http\Message\ResponseInterface; |
|||
use GuzzleHttp\Command\Guzzle\Description; |
|||
use GuzzleHttp\Command\Guzzle\GuzzleClient; |
|||
use GuzzleHttp\Command\Guzzle\Deserializer; |
|||
use GuzzleHttp\Command\CommandInterface; |
|||
use GuzzleHttp\Command\Exception\CommandException; |
|||
use GuzzleHttp\Exception\RequestException; |
|||
use GuzzleHttp\Middleware; |
|||
use GuzzleHttp\Psr7; |
|||
use GuzzleHttp\Pool; |
|||
|
|||
|
|||
class Client extends GuzzleClient { |
|||
const VERSION = '2.0.8'; |
|||
|
|||
public $httpClient; |
|||
|
|||
private $api; |
|||
private $desc; |
|||
private $action; |
|||
private $operation; |
|||
private $cosConfig; |
|||
private $signature; |
|||
private $rawCosConfig; |
|||
|
|||
public function __construct($cosConfig) { |
|||
$this->rawCosConfig = $cosConfig; |
|||
$this->cosConfig['schema'] = isset($cosConfig['schema']) ? $cosConfig['schema'] : 'http'; |
|||
$this->cosConfig['region'] = region_map($cosConfig['region']); |
|||
$this->cosConfig['appId'] = isset($cosConfig['credentials']['appId']) ? $cosConfig['credentials']['appId'] : null; |
|||
$this->cosConfig['secretId'] = isset($cosConfig['credentials']['secretId']) ? $cosConfig['credentials']['secretId'] : ""; |
|||
$this->cosConfig['secretKey'] = isset($cosConfig['credentials']['secretKey']) ? $cosConfig['credentials']['secretKey'] : ""; |
|||
$this->cosConfig['anonymous'] = isset($cosConfig['credentials']['anonymous']) ? $cosConfig['anonymous']['anonymous'] : false; |
|||
$this->cosConfig['token'] = isset($cosConfig['credentials']['token']) ? $cosConfig['credentials']['token'] : null; |
|||
$this->cosConfig['timeout'] = isset($cosConfig['timeout']) ? $cosConfig['timeout'] : 3600; |
|||
$this->cosConfig['connect_timeout'] = isset($cosConfig['connect_timeout']) ? $cosConfig['connect_timeout'] : 3600; |
|||
$this->cosConfig['ip'] = isset($cosConfig['ip']) ? $cosConfig['ip'] : null; |
|||
$this->cosConfig['port'] = isset($cosConfig['port']) ? $cosConfig['port'] : null; |
|||
$this->cosConfig['endpoint'] = isset($cosConfig['endpoint']) ? $cosConfig['endpoint'] : 'myqcloud.com'; |
|||
$this->cosConfig['domain'] = isset($cosConfig['domain']) ? $cosConfig['domain'] : null; |
|||
$this->cosConfig['proxy'] = isset($cosConfig['proxy']) ? $cosConfig['proxy'] : null; |
|||
$this->cosConfig['userAgent'] = isset($cosConfig['userAgent']) ? $cosConfig['userAgent'] : 'cos-php-sdk-v5.'. Client::VERSION; |
|||
$this->cosConfig['pathStyle'] = isset($cosConfig['pathStyle']) ? $cosConfig['pathStyle'] : false; |
|||
|
|||
|
|||
$service = Service::getService(); |
|||
$handler = HandlerStack::create(); |
|||
$handler->push(Middleware::mapRequest(function (RequestInterface $request) { |
|||
return $request->withHeader('User-Agent', $this->cosConfig['userAgent']); |
|||
})); |
|||
if ($this->cosConfig['anonymous'] != true) { |
|||
$handler->push($this::handleSignature($this->cosConfig['secretId'], $this->cosConfig['secretKey'])); |
|||
} |
|||
if ($this->cosConfig['token'] != null) { |
|||
$handler->push(Middleware::mapRequest(function (RequestInterface $request) { |
|||
return $request->withHeader('x-cos-security-token', $this->cosConfig['token']); |
|||
})); |
|||
} |
|||
$handler->push($this::handleErrors()); |
|||
$this->signature = new Signature($this->cosConfig['secretId'], $this->cosConfig['secretKey'], $this->cosConfig['token']); |
|||
$this->httpClient = new HttpClient([ |
|||
'base_uri' => $this->cosConfig['schema'].'://cos.' . $this->cosConfig['region'] . '.myqcloud.com/', |
|||
'timeout' => $this->cosConfig['timeout'], |
|||
'handler' => $handler, |
|||
'proxy' => $this->cosConfig['proxy'], |
|||
]); |
|||
$this->desc = new Description($service); |
|||
$this->api = (array)($this->desc->getOperations()); |
|||
parent::__construct($this->httpClient, $this->desc, [$this, |
|||
'commandToRequestTransformer'], [$this, 'responseToResultTransformer'], |
|||
null); |
|||
} |
|||
|
|||
public function commandToRequestTransformer(CommandInterface $command) |
|||
{ |
|||
$this->action = $command->GetName(); |
|||
$this->operation = $this->api[$this->action]; |
|||
$transformer = new CommandToRequestTransformer($this->cosConfig, $this->operation); |
|||
$seri = new Serializer($this->desc); |
|||
$request = $seri($command); |
|||
$request = $transformer->bucketStyleTransformer($command, $request); |
|||
$request = $transformer->uploadBodyTransformer($command, $request); |
|||
$request = $transformer->metadataTransformer($command, $request); |
|||
$request = $transformer->md5Transformer($command, $request); |
|||
$request = $transformer->specialParamTransformer($command, $request); |
|||
return $request; |
|||
} |
|||
|
|||
public function responseToResultTransformer(ResponseInterface $response, RequestInterface $request, CommandInterface $command) |
|||
{ |
|||
$transformer = new ResultTransformer($this->cosConfig, $this->operation); |
|||
$transformer->writeDataToLocal($command, $request, $response); |
|||
$deseri = new Deserializer($this->desc, true); |
|||
$result = $deseri($response, $request, $command); |
|||
|
|||
$result = $transformer->metaDataTransformer($command, $response, $result); |
|||
$result = $transformer->extraHeadersTransformer($command, $request, $result); |
|||
$result = $transformer->selectContentTransformer($command, $result); |
|||
return $result; |
|||
} |
|||
|
|||
public function __destruct() { |
|||
} |
|||
|
|||
public function __call($method, array $args) { |
|||
try { |
|||
return parent::__call(ucfirst($method), $args); |
|||
} catch (CommandException $e) { |
|||
$previous = $e->getPrevious(); |
|||
if ($previous !== null) { |
|||
throw $previous; |
|||
} else { |
|||
throw $e; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public function getApi() { |
|||
return $this->api; |
|||
} |
|||
|
|||
private function getCosConfig() { |
|||
return $this->cosConfig; |
|||
} |
|||
|
|||
private function createPresignedUrl(RequestInterface $request, $expires) { |
|||
return $this->signature->createPresignedUrl($request, $expires); |
|||
} |
|||
|
|||
public function getPresignetUrl($method, $args, $expires = null) { |
|||
$command = $this->getCommand($method, $args); |
|||
$request = $this->commandToRequestTransformer($command); |
|||
return $this->createPresignedUrl($request, $expires); |
|||
} |
|||
|
|||
public function getObjectUrl($bucket, $key, $expires = null, array $args = array()) { |
|||
$command = $this->getCommand('GetObject', $args + array('Bucket' => $bucket, 'Key' => $key)); |
|||
$request = $this->commandToRequestTransformer($command); |
|||
return $this->createPresignedUrl($request, $expires)->__toString(); |
|||
} |
|||
|
|||
public function upload($bucket, $key, $body, $options = array()) { |
|||
$body = Psr7\stream_for($body); |
|||
$options['PartSize'] = isset($options['PartSize']) ? $options['PartSize'] : MultipartUpload::MIN_PART_SIZE; |
|||
if ($body->getSize() < $options['PartSize']) { |
|||
$rt = $this->putObject(array( |
|||
'Bucket' => $bucket, |
|||
'Key' => $key, |
|||
'Body' => $body, |
|||
) + $options); |
|||
} |
|||
else { |
|||
$multipartUpload = new MultipartUpload($this, $body, array( |
|||
'Bucket' => $bucket, |
|||
'Key' => $key, |
|||
) + $options); |
|||
|
|||
$rt = $multipartUpload->performUploading(); |
|||
} |
|||
return $rt; |
|||
} |
|||
|
|||
public function resumeUpload($bucket, $key, $body, $uploadId, $options = array()) { |
|||
$body = Psr7\stream_for($body); |
|||
$options['PartSize'] = isset($options['PartSize']) ? $options['PartSize'] : MultipartUpload::DEFAULT_PART_SIZE; |
|||
$multipartUpload = new MultipartUpload($this, $body, array( |
|||
'Bucket' => $bucket, |
|||
'Key' => $key, |
|||
'UploadId' => $uploadId, |
|||
) + $options); |
|||
|
|||
$rt = $multipartUpload->resumeUploading(); |
|||
return $rt; |
|||
} |
|||
|
|||
public function copy($bucket, $key, $copySource, $options = array()) { |
|||
|
|||
$options['PartSize'] = isset($options['PartSize']) ? $options['PartSize'] : Copy::DEFAULT_PART_SIZE; |
|||
|
|||
// set copysource client |
|||
$sourceConfig = $this->rawCosConfig; |
|||
$sourceConfig['region'] = $copySource['Region']; |
|||
$cosSourceClient = new Client($sourceConfig); |
|||
$copySource['VersionId'] = isset($copySource['VersionId']) ? $copySource['VersionId'] : ""; |
|||
try { |
|||
$rt = $cosSourceClient->headObject( |
|||
array('Bucket'=>$copySource['Bucket'], |
|||
'Key'=>$copySource['Key'], |
|||
'VersionId'=>$copySource['VersionId'], |
|||
) |
|||
); |
|||
} catch (\Exception $e) { |
|||
throw $e; |
|||
} |
|||
|
|||
$contentLength =$rt['ContentLength']; |
|||
// sample copy |
|||
if ($contentLength < $options['PartSize']) { |
|||
$rt = $this->copyObject(array( |
|||
'Bucket' => $bucket, |
|||
'Key' => $key, |
|||
'CopySource' => $copySource['Bucket']. '.cos.'. $copySource['Region']. |
|||
".myqcloud.com/". $copySource['Key']. "?versionId=". $copySource['VersionId'], |
|||
) + $options |
|||
); |
|||
return $rt; |
|||
} |
|||
// multi part copy |
|||
$copySource['ContentLength'] = $contentLength; |
|||
$copy = new Copy($this, $copySource, array( |
|||
'Bucket' => $bucket, |
|||
'Key' => $key |
|||
) + $options |
|||
); |
|||
return $copy->copy(); |
|||
} |
|||
|
|||
public function doesBucketExist($bucket, array $options = array()) |
|||
{ |
|||
try { |
|||
$this->HeadBucket(array( |
|||
'Bucket' => $bucket)); |
|||
return True; |
|||
} catch (\Exception $e){ |
|||
return False; |
|||
} |
|||
} |
|||
|
|||
public function doesObjectExist($bucket, $key, array $options = array()) |
|||
{ |
|||
try { |
|||
$this->HeadObject(array( |
|||
'Bucket' => $bucket, |
|||
'Key' => $key)); |
|||
return True; |
|||
} catch (\Exception $e){ |
|||
return False; |
|||
} |
|||
} |
|||
|
|||
public static function explodeKey($key) { |
|||
// Remove a leading slash if one is found |
|||
$split_key = explode('/', $key && $key[0] == '/' ? substr($key, 1) : $key); |
|||
// Remove empty element |
|||
$split_key = array_filter($split_key, function($var) { |
|||
return !($var == '' || $var == null); |
|||
}); |
|||
$final_key = implode("/", $split_key); |
|||
if (substr($key, -1) == '/') { |
|||
$final_key = $final_key . '/'; |
|||
} |
|||
return $final_key; |
|||
} |
|||
|
|||
public static function handleSignature($secretId, $secretKey) { |
|||
return function (callable $handler) use ($secretId, $secretKey) { |
|||
return new SignatureMiddleware($handler, $secretId, $secretKey); |
|||
}; |
|||
} |
|||
|
|||
public static function handleErrors() { |
|||
return function (callable $handler) { |
|||
return new ExceptionMiddleware($handler); |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,162 @@ |
|||
<?php |
|||
|
|||
namespace Qcloud\Cos; |
|||
|
|||
use Guzzle\Service\Description\Parameter; |
|||
use Guzzle\Service\Description\ServiceDescription; |
|||
use GuzzleHttp\HandlerStack; |
|||
use Psr\Http\Message\RequestInterface; |
|||
use Psr\Http\Message\ResponseInterface; |
|||
use Qcloud\Cos\Signature; |
|||
use GuzzleHttp\Command\Guzzle\Description; |
|||
use GuzzleHttp\Command\Guzzle\GuzzleClient; |
|||
use GuzzleHttp\Command\CommandInterface; |
|||
use GuzzleHttp\Exception\RequestException; |
|||
use GuzzleHttp\Middleware; |
|||
use GuzzleHttp\Psr7; |
|||
use GuzzleHttp\Psr7\Uri; |
|||
use InvalidArgumentException; |
|||
|
|||
|
|||
class CommandToRequestTransformer { |
|||
private $config; |
|||
private $operation; |
|||
|
|||
public function __construct($config ,$operation) { |
|||
$this->config = $config; |
|||
$this->operation = $operation; |
|||
} |
|||
|
|||
// format bucket style |
|||
public function bucketStyleTransformer(CommandInterface $command, RequestInterface $request) { |
|||
$action = $command->getName(); |
|||
if ($action == 'ListBuckets') { |
|||
return $request->withUri(new Uri($this->config['schema']."://service.cos.myqcloud.com/")); |
|||
} |
|||
$operation = $this->operation; |
|||
$bucketname = $command['Bucket']; |
|||
|
|||
$appId = $this->config['appId']; |
|||
if ($appId != null && endWith($bucketname, '-'.$appId) == False) |
|||
{ |
|||
$bucketname = $bucketname.'-'.$appId; |
|||
} |
|||
$command['Bucket'] = $bucketname; |
|||
$path = ''; |
|||
$http_method = $operation['httpMethod']; |
|||
$uri = $operation['uri']; |
|||
|
|||
// Hoststyle is used by default |
|||
// Pathstyle |
|||
if ($this->config['pathStyle'] != true) { |
|||
if (isset($operation['parameters']['Bucket']) && $command->hasParam('Bucket')) { |
|||
$uri = str_replace("{Bucket}", '', $uri); |
|||
} |
|||
if (isset($operation['parameters']['Key']) && $command->hasParam('Key')) { |
|||
$uri = str_replace("{/Key*}", encodeKey($command['Key']), $uri); |
|||
} |
|||
} |
|||
$origin_host = $bucketname. '.cos.' . $this->config['region'] . '.' . $this->config['endpoint']; |
|||
// domain |
|||
if ($this->config['domain'] != null) { |
|||
$origin_host = $this->config['domain']; |
|||
} |
|||
$host = $origin_host; |
|||
if ($this->config['ip'] != null) { |
|||
$host = $this->config['ip']; |
|||
if ($this->config['port'] != null) { |
|||
$host = $this->config['ip'] . ":" . $this->config['port']; |
|||
} |
|||
} |
|||
|
|||
|
|||
$path = $this->config['schema'].'://'. $host . $uri; |
|||
$uri = new Uri($path); |
|||
$query = $request->getUri()->getQuery(); |
|||
if ($uri->getQuery() != $query && $uri->getQuery() != "") { |
|||
$query = $uri->getQuery() . "&" . $request->getUri()->getQuery(); |
|||
} |
|||
$uri = $uri->withQuery($query); |
|||
$request = $request->withUri($uri); |
|||
$request = $request->withHeader('Host', $origin_host); |
|||
return $request; |
|||
} |
|||
|
|||
// format upload body |
|||
public function uploadBodyTransformer(CommandInterface $command, $request, $bodyParameter = 'Body', $sourceParameter = 'SourceFile') { |
|||
|
|||
$operation = $this->operation; |
|||
if (!isset($operation['parameters']['Body'])) { |
|||
return $request; |
|||
} |
|||
$source = isset($command[$sourceParameter]) ? $command[$sourceParameter] : null; |
|||
$body = isset($command[$bodyParameter]) ? $command[$bodyParameter] : null; |
|||
// If a file path is passed in then get the file handle |
|||
if (is_string($source) && file_exists($source)) { |
|||
$body = fopen($source, 'rb'); |
|||
} |
|||
// Prepare the body parameter and remove the source file parameter |
|||
if (null !== $body) { |
|||
return $request; |
|||
} else { |
|||
throw new InvalidArgumentException( |
|||
"You must specify a non-null value for the {$bodyParameter} or {$sourceParameter} parameters."); |
|||
} |
|||
} |
|||
|
|||
// update md5 |
|||
public function md5Transformer(CommandInterface $command, $request) { |
|||
$operation = $this->operation; |
|||
if (isset($operation['data']['contentMd5'])) { |
|||
$request = $this->addMd5($request); |
|||
} |
|||
if (isset($operation['parameters']['ContentMD5']) && |
|||
isset($command['ContentMD5'])) { |
|||
$value = $command['ContentMD5']; |
|||
if ($value === true) { |
|||
$request = $this->addMd5($request); |
|||
} |
|||
} |
|||
|
|||
return $request; |
|||
} |
|||
|
|||
// add meta |
|||
public function metadataTransformer(CommandInterface $command, $request) { |
|||
$operation = $this->operation; |
|||
if (isset($command['Metadata'])) { |
|||
$meta = $command['Metadata']; |
|||
foreach ($meta as $key => $value) { |
|||
$request = $request->withHeader('x-cos-meta-' . $key, $value); |
|||
} |
|||
} |
|||
return $request; |
|||
} |
|||
|
|||
// count md5 |
|||
private function addMd5($request) { |
|||
$body = $request->getBody(); |
|||
if ($body && $body->getSize() > 0) { |
|||
$md5 = base64_encode(md5($body, true)); |
|||
return $request->withHeader('Content-MD5', $md5); |
|||
} |
|||
return $request; |
|||
} |
|||
|
|||
// inventoryId |
|||
public function specialParamTransformer(CommandInterface $command, $request) { |
|||
$action = $command->getName(); |
|||
if ($action == 'PutBucketInventory') { |
|||
$id = $command['Id']; |
|||
$uri = $request->getUri(); |
|||
$query = $uri->getQuery(); |
|||
$uri = $uri->withQuery($query . "&Id=".$id); |
|||
return $request->withUri($uri); |
|||
} |
|||
return $request; |
|||
} |
|||
|
|||
public function __destruct() { |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
<?php |
|||
|
|||
namespace Qcloud\Cos; |
|||
|
|||
function region_map($region) { |
|||
$regionmap = array('cn-east'=>'ap-shanghai', |
|||
'cn-south'=>'ap-guangzhou', |
|||
'cn-north'=>'ap-beijing-1', |
|||
'cn-south-2'=>'ap-guangzhou-2', |
|||
'cn-southwest'=>'ap-chengdu', |
|||
'sg'=>'ap-singapore', |
|||
'tj'=>'ap-beijing-1', |
|||
'bj'=>'ap-beijing', |
|||
'sh'=>'ap-shanghai', |
|||
'gz'=>'ap-guangzhou', |
|||
'cd'=>'ap-chengdu', |
|||
'sgp'=>'ap-singapore'); |
|||
if (array_key_exists($region, $regionmap)) { |
|||
return $regionmap[$region]; |
|||
} |
|||
return $region; |
|||
} |
|||
|
|||
function encodeKey($key) { |
|||
return str_replace('%2F', '/', rawurlencode($key)); |
|||
} |
|||
|
|||
function endWith($haystack, $needle) { |
|||
$length = strlen($needle); |
|||
if($length == 0) |
|||
{ |
|||
return true; |
|||
} |
|||
return (substr($haystack, -$length) === $needle); |
|||
} |
|||
@ -0,0 +1,144 @@ |
|||
<?php |
|||
|
|||
namespace Qcloud\Cos; |
|||
|
|||
use GuzzleHttp\Psr7\Request; |
|||
use GuzzleHttp\Pool; |
|||
|
|||
class Copy { |
|||
/** |
|||
* const var: part size from 1MB to 5GB, and max parts of 10000 are allowed for each upload. |
|||
*/ |
|||
const MIN_PART_SIZE = 1048576; |
|||
const MAX_PART_SIZE = 5368709120; |
|||
const DEFAULT_PART_SIZE = 52428800; |
|||
const MAX_PARTS = 10000; |
|||
|
|||
private $client; |
|||
private $copySource; |
|||
private $options; |
|||
private $partSize; |
|||
private $parts; |
|||
private $size; |
|||
private $commandList = []; |
|||
private $requestList = []; |
|||
|
|||
public function __construct($client, $source, $options = array()) { |
|||
$minPartSize = $options['PartSize']; |
|||
unset($options['PartSize']); |
|||
$this->client = $client; |
|||
$this->copySource = $source; |
|||
$this->options = $options; |
|||
$this->size = $source['ContentLength']; |
|||
unset($source['ContentLength']); |
|||
$this->partSize = $this->calculatePartSize($minPartSize); |
|||
$this->concurrency = isset($options['Concurrency']) ? $options['Concurrency'] : 10; |
|||
$this->retry = isset($options['Retry']) ? $options['Retry'] : 5; |
|||
} |
|||
public function copy() { |
|||
$uploadId= $this->initiateMultipartUpload(); |
|||
for ($i = 0; $i < $this->retry; $i += 1) { |
|||
$rt = $this->uploadParts($uploadId); |
|||
if ($rt == 0) { |
|||
break; |
|||
} |
|||
sleep(1 << $i); |
|||
} |
|||
foreach ( $this->parts as $key => $row ){ |
|||
$num1[$key] = $row ['PartNumber']; |
|||
$num2[$key] = $row ['ETag']; |
|||
} |
|||
array_multisort($num1, SORT_ASC, $num2, SORT_ASC, $this->parts); |
|||
return $this->client->completeMultipartUpload(array( |
|||
'Bucket' => $this->options['Bucket'], |
|||
'Key' => $this->options['Key'], |
|||
'UploadId' => $uploadId, |
|||
'Parts' => $this->parts) |
|||
); |
|||
|
|||
} |
|||
public function uploadParts($uploadId) { |
|||
$copyRequests = function ($uploadId) { |
|||
$offset = 0; |
|||
$partNumber = 1; |
|||
$partSize = $this->partSize; |
|||
$finishedNum = 0; |
|||
$this->parts = array(); |
|||
for ($index = 1; ; $index ++) { |
|||
if ($offset + $partSize >= $this->size) |
|||
{ |
|||
$partSize = $this->size - $offset; |
|||
} |
|||
$copySourcePath = $this->copySource['Bucket']. '.cos.'. $this->copySource['Region']. |
|||
".myqcloud.com/". $this->copySource['Key']. "?versionId=". $this->copySource['VersionId']; |
|||
$params = array( |
|||
'Bucket' => $this->options['Bucket'], |
|||
'Key' => $this->options['Key'], |
|||
'UploadId' => $uploadId, |
|||
'PartNumber' => $partNumber, |
|||
'CopySource'=> $copySourcePath, |
|||
'CopySourceRange' => 'bytes='.((string)$offset).'-'.(string)($offset+$partSize - 1), |
|||
); |
|||
if(!isset($parts[$partNumber])) { |
|||
$command = $this->client->getCommand('uploadPartCopy', $params); |
|||
$request = $this->client->commandToRequestTransformer($command); |
|||
$this->commandList[$index] = $command; |
|||
$this->requestList[$index] = $request; |
|||
yield $request; |
|||
} |
|||
++$partNumber; |
|||
$offset += $partSize; |
|||
if ($this->size == $offset) { |
|||
break; |
|||
} |
|||
} |
|||
}; |
|||
$pool = new Pool($this->client->httpClient, $copyRequests($uploadId), [ |
|||
'concurrency' => $this->concurrency, |
|||
'fulfilled' => function ($response, $index) { |
|||
$index = $index + 1; |
|||
$response = $this->client->responseToResultTransformer($response, $this->requestList[$index], $this->commandList[$index]); |
|||
$part = array('PartNumber' => $index, 'ETag' => $response['ETag']); |
|||
$this->parts[$index] = $part; |
|||
}, |
|||
|
|||
'rejected' => function ($reason, $index) { |
|||
$retry = 2; |
|||
for ($i = 1; $i <= $retry; $i++) { |
|||
$index = $index += 1; |
|||
try { |
|||
$rt =$this->client->execute($commandList[$index]); |
|||
$part = array('PartNumber' => $index, 'ETag' => $rt['ETag']); |
|||
$this->parts[$index] = $part; |
|||
} catch(Exception $e) { |
|||
if ($i == $retry) { |
|||
throw($e); |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
]); |
|||
|
|||
// Initiate the transfers and create a promise |
|||
$promise = $pool->promise(); |
|||
|
|||
// Force the pool of requests to complete. |
|||
$promise->wait(); |
|||
} |
|||
|
|||
|
|||
private function calculatePartSize($minPartSize) |
|||
{ |
|||
$partSize = intval(ceil(($this->size / self::MAX_PARTS))); |
|||
$partSize = max($minPartSize, $partSize); |
|||
$partSize = min($partSize, self::MAX_PART_SIZE); |
|||
$partSize = max($partSize, self::MIN_PART_SIZE); |
|||
return $partSize; |
|||
} |
|||
|
|||
private function initiateMultipartUpload() { |
|||
$result = $this->client->createMultipartUpload($this->options); |
|||
return $result['UploadId']; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,162 @@ |
|||
<?php |
|||
|
|||
namespace Qcloud\Cos; |
|||
|
|||
use Guzzle\Service\Description\Parameter; |
|||
use Guzzle\Service\Description\ServiceDescription; |
|||
use GuzzleHttp\HandlerStack; |
|||
use Psr\Http\Message\RequestInterface; |
|||
use Psr\Http\Message\ResponseInterface; |
|||
use Qcloud\Cos\Signature; |
|||
use GuzzleHttp\Command\Guzzle\Description; |
|||
use GuzzleHttp\Command\Guzzle\GuzzleClient; |
|||
use GuzzleHttp\Command\CommandInterface; |
|||
use GuzzleHttp\Exception\RequestException; |
|||
use GuzzleHttp\Middleware; |
|||
use GuzzleHttp\Psr7; |
|||
use GuzzleHttp\Psr7\Uri; |
|||
use InvalidArgumentException; |
|||
|
|||
|
|||
class CosTransformer { |
|||
private $config; |
|||
private $operation; |
|||
|
|||
public function __construct($config ,$operation) { |
|||
$this->config = $config; |
|||
$this->operation = $operation; |
|||
} |
|||
|
|||
// format bucket style |
|||
public function bucketStyleTransformer(CommandInterface $command, RequestInterface $request) { |
|||
$action = $command->getName(); |
|||
if ($action == 'ListBuckets') { |
|||
return $request->withUri(new Uri($this->config['schema']."://service.cos.myqcloud.com/")); |
|||
} |
|||
$operation = $this->operation; |
|||
$bucketname = $command['Bucket']; |
|||
|
|||
$appId = $this->config['appId']; |
|||
if ($appId != null && endWith($bucketname, '-'.$appId) == False) |
|||
{ |
|||
$bucketname = $bucketname.'-'.$appId; |
|||
} |
|||
$command['Bucket'] = $bucketname; |
|||
$path = ''; |
|||
$http_method = $operation['httpMethod']; |
|||
$uri = $operation['uri']; |
|||
|
|||
// Hoststyle is used by default |
|||
// Pathstyle |
|||
if ($this->config['pathStyle'] != true) { |
|||
if (isset($operation['parameters']['Bucket']) && $command->hasParam('Bucket')) { |
|||
$uri = str_replace("{Bucket}", '', $uri); |
|||
} |
|||
if (isset($operation['parameters']['Key']) && $command->hasParam('Key')) { |
|||
$uri = str_replace("{/Key*}", encodeKey($command['Key']), $uri); |
|||
} |
|||
} |
|||
$origin_host = $bucketname. '.cos.' . $this->config['region'] . '.' . $this->config['endpoint']; |
|||
// domain |
|||
if ($this->config['domain'] != null) { |
|||
$origin_host = $this->config['domain']; |
|||
} |
|||
$host = $origin_host; |
|||
if ($this->config['ip'] != null) { |
|||
$host = $this->config['ip']; |
|||
if ($this->config['port'] != null) { |
|||
$host = $this->config['ip'] . ":" . $this->config['port']; |
|||
} |
|||
} |
|||
|
|||
|
|||
$path = $this->config['schema'].'://'. $host . $uri; |
|||
$uri = new Uri($path); |
|||
$query = $request->getUri()->getQuery(); |
|||
if ($uri->getQuery() != $query && $uri->getQuery() != "") { |
|||
$query = $uri->getQuery() . "&" . $request->getUri()->getQuery(); |
|||
} |
|||
$uri = $uri->withQuery($query); |
|||
$request = $request->withUri($uri); |
|||
$request = $request->withHeader('Host', $origin_host); |
|||
return $request; |
|||
} |
|||
|
|||
// format upload body |
|||
public function uploadBodyTransformer(CommandInterface $command, $request, $bodyParameter = 'Body', $sourceParameter = 'SourceFile') { |
|||
|
|||
$operation = $this->operation; |
|||
if (!isset($operation['parameters']['Body'])) { |
|||
return $request; |
|||
} |
|||
$source = isset($command[$sourceParameter]) ? $command[$sourceParameter] : null; |
|||
$body = isset($command[$bodyParameter]) ? $command[$bodyParameter] : null; |
|||
// If a file path is passed in then get the file handle |
|||
if (is_string($source) && file_exists($source)) { |
|||
$body = fopen($source, 'rb'); |
|||
} |
|||
// Prepare the body parameter and remove the source file parameter |
|||
if (null !== $body) { |
|||
return $request; |
|||
} else { |
|||
throw new InvalidArgumentException( |
|||
"You must specify a non-null value for the {$bodyParameter} or {$sourceParameter} parameters."); |
|||
} |
|||
} |
|||
|
|||
// update md5 |
|||
public function md5Transformer(CommandInterface $command, $request) { |
|||
$operation = $this->operation; |
|||
if (isset($operation['data']['contentMd5'])) { |
|||
$request = $this->addMd5($request); |
|||
} |
|||
if (isset($operation['parameters']['ContentMD5']) && |
|||
isset($command['ContentMD5'])) { |
|||
$value = $command['ContentMD5']; |
|||
if ($value === true) { |
|||
$request = $this->addMd5($request); |
|||
} |
|||
} |
|||
|
|||
return $request; |
|||
} |
|||
|
|||
// add meta |
|||
public function metadataTransformer(CommandInterface $command, $request) { |
|||
$operation = $this->operation; |
|||
if (isset($command['Metadata'])) { |
|||
$meta = $command['Metadata']; |
|||
foreach ($meta as $key => $value) { |
|||
$request = $request->withHeader('x-cos-meta-' . $key, $value); |
|||
} |
|||
} |
|||
return $request; |
|||
} |
|||
|
|||
// count md5 |
|||
private function addMd5($request) { |
|||
$body = $request->getBody(); |
|||
if ($body && $body->getSize() > 0) { |
|||
$md5 = base64_encode(md5($body, true)); |
|||
return $request->withHeader('Content-MD5', $md5); |
|||
} |
|||
return $request; |
|||
} |
|||
|
|||
// inventoryId |
|||
public function specialParamTransformer(CommandInterface $command, $request) { |
|||
$action = $command->getName(); |
|||
if ($action == 'PutBucketInventory') { |
|||
$id = $command['Id']; |
|||
$uri = $request->getUri(); |
|||
$query = $uri->getQuery(); |
|||
$uri = $uri->withQuery($query . "&Id=".$id); |
|||
return $request->withUri($uri); |
|||
} |
|||
return $request; |
|||
} |
|||
|
|||
public function __destruct() { |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
<?php |
|||
|
|||
namespace Qcloud\Cos\Exception; |
|||
|
|||
use Qcloud\Cos\Exception\ServiceResponseException; |
|||
|
|||
class CosException extends ServiceResponseException {} |
|||
@ -0,0 +1,189 @@ |
|||
<?php |
|||
|
|||
namespace Qcloud\Cos\Exception; |
|||
|
|||
use Psr\Http\Message\RequestInterface; |
|||
use Psr\Http\Message\ResponseInterface; |
|||
|
|||
class ServiceResponseException extends \RuntimeException { |
|||
|
|||
/** |
|||
* @var Response Response |
|||
*/ |
|||
protected $response; |
|||
|
|||
/** |
|||
* @var RequestInterface Request |
|||
*/ |
|||
protected $request; |
|||
|
|||
/** |
|||
* @var string Request ID |
|||
*/ |
|||
protected $requestId; |
|||
|
|||
/** |
|||
* @var string Exception type (client / server) |
|||
*/ |
|||
protected $exceptionType; |
|||
|
|||
/** |
|||
* @var string Exception code |
|||
*/ |
|||
protected $exceptionCode; |
|||
|
|||
/** |
|||
* Set the exception code |
|||
* |
|||
* @param string $code Exception code |
|||
*/ |
|||
public function setExceptionCode($code) { |
|||
$this->exceptionCode = $code; |
|||
} |
|||
|
|||
/** |
|||
* Get the exception code |
|||
* |
|||
* @return string|null |
|||
*/ |
|||
public function getExceptionCode() { |
|||
return $this->exceptionCode; |
|||
} |
|||
|
|||
/** |
|||
* Set the exception type |
|||
* |
|||
* @param string $type Exception type |
|||
*/ |
|||
public function setExceptionType($type) { |
|||
$this->exceptionType = $type; |
|||
} |
|||
|
|||
/** |
|||
* Get the exception type (one of client or server) |
|||
* |
|||
* @return string|null |
|||
*/ |
|||
public function getExceptionType() { |
|||
return $this->exceptionType; |
|||
} |
|||
|
|||
/** |
|||
* Set the request ID |
|||
* |
|||
* @param string $id Request ID |
|||
*/ |
|||
public function setRequestId($id) { |
|||
$this->requestId = $id; |
|||
} |
|||
|
|||
/** |
|||
* Get the Request ID |
|||
* |
|||
* @return string|null |
|||
*/ |
|||
public function getRequestId() { |
|||
return $this->requestId; |
|||
} |
|||
|
|||
/** |
|||
* Set the associated response |
|||
* |
|||
* @param Response $response Response |
|||
*/ |
|||
public function setResponse(ResponseInterface $response) { |
|||
$this->response = $response; |
|||
} |
|||
|
|||
/** |
|||
* Get the associated response object |
|||
* |
|||
* @return Response|null |
|||
*/ |
|||
public function getResponse() { |
|||
return $this->response; |
|||
} |
|||
|
|||
/** |
|||
* Set the associated request |
|||
* |
|||
* @param RequestInterface $request |
|||
*/ |
|||
public function setRequest(RequestInterface $request) { |
|||
$this->request = $request; |
|||
} |
|||
|
|||
/** |
|||
* Get the associated request object |
|||
* |
|||
* @return RequestInterface|null |
|||
*/ |
|||
public function getRequest() { |
|||
return $this->request; |
|||
} |
|||
|
|||
/** |
|||
* Get the status code of the response |
|||
* |
|||
* @return int|null |
|||
*/ |
|||
public function getStatusCode() { |
|||
return $this->response ? $this->response->getStatusCode() : null; |
|||
} |
|||
|
|||
/** |
|||
* Cast to a string |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function __toString() { |
|||
$message = get_class($this) . ': ' |
|||
. 'Cos Error Code: ' . $this->getExceptionCode() . ', ' |
|||
. 'Status Code: ' . $this->getStatusCode() . ', ' |
|||
. 'Cos Request ID: ' . $this->getRequestId() . ', ' |
|||
. 'Cos Error Type: ' . $this->getExceptionType() . ', ' |
|||
. 'Cos Error Message: ' . $this->getMessage(); |
|||
|
|||
// Add the User-Agent if available |
|||
if ($this->request) { |
|||
$message .= ', ' . 'User-Agent: ' . $this->request->getHeader('User-Agent')[0]; |
|||
} |
|||
|
|||
return $message; |
|||
} |
|||
|
|||
/** |
|||
* Get the request ID of the error. This value is only present if a |
|||
* response was received, and is not present in the event of a networking |
|||
* error. |
|||
* |
|||
* Same as `getRequestId()` method, but matches the interface for SDKv3. |
|||
* |
|||
* @return string|null Returns null if no response was received |
|||
*/ |
|||
public function getCosRequestId() { |
|||
return $this->requestId; |
|||
} |
|||
|
|||
/** |
|||
* Get the Cos error type. |
|||
* |
|||
* Same as `getExceptionType()` method, but matches the interface for SDKv3. |
|||
* |
|||
* @return string|null Returns null if no response was received |
|||
*/ |
|||
public function getCosErrorType() { |
|||
return $this->exceptionType; |
|||
} |
|||
|
|||
/** |
|||
* Get the Cos error code. |
|||
* |
|||
* Same as `getExceptionCode()` method, but matches the interface for SDKv3. |
|||
* |
|||
* @return string|null Returns null if no response was received |
|||
*/ |
|||
public function getCosErrorCode() { |
|||
return $this->exceptionCode; |
|||
} |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
<?php |
|||
|
|||
namespace Qcloud\Cos; |
|||
|
|||
use Qcloud\Cos\Exception\ServiceResponseException; |
|||
use GuzzleHttp\Promise\PromiseInterface; |
|||
use GuzzleHttp\Psr7; |
|||
use Psr\Http\Message\RequestInterface; |
|||
use Psr\Http\Message\ResponseInterface; |
|||
use GuzzleHttp\Exception\RequestException; |
|||
|
|||
class ExceptionMiddleware { |
|||
private $nextHandler; |
|||
protected $parser; |
|||
protected $defaultException; |
|||
|
|||
/** |
|||
* @param callable $nextHandler Next handler to invoke. |
|||
*/ |
|||
public function __construct(callable $nextHandler) { |
|||
$this->nextHandler = $nextHandler; |
|||
$this->parser = new ExceptionParser(); |
|||
$this->defaultException = 'Qcloud\Cos\Exception\ServiceResponseException'; |
|||
} |
|||
|
|||
/** |
|||
* @param RequestInterface $request |
|||
* @param array $options |
|||
* |
|||
* @return PromiseInterface |
|||
*/ |
|||
public function __invoke(RequestInterface $request, array $options) { |
|||
$fn = $this->nextHandler; |
|||
return $fn($request, $options)->then( |
|||
function (ResponseInterface $response) use ($request) { |
|||
return $this->handle($request, $response); |
|||
} |
|||
); |
|||
} |
|||
|
|||
public function handle(RequestInterface $request, ResponseInterface $response) { |
|||
$code = $response->getStatusCode(); |
|||
if ($code < 400) { |
|||
return $response; |
|||
} |
|||
|
|||
//throw RequestException::create($request, $response); |
|||
$parts = $this->parser->parse($request, $response); |
|||
|
|||
$className = 'Qcloud\\Cos\\Exception\\' . $parts['code']; |
|||
if (substr($className, -9) !== 'Exception') { |
|||
$className .= 'Exception'; |
|||
} |
|||
|
|||
$className = class_exists($className) ? $className : $this->defaultException; |
|||
|
|||
throw $this->createException($className, $request, $response, $parts); |
|||
} |
|||
|
|||
protected function createException($className, RequestInterface $request, ResponseInterface $response, array $parts) { |
|||
$class = new $className($parts['message']); |
|||
|
|||
if ($class instanceof ServiceResponseException) { |
|||
$class->setExceptionCode($parts['code']); |
|||
$class->setExceptionType($parts['type']); |
|||
$class->setResponse($response); |
|||
$class->setRequest($request); |
|||
$class->setRequestId($parts['request_id']); |
|||
} |
|||
return $class; |
|||
} |
|||
} |
|||
@ -0,0 +1,112 @@ |
|||
<?php |
|||
|
|||
namespace Qcloud\Cos; |
|||
|
|||
use Psr\Http\Message\RequestInterface; |
|||
use Psr\Http\Message\ResponseInterface; |
|||
|
|||
/** |
|||
* Parses default XML exception responses |
|||
*/ |
|||
class ExceptionParser { |
|||
|
|||
public function parse(RequestInterface $request, ResponseInterface $response) { |
|||
$data = array( |
|||
'code' => null, |
|||
'message' => null, |
|||
//'type' => $response->isClientError() ? 'client' : 'server', |
|||
'type' => 'client', |
|||
'request_id' => null, |
|||
'parsed' => null |
|||
); |
|||
|
|||
$body = strval($response->getBody()); |
|||
|
|||
if (empty($body)) { |
|||
$this->parseHeaders($request, $response, $data); |
|||
return $data; |
|||
} |
|||
|
|||
try { |
|||
$xml = new \SimpleXMLElement(utf8_encode($body)); |
|||
$this->parseBody($xml, $data); |
|||
return $data; |
|||
} catch (\Exception $e) { |
|||
$data['code'] = 'PhpInternalXmlParseError'; |
|||
$data['message'] = 'A non-XML response was received'; |
|||
return $data; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Parses additional exception information from the response headers |
|||
* |
|||
* @param RequestInterface $request Request that was issued |
|||
* @param Response $response The response from the request |
|||
* @param array $data The current set of exception data |
|||
*/ |
|||
protected function parseHeaders(RequestInterface $request, ResponseInterface $response, array &$data) { |
|||
$data['message'] = $response->getStatusCode() . ' ' . $response->getReasonPhrase(); |
|||
$requestId = $response->getHeader('x-cos-request-id'); |
|||
if (isset($requestId[0])) { |
|||
$requestId = $requestId[0]; |
|||
$data['request_id'] = $requestId; |
|||
$data['message'] .= " (Request-ID: $requestId)"; |
|||
} |
|||
|
|||
// Get the request |
|||
$status = $response->getStatusCode(); |
|||
$method = $request->getMethod(); |
|||
|
|||
// Attempt to determine code for 403s and 404s |
|||
if ($status === 403) { |
|||
$data['code'] = 'AccessDenied'; |
|||
} elseif ($method === 'HEAD' && $status === 404) { |
|||
$path = explode('/', trim($request->getUri()->getPath(), '/')); |
|||
$host = explode('.', $request->getUri()->getHost()); |
|||
$bucket = (count($host) >= 4) ? $host[0] : array_shift($path); |
|||
$object = array_shift($path); |
|||
|
|||
if ($bucket && $object) { |
|||
$data['code'] = 'NoSuchKey'; |
|||
} elseif ($bucket) { |
|||
$data['code'] = 'NoSuchBucket'; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Parses additional exception information from the response body |
|||
* |
|||
* @param \SimpleXMLElement $body The response body as XML |
|||
* @param array $data The current set of exception data |
|||
*/ |
|||
protected function parseBody(\SimpleXMLElement $body, array &$data) { |
|||
$data['parsed'] = $body; |
|||
|
|||
$namespaces = $body->getDocNamespaces(); |
|||
if (isset($namespaces[''])) { |
|||
// Account for the default namespace being defined and PHP not being able to handle it :( |
|||
$body->registerXPathNamespace('ns', $namespaces['']); |
|||
$prefix = 'ns:'; |
|||
} else { |
|||
$prefix = ''; |
|||
} |
|||
|
|||
if ($tempXml = $body->xpath("//{$prefix}Code[1]")) { |
|||
$data['code'] = (string) $tempXml[0]; |
|||
} |
|||
|
|||
if ($tempXml = $body->xpath("//{$prefix}Message[1]")) { |
|||
$data['message'] = (string) $tempXml[0]; |
|||
} |
|||
|
|||
$tempXml = $body->xpath("//{$prefix}RequestId[1]"); |
|||
if (empty($tempXml)) { |
|||
$tempXml = $body->xpath("//{$prefix}RequestID[1]"); |
|||
} |
|||
if (isset($tempXml[0])) { |
|||
$data['request_id'] = (string) $tempXml[0]; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,126 @@ |
|||
<?php |
|||
|
|||
namespace Qcloud\Cos; |
|||
|
|||
use Qcloud\Cos\Exception\CosException; |
|||
|
|||
class MultipartUpload { |
|||
/** |
|||
* const var: part size from 1MB to 5GB, and max parts of 10000 are allowed for each upload. |
|||
*/ |
|||
const MIN_PART_SIZE = 1048576; |
|||
const MAX_PART_SIZE = 5368709120; |
|||
const DEFAULT_PART_SIZE = 52428800; |
|||
const MAX_PARTS = 10000; |
|||
|
|||
private $client; |
|||
private $body; |
|||
private $options; |
|||
private $partSize; |
|||
|
|||
public function __construct($client, $body, $options = array()) { |
|||
$this->client = $client; |
|||
$this->body = $body; |
|||
$this->options = $options; |
|||
$this->partSize = $this->calculatePartSize($options['PartSize']); |
|||
unset($options['PartSize']); |
|||
} |
|||
|
|||
public function performUploading() { |
|||
$rt = $this->initiateMultipartUpload(); |
|||
$uploadId = $rt['UploadId']; |
|||
$partNumber = 1; |
|||
$parts = array(); |
|||
for (;;) { |
|||
if ($this->body->eof()) { |
|||
break; |
|||
} |
|||
$body = $this->body->read($this->partSize); |
|||
if (empty($body)) { |
|||
break; |
|||
} |
|||
$result = $this->client->uploadPart(array( |
|||
'Bucket' => $this->options['Bucket'], |
|||
'Key' => $this->options['Key'], |
|||
'Body' => $body, |
|||
'UploadId' => $uploadId, |
|||
'PartNumber' => $partNumber)); |
|||
if (md5($body) != substr($result['ETag'], 1, -1)){ |
|||
throw new CosException("ETag check inconsistency"); |
|||
} |
|||
$part = array('PartNumber' => $partNumber, 'ETag' => $result['ETag']); |
|||
array_push($parts, $part); |
|||
++$partNumber; |
|||
} |
|||
try { |
|||
$rt = $this->client->completeMultipartUpload(array( |
|||
'Bucket' => $this->options['Bucket'], |
|||
'Key' => $this->options['Key'], |
|||
'UploadId' => $uploadId, |
|||
'Parts' => $parts)); |
|||
} catch(\Exception $e){ |
|||
throw $e; |
|||
} |
|||
return $rt; |
|||
} |
|||
|
|||
public function resumeUploading() { |
|||
$uploadId = $this->options['UploadId']; |
|||
$rt = $this->client->ListParts( |
|||
array('UploadId' => $uploadId, |
|||
'Bucket'=>$this->options['Bucket'], |
|||
'Key'=>$this->options['Key'])); |
|||
$parts = array(); |
|||
if (count($rt['Parts']) > 0) { |
|||
foreach ($rt['Parts'] as $part) { |
|||
$parts[$part['PartNumber'] - 1] = array('PartNumber' => $part['PartNumber'], 'ETag' => $part['ETag']); |
|||
} |
|||
} |
|||
for ($partNumber = 1;;++$partNumber) { |
|||
if ($this->body->eof()) { |
|||
break; |
|||
} |
|||
$body = $this->body->read($this->partSize); |
|||
|
|||
if (array_key_exists($partNumber-1, $parts)){ |
|||
|
|||
if (md5($body) != substr($parts[$partNumber-1]['ETag'], 1, -1)){ |
|||
throw new CosException("ETag check inconsistency"); |
|||
} |
|||
continue; |
|||
} |
|||
|
|||
$result = $this->client->uploadPart(array( |
|||
'Bucket' => $this->options['Bucket'], |
|||
'Key' => $this->options['Key'], |
|||
'Body' => $body, |
|||
'UploadId' => $uploadId, |
|||
'PartNumber' => $partNumber)); |
|||
if (md5($body) != substr($result['ETag'], 1, -1)){ |
|||
throw new CosException("ETag check inconsistency"); |
|||
} |
|||
$parts[$partNumber-1] = array('PartNumber' => $partNumber, 'ETag' => $result['ETag']); |
|||
|
|||
} |
|||
$rt = $this->client->completeMultipartUpload(array( |
|||
'Bucket' => $this->options['Bucket'], |
|||
'Key' => $this->options['Key'], |
|||
'UploadId' => $uploadId, |
|||
'Parts' => $parts)); |
|||
return $rt; |
|||
} |
|||
|
|||
private function calculatePartSize($minPartSize) { |
|||
$partSize = intval(ceil(($this->body->getSize() / self::MAX_PARTS))); |
|||
$partSize = max($minPartSize, $partSize); |
|||
$partSize = min($partSize, self::MAX_PART_SIZE); |
|||
$partSize = max($partSize, self::MIN_PART_SIZE); |
|||
|
|||
return $partSize; |
|||
} |
|||
|
|||
private function initiateMultipartUpload() { |
|||
$result = $this->client->createMultipartUpload($this->options); |
|||
return $result; |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
<?php |
|||
|
|||
namespace Qcloud\Cos\Request; |
|||
|
|||
use GuzzleHttp\Command\Guzzle\RequestLocation\AbstractLocation; |
|||
use GuzzleHttp\Command\CommandInterface; |
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Psr7; |
|||
use Psr\Http\Message\MessageInterface; |
|||
use Psr\Http\Message\RequestInterface; |
|||
|
|||
/** |
|||
* Adds a raw/binary body to a request. |
|||
* This is here because: https://github.com/guzzle/guzzle-services/issues/160 |
|||
*/ |
|||
class BodyLocation extends AbstractLocation |
|||
{ |
|||
|
|||
/** |
|||
* Set the name of the location |
|||
* |
|||
* @param string $locationName |
|||
*/ |
|||
public function __construct($locationName = 'body') |
|||
{ |
|||
parent::__construct($locationName); |
|||
} |
|||
|
|||
/** |
|||
* @param CommandInterface $command |
|||
* @param RequestInterface $request |
|||
* @param Parameter $param |
|||
* |
|||
* @return MessageInterface |
|||
*/ |
|||
public function visit( |
|||
CommandInterface $command, |
|||
RequestInterface $request, |
|||
Parameter $param |
|||
) { |
|||
$value = $request->getBody()->getContents(); |
|||
if ('' !== $value) { |
|||
throw new \RuntimeException('Only one "body" location may exist per operation'); |
|||
} |
|||
// binary string data from bound parameter |
|||
$value = $command[$param->getName()]; |
|||
return $request->withBody(Psr7\stream_for($value)); |
|||
} |
|||
} |
|||
@ -0,0 +1,119 @@ |
|||
<?php |
|||
|
|||
namespace Qcloud\Cos; |
|||
|
|||
use Guzzle\Service\Description\Parameter; |
|||
use Guzzle\Service\Description\ServiceDescription; |
|||
use GuzzleHttp\HandlerStack; |
|||
use Psr\Http\Message\RequestInterface; |
|||
use Psr\Http\Message\ResponseInterface; |
|||
use Qcloud\Cos\Signature; |
|||
use GuzzleHttp\Command\Guzzle\Description; |
|||
use GuzzleHttp\Command\Guzzle\GuzzleClient; |
|||
use GuzzleHttp\Command\CommandInterface; |
|||
use GuzzleHttp\Exception\RequestException; |
|||
use GuzzleHttp\Middleware; |
|||
use GuzzleHttp\Psr7; |
|||
use GuzzleHttp\Psr7\Uri; |
|||
use GuzzleHttp\Command\Result; |
|||
use InvalidArgumentException; |
|||
|
|||
|
|||
class ResultTransformer { |
|||
private $config; |
|||
private $operation; |
|||
|
|||
public function __construct($config, $operation) { |
|||
$this->config = $config; |
|||
$this->operation = $operation; |
|||
} |
|||
|
|||
public function writeDataToLocal(CommandInterface $command, RequestInterface $request, ResponseInterface $response) { |
|||
$action = $command->getName(); |
|||
if ($action == "GetObject") { |
|||
if (isset($command['SaveAs'])) { |
|||
$fp = fopen($command['SaveAs'], "wb"); |
|||
fwrite($fp, $response->getBody()); |
|||
fclose($fp); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public function metaDataTransformer(CommandInterface $command, ResponseInterface $response, Result $result) { |
|||
$headers = $response->getHeaders(); |
|||
$metadata = array(); |
|||
foreach ($headers as $key => $value) { |
|||
if (strpos($key, "x-cos-meta-") === 0) { |
|||
$metadata[substr($key, 11)] = $value[0]; |
|||
} |
|||
} |
|||
if (!empty($metadata)) { |
|||
$result['Metadata'] = $metadata; |
|||
} |
|||
return $result; |
|||
} |
|||
|
|||
public function extraHeadersTransformer(CommandInterface $command, RequestInterface $request, Result $result) { |
|||
if ($command['Key'] != null && $result['Key'] == null) { |
|||
$result['Key'] = $command['Key']; |
|||
} |
|||
if ($command['Bucket'] != null && $result['Bucket'] == null) { |
|||
$result['Bucket'] = $command['Bucket']; |
|||
} |
|||
$result['Location'] = $request->getHeader("Host")[0] . $request->getUri()->getPath(); |
|||
return $result; |
|||
} |
|||
|
|||
public function selectContentTransformer(CommandInterface $command, Result $result) { |
|||
$action = $command->getName(); |
|||
if ($action == "SelectObjectContent") { |
|||
$result['Data'] = $this->getSelectContents($result); |
|||
} |
|||
return $result; |
|||
} |
|||
|
|||
public function getSelectContents($result) { |
|||
$f = $result['RawData']; |
|||
while (!$f->eof()) { |
|||
$data = array(); |
|||
$tmp = $f->read(4); |
|||
if (empty($tmp)) { |
|||
break; |
|||
} |
|||
$totol_length = (int)(unpack("N", $tmp)[1]); |
|||
$headers_length = (int)(unpack("N", $f->read(4))[1]); |
|||
$body_length = $totol_length - $headers_length - 16; |
|||
$predule_crc = (int)(unpack("N", $f->read(4))[1]); |
|||
$headers = array(); |
|||
for ($offset = 0; $offset < $headers_length;) { |
|||
$key_length = (int)(unpack("C", $f->read(1))[1]); |
|||
$key = $f->read($key_length); |
|||
|
|||
$head_value_type = (int)(unpack("C", $f->read(1))[1]); |
|||
|
|||
$value_length = (int)(unpack("n", $f->read(2))[1]); |
|||
$value = $f->read($value_length); |
|||
$offset += 4 + $key_length + $value_length; |
|||
if ($key == ":message-type") { |
|||
$data['MessageType'] = $value; |
|||
} |
|||
if ($key == ":event-type") { |
|||
$data['EventType'] = $value; |
|||
} |
|||
if ($key == ":error-code") { |
|||
$data['ErrorCode'] = $value; |
|||
} |
|||
if ($key == ":error-message") { |
|||
$data['ErrorMessage'] = $value; |
|||
} |
|||
} |
|||
$body = $f->read($body_length); |
|||
$message_crc = (int)(unpack("N", $f->read(4))[1]); |
|||
$data['Body'] = $body; |
|||
yield $data; |
|||
} |
|||
} |
|||
public function __destruct() { |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
<?php |
|||
namespace Qcloud\Cos; |
|||
use GuzzleHttp\Command\CommandInterface; |
|||
use GuzzleHttp\Command\Guzzle\SchemaValidator; |
|||
use GuzzleHttp\Command\Guzzle\DescriptionInterface; |
|||
use GuzzleHttp\Command\Guzzle\Serializer as DefaultSerializer; |
|||
use Psr\Http\Message\RequestInterface; |
|||
/** |
|||
* Override Request serializer to modify authentication mechanism |
|||
*/ |
|||
class Serializer extends DefaultSerializer |
|||
{ |
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function __construct( |
|||
DescriptionInterface $description, |
|||
array $requestLocations = [] |
|||
) { |
|||
// Override Guzzle's body location as it isn't raw binary data |
|||
$requestLocations['body'] = new Request\BodyLocation; |
|||
parent::__construct($description, $requestLocations); |
|||
} |
|||
/** |
|||
* Authorization header is Loco's preferred authorization method. |
|||
* Add Authorization header to request if API key is set, unless query is explicitly configured as auth method. |
|||
* Unset key from command to avoid sending it as a query param. |
|||
* |
|||
* @override |
|||
* |
|||
* @param CommandInterface $command |
|||
* @param RequestInterface $request |
|||
* |
|||
* @return RequestInterface |
|||
* |
|||
* @throws \InvalidArgumentException |
|||
*/ |
|||
protected function prepareRequest( |
|||
CommandInterface $command, |
|||
RequestInterface $request |
|||
) { |
|||
/* |
|||
if ($command->offsetExists('key') === true) { |
|||
$mode = empty($command->offsetGet('auth')) === false |
|||
? $command->offsetGet('auth') |
|||
: 'loco'; |
|||
if ($mode !== 'query') { |
|||
// else use Authorization header of various types |
|||
if ($mode === 'loco') { |
|||
$value = 'Loco '.$command->offsetGet('key'); |
|||
$request = $request->withHeader('Authorization', $value); |
|||
} elseif ($mode === 'basic') { |
|||
$value = 'Basic '.base64_encode($command->offsetGet('key').':'); |
|||
$request = $request->withHeader('Authorization', $value); |
|||
} else { |
|||
throw new \InvalidArgumentException("Invalid auth type: {$mode}"); |
|||
} |
|||
// avoid request sending key parameter in query string |
|||
$command->offsetUnset('key'); |
|||
} |
|||
} |
|||
// Remap legacy parameters to common `data` binding on request body |
|||
static $remap = [ |
|||
'import' => ['src'=>'data'], |
|||
'translate' => ['translation'=>'data'], |
|||
]; |
|||
$name = $command->getName(); |
|||
if (isset($remap[$name])) { |
|||
foreach ($remap[$name] as $old => $new) { |
|||
if ($command->offsetExists($old)) { |
|||
$command->offsetSet($new, $command->offsetGet($old)); |
|||
$command->offsetUnset($old); |
|||
} |
|||
} |
|||
} |
|||
*/ |
|||
return parent::prepareRequest($command, $request); |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,45 @@ |
|||
<?php |
|||
|
|||
namespace Qcloud\Cos; |
|||
|
|||
use Psr\Http\Message\RequestInterface; |
|||
|
|||
class Signature { |
|||
private $accessKey; // string: access key. |
|||
private $secretKey; // string: secret key. |
|||
public function __construct($accessKey, $secretKey, $token=null) { |
|||
$this->accessKey = $accessKey; |
|||
$this->secretKey = $secretKey; |
|||
$this->token = $token; |
|||
date_default_timezone_set("PRC"); |
|||
} |
|||
public function __destruct() { |
|||
} |
|||
public function signRequest(RequestInterface $request) { |
|||
$authorization = $this->createAuthorization($request); |
|||
return $request->withHeader('Authorization', $authorization); |
|||
} |
|||
public function createAuthorization(RequestInterface $request, $expires = "+30 minutes") { |
|||
$signTime = (string)(time() - 60) . ';' . (string)(strtotime($expires)); |
|||
$httpString = strtolower($request->getMethod()) . "\n" . urldecode($request->getUri()->getPath()) . |
|||
"\n\nhost=" . $request->getHeader("Host")[0]. "\n"; |
|||
$sha1edHttpString = sha1($httpString); |
|||
$stringToSign = "sha1\n$signTime\n$sha1edHttpString\n"; |
|||
$signKey = hash_hmac('sha1', $signTime, $this->secretKey); |
|||
$signature = hash_hmac('sha1', $stringToSign, $signKey); |
|||
$authorization = 'q-sign-algorithm=sha1&q-ak='. $this->accessKey . |
|||
"&q-sign-time=$signTime&q-key-time=$signTime&q-header-list=host&q-url-param-list=&" . |
|||
"q-signature=$signature"; |
|||
return $authorization; |
|||
} |
|||
public function createPresignedUrl(RequestInterface $request, $expires = "+30 minutes") { |
|||
$authorization = $this->createAuthorization($request, $expires); |
|||
$uri = $request->getUri(); |
|||
$query = "sign=".urlencode($authorization); |
|||
if ($this->token != null) { |
|||
$query = $query."&x-cos-security-token=".$this->token; |
|||
} |
|||
$uri = $uri->withQuery($query); |
|||
return $uri; |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
<?php |
|||
|
|||
namespace Qcloud\Cos; |
|||
|
|||
use Qcloud\Cos\Exception\ServiceResponseException; |
|||
use GuzzleHttp\Promise\PromiseInterface; |
|||
use GuzzleHttp\Psr7; |
|||
use Psr\Http\Message\RequestInterface; |
|||
use Psr\Http\Message\ResponseInterface; |
|||
use GuzzleHttp\Exception\RequestException; |
|||
|
|||
class SignatureMiddleware { |
|||
private $nextHandler; |
|||
protected $signature; |
|||
|
|||
/** |
|||
* @param callable $nextHandler Next handler to invoke. |
|||
*/ |
|||
public function __construct(callable $nextHandler, $accessKey, $secretKey) { |
|||
$this->nextHandler = $nextHandler; |
|||
$this->signature = new Signature($accessKey, $secretKey); |
|||
} |
|||
|
|||
public function __invoke(RequestInterface $request, array $options) { |
|||
$fn = $this->nextHandler; |
|||
return $fn($this->signature->signRequest($request), $options); |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,45 @@ |
|||
<?php |
|||
|
|||
namespace Qcloud\Cos\Tests; |
|||
|
|||
use Qcloud\Cos\Client; |
|||
|
|||
class TestHelper { |
|||
|
|||
public static function nuke($bucket) { |
|||
try { |
|||
$cosClient = new Client(array('region' => getenv('COS_REGION'), |
|||
'credentials'=> array( |
|||
'appId' => getenv('COS_APPID'), |
|||
'secretId' => getenv('COS_KEY'), |
|||
'secretKey' => getenv('COS_SECRET')))); |
|||
$result = $cosClient->listObjects(array('Bucket' => $bucket)); |
|||
if (isset($result['Contents'])) { |
|||
foreach ($result['Contents'] as $content) { |
|||
$cosClient->deleteObject(array('Bucket' => $bucket, 'Key' => $content['Key'])); |
|||
} |
|||
} |
|||
|
|||
while(True){ |
|||
$result = $cosClient->ListMultipartUploads( |
|||
array('Bucket' => $bucket)); |
|||
if (count($result['Uploads']) == 0){ |
|||
break; |
|||
} |
|||
foreach ($result['Uploads'] as $upload) { |
|||
try { |
|||
$rt = $cosClient->AbortMultipartUpload( |
|||
array('Bucket' => $bucket, |
|||
'Key' => $upload['Key'], |
|||
'UploadId' => $upload['UploadId'])); |
|||
} catch (\Exception $e) { |
|||
print_r($e); |
|||
} |
|||
} |
|||
} |
|||
$cosClient->deleteBucket(array('Bucket' => $bucket)); |
|||
} catch (\Exception $e) { |
|||
// echo "$e\n"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
<?php |
|||
|
|||
// autoload.php @generated by Composer |
|||
|
|||
require_once __DIR__ . '/composer/autoload_real.php'; |
|||
|
|||
return ComposerAutoloaderInit6790e31be3eb151cb678c9fe759a5905::getLoader(); |
|||
@ -0,0 +1,445 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Composer. |
|||
* |
|||
* (c) Nils Adermann <naderman@naderman.de> |
|||
* Jordi Boggiano <j.boggiano@seld.be> |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
namespace Composer\Autoload; |
|||
|
|||
/** |
|||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader. |
|||
* |
|||
* $loader = new \Composer\Autoload\ClassLoader(); |
|||
* |
|||
* // register classes with namespaces |
|||
* $loader->add('Symfony\Component', __DIR__.'/component'); |
|||
* $loader->add('Symfony', __DIR__.'/framework'); |
|||
* |
|||
* // activate the autoloader |
|||
* $loader->register(); |
|||
* |
|||
* // to enable searching the include path (eg. for PEAR packages) |
|||
* $loader->setUseIncludePath(true); |
|||
* |
|||
* In this example, if you try to use a class in the Symfony\Component |
|||
* namespace or one of its children (Symfony\Component\Console for instance), |
|||
* the autoloader will first look for the class under the component/ |
|||
* directory, and it will then fallback to the framework/ directory if not |
|||
* found before giving up. |
|||
* |
|||
* This class is loosely based on the Symfony UniversalClassLoader. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
* @author Jordi Boggiano <j.boggiano@seld.be> |
|||
* @see http://www.php-fig.org/psr/psr-0/ |
|||
* @see http://www.php-fig.org/psr/psr-4/ |
|||
*/ |
|||
class ClassLoader |
|||
{ |
|||
// PSR-4 |
|||
private $prefixLengthsPsr4 = array(); |
|||
private $prefixDirsPsr4 = array(); |
|||
private $fallbackDirsPsr4 = array(); |
|||
|
|||
// PSR-0 |
|||
private $prefixesPsr0 = array(); |
|||
private $fallbackDirsPsr0 = array(); |
|||
|
|||
private $useIncludePath = false; |
|||
private $classMap = array(); |
|||
private $classMapAuthoritative = false; |
|||
private $missingClasses = array(); |
|||
private $apcuPrefix; |
|||
|
|||
public function getPrefixes() |
|||
{ |
|||
if (!empty($this->prefixesPsr0)) { |
|||
return call_user_func_array('array_merge', $this->prefixesPsr0); |
|||
} |
|||
|
|||
return array(); |
|||
} |
|||
|
|||
public function getPrefixesPsr4() |
|||
{ |
|||
return $this->prefixDirsPsr4; |
|||
} |
|||
|
|||
public function getFallbackDirs() |
|||
{ |
|||
return $this->fallbackDirsPsr0; |
|||
} |
|||
|
|||
public function getFallbackDirsPsr4() |
|||
{ |
|||
return $this->fallbackDirsPsr4; |
|||
} |
|||
|
|||
public function getClassMap() |
|||
{ |
|||
return $this->classMap; |
|||
} |
|||
|
|||
/** |
|||
* @param array $classMap Class to filename map |
|||
*/ |
|||
public function addClassMap(array $classMap) |
|||
{ |
|||
if ($this->classMap) { |
|||
$this->classMap = array_merge($this->classMap, $classMap); |
|||
} else { |
|||
$this->classMap = $classMap; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Registers a set of PSR-0 directories for a given prefix, either |
|||
* appending or prepending to the ones previously set for this prefix. |
|||
* |
|||
* @param string $prefix The prefix |
|||
* @param array|string $paths The PSR-0 root directories |
|||
* @param bool $prepend Whether to prepend the directories |
|||
*/ |
|||
public function add($prefix, $paths, $prepend = false) |
|||
{ |
|||
if (!$prefix) { |
|||
if ($prepend) { |
|||
$this->fallbackDirsPsr0 = array_merge( |
|||
(array) $paths, |
|||
$this->fallbackDirsPsr0 |
|||
); |
|||
} else { |
|||
$this->fallbackDirsPsr0 = array_merge( |
|||
$this->fallbackDirsPsr0, |
|||
(array) $paths |
|||
); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
$first = $prefix[0]; |
|||
if (!isset($this->prefixesPsr0[$first][$prefix])) { |
|||
$this->prefixesPsr0[$first][$prefix] = (array) $paths; |
|||
|
|||
return; |
|||
} |
|||
if ($prepend) { |
|||
$this->prefixesPsr0[$first][$prefix] = array_merge( |
|||
(array) $paths, |
|||
$this->prefixesPsr0[$first][$prefix] |
|||
); |
|||
} else { |
|||
$this->prefixesPsr0[$first][$prefix] = array_merge( |
|||
$this->prefixesPsr0[$first][$prefix], |
|||
(array) $paths |
|||
); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Registers a set of PSR-4 directories for a given namespace, either |
|||
* appending or prepending to the ones previously set for this namespace. |
|||
* |
|||
* @param string $prefix The prefix/namespace, with trailing '\\' |
|||
* @param array|string $paths The PSR-4 base directories |
|||
* @param bool $prepend Whether to prepend the directories |
|||
* |
|||
* @throws \InvalidArgumentException |
|||
*/ |
|||
public function addPsr4($prefix, $paths, $prepend = false) |
|||
{ |
|||
if (!$prefix) { |
|||
// Register directories for the root namespace. |
|||
if ($prepend) { |
|||
$this->fallbackDirsPsr4 = array_merge( |
|||
(array) $paths, |
|||
$this->fallbackDirsPsr4 |
|||
); |
|||
} else { |
|||
$this->fallbackDirsPsr4 = array_merge( |
|||
$this->fallbackDirsPsr4, |
|||
(array) $paths |
|||
); |
|||
} |
|||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) { |
|||
// Register directories for a new namespace. |
|||
$length = strlen($prefix); |
|||
if ('\\' !== $prefix[$length - 1]) { |
|||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); |
|||
} |
|||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; |
|||
$this->prefixDirsPsr4[$prefix] = (array) $paths; |
|||
} elseif ($prepend) { |
|||
// Prepend directories for an already registered namespace. |
|||
$this->prefixDirsPsr4[$prefix] = array_merge( |
|||
(array) $paths, |
|||
$this->prefixDirsPsr4[$prefix] |
|||
); |
|||
} else { |
|||
// Append directories for an already registered namespace. |
|||
$this->prefixDirsPsr4[$prefix] = array_merge( |
|||
$this->prefixDirsPsr4[$prefix], |
|||
(array) $paths |
|||
); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Registers a set of PSR-0 directories for a given prefix, |
|||
* replacing any others previously set for this prefix. |
|||
* |
|||
* @param string $prefix The prefix |
|||
* @param array|string $paths The PSR-0 base directories |
|||
*/ |
|||
public function set($prefix, $paths) |
|||
{ |
|||
if (!$prefix) { |
|||
$this->fallbackDirsPsr0 = (array) $paths; |
|||
} else { |
|||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Registers a set of PSR-4 directories for a given namespace, |
|||
* replacing any others previously set for this namespace. |
|||
* |
|||
* @param string $prefix The prefix/namespace, with trailing '\\' |
|||
* @param array|string $paths The PSR-4 base directories |
|||
* |
|||
* @throws \InvalidArgumentException |
|||
*/ |
|||
public function setPsr4($prefix, $paths) |
|||
{ |
|||
if (!$prefix) { |
|||
$this->fallbackDirsPsr4 = (array) $paths; |
|||
} else { |
|||
$length = strlen($prefix); |
|||
if ('\\' !== $prefix[$length - 1]) { |
|||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); |
|||
} |
|||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; |
|||
$this->prefixDirsPsr4[$prefix] = (array) $paths; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Turns on searching the include path for class files. |
|||
* |
|||
* @param bool $useIncludePath |
|||
*/ |
|||
public function setUseIncludePath($useIncludePath) |
|||
{ |
|||
$this->useIncludePath = $useIncludePath; |
|||
} |
|||
|
|||
/** |
|||
* Can be used to check if the autoloader uses the include path to check |
|||
* for classes. |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function getUseIncludePath() |
|||
{ |
|||
return $this->useIncludePath; |
|||
} |
|||
|
|||
/** |
|||
* Turns off searching the prefix and fallback directories for classes |
|||
* that have not been registered with the class map. |
|||
* |
|||
* @param bool $classMapAuthoritative |
|||
*/ |
|||
public function setClassMapAuthoritative($classMapAuthoritative) |
|||
{ |
|||
$this->classMapAuthoritative = $classMapAuthoritative; |
|||
} |
|||
|
|||
/** |
|||
* Should class lookup fail if not found in the current class map? |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function isClassMapAuthoritative() |
|||
{ |
|||
return $this->classMapAuthoritative; |
|||
} |
|||
|
|||
/** |
|||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled. |
|||
* |
|||
* @param string|null $apcuPrefix |
|||
*/ |
|||
public function setApcuPrefix($apcuPrefix) |
|||
{ |
|||
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; |
|||
} |
|||
|
|||
/** |
|||
* The APCu prefix in use, or null if APCu caching is not enabled. |
|||
* |
|||
* @return string|null |
|||
*/ |
|||
public function getApcuPrefix() |
|||
{ |
|||
return $this->apcuPrefix; |
|||
} |
|||
|
|||
/** |
|||
* Registers this instance as an autoloader. |
|||
* |
|||
* @param bool $prepend Whether to prepend the autoloader or not |
|||
*/ |
|||
public function register($prepend = false) |
|||
{ |
|||
spl_autoload_register(array($this, 'loadClass'), true, $prepend); |
|||
} |
|||
|
|||
/** |
|||
* Unregisters this instance as an autoloader. |
|||
*/ |
|||
public function unregister() |
|||
{ |
|||
spl_autoload_unregister(array($this, 'loadClass')); |
|||
} |
|||
|
|||
/** |
|||
* Loads the given class or interface. |
|||
* |
|||
* @param string $class The name of the class |
|||
* @return bool|null True if loaded, null otherwise |
|||
*/ |
|||
public function loadClass($class) |
|||
{ |
|||
if ($file = $this->findFile($class)) { |
|||
includeFile($file); |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Finds the path to the file where the class is defined. |
|||
* |
|||
* @param string $class The name of the class |
|||
* |
|||
* @return string|false The path if found, false otherwise |
|||
*/ |
|||
public function findFile($class) |
|||
{ |
|||
// class map lookup |
|||
if (isset($this->classMap[$class])) { |
|||
return $this->classMap[$class]; |
|||
} |
|||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { |
|||
return false; |
|||
} |
|||
if (null !== $this->apcuPrefix) { |
|||
$file = apcu_fetch($this->apcuPrefix.$class, $hit); |
|||
if ($hit) { |
|||
return $file; |
|||
} |
|||
} |
|||
|
|||
$file = $this->findFileWithExtension($class, '.php'); |
|||
|
|||
// Search for Hack files if we are running on HHVM |
|||
if (false === $file && defined('HHVM_VERSION')) { |
|||
$file = $this->findFileWithExtension($class, '.hh'); |
|||
} |
|||
|
|||
if (null !== $this->apcuPrefix) { |
|||
apcu_add($this->apcuPrefix.$class, $file); |
|||
} |
|||
|
|||
if (false === $file) { |
|||
// Remember that this class does not exist. |
|||
$this->missingClasses[$class] = true; |
|||
} |
|||
|
|||
return $file; |
|||
} |
|||
|
|||
private function findFileWithExtension($class, $ext) |
|||
{ |
|||
// PSR-4 lookup |
|||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; |
|||
|
|||
$first = $class[0]; |
|||
if (isset($this->prefixLengthsPsr4[$first])) { |
|||
$subPath = $class; |
|||
while (false !== $lastPos = strrpos($subPath, '\\')) { |
|||
$subPath = substr($subPath, 0, $lastPos); |
|||
$search = $subPath . '\\'; |
|||
if (isset($this->prefixDirsPsr4[$search])) { |
|||
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); |
|||
foreach ($this->prefixDirsPsr4[$search] as $dir) { |
|||
if (file_exists($file = $dir . $pathEnd)) { |
|||
return $file; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// PSR-4 fallback dirs |
|||
foreach ($this->fallbackDirsPsr4 as $dir) { |
|||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { |
|||
return $file; |
|||
} |
|||
} |
|||
|
|||
// PSR-0 lookup |
|||
if (false !== $pos = strrpos($class, '\\')) { |
|||
// namespaced class name |
|||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) |
|||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); |
|||
} else { |
|||
// PEAR-like class name |
|||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; |
|||
} |
|||
|
|||
if (isset($this->prefixesPsr0[$first])) { |
|||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { |
|||
if (0 === strpos($class, $prefix)) { |
|||
foreach ($dirs as $dir) { |
|||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { |
|||
return $file; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// PSR-0 fallback dirs |
|||
foreach ($this->fallbackDirsPsr0 as $dir) { |
|||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { |
|||
return $file; |
|||
} |
|||
} |
|||
|
|||
// PSR-0 include paths. |
|||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { |
|||
return $file; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Scope isolated include. |
|||
* |
|||
* Prevents access to $this/self from included files. |
|||
*/ |
|||
function includeFile($file) |
|||
{ |
|||
include $file; |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
<?php |
|||
|
|||
// autoload_classmap.php @generated by Composer |
|||
|
|||
$vendorDir = dirname(dirname(__FILE__)); |
|||
$baseDir = dirname($vendorDir); |
|||
|
|||
return array( |
|||
); |
|||
@ -0,0 +1,13 @@ |
|||
<?php |
|||
|
|||
// autoload_files.php @generated by Composer |
|||
|
|||
$vendorDir = dirname(dirname(__FILE__)); |
|||
$baseDir = dirname($vendorDir); |
|||
|
|||
return array( |
|||
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', |
|||
'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php', |
|||
'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php', |
|||
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', |
|||
); |
|||
@ -0,0 +1,10 @@ |
|||
<?php |
|||
|
|||
// autoload_namespaces.php @generated by Composer |
|||
|
|||
$vendorDir = dirname(dirname(__FILE__)); |
|||
$baseDir = dirname($vendorDir); |
|||
|
|||
return array( |
|||
'Qcloud\\Cos\\' => array($baseDir . '/src'), |
|||
); |
|||
@ -0,0 +1,15 @@ |
|||
<?php |
|||
|
|||
// autoload_psr4.php @generated by Composer |
|||
|
|||
$vendorDir = dirname(dirname(__FILE__)); |
|||
$baseDir = dirname($vendorDir); |
|||
|
|||
return array( |
|||
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'), |
|||
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'), |
|||
'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'), |
|||
'GuzzleHttp\\Command\\Guzzle\\' => array($vendorDir . '/guzzlehttp/guzzle-services/src'), |
|||
'GuzzleHttp\\Command\\' => array($vendorDir . '/guzzlehttp/command/src'), |
|||
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), |
|||
); |
|||
@ -0,0 +1,70 @@ |
|||
<?php |
|||
|
|||
// autoload_real.php @generated by Composer |
|||
|
|||
class ComposerAutoloaderInit6790e31be3eb151cb678c9fe759a5905 |
|||
{ |
|||
private static $loader; |
|||
|
|||
public static function loadClassLoader($class) |
|||
{ |
|||
if ('Composer\Autoload\ClassLoader' === $class) { |
|||
require __DIR__ . '/ClassLoader.php'; |
|||
} |
|||
} |
|||
|
|||
public static function getLoader() |
|||
{ |
|||
if (null !== self::$loader) { |
|||
return self::$loader; |
|||
} |
|||
|
|||
spl_autoload_register(array('ComposerAutoloaderInit6790e31be3eb151cb678c9fe759a5905', 'loadClassLoader'), true, true); |
|||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(); |
|||
spl_autoload_unregister(array('ComposerAutoloaderInit6790e31be3eb151cb678c9fe759a5905', 'loadClassLoader')); |
|||
|
|||
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); |
|||
if ($useStaticLoader) { |
|||
require_once __DIR__ . '/autoload_static.php'; |
|||
|
|||
call_user_func(\Composer\Autoload\ComposerStaticInit6790e31be3eb151cb678c9fe759a5905::getInitializer($loader)); |
|||
} else { |
|||
$map = require __DIR__ . '/autoload_namespaces.php'; |
|||
foreach ($map as $namespace => $path) { |
|||
$loader->set($namespace, $path); |
|||
} |
|||
|
|||
$map = require __DIR__ . '/autoload_psr4.php'; |
|||
foreach ($map as $namespace => $path) { |
|||
$loader->setPsr4($namespace, $path); |
|||
} |
|||
|
|||
$classMap = require __DIR__ . '/autoload_classmap.php'; |
|||
if ($classMap) { |
|||
$loader->addClassMap($classMap); |
|||
} |
|||
} |
|||
|
|||
$loader->register(true); |
|||
|
|||
if ($useStaticLoader) { |
|||
$includeFiles = Composer\Autoload\ComposerStaticInit6790e31be3eb151cb678c9fe759a5905::$files; |
|||
} else { |
|||
$includeFiles = require __DIR__ . '/autoload_files.php'; |
|||
} |
|||
foreach ($includeFiles as $fileIdentifier => $file) { |
|||
composerRequire6790e31be3eb151cb678c9fe759a5905($fileIdentifier, $file); |
|||
} |
|||
|
|||
return $loader; |
|||
} |
|||
} |
|||
|
|||
function composerRequire6790e31be3eb151cb678c9fe759a5905($fileIdentifier, $file) |
|||
{ |
|||
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { |
|||
require $file; |
|||
|
|||
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; |
|||
} |
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
<?php |
|||
|
|||
// autoload_static.php @generated by Composer |
|||
|
|||
namespace Composer\Autoload; |
|||
|
|||
class ComposerStaticInit6790e31be3eb151cb678c9fe759a5905 |
|||
{ |
|||
public static $files = array ( |
|||
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', |
|||
'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php', |
|||
'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php', |
|||
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', |
|||
); |
|||
|
|||
public static $prefixLengthsPsr4 = array ( |
|||
'P' => |
|||
array ( |
|||
'Psr\\Http\\Message\\' => 17, |
|||
), |
|||
'G' => |
|||
array ( |
|||
'GuzzleHttp\\Psr7\\' => 16, |
|||
'GuzzleHttp\\Promise\\' => 19, |
|||
'GuzzleHttp\\Command\\Guzzle\\' => 26, |
|||
'GuzzleHttp\\Command\\' => 19, |
|||
'GuzzleHttp\\' => 11, |
|||
), |
|||
); |
|||
|
|||
public static $prefixDirsPsr4 = array ( |
|||
'Psr\\Http\\Message\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/psr/http-message/src', |
|||
), |
|||
'GuzzleHttp\\Psr7\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src', |
|||
), |
|||
'GuzzleHttp\\Promise\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/guzzlehttp/promises/src', |
|||
), |
|||
'GuzzleHttp\\Command\\Guzzle\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/guzzlehttp/guzzle-services/src', |
|||
), |
|||
'GuzzleHttp\\Command\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/guzzlehttp/command/src', |
|||
), |
|||
'GuzzleHttp\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src', |
|||
), |
|||
); |
|||
|
|||
public static $prefixesPsr0 = array ( |
|||
'Q' => |
|||
array ( |
|||
'Qcloud\\Cos\\' => |
|||
array ( |
|||
0 => __DIR__ . '/../..' . '/src', |
|||
), |
|||
), |
|||
); |
|||
|
|||
public static function getInitializer(ClassLoader $loader) |
|||
{ |
|||
return \Closure::bind(function () use ($loader) { |
|||
$loader->prefixLengthsPsr4 = ComposerStaticInit6790e31be3eb151cb678c9fe759a5905::$prefixLengthsPsr4; |
|||
$loader->prefixDirsPsr4 = ComposerStaticInit6790e31be3eb151cb678c9fe759a5905::$prefixDirsPsr4; |
|||
$loader->prefixesPsr0 = ComposerStaticInit6790e31be3eb151cb678c9fe759a5905::$prefixesPsr0; |
|||
|
|||
}, null, ClassLoader::class); |
|||
} |
|||
} |
|||
@ -0,0 +1,408 @@ |
|||
[ |
|||
{ |
|||
"name": "guzzlehttp/command", |
|||
"version": "1.0.0", |
|||
"version_normalized": "1.0.0.0", |
|||
"source": { |
|||
"type": "git", |
|||
"url": "https://github.com/guzzle/command.git", |
|||
"reference": "2aaa2521a8f8269d6f5dfc13fe2af12c76921034" |
|||
}, |
|||
"dist": { |
|||
"type": "zip", |
|||
"url": "https://api.github.com/repos/guzzle/command/zipball/2aaa2521a8f8269d6f5dfc13fe2af12c76921034", |
|||
"reference": "2aaa2521a8f8269d6f5dfc13fe2af12c76921034", |
|||
"shasum": "" |
|||
}, |
|||
"require": { |
|||
"guzzlehttp/guzzle": "^6.2", |
|||
"guzzlehttp/promises": "~1.3", |
|||
"guzzlehttp/psr7": "~1.0", |
|||
"php": ">=5.5.0" |
|||
}, |
|||
"require-dev": { |
|||
"phpunit/phpunit": "~4.0|~5.0" |
|||
}, |
|||
"time": "2016-11-24T13:34:15+00:00", |
|||
"type": "library", |
|||
"extra": { |
|||
"branch-alias": { |
|||
"dev-master": "0.9-dev" |
|||
} |
|||
}, |
|||
"installation-source": "dist", |
|||
"autoload": { |
|||
"psr-4": { |
|||
"GuzzleHttp\\Command\\": "src/" |
|||
} |
|||
}, |
|||
"notification-url": "https://packagist.org/downloads/", |
|||
"license": [ |
|||
"MIT" |
|||
], |
|||
"authors": [ |
|||
{ |
|||
"name": "Michael Dowling", |
|||
"email": "mtdowling@gmail.com", |
|||
"homepage": "https://github.com/mtdowling" |
|||
}, |
|||
{ |
|||
"name": "Jeremy Lindblom", |
|||
"email": "jeremeamia@gmail.com", |
|||
"homepage": "https://github.com/jeremeamia" |
|||
} |
|||
], |
|||
"description": "Provides the foundation for building command-based web service clients" |
|||
}, |
|||
{ |
|||
"name": "guzzlehttp/guzzle", |
|||
"version": "6.5.2", |
|||
"version_normalized": "6.5.2.0", |
|||
"source": { |
|||
"type": "git", |
|||
"url": "https://github.com/guzzle/guzzle.git", |
|||
"reference": "43ece0e75098b7ecd8d13918293029e555a50f82" |
|||
}, |
|||
"dist": { |
|||
"type": "zip", |
|||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/43ece0e75098b7ecd8d13918293029e555a50f82", |
|||
"reference": "43ece0e75098b7ecd8d13918293029e555a50f82", |
|||
"shasum": "" |
|||
}, |
|||
"require": { |
|||
"ext-json": "*", |
|||
"guzzlehttp/promises": "^1.0", |
|||
"guzzlehttp/psr7": "^1.6.1", |
|||
"php": ">=5.5" |
|||
}, |
|||
"require-dev": { |
|||
"ext-curl": "*", |
|||
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", |
|||
"psr/log": "^1.1" |
|||
}, |
|||
"suggest": { |
|||
"ext-intl": "Required for Internationalized Domain Name (IDN) support", |
|||
"psr/log": "Required for using the Log middleware" |
|||
}, |
|||
"time": "2019-12-23T11:57:10+00:00", |
|||
"type": "library", |
|||
"extra": { |
|||
"branch-alias": { |
|||
"dev-master": "6.5-dev" |
|||
} |
|||
}, |
|||
"installation-source": "dist", |
|||
"autoload": { |
|||
"psr-4": { |
|||
"GuzzleHttp\\": "src/" |
|||
}, |
|||
"files": [ |
|||
"src/functions_include.php" |
|||
] |
|||
}, |
|||
"notification-url": "https://packagist.org/downloads/", |
|||
"license": [ |
|||
"MIT" |
|||
], |
|||
"authors": [ |
|||
{ |
|||
"name": "Michael Dowling", |
|||
"email": "mtdowling@gmail.com", |
|||
"homepage": "https://github.com/mtdowling" |
|||
} |
|||
], |
|||
"description": "Guzzle is a PHP HTTP client library", |
|||
"homepage": "http://guzzlephp.org/", |
|||
"keywords": [ |
|||
"client", |
|||
"curl", |
|||
"framework", |
|||
"http", |
|||
"http client", |
|||
"rest", |
|||
"web service" |
|||
] |
|||
}, |
|||
{ |
|||
"name": "guzzlehttp/guzzle-services", |
|||
"version": "1.1.3", |
|||
"version_normalized": "1.1.3.0", |
|||
"source": { |
|||
"type": "git", |
|||
"url": "https://github.com/guzzle/guzzle-services.git", |
|||
"reference": "9e3abf20161cbf662d616cbb995f2811771759f7" |
|||
}, |
|||
"dist": { |
|||
"type": "zip", |
|||
"url": "https://api.github.com/repos/guzzle/guzzle-services/zipball/9e3abf20161cbf662d616cbb995f2811771759f7", |
|||
"reference": "9e3abf20161cbf662d616cbb995f2811771759f7", |
|||
"shasum": "" |
|||
}, |
|||
"require": { |
|||
"guzzlehttp/command": "~1.0", |
|||
"guzzlehttp/guzzle": "^6.2", |
|||
"php": ">=5.5" |
|||
}, |
|||
"require-dev": { |
|||
"phpunit/phpunit": "~4.0" |
|||
}, |
|||
"suggest": { |
|||
"gimler/guzzle-description-loader": "^0.0.4" |
|||
}, |
|||
"time": "2017-10-06T14:32:02+00:00", |
|||
"type": "library", |
|||
"extra": { |
|||
"branch-alias": { |
|||
"dev-master": "1.0.x-dev" |
|||
} |
|||
}, |
|||
"installation-source": "dist", |
|||
"autoload": { |
|||
"psr-4": { |
|||
"GuzzleHttp\\Command\\Guzzle\\": "src/" |
|||
} |
|||
}, |
|||
"notification-url": "https://packagist.org/downloads/", |
|||
"license": [ |
|||
"MIT" |
|||
], |
|||
"authors": [ |
|||
{ |
|||
"name": "Michael Dowling", |
|||
"email": "mtdowling@gmail.com", |
|||
"homepage": "https://github.com/mtdowling" |
|||
}, |
|||
{ |
|||
"name": "Jeremy Lindblom", |
|||
"email": "jeremeamia@gmail.com", |
|||
"homepage": "https://github.com/jeremeamia" |
|||
}, |
|||
{ |
|||
"name": "Stefano Kowalke", |
|||
"email": "blueduck@mail.org", |
|||
"homepage": "https://github.com/konafets" |
|||
} |
|||
], |
|||
"description": "Provides an implementation of the Guzzle Command library that uses Guzzle service descriptions to describe web services, serialize requests, and parse responses into easy to use model structures." |
|||
}, |
|||
{ |
|||
"name": "guzzlehttp/promises", |
|||
"version": "v1.3.1", |
|||
"version_normalized": "1.3.1.0", |
|||
"source": { |
|||
"type": "git", |
|||
"url": "https://github.com/guzzle/promises.git", |
|||
"reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" |
|||
}, |
|||
"dist": { |
|||
"type": "zip", |
|||
"url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", |
|||
"reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", |
|||
"shasum": "" |
|||
}, |
|||
"require": { |
|||
"php": ">=5.5.0" |
|||
}, |
|||
"require-dev": { |
|||
"phpunit/phpunit": "^4.0" |
|||
}, |
|||
"time": "2016-12-20T10:07:11+00:00", |
|||
"type": "library", |
|||
"extra": { |
|||
"branch-alias": { |
|||
"dev-master": "1.4-dev" |
|||
} |
|||
}, |
|||
"installation-source": "dist", |
|||
"autoload": { |
|||
"psr-4": { |
|||
"GuzzleHttp\\Promise\\": "src/" |
|||
}, |
|||
"files": [ |
|||
"src/functions_include.php" |
|||
] |
|||
}, |
|||
"notification-url": "https://packagist.org/downloads/", |
|||
"license": [ |
|||
"MIT" |
|||
], |
|||
"authors": [ |
|||
{ |
|||
"name": "Michael Dowling", |
|||
"email": "mtdowling@gmail.com", |
|||
"homepage": "https://github.com/mtdowling" |
|||
} |
|||
], |
|||
"description": "Guzzle promises library", |
|||
"keywords": [ |
|||
"promise" |
|||
] |
|||
}, |
|||
{ |
|||
"name": "guzzlehttp/psr7", |
|||
"version": "1.6.1", |
|||
"version_normalized": "1.6.1.0", |
|||
"source": { |
|||
"type": "git", |
|||
"url": "https://github.com/guzzle/psr7.git", |
|||
"reference": "239400de7a173fe9901b9ac7c06497751f00727a" |
|||
}, |
|||
"dist": { |
|||
"type": "zip", |
|||
"url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", |
|||
"reference": "239400de7a173fe9901b9ac7c06497751f00727a", |
|||
"shasum": "" |
|||
}, |
|||
"require": { |
|||
"php": ">=5.4.0", |
|||
"psr/http-message": "~1.0", |
|||
"ralouphie/getallheaders": "^2.0.5 || ^3.0.0" |
|||
}, |
|||
"provide": { |
|||
"psr/http-message-implementation": "1.0" |
|||
}, |
|||
"require-dev": { |
|||
"ext-zlib": "*", |
|||
"phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" |
|||
}, |
|||
"suggest": { |
|||
"zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" |
|||
}, |
|||
"time": "2019-07-01T23:21:34+00:00", |
|||
"type": "library", |
|||
"extra": { |
|||
"branch-alias": { |
|||
"dev-master": "1.6-dev" |
|||
} |
|||
}, |
|||
"installation-source": "dist", |
|||
"autoload": { |
|||
"psr-4": { |
|||
"GuzzleHttp\\Psr7\\": "src/" |
|||
}, |
|||
"files": [ |
|||
"src/functions_include.php" |
|||
] |
|||
}, |
|||
"notification-url": "https://packagist.org/downloads/", |
|||
"license": [ |
|||
"MIT" |
|||
], |
|||
"authors": [ |
|||
{ |
|||
"name": "Michael Dowling", |
|||
"email": "mtdowling@gmail.com", |
|||
"homepage": "https://github.com/mtdowling" |
|||
}, |
|||
{ |
|||
"name": "Tobias Schultze", |
|||
"homepage": "https://github.com/Tobion" |
|||
} |
|||
], |
|||
"description": "PSR-7 message implementation that also provides common utility methods", |
|||
"keywords": [ |
|||
"http", |
|||
"message", |
|||
"psr-7", |
|||
"request", |
|||
"response", |
|||
"stream", |
|||
"uri", |
|||
"url" |
|||
] |
|||
}, |
|||
{ |
|||
"name": "psr/http-message", |
|||
"version": "1.0.1", |
|||
"version_normalized": "1.0.1.0", |
|||
"source": { |
|||
"type": "git", |
|||
"url": "https://github.com/php-fig/http-message.git", |
|||
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" |
|||
}, |
|||
"dist": { |
|||
"type": "zip", |
|||
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", |
|||
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", |
|||
"shasum": "" |
|||
}, |
|||
"require": { |
|||
"php": ">=5.3.0" |
|||
}, |
|||
"time": "2016-08-06T14:39:51+00:00", |
|||
"type": "library", |
|||
"extra": { |
|||
"branch-alias": { |
|||
"dev-master": "1.0.x-dev" |
|||
} |
|||
}, |
|||
"installation-source": "dist", |
|||
"autoload": { |
|||
"psr-4": { |
|||
"Psr\\Http\\Message\\": "src/" |
|||
} |
|||
}, |
|||
"notification-url": "https://packagist.org/downloads/", |
|||
"license": [ |
|||
"MIT" |
|||
], |
|||
"authors": [ |
|||
{ |
|||
"name": "PHP-FIG", |
|||
"homepage": "http://www.php-fig.org/" |
|||
} |
|||
], |
|||
"description": "Common interface for HTTP messages", |
|||
"homepage": "https://github.com/php-fig/http-message", |
|||
"keywords": [ |
|||
"http", |
|||
"http-message", |
|||
"psr", |
|||
"psr-7", |
|||
"request", |
|||
"response" |
|||
] |
|||
}, |
|||
{ |
|||
"name": "ralouphie/getallheaders", |
|||
"version": "3.0.3", |
|||
"version_normalized": "3.0.3.0", |
|||
"source": { |
|||
"type": "git", |
|||
"url": "https://github.com/ralouphie/getallheaders.git", |
|||
"reference": "120b605dfeb996808c31b6477290a714d356e822" |
|||
}, |
|||
"dist": { |
|||
"type": "zip", |
|||
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", |
|||
"reference": "120b605dfeb996808c31b6477290a714d356e822", |
|||
"shasum": "" |
|||
}, |
|||
"require": { |
|||
"php": ">=5.6" |
|||
}, |
|||
"require-dev": { |
|||
"php-coveralls/php-coveralls": "^2.1", |
|||
"phpunit/phpunit": "^5 || ^6.5" |
|||
}, |
|||
"time": "2019-03-08T08:55:37+00:00", |
|||
"type": "library", |
|||
"installation-source": "dist", |
|||
"autoload": { |
|||
"files": [ |
|||
"src/getallheaders.php" |
|||
] |
|||
}, |
|||
"notification-url": "https://packagist.org/downloads/", |
|||
"license": [ |
|||
"MIT" |
|||
], |
|||
"authors": [ |
|||
{ |
|||
"name": "Ralph Khattar", |
|||
"email": "ralph.khattar@gmail.com" |
|||
} |
|||
], |
|||
"description": "A polyfill for getallheaders." |
|||
} |
|||
] |
|||
@ -0,0 +1,134 @@ |
|||
# Guzzle Commands |
|||
|
|||
[](https://packagist.org/packages/guzzlehttp/command) |
|||
[](https://travis-ci.org/guzzle/command) |
|||
[](https://scrutinizer-ci.com/g/guzzle/command/?branch=master) |
|||
[](https://scrutinizer-ci.com/g/guzzle/command/?branch=master) |
|||
[](https://insight.sensiolabs.com/projects/7a93338e-50cd-42f7-9299-17c44d92148f) |
|||
[](https://packagist.org/packages/guzzlehttp/command) |
|||
[](https://packagist.org/packages/guzzlehttp/command) |
|||
[](https://packagist.org/packages/guzzlehttp/command) |
|||
|
|||
This library uses Guzzle (``guzzlehttp/guzzle``, version 6.x) and provides the |
|||
foundations to create fully-featured web service clients by abstracting Guzzle |
|||
HTTP **requests** and **responses** into higher-level **commands** and |
|||
**results**. A **middleware** system, analogous to — but separate from — the one |
|||
in the HTTP layer may be used to customize client behavior when preparing |
|||
commands into requests and processing responses into results. |
|||
|
|||
### Commands |
|||
|
|||
Key-value pair objects representing an operation of a web service. Commands have a name and a set of parameters. |
|||
|
|||
### Results |
|||
|
|||
Key-value pair objects representing the processed result of executing an operation of a web service. |
|||
|
|||
## Installing |
|||
|
|||
This project can be installed using Composer: |
|||
|
|||
``composer require guzzlehttp/command`` |
|||
|
|||
For **Guzzle 5**, use ``composer require guzzlehttp/command:0.8.*``. The source |
|||
code for the Guzzle 5 version is available on the |
|||
`0.8 branch <https://github.com/guzzle/command/tree/0.8>`_. |
|||
|
|||
**Note:** If Composer is not |
|||
`installed globally <https://getcomposer.org/doc/00-intro.md#globally>`_, |
|||
then you may need to run the preceding Composer commands using |
|||
``php composer.phar`` (where ``composer.phar`` is the path to your copy of |
|||
Composer), instead of just ``composer``. |
|||
|
|||
## Service Clients |
|||
|
|||
Service Clients are web service clients that implement the |
|||
``GuzzleHttp\Command\ServiceClientInterface`` and use an underlying Guzzle HTTP |
|||
client (``GuzzleHttp\Client``) to communicate with the service. Service clients |
|||
create and execute **commands** (``GuzzleHttp\Command\CommandInterface``), |
|||
which encapsulate operations within the web service, including the operation |
|||
name and parameters. This library provides a generic implementation of a service |
|||
client: the ``GuzzleHttp\Command\ServiceClient`` class. |
|||
|
|||
## Instantiating a Service Client |
|||
|
|||
@TODO Add documentation |
|||
|
|||
* ``ServiceClient``'s constructor |
|||
* Transformer functions (``$commandToRequestTransformer`` and ``$responseToResultTransformer``) |
|||
* The ``HandlerStack`` |
|||
|
|||
## Executing Commands |
|||
|
|||
Service clients create command objects using the ``getCommand()`` method. |
|||
|
|||
```php |
|||
$commandName = 'foo'; |
|||
$arguments = ['baz' => 'bar']; |
|||
$command = $client->getCommand($commandName, $arguments); |
|||
|
|||
``` |
|||
|
|||
After creating a command, you may execute the command using the ``execute()`` |
|||
method of the client. |
|||
|
|||
```php |
|||
$result = $client->execute($command); |
|||
``` |
|||
|
|||
The result of executing a command will be a ``GuzzleHttp\Command\ResultInterface`` |
|||
object. Result objects are ``ArrayAccess``-ible and contain the data parsed from |
|||
HTTP response. |
|||
|
|||
Service clients have magic methods that act as shortcuts to executing commands |
|||
by name without having to create the ``Command`` object in a separate step |
|||
before executing it. |
|||
|
|||
```php |
|||
$result = $client->foo(['baz' => 'bar']); |
|||
``` |
|||
|
|||
## Asynchronous Commands |
|||
|
|||
@TODO Add documentation |
|||
|
|||
* ``-Async`` suffix for client methods |
|||
* Promises |
|||
|
|||
```php |
|||
// Create and execute an asynchronous command. |
|||
$command = $command = $client->getCommand('foo', ['baz' => 'bar']); |
|||
$promise = $client->executeAsync($command); |
|||
|
|||
// Use asynchronous commands with magic methods. |
|||
$promise = $client->fooAsync(['baz' => 'bar']); |
|||
``` |
|||
|
|||
@TODO Add documentation |
|||
|
|||
* ``wait()``-ing on promises. |
|||
|
|||
```php |
|||
$result = $promise->wait(); |
|||
|
|||
echo $result['fizz']; //> 'buzz' |
|||
``` |
|||
|
|||
## Concurrent Requests |
|||
|
|||
@TODO Add documentation |
|||
|
|||
* ``executeAll()`` |
|||
* ``executeAllAsync()``. |
|||
* Options (``fulfilled``, ``rejected``, ``concurrency``) |
|||
|
|||
## Middleware: Extending the Client |
|||
|
|||
Middleware can be added to the service client or underlying HTTP client to |
|||
implement additional behavior and customize the ``Command``-to-``Result`` and |
|||
``Request``-to-``Response`` lifecycles, respectively. |
|||
|
|||
## Todo |
|||
|
|||
* Middleware system and command vs request layers |
|||
* The ``HandlerStack`` |
|||
@ -0,0 +1,36 @@ |
|||
{ |
|||
"name": "guzzlehttp/command", |
|||
"description": "Provides the foundation for building command-based web service clients", |
|||
"license": "MIT", |
|||
"authors": [ |
|||
{ |
|||
"name": "Michael Dowling", |
|||
"email": "mtdowling@gmail.com", |
|||
"homepage": "https://github.com/mtdowling" |
|||
}, |
|||
{ |
|||
"name": "Jeremy Lindblom", |
|||
"email": "jeremeamia@gmail.com", |
|||
"homepage": "https://github.com/jeremeamia" |
|||
} |
|||
], |
|||
"require": { |
|||
"php": ">=5.5.0", |
|||
"guzzlehttp/guzzle": "^6.2", |
|||
"guzzlehttp/promises": "~1.3", |
|||
"guzzlehttp/psr7": "~1.0" |
|||
}, |
|||
"require-dev": { |
|||
"phpunit/phpunit": "~4.0|~5.0" |
|||
}, |
|||
"autoload": { |
|||
"psr-4": { |
|||
"GuzzleHttp\\Command\\": "src/" |
|||
} |
|||
}, |
|||
"extra": { |
|||
"branch-alias": { |
|||
"dev-master": "0.9-dev" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command; |
|||
|
|||
use GuzzleHttp\HandlerStack; |
|||
|
|||
/** |
|||
* Default command implementation. |
|||
*/ |
|||
class Command implements CommandInterface |
|||
{ |
|||
use HasDataTrait; |
|||
|
|||
/** @var string */ |
|||
private $name; |
|||
|
|||
/** @var HandlerStack */ |
|||
private $handlerStack; |
|||
|
|||
/** |
|||
* @param string $name Name of the command |
|||
* @param array $args Arguments to pass to the command |
|||
* @param HandlerStack $handlerStack Stack of middleware for the command |
|||
*/ |
|||
public function __construct( |
|||
$name, |
|||
array $args = [], |
|||
HandlerStack $handlerStack = null |
|||
) { |
|||
$this->name = $name; |
|||
$this->data = $args; |
|||
$this->handlerStack = $handlerStack; |
|||
} |
|||
|
|||
public function getHandlerStack() |
|||
{ |
|||
return $this->handlerStack; |
|||
} |
|||
|
|||
public function getName() |
|||
{ |
|||
return $this->name; |
|||
} |
|||
|
|||
public function hasParam($name) |
|||
{ |
|||
return array_key_exists($name, $this->data); |
|||
} |
|||
|
|||
public function __clone() |
|||
{ |
|||
if ($this->handlerStack) { |
|||
$this->handlerStack = clone $this->handlerStack; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command; |
|||
|
|||
use GuzzleHttp\HandlerStack; |
|||
|
|||
/** |
|||
* A command object encapsulates the input parameters used to control the |
|||
* creation of a HTTP request and processing of a HTTP response. |
|||
* |
|||
* Using the getParams() method will return the input parameters of the command |
|||
* as an associative array. |
|||
*/ |
|||
interface CommandInterface extends \ArrayAccess, \IteratorAggregate, \Countable, ToArrayInterface |
|||
{ |
|||
/** |
|||
* Retrieves the handler stack specific to this command's execution. |
|||
* |
|||
* This can be used to add middleware that is specific to the command instance. |
|||
* |
|||
* @return HandlerStack |
|||
*/ |
|||
public function getHandlerStack(); |
|||
|
|||
/** |
|||
* Get the name of the command. |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function getName(); |
|||
|
|||
/** |
|||
* Check if the command has a parameter by name. |
|||
* |
|||
* @param string $name Name of the parameter to check. |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function hasParam($name); |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Exception; |
|||
|
|||
/** |
|||
* Exception encountered when a 4xx level response is received for a request |
|||
*/ |
|||
class CommandClientException extends CommandException {} |
|||
@ -0,0 +1,109 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Exception; |
|||
|
|||
use GuzzleHttp\Exception\GuzzleException; |
|||
use GuzzleHttp\Exception\RequestException; |
|||
use GuzzleHttp\Command\CommandInterface; |
|||
use Psr\Http\Message\RequestInterface; |
|||
use Psr\Http\Message\ResponseInterface; |
|||
|
|||
/** |
|||
* Exception encountered while executing a command. |
|||
*/ |
|||
class CommandException extends \RuntimeException implements GuzzleException |
|||
{ |
|||
/** @var CommandInterface */ |
|||
private $command; |
|||
|
|||
/** @var RequestInterface */ |
|||
private $request; |
|||
|
|||
/** @var ResponseInterface */ |
|||
private $response; |
|||
|
|||
/** |
|||
* @param CommandInterface $command |
|||
* @param \Exception $prev |
|||
* @return CommandException |
|||
*/ |
|||
public static function fromPrevious(CommandInterface $command, \Exception $prev) |
|||
{ |
|||
// If the exception is already a command exception, return it. |
|||
if ($prev instanceof self && $command === $prev->getCommand()) { |
|||
return $prev; |
|||
} |
|||
|
|||
// If the exception is a RequestException, get the Request and Response. |
|||
$request = $response = null; |
|||
if ($prev instanceof RequestException) { |
|||
$request = $prev->getRequest(); |
|||
$response = $prev->getResponse(); |
|||
} |
|||
|
|||
// Throw a more specific exception for 4XX or 5XX responses. |
|||
$class = self::class; |
|||
$statusCode = $response ? $response->getStatusCode() : 0; |
|||
if ($statusCode >= 400 && $statusCode < 500) { |
|||
$class = CommandClientException::class; |
|||
} elseif ($statusCode >= 500 && $statusCode < 600) { |
|||
$class = CommandServerException::class; |
|||
} |
|||
|
|||
// Prepare the message. |
|||
$message = 'There was an error executing the ' . $command->getName() |
|||
. ' command: ' . $prev->getMessage(); |
|||
|
|||
// Create the exception. |
|||
return new $class($message, $command, $prev, $request, $response); |
|||
} |
|||
|
|||
/** |
|||
* @param string $message Exception message |
|||
* @param CommandInterface $command |
|||
* @param \Exception $previous Previous exception (if any) |
|||
* @param RequestInterface $request |
|||
* @param ResponseInterface $response |
|||
*/ |
|||
public function __construct( |
|||
$message, |
|||
CommandInterface $command, |
|||
\Exception $previous = null, |
|||
RequestInterface $request = null, |
|||
ResponseInterface $response = null |
|||
) { |
|||
$this->command = $command; |
|||
$this->request = $request; |
|||
$this->response = $response; |
|||
parent::__construct($message, 0, $previous); |
|||
} |
|||
|
|||
/** |
|||
* Gets the command that failed. |
|||
* |
|||
* @return CommandInterface |
|||
*/ |
|||
public function getCommand() |
|||
{ |
|||
return $this->command; |
|||
} |
|||
|
|||
/** |
|||
* Gets the request that caused the exception |
|||
* |
|||
* @return RequestInterface|null |
|||
*/ |
|||
public function getRequest() |
|||
{ |
|||
return $this->request; |
|||
} |
|||
|
|||
/** |
|||
* Gets the associated response |
|||
* |
|||
* @return ResponseInterface|null |
|||
*/ |
|||
public function getResponse() |
|||
{ |
|||
return $this->response; |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Exception; |
|||
|
|||
/** |
|||
* Exception encountered when a 5xx level response is received for a request |
|||
*/ |
|||
class CommandServerException extends CommandException {} |
|||
@ -0,0 +1,60 @@ |
|||
<?php |
|||
|
|||
namespace GuzzleHttp\Command; |
|||
|
|||
/** |
|||
* Basic collection behavior for Command and Result objects. |
|||
* |
|||
* The methods in the class are primarily for implementing the ArrayAccess, |
|||
* Countable, and IteratorAggregate interfaces. |
|||
*/ |
|||
trait HasDataTrait |
|||
{ |
|||
/** @var array Data stored in the collection. */ |
|||
protected $data; |
|||
|
|||
public function __toString() |
|||
{ |
|||
return print_r($this, true); |
|||
} |
|||
|
|||
public function __debugInfo() |
|||
{ |
|||
return $this->data; |
|||
} |
|||
|
|||
public function offsetExists($offset) |
|||
{ |
|||
return array_key_exists($offset, $this->data); |
|||
} |
|||
|
|||
public function offsetGet($offset) |
|||
{ |
|||
return isset($this->data[$offset]) ? $this->data[$offset] : null; |
|||
} |
|||
|
|||
public function offsetSet($offset, $value) |
|||
{ |
|||
$this->data[$offset] = $value; |
|||
} |
|||
|
|||
public function offsetUnset($offset) |
|||
{ |
|||
unset($this->data[$offset]); |
|||
} |
|||
|
|||
public function count() |
|||
{ |
|||
return count($this->data); |
|||
} |
|||
|
|||
public function getIterator() |
|||
{ |
|||
return new \ArrayIterator($this->data); |
|||
} |
|||
|
|||
public function toArray() |
|||
{ |
|||
return $this->data; |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command; |
|||
|
|||
/** |
|||
* Default command implementation. |
|||
*/ |
|||
class Result implements ResultInterface |
|||
{ |
|||
use HasDataTrait; |
|||
|
|||
/** |
|||
* @param array $data |
|||
*/ |
|||
public function __construct(array $data = []) |
|||
{ |
|||
$this->data = $data; |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command; |
|||
|
|||
/** |
|||
* An array-like object that represents the result of executing a command. |
|||
*/ |
|||
interface ResultInterface extends \ArrayAccess, \IteratorAggregate, \Countable, ToArrayInterface |
|||
{ |
|||
} |
|||
@ -0,0 +1,217 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command; |
|||
|
|||
use GuzzleHttp\ClientInterface as HttpClient; |
|||
use GuzzleHttp\Command\Exception\CommandException; |
|||
use GuzzleHttp\HandlerStack; |
|||
use GuzzleHttp\Promise; |
|||
use GuzzleHttp\Promise\PromiseInterface; |
|||
use Psr\Http\Message\RequestInterface; |
|||
use Psr\Http\Message\ResponseInterface; |
|||
|
|||
/** |
|||
* The Guzzle ServiceClient serves as the foundation for creating web service |
|||
* clients that interact with RPC-style APIs. |
|||
*/ |
|||
class ServiceClient implements ServiceClientInterface |
|||
{ |
|||
/** @var HttpClient HTTP client used to send requests */ |
|||
private $httpClient; |
|||
|
|||
/** @var HandlerStack */ |
|||
private $handlerStack; |
|||
|
|||
/** @var callable */ |
|||
private $commandToRequestTransformer; |
|||
|
|||
/** @var callable */ |
|||
private $responseToResultTransformer; |
|||
|
|||
/** |
|||
* Instantiates a Guzzle ServiceClient for making requests to a web service. |
|||
* |
|||
* @param HttpClient $httpClient A fully-configured Guzzle HTTP client that |
|||
* will be used to perform the underlying HTTP requests. |
|||
* @param callable $commandToRequestTransformer A callable that transforms |
|||
* a Command into a Request. The function should accept a |
|||
* `GuzzleHttp\Command\CommandInterface` object and return a |
|||
* `Psr\Http\Message\RequestInterface` object. |
|||
* @param callable $responseToResultTransformer A callable that transforms a |
|||
* Response into a Result. The function should accept a |
|||
* `Psr\Http\Message\ResponseInterface` object (and optionally a |
|||
* `Psr\Http\Message\RequestInterface` object) and return a |
|||
* `GuzzleHttp\Command\ResultInterface` object. |
|||
* @param HandlerStack $commandHandlerStack A Guzzle HandlerStack, which can |
|||
* be used to add command-level middleware to the service client. |
|||
*/ |
|||
public function __construct( |
|||
HttpClient $httpClient, |
|||
callable $commandToRequestTransformer, |
|||
callable $responseToResultTransformer, |
|||
HandlerStack $commandHandlerStack = null |
|||
) { |
|||
$this->httpClient = $httpClient; |
|||
$this->commandToRequestTransformer = $commandToRequestTransformer; |
|||
$this->responseToResultTransformer = $responseToResultTransformer; |
|||
$this->handlerStack = $commandHandlerStack ?: new HandlerStack(); |
|||
$this->handlerStack->setHandler($this->createCommandHandler()); |
|||
} |
|||
|
|||
public function getHttpClient() |
|||
{ |
|||
return $this->httpClient; |
|||
} |
|||
|
|||
public function getHandlerStack() |
|||
{ |
|||
return $this->handlerStack; |
|||
} |
|||
|
|||
public function getCommand($name, array $params = []) |
|||
{ |
|||
return new Command($name, $params, clone $this->handlerStack); |
|||
} |
|||
|
|||
public function execute(CommandInterface $command) |
|||
{ |
|||
return $this->executeAsync($command)->wait(); |
|||
} |
|||
|
|||
public function executeAsync(CommandInterface $command) |
|||
{ |
|||
$stack = $command->getHandlerStack() ?: $this->handlerStack; |
|||
$handler = $stack->resolve(); |
|||
|
|||
return $handler($command); |
|||
} |
|||
|
|||
public function executeAll($commands, array $options = []) |
|||
{ |
|||
// Modify provided callbacks to track results. |
|||
$results = []; |
|||
$options['fulfilled'] = function ($v, $k) use (&$results, $options) { |
|||
if (isset($options['fulfilled'])) { |
|||
$options['fulfilled']($v, $k); |
|||
} |
|||
$results[$k] = $v; |
|||
}; |
|||
$options['rejected'] = function ($v, $k) use (&$results, $options) { |
|||
if (isset($options['rejected'])) { |
|||
$options['rejected']($v, $k); |
|||
} |
|||
$results[$k] = $v; |
|||
}; |
|||
|
|||
// Execute multiple commands synchronously, then sort and return the results. |
|||
return $this->executeAllAsync($commands, $options) |
|||
->then(function () use (&$results) { |
|||
ksort($results); |
|||
return $results; |
|||
}) |
|||
->wait(); |
|||
} |
|||
|
|||
public function executeAllAsync($commands, array $options = []) |
|||
{ |
|||
// Apply default concurrency. |
|||
if (!isset($options['concurrency'])) { |
|||
$options['concurrency'] = 25; |
|||
} |
|||
|
|||
// Convert the iterator of commands to a generator of promises. |
|||
$commands = Promise\iter_for($commands); |
|||
$promises = function () use ($commands) { |
|||
foreach ($commands as $key => $command) { |
|||
if (!$command instanceof CommandInterface) { |
|||
throw new \InvalidArgumentException('The iterator must ' |
|||
. 'yield instances of ' . CommandInterface::class); |
|||
} |
|||
yield $key => $this->executeAsync($command); |
|||
} |
|||
}; |
|||
|
|||
// Execute the commands using a pool. |
|||
return (new Promise\EachPromise($promises(), $options))->promise(); |
|||
} |
|||
|
|||
/** |
|||
* Creates and executes a command for an operation by name. |
|||
* |
|||
* @param string $name Name of the command to execute. |
|||
* @param array $args Arguments to pass to the getCommand method. |
|||
* |
|||
* @return ResultInterface|PromiseInterface |
|||
* @see \GuzzleHttp\Command\ServiceClientInterface::getCommand |
|||
*/ |
|||
public function __call($name, array $args) |
|||
{ |
|||
$args = isset($args[0]) ? $args[0] : []; |
|||
if (substr($name, -5) === 'Async') { |
|||
$command = $this->getCommand(substr($name, 0, -5), $args); |
|||
return $this->executeAsync($command); |
|||
} else { |
|||
return $this->execute($this->getCommand($name, $args)); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Defines the main handler for commands that uses the HTTP client. |
|||
* |
|||
* @return callable |
|||
*/ |
|||
private function createCommandHandler() |
|||
{ |
|||
return function (CommandInterface $command) { |
|||
return Promise\coroutine(function () use ($command) { |
|||
// Prepare the HTTP options. |
|||
$opts = $command['@http'] ?: []; |
|||
unset($command['@http']); |
|||
|
|||
try { |
|||
// Prepare the request from the command and send it. |
|||
$request = $this->transformCommandToRequest($command); |
|||
$promise = $this->httpClient->sendAsync($request, $opts); |
|||
|
|||
// Create a result from the response. |
|||
$response = (yield $promise); |
|||
yield $this->transformResponseToResult($response, $request, $command); |
|||
} catch (\Exception $e) { |
|||
throw CommandException::fromPrevious($command, $e); |
|||
} |
|||
}); |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Transforms a Command object into a Request object. |
|||
* |
|||
* @param CommandInterface $command |
|||
* @return RequestInterface |
|||
*/ |
|||
private function transformCommandToRequest(CommandInterface $command) |
|||
{ |
|||
$transform = $this->commandToRequestTransformer; |
|||
|
|||
return $transform($command); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Transforms a Response object, also using data from the Request object, |
|||
* into a Result object. |
|||
* |
|||
* @param ResponseInterface $response |
|||
* @param RequestInterface $request |
|||
* @param CommandInterface $command |
|||
* @return ResultInterface |
|||
*/ |
|||
private function transformResponseToResult( |
|||
ResponseInterface $response, |
|||
RequestInterface $request, |
|||
CommandInterface $command |
|||
) { |
|||
$transform = $this->responseToResultTransformer; |
|||
|
|||
return $transform($response, $request, $command); |
|||
} |
|||
} |
|||
@ -0,0 +1,92 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command; |
|||
|
|||
use GuzzleHttp\ClientInterface; |
|||
use GuzzleHttp\Command\Exception\CommandException; |
|||
use GuzzleHttp\HandlerStack; |
|||
use GuzzleHttp\Promise\PromiseInterface; |
|||
|
|||
/** |
|||
* Web service client interface. |
|||
*/ |
|||
interface ServiceClientInterface |
|||
{ |
|||
/** |
|||
* Create a command for an operation name. |
|||
* |
|||
* Special keys may be set on the command to control how it behaves. |
|||
* Implementations SHOULD be able to utilize the following keys or throw |
|||
* an exception if unable. |
|||
* |
|||
* @param string $name Name of the operation to use in the command |
|||
* @param array $args Arguments to pass to the command |
|||
* |
|||
* @return CommandInterface |
|||
* @throws \InvalidArgumentException if no command can be found by name |
|||
*/ |
|||
public function getCommand($name, array $args = []); |
|||
|
|||
/** |
|||
* Execute a single command. |
|||
* |
|||
* @param CommandInterface $command Command to execute |
|||
* |
|||
* @return ResultInterface The result of the executed command |
|||
* @throws CommandException |
|||
*/ |
|||
public function execute(CommandInterface $command); |
|||
|
|||
/** |
|||
* Execute a single command asynchronously |
|||
* |
|||
* @param CommandInterface $command Command to execute |
|||
* |
|||
* @return PromiseInterface A Promise that resolves to a Result. |
|||
*/ |
|||
public function executeAsync(CommandInterface $command); |
|||
|
|||
/** |
|||
* Executes multiple commands concurrently using a fixed pool size. |
|||
* |
|||
* @param array|\Iterator $commands Array or iterator that contains |
|||
* CommandInterface objects to execute with the client. |
|||
* @param array $options Associative array of options to apply. |
|||
* - concurrency: (int) Max number of commands to execute concurrently. |
|||
* - fulfilled: (callable) Function to invoke when a command completes. |
|||
* - rejected: (callable) Function to invoke when a command fails. |
|||
* |
|||
* @return array |
|||
* @see GuzzleHttp\Command\ServiceClientInterface::createPool for options. |
|||
*/ |
|||
public function executeAll($commands, array $options = []); |
|||
|
|||
/** |
|||
* Executes multiple commands concurrently and asynchronously using a |
|||
* fixed pool size. |
|||
* |
|||
* @param array|\Iterator $commands Array or iterator that contains |
|||
* CommandInterface objects to execute with the client. |
|||
* @param array $options Associative array of options to apply. |
|||
* - concurrency: (int) Max number of commands to execute concurrently. |
|||
* - fulfilled: (callable) Function to invoke when a command completes. |
|||
* - rejected: (callable) Function to invoke when a command fails. |
|||
* |
|||
* @return PromiseInterface |
|||
* @see GuzzleHttp\Command\ServiceClientInterface::createPool for options. |
|||
*/ |
|||
public function executeAllAsync($commands, array $options = []); |
|||
|
|||
/** |
|||
* Get the HTTP client used to send requests for the web service client |
|||
* |
|||
* @return ClientInterface |
|||
*/ |
|||
public function getHttpClient(); |
|||
|
|||
/** |
|||
* Get the HandlerStack which can be used to add middleware to the client. |
|||
* |
|||
* @return HandlerStack |
|||
*/ |
|||
public function getHandlerStack(); |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
<?php |
|||
|
|||
namespace GuzzleHttp\Command; |
|||
|
|||
/** |
|||
* An object that can be represented as an array |
|||
*/ |
|||
interface ToArrayInterface |
|||
{ |
|||
/** |
|||
* Get the array representation of an object |
|||
* |
|||
* @return array |
|||
*/ |
|||
public function toArray(); |
|||
} |
|||
@ -0,0 +1,351 @@ |
|||
# Change Log |
|||
|
|||
## [1.1.3](https://github.com/guzzle/guzzle-services/tree/1.1.3) (2017-10-06) |
|||
|
|||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/1.1.2...HEAD) |
|||
|
|||
**Closed issues:** |
|||
|
|||
- Parameter type configuration causes issue when filters change input type [\#147](https://github.com/guzzle/guzzle-services/issues/147) |
|||
|
|||
**Merged pull requests:** |
|||
|
|||
- Use wire name when visiting array [\#152](https://github.com/guzzle/guzzle-services/pull/152) ([my2ter](https://github.com/my2ter)) |
|||
|
|||
- Adding descriptive error message on parameter failure [\#144](https://github.com/guzzle/guzzle-services/pull/144) ([igorsantos07](https://github.com/igorsantos07)) |
|||
|
|||
## [1.1.2](https://github.com/guzzle/guzzle-services/tree/1.1.2) (2017-05-19) |
|||
|
|||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/1.1.1...1.1.2) |
|||
|
|||
**Closed issues:** |
|||
|
|||
- Default values ignored in 1.1 [\#146](https://github.com/guzzle/guzzle-services/issues/146) |
|||
|
|||
- Operations extends is broken in 1.1.1 [\#145](https://github.com/guzzle/guzzle-services/issues/145) |
|||
|
|||
## [1.1.1](https://github.com/guzzle/guzzle-services/tree/1.1.1) (2017-05-15) |
|||
|
|||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/1.1.0...1.1.1) |
|||
|
|||
**Closed issues:** |
|||
|
|||
- Filters are applied twice [\#134](https://github.com/guzzle/guzzle-services/issues/134) |
|||
|
|||
- Is it possible to NOT urlencode a specific uri parameter value? [\#97](https://github.com/guzzle/guzzle-services/issues/97) |
|||
|
|||
**Merged pull requests:** |
|||
|
|||
- Fix minor typos in documentation. [\#139](https://github.com/guzzle/guzzle-services/pull/139) ([forevermatt](https://github.com/forevermatt)) |
|||
|
|||
- Do not mutate command at validation [\#135](https://github.com/guzzle/guzzle-services/pull/135) ([danizord](https://github.com/danizord)) |
|||
|
|||
- Added tests for JSON array of arrays and array of objects [\#131](https://github.com/guzzle/guzzle-services/pull/131) ([selfcatering](https://github.com/selfcatering)) |
|||
|
|||
- Allow filters on response model [\#138](https://github.com/guzzle/guzzle-services/pull/138) ([danizord](https://github.com/danizord)) |
|||
|
|||
- Exposing properties to a parent class [\#136](https://github.com/guzzle/guzzle-services/pull/136) ([Napas](https://github.com/Napas)) |
|||
|
|||
## [1.1.0](https://github.com/guzzle/guzzle-services/tree/1.1.0) (2017-01-31) |
|||
|
|||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/1.0.1...1.1.0) |
|||
|
|||
**Closed issues:** |
|||
|
|||
- Grab a list of objects when they are not located at top level of a json response \(HATEOAS\) [\#90](https://github.com/guzzle/guzzle-services/issues/90) |
|||
|
|||
- Regression of Issue \#51 - XmlLocation response not handling multiple tags of the same name correctly [\#82](https://github.com/guzzle/guzzle-services/issues/82) |
|||
|
|||
- PUT requests with parameters with location of "postField" result in Exception [\#78](https://github.com/guzzle/guzzle-services/issues/78) |
|||
|
|||
- Allow to provide Post Body as an Array [\#77](https://github.com/guzzle/guzzle-services/issues/77) |
|||
|
|||
**Merged pull requests:** |
|||
|
|||
- Bring more flexibility to query params serialization [\#132](https://github.com/guzzle/guzzle-services/pull/132) ([bakura10](https://github.com/bakura10)) |
|||
|
|||
- Allow to fix validation for parameters with a format [\#130](https://github.com/guzzle/guzzle-services/pull/130) ([bakura10](https://github.com/bakura10)) |
|||
|
|||
## [1.0.1](https://github.com/guzzle/guzzle-services/tree/1.0.1) (2017-01-13) |
|||
|
|||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/1.0.0...1.0.1) |
|||
|
|||
**Implemented enhancements:** |
|||
|
|||
- Set a name when pushing ValidatedDescriptionHandler to stack [\#127](https://github.com/guzzle/guzzle-services/issues/127) |
|||
|
|||
**Fixed bugs:** |
|||
|
|||
- combine method in Uri [\#101](https://github.com/guzzle/guzzle-services/issues/101) |
|||
|
|||
- Undefined Variable [\#88](https://github.com/guzzle/guzzle-services/issues/88) |
|||
|
|||
- Regression in array parameter serialization [\#128](https://github.com/guzzle/guzzle-services/issues/128) |
|||
|
|||
- Unable to POST multiple multipart parameters [\#123](https://github.com/guzzle/guzzle-services/issues/123) |
|||
|
|||
**Closed issues:** |
|||
|
|||
- Tag pre 1.0.0 release [\#121](https://github.com/guzzle/guzzle-services/issues/121) |
|||
|
|||
- Adjust inline documentation of Parameter [\#120](https://github.com/guzzle/guzzle-services/issues/120) |
|||
|
|||
- postField location not recognized after upgrading to 1.0 [\#119](https://github.com/guzzle/guzzle-services/issues/119) |
|||
|
|||
- Create a new release for the guzzle6 branch [\#118](https://github.com/guzzle/guzzle-services/issues/118) |
|||
|
|||
- Compatibility problem with PHP7.0 ? [\#116](https://github.com/guzzle/guzzle-services/issues/116) |
|||
|
|||
- What is the correct type of Parameter static option [\#113](https://github.com/guzzle/guzzle-services/issues/113) |
|||
|
|||
- Improve the construction of baseUri in Description [\#112](https://github.com/guzzle/guzzle-services/issues/112) |
|||
|
|||
- Please create version tag for current master branch [\#110](https://github.com/guzzle/guzzle-services/issues/110) |
|||
|
|||
- Problems with postField params [\#98](https://github.com/guzzle/guzzle-services/issues/98) |
|||
|
|||
**Merged pull requests:** |
|||
|
|||
- Fix serialization of query params [\#129](https://github.com/guzzle/guzzle-services/pull/129) ([bakura10](https://github.com/bakura10)) |
|||
|
|||
## [1.0.0](https://github.com/guzzle/guzzle-services/tree/1.0.0) (2016-11-24) |
|||
|
|||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/0.6.0...1.0.0) |
|||
|
|||
**Closed issues:** |
|||
|
|||
- AbstractClient' not found [\#117](https://github.com/guzzle/guzzle-services/issues/117) |
|||
|
|||
**Merged pull requests:** |
|||
|
|||
- Make Guzzle Services compatible with Guzzle6 [\#109](https://github.com/guzzle/guzzle-services/pull/109) ([Konafets](https://github.com/Konafets)) |
|||
|
|||
## [0.6.0](https://github.com/guzzle/guzzle-services/tree/0.6.0) (2016-10-21) |
|||
|
|||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/0.5.0...0.6.0) |
|||
|
|||
**Closed issues:** |
|||
|
|||
- Broken composer install [\#111](https://github.com/guzzle/guzzle-services/issues/111) |
|||
|
|||
- The visit\(\) method is expected to return a RequestInterface but it doesn't in JsonLocation [\#106](https://github.com/guzzle/guzzle-services/issues/106) |
|||
|
|||
- Allow parameters in baseUrl [\#102](https://github.com/guzzle/guzzle-services/issues/102) |
|||
|
|||
- Have default params at client construction, gone away? [\#100](https://github.com/guzzle/guzzle-services/issues/100) |
|||
|
|||
- Runtime Exception Error is always empty [\#99](https://github.com/guzzle/guzzle-services/issues/99) |
|||
|
|||
- PHP Fatal error: Unsupported operand types in guzzlehttp/guzzle-services/src/GuzzleClient.php on line 72 [\#95](https://github.com/guzzle/guzzle-services/issues/95) |
|||
|
|||
- Date of next version [\#94](https://github.com/guzzle/guzzle-services/issues/94) |
|||
|
|||
- Map null reponse values to defined reponse model properties [\#91](https://github.com/guzzle/guzzle-services/issues/91) |
|||
|
|||
- Map a json-array into a Model [\#80](https://github.com/guzzle/guzzle-services/issues/80) |
|||
|
|||
- If property specified in json model but empty, notice raised [\#75](https://github.com/guzzle/guzzle-services/issues/75) |
|||
|
|||
- Allow primitive response types for operations [\#73](https://github.com/guzzle/guzzle-services/issues/73) |
|||
|
|||
- Allow shortened definition of properties in models [\#71](https://github.com/guzzle/guzzle-services/issues/71) |
|||
|
|||
- Where's the ServiceDescriptionLoader/AbstractConfigLoader? [\#68](https://github.com/guzzle/guzzle-services/issues/68) |
|||
|
|||
- errorResposnes from operation is never used [\#66](https://github.com/guzzle/guzzle-services/issues/66) |
|||
|
|||
- Updating the description [\#65](https://github.com/guzzle/guzzle-services/issues/65) |
|||
|
|||
- Parameter type validation is too strict [\#7](https://github.com/guzzle/guzzle-services/issues/7) |
|||
|
|||
**Merged pull requests:** |
|||
|
|||
- fix code example [\#115](https://github.com/guzzle/guzzle-services/pull/115) ([snoek09](https://github.com/snoek09)) |
|||
|
|||
- Bug Fix for GuzzleClient constructor [\#96](https://github.com/guzzle/guzzle-services/pull/96) ([peterfox](https://github.com/peterfox)) |
|||
|
|||
- add plugin section to readme [\#93](https://github.com/guzzle/guzzle-services/pull/93) ([gimler](https://github.com/gimler)) |
|||
|
|||
- Allow mapping null response values to defined response model properties [\#92](https://github.com/guzzle/guzzle-services/pull/92) ([shaun785](https://github.com/shaun785)) |
|||
|
|||
- Updated exception message for better debugging [\#85](https://github.com/guzzle/guzzle-services/pull/85) ([stovak](https://github.com/stovak)) |
|||
|
|||
- Gracefully handle null return from $this-\>getConfig\('defaults'\) [\#84](https://github.com/guzzle/guzzle-services/pull/84) ([fuhry](https://github.com/fuhry)) |
|||
|
|||
- Fixing issue \#82 to address regression for handling elements with the sa... [\#83](https://github.com/guzzle/guzzle-services/pull/83) ([sprak3000](https://github.com/sprak3000)) |
|||
|
|||
- Fix for specified property but no value in json \(notice for undefined in... [\#76](https://github.com/guzzle/guzzle-services/pull/76) ([rfink](https://github.com/rfink)) |
|||
|
|||
- Add ErrorHandler subscriber [\#67](https://github.com/guzzle/guzzle-services/pull/67) ([bakura10](https://github.com/bakura10)) |
|||
|
|||
- Fix combine base url and command uri [\#108](https://github.com/guzzle/guzzle-services/pull/108) ([vlastv](https://github.com/vlastv)) |
|||
|
|||
- Fixing JsonLocation::visit\(\) not returning a request \#106 [\#107](https://github.com/guzzle/guzzle-services/pull/107) ([Pinolo](https://github.com/Pinolo)) |
|||
|
|||
- Fix call to undefined method "GuzzleHttp\Psr7\Uri::combine" [\#105](https://github.com/guzzle/guzzle-services/pull/105) ([horrorin](https://github.com/horrorin)) |
|||
|
|||
- fix description for get request example [\#87](https://github.com/guzzle/guzzle-services/pull/87) ([snoek09](https://github.com/snoek09)) |
|||
|
|||
- Allow raw values \(non array/object\) for root model definitions [\#74](https://github.com/guzzle/guzzle-services/pull/74) ([rfink](https://github.com/rfink)) |
|||
|
|||
- Allow shortened definition of properties by assigning them directly to a type [\#72](https://github.com/guzzle/guzzle-services/pull/72) ([rfink](https://github.com/rfink)) |
|||
|
|||
## [0.5.0](https://github.com/guzzle/guzzle-services/tree/0.5.0) (2014-12-23) |
|||
|
|||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/0.4.0...0.5.0) |
|||
|
|||
**Closed issues:** |
|||
|
|||
- Does it supports custom class instantiate to define an operation using a service description [\#62](https://github.com/guzzle/guzzle-services/issues/62) |
|||
|
|||
- Tag version 0.4.0 [\#61](https://github.com/guzzle/guzzle-services/issues/61) |
|||
|
|||
- XmlLocation not adding attributes to non-leaf child nodes [\#52](https://github.com/guzzle/guzzle-services/issues/52) |
|||
|
|||
- XmlLocation response not handling multiple tags of the same name correctly [\#51](https://github.com/guzzle/guzzle-services/issues/51) |
|||
|
|||
- Validation Bug [\#47](https://github.com/guzzle/guzzle-services/issues/47) |
|||
|
|||
- CommandException doesn't contain response data [\#44](https://github.com/guzzle/guzzle-services/issues/44) |
|||
|
|||
- \[Fix included\] XmlLocation requires text value to have attributes [\#37](https://github.com/guzzle/guzzle-services/issues/37) |
|||
|
|||
- Question: Mocking a Response does not throw exception [\#35](https://github.com/guzzle/guzzle-services/issues/35) |
|||
|
|||
- allow default 'location' on Model [\#26](https://github.com/guzzle/guzzle-services/issues/26) |
|||
|
|||
- create mock subscriber requests from descriptions [\#25](https://github.com/guzzle/guzzle-services/issues/25) |
|||
|
|||
**Merged pull requests:** |
|||
|
|||
- Documentation: Add 'boolean-string' as a supported "format" value [\#63](https://github.com/guzzle/guzzle-services/pull/63) ([jwcobb](https://github.com/jwcobb)) |
|||
|
|||
## [0.4.0](https://github.com/guzzle/guzzle-services/tree/0.4.0) (2014-11-03) |
|||
|
|||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/0.3.0...0.4.0) |
|||
|
|||
**Closed issues:** |
|||
|
|||
- Exceptions Thrown From Subscribers Are Ignored? [\#58](https://github.com/guzzle/guzzle-services/issues/58) |
|||
|
|||
- Totally Broken With Guzzle 5 [\#57](https://github.com/guzzle/guzzle-services/issues/57) |
|||
|
|||
- GuzzleHTTP/Command Dependency fail [\#50](https://github.com/guzzle/guzzle-services/issues/50) |
|||
|
|||
- Request parameter PathLocation [\#46](https://github.com/guzzle/guzzle-services/issues/46) |
|||
|
|||
- Requesting a new version tag [\#45](https://github.com/guzzle/guzzle-services/issues/45) |
|||
|
|||
- CommandException expects second parameter to be CommandTransaction instance [\#43](https://github.com/guzzle/guzzle-services/issues/43) |
|||
|
|||
- Cannot add Autorization header to my requests [\#39](https://github.com/guzzle/guzzle-services/issues/39) |
|||
|
|||
- Resouce Itterators [\#36](https://github.com/guzzle/guzzle-services/issues/36) |
|||
|
|||
- Question [\#33](https://github.com/guzzle/guzzle-services/issues/33) |
|||
|
|||
- query location array can be comma separated [\#31](https://github.com/guzzle/guzzle-services/issues/31) |
|||
|
|||
- Automatically returns array from command? [\#30](https://github.com/guzzle/guzzle-services/issues/30) |
|||
|
|||
- Arrays nested under objects in JSON response broken? [\#27](https://github.com/guzzle/guzzle-services/issues/27) |
|||
|
|||
- Question? [\#23](https://github.com/guzzle/guzzle-services/issues/23) |
|||
|
|||
**Merged pull requests:** |
|||
|
|||
- Bump the version in the readme [\#60](https://github.com/guzzle/guzzle-services/pull/60) ([GrahamCampbell](https://github.com/GrahamCampbell)) |
|||
|
|||
- Bump the next version to 0.4 [\#56](https://github.com/guzzle/guzzle-services/pull/56) ([GrahamCampbell](https://github.com/GrahamCampbell)) |
|||
|
|||
- Fixed the guzzlehttp/command version constraint [\#55](https://github.com/guzzle/guzzle-services/pull/55) ([GrahamCampbell](https://github.com/GrahamCampbell)) |
|||
|
|||
- Work with latest Guzzle 5 and Command updates [\#54](https://github.com/guzzle/guzzle-services/pull/54) ([mtdowling](https://github.com/mtdowling)) |
|||
|
|||
- Addressing Issue \#51 & Issue \#52 [\#53](https://github.com/guzzle/guzzle-services/pull/53) ([sprak3000](https://github.com/sprak3000)) |
|||
|
|||
- added description interface to extend it [\#49](https://github.com/guzzle/guzzle-services/pull/49) ([danieledangeli](https://github.com/danieledangeli)) |
|||
|
|||
- Update readme to improve documentation \(\#46\) [\#48](https://github.com/guzzle/guzzle-services/pull/48) ([bonndan](https://github.com/bonndan)) |
|||
|
|||
- Fixed the readme version constraint [\#42](https://github.com/guzzle/guzzle-services/pull/42) ([GrahamCampbell](https://github.com/GrahamCampbell)) |
|||
|
|||
- Update .travis.yml [\#41](https://github.com/guzzle/guzzle-services/pull/41) ([GrahamCampbell](https://github.com/GrahamCampbell)) |
|||
|
|||
- Added a branch alias [\#40](https://github.com/guzzle/guzzle-services/pull/40) ([GrahamCampbell](https://github.com/GrahamCampbell)) |
|||
|
|||
- Fixes Response\XmlLocation requires text value [\#38](https://github.com/guzzle/guzzle-services/pull/38) ([magnetik](https://github.com/magnetik)) |
|||
|
|||
- Removing unnecessary \(\) from docblock [\#32](https://github.com/guzzle/guzzle-services/pull/32) ([jamiehannaford](https://github.com/jamiehannaford)) |
|||
|
|||
- Fix JSON response location so that both is supported: arrays nested unde... [\#28](https://github.com/guzzle/guzzle-services/pull/28) ([ukautz](https://github.com/ukautz)) |
|||
|
|||
- Throw Any Exceptions On Process [\#59](https://github.com/guzzle/guzzle-services/pull/59) ([GrahamCampbell](https://github.com/GrahamCampbell)) |
|||
|
|||
- Allow extension to work recursively over models [\#34](https://github.com/guzzle/guzzle-services/pull/34) ([jamiehannaford](https://github.com/jamiehannaford)) |
|||
|
|||
- A custom class can be configured for command instances. [\#29](https://github.com/guzzle/guzzle-services/pull/29) ([robinvdvleuten](https://github.com/robinvdvleuten)) |
|||
|
|||
- \[WIP\] doing some experimentation [\#24](https://github.com/guzzle/guzzle-services/pull/24) ([cordoval](https://github.com/cordoval)) |
|||
|
|||
## [0.3.0](https://github.com/guzzle/guzzle-services/tree/0.3.0) (2014-06-01) |
|||
|
|||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/0.2.0...0.3.0) |
|||
|
|||
**Closed issues:** |
|||
|
|||
- Testing Guzzle Services doesn't work [\#19](https://github.com/guzzle/guzzle-services/issues/19) |
|||
|
|||
- Description factory [\#18](https://github.com/guzzle/guzzle-services/issues/18) |
|||
|
|||
- support to load service description from file [\#15](https://github.com/guzzle/guzzle-services/issues/15) |
|||
|
|||
- Update dependency on guzzlehttp/command [\#11](https://github.com/guzzle/guzzle-services/issues/11) |
|||
|
|||
**Merged pull requests:** |
|||
|
|||
- Add license file [\#22](https://github.com/guzzle/guzzle-services/pull/22) ([siwinski](https://github.com/siwinski)) |
|||
|
|||
- Fix 'Invalid argument supplied for foreach\(\)' [\#21](https://github.com/guzzle/guzzle-services/pull/21) ([Olden](https://github.com/Olden)) |
|||
|
|||
- Fixed string zero \('0'\) values not being filtered in XML. [\#20](https://github.com/guzzle/guzzle-services/pull/20) ([dragonwize](https://github.com/dragonwize)) |
|||
|
|||
- baseUrl can be a string or an uri template [\#16](https://github.com/guzzle/guzzle-services/pull/16) ([robinvdvleuten](https://github.com/robinvdvleuten)) |
|||
|
|||
## [0.2.0](https://github.com/guzzle/guzzle-services/tree/0.2.0) (2014-03-30) |
|||
|
|||
[Full Changelog](https://github.com/guzzle/guzzle-services/compare/0.1.0...0.2.0) |
|||
|
|||
**Closed issues:** |
|||
|
|||
- please remove wiki [\#13](https://github.com/guzzle/guzzle-services/issues/13) |
|||
|
|||
- Parameter validation fails for union types [\#12](https://github.com/guzzle/guzzle-services/issues/12) |
|||
|
|||
- question on integration with Guzzle4 [\#8](https://github.com/guzzle/guzzle-services/issues/8) |
|||
|
|||
- typehints for operations property [\#6](https://github.com/guzzle/guzzle-services/issues/6) |
|||
|
|||
- improve exception message [\#5](https://github.com/guzzle/guzzle-services/issues/5) |
|||
|
|||
**Merged pull requests:** |
|||
|
|||
- Update composer.json [\#14](https://github.com/guzzle/guzzle-services/pull/14) ([GrahamCampbell](https://github.com/GrahamCampbell)) |
|||
|
|||
- Update composer.json [\#9](https://github.com/guzzle/guzzle-services/pull/9) ([GrahamCampbell](https://github.com/GrahamCampbell)) |
|||
|
|||
- some fixes [\#4](https://github.com/guzzle/guzzle-services/pull/4) ([cordoval](https://github.com/cordoval)) |
|||
|
|||
- Fix the CommandException path used in ValidateInput [\#2](https://github.com/guzzle/guzzle-services/pull/2) ([mookle](https://github.com/mookle)) |
|||
|
|||
- Minor improvements [\#1](https://github.com/guzzle/guzzle-services/pull/1) ([GrahamCampbell](https://github.com/GrahamCampbell)) |
|||
|
|||
- Use latest guzzlehttp/command to fix dependencies [\#10](https://github.com/guzzle/guzzle-services/pull/10) ([sbward](https://github.com/sbward)) |
|||
|
|||
- some collaboration using Gush :\) [\#3](https://github.com/guzzle/guzzle-services/pull/3) ([cordoval](https://github.com/cordoval)) |
|||
|
|||
## [0.1.0](https://github.com/guzzle/guzzle-services/tree/0.1.0) (2014-03-15) |
|||
|
|||
|
|||
|
|||
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* |
|||
@ -0,0 +1,15 @@ |
|||
all: clean test |
|||
|
|||
test: |
|||
vendor/bin/phpunit |
|||
|
|||
coverage: |
|||
vendor/bin/phpunit --coverage-html=artifacts/coverage |
|||
|
|||
view-coverage: |
|||
open artifacts/coverage/index.html |
|||
|
|||
clean: |
|||
rm -rf artifacts/* |
|||
|
|||
.PHONY: coverage |
|||
@ -0,0 +1,129 @@ |
|||
# Guzzle Services |
|||
|
|||
[](https://packagist.org/packages/guzzlehttp/guzzle-services) |
|||
[](https://travis-ci.org/guzzle/guzzle-services) |
|||
[](https://scrutinizer-ci.com/g/guzzle/guzzle-services/?branch=master) |
|||
[](https://scrutinizer-ci.com/g/guzzle/guzzle-services/?branch=master) |
|||
[](https://insight.sensiolabs.com/projects/b08be676-b209-40b7-a6df-b6d13e8dff62) |
|||
[](https://packagist.org/packages/guzzlehttp/guzzle-services) |
|||
[](https://packagist.org/packages/guzzlehttp/guzzle-services) |
|||
[](https://packagist.org/packages/guzzlehttp/guzzle-services) |
|||
|
|||
Provides an implementation of the Guzzle Command library that uses Guzzle service descriptions to describe web services, serialize requests, and parse responses into easy to use model structures. |
|||
|
|||
```php |
|||
use GuzzleHttp\Client; |
|||
use GuzzleHttp\Command\Guzzle\GuzzleClient; |
|||
use GuzzleHttp\Command\Guzzle\Description; |
|||
|
|||
$client = new Client(); |
|||
$description = new Description([ |
|||
'baseUri' => 'http://httpbin.org/', |
|||
'operations' => [ |
|||
'testing' => [ |
|||
'httpMethod' => 'GET', |
|||
'uri' => '/get{?foo}', |
|||
'responseModel' => 'getResponse', |
|||
'parameters' => [ |
|||
'foo' => [ |
|||
'type' => 'string', |
|||
'location' => 'uri' |
|||
], |
|||
'bar' => [ |
|||
'type' => 'string', |
|||
'location' => 'query' |
|||
] |
|||
] |
|||
] |
|||
], |
|||
'models' => [ |
|||
'getResponse' => [ |
|||
'type' => 'object', |
|||
'additionalProperties' => [ |
|||
'location' => 'json' |
|||
] |
|||
] |
|||
] |
|||
]); |
|||
|
|||
$guzzleClient = new GuzzleClient($client, $description); |
|||
|
|||
$result = $guzzleClient->testing(['foo' => 'bar']); |
|||
echo $result['args']['foo']; |
|||
// bar |
|||
``` |
|||
|
|||
## Installing |
|||
|
|||
This project can be installed using Composer: |
|||
|
|||
``composer require guzzlehttp/guzzle-services`` |
|||
|
|||
For **Guzzle 5**, use ``composer require guzzlehttp/guzzle-services:0.6``. |
|||
|
|||
**Note:** If Composer is not installed [globally](https://getcomposer.org/doc/00-intro.md#globally) then you may need to run the preceding Composer commands using ``php composer.phar`` (where ``composer.phar`` is the path to your copy of Composer), instead of just ``composer``. |
|||
|
|||
## Plugins |
|||
|
|||
* Load Service description from file [https://github.com/gimler/guzzle-description-loader] |
|||
|
|||
## Transition guide from Guzzle 5.0 to 6.0 |
|||
|
|||
### Change regarding PostField and PostFile |
|||
|
|||
The request locations `postField` and `postFile` were removed in favor of `formParam` and `multipart`. If your description looks like |
|||
|
|||
```php |
|||
[ |
|||
'baseUri' => 'http://httpbin.org/', |
|||
'operations' => [ |
|||
'testing' => [ |
|||
'httpMethod' => 'GET', |
|||
'uri' => '/get{?foo}', |
|||
'responseModel' => 'getResponse', |
|||
'parameters' => [ |
|||
'foo' => [ |
|||
'type' => 'string', |
|||
'location' => 'postField' |
|||
], |
|||
'bar' => [ |
|||
'type' => 'string', |
|||
'location' => 'postFile' |
|||
] |
|||
] |
|||
] |
|||
], |
|||
] |
|||
``` |
|||
|
|||
you need to change `postField` to `formParam` and `postFile` to `multipart`. |
|||
|
|||
More documentation coming soon. |
|||
|
|||
## Cookbook |
|||
|
|||
### Changing the way query params are serialized |
|||
|
|||
By default, query params are serialized using strict RFC3986 rules, using `http_build_query` method. With this, array params are serialized this way: |
|||
|
|||
```php |
|||
$client->myMethod(['foo' => ['bar', 'baz']]); |
|||
|
|||
// Query params will be foo[0]=bar&foo[1]=baz |
|||
``` |
|||
|
|||
However, a lot of APIs in the wild require the numeric indices to be removed, so that the query params end up being `foo[]=bar&foo[]=baz`. You |
|||
can easily change the behaviour by creating your own serializer and overriding the "query" request location: |
|||
|
|||
```php |
|||
use GuzzleHttp\Command\Guzzle\GuzzleClient; |
|||
use GuzzleHttp\Command\Guzzle\RequestLocation\QueryLocation; |
|||
use GuzzleHttp\Command\Guzzle\QuerySerializer\Rfc3986Serializer; |
|||
use GuzzleHttp\Command\Guzzle\Serializer; |
|||
|
|||
$queryLocation = new QueryLocation('query', new Rfc3986Serializer(true)); |
|||
$serializer = new Serializer($description, ['query' => $queryLocation]); |
|||
$guzzleClient = new GuzzleClient($client, $description, $serializer); |
|||
``` |
|||
|
|||
You can also create your own serializer if you have specific needs. |
|||
@ -0,0 +1,49 @@ |
|||
{ |
|||
"name": "guzzlehttp/guzzle-services", |
|||
"description": "Provides an implementation of the Guzzle Command library that uses Guzzle service descriptions to describe web services, serialize requests, and parse responses into easy to use model structures.", |
|||
"type": "library", |
|||
"license": "MIT", |
|||
"authors": [ |
|||
{ |
|||
"name": "Michael Dowling", |
|||
"email": "mtdowling@gmail.com", |
|||
"homepage": "https://github.com/mtdowling" |
|||
}, |
|||
{ |
|||
"name": "Jeremy Lindblom", |
|||
"email": "jeremeamia@gmail.com", |
|||
"homepage": "https://github.com/jeremeamia" |
|||
}, |
|||
{ |
|||
"name": "Stefano Kowalke", |
|||
"email": "blueduck@mail.org", |
|||
"homepage": "https://github.com/konafets" |
|||
} |
|||
], |
|||
"require": { |
|||
"php": ">=5.5", |
|||
"guzzlehttp/guzzle": "^6.2", |
|||
"guzzlehttp/command": "~1.0" |
|||
}, |
|||
"require-dev": { |
|||
"phpunit/phpunit": "~4.0" |
|||
}, |
|||
"autoload": { |
|||
"psr-4": { |
|||
"GuzzleHttp\\Command\\Guzzle\\": "src/" |
|||
} |
|||
}, |
|||
"autoload-dev": { |
|||
"psr-4": { |
|||
"GuzzleHttp\\Tests\\Command\\Guzzle\\": "tests/" |
|||
} |
|||
}, |
|||
"suggest": { |
|||
"gimler/guzzle-description-loader": "^0.0.4" |
|||
}, |
|||
"extra": { |
|||
"branch-alias": { |
|||
"dev-master": "1.0.x-dev" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<phpunit bootstrap="./vendor/autoload.php" |
|||
colors="true"> |
|||
<testsuites> |
|||
<testsuite> |
|||
<directory>tests</directory> |
|||
</testsuite> |
|||
</testsuites> |
|||
<filter> |
|||
<whitelist> |
|||
<directory suffix=".php">src</directory> |
|||
</whitelist> |
|||
</filter> |
|||
</phpunit> |
|||
@ -0,0 +1,265 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle; |
|||
|
|||
use GuzzleHttp\Psr7\Uri; |
|||
|
|||
/** |
|||
* Represents a Guzzle service description |
|||
*/ |
|||
class Description implements DescriptionInterface |
|||
{ |
|||
/** @var array Array of {@see OperationInterface} objects */ |
|||
private $operations = []; |
|||
|
|||
/** @var array Array of API models */ |
|||
private $models = []; |
|||
|
|||
/** @var string Name of the API */ |
|||
private $name; |
|||
|
|||
/** @var string API version */ |
|||
private $apiVersion; |
|||
|
|||
/** @var string Summary of the API */ |
|||
private $description; |
|||
|
|||
/** @var array Any extra API data */ |
|||
private $extraData = []; |
|||
|
|||
/** @var Uri baseUri/basePath */ |
|||
private $baseUri; |
|||
|
|||
/** @var SchemaFormatter */ |
|||
private $formatter; |
|||
|
|||
/** |
|||
* @param array $config Service description data |
|||
* @param array $options Custom options to apply to the description |
|||
* - formatter: Can provide a custom SchemaFormatter class |
|||
* |
|||
* @throws \InvalidArgumentException |
|||
*/ |
|||
public function __construct(array $config, array $options = []) |
|||
{ |
|||
// Keep a list of default keys used in service descriptions that is |
|||
// later used to determine extra data keys. |
|||
static $defaultKeys = ['name', 'models', 'apiVersion', 'description']; |
|||
|
|||
// Pull in the default configuration values |
|||
foreach ($defaultKeys as $key) { |
|||
if (isset($config[$key])) { |
|||
$this->{$key} = $config[$key]; |
|||
} |
|||
} |
|||
|
|||
// Set the baseUri |
|||
// Account for the old style of using baseUrl |
|||
if (isset($config['baseUrl'])) { |
|||
$config['baseUri'] = $config['baseUrl']; |
|||
} |
|||
$this->baseUri = isset($config['baseUri']) ? new Uri($config['baseUri']) : new Uri(); |
|||
|
|||
// Ensure that the models and operations properties are always arrays |
|||
$this->models = (array) $this->models; |
|||
$this->operations = (array) $this->operations; |
|||
|
|||
// We want to add operations differently than adding the other properties |
|||
$defaultKeys[] = 'operations'; |
|||
|
|||
// Create operations for each operation |
|||
if (isset($config['operations'])) { |
|||
foreach ($config['operations'] as $name => $operation) { |
|||
if (!is_array($operation)) { |
|||
throw new \InvalidArgumentException('Operations must be arrays'); |
|||
} |
|||
$this->operations[$name] = $operation; |
|||
} |
|||
} |
|||
|
|||
// Get all of the additional properties of the service description and |
|||
// store them in a data array |
|||
foreach (array_diff(array_keys($config), $defaultKeys) as $key) { |
|||
$this->extraData[$key] = $config[$key]; |
|||
} |
|||
|
|||
// Configure the schema formatter |
|||
if (isset($options['formatter'])) { |
|||
$this->formatter = $options['formatter']; |
|||
} else { |
|||
static $defaultFormatter; |
|||
if (!$defaultFormatter) { |
|||
$defaultFormatter = new SchemaFormatter(); |
|||
} |
|||
$this->formatter = $defaultFormatter; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Get the basePath/baseUri of the description |
|||
* |
|||
* @return Uri |
|||
*/ |
|||
public function getBaseUri() |
|||
{ |
|||
return $this->baseUri; |
|||
} |
|||
|
|||
/** |
|||
* Get the API operations of the service |
|||
* |
|||
* @return Operation[] Returns an array of {@see Operation} objects |
|||
*/ |
|||
public function getOperations() |
|||
{ |
|||
return $this->operations; |
|||
} |
|||
|
|||
/** |
|||
* Check if the service has an operation by name |
|||
* |
|||
* @param string $name Name of the operation to check |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function hasOperation($name) |
|||
{ |
|||
return isset($this->operations[$name]); |
|||
} |
|||
|
|||
/** |
|||
* Get an API operation by name |
|||
* |
|||
* @param string $name Name of the command |
|||
* |
|||
* @return Operation |
|||
* @throws \InvalidArgumentException if the operation is not found |
|||
*/ |
|||
public function getOperation($name) |
|||
{ |
|||
if (!$this->hasOperation($name)) { |
|||
throw new \InvalidArgumentException("No operation found named $name"); |
|||
} |
|||
|
|||
// Lazily create operations as they are retrieved |
|||
if (!($this->operations[$name] instanceof Operation)) { |
|||
$this->operations[$name]['name'] = $name; |
|||
$this->operations[$name] = new Operation($this->operations[$name], $this); |
|||
} |
|||
|
|||
return $this->operations[$name]; |
|||
} |
|||
|
|||
/** |
|||
* Get a shared definition structure. |
|||
* |
|||
* @param string $id ID/name of the model to retrieve |
|||
* |
|||
* @return Parameter |
|||
* @throws \InvalidArgumentException if the model is not found |
|||
*/ |
|||
public function getModel($id) |
|||
{ |
|||
if (!$this->hasModel($id)) { |
|||
throw new \InvalidArgumentException("No model found named $id"); |
|||
} |
|||
|
|||
// Lazily create models as they are retrieved |
|||
if (!($this->models[$id] instanceof Parameter)) { |
|||
$this->models[$id] = new Parameter( |
|||
$this->models[$id], |
|||
['description' => $this] |
|||
); |
|||
} |
|||
|
|||
return $this->models[$id]; |
|||
} |
|||
|
|||
/** |
|||
* Get all models of the service description. |
|||
* |
|||
* @return array |
|||
*/ |
|||
public function getModels() |
|||
{ |
|||
$models = []; |
|||
foreach ($this->models as $name => $model) { |
|||
$models[$name] = $this->getModel($name); |
|||
} |
|||
|
|||
return $models; |
|||
} |
|||
|
|||
/** |
|||
* Check if the service description has a model by name. |
|||
* |
|||
* @param string $id Name/ID of the model to check |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function hasModel($id) |
|||
{ |
|||
return isset($this->models[$id]); |
|||
} |
|||
|
|||
/** |
|||
* Get the API version of the service |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function getApiVersion() |
|||
{ |
|||
return $this->apiVersion; |
|||
} |
|||
|
|||
/** |
|||
* Get the name of the API |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function getName() |
|||
{ |
|||
return $this->name; |
|||
} |
|||
|
|||
/** |
|||
* Get a summary of the purpose of the API |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function getDescription() |
|||
{ |
|||
return $this->description; |
|||
} |
|||
|
|||
/** |
|||
* Format a parameter using named formats. |
|||
* |
|||
* @param string $format Format to convert it to |
|||
* @param mixed $input Input string |
|||
* |
|||
* @return mixed |
|||
*/ |
|||
public function format($format, $input) |
|||
{ |
|||
return $this->formatter->format($format, $input); |
|||
} |
|||
|
|||
/** |
|||
* Get arbitrary data from the service description that is not part of the |
|||
* Guzzle service description specification. |
|||
* |
|||
* @param string $key Data key to retrieve or null to retrieve all extra |
|||
* |
|||
* @return null|mixed |
|||
*/ |
|||
public function getData($key = null) |
|||
{ |
|||
if ($key === null) { |
|||
return $this->extraData; |
|||
} elseif (isset($this->extraData[$key])) { |
|||
return $this->extraData[$key]; |
|||
} else { |
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,107 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle; |
|||
|
|||
use GuzzleHttp\Psr7\Uri; |
|||
|
|||
interface DescriptionInterface |
|||
{ |
|||
/** |
|||
* Get the basePath/baseUri of the description |
|||
* |
|||
* @return Uri |
|||
*/ |
|||
public function getBaseUri(); |
|||
|
|||
/** |
|||
* Get the API operations of the service |
|||
* |
|||
* @return Operation[] Returns an array of {@see Operation} objects |
|||
*/ |
|||
public function getOperations(); |
|||
|
|||
/** |
|||
* Check if the service has an operation by name |
|||
* |
|||
* @param string $name Name of the operation to check |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function hasOperation($name); |
|||
|
|||
/** |
|||
* Get an API operation by name |
|||
* |
|||
* @param string $name Name of the command |
|||
* |
|||
* @return Operation |
|||
* @throws \InvalidArgumentException if the operation is not found |
|||
*/ |
|||
public function getOperation($name); |
|||
|
|||
/** |
|||
* Get a shared definition structure. |
|||
* |
|||
* @param string $id ID/name of the model to retrieve |
|||
* |
|||
* @return Parameter |
|||
* @throws \InvalidArgumentException if the model is not found |
|||
*/ |
|||
public function getModel($id); |
|||
|
|||
/** |
|||
* Get all models of the service description. |
|||
* |
|||
* @return array |
|||
*/ |
|||
public function getModels(); |
|||
|
|||
/** |
|||
* Check if the service description has a model by name. |
|||
* |
|||
* @param string $id Name/ID of the model to check |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function hasModel($id); |
|||
|
|||
/** |
|||
* Get the API version of the service |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function getApiVersion(); |
|||
|
|||
/** |
|||
* Get the name of the API |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function getName(); |
|||
|
|||
/** |
|||
* Get a summary of the purpose of the API |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function getDescription(); |
|||
|
|||
/** |
|||
* Format a parameter using named formats. |
|||
* |
|||
* @param string $format Format to convert it to |
|||
* @param mixed $input Input string |
|||
* |
|||
* @return mixed |
|||
*/ |
|||
public function format($format, $input); |
|||
|
|||
/** |
|||
* Get arbitrary data from the service description that is not part of the |
|||
* Guzzle service description specification. |
|||
* |
|||
* @param string $key Data key to retrieve or null to retrieve all extra |
|||
* |
|||
* @return null|mixed |
|||
*/ |
|||
public function getData($key = null); |
|||
} |
|||
@ -0,0 +1,294 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle; |
|||
|
|||
use GuzzleHttp\Command\CommandInterface; |
|||
use GuzzleHttp\Command\Guzzle\ResponseLocation\BodyLocation; |
|||
use GuzzleHttp\Command\Guzzle\ResponseLocation\HeaderLocation; |
|||
use GuzzleHttp\Command\Guzzle\ResponseLocation\JsonLocation; |
|||
use GuzzleHttp\Command\Guzzle\ResponseLocation\ReasonPhraseLocation; |
|||
use GuzzleHttp\Command\Guzzle\ResponseLocation\ResponseLocationInterface; |
|||
use GuzzleHttp\Command\Guzzle\ResponseLocation\StatusCodeLocation; |
|||
use GuzzleHttp\Command\Guzzle\ResponseLocation\XmlLocation; |
|||
use GuzzleHttp\Command\Result; |
|||
use GuzzleHttp\Command\ResultInterface; |
|||
use Psr\Http\Message\RequestInterface; |
|||
use Psr\Http\Message\ResponseInterface; |
|||
|
|||
/** |
|||
* Handler used to create response models based on an HTTP response and |
|||
* a service description. |
|||
* |
|||
* Response location visitors are registered with this Handler to handle |
|||
* locations (e.g., 'xml', 'json', 'header'). All of the locations of a response |
|||
* model that will be visited first have their ``before`` method triggered. |
|||
* After the before method is called on every visitor that will be walked, each |
|||
* visitor is triggered using the ``visit()`` method. After all of the visitors |
|||
* are visited, the ``after()`` method is called on each visitor. This is the |
|||
* place in which you should handle things like additionalProperties with |
|||
* custom locations (i.e., this is how it is handled in the JSON visitor). |
|||
*/ |
|||
class Deserializer |
|||
{ |
|||
/** @var ResponseLocationInterface[] $responseLocations */ |
|||
private $responseLocations; |
|||
|
|||
/** @var DescriptionInterface $description */ |
|||
private $description; |
|||
|
|||
/** @var boolean $process */ |
|||
private $process; |
|||
|
|||
/** |
|||
* @param DescriptionInterface $description |
|||
* @param bool $process |
|||
* @param ResponseLocationInterface[] $responseLocations Extra response locations |
|||
*/ |
|||
public function __construct( |
|||
DescriptionInterface $description, |
|||
$process, |
|||
array $responseLocations = [] |
|||
) { |
|||
static $defaultResponseLocations; |
|||
if (!$defaultResponseLocations) { |
|||
$defaultResponseLocations = [ |
|||
'body' => new BodyLocation(), |
|||
'header' => new HeaderLocation(), |
|||
'reasonPhrase' => new ReasonPhraseLocation(), |
|||
'statusCode' => new StatusCodeLocation(), |
|||
'xml' => new XmlLocation(), |
|||
'json' => new JsonLocation(), |
|||
]; |
|||
} |
|||
|
|||
$this->responseLocations = $responseLocations + $defaultResponseLocations; |
|||
$this->description = $description; |
|||
$this->process = $process; |
|||
} |
|||
|
|||
/** |
|||
* Deserialize the response into the specified result representation |
|||
* |
|||
* @param ResponseInterface $response |
|||
* @param RequestInterface|null $request |
|||
* @param CommandInterface $command |
|||
* @return Result|ResultInterface|void|ResponseInterface |
|||
*/ |
|||
public function __invoke(ResponseInterface $response, RequestInterface $request, CommandInterface $command) |
|||
{ |
|||
// If the user don't want to process the result, just return the plain response here |
|||
if ($this->process === false) { |
|||
return $response; |
|||
} |
|||
|
|||
$name = $command->getName(); |
|||
$operation = $this->description->getOperation($name); |
|||
|
|||
$this->handleErrorResponses($response, $request, $command, $operation); |
|||
|
|||
// Add a default Model as the result if no matching schema was found |
|||
if (!($modelName = $operation->getResponseModel())) { |
|||
// Not sure if this should be empty or contains the response. |
|||
// Decided to do it how it was in the old version for now. |
|||
return new Result(); |
|||
} |
|||
|
|||
$model = $operation->getServiceDescription()->getModel($modelName); |
|||
if (!$model) { |
|||
throw new \RuntimeException("Unknown model: {$modelName}"); |
|||
} |
|||
|
|||
return $this->visit($model, $response); |
|||
} |
|||
|
|||
/** |
|||
* Handles visit() and after() methods of the Response locations |
|||
* |
|||
* @param Parameter $model |
|||
* @param ResponseInterface $response |
|||
* @return Result|ResultInterface|void |
|||
*/ |
|||
protected function visit(Parameter $model, ResponseInterface $response) |
|||
{ |
|||
$result = new Result(); |
|||
$context = ['visitors' => []]; |
|||
|
|||
if ($model->getType() === 'object') { |
|||
$result = $this->visitOuterObject($model, $result, $response, $context); |
|||
} elseif ($model->getType() === 'array') { |
|||
$result = $this->visitOuterArray($model, $result, $response, $context); |
|||
} else { |
|||
throw new \InvalidArgumentException('Invalid response model: ' . $model->getType()); |
|||
} |
|||
|
|||
// Call the after() method of each found visitor |
|||
/** @var ResponseLocationInterface $visitor */ |
|||
foreach ($context['visitors'] as $visitor) { |
|||
$result = $visitor->after($result, $response, $model); |
|||
} |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* Handles the before() method of Response locations |
|||
* |
|||
* @param string $location |
|||
* @param Parameter $model |
|||
* @param ResultInterface $result |
|||
* @param ResponseInterface $response |
|||
* @param array $context |
|||
* @return ResultInterface |
|||
*/ |
|||
private function triggerBeforeVisitor( |
|||
$location, |
|||
Parameter $model, |
|||
ResultInterface $result, |
|||
ResponseInterface $response, |
|||
array &$context |
|||
) { |
|||
if (!isset($this->responseLocations[$location])) { |
|||
throw new \RuntimeException("Unknown location: $location"); |
|||
} |
|||
|
|||
$context['visitors'][$location] = $this->responseLocations[$location]; |
|||
|
|||
$result = $this->responseLocations[$location]->before( |
|||
$result, |
|||
$response, |
|||
$model |
|||
); |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* Visits the outer object |
|||
* |
|||
* @param Parameter $model |
|||
* @param ResultInterface $result |
|||
* @param ResponseInterface $response |
|||
* @param array $context |
|||
* @return ResultInterface |
|||
*/ |
|||
private function visitOuterObject( |
|||
Parameter $model, |
|||
ResultInterface $result, |
|||
ResponseInterface $response, |
|||
array &$context |
|||
) { |
|||
$parentLocation = $model->getLocation(); |
|||
|
|||
// If top-level additionalProperties is a schema, then visit it |
|||
$additional = $model->getAdditionalProperties(); |
|||
if ($additional instanceof Parameter) { |
|||
// Use the model location if none set on additionalProperties. |
|||
$location = $additional->getLocation() ?: $parentLocation; |
|||
$result = $this->triggerBeforeVisitor($location, $model, $result, $response, $context); |
|||
} |
|||
|
|||
// Use 'location' from all individual defined properties, but fall back |
|||
// to the model location if no per-property location is set. Collect |
|||
// the properties that need to be visited into an array. |
|||
$visitProperties = []; |
|||
foreach ($model->getProperties() as $schema) { |
|||
$location = $schema->getLocation() ?: $parentLocation; |
|||
if ($location) { |
|||
$visitProperties[] = [$location, $schema]; |
|||
// Trigger the before method on each unique visitor location |
|||
if (!isset($context['visitors'][$location])) { |
|||
$result = $this->triggerBeforeVisitor($location, $model, $result, $response, $context); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Actually visit each response element |
|||
foreach ($visitProperties as $property) { |
|||
$result = $this->responseLocations[$property[0]]->visit($result, $response, $property[1]); |
|||
} |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* Visits the outer array |
|||
* |
|||
* @param Parameter $model |
|||
* @param ResultInterface $result |
|||
* @param ResponseInterface $response |
|||
* @param array $context |
|||
* @return ResultInterface|void |
|||
*/ |
|||
private function visitOuterArray( |
|||
Parameter $model, |
|||
ResultInterface $result, |
|||
ResponseInterface $response, |
|||
array &$context |
|||
) { |
|||
// Use 'location' defined on the top of the model |
|||
if (!($location = $model->getLocation())) { |
|||
return; |
|||
} |
|||
|
|||
// Trigger the before method on each unique visitor location |
|||
if (!isset($context['visitors'][$location])) { |
|||
$result = $this->triggerBeforeVisitor($location, $model, $result, $response, $context); |
|||
} |
|||
|
|||
// Visit each item in the response |
|||
$result = $this->responseLocations[$location]->visit($result, $response, $model); |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* Reads the "errorResponses" from commands, and trigger appropriate exceptions |
|||
* |
|||
* In order for the exception to be properly triggered, all your exceptions must be instance |
|||
* of "GuzzleHttp\Command\Exception\CommandException". If that's not the case, your exceptions will be wrapped |
|||
* around a CommandException |
|||
* |
|||
* @param ResponseInterface $response |
|||
* @param RequestInterface $request |
|||
* @param CommandInterface $command |
|||
* @param Operation $operation |
|||
*/ |
|||
protected function handleErrorResponses( |
|||
ResponseInterface $response, |
|||
RequestInterface $request, |
|||
CommandInterface $command, |
|||
Operation $operation |
|||
) { |
|||
$errors = $operation->getErrorResponses(); |
|||
|
|||
// We iterate through each errors in service description. If the descriptor contains both a phrase and |
|||
// status code, there must be an exact match of both. Otherwise, a match of status code is enough |
|||
$bestException = null; |
|||
|
|||
foreach ($errors as $error) { |
|||
$code = (int) $error['code']; |
|||
|
|||
if ($response->getStatusCode() !== $code) { |
|||
continue; |
|||
} |
|||
|
|||
if (isset($error['phrase']) && ! ($error['phrase'] === $response->getReasonPhrase())) { |
|||
continue; |
|||
} |
|||
|
|||
$bestException = $error['class']; |
|||
|
|||
// If there is an exact match of phrase + code, then we cannot find a more specialized exception in |
|||
// the array, so we can break early instead of iterating the remaining ones |
|||
if (isset($error['phrase'])) { |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (null !== $bestException) { |
|||
throw new $bestException($response->getReasonPhrase(), $command, null, $request, $response); |
|||
} |
|||
|
|||
// If we reach here, no exception could be match from descriptor, and Guzzle exception will propagate if |
|||
// option "http_errors" is set to true, which is the default setting. |
|||
} |
|||
} |
|||
@ -0,0 +1,169 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle; |
|||
|
|||
use GuzzleHttp\ClientInterface; |
|||
use GuzzleHttp\Command\CommandInterface; |
|||
use GuzzleHttp\Command\Guzzle\Handler\ValidatedDescriptionHandler; |
|||
use GuzzleHttp\Command\ServiceClient; |
|||
use GuzzleHttp\HandlerStack; |
|||
|
|||
/** |
|||
* Default Guzzle web service client implementation. |
|||
*/ |
|||
class GuzzleClient extends ServiceClient |
|||
{ |
|||
/** @var array $config */ |
|||
private $config; |
|||
|
|||
/** @var DescriptionInterface Guzzle service description */ |
|||
private $description; |
|||
|
|||
/** |
|||
* The client constructor accepts an associative array of configuration |
|||
* options: |
|||
* |
|||
* - defaults: Associative array of default command parameters to add to |
|||
* each command created by the client. |
|||
* - validate: Specify if command input is validated (defaults to true). |
|||
* Changing this setting after the client has been created will have no |
|||
* effect. |
|||
* - process: Specify if HTTP responses are parsed (defaults to true). |
|||
* Changing this setting after the client has been created will have no |
|||
* effect. |
|||
* - response_locations: Associative array of location types mapping to |
|||
* ResponseLocationInterface objects. |
|||
* |
|||
* @param ClientInterface $client HTTP client to use. |
|||
* @param DescriptionInterface $description Guzzle service description |
|||
* @param callable $commandToRequestTransformer |
|||
* @param callable $responseToResultTransformer |
|||
* @param HandlerStack $commandHandlerStack |
|||
* @param array $config Configuration options |
|||
*/ |
|||
public function __construct( |
|||
ClientInterface $client, |
|||
DescriptionInterface $description, |
|||
callable $commandToRequestTransformer = null, |
|||
callable $responseToResultTransformer = null, |
|||
HandlerStack $commandHandlerStack = null, |
|||
array $config = [] |
|||
) { |
|||
$this->config = $config; |
|||
$this->description = $description; |
|||
$serializer = $this->getSerializer($commandToRequestTransformer); |
|||
$deserializer = $this->getDeserializer($responseToResultTransformer); |
|||
|
|||
parent::__construct($client, $serializer, $deserializer, $commandHandlerStack); |
|||
$this->processConfig($config); |
|||
} |
|||
|
|||
/** |
|||
* Returns the command if valid; otherwise an Exception |
|||
* @param string $name |
|||
* @param array $args |
|||
* @return CommandInterface |
|||
* @throws \InvalidArgumentException |
|||
*/ |
|||
public function getCommand($name, array $args = []) |
|||
{ |
|||
if (!$this->description->hasOperation($name)) { |
|||
$name = ucfirst($name); |
|||
if (!$this->description->hasOperation($name)) { |
|||
throw new \InvalidArgumentException( |
|||
"No operation found named {$name}" |
|||
); |
|||
} |
|||
} |
|||
|
|||
// Merge in default command options |
|||
$args += $this->getConfig('defaults'); |
|||
|
|||
return parent::getCommand($name, $args); |
|||
} |
|||
|
|||
/** |
|||
* Return the description |
|||
* |
|||
* @return DescriptionInterface |
|||
*/ |
|||
public function getDescription() |
|||
{ |
|||
return $this->description; |
|||
} |
|||
|
|||
/** |
|||
* Returns the passed Serializer when set, a new instance otherwise |
|||
* |
|||
* @param callable|null $commandToRequestTransformer |
|||
* @return \GuzzleHttp\Command\Guzzle\Serializer |
|||
*/ |
|||
private function getSerializer($commandToRequestTransformer) |
|||
{ |
|||
return $commandToRequestTransformer ==! null |
|||
? $commandToRequestTransformer |
|||
: new Serializer($this->description); |
|||
} |
|||
|
|||
/** |
|||
* Returns the passed Deserializer when set, a new instance otherwise |
|||
* |
|||
* @param callable|null $responseToResultTransformer |
|||
* @return \GuzzleHttp\Command\Guzzle\Deserializer |
|||
*/ |
|||
private function getDeserializer($responseToResultTransformer) |
|||
{ |
|||
$process = (! isset($this->config['process']) || $this->config['process'] === true); |
|||
|
|||
return $responseToResultTransformer ==! null |
|||
? $responseToResultTransformer |
|||
: new Deserializer($this->description, $process); |
|||
} |
|||
|
|||
/** |
|||
* Get the config of the client |
|||
* |
|||
* @param array|string $option |
|||
* @return mixed |
|||
*/ |
|||
public function getConfig($option = null) |
|||
{ |
|||
return $option === null |
|||
? $this->config |
|||
: (isset($this->config[$option]) ? $this->config[$option] : []); |
|||
} |
|||
|
|||
/** |
|||
* @param $option |
|||
* @param $value |
|||
*/ |
|||
public function setConfig($option, $value) |
|||
{ |
|||
$this->config[$option] = $value; |
|||
} |
|||
|
|||
/** |
|||
* Prepares the client based on the configuration settings of the client. |
|||
* |
|||
* @param array $config Constructor config as an array |
|||
*/ |
|||
protected function processConfig(array $config) |
|||
{ |
|||
// set defaults as an array if not provided |
|||
if (!isset($config['defaults'])) { |
|||
$config['defaults'] = []; |
|||
} |
|||
|
|||
// Add the handlers based on the configuration option |
|||
$stack = $this->getHandlerStack(); |
|||
|
|||
if (!isset($config['validate']) || $config['validate'] === true) { |
|||
$stack->push(new ValidatedDescriptionHandler($this->description), 'validate_description'); |
|||
} |
|||
|
|||
if (!isset($config['process']) || $config['process'] === true) { |
|||
// TODO: This belongs to the Deserializer and should be handled there. |
|||
// Question: What is the result when the Deserializer is bypassed? |
|||
// Possible answer: The raw response. |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
<?php namespace GuzzleHttp\Command\Guzzle\Handler; |
|||
|
|||
use GuzzleHttp\Command\CommandInterface; |
|||
use GuzzleHttp\Command\Exception\CommandException; |
|||
use GuzzleHttp\Command\Guzzle\DescriptionInterface; |
|||
use GuzzleHttp\Command\Guzzle\SchemaValidator; |
|||
|
|||
/** |
|||
* Handler used to validate command input against a service description. |
|||
* |
|||
* @author Stefano Kowalke <info@arroba-it.de> |
|||
*/ |
|||
class ValidatedDescriptionHandler |
|||
{ |
|||
/** @var SchemaValidator $validator */ |
|||
private $validator; |
|||
|
|||
/** @var DescriptionInterface $description */ |
|||
private $description; |
|||
|
|||
/** |
|||
* ValidatedDescriptionHandler constructor. |
|||
* |
|||
* @param DescriptionInterface $description |
|||
* @param SchemaValidator|null $schemaValidator |
|||
*/ |
|||
public function __construct(DescriptionInterface $description, SchemaValidator $schemaValidator = null) |
|||
{ |
|||
$this->description = $description; |
|||
$this->validator = $schemaValidator ?: new SchemaValidator(); |
|||
} |
|||
|
|||
/** |
|||
* @param callable $handler |
|||
* @return \Closure |
|||
*/ |
|||
public function __invoke(callable $handler) |
|||
{ |
|||
return function (CommandInterface $command) use ($handler) { |
|||
$errors = []; |
|||
$operation = $this->description->getOperation($command->getName()); |
|||
|
|||
foreach ($operation->getParams() as $name => $schema) { |
|||
$value = $command[$name]; |
|||
|
|||
if ($value) { |
|||
$value = $schema->filter($value); |
|||
} |
|||
|
|||
if (! $this->validator->validate($schema, $value)) { |
|||
$errors = array_merge($errors, $this->validator->getErrors()); |
|||
} elseif ($value !== $command[$name]) { |
|||
// Update the config value if it changed and no validation errors were encountered. |
|||
// This happen when the user extending an operation |
|||
// See https://github.com/guzzle/guzzle-services/issues/145 |
|||
$command[$name] = $value; |
|||
} |
|||
} |
|||
|
|||
if ($params = $operation->getAdditionalParameters()) { |
|||
foreach ($command->toArray() as $name => $value) { |
|||
// It's only additional if it isn't defined in the schema |
|||
if (! $operation->hasParam($name)) { |
|||
// Always set the name so that error messages are useful |
|||
$params->setName($name); |
|||
if (! $this->validator->validate($params, $value)) { |
|||
$errors = array_merge($errors, $this->validator->getErrors()); |
|||
} elseif ($value !== $command[$name]) { |
|||
$command[$name] = $value; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
if ($errors) { |
|||
throw new CommandException('Validation errors: ' . implode("\n", $errors), $command); |
|||
} |
|||
|
|||
return $handler($command); |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,312 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle; |
|||
|
|||
use GuzzleHttp\Command\ToArrayInterface; |
|||
|
|||
/** |
|||
* Guzzle operation |
|||
*/ |
|||
class Operation implements ToArrayInterface |
|||
{ |
|||
/** @var array Parameters */ |
|||
private $parameters = []; |
|||
|
|||
/** @var Parameter Additional parameters schema */ |
|||
private $additionalParameters; |
|||
|
|||
/** @var DescriptionInterface */ |
|||
private $description; |
|||
|
|||
/** @var array Config data */ |
|||
private $config; |
|||
|
|||
/** |
|||
* Builds an Operation object using an array of configuration data. |
|||
* |
|||
* - name: (string) Name of the command |
|||
* - httpMethod: (string) HTTP method of the operation |
|||
* - uri: (string) URI template that can create a relative or absolute URL |
|||
* - parameters: (array) Associative array of parameters for the command. |
|||
* Each value must be an array that is used to create {@see Parameter} |
|||
* objects. |
|||
* - summary: (string) This is a short summary of what the operation does |
|||
* - notes: (string) A longer description of the operation. |
|||
* - documentationUrl: (string) Reference URL providing more information |
|||
* about the operation. |
|||
* - responseModel: (string) The model name used for processing response. |
|||
* - deprecated: (bool) Set to true if this is a deprecated command |
|||
* - errorResponses: (array) Errors that could occur when executing the |
|||
* command. Array of hashes, each with a 'code' (the HTTP response code), |
|||
* 'phrase' (response reason phrase or description of the error), and |
|||
* 'class' (a custom exception class that would be thrown if the error is |
|||
* encountered). |
|||
* - data: (array) Any extra data that might be used to help build or |
|||
* serialize the operation |
|||
* - additionalParameters: (null|array) Parameter schema to use when an |
|||
* option is passed to the operation that is not in the schema |
|||
* |
|||
* @param array $config Array of configuration data |
|||
* @param DescriptionInterface $description Service description used to resolve models if $ref tags are found |
|||
* @throws \InvalidArgumentException |
|||
*/ |
|||
public function __construct(array $config = [], DescriptionInterface $description = null) |
|||
{ |
|||
static $defaults = [ |
|||
'name' => '', |
|||
'httpMethod' => '', |
|||
'uri' => '', |
|||
'responseModel' => null, |
|||
'notes' => '', |
|||
'summary' => '', |
|||
'documentationUrl' => null, |
|||
'deprecated' => false, |
|||
'data' => [], |
|||
'parameters' => [], |
|||
'additionalParameters' => null, |
|||
'errorResponses' => [] |
|||
]; |
|||
|
|||
$this->description = $description === null ? new Description([]) : $description; |
|||
|
|||
if (isset($config['extends'])) { |
|||
$config = $this->resolveExtends($config['extends'], $config); |
|||
} |
|||
|
|||
$this->config = $config + $defaults; |
|||
|
|||
// Account for the old style of using responseClass |
|||
if (isset($config['responseClass'])) { |
|||
$this->config['responseModel'] = $config['responseClass']; |
|||
} |
|||
|
|||
$this->resolveParameters(); |
|||
} |
|||
|
|||
/** |
|||
* @return array |
|||
*/ |
|||
public function toArray() |
|||
{ |
|||
return $this->config; |
|||
} |
|||
|
|||
/** |
|||
* Get the service description that the operation belongs to |
|||
* |
|||
* @return Description |
|||
*/ |
|||
public function getServiceDescription() |
|||
{ |
|||
return $this->description; |
|||
} |
|||
|
|||
/** |
|||
* Get the params of the operation |
|||
* |
|||
* @return Parameter[] |
|||
*/ |
|||
public function getParams() |
|||
{ |
|||
return $this->parameters; |
|||
} |
|||
|
|||
/** |
|||
* Get additionalParameters of the operation |
|||
* |
|||
* @return Parameter|null |
|||
*/ |
|||
public function getAdditionalParameters() |
|||
{ |
|||
return $this->additionalParameters; |
|||
} |
|||
|
|||
/** |
|||
* Check if the operation has a specific parameter by name |
|||
* |
|||
* @param string $name Name of the param |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function hasParam($name) |
|||
{ |
|||
return isset($this->parameters[$name]); |
|||
} |
|||
|
|||
/** |
|||
* Get a single parameter of the operation |
|||
* |
|||
* @param string $name Parameter to retrieve by name |
|||
* |
|||
* @return Parameter|null |
|||
*/ |
|||
public function getParam($name) |
|||
{ |
|||
return isset($this->parameters[$name]) |
|||
? $this->parameters[$name] |
|||
: null; |
|||
} |
|||
|
|||
/** |
|||
* Get the HTTP method of the operation |
|||
* |
|||
* @return string|null |
|||
*/ |
|||
public function getHttpMethod() |
|||
{ |
|||
return $this->config['httpMethod']; |
|||
} |
|||
|
|||
/** |
|||
* Get the name of the operation |
|||
* |
|||
* @return string|null |
|||
*/ |
|||
public function getName() |
|||
{ |
|||
return $this->config['name']; |
|||
} |
|||
|
|||
/** |
|||
* Get a short summary of what the operation does |
|||
* |
|||
* @return string|null |
|||
*/ |
|||
public function getSummary() |
|||
{ |
|||
return $this->config['summary']; |
|||
} |
|||
|
|||
/** |
|||
* Get a longer text field to explain the behavior of the operation |
|||
* |
|||
* @return string|null |
|||
*/ |
|||
public function getNotes() |
|||
{ |
|||
return $this->config['notes']; |
|||
} |
|||
|
|||
/** |
|||
* Get the documentation URL of the operation |
|||
* |
|||
* @return string|null |
|||
*/ |
|||
public function getDocumentationUrl() |
|||
{ |
|||
return $this->config['documentationUrl']; |
|||
} |
|||
|
|||
/** |
|||
* Get the name of the model used for processing the response. |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function getResponseModel() |
|||
{ |
|||
return $this->config['responseModel']; |
|||
} |
|||
|
|||
/** |
|||
* Get whether or not the operation is deprecated |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function getDeprecated() |
|||
{ |
|||
return $this->config['deprecated']; |
|||
} |
|||
|
|||
/** |
|||
* Get the URI that will be merged into the generated request |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function getUri() |
|||
{ |
|||
return $this->config['uri']; |
|||
} |
|||
|
|||
/** |
|||
* Get the errors that could be encountered when executing the operation |
|||
* |
|||
* @return array |
|||
*/ |
|||
public function getErrorResponses() |
|||
{ |
|||
return $this->config['errorResponses']; |
|||
} |
|||
|
|||
/** |
|||
* Get extra data from the operation |
|||
* |
|||
* @param string $name Name of the data point to retrieve or null to |
|||
* retrieve all of the extra data. |
|||
* |
|||
* @return mixed|null |
|||
*/ |
|||
public function getData($name = null) |
|||
{ |
|||
if ($name === null) { |
|||
return $this->config['data']; |
|||
} elseif (isset($this->config['data'][$name])) { |
|||
return $this->config['data'][$name]; |
|||
} else { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @param $name |
|||
* @param array $config |
|||
* @return array |
|||
*/ |
|||
private function resolveExtends($name, array $config) |
|||
{ |
|||
if (!$this->description->hasOperation($name)) { |
|||
throw new \InvalidArgumentException('No operation named ' . $name); |
|||
} |
|||
|
|||
// Merge parameters together one level deep |
|||
$base = $this->description->getOperation($name)->toArray(); |
|||
$result = $config + $base; |
|||
|
|||
if (isset($base['parameters']) && isset($config['parameters'])) { |
|||
$result['parameters'] = $config['parameters'] + $base['parameters']; |
|||
} |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* Process the description and extract the parameter config |
|||
* |
|||
* @return void |
|||
*/ |
|||
private function resolveParameters() |
|||
{ |
|||
// Parameters need special handling when adding |
|||
foreach ($this->config['parameters'] as $name => $param) { |
|||
if (!is_array($param)) { |
|||
throw new \InvalidArgumentException( |
|||
"Parameters must be arrays, {$this->config['name']}.$name is ".gettype($param) |
|||
); |
|||
} |
|||
$param['name'] = $name; |
|||
$this->parameters[$name] = new Parameter( |
|||
$param, |
|||
['description' => $this->description] |
|||
); |
|||
} |
|||
|
|||
if ($this->config['additionalParameters']) { |
|||
if (is_array($this->config['additionalParameters'])) { |
|||
$this->additionalParameters = new Parameter( |
|||
$this->config['additionalParameters'], |
|||
['description' => $this->description] |
|||
); |
|||
} else { |
|||
$this->additionalParameters = $this->config['additionalParameters']; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,655 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle; |
|||
|
|||
use GuzzleHttp\Command\ToArrayInterface; |
|||
|
|||
/** |
|||
* API parameter object used with service descriptions |
|||
*/ |
|||
class Parameter implements ToArrayInterface |
|||
{ |
|||
private $originalData; |
|||
|
|||
/** @var string $name */ |
|||
private $name; |
|||
|
|||
/** @var string $description */ |
|||
private $description; |
|||
|
|||
/** @var string|array $type */ |
|||
private $type; |
|||
|
|||
/** @var bool $required*/ |
|||
private $required; |
|||
|
|||
/** @var array|null $enum */ |
|||
private $enum; |
|||
|
|||
/** @var string $pattern */ |
|||
private $pattern; |
|||
|
|||
/** @var int $minimum*/ |
|||
private $minimum; |
|||
|
|||
/** @var int $maximum */ |
|||
private $maximum; |
|||
|
|||
/** @var int $minLength */ |
|||
private $minLength; |
|||
|
|||
/** @var int $maxLength */ |
|||
private $maxLength; |
|||
|
|||
/** @var int $minItems */ |
|||
private $minItems; |
|||
|
|||
/** @var int $maxItems */ |
|||
private $maxItems; |
|||
|
|||
/** @var mixed $default */ |
|||
private $default; |
|||
|
|||
/** @var bool $static */ |
|||
private $static; |
|||
|
|||
/** @var array $filters */ |
|||
private $filters; |
|||
|
|||
/** @var string $location */ |
|||
private $location; |
|||
|
|||
/** @var string $sentAs */ |
|||
private $sentAs; |
|||
|
|||
/** @var array $data */ |
|||
private $data; |
|||
|
|||
/** @var array $properties */ |
|||
private $properties = []; |
|||
|
|||
/** @var array|bool|Parameter $additionalProperties */ |
|||
private $additionalProperties; |
|||
|
|||
/** @var array|Parameter $items */ |
|||
private $items; |
|||
|
|||
/** @var string $format */ |
|||
private $format; |
|||
|
|||
private $propertiesCache = null; |
|||
|
|||
/** @var Description */ |
|||
private $serviceDescription; |
|||
|
|||
/** |
|||
* Create a new Parameter using an associative array of data. |
|||
* |
|||
* The array can contain the following information: |
|||
* |
|||
* - name: (string) Unique name of the parameter |
|||
* |
|||
* - type: (string|array) Type of variable (string, number, integer, |
|||
* boolean, object, array, numeric, null, any). Types are used for |
|||
* validation and determining the structure of a parameter. You can use a |
|||
* union type by providing an array of simple types. If one of the union |
|||
* types matches the provided value, then the value is valid. |
|||
* |
|||
* - required: (bool) Whether or not the parameter is required |
|||
* |
|||
* - default: (mixed) Default value to use if no value is supplied |
|||
* |
|||
* - static: (bool) Set to true to specify that the parameter value cannot |
|||
* be changed from the default. |
|||
* |
|||
* - description: (string) Documentation of the parameter |
|||
* |
|||
* - location: (string) The location of a request used to apply a parameter. |
|||
* Custom locations can be registered with a command, but the defaults |
|||
* are uri, query, header, body, json, xml, formParam, multipart. |
|||
* |
|||
* - sentAs: (string) Specifies how the data being modeled is sent over the |
|||
* wire. For example, you may wish to include certain headers in a |
|||
* response model that have a normalized casing of FooBar, but the actual |
|||
* header is x-foo-bar. In this case, sentAs would be set to x-foo-bar. |
|||
* |
|||
* - filters: (array) Array of static method names to run a parameter |
|||
* value through. Each value in the array must be a string containing the |
|||
* full class path to a static method or an array of complex filter |
|||
* information. You can specify static methods of classes using the full |
|||
* namespace class name followed by '::' (e.g. Foo\Bar::baz). Some |
|||
* filters require arguments in order to properly filter a value. For |
|||
* complex filters, use a hash containing a 'method' key pointing to a |
|||
* static method, and an 'args' key containing an array of positional |
|||
* arguments to pass to the method. Arguments can contain keywords that |
|||
* are replaced when filtering a value: '@value' is replaced with the |
|||
* value being validated, '@api' is replaced with the Parameter object. |
|||
* |
|||
* - properties: When the type is an object, you can specify nested parameters |
|||
* |
|||
* - additionalProperties: (array) This attribute defines a schema for all |
|||
* properties that are not explicitly defined in an object type |
|||
* definition. If specified, the value MUST be a schema or a boolean. If |
|||
* false is provided, no additional properties are allowed beyond the |
|||
* properties defined in the schema. The default value is an empty schema |
|||
* which allows any value for additional properties. |
|||
* |
|||
* - items: This attribute defines the allowed items in an instance array, |
|||
* and MUST be a schema or an array of schemas. The default value is an |
|||
* empty schema which allows any value for items in the instance array. |
|||
* When this attribute value is a schema and the instance value is an |
|||
* array, then all the items in the array MUST be valid according to the |
|||
* schema. |
|||
* |
|||
* - pattern: When the type is a string, you can specify the regex pattern |
|||
* that a value must match |
|||
* |
|||
* - enum: When the type is a string, you can specify a list of acceptable |
|||
* values. |
|||
* |
|||
* - minItems: (int) Minimum number of items allowed in an array |
|||
* |
|||
* - maxItems: (int) Maximum number of items allowed in an array |
|||
* |
|||
* - minLength: (int) Minimum length of a string |
|||
* |
|||
* - maxLength: (int) Maximum length of a string |
|||
* |
|||
* - minimum: (int) Minimum value of an integer |
|||
* |
|||
* - maximum: (int) Maximum value of an integer |
|||
* |
|||
* - data: (array) Any additional custom data to use when serializing, |
|||
* validating, etc |
|||
* |
|||
* - format: (string) Format used to coax a value into the correct format |
|||
* when serializing or unserializing. You may specify either an array of |
|||
* filters OR a format, but not both. Supported values: date-time, date, |
|||
* time, timestamp, date-time-http, and boolean-string. |
|||
* |
|||
* - $ref: (string) String referencing a service description model. The |
|||
* parameter is replaced by the schema contained in the model. |
|||
* |
|||
* @param array $data Array of data as seen in service descriptions |
|||
* @param array $options Options used when creating the parameter. You can |
|||
* specify a Guzzle service description in the 'description' key. |
|||
* |
|||
* @throws \InvalidArgumentException |
|||
*/ |
|||
public function __construct(array $data = [], array $options = []) |
|||
{ |
|||
$this->originalData = $data; |
|||
|
|||
if (isset($options['description'])) { |
|||
$this->serviceDescription = $options['description']; |
|||
if (!($this->serviceDescription instanceof DescriptionInterface)) { |
|||
throw new \InvalidArgumentException('description must be a Description'); |
|||
} |
|||
if (isset($data['$ref'])) { |
|||
if ($model = $this->serviceDescription->getModel($data['$ref'])) { |
|||
$name = isset($data['name']) ? $data['name'] : null; |
|||
$data = $model->toArray() + $data; |
|||
if ($name) { |
|||
$data['name'] = $name; |
|||
} |
|||
} |
|||
} elseif (isset($data['extends'])) { |
|||
// If this parameter extends from another parameter then start |
|||
// with the actual data union in the parent's data (e.g. actual |
|||
// supersedes parent) |
|||
if ($extends = $this->serviceDescription->getModel($data['extends'])) { |
|||
$data += $extends->toArray(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Pull configuration data into the parameter |
|||
foreach ($data as $key => $value) { |
|||
$this->{$key} = $value; |
|||
} |
|||
|
|||
$this->required = (bool) $this->required; |
|||
$this->data = (array) $this->data; |
|||
|
|||
if ($this->filters) { |
|||
$this->setFilters((array) $this->filters); |
|||
} |
|||
|
|||
if ($this->type == 'object' && $this->additionalProperties === null) { |
|||
$this->additionalProperties = true; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Convert the object to an array |
|||
* |
|||
* @return array |
|||
*/ |
|||
public function toArray() |
|||
{ |
|||
return $this->originalData; |
|||
} |
|||
|
|||
/** |
|||
* Get the default or static value of the command based on a value |
|||
* |
|||
* @param string $value Value that is currently set |
|||
* |
|||
* @return mixed Returns the value, a static value if one is present, or a default value |
|||
*/ |
|||
public function getValue($value) |
|||
{ |
|||
if ($this->static || ($this->default !== null && $value === null)) { |
|||
return $this->default; |
|||
} |
|||
|
|||
return $value; |
|||
} |
|||
|
|||
/** |
|||
* Run a value through the filters OR format attribute associated with the |
|||
* parameter. |
|||
* |
|||
* @param mixed $value Value to filter |
|||
* |
|||
* @return mixed Returns the filtered value |
|||
* @throws \RuntimeException when trying to format when no service |
|||
* description is available. |
|||
*/ |
|||
public function filter($value) |
|||
{ |
|||
// Formats are applied exclusively and supersed filters |
|||
if ($this->format) { |
|||
if (!$this->serviceDescription) { |
|||
throw new \RuntimeException('No service description was set so ' |
|||
. 'the value cannot be formatted.'); |
|||
} |
|||
return $this->serviceDescription->format($this->format, $value); |
|||
} |
|||
|
|||
// Convert Boolean values |
|||
if ($this->type == 'boolean' && !is_bool($value)) { |
|||
$value = filter_var($value, FILTER_VALIDATE_BOOLEAN); |
|||
} |
|||
|
|||
// Apply filters to the value |
|||
if ($this->filters) { |
|||
foreach ($this->filters as $filter) { |
|||
if (is_array($filter)) { |
|||
// Convert complex filters that hold value place holders |
|||
foreach ($filter['args'] as &$data) { |
|||
if ($data == '@value') { |
|||
$data = $value; |
|||
} elseif ($data == '@api') { |
|||
$data = $this; |
|||
} |
|||
} |
|||
$value = call_user_func_array( |
|||
$filter['method'], |
|||
$filter['args'] |
|||
); |
|||
} else { |
|||
$value = call_user_func($filter, $value); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return $value; |
|||
} |
|||
|
|||
/** |
|||
* Get the name of the parameter |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function getName() |
|||
{ |
|||
return $this->name; |
|||
} |
|||
|
|||
/** |
|||
* Set the name of the parameter |
|||
* |
|||
* @param string $name Name to set |
|||
*/ |
|||
public function setName($name) |
|||
{ |
|||
$this->name = $name; |
|||
} |
|||
|
|||
/** |
|||
* Get the key of the parameter, where sentAs will supersede name if it is |
|||
* set. |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function getWireName() |
|||
{ |
|||
return $this->sentAs ?: $this->name; |
|||
} |
|||
|
|||
/** |
|||
* Get the type(s) of the parameter |
|||
* |
|||
* @return string|array |
|||
*/ |
|||
public function getType() |
|||
{ |
|||
return $this->type; |
|||
} |
|||
|
|||
/** |
|||
* Get if the parameter is required |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function isRequired() |
|||
{ |
|||
return $this->required; |
|||
} |
|||
|
|||
/** |
|||
* Get the default value of the parameter |
|||
* |
|||
* @return string|null |
|||
*/ |
|||
public function getDefault() |
|||
{ |
|||
return $this->default; |
|||
} |
|||
|
|||
/** |
|||
* Get the description of the parameter |
|||
* |
|||
* @return string|null |
|||
*/ |
|||
public function getDescription() |
|||
{ |
|||
return $this->description; |
|||
} |
|||
|
|||
/** |
|||
* Get the minimum acceptable value for an integer |
|||
* |
|||
* @return int|null |
|||
*/ |
|||
public function getMinimum() |
|||
{ |
|||
return $this->minimum; |
|||
} |
|||
|
|||
/** |
|||
* Get the maximum acceptable value for an integer |
|||
* |
|||
* @return int|null |
|||
*/ |
|||
public function getMaximum() |
|||
{ |
|||
return $this->maximum; |
|||
} |
|||
|
|||
/** |
|||
* Get the minimum allowed length of a string value |
|||
* |
|||
* @return int |
|||
*/ |
|||
public function getMinLength() |
|||
{ |
|||
return $this->minLength; |
|||
} |
|||
|
|||
/** |
|||
* Get the maximum allowed length of a string value |
|||
* |
|||
* @return int|null |
|||
*/ |
|||
public function getMaxLength() |
|||
{ |
|||
return $this->maxLength; |
|||
} |
|||
|
|||
/** |
|||
* Get the maximum allowed number of items in an array value |
|||
* |
|||
* @return int|null |
|||
*/ |
|||
public function getMaxItems() |
|||
{ |
|||
return $this->maxItems; |
|||
} |
|||
|
|||
/** |
|||
* Get the minimum allowed number of items in an array value |
|||
* |
|||
* @return int |
|||
*/ |
|||
public function getMinItems() |
|||
{ |
|||
return $this->minItems; |
|||
} |
|||
|
|||
/** |
|||
* Get the location of the parameter |
|||
* |
|||
* @return string|null |
|||
*/ |
|||
public function getLocation() |
|||
{ |
|||
return $this->location; |
|||
} |
|||
|
|||
/** |
|||
* Get the sentAs attribute of the parameter that used with locations to |
|||
* sentAs an attribute when it is being applied to a location. |
|||
* |
|||
* @return string|null |
|||
*/ |
|||
public function getSentAs() |
|||
{ |
|||
return $this->sentAs; |
|||
} |
|||
|
|||
/** |
|||
* Retrieve a known property from the parameter by name or a data property |
|||
* by name. When no specific name value is passed, all data properties |
|||
* will be returned. |
|||
* |
|||
* @param string|null $name Specify a particular property name to retrieve |
|||
* |
|||
* @return array|mixed|null |
|||
*/ |
|||
public function getData($name = null) |
|||
{ |
|||
if (!$name) { |
|||
return $this->data; |
|||
} elseif (isset($this->data[$name])) { |
|||
return $this->data[$name]; |
|||
} elseif (isset($this->{$name})) { |
|||
return $this->{$name}; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* Get whether or not the default value can be changed |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function isStatic() |
|||
{ |
|||
return $this->static; |
|||
} |
|||
|
|||
/** |
|||
* Get an array of filters used by the parameter |
|||
* |
|||
* @return array |
|||
*/ |
|||
public function getFilters() |
|||
{ |
|||
return $this->filters ?: []; |
|||
} |
|||
|
|||
/** |
|||
* Get the properties of the parameter |
|||
* |
|||
* @return Parameter[] |
|||
*/ |
|||
public function getProperties() |
|||
{ |
|||
if (!$this->propertiesCache) { |
|||
$this->propertiesCache = []; |
|||
foreach (array_keys($this->properties) as $name) { |
|||
$this->propertiesCache[$name] = $this->getProperty($name); |
|||
} |
|||
} |
|||
|
|||
return $this->propertiesCache; |
|||
} |
|||
|
|||
/** |
|||
* Get a specific property from the parameter |
|||
* |
|||
* @param string $name Name of the property to retrieve |
|||
* |
|||
* @return null|Parameter |
|||
*/ |
|||
public function getProperty($name) |
|||
{ |
|||
if (!isset($this->properties[$name])) { |
|||
return null; |
|||
} |
|||
|
|||
if (!($this->properties[$name] instanceof self)) { |
|||
$this->properties[$name]['name'] = $name; |
|||
$this->properties[$name] = new static( |
|||
$this->properties[$name], |
|||
['description' => $this->serviceDescription] |
|||
); |
|||
} |
|||
|
|||
return $this->properties[$name]; |
|||
} |
|||
|
|||
/** |
|||
* Get the additionalProperties value of the parameter |
|||
* |
|||
* @return bool|Parameter|null |
|||
*/ |
|||
public function getAdditionalProperties() |
|||
{ |
|||
if (is_array($this->additionalProperties)) { |
|||
$this->additionalProperties = new static( |
|||
$this->additionalProperties, |
|||
['description' => $this->serviceDescription] |
|||
); |
|||
} |
|||
|
|||
return $this->additionalProperties; |
|||
} |
|||
|
|||
/** |
|||
* Get the item data of the parameter |
|||
* |
|||
* @return Parameter |
|||
*/ |
|||
public function getItems() |
|||
{ |
|||
if (is_array($this->items)) { |
|||
$this->items = new static( |
|||
$this->items, |
|||
['description' => $this->serviceDescription] |
|||
); |
|||
} |
|||
|
|||
return $this->items; |
|||
} |
|||
|
|||
/** |
|||
* Get the enum of strings that are valid for the parameter |
|||
* |
|||
* @return array|null |
|||
*/ |
|||
public function getEnum() |
|||
{ |
|||
return $this->enum; |
|||
} |
|||
|
|||
/** |
|||
* Get the regex pattern that must match a value when the value is a string |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function getPattern() |
|||
{ |
|||
return $this->pattern; |
|||
} |
|||
|
|||
/** |
|||
* Get the format attribute of the schema |
|||
* |
|||
* @return string |
|||
*/ |
|||
public function getFormat() |
|||
{ |
|||
return $this->format; |
|||
} |
|||
|
|||
/** |
|||
* Set the array of filters used by the parameter |
|||
* |
|||
* @param array $filters Array of functions to use as filters |
|||
* |
|||
* @return self |
|||
*/ |
|||
private function setFilters(array $filters) |
|||
{ |
|||
$this->filters = []; |
|||
foreach ($filters as $filter) { |
|||
$this->addFilter($filter); |
|||
} |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Add a filter to the parameter |
|||
* |
|||
* @param string|array $filter Method to filter the value through |
|||
* |
|||
* @return self |
|||
* @throws \InvalidArgumentException |
|||
*/ |
|||
private function addFilter($filter) |
|||
{ |
|||
if (is_array($filter)) { |
|||
if (!isset($filter['method'])) { |
|||
throw new \InvalidArgumentException( |
|||
'A [method] value must be specified for each complex filter' |
|||
); |
|||
} |
|||
} |
|||
|
|||
if (!$this->filters) { |
|||
$this->filters = [$filter]; |
|||
} else { |
|||
$this->filters[] = $filter; |
|||
} |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Check if a parameter has a specific variable and if it set. |
|||
* |
|||
* @param string $var |
|||
* @return bool |
|||
*/ |
|||
public function has($var) |
|||
{ |
|||
if (!is_string($var)) { |
|||
throw new \InvalidArgumentException('Expected a string. Got: ' . (is_object($var) ? get_class($var) : gettype($var))); |
|||
} |
|||
return isset($this->{$var}) && !empty($this->{$var}); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle\QuerySerializer; |
|||
|
|||
interface QuerySerializerInterface |
|||
{ |
|||
/** |
|||
* Aggregate query params and transform them into a string |
|||
* |
|||
* @param array $queryParams |
|||
* @return string |
|||
*/ |
|||
public function aggregate(array $queryParams); |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
<?php |
|||
|
|||
namespace GuzzleHttp\Command\Guzzle\QuerySerializer; |
|||
|
|||
class Rfc3986Serializer implements QuerySerializerInterface |
|||
{ |
|||
/** |
|||
* @var bool |
|||
*/ |
|||
private $removeNumericIndices; |
|||
|
|||
/** |
|||
* @param bool $removeNumericIndices |
|||
*/ |
|||
public function __construct($removeNumericIndices = false) |
|||
{ |
|||
$this->removeNumericIndices = $removeNumericIndices; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function aggregate(array $queryParams) |
|||
{ |
|||
$queryString = http_build_query($queryParams, null, '&', PHP_QUERY_RFC3986); |
|||
|
|||
if ($this->removeNumericIndices) { |
|||
$queryString = preg_replace('/%5B[0-9]+%5D/simU', '%5B%5D', $queryString); |
|||
} |
|||
|
|||
return $queryString; |
|||
} |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle\RequestLocation; |
|||
|
|||
use GuzzleHttp\Command\CommandInterface; |
|||
use GuzzleHttp\Command\Guzzle\Operation; |
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use Psr\Http\Message\RequestInterface; |
|||
|
|||
abstract class AbstractLocation implements RequestLocationInterface |
|||
{ |
|||
/** @var string */ |
|||
protected $locationName; |
|||
|
|||
/** |
|||
* Set the name of the location |
|||
* |
|||
* @param $locationName |
|||
*/ |
|||
public function __construct($locationName) |
|||
{ |
|||
$this->locationName = $locationName; |
|||
} |
|||
|
|||
/** |
|||
* @param CommandInterface $command |
|||
* @param RequestInterface $request |
|||
* @param Parameter $param |
|||
* @return RequestInterface |
|||
*/ |
|||
public function visit( |
|||
CommandInterface $command, |
|||
RequestInterface $request, |
|||
Parameter $param |
|||
) { |
|||
return $request; |
|||
} |
|||
|
|||
/** |
|||
* @param CommandInterface $command |
|||
* @param RequestInterface $request |
|||
* @param Operation $operation |
|||
* @return RequestInterface |
|||
*/ |
|||
public function after( |
|||
CommandInterface $command, |
|||
RequestInterface $request, |
|||
Operation $operation |
|||
) { |
|||
return $request; |
|||
} |
|||
|
|||
/** |
|||
* Prepare (filter and set desired name for request item) the value for |
|||
* request. |
|||
* |
|||
* @param mixed $value |
|||
* @param Parameter $param |
|||
* |
|||
* @return array|mixed |
|||
*/ |
|||
protected function prepareValue($value, Parameter $param) |
|||
{ |
|||
return is_array($value) |
|||
? $this->resolveRecursively($value, $param) |
|||
: $param->filter($value); |
|||
} |
|||
|
|||
/** |
|||
* Recursively prepare and filter nested values. |
|||
* |
|||
* @param array $value Value to map |
|||
* @param Parameter $param Parameter related to the current key. |
|||
* |
|||
* @return array Returns the mapped array |
|||
*/ |
|||
protected function resolveRecursively(array $value, Parameter $param) |
|||
{ |
|||
foreach ($value as $name => &$v) { |
|||
switch ($param->getType()) { |
|||
case 'object': |
|||
if ($subParam = $param->getProperty($name)) { |
|||
$key = $subParam->getWireName(); |
|||
$value[$key] = $this->prepareValue($v, $subParam); |
|||
if ($name != $key) { |
|||
unset($value[$name]); |
|||
} |
|||
} elseif ($param->getAdditionalProperties() instanceof Parameter) { |
|||
$v = $this->prepareValue($v, $param->getAdditionalProperties()); |
|||
} |
|||
break; |
|||
case 'array': |
|||
if ($items = $param->getItems()) { |
|||
$v = $this->prepareValue($v, $items); |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return $param->filter($value); |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle\RequestLocation; |
|||
|
|||
use GuzzleHttp\Command\CommandInterface; |
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Psr7; |
|||
use Psr\Http\Message\MessageInterface; |
|||
use Psr\Http\Message\RequestInterface; |
|||
|
|||
/** |
|||
* Adds a body to a request |
|||
*/ |
|||
class BodyLocation extends AbstractLocation |
|||
{ |
|||
|
|||
/** |
|||
* Set the name of the location |
|||
* |
|||
* @param string $locationName |
|||
*/ |
|||
public function __construct($locationName = 'body') |
|||
{ |
|||
parent::__construct($locationName); |
|||
} |
|||
|
|||
/** |
|||
* @param CommandInterface $command |
|||
* @param RequestInterface $request |
|||
* @param Parameter $param |
|||
* |
|||
* @return MessageInterface |
|||
*/ |
|||
public function visit( |
|||
CommandInterface $command, |
|||
RequestInterface $request, |
|||
Parameter $param |
|||
) { |
|||
$oldValue = $request->getBody()->getContents(); |
|||
|
|||
$value = $command[$param->getName()]; |
|||
$value = $param->getName() . '=' . $param->filter($value); |
|||
|
|||
if ($oldValue !== '') { |
|||
$value = $oldValue . '&' . $value; |
|||
} |
|||
|
|||
return $request->withBody(Psr7\stream_for($value)); |
|||
} |
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle\RequestLocation; |
|||
|
|||
use GuzzleHttp\Command\CommandInterface; |
|||
use GuzzleHttp\Command\Guzzle\Operation; |
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Psr7; |
|||
use Psr\Http\Message\RequestInterface; |
|||
|
|||
/** |
|||
* Add form_params to a request |
|||
*/ |
|||
class FormParamLocation extends AbstractLocation |
|||
{ |
|||
/** @var string $contentType */ |
|||
protected $contentType = 'application/x-www-form-urlencoded; charset=utf-8'; |
|||
|
|||
/** @var array $formParamsData */ |
|||
protected $formParamsData = []; |
|||
|
|||
/** |
|||
* Set the name of the location |
|||
* |
|||
* @param string $locationName |
|||
*/ |
|||
public function __construct($locationName = 'formParam') |
|||
{ |
|||
parent::__construct($locationName); |
|||
} |
|||
|
|||
/** |
|||
* @param CommandInterface $command |
|||
* @param RequestInterface $request |
|||
* @param Parameter $param |
|||
* |
|||
* @return RequestInterface |
|||
*/ |
|||
public function visit( |
|||
CommandInterface $command, |
|||
RequestInterface $request, |
|||
Parameter $param |
|||
) { |
|||
$this->formParamsData['form_params'][$param->getWireName()] = $this->prepareValue( |
|||
$command[$param->getName()], |
|||
$param |
|||
); |
|||
|
|||
return $request; |
|||
} |
|||
|
|||
/** |
|||
* @param CommandInterface $command |
|||
* @param RequestInterface $request |
|||
* @param Operation $operation |
|||
* |
|||
* @return RequestInterface |
|||
*/ |
|||
public function after( |
|||
CommandInterface $command, |
|||
RequestInterface $request, |
|||
Operation $operation |
|||
) { |
|||
$data = $this->formParamsData; |
|||
$this->formParamsData = []; |
|||
$modify = []; |
|||
|
|||
// Add additional parameters to the form_params array |
|||
$additional = $operation->getAdditionalParameters(); |
|||
if ($additional && $additional->getLocation() == $this->locationName) { |
|||
foreach ($command->toArray() as $key => $value) { |
|||
if (!$operation->hasParam($key)) { |
|||
$data['form_params'][$key] = $this->prepareValue($value, $additional); |
|||
} |
|||
} |
|||
} |
|||
|
|||
$body = http_build_query($data['form_params'], '', '&'); |
|||
$modify['body'] = Psr7\stream_for($body); |
|||
$modify['set_headers']['Content-Type'] = $this->contentType; |
|||
$request = Psr7\modify_request($request, $modify); |
|||
|
|||
return $request; |
|||
} |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle\RequestLocation; |
|||
|
|||
use GuzzleHttp\Command\CommandInterface; |
|||
use GuzzleHttp\Command\Guzzle\Operation; |
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use Psr\Http\Message\MessageInterface; |
|||
use Psr\Http\Message\RequestInterface; |
|||
|
|||
/** |
|||
* Request header location |
|||
*/ |
|||
class HeaderLocation extends AbstractLocation |
|||
{ |
|||
|
|||
/** |
|||
* Set the name of the location |
|||
* |
|||
* @param string $locationName |
|||
*/ |
|||
public function __construct($locationName = 'header') |
|||
{ |
|||
parent::__construct($locationName); |
|||
} |
|||
|
|||
/** |
|||
* @param CommandInterface $command |
|||
* @param RequestInterface $request |
|||
* @param Parameter $param |
|||
* |
|||
* @return MessageInterface |
|||
*/ |
|||
public function visit( |
|||
CommandInterface $command, |
|||
RequestInterface $request, |
|||
Parameter $param |
|||
) { |
|||
$value = $command[$param->getName()]; |
|||
|
|||
return $request->withHeader($param->getWireName(), $param->filter($value)); |
|||
} |
|||
|
|||
/** |
|||
* @param CommandInterface $command |
|||
* @param RequestInterface $request |
|||
* @param Operation $operation |
|||
* |
|||
* @return RequestInterface |
|||
*/ |
|||
public function after( |
|||
CommandInterface $command, |
|||
RequestInterface $request, |
|||
Operation $operation |
|||
) { |
|||
/** @var Parameter $additional */ |
|||
$additional = $operation->getAdditionalParameters(); |
|||
if ($additional && ($additional->getLocation() === $this->locationName)) { |
|||
foreach ($command->toArray() as $key => $value) { |
|||
if (!$operation->hasParam($key)) { |
|||
$request = $request->withHeader($key, $additional->filter($value)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return $request; |
|||
} |
|||
} |
|||
@ -0,0 +1,85 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle\RequestLocation; |
|||
|
|||
use GuzzleHttp\Command\CommandInterface; |
|||
use GuzzleHttp\Command\Guzzle\Operation; |
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Psr7; |
|||
use Psr\Http\Message\MessageInterface; |
|||
use Psr\Http\Message\RequestInterface; |
|||
|
|||
/** |
|||
* Creates a JSON document |
|||
*/ |
|||
class JsonLocation extends AbstractLocation |
|||
{ |
|||
/** @var string Whether or not to add a Content-Type header when JSON is found */ |
|||
private $jsonContentType; |
|||
|
|||
/** @var array */ |
|||
private $jsonData; |
|||
|
|||
/** |
|||
* @param string $locationName Name of the location |
|||
* @param string $contentType Content-Type header to add to the request if |
|||
* JSON is added to the body. Pass an empty string to omit. |
|||
*/ |
|||
public function __construct($locationName = 'json', $contentType = 'application/json') |
|||
{ |
|||
parent::__construct($locationName); |
|||
$this->jsonContentType = $contentType; |
|||
} |
|||
|
|||
/** |
|||
* @param CommandInterface $command |
|||
* @param RequestInterface $request |
|||
* @param Parameter $param |
|||
* |
|||
* @return RequestInterface |
|||
*/ |
|||
public function visit( |
|||
CommandInterface $command, |
|||
RequestInterface $request, |
|||
Parameter $param |
|||
) { |
|||
$this->jsonData[$param->getWireName()] = $this->prepareValue( |
|||
$command[$param->getName()], |
|||
$param |
|||
); |
|||
|
|||
return $request->withBody(Psr7\stream_for(\GuzzleHttp\json_encode($this->jsonData))); |
|||
} |
|||
|
|||
/** |
|||
* @param CommandInterface $command |
|||
* @param RequestInterface $request |
|||
* @param Operation $operation |
|||
* |
|||
* @return MessageInterface |
|||
*/ |
|||
public function after( |
|||
CommandInterface $command, |
|||
RequestInterface $request, |
|||
Operation $operation |
|||
) { |
|||
$data = $this->jsonData; |
|||
$this->jsonData = []; |
|||
|
|||
// Add additional parameters to the JSON document |
|||
$additional = $operation->getAdditionalParameters(); |
|||
if ($additional && ($additional->getLocation() === $this->locationName)) { |
|||
foreach ($command->toArray() as $key => $value) { |
|||
if (!$operation->hasParam($key)) { |
|||
$data[$key] = $this->prepareValue($value, $additional); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Don't overwrite the Content-Type if one is set |
|||
if ($this->jsonContentType && !$request->hasHeader('Content-Type')) { |
|||
$request = $request->withHeader('Content-Type', $this->jsonContentType); |
|||
} |
|||
|
|||
return $request->withBody(Psr7\stream_for(\GuzzleHttp\json_encode($data))); |
|||
} |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle\RequestLocation; |
|||
|
|||
use GuzzleHttp\Command\CommandInterface; |
|||
use GuzzleHttp\Command\Guzzle\Operation; |
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Psr7; |
|||
use Psr\Http\Message\RequestInterface; |
|||
|
|||
/** |
|||
* Adds POST files to a request |
|||
*/ |
|||
class MultiPartLocation extends AbstractLocation |
|||
{ |
|||
/** @var string $contentType */ |
|||
protected $contentType = 'multipart/form-data; boundary='; |
|||
|
|||
/** @var array $formParamsData */ |
|||
protected $multipartData = []; |
|||
|
|||
/** |
|||
* Set the name of the location |
|||
* |
|||
* @param string $locationName |
|||
*/ |
|||
public function __construct($locationName = 'multipart') |
|||
{ |
|||
parent::__construct($locationName); |
|||
} |
|||
|
|||
/** |
|||
* @param CommandInterface $command |
|||
* @param RequestInterface $request |
|||
* @param Parameter $param |
|||
* @return RequestInterface |
|||
*/ |
|||
public function visit( |
|||
CommandInterface $command, |
|||
RequestInterface $request, |
|||
Parameter $param |
|||
) { |
|||
$this->multipartData[] = [ |
|||
'name' => $param->getWireName(), |
|||
'contents' => $this->prepareValue($command[$param->getName()], $param) |
|||
]; |
|||
|
|||
return $request; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* @param CommandInterface $command |
|||
* @param RequestInterface $request |
|||
* @param Operation $operation |
|||
* @return RequestInterface |
|||
*/ |
|||
public function after( |
|||
CommandInterface $command, |
|||
RequestInterface $request, |
|||
Operation $operation |
|||
) { |
|||
$data = $this->multipartData; |
|||
$this->multipartData = []; |
|||
$modify = []; |
|||
|
|||
$body = new Psr7\MultipartStream($data); |
|||
$modify['body'] = Psr7\stream_for($body); |
|||
$request = Psr7\modify_request($request, $modify); |
|||
if ($request->getBody() instanceof Psr7\MultipartStream) { |
|||
// Use a multipart/form-data POST if a Content-Type is not set. |
|||
$request->withHeader('Content-Type', $this->contentType . $request->getBody()->getBoundary()); |
|||
} |
|||
|
|||
return $request; |
|||
} |
|||
} |
|||
@ -0,0 +1,92 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle\RequestLocation; |
|||
|
|||
use GuzzleHttp\Command\CommandInterface; |
|||
use GuzzleHttp\Command\Guzzle\Operation; |
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\Guzzle\QuerySerializer\QuerySerializerInterface; |
|||
use GuzzleHttp\Command\Guzzle\QuerySerializer\Rfc3986Serializer; |
|||
use GuzzleHttp\Psr7; |
|||
use Psr\Http\Message\RequestInterface; |
|||
|
|||
/** |
|||
* Adds query string values to requests |
|||
*/ |
|||
class QueryLocation extends AbstractLocation |
|||
{ |
|||
/** |
|||
* @var QuerySerializerInterface |
|||
*/ |
|||
private $querySerializer; |
|||
|
|||
/** |
|||
* Set the name of the location |
|||
* |
|||
* @param string $locationName |
|||
* @param QuerySerializerInterface|null $querySerializer |
|||
*/ |
|||
public function __construct($locationName = 'query', QuerySerializerInterface $querySerializer = null) |
|||
{ |
|||
parent::__construct($locationName); |
|||
|
|||
$this->querySerializer = $querySerializer ?: new Rfc3986Serializer(); |
|||
} |
|||
|
|||
/** |
|||
* @param CommandInterface $command |
|||
* @param RequestInterface $request |
|||
* @param Parameter $param |
|||
* |
|||
* @return RequestInterface |
|||
*/ |
|||
public function visit( |
|||
CommandInterface $command, |
|||
RequestInterface $request, |
|||
Parameter $param |
|||
) { |
|||
$uri = $request->getUri(); |
|||
$query = Psr7\parse_query($uri->getQuery()); |
|||
|
|||
$query[$param->getWireName()] = $this->prepareValue( |
|||
$command[$param->getName()], |
|||
$param |
|||
); |
|||
|
|||
$uri = $uri->withQuery($this->querySerializer->aggregate($query)); |
|||
|
|||
return $request->withUri($uri); |
|||
} |
|||
|
|||
/** |
|||
* @param CommandInterface $command |
|||
* @param RequestInterface $request |
|||
* @param Operation $operation |
|||
* |
|||
* @return RequestInterface |
|||
*/ |
|||
public function after( |
|||
CommandInterface $command, |
|||
RequestInterface $request, |
|||
Operation $operation |
|||
) { |
|||
$additional = $operation->getAdditionalParameters(); |
|||
if ($additional && $additional->getLocation() == $this->locationName) { |
|||
foreach ($command->toArray() as $key => $value) { |
|||
if (!$operation->hasParam($key)) { |
|||
$uri = $request->getUri(); |
|||
$query = Psr7\parse_query($uri->getQuery()); |
|||
|
|||
$query[$key] = $this->prepareValue( |
|||
$value, |
|||
$additional |
|||
); |
|||
|
|||
$uri = $uri->withQuery($this->querySerializer->aggregate($query)); |
|||
$request = $request->withUri($uri); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return $request; |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
<?php |
|||
|
|||
namespace GuzzleHttp\Command\Guzzle\RequestLocation; |
|||
|
|||
use GuzzleHttp\Command\CommandInterface; |
|||
use GuzzleHttp\Command\Guzzle\Operation; |
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use Psr\Http\Message\RequestInterface; |
|||
|
|||
/** |
|||
* Handles locations specified in a service description |
|||
*/ |
|||
interface RequestLocationInterface |
|||
{ |
|||
/** |
|||
* Visits a location for each top-level parameter |
|||
* |
|||
* @param CommandInterface $command Command being prepared |
|||
* @param RequestInterface $request Request being modified |
|||
* @param Parameter $param Parameter being visited |
|||
* |
|||
* @return RequestInterface Modified request |
|||
*/ |
|||
public function visit( |
|||
CommandInterface $command, |
|||
RequestInterface $request, |
|||
Parameter $param |
|||
); |
|||
|
|||
/** |
|||
* Called when all of the parameters of a command have been visited. |
|||
* |
|||
* @param CommandInterface $command Command being prepared |
|||
* @param RequestInterface $request Request being modified |
|||
* @param Operation $operation Operation being serialized |
|||
* |
|||
* @return RequestInterface Modified request |
|||
*/ |
|||
public function after( |
|||
CommandInterface $command, |
|||
RequestInterface $request, |
|||
Operation $operation |
|||
); |
|||
} |
|||
@ -0,0 +1,328 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle\RequestLocation; |
|||
|
|||
use GuzzleHttp\Command\CommandInterface; |
|||
use GuzzleHttp\Command\Guzzle\Operation; |
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Psr7; |
|||
use Psr\Http\Message\RequestInterface; |
|||
|
|||
/** |
|||
* Creates an XML document |
|||
*/ |
|||
class XmlLocation extends AbstractLocation |
|||
{ |
|||
/** @var \XMLWriter XML writer resource */ |
|||
private $writer; |
|||
|
|||
/** @var string Content-Type header added when XML is found */ |
|||
private $contentType; |
|||
|
|||
/** @var Parameter[] Buffered elements to write */ |
|||
private $buffered = []; |
|||
|
|||
/** |
|||
* @param string $locationName Name of the location |
|||
* @param string $contentType Set to a non-empty string to add a |
|||
* Content-Type header to a request if any XML content is added to the |
|||
* body. Pass an empty string to disable the addition of the header. |
|||
*/ |
|||
public function __construct($locationName = 'xml', $contentType = 'application/xml') |
|||
{ |
|||
parent::__construct($locationName); |
|||
$this->contentType = $contentType; |
|||
} |
|||
|
|||
/** |
|||
* @param CommandInterface $command |
|||
* @param RequestInterface $request |
|||
* @param Parameter $param |
|||
* |
|||
* @return RequestInterface |
|||
*/ |
|||
public function visit( |
|||
CommandInterface $command, |
|||
RequestInterface $request, |
|||
Parameter $param |
|||
) { |
|||
// Buffer and order the parameters to visit based on if they are |
|||
// top-level attributes or child nodes. |
|||
// @link https://github.com/guzzle/guzzle/pull/494 |
|||
if ($param->getData('xmlAttribute')) { |
|||
array_unshift($this->buffered, $param); |
|||
} else { |
|||
$this->buffered[] = $param; |
|||
} |
|||
|
|||
return $request; |
|||
} |
|||
|
|||
/** |
|||
* @param CommandInterface $command |
|||
* @param RequestInterface $request |
|||
* @param Operation $operation |
|||
* |
|||
* @return RequestInterface |
|||
*/ |
|||
public function after( |
|||
CommandInterface $command, |
|||
RequestInterface $request, |
|||
Operation $operation |
|||
) { |
|||
foreach ($this->buffered as $param) { |
|||
$this->visitWithValue( |
|||
$command[$param->getName()], |
|||
$param, |
|||
$operation |
|||
); |
|||
} |
|||
|
|||
$this->buffered = []; |
|||
|
|||
$additional = $operation->getAdditionalParameters(); |
|||
if ($additional && $additional->getLocation() == $this->locationName) { |
|||
foreach ($command->toArray() as $key => $value) { |
|||
if (!$operation->hasParam($key)) { |
|||
$additional->setName($key); |
|||
$this->visitWithValue($value, $additional, $operation); |
|||
} |
|||
} |
|||
$additional->setName(null); |
|||
} |
|||
|
|||
// If data was found that needs to be serialized, then do so |
|||
$xml = ''; |
|||
if ($this->writer) { |
|||
$xml = $this->finishDocument($this->writer); |
|||
} elseif ($operation->getData('xmlAllowEmpty')) { |
|||
// Check if XML should always be sent for the command |
|||
$writer = $this->createRootElement($operation); |
|||
$xml = $this->finishDocument($writer); |
|||
} |
|||
|
|||
if ($xml !== '') { |
|||
$request = $request->withBody(Psr7\stream_for($xml)); |
|||
// Don't overwrite the Content-Type if one is set |
|||
if ($this->contentType && !$request->hasHeader('Content-Type')) { |
|||
$request = $request->withHeader('Content-Type', $this->contentType); |
|||
} |
|||
} |
|||
|
|||
$this->writer = null; |
|||
|
|||
return $request; |
|||
} |
|||
|
|||
/** |
|||
* Create the root XML element to use with a request |
|||
* |
|||
* @param Operation $operation Operation object |
|||
* |
|||
* @return \XMLWriter |
|||
*/ |
|||
protected function createRootElement(Operation $operation) |
|||
{ |
|||
static $defaultRoot = ['name' => 'Request']; |
|||
// If no root element was specified, then just wrap the XML in 'Request' |
|||
$root = $operation->getData('xmlRoot') ?: $defaultRoot; |
|||
// Allow the XML declaration to be customized with xmlEncoding |
|||
$encoding = $operation->getData('xmlEncoding'); |
|||
$writer = $this->startDocument($encoding); |
|||
$writer->startElement($root['name']); |
|||
|
|||
// Create the wrapping element with no namespaces if no namespaces were present |
|||
if (!empty($root['namespaces'])) { |
|||
// Create the wrapping element with an array of one or more namespaces |
|||
foreach ((array) $root['namespaces'] as $prefix => $uri) { |
|||
$nsLabel = 'xmlns'; |
|||
if (!is_numeric($prefix)) { |
|||
$nsLabel .= ':'.$prefix; |
|||
} |
|||
$writer->writeAttribute($nsLabel, $uri); |
|||
} |
|||
} |
|||
|
|||
return $writer; |
|||
} |
|||
|
|||
/** |
|||
* Recursively build the XML body |
|||
* |
|||
* @param \XMLWriter $writer XML to modify |
|||
* @param Parameter $param API Parameter |
|||
* @param mixed $value Value to add |
|||
*/ |
|||
protected function addXml(\XMLWriter $writer, Parameter $param, $value) |
|||
{ |
|||
$value = $param->filter($value); |
|||
$type = $param->getType(); |
|||
$name = $param->getWireName(); |
|||
$prefix = null; |
|||
$namespace = $param->getData('xmlNamespace'); |
|||
if (false !== strpos($name, ':')) { |
|||
list($prefix, $name) = explode(':', $name, 2); |
|||
} |
|||
|
|||
if ($type == 'object' || $type == 'array') { |
|||
if (!$param->getData('xmlFlattened')) { |
|||
if ($namespace) { |
|||
$writer->startElementNS(null, $name, $namespace); |
|||
} else { |
|||
$writer->startElement($name); |
|||
} |
|||
} |
|||
if ($param->getType() == 'array') { |
|||
$this->addXmlArray($writer, $param, $value); |
|||
} elseif ($param->getType() == 'object') { |
|||
$this->addXmlObject($writer, $param, $value); |
|||
} |
|||
if (!$param->getData('xmlFlattened')) { |
|||
$writer->endElement(); |
|||
} |
|||
return; |
|||
} |
|||
if ($param->getData('xmlAttribute')) { |
|||
$this->writeAttribute($writer, $prefix, $name, $namespace, $value); |
|||
} else { |
|||
$this->writeElement($writer, $prefix, $name, $namespace, $value); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Write an attribute with namespace if used |
|||
* |
|||
* @param \XMLWriter $writer XMLWriter instance |
|||
* @param string $prefix Namespace prefix if any |
|||
* @param string $name Attribute name |
|||
* @param string $namespace The uri of the namespace |
|||
* @param string $value The attribute content |
|||
*/ |
|||
protected function writeAttribute($writer, $prefix, $name, $namespace, $value) |
|||
{ |
|||
if ($namespace) { |
|||
$writer->writeAttributeNS($prefix, $name, $namespace, $value); |
|||
} else { |
|||
$writer->writeAttribute($name, $value); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Write an element with namespace if used |
|||
* |
|||
* @param \XMLWriter $writer XML writer resource |
|||
* @param string $prefix Namespace prefix if any |
|||
* @param string $name Element name |
|||
* @param string $namespace The uri of the namespace |
|||
* @param string $value The element content |
|||
*/ |
|||
protected function writeElement(\XMLWriter $writer, $prefix, $name, $namespace, $value) |
|||
{ |
|||
if ($namespace) { |
|||
$writer->startElementNS($prefix, $name, $namespace); |
|||
} else { |
|||
$writer->startElement($name); |
|||
} |
|||
if (strpbrk($value, '<>&')) { |
|||
$writer->writeCData($value); |
|||
} else { |
|||
$writer->writeRaw($value); |
|||
} |
|||
$writer->endElement(); |
|||
} |
|||
|
|||
/** |
|||
* Create a new xml writer and start a document |
|||
* |
|||
* @param string $encoding document encoding |
|||
* |
|||
* @return \XMLWriter the writer resource |
|||
* @throws \RuntimeException if the document cannot be started |
|||
*/ |
|||
protected function startDocument($encoding) |
|||
{ |
|||
$this->writer = new \XMLWriter(); |
|||
if (!$this->writer->openMemory()) { |
|||
throw new \RuntimeException('Unable to open XML document in memory'); |
|||
} |
|||
if (!$this->writer->startDocument('1.0', $encoding)) { |
|||
throw new \RuntimeException('Unable to start XML document'); |
|||
} |
|||
|
|||
return $this->writer; |
|||
} |
|||
|
|||
/** |
|||
* End the document and return the output |
|||
* |
|||
* @param \XMLWriter $writer |
|||
* |
|||
* @return string the writer resource |
|||
*/ |
|||
protected function finishDocument($writer) |
|||
{ |
|||
$writer->endDocument(); |
|||
|
|||
return $writer->outputMemory(); |
|||
} |
|||
|
|||
/** |
|||
* Add an array to the XML |
|||
* |
|||
* @param \XMLWriter $writer |
|||
* @param Parameter $param |
|||
* @param $value |
|||
*/ |
|||
protected function addXmlArray(\XMLWriter $writer, Parameter $param, &$value) |
|||
{ |
|||
if ($items = $param->getItems()) { |
|||
foreach ($value as $v) { |
|||
$this->addXml($writer, $items, $v); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Add an object to the XML |
|||
* |
|||
* @param \XMLWriter $writer |
|||
* @param Parameter $param |
|||
* @param $value |
|||
*/ |
|||
protected function addXmlObject(\XMLWriter $writer, Parameter $param, &$value) |
|||
{ |
|||
$noAttributes = []; |
|||
|
|||
// add values which have attributes |
|||
foreach ($value as $name => $v) { |
|||
if ($property = $param->getProperty($name)) { |
|||
if ($property->getData('xmlAttribute')) { |
|||
$this->addXml($writer, $property, $v); |
|||
} else { |
|||
$noAttributes[] = ['value' => $v, 'property' => $property]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// now add values with no attributes |
|||
foreach ($noAttributes as $element) { |
|||
$this->addXml($writer, $element['property'], $element['value']); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @param $value |
|||
* @param Parameter $param |
|||
* @param Operation $operation |
|||
*/ |
|||
private function visitWithValue( |
|||
$value, |
|||
Parameter $param, |
|||
Operation $operation |
|||
) { |
|||
if (!$this->writer) { |
|||
$this->createRootElement($operation); |
|||
} |
|||
|
|||
$this->addXml($this->writer, $param, $value); |
|||
} |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle\ResponseLocation; |
|||
|
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\ResultInterface; |
|||
use Psr\Http\Message\ResponseInterface; |
|||
|
|||
/** |
|||
* Class AbstractLocation |
|||
* |
|||
* @package GuzzleHttp\Command\Guzzle\ResponseLocation |
|||
*/ |
|||
abstract class AbstractLocation implements ResponseLocationInterface |
|||
{ |
|||
/** @var string $locationName */ |
|||
protected $locationName; |
|||
|
|||
/** |
|||
* Set the name of the location |
|||
* |
|||
* @param $locationName |
|||
*/ |
|||
public function __construct($locationName) |
|||
{ |
|||
$this->locationName = $locationName; |
|||
} |
|||
|
|||
/** |
|||
* @param ResultInterface $result |
|||
* @param ResponseInterface $response |
|||
* @param Parameter $model |
|||
* @return ResultInterface |
|||
*/ |
|||
public function before( |
|||
ResultInterface $result, |
|||
ResponseInterface $response, |
|||
Parameter $model |
|||
) { |
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* @param ResultInterface $result |
|||
* @param ResponseInterface $response |
|||
* @param Parameter $model |
|||
* @return ResultInterface |
|||
*/ |
|||
public function after( |
|||
ResultInterface $result, |
|||
ResponseInterface $response, |
|||
Parameter $model |
|||
) { |
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* @param ResultInterface $result |
|||
* @param ResponseInterface $response |
|||
* @param Parameter $param |
|||
* @return ResultInterface |
|||
*/ |
|||
public function visit( |
|||
ResultInterface $result, |
|||
ResponseInterface $response, |
|||
Parameter $param |
|||
) { |
|||
return $result; |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle\ResponseLocation; |
|||
|
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\ResultInterface; |
|||
use Psr\Http\Message\ResponseInterface; |
|||
|
|||
/** |
|||
* Extracts the body of a response into a result field |
|||
*/ |
|||
class BodyLocation extends AbstractLocation |
|||
{ |
|||
|
|||
/** |
|||
* Set the name of the location |
|||
* |
|||
* @param string $locationName |
|||
*/ |
|||
public function __construct($locationName = 'body') |
|||
{ |
|||
parent::__construct($locationName); |
|||
} |
|||
|
|||
/** |
|||
* @param ResultInterface $result |
|||
* @param ResponseInterface $response |
|||
* @param Parameter $param |
|||
* @return ResultInterface |
|||
*/ |
|||
public function visit( |
|||
ResultInterface $result, |
|||
ResponseInterface $response, |
|||
Parameter $param |
|||
) { |
|||
$result[$param->getName()] = $param->filter($response->getBody()); |
|||
|
|||
return $result; |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle\ResponseLocation; |
|||
|
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\ResultInterface; |
|||
use Psr\Http\Message\ResponseInterface; |
|||
|
|||
/** |
|||
* Extracts headers from the response into a result fields |
|||
*/ |
|||
class HeaderLocation extends AbstractLocation |
|||
{ |
|||
|
|||
/** |
|||
* Set the name of the location |
|||
* |
|||
* @param string $locationName |
|||
*/ |
|||
public function __construct($locationName = 'header') |
|||
{ |
|||
parent::__construct($locationName); |
|||
} |
|||
|
|||
/** |
|||
* @param ResultInterface $result |
|||
* @param ResponseInterface $response |
|||
* @param Parameter $param |
|||
* |
|||
* @return ResultInterface |
|||
*/ |
|||
public function visit( |
|||
ResultInterface $result, |
|||
ResponseInterface $response, |
|||
Parameter $param |
|||
) { |
|||
// Retrieving a single header by name |
|||
$name = $param->getName(); |
|||
if ($header = $response->getHeader($param->getWireName())) { |
|||
if (is_array($header)) { |
|||
$header = array_shift($header); |
|||
} |
|||
$result[$name] = $param->filter($header); |
|||
} |
|||
|
|||
return $result; |
|||
} |
|||
} |
|||
@ -0,0 +1,176 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle\ResponseLocation; |
|||
|
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\Result; |
|||
use GuzzleHttp\Command\ResultInterface; |
|||
use Psr\Http\Message\ResponseInterface; |
|||
|
|||
/** |
|||
* Extracts elements from a JSON document. |
|||
*/ |
|||
class JsonLocation extends AbstractLocation |
|||
{ |
|||
/** @var array The JSON document being visited */ |
|||
private $json = []; |
|||
|
|||
/** |
|||
* Set the name of the location |
|||
* |
|||
* @param string $locationName |
|||
*/ |
|||
public function __construct($locationName = 'json') |
|||
{ |
|||
parent::__construct($locationName); |
|||
} |
|||
|
|||
/** |
|||
* @param \GuzzleHttp\Command\ResultInterface $result |
|||
* @param \Psr\Http\Message\ResponseInterface $response |
|||
* @param \GuzzleHttp\Command\Guzzle\Parameter $model |
|||
* |
|||
* @return \GuzzleHttp\Command\ResultInterface |
|||
*/ |
|||
public function before( |
|||
ResultInterface $result, |
|||
ResponseInterface $response, |
|||
Parameter $model |
|||
) { |
|||
$body = (string) $response->getBody(); |
|||
$body = $body ?: "{}"; |
|||
$this->json = \GuzzleHttp\json_decode($body, true); |
|||
// relocate named arrays, so that they have the same structure as |
|||
// arrays nested in objects and visit can work on them in the same way |
|||
if ($model->getType() === 'array' && ($name = $model->getName())) { |
|||
$this->json = [$name => $this->json]; |
|||
} |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* @param ResultInterface $result |
|||
* @param ResponseInterface $response |
|||
* @param Parameter $model |
|||
* @return ResultInterface |
|||
*/ |
|||
public function after( |
|||
ResultInterface $result, |
|||
ResponseInterface $response, |
|||
Parameter $model |
|||
) { |
|||
// Handle additional, undefined properties |
|||
$additional = $model->getAdditionalProperties(); |
|||
if (!($additional instanceof Parameter)) { |
|||
return $result; |
|||
} |
|||
|
|||
// Use the model location as the default if one is not set on additional |
|||
$addLocation = $additional->getLocation() ?: $model->getLocation(); |
|||
if ($addLocation == $this->locationName) { |
|||
foreach ($this->json as $prop => $val) { |
|||
if (!isset($result[$prop])) { |
|||
// Only recurse if there is a type specified |
|||
$result[$prop] = $additional->getType() |
|||
? $this->recurse($additional, $val) |
|||
: $val; |
|||
} |
|||
} |
|||
} |
|||
|
|||
$this->json = []; |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* @param ResultInterface $result |
|||
* @param ResponseInterface $response |
|||
* @param Parameter $param |
|||
* @return Result|ResultInterface |
|||
*/ |
|||
public function visit( |
|||
ResultInterface $result, |
|||
ResponseInterface $response, |
|||
Parameter $param |
|||
) { |
|||
$name = $param->getName(); |
|||
$key = $param->getWireName(); |
|||
|
|||
// Check if the result should be treated as a list |
|||
if ($param->getType() == 'array') { |
|||
// Treat as javascript array |
|||
if ($name) { |
|||
// name provided, store it under a key in the array |
|||
$subArray = isset($this->json[$key]) ? $this->json[$key] : null; |
|||
$result[$name] = $this->recurse($param, $subArray); |
|||
} else { |
|||
// top-level `array` or an empty name |
|||
$result = new Result(array_merge( |
|||
$result->toArray(), |
|||
$this->recurse($param, $this->json) |
|||
)); |
|||
} |
|||
} elseif (isset($this->json[$key])) { |
|||
$result[$name] = $this->recurse($param, $this->json[$key]); |
|||
} |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* Recursively process a parameter while applying filters |
|||
* |
|||
* @param Parameter $param API parameter being validated |
|||
* @param mixed $value Value to process. |
|||
* @return mixed|null |
|||
*/ |
|||
private function recurse(Parameter $param, $value) |
|||
{ |
|||
if (!is_array($value)) { |
|||
return $param->filter($value); |
|||
} |
|||
|
|||
$result = []; |
|||
$type = $param->getType(); |
|||
|
|||
if ($type == 'array') { |
|||
$items = $param->getItems(); |
|||
foreach ($value as $val) { |
|||
$result[] = $this->recurse($items, $val); |
|||
} |
|||
} elseif ($type == 'object' && !isset($value[0])) { |
|||
// On the above line, we ensure that the array is associative and |
|||
// not numerically indexed |
|||
if ($properties = $param->getProperties()) { |
|||
foreach ($properties as $property) { |
|||
$key = $property->getWireName(); |
|||
if (array_key_exists($key, $value)) { |
|||
$result[$property->getName()] = $this->recurse( |
|||
$property, |
|||
$value[$key] |
|||
); |
|||
// Remove from the value so that AP can later be handled |
|||
unset($value[$key]); |
|||
} |
|||
} |
|||
} |
|||
// Only check additional properties if everything wasn't already |
|||
// handled |
|||
if ($value) { |
|||
$additional = $param->getAdditionalProperties(); |
|||
if ($additional === null || $additional === true) { |
|||
// Merge the JSON under the resulting array |
|||
$result += $value; |
|||
} elseif ($additional instanceof Parameter) { |
|||
// Process all child elements according to the given schema |
|||
foreach ($value as $prop => $val) { |
|||
$result[$prop] = $this->recurse($additional, $val); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
return $param->filter($result); |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle\ResponseLocation; |
|||
|
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\ResultInterface; |
|||
use Psr\Http\Message\ResponseInterface; |
|||
|
|||
/** |
|||
* Extracts the reason phrase of a response into a result field |
|||
*/ |
|||
class ReasonPhraseLocation extends AbstractLocation |
|||
{ |
|||
|
|||
/** |
|||
* Set the name of the location |
|||
* |
|||
* @param string $locationName |
|||
*/ |
|||
public function __construct($locationName = 'reasonPhrase') |
|||
{ |
|||
parent::__construct($locationName); |
|||
} |
|||
|
|||
/** |
|||
* @param ResultInterface $result |
|||
* @param ResponseInterface $response |
|||
* @param Parameter $param |
|||
* @return ResultInterface |
|||
*/ |
|||
public function visit( |
|||
ResultInterface $result, |
|||
ResponseInterface $response, |
|||
Parameter $param |
|||
) { |
|||
$result[$param->getName()] = $param->filter( |
|||
$response->getReasonPhrase() |
|||
); |
|||
|
|||
return $result; |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle\ResponseLocation; |
|||
|
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\ResultInterface; |
|||
use Psr\Http\Message\ResponseInterface; |
|||
|
|||
/** |
|||
* Location visitor used to parse values out of a response into an associative |
|||
* array |
|||
*/ |
|||
interface ResponseLocationInterface |
|||
{ |
|||
/** |
|||
* Called before visiting all parameters. This can be used for seeding the |
|||
* result of a command with default data (e.g. populating with JSON data in |
|||
* the response then adding to the parsed data). |
|||
* |
|||
* @param ResultInterface $result Result being created |
|||
* @param ResponseInterface $response Response being visited |
|||
* @param Parameter $model Response model |
|||
* |
|||
* @return ResultInterface Modified result |
|||
*/ |
|||
public function before( |
|||
ResultInterface $result, |
|||
ResponseInterface $response, |
|||
Parameter $model |
|||
); |
|||
|
|||
/** |
|||
* Called after visiting all parameters |
|||
* |
|||
* @param ResultInterface $result Result being created |
|||
* @param ResponseInterface $response Response being visited |
|||
* @param Parameter $model Response model |
|||
* |
|||
* @return ResultInterface Modified result |
|||
*/ |
|||
public function after( |
|||
ResultInterface $result, |
|||
ResponseInterface $response, |
|||
Parameter $model |
|||
); |
|||
|
|||
/** |
|||
* Called once for each parameter being visited that matches the location |
|||
* type. |
|||
* |
|||
* @param ResultInterface $result Result being created |
|||
* @param ResponseInterface $response Response being visited |
|||
* @param Parameter $param Parameter being visited |
|||
* |
|||
* @return ResultInterface Modified result |
|||
*/ |
|||
public function visit( |
|||
ResultInterface $result, |
|||
ResponseInterface $response, |
|||
Parameter $param |
|||
); |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle\ResponseLocation; |
|||
|
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\ResultInterface; |
|||
use Psr\Http\Message\ResponseInterface; |
|||
|
|||
/** |
|||
* Extracts the status code of a response into a result field |
|||
*/ |
|||
class StatusCodeLocation extends AbstractLocation |
|||
{ |
|||
|
|||
/** |
|||
* Set the name of the location |
|||
* |
|||
* @param string $locationName |
|||
*/ |
|||
public function __construct($locationName = 'statusCode') |
|||
{ |
|||
parent::__construct($locationName); |
|||
} |
|||
|
|||
/** |
|||
* @param ResultInterface $result |
|||
* @param ResponseInterface $response |
|||
* @param Parameter $param |
|||
* @return ResultInterface |
|||
*/ |
|||
public function visit( |
|||
ResultInterface $result, |
|||
ResponseInterface $response, |
|||
Parameter $param |
|||
) { |
|||
$result[$param->getName()] = $param->filter($response->getStatusCode()); |
|||
|
|||
return $result; |
|||
} |
|||
} |
|||
@ -0,0 +1,311 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle\ResponseLocation; |
|||
|
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\Result; |
|||
use GuzzleHttp\Command\ResultInterface; |
|||
use Psr\Http\Message\ResponseInterface; |
|||
|
|||
/** |
|||
* Extracts elements from an XML document |
|||
*/ |
|||
class XmlLocation extends AbstractLocation |
|||
{ |
|||
/** @var \SimpleXMLElement XML document being visited */ |
|||
private $xml; |
|||
|
|||
/** |
|||
* Set the name of the location |
|||
* |
|||
* @param string $locationName |
|||
*/ |
|||
public function __construct($locationName = 'xml') |
|||
{ |
|||
parent::__construct($locationName); |
|||
} |
|||
|
|||
/** |
|||
* @param ResultInterface $result |
|||
* @param ResponseInterface $response |
|||
* @param Parameter $model |
|||
* @return ResultInterface |
|||
*/ |
|||
public function before( |
|||
ResultInterface $result, |
|||
ResponseInterface $response, |
|||
Parameter $model |
|||
) { |
|||
$this->xml = simplexml_load_string((string) $response->getBody()); |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* @param ResultInterface $result |
|||
* @param ResponseInterface $response |
|||
* @param Parameter $model |
|||
* @return Result|ResultInterface |
|||
*/ |
|||
public function after( |
|||
ResultInterface $result, |
|||
ResponseInterface $response, |
|||
Parameter $model |
|||
) { |
|||
// Handle additional, undefined properties |
|||
$additional = $model->getAdditionalProperties(); |
|||
if ($additional instanceof Parameter && |
|||
$additional->getLocation() == $this->locationName |
|||
) { |
|||
$result = new Result(array_merge( |
|||
$result->toArray(), |
|||
self::xmlToArray($this->xml) |
|||
)); |
|||
} |
|||
|
|||
$this->xml = null; |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* @param ResultInterface $result |
|||
* @param ResponseInterface $response |
|||
* @param Parameter $param |
|||
* @return ResultInterface |
|||
*/ |
|||
public function visit( |
|||
ResultInterface $result, |
|||
ResponseInterface $response, |
|||
Parameter $param |
|||
) { |
|||
$sentAs = $param->getWireName(); |
|||
$ns = null; |
|||
if (strstr($sentAs, ':')) { |
|||
list($ns, $sentAs) = explode(':', $sentAs); |
|||
} |
|||
|
|||
// Process the primary property |
|||
if (count($this->xml->children($ns, true)->{$sentAs})) { |
|||
$result[$param->getName()] = $this->recursiveProcess( |
|||
$param, |
|||
$this->xml->children($ns, true)->{$sentAs} |
|||
); |
|||
} |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* Recursively process a parameter while applying filters |
|||
* |
|||
* @param Parameter $param API parameter being processed |
|||
* @param \SimpleXMLElement $node Node being processed |
|||
* @return array |
|||
*/ |
|||
private function recursiveProcess( |
|||
Parameter $param, |
|||
\SimpleXMLElement $node |
|||
) { |
|||
$result = []; |
|||
$type = $param->getType(); |
|||
|
|||
if ($type == 'object') { |
|||
$result = $this->processObject($param, $node); |
|||
} elseif ($type == 'array') { |
|||
$result = $this->processArray($param, $node); |
|||
} else { |
|||
// We are probably handling a flat data node (i.e. string or |
|||
// integer), so let's check if it's childless, which indicates a |
|||
// node containing plain text. |
|||
if ($node->children()->count() == 0) { |
|||
// Retrieve text from node |
|||
$result = (string) $node; |
|||
} |
|||
} |
|||
|
|||
// Filter out the value |
|||
if (isset($result)) { |
|||
$result = $param->filter($result); |
|||
} |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* @param Parameter $param |
|||
* @param \SimpleXMLElement $node |
|||
* @return array |
|||
*/ |
|||
private function processArray(Parameter $param, \SimpleXMLElement $node) |
|||
{ |
|||
// Cast to an array if the value was a string, but should be an array |
|||
$items = $param->getItems(); |
|||
$sentAs = $items->getWireName(); |
|||
$result = []; |
|||
$ns = null; |
|||
|
|||
if (strstr($sentAs, ':')) { |
|||
// Get namespace from the wire name |
|||
list($ns, $sentAs) = explode(':', $sentAs); |
|||
} else { |
|||
// Get namespace from data |
|||
$ns = $items->getData('xmlNs'); |
|||
} |
|||
|
|||
if ($sentAs === null) { |
|||
// A general collection of nodes |
|||
foreach ($node as $child) { |
|||
$result[] = $this->recursiveProcess($items, $child); |
|||
} |
|||
} else { |
|||
// A collection of named, repeating nodes |
|||
// (i.e. <collection><foo></foo><foo></foo></collection>) |
|||
$children = $node->children($ns, true)->{$sentAs}; |
|||
foreach ($children as $child) { |
|||
$result[] = $this->recursiveProcess($items, $child); |
|||
} |
|||
} |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* Process an object |
|||
* |
|||
* @param Parameter $param API parameter being parsed |
|||
* @param \SimpleXMLElement $node Value to process |
|||
* @return array |
|||
*/ |
|||
private function processObject(Parameter $param, \SimpleXMLElement $node) |
|||
{ |
|||
$result = $knownProps = $knownAttributes = []; |
|||
|
|||
// Handle known properties |
|||
if ($properties = $param->getProperties()) { |
|||
foreach ($properties as $property) { |
|||
$name = $property->getName(); |
|||
$sentAs = $property->getWireName(); |
|||
$knownProps[$sentAs] = 1; |
|||
if (strpos($sentAs, ':')) { |
|||
list($ns, $sentAs) = explode(':', $sentAs); |
|||
} else { |
|||
$ns = $property->getData('xmlNs'); |
|||
} |
|||
|
|||
if ($property->getData('xmlAttribute')) { |
|||
// Handle XML attributes |
|||
$result[$name] = (string) $node->attributes($ns, true)->{$sentAs}; |
|||
$knownAttributes[$sentAs] = 1; |
|||
} elseif (count($node->children($ns, true)->{$sentAs})) { |
|||
// Found a child node matching wire name |
|||
$childNode = $node->children($ns, true)->{$sentAs}; |
|||
$result[$name] = $this->recursiveProcess( |
|||
$property, |
|||
$childNode |
|||
); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Handle additional, undefined properties |
|||
$additional = $param->getAdditionalProperties(); |
|||
if ($additional instanceof Parameter) { |
|||
// Process all child elements according to the given schema |
|||
foreach ($node->children($additional->getData('xmlNs'), true) as $childNode) { |
|||
$sentAs = $childNode->getName(); |
|||
if (!isset($knownProps[$sentAs])) { |
|||
$result[$sentAs] = $this->recursiveProcess( |
|||
$additional, |
|||
$childNode |
|||
); |
|||
} |
|||
} |
|||
} elseif ($additional === null || $additional === true) { |
|||
// Blindly transform the XML into an array preserving as much data |
|||
// as possible. Remove processed, aliased properties. |
|||
$array = array_diff_key(self::xmlToArray($node), $knownProps); |
|||
// Remove @attributes that were explicitly plucked from the |
|||
// attributes list. |
|||
if (isset($array['@attributes']) && $knownAttributes) { |
|||
$array['@attributes'] = array_diff_key($array['@attributes'], $knownProps); |
|||
if (!$array['@attributes']) { |
|||
unset($array['@attributes']); |
|||
} |
|||
} |
|||
|
|||
// Merge it together with the original result |
|||
$result = array_merge($array, $result); |
|||
} |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* Convert an XML document to an array. |
|||
* |
|||
* @param \SimpleXMLElement $xml |
|||
* @param int $nesting |
|||
* @param null $ns |
|||
* |
|||
* @return array |
|||
*/ |
|||
private static function xmlToArray( |
|||
\SimpleXMLElement $xml, |
|||
$ns = null, |
|||
$nesting = 0 |
|||
) { |
|||
$result = []; |
|||
$children = $xml->children($ns, true); |
|||
|
|||
foreach ($children as $name => $child) { |
|||
$attributes = (array) $child->attributes($ns, true); |
|||
if (!isset($result[$name])) { |
|||
$childArray = self::xmlToArray($child, $ns, $nesting + 1); |
|||
$result[$name] = $attributes |
|||
? array_merge($attributes, $childArray) |
|||
: $childArray; |
|||
continue; |
|||
} |
|||
// A child element with this name exists so we're assuming |
|||
// that the node contains a list of elements |
|||
if (!is_array($result[$name])) { |
|||
$result[$name] = [$result[$name]]; |
|||
} elseif (!isset($result[$name][0])) { |
|||
// Convert the first child into the first element of a numerically indexed array |
|||
$firstResult = $result[$name]; |
|||
$result[$name] = []; |
|||
$result[$name][] = $firstResult; |
|||
} |
|||
$childArray = self::xmlToArray($child, $ns, $nesting + 1); |
|||
if ($attributes) { |
|||
$result[$name][] = array_merge($attributes, $childArray); |
|||
} else { |
|||
$result[$name][] = $childArray; |
|||
} |
|||
} |
|||
|
|||
// Extract text from node |
|||
$text = trim((string) $xml); |
|||
if ($text === '') { |
|||
$text = null; |
|||
} |
|||
|
|||
// Process attributes |
|||
$attributes = (array) $xml->attributes($ns, true); |
|||
if ($attributes) { |
|||
if ($text !== null) { |
|||
$result['value'] = $text; |
|||
} |
|||
$result = array_merge($attributes, $result); |
|||
} elseif ($text !== null) { |
|||
$result = $text; |
|||
} |
|||
|
|||
// Make sure we're always returning an array |
|||
if ($nesting == 0 && !is_array($result)) { |
|||
$result = [$result]; |
|||
} |
|||
|
|||
return $result; |
|||
} |
|||
} |
|||
@ -0,0 +1,141 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle; |
|||
|
|||
/** |
|||
* JSON Schema formatter class |
|||
*/ |
|||
class SchemaFormatter |
|||
{ |
|||
/** |
|||
* Format a value by a registered format name |
|||
* |
|||
* @param string $format Registered format used to format the value |
|||
* @param mixed $value Value being formatted |
|||
* |
|||
* @return mixed |
|||
*/ |
|||
public function format($format, $value) |
|||
{ |
|||
switch ($format) { |
|||
case 'date-time': |
|||
return $this->formatDateTime($value); |
|||
case 'date-time-http': |
|||
return $this->formatDateTimeHttp($value); |
|||
case 'date': |
|||
return $this->formatDate($value); |
|||
case 'time': |
|||
return $this->formatTime($value); |
|||
case 'timestamp': |
|||
return $this->formatTimestamp($value); |
|||
case 'boolean-string': |
|||
return $this->formatBooleanAsString($value); |
|||
default: |
|||
return $value; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Perform the actual DateTime formatting |
|||
* |
|||
* @param int|string|\DateTime $dateTime Date time value |
|||
* @param string $format Format of the result |
|||
* |
|||
* @return string |
|||
* @throws \InvalidArgumentException |
|||
*/ |
|||
protected function dateFormatter($dateTime, $format) |
|||
{ |
|||
if (is_numeric($dateTime)) { |
|||
return gmdate($format, (int) $dateTime); |
|||
} |
|||
|
|||
if (is_string($dateTime)) { |
|||
$dateTime = new \DateTime($dateTime); |
|||
} |
|||
|
|||
if ($dateTime instanceof \DateTimeInterface) { |
|||
static $utc; |
|||
if (!$utc) { |
|||
$utc = new \DateTimeZone('UTC'); |
|||
} |
|||
return $dateTime->setTimezone($utc)->format($format); |
|||
} |
|||
|
|||
throw new \InvalidArgumentException('Date/Time values must be either ' |
|||
. 'be a string, integer, or DateTime object'); |
|||
} |
|||
|
|||
/** |
|||
* Create a ISO 8601 (YYYY-MM-DDThh:mm:ssZ) formatted date time value in |
|||
* UTC time. |
|||
* |
|||
* @param string|integer|\DateTime $value Date time value |
|||
* |
|||
* @return string |
|||
*/ |
|||
private function formatDateTime($value) |
|||
{ |
|||
return $this->dateFormatter($value, 'Y-m-d\TH:i:s\Z'); |
|||
} |
|||
|
|||
/** |
|||
* Create an HTTP date (RFC 1123 / RFC 822) formatted UTC date-time string |
|||
* |
|||
* @param string|integer|\DateTime $value Date time value |
|||
* |
|||
* @return string |
|||
*/ |
|||
private function formatDateTimeHttp($value) |
|||
{ |
|||
return $this->dateFormatter($value, 'D, d M Y H:i:s \G\M\T'); |
|||
} |
|||
|
|||
/** |
|||
* Create a YYYY-MM-DD formatted string |
|||
* |
|||
* @param string|integer|\DateTime $value Date time value |
|||
* |
|||
* @return string |
|||
*/ |
|||
private function formatDate($value) |
|||
{ |
|||
return $this->dateFormatter($value, 'Y-m-d'); |
|||
} |
|||
|
|||
/** |
|||
* Create a hh:mm:ss formatted string |
|||
* |
|||
* @param string|integer|\DateTime $value Date time value |
|||
* |
|||
* @return string |
|||
*/ |
|||
private function formatTime($value) |
|||
{ |
|||
return $this->dateFormatter($value, 'H:i:s'); |
|||
} |
|||
|
|||
/** |
|||
* Formats a boolean value as a string |
|||
* |
|||
* @param string|integer|bool $value Value to convert to a boolean |
|||
* 'true' / 'false' value |
|||
* |
|||
* @return string |
|||
*/ |
|||
private function formatBooleanAsString($value) |
|||
{ |
|||
return filter_var($value, FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false'; |
|||
} |
|||
|
|||
/** |
|||
* Return a UNIX timestamp in the UTC timezone |
|||
* |
|||
* @param string|integer|\DateTime $value Time value |
|||
* |
|||
* @return int |
|||
*/ |
|||
private function formatTimestamp($value) |
|||
{ |
|||
return (int) $this->dateFormatter($value, 'U'); |
|||
} |
|||
} |
|||
@ -0,0 +1,297 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle; |
|||
|
|||
use GuzzleHttp\Command\ToArrayInterface; |
|||
|
|||
/** |
|||
* Default parameter validator |
|||
*/ |
|||
class SchemaValidator |
|||
{ |
|||
/** |
|||
* Whether or not integers are converted to strings when an integer is |
|||
* received for a string input |
|||
* |
|||
* @var bool |
|||
*/ |
|||
protected $castIntegerToStringType; |
|||
|
|||
/** @var array Errors encountered while validating */ |
|||
protected $errors; |
|||
|
|||
/** |
|||
* @param bool $castIntegerToStringType Set to true to convert integers |
|||
* into strings when a required type is a string and the input value is |
|||
* an integer. Defaults to true. |
|||
*/ |
|||
public function __construct($castIntegerToStringType = true) |
|||
{ |
|||
$this->castIntegerToStringType = $castIntegerToStringType; |
|||
} |
|||
|
|||
/** |
|||
* @param Parameter $param |
|||
* @param $value |
|||
* @return bool |
|||
*/ |
|||
public function validate(Parameter $param, &$value) |
|||
{ |
|||
$this->errors = []; |
|||
$this->recursiveProcess($param, $value); |
|||
|
|||
if (empty($this->errors)) { |
|||
return true; |
|||
} else { |
|||
sort($this->errors); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Get the errors encountered while validating |
|||
* |
|||
* @return array |
|||
*/ |
|||
public function getErrors() |
|||
{ |
|||
return $this->errors ?: []; |
|||
} |
|||
|
|||
/** |
|||
* From the allowable types, determine the type that the variable matches |
|||
* |
|||
* @param string|array $type Parameter type |
|||
* @param mixed $value Value to determine the type |
|||
* |
|||
* @return string|false Returns the matching type on |
|||
*/ |
|||
protected function determineType($type, $value) |
|||
{ |
|||
foreach ((array) $type as $t) { |
|||
if ($t == 'string' |
|||
&& (is_string($value) || (is_object($value) && method_exists($value, '__toString'))) |
|||
) { |
|||
return 'string'; |
|||
} elseif ($t == 'object' && (is_array($value) || is_object($value))) { |
|||
return 'object'; |
|||
} elseif ($t == 'array' && is_array($value)) { |
|||
return 'array'; |
|||
} elseif ($t == 'integer' && is_integer($value)) { |
|||
return 'integer'; |
|||
} elseif ($t == 'boolean' && is_bool($value)) { |
|||
return 'boolean'; |
|||
} elseif ($t == 'number' && is_numeric($value)) { |
|||
return 'number'; |
|||
} elseif ($t == 'numeric' && is_numeric($value)) { |
|||
return 'numeric'; |
|||
} elseif ($t == 'null' && !$value) { |
|||
return 'null'; |
|||
} elseif ($t == 'any') { |
|||
return 'any'; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* Recursively validate a parameter |
|||
* |
|||
* @param Parameter $param API parameter being validated |
|||
* @param mixed $value Value to validate and validate. The value may |
|||
* change during this validate. |
|||
* @param string $path Current validation path (used for error reporting) |
|||
* @param int $depth Current depth in the validation validate |
|||
* |
|||
* @return bool Returns true if valid, or false if invalid |
|||
*/ |
|||
protected function recursiveProcess( |
|||
Parameter $param, |
|||
&$value, |
|||
$path = '', |
|||
$depth = 0 |
|||
) { |
|||
// Update the value by adding default or static values |
|||
$value = $param->getValue($value); |
|||
|
|||
$required = $param->isRequired(); |
|||
// if the value is null and the parameter is not required or is static, |
|||
// then skip any further recursion |
|||
if ((null === $value && !$required) || $param->isStatic()) { |
|||
return true; |
|||
} |
|||
|
|||
$type = $param->getType(); |
|||
// Attempt to limit the number of times is_array is called by tracking |
|||
// if the value is an array |
|||
$valueIsArray = is_array($value); |
|||
// If a name is set then update the path so that validation messages |
|||
// are more helpful |
|||
if ($name = $param->getName()) { |
|||
$path .= "[{$name}]"; |
|||
} |
|||
|
|||
if ($type == 'object') { |
|||
// Determine whether or not this "value" has properties and should |
|||
// be traversed |
|||
$traverse = $temporaryValue = false; |
|||
|
|||
// Convert the value to an array |
|||
if (!$valueIsArray && $value instanceof ToArrayInterface) { |
|||
$value = $value->toArray(); |
|||
} |
|||
|
|||
if ($valueIsArray) { |
|||
// Ensure that the array is associative and not numerically |
|||
// indexed |
|||
if (isset($value[0])) { |
|||
$this->errors[] = "{$path} must be an array of properties. Got a numerically indexed array."; |
|||
return false; |
|||
} |
|||
$traverse = true; |
|||
} elseif ($value === null) { |
|||
// Attempt to let the contents be built up by default values if |
|||
// possible |
|||
$value = []; |
|||
$temporaryValue = $valueIsArray = $traverse = true; |
|||
} |
|||
|
|||
if ($traverse) { |
|||
if ($properties = $param->getProperties()) { |
|||
// if properties were found, validate each property |
|||
foreach ($properties as $property) { |
|||
$name = $property->getName(); |
|||
if (isset($value[$name])) { |
|||
$this->recursiveProcess($property, $value[$name], $path, $depth + 1); |
|||
} else { |
|||
$current = null; |
|||
$this->recursiveProcess($property, $current, $path, $depth + 1); |
|||
// Only set the value if it was populated |
|||
if (null !== $current) { |
|||
$value[$name] = $current; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
$additional = $param->getAdditionalProperties(); |
|||
if ($additional !== true) { |
|||
// If additional properties were found, then validate each |
|||
// against the additionalProperties attr. |
|||
$keys = array_keys($value); |
|||
// Determine the keys that were specified that were not |
|||
// listed in the properties of the schema |
|||
$diff = array_diff($keys, array_keys($properties)); |
|||
if (!empty($diff)) { |
|||
// Determine which keys are not in the properties |
|||
if ($additional instanceof Parameter) { |
|||
foreach ($diff as $key) { |
|||
$this->recursiveProcess($additional, $value[$key], "{$path}[{$key}]", $depth); |
|||
} |
|||
} else { |
|||
// if additionalProperties is set to false and there |
|||
// are additionalProperties in the values, then fail |
|||
foreach ($diff as $prop) { |
|||
$this->errors[] = sprintf('%s[%s] is not an allowed property', $path, $prop); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// A temporary value will be used to traverse elements that |
|||
// have no corresponding input value. This allows nested |
|||
// required parameters with default values to bubble up into the |
|||
// input. Here we check if we used a temp value and nothing |
|||
// bubbled up, then we need to remote the value. |
|||
if ($temporaryValue && empty($value)) { |
|||
$value = null; |
|||
$valueIsArray = false; |
|||
} |
|||
} |
|||
|
|||
} elseif ($type == 'array' && $valueIsArray && $param->getItems()) { |
|||
foreach ($value as $i => &$item) { |
|||
// Validate each item in an array against the items attribute of the schema |
|||
$this->recursiveProcess($param->getItems(), $item, $path . "[{$i}]", $depth + 1); |
|||
} |
|||
} |
|||
|
|||
// If the value is required and the type is not null, then there is an |
|||
// error if the value is not set |
|||
if ($required && $value === null && $type != 'null') { |
|||
$message = "{$path} is " . ($param->getType() |
|||
? ('a required ' . implode(' or ', (array) $param->getType())) |
|||
: 'required'); |
|||
if ($param->has('description')) { |
|||
$message .= ': ' . $param->getDescription(); |
|||
} |
|||
$this->errors[] = $message; |
|||
return false; |
|||
} |
|||
|
|||
// Validate that the type is correct. If the type is string but an |
|||
// integer was passed, the class can be instructed to cast the integer |
|||
// to a string to pass validation. This is the default behavior. |
|||
if ($type && (!$type = $this->determineType($type, $value))) { |
|||
if ($this->castIntegerToStringType |
|||
&& $param->getType() == 'string' |
|||
&& is_integer($value) |
|||
) { |
|||
$value = (string) $value; |
|||
} else { |
|||
$this->errors[] = "{$path} must be of type " . implode(' or ', (array) $param->getType()); |
|||
} |
|||
} |
|||
|
|||
// Perform type specific validation for strings, arrays, and integers |
|||
if ($type == 'string') { |
|||
// Strings can have enums which are a list of predefined values |
|||
if (($enum = $param->getEnum()) && !in_array($value, $enum)) { |
|||
$this->errors[] = "{$path} must be one of " . implode(' or ', array_map(function ($s) { |
|||
return '"' . addslashes($s) . '"'; |
|||
}, $enum)); |
|||
} |
|||
// Strings can have a regex pattern that the value must match |
|||
if (($pattern = $param->getPattern()) && !preg_match($pattern, $value)) { |
|||
$this->errors[] = "{$path} must match the following regular expression: {$pattern}"; |
|||
} |
|||
|
|||
$strLen = null; |
|||
if ($min = $param->getMinLength()) { |
|||
$strLen = strlen($value); |
|||
if ($strLen < $min) { |
|||
$this->errors[] = "{$path} length must be greater than or equal to {$min}"; |
|||
} |
|||
} |
|||
if ($max = $param->getMaxLength()) { |
|||
if (($strLen ?: strlen($value)) > $max) { |
|||
$this->errors[] = "{$path} length must be less than or equal to {$max}"; |
|||
} |
|||
} |
|||
|
|||
} elseif ($type == 'array') { |
|||
$size = null; |
|||
if ($min = $param->getMinItems()) { |
|||
$size = count($value); |
|||
if ($size < $min) { |
|||
$this->errors[] = "{$path} must contain {$min} or more elements"; |
|||
} |
|||
} |
|||
if ($max = $param->getMaxItems()) { |
|||
if (($size ?: count($value)) > $max) { |
|||
$this->errors[] = "{$path} must contain {$max} or fewer elements"; |
|||
} |
|||
} |
|||
|
|||
} elseif ($type == 'integer' || $type == 'number' || $type == 'numeric') { |
|||
if (($min = $param->getMinimum()) && $value < $min) { |
|||
$this->errors[] = "{$path} must be greater than or equal to {$min}"; |
|||
} |
|||
if (($max = $param->getMaximum()) && $value > $max) { |
|||
$this->errors[] = "{$path} must be less than or equal to {$max}"; |
|||
} |
|||
} |
|||
|
|||
return empty($this->errors); |
|||
} |
|||
} |
|||
@ -0,0 +1,164 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Command\Guzzle; |
|||
|
|||
use GuzzleHttp\Command\CommandInterface; |
|||
use GuzzleHttp\Command\Guzzle\RequestLocation\BodyLocation; |
|||
use GuzzleHttp\Command\Guzzle\RequestLocation\FormParamLocation; |
|||
use GuzzleHttp\Command\Guzzle\RequestLocation\HeaderLocation; |
|||
use GuzzleHttp\Command\Guzzle\RequestLocation\JsonLocation; |
|||
use GuzzleHttp\Command\Guzzle\RequestLocation\MultiPartLocation; |
|||
use GuzzleHttp\Command\Guzzle\RequestLocation\QueryLocation; |
|||
use GuzzleHttp\Command\Guzzle\RequestLocation\RequestLocationInterface; |
|||
use GuzzleHttp\Command\Guzzle\RequestLocation\XmlLocation; |
|||
use GuzzleHttp\Psr7\Request; |
|||
use GuzzleHttp\Psr7\Uri; |
|||
use Psr\Http\Message\RequestInterface; |
|||
|
|||
/** |
|||
* Serializes requests for a given command. |
|||
*/ |
|||
class Serializer |
|||
{ |
|||
/** @var RequestLocationInterface[] */ |
|||
private $locations; |
|||
|
|||
/** @var DescriptionInterface */ |
|||
private $description; |
|||
|
|||
/** |
|||
* @param DescriptionInterface $description |
|||
* @param RequestLocationInterface[] $requestLocations Extra request locations |
|||
*/ |
|||
public function __construct( |
|||
DescriptionInterface $description, |
|||
array $requestLocations = [] |
|||
) { |
|||
static $defaultRequestLocations; |
|||
if (!$defaultRequestLocations) { |
|||
$defaultRequestLocations = [ |
|||
'body' => new BodyLocation(), |
|||
'query' => new QueryLocation(), |
|||
'header' => new HeaderLocation(), |
|||
'json' => new JsonLocation(), |
|||
'xml' => new XmlLocation(), |
|||
'formParam' => new FormParamLocation(), |
|||
'multipart' => new MultiPartLocation(), |
|||
]; |
|||
} |
|||
|
|||
$this->locations = $requestLocations + $defaultRequestLocations; |
|||
$this->description = $description; |
|||
} |
|||
|
|||
/** |
|||
* @param CommandInterface $command |
|||
* @return RequestInterface |
|||
*/ |
|||
public function __invoke(CommandInterface $command) |
|||
{ |
|||
$request = $this->createRequest($command); |
|||
return $this->prepareRequest($command, $request); |
|||
} |
|||
|
|||
/** |
|||
* Prepares a request for sending using location visitors |
|||
* |
|||
* @param CommandInterface $command |
|||
* @param RequestInterface $request Request being created |
|||
* @return RequestInterface |
|||
* @throws \RuntimeException If a location cannot be handled |
|||
*/ |
|||
protected function prepareRequest( |
|||
CommandInterface $command, |
|||
RequestInterface $request |
|||
) { |
|||
$visitedLocations = []; |
|||
$operation = $this->description->getOperation($command->getName()); |
|||
|
|||
// Visit each actual parameter |
|||
foreach ($operation->getParams() as $name => $param) { |
|||
/* @var Parameter $param */ |
|||
$location = $param->getLocation(); |
|||
// Skip parameters that have not been set or are URI location |
|||
if ($location == 'uri' || !$command->hasParam($name)) { |
|||
continue; |
|||
} |
|||
if (!isset($this->locations[$location])) { |
|||
throw new \RuntimeException("No location registered for $name"); |
|||
} |
|||
$visitedLocations[$location] = true; |
|||
$request = $this->locations[$location]->visit($command, $request, $param); |
|||
} |
|||
|
|||
// Ensure that the after() method is invoked for additionalParameters |
|||
/** @var Parameter $additional */ |
|||
if ($additional = $operation->getAdditionalParameters()) { |
|||
$visitedLocations[$additional->getLocation()] = true; |
|||
} |
|||
|
|||
// Call the after() method for each visited location |
|||
foreach (array_keys($visitedLocations) as $location) { |
|||
$request = $this->locations[$location]->after($command, $request, $operation); |
|||
} |
|||
|
|||
return $request; |
|||
} |
|||
|
|||
/** |
|||
* Create a request for the command and operation |
|||
* |
|||
* @param CommandInterface $command |
|||
* |
|||
* @return RequestInterface |
|||
* @throws \RuntimeException |
|||
*/ |
|||
protected function createRequest(CommandInterface $command) |
|||
{ |
|||
$operation = $this->description->getOperation($command->getName()); |
|||
|
|||
// If command does not specify a template, assume the client's base URL. |
|||
if (null === $operation->getUri()) { |
|||
return new Request( |
|||
$operation->getHttpMethod(), |
|||
$this->description->getBaseUri() |
|||
); |
|||
} |
|||
|
|||
return $this->createCommandWithUri($operation, $command); |
|||
} |
|||
|
|||
/** |
|||
* Create a request for an operation with a uri merged onto a base URI |
|||
* |
|||
* @param \GuzzleHttp\Command\Guzzle\Operation $operation |
|||
* @param \GuzzleHttp\Command\CommandInterface $command |
|||
* |
|||
* @return \GuzzleHttp\Psr7\Request |
|||
*/ |
|||
private function createCommandWithUri( |
|||
Operation $operation, |
|||
CommandInterface $command |
|||
) { |
|||
// Get the path values and use the client config settings |
|||
$variables = []; |
|||
foreach ($operation->getParams() as $name => $arg) { |
|||
/* @var Parameter $arg */ |
|||
if ($arg->getLocation() == 'uri') { |
|||
if (isset($command[$name])) { |
|||
$variables[$name] = $arg->filter($command[$name]); |
|||
if (!is_array($variables[$name])) { |
|||
$variables[$name] = (string) $variables[$name]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Expand the URI template. |
|||
$uri = \GuzzleHttp\uri_template($operation->getUri(), $variables); |
|||
|
|||
return new Request( |
|||
$operation->getHttpMethod(), |
|||
Uri::resolve($this->description->getBaseUri(), $uri) |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Tests\Command\Guzzle\Asset\Exception; |
|||
|
|||
use GuzzleHttp\Command\Exception\CommandException; |
|||
|
|||
/** |
|||
* Class CustomCommandException |
|||
* |
|||
* @package GuzzleHttp\Tests\Command\Guzzle\Asset\Exception |
|||
*/ |
|||
class CustomCommandException extends CommandException |
|||
{ |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Tests\Command\Guzzle\Asset\Exception; |
|||
|
|||
use GuzzleHttp\Command\Exception\CommandException; |
|||
|
|||
/** |
|||
* Class OtherCustomCommandException |
|||
* |
|||
* @package GuzzleHttp\Tests\Command\Guzzle\Asset\Exception |
|||
*/ |
|||
class OtherCustomCommandException extends CommandException |
|||
{ |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<title>Title</title> |
|||
</head> |
|||
<body> |
|||
|
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,184 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Tests\Command\Guzzle; |
|||
|
|||
use GuzzleHttp\Command\Guzzle\Description; |
|||
use GuzzleHttp\Command\Guzzle\Operation; |
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\Guzzle\SchemaFormatter; |
|||
|
|||
/** |
|||
* @covers \GuzzleHttp\Command\Guzzle\Description |
|||
*/ |
|||
class DescriptionTest extends \PHPUnit_Framework_TestCase |
|||
{ |
|||
protected $operations; |
|||
|
|||
public function setup() |
|||
{ |
|||
$this->operations = [ |
|||
'test_command' => [ |
|||
'name' => 'test_command', |
|||
'description' => 'documentationForCommand', |
|||
'httpMethod' => 'DELETE', |
|||
'class' => 'FooModel', |
|||
'parameters' => [ |
|||
'bucket' => ['required' => true], |
|||
'key' => ['required' => true] |
|||
] |
|||
] |
|||
]; |
|||
} |
|||
|
|||
public function testConstructor() |
|||
{ |
|||
$service = new Description(['operations' => $this->operations]); |
|||
$this->assertEquals(1, count($service->getOperations())); |
|||
$this->assertFalse($service->hasOperation('foobar')); |
|||
$this->assertTrue($service->hasOperation('test_command')); |
|||
} |
|||
|
|||
public function testContainsModels() |
|||
{ |
|||
$d = new Description([ |
|||
'operations' => ['foo' => []], |
|||
'models' => [ |
|||
'Tag' => ['type' => 'object'], |
|||
'Person' => ['type' => 'object'] |
|||
] |
|||
]); |
|||
$this->assertTrue($d->hasModel('Tag')); |
|||
$this->assertTrue($d->hasModel('Person')); |
|||
$this->assertFalse($d->hasModel('Foo')); |
|||
$this->assertInstanceOf(Parameter::class, $d->getModel('Tag')); |
|||
$this->assertEquals(['Tag', 'Person'], array_keys($d->getModels())); |
|||
} |
|||
|
|||
public function testCanUseResponseClass() |
|||
{ |
|||
$d = new Description([ |
|||
'operations' => [ |
|||
'foo' => ['responseClass' => 'Tag'] |
|||
], |
|||
'models' => ['Tag' => ['type' => 'object']] |
|||
]); |
|||
$op = $d->getOperation('foo'); |
|||
$this->assertNotNull($op->getResponseModel()); |
|||
} |
|||
|
|||
/** |
|||
* @expectedException \InvalidArgumentException |
|||
*/ |
|||
public function testRetrievingMissingModelThrowsException() |
|||
{ |
|||
$d = new Description([]); |
|||
$d->getModel('foo'); |
|||
} |
|||
|
|||
public function testHasAttributes() |
|||
{ |
|||
$d = new Description([ |
|||
'operations' => [], |
|||
'name' => 'Name', |
|||
'description' => 'Description', |
|||
'apiVersion' => '1.24' |
|||
]); |
|||
|
|||
$this->assertEquals('Name', $d->getName()); |
|||
$this->assertEquals('Description', $d->getDescription()); |
|||
$this->assertEquals('1.24', $d->getApiVersion()); |
|||
} |
|||
|
|||
public function testPersistsCustomAttributes() |
|||
{ |
|||
$data = [ |
|||
'operations' => ['foo' => ['class' => 'foo', 'parameters' => []]], |
|||
'name' => 'Name', |
|||
'description' => 'Test', |
|||
'apiVersion' => '1.24', |
|||
'auth' => 'foo', |
|||
'keyParam' => 'bar' |
|||
]; |
|||
$d = new Description($data); |
|||
$this->assertEquals('foo', $d->getData('auth')); |
|||
$this->assertEquals('bar', $d->getData('keyParam')); |
|||
$this->assertEquals(['auth' => 'foo', 'keyParam' => 'bar'], $d->getData()); |
|||
$this->assertNull($d->getData('missing')); |
|||
} |
|||
|
|||
/** |
|||
* @expectedException \InvalidArgumentException |
|||
*/ |
|||
public function testThrowsExceptionForMissingOperation() |
|||
{ |
|||
$s = new Description([]); |
|||
$this->assertNull($s->getOperation('foo')); |
|||
} |
|||
|
|||
/** |
|||
* @expectedException \InvalidArgumentException |
|||
*/ |
|||
public function testValidatesOperationTypes() |
|||
{ |
|||
new Description([ |
|||
'operations' => ['foo' => new \stdClass()] |
|||
]); |
|||
} |
|||
|
|||
public function testHasbaseUrl() |
|||
{ |
|||
$description = new Description(['baseUrl' => 'http://foo.com']); |
|||
$this->assertEquals('http://foo.com', $description->getBaseUri()); |
|||
} |
|||
|
|||
public function testHasbaseUri() |
|||
{ |
|||
$description = new Description(['baseUri' => 'http://foo.com']); |
|||
$this->assertEquals('http://foo.com', $description->getBaseUri()); |
|||
} |
|||
|
|||
public function testModelsHaveNames() |
|||
{ |
|||
$desc = [ |
|||
'models' => [ |
|||
'date' => ['type' => 'string'], |
|||
'user'=> [ |
|||
'type' => 'object', |
|||
'properties' => [ |
|||
'dob' => ['$ref' => 'date'] |
|||
] |
|||
] |
|||
] |
|||
]; |
|||
|
|||
$s = new Description($desc); |
|||
$this->assertEquals('string', $s->getModel('date')->getType()); |
|||
$this->assertEquals('dob', $s->getModel('user')->getProperty('dob')->getName()); |
|||
} |
|||
|
|||
public function testHasOperations() |
|||
{ |
|||
$desc = ['operations' => ['foo' => ['parameters' => ['foo' => [ |
|||
'name' => 'foo' |
|||
]]]]]; |
|||
$s = new Description($desc); |
|||
$this->assertInstanceOf(Operation::class, $s->getOperation('foo')); |
|||
$this->assertSame($s->getOperation('foo'), $s->getOperation('foo')); |
|||
} |
|||
|
|||
public function testHasFormatter() |
|||
{ |
|||
$s = new Description([]); |
|||
$this->assertNotEmpty($s->format('date', 'now')); |
|||
} |
|||
|
|||
public function testCanUseCustomFormatter() |
|||
{ |
|||
$formatter = $this->getMockBuilder(SchemaFormatter::class) |
|||
->setMethods(['format']) |
|||
->getMock(); |
|||
$formatter->expects($this->once()) |
|||
->method('format'); |
|||
$s = new Description([], ['formatter' => $formatter]); |
|||
$s->format('time', 'now'); |
|||
} |
|||
} |
|||
@ -0,0 +1,386 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Tests\Command\Guzzle; |
|||
|
|||
use GuzzleHttp\Client as HttpClient; |
|||
use GuzzleHttp\Command\CommandInterface; |
|||
use GuzzleHttp\Command\Guzzle\Description; |
|||
use GuzzleHttp\Command\Guzzle\DescriptionInterface; |
|||
use GuzzleHttp\Command\Guzzle\GuzzleClient; |
|||
use GuzzleHttp\Command\Guzzle\Operation; |
|||
use GuzzleHttp\Command\ServiceClientInterface; |
|||
use GuzzleHttp\Handler\MockHandler; |
|||
use GuzzleHttp\HandlerStack; |
|||
use GuzzleHttp\Psr7\Response; |
|||
use GuzzleHttp\Tests\Command\Guzzle\Asset\Exception\CustomCommandException; |
|||
use GuzzleHttp\Tests\Command\Guzzle\Asset\Exception\OtherCustomCommandException; |
|||
use Predis\Response\ResponseInterface; |
|||
|
|||
/** |
|||
* @covers \GuzzleHttp\Command\Guzzle\Deserializer |
|||
*/ |
|||
class DeserializerTest extends \PHPUnit_Framework_TestCase |
|||
{ |
|||
/** @var ServiceClientInterface|\PHPUnit_Framework_MockObject_MockObject */ |
|||
private $serviceClient; |
|||
|
|||
/** @var CommandInterface|\PHPUnit_Framework_MockObject_MockObject */ |
|||
private $command; |
|||
|
|||
public function setUp() |
|||
{ |
|||
$this->serviceClient = $this->getMockBuilder(GuzzleClient::class) |
|||
->disableOriginalConstructor() |
|||
->getMock(); |
|||
$this->command = $this->getMockBuilder(CommandInterface::class)->getMock(); |
|||
} |
|||
|
|||
protected function prepareErrorResponses($commandName, array $errors = []) |
|||
{ |
|||
$this->command->expects($this->once())->method('getName')->will($this->returnValue($commandName)); |
|||
|
|||
$description = $this->getMockBuilder(DescriptionInterface::class)->getMock(); |
|||
$operation = new Operation(['errorResponses' => $errors], $description); |
|||
|
|||
$description->expects($this->once()) |
|||
->method('getOperation') |
|||
->with($commandName) |
|||
->will($this->returnValue($operation)); |
|||
|
|||
$this->serviceClient->expects($this->once()) |
|||
->method('getDescription') |
|||
->will($this->returnValue($description)); |
|||
} |
|||
|
|||
public function testDoNothingIfNoException() |
|||
{ |
|||
$mock = new MockHandler([new Response(200)]); |
|||
$description = new Description([ |
|||
'operations' => [ |
|||
'foo' => [ |
|||
'uri' => 'http://httpbin.org/{foo}', |
|||
'httpMethod' => 'GET', |
|||
'responseModel' => 'j', |
|||
'parameters' => [ |
|||
'bar' => [ |
|||
'type' => 'string', |
|||
'required' => true, |
|||
'location' => 'uri' |
|||
] |
|||
] |
|||
] |
|||
], |
|||
'models' => [ |
|||
'j' => [ |
|||
'type' => 'object' |
|||
] |
|||
] |
|||
]); |
|||
$httpClient = new HttpClient(['handler' => $mock]); |
|||
$client = new GuzzleClient($httpClient, $description); |
|||
$client->foo(['bar' => 'baz']); |
|||
} |
|||
|
|||
/** |
|||
* @expectedException \GuzzleHttp\Tests\Command\Guzzle\Asset\Exception\CustomCommandException |
|||
*/ |
|||
public function testCreateExceptionWithCode() |
|||
{ |
|||
$response = new Response(404); |
|||
$mock = new MockHandler([$response]); |
|||
|
|||
$description = new Description([ |
|||
'name' => 'Test API', |
|||
'baseUri' => 'http://httpbin.org', |
|||
'operations' => [ |
|||
'foo' => [ |
|||
'uri' => '/{foo}', |
|||
'httpMethod' => 'GET', |
|||
'responseClass' => 'Foo', |
|||
'parameters' => [ |
|||
'bar' => [ |
|||
'type' => 'string', |
|||
'required' => true, |
|||
'description' => 'Unique user name (alphanumeric)', |
|||
'location' => 'json' |
|||
], |
|||
], |
|||
'errorResponses' => [ |
|||
['code' => 404, 'class' => CustomCommandException::class] |
|||
] |
|||
] |
|||
], |
|||
'models' => [ |
|||
'Foo' => [ |
|||
'type' => 'object', |
|||
'additionalProperties' => [ |
|||
'location' => 'json' |
|||
] |
|||
] |
|||
] |
|||
]); |
|||
|
|||
$httpClient = new HttpClient(['handler' => $mock]); |
|||
$client = new GuzzleClient($httpClient, $description); |
|||
$client->foo(['bar' => 'baz']); |
|||
} |
|||
|
|||
public function testNotCreateExceptionIfDoesNotMatchCode() |
|||
{ |
|||
$response = new Response(401); |
|||
$mock = new MockHandler([$response]); |
|||
|
|||
$description = new Description([ |
|||
'name' => 'Test API', |
|||
'baseUri' => 'http://httpbin.org', |
|||
'operations' => [ |
|||
'foo' => [ |
|||
'uri' => '/{foo}', |
|||
'httpMethod' => 'GET', |
|||
'responseClass' => 'Foo', |
|||
'parameters' => [ |
|||
'bar' => [ |
|||
'type' => 'string', |
|||
'required' => true, |
|||
'description' => 'Unique user name (alphanumeric)', |
|||
'location' => 'json' |
|||
], |
|||
], |
|||
'errorResponses' => [ |
|||
['code' => 404, 'class' => CustomCommandException::class] |
|||
] |
|||
] |
|||
], |
|||
'models' => [ |
|||
'Foo' => [ |
|||
'type' => 'object', |
|||
'additionalProperties' => [ |
|||
'location' => 'json' |
|||
] |
|||
] |
|||
] |
|||
]); |
|||
|
|||
$httpClient = new HttpClient(['handler' => $mock]); |
|||
$client = new GuzzleClient($httpClient, $description); |
|||
$client->foo(['bar' => 'baz']); |
|||
} |
|||
|
|||
/** |
|||
* @expectedException \GuzzleHttp\Tests\Command\Guzzle\Asset\Exception\CustomCommandException |
|||
*/ |
|||
public function testCreateExceptionWithExactMatchOfReasonPhrase() |
|||
{ |
|||
$response = new Response(404, [], null, '1.1', 'Bar'); |
|||
$mock = new MockHandler([$response]); |
|||
|
|||
$description = new Description([ |
|||
'name' => 'Test API', |
|||
'baseUri' => 'http://httpbin.org', |
|||
'operations' => [ |
|||
'foo' => [ |
|||
'uri' => '/{foo}', |
|||
'httpMethod' => 'GET', |
|||
'responseClass' => 'Foo', |
|||
'parameters' => [ |
|||
'bar' => [ |
|||
'type' => 'string', |
|||
'required' => true, |
|||
'description' => 'Unique user name (alphanumeric)', |
|||
'location' => 'json' |
|||
], |
|||
], |
|||
'errorResponses' => [ |
|||
['code' => 404, 'phrase' => 'Bar', 'class' => CustomCommandException::class] |
|||
] |
|||
] |
|||
], |
|||
'models' => [ |
|||
'Foo' => [ |
|||
'type' => 'object', |
|||
'additionalProperties' => [ |
|||
'location' => 'json' |
|||
] |
|||
] |
|||
] |
|||
]); |
|||
|
|||
$httpClient = new HttpClient(['handler' => $mock]); |
|||
$client = new GuzzleClient($httpClient, $description); |
|||
$client->foo(['bar' => 'baz']); |
|||
} |
|||
|
|||
/** |
|||
* @expectedException \GuzzleHttp\Tests\Command\Guzzle\Asset\Exception\OtherCustomCommandException |
|||
*/ |
|||
public function testFavourMostPreciseMatch() |
|||
{ |
|||
$response = new Response(404, [], null, '1.1', 'Bar'); |
|||
$mock = new MockHandler([$response]); |
|||
|
|||
$description = new Description([ |
|||
'name' => 'Test API', |
|||
'baseUri' => 'http://httpbin.org', |
|||
'operations' => [ |
|||
'foo' => [ |
|||
'uri' => '/{foo}', |
|||
'httpMethod' => 'GET', |
|||
'responseClass' => 'Foo', |
|||
'parameters' => [ |
|||
'bar' => [ |
|||
'type' => 'string', |
|||
'required' => true, |
|||
'description' => 'Unique user name (alphanumeric)', |
|||
'location' => 'json' |
|||
], |
|||
], |
|||
'errorResponses' => [ |
|||
['code' => 404, 'class' => CustomCommandException::class], |
|||
['code' => 404, 'phrase' => 'Bar', 'class' => OtherCustomCommandException::class], |
|||
] |
|||
] |
|||
], |
|||
'models' => [ |
|||
'Foo' => [ |
|||
'type' => 'object', |
|||
'additionalProperties' => [ |
|||
'location' => 'json' |
|||
] |
|||
] |
|||
] |
|||
]); |
|||
|
|||
$httpClient = new HttpClient(['handler' => $mock]); |
|||
$client = new GuzzleClient($httpClient, $description); |
|||
$client->foo(['bar' => 'baz']); |
|||
} |
|||
|
|||
/** |
|||
* @expectedException \GuzzleHttp\Command\Exception\CommandException |
|||
* @expectedExceptionMessage 404 |
|||
*/ |
|||
public function testDoesNotAddResultWhenExceptionIsPresent() |
|||
{ |
|||
$description = new Description([ |
|||
'operations' => [ |
|||
'foo' => [ |
|||
'uri' => 'http://httpbin.org/{foo}', |
|||
'httpMethod' => 'GET', |
|||
'responseModel' => 'j', |
|||
'parameters' => [ |
|||
'bar' => [ |
|||
'type' => 'string', |
|||
'required' => true, |
|||
'location' => 'uri' |
|||
] |
|||
] |
|||
] |
|||
], |
|||
'models' => [ |
|||
'j' => [ |
|||
'type' => 'object' |
|||
] |
|||
] |
|||
]); |
|||
|
|||
$mock = new MockHandler([new Response(404)]); |
|||
$stack = HandlerStack::create($mock); |
|||
$httpClient = new HttpClient(['handler' => $stack]); |
|||
$client = new GuzzleClient($httpClient, $description); |
|||
$client->foo(['bar' => 'baz']); |
|||
} |
|||
|
|||
public function testReturnsExpectedResult() |
|||
{ |
|||
$loginResponse = new Response( |
|||
200, |
|||
[], |
|||
'{ |
|||
"LoginResponse":{ |
|||
"result":{ |
|||
"type":4, |
|||
"username":{ |
|||
"uid":38664492, |
|||
"content":"skyfillers-api-test" |
|||
}, |
|||
"token":"3FB1F21014D630481D35CBC30CBF4043" |
|||
}, |
|||
"status":{ |
|||
"code":200, |
|||
"content":"OK" |
|||
} |
|||
} |
|||
}' |
|||
); |
|||
$mock = new MockHandler([$loginResponse]); |
|||
|
|||
$description = new Description([ |
|||
'name' => 'Test API', |
|||
'baseUri' => 'http://httpbin.org', |
|||
'operations' => [ |
|||
'Login' => [ |
|||
'uri' => '/{foo}', |
|||
'httpMethod' => 'POST', |
|||
'responseClass' => 'LoginResponse', |
|||
'parameters' => [ |
|||
'username' => [ |
|||
'type' => 'string', |
|||
'required' => true, |
|||
'description' => 'Unique user name (alphanumeric)', |
|||
'location' => 'json' |
|||
], |
|||
'password' => [ |
|||
'type' => 'string', |
|||
'required' => true, |
|||
'description' => 'User\'s password', |
|||
'location' => 'json' |
|||
], |
|||
'response' => [ |
|||
'type' => 'string', |
|||
'required' => false, |
|||
'description' => 'Determines the response type: xml = result content will be xml formatted (default); plain = result content will be simple text, without structure; json = result content will be json formatted', |
|||
'location' => 'json' |
|||
], |
|||
'token' => [ |
|||
'type' => 'string', |
|||
'required' => false, |
|||
'description' => 'Provides the authentication token', |
|||
'location' => 'json' |
|||
] |
|||
] |
|||
] |
|||
], |
|||
'models' => [ |
|||
'LoginResponse' => [ |
|||
'type' => 'object', |
|||
'additionalProperties' => [ |
|||
'location' => 'json' |
|||
] |
|||
] |
|||
] |
|||
]); |
|||
|
|||
$httpClient = new HttpClient(['handler' => $mock]); |
|||
$client = new GuzzleClient($httpClient, $description); |
|||
$result = $client->Login([ |
|||
'username' => 'test', |
|||
'password' => 'test', |
|||
'response' => 'json', |
|||
]); |
|||
|
|||
$expected = [ |
|||
'result' => [ |
|||
'type' => 4, |
|||
'username' => [ |
|||
'uid' => 38664492, |
|||
'content' => 'skyfillers-api-test' |
|||
], |
|||
'token' => '3FB1F21014D630481D35CBC30CBF4043' |
|||
], |
|||
'status' => [ |
|||
'code' => 200, |
|||
'content' => 'OK' |
|||
] |
|||
]; |
|||
$this->assertArraySubset($expected, $result['LoginResponse']); |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,112 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Tests\Command\Guzzle\Handler; |
|||
|
|||
use GuzzleHttp\Client as HttpClient; |
|||
use GuzzleHttp\Command\Guzzle\Description; |
|||
use GuzzleHttp\Command\Guzzle\GuzzleClient; |
|||
|
|||
/** |
|||
* @covers \GuzzleHttp\Command\Guzzle\Handler\ValidatedDescriptionHandler |
|||
*/ |
|||
class ValidatedDescriptionHandlerTest extends \PHPUnit_Framework_TestCase |
|||
{ |
|||
|
|||
/** |
|||
* @expectedException \GuzzleHttp\Command\Exception\CommandException |
|||
* @expectedExceptionMessage Validation errors: [bar] is a required string |
|||
*/ |
|||
public function testValidates() |
|||
{ |
|||
$description = new Description([ |
|||
'operations' => [ |
|||
'foo' => [ |
|||
'uri' => 'http://httpbin.org', |
|||
'httpMethod' => 'GET', |
|||
'responseModel' => 'j', |
|||
'parameters' => [ |
|||
'bar' => [ |
|||
'type' => 'string', |
|||
'required' => true |
|||
] |
|||
] |
|||
] |
|||
] |
|||
]); |
|||
|
|||
$client = new GuzzleClient(new HttpClient(), $description); |
|||
$client->foo([]); |
|||
} |
|||
|
|||
public function testSuccessfulValidationDoesNotThrow() |
|||
{ |
|||
$description = new Description([ |
|||
'operations' => [ |
|||
'foo' => [ |
|||
'uri' => 'http://httpbin.org', |
|||
'httpMethod' => 'GET', |
|||
'responseModel' => 'j', |
|||
'parameters' => [] |
|||
] |
|||
], |
|||
'models' => [ |
|||
'j' => [ |
|||
'type' => 'object' |
|||
] |
|||
] |
|||
]); |
|||
|
|||
$client = new GuzzleClient(new HttpClient(), $description); |
|||
$client->foo([]); |
|||
} |
|||
|
|||
/** |
|||
* @expectedException \GuzzleHttp\Command\Exception\CommandException |
|||
* @expectedExceptionMessage Validation errors: [bar] must be of type string |
|||
*/ |
|||
public function testValidatesAdditionalParameters() |
|||
{ |
|||
$description = new Description([ |
|||
'operations' => [ |
|||
'foo' => [ |
|||
'uri' => 'http://httpbin.org', |
|||
'httpMethod' => 'GET', |
|||
'responseModel' => 'j', |
|||
'additionalParameters' => [ |
|||
'type' => 'string' |
|||
] |
|||
] |
|||
], |
|||
'models' => [ |
|||
'j' => [ |
|||
'type' => 'object' |
|||
] |
|||
] |
|||
]); |
|||
|
|||
$client = new GuzzleClient(new HttpClient(), $description); |
|||
$client->foo(['bar' => new \stdClass()]); |
|||
} |
|||
|
|||
public function testFilterBeforeValidate() |
|||
{ |
|||
$description = new Description([ |
|||
'operations' => [ |
|||
'foo' => [ |
|||
'uri' => 'http://httpbin.org', |
|||
'httpMethod' => 'GET', |
|||
'parameters' => [ |
|||
'bar' => [ |
|||
'location' => 'uri', |
|||
'type' => 'string', |
|||
'format' => 'date-time', |
|||
'required' => true |
|||
] |
|||
] |
|||
] |
|||
] |
|||
]); |
|||
|
|||
$client = new GuzzleClient(new HttpClient(), $description); |
|||
$client->foo(['bar' => new \DateTimeImmutable()]); // Should not throw any exception |
|||
} |
|||
} |
|||
@ -0,0 +1,227 @@ |
|||
<?php |
|||
namespace Guzzle\Tests\Service\Description; |
|||
|
|||
use GuzzleHttp\Command\Guzzle\Description; |
|||
use GuzzleHttp\Command\Guzzle\Operation; |
|||
|
|||
/** |
|||
* @covers \GuzzleHttp\Command\Guzzle\Operation |
|||
*/ |
|||
class OperationTest extends \PHPUnit_Framework_TestCase |
|||
{ |
|||
public static function strtoupper($string) |
|||
{ |
|||
return strtoupper($string); |
|||
} |
|||
|
|||
public function testOperationIsDataObject() |
|||
{ |
|||
$c = new Operation([ |
|||
'name' => 'test', |
|||
'summary' => 'doc', |
|||
'notes' => 'notes', |
|||
'documentationUrl' => 'http://www.example.com', |
|||
'httpMethod' => 'POST', |
|||
'uri' => '/api/v1', |
|||
'responseModel' => 'abc', |
|||
'deprecated' => true, |
|||
'parameters' => [ |
|||
'key' => [ |
|||
'required' => true, |
|||
'type' => 'string', |
|||
'maxLength' => 10, |
|||
'name' => 'key' |
|||
], |
|||
'key_2' => [ |
|||
'required' => true, |
|||
'type' => 'integer', |
|||
'default' => 10, |
|||
'name' => 'key_2' |
|||
] |
|||
] |
|||
]); |
|||
|
|||
$this->assertEquals('test', $c->getName()); |
|||
$this->assertEquals('doc', $c->getSummary()); |
|||
$this->assertEquals('http://www.example.com', $c->getDocumentationUrl()); |
|||
$this->assertEquals('POST', $c->getHttpMethod()); |
|||
$this->assertEquals('/api/v1', $c->getUri()); |
|||
$this->assertEquals('abc', $c->getResponseModel()); |
|||
$this->assertTrue($c->getDeprecated()); |
|||
|
|||
$params = array_map(function ($c) { |
|||
return $c->toArray(); |
|||
}, $c->getParams()); |
|||
|
|||
$this->assertEquals([ |
|||
'key' => [ |
|||
'required' => true, |
|||
'type' => 'string', |
|||
'maxLength' => 10, |
|||
'name' => 'key' |
|||
], |
|||
'key_2' => [ |
|||
'required' => true, |
|||
'type' => 'integer', |
|||
'default' => 10, |
|||
'name' => 'key_2' |
|||
] |
|||
], $params); |
|||
|
|||
$this->assertEquals([ |
|||
'required' => true, |
|||
'type' => 'integer', |
|||
'default' => 10, |
|||
'name' => 'key_2' |
|||
], $c->getParam('key_2')->toArray()); |
|||
|
|||
$this->assertNull($c->getParam('afefwef')); |
|||
$this->assertArrayNotHasKey('parent', $c->getParam('key_2')->toArray()); |
|||
} |
|||
|
|||
public function testDeterminesIfHasParam() |
|||
{ |
|||
$command = $this->getTestCommand(); |
|||
$this->assertTrue($command->hasParam('data')); |
|||
$this->assertFalse($command->hasParam('baz')); |
|||
} |
|||
|
|||
protected function getTestCommand() |
|||
{ |
|||
return new Operation([ |
|||
'parameters' => [ |
|||
'data' => ['type' => 'string'] |
|||
] |
|||
]); |
|||
} |
|||
|
|||
public function testAddsNameToParametersIfNeeded() |
|||
{ |
|||
$command = new Operation(['parameters' => ['foo' => []]]); |
|||
$this->assertEquals('foo', $command->getParam('foo')->getName()); |
|||
} |
|||
|
|||
public function testContainsApiErrorInformation() |
|||
{ |
|||
$command = $this->getOperation(); |
|||
$this->assertEquals(1, count($command->getErrorResponses())); |
|||
} |
|||
|
|||
public function testHasNotes() |
|||
{ |
|||
$o = new Operation(['notes' => 'foo']); |
|||
$this->assertEquals('foo', $o->getNotes()); |
|||
} |
|||
|
|||
public function testHasData() |
|||
{ |
|||
$o = new Operation(['data' => ['foo' => 'baz', 'bar' => 123]]); |
|||
$this->assertEquals('baz', $o->getData('foo')); |
|||
$this->assertEquals(123, $o->getData('bar')); |
|||
$this->assertNull($o->getData('wfefwe')); |
|||
$this->assertEquals(['foo' => 'baz', 'bar' => 123], $o->getData()); |
|||
} |
|||
|
|||
/** |
|||
* @expectedException \InvalidArgumentException |
|||
* @expectedExceptionMesssage Parameters must be arrays |
|||
*/ |
|||
public function testEnsuresParametersAreArrays() |
|||
{ |
|||
new Operation(['parameters' => ['foo' => true]]); |
|||
} |
|||
|
|||
public function testHasDescription() |
|||
{ |
|||
$s = new Description([]); |
|||
$o = new Operation([], $s); |
|||
$this->assertSame($s, $o->getServiceDescription()); |
|||
} |
|||
|
|||
public function testHasAdditionalParameters() |
|||
{ |
|||
$o = new Operation([ |
|||
'additionalParameters' => [ |
|||
'type' => 'string', 'name' => 'binks', |
|||
], |
|||
'parameters' => [ |
|||
'foo' => ['type' => 'integer'], |
|||
], |
|||
]); |
|||
$this->assertEquals('string', $o->getAdditionalParameters()->getType()); |
|||
} |
|||
|
|||
/** |
|||
* @return Operation |
|||
*/ |
|||
protected function getOperation() |
|||
{ |
|||
return new Operation([ |
|||
'name' => 'OperationTest', |
|||
'class' => get_class($this), |
|||
'parameters' => [ |
|||
'test' => ['type' => 'object'], |
|||
'bool_1' => ['default' => true, 'type' => 'boolean'], |
|||
'bool_2' => ['default' => false], |
|||
'float' => ['type' => 'numeric'], |
|||
'int' => ['type' => 'integer'], |
|||
'date' => ['type' => 'string'], |
|||
'timestamp' => ['type' => 'string'], |
|||
'string' => ['type' => 'string'], |
|||
'username' => ['type' => 'string', 'required' => true, 'filters' => 'strtolower'], |
|||
'test_function' => ['type' => 'string', 'filters' => __CLASS__ . '::strtoupper'], |
|||
], |
|||
'errorResponses' => [ |
|||
[ |
|||
'code' => 503, |
|||
'reason' => 'InsufficientCapacity', |
|||
'class' => 'Guzzle\\Exception\\RuntimeException', |
|||
], |
|||
], |
|||
]); |
|||
} |
|||
|
|||
public function testCanExtendFromOtherOperations() |
|||
{ |
|||
$d = new Description([ |
|||
'operations' => [ |
|||
'A' => [ |
|||
'parameters' => [ |
|||
'A' => [ |
|||
'type' => 'object', |
|||
'properties' => ['foo' => ['type' => 'string']] |
|||
], |
|||
'B' => ['type' => 'string'] |
|||
], |
|||
'summary' => 'foo' |
|||
], |
|||
'B' => [ |
|||
'extends' => 'A', |
|||
'summary' => 'Bar' |
|||
], |
|||
'C' => [ |
|||
'extends' => 'B', |
|||
'summary' => 'Bar', |
|||
'parameters' => [ |
|||
'B' => ['type' => 'number'] |
|||
] |
|||
] |
|||
] |
|||
]); |
|||
|
|||
$a = $d->getOperation('A'); |
|||
$this->assertEquals('foo', $a->getSummary()); |
|||
$this->assertTrue($a->hasParam('A')); |
|||
$this->assertEquals('string', $a->getParam('B')->getType()); |
|||
|
|||
$b = $d->getOperation('B'); |
|||
$this->assertTrue($a->hasParam('A')); |
|||
$this->assertEquals('Bar', $b->getSummary()); |
|||
$this->assertEquals('string', $a->getParam('B')->getType()); |
|||
|
|||
$c = $d->getOperation('C'); |
|||
$this->assertTrue($a->hasParam('A')); |
|||
$this->assertEquals('Bar', $c->getSummary()); |
|||
$this->assertEquals('number', $c->getParam('B')->getType()); |
|||
} |
|||
} |
|||
@ -0,0 +1,378 @@ |
|||
<?php |
|||
namespace Guzzle\Tests\Service\Description; |
|||
|
|||
use GuzzleHttp\Command\Guzzle\Description; |
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
|
|||
/** |
|||
* @covers \GuzzleHttp\Command\Guzzle\Parameter |
|||
*/ |
|||
class ParameterTest extends \PHPUnit_Framework_TestCase |
|||
{ |
|||
protected $data = [ |
|||
'name' => 'foo', |
|||
'type' => 'bar', |
|||
'required' => true, |
|||
'default' => '123', |
|||
'description' => '456', |
|||
'minLength' => 2, |
|||
'maxLength' => 5, |
|||
'location' => 'body', |
|||
'static' => true, |
|||
'filters' => ['trim', 'json_encode'] |
|||
]; |
|||
|
|||
public function testCreatesParamFromArray() |
|||
{ |
|||
$p = new Parameter($this->data); |
|||
$this->assertEquals('foo', $p->getName()); |
|||
$this->assertEquals('bar', $p->getType()); |
|||
$this->assertTrue($p->isRequired()); |
|||
$this->assertEquals('123', $p->getDefault()); |
|||
$this->assertEquals('456', $p->getDescription()); |
|||
$this->assertEquals(2, $p->getMinLength()); |
|||
$this->assertEquals(5, $p->getMaxLength()); |
|||
$this->assertEquals('body', $p->getLocation()); |
|||
$this->assertTrue($p->isStatic()); |
|||
$this->assertEquals(['trim', 'json_encode'], $p->getFilters()); |
|||
$p->setName('abc'); |
|||
$this->assertEquals('abc', $p->getName()); |
|||
} |
|||
|
|||
/** |
|||
* @expectedException \InvalidArgumentException |
|||
*/ |
|||
public function testValidatesDescription() |
|||
{ |
|||
new Parameter($this->data, ['description' => 'foo']); |
|||
} |
|||
|
|||
public function testCanConvertToArray() |
|||
{ |
|||
$p = new Parameter($this->data); |
|||
$this->assertEquals($this->data, $p->toArray()); |
|||
} |
|||
|
|||
public function testUsesStatic() |
|||
{ |
|||
$d = $this->data; |
|||
$d['default'] = 'booboo'; |
|||
$d['static'] = true; |
|||
$p = new Parameter($d); |
|||
$this->assertEquals('booboo', $p->getValue('bar')); |
|||
} |
|||
|
|||
public function testUsesDefault() |
|||
{ |
|||
$d = $this->data; |
|||
$d['default'] = 'foo'; |
|||
$d['static'] = null; |
|||
$p = new Parameter($d); |
|||
$this->assertEquals('foo', $p->getValue(null)); |
|||
} |
|||
|
|||
public function testReturnsYourValue() |
|||
{ |
|||
$d = $this->data; |
|||
$d['static'] = null; |
|||
$p = new Parameter($d); |
|||
$this->assertEquals('foo', $p->getValue('foo')); |
|||
} |
|||
|
|||
public function testZeroValueDoesNotCauseDefaultToBeReturned() |
|||
{ |
|||
$d = $this->data; |
|||
$d['default'] = '1'; |
|||
$d['static'] = null; |
|||
$p = new Parameter($d); |
|||
$this->assertEquals('0', $p->getValue('0')); |
|||
} |
|||
|
|||
public function testFiltersValues() |
|||
{ |
|||
$d = $this->data; |
|||
$d['static'] = null; |
|||
$d['filters'] = 'strtoupper'; |
|||
$p = new Parameter($d); |
|||
$this->assertEquals('FOO', $p->filter('foo')); |
|||
} |
|||
|
|||
/** |
|||
* @expectedException \RuntimeException |
|||
* @expectedExceptionMessage No service description |
|||
*/ |
|||
public function testRequiresServiceDescriptionForFormatting() |
|||
{ |
|||
$d = $this->data; |
|||
$d['format'] = 'foo'; |
|||
$p = new Parameter($d); |
|||
$p->filter('bar'); |
|||
} |
|||
|
|||
public function testConvertsBooleans() |
|||
{ |
|||
$p = new Parameter(['type' => 'boolean']); |
|||
$this->assertEquals(true, $p->filter('true')); |
|||
$this->assertEquals(false, $p->filter('false')); |
|||
} |
|||
|
|||
public function testUsesArrayByDefaultForFilters() |
|||
{ |
|||
$d = $this->data; |
|||
$d['filters'] = null; |
|||
$p = new Parameter($d); |
|||
$this->assertEquals([], $p->getFilters()); |
|||
} |
|||
|
|||
public function testAllowsSimpleLocationValue() |
|||
{ |
|||
$p = new Parameter(['name' => 'myname', 'location' => 'foo', 'sentAs' => 'Hello']); |
|||
$this->assertEquals('foo', $p->getLocation()); |
|||
$this->assertEquals('Hello', $p->getSentAs()); |
|||
} |
|||
|
|||
public function testParsesTypeValues() |
|||
{ |
|||
$p = new Parameter(['type' => 'foo']); |
|||
$this->assertEquals('foo', $p->getType()); |
|||
} |
|||
|
|||
/** |
|||
* @expectedException \InvalidArgumentException |
|||
* @expectedExceptionMessage A [method] value must be specified for each complex filter |
|||
*/ |
|||
public function testValidatesComplexFilters() |
|||
{ |
|||
$p = new Parameter(['filters' => [['args' => 'foo']]]); |
|||
} |
|||
|
|||
public function testAllowsComplexFilters() |
|||
{ |
|||
$that = $this; |
|||
$param = new Parameter([ |
|||
'filters' => [ |
|||
[ |
|||
'method' => function ($a, $b, $c, $d) use ($that, &$param) { |
|||
$that->assertEquals('test', $a); |
|||
$that->assertEquals('my_value!', $b); |
|||
$that->assertEquals('bar', $c); |
|||
$that->assertSame($param, $d); |
|||
return 'abc' . $b; |
|||
}, |
|||
'args' => ['test', '@value', 'bar', '@api'] |
|||
] |
|||
] |
|||
]); |
|||
|
|||
$this->assertEquals('abcmy_value!', $param->filter('my_value!')); |
|||
} |
|||
|
|||
public function testAddsAdditionalProperties() |
|||
{ |
|||
$p = new Parameter([ |
|||
'type' => 'object', |
|||
'additionalProperties' => ['type' => 'string'] |
|||
]); |
|||
$this->assertInstanceOf('GuzzleHttp\Command\Guzzle\Parameter', $p->getAdditionalProperties()); |
|||
$this->assertNull($p->getAdditionalProperties()->getAdditionalProperties()); |
|||
$p = new Parameter(['type' => 'object']); |
|||
$this->assertTrue($p->getAdditionalProperties()); |
|||
} |
|||
|
|||
public function testAddsItems() |
|||
{ |
|||
$p = new Parameter([ |
|||
'type' => 'array', |
|||
'items' => ['type' => 'string'] |
|||
]); |
|||
$this->assertInstanceOf('GuzzleHttp\Command\Guzzle\Parameter', $p->getItems()); |
|||
$out = $p->toArray(); |
|||
$this->assertEquals('array', $out['type']); |
|||
$this->assertInternalType('array', $out['items']); |
|||
} |
|||
|
|||
public function testCanRetrieveKnownPropertiesUsingDataMethod() |
|||
{ |
|||
$p = new Parameter(['data' => ['name' => 'test'], 'extra' => 'hi!']); |
|||
$this->assertEquals('test', $p->getData('name')); |
|||
$this->assertEquals(['name' => 'test'], $p->getData()); |
|||
$this->assertNull($p->getData('fjnweefe')); |
|||
$this->assertEquals('hi!', $p->getData('extra')); |
|||
} |
|||
|
|||
public function testHasPattern() |
|||
{ |
|||
$p = new Parameter(['pattern' => '/[0-9]+/']); |
|||
$this->assertEquals('/[0-9]+/', $p->getPattern()); |
|||
} |
|||
|
|||
public function testHasEnum() |
|||
{ |
|||
$p = new Parameter(['enum' => ['foo', 'bar']]); |
|||
$this->assertEquals(['foo', 'bar'], $p->getEnum()); |
|||
} |
|||
|
|||
public function testSerializesItems() |
|||
{ |
|||
$p = new Parameter([ |
|||
'type' => 'object', |
|||
'additionalProperties' => ['type' => 'string'] |
|||
]); |
|||
$this->assertEquals([ |
|||
'type' => 'object', |
|||
'additionalProperties' => ['type' => 'string'] |
|||
], $p->toArray()); |
|||
} |
|||
|
|||
public function testResolvesRefKeysRecursively() |
|||
{ |
|||
$description = new Description([ |
|||
'models' => [ |
|||
'JarJar' => ['type' => 'string', 'default' => 'Mesa address tha senate!'], |
|||
'Anakin' => ['type' => 'array', 'items' => ['$ref' => 'JarJar']] |
|||
], |
|||
]); |
|||
$p = new Parameter(['$ref' => 'Anakin', 'description' => 'added'], ['description' => $description]); |
|||
$this->assertEquals([ |
|||
'description' => 'added', |
|||
'$ref' => 'Anakin' |
|||
], $p->toArray()); |
|||
} |
|||
|
|||
public function testResolvesExtendsRecursively() |
|||
{ |
|||
$jarJar = ['type' => 'string', 'default' => 'Mesa address tha senate!', 'description' => 'a']; |
|||
$anakin = ['type' => 'array', 'items' => ['extends' => 'JarJar', 'description' => 'b']]; |
|||
$description = new Description([ |
|||
'models' => ['JarJar' => $jarJar, 'Anakin' => $anakin] |
|||
]); |
|||
// Description attribute will be updated, and format added |
|||
$p = new Parameter(['extends' => 'Anakin', 'format' => 'date'], ['description' => $description]); |
|||
$this->assertEquals([ |
|||
'format' => 'date', |
|||
'extends' => 'Anakin' |
|||
], $p->toArray()); |
|||
} |
|||
|
|||
public function testHasKeyMethod() |
|||
{ |
|||
$p = new Parameter(['name' => 'foo', 'sentAs' => 'bar']); |
|||
$this->assertEquals('bar', $p->getWireName()); |
|||
} |
|||
|
|||
public function testIncludesNameInToArrayWhenItemsAttributeHasName() |
|||
{ |
|||
$p = new Parameter([ |
|||
'type' => 'array', |
|||
'name' => 'Abc', |
|||
'items' => [ |
|||
'name' => 'Foo', |
|||
'type' => 'object' |
|||
] |
|||
]); |
|||
$result = $p->toArray(); |
|||
$this->assertEquals([ |
|||
'type' => 'array', |
|||
'name' => 'Abc', |
|||
'items' => [ |
|||
'name' => 'Foo', |
|||
'type' => 'object' |
|||
] |
|||
], $result); |
|||
} |
|||
|
|||
public function dateTimeProvider() |
|||
{ |
|||
$d = 'October 13, 2012 16:15:46 UTC'; |
|||
|
|||
return [ |
|||
[$d, 'date-time', '2012-10-13T16:15:46Z'], |
|||
[$d, 'date', '2012-10-13'], |
|||
[$d, 'timestamp', strtotime($d)], |
|||
[new \DateTime($d), 'timestamp', strtotime($d)] |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* @dataProvider dateTimeProvider |
|||
*/ |
|||
public function testAppliesFormat($d, $format, $result) |
|||
{ |
|||
$p = new Parameter(['format' => $format], ['description' => new Description([])]); |
|||
$this->assertEquals($format, $p->getFormat()); |
|||
$this->assertEquals($result, $p->filter($d)); |
|||
} |
|||
|
|||
public function testHasMinAndMax() |
|||
{ |
|||
$p = new Parameter([ |
|||
'minimum' => 2, |
|||
'maximum' => 3, |
|||
'minItems' => 4, |
|||
'maxItems' => 5, |
|||
]); |
|||
$this->assertEquals(2, $p->getMinimum()); |
|||
$this->assertEquals(3, $p->getMaximum()); |
|||
$this->assertEquals(4, $p->getMinItems()); |
|||
$this->assertEquals(5, $p->getMaxItems()); |
|||
} |
|||
|
|||
public function testHasProperties() |
|||
{ |
|||
$data = [ |
|||
'type' => 'object', |
|||
'properties' => [ |
|||
'foo' => ['type' => 'string'], |
|||
'bar' => ['type' => 'string'], |
|||
] |
|||
]; |
|||
$p = new Parameter($data); |
|||
$this->assertInstanceOf('GuzzleHttp\\Command\\Guzzle\\Parameter', $p->getProperty('foo')); |
|||
$this->assertSame($p->getProperty('foo'), $p->getProperty('foo')); |
|||
$this->assertNull($p->getProperty('wefwe')); |
|||
|
|||
$properties = $p->getProperties(); |
|||
$this->assertInternalType('array', $properties); |
|||
foreach ($properties as $prop) { |
|||
$this->assertInstanceOf('GuzzleHttp\\Command\\Guzzle\\Parameter', $prop); |
|||
} |
|||
|
|||
$this->assertEquals($data, $p->toArray()); |
|||
} |
|||
|
|||
/** |
|||
* @expectedException \InvalidArgumentException |
|||
* @expectedExceptionMessage Expected a string. Got: array |
|||
*/ |
|||
public function testThrowsWhenNotPassString() |
|||
{ |
|||
$emptyParam = new Parameter(); |
|||
$this->assertFalse($emptyParam->has([])); |
|||
$this->assertFalse($emptyParam->has(new \stdClass())); |
|||
$this->assertFalse($emptyParam->has('1')); |
|||
$this->assertFalse($emptyParam->has(1)); |
|||
} |
|||
|
|||
public function testHasReturnsFalseForWrongOrEmptyValues() |
|||
{ |
|||
$emptyParam = new Parameter(); |
|||
$this->assertFalse($emptyParam->has('')); |
|||
$this->assertFalse($emptyParam->has('description')); |
|||
$this->assertFalse($emptyParam->has('noExisting')); |
|||
} |
|||
|
|||
public function testHasReturnsTrueForCorrectValues() |
|||
{ |
|||
$p = new Parameter([ |
|||
'minimum' => 2, |
|||
'maximum' => 3, |
|||
'minItems' => 4, |
|||
'maxItems' => 5, |
|||
]); |
|||
|
|||
$this->assertTrue($p->has('minimum')); |
|||
$this->assertTrue($p->has('maximum')); |
|||
$this->assertTrue($p->has('minItems')); |
|||
$this->assertTrue($p->has('maxItems')); |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Tests\Command\Guzzle\QuerySerializer; |
|||
|
|||
use GuzzleHttp\Command\Guzzle\QuerySerializer\Rfc3986Serializer; |
|||
|
|||
class Rfc3986SerializerTest extends \PHPUnit_Framework_TestCase |
|||
{ |
|||
public function queryProvider() |
|||
{ |
|||
return [ |
|||
[['foo' => 'bar'], 'foo=bar'], |
|||
[['foo' => [1, 2]], 'foo[0]=1&foo[1]=2'], |
|||
[['foo' => ['bar' => 'baz', 'bim' => [4, 5]]], 'foo[bar]=baz&foo[bim][0]=4&foo[bim][1]=5'] |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* @dataProvider queryProvider |
|||
*/ |
|||
public function testSerializeQueryParams(array $params, $expectedResult) |
|||
{ |
|||
$serializer = new Rfc3986Serializer(); |
|||
$result = $serializer->aggregate($params); |
|||
|
|||
$this->assertEquals($expectedResult, urldecode($result)); |
|||
} |
|||
|
|||
public function testCanRemoveNumericIndices() |
|||
{ |
|||
$serializer = new Rfc3986Serializer(true); |
|||
$result = $serializer->aggregate(['foo' => ['bar', 'baz'], 'bar' => ['bim' => [4, 5]]]); |
|||
|
|||
$this->assertEquals('foo[]=bar&foo[]=baz&bar[bim][]=4&bar[bim][]=5', urldecode($result)); |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Tests\Command\Guzzle\RequestLocation; |
|||
|
|||
use GuzzleHttp\Command\Command; |
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\Guzzle\RequestLocation\BodyLocation; |
|||
use GuzzleHttp\Psr7\Request; |
|||
|
|||
/** |
|||
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\BodyLocation |
|||
*/ |
|||
class BodyLocationTest extends \PHPUnit_Framework_TestCase |
|||
{ |
|||
/** |
|||
* @group RequestLocation |
|||
*/ |
|||
public function testVisitsLocation() |
|||
{ |
|||
$location = new BodyLocation('body'); |
|||
$command = new Command('foo', ['foo' => 'bar']); |
|||
$request = new Request('POST', 'http://httbin.org'); |
|||
$param = new Parameter(['name' => 'foo']); |
|||
$request = $location->visit($command, $request, $param); |
|||
$this->assertEquals('foo=bar', $request->getBody()->getContents()); |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Tests\Command\Guzzle\RequestLocation; |
|||
|
|||
use GuzzleHttp\Command\Command; |
|||
use GuzzleHttp\Command\Guzzle\Operation; |
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\Guzzle\RequestLocation\FormParamLocation; |
|||
use GuzzleHttp\Command\Guzzle\RequestLocation\PostFieldLocation; |
|||
use GuzzleHttp\Psr7\Request; |
|||
|
|||
/** |
|||
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\FormParamLocation |
|||
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\AbstractLocation |
|||
*/ |
|||
class FormParamLocationTest extends \PHPUnit_Framework_TestCase |
|||
{ |
|||
/** |
|||
* @group RequestLocation |
|||
*/ |
|||
public function testVisitsLocation() |
|||
{ |
|||
$location = new FormParamLocation(); |
|||
$command = new Command('foo', ['foo' => 'bar']); |
|||
$request = new Request('POST', 'http://httbin.org'); |
|||
$param = new Parameter(['name' => 'foo']); |
|||
$request = $location->visit($command, $request, $param); |
|||
$operation = new Operation(); |
|||
$request = $location->after($command, $request, $operation); |
|||
$this->assertEquals('foo=bar', $request->getBody()->getContents()); |
|||
$this->assertArraySubset([0 => 'application/x-www-form-urlencoded; charset=utf-8'], $request->getHeader('Content-Type')); |
|||
} |
|||
|
|||
/** |
|||
* @group RequestLocation |
|||
*/ |
|||
public function testAddsAdditionalProperties() |
|||
{ |
|||
$location = new FormParamLocation(); |
|||
$command = new Command('foo', ['foo' => 'bar']); |
|||
$command['add'] = 'props'; |
|||
$request = new Request('POST', 'http://httbin.org', []); |
|||
$param = new Parameter(['name' => 'foo']); |
|||
$request = $location->visit($command, $request, $param); |
|||
$operation = new Operation([ |
|||
'additionalParameters' => [ |
|||
'location' => 'formParam' |
|||
] |
|||
]); |
|||
$request = $location->after($command, $request, $operation); |
|||
$this->assertEquals('foo=bar&add=props', $request->getBody()->getContents()); |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Tests\Command\Guzzle\RequestLocation; |
|||
|
|||
use GuzzleHttp\Command\Command; |
|||
use GuzzleHttp\Command\Guzzle\Operation; |
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\Guzzle\RequestLocation\HeaderLocation; |
|||
use GuzzleHttp\Psr7\Request; |
|||
|
|||
/** |
|||
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\HeaderLocation |
|||
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\AbstractLocation |
|||
*/ |
|||
class HeaderLocationTest extends \PHPUnit_Framework_TestCase |
|||
{ |
|||
/** |
|||
* @group RequestLocation |
|||
*/ |
|||
public function testVisitsLocation() |
|||
{ |
|||
$location = new HeaderLocation('header'); |
|||
$command = new Command('foo', ['foo' => 'bar']); |
|||
$request = new Request('POST', 'http://httbin.org'); |
|||
$param = new Parameter(['name' => 'foo']); |
|||
$request = $location->visit($command, $request, $param); |
|||
|
|||
$header = $request->getHeader('foo'); |
|||
$this->assertTrue(is_array($header)); |
|||
$this->assertArraySubset([0 => 'bar'], $request->getHeader('foo')); |
|||
} |
|||
|
|||
/** |
|||
* @group RequestLocation |
|||
*/ |
|||
public function testAddsAdditionalProperties() |
|||
{ |
|||
$location = new HeaderLocation('header'); |
|||
$command = new Command('foo', ['foo' => 'bar']); |
|||
$command['add'] = 'props'; |
|||
$operation = new Operation([ |
|||
'additionalParameters' => [ |
|||
'location' => 'header' |
|||
] |
|||
]); |
|||
$request = new Request('POST', 'http://httbin.org'); |
|||
$request = $location->after($command, $request, $operation); |
|||
|
|||
$header = $request->getHeader('add'); |
|||
$this->assertTrue(is_array($header)); |
|||
$this->assertArraySubset([0 => 'props'], $header); |
|||
} |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Tests\Command\Guzzle\RequestLocation; |
|||
|
|||
use GuzzleHttp\Command\Command; |
|||
use GuzzleHttp\Command\Guzzle\Operation; |
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\Guzzle\RequestLocation\JsonLocation; |
|||
use GuzzleHttp\Psr7\Request; |
|||
|
|||
/** |
|||
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\JsonLocation |
|||
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\AbstractLocation |
|||
*/ |
|||
class JsonLocationTest extends \PHPUnit_Framework_TestCase |
|||
{ |
|||
/** |
|||
* @group RequestLocation |
|||
*/ |
|||
public function testVisitsLocation() |
|||
{ |
|||
$location = new JsonLocation('json'); |
|||
$command = new Command('foo', ['foo' => 'bar']); |
|||
$request = new Request('POST', 'http://httbin.org'); |
|||
$param = new Parameter(['name' => 'foo']); |
|||
$location->visit($command, $request, $param); |
|||
$operation = new Operation(); |
|||
$request = $location->after($command, $request, $operation); |
|||
$this->assertEquals('{"foo":"bar"}', $request->getBody()->getContents()); |
|||
$this->assertArraySubset([0 => 'application/json'], $request->getHeader('Content-Type')); |
|||
} |
|||
|
|||
/** |
|||
* @group RequestLocation |
|||
*/ |
|||
public function testVisitsAdditionalProperties() |
|||
{ |
|||
$location = new JsonLocation('json', 'foo'); |
|||
$command = new Command('foo', ['foo' => 'bar']); |
|||
$command['baz'] = ['bam' => [1]]; |
|||
$request = new Request('POST', 'http://httbin.org'); |
|||
$param = new Parameter(['name' => 'foo']); |
|||
$location->visit($command, $request, $param); |
|||
$operation = new Operation([ |
|||
'additionalParameters' => [ |
|||
'location' => 'json' |
|||
] |
|||
]); |
|||
$request = $location->after($command, $request, $operation); |
|||
$this->assertEquals('{"foo":"bar","baz":{"bam":[1]}}', $request->getBody()->getContents()); |
|||
$this->assertEquals([0 => 'foo'], $request->getHeader('Content-Type')); |
|||
} |
|||
|
|||
/** |
|||
* @group RequestLocation |
|||
*/ |
|||
public function testVisitsNestedLocation() |
|||
{ |
|||
$location = new JsonLocation('json'); |
|||
$command = new Command('foo', ['foo' => 'bar']); |
|||
$request = new Request('POST', 'http://httbin.org'); |
|||
$param = new Parameter([ |
|||
'name' => 'foo', |
|||
'type' => 'object', |
|||
'properties' => [ |
|||
'baz' => [ |
|||
'type' => 'array', |
|||
'items' => [ |
|||
'type' => 'string', |
|||
'filters' => ['strtoupper'] |
|||
] |
|||
] |
|||
], |
|||
'additionalProperties' => [ |
|||
'type' => 'array', |
|||
'items' => [ |
|||
'type' => 'string', |
|||
'filters' => ['strtolower'] |
|||
] |
|||
] |
|||
]); |
|||
$command['foo'] = [ |
|||
'baz' => ['a', 'b'], |
|||
'bam' => ['A', 'B'], |
|||
]; |
|||
$location->visit($command, $request, $param); |
|||
$operation = new Operation(); |
|||
$request = $location->after($command, $request, $operation); |
|||
$this->assertEquals('{"foo":{"baz":["A","B"],"bam":["a","b"]}}', (string) $request->getBody()->getContents()); |
|||
$this->assertEquals([0 => 'application/json'], $request->getHeader('Content-Type')); |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Tests\Command\Guzzle\RequestLocation; |
|||
|
|||
use GuzzleHttp\Command\Command; |
|||
use GuzzleHttp\Command\Guzzle\Operation; |
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\Guzzle\RequestLocation\MultiPartLocation; |
|||
use GuzzleHttp\Command\Guzzle\RequestLocation\PostFileLocation; |
|||
use GuzzleHttp\Psr7\Request; |
|||
|
|||
/** |
|||
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\MultiPartLocation |
|||
*/ |
|||
class MultiPartLocationTest extends \PHPUnit_Framework_TestCase |
|||
{ |
|||
/** |
|||
* @group RequestLocation |
|||
*/ |
|||
public function testVisitsLocation() |
|||
{ |
|||
$location = new MultiPartLocation(); |
|||
$command = new Command('foo', ['foo' => 'bar']); |
|||
$request = new Request('POST', 'http://httbin.org', []); |
|||
$param = new Parameter(['name' => 'foo']); |
|||
$request = $location->visit($command, $request, $param); |
|||
$operation = new Operation(); |
|||
$request = $location->after($command, $request, $operation); |
|||
$actual = $request->getBody()->getContents(); |
|||
|
|||
$this->assertNotFalse(strpos($actual, 'name="foo"')); |
|||
$this->assertNotFalse(strpos($actual, 'bar')); |
|||
} |
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Tests\Command\Guzzle\RequestLocation; |
|||
|
|||
use GuzzleHttp\Command\Command; |
|||
use GuzzleHttp\Command\Guzzle\Operation; |
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\Guzzle\RequestLocation\QueryLocation; |
|||
use GuzzleHttp\Psr7; |
|||
use GuzzleHttp\Psr7\Request; |
|||
|
|||
/** |
|||
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\QueryLocation |
|||
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\AbstractLocation |
|||
*/ |
|||
class QueryLocationTest extends \PHPUnit_Framework_TestCase |
|||
{ |
|||
public function queryProvider() |
|||
{ |
|||
return [ |
|||
[['foo' => 'bar'], 'foo=bar'], |
|||
[['foo' => [1, 2]], 'foo[0]=1&foo[1]=2'], |
|||
[['foo' => ['bar' => 'baz', 'bim' => [4, 5]]], 'foo[bar]=baz&foo[bim][0]=4&foo[bim][1]=5'] |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* @group RequestLocation |
|||
*/ |
|||
public function testVisitsLocation() |
|||
{ |
|||
$location = new QueryLocation(); |
|||
$command = new Command('foo', ['foo' => 'bar']); |
|||
$request = new Request('POST', 'http://httbin.org'); |
|||
$param = new Parameter(['name' => 'foo']); |
|||
$request = $location->visit($command, $request, $param); |
|||
|
|||
$this->assertEquals('foo=bar', urldecode($request->getUri()->getQuery())); |
|||
} |
|||
|
|||
public function testVisitsMultipleLocations() |
|||
{ |
|||
$request = new Request('POST', 'http://httbin.org'); |
|||
|
|||
// First location |
|||
$location = new QueryLocation(); |
|||
$command = new Command('foo', ['foo' => 'bar']); |
|||
$param = new Parameter(['name' => 'foo']); |
|||
$request = $location->visit($command, $request, $param); |
|||
|
|||
// Second location |
|||
$location = new QueryLocation(); |
|||
$command = new Command('baz', ['baz' => [6, 7]]); |
|||
$param = new Parameter(['name' => 'baz']); |
|||
$request = $location->visit($command, $request, $param); |
|||
|
|||
$this->assertEquals('foo=bar&baz[0]=6&baz[1]=7', urldecode($request->getUri()->getQuery())); |
|||
} |
|||
|
|||
/** |
|||
* @group RequestLocation |
|||
*/ |
|||
public function testAddsAdditionalProperties() |
|||
{ |
|||
$location = new QueryLocation(); |
|||
$command = new Command('foo', ['foo' => 'bar']); |
|||
$command['add'] = 'props'; |
|||
$operation = new Operation([ |
|||
'additionalParameters' => [ |
|||
'location' => 'query' |
|||
] |
|||
]); |
|||
$request = new Request('POST', 'http://httbin.org'); |
|||
$request = $location->after($command, $request, $operation); |
|||
|
|||
$this->assertEquals('props', Psr7\parse_query($request->getUri()->getQuery())['add']); |
|||
} |
|||
} |
|||
@ -0,0 +1,525 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Tests\Command\Guzzle\RequestLocation; |
|||
|
|||
use GuzzleHttp\Client; |
|||
use GuzzleHttp\Command\Command; |
|||
use GuzzleHttp\Command\Guzzle\Description; |
|||
use GuzzleHttp\Command\Guzzle\GuzzleClient; |
|||
use GuzzleHttp\Command\Guzzle\Operation; |
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\Guzzle\RequestLocation\XmlLocation; |
|||
use GuzzleHttp\Handler\MockHandler; |
|||
use GuzzleHttp\HandlerStack; |
|||
use GuzzleHttp\Middleware; |
|||
use GuzzleHttp\Psr7\Request; |
|||
use GuzzleHttp\Psr7\Response; |
|||
|
|||
/** |
|||
* @covers \GuzzleHttp\Command\Guzzle\RequestLocation\XmlLocation |
|||
*/ |
|||
class XmlLocationTest extends \PHPUnit_Framework_TestCase |
|||
{ |
|||
/** |
|||
* @group RequestLocation |
|||
*/ |
|||
public function testVisitsLocation() |
|||
{ |
|||
$location = new XmlLocation(); |
|||
$command = new Command('foo', ['foo' => 'bar']); |
|||
$command['bar'] = 'test'; |
|||
$request = new Request('POST', 'http://httbin.org'); |
|||
$param = new Parameter(['name' => 'foo']); |
|||
$location->visit($command, $request, $param); |
|||
$param = new Parameter(['name' => 'bar']); |
|||
$location->visit($command, $request, $param); |
|||
$operation = new Operation(); |
|||
$request = $location->after($command, $request, $operation); |
|||
$xml = $request->getBody()->getContents(); |
|||
|
|||
$this->assertEquals('<?xml version="1.0"?>' . "\n" |
|||
. '<Request><foo>bar</foo><bar>test</bar></Request>' . "\n", $xml); |
|||
$header = $request->getHeader('Content-Type'); |
|||
$this->assertArraySubset([0 => 'application/xml'], $header); |
|||
} |
|||
|
|||
/** |
|||
* @group RequestLocation |
|||
*/ |
|||
public function testCreatesBodyForEmptyDocument() |
|||
{ |
|||
$location = new XmlLocation(); |
|||
$command = new Command('foo', ['foo' => 'bar']); |
|||
$request = new Request('POST', 'http://httbin.org'); |
|||
$operation = new Operation([ |
|||
'data' => ['xmlAllowEmpty' => true] |
|||
]); |
|||
$request = $location->after($command, $request, $operation); |
|||
$xml = $request->getBody()->getContents(); |
|||
$this->assertEquals('<?xml version="1.0"?>' . "\n" |
|||
. '<Request/>' . "\n", $xml); |
|||
|
|||
$header = $request->getHeader('Content-Type'); |
|||
$this->assertArraySubset([0 => 'application/xml'], $header); |
|||
} |
|||
|
|||
/** |
|||
* @group RequestLocation |
|||
*/ |
|||
public function testAddsAdditionalParameters() |
|||
{ |
|||
$location = new XmlLocation('xml', 'test'); |
|||
$command = new Command('foo', ['foo' => 'bar']); |
|||
$request = new Request('POST', 'http://httbin.org'); |
|||
$param = new Parameter(['name' => 'foo']); |
|||
$command['foo'] = 'bar'; |
|||
$location->visit($command, $request, $param); |
|||
$operation = new Operation([ |
|||
'additionalParameters' => [ |
|||
'location' => 'xml' |
|||
] |
|||
]); |
|||
$command['bam'] = 'boo'; |
|||
$request = $location->after($command, $request, $operation); |
|||
$xml = $request->getBody()->getContents(); |
|||
$this->assertEquals('<?xml version="1.0"?>' . "\n" |
|||
. '<Request><foo>bar</foo><foo>bar</foo><bam>boo</bam></Request>' . "\n", $xml); |
|||
$header = $request->getHeader('Content-Type'); |
|||
$this->assertArraySubset([0 => 'test'], $header); |
|||
} |
|||
|
|||
/** |
|||
* @group RequestLocation |
|||
*/ |
|||
public function testAllowsXmlEncoding() |
|||
{ |
|||
$location = new XmlLocation(); |
|||
$operation = new Operation([ |
|||
'data' => ['xmlEncoding' => 'UTF-8'] |
|||
]); |
|||
$command = new Command('foo', ['foo' => 'bar']); |
|||
$request = new Request('POST', 'http://httbin.org'); |
|||
$param = new Parameter(['name' => 'foo']); |
|||
$command['foo'] = 'bar'; |
|||
$location->visit($command, $request, $param); |
|||
$request = $location->after($command, $request, $operation); |
|||
$xml = $request->getBody()->getContents(); |
|||
$this->assertEquals('<?xml version="1.0" encoding="UTF-8"?>' . "\n" |
|||
. '<Request><foo>bar</foo></Request>' . "\n", $xml); |
|||
} |
|||
|
|||
public function xmlProvider() |
|||
{ |
|||
return [ |
|||
[ |
|||
[ |
|||
'data' => [ |
|||
'xmlRoot' => [ |
|||
'name' => 'test', |
|||
'namespaces' => 'http://foo.com' |
|||
] |
|||
], |
|||
'parameters' => [ |
|||
'Foo' => [ |
|||
'location' => 'xml', |
|||
'type' => 'string' |
|||
], |
|||
'Baz' => [ |
|||
'location' => 'xml', |
|||
'type' => 'string' |
|||
] |
|||
] |
|||
], |
|||
[ |
|||
'Foo' => 'test', |
|||
'Baz' => 'bar' |
|||
], |
|||
'<test xmlns="http://foo.com"><Foo>test</Foo><Baz>bar</Baz></test>' |
|||
], |
|||
// Ensure that the content-type is not added |
|||
[ |
|||
[ |
|||
'parameters' => [ |
|||
'Foo' => [ |
|||
'location' => 'xml', |
|||
'type' => 'string' |
|||
] |
|||
] |
|||
], |
|||
[], |
|||
'' |
|||
], |
|||
// Test with adding attributes and no namespace |
|||
[ |
|||
[ |
|||
'data' => [ |
|||
'xmlRoot' => [ |
|||
'name' => 'test' |
|||
] |
|||
], |
|||
'parameters' => [ |
|||
'Foo' => [ |
|||
'location' => 'xml', |
|||
'type' => 'string', |
|||
'data' => ['xmlAttribute' => true] |
|||
] |
|||
] |
|||
], |
|||
[ |
|||
'Foo' => 'test', |
|||
'Baz' => 'bar' |
|||
], |
|||
'<test Foo="test"/>' |
|||
], |
|||
// Test adding with an array |
|||
[ |
|||
[ |
|||
'parameters' => [ |
|||
'Foo' => [ |
|||
'location' => 'xml', |
|||
'type' => 'string' |
|||
], |
|||
'Baz' => [ |
|||
'type' => 'array', |
|||
'location' => 'xml', |
|||
'items' => [ |
|||
'type' => 'numeric', |
|||
'sentAs' => 'Bar' |
|||
] |
|||
] |
|||
] |
|||
], |
|||
['Foo' => 'test', 'Baz' => [1, 2]], |
|||
'<Request><Foo>test</Foo><Baz><Bar>1</Bar><Bar>2</Bar></Baz></Request>' |
|||
], |
|||
// Test adding an object |
|||
[ |
|||
[ |
|||
'parameters' => [ |
|||
'Foo' => ['location' => 'xml', 'type' => 'string'], |
|||
'Baz' => [ |
|||
'type' => 'object', |
|||
'location' => 'xml', |
|||
'properties' => [ |
|||
'Bar' => ['type' => 'string'], |
|||
'Bam' => [] |
|||
] |
|||
] |
|||
] |
|||
], |
|||
[ |
|||
'Foo' => 'test', |
|||
'Baz' => [ |
|||
'Bar' => 'abc', |
|||
'Bam' => 'foo' |
|||
] |
|||
], |
|||
'<Request><Foo>test</Foo><Baz><Bar>abc</Bar><Bam>foo</Bam></Baz></Request>' |
|||
], |
|||
// Add an array that contains an object |
|||
[ |
|||
[ |
|||
'parameters' => [ |
|||
'Baz' => [ |
|||
'type' => 'array', |
|||
'location' => 'xml', |
|||
'items' => [ |
|||
'type' => 'object', |
|||
'sentAs' => 'Bar', |
|||
'properties' => ['A' => [], 'B' => []] |
|||
] |
|||
] |
|||
] |
|||
], |
|||
['Baz' => [ |
|||
[ |
|||
'A' => '1', |
|||
'B' => '2' |
|||
], |
|||
[ |
|||
'A' => '3', |
|||
'B' => '4' |
|||
] |
|||
]], |
|||
'<Request><Baz><Bar><A>1</A><B>2</B></Bar><Bar><A>3</A><B>4</B></Bar></Baz></Request>' |
|||
], |
|||
// Add an object of attributes |
|||
[ |
|||
[ |
|||
'parameters' => [ |
|||
'Foo' => [ |
|||
'location' => 'xml', |
|||
'type' => 'string' |
|||
], |
|||
'Baz' => [ |
|||
'type' => 'object', |
|||
'location' => 'xml', |
|||
'properties' => [ |
|||
'Bar' => [ |
|||
'type' => 'string', |
|||
'data' => [ |
|||
'xmlAttribute' => true |
|||
] |
|||
], |
|||
'Bam' => [] |
|||
] |
|||
] |
|||
] |
|||
], |
|||
[ |
|||
'Foo' => 'test', |
|||
'Baz' => [ |
|||
'Bar' => 'abc', |
|||
'Bam' => 'foo' |
|||
] |
|||
], |
|||
'<Request><Foo>test</Foo><Baz Bar="abc"><Bam>foo</Bam></Baz></Request>' |
|||
], |
|||
// Check order doesn't matter |
|||
[ |
|||
[ |
|||
'parameters' => [ |
|||
'Foo' => [ |
|||
'location' => 'xml', |
|||
'type' => 'string' |
|||
], |
|||
'Baz' => [ |
|||
'type' => 'object', |
|||
'location' => 'xml', |
|||
'properties' => [ |
|||
'Bar' => [ |
|||
'type' => 'string', |
|||
'data' => [ |
|||
'xmlAttribute' => true |
|||
] |
|||
], |
|||
'Bam' => [] |
|||
] |
|||
] |
|||
] |
|||
], |
|||
[ |
|||
'Foo' => 'test', |
|||
'Baz' => [ |
|||
'Bam' => 'foo', |
|||
'Bar' => 'abc' |
|||
] |
|||
], |
|||
'<Request><Foo>test</Foo><Baz Bar="abc"><Bam>foo</Bam></Baz></Request>' |
|||
], |
|||
// Add values with custom namespaces |
|||
[ |
|||
[ |
|||
'parameters' => [ |
|||
'Foo' => [ |
|||
'location' => 'xml', |
|||
'type' => 'string', |
|||
'data' => [ |
|||
'xmlNamespace' => 'http://foo.com' |
|||
] |
|||
] |
|||
] |
|||
], |
|||
['Foo' => 'test'], |
|||
'<Request><Foo xmlns="http://foo.com">test</Foo></Request>' |
|||
], |
|||
// Add attributes with custom namespace prefix |
|||
[ |
|||
[ |
|||
'parameters' => [ |
|||
'Wrap' => [ |
|||
'type' => 'object', |
|||
'location' => 'xml', |
|||
'properties' => [ |
|||
'Foo' => [ |
|||
'type' => 'string', |
|||
'sentAs' => 'xsi:baz', |
|||
'data' => [ |
|||
'xmlNamespace' => 'http://foo.com', |
|||
'xmlAttribute' => true |
|||
] |
|||
] |
|||
] |
|||
], |
|||
] |
|||
], |
|||
['Wrap' => [ |
|||
'Foo' => 'test' |
|||
]], |
|||
'<Request><Wrap xsi:baz="test" xmlns:xsi="http://foo.com"/></Request>' |
|||
], |
|||
// Add nodes with custom namespace prefix |
|||
[ |
|||
[ |
|||
'parameters' => [ |
|||
'Wrap' => [ |
|||
'type' => 'object', |
|||
'location' => 'xml', |
|||
'properties' => [ |
|||
'Foo' => [ |
|||
'type' => 'string', |
|||
'sentAs' => 'xsi:Foo', |
|||
'data' => [ |
|||
'xmlNamespace' => 'http://foobar.com' |
|||
] |
|||
] |
|||
] |
|||
], |
|||
] |
|||
], |
|||
['Wrap' => [ |
|||
'Foo' => 'test' |
|||
]], |
|||
'<Request><Wrap><xsi:Foo xmlns:xsi="http://foobar.com">test</xsi:Foo></Wrap></Request>' |
|||
], |
|||
[ |
|||
[ |
|||
'parameters' => [ |
|||
'Foo' => [ |
|||
'location' => 'xml', |
|||
'type' => 'string', |
|||
'data' => [ |
|||
'xmlNamespace' => 'http://foo.com' |
|||
] |
|||
] |
|||
] |
|||
], |
|||
['Foo' => '<h1>This is a title</h1>'], |
|||
'<Request><Foo xmlns="http://foo.com"><![CDATA[<h1>This is a title</h1>]]></Foo></Request>' |
|||
], |
|||
// Flat array at top level |
|||
[ |
|||
[ |
|||
'parameters' => [ |
|||
'Bars' => [ |
|||
'type' => 'array', |
|||
'data' => ['xmlFlattened' => true], |
|||
'location' => 'xml', |
|||
'items' => [ |
|||
'type' => 'object', |
|||
'sentAs' => 'Bar', |
|||
'properties' => [ |
|||
'A' => [], |
|||
'B' => [] |
|||
] |
|||
] |
|||
], |
|||
'Boos' => [ |
|||
'type' => 'array', |
|||
'data' => ['xmlFlattened' => true], |
|||
'location' => 'xml', |
|||
'items' => [ |
|||
'sentAs' => 'Boo', |
|||
'type' => 'string' |
|||
] |
|||
] |
|||
] |
|||
], |
|||
[ |
|||
'Bars' => [ |
|||
['A' => '1', 'B' => '2'], |
|||
['A' => '3', 'B' => '4'] |
|||
], |
|||
'Boos' => ['test', '123'] |
|||
], |
|||
'<Request><Bar><A>1</A><B>2</B></Bar><Bar><A>3</A><B>4</B></Bar><Boo>test</Boo><Boo>123</Boo></Request>' |
|||
], |
|||
// Nested flat arrays |
|||
[ |
|||
[ |
|||
'parameters' => [ |
|||
'Delete' => [ |
|||
'type' => 'object', |
|||
'location' => 'xml', |
|||
'properties' => [ |
|||
'Items' => [ |
|||
'type' => 'array', |
|||
'data' => ['xmlFlattened' => true], |
|||
'items' => [ |
|||
'type' => 'object', |
|||
'sentAs' => 'Item', |
|||
'properties' => [ |
|||
'A' => [], |
|||
'B' => [] |
|||
] |
|||
] |
|||
] |
|||
] |
|||
] |
|||
] |
|||
], |
|||
[ |
|||
'Delete' => [ |
|||
'Items' => [ |
|||
['A' => '1', 'B' => '2'], |
|||
['A' => '3', 'B' => '4'] |
|||
] |
|||
] |
|||
], |
|||
'<Request><Delete><Item><A>1</A><B>2</B></Item><Item><A>3</A><B>4</B></Item></Delete></Request>' |
|||
], |
|||
// Test adding root node attributes after nodes |
|||
[ |
|||
[ |
|||
'data' => [ |
|||
'xmlRoot' => [ |
|||
'name' => 'test' |
|||
] |
|||
], |
|||
'parameters' => [ |
|||
'Foo' => ['location' => 'xml', 'type' => 'string'], |
|||
'Baz' => ['location' => 'xml', 'type' => 'string', 'data' => ['xmlAttribute' => true]], |
|||
] |
|||
], |
|||
['Foo' => 'test', 'Baz' => 'bar'], |
|||
'<test Baz="bar"><Foo>test</Foo></test>' |
|||
], |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* @param array $operation |
|||
* @param array $input |
|||
* @param string $xml |
|||
* @dataProvider xmlProvider |
|||
* @group RequestLocation |
|||
*/ |
|||
public function testSerializesXml(array $operation, array $input, $xml) |
|||
{ |
|||
$container = []; |
|||
$history = Middleware::history($container); |
|||
$mock = new MockHandler([new Response(200)]); |
|||
|
|||
$stack = new HandlerStack($mock); |
|||
$stack->push($history); |
|||
$operation['uri'] = 'http://httpbin.org'; |
|||
$client = new GuzzleClient( |
|||
new Client(['handler' => $stack]), |
|||
new Description([ |
|||
'operations' => [ |
|||
'foo' => $operation |
|||
] |
|||
]) |
|||
); |
|||
|
|||
$command = $client->getCommand('foo', $input); |
|||
|
|||
$client->execute($command); |
|||
|
|||
$this->assertCount(1, $container); |
|||
|
|||
foreach ($container as $transaction) { |
|||
/** @var Request $request */ |
|||
$request = $transaction['request']; |
|||
if (empty($input)) { |
|||
if ($request->hasHeader('Content-Type')) { |
|||
$this->assertArraySubset([0 => ''], $request->getHeader('Content-Type')); |
|||
} |
|||
} else { |
|||
$this->assertArraySubset([0 => 'application/xml'], $request->getHeader('Content-Type')); |
|||
} |
|||
|
|||
$body = str_replace(["\n", "<?xml version=\"1.0\"?>"], '', (string) $request->getBody()); |
|||
$this->assertEquals($xml, $body); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Tests\Command\Guzzle\ResponseLocation; |
|||
|
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\Guzzle\ResponseLocation\BodyLocation; |
|||
use GuzzleHttp\Command\Result; |
|||
use GuzzleHttp\Psr7\Response; |
|||
|
|||
/** |
|||
* @covers \GuzzleHttp\Command\Guzzle\ResponseLocation\BodyLocation |
|||
* @covers \GuzzleHttp\Command\Guzzle\ResponseLocation\AbstractLocation |
|||
*/ |
|||
class BodyLocationTest extends \PHPUnit_Framework_TestCase |
|||
{ |
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testVisitsLocation() |
|||
{ |
|||
$location = new BodyLocation(); |
|||
$parameter = new Parameter([ |
|||
'name' => 'val', |
|||
'filters' => ['strtoupper'] |
|||
]); |
|||
$response = new Response(200, [], 'foo'); |
|||
$result = new Result(); |
|||
$result = $location->visit($result, $response, $parameter); |
|||
$this->assertEquals('FOO', $result['val']); |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Tests\Command\Guzzle\ResponseLocation; |
|||
|
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\Guzzle\ResponseLocation\HeaderLocation; |
|||
use GuzzleHttp\Command\Result; |
|||
use GuzzleHttp\Psr7\Response; |
|||
|
|||
/** |
|||
* @covers \GuzzleHttp\Command\Guzzle\ResponseLocation\HeaderLocation |
|||
* @covers \GuzzleHttp\Command\Guzzle\ResponseLocation\AbstractLocation |
|||
*/ |
|||
class HeaderLocationTest extends \PHPUnit_Framework_TestCase |
|||
{ |
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testVisitsLocation() |
|||
{ |
|||
$location = new HeaderLocation(); |
|||
$parameter = new Parameter([ |
|||
'name' => 'val', |
|||
'sentAs' => 'X-Foo', |
|||
'filters' => ['strtoupper'] |
|||
]); |
|||
$response = new Response(200, ['X-Foo' => 'bar']); |
|||
$result = new Result(); |
|||
$result = $location->visit($result, $response, $parameter); |
|||
$this->assertEquals('BAR', $result['val']); |
|||
} |
|||
} |
|||
@ -0,0 +1,581 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Tests\Command\Guzzle\ResponseLocation; |
|||
|
|||
use GuzzleHttp\Client; |
|||
use GuzzleHttp\Command\Guzzle\Description; |
|||
use GuzzleHttp\Command\Guzzle\GuzzleClient; |
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\Guzzle\ResponseLocation\JsonLocation; |
|||
use GuzzleHttp\Command\Result; |
|||
use GuzzleHttp\Command\ResultInterface; |
|||
use GuzzleHttp\Handler\MockHandler; |
|||
use GuzzleHttp\Psr7\Response; |
|||
|
|||
/** |
|||
* @covers \GuzzleHttp\Command\Guzzle\ResponseLocation\JsonLocation |
|||
* @covers \GuzzleHttp\Command\Guzzle\Deserializer |
|||
*/ |
|||
class JsonLocationTest extends \PHPUnit_Framework_TestCase |
|||
{ |
|||
|
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testVisitsLocation() |
|||
{ |
|||
$location = new JsonLocation(); |
|||
$parameter = new Parameter([ |
|||
'name' => 'val', |
|||
'sentAs' => 'vim', |
|||
'filters' => ['strtoupper'] |
|||
]); |
|||
$response = new Response(200, [], '{"vim":"bar"}'); |
|||
$result = new Result(); |
|||
$result = $location->before($result, $response, $parameter); |
|||
$result = $location->visit($result, $response, $parameter); |
|||
$this->assertEquals('BAR', $result['val']); |
|||
} |
|||
/** |
|||
* @group ResponseLocation |
|||
* @param $name |
|||
* @param $expected |
|||
*/ |
|||
public function testVisitsWiredArray() |
|||
{ |
|||
$json = ['car_models' => ['ferrari', 'aston martin']]; |
|||
$body = \GuzzleHttp\json_encode($json); |
|||
$response = new Response(200, ['Content-Type' => 'application/json'], $body); |
|||
$mock = new MockHandler([$response]); |
|||
|
|||
$guzzle = new Client(['handler' => $mock]); |
|||
|
|||
$description = new Description([ |
|||
'operations' => [ |
|||
'getCars' => [ |
|||
'uri' => 'http://httpbin.org', |
|||
'httpMethod' => 'GET', |
|||
'responseModel' => 'Cars' |
|||
] |
|||
], |
|||
'models' => [ |
|||
'Cars' => [ |
|||
'type' => 'object', |
|||
'location' => 'json', |
|||
'properties' => [ |
|||
'cars' => [ |
|||
'type' => 'array', |
|||
'sentAs' => 'car_models', |
|||
'items' => [ |
|||
'type' => 'object', |
|||
] |
|||
] |
|||
], |
|||
] |
|||
] |
|||
]); |
|||
|
|||
$guzzle = new GuzzleClient($guzzle, $description); |
|||
$result = $guzzle->getCars(); |
|||
|
|||
$this->assertEquals(['cars' => ['ferrari', 'aston martin']], $result->toArray()); |
|||
} |
|||
|
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testVisitsAdditionalProperties() |
|||
{ |
|||
$location = new JsonLocation(); |
|||
$parameter = new Parameter(); |
|||
$model = new Parameter(['additionalProperties' => ['location' => 'json']]); |
|||
$response = new Response(200, [], '{"vim":"bar","qux":[1,2]}'); |
|||
$result = new Result(); |
|||
$result = $location->before($result, $response, $parameter); |
|||
$result = $location->visit($result, $response, $parameter); |
|||
$result = $location->after($result, $response, $model); |
|||
$this->assertEquals('bar', $result['vim']); |
|||
$this->assertEquals([1, 2], $result['qux']); |
|||
} |
|||
|
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testVisitsAdditionalPropertiesWithEmptyResponse() |
|||
{ |
|||
$location = new JsonLocation(); |
|||
$parameter = new Parameter(); |
|||
$model = new Parameter(['additionalProperties' => ['location' => 'json']]); |
|||
$response = new Response(204); |
|||
$result = new Result(); |
|||
$result = $location->before($result, $response, $parameter); |
|||
$result = $location->visit($result, $response, $parameter); |
|||
$result = $location->after($result, $response, $model); |
|||
$this->assertEquals([], $result->toArray()); |
|||
} |
|||
|
|||
public function jsonProvider() |
|||
{ |
|||
return [ |
|||
[null, [['foo' => 'BAR'], ['baz' => 'BAM']]], |
|||
['under_me', ['under_me' => [['foo' => 'BAR'], ['baz' => 'BAM']]]], |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* @dataProvider jsonProvider |
|||
* @group ResponseLocation |
|||
* @param $name |
|||
* @param $expected |
|||
*/ |
|||
public function testVisitsTopLevelArrays($name, $expected) |
|||
{ |
|||
$json = [ |
|||
['foo' => 'bar'], |
|||
['baz' => 'bam'], |
|||
]; |
|||
$body = \GuzzleHttp\json_encode($json); |
|||
$response = new Response(200, ['Content-Type' => 'application/json'], $body); |
|||
$mock = new MockHandler([$response]); |
|||
|
|||
$guzzle = new Client(['handler' => $mock]); |
|||
|
|||
$description = new Description([ |
|||
'operations' => [ |
|||
'foo' => [ |
|||
'uri' => 'http://httpbin.org', |
|||
'httpMethod' => 'GET', |
|||
'responseModel' => 'j' |
|||
] |
|||
], |
|||
'models' => [ |
|||
'j' => [ |
|||
'type' => 'array', |
|||
'location' => 'json', |
|||
'name' => $name, |
|||
'items' => [ |
|||
'type' => 'object', |
|||
'additionalProperties' => [ |
|||
'type' => 'string', |
|||
'filters' => ['strtoupper'] |
|||
] |
|||
] |
|||
] |
|||
] |
|||
]); |
|||
$guzzle = new GuzzleClient($guzzle, $description); |
|||
/** @var ResultInterface $result */ |
|||
$result = $guzzle->foo(); |
|||
$this->assertEquals($expected, $result->toArray()); |
|||
} |
|||
|
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testVisitsNestedArrays() |
|||
{ |
|||
$json = [ |
|||
'scalar' => 'foo', |
|||
'nested' => [ |
|||
'bar', |
|||
'baz' |
|||
] |
|||
]; |
|||
$body = \GuzzleHttp\json_encode($json); |
|||
$response = new Response(200, ['Content-Type' => 'application/json'], $body); |
|||
$mock = new MockHandler([$response]); |
|||
|
|||
$httpClient = new Client(['handler' => $mock]); |
|||
|
|||
$description = new Description([ |
|||
'operations' => [ |
|||
'foo' => [ |
|||
'uri' => 'http://httpbin.org', |
|||
'httpMethod' => 'GET', |
|||
'responseModel' => 'j' |
|||
] |
|||
], |
|||
'models' => [ |
|||
'j' => [ |
|||
'type' => 'object', |
|||
'location' => 'json', |
|||
'properties' => [ |
|||
'scalar' => ['type' => 'string'], |
|||
'nested' => [ |
|||
'type' => 'array', |
|||
'items' => ['type' => 'string'] |
|||
] |
|||
] |
|||
] |
|||
] |
|||
]); |
|||
$guzzle = new GuzzleClient($httpClient, $description); |
|||
/** @var ResultInterface $result */ |
|||
$result = $guzzle->foo(); |
|||
$expected = [ |
|||
'scalar' => 'foo', |
|||
'nested' => [ |
|||
'bar', |
|||
'baz' |
|||
] |
|||
]; |
|||
$this->assertEquals($expected, $result->toArray()); |
|||
} |
|||
|
|||
public function nestedProvider() |
|||
{ |
|||
return [ |
|||
[ |
|||
[ |
|||
'operations' => [ |
|||
'foo' => [ |
|||
'uri' => 'http://httpbin.org', |
|||
'httpMethod' => 'GET', |
|||
'responseModel' => 'j' |
|||
] |
|||
], |
|||
'models' => [ |
|||
'j' => [ |
|||
'type' => 'object', |
|||
'properties' => [ |
|||
'nested' => [ |
|||
'location' => 'json', |
|||
'type' => 'object', |
|||
'properties' => [ |
|||
'foo' => ['type' => 'string'], |
|||
'bar' => ['type' => 'number'], |
|||
'bam' => [ |
|||
'type' => 'object', |
|||
'properties' => [ |
|||
'abc' => [ |
|||
'type' => 'number' |
|||
] |
|||
] |
|||
] |
|||
] |
|||
] |
|||
], |
|||
'additionalProperties' => [ |
|||
'location' => 'json', |
|||
'type' => 'string', |
|||
'filters' => ['strtoupper'] |
|||
] |
|||
] |
|||
] |
|||
] |
|||
], |
|||
[ |
|||
[ |
|||
'operations' => [ |
|||
'foo' => [ |
|||
'uri' => 'http://httpbin.org', |
|||
'httpMethod' => 'GET', |
|||
'responseModel' => 'j' |
|||
] |
|||
], |
|||
'models' => [ |
|||
'j' => [ |
|||
'type' => 'object', |
|||
'location' => 'json', |
|||
'properties' => [ |
|||
'nested' => [ |
|||
'type' => 'object', |
|||
'properties' => [ |
|||
'foo' => ['type' => 'string'], |
|||
'bar' => ['type' => 'number'], |
|||
'bam' => [ |
|||
'type' => 'object', |
|||
'properties' => [ |
|||
'abc' => [ |
|||
'type' => 'number' |
|||
] |
|||
] |
|||
] |
|||
] |
|||
] |
|||
], |
|||
'additionalProperties' => [ |
|||
'type' => 'string', |
|||
'filters' => ['strtoupper'] |
|||
] |
|||
] |
|||
] |
|||
] |
|||
] |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* @dataProvider nestedProvider |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testVisitsNestedProperties($desc) |
|||
{ |
|||
$json = [ |
|||
'nested' => [ |
|||
'foo' => 'abc', |
|||
'bar' => 123, |
|||
'bam' => [ |
|||
'abc' => 456 |
|||
] |
|||
], |
|||
'baz' => 'boo' |
|||
]; |
|||
$body = \GuzzleHttp\json_encode($json); |
|||
$response = new Response(200, ['Content-Type' => 'application/json'], $body); |
|||
$mock = new MockHandler([$response]); |
|||
|
|||
$httpClient = new Client(['handler' => $mock]); |
|||
|
|||
$description = new Description($desc); |
|||
$guzzle = new GuzzleClient($httpClient, $description); |
|||
/** @var ResultInterface $result */ |
|||
$result = $guzzle->foo(); |
|||
$expected = [ |
|||
'nested' => [ |
|||
'foo' => 'abc', |
|||
'bar' => 123, |
|||
'bam' => [ |
|||
'abc' => 456 |
|||
] |
|||
], |
|||
'baz' => 'BOO' |
|||
]; |
|||
|
|||
$this->assertEquals($expected, $result->toArray()); |
|||
} |
|||
|
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testVisitsNullResponseProperties() |
|||
{ |
|||
|
|||
$json = [ |
|||
'data' => [ |
|||
'link' => null |
|||
] |
|||
]; |
|||
|
|||
$body = \GuzzleHttp\json_encode($json); |
|||
$response = new Response(200, ['Content-Type' => 'application/json'], $body); |
|||
$mock = new MockHandler([$response]); |
|||
|
|||
$httpClient = new Client(['handler' => $mock]); |
|||
|
|||
$description = new Description( |
|||
[ |
|||
'operations' => [ |
|||
'foo' => [ |
|||
'uri' => 'http://httpbin.org', |
|||
'httpMethod' => 'GET', |
|||
'responseModel' => 'j' |
|||
] |
|||
], |
|||
'models' => [ |
|||
'j' => [ |
|||
'type' => 'object', |
|||
'location' => 'json', |
|||
'properties' => [ |
|||
'scalar' => ['type' => 'string'], |
|||
'data' => [ |
|||
'type' => 'object', |
|||
'location' => 'json', |
|||
'properties' => [ |
|||
'link' => [ |
|||
'name' => 'val', |
|||
'type' => 'string', |
|||
'location' => 'json' |
|||
], |
|||
], |
|||
'additionalProperties' => false |
|||
] |
|||
] |
|||
] |
|||
] |
|||
] |
|||
); |
|||
$guzzle = new GuzzleClient($httpClient, $description); |
|||
/** @var ResultInterface $result */ |
|||
$result = $guzzle->foo(); |
|||
|
|||
$expected = [ |
|||
'data' => [ |
|||
'link' => null |
|||
] |
|||
]; |
|||
|
|||
$this->assertEquals($expected, $result->toArray()); |
|||
} |
|||
|
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testVisitsNestedArrayOfArrays() |
|||
{ |
|||
$json = [ |
|||
'scalar' => 'foo', |
|||
'nested' => [ |
|||
[ |
|||
'bar' => 123, |
|||
'baz' => false, |
|||
], |
|||
[ |
|||
'bar' => 345, |
|||
'baz' => true, |
|||
], |
|||
[ |
|||
'bar' => 678, |
|||
'baz' => true, |
|||
], |
|||
] |
|||
]; |
|||
|
|||
$body = \GuzzleHttp\json_encode($json); |
|||
$response = new Response(200, ['Content-Type' => 'application/json'], $body); |
|||
$mock = new MockHandler([$response]); |
|||
|
|||
$httpClient = new Client(['handler' => $mock]); |
|||
|
|||
$description = new Description([ |
|||
'operations' => [ |
|||
'foo' => [ |
|||
'uri' => 'http://httpbin.org', |
|||
'httpMethod' => 'GET', |
|||
'responseModel' => 'j' |
|||
] |
|||
], |
|||
'models' => [ |
|||
'j' => [ |
|||
'type' => 'object', |
|||
'properties' => [ |
|||
'scalar' => [ |
|||
// for some reason (probably because location is also set on array of arrays) |
|||
// array of arrays sibling elements must have location set to `json` |
|||
// otherwise JsonLocation ignores them |
|||
'location' => 'json', |
|||
'type' => 'string' |
|||
], |
|||
'nested' => [ |
|||
// array of arrays type must be set to `array` |
|||
// without that JsonLocation throws an exception |
|||
'type' => 'array', |
|||
// for array of arrays `location` must be set to `json` |
|||
// otherwise JsonLocation returns an empty array |
|||
'location' => 'json', |
|||
'items' => [ |
|||
// although this is array of arrays, array items type |
|||
// must be set as `object` |
|||
'type' => 'object', |
|||
'properties' => [ |
|||
'bar' => [ |
|||
'type' => 'integer', |
|||
], |
|||
'baz' => [ |
|||
'type' => 'boolean', |
|||
], |
|||
], |
|||
] |
|||
] |
|||
] |
|||
] |
|||
] |
|||
]); |
|||
|
|||
$guzzle = new GuzzleClient($httpClient, $description); |
|||
/** @var ResultInterface $result */ |
|||
$result = $guzzle->foo(); |
|||
$expected = [ |
|||
'scalar' => 'foo', |
|||
'nested' => [ |
|||
[ |
|||
'bar' => 123, |
|||
'baz' => false, |
|||
], |
|||
[ |
|||
'bar' => 345, |
|||
'baz' => true, |
|||
], |
|||
[ |
|||
'bar' => 678, |
|||
'baz' => true, |
|||
], |
|||
] |
|||
]; |
|||
|
|||
$this->assertEquals($expected, $result->toArray()); |
|||
} |
|||
|
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testVisitsNestedArrayOfObjects() |
|||
{ |
|||
$json = json_decode('{"scalar":"foo","nested":[{"bar":123,"baz":false},{"bar":345,"baz":true},{"bar":678,"baz":true}]}'); |
|||
|
|||
$body = \GuzzleHttp\json_encode($json); |
|||
$response = new Response(200, ['Content-Type' => 'application/json'], $body); |
|||
$mock = new MockHandler([$response]); |
|||
|
|||
$httpClient = new Client(['handler' => $mock]); |
|||
|
|||
$description = new Description([ |
|||
'operations' => [ |
|||
'foo' => [ |
|||
'uri' => 'http://httpbin.org', |
|||
'httpMethod' => 'GET', |
|||
'responseModel' => 'j' |
|||
] |
|||
], |
|||
'models' => [ |
|||
'j' => [ |
|||
'type' => 'object', |
|||
'location' => 'json', |
|||
'properties' => [ |
|||
'scalar' => [ |
|||
'type' => 'string' |
|||
], |
|||
'nested' => [ |
|||
// array of objects type must be set to `array` |
|||
// without that JsonLocation throws an exception |
|||
'type' => 'array', |
|||
'items' => [ |
|||
// array elements type must be set to `object` |
|||
'type' => 'object', |
|||
'properties' => [ |
|||
'bar' => [ |
|||
'type' => 'integer', |
|||
], |
|||
'baz' => [ |
|||
'type' => 'boolean', |
|||
], |
|||
], |
|||
] |
|||
] |
|||
] |
|||
] |
|||
] |
|||
]); |
|||
|
|||
$guzzle = new GuzzleClient($httpClient, $description); |
|||
/** @var ResultInterface $result */ |
|||
$result = $guzzle->foo(); |
|||
$expected = [ |
|||
'scalar' => 'foo', |
|||
'nested' => [ |
|||
[ |
|||
'bar' => 123, |
|||
'baz' => false, |
|||
], |
|||
[ |
|||
'bar' => 345, |
|||
'baz' => true, |
|||
], |
|||
[ |
|||
'bar' => 678, |
|||
'baz' => true, |
|||
], |
|||
] |
|||
]; |
|||
$this->assertEquals($expected, $result->toArray()); |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Tests\Command\Guzzle\ResponseLocation; |
|||
|
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\Guzzle\ResponseLocation\ReasonPhraseLocation; |
|||
use GuzzleHttp\Command\Result; |
|||
use GuzzleHttp\Psr7\Response; |
|||
|
|||
/** |
|||
* @covers \GuzzleHttp\Command\Guzzle\ResponseLocation\ReasonPhraseLocation |
|||
* @covers \GuzzleHttp\Command\Guzzle\ResponseLocation\AbstractLocation |
|||
*/ |
|||
class ReasonPhraseLocationTest extends \PHPUnit_Framework_TestCase |
|||
{ |
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testVisitsLocation() |
|||
{ |
|||
$location = new ReasonPhraseLocation(); |
|||
$parameter = new Parameter([ |
|||
'name' => 'val', |
|||
'filters' => ['strtolower'] |
|||
]); |
|||
$response = new Response(200); |
|||
$result = new Result(); |
|||
$result = $location->visit($result, $response, $parameter); |
|||
$this->assertEquals('ok', $result['val']); |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Tests\Command\Guzzle\ResponseLocation; |
|||
|
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\Guzzle\ResponseLocation\StatusCodeLocation; |
|||
use GuzzleHttp\Command\Result; |
|||
use GuzzleHttp\Psr7\Response; |
|||
|
|||
/** |
|||
* @covers \GuzzleHttp\Command\Guzzle\ResponseLocation\StatusCodeLocation |
|||
* @covers \GuzzleHttp\Command\Guzzle\ResponseLocation\AbstractLocation |
|||
*/ |
|||
class StatusCodeLocationTest extends \PHPUnit_Framework_TestCase |
|||
{ |
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testVisitsLocation() |
|||
{ |
|||
$location = new StatusCodeLocation(); |
|||
$parameter = new Parameter(['name' => 'val']); |
|||
$response = new Response(200); |
|||
$result = new Result(); |
|||
$result = $location->visit($result, $response, $parameter); |
|||
$this->assertEquals(200, $result['val']); |
|||
} |
|||
} |
|||
@ -0,0 +1,795 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Tests\Command\Guzzle\ResponseLocation; |
|||
|
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\Guzzle\ResponseLocation\XmlLocation; |
|||
use GuzzleHttp\Command\Result; |
|||
use GuzzleHttp\Psr7\Response; |
|||
|
|||
/** |
|||
* @covers \GuzzleHttp\Command\Guzzle\ResponseLocation\XmlLocation |
|||
*/ |
|||
class XmlLocationTest extends \PHPUnit_Framework_TestCase |
|||
{ |
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testVisitsLocation() |
|||
{ |
|||
$location = new XmlLocation(); |
|||
$parameter = new Parameter([ |
|||
'name' => 'val', |
|||
'sentAs' => 'vim', |
|||
'filters' => ['strtoupper'] |
|||
]); |
|||
$model = new Parameter(); |
|||
$response = new Response(200, [], \GuzzleHttp\Psr7\stream_for('<w><vim>bar</vim></w>')); |
|||
$result = new Result(); |
|||
$result = $location->before($result, $response, $model); |
|||
$result = $location->visit($result, $response, $parameter); |
|||
$result = $location->after($result, $response, $model); |
|||
$this->assertEquals('BAR', $result['val']); |
|||
} |
|||
|
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testVisitsAdditionalProperties() |
|||
{ |
|||
$location = new XmlLocation(); |
|||
$parameter = new Parameter(); |
|||
$model = new Parameter(['additionalProperties' => ['location' => 'xml']]); |
|||
$response = new Response(200, [], \GuzzleHttp\Psr7\stream_for('<w><vim>bar</vim></w>')); |
|||
$result = new Result(); |
|||
$result = $location->before($result, $response, $parameter); |
|||
$result = $location->visit($result, $response, $parameter); |
|||
$result = $location->after($result, $response, $model); |
|||
$this->assertEquals('bar', $result['vim']); |
|||
} |
|||
|
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testEnsuresFlatArraysAreFlat() |
|||
{ |
|||
$param = new Parameter([ |
|||
'location' => 'xml', |
|||
'name' => 'foo', |
|||
'type' => 'array', |
|||
'items' => ['type' => 'string'], |
|||
]); |
|||
|
|||
$xml = '<xml><foo>bar</foo><foo>baz</foo></xml>'; |
|||
$this->xmlTest($param, $xml, ['foo' => ['bar', 'baz']]); |
|||
$this->xmlTest($param, '<xml><foo>bar</foo></xml>', ['foo' => ['bar']]); |
|||
} |
|||
|
|||
public function xmlDataProvider() |
|||
{ |
|||
$param = new Parameter([ |
|||
'location' => 'xml', |
|||
'name' => 'Items', |
|||
'type' => 'array', |
|||
'items' => [ |
|||
'type' => 'object', |
|||
'name' => 'Item', |
|||
'properties' => [ |
|||
'Bar' => ['type' => 'string'], |
|||
'Baz' => ['type' => 'string'], |
|||
], |
|||
], |
|||
]); |
|||
|
|||
return [ |
|||
[$param, '<Test><Items><Item><Bar>1</Bar></Item><Item><Bar>2</Bar></Item></Items></Test>', [ |
|||
'Items' => [ |
|||
['Bar' => 1], |
|||
['Bar' => 2], |
|||
], |
|||
]], |
|||
[$param, '<Test><Items><Item><Bar>1</Bar></Item></Items></Test>', [ |
|||
'Items' => [ |
|||
['Bar' => 1], |
|||
] |
|||
]], |
|||
[$param, '<Test><Items /></Test>', [ |
|||
'Items' => [], |
|||
]] |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* @dataProvider xmlDataProvider |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testEnsuresWrappedArraysAreInCorrectLocations($param, $xml, $expected) |
|||
{ |
|||
$location = new XmlLocation(); |
|||
$model = new Parameter(); |
|||
$response = new Response(200, [], \GuzzleHttp\Psr7\stream_for($xml)); |
|||
$result = new Result(); |
|||
$result = $location->before($result, $response, $param); |
|||
$result = $location->visit($result, $response, $param); |
|||
$result = $location->after($result, $response, $model); |
|||
$this->assertEquals($expected, $result->toArray()); |
|||
} |
|||
|
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testCanRenameValues() |
|||
{ |
|||
$param = new Parameter([ |
|||
'name' => 'TerminatingInstances', |
|||
'type' => 'array', |
|||
'location' => 'xml', |
|||
'sentAs' => 'instancesSet', |
|||
'items' => [ |
|||
'name' => 'item', |
|||
'type' => 'object', |
|||
'sentAs' => 'item', |
|||
'properties' => [ |
|||
'InstanceId' => [ |
|||
'type' => 'string', |
|||
'sentAs' => 'instanceId', |
|||
], |
|||
'CurrentState' => [ |
|||
'type' => 'object', |
|||
'sentAs' => 'currentState', |
|||
'properties' => [ |
|||
'Code' => [ |
|||
'type' => 'numeric', |
|||
'sentAs' => 'code', |
|||
], |
|||
'Name' => [ |
|||
'type' => 'string', |
|||
'sentAs' => 'name', |
|||
], |
|||
], |
|||
], |
|||
'PreviousState' => [ |
|||
'type' => 'object', |
|||
'sentAs' => 'previousState', |
|||
'properties' => [ |
|||
'Code' => [ |
|||
'type' => 'numeric', |
|||
'sentAs' => 'code', |
|||
], |
|||
'Name' => [ |
|||
'type' => 'string', |
|||
'sentAs' => 'name', |
|||
], |
|||
], |
|||
], |
|||
], |
|||
] |
|||
]); |
|||
|
|||
$xml = ' |
|||
<xml> |
|||
<instancesSet> |
|||
<item> |
|||
<instanceId>i-3ea74257</instanceId> |
|||
<currentState> |
|||
<code>32</code> |
|||
<name>shutting-down</name> |
|||
</currentState> |
|||
<previousState> |
|||
<code>16</code> |
|||
<name>running</name> |
|||
</previousState> |
|||
</item> |
|||
</instancesSet> |
|||
</xml> |
|||
'; |
|||
|
|||
$this->xmlTest($param, $xml, [ |
|||
'TerminatingInstances' => [ |
|||
[ |
|||
'InstanceId' => 'i-3ea74257', |
|||
'CurrentState' => [ |
|||
'Code' => '32', |
|||
'Name' => 'shutting-down', |
|||
], |
|||
'PreviousState' => [ |
|||
'Code' => '16', |
|||
'Name' => 'running', |
|||
], |
|||
], |
|||
], |
|||
]); |
|||
} |
|||
|
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testCanRenameAttributes() |
|||
{ |
|||
$param = new Parameter([ |
|||
'name' => 'RunningQueues', |
|||
'type' => 'array', |
|||
'location' => 'xml', |
|||
'items' => [ |
|||
'type' => 'object', |
|||
'sentAs' => 'item', |
|||
'properties' => [ |
|||
'QueueId' => [ |
|||
'type' => 'string', |
|||
'sentAs' => 'queue_id', |
|||
'data' => [ |
|||
'xmlAttribute' => true, |
|||
], |
|||
], |
|||
'CurrentState' => [ |
|||
'type' => 'object', |
|||
'properties' => [ |
|||
'Code' => [ |
|||
'type' => 'numeric', |
|||
'sentAs' => 'code', |
|||
'data' => [ |
|||
'xmlAttribute' => true, |
|||
], |
|||
], |
|||
'Name' => [ |
|||
'sentAs' => 'name', |
|||
'data' => [ |
|||
'xmlAttribute' => true, |
|||
], |
|||
], |
|||
], |
|||
], |
|||
'PreviousState' => [ |
|||
'type' => 'object', |
|||
'properties' => [ |
|||
'Code' => [ |
|||
'type' => 'numeric', |
|||
'sentAs' => 'code', |
|||
'data' => [ |
|||
'xmlAttribute' => true, |
|||
], |
|||
], |
|||
'Name' => [ |
|||
'sentAs' => 'name', |
|||
'data' => [ |
|||
'xmlAttribute' => true, |
|||
], |
|||
], |
|||
], |
|||
], |
|||
], |
|||
] |
|||
]); |
|||
|
|||
$xml = ' |
|||
<wrap> |
|||
<RunningQueues> |
|||
<item queue_id="q-3ea74257"> |
|||
<CurrentState code="32" name="processing" /> |
|||
<PreviousState code="16" name="wait" /> |
|||
</item> |
|||
</RunningQueues> |
|||
</wrap>'; |
|||
|
|||
$this->xmlTest($param, $xml, [ |
|||
'RunningQueues' => [ |
|||
[ |
|||
'QueueId' => 'q-3ea74257', |
|||
'CurrentState' => [ |
|||
'Code' => '32', |
|||
'Name' => 'processing', |
|||
], |
|||
'PreviousState' => [ |
|||
'Code' => '16', |
|||
'Name' => 'wait', |
|||
], |
|||
], |
|||
], |
|||
]); |
|||
} |
|||
|
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testAddsEmptyArraysWhenValueIsMissing() |
|||
{ |
|||
$param = new Parameter([ |
|||
'name' => 'Foo', |
|||
'type' => 'array', |
|||
'location' => 'xml', |
|||
'items' => [ |
|||
'type' => 'object', |
|||
'properties' => [ |
|||
'Baz' => ['type' => 'array'], |
|||
'Bar' => [ |
|||
'type' => 'object', |
|||
'properties' => [ |
|||
'Baz' => ['type' => 'array'], |
|||
], |
|||
], |
|||
], |
|||
], |
|||
]); |
|||
|
|||
$xml = '<xml><Foo><Bar></Bar></Foo></xml>'; |
|||
|
|||
$this->xmlTest($param, $xml, [ |
|||
'Foo' => [ |
|||
[ |
|||
'Bar' => [], |
|||
] |
|||
], |
|||
]); |
|||
} |
|||
|
|||
/** |
|||
* @group issue-399, ResponseLocation |
|||
* @link https://github.com/guzzle/guzzle/issues/399 |
|||
*/ |
|||
public function testDiscardingUnknownProperties() |
|||
{ |
|||
$param = new Parameter([ |
|||
'name' => 'foo', |
|||
'type' => 'object', |
|||
'additionalProperties' => false, |
|||
'properties' => [ |
|||
'bar' => [ |
|||
'type' => 'string', |
|||
'name' => 'bar', |
|||
], |
|||
], |
|||
]); |
|||
|
|||
$xml = ' |
|||
<xml> |
|||
<foo> |
|||
<bar>15</bar> |
|||
<unknown>discard me</unknown> |
|||
</foo> |
|||
</xml> |
|||
'; |
|||
|
|||
$this->xmlTest($param, $xml, [ |
|||
'foo' => [ |
|||
'bar' => 15 |
|||
] |
|||
]); |
|||
} |
|||
|
|||
/** |
|||
* @group issue-399, ResponseLocation |
|||
* @link https://github.com/guzzle/guzzle/issues/399 |
|||
*/ |
|||
public function testDiscardingUnknownPropertiesWithAliasing() |
|||
{ |
|||
$param = new Parameter([ |
|||
'name' => 'foo', |
|||
'type' => 'object', |
|||
'additionalProperties' => false, |
|||
'properties' => [ |
|||
'bar' => [ |
|||
'name' => 'bar', |
|||
'sentAs' => 'baz', |
|||
], |
|||
], |
|||
]); |
|||
|
|||
$xml = ' |
|||
<xml> |
|||
<foo> |
|||
<baz>15</baz> |
|||
<unknown>discard me</unknown> |
|||
</foo> |
|||
</xml> |
|||
'; |
|||
|
|||
$this->xmlTest($param, $xml, [ |
|||
'foo' => [ |
|||
'bar' => 15, |
|||
], |
|||
]); |
|||
} |
|||
|
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testProcessingOfNestedAdditionalProperties() |
|||
{ |
|||
$param = new Parameter([ |
|||
'name' => 'foo', |
|||
'type' => 'object', |
|||
'additionalProperties' => true, |
|||
'properties' => [ |
|||
'bar' => [ |
|||
'name' => 'bar', |
|||
'sentAs' => 'baz', |
|||
], |
|||
'nestedNoAdditional' => [ |
|||
'type' => 'object', |
|||
'additionalProperties' => false, |
|||
'properties' => [ |
|||
'id' => [ |
|||
'type' => 'integer', |
|||
], |
|||
], |
|||
], |
|||
'nestedWithAdditional' => [ |
|||
'type' => 'object', |
|||
'additionalProperties' => true, |
|||
], |
|||
'nestedWithAdditionalSchema' => [ |
|||
'type' => 'object', |
|||
'additionalProperties' => [ |
|||
'type' => 'array', |
|||
'items' => [ |
|||
'type' => 'string', |
|||
], |
|||
], |
|||
], |
|||
], |
|||
]); |
|||
|
|||
$xml = ' |
|||
<xml> |
|||
<foo> |
|||
<baz>15</baz> |
|||
<additional>include me</additional> |
|||
<nestedNoAdditional> |
|||
<id>15</id> |
|||
<unknown>discard me</unknown> |
|||
</nestedNoAdditional> |
|||
<nestedWithAdditional> |
|||
<id>15</id> |
|||
<additional>include me</additional> |
|||
</nestedWithAdditional> |
|||
<nestedWithAdditionalSchema> |
|||
<arrayA> |
|||
<item>1</item> |
|||
<item>2</item> |
|||
<item>3</item> |
|||
</arrayA> |
|||
<arrayB> |
|||
<item>A</item> |
|||
<item>B</item> |
|||
<item>C</item> |
|||
</arrayB> |
|||
</nestedWithAdditionalSchema> |
|||
</foo> |
|||
</xml> |
|||
'; |
|||
|
|||
$this->xmlTest($param, $xml, [ |
|||
'foo' => [ |
|||
'bar' => '15', |
|||
'additional' => 'include me', |
|||
'nestedNoAdditional' => [ |
|||
'id' => '15', |
|||
], |
|||
'nestedWithAdditional' => [ |
|||
'id' => '15', |
|||
'additional' => 'include me', |
|||
], |
|||
'nestedWithAdditionalSchema' => [ |
|||
'arrayA' => ['1', '2', '3'], |
|||
'arrayB' => ['A', 'B', 'C'], |
|||
], |
|||
], |
|||
]); |
|||
} |
|||
|
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testConvertsMultipleAssociativeElementsToArray() |
|||
{ |
|||
$param = new Parameter([ |
|||
'name' => 'foo', |
|||
'type' => 'object', |
|||
'additionalProperties' => true, |
|||
]); |
|||
|
|||
$xml = ' |
|||
<xml> |
|||
<foo> |
|||
<baz>15</baz> |
|||
<baz>25</baz> |
|||
<bar>hi</bar> |
|||
<bam>test</bam> |
|||
<bam attr="hi" /> |
|||
</foo> |
|||
</xml> |
|||
'; |
|||
|
|||
$this->xmlTest($param, $xml, [ |
|||
'foo' => [ |
|||
'baz' => ['15', '25'], |
|||
'bar' => 'hi', |
|||
'bam' => [ |
|||
'test', |
|||
['@attributes' => ['attr' => 'hi']] |
|||
] |
|||
] |
|||
]); |
|||
} |
|||
|
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testUnderstandsNamespaces() |
|||
{ |
|||
$param = new Parameter([ |
|||
'name' => 'nstest', |
|||
'type' => 'array', |
|||
'location' => 'xml', |
|||
'items' => [ |
|||
'name' => 'item', |
|||
'type' => 'object', |
|||
'sentAs' => 'item', |
|||
'properties' => [ |
|||
'id' => [ |
|||
'type' => 'string', |
|||
], |
|||
'isbn:number' => [ |
|||
'type' => 'string', |
|||
], |
|||
'meta' => [ |
|||
'type' => 'object', |
|||
'sentAs' => 'abstract:meta', |
|||
'properties' => [ |
|||
'foo' => [ |
|||
'type' => 'numeric', |
|||
], |
|||
'bar' => [ |
|||
'type' => 'object', |
|||
'properties' =>[ |
|||
'attribute' => [ |
|||
'type' => 'string', |
|||
'data' => [ |
|||
'xmlAttribute' => true, |
|||
'xmlNs' => 'abstract', |
|||
], |
|||
], |
|||
], |
|||
], |
|||
], |
|||
], |
|||
'gamma' => [ |
|||
'type' => 'object', |
|||
'data' => [ |
|||
'xmlNs' => 'abstract', |
|||
], |
|||
'additionalProperties' => true, |
|||
], |
|||
'nonExistent' => [ |
|||
'type' => 'object', |
|||
'data' => [ |
|||
'xmlNs' => 'abstract', |
|||
], |
|||
'additionalProperties' => true, |
|||
], |
|||
'nonExistent2' => [ |
|||
'type' => 'object', |
|||
'additionalProperties' => true, |
|||
], |
|||
], |
|||
], |
|||
]); |
|||
|
|||
$xml = ' |
|||
<xml> |
|||
<nstest xmlns:isbn="urn:ISBN:0-395-36341-6" xmlns:abstract="urn:my.org:abstract"> |
|||
<item> |
|||
<id>101</id> |
|||
<isbn:number>1568491379</isbn:number> |
|||
<abstract:meta> |
|||
<foo>10</foo> |
|||
<bar abstract:attribute="foo"></bar> |
|||
</abstract:meta> |
|||
<abstract:gamma> |
|||
<foo>bar</foo> |
|||
</abstract:gamma> |
|||
</item> |
|||
<item> |
|||
<id>102</id> |
|||
<isbn:number>1568491999</isbn:number> |
|||
<abstract:meta> |
|||
<foo>20</foo> |
|||
<bar abstract:attribute="bar"></bar> |
|||
</abstract:meta> |
|||
<abstract:gamma> |
|||
<foo>baz</foo> |
|||
</abstract:gamma> |
|||
</item> |
|||
</nstest> |
|||
</xml> |
|||
'; |
|||
|
|||
$this->xmlTest($param, $xml, [ |
|||
'nstest' => [ |
|||
[ |
|||
'id' => '101', |
|||
'isbn:number' => 1568491379, |
|||
'meta' => [ |
|||
'foo' => 10, |
|||
'bar' => [ |
|||
'attribute' => 'foo', |
|||
], |
|||
], |
|||
'gamma' => [ |
|||
'foo' => 'bar', |
|||
], |
|||
], |
|||
[ |
|||
'id' => '102', |
|||
'isbn:number' => 1568491999, |
|||
'meta' => [ |
|||
'foo' => 20, |
|||
'bar' => [ |
|||
'attribute' => 'bar' |
|||
], |
|||
], |
|||
'gamma' => [ |
|||
'foo' => 'baz', |
|||
], |
|||
], |
|||
], |
|||
]); |
|||
} |
|||
|
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testCanWalkUndefinedPropertiesWithNamespace() |
|||
{ |
|||
$param = new Parameter([ |
|||
'name' => 'nstest', |
|||
'type' => 'array', |
|||
'location' => 'xml', |
|||
'items' => [ |
|||
'name' => 'item', |
|||
'type' => 'object', |
|||
'sentAs' => 'item', |
|||
'additionalProperties' => [ |
|||
'type' => 'object', |
|||
'data' => [ |
|||
'xmlNs' => 'abstract' |
|||
], |
|||
], |
|||
'properties' => [ |
|||
'id' => [ |
|||
'type' => 'string', |
|||
], |
|||
'isbn:number' => [ |
|||
'type' => 'string', |
|||
], |
|||
], |
|||
], |
|||
]); |
|||
|
|||
$xml = ' |
|||
<xml> |
|||
<nstest xmlns:isbn="urn:ISBN:0-395-36341-6" xmlns:abstract="urn:my.org:abstract"> |
|||
<item> |
|||
<id>101</id> |
|||
<isbn:number>1568491379</isbn:number> |
|||
<abstract:meta> |
|||
<foo>10</foo> |
|||
<bar>baz</bar> |
|||
</abstract:meta> |
|||
</item> |
|||
<item> |
|||
<id>102</id> |
|||
<isbn:number>1568491999</isbn:number> |
|||
<abstract:meta> |
|||
<foo>20</foo> |
|||
<bar>foo</bar> |
|||
</abstract:meta> |
|||
</item> |
|||
</nstest> |
|||
</xml> |
|||
'; |
|||
|
|||
$this->xmlTest($param, $xml, [ |
|||
'nstest' => [ |
|||
[ |
|||
'id' => '101', |
|||
'isbn:number' => 1568491379, |
|||
'meta' => [ |
|||
'foo' => 10, |
|||
'bar' => 'baz', |
|||
], |
|||
], |
|||
[ |
|||
'id' => '102', |
|||
'isbn:number' => 1568491999, |
|||
'meta' => [ |
|||
'foo' => 20, |
|||
'bar' => 'foo', |
|||
], |
|||
], |
|||
] |
|||
]); |
|||
} |
|||
|
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testCanWalkSimpleArrayWithNamespace() |
|||
{ |
|||
$param = new Parameter([ |
|||
'name' => 'nstest', |
|||
'type' => 'array', |
|||
'location' => 'xml', |
|||
'items' => [ |
|||
'type' => 'string', |
|||
'sentAs' => 'number', |
|||
'data' => [ |
|||
'xmlNs' => 'isbn' |
|||
], |
|||
], |
|||
]); |
|||
|
|||
$xml = ' |
|||
<xml> |
|||
<nstest xmlns:isbn="urn:ISBN:0-395-36341-6"> |
|||
<isbn:number>1568491379</isbn:number> |
|||
<isbn:number>1568491999</isbn:number> |
|||
<isbn:number>1568492999</isbn:number> |
|||
</nstest> |
|||
</xml> |
|||
'; |
|||
|
|||
$this->xmlTest($param, $xml, [ |
|||
'nstest' => [ |
|||
1568491379, |
|||
1568491999, |
|||
1568492999, |
|||
], |
|||
]); |
|||
} |
|||
|
|||
/** |
|||
* @group ResponseLocation |
|||
*/ |
|||
public function testCanWalkSimpleArrayWithNamespace2() |
|||
{ |
|||
$param = new Parameter([ |
|||
'name' => 'nstest', |
|||
'type' => 'array', |
|||
'location' => 'xml', |
|||
'items' => [ |
|||
'type' => 'string', |
|||
'sentAs' => 'isbn:number', |
|||
] |
|||
]); |
|||
|
|||
$xml = ' |
|||
<xml> |
|||
<nstest xmlns:isbn="urn:ISBN:0-395-36341-6"> |
|||
<isbn:number>1568491379</isbn:number> |
|||
<isbn:number>1568491999</isbn:number> |
|||
<isbn:number>1568492999</isbn:number> |
|||
</nstest> |
|||
</xml> |
|||
'; |
|||
|
|||
$this->xmlTest($param, $xml, [ |
|||
'nstest' => [ |
|||
1568491379, |
|||
1568491999, |
|||
1568492999, |
|||
], |
|||
]); |
|||
} |
|||
|
|||
private function xmlTest(Parameter $param, $xml, $expected) |
|||
{ |
|||
$location = new XmlLocation(); |
|||
$model = new Parameter(); |
|||
$response = new Response(200, [], \GuzzleHttp\Psr7\stream_for($xml)); |
|||
$result = new Result(); |
|||
$result = $location->before($result, $response, $param); |
|||
$result = $location->visit($result, $response, $param); |
|||
$result = $location->after($result, $response, $model); |
|||
$this->assertEquals($expected, $result->toArray()); |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
<?php |
|||
namespace GuzzleHttp\Tests\Command\Guzzle; |
|||
|
|||
use GuzzleHttp\Command\Guzzle\SchemaFormatter; |
|||
|
|||
/** |
|||
* @covers \GuzzleHttp\Command\Guzzle\SchemaFormatter |
|||
*/ |
|||
class SchemaFormatterTest extends \PHPUnit_Framework_TestCase |
|||
{ |
|||
public function dateTimeProvider() |
|||
{ |
|||
$dateUtc = 'October 13, 2012 16:15:46 UTC'; |
|||
$dateOffset = 'October 13, 2012 10:15:46 -06:00'; |
|||
$expectedDateTime = '2012-10-13T16:15:46Z'; |
|||
|
|||
return [ |
|||
['foo', 'does-not-exist', 'foo'], |
|||
[$dateUtc, 'date-time', $expectedDateTime], |
|||
[$dateUtc, 'date-time-http', 'Sat, 13 Oct 2012 16:15:46 GMT'], |
|||
[$dateUtc, 'date', '2012-10-13'], |
|||
[$dateUtc, 'timestamp', strtotime($dateUtc)], |
|||
[new \DateTime($dateUtc), 'timestamp', strtotime($dateUtc)], |
|||
[$dateUtc, 'time', '16:15:46'], |
|||
[strtotime($dateUtc), 'time', '16:15:46'], |
|||
[strtotime($dateUtc), 'timestamp', strtotime($dateUtc)], |
|||
['true', 'boolean-string', 'true'], |
|||
[true, 'boolean-string', 'true'], |
|||
['false', 'boolean-string', 'false'], |
|||
[false, 'boolean-string', 'false'], |
|||
['1350144946', 'date-time', $expectedDateTime], |
|||
[1350144946, 'date-time', $expectedDateTime], |
|||
[$dateOffset, 'date-time', $expectedDateTime], |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* @dataProvider dateTimeProvider |
|||
*/ |
|||
public function testFilters($value, $format, $result) |
|||
{ |
|||
$this->assertEquals($result, (new SchemaFormatter)->format($format, $value)); |
|||
} |
|||
|
|||
/** |
|||
* @expectedException \InvalidArgumentException |
|||
*/ |
|||
public function testValidatesDateTimeInput() |
|||
{ |
|||
(new SchemaFormatter)->format('date-time', false); |
|||
} |
|||
|
|||
public function testEnsuresTimestampsAreIntegers() |
|||
{ |
|||
$t = time(); |
|||
$result = (new SchemaFormatter)->format('timestamp', $t); |
|||
$this->assertSame($t, $result); |
|||
$this->assertInternalType('int', $result); |
|||
} |
|||
} |
|||
@ -0,0 +1,330 @@ |
|||
<?php |
|||
namespace Guzzle\Tests\Service\Description; |
|||
|
|||
use GuzzleHttp\Command\Guzzle\Parameter; |
|||
use GuzzleHttp\Command\Guzzle\SchemaValidator; |
|||
use GuzzleHttp\Command\ToArrayInterface; |
|||
|
|||
/** |
|||
* @covers \GuzzleHttp\Command\Guzzle\SchemaValidator |
|||
*/ |
|||
class SchemaValidatorTest extends \PHPUnit_Framework_TestCase |
|||
{ |
|||
/** @var SchemaValidator */ |
|||
protected $validator; |
|||
|
|||
public function setUp() |
|||
{ |
|||
$this->validator = new SchemaValidator(); |
|||
} |
|||
|
|||
public function testValidatesArrayListsAreNumericallyIndexed() |
|||
{ |
|||
$value = [[1]]; |
|||
$this->assertFalse($this->validator->validate($this->getComplexParam(), $value)); |
|||
$this->assertEquals( |
|||
['[Foo][0] must be an array of properties. Got a numerically indexed array.'], |
|||
$this->validator->getErrors() |
|||
); |
|||
} |
|||
|
|||
public function testValidatesArrayListsContainProperItems() |
|||
{ |
|||
$value = [true]; |
|||
$this->assertFalse($this->validator->validate($this->getComplexParam(), $value)); |
|||
$this->assertEquals( |
|||
['[Foo][0] must be of type object'], |
|||
$this->validator->getErrors() |
|||
); |
|||
} |
|||
|
|||
public function testAddsDefaultValuesInLists() |
|||
{ |
|||
$value = [[]]; |
|||
$this->assertTrue($this->validator->validate($this->getComplexParam(), $value)); |
|||
$this->assertEquals([['Bar' => true]], $value); |
|||
} |
|||
|
|||
public function testMergesDefaultValuesInLists() |
|||
{ |
|||
$value = [ |
|||
['Baz' => 'hello!'], |
|||
['Bar' => false], |
|||
]; |
|||
$this->assertTrue($this->validator->validate($this->getComplexParam(), $value)); |
|||
$this->assertEquals([ |
|||
[ |
|||
'Baz' => 'hello!', |
|||
'Bar' => true, |
|||
], |
|||
['Bar' => false], |
|||
], $value); |
|||
} |
|||
|
|||
public function testCorrectlyConvertsParametersToArrayWhenArraysArePresent() |
|||
{ |
|||
$param = $this->getComplexParam(); |
|||
$result = $param->toArray(); |
|||
$this->assertInternalType('array', $result['items']); |
|||
$this->assertEquals('array', $result['type']); |
|||
$this->assertInstanceOf('GuzzleHttp\Command\Guzzle\Parameter', $param->getItems()); |
|||
} |
|||
|
|||
public function testEnforcesInstanceOfOnlyWhenObject() |
|||
{ |
|||
$p = new Parameter([ |
|||
'name' => 'foo', |
|||
'type' => ['object', 'string'], |
|||
'instanceOf' => get_class($this) |
|||
]); |
|||
$this->assertTrue($this->validator->validate($p, $this)); |
|||
$s = 'test'; |
|||
$this->assertTrue($this->validator->validate($p, $s)); |
|||
} |
|||
|
|||
public function testConvertsObjectsToArraysWhenToArrayInterface() |
|||
{ |
|||
$o = $this->getMockBuilder(ToArrayInterface::class) |
|||
->setMethods(['toArray']) |
|||
->getMockForAbstractClass(); |
|||
$o->expects($this->once()) |
|||
->method('toArray') |
|||
->will($this->returnValue(['foo' => 'bar'])); |
|||
$p = new Parameter([ |
|||
'name' => 'test', |
|||
'type' => 'object', |
|||
'properties' => [ |
|||
'foo' => ['required' => 'true'], |
|||
], |
|||
]); |
|||
$this->assertTrue($this->validator->validate($p, $o)); |
|||
} |
|||
|
|||
public function testMergesValidationErrorsInPropertiesWithParent() |
|||
{ |
|||
$p = new Parameter([ |
|||
'name' => 'foo', |
|||
'type' => 'object', |
|||
'properties' => [ |
|||
'bar' => ['type' => 'string', 'required' => true, 'description' => 'This is what it does'], |
|||
'test' => ['type' => 'string', 'minLength' => 2, 'maxLength' => 5], |
|||
'test2' => ['type' => 'string', 'minLength' => 2, 'maxLength' => 2], |
|||
'test3' => ['type' => 'integer', 'minimum' => 100], |
|||
'test4' => ['type' => 'integer', 'maximum' => 10], |
|||
'test5' => ['type' => 'array', 'maxItems' => 2], |
|||
'test6' => ['type' => 'string', 'enum' => ['a', 'bc']], |
|||
'test7' => ['type' => 'string', 'pattern' => '/[0-9]+/'], |
|||
'test8' => ['type' => 'number'], |
|||
'baz' => [ |
|||
'type' => 'array', |
|||
'minItems' => 2, |
|||
'required' => true, |
|||
"items" => ["type" => "string"], |
|||
], |
|||
], |
|||
]); |
|||
|
|||
$value = [ |
|||
'test' => 'a', |
|||
'test2' => 'abc', |
|||
'baz' => [false], |
|||
'test3' => 10, |
|||
'test4' => 100, |
|||
'test5' => [1, 3, 4], |
|||
'test6' => 'Foo', |
|||
'test7' => 'abc', |
|||
'test8' => 'abc', |
|||
]; |
|||
|
|||
$this->assertFalse($this->validator->validate($p, $value)); |
|||
$this->assertEquals([ |
|||
'[foo][bar] is a required string: This is what it does', |
|||
'[foo][baz] must contain 2 or more elements', |
|||
'[foo][baz][0] must be of type string', |
|||
'[foo][test2] length must be less than or equal to 2', |
|||
'[foo][test3] must be greater than or equal to 100', |
|||
'[foo][test4] must be less than or equal to 10', |
|||
'[foo][test5] must contain 2 or fewer elements', |
|||
'[foo][test6] must be one of "a" or "bc"', |
|||
'[foo][test7] must match the following regular expression: /[0-9]+/', |
|||
'[foo][test8] must be of type number', |
|||
'[foo][test] length must be greater than or equal to 2', |
|||
], $this->validator->getErrors()); |
|||
} |
|||
|
|||
public function testHandlesNullValuesInArraysWithDefaults() |
|||
{ |
|||
$p = new Parameter([ |
|||
'name' => 'foo', |
|||
'type' => 'object', |
|||
'required' => true, |
|||
'properties' => [ |
|||
'bar' => [ |
|||
'type' => 'object', |
|||
'required' => true, |
|||
'properties' => [ |
|||
'foo' => ['default' => 'hi'], |
|||
], |
|||
], |
|||
], |
|||
]); |
|||
$value = []; |
|||
$this->assertTrue($this->validator->validate($p, $value)); |
|||
$this->assertEquals(['bar' => ['foo' => 'hi']], $value); |
|||
} |
|||
|
|||
public function testFailsWhenNullValuesInArraysWithNoDefaults() |
|||
{ |
|||
$p = new Parameter([ |
|||
'name' => 'foo', |
|||
'type' => 'object', |
|||
'required' => true, |
|||
'properties' => [ |
|||
'bar' => [ |
|||
'type' => 'object', |
|||
'required' => true, |
|||
'properties' => [ |
|||
'foo' => ['type' => 'string'], |
|||
], |
|||
], |
|||
], |
|||
]); |
|||
$value = []; |
|||
$this->assertFalse($this->validator->validate($p, $value)); |
|||
$this->assertEquals(['[foo][bar] is a required object'], $this->validator->getErrors()); |
|||
} |
|||
|
|||
public function testChecksTypes() |
|||
{ |
|||
$p = new SchemaValidator(); |
|||
$r = new \ReflectionMethod($p, 'determineType'); |
|||
$r->setAccessible(true); |
|||
$this->assertEquals('any', $r->invoke($p, 'any', 'hello')); |
|||
$this->assertEquals(false, $r->invoke($p, 'foo', 'foo')); |
|||
$this->assertEquals('string', $r->invoke($p, 'string', 'hello')); |
|||
$this->assertEquals(false, $r->invoke($p, 'string', false)); |
|||
$this->assertEquals('integer', $r->invoke($p, 'integer', 1)); |
|||
$this->assertEquals(false, $r->invoke($p, 'integer', 'abc')); |
|||
$this->assertEquals('numeric', $r->invoke($p, 'numeric', 1)); |
|||
$this->assertEquals('numeric', $r->invoke($p, 'numeric', '1')); |
|||
$this->assertEquals('number', $r->invoke($p, 'number', 1)); |
|||
$this->assertEquals('number', $r->invoke($p, 'number', '1')); |
|||
$this->assertEquals(false, $r->invoke($p, 'numeric', 'a')); |
|||
$this->assertEquals('boolean', $r->invoke($p, 'boolean', true)); |
|||
$this->assertEquals('boolean', $r->invoke($p, 'boolean', false)); |
|||
$this->assertEquals(false, $r->invoke($p, 'boolean', 'false')); |
|||
$this->assertEquals('null', $r->invoke($p, 'null', null)); |
|||
$this->assertEquals(false, $r->invoke($p, 'null', 'abc')); |
|||
$this->assertEquals('array', $r->invoke($p, 'array', [])); |
|||
$this->assertEquals(false, $r->invoke($p, 'array', 'foo')); |
|||
} |
|||
|
|||
public function testValidatesFalseAdditionalProperties() |
|||
{ |
|||
$param = new Parameter([ |
|||
'name' => 'foo', |
|||
'type' => 'object', |
|||
'properties' => [ |
|||
'bar' => ['type' => 'string'], |
|||
], |
|||
'additionalProperties' => false, |
|||
]); |
|||
$value = ['test' => '123']; |
|||
$this->assertFalse($this->validator->validate($param, $value)); |
|||
$this->assertEquals(['[foo][test] is not an allowed property'], $this->validator->getErrors()); |
|||
$value = ['bar' => '123']; |
|||
$this->assertTrue($this->validator->validate($param, $value)); |
|||
} |
|||
|
|||
public function testAllowsUndefinedAdditionalProperties() |
|||
{ |
|||
$param = new Parameter([ |
|||
'name' => 'foo', |
|||
'type' => 'object', |
|||
'properties' => [ |
|||
'bar' => ['type' => 'string'], |
|||
] |
|||
]); |
|||
$value = ['test' => '123']; |
|||
$this->assertTrue($this->validator->validate($param, $value)); |
|||
} |
|||
|
|||
public function testValidatesAdditionalProperties() |
|||
{ |
|||
$param = new Parameter([ |
|||
'name' => 'foo', |
|||
'type' => 'object', |
|||
'properties' => [ |
|||
'bar' => ['type' => 'string'], |
|||
], |
|||
'additionalProperties' => ['type' => 'integer'], |
|||
]); |
|||
$value = ['test' => 'foo']; |
|||
$this->assertFalse($this->validator->validate($param, $value)); |
|||
$this->assertEquals(['[foo][test] must be of type integer'], $this->validator->getErrors()); |
|||
} |
|||
|
|||
public function testValidatesAdditionalPropertiesThatArrayArrays() |
|||
{ |
|||
$param = new Parameter([ |
|||
'name' => 'foo', |
|||
'type' => 'object', |
|||
'additionalProperties' => [ |
|||
'type' => 'array', |
|||
'items' => ['type' => 'string'], |
|||
], |
|||
]); |
|||
$value = ['test' => [true]]; |
|||
$this->assertFalse($this->validator->validate($param, $value)); |
|||
$this->assertEquals(['[foo][test][0] must be of type string'], $this->validator->getErrors()); |
|||
} |
|||
|
|||
public function testIntegersCastToStringWhenTypeMismatch() |
|||
{ |
|||
$param = new Parameter([ |
|||
'name' => 'test', |
|||
'type' => 'string', |
|||
]); |
|||
$value = 12; |
|||
$this->assertTrue($this->validator->validate($param, $value)); |
|||
$this->assertEquals('12', $value); |
|||
} |
|||
|
|||
public function testRequiredMessageIncludesType() |
|||
{ |
|||
$param = new Parameter([ |
|||
'name' => 'test', |
|||
'type' => [ |
|||
'string', |
|||
'boolean', |
|||
], |
|||
'required' => true, |
|||
]); |
|||
$value = null; |
|||
$this->assertFalse($this->validator->validate($param, $value)); |
|||
$this->assertEquals(['[test] is a required string or boolean'], $this->validator->getErrors()); |
|||
} |
|||
|
|||
protected function getComplexParam() |
|||
{ |
|||
return new Parameter([ |
|||
'name' => 'Foo', |
|||
'type' => 'array', |
|||
'required' => true, |
|||
'min' => 1, |
|||
'items' => [ |
|||
'type' => 'object', |
|||
'properties' => [ |
|||
'Baz' => [ |
|||
'type' => 'string', |
|||
], |
|||
'Bar' => [ |
|||
'required' => true, |
|||
'type' => 'boolean', |
|||
'default' => true, |
|||
], |
|||
], |
|||
], |
|||
]); |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue