PHP
Laravel
LT

UploadedFile::fake() について調べてみた


$ php artisan help @tetsunosuke


  • 会場スポンサーの中の人です

  • PHP歴:15年 / Laravel歴:2週間

  • 人事歴:9ヶ月半(いまここ)


    • 研修(ワークショップデザイン)

    • 採用(エンジニアをWe are hiring!)

    • イベント(会場貸してます!)





はじめに

リファレンスの HTTP Tests のところで、下記のような記述があり、ファイルを生成してそれをアップロードするテストが書けると知りました。

UploadedFile::fake()->image('avatar.jpg', $width, $height)

->size(100);



ニーズとしては


  • アップロードサイズ制限でのエラー: upload_max_filesize 

  • HTTP POSTのサイズ上限: post_max_size

あたりが決まっている場合にテストできるよねということです。


...でもこれはphp.iniとか .htaccessの設定。

Apacheとかじゃないと動かないとかありそう



リファレンス通りにやってみる

<?php

namespace Tests\Feature;
public function testAvatarUpload()
{
Storage::fake('avatars');
// リファレンスから変更 w:200, h:100, size:1000(kB?)
$file = UploadedFile::fake()->image('avatar.jpg', 200, 100)->size(1000);

$response = $this->json('POST', '/avatar', [
'avatar' => $file,
]);
// ...
}



アップロードされた画像を見てみる


*Controller.php

    public function upload(Request $request)

{
// これにより tmp/upload/phpq2D07W のようなファイルができる
$filename = $request->file->move('tmp/upload');
return redirect('/');
}



こんな。

確かに w:200, h:100。 image.png



ファイルのサイズを見てみると

$ wc -c < tmp/upload/phpq2D07W

1055

あれ?



そもそも

真っ黒なファイルで縦横サイズを決めて、ファイルサイズを決めるなんて可能なのだろうか...?

(...メタデータに無駄なバイトを書き込んでおく?)



このファイルがどのようにFakeで生成されるか調べた



これっぽい


Illuminate/Http/Testing/File.php

    /**

* Create a new fake image.
* 略
* @return \Illuminate\Http\Testing\File
*/

public static function image($name, $width = 10, $height = 10)
{
// デフォルトで `$file = UploadedFile::fake()->image('avatar.jpg');`
// したときは 10x10 の画像だった。

return (new FileFactory)->image($name, $width, $height);
}




その他追っていくと


Illuminate/Http/Testing/FileFactory.php

    /**

* Generate a dummy image of the given width and height.
* 略
*/

protected function generateImage($width, $height, $type)
{
return tap(tmpfile(), function ($temp) use ($width, $height, $type) {
// ...
// 確かに縦横ベースで画像データを作っている!
$image = imagecreatetruecolor($width, $height);
// ...ここでpngにするかjpegにするか変換している処理も
});
}



tap ってなんだ?!

tap(tmpfile(), function () {});



ヘルパー関数だ!


Illuminate\Support\helpers.php

if (! function_exists('tap')) {

/**
* Call the given Closure with the given value then return the value.
* 略
*/

function tap($value, $callback = null)
{
// 略
}
}



Laravel作者はtapがオキニの様子



一時ファイルの生成はわかったけどsize() はどうなってんだ?



リクエストのHTTPヘッダー見てみる


app/Http/Controllers/HomeController.php

public function upload(Request $request)

{
// header() 引数なしで呼ぶと全部
// 引数あり、たとえば Content-typeを渡すと
// application/x-www-form-urlencoded とかが取れる
$headers = $request->header();
var_dump($headers);
}



Content-Length もない...

array(6) {

'host' =>
array(1) {
[0] =>
string(14) "127.0.0.1:8000"
}
'user-agent' =>
array(1) {
[0] =>
string(7) "Symfony" ← へーそうなんだ!!
}
'accept' =>
array(1) {
[0] =>
string(63) "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
}
'accept-language' =>
array(1) {
[0] =>
string(14) "en-us,en;q=0.5"
}
'accept-charset' =>
array(1) {
[0] =>
string(30) "ISO-8859-1,utf-8;q=0.7,*;q=0.7"
}
'content-type' =>
array(1) {
[0] =>
string(33) "application/x-www-form-urlencoded"
}
}



アップロード側のデータを見てみる


tests/Feature/Upload.php

        $file = UploadedFile::fake()->image('avatar.jpg', 200, 100)->size(1000);

var_dump($file);

class Illuminate\Http\Testing\File#296 (10) {

...
public $sizeToReport => int(1024000)
...
}



ファイルのサイズは

$request->file('file') ->getSize()で取得できますね。


app/Http/Controllers/HomeController.php

public function upload(Request $request)

{
// これでアップロードされたファイルサイズに応じた処理ができる
// $_FILES['file']['size']相当
$size = $request->file('file')->getSize();
}



まとめ


  • フレームワークの中のコードを追っていくと納得できる!


  • php.ini まで面倒見てくれないこともあるようだ!

  • 今度はDuskの内部挙動を調べてみたい

余談:Qiitaのスライドモードムズい。

$ php artisan follow @tetsunosukeito