This commit is contained in:
Paolo A
2024-08-13 13:44:16 +00:00
parent 1bbb23088d
commit e796d76612
4001 changed files with 30101 additions and 40075 deletions

View File

@@ -3,18 +3,67 @@
namespace Illuminate\Http\Client;
use Closure;
use function GuzzleHttp\Promise\promise_for;
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;
/**
* @method \Illuminate\Http\Client\PendingRequest accept(string $contentType)
* @method \Illuminate\Http\Client\PendingRequest acceptJson()
* @method \Illuminate\Http\Client\PendingRequest asForm()
* @method \Illuminate\Http\Client\PendingRequest asJson()
* @method \Illuminate\Http\Client\PendingRequest asMultipart()
* @method \Illuminate\Http\Client\PendingRequest async()
* @method \Illuminate\Http\Client\PendingRequest attach(string|array $name, string|resource $contents = '', string|null $filename = null, array $headers = [])
* @method \Illuminate\Http\Client\PendingRequest baseUrl(string $url)
* @method \Illuminate\Http\Client\PendingRequest beforeSending(callable $callback)
* @method \Illuminate\Http\Client\PendingRequest bodyFormat(string $format)
* @method \Illuminate\Http\Client\PendingRequest contentType(string $contentType)
* @method \Illuminate\Http\Client\PendingRequest dd()
* @method \Illuminate\Http\Client\PendingRequest dump()
* @method \Illuminate\Http\Client\PendingRequest retry(int $times, int $sleep = 0, ?callable $when = null)
* @method \Illuminate\Http\Client\PendingRequest sink(string|resource $to)
* @method \Illuminate\Http\Client\PendingRequest stub(callable $callback)
* @method \Illuminate\Http\Client\PendingRequest timeout(int $seconds)
* @method \Illuminate\Http\Client\PendingRequest withBasicAuth(string $username, string $password)
* @method \Illuminate\Http\Client\PendingRequest withBody(resource|string $content, string $contentType)
* @method \Illuminate\Http\Client\PendingRequest withCookies(array $cookies, string $domain)
* @method \Illuminate\Http\Client\PendingRequest withDigestAuth(string $username, string $password)
* @method \Illuminate\Http\Client\PendingRequest withHeaders(array $headers)
* @method \Illuminate\Http\Client\PendingRequest withMiddleware(callable $middleware)
* @method \Illuminate\Http\Client\PendingRequest withOptions(array $options)
* @method \Illuminate\Http\Client\PendingRequest withToken(string $token, string $type = 'Bearer')
* @method \Illuminate\Http\Client\PendingRequest withUserAgent(string $userAgent)
* @method \Illuminate\Http\Client\PendingRequest withoutRedirecting()
* @method \Illuminate\Http\Client\PendingRequest withoutVerifying()
* @method array pool(callable $callback)
* @method \Illuminate\Http\Client\Response delete(string $url, array $data = [])
* @method \Illuminate\Http\Client\Response get(string $url, array|string|null $query = null)
* @method \Illuminate\Http\Client\Response head(string $url, array|string|null $query = null)
* @method \Illuminate\Http\Client\Response patch(string $url, array $data = [])
* @method \Illuminate\Http\Client\Response post(string $url, array $data = [])
* @method \Illuminate\Http\Client\Response put(string $url, array $data = [])
* @method \Illuminate\Http\Client\Response send(string $method, string $url, array $options = [])
*
* @see \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.
*
@@ -46,10 +95,13 @@ class Factory
/**
* Create a new factory instance.
*
* @param \Illuminate\Contracts\Events\Dispatcher|null $dispatcher
* @return void
*/
public function __construct()
public function __construct(Dispatcher $dispatcher = null)
{
$this->dispatcher = $dispatcher;
$this->stubCallbacks = collect();
}
@@ -69,7 +121,11 @@ class Factory
$headers['Content-Type'] = 'application/json';
}
return promise_for(new Psr7Response($status, $headers, $body));
$response = new Psr7Response($status, $headers, $body);
return class_exists(\GuzzleHttp\Promise\Create::class)
? \GuzzleHttp\Promise\Create::promiseFor($response)
: \GuzzleHttp\Promise\promise_for($response);
}
/**
@@ -93,6 +149,8 @@ class Factory
{
$this->record();
$this->recorded = [];
if (is_null($callback)) {
$callback = function () {
return static::response();
@@ -108,11 +166,20 @@ class Factory
}
$this->stubCallbacks = $this->stubCallbacks->merge(collect([
$callback instanceof Closure
? $callback
: function () use ($callback) {
return $callback;
},
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;
@@ -191,6 +258,28 @@ class Factory
);
}
/**
* 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.
*
@@ -250,7 +339,7 @@ class Factory
* @param callable $callback
* @return \Illuminate\Support\Collection
*/
public function recorded($callback)
public function recorded($callback = null)
{
if (empty($this->recorded)) {
return collect();
@@ -265,6 +354,26 @@ class Factory
});
}
/**
* 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.
*
@@ -278,7 +387,7 @@ class Factory
return $this->macroCall($method, $parameters);
}
return tap(new PendingRequest($this), function ($request) {
return tap($this->newPendingRequest(), function ($request) {
$request->stub($this->stubCallbacks);
})->{$method}(...$parameters);
}

View File

@@ -5,14 +5,24 @@ namespace Illuminate\Http\Client;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\TransferException;
use GuzzleHttp\HandlerStack;
use Illuminate\Http\Client\Events\ConnectionFailed;
use Illuminate\Http\Client\Events\RequestSending;
use Illuminate\Http\Client\Events\ResponseReceived;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Conditionable;
use Illuminate\Support\Traits\Macroable;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Symfony\Component\VarDumper\VarDumper;
class PendingRequest
{
use Macroable;
use Conditionable, Macroable;
/**
* The factory instance.
@@ -21,6 +31,13 @@ class PendingRequest
*/
protected $factory;
/**
* The Guzzle client instance.
*
* @var \GuzzleHttp\Client
*/
protected $client;
/**
* The base URL for the request.
*
@@ -84,6 +101,13 @@ class PendingRequest
*/
protected $retryDelay = 100;
/**
* The callback that will determine if the request should be retried.
*
* @var callable|null
*/
protected $retryWhenCallback = null;
/**
* The callbacks that should execute before the request is sent.
*
@@ -105,6 +129,41 @@ class PendingRequest
*/
protected $middleware;
/**
* Whether the requests should be asynchronous.
*
* @var bool
*/
protected $async = false;
/**
* The pending request promise.
*
* @var \GuzzleHttp\Promise\PromiseInterface
*/
protected $promise;
/**
* The sent request object, if a request has been made.
*
* @var \Illuminate\Http\Client\Request|null
*/
protected $request;
/**
* The Guzzle request options that are mergable via array_merge_recursive.
*
* @var array
*/
protected $mergableOptions = [
'cookies',
'form_params',
'headers',
'json',
'multipart',
'query',
];
/**
* Create a new HTTP Client instance.
*
@@ -122,8 +181,11 @@ class PendingRequest
'http_errors' => false,
];
$this->beforeSendingCallbacks = collect([function (Request $request, array $options) {
$this->cookies = $options['cookies'];
$this->beforeSendingCallbacks = collect([function (Request $request, array $options, PendingRequest $pendingRequest) {
$pendingRequest->request = $request;
$pendingRequest->cookies = $options['cookies'];
$pendingRequest->dispatchRequestSendingEvent();
}]);
}
@@ -143,7 +205,7 @@ class PendingRequest
/**
* Attach a raw body to the request.
*
* @param resource|string $content
* @param string $content
* @param string $contentType
* @return $this
*/
@@ -181,14 +243,22 @@ class PendingRequest
/**
* Attach a file to the request.
*
* @param string $name
* @param string $contents
* @param string|array $name
* @param string|resource $contents
* @param string|null $filename
* @param array $headers
* @return $this
*/
public function attach($name, $contents, $filename = null, array $headers = [])
public function attach($name, $contents = '', $filename = null, array $headers = [])
{
if (is_array($name)) {
foreach ($name as $file) {
$this->attach(...$file);
}
return $this;
}
$this->asMultipart();
$this->pendingFiles[] = array_filter([
@@ -313,6 +383,19 @@ class PendingRequest
});
}
/**
* Specify the user agent for the request.
*
* @param string $userAgent
* @return $this
*/
public function withUserAgent($userAgent)
{
return tap($this, function ($request) use ($userAgent) {
return $this->options['headers']['User-Agent'] = trim($userAgent);
});
}
/**
* Specify the cookies that should be included with the request.
*
@@ -384,18 +467,20 @@ class PendingRequest
*
* @param int $times
* @param int $sleep
* @param callable|null $when
* @return $this
*/
public function retry(int $times, int $sleep = 0)
public function retry(int $times, int $sleep = 0, ?callable $when = null)
{
$this->tries = $times;
$this->retryDelay = $sleep;
$this->retryWhenCallback = $when;
return $this;
}
/**
* Merge new options into the client.
* Replace the specified options on the request.
*
* @param array $options
* @return $this
@@ -403,7 +488,10 @@ class PendingRequest
public function withOptions(array $options)
{
return tap($this, function ($request) use ($options) {
return $this->options = array_merge_recursive($this->options, $options);
return $this->options = array_replace_recursive(
array_merge_recursive($this->options, Arr::only($options, $this->mergableOptions)),
$options
);
});
}
@@ -433,6 +521,40 @@ class PendingRequest
});
}
/**
* Dump the request before sending.
*
* @return $this
*/
public function dump()
{
$values = func_get_args();
return $this->beforeSending(function (Request $request, array $options) use ($values) {
foreach (array_merge($values, [$request, $options]) as $value) {
VarDumper::dump($value);
}
});
}
/**
* Dump the request before sending and end the script.
*
* @return $this
*/
public function dd()
{
$values = func_get_args();
return $this->beforeSending(function (Request $request, array $options) use ($values) {
foreach (array_merge($values, [$request, $options]) as $value) {
VarDumper::dump($value);
}
exit(1);
});
}
/**
* Issue a GET request to the given URL.
*
@@ -442,7 +564,7 @@ class PendingRequest
*/
public function get(string $url, $query = null)
{
return $this->send('GET', $url, [
return $this->send('GET', $url, func_num_args() === 1 ? [] : [
'query' => $query,
]);
}
@@ -456,7 +578,7 @@ class PendingRequest
*/
public function head(string $url, $query = null)
{
return $this->send('HEAD', $url, [
return $this->send('HEAD', $url, func_num_args() === 1 ? [] : [
'query' => $query,
]);
}
@@ -517,6 +639,25 @@ class PendingRequest
]);
}
/**
* Send a pool of asynchronous requests concurrently.
*
* @param callable $callback
* @return array
*/
public function pool(callable $callback)
{
$results = [];
$requests = tap(new Pool($this->factory), $callback)->getRequests();
foreach ($requests as $key => $item) {
$results[$key] = $item instanceof static ? $item->getPromise()->wait() : $item->wait();
}
return $results;
}
/**
* Send the request to the given URL.
*
@@ -543,31 +684,33 @@ class PendingRequest
$options[$this->bodyFormat], $this->pendingFiles
);
}
} else {
$options[$this->bodyFormat] = $this->pendingBody;
}
[$this->pendingBody, $this->pendingFiles] = [null, []];
if ($this->async) {
return $this->makePromise($method, $url, $options);
}
return retry($this->tries ?? 1, function () use ($method, $url, $options) {
try {
$laravelData = $this->parseRequestData($method, $url, $options);
return tap(new Response($this->buildClient()->request($method, $url, $this->mergeOptions([
'laravel_data' => $laravelData,
'on_stats' => function ($transferStats) {
$this->transferStats = $transferStats;
},
], $options))), function ($response) {
$response->cookies = $this->cookies;
$response->transferStats = $this->transferStats;
return tap(new Response($this->sendRequest($method, $url, $options)), function ($response) {
$this->populateResponse($response);
if ($this->tries > 1 && ! $response->successful()) {
$response->throw();
}
$this->dispatchResponseReceivedEvent($response);
});
} catch (ConnectException $e) {
$this->dispatchConnectionFailedEvent();
throw new ConnectionException($e->getMessage(), 0, $e);
}
}, $this->retryDelay ?? 100);
}, $this->retryDelay ?? 100, $this->retryWhenCallback);
}
/**
@@ -583,6 +726,52 @@ class PendingRequest
})->values()->all();
}
/**
* Send an asynchronous request to the given URL.
*
* @param string $method
* @param string $url
* @param array $options
* @return \GuzzleHttp\Promise\PromiseInterface
*/
protected function makePromise(string $method, string $url, array $options = [])
{
return $this->promise = $this->sendRequest($method, $url, $options)
->then(function (MessageInterface $message) {
return tap(new Response($message), function ($response) {
$this->populateResponse($response);
$this->dispatchResponseReceivedEvent($response);
});
})
->otherwise(function (TransferException $e) {
return $e instanceof RequestException ? $this->populateResponse(new Response($e->getResponse())) : $e;
});
}
/**
* Send a request either synchronously or asynchronously.
*
* @param string $method
* @param string $url
* @param array $options
* @return \Psr\Http\Message\MessageInterface|\GuzzleHttp\Promise\PromiseInterface
*
* @throws \Exception
*/
protected function sendRequest(string $method, string $url, array $options = [])
{
$clientMethod = $this->async ? 'requestAsync' : 'request';
$laravelData = $this->parseRequestData($method, $url, $options);
return $this->buildClient()->$clientMethod($method, $url, $this->mergeOptions([
'laravel_data' => $laravelData,
'on_stats' => function ($transferStats) {
$this->transferStats = $transferStats;
},
], $options));
}
/**
* Get the request data as an array so that we can attach it to the request for convenient assertions.
*
@@ -610,27 +799,86 @@ class PendingRequest
return $laravelData;
}
/**
* Populate the given response with additional data.
*
* @param \Illuminate\Http\Client\Response $response
* @return \Illuminate\Http\Client\Response
*/
protected function populateResponse(Response $response)
{
$response->cookies = $this->cookies;
$response->transferStats = $this->transferStats;
return $response;
}
/**
* Build the Guzzle client.
*
* @return \GuzzleHttp\Client
*/
public function buildClient()
{
return $this->requestsReusableClient()
? $this->getReusableClient()
: $this->createClient($this->buildHandlerStack());
}
/**
* Determine if a reusable client is required.
*
* @return bool
*/
protected function requestsReusableClient()
{
return ! is_null($this->client) || $this->async;
}
/**
* Retrieve a reusable Guzzle client.
*
* @return \GuzzleHttp\Client
*/
protected function getReusableClient()
{
return $this->client = $this->client ?: $this->createClient($this->buildHandlerStack());
}
/**
* Create new Guzzle client.
*
* @param \GuzzleHttp\HandlerStack $handlerStack
* @return \GuzzleHttp\Client
*/
public function createClient($handlerStack)
{
return new Client([
'handler' => $this->buildHandlerStack(),
'handler' => $handlerStack,
'cookies' => true,
]);
}
/**
* Build the before sending handler stack.
* Build the Guzzle client handler stack.
*
* @return \GuzzleHttp\HandlerStack
*/
public function buildHandlerStack()
{
return tap(HandlerStack::create(), function ($stack) {
return $this->pushHandlers(HandlerStack::create());
}
/**
* Add the necessary handlers to the given handler stack.
*
* @param \GuzzleHttp\HandlerStack $handlerStack
* @return \GuzzleHttp\HandlerStack
*/
public function pushHandlers($handlerStack)
{
return tap($handlerStack, function ($stack) {
$stack->push($this->buildBeforeSendingHandler());
$stack->push($this->buildRecorderHandler());
$stack->push($this->buildStubHandler());
@@ -664,7 +912,7 @@ class PendingRequest
{
return function ($handler) {
return function ($request, $options) use ($handler) {
$promise = $handler($this->runBeforeSendingCallbacks($request, $options), $options);
$promise = $handler($request, $options);
return $promise->then(function ($response) use ($request, $options) {
optional($this->factory)->recordRequestResponsePair(
@@ -737,27 +985,37 @@ class PendingRequest
*
* @param \GuzzleHttp\Psr7\RequestInterface $request
* @param array $options
* @return \Closure
* @return \GuzzleHttp\Psr7\RequestInterface
*/
public function runBeforeSendingCallbacks($request, array $options)
{
return tap($request, function ($request) use ($options) {
$this->beforeSendingCallbacks->each->__invoke(
(new Request($request))->withData($options['laravel_data']),
$options
);
return tap($request, function (&$request) use ($options) {
$this->beforeSendingCallbacks->each(function ($callback) use (&$request, $options) {
$callbackResult = call_user_func(
$callback, (new Request($request))->withData($options['laravel_data']), $options, $this
);
if ($callbackResult instanceof RequestInterface) {
$request = $callbackResult;
} elseif ($callbackResult instanceof Request) {
$request = $callbackResult->toPsrRequest();
}
});
});
}
/**
* Merge the given options with the current request options.
* Replace the given options with the current request options.
*
* @param array $options
* @return array
*/
public function mergeOptions(...$options)
{
return array_merge_recursive($this->options, ...$options);
return array_replace_recursive(
array_merge_recursive($this->options, Arr::only($options, $this->mergableOptions)),
...$options
);
}
/**
@@ -772,4 +1030,105 @@ class PendingRequest
return $this;
}
/**
* Toggle asynchronicity in requests.
*
* @param bool $async
* @return $this
*/
public function async(bool $async = true)
{
$this->async = $async;
return $this;
}
/**
* Retrieve the pending request promise.
*
* @return \GuzzleHttp\Promise\PromiseInterface|null
*/
public function getPromise()
{
return $this->promise;
}
/**
* Dispatch the RequestSending event if a dispatcher is available.
*
* @return void
*/
protected function dispatchRequestSendingEvent()
{
if ($dispatcher = optional($this->factory)->getDispatcher()) {
$dispatcher->dispatch(new RequestSending($this->request));
}
}
/**
* Dispatch the ResponseReceived event if a dispatcher is available.
*
* @param \Illuminate\Http\Client\Response $response
* @return void
*/
protected function dispatchResponseReceivedEvent(Response $response)
{
if (! ($dispatcher = optional($this->factory)->getDispatcher()) ||
! $this->request) {
return;
}
$dispatcher->dispatch(new ResponseReceived($this->request, $response));
}
/**
* Dispatch the ConnectionFailed event if a dispatcher is available.
*
* @return void
*/
protected function dispatchConnectionFailedEvent()
{
if ($dispatcher = optional($this->factory)->getDispatcher()) {
$dispatcher->dispatch(new ConnectionFailed($this->request));
}
}
/**
* Set the client instance.
*
* @param \GuzzleHttp\Client $client
* @return $this
*/
public function setClient(Client $client)
{
$this->client = $client;
return $this;
}
/**
* Create a new client instance using the given handler.
*
* @param callable $handler
* @return $this
*/
public function setHandler($handler)
{
$this->client = $this->createClient(
$this->pushHandlers(HandlerStack::create($handler))
);
return $this;
}
/**
* Get the pending request options.
*
* @return array
*/
public function getOptions()
{
return $this->options;
}
}

View File

@@ -5,10 +5,13 @@ namespace Illuminate\Http\Client;
use ArrayAccess;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
use LogicException;
class Request implements ArrayAccess
{
use Macroable;
/**
* The underlying PSR request.
*
@@ -117,9 +120,7 @@ class Request implements ArrayAccess
*/
public function headers()
{
return collect($this->request->getHeaders())->mapWithKeys(function ($values, $header) {
return [$header => $values];
})->all();
return $this->request->getHeaders();
}
/**
@@ -260,6 +261,7 @@ class Request implements ArrayAccess
* @param string $offset
* @return bool
*/
#[\ReturnTypeWillChange]
public function offsetExists($offset)
{
return isset($this->data()[$offset]);
@@ -271,6 +273,7 @@ class Request implements ArrayAccess
* @param string $offset
* @return mixed
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->data()[$offset];
@@ -285,6 +288,7 @@ class Request implements ArrayAccess
*
* @throws \LogicException
*/
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
throw new LogicException('Request data may not be mutated using array access.');
@@ -298,6 +302,7 @@ class Request implements ArrayAccess
*
* @throws \LogicException
*/
#[\ReturnTypeWillChange]
public function offsetUnset($offset)
{
throw new LogicException('Request data may not be mutated using array access.');

View File

@@ -19,8 +19,25 @@ class RequestException extends HttpClientException
*/
public function __construct(Response $response)
{
parent::__construct("HTTP request returned status code {$response->status()}.", $response->status());
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

@@ -3,6 +3,7 @@
namespace Illuminate\Http\Client;
use ArrayAccess;
use Illuminate\Support\Collection;
use Illuminate\Support\Traits\Macroable;
use LogicException;
@@ -50,15 +51,21 @@ class Response implements ArrayAccess
/**
* 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()
public function json($key = null, $default = null)
{
if (! $this->decoded) {
$this->decoded = json_decode($this->body(), true);
}
return $this->decoded;
if (is_null($key)) {
return $this->decoded;
}
return data_get($this->decoded, $key, $default);
}
/**
@@ -71,6 +78,17 @@ class Response implements ArrayAccess
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.
*
@@ -89,9 +107,7 @@ class Response implements ArrayAccess
*/
public function headers()
{
return collect($this->response->getHeaders())->mapWithKeys(function ($v, $k) {
return [$k => $v];
})->all();
return $this->response->getHeaders();
}
/**
@@ -104,14 +120,24 @@ class Response implements ArrayAccess
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
* @return \Psr\Http\Message\UriInterface|null
*/
public function effectiveUri()
{
return $this->transferStats->getEffectiveUri();
return optional($this->transferStats)->getEffectiveUri();
}
/**
@@ -144,6 +170,26 @@ class Response implements ArrayAccess
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.
*
@@ -174,6 +220,21 @@ class Response implements ArrayAccess
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.
*
@@ -184,6 +245,28 @@ class Response implements ArrayAccess
return $this->cookies;
}
/**
* Get the handler stats of the response.
*
* @return array
*/
public function handlerStats()
{
return optional($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.
*
@@ -194,28 +277,61 @@ class Response implements ArrayAccess
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()
{
if ($this->serverError() || $this->clientError()) {
throw new RequestException($this);
$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 bool $condition
* @return $this
*
* @throws \Illuminate\Http\Client\RequestException
*/
public function throwIf($condition)
{
return $condition ? $this->throw() : $this;
}
/**
* Determine if the given offset exists.
*
* @param string $offset
* @return bool
*/
#[\ReturnTypeWillChange]
public function offsetExists($offset)
{
return isset($this->json()[$offset]);
@@ -227,6 +343,7 @@ class Response implements ArrayAccess
* @param string $offset
* @return mixed
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->json()[$offset];
@@ -241,6 +358,7 @@ class Response implements ArrayAccess
*
* @throws \LogicException
*/
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
throw new LogicException('Response data may not be mutated using array access.');
@@ -254,6 +372,7 @@ class Response implements ArrayAccess
*
* @throws \LogicException
*/
#[\ReturnTypeWillChange]
public function offsetUnset($offset)
{
throw new LogicException('Response data may not be mutated using array access.');

View File

@@ -2,10 +2,13 @@
namespace Illuminate\Http\Client;
use Illuminate\Support\Traits\Macroable;
use OutOfBoundsException;
class ResponseSequence
{
use Macroable;
/**
* The responses in the sequence.
*
@@ -137,6 +140,8 @@ class ResponseSequence
* Get the next response in the sequence.
*
* @return mixed
*
* @throws \OutOfBoundsException
*/
public function __invoke()
{

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.
*
@@ -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;
}
@@ -152,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

@@ -9,7 +9,7 @@ trait InteractsWithFlashData
*
* @param string|null $key
* @param string|array|null $default
* @return string|array
* @return string|array|null
*/
public function old($key = null, $default = null)
{

View File

@@ -4,9 +4,10 @@ namespace Illuminate\Http\Concerns;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Date;
use SplFileInfo;
use stdClass;
use Symfony\Component\VarDumper\VarDumper;
trait InteractsWithInput
{
@@ -54,8 +55,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 strpos($header, ',') !== false ? strstr($header, ',', true) : $header;
}
}
@@ -111,14 +116,19 @@ trait InteractsWithInput
*
* @param string $key
* @param callable $callback
* @param callable|null $default
* @return $this|mixed
*/
public function whenHas($key, callable $callback)
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;
}
@@ -184,14 +194,19 @@ trait InteractsWithInput
*
* @param string $key
* @param callable $callback
* @param callable|null $default
* @return $this|mixed
*/
public function whenFilled($key, callable $callback)
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;
}
@@ -282,6 +297,38 @@ trait InteractsWithInput
return filter_var($this->input($key, $default), FILTER_VALIDATE_BOOLEAN);
}
/**
* 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
*/
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 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.
*
@@ -462,4 +509,32 @@ trait InteractsWithInput
return $this->$source->get($key, $default);
}
/**
* Dump the request items and end the script.
*
* @param mixed $keys
* @return void
*/
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;
}
}

22
vendor/laravel/framework/src/Illuminate/Http/JsonResponse.php vendored Normal file → Executable file
View File

@@ -22,13 +22,24 @@ class JsonResponse extends BaseJsonResponse
* @param int $status
* @param array $headers
* @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 = [])
{
return new static($data, $status, $headers, 0, true);
}
/**
@@ -56,11 +67,16 @@ class JsonResponse extends BaseJsonResponse
/**
* {@inheritdoc}
*
* @return static
*/
public function setData($data = [])
{
$this->original = $data;
// Ensure json_last_error() is cleared...
json_decode('[]');
if ($data instanceof Jsonable) {
$this->data = $data->toJson($this->encodingOptions);
} elseif ($data instanceof JsonSerializable) {
@@ -100,6 +116,8 @@ class JsonResponse extends BaseJsonResponse
/**
* {@inheritdoc}
*
* @return static
*/
public function setEncodingOptions($options)
{

View File

@@ -55,7 +55,7 @@ class SetCacheHeaders
*/
protected function parseOptions($options)
{
return collect(explode(';', $options))->mapWithKeys(function ($option) {
return collect(explode(';', rtrim($options, ';')))->mapWithKeys(function ($option) {
$data = explode('=', $option, 2);
return [$data[0] => $data[1] ?? true];

30
vendor/laravel/framework/src/Illuminate/Http/RedirectResponse.php vendored Normal file → Executable file
View File

@@ -145,6 +145,21 @@ class RedirectResponse extends BaseRedirectResponse
return $this;
}
/**
* Parse the given errors into an appropriate value.
*
* @param \Illuminate\Contracts\Support\MessageProvider|array|string $provider
* @return \Illuminate\Support\MessageBag
*/
protected function parseErrors($provider)
{
if ($provider instanceof MessageProvider) {
return $provider->getMessageBag();
}
return new MessageBag((array) $provider);
}
/**
* Add a fragment identifier to the URL.
*
@@ -167,21 +182,6 @@ class RedirectResponse extends BaseRedirectResponse
return $this->setTargetUrl(Str::before($this->getTargetUrl(), '#'));
}
/**
* Parse the given errors into an appropriate value.
*
* @param \Illuminate\Contracts\Support\MessageProvider|array|string $provider
* @return \Illuminate\Support\MessageBag
*/
protected function parseErrors($provider)
{
if ($provider instanceof MessageProvider) {
return $provider->getMessageBag();
}
return new MessageBag((array) $provider);
}
/**
* Get the original response content.
*

View File

@@ -133,6 +133,23 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
: $this->fullUrl().$question.Arr::query($query);
}
/**
* Get the full URL for the request without the given query string parameters.
*
* @param array|string $query
* @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();
}
/**
* Get the current path info for the request.
*
@@ -142,7 +159,7 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
{
$pattern = trim($this->getPathInfo(), '/');
return $pattern == '' ? '/' : $pattern;
return $pattern === '' ? '/' : $pattern;
}
/**
@@ -212,7 +229,7 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
}
/**
* 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
@@ -241,7 +258,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
*/
@@ -251,14 +268,14 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
}
/**
* Determine if the request is the result of an prefetch call.
* 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;
return strcasecmp($this->server->get('HTTP_X_MOZ') ?? '', 'prefetch') === 0 ||
strcasecmp($this->headers->get('Purpose') ?? '', 'prefetch') === 0;
}
/**
@@ -314,6 +331,19 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
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.
*
@@ -439,6 +469,8 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
/**
* {@inheritdoc}
*
* @return static
*/
public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null)
{
@@ -634,10 +666,13 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
* @param string $offset
* @return bool
*/
#[\ReturnTypeWillChange]
public function offsetExists($offset)
{
$route = $this->route();
return Arr::has(
$this->all() + $this->route()->parameters(),
$this->all() + ($route ? $route->parameters() : []),
$offset
);
}
@@ -648,6 +683,7 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
* @param string $offset
* @return mixed
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->__get($offset);
@@ -660,6 +696,7 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
* @param mixed $value
* @return void
*/
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
$this->getInputSource()->set($offset, $value);
@@ -671,6 +708,7 @@ class Request extends SymfonyRequest implements Arrayable, ArrayAccess
* @param string $offset
* @return void
*/
#[\ReturnTypeWillChange]
public function offsetUnset($offset)
{
$this->getInputSource()->remove($offset);

View File

@@ -2,9 +2,11 @@
namespace Illuminate\Http\Resources;
use Illuminate\Pagination\AbstractCursorPaginator;
use Illuminate\Pagination\AbstractPaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use ReflectionClass;
trait CollectsResources
{
@@ -30,7 +32,7 @@ trait CollectsResources
? $resource->mapInto($collects)
: $resource->toBase();
return $resource instanceof AbstractPaginator
return ($resource instanceof AbstractPaginator || $resource instanceof AbstractCursorPaginator)
? $resource->setCollection($this->collection)
: $this->collection;
}
@@ -47,16 +49,36 @@ trait CollectsResources
}
if (Str::endsWith(class_basename($this), 'Collection') &&
class_exists($class = Str::replaceLast('Collection', '', get_class($this)))) {
(class_exists($class = Str::replaceLast('Collection', '', get_class($this))) ||
class_exists($class = Str::replaceLast('Collection', 'Resource', get_class($this))))) {
return $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
*/
#[\ReturnTypeWillChange]
public function getIterator()
{
return $this->collection->getIterator();

View File

@@ -64,6 +64,7 @@ trait DelegatesToResource
* @param mixed $offset
* @return bool
*/
#[\ReturnTypeWillChange]
public function offsetExists($offset)
{
return isset($this->resource[$offset]);
@@ -75,6 +76,7 @@ trait DelegatesToResource
* @param mixed $offset
* @return mixed
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->resource[$offset];
@@ -87,6 +89,7 @@ trait DelegatesToResource
* @param mixed $value
* @return void
*/
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
$this->resource[$offset] = $value;
@@ -98,6 +101,7 @@ trait DelegatesToResource
* @param mixed $offset
* @return void
*/
#[\ReturnTypeWillChange]
public function offsetUnset($offset)
{
unset($this->resource[$offset]);

View File

@@ -42,7 +42,7 @@ class JsonResource implements ArrayAccess, JsonSerializable, Responsable, UrlRou
/**
* The "data" wrapper that should be applied.
*
* @var string
* @var string|null
*/
public static $wrap = 'data';
@@ -69,7 +69,7 @@ class JsonResource implements ArrayAccess, JsonSerializable, Responsable, UrlRou
}
/**
* Create new anonymous resource collection.
* Create a new anonymous resource collection.
*
* @param mixed $resource
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
@@ -108,7 +108,7 @@ class JsonResource implements ArrayAccess, JsonSerializable, Responsable, UrlRou
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
@@ -164,6 +164,16 @@ class JsonResource implements ArrayAccess, JsonSerializable, Responsable, UrlRou
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.
*
@@ -226,6 +236,7 @@ class JsonResource implements ArrayAccess, JsonSerializable, Responsable, UrlRou
*
* @return array
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return $this->resolve(Container::getInstance()->make('request'));

View File

@@ -23,7 +23,9 @@ class PaginatedResourceResponse extends ResourceResponse
$this->resource->additional
)
),
$this->calculateStatus()
$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;
@@ -43,10 +45,16 @@ class PaginatedResourceResponse extends ResourceResponse
{
$paginated = $this->resource->resource->toArray();
return [
$default = [
'links' => $this->paginationLinks($paginated),
'meta' => $this->meta($paginated),
];
if (method_exists($this->resource, 'paginationInformation')) {
return $this->resource->paginationInformation($request, $paginated, $default);
}
return $default;
}
/**

View File

@@ -4,6 +4,7 @@ namespace Illuminate\Http\Resources\Json;
use Countable;
use Illuminate\Http\Resources\CollectsResources;
use Illuminate\Pagination\AbstractCursorPaginator;
use Illuminate\Pagination\AbstractPaginator;
use IteratorAggregate;
@@ -84,6 +85,7 @@ class ResourceCollection extends JsonResource implements Countable, IteratorAggr
*
* @return int
*/
#[\ReturnTypeWillChange]
public function count()
{
return $this->collection->count();
@@ -93,7 +95,7 @@ class ResourceCollection extends JsonResource implements Countable, IteratorAggr
* Transform the resource into a JSON array.
*
* @param \Illuminate\Http\Request $request
* @return array
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
@@ -108,7 +110,7 @@ class ResourceCollection extends JsonResource implements Countable, IteratorAggr
*/
public function toResponse($request)
{
if ($this->resource instanceof AbstractPaginator) {
if ($this->resource instanceof AbstractPaginator || $this->resource instanceof AbstractCursorPaginator) {
return $this->preparePaginatedResponse($request);
}

View File

@@ -40,7 +40,9 @@ class ResourceResponse implements Responsable
$this->resource->with($request),
$this->resource->additional
),
$this->calculateStatus()
$this->calculateStatus(),
[],
$this->resource->jsonOptions()
), function ($response) use ($request) {
$response->original = $this->resource->resource;

View File

@@ -15,7 +15,7 @@ class MergeValue
public $data;
/**
* Create new merge value instance.
* Create a new merge value instance.
*
* @param \Illuminate\Support\Collection|\JsonSerializable|array $data
* @return void

7
vendor/laravel/framework/src/Illuminate/Http/Response.php vendored Normal file → Executable file
View File

@@ -7,6 +7,7 @@ use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Support\Traits\Macroable;
use InvalidArgumentException;
use JsonSerializable;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
@@ -41,6 +42,8 @@ class Response extends SymfonyResponse
*
* @param mixed $content
* @return $this
*
* @throws \InvalidArgumentException
*/
public function setContent($content)
{
@@ -53,6 +56,10 @@ class Response extends SymfonyResponse
$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

View File

@@ -32,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.
*
@@ -116,6 +126,25 @@ trait ResponseTrait
return $this;
}
/**
* Expire a cookie when sending the response.
*
* @param \Symfony\Component\HttpFoundation\Cookie|mixed $cookie
* @param string|null $path
* @param string|null $domain
* @return $this
*/
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.
*

View File

@@ -107,6 +107,7 @@ class File extends UploadedFile
*
* @return int
*/
#[\ReturnTypeWillChange]
public function getSize()
{
return $this->sizeToReport ?: parent::getSize();

View File

@@ -2,8 +2,6 @@
namespace Illuminate\Http\Testing;
use Illuminate\Support\Str;
class FileFactory
{
/**
@@ -55,7 +53,7 @@ class FileFactory
public function image($name, $width = 10, $height = 10)
{
return new File($name, $this->generateImage(
$width, $height, Str::endsWith(Str::lower($name), ['.jpg', '.jpeg']) ? 'jpeg' : 'png'
$width, $height, pathinfo($name, PATHINFO_EXTENSION)
));
}
@@ -64,24 +62,21 @@ class FileFactory
*
* @param int $width
* @param int $height
* @param string $type
* @param string $extension
* @return resource
*/
protected function generateImage($width, $height, $type)
protected function generateImage($width, $height, $extension)
{
return tap(tmpfile(), function ($temp) use ($width, $height, $type) {
return tap(tmpfile(), function ($temp) use ($width, $height, $extension) {
ob_start();
$extension = in_array($extension, ['jpeg', 'png', 'gif', 'webp', 'wbmp', 'bmp'])
? strtolower($extension)
: 'jpeg';
$image = imagecreatetruecolor($width, $height);
switch ($type) {
case 'jpeg':
imagejpeg($image);
break;
case 'png':
imagepng($image);
break;
}
call_user_func("image{$extension}", $image);
fwrite($temp, ob_get_clean());
});

View File

@@ -22,7 +22,7 @@ class MimeType
public static function getMimeTypes()
{
if (self::$mime === null) {
self::$mime = new MimeTypes();
self::$mime = new MimeTypes;
}
return self::$mime;

View File

@@ -91,7 +91,7 @@ class UploadedFile extends SymfonyUploadedFile
/**
* Get the contents of the uploaded file.
*
* @return bool|string
* @return false|string
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/

18
vendor/laravel/framework/src/Illuminate/Http/composer.json vendored Normal file → Executable file
View File

@@ -14,13 +14,15 @@
}
],
"require": {
"php": "^7.2.5|^8.0",
"php": "^7.3|^8.0",
"ext-json": "*",
"illuminate/session": "^7.0",
"illuminate/support": "^7.0",
"symfony/http-foundation": "^5.0",
"symfony/http-kernel": "^5.0",
"symfony/mime": "^5.0"
"illuminate/collections": "^8.0",
"illuminate/macroable": "^8.0",
"illuminate/session": "^8.0",
"illuminate/support": "^8.0",
"symfony/http-foundation": "^5.4",
"symfony/http-kernel": "^5.4",
"symfony/mime": "^5.4"
},
"autoload": {
"psr-4": {
@@ -29,11 +31,11 @@
},
"suggest": {
"ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().",
"guzzlehttp/guzzle": "Required to use the HTTP Client (^6.3.1|^7.0.1)."
"guzzlehttp/guzzle": "Required to use the HTTP Client (^6.5.5|^7.0.1)."
},
"extra": {
"branch-alias": {
"dev-master": "7.x-dev"
"dev-master": "8.x-dev"
}
},
"config": {