※今回はSymfony3.4で問題が発生したお話です。
SymfonyでValidationを使う場合は大体DoctrineのEntityクラスにAnnotationを使って設定するやり方が一般的です。
しかし、時にはEntityを使わないForm(FormType)を使うことがあります。
Validationの設定に関しては「symfony validation処理の方法」という記事に詳しくかかれているので、そちらを読むのをおすすめします。
今回Formに直接Validationを設定するやりかたでCSVアップロード用のFormTypeを作成していましたが、mimeTypeのValidationをやろうとしたらうまく動きませんでした。
validation条件でmimeTypeをtext/csv設定しているのにtext/plainとして判定される
なぜかFormTypeでFileバリデーションでmimeTypeをtext/csv設定しているのにtext/plainとして判定されるちょっとよくわからない現象に遭遇しました。
普通にFileでmimeTypeをtext/csvにしてるだけです。
特に問題内容に見えます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
<?php
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Serializer\Encoder\DecoderInterface;
use Symfony\Component\Validator\Constraints as Assert;
class CsvUploadType extends AbstractType
{
/**
* @var DecoderInterface
*/
private $decoder;
/**
* CsvFileType constructor.
* @param DecoderInterface $decoder
*/
public function __construct(DecoderInterface $decoder)
{
$this->decoder = $decoder;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('csv', FileType::class,[
'label' => 'csvファイル',
'constraints' => [
new Assert\File([
'mimeTypes' => 'text/csv',
]),
],
'error_bubbling' => true,
]);
$builder->addModelTransformer(new CallbackTransformer(function($data){
return $data;
}, function(array $data) {
/** @var UploadedFile $csvFile */
$csvFile = $data['csv'];
return $this->decoder->decode(file_get_contents($csvFile->getRealPath()),'csv');
}));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(['translation_domain' => false]);
}
}
|
原因はFileinfoMimeTypeGuesserでのmimeTypeの判定だった・・・。
FileのmimeTypeのvalidationが実際にどこで行われているのか。
これはFileValidatorクラスのここの部分で実行されています。
validator/FileValidator.php
ここで実行されているgetMimeTypeメソッドですがこれは
最終的にFileinfoMimeTypeGuesser::guess()が呼ばれています。
http-foundation/FileinfoMimeTypeGuesser.php
このときに渡されるfilepathがテンポラリディレクトリに格納されているアップロードされたファイルなんですよね・・・。
つまりファイル名に拡張子がない状態になります。
例:
1
|
/private/var/folders/tn/2mr79vzx7pq5ff29tb15p90r0000gn/T/phpD9xCri
|
ということはFileinfoMimeTypeGuesserはCSVファイルをtext/plainと判定してしまいます・・・。
解決方法
いくつか方法は思いつきましたが、自分でValidatorを作ることにしました。
今回は管理画面でのCSVアップロードを想定しているので、UploadedFile::getClientMimeType()をつかったカスタムバリデーションを作成しました。
src/Validator/Constraints/ClientMimeType
1
2
3
4
5
6
7
8
9
10
11
12
|
<?php
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
class ClientMimeType extends Constraint
{
public $mimeTypes = array();
public $mimeTypesMessage = 'The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}.';
}
|
src/Validator/Constraints/ClientMimeType/ClientMimeTypeValidator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
<?php
namespace App\Validator\Constraints;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class ClientMimeTypeValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
if ($value instanceof UploadedFile && $value->isValid()) {
$mimeTypes = (array) $constraint->mimeTypes;
$mime = $value->getClientMimeType();
foreach ($mimeTypes as $mimeType) {
if ($mimeType === $mime) {
return;
}
}
$this->context->buildViolation($constraint->mimeTypesMessage)
->setParameter('{{ type }}', $mime)
->setParameter('{{ types }}', implode(', ', $mimeTypes))
->addViolation();
}
}
}
|