Primo Committ

This commit is contained in:
paoloar77
2024-05-07 12:17:25 +02:00
commit e73d0e5113
7204 changed files with 884387 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
<?php
namespace Illuminate\Http\Client;
class ConnectionException extends HttpClientException
{
//
}

View File

@@ -0,0 +1,285 @@
<?php
namespace Illuminate\Http\Client;
use Closure;
use function GuzzleHttp\Promise\promise_for;
use GuzzleHttp\Psr7\Response as Psr7Response;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
use PHPUnit\Framework\Assert as PHPUnit;
class Factory
{
use Macroable {
__call as macroCall;
}
/**
* The stub callables that will handle requests.
*
* @var \Illuminate\Support\Collection
*/
protected $stubCallbacks;
/**
* Indicates if the factory is recording requests and responses.
*
* @var bool
*/
protected $recording = false;
/**
* The recorded response array.
*
* @var array
*/
protected $recorded = [];
/**
* All created response sequences.
*
* @var array
*/
protected $responseSequences = [];
/**
* Create a new factory instance.
*
* @return void
*/
public function __construct()
{
$this->stubCallbacks = collect();
}
/**
* Create a new response instance for use during stubbing.
*
* @param array|string $body
* @param int $status
* @param array $headers
* @return \GuzzleHttp\Promise\PromiseInterface
*/
public static function response($body = null, $status = 200, $headers = [])
{
if (is_array($body)) {
$body = json_encode($body);
$headers['Content-Type'] = 'application/json';
}
return promise_for(new Psr7Response($status, $headers, $body));
}
/**
* Get an invokable object that returns a sequence of responses in order for use during stubbing.
*
* @param array $responses
* @return \Illuminate\Http\Client\ResponseSequence
*/
public function sequence(array $responses = [])
{
return $this->responseSequences[] = new ResponseSequence($responses);
}
/**
* Register a stub callable that will intercept requests and be able to return stub responses.
*
* @param callable|array $callback
* @return $this
*/
public function fake($callback = null)
{
$this->record();
if (is_null($callback)) {
$callback = function () {
return static::response();
};
}
if (is_array($callback)) {
foreach ($callback as $url => $callable) {
$this->stubUrl($url, $callable);
}
return $this;
}
$this->stubCallbacks = $this->stubCallbacks->merge(collect([
$callback instanceof Closure
? $callback
: function () use ($callback) {
return $callback;
},
]));
return $this;
}
/**
* Register a response sequence for the given URL pattern.
*
* @param string $url
* @return \Illuminate\Http\Client\ResponseSequence
*/
public function fakeSequence($url = '*')
{
return tap($this->sequence(), function ($sequence) use ($url) {
$this->fake([$url => $sequence]);
});
}
/**
* Stub the given URL using the given callback.
*
* @param string $url
* @param \Illuminate\Http\Client\Response|\GuzzleHttp\Promise\PromiseInterface|callable $callback
* @return $this
*/
public function stubUrl($url, $callback)
{
return $this->fake(function ($request, $options) use ($url, $callback) {
if (! Str::is(Str::start($url, '*'), $request->url())) {
return;
}
return $callback instanceof Closure || $callback instanceof ResponseSequence
? $callback($request, $options)
: $callback;
});
}
/**
* Begin recording request / response pairs.
*
* @return $this
*/
protected function record()
{
$this->recording = true;
return $this;
}
/**
* Record a request response pair.
*
* @param \Illuminate\Http\Client\Request $request
* @param \Illuminate\Http\Client\Response $response
* @return void
*/
public function recordRequestResponsePair($request, $response)
{
if ($this->recording) {
$this->recorded[] = [$request, $response];
}
}
/**
* Assert that a request / response pair was recorded matching a given truth test.
*
* @param callable $callback
* @return void
*/
public function assertSent($callback)
{
PHPUnit::assertTrue(
$this->recorded($callback)->count() > 0,
'An expected request was not recorded.'
);
}
/**
* Assert that a request / response pair was not recorded matching a given truth test.
*
* @param callable $callback
* @return void
*/
public function assertNotSent($callback)
{
PHPUnit::assertFalse(
$this->recorded($callback)->count() > 0,
'Unexpected request was recorded.'
);
}
/**
* Assert that no request / response pair was recorded.
*
* @return void
*/
public function assertNothingSent()
{
PHPUnit::assertEmpty(
$this->recorded,
'Requests were recorded.'
);
}
/**
* Assert how many requests have been recorded.
*
* @param int $count
* @return void
*/
public function assertSentCount($count)
{
PHPUnit::assertCount($count, $this->recorded);
}
/**
* Assert that every created response sequence is empty.
*
* @return void
*/
public function assertSequencesAreEmpty()
{
foreach ($this->responseSequences as $responseSequence) {
PHPUnit::assertTrue(
$responseSequence->isEmpty(),
'Not all response sequences are empty.'
);
}
}
/**
* Get a collection of the request / response pairs matching the given truth test.
*
* @param callable $callback
* @return \Illuminate\Support\Collection
*/
public function recorded($callback)
{
if (empty($this->recorded)) {
return collect();
}
$callback = $callback ?: function () {
return true;
};
return collect($this->recorded)->filter(function ($pair) use ($callback) {
return $callback($pair[0], $pair[1]);
});
}
/**
* Execute a method against a new pending request instance.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
if (static::hasMacro($method)) {
return $this->macroCall($method, $parameters);
}
return tap(new PendingRequest($this), function ($request) {
$request->stub($this->stubCallbacks);
})->{$method}(...$parameters);
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Illuminate\Http\Client;
use Exception;
class HttpClientException extends Exception
{
//
}

View File

@@ -0,0 +1,775 @@
<?php
namespace Illuminate\Http\Client;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\HandlerStack;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
class PendingRequest
{
use Macroable;
/**
* The factory instance.
*
* @var \Illuminate\Http\Client\Factory|null
*/
protected $factory;
/**
* The base URL for the request.
*
* @var string
*/
protected $baseUrl = '';
/**
* The request body format.
*
* @var string
*/
protected $bodyFormat;
/**
* The raw body for the request.
*
* @var string
*/
protected $pendingBody;
/**
* The pending files for the request.
*
* @var array
*/
protected $pendingFiles = [];
/**
* The request cookies.
*
* @var array
*/
protected $cookies;
/**
* The transfer stats for the request.
*
* \GuzzleHttp\TransferStats
*/
protected $transferStats;
/**
* The request options.
*
* @var array
*/
protected $options = [];
/**
* The number of times to try the request.
*
* @var int
*/
protected $tries = 1;
/**
* The number of milliseconds to wait between retries.
*
* @var int
*/
protected $retryDelay = 100;
/**
* The callbacks that should execute before the request is sent.
*
* @var \Illuminate\Support\Collection
*/
protected $beforeSendingCallbacks;
/**
* The stub callables that will handle requests.
*
* @var \Illuminate\Support\Collection|null
*/
protected $stubCallbacks;
/**
* The middleware callables added by users that will handle requests.
*
* @var \Illuminate\Support\Collection
*/
protected $middleware;
/**
* Create a new HTTP Client instance.
*
* @param \Illuminate\Http\Client\Factory|null $factory
* @return void
*/
public function __construct(Factory $factory = null)
{
$this->factory = $factory;
$this->middleware = new Collection;
$this->asJson();
$this->options = [
'http_errors' => false,
];
$this->beforeSendingCallbacks = collect([function (Request $request, array $options) {
$this->cookies = $options['cookies'];
}]);
}
/**
* Set the base URL for the pending request.
*
* @param string $url
* @return $this
*/
public function baseUrl(string $url)
{
$this->baseUrl = $url;
return $this;
}
/**
* Attach a raw body to the request.
*
* @param resource|string $content
* @param string $contentType
* @return $this
*/
public function withBody($content, $contentType)
{
$this->bodyFormat('body');
$this->pendingBody = $content;
$this->contentType($contentType);
return $this;
}
/**
* Indicate the request contains JSON.
*
* @return $this
*/
public function asJson()
{
return $this->bodyFormat('json')->contentType('application/json');
}
/**
* Indicate the request contains form parameters.
*
* @return $this
*/
public function asForm()
{
return $this->bodyFormat('form_params')->contentType('application/x-www-form-urlencoded');
}
/**
* Attach a file to the request.
*
* @param string $name
* @param string $contents
* @param string|null $filename
* @param array $headers
* @return $this
*/
public function attach($name, $contents, $filename = null, array $headers = [])
{
$this->asMultipart();
$this->pendingFiles[] = array_filter([
'name' => $name,
'contents' => $contents,
'headers' => $headers,
'filename' => $filename,
]);
return $this;
}
/**
* Indicate the request is a multi-part form request.
*
* @return $this
*/
public function asMultipart()
{
return $this->bodyFormat('multipart');
}
/**
* Specify the body format of the request.
*
* @param string $format
* @return $this
*/
public function bodyFormat(string $format)
{
return tap($this, function ($request) use ($format) {
$this->bodyFormat = $format;
});
}
/**
* Specify the request's content type.
*
* @param string $contentType
* @return $this
*/
public function contentType(string $contentType)
{
return $this->withHeaders(['Content-Type' => $contentType]);
}
/**
* Indicate that JSON should be returned by the server.
*
* @return $this
*/
public function acceptJson()
{
return $this->accept('application/json');
}
/**
* Indicate the type of content that should be returned by the server.
*
* @param string $contentType
* @return $this
*/
public function accept($contentType)
{
return $this->withHeaders(['Accept' => $contentType]);
}
/**
* Add the given headers to the request.
*
* @param array $headers
* @return $this
*/
public function withHeaders(array $headers)
{
return tap($this, function ($request) use ($headers) {
return $this->options = array_merge_recursive($this->options, [
'headers' => $headers,
]);
});
}
/**
* Specify the basic authentication username and password for the request.
*
* @param string $username
* @param string $password
* @return $this
*/
public function withBasicAuth(string $username, string $password)
{
return tap($this, function ($request) use ($username, $password) {
return $this->options['auth'] = [$username, $password];
});
}
/**
* Specify the digest authentication username and password for the request.
*
* @param string $username
* @param string $password
* @return $this
*/
public function withDigestAuth($username, $password)
{
return tap($this, function ($request) use ($username, $password) {
return $this->options['auth'] = [$username, $password, 'digest'];
});
}
/**
* Specify an authorization token for the request.
*
* @param string $token
* @param string $type
* @return $this
*/
public function withToken($token, $type = 'Bearer')
{
return tap($this, function ($request) use ($token, $type) {
return $this->options['headers']['Authorization'] = trim($type.' '.$token);
});
}
/**
* Specify the cookies that should be included with the request.
*
* @param array $cookies
* @param string $domain
* @return $this
*/
public function withCookies(array $cookies, string $domain)
{
return tap($this, function ($request) use ($cookies, $domain) {
return $this->options = array_merge_recursive($this->options, [
'cookies' => CookieJar::fromArray($cookies, $domain),
]);
});
}
/**
* Indicate that redirects should not be followed.
*
* @return $this
*/
public function withoutRedirecting()
{
return tap($this, function ($request) {
return $this->options['allow_redirects'] = false;
});
}
/**
* Indicate that TLS certificates should not be verified.
*
* @return $this
*/
public function withoutVerifying()
{
return tap($this, function ($request) {
return $this->options['verify'] = false;
});
}
/**
* Specify the path where the body of the response should be stored.
*
* @param string|resource $to
* @return $this
*/
public function sink($to)
{
return tap($this, function ($request) use ($to) {
return $this->options['sink'] = $to;
});
}
/**
* Specify the timeout (in seconds) for the request.
*
* @param int $seconds
* @return $this
*/
public function timeout(int $seconds)
{
return tap($this, function () use ($seconds) {
$this->options['timeout'] = $seconds;
});
}
/**
* Specify the number of times the request should be attempted.
*
* @param int $times
* @param int $sleep
* @return $this
*/
public function retry(int $times, int $sleep = 0)
{
$this->tries = $times;
$this->retryDelay = $sleep;
return $this;
}
/**
* Merge new options into the client.
*
* @param array $options
* @return $this
*/
public function withOptions(array $options)
{
return tap($this, function ($request) use ($options) {
return $this->options = array_merge_recursive($this->options, $options);
});
}
/**
* Add new middleware the client handler stack.
*
* @param callable $middleware
* @return $this
*/
public function withMiddleware(callable $middleware)
{
$this->middleware->push($middleware);
return $this;
}
/**
* Add a new "before sending" callback to the request.
*
* @param callable $callback
* @return $this
*/
public function beforeSending($callback)
{
return tap($this, function () use ($callback) {
$this->beforeSendingCallbacks[] = $callback;
});
}
/**
* Issue a GET request to the given URL.
*
* @param string $url
* @param array|string|null $query
* @return \Illuminate\Http\Client\Response
*/
public function get(string $url, $query = null)
{
return $this->send('GET', $url, [
'query' => $query,
]);
}
/**
* Issue a HEAD request to the given URL.
*
* @param string $url
* @param array|string|null $query
* @return \Illuminate\Http\Client\Response
*/
public function head(string $url, $query = null)
{
return $this->send('HEAD', $url, [
'query' => $query,
]);
}
/**
* Issue a POST request to the given URL.
*
* @param string $url
* @param array $data
* @return \Illuminate\Http\Client\Response
*/
public function post(string $url, array $data = [])
{
return $this->send('POST', $url, [
$this->bodyFormat => $data,
]);
}
/**
* Issue a PATCH request to the given URL.
*
* @param string $url
* @param array $data
* @return \Illuminate\Http\Client\Response
*/
public function patch($url, $data = [])
{
return $this->send('PATCH', $url, [
$this->bodyFormat => $data,
]);
}
/**
* Issue a PUT request to the given URL.
*
* @param string $url
* @param array $data
* @return \Illuminate\Http\Client\Response
*/
public function put($url, $data = [])
{
return $this->send('PUT', $url, [
$this->bodyFormat => $data,
]);
}
/**
* Issue a DELETE request to the given URL.
*
* @param string $url
* @param array $data
* @return \Illuminate\Http\Client\Response
*/
public function delete($url, $data = [])
{
return $this->send('DELETE', $url, empty($data) ? [] : [
$this->bodyFormat => $data,
]);
}
/**
* Send the request to the given URL.
*
* @param string $method
* @param string $url
* @param array $options
* @return \Illuminate\Http\Client\Response
*
* @throws \Exception
*/
public function send(string $method, string $url, array $options = [])
{
$url = ltrim(rtrim($this->baseUrl, '/').'/'.ltrim($url, '/'), '/');
if (isset($options[$this->bodyFormat])) {
if ($this->bodyFormat === 'multipart') {
$options[$this->bodyFormat] = $this->parseMultipartBodyFormat($options[$this->bodyFormat]);
} elseif ($this->bodyFormat === 'body') {
$options[$this->bodyFormat] = $this->pendingBody;
}
if (is_array($options[$this->bodyFormat])) {
$options[$this->bodyFormat] = array_merge(
$options[$this->bodyFormat], $this->pendingFiles
);
}
}
[$this->pendingBody, $this->pendingFiles] = [null, []];
return retry($this->tries ?? 1, function () use ($method, $url, $options) {
try {
$laravelData = $this->parseRequestData($method, $url, $options);
return tap(new Response($this->buildClient()->request($method, $url, $this->mergeOptions([
'laravel_data' => $laravelData,
'on_stats' => function ($transferStats) {
$this->transferStats = $transferStats;
},
], $options))), function ($response) {
$response->cookies = $this->cookies;
$response->transferStats = $this->transferStats;
if ($this->tries > 1 && ! $response->successful()) {
$response->throw();
}
});
} catch (ConnectException $e) {
throw new ConnectionException($e->getMessage(), 0, $e);
}
}, $this->retryDelay ?? 100);
}
/**
* Parse multi-part form data.
*
* @param array $data
* @return array|array[]
*/
protected function parseMultipartBodyFormat(array $data)
{
return collect($data)->map(function ($value, $key) {
return is_array($value) ? $value : ['name' => $key, 'contents' => $value];
})->values()->all();
}
/**
* Get the request data as an array so that we can attach it to the request for convenient assertions.
*
* @param string $method
* @param string $url
* @param array $options
* @return array
*/
protected function parseRequestData($method, $url, array $options)
{
$laravelData = $options[$this->bodyFormat] ?? $options['query'] ?? [];
$urlString = Str::of($url);
if (empty($laravelData) && $method === 'GET' && $urlString->contains('?')) {
$laravelData = (string) $urlString->after('?');
}
if (is_string($laravelData)) {
parse_str($laravelData, $parsedData);
$laravelData = is_array($parsedData) ? $parsedData : [];
}
return $laravelData;
}
/**
* Build the Guzzle client.
*
* @return \GuzzleHttp\Client
*/
public function buildClient()
{
return new Client([
'handler' => $this->buildHandlerStack(),
'cookies' => true,
]);
}
/**
* Build the before sending handler stack.
*
* @return \GuzzleHttp\HandlerStack
*/
public function buildHandlerStack()
{
return tap(HandlerStack::create(), function ($stack) {
$stack->push($this->buildBeforeSendingHandler());
$stack->push($this->buildRecorderHandler());
$stack->push($this->buildStubHandler());
$this->middleware->each(function ($middleware) use ($stack) {
$stack->push($middleware);
});
});
}
/**
* Build the before sending handler.
*
* @return \Closure
*/
public function buildBeforeSendingHandler()
{
return function ($handler) {
return function ($request, $options) use ($handler) {
return $handler($this->runBeforeSendingCallbacks($request, $options), $options);
};
};
}
/**
* Build the recorder handler.
*
* @return \Closure
*/
public function buildRecorderHandler()
{
return function ($handler) {
return function ($request, $options) use ($handler) {
$promise = $handler($this->runBeforeSendingCallbacks($request, $options), $options);
return $promise->then(function ($response) use ($request, $options) {
optional($this->factory)->recordRequestResponsePair(
(new Request($request))->withData($options['laravel_data']),
new Response($response)
);
return $response;
});
};
};
}
/**
* Build the stub handler.
*
* @return \Closure
*/
public function buildStubHandler()
{
return function ($handler) {
return function ($request, $options) use ($handler) {
$response = ($this->stubCallbacks ?? collect())
->map
->__invoke((new Request($request))->withData($options['laravel_data']), $options)
->filter()
->first();
if (is_null($response)) {
return $handler($request, $options);
}
$response = is_array($response) ? Factory::response($response) : $response;
$sink = $options['sink'] ?? null;
if ($sink) {
$response->then($this->sinkStubHandler($sink));
}
return $response;
};
};
}
/**
* Get the sink stub handler callback.
*
* @param string $sink
* @return \Closure
*/
protected function sinkStubHandler($sink)
{
return function ($response) use ($sink) {
$body = $response->getBody()->getContents();
if (is_string($sink)) {
file_put_contents($sink, $body);
return;
}
fwrite($sink, $body);
rewind($sink);
};
}
/**
* Execute the "before sending" callbacks.
*
* @param \GuzzleHttp\Psr7\RequestInterface $request
* @param array $options
* @return \Closure
*/
public function runBeforeSendingCallbacks($request, array $options)
{
return tap($request, function ($request) use ($options) {
$this->beforeSendingCallbacks->each->__invoke(
(new Request($request))->withData($options['laravel_data']),
$options
);
});
}
/**
* Merge the given options with the current request options.
*
* @param array $options
* @return array
*/
public function mergeOptions(...$options)
{
return array_merge_recursive($this->options, ...$options);
}
/**
* Register a stub callable that will intercept requests and be able to return stub responses.
*
* @param callable $callback
* @return $this
*/
public function stub($callback)
{
$this->stubCallbacks = collect($callback);
return $this;
}
}

