CakePHP 2.8.3、3.0.18、3.1.13、3.2.6 のリリースで、Validation::uploadedFile()が更新されました。
CakePHP 2.8.3, 3.0.18, 3.1.13 and 3.2.6 Released
具体的には、is_uploaded_file($file['tmp_name'])
のチェックが増えて、本当にPOSTでアップロードされたファイルなのかを確認するようになりました。
まぁそれはいいんですが、テストでファイルのアップロードを実行していたところが通らなくなってしまいました…。
今までこんな風にテストしていました。
public function testAdd()
{
$data = [
'title' => 'test title',
'file1' => [
'name' => 'picture.jpg',
'type' => 'image/jpg',
'tmp_name' => ROOT . DS . 'tests' . DS . 'Fixture' . DS . 'sample.jpg',
'error' => UPLOAD_ERR_OK,
'size' => 1475
]
];
$this->post('/articles/add', $data);
$this->assertRedirect(['controller' => 'articles', 'action' => 'index']);
}
Fixtureディレクトリに置いてあるsample.jpgファイルをPOSTしたようにしてテストしていたのですが、file1は以下のようにuploadedFileバリデーションを使っていたため、is_uploaded_file()
で引っかかってバリデーションエラーが返るようになってしまいました。
public function validationDefault(Validator $validator)
{
$validator
->add('file1', 'image', [
'rule' => ['uploadedFile', [
'types' => Configure::read('image.allow_mime_types'),
'maxSize' => Configure::read('image.max_size')
]],
'message' => 'サイズが大きすぎるか、不正なファイルタイプです'
])
->allowEmpty('file1');
is_uploaded_file()
を偽装(?)する方法はないものかと調べてみましたが、簡単にできそうになく(まぁそういうものかもしれませんが)、uploadedFileバリデーションをコピーしてカスタムバリデーションを作ることにしました。
<?php
namespace App\Model;
class Validation extends \Cake\Validation\Validation
{
public static function uploadedFile($file, array $options = [])
{
$options += [
'minSize' => null,
'maxSize' => null,
'types' => null,
'optional' => false,
];
if (!is_array($file)) {
return false;
}
$keys = ['error', 'name', 'size', 'tmp_name', 'type'];
ksort($file);
if (array_keys($file) != $keys) {
return false;
}
if (!static::uploadError($file, $options['optional'])) {
return false;
}
if ($options['optional'] && (int)$file['error'] === UPLOAD_ERR_NO_FILE) {
return true;
}
if (isset($options['minSize']) && !static::fileSize($file, '>=', $options['minSize'])) {
return false;
}
if (isset($options['maxSize']) && !static::fileSize($file, '<=', $options['maxSize'])) {
return false;
}
if (isset($options['types']) && !static::mimeType($file, $options['types'])) {
return false;
}
if (defined('IS_TEST') && IS_TEST) {
return true;
}
return is_uploaded_file($file['tmp_name']);
}
}
最後のreturnの手前のIS_TESTをチェックしているところ以外は元のuploadedFileと同じです。
IS_TESTはtests/bootstrap.phpでdefineしておきます。
↓を追加。
define('IS_TEST', true);
カスタムバリデーションを指定します。
public function validationDefault(Validator $validator)
{
$validator->provider('custom', 'App\Model\Validation');
$validator
->add('file1', 'image', [
'rule' => ['uploadedFile', [
'types' => Configure::read('image.allow_mime_types'),
'maxSize' => Configure::read('image.max_size')
]],
'provider' => 'custom',
'message' => 'サイズが大きすぎるか、不正なファイルタイプです'
])
->allowEmpty('file1');
これでテストの時はis_uploaded_file()
をチェックしないようになったので、テストが通るようになりました。
なんかすっきりしないけど…。
追記 (2016/04/19)
コメントで教えてもらった方法で、元のソースに手を入れなくてもテストを通すことができました。
https://github.com/cakephp/cakephp/blob/master/tests/TestCase/Validation/stubs.php
↑をコピーして、tests/TestCase/Validation/stubs.phpというファイル(場所は特に決まってないと思いますが、同じようにしました)を作成し、該当のテスト(上記例だとtests/TestCase/Controller/ArticlesControllerTest.php)で
require_once TESTS . 'TestCase' . DS . 'Validation' . DS . 'stubs.php';
をuseの次ぐらいに書くだけです。すっきり!
名前空間上に関数が存在すればその関数を呼ぶなんて知りませんでした。