Yii2 Kartik-v fileiput:包裹在 $_FILES 数组而不是 $_POST 中时获取上传的图像

Yii2 Kartik-v fileiput: Get uploaded images when wrapped in the array of $_FILES instead of $_POST

我正在使用 yii2-formwizard 小部件开发我的应用程序。我已经让它在所有其他部分工作得很好,甚至完美地提交了一张图片。当我尝试提交多张图片时,问题就来了。我得到 $_POST 数组,其中包含表单模型值和图像模型作为 $_POST 内的空数组,但随后所有图像模型值都包含在 $_FILES 数组中。我该如何解决这个问题,因为我以前从未处理过这个问题?以下是相关代码:

_表单视图

<?php

use yii\helpers\Html;
use kartik\widgets\ActiveForm;
use kartik\builder\Form;
use kartik\datecontrol\DateControl;
use kartik\widgets\Select2;
use buttflattery\formwizard\FormWizard;
use yii\helpers\ArrayHelper;
use kartik\file\FileInput;

/**
 * @var yii\web\View $this
 * @var common\models\Listing $model
 * @var yii\widgets\ActiveForm $form
 */
?>

<?php
    echo FormWizard::widget([
        'theme' => FormWizard::THEME_MATERIAL,
        'labelFinish' => 'Submit',
        // 'formOptions'=>[
        //     'options'=>['enctype'=>'multipart/form-data'],
        // ],
        'steps' => [
            [
                'model'=>$listingModel,
                'title'=>'Basic',
                'description'=>'Give us the details of the list',
                'formInfoText'=>'Fill all required fields',
                'fieldConfig' => [
                    'created_by' => false, //hide a specific field
                    'updated_at' => false, //hide a specific field
                    'created_at' => false, //hide a specific field
                    'expires_on' => false, //hide a specific field
                    'status' => false, //hide a specific field
                    'listing_type_id' => false, //hide a specific field
                    'latitude' => false, //hide a specific field
                    'longitude' => false, //hide a specific field

                    'listing_title' => ['type' => Form::INPUT_TEXT, 'options' => ['placeholder' => 'Enter Listing Title...', 'maxlength' => 50]],

                    'country' => [
                        'widget' => Select2::class, //widget class name
                        'options' => [
                            'data' => ArrayHelper::map(common\models\Country::find()->all(), 'country_id', 'country_name'),
                            'options' => [ 
                                'prompt'=>'Select Country',
                                'onchange'=>'
                                    $.post( "'.Yii::$app->urlManager->createUrl('listing/select-state?id=').'"+$(this).val(), function( data ) {
                                      $( "select#state_id" ).html( data );
                                    });
                                '
                            ]
                        ]
                    ],

                    'states' => [
                        'widget' => Select2::class, //widget class name
                        'options' => [
                            'data' => ArrayHelper::map(common\models\States::find()->all(), 'state_id', 'state_name'),
                            'options' => [ 
                                'id'=>'state_id',
                                'prompt'=>'Select Select the Country First',
                                'onchange'=>'
                                    $.post( "'.Yii::$app->urlManager->createUrl('listing/select-area?id=').'"+$(this).val(), function( data ) {
                                      $( "select#area_code" ).html( data );
                                    });
                                '
                            ]
                        ]
                    ],

                    'area_id' => [
                        'widget' => Select2::class, //widget class name
                        'options' => [
                            'data' => ArrayHelper::map(common\models\Areas::find()->all(), 'area_id', 'area_name'),
                            'options' => [
                                'prompt'=>'Please Select the State/Region First', 
                                'id' => 'area_code'
                            ],
                        ]
                    ],

                    'physical_address' => ['type' => Form::INPUT_TEXT, 'options' => ['placeholder' => 'Enter The Actual Physical Address...', 'maxlength' => 50]],

                    'neighborhood' => ['type' => Form::INPUT_TEXT, 'options' => ['placeholder' => 'Enter The Nearby Landmark or Neighbourhood...', 'maxlength' => 50]],

                    'address' => [
                        'widget' => \kalyabin\maplocation\SelectMapLocationWidget::className(),
                        'options' => [
                            'attributeLatitude' => 'latitude',
                            'attributeLongitude' => 'longitude',
                            'googleMapApiKey' => 'AIzaSyDU30XgKi1ik7wpWteHUENKVH_d09sTqRg',
                            'draggable' => true,
                        ],
                    ]
                ]
            ],
            [
                'model'=>$model,
                'title'=>'Prices and More',
                'description'=>'Give us the details of the list',
                'formInfoText'=>'Fill all required fields',
                'fieldConfig' => [

                    // 'only' => ['property_category', 'sub_category_id', 'available_from', 'desc', 'price', 'currency_id', 'price_conditions', 'deposit', 'agent_commission', 'other_payments'],

                    'only' => ['property_category', 'sub_category_id', 'price', 'currency_id'],

                    'property_category' => [
                        'widget' => Select2::class, //widget class name
                        'options' => [
                            'data' => ArrayHelper::map(common\models\PropertyCategory::find()->all(), 'category_id', 'category_name'),
                            'options' => [ 
                                'id'=>'state_id',
                                'prompt'=>'Select Property Category',
                                'onchange'=>'
                                    $.post( "'.Yii::$app->urlManager->createUrl('listing/select-property-category?id=').'"+$(this).val(), function( data ) {
                                      $( "select#sub_category_value" ).html( data );
                                    });
                                '
                            ]
                        ]
                    ],

                    'sub_category_id' => [
                        'widget' => Select2::class, //widget class name
                        'options' => [
                            'data' => ArrayHelper::map(common\models\PropertySubCategory::find()->all(), 'sub_category_id', 'name'),
                            'options' => [
                                'prompt'=>'Please Select the Property Category First', 
                                'id' => 'sub_category_value'
                            ],
                        ]
                    ],

                    'currency_id' => [
                        'widget' => Select2::class, //widget class name
                        'options' => [
                            'data' => ArrayHelper::map(common\models\Currency::find()->all(), 'currency_id', 'title'),
                        ]
                    ],

                    'price' => ['type' => Form::INPUT_TEXT, 'options' => ['placeholder' => 'Enter Price...']],
                ]
            ],
            [
                'model'=>$model,
                'title'=>'Features',
                'description'=>'Give us the details of the list',
                'formInfoText'=>'Fill all required fields',
                'fieldConfig' => [
                    // 'only' => ['beds', 'baths', 'rooms', 'living_area', 'living_size', 'floor', 'total_floors', 'build_year', 'car_spaces', 'fully_furnished', 'property_features'],

                    'only' => ['property_features'],

                    'property_features' => [
                        'widget' => Select2::class, //widget class name
                        'options' => [
                            'data' => ArrayHelper::map(common\models\PropertyFeatures::find()->all(), 'feature_id', 'feature_name', 'featuresType.type_name'),
                            'options' => ['multiple' => true, 'placeholder' => 'Select Property Features ...']
                        ]
                    ],
                ]
            ],
            [
                'model'=>$imageModel,
                'title'=>'Images',
                'description'=>'Give us the details of the list',
                'formInfoText'=>'Fill all required fields',
                'fieldConfig' => [
                    'only' => ['image'],
                    'image' => [
                        'multifield'=>true,
                        'widget' => FileInput::classname(),
                        'options' =>[
                            'options' => [
                                'multiple' => true,
                                'accept' => 'image/*',
                                'pluginOptions' => [
                                    'showCaption' => false,
                                    'showRemove' => false,
                                    'showUpload' => false,
                                    'browseClass' => 'btn btn-primary btn-block',
                                    'browseIcon' => '<i class="glyphicon glyphicon-camera"></i> ',
                                    'browseLabel' =>  'Attach Listing Images',
                                    'allowedFileExtensions' => ['jpg','gif','png'],
                                    'overwriteInitial' => false
                                ],
                            ],
                        ],
                    ]
                ]
            ],
        ]
    ]);
