Студия разработки сайтов и приложений

Netspark.ru

Laravel и кастомная валидация docx

В Laravel довольно удобно сделана валидация данных, переданных в запросе (например, от формы). Достаточно написать в контроллере

$data = $this->validate($request,[
    'myvalue' => 'required|integer',
    'myfile' => 'file',
    // ...
]);

и в $data попадет отвалидированный массив данных, если все условия соблюдены. А если нет — будет возвращен код ошибки 422 с соответствующими сообщениями.

Перечень доступных из коробки правил можно найти в документации. Иногда их оказывается недостаточно. У меня вот, например, возникла нужда отвалидировать, что пользователь загрузил файл docx. Стандартное правило должно по идее выглядеть вот так:

'myfile' => 'file|mimes:docx',

но на деле оказывается, что оно не работает. Во всяком случае у меня. На каждый нормальный, сделанный опен-офисом файл Ларавел ругается и говорит, что это не docx. Выяснилось, что методы, используемые для определения формата файла, возвращают application/octet-stream, то есть считают мой документ мутным непонятным бинарником. Как заставить систему определять файл правильно — я пока не понял. Если вы знаете, как, напишите пожалуйста в комментах или в stackoverflow.

А пока ответ не найден, я решил просто написать кастомное правило, валидирующее docx-файл с помощью соответствующей профильной библиотеки PHPWord, которая всё равно в моем проекте уже используется. Сделать это в Ларавеле довольно нетрудно.

Сначала создаем файл для правила:

php artisan make:rule ValidDocx

Затем редактируем код класса с правилом. Реализовать нужно всего два метода: passes(), который определяет, прошло значение валидацию, или нет. И message(), который возвращает сообщение, показываемое пользователю в случае ошибки.

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;
use PhpOffice\PhpWord\Reader\Word2007;

class ValidDocx implements Rule
{
    public function passes($attribute, $value) {
        // сначала попробуем определить тип стандартным способом
        // optional() нужен на случай если в $value передан вообще не файл, а, например, строка
        $mimeType = optional($value)->getMimeType();
        if (!$mimeType) {
            // если нет никакого типа, то это не файл
            return false;
        }

        if ($mimeType == 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
            // если тип уже определился как docx, нет нужды применять тяжелую проверку через PHPWord
            return true;
        }

        if ($mimeType != 'application/octet-stream') {
            // Если не octet-stream, то это файл какого-то другого типа
            // например, картинка. Нет нужны проверять через PHPWord.
            return false;
        }

        // проверяем файл через PHPWord
        $doc = new Word2007();
        try {
            $doc->load($value->getRealPath());
        } catch (\Exception $e) {
            return false;
        }
        if ($doc) {
            return true;
        }

        return false;
    }

    public function message() {
        return 'The :attribute value must be a valid .docx file.';
    }
}

Теперь правило для проверки загруженного docx-файла будет выглядеть вот так:

'myfile' => ['file', new ValidDocx],

Комментарии