View File

@@ -0,0 +1,305 @@
<?php
namespace Illuminate\Http\Client;
use ArrayAccess;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use LogicException;
class Request implements ArrayAccess
{
/**
* The underlying PSR request.
*
* @var \Psr\Http\Message\RequestInterface
*/
protected $request;
/**
* The decoded payload for the request.
*
* @var array
*/
protected $data;
/**
* Create a new request instance.
*
* @param \Psr\Http\Message\RequestInterface $request
* @return void
*/
public function __construct($request)
{
$this->request = $request;
}
/**
* Get the request method.
*
* @return string
*/
public function method()
{
return $this->request->getMethod();
}
/**
* Get the URL of the request.
*
* @return string
*/
public function url()
{
return (string) $this->request->getUri();
}
/**
* Determine if the request has a given header.
*
* @param string $key
* @param mixed $value
* @return bool
*/
public function hasHeader($key, $value = null)
{
if (is_null($value)) {
return ! empty($this->request->getHeaders()[$key]);
}
$headers = $this->headers();
if (! Arr::has($headers, $key)) {
return false;
}
$value = is_array($value) ? $value : [$value];
return empty(array_diff($value, $headers[$key]));
}
/**
* Determine if the request has the given headers.
*
* @param array|string $headers
* @return bool
*/
public function hasHeaders($headers)
{
if (is_string($headers)) {
$headers = [$headers => null];
}
foreach ($headers as $key => $value) {
if (! $this->hasHeader($key, $value)) {
return false;
}
}
return true;
}
/**
* Get the values for the header with the given name.
*
* @param string $key
* @return array
*/
public function header($key)
{
return Arr::get($this->headers(), $key, []);
}
/**
* Get the request headers.
*
* @return array
*/
public function headers()
{
return collect($this->request->getHeaders())->mapWithKeys(function ($values, $header) {
return [$header => $values];
})->all();
}
/**
* Get the body of the request.
*
* @return string
*/
public function body()
{
return (string) $this->request->getBody();
}
/**
* Determine if the request contains the given file.
*
* @param string $name
* @param string|null $value
* @param string|null $filename
* @return bool
*/
public function hasFile($name, $value = null, $filename = null)
{
if (! $this->isMultipart()) {
return false;
}
return collect($this->data)->reject(function ($file) use ($name, $value, $filename) {
return $file['name'] != $name ||
($value && $file['contents'] != $value) ||
($filename && $file['filename'] != $filename);
})->count() > 0;
}
/**
* Get the request's data (form parameters or JSON).
*
* @return array
*/
public function data()
{
if ($this->isForm()) {
return $this->parameters();
} elseif ($this->isJson()) {
return $this->json();
}
return $this->data ?? [];
}
/**
* Get the request's form parameters.
*
* @return array
*/
protected function parameters()
{
if (! $this->data) {
parse_str($this->body(), $parameters);
$this->data = $parameters;
}
return $this->data;
}
/**
* Get the JSON decoded body of the request.
*
* @return array
*/
protected function json()
{
if (! $this->data) {
$this->data = json_decode($this->body(), true);
}
return $this->data;
}
/**
* Determine if the request is simple form data.
*
* @return bool
*/
public function isForm()
{
return $this->hasHeader('Content-Type', 'application/x-www-form-urlencoded');
}
/**
* Determine if the request is JSON.
*
* @return bool
*/
public function isJson()
{
return $this->hasHeader('Content-Type') &&
Str::contains($this->header('Content-Type')[0], 'json');
}
/**
* Determine if the request is multipart.
*
* @return bool
*/
public function isMultipart()
{
return $this->hasHeader('Content-Type') &&
Str::contains($this->header('Content-Type')[0], 'multipart');
}
/**
* Set the decoded data on the request.
*
* @param array $data
* @return $this
*/
public function withData(array $data)
{
$this->data = $data;
return $this;
}
/**
* Get the underlying PSR compliant request instance.
*
* @return \Psr\Http\Message\RequestInterface
*/
public function toPsrRequest()
{
return $this->request;
}
/**
* Determine if the given offset exists.
*
* @param string $offset
* @return bool
*/
public function offsetExists($offset)
{
return isset($this->data()[$offset]);
}
/**
* Get the value for a given offset.
*
* @param string $offset
* @return mixed
*/
public function offsetGet($offset)
{
return $this->data()[$offset];
}
/**
* Set the value at the given offset.
*
* @param string $offset
* @param mixed $value
* @return void
*
* @throws \LogicException
*/
public function offsetSet($offset, $value)
{
throw new LogicException('Request data may not be mutated using array access.');
}
/**
* Unset the value at the given offset.
*
* @param string $offset
* @return void
*
* @throws \LogicException
*/
public function offsetUnset($offset)
{
throw new LogicException('Request data may not be mutated using array access.');
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Illuminate\Http\Client;
class RequestException extends HttpClientException
{
/**
* The response instance.
*
* @var \Illuminate\Http\Client\Response
*/
public $response;
/**
* Create a new exception instance.
*
* @param \Illuminate\Http\Client\Response $response
* @return void
*/
public function __construct(Response $response)
{
parent::__construct("HTTP request returned status code {$response->status()}.", $response->status());
$this->response = $response;
}
}

View File

@@ -0,0 +1,285 @@
<?php
namespace Illuminate\Http\Client;
use ArrayAccess;
use Illuminate\Support\Traits\Macroable;
use LogicException;
class Response implements ArrayAccess
{
use Macroable {
__call as macroCall;
}
/**
* The underlying PSR response.
*
* @var \Psr\Http\Message\ResponseInterface
*/
protected $response;
/**
* The decoded JSON response.
*
* @var array
*/
protected $decoded;
/**
* Create a new response instance.
*
* @param \Psr\Http\Message\MessageInterface $response
* @return void
*/
public function __construct($response)
{
$this->response = $response;
}
/**
* Get the body of the response.
*
* @return string
*/
public function body()
{
return (string) $this->response->getBody();
}
/**
* Get the JSON decoded body of the response as an array or scalar value.
*
* @return mixed
*/
public function json()
{
if (! $this->decoded) {
$this->decoded = json_decode($this->body(), true);
}
return $this->decoded;
}
/**
* Get the JSON decoded body of the response as an object.
*
* @return object
*/
public function object()
{
return json_decode($this->body(), false);
}
/**
* Get a header from the response.
*
* @param string $header
* @return string
*/
public function header(string $header)
{
return $this->response->getHeaderLine($header);
}
/**
* Get the headers from the response.
*
* @return array
*/
public function headers()
{
return collect($this->response->getHeaders())->mapWithKeys(function ($v, $k) {
return [$k => $v];
})->all();
}
/**
* Get the status code of the response.
*
* @return int
*/
public function status()
{
return (int) $this->response->getStatusCode();
}
/**
* Get the effective URI of the response.
*
* @return \Psr\Http\Message\UriInterface
*/
public function effectiveUri()
{
return $this->transferStats->getEffectiveUri();
}
/**
* Determine if the request was successful.
*
* @return bool
*/
public function successful()
{
return $this->status() >= 200 && $this->status() < 300;
}
/**
* Determine if the response code was "OK".
*
* @return bool
*/
public function ok()
{
return $this->status() === 200;
}
/**
* Determine if the response was a redirect.
*
* @return bool
*/
public function redirect()
{
return $this->status() >= 300 && $this->status() < 400;
}
/**
* Determine if the response indicates a client or server error occurred.
*
* @return bool
*/
public function failed()
{
return $this->serverError() || $this->clientError();
}
/**
* Determine if the response indicates a client error occurred.
*
* @return bool
*/
public function clientError()
{
return $this->status() >= 400 && $this->status() < 500;
}
/**
* Determine if the response indicates a server error occurred.
*
* @return bool
*/
public function serverError()
{
return $this->status() >= 500;
}
/**
* Get the response cookies.
*
* @return \GuzzleHttp\Cookie\CookieJar
*/
public function cookies()
{
return $this->cookies;
}
/**
* Get the underlying PSR response for the response.
*
* @return \Psr\Http\Message\ResponseInterface
*/
public function toPsrResponse()
{
return $this->response;
}
/**
* Throw an exception if a server or client error occurred.
*
* @return $this
*
* @throws \Illuminate\Http\Client\RequestException
*/
public function throw()
{
if ($this->serverError() || $this->clientError()) {
throw new RequestException($this);
}
return $this;
}
/**
* Determine if the given offset exists.
*
* @param string $offset
* @return bool
*/
public function offsetExists($offset)
{
return isset($this->json()[$offset]);
}
/**
* Get the value for a given offset.
*
* @param string $offset
* @return mixed
*/
public function offsetGet($offset)
{
return $this->json()[$offset];
}
/**
* Set the value at the given offset.
*
* @param string $offset
* @param mixed $value
* @return void
*
* @throws \LogicException
*/
public function offsetSet($offset, $value)
{
throw new LogicException('Response data may not be mutated using array access.');
}
/**
* Unset the value at the given offset.
*
* @param string $offset
* @return void
*
* @throws \LogicException
*/
public function offsetUnset($offset)
{
throw new LogicException('Response data may not be mutated using array access.');
}
/**
* Get the body of the response.
*
* @return string
*/
public function __toString()
{
return $this->body();
}
/**
* Dynamically proxy other methods to the underlying response.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return static::hasMacro($method)
? $this->macroCall($method, $parameters)
: $this->response->{$method}(...$parameters);
}
}

View File

@@ -0,0 +1,153 @@
<?php
namespace Illuminate\Http\Client;
use OutOfBoundsException;
class ResponseSequence
{
/**
* The responses in the sequence.
*
* @var array
*/
protected $responses;
/**
* Indicates that invoking this sequence when it is empty should throw an exception.
*
* @var bool
*/
protected $failWhenEmpty = true;
/**
* The response that should be returned when the sequence is empty.
*
* @var \GuzzleHttp\Promise\PromiseInterface
*/
protected $emptyResponse;
/**
* Create a new response sequence.
*
* @param array $responses
* @return void
*/
public function __construct(array $responses)
{
$this->responses = $responses;
}
/**
* Push a response to the sequence.
*
* @param string|array $body
* @param int $status
* @param array $headers
* @return $this
*/
public function push($body = '', int $status = 200, array $headers = [])
{
$body = is_array($body) ? json_encode($body) : $body;
return $this->pushResponse(
Factory::response($body, $status, $headers)
);
}
/**
* Push a response with the given status code to the sequence.
*
* @param int $status
* @param array $headers
* @return $this
*/
public function pushStatus(int $status, array $headers = [])
{
return $this->pushResponse(
Factory::response('', $status, $headers)
);
}
/**
* Push response with the contents of a file as the body to the sequence.
*
* @param string $filePath
* @param int $status
* @param array $headers
* @return $this
*/
public function pushFile(string $filePath, int $status = 200, array $headers = [])
{
$string = file_get_contents($filePath);
return $this->pushResponse(
Factory::response($string, $status, $headers)
);
}
/**
* Push a response to the sequence.
*
* @param mixed $response
* @return $this
*/
public function pushResponse($response)
{
$this->responses[] = $response;
return $this;
}
/**
* Make the sequence return a default response when it is empty.
*
* @param \GuzzleHttp\Promise\PromiseInterface|\Closure $response
* @return $this
*/
public function whenEmpty($response)
{
$this->failWhenEmpty = false;
$this->emptyResponse = $response;
return $this;
}
/**
* Make the sequence return a default response when it is empty.
*
* @return $this
*/
public function dontFailWhenEmpty()
{
return $this->whenEmpty(Factory::response());
}
/**
* Indicate that this sequence has depleted all of its responses.
*
* @return bool
*/
public function isEmpty()
{
return count($this->responses) === 0;
}
/**
* Get the next response in the sequence.
*
* @return mixed
*/
public function __invoke()
{
if ($this->failWhenEmpty && count($this->responses) === 0) {
throw new OutOfBoundsException('A request was made, but the response sequence is empty.');
}
if (! $this->failWhenEmpty && count($this->responses) === 0) {
return value($this->emptyResponse ?? Factory::response());
}
return array_shift($this->responses);
}
}

View File

@@ -0,0 +1,171 @@
<?php
namespace Illuminate\Http\Concerns;
use Illuminate\Support\Str;
trait InteractsWithContentTypes
{
/**
* Determine if the given content types match.
*
* @param string $actual
* @param string $type
* @return bool
*/
public static function matchesType($actual, $type)
{
if ($actual === $type) {
return true;
}
$split = explode('/', $actual);
return isset($split[1]) && preg_match('#'.preg_quote($split[0], '#').'/.+\+'.preg_quote($split[1], '#').'#', $type);
}
/**
* Determine if the request is sending JSON.
*
* @return bool
*/
public function isJson()
{
return Str::contains($this->header('CONTENT_TYPE') ?? '', ['/json', '+json']);
}
/**
* Determine if the current request probably expects a JSON response.
*
* @return bool
*/
public function expectsJson()
{
return ($this->ajax() && ! $this->pjax() && $this->acceptsAnyContentType()) || $this->wantsJson();
}
/**
* Determine if the current request is asking for JSON.
*
* @return bool
*/
public function wantsJson()
{
$acceptable = $this->getAcceptableContentTypes();
return isset($acceptable[0]) && Str::contains($acceptable[0], ['/json', '+json']);
}
/**
* Determines whether the current requests accepts a given content type.
*
* @param string|array $contentTypes
* @return bool
*/
public function accepts($contentTypes)
{
$accepts = $this->getAcceptableContentTypes();
if (count($accepts) === 0) {
return true;
}
$types = (array) $contentTypes;
foreach ($accepts as $accept) {
if ($accept === '*/*' || $accept === '*') {
return true;
}
foreach ($types as $type) {
if ($this->matchesType($accept, $type) || $accept === strtok($type, '/').'/*') {
return true;
}
}
}
return false;
}
/**
* Return the most suitable content type from the given array based on content negotiation.
*
* @param string|array $contentTypes
* @return string|null
*/
public function prefers($contentTypes)
{
$accepts = $this->getAcceptableContentTypes();
$contentTypes = (array) $contentTypes;
foreach ($accepts as $accept) {
if (in_array($accept, ['*/*', '*'])) {
return $contentTypes[0];
}
foreach ($contentTypes as $contentType) {
$type = $contentType;
if (! is_null($mimeType = $this->getMimeType($contentType))) {
$type = $mimeType;
}
if ($this->matchesType($type, $accept) || $accept === strtok($type, '/').'/*') {
return $contentType;
}
}
}
}
/**
* Determine if the current request accepts any content type.
*
* @return bool
*/
public function acceptsAnyContentType()
{
$acceptable = $this->getAcceptableContentTypes();
return count($acceptable) === 0 || (
isset($acceptable[0]) && ($acceptable[0] === '*/*' || $acceptable[0] === '*')
);
}
/**
* Determines whether a request accepts JSON.
*
* @return bool
*/
public function acceptsJson()
{
return $this->accepts('application/json');
}
/**
* Determines whether a request accepts HTML.
*
* @return bool
*/
public function acceptsHtml()
{
return $this->accepts('text/html');
}
/**
* Get the data format expected in the response.
*
* @param string $default
* @return string
*/
public function format($default = 'html')
{
foreach ($this->getAcceptableContentTypes() as $type) {
if ($format = $this->getFormat($type)) {
return $format;
}
}
return $default;
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Illuminate\Http\Concerns;
trait InteractsWithFlashData
{
/**
* Retrieve an old input item.
*
* @param string|null $key
* @param string|array|null $default
* @return string|array
*/
public function old($key = null, $default = null)
{
return $this->hasSession() ? $this->session()->getOldInput($key, $default) : $default;
}
/**
* Flash the input for the current request to the session.
*
* @return void
*/
public function flash()
{
$this->session()->flashInput($this->input());
}
/**
* Flash only some of the input to the session.
*
* @param array|mixed $keys
* @return void
*/
public function flashOnly($keys)
{
$this->session()->flashInput(
$this->only(is_array($keys) ? $keys : func_get_args())
);
}
/**
* Flash only some of the input to the session.
*
* @param array|mixed $keys
* @return void
*/
public function flashExcept($keys)
{
$this->session()->flashInput(
$this->except(is_array($keys) ? $keys : func_get_args())
);
}
/**
* Flush all of the old input from the session.
*
* @return void
*/
public function flush()
{
$this->session()->flashInput([]);
}
}

View File

@@ -0,0 +1,465 @@
<?php
namespace Illuminate\Http\Concerns;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use SplFileInfo;
use stdClass;
trait InteractsWithInput
{
/**
* Retrieve a server variable from the request.
*
* @param string|null $key
* @param string|array|null $default
* @return string|array|null
*/
public function server($key = null, $default = null)
{
return $this->retrieveItem('server', $key, $default);
}
/**
* Determine if a header is set on the request.
*
* @param string $key
* @return bool
*/
public function hasHeader($key)
{
return ! is_null($this->header($key));
}
/**
* Retrieve a header from the request.
*
* @param string|null $key
* @param string|array|null $default
* @return string|array|null
*/
public function header($key = null, $default = null)
{
return $this->retrieveItem('headers', $key, $default);
}
/**
* Get the bearer token from the request headers.
*
* @return string|null
*/
public function bearerToken()
{
$header = $this->header('Authorization', '');
if (Str::startsWith($header, 'Bearer ')) {
return Str::substr($header, 7);
}
}
/**
* Determine if the request contains a given input item key.
*
* @param string|array $key
* @return bool
*/
public function exists($key)
{
return $this->has($key);
}
/**
* Determine if the request contains a given input item key.
*
* @param string|array $key
* @return bool
*/
public function has($key)
{
$keys = is_array($key) ? $key : func_get_args();
$input = $this->all();
foreach ($keys as $value) {
if (! Arr::has($input, $value)) {
return false;
}
}
return true;
}
/**
* Determine if the request contains any of the given inputs.
*
* @param string|array $keys
* @return bool
*/
public function hasAny($keys)
{
$keys = is_array($keys) ? $keys : func_get_args();
$input = $this->all();
return Arr::hasAny($input, $keys);
}
/**
* Apply the callback if the request contains the given input item key.
*
* @param string $key
* @param callable $callback
* @return $this|mixed
*/
public function whenHas($key, callable $callback)
{
if ($this->has($key)) {
return $callback(data_get($this->all(), $key)) ?: $this;
}
return $this;
}
/**
* Determine if the request contains a non-empty value for an input item.
*
* @param string|array $key
* @return bool
*/
public function filled($key)
{
$keys = is_array($key) ? $key : func_get_args();
foreach ($keys as $value) {
if ($this->isEmptyString($value)) {
return false;
}
}
return true;
}
/**
* Determine if the request contains an empty value for an input item.
*
* @param string|array $key
* @return bool
*/
public function isNotFilled($key)
{
$keys = is_array($key) ? $key : func_get_args();
foreach ($keys as $value) {
if (! $this->isEmptyString($value)) {
return false;
}
}
return true;
}
/**
* Determine if the request contains a non-empty value for any of the given inputs.
*
* @param string|array $keys
* @return bool
*/
public function anyFilled($keys)
{
$keys = is_array($keys) ? $keys : func_get_args();
foreach ($keys as $key) {
if ($this->filled($key)) {
return true;
}
}
return false;
}
/**
* Apply the callback if the request contains a non-empty value for the given input item key.
*
* @param string $key
* @param callable $callback
* @return $this|mixed
*/
public function whenFilled($key, callable $callback)
{
if ($this->filled($key)) {
return $callback(data_get($this->all(), $key)) ?: $this;
}
return $this;
}
/**
* Determine if the request is missing a given input item key.
*
* @param string|array $key
* @return bool
*/
public function missing($key)
{
$keys = is_array($key) ? $key : func_get_args();
return ! $this->has($keys);
}
/**
* Determine if the given input key is an empty string for "has".
*
* @param string $key
* @return bool
*/
protected function isEmptyString($key)
{
$value = $this->input($key);
return ! is_bool($value) && ! is_array($value) && trim((string) $value) === '';
}
/**
* Get the keys for all of the input and files.
*
* @return array
*/
public function keys()
{
return array_merge(array_keys($this->input()), $this->files->keys());
}
/**
* Get all of the input and files for the request.
*
* @param array|mixed|null $keys
* @return array
*/
public function all($keys = null)
{
$input = array_replace_recursive($this->input(), $this->allFiles());
if (! $keys) {
return $input;
}
$results = [];
foreach (is_array($keys) ? $keys : func_get_args() as $key) {
Arr::set($results, $key, Arr::get($input, $key));
}
return $results;
}
/**
* Retrieve an input item from the request.
*
* @param string|null $key
* @param mixed $default
* @return mixed
*/
public function input($key = null, $default = null)
{
return data_get(
$this->getInputSource()->all() + $this->query->all(), $key, $default
);
}
/**
* Retrieve input as a boolean value.
*
* Returns true when value is "1", "true", "on", and "yes". Otherwise, returns false.
*
* @param string|null $key
* @param bool $default
* @return bool
*/
public function boolean($key = null, $default = false)
{
return filter_var($this->input($key, $default), FILTER_VALIDATE_BOOLEAN);
}
/**
* Get a subset containing the provided keys with values from the input data.
*
* @param array|mixed $keys
* @return array
*/
public function only($keys)
{
$results = [];
$input = $this->all();
$placeholder = new stdClass;
foreach (is_array($keys) ? $keys : func_get_args() as $key) {
$value = data_get($input, $key, $placeholder);
if ($value !== $placeholder) {
Arr::set($results, $key, $value);
}
}
return $results;
}
/**
* Get all of the input except for a specified array of items.
*
* @param array|mixed $keys
* @return array
*/
public function except($keys)
{
$keys = is_array($keys) ? $keys : func_get_args();
$results = $this->all();
Arr::forget($results, $keys);
return $results;
}
/**
* Retrieve a query string item from the request.
*
* @param string|null $key
* @param string|array|null $default
* @return string|array|null
*/
public function query($key = null, $default = null)
{
return $this->retrieveItem('query', $key, $default);
}
/**
* Retrieve a request payload item from the request.
*
* @param string|null $key
* @param string|array|null $default
* @return string|array|null
*/
public function post($key = null, $default = null)
{
return $this->retrieveItem('request', $key, $default);
}
/**
* Determine if a cookie is set on the request.
*
* @param string $key
* @return bool
*/
public function hasCookie($key)
{
return ! is_null($this->cookie($key));
}
/**
* Retrieve a cookie from the request.
*
* @param string|null $key
* @param string|array|null $default
* @return string|array|null
*/
public function cookie($key = null, $default = null)
{
return $this->retrieveItem('cookies', $key, $default);
}
/**
* Get an array of all of the files on the request.
*
* @return array
*/
public function allFiles()
{
$files = $this->files->all();
return $this->convertedFiles = $this->convertedFiles ?? $this->convertUploadedFiles($files);
}
/**
* Convert the given array of Symfony UploadedFiles to custom Laravel UploadedFiles.
*
* @param array $files
* @return array
*/
protected function convertUploadedFiles(array $files)
{
return array_map(function ($file) {
if (is_null($file) || (is_array($file) && empty(array_filter($file)))) {
return $file;
}
return is_array($file)
? $this->convertUploadedFiles($file)
: UploadedFile::createFromBase($file);
}, $files);
}
/**
* Determine if the uploaded data contains a file.
*
* @param string $key
* @return bool
*/
public function hasFile($key)
{
if (! is_array($files = $this->file($key))) {
$files = [$files];
}
foreach ($files as $file) {
if ($this->isValidFile($file)) {
return true;
}
}
return false;
}
/**
* Check that the given file is a valid file instance.
*
* @param mixed $file
* @return bool
*/
protected function isValidFile($file)
{
return $file instanceof SplFileInfo && $file->getPath() !== '';
}
/**
* Retrieve a file from the request.
*
* @param string|null $key
* @param mixed $default
* @return \Illuminate\Http\UploadedFile|\Illuminate\Http\UploadedFile[]|array|null
*/
public function file($key = null, $default = null)
{
return data_get($this->allFiles(), $key, $default);
}
/**
* Retrieve a parameter item from a given source.
*
* @param string $source
* @param string $key
* @param string|array|null $default
* @return string|array|null
*/
protected function retrieveItem($source, $key, $default)
{
if (is_null($key)) {
return $this->$source->all();
}
return $this->$source->get($key, $default);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Illuminate\Http\Exceptions;
use RuntimeException;
use Symfony\Component\HttpFoundation\Response;
class HttpResponseException extends RuntimeException
{
/**
* The underlying response instance.
*
* @var \Symfony\Component\HttpFoundation\Response
*/
protected $response;
/**
* Create a new HTTP response exception instance.
*
* @param \Symfony\Component\HttpFoundation\Response $response
* @return void
*/
public function __construct(Response $response)
{
$this->response = $response;
}
/**
* Get the underlying response instance.
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function getResponse()
{
return $this->response;
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Illuminate\Http\Exceptions;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Throwable;
class PostTooLargeException extends HttpException
{
/**
* Create a new "post too large" exception instance.
*
* @param string|null $message
* @param \Throwable|null $previous
* @param array $headers
* @param int $code
* @return void
*/
public function __construct($message = null, Throwable $previous = null, array $headers = [], $code = 0)
{
parent::__construct(413, $message, $previous, $headers, $code);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Illuminate\Http\Exceptions;
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
use Throwable;
class ThrottleRequestsException extends TooManyRequestsHttpException
{
/**
* Create a new throttle requests exception instance.
*
* @param string|null $message
* @param \Throwable|null $previous
* @param array $headers
* @param int $code
* @return void
*/
public function __construct($message = null, Throwable $previous = null, array $headers = [], $code = 0)
{
parent::__construct(null, $message, $previous, $code, $headers);
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Illuminate\Http;
use Symfony\Component\HttpFoundation\File\File as SymfonyFile;
class File extends SymfonyFile
{
use FileHelpers;
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Illuminate\Http;
use Illuminate\Support\Str;
trait FileHelpers
{
/**
* The cache copy of the file's hash name.
*
* @var string
*/
protected $hashName = null;
/**
* Get the fully qualified path to the file.
*
* @return string
*/
public function path()
{
return $this->getRealPath();
}
/**
* Get the file's extension.
*
* @return string
*/
public function extension()
{
return $this->guessExtension();
}
/**
* Get a filename for the file.
*
* @param string|null $path
* @return string
*/
public function hashName($path = null)
{
if ($path) {
$path = rtrim($path, '/').'/';
}
$hash = $this->hashName ?: $this->hashName = Str::random(40);
if ($extension = $this->guessExtension()) {
$extension = '.'.$extension;
}
return $path.$hash.$extension;
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace Illuminate\Http;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Support\Traits\Macroable;
use InvalidArgumentException;
use JsonSerializable;
use Symfony\Component\HttpFoundation\JsonResponse as BaseJsonResponse;
class JsonResponse extends BaseJsonResponse
{
use ResponseTrait, Macroable {
Macroable::__call as macroCall;
}
/**
* Constructor.
*
* @param mixed $data
* @param int $status
* @param array $headers
* @param int $options
* @return void
*/
public function __construct($data = null, $status = 200, $headers = [], $options = 0)
{
$this->encodingOptions = $options;
parent::__construct($data, $status, $headers);
}
/**
* Sets the JSONP callback.
*
* @param string|null $callback
* @return $this
*/
public function withCallback($callback = null)
{
return $this->setCallback($callback);
}
/**
* Get the json_decoded data from the response.
*
* @param bool $assoc
* @param int $depth
* @return mixed
*/
public function getData($assoc = false, $depth = 512)
{
return json_decode($this->data, $assoc, $depth);
}
/**
* {@inheritdoc}
*/
public function setData($data = [])
{
$this->original = $data;
if ($data instanceof Jsonable) {
$this->data = $data->toJson($this->encodingOptions);
} elseif ($data instanceof JsonSerializable) {
$this->data = json_encode($data->jsonSerialize(), $this->encodingOptions);
} elseif ($data instanceof Arrayable) {
$this->data = json_encode($data->toArray(), $this->encodingOptions);
} else {
$this->data = json_encode($data, $this->encodingOptions);
}
if (! $this->hasValidJson(json_last_error())) {
throw new InvalidArgumentException(json_last_error_msg());
}
return $this->update();
}
/**
* Determine if an error occurred during JSON encoding.
*
* @param int $jsonError
* @return bool
*/
protected function hasValidJson($jsonError)
{
if ($jsonError === JSON_ERROR_NONE) {
return true;
}
return $this->hasEncodingOption(JSON_PARTIAL_OUTPUT_ON_ERROR) &&
in_array($jsonError, [
JSON_ERROR_RECURSION,
JSON_ERROR_INF_OR_NAN,
JSON_ERROR_UNSUPPORTED_TYPE,
]);
}
/**
* {@inheritdoc}
*/
public function setEncodingOptions($options)
{
$this->encodingOptions = (int) $options;
return $this->setData($this->getData());
}
/**
* Determine if a JSON encoding option is set.
*
* @param int $option
* @return bool
*/
public function hasEncodingOption($option)
{
return (bool) ($this->encodingOptions & $option);
}
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Taylor Otwell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,27 @@
<?php
namespace Illuminate\Http\Middleware;
use Closure;
use Symfony\Component\HttpFoundation\Response;
class CheckResponseForModifications
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$response = $next($request);
if ($response instanceof Response) {
$response->isNotModified($request);
}
return $response;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Illuminate\Http\Middleware;
use Closure;
class FrameGuard
{
/**
* Handle the given request and get the response.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return \Symfony\Component\HttpFoundation\Response
*/
public function handle($request, Closure $next)
{
$response = $next($request);
$response->headers->set('X-Frame-Options', 'SAMEORIGIN', false);
return $response;
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Illuminate\Http\Middleware;
use Closure;
use Illuminate\Support\Carbon;
class SetCacheHeaders
{
/**
* Add cache related HTTP headers.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|array $options
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \InvalidArgumentException
*/
public function handle($request, Closure $next, $options = [])
{
$response = $next($request);
if (! $request->isMethodCacheable() || ! $response->getContent()) {
return $response;
}
if (is_string($options)) {
$options = $this->parseOptions($options);
}
if (isset($options['etag']) && $options['etag'] === true) {
$options['etag'] = md5($response->getContent());
}
if (isset($options['last_modified'])) {
if (is_numeric($options['last_modified'])) {
$options['last_modified'] = Carbon::createFromTimestamp($options['last_modified']);
} else {
$options['last_modified'] = Carbon::parse($options['last_modified']);
}
}
$response->setCache($options);
$response->isNotModified($request);
return $response;
}
/**
* Parse the given header options.
*
* @param string $options
* @return array
*/
protected function parseOptions($options)
{
return collect(explode(';', $options))->mapWithKeys(function ($option) {
$data = explode('=', $option, 2);
return [$data[0] => $data[1] ?? true];
})->all();
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace Illuminate\Http\Middleware;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\Request;
abstract class TrustHosts
{
/**
* The application instance.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* Create a new middleware instance.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function __construct(Application $app)
{
$this->app = $app;
}
/**
* Get the host patterns that should be trusted.
*
* @return array
*/
abstract public function hosts();
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param callable $next
* @return \Illuminate\Http\Response
*/
public function handle(Request $request, $next)
{
if ($this->shouldSpecifyTrustedHosts()) {
Request::setTrustedHosts(array_filter($this->hosts()));
}
return $next($request);
}
/**
* Determine if the application should specify trusted hosts.
*
* @return bool
*/
protected function shouldSpecifyTrustedHosts()
{
return config('app.env') !== 'local' &&
! $this->app->runningUnitTests();
}
/**
* Get a regular expression matching the application URL and all of its subdomains.
*
* @return string|null
*/
protected function allSubdomainsOfApplicationUrl()
{
if ($host = parse_url($this->app['config']->get('app.url'), PHP_URL_HOST)) {
return '^(.+\.)?'.preg_quote($host).'$';
}
}
}

View File

@@ -0,0 +1,258 @@
<?php
namespace Illuminate\Http;
use Illuminate\Contracts\Support\MessageProvider;
use Illuminate\Session\Store as SessionStore;
use Illuminate\Support\MessageBag;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\ForwardsCalls;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Support\ViewErrorBag;
use Symfony\Component\HttpFoundation\File\UploadedFile as SymfonyUploadedFile;
use Symfony\Component\HttpFoundation\RedirectResponse as BaseRedirectResponse;
class RedirectResponse extends BaseRedirectResponse
{
use ForwardsCalls, ResponseTrait, Macroable {
Macroable::__call as macroCall;
}
/**
* The request instance.
*
* @var \Illuminate\Http\Request
*/
protected $request;
/**
* The session store instance.
*
* @var \Illuminate\Session\Store
*/
protected $session;
/**
* Flash a piece of data to the session.
*
* @param string|array $key
* @param mixed $value
* @return $this
*/
public function with($key, $value = null)
{
$key = is_array($key) ? $key : [$key => $value];
foreach ($key as $k => $v) {
$this->session->flash($k, $v);
}
return $this;
}
/**
* Add multiple cookies to the response.
*
* @param array $cookies
* @return $this
*/
public function withCookies(array $cookies)
{
foreach ($cookies as $cookie) {
$this->headers->setCookie($cookie);
}
return $this;
}
/**
* Flash an array of input to the session.
*
* @param array|null $input
* @return $this
*/
public function withInput(array $input = null)
{
$this->session->flashInput($this->removeFilesFromInput(
! is_null($input) ? $input : $this->request->input()
));
return $this;
}
/**
* Remove all uploaded files form the given input array.
*
* @param array $input
* @return array
*/
protected function removeFilesFromInput(array $input)
{
foreach ($input as $key => $value) {
if (is_array($value)) {
$input[$key] = $this->removeFilesFromInput($value);
}
if ($value instanceof SymfonyUploadedFile) {
unset($input[$key]);
}
}
return $input;
}
/**
* Flash an array of input to the session.
*
* @return $this
*/
public function onlyInput()
{
return $this->withInput($this->request->only(func_get_args()));
}
/**
* Flash an array of input to the session.
*
* @return $this
*/
public function exceptInput()
{
return $this->withInput($this->request->except(func_get_args()));
}
/**
* Flash a container of errors to the session.
*
* @param \Illuminate\Contracts\Support\MessageProvider|array|string $provider
* @param string $key
* @return $this
*/
public function withErrors($provider, $key = 'default')
{
$value = $this->parseErrors($provider);
$errors = $this->session->get('errors', new ViewErrorBag);
if (! $errors instanceof ViewErrorBag) {
$errors = new ViewErrorBag;
}
$this->session->flash(
'errors', $errors->put($key, $value)
);
return $this;
}
/**
* Add a fragment identifier to the URL.
*
* @param string $fragment
* @return $this
*/
public function withFragment($fragment)
{
return $this->withoutFragment()
->setTargetUrl($this->getTargetUrl().'#'.Str::after($fragment, '#'));
}
/**
* Remove any fragment identifier from the response URL.
*
* @return $this
*/
public function withoutFragment()
{
return $this->setTargetUrl(Str::before($this->getTargetUrl(), '#'));
}
/**
* Parse the given errors into an appropriate value.
*
* @param \Illuminate\Contracts\Support\MessageProvider|array|string $provider
* @return \Illuminate\Support\MessageBag
*/
protected function parseErrors($provider)
{
if ($provider instanceof MessageProvider) {
return $provider->getMessageBag();
}
return new MessageBag((array) $provider);
}
/**
* Get the original response content.
*
* @return null
*/
public function getOriginalContent()
{
//
}
/**
* Get the request instance.
*
* @return \Illuminate\Http\Request|null
*/
public function getRequest()
{
return $this->request;
}
/**
* Set the request instance.
*
* @param \Illuminate\Http\Request $request
* @return void
*/
public function setRequest(Request $request)
{
$this->request = $request;
}
/**
* Get the session store instance.
*
* @return \Illuminate\Session\Store|null
*/
public function getSession()
{
return $this->session;
}
/**
* Set the session store instance.
*
* @param \Illuminate\Session\Store $session
* @return void
*/
public function setSession(SessionStore $session)
{
$this->session = $session;
}
/**
* Dynamically bind flash data in the session.
*
* @param string $method
* @param array $parameters
* @return mixed
*
* @throws \BadMethodCallException
*/
public function __call($method, $parameters)
{
if (static::hasMacro($method)) {
return $this->macroCall($method, $parameters);
}
if (Str::startsWith($method, 'with')) {
return $this->with(Str::snake(substr($method, 4)), $parameters[0]);
}
static::throwBadMethodCallException($method);
}
}

View File

@@ -0,0 +1,702 @@
<?php
namespace Illuminate\Http;
use ArrayAccess;
use Closure;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
use RuntimeException;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
/**
* @method array validate(array $rules, ...$params)
* @method array validateWithBag(string $errorBag, array $rules, ...$params)
* @method bool hasValidSignature(bool $absolute = true)
*/
class Request extends SymfonyRequest implements Arrayable, ArrayAccess
{
use Concerns\InteractsWithContentTypes,
Concerns\InteractsWithFlashData,
Concerns\InteractsWithInput,
Macroable;
/**
* The decoded JSON content for the request.
*
* @var \Symfony\Component\HttpFoundation\ParameterBag|null
*/
protected $json;
/**
* All of the converted files for the request.
*
* @var array
*/
protected $convertedFiles;
/**
* The user resolver callback.
*
* @var \Closure
*/
protected $userResolver;
/**
* The route resolver callback.
*
* @var \Closure
*/
protected $routeResolver;
/**
* Create a new Illuminate HTTP request from server variables.
*
* @return static
*/
public static function capture()
{
static::enableHttpMethodParameterOverride();
return static::createFromBase(SymfonyRequest::createFromGlobals());
}
/**
* Return the Request instance.
*
* @return $this
*/
public function instance()
{
return $this;
}
/**
* Get the request method.
*
* @return string
*/
public function method()
{
return $this->getMethod();
}
/**
* Get the root URL for the application.
*
* @return string
*/
public function root()
{
return rtrim($this->getSchemeAndHttpHost().$this->getBaseUrl(), '/');
}
/**
* Get the URL (no query string) for the request.
*
* @return string
*/
public function url()
{
return rtrim(preg_replace('/\?.*/', '', $this->getUri()), '/');
}
/**
* Get the full URL for the request.
*
* @return string
*/
public function fullUrl()
{
$query = $this->getQueryString();
$question = $this->getBaseUrl().$this->getPathInfo() === '/' ? '/?' : '?';
return $query ? $this->url().$question.$query : $this->url();
}
/**
* Get the full URL for the request with the added query string parameters.
*
* @param array $query
* @return string
*/
public function fullUrlWithQuery(array $query)
{
$question = $this->getBaseUrl().$this->getPathInfo() === '/' ? '/?' : '?';
return count($this->query()) > 0
? $this->url().$question.Arr::query(array_merge($this->query(), $query))
: $this->fullUrl().$question.Arr::query($query);
}
/**
* Get the current path info for the request.
*
* @return string
*/
public function path()
{
$pattern = trim($this->getPathInfo(), '/');
return $pattern == '' ? '/' : $pattern;
}
/**
* Get the current decoded path info for the request.
*
* @return string
*/
public function decodedPath()
{
return rawurldecode($this->path());
}
/**
* Get a segment from the URI (1 based index).
*
* @param int $index
* @param string|null $default
* @return string|null
*/
public function segment($index, $default = null)
{
return Arr::get($this->segments(), $index - 1, $default);
}
/**
* Get all of the segments for the request path.
*
* @return array
*/
public function segments()
{
$segments = explode('/', $this->decodedPath());
return array_values(array_filter($segments, function ($value) {
return $value !== '';
}));
}
/**
* Determine if the current request URI matches a pattern.
*
* @param mixed ...$patterns
* @return bool
*/
public function is(...$patterns)
{
$path = $this->decodedPath();
foreach ($patterns as $pattern) {
if (Str::is($pattern, $path)) {
return true;
}
}
return false;
}
/**
* Determine if the route name matches a given pattern.
*
* @param mixed ...$patterns
* @return bool
*/
public function routeIs(...$patterns)
{
return $this->route() && $this->route()->named(...$patterns);
}
/**
* Determine if the current request URL and query string matches a pattern.
*
* @param mixed ...$patterns
* @return bool
*/
public function fullUrlIs(...$patterns)
{
$url = $this->fullUrl();
foreach ($patterns as $pattern) {
if (Str::is($pattern, $url)) {
return true;
}
}
return false;
}
/**
* Determine if the request is the result of an AJAX call.
*
* @return bool
*/
public function ajax()
{
return $this->isXmlHttpRequest();
}
/**
* Determine if the request is the result of an PJAX call.
*
* @return bool
*/
public function pjax()
{
return $this->headers->get('X-PJAX') == true;
}
/**
* Determine if the request is the result of an prefetch call.
*
* @return bool
*/
public function prefetch()
{
return strcasecmp($this->server->get('HTTP_X_MOZ'), 'prefetch') === 0 ||
strcasecmp($this->headers->get('Purpose'), 'prefetch') === 0;
}
/**
* Determine if the request is over HTTPS.
*
* @return bool
*/
public function secure()
{
return $this->isSecure();
}
/**
* Get the client IP address.
*
* @return string|null
*/
public function ip()
{
return $this->getClientIp();
}
/**
* Get the client IP addresses.
*
* @return array
*/
public function ips()
{
return $this->getClientIps();
}
/**
* Get the client user agent.
*
* @return string|null
*/
public function userAgent()
{
return $this->headers->get('User-Agent');
}
/**
* Merge new input into the current request's input array.
*
* @param array $input
* @return $this
*/
public function merge(array $input)
{
$this->getInputSource()->add($input);
return $this;
}
/**
* Replace the input for the current request.
*
* @param array $input
* @return $this
*/
public function replace(array $input)
{
$this->getInputSource()->replace($input);
return $this;
}
/**
* This method belongs to Symfony HttpFoundation and is not usually needed when using Laravel.
*
* Instead, you may use the "input" method.
*
* @param string $key
* @param mixed $default
* @return mixed
*/
public function get(string $key, $default = null)
{
return parent::get($key, $default);
}
/**
* Get the JSON payload for the request.
*
* @param string|null $key
* @param mixed $default
* @return \Symfony\Component\HttpFoundation\ParameterBag|mixed
*/
public function json($key = null, $default = null)
{
if (! isset($this->json)) {
$this->json = new ParameterBag((array) json_decode($this->getContent(), true));
}
if (is_null($key)) {
return $this->json;
}
return data_get($this->json->all(), $key, $default);
}
/**
* Get the input source for the request.
*
* @return \Symfony\Component\HttpFoundation\ParameterBag
*/
protected function getInputSource()
{
if ($this->isJson()) {
return $this->json();
}
return in_array($this->getRealMethod(), ['GET', 'HEAD']) ? $this->query : $this->request;
}
/**
* Create a new request instance from the given Laravel request.
*
* @param \Illuminate\Http\Request $from
* @param \Illuminate\Http\Request|null $to
* @return static
*/
public static function createFrom(self $from, $to = null)
{
$request = $to ?: new static;
$files = $from->files->all();
$files = is_array($files) ? array_filter($files) : $files;
$request->initialize(
$from->query->all(),
$from->request->all(),
$from->attributes->all(),
$from->cookies->all(),
$files,
$from->server->all(),
$from->getContent()
);
$request->headers->replace($from->headers->all());
$request->setJson($from->json());
if ($session = $from->getSession()) {
$request->setLaravelSession($session);
}
$request->setUserResolver($from->getUserResolver());
$request->setRouteResolver($from->getRouteResolver());
return $request;
}
/**
* Create an Illuminate request from a Symfony instance.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* @return static
*/
public static function createFromBase(SymfonyRequest $request)
{
$newRequest = (new static)->duplicate(
$request->query->all(), $request->request->all(), $request->attributes->all(),
$request->cookies->all(), $request->files->all(), $request->server->all()
);
$newRequest->headers->replace($request->headers->all());
$newRequest->content = $request->content;
$newRequest->request = $newRequest->getInputSource();
return $newRequest;
}
/**
* {@inheritdoc}
*/
public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null)
{
return parent::duplicate($query, $request, $attributes, $cookies, $this->filterFiles($files), $server);
}
/**
* Filter the given array of files, removing any empty values.
*
* @param mixed $files
* @return mixed
*/
protected function filterFiles($files)
{
if (! $files) {
return;
}
foreach ($files as $key => $file) {
if (is_array($file)) {
$files[$key] = $this->filterFiles($files[$key]);
}
if (empty($files[$key])) {
unset($files[$key]);
}
}
return $files;
}
/**
* Get the session associated with the request.
*
* @return \Illuminate\Session\Store
*
* @throws \RuntimeException
*/
public function session()
{
if (! $this->hasSession()) {
throw new RuntimeException('Session store not set on request.');
}
return $this->session;
}
/**
* Get the session associated with the request.
*
* @return \Illuminate\Session\Store|null
*/
public function getSession()
{
return $this->session;
}
/**
* Set the session instance on the request.
*
* @param \Illuminate\Contracts\Session\Session $session
* @return void
*/
public function setLaravelSession($session)
{
$this->session = $session;
}
/**
* Get the user making the request.
*
* @param string|null $guard
* @return mixed
*/
public function user($guard = null)
{
return call_user_func($this->getUserResolver(), $guard);
}
/**
* Get the route handling the request.
*
* @param string|null $param
* @param mixed $default
* @return \Illuminate\Routing\Route|object|string|null
*/
public function route($param = null, $default = null)
{
$route = call_user_func($this->getRouteResolver());
if (is_null($route) || is_null($param)) {
return $route;
}
return $route->parameter($param, $default);
}
/**
* Get a unique fingerprint for the request / route / IP address.
*
* @return string
*
* @throws \RuntimeException
*/
public function fingerprint()
{
if (! $route = $this->route()) {
throw new RuntimeException('Unable to generate fingerprint. Route unavailable.');
}
return sha1(implode('|', array_merge(
$route->methods(),
[$route->getDomain(), $route->uri(), $this->ip()]
)));
}
/**
* Set the JSON payload for the request.
*
* @param \Symfony\Component\HttpFoundation\ParameterBag $json
* @return $this
*/
public function setJson($json)
{
$this->json = $json;
return $this;
}
/**
* Get the user resolver callback.
*
* @return \Closure
*/
public function getUserResolver()
{
return $this->userResolver ?: function () {
//
};
}
/**
* Set the user resolver callback.
*
* @param \Closure $callback
* @return $this
*/
public function setUserResolver(Closure $callback)
{
$this->userResolver = $callback;
return $this;
}
/**
* Get the route resolver callback.
*
* @return \Closure
*/
public function getRouteResolver()
{
return $this->routeResolver ?: function () {
//
};
}
/**
* Set the route resolver callback.
*
* @param \Closure $callback
* @return $this
*/
public function setRouteResolver(Closure $callback)
{
$this->routeResolver = $callback;
return $this;
}
/**
* Get all of the input and files for the request.
*
* @return array
*/
public function toArray()
{
return $this->all();
}
/**
* Determine if the given offset exists.
*
* @param string $offset
* @return bool
*/
public function offsetExists($offset)
{
return Arr::has(
$this->all() + $this->route()->parameters(),
$offset
);
}
/**
* Get the value at the given offset.
*
* @param string $offset
* @return mixed
*/
public function offsetGet($offset)
{
return $this->__get($offset);
}
/**
* Set the value at the given offset.
*
* @param string $offset
* @param mixed $value
* @return void
*/
public function offsetSet($offset, $value)
{
$this->getInputSource()->set($offset, $value);
}
/**
* Remove the value at the given offset.
*
* @param string $offset
* @return void
*/
public function offsetUnset($offset)
{
$this->getInputSource()->remove($offset);
}
/**
* Check if an input element is set on the request.
*
* @param string $key
* @return bool
*/
public function __isset($key)
{
return ! is_null($this->__get($key));
}
/**
* Get an input element from the request.
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
return Arr::get($this->all(), $key, function () use ($key) {
return $this->route($key);
});
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Illuminate\Http\Resources;
use Illuminate\Pagination\AbstractPaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
trait CollectsResources
{
/**
* Map the given collection resource into its individual resources.
*
* @param mixed $resource
* @return mixed
*/
protected function collectResource($resource)
{
if ($resource instanceof MissingValue) {
return $resource;
}
if (is_array($resource)) {
$resource = new Collection($resource);
}
$collects = $this->collects();
$this->collection = $collects && ! $resource->first() instanceof $collects
? $resource->mapInto($collects)
: $resource->toBase();
return $resource instanceof AbstractPaginator
? $resource->setCollection($this->collection)
: $this->collection;
}
/**
* Get the resource that this resource collects.
*
* @return string|null
*/
protected function collects()
{
if ($this->collects) {
return $this->collects;
}
if (Str::endsWith(class_basename($this), 'Collection') &&
class_exists($class = Str::replaceLast('Collection', '', get_class($this)))) {
return $class;
}
}
/**
* Get an iterator for the resource collection.
*
* @return \ArrayIterator
*/
public function getIterator()
{
return $this->collection->getIterator();
}
}

View File

@@ -0,0 +1,243 @@
<?php
namespace Illuminate\Http\Resources;
use Illuminate\Support\Arr;
trait ConditionallyLoadsAttributes
{
/**
* Filter the given data, removing any optional values.
*
* @param array $data
* @return array
*/
protected function filter($data)
{
$index = -1;
foreach ($data as $key => $value) {
$index++;
if (is_array($value)) {
$data[$key] = $this->filter($value);
continue;
}
if (is_numeric($key) && $value instanceof MergeValue) {
return $this->mergeData(
$data, $index, $this->filter($value->data),
array_values($value->data) === $value->data
);
}
if ($value instanceof self && is_null($value->resource)) {
$data[$key] = null;
}
}
return $this->removeMissingValues($data);
}
/**
* Merge the given data in at the given index.
*
* @param array $data
* @param int $index
* @param array $merge
* @param bool $numericKeys
* @return array
*/
protected function mergeData($data, $index, $merge, $numericKeys)
{
if ($numericKeys) {
return $this->removeMissingValues(array_merge(
array_merge(array_slice($data, 0, $index, true), $merge),
$this->filter(array_values(array_slice($data, $index + 1, null, true)))
));
}
return $this->removeMissingValues(array_slice($data, 0, $index, true) +
$merge +
$this->filter(array_slice($data, $index + 1, null, true)));
}
/**
* Remove the missing values from the filtered data.
*
* @param array $data
* @return array
*/
protected function removeMissingValues($data)
{
$numericKeys = true;
foreach ($data as $key => $value) {
if (($value instanceof PotentiallyMissing && $value->isMissing()) ||
($value instanceof self &&
$value->resource instanceof PotentiallyMissing &&
$value->isMissing())) {
unset($data[$key]);
} else {
$numericKeys = $numericKeys && is_numeric($key);
}
}
if (property_exists($this, 'preserveKeys') && $this->preserveKeys === true) {
return $data;
}
return $numericKeys ? array_values($data) : $data;
}
/**
* Retrieve a value based on a given condition.
*
* @param bool $condition
* @param mixed $value
* @param mixed $default
* @return \Illuminate\Http\Resources\MissingValue|mixed
*/
protected function when($condition, $value, $default = null)
{
if ($condition) {
return value($value);
}
return func_num_args() === 3 ? value($default) : new MissingValue;
}
/**
* Merge a value into the array.
*
* @param mixed $value
* @return \Illuminate\Http\Resources\MergeValue|mixed
*/
protected function merge($value)
{
return $this->mergeWhen(true, $value);
}
/**
* Merge a value based on a given condition.
*
* @param bool $condition
* @param mixed $value
* @return \Illuminate\Http\Resources\MergeValue|mixed
*/
protected function mergeWhen($condition, $value)
{
return $condition ? new MergeValue(value($value)) : new MissingValue;
}
/**
* Merge the given attributes.
*
* @param array $attributes
* @return \Illuminate\Http\Resources\MergeValue
*/
protected function attributes($attributes)
{
return new MergeValue(
Arr::only($this->resource->toArray(), $attributes)
);
}
/**
* Retrieve an accessor when it has been appended.
*
* @param string $attribute
* @param mixed $value
* @param mixed $default
* @return \Illuminate\Http\Resources\MissingValue|mixed
*/
protected function whenAppended($attribute, $value = null, $default = null)
{
if ($this->resource->hasAppended($attribute)) {
return func_num_args() >= 2 ? value($value) : $this->resource->$attribute;
}
return func_num_args() === 3 ? value($default) : new MissingValue;
}
/**
* Retrieve a relationship if it has been loaded.
*
* @param string $relationship
* @param mixed $value
* @param mixed $default
* @return \Illuminate\Http\Resources\MissingValue|mixed
*/
protected function whenLoaded($relationship, $value = null, $default = null)
{
if (func_num_args() < 3) {
$default = new MissingValue;
}
if (! $this->resource->relationLoaded($relationship)) {
return value($default);
}
if (func_num_args() === 1) {
return $this->resource->{$relationship};
}
if ($this->resource->{$relationship} === null) {
return;
}
return value($value);
}
/**
* Execute a callback if the given pivot table has been loaded.
*
* @param string $table
* @param mixed $value
* @param mixed $default
* @return \Illuminate\Http\Resources\MissingValue|mixed
*/
protected function whenPivotLoaded($table, $value, $default = null)
{
return $this->whenPivotLoadedAs('pivot', ...func_get_args());
}
/**
* Execute a callback if the given pivot table with a custom accessor has been loaded.
*
* @param string $accessor
* @param string $table
* @param mixed $value
* @param mixed $default
* @return \Illuminate\Http\Resources\MissingValue|mixed
*/
protected function whenPivotLoadedAs($accessor, $table, $value, $default = null)
{
if (func_num_args() === 3) {
$default = new MissingValue;
}
return $this->when(
$this->resource->$accessor &&
($this->resource->$accessor instanceof $table ||
$this->resource->$accessor->getTable() === $table),
...[$value, $default]
);
}
/**
* Transform the given value if it is present.
*
* @param mixed $value
* @param callable $callback
* @param mixed $default
* @return mixed
*/
protected function transform($value, callable $callback, $default = null)
{
return transform(
$value, $callback, func_num_args() === 3 ? $default : new MissingValue
);
}
}

View File

@@ -0,0 +1,150 @@
<?php
namespace Illuminate\Http\Resources;
use Exception;
use Illuminate\Support\Traits\ForwardsCalls;
trait DelegatesToResource
{
use ForwardsCalls;
/**
* Get the value of the resource's route key.
*
* @return mixed
*/
public function getRouteKey()
{
return $this->resource->getRouteKey();
}
/**
* Get the route key for the resource.
*
* @return string
*/
public function getRouteKeyName()
{
return $this->resource->getRouteKeyName();
}
/**
* Retrieve the model for a bound value.
*
* @param mixed $value
* @param string|null $field
* @return void
*
* @throws \Exception
*/
public function resolveRouteBinding($value, $field = null)
{
throw new Exception('Resources may not be implicitly resolved from route bindings.');
}
/**
* Retrieve the model for a bound value.
*
* @param string $childType
* @param mixed $value
* @param string|null $field
* @return void
*
* @throws \Exception
*/
public function resolveChildRouteBinding($childType, $value, $field = null)
{
throw new Exception('Resources may not be implicitly resolved from route bindings.');
}
/**
* Determine if the given attribute exists.
*
* @param mixed $offset
* @return bool
*/
public function offsetExists($offset)
{
return isset($this->resource[$offset]);
}
/**
* Get the value for a given offset.
*
* @param mixed $offset
* @return mixed
*/
public function offsetGet($offset)
{
return $this->resource[$offset];
}
/**
* Set the value for a given offset.
*
* @param mixed $offset
* @param mixed $value
* @return void
*/
public function offsetSet($offset, $value)
{
$this->resource[$offset] = $value;
}
/**
* Unset the value for a given offset.
*
* @param mixed $offset
* @return void
*/
public function offsetUnset($offset)
{
unset($this->resource[$offset]);
}
/**
* Determine if an attribute exists on the resource.
*
* @param string $key
* @return bool
*/
public function __isset($key)
{
return isset($this->resource->{$key});
}
/**
* Unset an attribute on the resource.
*
* @param string $key
* @return void
*/
public function __unset($key)
{
unset($this->resource->{$key});
}
/**
* Dynamically get properties from the underlying resource.
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
return $this->resource->{$key};
}
/**
* Dynamically pass method calls to the underlying resource.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return $this->forwardCallTo($this->resource, $method, $parameters);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Illuminate\Http\Resources\Json;
class AnonymousResourceCollection extends ResourceCollection
{
/**
* The name of the resource being collected.
*
* @var string
*/
public $collects;
/**
* Create a new anonymous resource collection.
*
* @param mixed $resource
* @param string $collects
* @return void
*/
public function __construct($resource, $collects)
{
$this->collects = $collects;
parent::__construct($resource);
}
}

View File

@@ -0,0 +1,233 @@
<?php
namespace Illuminate\Http\Resources\Json;
use ArrayAccess;
use Illuminate\Container\Container;
use Illuminate\Contracts\Routing\UrlRoutable;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Responsable;
use Illuminate\Database\Eloquent\JsonEncodingException;
use Illuminate\Http\Resources\ConditionallyLoadsAttributes;
use Illuminate\Http\Resources\DelegatesToResource;
use JsonSerializable;
class JsonResource implements ArrayAccess, JsonSerializable, Responsable, UrlRoutable
{
use ConditionallyLoadsAttributes, DelegatesToResource;
/**
* The resource instance.
*
* @var mixed
*/
public $resource;
/**
* The additional data that should be added to the top-level resource array.
*
* @var array
*/
public $with = [];
/**
* The additional meta data that should be added to the resource response.
*
* Added during response construction by the developer.
*
* @var array
*/
public $additional = [];
/**
* The "data" wrapper that should be applied.
*
* @var string
*/
public static $wrap = 'data';
/**
* Create a new resource instance.
*
* @param mixed $resource
* @return void
*/
public function __construct($resource)
{
$this->resource = $resource;
}
/**
* Create a new resource instance.
*
* @param mixed ...$parameters
* @return static
*/
public static function make(...$parameters)
{
return new static(...$parameters);
}
/**
* Create new anonymous resource collection.
*
* @param mixed $resource
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public static function collection($resource)
{
return tap(new AnonymousResourceCollection($resource, static::class), function ($collection) {
if (property_exists(static::class, 'preserveKeys')) {
$collection->preserveKeys = (new static([]))->preserveKeys === true;
}
});
}
/**
* Resolve the resource to an array.
*
* @param \Illuminate\Http\Request|null $request
* @return array
*/
public function resolve($request = null)
{
$data = $this->toArray(
$request = $request ?: Container::getInstance()->make('request')
);
if ($data instanceof Arrayable) {
$data = $data->toArray();
} elseif ($data instanceof JsonSerializable) {
$data = $data->jsonSerialize();
}
return $this->filter((array) $data);
}
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
if (is_null($this->resource)) {
return [];
}
return is_array($this->resource)
? $this->resource
: $this->resource->toArray();
}
/**
* Convert the model instance to JSON.
*
* @param int $options
* @return string
*
* @throws \Illuminate\Database\Eloquent\JsonEncodingException
*/
public function toJson($options = 0)
{
$json = json_encode($this->jsonSerialize(), $options);
if (JSON_ERROR_NONE !== json_last_error()) {
throw JsonEncodingException::forResource($this, json_last_error_msg());
}
return $json;
}
/**
* Get any additional data that should be returned with the resource array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function with($request)
{
return $this->with;
}
/**
* Add additional meta data to the resource response.
*
* @param array $data
* @return $this
*/
public function additional(array $data)
{
$this->additional = $data;
return $this;
}
/**
* Customize the response for a request.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\JsonResponse $response
* @return void
*/
public function withResponse($request, $response)
{
//
}
/**
* Set the string that should wrap the outer-most resource array.
*
* @param string $value
* @return void
*/
public static function wrap($value)
{
static::$wrap = $value;
}
/**
* Disable wrapping of the outer-most resource array.
*
* @return void
*/
public static function withoutWrapping()
{
static::$wrap = null;
}
/**
* Transform the resource into an HTTP response.
*
* @param \Illuminate\Http\Request|null $request
* @return \Illuminate\Http\JsonResponse
*/
public function response($request = null)
{
return $this->toResponse(
$request ?: Container::getInstance()->make('request')
);
}
/**
* Create an HTTP response that represents the object.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function toResponse($request)
{
return (new ResourceResponse($this))->toResponse($request);
}
/**
* Prepare the resource for JSON serialization.
*
* @return array
*/
public function jsonSerialize()
{
return $this->resolve(Container::getInstance()->make('request'));
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace Illuminate\Http\Resources\Json;
use Illuminate\Support\Arr;
class PaginatedResourceResponse extends ResourceResponse
{
/**
* Create an HTTP response that represents the object.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function toResponse($request)
{
return tap(response()->json(
$this->wrap(
$this->resource->resolve($request),
array_merge_recursive(
$this->paginationInformation($request),
$this->resource->with($request),
$this->resource->additional
)
),
$this->calculateStatus()
), function ($response) use ($request) {
$response->original = $this->resource->resource->map(function ($item) {
return is_array($item) ? Arr::get($item, 'resource') : $item->resource;
});
$this->resource->withResponse($request, $response);
});
}
/**
* Add the pagination information to the response.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
protected function paginationInformation($request)
{
$paginated = $this->resource->resource->toArray();
return [
'links' => $this->paginationLinks($paginated),
'meta' => $this->meta($paginated),
];
}
/**
* Get the pagination links for the response.
*
* @param array $paginated
* @return array
*/
protected function paginationLinks($paginated)
{
return [
'first' => $paginated['first_page_url'] ?? null,
'last' => $paginated['last_page_url'] ?? null,
'prev' => $paginated['prev_page_url'] ?? null,
'next' => $paginated['next_page_url'] ?? null,
];
}
/**
* Gather the meta data for the response.
*
* @param array $paginated
* @return array
*/
protected function meta($paginated)
{
return Arr::except($paginated, [
'data',
'first_page_url',
'last_page_url',
'prev_page_url',
'next_page_url',
]);
}
}

View File

@@ -0,0 +1,134 @@
<?php
namespace Illuminate\Http\Resources\Json;
use Countable;
use Illuminate\Http\Resources\CollectsResources;
use Illuminate\Pagination\AbstractPaginator;
use IteratorAggregate;
class ResourceCollection extends JsonResource implements Countable, IteratorAggregate
{
use CollectsResources;
/**
* The resource that this resource collects.
*
* @var string
*/
public $collects;
/**
* The mapped collection instance.
*
* @var \Illuminate\Support\Collection
*/
public $collection;
/**
* Indicates if all existing request query parameters should be added to pagination links.
*
* @var bool
*/
protected $preserveAllQueryParameters = false;
/**
* The query parameters that should be added to the pagination links.
*
* @var array|null
*/
protected $queryParameters;
/**
* Create a new resource instance.
*
* @param mixed $resource
* @return void
*/
public function __construct($resource)
{
parent::__construct($resource);
$this->resource = $this->collectResource($resource);
}
/**
* Indicate that all current query parameters should be appended to pagination links.
*
* @return $this
*/
public function preserveQuery()
{
$this->preserveAllQueryParameters = true;
return $this;
}
/**
* Specify the query string parameters that should be present on pagination links.
*
* @param array $query
* @return $this
*/
public function withQuery(array $query)
{
$this->preserveAllQueryParameters = false;
$this->queryParameters = $query;
return $this;
}
/**
* Return the count of items in the resource collection.
*
* @return int
*/
public function count()
{
return $this->collection->count();
}
/**
* Transform the resource into a JSON array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return $this->collection->map->toArray($request)->all();
}
/**
* Create an HTTP response that represents the object.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function toResponse($request)
{
if ($this->resource instanceof AbstractPaginator) {
return $this->preparePaginatedResponse($request);
}
return parent::toResponse($request);
}
/**
* Create a paginate-aware HTTP response.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
protected function preparePaginatedResponse($request)
{
if ($this->preserveAllQueryParameters) {
$this->resource->appends($request->query());
} elseif (! is_null($this->queryParameters)) {
$this->resource->appends($this->queryParameters);
}
return (new PaginatedResourceResponse($this))->toResponse($request);
}
}

View File

@@ -0,0 +1,120 @@
<?php
namespace Illuminate\Http\Resources\Json;
use Illuminate\Contracts\Support\Responsable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
class ResourceResponse implements Responsable
{
/**
* The underlying resource.
*
* @var mixed
*/
public $resource;
/**
* Create a new resource response.
*
* @param mixed $resource
* @return void
*/
public function __construct($resource)
{
$this->resource = $resource;
}
/**
* Create an HTTP response that represents the object.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function toResponse($request)
{
return tap(response()->json(
$this->wrap(
$this->resource->resolve($request),
$this->resource->with($request),
$this->resource->additional
),
$this->calculateStatus()
), function ($response) use ($request) {
$response->original = $this->resource->resource;
$this->resource->withResponse($request, $response);
});
}
/**
* Wrap the given data if necessary.
*
* @param array $data
* @param array $with
* @param array $additional
* @return array
*/
protected function wrap($data, $with = [], $additional = [])
{
if ($data instanceof Collection) {
$data = $data->all();
}
if ($this->haveDefaultWrapperAndDataIsUnwrapped($data)) {
$data = [$this->wrapper() => $data];
} elseif ($this->haveAdditionalInformationAndDataIsUnwrapped($data, $with, $additional)) {
$data = [($this->wrapper() ?? 'data') => $data];
}
return array_merge_recursive($data, $with, $additional);
}
/**
* Determine if we have a default wrapper and the given data is unwrapped.
*
* @param array $data
* @return bool
*/
protected function haveDefaultWrapperAndDataIsUnwrapped($data)
{
return $this->wrapper() && ! array_key_exists($this->wrapper(), $data);
}
/**
* Determine if "with" data has been added and our data is unwrapped.
*
* @param array $data
* @param array $with
* @param array $additional
* @return bool
*/
protected function haveAdditionalInformationAndDataIsUnwrapped($data, $with, $additional)
{
return (! empty($with) || ! empty($additional)) &&
(! $this->wrapper() ||
! array_key_exists($this->wrapper(), $data));
}
/**
* Get the default data wrapper for the resource.
*
* @return string
*/
protected function wrapper()
{
return get_class($this->resource)::$wrap;
}
/**
* Calculate the appropriate status code for the response.
*
* @return int
*/
protected function calculateStatus()
{
return $this->resource->resource instanceof Model &&
$this->resource->resource->wasRecentlyCreated ? 201 : 200;
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Illuminate\Http\Resources;
use Illuminate\Support\Collection;
use JsonSerializable;
class MergeValue
{
/**
* The data to be merged.
*
* @var array
*/
public $data;
/**
* Create new merge value instance.
*
* @param \Illuminate\Support\Collection|\JsonSerializable|array $data
* @return void
*/
public function __construct($data)
{
if ($data instanceof Collection) {
$this->data = $data->all();
} elseif ($data instanceof JsonSerializable) {
$this->data = $data->jsonSerialize();
} else {
$this->data = $data;
}
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Illuminate\Http\Resources;
class MissingValue implements PotentiallyMissing
{
/**
* Determine if the object should be considered "missing".
*
* @return bool
*/
public function isMissing()
{
return true;
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Illuminate\Http\Resources;
interface PotentiallyMissing
{
/**
* Determine if the object should be considered "missing".
*
* @return bool
*/
public function isMissing();
}

View File

@@ -0,0 +1,101 @@
<?php
namespace Illuminate\Http;
use ArrayObject;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Support\Traits\Macroable;
use JsonSerializable;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
class Response extends SymfonyResponse
{
use ResponseTrait, Macroable {
Macroable::__call as macroCall;
}
/**
* Create a new HTTP response.
*
* @param mixed $content
* @param int $status
* @param array $headers
* @return void
*
* @throws \InvalidArgumentException
*/
public function __construct($content = '', $status = 200, array $headers = [])
{
$this->headers = new ResponseHeaderBag($headers);
$this->setContent($content);
$this->setStatusCode($status);
$this->setProtocolVersion('1.0');
}
/**
* Set the content on the response.
*
* @param mixed $content
* @return $this
*/
public function setContent($content)
{
$this->original = $content;
// If the content is "JSONable" we will set the appropriate header and convert
// the content to JSON. This is useful when returning something like models
// from routes that will be automatically transformed to their JSON form.
if ($this->shouldBeJson($content)) {
$this->header('Content-Type', 'application/json');
$content = $this->morphToJson($content);
}
// If this content implements the "Renderable" interface then we will call the
// render method on the object so we will avoid any "__toString" exceptions
// that might be thrown and have their errors obscured by PHP's handling.
elseif ($content instanceof Renderable) {
$content = $content->render();
}
parent::setContent($content);
return $this;
}
/**
* Determine if the given content should be turned into JSON.
*
* @param mixed $content
* @return bool
*/
protected function shouldBeJson($content)
{
return $content instanceof Arrayable ||
$content instanceof Jsonable ||
$content instanceof ArrayObject ||
$content instanceof JsonSerializable ||
is_array($content);
}
/**
* Morph the given content into JSON.
*
* @param mixed $content
* @return string
*/
protected function morphToJson($content)
{
if ($content instanceof Jsonable) {
return $content->toJson();
} elseif ($content instanceof Arrayable) {
return json_encode($content->toArray());
}
return json_encode($content);
}
}

View File

@@ -0,0 +1,153 @@
<?php
namespace Illuminate\Http;
use Illuminate\Http\Exceptions\HttpResponseException;
use Symfony\Component\HttpFoundation\HeaderBag;
use Throwable;
trait ResponseTrait
{
/**
* The original content of the response.
*
* @var mixed
*/
public $original;
/**
* The exception that triggered the error response (if applicable).
*
* @var \Throwable|null
*/
public $exception;
/**
* Get the status code for the response.
*
* @return int
*/
public function status()
{
return $this->getStatusCode();
}
/**
* Get the content of the response.
*
* @return string
*/
public function content()
{
return $this->getContent();
}
/**
* Get the original response content.
*
* @return mixed
*/
public function getOriginalContent()
{
$original = $this->original;
return $original instanceof self ? $original->{__FUNCTION__}() : $original;
}
/**
* Set a header on the Response.
*
* @param string $key
* @param array|string $values
* @param bool $replace
* @return $this
*/
public function header($key, $values, $replace = true)
{
$this->headers->set($key, $values, $replace);
return $this;
}
/**
* Add an array of headers to the response.
*
* @param \Symfony\Component\HttpFoundation\HeaderBag|array $headers
* @return $this
*/
public function withHeaders($headers)
{
if ($headers instanceof HeaderBag) {
$headers = $headers->all();
}
foreach ($headers as $key => $value) {
$this->headers->set($key, $value);
}
return $this;
}
/**
* Add a cookie to the response.
*
* @param \Symfony\Component\HttpFoundation\Cookie|mixed $cookie
* @return $this
*/
public function cookie($cookie)
{
return $this->withCookie(...func_get_args());
}
/**
* Add a cookie to the response.
*
* @param \Symfony\Component\HttpFoundation\Cookie|mixed $cookie
* @return $this
*/
public function withCookie($cookie)
{
if (is_string($cookie) && function_exists('cookie')) {
$cookie = cookie(...func_get_args());
}
$this->headers->setCookie($cookie);
return $this;
}
/**
* Get the callback of the response.
*
* @return string|null
*/
public function getCallback()
{
return $this->callback ?? null;
}
/**
* Set the exception to attach to the response.
*
* @param \Throwable $e
* @return $this
*/
public function withException(Throwable $e)
{
$this->exception = $e;
return $this;
}
/**
* Throws the response in a HttpResponseException instance.
*
* @return void
*
* @throws \Illuminate\Http\Exceptions\HttpResponseException
*/
public function throwResponse()
{
throw new HttpResponseException($this);
}
}

View File

@@ -0,0 +1,147 @@
<?php
namespace Illuminate\Http\Testing;
use Illuminate\Http\UploadedFile;
class File extends UploadedFile
{
/**
* The name of the file.
*
* @var string
*/
public $name;
/**
* The temporary file resource.
*
* @var resource
*/
public $tempFile;
/**
* The "size" to report.
*
* @var int
*/
public $sizeToReport;
/**
* The MIME type to report.
*
* @var string|null
*/
public $mimeTypeToReport;
/**
* Create a new file instance.
*
* @param string $name
* @param resource $tempFile
* @return void
*/
public function __construct($name, $tempFile)
{
$this->name = $name;
$this->tempFile = $tempFile;
parent::__construct(
$this->tempFilePath(), $name, $this->getMimeType(),
null, true
);
}
/**
* Create a new fake file.
*
* @param string $name
* @param string|int $kilobytes
* @return \Illuminate\Http\Testing\File
*/
public static function create($name, $kilobytes = 0)
{
return (new FileFactory)->create($name, $kilobytes);
}
/**
* Create a new fake file with content.
*
* @param string $name
* @param string $content
* @return \Illuminate\Http\Testing\File
*/
public static function createWithContent($name, $content)
{
return (new FileFactory)->createWithContent($name, $content);
}
/**
* Create a new fake image.
*
* @param string $name
* @param int $width
* @param int $height
* @return \Illuminate\Http\Testing\File
*/
public static function image($name, $width = 10, $height = 10)
{
return (new FileFactory)->image($name, $width, $height);
}
/**
* Set the "size" of the file in kilobytes.
*
* @param int $kilobytes
* @return $this
*/
public function size($kilobytes)
{
$this->sizeToReport = $kilobytes * 1024;
return $this;
}
/**
* Get the size of the file.
*
* @return int
*/
public function getSize()
{
return $this->sizeToReport ?: parent::getSize();
}
/**
* Set the "MIME type" for the file.
*
* @param string $mimeType
* @return $this
*/
public function mimeType($mimeType)
{
$this->mimeTypeToReport = $mimeType;
return $this;
}
/**
* Get the MIME type of the file.
*
* @return string
*/
public function getMimeType()
{
return $this->mimeTypeToReport ?: MimeType::from($this->name);
}
/**
* Get the path to the temporary file.
*
* @return string
*/
protected function tempFilePath()
{
return stream_get_meta_data($this->tempFile)['uri'];
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace Illuminate\Http\Testing;
use Illuminate\Support\Str;
class FileFactory
{
/**
* Create a new fake file.
*
* @param string $name
* @param string|int $kilobytes
* @param string|null $mimeType
* @return \Illuminate\Http\Testing\File
*/
public function create($name, $kilobytes = 0, $mimeType = null)
{
if (is_string($kilobytes)) {
return $this->createWithContent($name, $kilobytes);
}
return tap(new File($name, tmpfile()), function ($file) use ($kilobytes, $mimeType) {
$file->sizeToReport = $kilobytes * 1024;
$file->mimeTypeToReport = $mimeType;
});
}
/**
* Create a new fake file with content.
*
* @param string $name
* @param string $content
* @return \Illuminate\Http\Testing\File
*/
public function createWithContent($name, $content)
{
$tmpfile = tmpfile();
fwrite($tmpfile, $content);
return tap(new File($name, $tmpfile), function ($file) use ($tmpfile) {
$file->sizeToReport = fstat($tmpfile)['size'];
});
}
/**
* Create a new fake image.
*
* @param string $name
* @param int $width
* @param int $height
* @return \Illuminate\Http\Testing\File
*/
public function image($name, $width = 10, $height = 10)
{
return new File($name, $this->generateImage(
$width, $height, Str::endsWith(Str::lower($name), ['.jpg', '.jpeg']) ? 'jpeg' : 'png'
));
}
/**
* Generate a dummy image of the given width and height.
*
* @param int $width
* @param int $height
* @param string $type
* @return resource
*/
protected function generateImage($width, $height, $type)
{
return tap(tmpfile(), function ($temp) use ($width, $height, $type) {
ob_start();
$image = imagecreatetruecolor($width, $height);
switch ($type) {
case 'jpeg':
imagejpeg($image);
break;
case 'png':
imagepng($image);
break;
}
fwrite($temp, ob_get_clean());
});
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Illuminate\Http\Testing;
use Illuminate\Support\Arr;
use Symfony\Component\Mime\MimeTypes;
class MimeType
{
/**
* The mime types instance.
*
* @var \Symfony\Component\Mime\MimeTypes|null
*/
private static $mime;
/**
* Get the mime types instance.
*
* @return \Symfony\Component\Mime\MimeTypesInterface
*/
public static function getMimeTypes()
{
if (self::$mime === null) {
self::$mime = new MimeTypes();
}
return self::$mime;
}
/**
* Get the MIME type for a file based on the file's extension.
*
* @param string $filename
* @return string
*/
public static function from($filename)
{
$extension = pathinfo($filename, PATHINFO_EXTENSION);
return self::get($extension);
}
/**
* Get the MIME type for a given extension or return all mimes.
*
* @param string $extension
* @return string
*/
public static function get($extension)
{
return Arr::first(self::getMimeTypes()->getMimeTypes($extension)) ?? 'application/octet-stream';
}
/**
* Search for the extension of a given MIME type.
*
* @param string $mimeType
* @return string|null
*/
public static function search($mimeType)
{
return Arr::first(self::getMimeTypes()->getExtensions($mimeType));
}
}

View File

@@ -0,0 +1,149 @@
<?php
namespace Illuminate\Http;
use Illuminate\Container\Container;
use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Http\Testing\FileFactory;
use Illuminate\Support\Arr;
use Illuminate\Support\Traits\Macroable;
use Symfony\Component\HttpFoundation\File\UploadedFile as SymfonyUploadedFile;
class UploadedFile extends SymfonyUploadedFile
{
use FileHelpers, Macroable;
/**
* Begin creating a new file fake.
*
* @return \Illuminate\Http\Testing\FileFactory
*/
public static function fake()
{
return new FileFactory;
}
/**
* Store the uploaded file on a filesystem disk.
*
* @param string $path
* @param array|string $options
* @return string|false
*/
public function store($path, $options = [])
{
return $this->storeAs($path, $this->hashName(), $this->parseOptions($options));
}
/**
* Store the uploaded file on a filesystem disk with public visibility.
*
* @param string $path
* @param array|string $options
* @return string|false
*/
public function storePublicly($path, $options = [])
{
$options = $this->parseOptions($options);
$options['visibility'] = 'public';
return $this->storeAs($path, $this->hashName(), $options);
}
/**
* Store the uploaded file on a filesystem disk with public visibility.
*
* @param string $path
* @param string $name
* @param array|string $options
* @return string|false
*/
public function storePubliclyAs($path, $name, $options = [])
{
$options = $this->parseOptions($options);
$options['visibility'] = 'public';
return $this->storeAs($path, $name, $options);
}
/**
* Store the uploaded file on a filesystem disk.
*
* @param string $path
* @param string $name
* @param array|string $options
* @return string|false
*/
public function storeAs($path, $name, $options = [])
{
$options = $this->parseOptions($options);
$disk = Arr::pull($options, 'disk');
return Container::getInstance()->make(FilesystemFactory::class)->disk($disk)->putFileAs(
$path, $this, $name, $options
);
}
/**
* Get the contents of the uploaded file.
*
* @return bool|string
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public function get()
{
if (! $this->isValid()) {
throw new FileNotFoundException("File does not exist at path {$this->getPathname()}.");
}
return file_get_contents($this->getPathname());
}
/**
* Get the file's extension supplied by the client.
*
* @return string
*/
public function clientExtension()
{
return $this->guessClientExtension();
}
/**
* Create a new file instance from a base instance.
*
* @param \Symfony\Component\HttpFoundation\File\UploadedFile $file
* @param bool $test
* @return static
*/
public static function createFromBase(SymfonyUploadedFile $file, $test = false)
{
return $file instanceof static ? $file : new static(
$file->getPathname(),
$file->getClientOriginalName(),
$file->getClientMimeType(),
$file->getError(),
$test
);
}
/**
* Parse and format the given options.
*
* @param array|string $options
* @return array
*/
protected function parseOptions($options)
{
if (is_string($options)) {
$options = ['disk' => $options];
}
return $options;
}
}

View File

@@ -0,0 +1,43 @@
{
"name": "illuminate/http",
"description": "The Illuminate Http package.",
"license": "MIT",
"homepage": "https://laravel.com",
"support": {
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"require": {
"php": "^7.2.5|^8.0",
"ext-json": "*",
"illuminate/session": "^7.0",
"illuminate/support": "^7.0",
"symfony/http-foundation": "^5.0",
"symfony/http-kernel": "^5.0",
"symfony/mime": "^5.0"
},
"autoload": {
"psr-4": {
"Illuminate\\Http\\": ""
}
},
"suggest": {
"ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().",
"guzzlehttp/guzzle": "Required to use the HTTP Client (^6.3.1|^7.0.1)."
},
"extra": {
"branch-alias": {
"dev-master": "7.x-dev"
}
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev"
}