?>

控制器动作

/**
 * Creates a new Property model.
 * If creation is successful, the browser will be redirected to the 'view' page.
 * @return mixed
 */
public function actionProperty()
{
    $listingModel = new Listing;
    $imageModel = new ListingImages;
    $model = new Property;

    if ($listingModel->load(Yii::$app->request->post()) && $imageModel->load(Yii::$app->request->post()) && $model->load(Yii::$app->request->post())) {

        $transaction = Yii::$app->db->beginTransaction();
        try {

            $listingModel->listing_type_id = $listingModel->listingType('Property');
            $listingModel->created_by = Yii::$app->user->id;

            if ($flag = $listingModel->save(false)) {

                $model->listing_id = $listingModel->listing_id;
                $model->physical_address = $listingModel->physical_address;
                $model->neighborhood = $listingModel->neighborhood;
                $model->area_id = $listingModel->area_code;
                $model->address = $listingModel->physical_address;
                $model->latitude = $listingModel->latitude;
                $model->longitude = $listingModel->longitude;
                $model->created_by = Yii::$app->user->id;

                $flag = $model->save(false);

                foreach ($_FILES['ListingImages']['name']['image'] as $key => $image) {
                    $image = $imageModel->uploadImage();

                    $imageModel->created_by = Yii::$app->user->id;
                    $imageModel->listing_id = $listingModel->listing_id;
                    $imageModel->active = 'Y';

                    if ($flag = $imageModel->save()) {
                        if ($image !== false) {
                            $path = $imageModel->getImageFile();
                            $image->saveAs($path);
                        }
                    }
                }
            }

            if ($flag) {
                $transaction->commit();

                return $this->redirect(['view', 'id' => $listingModel->listing_id]);
            } else {
                $transaction->rollBack();
            }
        } catch (Exception $e) {
            $transaction->rollBack();
        }
    } else {
        return $this->render('create', [
            'listingModel' => $listingModel, 'model' => $model, 'form' => '_property', 'imageModel' => $imageModel,
        ]);
    }
}

