Получить файл из RequestBody

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
Аватара пользователя
Uniser
Сообщения: 19
Зарегистрирован: 2015.05.22, 00:01
Откуда: Украина
Контактная информация:

Получить файл из RequestBody

Сообщение Uniser »

Методом PATCH отправляю данные с файлом и в отладке вижу их в Request Body
т.е. очевидно они доступны через \Yii::$app->request->getBodyParams()
но при этом перестаёт работать UploadedFile::getInstanceByName('avatar');
возвращает null, т.к. $_FILES пустая.
как правильно загрузить файл в этом случае?

Код: Выделить всё

var data = new FormData();
data.append('avatar', $('input#avatar').get(0).files[0]);
data.append(...);
$.ajax({
    url: 'api/medias',
    data: data,
    cache: false,
    contentType: false,
    processData: false,
    type: 'PATCH',
    success: function(data){
        console.log('media PATCH',data);
    },
    error: function(data){
        console.log('no upload');
    }
});     
Аватара пользователя
ksetrin
Сообщения: 81
Зарегистрирован: 2015.03.06, 10:00
Контактная информация:

Re: Получить файл из RequestBody

Сообщение ksetrin »

Цитатка "An associative array of items uploaded to the current script via the HTTP POST method." выдрана отсюда http://php.net/manual/en/reserved.variables.files.php
Полагаю, что отправляя методом PATCH не попадает в $_FILES
Отправь методом POST и погляди

Как ловить PATCH методы не подскажу
Аватара пользователя
Uniser
Сообщения: 19
Зарегистрирован: 2015.05.22, 00:01
Откуда: Украина
Контактная информация:

Re: Получить файл из RequestBody

Сообщение Uniser »

Метод POST заполнил $_FILES, но к сожалению на POST другая операция, тоже с передачей файла.
У меня подобие RESTful, потому не хотелось бы усложнять url
Аватара пользователя
Uniser
Сообщения: 19
Зарегистрирован: 2015.05.22, 00:01
Откуда: Украина
Контактная информация:

Re: Получить файл из RequestBody

Сообщение Uniser »

Спасибо за ответы. Как правило советы подразумевают передачу одного файла. В моём случае это multipart/form-data. Может посоветуете хороший парсер для чего-то подобного:

Код: Выделить всё

        'request' => [
            'cookieValidationKey' => '...'',
            'parsers' => [
                //'application/json' => 'yii\web\JsonParser',
                'multipart/form-data' => 'app\components\FormParser',
            ]            
        ],
Пока попробую реализовать этот код http://www.chlab.ch/blog/archives/php/m ... p-data-php
Но какой-то он несовершенный :)
Аватара пользователя
Uniser
Сообщения: 19
Зарегистрирован: 2015.05.22, 00:01
Откуда: Украина
Контактная информация:

Re: Получить файл из RequestBody

Сообщение Uniser »

Победил. С удовольствием поделюсь классом FormParser, который можете для себя расширить:

Код: Выделить всё

<?php
namespace app\components;
use yii\web\RequestParserInterface;
use yii\base\InvalidParamException;
/**
 * Parses a raw HTTP request using regexp
 * To enable parsing for Multipart/Form-data requests you can configure [[Request::parsers]] using this class:
 * 'request' => [
 *     'parsers' => [
 *         'multipart/form-data' => 'app\components\FormParser',
 *     ]
 * ]
 * @author Uniser <uniserpl@gmail.com>
 * @since 2.0
 */
