Skip to content

Commit bc001b7

Browse files
authored
Merge branch 'master' into feat/route-compact-syntax
2 parents a788c8a + 128b6e3 commit bc001b7

File tree

14 files changed

+565
-59
lines changed

14 files changed

+565
-59
lines changed

.github/workflows/test.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: Pull Request Check
2+
on: [pull_request]
3+
4+
jobs:
5+
unit-test:
6+
name: Unit testing
7+
strategy:
8+
fail-fast: false
9+
matrix:
10+
php: [7.4, 8.0, 8.1, 8.2, 8.3]
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout repository
14+
uses: actions/checkout@v4
15+
with:
16+
fetch-depth: 0
17+
- uses: shivammathur/setup-php@v2
18+
with:
19+
php-version: ${{ matrix.php }}
20+
extensions: curl, mbstring
21+
tools: composer:v2
22+
- run: composer install
23+
- run: composer test

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
},
4242
"require-dev": {
4343
"ext-pdo_sqlite": "*",
44-
"flightphp/runway": "^0.2.0",
44+
"flightphp/runway": "^0.2.3 || ^1.0",
4545
"league/container": "^4.2",
4646
"level-2/dice": "^4.0",
4747
"phpstan/extension-installer": "^1.3",

flight/Engine.php

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,10 @@
6464
* @method void jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0)
6565
* Sends a JSONP response.
6666
*
67-
* # HTTP caching
67+
* # HTTP methods
6868
* @method void etag(string $id, ('strong'|'weak') $type = 'strong') Handles ETag HTTP caching.
6969
* @method void lastModified(int $time) Handles last modified HTTP caching.
70+
* @method void download(string $filePath) Downloads a file
7071
*
7172
* phpcs:disable PSR2.Methods.MethodDeclaration.Underscore
7273
*/
@@ -78,7 +79,7 @@ class Engine
7879
private const MAPPABLE_METHODS = [
7980
'start', 'stop', 'route', 'halt', 'error', 'notFound',
8081
'render', 'redirect', 'etag', 'lastModified', 'json', 'jsonHalt', 'jsonp',
81-
'post', 'put', 'patch', 'delete', 'group', 'getUrl', 'resource'
82+
'post', 'put', 'patch', 'delete', 'group', 'getUrl', 'download', 'resource'
8283
];
8384