ListingImages 模型

<?php

namespace common\models;

use Yii;
use yii\web\UploadedFile;
use yii\helpers\FileHelper;

/**
 * This is the model class for table "listing_images".
 *
 * @property int $image_id
 * @property int $listing_id
 * @property string $image_url_link generated filename on server
 * @property string $updated_at
 * @property string $created_at
 * @property int $created_by
 * @property string $active
 * @property string $filename source filename from client
 *
 * @property Listing $listing
 */
class ListingImages extends \yii\db\ActiveRecord
{
    const PERMISSIONS_PRIVATE = 10;
    const PERMISSIONS_PUBLIC = 20; 
    public $filename; 
    public $image;
    /**
     * {@inheritdoc}
     */
    public static function tableName()
    {
        return 'listing_images';
    }

    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [['listing_id', 'image_url_link', 'created_by', 'active'], 'required'],
            [['listing_id', 'created_by'], 'integer'],
            [['updated_at', 'created_at', 'filename'], 'safe'],
            [['active'], 'string'],
            [['image_url_link'], 'string', 'max' => 80],
            [['listing_id'], 'exist', 'skipOnError' => true, 'targetClass' => Listing::className(), 'targetAttribute' => ['listing_id' => 'listing_id']],
            // [['image'], 'file', 'extensions'=>'jpg, gif, png'],
            // [['image'], 'file', 'maxSize'=>'2048000'],
            // [['image'], 'file','maxFiles' => 30],
            [['image'], 'file', 'extensions' => ['png', 'jpg', 'gif'], 'maxSize' => 2048000, 'maxFiles' => 30],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function attributeLabels()
    {
        return [
            'image_id' => Yii::t('app', 'Image ID'),
            'listing_id' => Yii::t('app', 'Listing ID'),
            'image_url_link' => Yii::t('app', 'Listing Image'),
            'updated_at' => Yii::t('app', 'Updated At'),
            'created_at' => Yii::t('app', 'Created At'),
            'created_by' => Yii::t('app', 'Created By'),
            'active' => Yii::t('app', 'Active'),
        ];
    }

    /**
     * @return \yii\db\ActiveQuery
     */
    public function getListing()
    {
        return $this->hasOne(Listing::className(), ['listing_id' => 'listing_id']);
    }

    /**
     * fetch stored image file name with complete path 
     * @return string
     */
    public function getImageFile() 
    {
        $directory = Yii::$app->params['uploadPath'];
        if (!is_dir($directory)) {
            FileHelper::createDirectory($directory);
        }
        return isset($this->image_url_link) ? $directory . '/' . $this->image_url_link : null;
    }

    /**
     * fetch stored image url
     * @return string
     */
    public function getImageUrl() 
    {
        $directory = Yii::$app->params['uploadUrl'];
        // return a default image placeholder if your source image_url_link is not found
        $image_url_link = isset($this->image_url_link) ? $this->image_url_link : 'default_user.jpg';
        return $directory . $image_url_link;
    }

    /**
    * Process upload of image
    *
    * @return mixed the uploaded image instance
    */
    public function uploadImage() {
        // get the uploaded file instance. for multiple file uploads
        // the following data will return an array (you may need to use
        // getInstances method)
        $image = UploadedFile::getInstance($this, 'image');

        // if no image was uploaded abort the upload
        if (empty($image)) {
            return false;
        }

        // store the source file name
        $tmp = explode(".", $image->name);
        $ext = end($tmp);

        // generate a unique file name
        $this->image_url_link = Yii::$app->security->generateRandomString().".{$ext}";

        // the uploaded image instance
        return $image;
    }

    /**
    * Process deletion of image
    *
    * @return boolean the status of deletion
    */
    public function deleteImage() {
        $file = $this->getImageFile();

        // check if file exists on server
        if (empty($file) || !file_exists($file)) {
            return false;
        }

        // check if uploaded file can be deleted on server
        if (!unlink($file)) {
            return false;
        }

        // if deletion successful, reset your file attributes
        $this->image_url_link = null;
        $this->filename = null;

        return true;
    }
}