class FormParser implements RequestParserInterface
{
    public $asArray = true;
    public $throwException = true;
    public function parse($rawBody, $contentType)
    {
        try {
            // Считываем разделитель из полной версии CONTENT_TYPE
            preg_match('/boundary=(.*)$/', $_SERVER["CONTENT_TYPE"], $matches);

            // Если разделителя нет, то парсим стандартной ф-ей
            if (!count($matches)) {
                parse_str(urldecode($rawBody), $a_data);
                return $a_data;
            }

            $boundary = $matches[1];

            // разбиваем контент на фрагменты и удаляем начало
            $a_blocks = preg_split("/-+$boundary/", $rawBody);
            array_pop($a_blocks);
            
            $a_data = [];
            // анализируем каждый блок
            foreach ($a_blocks as $id => $block) {
                if (empty($block))
                    continue;
                // Примерный формат блока:
                //Content-Disposition: form-data; name=\"avatar\"; filename=\"УР-энергия-b-sBu5HJpDM.jpg\"
                //Content-Type: image/jpeg
                //
                //<value>
                //
                if (preg_match("/Content-Disposition:\s(\S+?);\s*(.*?)\r\n(Content-Type:\s(\S+?)\r\n)?\r\n(.*)\r\n/s", $block, $matches)) {
                    if ($matches[1] !== 'form-data')
                        throw new InvalidParamException('Не могу определить расположение:'.$matches[1]);
                    
                    if (preg_match_all('/\b([^\s=]+)="([^"]*)"(;|$)/', $matches[2],$vars)) {
                        $params = [];
                        foreach($vars[1] as $index=>$name)
                            $params[$name] = $vars[2][$index];
                        if (!isset($params['name']))
                            throw new InvalidParamException('Не могу получить имя переменной:'.$matches[2]);
                        
                        if (empty($matches[4])) // Content-type не задан
                            // Возможно, понадобятся фильтры
                            $a_data[$params['name']] = $matches[5];
                        else {
                            // Файлы
                            $filename = isset($params['filename']) ? $params['filename'] : '';
                            // Принудительно
                            $_FILES[$params['name']] = [
                                'error'    => UPLOAD_ERR_OK,
                                'name'     => $filename, //basename($filename),
                                'type'     => $matches[4],
                                'size'     => strlen($matches[5]),
                                'tmp_name' => $matches[5] // чтобы забрать с помощью UploadedFile->tempName
                            ];
                        }
                    } else
                        throw new InvalidParamException('Не могу получить имя переменной:'.$matches[2]);
                 
                } else // Здесь всё кроме multipart/form-data
                    throw new InvalidParamException('Неизвестный формат:'.substr($block,0,50).'...');
            }
            return $a_data;
        } catch (InvalidParamException $e) {
            if ($this->throwException)
                throw new BadRequestHttpException('Ошибка в данных: ' . $e->getMessage(), 0, $e);
            return null;
        }
    }
}
Теперь если есть такое правило:

Код: Выделить всё

'PATCH  api/medias' => 'api/media/update', // создать/заменить аватарку   
то без особого труда можно сохранить полученный файл наряду с использованием других параметров

Код: Выделить всё

    public function actionUpdate() {
        // В данном случае $_FILES наполнялась парсером FormParser
        // т.к. метод PATCH в отличие от POST не заполняет эту переменную
        // поэтому файл не сохранён под именем tmp_name
        // вместо этого контент файла находится в tmp_name
        // и мы не можем использовать $newAvatar->saveAs($name)
        // а должны вызвать file_put_contents($name,$newAvatar->tempName);
        
        $newAvatar = UploadedFile::getInstanceByName('avatar');
        $response = Yii::$app->getResponse();
        if (empty($newAvatar) || $newAvatar->size > Media::MAX_SIZE) {
            $response->setStatusCode(422, 'Data Validation Failed.');
            throw new BadRequestHttpException();
        }
        $filename = Yii::getAlias('@webroot').'media/'.basename($newAvatar->name);
        file_put_contents($filename,$newAvatar->tempName);
        @chmod($filename,0777);

        $response->format = Response::FORMAT_JSON;
        return $avatar->toArray();
    } 
теперь доступна отправка аяксом методом PATCH (см.начало темы)
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Получить файл из RequestBody

Сообщение zelenin »

хак всем хакам хак
goodini
Сообщения: 3
Зарегистрирован: 2016.05.30, 12:06

Re: Получить файл из RequestBody

Сообщение goodini »

Есть встроенный класс http://www.yiiframework.com/doc-2.0/yii ... arser.html

Код: Выделить всё

return [
    'components' => [
        'request' => [
            'parsers' => [
                'multipart/form-data' => 'yii\web\MultipartFormDataParser'
            ],
        ],
    ],
];
проблему с сохранение файла можно решить вот так

Код: Выделить всё

namespace app\components;

class UploadedFile extends \yii\web\UploadedFile
{
    public function saveAs($file, $deleteTempFile = true)
    {
        if ($this->error == UPLOAD_ERR_OK) {
            if ( is_uploaded_file($this->tempName) ) {
                return $deleteTempFile ? move_uploaded_file($this->tempName, $file)
                                       : copy($this->tempName, $file);
            } else {
                $data = file_get_contents($this->tempName);
                if ( $data ) {
                    file_put_contents($file, $data);
                    return true;
                } else
                    \Yii::info('файл не загружен');
            }
        }
        return false;
    }
}
Ответить