Победил. С удовольствием поделюсь классом 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 (см.начало темы)