请注意,代码的另一部分工作正常。我只扭曲了一些部分以获得有利的结果。这是通过表单提交的数据。真正让我感到困惑的部分是包含在这段代码末尾的 $_FILES 数组中,其中包含所有上传的文件信息。

$_POST = [
    '_csrf-backend' => '_ioSvkoWdYDTEG_L4AHgnyQSEe7pZOqEWwQfPANPbM2uGkbIKHo8y5ZAGriqT4XsQCBkl5sOnvweT31wUx01kg==',
    'Listing' => [
        'listing_title' => 'tyguhijokpl[',
        'country' => '2',
        'states' => '4',
        'area_id' => '1537',
        'physical_address' => 'yghbunjikmol,',
        'neighborhood' => 'gvybhunjimko,l',
        'address' => 'Dandora phase 4, Nairobi, Kenya',
        'latitude' => '-1.2423923',
        'longitude' => '36.90438449999999',
    ],
    'Property' => [
        'property_category' => '1',
        'sub_category_id' => '2',
        'currency_id' => '1',
        'price' => '897465123',
        'property_features' => [
            '1',
            '2',
            '3',
            '4',
            '5',
            '6',
            '7',
            '8',
            '9',
        ],
    ],
    'ListingImages' => [
        'image' => [
            '',
        ],
    ],
];

$_FILES = [
    'ListingImages' => [
        'name' => [
            'image' => [
                'coins-1015125_1920.jpg',
                'computer-768608_1920.jpg',
                'content-is-king-1132259_1920.jpg',
                'content-marketing.jpg',
                'contentpyramid.png',
                'cup-of-coffee-1280537_1920.jpg',
                'ecommerce-3546296_1920.jpg',
                'email-3249062_1280.png',
            ],
        ],
        'type' => [
            'image' => [
                'image/jpeg',
                'image/jpeg',
                'image/jpeg',
                'image/jpeg',
                'image/png',
                'image/jpeg',
                'image/jpeg',
                'image/png',
            ],
        ],
        'tmp_name' => [
            'image' => [
                'C:\xampp\tmp\php8AF1.tmp',
                'C:\xampp\tmp\php8B02.tmp',
                'C:\xampp\tmp\php8B03.tmp',
                'C:\xampp\tmp\php8B23.tmp',
                'C:\xampp\tmp\php8B34.tmp',
                'C:\xampp\tmp\php8B35.tmp',
                'C:\xampp\tmp\php8B46.tmp',
                'C:\xampp\tmp\php8B47.tmp',
            ],
        ],
        'error' => [
            'image' => [
                0,
                0,
                0,
                0,
                0,
                0,
                0,
                0,
            ],
        ],
        'size' => [
            'image' => [
                364796,
                275881,
                310313,
                301511,
                119458,
                508911,
                219479,
                242737,
            ],
        ],
    ],
];

几个月前我创建了这个扩展。您提交的文件将在 $_FILES 数组中而不是 $_POST 中,您需要调用 UploadedFile::getInstances('image') 来获取您选择上传的所有图像,然后迭代它们以上传,您可以访问每个图像的 here 列出的所有属性。

并且您并不是每次在 foreach ($_FILES['ListingImages']['name']['image'] as $key => $image) { 中插入图像时都创建新对象,这将仅显示最后插入的图像,您的 $imageModel 在操作开始时被初始化而你也应该把它放在 foreach 里面

$imageInstances=UploadedFile::getInstances('image');
foreach ($imageInstances as $instance) {
    $imageModel=new ListingImages();
    $image = $imageModel->uploadImage($instance);
    $imageModel->created_by = Yii::$app->user->id;
    $imageModel->listing_id = $listingModel->listing_id;
    $imageModel->active = 'Y';

    if ($flag = $imageModel->save()) {
        if ($image !== false) {
            $path = $imageModel->getImageFile();
            $image->saveAs($path);
        }
    }
}

并且在您内部 uploadImage() 使用此实例访问图像的 nametypesizeextension 以分配给特定字段

 /**
    * Process upload of image
    *
    * @return mixed the uploaded image instance
    */
    public function uploadImage($image) {
        // get the uploaded file instance. for multiple file uploads
        // the following data will return an array (you may need to use
        // getInstances method)

        // store the source file name
        $tmp = explode(".", $image->name);
        $ext = end($tmp);

        // generate a unique file name
        $this->image_url_link = Yii::$app->security->generateRandomString().".{$ext}";

        // the uploaded image instance
        return $image;
    }