Príklad: nahrávanie súborov
POST /api/v1/avatar prijme jeden obrázok a meno používateľa. POST /api/v1/attachments prijme viac súborov v jednom poli.
| Nahrávanie | Odpoveď |
|---|---|
| PNG do 100 kB | 201 s uloženým menom |
| bez súboru | 400 Field is required |
| nad 100 kB | 413 |
textový súbor poslaný ako image/png | 415 text/plain is not allowed |
tri súbory vo files[] | 200 s tromi položkami |
Handler pre jeden súbor
$params['avatar'] je položka z $_FILES: name, type, tmp_name, error a size.
<?php
declare(strict_types=1);
namespace App\Api;
use Tomaj\NetteApi\Handlers\BaseHandler;
use Tomaj\NetteApi\Params\FileInputParam;
use Tomaj\NetteApi\Params\PostInputParam;
use Tomaj\NetteApi\Response\JsonApiResponse;
use Tomaj\NetteApi\Response\ResponseInterface;
final class AvatarUploadHandler extends BaseHandler
{
private const MAX_BYTES = 100_000;
private const TYPES = ['image/png' => 'png', 'image/jpeg' => 'jpg'];
public function __construct(private string $storageDir)
{
parent::__construct();
}
public function summary(): string
{
return 'Upload an avatar';
}
public function params(): array
{
return [
// $_FILES['avatar']: name, type, tmp_name, error, size
(new FileInputParam('avatar'))->setRequired()->setDescription('PNG or JPEG, at most 100 kB'),
(new PostInputParam('user'))->setRequired(),
];
}
public function handle(array $params): ResponseInterface
{
$file = $params['avatar'];
// nette-api checks only that the file field is there; everything else is up to the handler
if ($file['error'] !== UPLOAD_ERR_OK || !is_uploaded_file($file['tmp_name'])) {
return new JsonApiResponse(400, ['status' => 'error', 'message' => 'Upload failed']);
}
if ($file['size'] > self::MAX_BYTES) {
return new JsonApiResponse(413, ['status' => 'error', 'message' => 'The file is larger than 100 kB']);
}
// never trust $file['type'], it comes from the client
$type = (string) (new \finfo(FILEINFO_MIME_TYPE))->file($file['tmp_name']);
if (!isset(self::TYPES[$type])) {
return new JsonApiResponse(415, ['status' => 'error', 'message' => "Type $type is not allowed"]);
}
// never use $file['name'] as a path either
$target = $this->storageDir . '/' . bin2hex(random_bytes(8)) . '.' . self::TYPES[$type];
move_uploaded_file($file['tmp_name'], $target);
return new JsonApiResponse(201, ['user' => $params['user'], 'type' => $type, 'size' => $file['size']]);
}
}Viac súborov
setMulti() urobí z parametra zoznam. Handler dostane jednu položku na súbor.
<?php
declare(strict_types=1);
namespace App\Api;
use Tomaj\NetteApi\Handlers\BaseHandler;
use Tomaj\NetteApi\Params\FileInputParam;
use Tomaj\NetteApi\Response\JsonApiResponse;
use Tomaj\NetteApi\Response\ResponseInterface;
final class AttachmentsUploadHandler extends BaseHandler
{
public function summary(): string
{
return 'Upload several files at once';
}
public function params(): array
{
return [
// files[]=@a.txt files[]=@b.txt; setMulti() turns $_FILES into one array per file
(new FileInputParam('files'))->setMulti()->setRequired(),
];
}
public function handle(array $params): ResponseInterface
{
$files = [];
foreach ($params['files'] as $file) {
$files[] = ['name' => basename($file['name']), 'size' => $file['size'], 'ok' => $file['error'] === UPLOAD_ERR_OK];
}
return new JsonApiResponse(200, ['files' => $files]);
}
}Služby
application:
mapping:
Api: Tomaj\NetteApi\Presenters\*Presenter
services:
router: App\RouterFactory::create
apiPresenter:
factory: Tomaj\NetteApi\Presenters\ApiPresenter
- Tomaj\NetteApi\Output\Configurator\EnvConfigurator
- Tomaj\NetteApi\Error\DefaultErrorHandler
apiDecider:
factory: Tomaj\NetteApi\ApiDecider
setup:
# POST /api/v1/avatar, multipart/form-data with avatar and user
- addApi(Tomaj\NetteApi\EndpointIdentifier('POST', 1, 'avatar'), App\Api\AvatarUploadHandler(%tempDir%), Tomaj\NetteApi\Authorization\NoAuthorization())
# POST /api/v1/attachments, multipart/form-data with files[]
- addApi(Tomaj\NetteApi\EndpointIdentifier('POST', 1, 'attachments'), App\Api\AttachmentsUploadHandler(), Tomaj\NetteApi\Authorization\NoAuthorization())Test
#!/usr/bin/env bash
# End-to-end check: one file and many files sent as multipart/form-data.
set -euo pipefail
cd "$(dirname "$0")"
[[ -f vendor/autoload.php ]] || { echo "fail: vendor/ is missing, run composer install first" >&2; exit 1; }
source ../ci/lib.sh
serve 8092
files="$(mktemp -d)"
# a 1x1 PNG, a text file and a 200 kB file
printf '\x89PNG\r\n\x1a\n\0\0\0\rIHDR\0\0\0\1\0\0\0\1\x08\x06\0\0\0\x1f\x15\xc4\x89\0\0\0\rIDATx\x9cc\xf8\x0f\0\0\x01\x01\0\x05\x18\xd8N\0\0\0\0IEND\xaeB`\x82' > "$files/a.png"
echo 'hello' > "$files/a.txt"
head -c 200000 /dev/zero > "$files/big.png"
request -F "avatar=@$files/a.png" -F user=ada "$BASE/api/v1/avatar"
expect_status 201 "png"
expect_body '"user":"ada","type":"image/png"' "png"
ok "a PNG is stored, the type is detected from the content"
request -F "avatar=@$files/a.txt;type=image/png" -F user=ada "$BASE/api/v1/avatar"
expect_status 415 "fake type"
expect_body 'text/plain is not allowed' "fake type"
ok "a text file sent as image/png answers 415"
request -F "avatar=@$files/big.png" -F user=ada "$BASE/api/v1/avatar"
expect_status 413 "too big"
ok "a file over 100 kB answers 413"
request -F user=ada "$BASE/api/v1/avatar"
expect_status 400 "no file"
expect_body '"avatar":["Field is required"]' "no file"
ok "a missing file answers 400 from nette-api"
request -F "files[]=@$files/a.txt" -F "files[]=@$files/a.png" "$BASE/api/v1/attachments"
expect_status 200 "many"
expect_body '{"files":[{"name":"a.txt","size":6,"ok":true},{"name":"a.png"' "many"
ok "setMulti() gives one array per uploaded file"
rm -rf "$files"Časté otázky
Prečo neveriť poľu type?
Posiela ho klient. finfo sa namiesto toho pozrie na bajty súboru.
Prečo náhodné meno súboru?
Meno od klienta môže obsahovať ../ alebo prepísať iný súbor. Ak pôvodné meno potrebujete, uložte ho do databázy.
Kde je limit veľkosti?
V handleri a v PHP: upload_max_filesize a post_max_size musia byť aspoň také veľké, inak PHP súbor zahodí skôr, než ho handler uvidí.
Funguje to s PUT?
Nie, PHP plní $_FILES iba pre multipart požiadavky POST. Pre PUT pošlite súbor ako surové telo, pozrite si JSON a surové telo.
Ako posiela súbory konzola?
Pre každý FileInputParam ukáže pole na výber súboru. Pozrite si Webová konzola.