adjusted phpstan linting to be friendlier and changed download() to also specify file name

pull/663/head
n0nag0n 2 weeks ago
parent f0dc1b1adf
commit 053227b3b0

@ -24,60 +24,58 @@ use Psr\Container\ContainerInterface;
* It is responsible for loading an HTTP request, running the assigned services, * It is responsible for loading an HTTP request, running the assigned services,
* and generating an HTTP response. * and generating an HTTP response.
* *
* @license MIT, http://flightphp.com/license * @license MIT, https://docs.flightphp.com/license
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com> * @copyright Copyright (c) 2011-2025, Mike Cao <mike@mikecao.com>, n0nag0n <n0nag0n@sky-9.com>
* *
* # Core methods * @method void start()
* @method void start() Starts engine * @method void stop()
* @method void stop() Stops framework and outputs current response * @method void halt(int $code = 200, string $message = '', bool $actuallyExit = true)
* @method void halt(int $code = 200, string $message = '', bool $actuallyExit = true) Stops processing and returns a given response. * @method EventDispatcher eventDispatcher()
* @method Route route(string $pattern, callable|string|array $callback, bool $pass_route = false, string $alias = '')
* @method void group(string $pattern, callable $callback, array $group_middlewares = [])
* @method Route post(string $pattern, callable|string|array $callback, bool $pass_route = false, string $alias = '')
* @method Route put(string $pattern, callable|string|array $callback, bool $pass_route = false, string $alias = '')
* @method Route patch(string $pattern, callable|string|array $callback, bool $pass_route = false, string $alias = '')
* @method Route delete(string $pattern, callable|string|array $callback, bool $pass_route = false, string $alias = '')
* @method void resource(string $pattern, string $controllerClass, array $methods = [])
* @method Router router()
* @method string getUrl(string $alias)
* @method void render(string $file, array $data = null, string $key = null)
* @method View view()
* @method void onEvent(string $event, callable $callback)
* @method void triggerEvent(string $event, ...$args)
* @method Request request()
* @method Response response()
* @method void error(Throwable $e)
* @method void notFound()
* @method void redirect(string $url, int $code = 303)
* @method void json($data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0)
* @method void jsonHalt($data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0)
* @method void jsonp($data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0)
* @method void etag(string $id, string $type = 'strong')
* @method void lastModified(int $time)
* @method void download(string $filePath)
* *
* # Class registration * @phpstan-template EngineTemplate of object
* @method EventDispatcher eventDispatcher() Gets event dispatcher * @phpstan-method void registerContainerHandler(ContainerInterface|callable(class-string<EngineTemplate> $id, array<int|string, mixed> $params): ?EngineTemplate $containerHandler)
* @phpstan-method Route route(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '')
* @phpstan-method void group(string $pattern, callable $callback, (class-string|callable|array{0: class-string, 1: string})[] $group_middlewares = [])
* @phpstan-method Route post(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '')
* @phpstan-method Route put(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '')
* @phpstan-method Route patch(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '')
* @phpstan-method Route delete(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '')
* @phpstan-method void resource(string $pattern, class-string $controllerClass, array<string, string|array<string>> $methods = [])
* @phpstan-method string getUrl(string $alias, array<string, mixed> $params = [])
* @phpstan-method void before(string $name, Closure(array<int, mixed> &$params, string &$output): (void|false) $callback)
* @phpstan-method void after(string $name, Closure(array<int, mixed> &$params, string &$output): (void|false) $callback)
* @phpstan-method void set(string|iterable<string, mixed> $key, ?mixed $value = null)
* @phpstan-method mixed get(?string $key)
* @phpstan-method void render(string $file, ?array<string, mixed> $data = null, ?string $key = null)
* @phpstan-method void json(mixed $data, int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512)
* @phpstan-method void jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0)
* @phpstan-method void jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512)
* *
* # Routing * Note: IDEs will use standard @method tags for autocompletion, while PHPStan will use @phpstan-* tags for advanced type checking.
* @method Route route(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '')
* Routes a URL to a callback function with all applicable methods
* @method void group(string $pattern, callable $callback, (class-string|callable|array{0: class-string, 1: string})[] $group_middlewares = [])
* Groups a set of routes together under a common prefix.
* @method Route post(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '')
* Routes a POST URL to a callback function.
* @method Route put(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '')
* Routes a PUT URL to a callback function.
* @method Route patch(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '')
* Routes a PATCH URL to a callback function.
* @method Route delete(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '')
* Routes a DELETE URL to a callback function.
* @method void resource(string $pattern, class-string $controllerClass, array<string, string|array<string>> $methods = [])
* Adds standardized RESTful routes for a controller.
* @method Router router() Gets router
* @method string getUrl(string $alias) Gets a url from an alias
*
* # Views
* @method void render(string $file, ?array<string,mixed> $data = null, ?string $key = null) Renders template
* @method View view() Gets current view
*
* # Events
* @method void onEvent(string $event, callable $callback) Registers a callback for an event.
* @method void triggerEvent(string $event, ...$args) Triggers an event.
*
* # Request-Response
* @method Request request() Gets current request
* @method Response response() Gets current response
* @method void error(Throwable $e) Sends an HTTP 500 response for any errors.
* @method void notFound() Sends an HTTP 404 response when a URL is not found.
* @method void redirect(string $url, int $code = 303) Redirects the current request to another URL.
* @method void json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0)
* Sends a JSON response.
* @method void jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0)
* Sends a JSON response and immediately halts the request.
* @method void jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0)
* Sends a JSONP response.
*
* # HTTP methods
* @method void etag(string $id, ('strong'|'weak') $type = 'strong') Handles ETag HTTP caching.
* @method void lastModified(int $time) Handles last modified HTTP caching.
* @method void download(string $filePath) Downloads a file
* *
* phpcs:disable PSR2.Methods.MethodDeclaration.Underscore * phpcs:disable PSR2.Methods.MethodDeclaration.Underscore
*/ */
@ -118,7 +116,7 @@ class Engine
/** Class loader. */ /** Class loader. */
protected Loader $loader; protected Loader $loader;
/** Method and class dispatcher. */ /** @var Dispatcher<EngineTemplate> Method and class dispatcher. */
protected Dispatcher $dispatcher; protected Dispatcher $dispatcher;
/** Event dispatcher. */ /** Event dispatcher. */
@ -370,7 +368,7 @@ class Engine
* *
* @param string|iterable<string, mixed> $key * @param string|iterable<string, mixed> $key
* Variable name as `string` or an iterable of `'varName' => $varValue` * Variable name as `string` or an iterable of `'varName' => $varValue`
* @param mixed $value Ignored if `$key` is an `iterable` * @param ?mixed $value Ignored if `$key` is an `iterable`
*/ */
public function set($key, $value = null): void public function set($key, $value = null): void
{ {
@ -981,14 +979,15 @@ class Engine
* Downloads a file * Downloads a file
* *
* @param string $filePath The path to the file to download * @param string $filePath The path to the file to download
* @param string $fileName The name the file should be downloaded as
* *
* @throws Exception If the file cannot be found * @throws Exception If the file cannot be found
* *
* @return void * @return void
*/ */
public function _download(string $filePath): void public function _download(string $filePath, string $fileName = ''): void
{ {
$this->response()->downloadFile($filePath); $this->response()->downloadFile($filePath, $fileName);
} }
/** /**
@ -1020,8 +1019,8 @@ class Engine
public function _lastModified(int $time): void public function _lastModified(int $time): void
{ {
$this->response()->header('Last-Modified', gmdate('D, d M Y H:i:s \G\M\T', $time)); $this->response()->header('Last-Modified', gmdate('D, d M Y H:i:s \G\M\T', $time));
$request = $this->request(); $request = $this->request();
$ifModifiedSince = $request->header('If-Modified-Since'); $ifModifiedSince = $request->header('If-Modified-Since');
$hit = isset($ifModifiedSince) && strtotime($ifModifiedSince) === $time; $hit = isset($ifModifiedSince) && strtotime($ifModifiedSince) === $time;
$this->triggerEvent('flight.cache.checked', 'lastModified', $hit, 0.0); $this->triggerEvent('flight.cache.checked', 'lastModified', $hit, 0.0);

@ -16,90 +16,76 @@ require_once __DIR__ . '/autoload.php';
/** /**
* The Flight class is a static representation of the framework. * The Flight class is a static representation of the framework.
* *
* @license MIT, http://flightphp.com/license * @license MIT, https://docs.flightphp.com/license
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com> * @copyright Copyright (c) 2011-2025, Mike Cao <mike@mikecao.com>, n0nag0n <n0nag0n@sky-9.com>
* *
* @template T of object * @method static void start()
* * @method static void path(string $dir)
* # Core methods * @method static void stop(int $code = null)
* @method static void start() Starts the framework.
* @method static void path(string $dir) Adds a path for autoloading classes.
* @method static void stop(?int $code = null) Stops the framework and sends a response.
* @method static void halt(int $code = 200, string $message = '', bool $actuallyExit = true) * @method static void halt(int $code = 200, string $message = '', bool $actuallyExit = true)
* Stop the framework with an optional status code and message. * @method static void register(string $name, string $class, array $params = [], callable $callback = null)
* @method static void register(string $name, string $class, array<int, mixed> $params = [], ?callable $callback = null)
* Registers a class to a framework method.
* @method static void unregister(string $methodName) * @method static void unregister(string $methodName)
* Unregisters a class to a framework method. * @method static void registerContainerHandler($containerHandler)
* @method static void registerContainerHandler(ContainerInterface|callable(class-string<T> $id, array<int|string, mixed> $params): ?T $containerHandler) Registers a container handler. * @method static EventDispatcher eventDispatcher()
* * @method static Route route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '')
* # Class registration * @method static void group(string $pattern, callable $callback, array $group_middlewares = [])
* @method EventDispatcher eventDispatcher() Gets event dispatcher * @method static Route post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '')
* * @method static Route put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '')
* # Class registration * @method static Route patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '')
* @method EventDispatcher eventDispatcher() Gets event dispatcher * @method static Route delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '')
* * @method static void resource(string $pattern, string $controllerClass, array $methods = [])
* # Routing * @method static Router router()
* @method static Route route(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '') * @method static string getUrl(string $alias, array $params = [])
* Maps a URL pattern to a callback with all applicable methods. * @method static void map(string $name, callable $callback)
* @method static void group(string $pattern, callable $callback, (class-string|callable|array{0: class-string, 1: string})[] $group_middlewares = []) * @method static void before(string $name, Closure $callback)
* Groups a set of routes together under a common prefix. * @method static void after(string $name, Closure $callback)
* @method static Route post(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '') * @method static void set($key, $value)
* Routes a POST URL to a callback function. * @method static mixed get($key = null)
* @method static Route put(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '') * @method static bool has(string $key)
* Routes a PUT URL to a callback function. * @method static void clear($key = null)
* @method static Route patch(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '') * @method static void render(string $file, array $data = null, string $key = null)
* Routes a PATCH URL to a callback function. * @method static View view()
* @method static Route delete(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '') * @method void onEvent(string $event, callable $callback)
* Routes a DELETE URL to a callback function. * @method void triggerEvent(string $event, ...$args)
* @method static void resource(string $pattern, class-string $controllerClass, array<string, string|array<string>> $methods = []) * @method static Request request()
* Adds standardized RESTful routes for a controller. * @method static Response response()
* @method static Router router() Returns Router instance. * @method static void redirect(string $url, int $code = 303)
* @method static string getUrl(string $alias, array<string, mixed> $params = []) Gets a url from an alias * @method static void json($data, int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512)
* @method static void map(string $name, callable $callback) Creates a custom framework method. * @method static void jsonHalt($data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0)
* * @method static void jsonp($data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512)
* # Filters * @method static void error(Throwable $exception)
* @method static void before(string $name, Closure(array<int, mixed> &$params, string &$output): (void|false) $callback) * @method static void notFound()
* Adds a filter before a framework method. * @method static void etag(string $id, string $type = 'strong')
* @method static void after(string $name, Closure(array<int, mixed> &$params, string &$output): (void|false) $callback) * @method static void lastModified(int $time)
* Adds a filter after a framework method. * @method static void download(string $filePath)
* *
* # Variables * @phpstan-template FlightTemplate of object
* @method static void set(string|iterable<string, mixed> $key, mixed $value) Sets a variable. * @phpstan-method static void register(string $name, class-string<FlightTemplate> $class, array<int|string, mixed> $params = [], (callable(class-string<FlightTemplate> $class, array<int|string, mixed> $params): void)|null $callback = null)
* @method static mixed get(?string $key) Gets a variable. * @phpstan-method static void registerContainerHandler(ContainerInterface|callable(class-string<FlightTemplate> $id, array<int|string, mixed> $params): ?FlightTemplate $containerHandler)
* @method static bool has(string $key) Checks if a variable is set. * @phpstan-method static Route route(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '')
* @method static void clear(?string $key = null) Clears a variable. * @phpstan-method static void group(string $pattern, callable $callback, (class-string|callable|array{0: class-string, 1: string})[] $group_middlewares = [])
* @phpstan-method static Route post(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '')
* @phpstan-method static Route put(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '')
* @phpstan-method static Route patch(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '')
* @phpstan-method static Route delete(string $pattern, callable|string|array{0: class-string, 1: string} $callback, bool $pass_route = false, string $alias = '')
* @phpstan-method static void resource(string $pattern, class-string $controllerClass, array<string, string|array<string>> $methods = [])
* @phpstan-method static string getUrl(string $alias, array<string, mixed> $params = [])
* @phpstan-method static void before(string $name, Closure(array<int, mixed> &$params, string &$output): (void|false) $callback)
* @phpstan-method static void after(string $name, Closure(array<int, mixed> &$params, string &$output): (void|false) $callback)
* @phpstan-method static void set(string|iterable<string, mixed> $key, mixed $value)
* @phpstan-method static mixed get(?string $key)
* @phpstan-method static void render(string $file, ?array<string, mixed> $data = null, ?string $key = null)
* @phpstan-method static void json(mixed $data, int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512)
* @phpstan-method static void jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0)
* @phpstan-method static void jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512)
* *
* # Views * Note: IDEs will use standard @method tags for autocompletion, while PHPStan will use @phpstan-* tags for advanced type checking.
* @method static void render(string $file, ?array<string, mixed> $data = null, ?string $key = null)
* Renders a template file.
* @method static View view() Returns View instance.
*
* # Events
* @method void onEvent(string $event, callable $callback) Registers a callback for an event.
* @method void triggerEvent(string $event, ...$args) Triggers an event.
*
* # Request-Response
* @method static Request request() Returns Request instance.
* @method static Response response() Returns Response instance.
* @method static void redirect(string $url, int $code = 303) Redirects to another URL.
* @method static void json(mixed $data, int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512)
* Sends a JSON response.
* @method static void jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0)
* Sends a JSON response and immediately halts the request.
* @method static void jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512)
* Sends a JSONP response.
* @method static void error(Throwable $exception) Sends an HTTP 500 response.
* @method static void notFound() Sends an HTTP 404 response.
*
* # HTTP methods
* @method static void etag(string $id, ('strong'|'weak') $type = 'strong') Performs ETag HTTP caching.
* @method static void lastModified(int $time) Performs last modified HTTP caching.
* @method static void download(string $filePath) Downloads a file
*/ */
class Flight class Flight
{ {
/** Framework engine. */ /**
* @var Engine<FlightTemplate>
*/
private static Engine $engine; private static Engine $engine;
/** /**
@ -138,7 +124,7 @@ class Flight
return self::app()->{$name}(...$params); return self::app()->{$name}(...$params);
} }
/** @return Engine Application instance */ /** @return Engine<FlightTemplate> Application instance */
public static function app(): Engine public static function app(): Engine
{ {
return self::$engine ?? self::$engine = new Engine(); return self::$engine ?? self::$engine = new Engine();
@ -147,7 +133,7 @@ class Flight
/** /**
* Set the engine instance * Set the engine instance
* *
* @param Engine $engine Vroom vroom! * @param Engine<FlightTemplate> $engine Vroom vroom!
*/ */
public static function setEngine(Engine $engine): void public static function setEngine(Engine $engine): void
{ {

@ -20,6 +20,7 @@ use TypeError;
* *
* @license MIT, http://flightphp.com/license * @license MIT, http://flightphp.com/license
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com> * @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
* @phpstan-template EngineTemplate of object
*/ */
class Dispatcher class Dispatcher
{ {
@ -29,7 +30,7 @@ class Dispatcher
/** Exception message if thrown by setting the container as a callable method. */ /** Exception message if thrown by setting the container as a callable method. */
protected ?Throwable $containerException = null; protected ?Throwable $containerException = null;
/** @var ?Engine $engine Engine instance. */ /** @var ?Engine<EngineTemplate> $engine Engine instance. */
protected ?Engine $engine = null; protected ?Engine $engine = null;
/** @var array<string, callable(): (void|mixed)> Mapped events. */ /** @var array<string, callable(): (void|mixed)> Mapped events. */
@ -77,6 +78,13 @@ class Dispatcher
); );
} }
/**
* Sets the engine instance
*
* @param Engine<EngineTemplate> $engine Flight instance
*
* @return void
*/
public function setEngine(Engine $engine): void public function setEngine(Engine $engine): void
{ {
$this->engine = $engine; $this->engine = $engine;
@ -88,8 +96,9 @@ class Dispatcher
* @param string $name Event name. * @param string $name Event name.
* @param array<int, mixed> $params Callback parameters. * @param array<int, mixed> $params Callback parameters.
* *
* @return mixed Output of callback
* @throws Exception If event name isn't found or if event throws an `Exception`. * @throws Exception If event name isn't found or if event throws an `Exception`.
*
* @return mixed Output of callback
*/ */
public function run(string $name, array $params = []) public function run(string $name, array $params = [])
{ {
@ -102,8 +111,9 @@ class Dispatcher
/** /**
* @param array<int, mixed> &$params * @param array<int, mixed> &$params
* *
* @return $this
* @throws Exception * @throws Exception
*
* @return $this
*/ */
protected function runPreFilters(string $eventName, array &$params): self protected function runPreFilters(string $eventName, array &$params): self
{ {

@ -100,9 +100,9 @@ class PdoWrapper extends PDO
* @param string $sql - Ex: "SELECT * FROM table WHERE something = ?" * @param string $sql - Ex: "SELECT * FROM table WHERE something = ?"
* @param array<int|string,mixed> $params - Ex: [ $something ] * @param array<int|string,mixed> $params - Ex: [ $something ]
* *
* @return Collection * @return Collection|array<mixed,mixed>
*/ */
public function fetchRow(string $sql, array $params = []): Collection public function fetchRow(string $sql, array $params = [])
{ {
$sql .= stripos($sql, 'LIMIT') === false ? ' LIMIT 1' : ''; $sql .= stripos($sql, 'LIMIT') === false ? ' LIMIT 1' : '';
$result = $this->fetchAll($sql, $params); $result = $this->fetchAll($sql, $params);
@ -120,7 +120,7 @@ class PdoWrapper extends PDO
* @param string $sql - Ex: "SELECT * FROM table WHERE something = ?" * @param string $sql - Ex: "SELECT * FROM table WHERE something = ?"
* @param array<int|string,mixed> $params - Ex: [ $something ] * @param array<int|string,mixed> $params - Ex: [ $something ]
* *
* @return array<int,Collection> * @return array<int,Collection|array<string,mixed>>
*/ */
public function fetchAll(string $sql, array $params = []) public function fetchAll(string $sql, array $params = [])
{ {

@ -156,16 +156,16 @@ class Request
{ {
// Default properties // Default properties
if (empty($config) === true) { if (empty($config) === true) {
$scheme = $this->getScheme(); $scheme = $this->getScheme();
$url = $this->getVar('REQUEST_URI', '/'); $url = $this->getVar('REQUEST_URI', '/');
if (strpos($url, '@') !== false) { if (strpos($url, '@') !== false) {
$url = str_replace('@', '%40', $url); $url = str_replace('@', '%40', $url);
} }
$base = $this->getVar('SCRIPT_NAME', ''); $base = $this->getVar('SCRIPT_NAME', '');
if (strpos($base, ' ') !== false || strpos($base, '\\') !== false) { if (strpos($base, ' ') !== false || strpos($base, '\\') !== false) {
$base = str_replace(['\\', ' '], ['/', '%20'], $base); $base = str_replace(['\\', ' '], ['/', '%20'], $base);
} }
$base = dirname($base); $base = dirname($base);
$config = [ $config = [
'url' => $url, 'url' => $url,
'base' => $base, 'base' => $base,
@ -261,9 +261,9 @@ class Request
$method = $this->method ?? $this->getMethod(); $method = $this->method ?? $this->getMethod();
if (in_array($method, ['POST', 'PUT', 'DELETE', 'PATCH'], true) === true) { if (in_array($method, ['POST', 'PUT', 'DELETE', 'PATCH'], true) === true) {
$body = file_get_contents($this->stream_path); $body = file_get_contents($this->stream_path);
} }
$this->body = $body; $this->body = $body;
@ -305,7 +305,7 @@ class Request
$flags = \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE; $flags = \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE;
foreach ($forwarded as $key) { foreach ($forwarded as $key) {
$serverVar = self::getVar($key); $serverVar = self::getVar($key);
if ($serverVar !== '') { if ($serverVar !== '') {
sscanf($serverVar, '%[^,]', $ip); sscanf($serverVar, '%[^,]', $ip);
if (filter_var($ip, \FILTER_VALIDATE_IP, $flags) !== false) { if (filter_var($ip, \FILTER_VALIDATE_IP, $flags) !== false) {
@ -414,17 +414,17 @@ class Request
*/ */
public static function parseQuery(string $url): array public static function parseQuery(string $url): array
{ {
$queryPos = strpos($url, '?'); $queryPos = strpos($url, '?');
if ($queryPos === false) { if ($queryPos === false) {
return []; return [];
} }
$query = substr($url, $queryPos + 1); $query = substr($url, $queryPos + 1);
if ($query === '') { if ($query === '') {
return []; return [];
} }
$params = []; $params = [];
parse_str($query, $params); parse_str($query, $params);
return $params; return $params;
} }
/** /**

@ -324,7 +324,7 @@ class Response
); );
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
} else { } else {
$serverProtocol = Request::getVar('SERVER_PROTOCOL') ?: 'HTTP/1.1'; $serverProtocol = Request::getVar('SERVER_PROTOCOL') ?: 'HTTP/1.1';
$this->setRealHeader( $this->setRealHeader(
sprintf( sprintf(
'%s %d %s', '%s %d %s',
@ -484,10 +484,13 @@ class Response
* Downloads a file. * Downloads a file.
* *
* @param string $filePath The path to the file to be downloaded. * @param string $filePath The path to the file to be downloaded.
* @param string $fileName The name the downloaded file should have. If not provided, the name of the file on disk will be used.
*
* @throws Exception If the file cannot be found.
* *
* @return void * @return void
*/ */
public function downloadFile(string $filePath): void public function downloadFile(string $filePath, string $fileName = ''): void
{ {
if (file_exists($filePath) === false) { if (file_exists($filePath) === false) {
throw new Exception("$filePath cannot be found."); throw new Exception("$filePath cannot be found.");
@ -498,10 +501,14 @@ class Response
$mimeType = mime_content_type($filePath); $mimeType = mime_content_type($filePath);
$mimeType = $mimeType !== false ? $mimeType : 'application/octet-stream'; $mimeType = $mimeType !== false ? $mimeType : 'application/octet-stream';
if ($fileName === '') {
$fileName = basename($filePath);
}
$this->send(); $this->send();
$this->setRealHeader('Content-Description: File Transfer'); $this->setRealHeader('Content-Description: File Transfer');
$this->setRealHeader('Content-Type: ' . $mimeType); $this->setRealHeader('Content-Type: ' . $mimeType);
$this->setRealHeader('Content-Disposition: attachment; filename="' . basename($filePath) . '"'); $this->setRealHeader('Content-Disposition: attachment; filename="' . $fileName . '"');
$this->setRealHeader('Expires: 0'); $this->setRealHeader('Expires: 0');
$this->setRealHeader('Cache-Control: must-revalidate'); $this->setRealHeader('Cache-Control: must-revalidate');
$this->setRealHeader('Pragma: public'); $this->setRealHeader('Pragma: public');

Loading…
Cancel
Save