Upgrade framework

This commit is contained in:
2023-11-14 16:54:35 +01:00
parent 1648a5cd42
commit 4fcf6fffcc
10548 changed files with 693138 additions and 466698 deletions

View File

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

View File

@@ -0,0 +1,26 @@
<?php
namespace Illuminate\Http\Client\Events;
use Illuminate\Http\Client\Request;
class ConnectionFailed
{
/**
* The request instance.
*
* @var \Illuminate\Http\Client\Request
*/
public $request;
/**
* Create a new event instance.
*
* @param \Illuminate\Http\Client\Request $request
* @return void
*/
public function __construct(Request $request)
{
$this->request = $request;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Illuminate\Http\Client\Events;
use Illuminate\Http\Client\Request;
class RequestSending
{
/**
* The request instance.
*
* @var \Illuminate\Http\Client\Request
*/
public $request;
/**
* Create a new event instance.
*
* @param \Illuminate\Http\Client\Request $request
* @return void
*/
public function __construct(Request $request)
{
$this->request = $request;
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Illuminate\Http\Client\Events;
use Illuminate\Http\Client\Request;
use Illuminate\Http\Client\Response;
class ResponseReceived
{
/**
* The request instance.
*
* @var \Illuminate\Http\Client\Request
*/
public $request;
/**
* The response instance.
*
* @var \Illuminate\Http\Client\Response
*/
public $response;
/**
* Create a new event instance.
*
* @param \Illuminate\Http\Client\Request $request
* @param \Illuminate\Http\Client\Response $response
* @return void
*/
public function __construct(Request $request, Response $response)
{
$this->request = $request;
$this->response = $response;
}
}

View File

@@ -0,0 +1,387 @@
<?php
namespace Illuminate\Http\Client;
use Closure;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7\Response as Psr7Response;
use GuzzleHttp\TransferStats;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
use PHPUnit\Framework\Assert as PHPUnit;
/**
* @mixin \Illuminate\Http\Client\PendingRequest
*/
class Factory
{
use Macroable {
__call as macroCall;
}
/**
* The event dispatcher implementation.
*
* @var \Illuminate\Contracts\Events\Dispatcher|null
*/
protected $dispatcher;
/**
* 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 = [];
/**
* Indicates that an exception should be thrown if any request is not faked.
*
* @var bool
*/
protected $preventStrayRequests = false;
/**
* Create a new factory instance.
*
* @param \Illuminate\Contracts\Events\Dispatcher|null $dispatcher
* @return void
*/
public function __construct(Dispatcher $dispatcher = null)
{
$this->dispatcher = $dispatcher;
$this->stubCallbacks = collect();
}
/**
* Create a new response instance for use during stubbing.
*
* @param array|string|null $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';
}
$response = new Psr7Response($status, $headers, $body);
return class_exists(\GuzzleHttp\Promise\Create::class)
? \GuzzleHttp\Promise\Create::promiseFor($response)
: \GuzzleHttp\Promise\promise_for($response);
}
/**
* 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|null $callback
* @return $this
*/
public function fake($callback = null)
{
$this->record();
$this->recorded = [];
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([
function ($request, $options) use ($callback) {
$response = $callback instanceof Closure
? $callback($request, $options)
: $callback;
if ($response instanceof PromiseInterface) {
$options['on_stats'](new TransferStats(
$request->toPsrRequest(),
$response->wait(),
));
}
return $response;
},
]));
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;
});
}
/**
* Indicate that an exception should be thrown if any request is not faked.
*
* @param bool $prevent
* @return $this
*/
public function preventStrayRequests($prevent = true)
{
$this->preventStrayRequests = $prevent;
return $this;
}
/**
* Indicate that an exception should not be thrown if any request is not faked.
*
* @return $this
*/
public function allowStrayRequests()
{
return $this->preventStrayRequests(false);
}
/**
* 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 the given request was sent in the given order.
*
* @param array $callbacks
* @return void
*/
public function assertSentInOrder($callbacks)
{
$this->assertSentCount(count($callbacks));
foreach ($callbacks as $index => $url) {
$callback = is_callable($url) ? $url : function ($request) use ($url) {
return $request->url() == $url;
};
PHPUnit::assertTrue($callback(
$this->recorded[$index][0],
$this->recorded[$index][1]
), 'An expected request (#'.($index + 1).') 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 = null)
{
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]);
});
}
/**
* Create a new pending request instance for this factory.
*
* @return \Illuminate\Http\Client\PendingRequest
*/
protected function newPendingRequest()
{
return new PendingRequest($this);
}
/**
* Get the current event dispatcher implementation.
*
* @return \Illuminate\Contracts\Events\Dispatcher|null
*/
public function getDispatcher()
{
return $this->dispatcher;
}
/**
* 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($this->newPendingRequest(), function ($request) {
$request->stub($this->stubCallbacks)->preventStrayRequests($this->preventStrayRequests);
})->{$method}(...$parameters);
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,92 @@
<?php
namespace Illuminate\Http\Client;
use GuzzleHttp\Utils;
/**
* @mixin \Illuminate\Http\Client\Factory
*/
class Pool
{
/**
* The factory instance.
*
* @var \Illuminate\Http\Client\Factory
*/
protected $factory;
/**
* The handler function for the Guzzle client.
*
* @var callable
*/
protected $handler;
/**
* The pool of requests.
*
* @var array
*/
protected $pool = [];
/**
* Create a new requests pool.
*
* @param \Illuminate\Http\Client\Factory|null $factory
* @return void
*/
public function __construct(Factory $factory = null)
{
$this->factory = $factory ?: new Factory();
if (method_exists(Utils::class, 'chooseHandler')) {
$this->handler = Utils::chooseHandler();
} else {
$this->handler = \GuzzleHttp\choose_handler();
}
}
/**
* Add a request to the pool with a key.
*
* @param string $key
* @return \Illuminate\Http\Client\PendingRequest
*/
public function as(string $key)
{
return $this->pool[$key] = $this->asyncRequest();
}
/**
* Retrieve a new async pending request.
*
* @return \Illuminate\Http\Client\PendingRequest
*/
protected function asyncRequest()
{
return $this->factory->setHandler($this->handler)->async();
}
/**
* Retrieve the requests in the pool.
*
* @return array
*/
public function getRequests()
{
return $this->pool;
}
/**
* Add a request to the pool with a numeric index.
*
* @param string $method
* @param array $parameters
* @return \Illuminate\Http\Client\PendingRequest
*/
public function __call($method, $parameters)
{
return $this->pool[] = $this->asyncRequest()->$method(...$parameters);
}
}

View File

@@ -0,0 +1,305 @@
<?php
namespace Illuminate\Http\Client;
use ArrayAccess;
use Illuminate\Support\Arr;
use Illuminate\Support\Traits\Macroable;
use LogicException;
class Request implements ArrayAccess
{
use Macroable;
/**
* 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 $this->request->getHeaders();
}
/**
* 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): bool
{
return isset($this->data()[$offset]);
}
/**
* Get the value for a given offset.
*
* @param string $offset
* @return mixed
*/
public function offsetGet($offset): mixed
{
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): void
{
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): void
{
throw new LogicException('Request data may not be mutated using array access.');
}
}

View File

@@ -0,0 +1,43 @@
<?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($this->prepareMessage($response), $response->status());
$this->response = $response;
}
/**
* Prepare the exception message.
*
* @param \Illuminate\Http\Client\Response $response
* @return string
*/
protected function prepareMessage(Response $response)
{
$message = "HTTP request returned status code {$response->status()}";
$summary = class_exists(\GuzzleHttp\Psr7\Message::class)
? \GuzzleHttp\Psr7\Message::bodySummary($response->toPsrResponse())
: \GuzzleHttp\Psr7\get_message_body_summary($response->toPsrResponse());
return is_null($summary) ? $message : $message .= ":\n{$summary}\n";
}
}

View File

