yiisoft / yiisoft/form-model

FormHydrator: гидрация UploadedFileInterface из POST+FILES (включая nested forms)

Open
#90 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
PHP
Stars
17
Forks
7
Avg merge
58m
Merged PRs (30d)
1

Description

Proposed new feature or change

FormHydrator::populateFromPost() и populateFromPostAndValidate() сейчас берут только $request->getParsedBody(), поэтому UploadedFileInterface-поля (включая nested/collection формы) не гидрируются без ручного merge POST + FILES.

Нужна доработка “из коробки” для файловой гидрации (в т.ч. вложенных структур), с корректной фильтрацией UPLOAD_ERR_NO_FILE (иначе в модель попадают “пустые” файлы).

Сейчас обходной путь такой:

$post = array_merge_recursive(
    $request->getParsedBody(),
    UploadedFilesFilter::filter($request->getUploadedFiles())
);
$this->formHydrator->populateAndValidate($form, $post);
<?php

declare(strict_types=1);

namespace App;

use Psr\Http\Message\UploadedFileInterface;

final class UploadedFilesFilter
{
    /**
     * @param array<array-key, mixed> $uploadedFiles
     *
     * @return array<array-key, mixed>
     */
    public static function filter(array $uploadedFiles): array
    {
        $result = [];

        foreach ($uploadedFiles as $key => $value) {
            if ($value instanceof UploadedFileInterface) {
                if ($value->getError() === UPLOAD_ERR_NO_FILE) {
                    continue;
                }

                $result[$key] = $value;
                continue;
            }

            if (is_array($value)) {
                $filtered = self::filter($value);
                if ($filtered !== []) {
                    $result[$key] = $filtered;
                }
                continue;
            }

            $result[$key] = $value;
        }

        return $result;
    }
}
Пример формы с вложенными изображениями
use Psr\Http\Message\UploadedFileInterface;
use Yiisoft\FormModel\FormModel;
use Yiisoft\Hydrator\Attribute\Parameter\Collection;
use Yiisoft\Validator\Label;
use Yiisoft\Validator\Rule\Each;
use Yiisoft\Validator\Rule\Image\Image;
use Yiisoft\Validator\Rule\Nested;
use Yiisoft\Validator\Rule\Length;

final class Form extends FormModel
{
    #[Label('Title')]
    #[Length(min: 2)]
    public string $title = '';
    
    #[Label('Picture')]
    #[Image(skipOnEmpty: true)]
    public ?UploadedFileInterface $picture = null;

    #[Label('Nested picture')]
    #[Nested(PictureForm::class)]
    public ?PictureForm $nestedPicture = null;

    /** @var array<UploadedFileInterface> */
    #[Label('Gallery')]
    #[Each([new Image(skipOnEmpty: true)])]
    public array $pictures = [];

    /** @var array<PictureForm> */
    #[Collection(PictureForm::class)]
    #[Label('Nested gallery')]
    #[Each([new Nested(PictureForm::class)])]
    public array $nestedPictures = [];
}

final class PictureForm extends FormModel
{
    #[Label('Description')]
    #[Length(min: 0)]
    public string $description = '';
    
    #[Label('Picture')]
    #[Image(skipOnEmpty: true)]
    public ?UploadedFileInterface $picture = null;

    /** @var array<NestedPictureForm> */
    #[Collection(NestedPictureForm::class)]
    #[Label('Nested gallery level 2')]
    #[Each([new Nested(NestedPictureForm::class)])]
    public array $nestedPictures = [];
}

final class NestedPictureForm extends FormModel
{
    #[Label('Description')]
    #[Length(min: 0)]
    public string $description = '';
    
    #[Label('Picture')]
    #[Image(skipOnEmpty: true)]
    public ?UploadedFileInterface $picture = null;
}
Input names
  • Form[title]
  • Form[picture]
  • Form[pictures][]
  • Form[nestedPicture][description]
  • Form[nestedPicture][picture]
  • Form[nestedPictures][0][description]
  • Form[nestedPictures][0][picture]
  • Form[nestedPictures][0][nestedPictures][0][description]
  • Form[nestedPictures][0][nestedPictures][0][picture]
Ожидание

Встроенный механизм в FormHydrator (новые методы или флаг в populateFromPost*), чтобы ручной merge/фильтр не дублировались в каждом action.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in FormHydrator::populateFromPost() and populateFromPostAndValidate(), tracing how getParsedBody() is used and where getUploadedFiles() can be combined. Verify the result with the nested form and collection structures described in the issue; done means UploadedFileInterface fields hydrate from POST plus FILES at every shown nesting level while UPLOAD_ERR_NO_FILE entries are excluded.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
backend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.