8485
/** @var array<string, mixed> Stored variables. */
@@ -600,7 +601,10 @@ public function _start(): void
600601
public function _error(Throwable $e): void
601602
{
602603
$msg = sprintf(
603-
<<<HTML
604+
605+
606+
607+
HTML
604608
<h1>500 Internal Server Error</h1>
605609
<h3>%s (%s)</h3>
606610
<pre>%s</pre>
@@ -612,6 +616,7 @@ public function _error(Throwable $e): void
612616

613617
try {
614618
$this->response()
619+
->cache(0)
615620
->clearBody()
616621
->status(500)
617622
->write($msg)
@@ -752,6 +757,10 @@ public function _resource(
752757
*/
753758
public function _halt(int $code = 200, string $message = '', bool $actuallyExit = true): void
754759
{
760+
if ($this->response()->getHeader('Cache-Control') === null) {
761+
$this->response()->cache(0);
762+
}
763+
755764
$this->response()
756765
->clearBody()
757766
->status($code)
@@ -906,6 +915,20 @@ public function _jsonp(
906915
}
907916
}
908917

918+
/**
919+
* Downloads a file
920+
*
921+
* @param string $filePath The path to the file to download
922+
*
923+
* @throws Exception If the file cannot be found
924+
*
925+
* @return void
926+
*/
927+
public function _download(string $filePath): void
928+
{
929+
$this->response()->downloadFile($filePath);
930+
}
931+
909932
/**
910933
* Handles ETag HTTP caching.
911934
*

flight/Flight.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,17 @@
7070
* @method static void redirect(string $url, int $code = 303) Redirects to another URL.
7171
* @method static void json(mixed $data, int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512)
7272
* Sends a JSON response.
73-
* @method void jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0)
73+
* @method static void jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0)
7474
* Sends a JSON response and immediately halts the request.
7575
* @method static void jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512)
7676
* Sends a JSONP response.
7777
* @method static void error(Throwable $exception) Sends an HTTP 500 response.
7878
* @method static void notFound() Sends an HTTP 404 response.
7979
*
80-
* # HTTP caching
80+
* # HTTP methods
8181
* @method static void etag(string $id, ('strong'|'weak') $type = 'strong') Performs ETag HTTP caching.
8282
* @method static void lastModified(int $time) Performs last modified HTTP caching.
83+
* @method static void download(string $filePath) Downloads a file
8384
*/
8485
class Flight
8586
{

flight/net/Request.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,4 +414,63 @@ public static function getScheme(): string
414414

415415
return 'http';
416416
}
417+
418+
/**
419+
* Retrieves the array of uploaded files.
420+
*
421+
* @return array<string, array<string,UploadedFile>|array<string,array<string,UploadedFile>>> The array of uploaded files.
422+
*/
423+
public function getUploadedFiles(): array
424+
{
425+
$files = [];
426+
$correctedFilesArray = $this->reArrayFiles($this->files);
427+
foreach ($correctedFilesArray as $keyName => $files) {
428+
foreach ($files as $file) {
429+
$UploadedFile = new UploadedFile(
430+
$file['name'],
431+
$file['type'],
432+
$file['size'],
433+
$file['tmp_name'],
434+
$file['error']
435+
);
436+
if (count($files) > 1) {
437+
$files[$keyName][] = $UploadedFile;
438+
} else {
439+
$files[$keyName] = $UploadedFile;
440+
}
441+
}
442+
}
443+
444+
return $files;
445+
}
446+
447+
/**
448+
* Re-arranges the files in the given files collection.
449+
*
450+
* @param Collection $filesCollection The collection of files to be re-arranged.
451+
*
452+
* @return array<string, array<int, array<string, mixed>>> The re-arranged files collection.
453+
*/
454+
protected function reArrayFiles(Collection $filesCollection): array
455+
{
456+
457+
$fileArray = [];
458+
foreach ($filesCollection as $fileKeyName => $file) {
459+
$isMulti = is_array($file['name']) === true && count($file['name']) > 1;
460+
$fileCount = $isMulti === true ? count($file['name']) : 1;
461+
$fileKeys = array_keys($file);
462+
463+
for ($i = 0; $i < $fileCount; $i++) {
464+
foreach ($fileKeys as $key) {
465+
if ($isMulti === true) {
466+
$fileArray[$fileKeyName][$i][$key] = $file[$key][$i];
467+
} else {
468+
$fileArray[$fileKeyName][$i][$key] = $file[$key];
469+
}
470+
}
471+
}
472+
}
473+
474+
return $fileArray;
475+
}
417476
}

flight/net/Response.php

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -286,15 +286,9 @@ public function clear(): self
286286
*/
287287
public function cache($expires): self
288288
{
289-
if ($expires === false) {
289+
if ($expires === false || $expires === 0) {
290290
$this->headers['Expires'] = 'Mon, 26 Jul 1997 05:00:00 GMT';
291-
292-
$this->headers['Cache-Control'] = [
293-
'no-store, no-cache, must-revalidate',
294-
'post-check=0, pre-check=0',
295-
'max-age=0',
296-
];
297-
291+
$this->headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0';
298292
$this->headers['Pragma'] = 'no-cache';
299293
} else {
300294
$expires = \is_int($expires) ? $expires : strtotime($expires);
@@ -437,15 +431,31 @@ public function send(): void
437431
$this->processResponseCallbacks();
438432
}
439433

440-
if (headers_sent() === false) {
441-
$this->sendHeaders(); // @codeCoverageIgnore
434+
if ($this->headersSent() === false) {
435+
// If you haven't set a Cache-Control header, we'll assume you don't want caching
436+
if ($this->getHeader('Cache-Control') === null) {
437+
$this->cache(false);
438+
}
439+
440+
$this->sendHeaders();
442441
}
443442

444443
echo $this->body;
445444

446445
$this->sent = true;
447446
}
448447

448+
/**
449+
* Headers have been sent
450+
*
451+
* @return bool
452+
* @codeCoverageIgnore
453+
*/
454+
public function headersSent(): bool
455+
{
456+
return headers_sent();
457+
}
458+
449459
/**
450460
* Adds a callback to process the response body before it's sent. These are processed in the order
451461
* they are added
@@ -470,4 +480,42 @@ protected function processResponseCallbacks(): void
470480
$this->body = $callback($this->body);
471481
}
472482
}
483+
484+
/**
485+
* Downloads a file.
486+
*
487+
* @param string $filePath The path to the file to be downloaded.
488+
*
489+
* @return void
490+
*/
491+
public function downloadFile(string $filePath): void
492+
{
493+
if (file_exists($filePath) === false) {
494+
throw new Exception("$filePath cannot be found.");
495+
}
496+
497+
$fileSize = filesize($filePath);
498+
499+
$mimeType = mime_content_type($filePath);
500+
$mimeType = $mimeType !== false ? $mimeType : 'application/octet-stream';
501+
502+
$this->send();
503+
$this->setRealHeader('Content-Description: File Transfer');
504+
$this->setRealHeader('Content-Type: ' . $mimeType);
505+
$this->setRealHeader('Content-Disposition: attachment; filename="' . basename($filePath) . '"');
506+
$this->setRealHeader('Expires: 0');
507+
$this->setRealHeader('Cache-Control: must-revalidate');
508+
$this->setRealHeader('Pragma: public');
509+
$this->setRealHeader('Content-Length: ' . $fileSize);
510+
511+
// // Clear the output buffer
512+
ob_clean();
513+
flush();
514+
515+
// // Read the file and send it to the output buffer
516+
readfile($filePath);
517+
if (empty(getenv('PHPUNIT_TEST'))) {
518+
exit; // @codeCoverageIgnore
519+
}
520+
}
473521
}

0 commit comments

Comments
 (0)