@@ -0,0 +1,415 @@
<?php
namespace Illuminate\Http\Client;
use ArrayAccess;
use Illuminate\Support\Collection;
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;
/**
* The request cookies.
*
* @var \GuzzleHttp\Cookie\CookieJar
*/
public $cookies;
/**
* The transfer stats for the request.
*
* @var \GuzzleHttp\TransferStats|null
*/
public $transferStats;
/**
* 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.
*
* @param string|null $key
* @param mixed $default
* @return mixed
*/
public function json($key = null, $default = null)
{
if (! $this->decoded) {
$this->decoded = json_decode($this->body(), true);
}
if (is_null($key)) {
return $this->decoded;
}
return data_get($this->decoded, $key, $default);
}
/**
* Get the JSON decoded body of the response as an object.
*
* @return object|array
*/
public function object()
{
return json_decode($this->body(), false);
}
/**
* Get the JSON decoded body of the response as a collection.
*
* @param string|null $key
* @return \Illuminate\Support\Collection
*/
public function collect($key = null)
{
return Collection::make($this->json($key));
}
/**
* 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 $this->response->getHeaders();
}
/**
* Get the status code of the response.
*
* @return int
*/
public function status()
{
return (int) $this->response->getStatusCode();
}
/**
* Get the reason phrase of the response.
*
* @return string
*/
public function reason()
{
return $this->response->getReasonPhrase();
}
/**
* Get the effective URI of the response.
*
* @return \Psr\Http\Message\UriInterface|null
*/
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 was a 401 "Unauthorized" response.
*
* @return bool
*/
public function unauthorized()
{
return $this->status() === 401;
}
/**
* Determine if the response was a 403 "Forbidden" response.
*
* @return bool
*/
public function forbidden()
{
return $this->status() === 403;
}
/**
* 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;
}
/**
* Execute the given callback if there was a server or client error.
*
* @param callable $callback
* @return $this
*/
public function onError(callable $callback)
{
if ($this->failed()) {
$callback($this);
}
return $this;
}
/**
* Get the response cookies.
*
* @return \GuzzleHttp\Cookie\CookieJar
*/
public function cookies()
{
return $this->cookies;
}
/**
* Get the handler stats of the response.
*
* @return array
*/
public function handlerStats()
{
return $this->transferStats?->getHandlerStats() ?? [];
}
/**
* Close the stream and any underlying resources.
*
* @return $this
*/
public function close()
{
$this->response->getBody()->close();
return $this;
}
/**
* Get the underlying PSR response for the response.
*
* @return \Psr\Http\Message\ResponseInterface
*/
public function toPsrResponse()
{
return $this->response;
}
/**
* Create an exception if a server or client error occurred.
*
* @return \Illuminate\Http\Client\RequestException|null
*/
public function toException()
{
if ($this->failed()) {
return new RequestException($this);
}
}
/**
* Throw an exception if a server or client error occurred.
*
* @param \Closure|null $callback
* @return $this
*
* @throws \Illuminate\Http\Client\RequestException
*/
public function throw()
{
$callback = func_get_args()[0] ?? null;
if ($this->failed()) {
throw tap($this->toException(), function ($exception) use ($callback) {
if ($callback && is_callable($callback)) {
$callback($this, $exception);
}
});
}
return $this;
}
/**
* Throw an exception if a server or client error occurred and the given condition evaluates to true.
*
* @param \Closure|bool $condition
* @param \Closure|null $throwCallback
* @return $this
*
* @throws \Illuminate\Http\Client\RequestException
*/
public function throwIf($condition)
{
return value($condition, $this) ? $this->throw(func_get_args()[1] ?? null) : $this;
}
/**
* Determine if the given offset exists.
*
* @param string $offset
* @return bool
*/
public function offsetExists($offset): bool
{
return isset($this->json()[$offset]);
}
/**
* Get the value for a given offset.
*
* @param string $offset
* @return mixed
*/
public function offsetGet($offset): mixed
{
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): void
{
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): void
{
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,156 @@
<?php
namespace Illuminate\Http\Client;
use Illuminate\Support\Traits\Macroable;
use OutOfBoundsException;
class ResponseSequence
{
use Macroable;
/**
* 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|null $body
* @param int $status
* @param array $headers
* @return $this
*/
public function push($body = null, int $status = 200, array $headers = [])
{
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
*
* @throws \OutOfBoundsException
*/
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,45 @@
<?php
namespace Illuminate\Http\Concerns;
use Illuminate\Support\Collection;
trait CanBePrecognitive
{
/**
* Filter the given array of rules into an array of rules that are included in precognitive headers.
*
* @param array $rules
* @return array
*/
public function filterPrecognitiveRules($rules)
{
if (! $this->headers->has('Precognition-Validate-Only')) {
return $rules;
}
return Collection::make($rules)
->only(explode(',', $this->header('Precognition-Validate-Only')))
->all();
}
/**
* Determine if the request is attempting to be precognitive.
*
* @return bool
*/
public function isAttemptingPrecognition()
{
return $this->header('Precognition') === 'true';
}
/**
* Determine if the request is precognitive.
*
* @return bool
*/
public function isPrecognitive()
{
return $this->attributes->get('precognitive', false);
}
}

View File

@@ -6,24 +6,6 @@ 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.
*
@@ -31,7 +13,7 @@ trait InteractsWithContentTypes
*/
public function isJson()
{
return Str::contains($this->header('CONTENT_TYPE'), ['/json', '+json']);
return Str::contains($this->header('CONTENT_TYPE') ?? '', ['/json', '+json']);
}
/**
@@ -41,11 +23,11 @@ trait InteractsWithContentTypes
*/
public function expectsJson()
{
return ($this->ajax() && ! $this->pjax()) || $this->wantsJson();
return ($this->ajax() && ! $this->pjax() && $this->acceptsAnyContentType()) || $this->wantsJson();
}
/**
* Determine if the current request is asking for JSON in return.
* Determine if the current request is asking for JSON.
*
* @return bool
*/
@@ -53,7 +35,7 @@ trait InteractsWithContentTypes
{
$acceptable = $this->getAcceptableContentTypes();
return isset($acceptable[0]) && Str::contains($acceptable[0], ['/json', '+json']);
return isset($acceptable[0]) && Str::contains(strtolower($acceptable[0]), ['/json', '+json']);
}
/**
@@ -78,6 +60,10 @@ trait InteractsWithContentTypes
}
foreach ($types as $type) {
$accept = strtolower($accept);
$type = strtolower($type);
if ($this->matchesType($accept, $type) || $accept === strtok($type, '/').'/*') {
return true;
}
@@ -111,6 +97,10 @@ trait InteractsWithContentTypes
$type = $mimeType;
}
$accept = strtolower($accept);
$type = strtolower($type);
if ($this->matchesType($type, $accept) || $accept === strtok($type, '/').'/*') {
return $contentType;
}
@@ -118,6 +108,20 @@ trait InteractsWithContentTypes
}
}
/**
* 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.
*
@@ -138,6 +142,24 @@ trait InteractsWithContentTypes
return $this->accepts('text/html');
}
/**
* 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);
}
/**
* Get the data format expected in the response.
*

View File

@@ -2,18 +2,22 @@
namespace Illuminate\Http\Concerns;
use Illuminate\Database\Eloquent\Model;
trait InteractsWithFlashData
{
/**
* Retrieve an old input item.
*
* @param string $key
* @param string|array|null $default
* @return string|array
* @param string|null $key
* @param \Illuminate\Database\Eloquent\Model|string|array|null $default
* @return string|array|null
*/
public function old($key = null, $default = null)
{
return $this->session()->getOldInput($key, $default);
$default = $default instanceof Model ? $default->getAttribute($key) : $default;
return $this->hasSession() ? $this->session()->getOldInput($key, $default) : $default;
}
/**

View File

@@ -2,19 +2,22 @@
namespace Illuminate\Http\Concerns;
use SplFileInfo;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Date;
use SplFileInfo;
use stdClass;
use Symfony\Component\HttpFoundation\InputBag;
use Symfony\Component\VarDumper\VarDumper;
trait InteractsWithInput
{
/**
* Retrieve a server variable from the request.
*
* @param string $key
* @param string|null $key
* @param string|array|null $default
* @return string|array
* @return string|array|null
*/
public function server($key = null, $default = null)
{
@@ -35,9 +38,9 @@ trait InteractsWithInput
/**
* Retrieve a header from the request.
*
* @param string $key
* @param string|null $key
* @param string|array|null $default
* @return string|array
* @return string|array|null
*/
public function header($key = null, $default = null)
{
@@ -53,8 +56,12 @@ trait InteractsWithInput
{
$header = $this->header('Authorization', '');
if (Str::startsWith($header, 'Bearer ')) {
return Str::substr($header, 7);
$position = strrpos($header, 'Bearer ');
if ($position !== false) {
$header = substr($header, $position + 7);
return str_contains($header, ',') ? strstr($header, ',', true) : $header;
}
}
@@ -65,6 +72,17 @@ trait InteractsWithInput
* @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();
@@ -79,13 +97,49 @@ trait InteractsWithInput
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
* @param callable|null $default
* @return $this|mixed
*/
public function whenHas($key, callable $callback, callable $default = null)
{
if ($this->has($key)) {
return $callback(data_get($this->all(), $key)) ?: $this;
}
if ($default) {
return $default();
}
return $this;
}
/**
* Determine if the request contains a non-empty value for an input item.
*
* @param string|array $key
* @return bool
*/
public function has($key)
public function filled($key)
{
$keys = is_array($key) ? $key : func_get_args();
@@ -98,6 +152,99 @@ trait InteractsWithInput
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
* @param callable|null $default
* @return $this|mixed
*/
public function whenFilled($key, callable $callback, callable $default = null)
{
if ($this->filled($key)) {
return $callback(data_get($this->all(), $key)) ?: $this;
}
if ($default) {
return $default();
}
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);
}
/**
* Apply the callback if the request is missing the given input item key.
*
* @param string $key
* @param callable $callback
* @param callable|null $default
* @return $this|mixed
*/
public function whenMissing($key, callable $callback, callable $default = null)
{
if ($this->missing($key)) {
return $callback(data_get($this->all(), $key)) ?: $this;
}
if ($default) {
return $default();
}
return $this;
}
/**
* Determine if the given input key is an empty string for "has".
*
@@ -112,21 +259,44 @@ trait InteractsWithInput
}
/**
* Get all of the input and files for the request.
* Get the keys for all of the input and files.
*
* @return array
*/
public function all()
public function keys()
{
return array_replace_recursive($this->input(), $this->allFiles());
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 $key
* @param string|array|null $default
* @return string|array
* @param string|null $key
* @param mixed $default
* @return mixed
*/
public function input($key = null, $default = null)
{
@@ -135,6 +305,123 @@ trait InteractsWithInput
);
}
/**
* Retrieve input from the request as a Stringable instance.
*
* @param string $key
* @param mixed $default
* @return \Illuminate\Support\Stringable
*/
public function str($key, $default = null)
{
return $this->string($key, $default);
}
/**
* Retrieve input from the request as a Stringable instance.
*
* @param string $key
* @param mixed $default
* @return \Illuminate\Support\Stringable
*/
public function string($key, $default = null)
{
return str($this->input($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);
}
/**
* Retrieve input as an integer value.
*
* @param string $key
* @param int $default
* @return int
*/
public function integer($key, $default = 0)
{
return intval($this->input($key, $default));
}
/**
* Retrieve input as a float value.
*
* @param string $key
* @param float $default
* @return float
*/
public function float($key, $default = 0.0)
{
return floatval($this->input($key, $default));
}
/**
* Retrieve input from the request as a Carbon instance.
*
* @param string $key
* @param string|null $format
* @param string|null $tz
* @return \Illuminate\Support\Carbon|null
*
* @throws \Carbon\Exceptions\InvalidFormatException
*/
public function date($key, $format = null, $tz = null)
{
if ($this->isNotFilled($key)) {
return null;
}
if (is_null($format)) {
return Date::parse($this->input($key), $tz);
}
return Date::createFromFormat($format, $this->input($key), $tz);
}
/**
* Retrieve input from the request as an enum.
*
* @template TEnum
*
* @param string $key
* @param class-string<TEnum> $enumClass
* @return TEnum|null
*/
public function enum($key, $enumClass)
{
if ($this->isNotFilled($key) ||
! function_exists('enum_exists') ||
! enum_exists($enumClass) ||
! method_exists($enumClass, 'tryFrom')) {
return null;
}
return $enumClass::tryFrom($this->input($key));
}
/**
* Retrieve input from the request as a collection.
*
* @param array|string|null $key
* @return \Illuminate\Support\Collection
*/
public function collect($key = null)
{
return collect(is_array($key) ? $this->only($key) : $this->input($key));
}
/**
* Get a subset containing the provided keys with values from the input data.
*
@@ -143,14 +430,18 @@ trait InteractsWithInput
*/
public function only($keys)
{
$keys = is_array($keys) ? $keys : func_get_args();
$results = [];
$input = $this->all();
foreach ($keys as $key) {
Arr::set($results, $key, data_get($input, $key));
$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;
@@ -173,29 +464,30 @@ trait InteractsWithInput
return $results;
}
/**
* Intersect an array of items with the input data.
*
* @param array|mixed $keys
* @return array
*/
public function intersect($keys)
{
return array_filter($this->only(is_array($keys) ? $keys : func_get_args()));
}
/**
* Retrieve a query string item from the request.
*
* @param string $key
* @param string|null $key
* @param string|array|null $default
* @return string|array
* @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.
*
@@ -210,9 +502,9 @@ trait InteractsWithInput
/**
* Retrieve a cookie from the request.
*
* @param string $key
* @param string|null $key
* @param string|array|null $default
* @return string|array
* @return string|array|null
*/
public function cookie($key = null, $default = null)
{
@@ -228,9 +520,7 @@ trait InteractsWithInput
{
$files = $this->files->all();
return $this->convertedFiles
? $this->convertedFiles
: $this->convertedFiles = $this->convertUploadedFiles($files);
return $this->convertedFiles = $this->convertedFiles ?? $this->convertUploadedFiles($files);
}
/**
@@ -281,15 +571,15 @@ trait InteractsWithInput
*/
protected function isValidFile($file)
{
return $file instanceof SplFileInfo && $file->getPath() != '';
return $file instanceof SplFileInfo && $file->getPath() !== '';
}
/**
* Retrieve a file from the request.
*
* @param string $key
* @param string|null $key
* @param mixed $default
* @return \Illuminate\Http\UploadedFile|array|null
* @return \Illuminate\Http\UploadedFile|\Illuminate\Http\UploadedFile[]|array|null
*/
public function file($key = null, $default = null)
{
@@ -302,7 +592,7 @@ trait InteractsWithInput
* @param string $source
* @param string $key
* @param string|array|null $default
* @return string|array
* @return string|array|null
*/
protected function retrieveItem($source, $key, $default)
{
@@ -310,6 +600,38 @@ trait InteractsWithInput
return $this->$source->all();
}
if ($this->$source instanceof InputBag) {
return $this->$source->all()[$key] ?? $default;
}
return $this->$source->get($key, $default);
}
/**
* Dump the request items and end the script.
*
* @param mixed $keys
* @return never
*/
public function dd(...$keys)
{
$this->dump(...$keys);
exit(1);
}
/**
* Dump the items.
*
* @param mixed $keys
* @return $this
*/
public function dump($keys = [])
{
$keys = is_array($keys) ? $keys : func_get_args();
VarDumper::dump(count($keys) > 0 ? $this->only($keys) : $this->all());
return $this;
}
}

View File

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

View File

@@ -33,20 +33,10 @@ trait FileHelpers
return $this->guessExtension();
}
/**
* Get the file's extension supplied by the client.
*
* @return string
*/
public function clientExtension()
{
return $this->guessClientExtension();
}
/**
* Get a filename for the file.
*
* @param string $path
* @param string|null $path
* @return string
*/
public function hashName($path = null)
@@ -57,6 +47,10 @@ trait FileHelpers
$hash = $this->hashName ?: $this->hashName = Str::random(40);
return $path.$hash.'.'.$this->guessExtension();
if ($extension = $this->guessExtension()) {
$extension = '.'.$extension;
}
return $path.$hash.$extension;
}
}

View File

@@ -2,29 +2,44 @@
namespace Illuminate\Http;
use JsonSerializable;
use InvalidArgumentException;
use Illuminate\Contracts\Support\Jsonable;
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;
use ResponseTrait, Macroable {
Macroable::__call as macroCall;
}
/**
* Constructor.
*
* @param mixed $data
* @param int $status
* @param int $status
* @param array $headers
* @param int $options
* @param int $options
* @param bool $json
* @return void
*/
public function __construct($data = null, $status = 200, $headers = [], $options = 0)
public function __construct($data = null, $status = 200, $headers = [], $options = 0, $json = false)
{
$this->encodingOptions = $options;
parent::__construct($data, $status, $headers);
parent::__construct($data, $status, $headers, $json);
}
/**
* {@inheritdoc}
*
* @return static
*/
public static function fromJsonString(?string $data = null, int $status = 200, array $headers = []): static
{
return new static($data, $status, $headers, 0, true);
}
/**
@@ -52,17 +67,22 @@ class JsonResponse extends BaseJsonResponse
/**
* {@inheritdoc}
*
* @return static
*/
public function setData($data = [])
public function setData($data = []): static
{
$this->original = $data;
if ($data instanceof Arrayable) {
$this->data = json_encode($data->toArray(), $this->encodingOptions);
} elseif ($data instanceof Jsonable) {
// Ensure json_last_error() is cleared...
json_decode('[]');
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);
}
@@ -82,15 +102,24 @@ class JsonResponse extends BaseJsonResponse
*/
protected function hasValidJson($jsonError)
{
return $jsonError === JSON_ERROR_NONE ||
($jsonError === JSON_ERROR_UNSUPPORTED_TYPE &&
$this->hasEncodingOption(JSON_PARTIAL_OUTPUT_ON_ERROR));
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}
*
* @return static
*/
public function setEncodingOptions($options)
public function setEncodingOptions($options): static
{
$this->encodingOptions = (int) $options;

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 Illuminate\Support\Collection;
use Illuminate\Support\Facades\Vite;
class AddLinkHeadersForPreloadedAssets
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return \Illuminate\Http\Response
*/
public function handle($request, $next)
{
return tap($next($request), function ($response) {
if (Vite::preloadedAssets() !== []) {
$response->header('Link', Collection::make(Vite::preloadedAssets())
->map(fn ($attributes, $url) => "<{$url}>; ".implode('; ', $attributes))
->join(', '));
}
});
}
}

View File

@@ -0,0 +1,112 @@
<?php
namespace Illuminate\Http\Middleware;
use Closure;
use Fruitcake\Cors\CorsService;
use Illuminate\Contracts\Container\Container;
use Illuminate\Http\Request;
class HandleCors
{
/**
* The container instance.
*
* @var \Illuminate\Contracts\Container\Container
*/
protected $container;
/**
* The CORS service instance.
*
* @var \Fruitcake\Cors\CorsService
*/
protected $cors;
/**
* Create a new middleware instance.
*
* @param \Illuminate\Contracts\Container\Container $container
* @param \Fruitcake\Cors\CorsService $cors
* @return void
*/
public function __construct(Container $container, CorsService $cors)
{
$this->container = $container;
$this->cors = $cors;
}
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return \Illuminate\Http\Response
*/
public function handle($request, Closure $next)
{
if (! $this->hasMatchingPath($request)) {
return $next($request);
}
$this->cors->setOptions($this->container['config']->get('cors', []));
if ($this->cors->isPreflightRequest($request)) {
$response = $this->cors->handlePreflightRequest($request);
$this->cors->varyHeader($response, 'Access-Control-Request-Method');
return $response;
}
$response = $next($request);
if ($request->getMethod() === 'OPTIONS') {
$this->cors->varyHeader($response, 'Access-Control-Request-Method');
}
return $this->cors->addActualRequestHeaders($response, $request);
}
/**
* Get the path from the configuration to determine if the CORS service should run.
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function hasMatchingPath(Request $request): bool
{
$paths = $this->getPathsByHost($request->getHost());
foreach ($paths as $path) {
if ($path !== '/') {
$path = trim($path, '/');
}
if ($request->fullUrlIs($path) || $request->is($path)) {
return true;
}
}
return false;
}
/**
* Get the CORS paths for the given host.
*
* @param string $host
* @return array
*/
protected function getPathsByHost(string $host)
{
$paths = $this->container['config']->get('cors.paths', []);
if (isset($paths[$host])) {
return $paths[$host];
}
return array_filter($paths, function ($path) {
return is_string($path);
});
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Illuminate\Http\Middleware;
use Closure;
use Illuminate\Support\Carbon;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
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() && ! $response instanceof BinaryFileResponse)) {
return $response;
}
if (is_string($options)) {
$options = $this->parseOptions($options);
}
if (isset($options['etag']) && $options['etag'] === true) {
$options['etag'] = $response->getEtag() ?? 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(';', rtrim($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 \Closure $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 ! $this->app->environment('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,116 @@
<?php
namespace Illuminate\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class TrustProxies
{
/**
* The trusted proxies for the application.
*
* @var array|string|null
*/
protected $proxies;
/**
* The proxy header mappings.
*
* @var int
*/
protected $headers = Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO | Request::HEADER_X_FORWARDED_PREFIX | Request::HEADER_X_FORWARDED_AWS_ELB;
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
*/
public function handle(Request $request, Closure $next)
{
$request::setTrustedProxies([], $this->getTrustedHeaderNames());
$this->setTrustedProxyIpAddresses($request);
return $next($request);
}
/**
* Sets the trusted proxies on the request.
*
* @param \Illuminate\Http\Request $request
* @return void
*/
protected function setTrustedProxyIpAddresses(Request $request)
{
$trustedIps = $this->proxies() ?: config('trustedproxy.proxies');
if ($trustedIps === '*' || $trustedIps === '**') {
return $this->setTrustedProxyIpAddressesToTheCallingIp($request);
}
$trustedIps = is_string($trustedIps)
? array_map('trim', explode(',', $trustedIps))
: $trustedIps;
if (is_array($trustedIps)) {
return $this->setTrustedProxyIpAddressesToSpecificIps($request, $trustedIps);
}
}
/**
* Specify the IP addresses to trust explicitly.
*
* @param \Illuminate\Http\Request $request
* @param array $trustedIps
* @return void
*/
protected function setTrustedProxyIpAddressesToSpecificIps(Request $request, array $trustedIps)
{
$request->setTrustedProxies($trustedIps, $this->getTrustedHeaderNames());
}
/**
* Set the trusted proxy to be the IP address calling this servers.
*
* @param \Illuminate\Http\Request $request
* @return void
*/
protected function setTrustedProxyIpAddressesToTheCallingIp(Request $request)
{
$request->setTrustedProxies([$request->server->get('REMOTE_ADDR')], $this->getTrustedHeaderNames());
}
/**
* Retrieve trusted header name(s), falling back to defaults if config not set.
*
* @return int A bit field of Request::HEADER_*, to set which headers to trust from your proxies.
*/
protected function getTrustedHeaderNames()
{
return match ($this->headers) {
'HEADER_X_FORWARDED_AWS_ELB', Request::HEADER_X_FORWARDED_AWS_ELB => Request::HEADER_X_FORWARDED_AWS_ELB,
'HEADER_FORWARDED', Request::HEADER_FORWARDED => Request::HEADER_FORWARDED,
'HEADER_X_FORWARDED_FOR', Request::HEADER_X_FORWARDED_FOR => Request::HEADER_X_FORWARDED_FOR,
'HEADER_X_FORWARDED_HOST', Request::HEADER_X_FORWARDED_HOST => Request::HEADER_X_FORWARDED_HOST,
'HEADER_X_FORWARDED_PORT', Request::HEADER_X_FORWARDED_PORT => Request::HEADER_X_FORWARDED_PORT,
'HEADER_X_FORWARDED_PROTO', Request::HEADER_X_FORWARDED_PROTO => Request::HEADER_X_FORWARDED_PROTO,
'HEADER_X_FORWARDED_PREFIX', Request::HEADER_X_FORWARDED_PREFIX => Request::HEADER_X_FORWARDED_PREFIX,
default => Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO | Request::HEADER_X_FORWARDED_PREFIX | Request::HEADER_X_FORWARDED_AWS_ELB,
};
}
/**
* Get the trusted proxies.
*
* @return array|string|null
*/
protected function proxies()
{
return $this->proxies;
}
}

View File

@@ -2,19 +2,19 @@
namespace Illuminate\Http;
use BadMethodCallException;
use Illuminate\Support\Str;
use Illuminate\Support\MessageBag;
use Illuminate\Support\ViewErrorBag;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Session\Store as SessionStore;
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 ResponseTrait, Macroable {
use ForwardsCalls, ResponseTrait, Macroable {
Macroable::__call as macroCall;
}
@@ -26,7 +26,7 @@ class RedirectResponse extends BaseRedirectResponse
protected $request;
/**
* The session store implementation.
* The session store instance.
*
* @var \Illuminate\Session\Store
*/
@@ -37,7 +37,7 @@ class RedirectResponse extends BaseRedirectResponse
*
* @param string|array $key
* @param mixed $value
* @return \Illuminate\Http\RedirectResponse
* @return $this
*/
public function with($key, $value = null)
{
@@ -68,7 +68,7 @@ class RedirectResponse extends BaseRedirectResponse
/**
* Flash an array of input to the session.
*
* @param array $input
* @param array|null $input
* @return $this
*/
public function withInput(array $input = null)
@@ -114,7 +114,7 @@ class RedirectResponse extends BaseRedirectResponse
/**
* Flash an array of input to the session.
*
* @return \Illuminate\Http\RedirectResponse
* @return $this
*/
public function exceptInput()
{
@@ -160,6 +160,28 @@ class RedirectResponse extends BaseRedirectResponse
return new MessageBag((array) $provider);
}
/**
* 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(), '#'));
}
/**
* Get the original response content.
*
@@ -192,7 +214,7 @@ class RedirectResponse extends BaseRedirectResponse
}
/**
* Get the session store implementation.
* Get the session store instance.
*
* @return \Illuminate\Session\Store|null
*/
@@ -202,7 +224,7 @@ class RedirectResponse extends BaseRedirectResponse
}
/**
* Set the session store implementation.
* Set the session store instance.
*
* @param \Illuminate\Session\Store $session
* @return void
@@ -217,7 +239,7 @@ class RedirectResponse extends BaseRedirectResponse
*
* @param string $method
* @param array $parameters
* @return $this
* @return mixed
*
* @throws \BadMethodCallException
*/
@@ -227,12 +249,10 @@ class RedirectResponse extends BaseRedirectResponse
return $this->macroCall($method, $parameters);
}
if (Str::startsWith($method, 'with')) {
if (str_starts_with($method, 'with')) {
return $this->with(Str::snake(substr($method, 4)), $parameters[0]);
}
throw new BadMethodCallException(
"Method [$method] does not exist on Redirect."
);
static::throwBadMethodCallException($method);
}
}

View File

@@ -2,19 +2,28 @@
namespace Illuminate\Http;
use Closure;
use ArrayAccess;
use RuntimeException;
use Closure;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Session\SymfonySessionDecorator;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Contracts\Support\Arrayable;
use RuntimeException;
use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
/**
* @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,
use Concerns\CanBePrecognitive,
Concerns\InteractsWithContentTypes,
Concerns\InteractsWithFlashData,
Concerns\InteractsWithInput,
Macroable;
@@ -22,7 +31,7 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
/**
* The decoded JSON content for the request.
*
* @var string
* @var \Symfony\Component\HttpFoundation\ParameterBag|null
*/
protected $json;
@@ -108,7 +117,7 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
{
$query = $this->getQueryString();
$question = $this->getBaseUrl().$this->getPathInfo() == '/' ? '/?' : '?';
$question = $this->getBaseUrl().$this->getPathInfo() === '/' ? '/?' : '?';
return $query ? $this->url().$question.$query : $this->url();
}
@@ -121,11 +130,28 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
*/
public function fullUrlWithQuery(array $query)
{
$question = $this->getBaseUrl().$this->getPathInfo() == '/' ? '/?' : '?';
$question = $this->getBaseUrl().$this->getPathInfo() === '/' ? '/?' : '?';
return count($this->query()) > 0
? $this->url().$question.http_build_query(array_merge($this->query(), $query))
: $this->fullUrl().$question.http_build_query($query);
? $this->url().$question.Arr::query(array_merge($this->query(), $query))
: $this->fullUrl().$question.Arr::query($query);
}
/**
* Get the full URL for the request without the given query string parameters.
*
* @param array|string $keys
* @return string
*/
public function fullUrlWithoutQuery($keys)
{
$query = Arr::except($this->query(), $keys);
$question = $this->getBaseUrl().$this->getPathInfo() === '/' ? '/?' : '?';
return count($query) > 0
? $this->url().$question.Arr::query($query)
: $this->url();
}
/**
@@ -137,11 +163,11 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
{
$pattern = trim($this->getPathInfo(), '/');
return $pattern == '' ? '/' : $pattern;
return $pattern === '' ? '/' : $pattern;
}
/**
* Get the current encoded path info for the request.
* Get the current decoded path info for the request.
*
* @return string
*/
@@ -171,54 +197,76 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
{
$segments = explode('/', $this->decodedPath());
return array_values(array_filter($segments, function ($v) {
return $v != '';
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()
public function is(...$patterns)
{
foreach (func_get_args() as $pattern) {
if (Str::is($pattern, $this->decodedPath())) {
return true;
}
}
$path = $this->decodedPath();
return false;
return collect($patterns)->contains(fn ($pattern) => Str::is($pattern, $path));
}
/**
* Check if the route name matches the given string.
* Determine if the route name matches a given pattern.
*
* @param string $name
* @param mixed ...$patterns
* @return bool
*/
public function routeIs($name)
public function routeIs(...$patterns)
{
return $this->route() && $this->route()->named($name);
return $this->route() && $this->route()->named(...$patterns);
}
/**
* Determine if the current request URL and query string matches a pattern.
* Determine if the current request URL and query string match a pattern.
*
* @param mixed ...$patterns
* @return bool
*/
public function fullUrlIs()
public function fullUrlIs(...$patterns)
{
$url = $this->fullUrl();
foreach (func_get_args() as $pattern) {
if (Str::is($pattern, $url)) {
return true;
}
}
return collect($patterns)->contains(fn ($pattern) => Str::is($pattern, $url));
}
return false;
/**
* Get the host name.
*
* @return string
*/
public function host()
{
return $this->getHost();
}
/**
* Get the HTTP host being requested.
*
* @return string
*/
public function httpHost()
{
return $this->getHttpHost();
}
/**
* Get the scheme and HTTP host.
*
* @return string
*/
public function schemeAndHttpHost()
{
return $this->getSchemeAndHttpHost();
}
/**
@@ -232,7 +280,7 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
}
/**
* Determine if the request is the result of an PJAX call.
* Determine if the request is the result of a PJAX call.
*
* @return bool
*/
@@ -241,6 +289,17 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
return $this->headers->get('X-PJAX') == true;
}
/**
* Determine if the request is the result of a 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.
*
@@ -252,9 +311,9 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
}
/**
* Returns the client IP address.
* Get the client IP address.
*
* @return string
* @return string|null
*/
public function ip()
{
@@ -262,7 +321,7 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
}
/**
* Returns the client IP addresses.
* Get the client IP addresses.
*
* @return array
*/
@@ -271,34 +330,75 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
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 void
* @return $this
*/
public function merge(array $input)
{
$this->getInputSource()->add($input);
return $this;
}
/**
* Merge new input into the request's input, but only when that key is missing from the request.
*
* @param array $input
* @return $this
*/
public function mergeIfMissing(array $input)
{
return $this->merge(collect($input)->filter(function ($value, $key) {
return $this->missing($key);
})->toArray());
}
/**
* Replace the input for the current request.
*
* @param array $input
* @return void
* @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, mixed $default = null): mixed
{
return parent::get($key, $default);
}
/**
* Get the JSON payload for the request.
*
* @param string $key
* @param mixed $default
* @return mixed
* @param string|null $key
* @param mixed $default
* @return \Symfony\Component\HttpFoundation\ParameterBag|mixed
*/
public function json($key = null, $default = null)
{
@@ -324,39 +424,81 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
return $this->json();
}
return $this->getRealMethod() == 'GET' ? $this->query : $this->request;
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 = array_filter($from->files->all());
$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->setRequestLocale($from->getLocale());
$request->setDefaultRequestLocale($from->getDefaultLocale());
$request->setJson($from->json());
if ($from->hasSession() && $session = $from->session()) {
$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 \Illuminate\Http\Request
* @return static
*/
public static function createFromBase(SymfonyRequest $request)
{
if ($request instanceof static) {
return $request;
}
$content = $request->content;
$request = (new static)->duplicate(
$newRequest = (new static)->duplicate(
$request->query->all(), $request->request->all(), $request->attributes->all(),
$request->cookies->all(), $request->files->all(), $request->server->all()
);
$request->content = $content;
$newRequest->headers->replace($request->headers->all());
$request->request = $request->getInputSource();
$newRequest->content = $request->content;
return $request;
if ($newRequest->isJson()) {
$newRequest->request = $newRequest->json();
}
return $newRequest;
}
/**
* {@inheritdoc}
*
* @return static
*/
public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null)
public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null): static
{
return parent::duplicate($query, $request, $attributes, $cookies, $this->filterFiles($files), $server);
}
@@ -386,10 +528,28 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
return $files;
}
/**
* {@inheritdoc}
*/
public function hasSession(bool $skipIfUninitialized = false): bool
{
return ! is_null($this->session);
}
/**
* {@inheritdoc}
*/
public function getSession(): SessionInterface
{
return $this->hasSession()
? new SymfonySessionDecorator($this->session())
: throw new SessionNotFoundException;
}
/**
* Get the session associated with the request.
*
* @return \Illuminate\Session\Store
* @return \Illuminate\Contracts\Session\Session
*
* @throws \RuntimeException
*/
@@ -399,7 +559,7 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
throw new RuntimeException('Session store not set on request.');
}
return $this->getSession();
return $this->session;
}
/**
@@ -413,6 +573,28 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
$this->session = $session;
}
/**
* Set the locale for the request instance.
*
* @param string $locale
* @return void
*/
public function setRequestLocale(string $locale)
{
$this->locale = $locale;
}
/**
* Set the default locale for the request instance.
*
* @param string $locale
* @return void
*/
public function setDefaultRequestLocale(string $locale)
{
$this->defaultLocale = $locale;
}
/**
* Get the user making the request.
*
@@ -428,10 +610,10 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
* Get the route handling the request.
*
* @param string|null $param
*
* @return \Illuminate\Routing\Route|object|string
* @param mixed $default
* @return \Illuminate\Routing\Route|object|string|null
*/
public function route($param = null)
public function route($param = null, $default = null)
{
$route = call_user_func($this->getRouteResolver());
@@ -439,7 +621,7 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
return $route;
}
return $route->parameter($param);
return $route->parameter($param, $default);
}
/**
@@ -456,14 +638,15 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
}
return sha1(implode('|', array_merge(
$route->methods(), [$route->domain(), $route->uri(), $this->ip()]
$route->methods(),
[$route->getDomain(), $route->uri(), $this->ip()]
)));
}
/**
* Set the JSON payload for the request.
*
* @param array $json
* @param \Symfony\Component\HttpFoundation\ParameterBag $json
* @return $this
*/
public function setJson($json)
@@ -528,7 +711,7 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
*
* @return array
*/
public function toArray()
public function toArray(): array
{
return $this->all();
}
@@ -539,9 +722,14 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
* @param string $offset
* @return bool
*/
public function offsetExists($offset)
public function offsetExists($offset): bool
{
return array_key_exists($offset, $this->all());
$route = $this->route();
return Arr::has(
$this->all() + ($route ? $route->parameters() : []),
$offset
);
}
/**
@@ -550,9 +738,9 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
* @param string $offset
* @return mixed
*/
public function offsetGet($offset)
public function offsetGet($offset): mixed
{
return data_get($this->all(), $offset);
return $this->__get($offset);
}
/**
@@ -562,7 +750,7 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
* @param mixed $value
* @return void
*/
public function offsetSet($offset, $value)
public function offsetSet($offset, $value): void
{
$this->getInputSource()->set($offset, $value);
}
@@ -573,7 +761,7 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
* @param string $offset
* @return void
*/
public function offsetUnset($offset)
public function offsetUnset($offset): void
{
$this->getInputSource()->remove($offset);
}
@@ -597,10 +785,6 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
*/
public function __get($key)
{
if ($this->offsetExists($key)) {
return $this->offsetGet($key);
}
return $this->route($key);
return Arr::get($this->all(), $key, fn () => $this->route($key));
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace Illuminate\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Pagination\AbstractCursorPaginator;
use Illuminate\Pagination\AbstractPaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use LogicException;
use ReflectionClass;
use Traversable;
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 instanceof AbstractCursorPaginator)
? $resource->setCollection($this->collection)
: $this->collection;
}
/**
* Get the resource that this resource collects.
*
* @return string|null
*/
protected function collects()
{
$collects = null;
if ($this->collects) {
$collects = $this->collects;
} elseif (str_ends_with(class_basename($this), 'Collection') &&
(class_exists($class = Str::replaceLast('Collection', '', get_class($this))) ||
class_exists($class = Str::replaceLast('Collection', 'Resource', get_class($this))))) {
$collects = $class;
}
if (! $collects || is_a($collects, JsonResource::class, true)) {
return $collects;
}
throw new LogicException('Resource collections must collect instances of '.JsonResource::class.'.');
}
/**
* Get the JSON serialization options that should be applied to the resource response.
*
* @return int
*/
public function jsonOptions()
{
$collects = $this->collects();
if (! $collects) {
return 0;
}
return (new ReflectionClass($collects))
->newInstanceWithoutConstructor()
->jsonOptions();
}
/**
* Get an iterator for the resource collection.
*
* @return \ArrayIterator
*/
public function getIterator(): Traversable
{
return $this->collection->getIterator();
}
}

View File

@@ -0,0 +1,315 @@
<?php
namespace Illuminate\Http\Resources;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
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 if the given condition is truthy.
*
* @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 a value unless the given condition is truthy.
*
* @param bool $condition
* @param mixed $value
* @return \Illuminate\Http\Resources\MergeValue|mixed
*/
protected function mergeUnless($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 a model attribute if it is null.
*
* @param mixed $value
* @param mixed $default
* @return \Illuminate\Http\Resources\MissingValue|mixed
*/
protected function whenNull($value, $default = null)
{
$arguments = func_num_args() == 1 ? [$value] : [$value, $default];
return $this->when(is_null($value), ...$arguments);
}
/**
* Retrieve a model attribute if it is not null.
*
* @param mixed $value
* @param mixed $default
* @return \Illuminate\Http\Resources\MissingValue|mixed
*/
protected function whenNotNull($value, $default = null)
{
$arguments = func_num_args() == 1 ? [$value] : [$value, $default];
return $this->when(! is_null($value), ...$arguments);
}
/**
* 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);
}
/**
* Retrieve a relationship count if it exists.
*
* @param string $relationship
* @param mixed $value
* @param mixed $default
* @return \Illuminate\Http\Resources\MissingValue|mixed
*/
public function whenCounted($relationship, $value = null, $default = null)
{
if (func_num_args() < 3) {
$default = new MissingValue();
}
$attribute = (string) Str::of($relationship)->snake()->finish('_count');
if (! isset($this->resource->getAttributes()[$attribute])) {
return value($default);
}
if (func_num_args() === 1) {
return $this->resource->{$attribute};
}
if ($this->resource->{$attribute} === null) {
return;
}
return value($value, $this->resource->{$attribute});
}
/**
* 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(
isset($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,157 @@
<?php
namespace Illuminate\Http\Resources;
use Exception;
use Illuminate\Support\Traits\ForwardsCalls;
use Illuminate\Support\Traits\Macroable;
trait DelegatesToResource
{
use ForwardsCalls, Macroable {
__call as macroCall;
}
/**
* 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): bool
{
return isset($this->resource[$offset]);
}
/**
* Get the value for a given offset.
*
* @param mixed $offset
* @return mixed
*/
public function offsetGet($offset): mixed
{
return $this->resource[$offset];
}
/**
* Set the value for a given offset.
*
* @param mixed $offset
* @param mixed $value
* @return void
*/
public function offsetSet($offset, $value): void
{
$this->resource[$offset] = $value;
}
/**
* Unset the value for a given offset.
*
* @param mixed $offset
* @return void
*/
public function offsetUnset($offset): void
{
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)
{
if (static::hasMacro($method)) {
return $this->macroCall($method, $parameters);
}
return $this->forwardCallTo($this->resource, $method, $parameters);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Illuminate\Http\Resources\Json;
class AnonymousResourceCollection extends ResourceCollection
{
/**
* The name of the resource being collected.
*
* @var string
*/
public $collects;
/**
* Indicates if the collection keys should be preserved.
*
* @var bool
*/
public $preserveKeys = false;
/**
* 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,243 @@
<?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|null
*/
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 a 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|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
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_last_error() !== JSON_ERROR_NONE) {
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;
}
/**
* Get the JSON serialization options that should be applied to the resource response.
*
* @return int
*/
public function jsonOptions()
{
return 0;
}
/**
* 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(): array
{
return $this->resolve(Container::getInstance()->make('request'));
}
}

View File

@@ -0,0 +1,92 @@
<?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(),
[],
$this->resource->jsonOptions()
), 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();
$default = [
'links' => $this->paginationLinks($paginated),
'meta' => $this->meta($paginated),
];
if (method_exists($this->resource, 'paginationInformation')) {
return $this->resource->paginationInformation($request, $paginated, $default);
}
return $default;
}
/**
* 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,135 @@
<?php
namespace Illuminate\Http\Resources\Json;
use Countable;
use Illuminate\Http\Resources\CollectsResources;
use Illuminate\Pagination\AbstractCursorPaginator;
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(): int
{
return $this->collection->count();
}
/**
* Transform the resource into a JSON array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
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 || $this->resource instanceof AbstractCursorPaginator) {
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,122 @@
<?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(),
[],
$this->resource->jsonOptions()
), 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 a 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

@@ -3,22 +3,49 @@
namespace Illuminate\Http;
use ArrayObject;
use JsonSerializable;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Contracts\Support\Renderable;
use Symfony\Component\HttpFoundation\Response as BaseResponse;
use Illuminate\Support\Traits\Macroable;
use InvalidArgumentException;
use JsonSerializable;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
class Response extends BaseResponse
class Response extends SymfonyResponse
{
use ResponseTrait;
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
*
* @throws \InvalidArgumentException
*/
public function setContent($content)
public function setContent(mixed $content): static
{
$this->original = $content;
@@ -29,6 +56,10 @@ class Response extends BaseResponse
$this->header('Content-Type', 'application/json');
$content = $this->morphToJson($content);
if ($content === false) {
throw new InvalidArgumentException(json_last_error_msg());
}
}
// If this content implements the "Renderable" interface then we will call the
@@ -38,7 +69,9 @@ class Response extends BaseResponse
$content = $content->render();
}
return parent::setContent($content);
parent::setContent($content);
return $this;
}
/**
@@ -49,7 +82,8 @@ class Response extends BaseResponse
*/
protected function shouldBeJson($content)
{
return $content instanceof Jsonable ||
return $content instanceof Arrayable ||
$content instanceof Jsonable ||
$content instanceof ArrayObject ||
$content instanceof JsonSerializable ||
is_array($content);
@@ -58,13 +92,15 @@ class Response extends BaseResponse
/**
* Morph the given content into JSON.
*
* @param mixed $content
* @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

@@ -2,8 +2,9 @@
namespace Illuminate\Http;
use Exception;
use Illuminate\Http\Exceptions\HttpResponseException;
use Symfony\Component\HttpFoundation\HeaderBag;
use Throwable;
trait ResponseTrait
{
@@ -17,7 +18,7 @@ trait ResponseTrait
/**
* The exception that triggered the error response (if applicable).
*
* @var \Exception|null
* @var \Throwable|null
*/
public $exception;
@@ -31,6 +32,16 @@ trait ResponseTrait
return $this->getStatusCode();
}
/**
* Get the status text for the response.
*
* @return string
*/
public function statusText()
{
return $this->statusText;
}
/**
* Get the content of the response.
*
@@ -48,7 +59,9 @@ trait ResponseTrait
*/
public function getOriginalContent()
{
return $this->original;
$original = $this->original;
return $original instanceof self ? $original->{__FUNCTION__}() : $original;
}
/**
@@ -56,7 +69,7 @@ trait ResponseTrait
*
* @param string $key
* @param array|string $values
* @param bool $replace
* @param bool $replace
* @return $this
*/
public function header($key, $values, $replace = true)
@@ -69,11 +82,15 @@ trait ResponseTrait
/**
* Add an array of headers to the response.
*
* @param array $headers
* @param \Symfony\Component\HttpFoundation\HeaderBag|array $headers
* @return $this
*/
public function withHeaders(array $headers)
public function withHeaders($headers)
{
if ($headers instanceof HeaderBag) {
$headers = $headers->all();
}
foreach ($headers as $key => $value) {
$this->headers->set($key, $value);
}
@@ -89,7 +106,7 @@ trait ResponseTrait
*/
public function cookie($cookie)
{
return call_user_func_array([$this, 'withCookie'], func_get_args());
return $this->withCookie(...func_get_args());
}
/**
@@ -101,7 +118,7 @@ trait ResponseTrait
public function withCookie($cookie)
{
if (is_string($cookie) && function_exists('cookie')) {
$cookie = call_user_func_array('cookie', func_get_args());
$cookie = cookie(...func_get_args());
}
$this->headers->setCookie($cookie);
@@ -110,12 +127,41 @@ trait ResponseTrait
}
/**
* Set the exception to attach to the response.
* Expire a cookie when sending the response.
*
* @param \Exception $e
* @param \Symfony\Component\HttpFoundation\Cookie|mixed $cookie
* @param string|null $path
* @param string|null $domain
* @return $this
*/
public function withException(Exception $e)
public function withoutCookie($cookie, $path = null, $domain = null)
{
if (is_string($cookie) && function_exists('cookie')) {
$cookie = cookie($cookie, null, -2628000, $path, $domain);
}
$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;
@@ -125,6 +171,8 @@ trait ResponseTrait
/**
* Throws the response in a HttpResponseException instance.
*
* @return void
*
* @throws \Illuminate\Http\Exceptions\HttpResponseException
*/
public function throwResponse()

View File

@@ -27,6 +27,13 @@ class File extends UploadedFile
*/
public $sizeToReport;
/**
* The MIME type to report.
*
* @var string|null
*/
public $mimeTypeToReport;
/**
* Create a new file instance.
*
@@ -41,7 +48,7 @@ class File extends UploadedFile
parent::__construct(
$this->tempFilePath(), $name, $this->getMimeType(),
filesize($this->tempFilePath()), $error = null, $test = true
null, true
);
}
@@ -49,7 +56,7 @@ class File extends UploadedFile
* Create a new fake file.
*
* @param string $name
* @param int $kilobytes
* @param string|int $kilobytes
* @return \Illuminate\Http\Testing\File
*/
public static function create($name, $kilobytes = 0)
@@ -57,6 +64,18 @@ class File extends UploadedFile
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.
*
@@ -88,19 +107,32 @@ class File extends UploadedFile
*
* @return int
*/
public function getSize()
public function getSize(): int
{
return $this->sizeToReport ?: parent::getSize();
}
/**
* Get the MIME type for the file.
* 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()
public function getMimeType(): string
{
return MimeType::from($this->name);
return $this->mimeTypeToReport ?: MimeType::from($this->name);
}
/**

View File

@@ -8,13 +8,37 @@ class FileFactory
* Create a new fake file.
*
* @param string $name
* @param int $kilobytes
* @param string|int $kilobytes
* @param string|null $mimeType
* @return \Illuminate\Http\Testing\File
*/
public function create($name, $kilobytes = 0)
public function create($name, $kilobytes = 0, $mimeType = null)
{
return tap(new File($name, tmpfile()), function ($file) use ($kilobytes) {
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'];
});
}
@@ -28,7 +52,9 @@ class FileFactory
*/
public function image($name, $width = 10, $height = 10)
{
return new File($name, $this->generateImage($width, $height));
return new File($name, $this->generateImage(
$width, $height, pathinfo($name, PATHINFO_EXTENSION)
));
}
/**
@@ -36,14 +62,21 @@ class FileFactory
*
* @param int $width
* @param int $height
* @param string $extension
* @return resource
*/
protected function generateImage($width, $height)
protected function generateImage($width, $height, $extension)
{
return tap(tmpfile(), function ($temp) use ($width, $height) {
return tap(tmpfile(), function ($temp) use ($width, $height, $extension) {
ob_start();
imagepng(imagecreatetruecolor($width, $height));
$extension = in_array($extension, ['jpeg', 'png', 'gif', 'webp', 'wbmp', 'bmp'])
? strtolower($extension)
: 'jpeg';
$image = imagecreatetruecolor($width, $height);
call_user_func("image{$extension}", $image);
fwrite($temp, ob_get_clean());
});

View File

@@ -2,780 +2,31 @@
namespace Illuminate\Http\Testing;
use Illuminate\Support\Arr;
use Symfony\Component\Mime\MimeTypes;
class MimeType
{
/**
* An array of extension to MIME types.
* The mime types instance.
*
* @var array
* @var \Symfony\Component\Mime\MimeTypes|null
*/
protected static $mimes = [
'ez' => 'application/andrew-inset',
'aw' => 'application/applixware',
'atom' => 'application/atom+xml',
'atomcat' => 'application/atomcat+xml',
'atomsvc' => 'application/atomsvc+xml',
'ccxml' => 'application/ccxml+xml',
'cdmia' => 'application/cdmi-capability',
'cdmic' => 'application/cdmi-container',
'cdmid' => 'application/cdmi-domain',
'cdmio' => 'application/cdmi-object',
'cdmiq' => 'application/cdmi-queue',
'cu' => 'application/cu-seeme',
'davmount' => 'application/davmount+xml',
'dbk' => 'application/docbook+xml',
'dssc' => 'application/dssc+der',
'xdssc' => 'application/dssc+xml',
'ecma' => 'application/ecmascript',
'emma' => 'application/emma+xml',
'epub' => 'application/epub+zip',
'exi' => 'application/exi',
'pfr' => 'application/font-tdpfr',
'gml' => 'application/gml+xml',
'gpx' => 'application/gpx+xml',
'gxf' => 'application/gxf',
'stk' => 'application/hyperstudio',
'ink' => 'application/inkml+xml',
'ipfix' => 'application/ipfix',
'jar' => 'application/java-archive',
'ser' => 'application/java-serialized-object',
'class' => 'application/java-vm',
'js' => 'application/javascript',
'json' => 'application/json',
'jsonml' => 'application/jsonml+json',
'lostxml' => 'application/lost+xml',
'hqx' => 'application/mac-binhex40',
'cpt' => 'application/mac-compactpro',
'mads' => 'application/mads+xml',
'mrc' => 'application/marc',
'mrcx' => 'application/marcxml+xml',
'ma' => 'application/mathematica',
'mathml' => 'application/mathml+xml',
'mbox' => 'application/mbox',
'mscml' => 'application/mediaservercontrol+xml',
'metalink' => 'application/metalink+xml',
'meta4' => 'application/metalink4+xml',
'mets' => 'application/mets+xml',
'mods' => 'application/mods+xml',
'm21' => 'application/mp21',
'mp4s' => 'application/mp4',
'doc' => 'application/msword',
'mxf' => 'application/mxf',
'bin' => 'application/octet-stream',
'oda' => 'application/oda',
'opf' => 'application/oebps-package+xml',
'ogx' => 'application/ogg',
'omdoc' => 'application/omdoc+xml',
'onetoc' => 'application/onenote',
'oxps' => 'application/oxps',
'xer' => 'application/patch-ops-error+xml',
'pdf' => 'application/pdf',
'pgp' => 'application/pgp-encrypted',
'asc' => 'application/pgp-signature',
'prf' => 'application/pics-rules',
'p10' => 'application/pkcs10',
'p7m' => 'application/pkcs7-mime',
'p7s' => 'application/pkcs7-signature',
'p8' => 'application/pkcs8',
'ac' => 'application/pkix-attr-cert',
'cer' => 'application/pkix-cert',
'crl' => 'application/pkix-crl',
'pkipath' => 'application/pkix-pkipath',
'pki' => 'application/pkixcmp',
'pls' => 'application/pls+xml',
'ai' => 'application/postscript',
'cww' => 'application/prs.cww',
'pskcxml' => 'application/pskc+xml',
'rdf' => 'application/rdf+xml',
'rif' => 'application/reginfo+xml',
'rnc' => 'application/relax-ng-compact-syntax',
'rl' => 'application/resource-lists+xml',
'rld' => 'application/resource-lists-diff+xml',
'rs' => 'application/rls-services+xml',
'gbr' => 'application/rpki-ghostbusters',
'mft' => 'application/rpki-manifest',
'roa' => 'application/rpki-roa',
'rsd' => 'application/rsd+xml',
'rss' => 'application/rss+xml',
'sbml' => 'application/sbml+xml',
'scq' => 'application/scvp-cv-request',
'scs' => 'application/scvp-cv-response',
'spq' => 'application/scvp-vp-request',
'spp' => 'application/scvp-vp-response',
'sdp' => 'application/sdp',
'setpay' => 'application/set-payment-initiation',
'setreg' => 'application/set-registration-initiation',
'shf' => 'application/shf+xml',
'smi' => 'application/smil+xml',
'rq' => 'application/sparql-query',
'srx' => 'application/sparql-results+xml',
'gram' => 'application/srgs',
'grxml' => 'application/srgs+xml',
'sru' => 'application/sru+xml',
'ssdl' => 'application/ssdl+xml',
'ssml' => 'application/ssml+xml',
'tei' => 'application/tei+xml',
'tfi' => 'application/thraud+xml',
'tsd' => 'application/timestamped-data',
'plb' => 'application/vnd.3gpp.pic-bw-large',
'psb' => 'application/vnd.3gpp.pic-bw-small',
'pvb' => 'application/vnd.3gpp.pic-bw-var',
'tcap' => 'application/vnd.3gpp2.tcap',
'pwn' => 'application/vnd.3m.post-it-notes',
'aso' => 'application/vnd.accpac.simply.aso',
'imp' => 'application/vnd.accpac.simply.imp',
'acu' => 'application/vnd.acucobol',
'atc' => 'application/vnd.acucorp',
'air' => 'application/vnd.adobe.air-application-installer-package+zip',
'fcdt' => 'application/vnd.adobe.formscentral.fcdt',
'fxp' => 'application/vnd.adobe.fxp',
'xdp' => 'application/vnd.adobe.xdp+xml',
'xfdf' => 'application/vnd.adobe.xfdf',
'ahead' => 'application/vnd.ahead.space',
'azf' => 'application/vnd.airzip.filesecure.azf',
'azs' => 'application/vnd.airzip.filesecure.azs',
'azw' => 'application/vnd.amazon.ebook',
'acc' => 'application/vnd.americandynamics.acc',
'ami' => 'application/vnd.amiga.ami',
'apk' => 'application/vnd.android.package-archive',
'cii' => 'application/vnd.anser-web-certificate-issue-initiation',
'fti' => 'application/vnd.anser-web-funds-transfer-initiation',
'atx' => 'application/vnd.antix.game-component',
'mpkg' => 'application/vnd.apple.installer+xml',
'm3u8' => 'application/vnd.apple.mpegurl',
'swi' => 'application/vnd.aristanetworks.swi',
'iota' => 'application/vnd.astraea-software.iota',
'aep' => 'application/vnd.audiograph',
'mpm' => 'application/vnd.blueice.multipass',
'bmi' => 'application/vnd.bmi',
'rep' => 'application/vnd.businessobjects',
'cdxml' => 'application/vnd.chemdraw+xml',
'mmd' => 'application/vnd.chipnuts.karaoke-mmd',
'cdy' => 'application/vnd.cinderella',
'cla' => 'application/vnd.claymore',
'rp9' => 'application/vnd.cloanto.rp9',
'c4g' => 'application/vnd.clonk.c4group',
'c11amc' => 'application/vnd.cluetrust.cartomobile-config',
'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg',
'csp' => 'application/vnd.commonspace',
'cdbcmsg' => 'application/vnd.contact.cmsg',
'cmc' => 'application/vnd.cosmocaller',
'clkx' => 'application/vnd.crick.clicker',
'clkk' => 'application/vnd.crick.clicker.keyboard',
'clkp' => 'application/vnd.crick.clicker.palette',
'clkt' => 'application/vnd.crick.clicker.template',
'clkw' => 'application/vnd.crick.clicker.wordbank',
'wbs' => 'application/vnd.criticaltools.wbs+xml',
'pml' => 'application/vnd.ctc-posml',
'ppd' => 'application/vnd.cups-ppd',
'car' => 'application/vnd.curl.car',
'pcurl' => 'application/vnd.curl.pcurl',
'dart' => 'application/vnd.dart',
'rdz' => 'application/vnd.data-vision.rdz',
'uvf' => 'application/vnd.dece.data',
'uvt' => 'application/vnd.dece.ttml+xml',
'uvx' => 'application/vnd.dece.unspecified',
'uvz' => 'application/vnd.dece.zip',
'fe_launch' => 'application/vnd.denovo.fcselayout-link',
'dna' => 'application/vnd.dna',
'mlp' => 'application/vnd.dolby.mlp',
'dpg' => 'application/vnd.dpgraph',
'dfac' => 'application/vnd.dreamfactory',
'kpxx' => 'application/vnd.ds-keypoint',
'ait' => 'application/vnd.dvb.ait',
'svc' => 'application/vnd.dvb.service',
'geo' => 'application/vnd.dynageo',
'mag' => 'application/vnd.ecowin.chart',
'nml' => 'application/vnd.enliven',
'esf' => 'application/vnd.epson.esf',
'msf' => 'application/vnd.epson.msf',
'qam' => 'application/vnd.epson.quickanime',
'slt' => 'application/vnd.epson.salt',
'ssf' => 'application/vnd.epson.ssf',
'es3' => 'application/vnd.eszigno3+xml',
'ez2' => 'application/vnd.ezpix-album',
'ez3' => 'application/vnd.ezpix-package',
'fdf' => 'application/vnd.fdf',
'mseed' => 'application/vnd.fdsn.mseed',
'seed' => 'application/vnd.fdsn.seed',
'gph' => 'application/vnd.flographit',
'ftc' => 'application/vnd.fluxtime.clip',
'fm' => 'application/vnd.framemaker',
'fnc' => 'application/vnd.frogans.fnc',
'ltf' => 'application/vnd.frogans.ltf',
'fsc' => 'application/vnd.fsc.weblaunch',
'oas' => 'application/vnd.fujitsu.oasys',
'oa2' => 'application/vnd.fujitsu.oasys2',
'oa3' => 'application/vnd.fujitsu.oasys3',
'fg5' => 'application/vnd.fujitsu.oasysgp',
'bh2' => 'application/vnd.fujitsu.oasysprs',
'ddd' => 'application/vnd.fujixerox.ddd',
'xdw' => 'application/vnd.fujixerox.docuworks',
'xbd' => 'application/vnd.fujixerox.docuworks.binder',
'fzs' => 'application/vnd.fuzzysheet',
'txd' => 'application/vnd.genomatix.tuxedo',
'ggb' => 'application/vnd.geogebra.file',
'ggt' => 'application/vnd.geogebra.tool',
'gex' => 'application/vnd.geometry-explorer',
'gxt' => 'application/vnd.geonext',
'g2w' => 'application/vnd.geoplan',
'g3w' => 'application/vnd.geospace',
'gmx' => 'application/vnd.gmx',
'kml' => 'application/vnd.google-earth.kml+xml',
'kmz' => 'application/vnd.google-earth.kmz',
'gqf' => 'application/vnd.grafeq',
'gac' => 'application/vnd.groove-account',
'ghf' => 'application/vnd.groove-help',
'gim' => 'application/vnd.groove-identity-message',
'grv' => 'application/vnd.groove-injector',
'gtm' => 'application/vnd.groove-tool-message',
'tpl' => 'application/vnd.groove-tool-template',
'vcg' => 'application/vnd.groove-vcard',
'hal' => 'application/vnd.hal+xml',
'zmm' => 'application/vnd.handheld-entertainment+xml',
'hbci' => 'application/vnd.hbci',
'les' => 'application/vnd.hhe.lesson-player',
'hpgl' => 'application/vnd.hp-hpgl',
'hpid' => 'application/vnd.hp-hpid',
'hps' => 'application/vnd.hp-hps',
'jlt' => 'application/vnd.hp-jlyt',
'pcl' => 'application/vnd.hp-pcl',
'pclxl' => 'application/vnd.hp-pclxl',
'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data',
'mpy' => 'application/vnd.ibm.minipay',
'afp' => 'application/vnd.ibm.modcap',
'irm' => 'application/vnd.ibm.rights-management',
'sc' => 'application/vnd.ibm.secure-container',
'icc' => 'application/vnd.iccprofile',
'igl' => 'application/vnd.igloader',
'ivp' => 'application/vnd.immervision-ivp',
'ivu' => 'application/vnd.immervision-ivu',
'igm' => 'application/vnd.insors.igm',
'xpw' => 'application/vnd.intercon.formnet',
'i2g' => 'application/vnd.intergeo',
'qbo' => 'application/vnd.intu.qbo',
'qfx' => 'application/vnd.intu.qfx',
'rcprofile' => 'application/vnd.ipunplugged.rcprofile',
'irp' => 'application/vnd.irepository.package+xml',
'xpr' => 'application/vnd.is-xpr',
'fcs' => 'application/vnd.isac.fcs',
'jam' => 'application/vnd.jam',
'rms' => 'application/vnd.jcp.javame.midlet-rms',
'jisp' => 'application/vnd.jisp',
'joda' => 'application/vnd.joost.joda-archive',
'ktz' => 'application/vnd.kahootz',
'karbon' => 'application/vnd.kde.karbon',
'chrt' => 'application/vnd.kde.kchart',
'kfo' => 'application/vnd.kde.kformula',
'flw' => 'application/vnd.kde.kivio',
'kon' => 'application/vnd.kde.kontour',
'kpr' => 'application/vnd.kde.kpresenter',
'ksp' => 'application/vnd.kde.kspread',
'kwd' => 'application/vnd.kde.kword',
'htke' => 'application/vnd.kenameaapp',
'kia' => 'application/vnd.kidspiration',
'kne' => 'application/vnd.kinar',
'skp' => 'application/vnd.koan',
'sse' => 'application/vnd.kodak-descriptor',
'lasxml' => 'application/vnd.las.las+xml',
'lbd' => 'application/vnd.llamagraphics.life-balance.desktop',
'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml',
'123' => 'application/vnd.lotus-1-2-3',
'apr' => 'application/vnd.lotus-approach',
'pre' => 'application/vnd.lotus-freelance',
'nsf' => 'application/vnd.lotus-notes',
'org' => 'application/vnd.lotus-organizer',
'scm' => 'application/vnd.lotus-screencam',
'lwp' => 'application/vnd.lotus-wordpro',
'portpkg' => 'application/vnd.macports.portpkg',
'mcd' => 'application/vnd.mcd',
'mc1' => 'application/vnd.medcalcdata',
'cdkey' => 'application/vnd.mediastation.cdkey',
'mwf' => 'application/vnd.mfer',
'mfm' => 'application/vnd.mfmp',
'flo' => 'application/vnd.micrografx.flo',
'igx' => 'application/vnd.micrografx.igx',
'mif' => 'application/vnd.mif',
'daf' => 'application/vnd.mobius.daf',
'dis' => 'application/vnd.mobius.dis',
'mbk' => 'application/vnd.mobius.mbk',
'mqy' => 'application/vnd.mobius.mqy',
'msl' => 'application/vnd.mobius.msl',
'plc' => 'application/vnd.mobius.plc',
'txf' => 'application/vnd.mobius.txf',
'mpn' => 'application/vnd.mophun.application',
'mpc' => 'application/vnd.mophun.certificate',
'xul' => 'application/vnd.mozilla.xul+xml',
'cil' => 'application/vnd.ms-artgalry',
'cab' => 'application/vnd.ms-cab-compressed',
'xls' => 'application/vnd.ms-excel',
'xlam' => 'application/vnd.ms-excel.addin.macroenabled.12',
'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroenabled.12',
'xlsm' => 'application/vnd.ms-excel.sheet.macroenabled.12',
'xltm' => 'application/vnd.ms-excel.template.macroenabled.12',
'eot' => 'application/vnd.ms-fontobject',
'chm' => 'application/vnd.ms-htmlhelp',
'ims' => 'application/vnd.ms-ims',
'lrm' => 'application/vnd.ms-lrm',
'thmx' => 'application/vnd.ms-officetheme',
'cat' => 'application/vnd.ms-pki.seccat',
'stl' => 'application/vnd.ms-pki.stl',
'ppt' => 'application/vnd.ms-powerpoint',
'ppam' => 'application/vnd.ms-powerpoint.addin.macroenabled.12',
'pptm' => 'application/vnd.ms-powerpoint.presentation.macroenabled.12',
'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12',
'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroenabled.12',
'potm' => 'application/vnd.ms-powerpoint.template.macroenabled.12',
'mpp' => 'application/vnd.ms-project',
'docm' => 'application/vnd.ms-word.document.macroenabled.12',
'dotm' => 'application/vnd.ms-word.template.macroenabled.12',
'wps' => 'application/vnd.ms-works',
'wpl' => 'application/vnd.ms-wpl',
'xps' => 'application/vnd.ms-xpsdocument',
'mseq' => 'application/vnd.mseq',
'mus' => 'application/vnd.musician',
'msty' => 'application/vnd.muvee.style',
'taglet' => 'application/vnd.mynfc',
'nlu' => 'application/vnd.neurolanguage.nlu',
'ntf' => 'application/vnd.nitf',
'nnd' => 'application/vnd.noblenet-directory',
'nns' => 'application/vnd.noblenet-sealer',
'nnw' => 'application/vnd.noblenet-web',
'ngdat' => 'application/vnd.nokia.n-gage.data',
'n-gage' => 'application/vnd.nokia.n-gage.symbian.install',
'rpst' => 'application/vnd.nokia.radio-preset',
'rpss' => 'application/vnd.nokia.radio-presets',
'edm' => 'application/vnd.novadigm.edm',
'edx' => 'application/vnd.novadigm.edx',
'ext' => 'application/vnd.novadigm.ext',
'odc' => 'application/vnd.oasis.opendocument.chart',
'otc' => 'application/vnd.oasis.opendocument.chart-template',
'odb' => 'application/vnd.oasis.opendocument.database',
'odf' => 'application/vnd.oasis.opendocument.formula',
'odft' => 'application/vnd.oasis.opendocument.formula-template',
'odg' => 'application/vnd.oasis.opendocument.graphics',
'otg' => 'application/vnd.oasis.opendocument.graphics-template',
'odi' => 'application/vnd.oasis.opendocument.image',
'oti' => 'application/vnd.oasis.opendocument.image-template',
'odp' => 'application/vnd.oasis.opendocument.presentation',
'otp' => 'application/vnd.oasis.opendocument.presentation-template',
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template',
'odt' => 'application/vnd.oasis.opendocument.text',
'odm' => 'application/vnd.oasis.opendocument.text-master',
'ott' => 'application/vnd.oasis.opendocument.text-template',
'oth' => 'application/vnd.oasis.opendocument.text-web',
'xo' => 'application/vnd.olpc-sugar',
'dd2' => 'application/vnd.oma.dd2+xml',
'oxt' => 'application/vnd.openofficeorg.extension',
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'mgp' => 'application/vnd.osgeo.mapguide.package',
'dp' => 'application/vnd.osgi.dp',
'esa' => 'application/vnd.osgi.subsystem',
'pdb' => 'application/vnd.palm',
'paw' => 'application/vnd.pawaafile',
'str' => 'application/vnd.pg.format',
'ei6' => 'application/vnd.pg.osasli',
'efif' => 'application/vnd.picsel',
'wg' => 'application/vnd.pmi.widget',
'plf' => 'application/vnd.pocketlearn',
'pbd' => 'application/vnd.powerbuilder6',
'box' => 'application/vnd.previewsystems.box',
'mgz' => 'application/vnd.proteus.magazine',
'qps' => 'application/vnd.publishare-delta-tree',
'ptid' => 'application/vnd.pvi.ptid1',
'qxd' => 'application/vnd.quark.quarkxpress',
'bed' => 'application/vnd.realvnc.bed',
'mxl' => 'application/vnd.recordare.musicxml',
'musicxml' => 'application/vnd.recordare.musicxml+xml',
'cryptonote' => 'application/vnd.rig.cryptonote',
'cod' => 'application/vnd.rim.cod',
'rm' => 'application/vnd.rn-realmedia',
'rmvb' => 'application/vnd.rn-realmedia-vbr',
'link66' => 'application/vnd.route66.link66+xml',
'st' => 'application/vnd.sailingtracker.track',
'see' => 'application/vnd.seemail',
'sema' => 'application/vnd.sema',
'semd' => 'application/vnd.semd',
'semf' => 'application/vnd.semf',
'ifm' => 'application/vnd.shana.informed.formdata',
'itp' => 'application/vnd.shana.informed.formtemplate',
'iif' => 'application/vnd.shana.informed.interchange',
'ipk' => 'application/vnd.shana.informed.package',
'twd' => 'application/vnd.simtech-mindmapper',
'mmf' => 'application/vnd.smaf',
'teacher' => 'application/vnd.smart.teacher',
'sdkm' => 'application/vnd.solent.sdkm+xml',
'dxp' => 'application/vnd.spotfire.dxp',
'sfs' => 'application/vnd.spotfire.sfs',
'sdc' => 'application/vnd.stardivision.calc',
'sda' => 'application/vnd.stardivision.draw',
'sdd' => 'application/vnd.stardivision.impress',
'smf' => 'application/vnd.stardivision.math',
'sdw' => 'application/vnd.stardivision.writer',
'sgl' => 'application/vnd.stardivision.writer-global',
'smzip' => 'application/vnd.stepmania.package',
'sm' => 'application/vnd.stepmania.stepchart',
'sxc' => 'application/vnd.sun.xml.calc',
'stc' => 'application/vnd.sun.xml.calc.template',
'sxd' => 'application/vnd.sun.xml.draw',
'std' => 'application/vnd.sun.xml.draw.template',
'sxi' => 'application/vnd.sun.xml.impress',
'sti' => 'application/vnd.sun.xml.impress.template',
'sxm' => 'application/vnd.sun.xml.math',
'sxw' => 'application/vnd.sun.xml.writer',
'sxg' => 'application/vnd.sun.xml.writer.global',
'stw' => 'application/vnd.sun.xml.writer.template',
'sus' => 'application/vnd.sus-calendar',
'svd' => 'application/vnd.svd',
'sis' => 'application/vnd.symbian.install',
'xsm' => 'application/vnd.syncml+xml',
'bdm' => 'application/vnd.syncml.dm+wbxml',
'xdm' => 'application/vnd.syncml.dm+xml',
'tao' => 'application/vnd.tao.intent-module-archive',
'pcap' => 'application/vnd.tcpdump.pcap',
'tmo' => 'application/vnd.tmobile-livetv',
'tpt' => 'application/vnd.trid.tpt',
'mxs' => 'application/vnd.triscape.mxs',
'tra' => 'application/vnd.trueapp',
'ufd' => 'application/vnd.ufdl',
'utz' => 'application/vnd.uiq.theme',
'umj' => 'application/vnd.umajin',
'unityweb' => 'application/vnd.unity',
'uoml' => 'application/vnd.uoml+xml',
'vcx' => 'application/vnd.vcx',
'vsd' => 'application/vnd.visio',
'vis' => 'application/vnd.visionary',
'vsf' => 'application/vnd.vsf',
'wbxml' => 'application/vnd.wap.wbxml',
'wmlc' => 'application/vnd.wap.wmlc',
'wmlsc' => 'application/vnd.wap.wmlscriptc',
'wtb' => 'application/vnd.webturbo',
'nbp' => 'application/vnd.wolfram.player',
'wpd' => 'application/vnd.wordperfect',
'wqd' => 'application/vnd.wqd',
'stf' => 'application/vnd.wt.stf',
'xar' => 'application/vnd.xara',
'xfdl' => 'application/vnd.xfdl',
'hvd' => 'application/vnd.yamaha.hv-dic',
'hvs' => 'application/vnd.yamaha.hv-script',
'hvp' => 'application/vnd.yamaha.hv-voice',
'osf' => 'application/vnd.yamaha.openscoreformat',
'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml',
'saf' => 'application/vnd.yamaha.smaf-audio',
'spf' => 'application/vnd.yamaha.smaf-phrase',
'cmp' => 'application/vnd.yellowriver-custom-menu',
'zir' => 'application/vnd.zul',
'zaz' => 'application/vnd.zzazz.deck+xml',
'vxml' => 'application/voicexml+xml',
'wgt' => 'application/widget',
'hlp' => 'application/winhlp',
'wsdl' => 'application/wsdl+xml',
'wspolicy' => 'application/wspolicy+xml',
'7z' => 'application/x-7z-compressed',
'abw' => 'application/x-abiword',
'ace' => 'application/x-ace-compressed',
'dmg' => 'application/x-apple-diskimage',
'aab' => 'application/x-authorware-bin',
'aam' => 'application/x-authorware-map',
'aas' => 'application/x-authorware-seg',
'bcpio' => 'application/x-bcpio',
'torrent' => 'application/x-bittorrent',
'blb' => 'application/x-blorb',
'bz' => 'application/x-bzip',
'bz2' => 'application/x-bzip2',
'cbr' => 'application/x-cbr',
'vcd' => 'application/x-cdlink',
'cfs' => 'application/x-cfs-compressed',
'chat' => 'application/x-chat',
'pgn' => 'application/x-chess-pgn',
'nsc' => 'application/x-conference',
'cpio' => 'application/x-cpio',
'csh' => 'application/x-csh',
'deb' => 'application/x-debian-package',
'dgc' => 'application/x-dgc-compressed',
'dir' => 'application/x-director',
'wad' => 'application/x-doom',
'ncx' => 'application/x-dtbncx+xml',
'dtb' => 'application/x-dtbook+xml',
'res' => 'application/x-dtbresource+xml',
'dvi' => 'application/x-dvi',
'evy' => 'application/x-envoy',
'eva' => 'application/x-eva',
'bdf' => 'application/x-font-bdf',
'gsf' => 'application/x-font-ghostscript',
'psf' => 'application/x-font-linux-psf',
'otf' => 'application/x-font-otf',
'pcf' => 'application/x-font-pcf',
'snf' => 'application/x-font-snf',
'ttf' => 'application/x-font-ttf',
'pfa' => 'application/x-font-type1',
'woff' => 'application/x-font-woff',
'arc' => 'application/x-freearc',
'spl' => 'application/x-futuresplash',
'gca' => 'application/x-gca-compressed',
'ulx' => 'application/x-glulx',
'gnumeric' => 'application/x-gnumeric',
'gramps' => 'application/x-gramps-xml',
'gtar' => 'application/x-gtar',
'hdf' => 'application/x-hdf',
'install' => 'application/x-install-instructions',
'iso' => 'application/x-iso9660-image',
'jnlp' => 'application/x-java-jnlp-file',
'latex' => 'application/x-latex',
'lzh' => 'application/x-lzh-compressed',
'mie' => 'application/x-mie',
'prc' => 'application/x-mobipocket-ebook',
'application' => 'application/x-ms-application',
'lnk' => 'application/x-ms-shortcut',
'wmd' => 'application/x-ms-wmd',
'wmz' => 'application/x-ms-wmz',
'xbap' => 'application/x-ms-xbap',
'mdb' => 'application/x-msaccess',
'obd' => 'application/x-msbinder',
'crd' => 'application/x-mscardfile',
'clp' => 'application/x-msclip',
'exe' => 'application/x-msdownload',
'mvb' => 'application/x-msmediaview',
'wmf' => 'application/x-msmetafile',
'mny' => 'application/x-msmoney',
'pub' => 'application/x-mspublisher',
'scd' => 'application/x-msschedule',
'trm' => 'application/x-msterminal',
'wri' => 'application/x-mswrite',
'nc' => 'application/x-netcdf',
'nzb' => 'application/x-nzb',
'p12' => 'application/x-pkcs12',
'p7b' => 'application/x-pkcs7-certificates',
'p7r' => 'application/x-pkcs7-certreqresp',
'rar' => 'application/x-rar',
'ris' => 'application/x-research-info-systems',
'sh' => 'application/x-sh',
'shar' => 'application/x-shar',
'swf' => 'application/x-shockwave-flash',
'xap' => 'application/x-silverlight-app',
'sql' => 'application/x-sql',
'sit' => 'application/x-stuffit',
'sitx' => 'application/x-stuffitx',
'srt' => 'application/x-subrip',
'sv4cpio' => 'application/x-sv4cpio',
'sv4crc' => 'application/x-sv4crc',
't3' => 'application/x-t3vm-image',
'gam' => 'application/x-tads',
'tar' => 'application/x-tar',
'tcl' => 'application/x-tcl',
'tex' => 'application/x-tex',
'tfm' => 'application/x-tex-tfm',
'texinfo' => 'application/x-texinfo',
'obj' => 'application/x-tgif',
'ustar' => 'application/x-ustar',
'src' => 'application/x-wais-source',
'der' => 'application/x-x509-ca-cert',
'fig' => 'application/x-xfig',
'xlf' => 'application/x-xliff+xml',
'xpi' => 'application/x-xpinstall',
'xz' => 'application/x-xz',
'z1' => 'application/x-zmachine',
'xaml' => 'application/xaml+xml',
'xdf' => 'application/xcap-diff+xml',
'xenc' => 'application/xenc+xml',
'xhtml' => 'application/xhtml+xml',
'xml' => 'application/xml',
'dtd' => 'application/xml-dtd',
'xop' => 'application/xop+xml',
'xpl' => 'application/xproc+xml',
'xslt' => 'application/xslt+xml',
'xspf' => 'application/xspf+xml',
'mxml' => 'application/xv+xml',
'yang' => 'application/yang',
'yin' => 'application/yin+xml',
'zip' => 'application/zip',
'adp' => 'audio/adpcm',
'au' => 'audio/basic',
'mid' => 'audio/midi',
'mp4a' => 'audio/mp4',
'mpga' => 'audio/mpeg',
'oga' => 'audio/ogg',
's3m' => 'audio/s3m',
'sil' => 'audio/silk',
'uva' => 'audio/vnd.dece.audio',
'eol' => 'audio/vnd.digital-winds',
'dra' => 'audio/vnd.dra',
'dts' => 'audio/vnd.dts',
'dtshd' => 'audio/vnd.dts.hd',
'lvp' => 'audio/vnd.lucent.voice',
'pya' => 'audio/vnd.ms-playready.media.pya',
'ecelp4800' => 'audio/vnd.nuera.ecelp4800',
'ecelp7470' => 'audio/vnd.nuera.ecelp7470',
'ecelp9600' => 'audio/vnd.nuera.ecelp9600',
'rip' => 'audio/vnd.rip',
'weba' => 'audio/webm',
'aac' => 'audio/x-aac',
'aif' => 'audio/x-aiff',
'caf' => 'audio/x-caf',
'flac' => 'audio/x-flac',
'mka' => 'audio/x-matroska',
'm3u' => 'audio/x-mpegurl',
'wax' => 'audio/x-ms-wax',
'wma' => 'audio/x-ms-wma',
'ram' => 'audio/x-pn-realaudio',
'rmp' => 'audio/x-pn-realaudio-plugin',
'wav' => 'audio/x-wav',
'xm' => 'audio/xm',
'cdx' => 'chemical/x-cdx',
'cif' => 'chemical/x-cif',
'cmdf' => 'chemical/x-cmdf',
'cml' => 'chemical/x-cml',
'csml' => 'chemical/x-csml',
'xyz' => 'chemical/x-xyz',
'bmp' => 'image/bmp',
'cgm' => 'image/cgm',
'g3' => 'image/g3fax',
'gif' => 'image/gif',
'ief' => 'image/ief',
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'ktx' => 'image/ktx',
'png' => 'image/png',
'btif' => 'image/prs.btif',
'sgi' => 'image/sgi',
'svg' => 'image/svg+xml',
'tiff' => 'image/tiff',
'psd' => 'image/vnd.adobe.photoshop',
'uvi' => 'image/vnd.dece.graphic',
'djvu' => 'image/vnd.djvu',
'dwg' => 'image/vnd.dwg',
'dxf' => 'image/vnd.dxf',
'fbs' => 'image/vnd.fastbidsheet',
'fpx' => 'image/vnd.fpx',
'fst' => 'image/vnd.fst',
'mmr' => 'image/vnd.fujixerox.edmics-mmr',
'rlc' => 'image/vnd.fujixerox.edmics-rlc',
'mdi' => 'image/vnd.ms-modi',
'wdp' => 'image/vnd.ms-photo',
'npx' => 'image/vnd.net-fpx',
'wbmp' => 'image/vnd.wap.wbmp',
'xif' => 'image/vnd.xiff',
'webp' => 'image/webp',
'3ds' => 'image/x-3ds',
'ras' => 'image/x-cmu-raster',
'cmx' => 'image/x-cmx',
'fh' => 'image/x-freehand',
'ico' => 'image/x-icon',
'sid' => 'image/x-mrsid-image',
'pcx' => 'image/x-pcx',
'pic' => 'image/x-pict',
'pnm' => 'image/x-portable-anymap',
'pbm' => 'image/x-portable-bitmap',
'pgm' => 'image/x-portable-graymap',
'ppm' => 'image/x-portable-pixmap',
'rgb' => 'image/x-rgb',
'tga' => 'image/x-tga',
'xbm' => 'image/x-xbitmap',
'xpm' => 'image/x-xpixmap',
'xwd' => 'image/x-xwindowdump',
'eml' => 'message/rfc822',
'igs' => 'model/iges',
'msh' => 'model/mesh',
'dae' => 'model/vnd.collada+xml',
'dwf' => 'model/vnd.dwf',
'gdl' => 'model/vnd.gdl',
'gtw' => 'model/vnd.gtw',
'mts' => 'model/vnd.mts',
'vtu' => 'model/vnd.vtu',
'wrl' => 'model/vrml',
'x3db' => 'model/x3d+binary',
'x3dv' => 'model/x3d+vrml',
'x3d' => 'model/x3d+xml',
'appcache' => 'text/cache-manifest',
'ics' => 'text/calendar',
'css' => 'text/css',
'csv' => 'text/csv',
'html' => 'text/html',
'n3' => 'text/n3',
'txt' => 'text/plain',
'dsc' => 'text/prs.lines.tag',
'rtx' => 'text/richtext',
'rtf' => 'text/rtf',
'sgml' => 'text/sgml',
'tsv' => 'text/tab-separated-values',
't' => 'text/troff',
'ttl' => 'text/turtle',
'uri' => 'text/uri-list',
'vcard' => 'text/vcard',
'curl' => 'text/vnd.curl',
'dcurl' => 'text/vnd.curl.dcurl',
'scurl' => 'text/vnd.curl.scurl',
'mcurl' => 'text/vnd.curl.mcurl',
'sub' => 'text/vnd.dvb.subtitle',
'fly' => 'text/vnd.fly',
'flx' => 'text/vnd.fmi.flexstor',
'gv' => 'text/vnd.graphviz',
'3dml' => 'text/vnd.in3d.3dml',
'spot' => 'text/vnd.in3d.spot',
'jad' => 'text/vnd.sun.j2me.app-descriptor',
'wml' => 'text/vnd.wap.wml',
'wmls' => 'text/vnd.wap.wmlscript',
's' => 'text/x-asm',
'c' => 'text/x-c',
'f' => 'text/x-fortran',
'p' => 'text/x-pascal',
'java' => 'text/x-java-source',
'opml' => 'text/x-opml',
'nfo' => 'text/x-nfo',
'etx' => 'text/x-setext',
'sfv' => 'text/x-sfv',
'uu' => 'text/x-uuencode',
'vcs' => 'text/x-vcalendar',
'vcf' => 'text/x-vcard',
'3gp' => 'video/3gpp',
'3g2' => 'video/3gpp2',
'h261' => 'video/h261',
'h263' => 'video/h263',
'h264' => 'video/h264',
'jpgv' => 'video/jpeg',
'jpm' => 'video/jpm',
'mj2' => 'video/mj2',
'mp4' => 'video/mp4',
'mpeg' => 'video/mpeg',
'ogv' => 'video/ogg',
'qt' => 'video/quicktime',
'uvh' => 'video/vnd.dece.hd',
'uvm' => 'video/vnd.dece.mobile',
'uvp' => 'video/vnd.dece.pd',
'uvs' => 'video/vnd.dece.sd',
'uvv' => 'video/vnd.dece.video',
'dvb' => 'video/vnd.dvb.file',
'fvt' => 'video/vnd.fvt',
'mxu' => 'video/vnd.mpegurl',
'pyv' => 'video/vnd.ms-playready.media.pyv',
'uvu' => 'video/vnd.uvvu.mp4',
'viv' => 'video/vnd.vivo',
'webm' => 'video/webm',
'f4v' => 'video/x-f4v',
'fli' => 'video/x-fli',
'flv' => 'video/x-flv',
'm4v' => 'video/x-m4v',
'mkv' => 'video/x-matroska',
'mng' => 'video/x-mng',
'asf' => 'video/x-ms-asf',
'vob' => 'video/x-ms-vob',
'wm' => 'video/x-ms-wm',
'wmv' => 'video/x-ms-wmv',
'wmx' => 'video/x-ms-wmx',
'wvx' => 'video/x-ms-wvx',
'avi' => 'video/x-msvideo',
'movie' => 'video/x-sgi-movie',
'smv' => 'video/x-smv',
'ice' => 'x-conference/x-cooltalk',
];
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.
@@ -787,6 +38,28 @@ class MimeType
{
$extension = pathinfo($filename, PATHINFO_EXTENSION);
return collect(self::$mimes)->get($extension, 'application/octet-stream');
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

@@ -2,10 +2,12 @@
namespace Illuminate\Http;
use Illuminate\Support\Arr;
use Illuminate\Container\Container;
use Illuminate\Support\Traits\Macroable;
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
@@ -19,14 +21,14 @@ class UploadedFile extends SymfonyUploadedFile
*/
public static function fake()
{
return new Testing\FileFactory;
return new FileFactory;
}
/**
* Store the uploaded file on a filesystem disk.
*
* @param string $path
* @param array $options
* @param array|string $options
* @return string|false
*/
public function store($path, $options = [])
@@ -38,7 +40,7 @@ class UploadedFile extends SymfonyUploadedFile
* Store the uploaded file on a filesystem disk with public visibility.
*
* @param string $path
* @param array $options
* @param array|string $options
* @return string|false
*/
public function storePublicly($path, $options = [])
@@ -55,7 +57,7 @@ class UploadedFile extends SymfonyUploadedFile
*
* @param string $path
* @param string $name
* @param array $options
* @param array|string $options
* @return string|false
*/
public function storePubliclyAs($path, $name, $options = [])
@@ -72,7 +74,7 @@ class UploadedFile extends SymfonyUploadedFile
*
* @param string $path
* @param string $name
* @param array $options
* @param array|string $options
* @return string|false
*/
public function storeAs($path, $name, $options = [])
@@ -86,11 +88,37 @@ class UploadedFile extends SymfonyUploadedFile
);
}
/**
* Get the contents of the uploaded file.
*
* @return false|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
* @param bool $test
* @return static
*/
public static function createFromBase(SymfonyUploadedFile $file, $test = false)
@@ -99,7 +127,6 @@ class UploadedFile extends SymfonyUploadedFile
$file->getPathname(),
$file->getClientOriginalName(),
$file->getClientMimeType(),
$file->getClientSize(),
$file->getError(),
$test
);

View File

@@ -14,20 +14,29 @@
}
],
"require": {
"php": ">=5.6.4",
"illuminate/session": "5.4.*",
"illuminate/support": "5.4.*",
"symfony/http-foundation": "~3.2",
"symfony/http-kernel": "~3.2"
"php": "^8.0.2",
"ext-json": "*",
"fruitcake/php-cors": "^1.2",
"illuminate/collections": "^9.0",
"illuminate/macroable": "^9.0",
"illuminate/session": "^9.0",
"illuminate/support": "^9.0",
"symfony/http-foundation": "^6.0",
"symfony/http-kernel": "^6.0",
"symfony/mime": "^6.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 (^7.5)."
},
"extra": {
"branch-alias": {
"dev-master": "5.4-dev"
"dev-master": "9.x-dev"
}
},
"config": {