yii2 FileInput - kartik

Witam,

próbowałem uruchomić widget github.com/kartik-v/yii2-widget-fileinput

wg

przy próbie dodania pliku (kliknięciu Upload) otrzymuję komunikat:

Trying to get property of non-object

W trybie debugera zaznaczoną mam linię:


$model->filename = $image->name;

cały kontroler wygląda tak:


<?php

namespace backend\controllers;


use Yii;

use app\models\Person;

use yii\web\UploadedFile;

use yii\web\Controller;

 

class PersonController extends Controller

{

    public function actionCreate()

    {

        $model = new Person;

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

            // get the uploaded file instance. for multiple file uploads

            // the following data will return an array

            $image = UploadedFile::getInstance($model, 'image');

 

            // store the source file name

            $model->filename = $image->name;

            $ext = end((explode(".", $image->name)));

 

            // generate a unique file name

            $model->avatar = Yii::$app->security->generateRandomString().".{$ext}";

 

            // the path to save file, you can set an uploadPath

            // in Yii::$app->params (as used in example below)

            $path = Yii::$app->params['uploads'] . $model->avatar;

 

            if($model->save()){

                $image->saveAs($path);

                return $this->redirect(['view', 'id'=>$model->_id]);

            } else {

                // error in saving model

            }

        }

        return $this->render('create', [

            'model'=>$model,

        ]);

    }

}

?>

Wzorując się na powyższym opisie autora widgetu utworzyłem w bazie tabelę "person".

Model:


<?php

namespace app\models;

 

use yii\db\ActiveRecord;

 

/**

* Class Person

* @package common\models

* @package common\models

* @property int $id unique person identifier

* @property string $name person / user name

* @property array $avatar generated filename on server

* @property string $filename source filename from client

*/

class Person extends ActiveRecord

{

    /**

    * @var mixed image the attribute for rendering the file input

    * widget for upload on the form

    */

    public $image;

 

    public function rules()

    {

        return [

            [['image'], 'safe'],

            [['image'], 'file', 'extensions'=>'jpg, gif, png'],

        ];

    }

}

 

?>

I widok formularza:


<?php


use yii\helpers\Html;

use yii\widgets\ActiveForm;

use kartik\file\FileInput;


 

$form = ActiveForm::begin([

    'options'=>['enctype'=>'multipart/form-data'] // important

]);


echo $form->field($model, 'id')->textInput();

//echo $form->field($model, 'filename'); 

echo $form->field($model, 'filename')->widget(FileInput::classname(), [

    'options' => ['accept' => 'image/*'],

]);

// your fileinput widget for single file upload

$form->field($model, 'image')->widget(FileInput::classname(), [

    'options'=>['accept'=>'image/*'],

    'pluginOptions'=>['allowedFileExtensions'=>['jpg','gif','png']

]]);

 

/**

* uncomment for multiple file upload

*

echo $form->field($model, 'image[]')->widget(FileInput::classname(), [

    'options'=>['accept'=>'image/*', 'multiple'=>true],

    'pluginOptions'=>['allowedFileExtensions'=>['jpg','gif','png']

]);

*

*/

echo Html::submitButton($model->isNewRecord ? 'Upload' : 'Update', [

    'class'=>$model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']

);

ActiveForm::end();

 ?>

W swoim przykładzie używam advanced application template, pozmieniałem niektóre fragmenty kodu z przykładu, aby pasowały do tej wersji aplikacji (mam nadzieję, że tutaj nie popełniłem błędu). Nie wiem gdzie powinienem utworzyć katalog uploads/ aby można było dodawać z niego pliki do linków CKEditor i oczywiście żeby były dostępne dla części frontend.

Witam,

Na początek sprawdź czy tworzenie rekordu Person bez dodawania zdjęcia przechodzi. Analizując powyższy model i kontroler wydaje mi się, że nie. Dlatego, że nie wstawiając zdjęcia, a dajesz taką możliwość (patrz rules w Person) UploadedFile zwróci Ci nulla wiec komunikat "[color="#1C2837"][size="2"]Trying to get property of non-object" wydaje się być słuszny[/size][/color].

Jeżeli to co napisałem wyżej jest prawdą proponuję zanim zaczniesz korzystać z $image sprawdzić czy nie jest null:




$image = UploadedFile::getInstance($model, 'image');




if ($image !== null) {


  $model->filename = $image->name;

  $ext = end((explode(".", $image->name)));

  ...

}

Dziękuję za odpowiedź.

Rzeczywiście nie dało się dodac rekordu (nawet bez zdjęcia). Usunąłem ten model i kontroler. Do utworzonej tabeli wygenerowałem (gii) nowy model i kontroler. Wyedytowałem je wg opisu autora widżetu. W tej chwili działa mi dodawanie do bazy nazwy pliku, ale mam problem z fizycznym umieszczaniem plików we wskazanym katalogu. Reasumując nie wiem:

  1. jak umieścić jako globalny parametr:

Yii::$app->params['uploadPath'] = Yii::$app->basePath . '/uploads/';

w jakim pliku i w jakiej postaci?

  1. gdzie utworzyć powyższy katalog, przy założeniu, że korzystam z advanced application template?

Poniżej zmieniona akcja Create kontrolera:


    public function actionCreate()

    {

        $model = new Person();


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

            return $this->redirect(['view', 'id' => $model->id]);

            

            // get the uploaded file instance. for multiple file uploads

            // the following data will return an array

            $image = UploadedFile::getInstance($model, 'image');

 

            // store the source file name

            $model->filename = $image->name;

            $ext = end((explode(".", $image->name)));

 

            // generate a unique file name

            $model->avatar = Yii::$app->security->generateRandomString().".{$ext}";

 

            // the path to save file, you can set an uploadPath

            // in Yii::$app->params (as used in example below)

            $path = Yii::$app->params['uploadPath'] . $model->avatar;

 

            if($model->save()){

                $image->saveAs($path);

                return $this->redirect(['view', 'id'=>$model->_id]);

            } else {

                // error in saving model

            }


            

        } else {

            return $this->render('create', [

                'model' => $model,

            ]);

        }

    }

Witam,

Nie za bardzo wiem o co Ci chodzi, ale postaram się pomóc.

W szablonie advanced spokojnie możesz dodać katalog uploads w folderze frontend/web/. Jeżeli chcesz przechowywać nazwę katalogu w params to wystarczy dodać do pliku params.php, który znajduje się w frontend/config wpis ‘uploadPath’ => ‘uploads’. Dzięki temu będziesz mógł swobodnie modyfikować sobie folder do którego mają trafiać pliki.

Aby uzyskać całą ścieżkę można użyć:




Yii::getAlias('@webroot/') . Yii::$app->params['uploadPath'];

lub

Yii::getAlias('@web/') . Yii::$app->params['uploadPath'];



w zależności czy będziesz plik wgrywał czy wyświetlał.

Co do twojego kontrolera to nadal coś jest nie tak, ta linijka


return $this->redirect(['view', 'id' => $model->id]);

sprawia że po wczytaniu danych do modelu, validacji i zapisaniu zostajesz przekierowany na stronę "view", czyli nie dochodzisz do zapisywania pliku. Tak jak pisałem wcześniej jeżeli zezwalasz na niewgrywanie zdjęcia to musisz sprawdzać czy:


$image !== null

jeżeli tego nie zrobisz to w przypadku braku zdjęcia otrzymasz błąd.

Dzięki za cierpliwość i za chęci :)

dodane

dodane

Ponieważ będę plik wgrywał korzystam z

rzeczywiście, nie zauważyłem, że tej linijki nie powinno być w tym miejscu.

Tutaj też oczywiście masz rację, co więcej nawet jak wybiorę zdjęcie (którego nazwa zapisze się w bazie) i tak dostaję błąd: Trying to get property of non-object

Jeśli jednak dodam warunek $image !== null po kliknięciu w przycisk "Create" pojawia mi się tylko biała pusta strona, rekord nie zostaje zapisany w bazie.

W efekcie ani razu nie udało mi się zapisać pliku w katalogu uploads.

Cały czas korzystam z tego opisu: http://webtips.krajee.com/upload-file-yii-2-using-fileinput-widget/

Poniżej zmieniona akcja Create kontrolera:


    public function actionCreate()

    {

        $model = new Person();


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

            

            // get the uploaded file instance. for multiple file uploads

            // the following data will return an array

            $image = UploadedFile::getInstance($model, 'image');

 

            if ($image !== null) {


                // store the source file name

                $model->filename = $image->name;

                $ext = end((explode(".", $image->name)));


                // generate a unique file name

                $model->avatar = Yii::$app->security->generateRandomString().".{$ext}";


                // the path to save file, you can set an uploadPath

                // in Yii::$app->params (as used in example below)

                $path = Yii::getAlias('@webroot/') . Yii::$app->params['uploadPath'] . $model->avatar;

                

                if($model->save()){

                    $image->saveAs($path);

                    return $this->redirect(['view', 'id'=>$model->_id]);

                } else {

                    // error in saving model

                }

            }

            

        } else {

            return $this->render('create', [

                'model' => $model,

            ]);

        }

    }

W widoku masz dwa razy wywołanie widgetu, zmień:





//echo $form->field($model, 'filename'); 

echo $form->field($model, 'filename')->widget(FileInput::classname(), [

    'options' => ['accept' => 'image/*'],

]);

// your fileinput widget for single file upload

$form->field($model, 'image')->widget(FileInput::classname(), [

    'options'=>['accept'=>'image/*'],

    'pluginOptions'=>['allowedFileExtensions'=>['jpg','gif','png']

]]);


na




echo $form->field($model, 'filename'); 


// your fileinput widget for single file upload

$form->field($model, 'image')->widget(FileInput::classname(), [

    'options'=>['accept'=>'image/*'],

    'pluginOptions'=>['allowedFileExtensions'=>['jpg','gif','png']

]]);



zobacz czy w klasie masz:


use yii\web\UploadedFile;

kolejna sprawa to twoja akcja w kontrolerze, biała strona się wyświetla gdyż nic nie robisz jeżeli nie wybrano zdjęcia, czyli warunek $image !== null false. Idąc dalej zmień:




$ext = end((explode(".", $image->name)));


na


$ext = end(explode(".", $image->name));



a najlepiej:





public function actionCreate()

    {

        $model = new Person();


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


            // get the uploaded file instance. for multiple file uploads

            // the following data will return an array

            $image = UploadedFile::getInstance($model, 'image');


            if ($image !== null) {


                // store the source file name

                $model->filename = $image->name;

                $ext = end(explode(".", $image->name));

                

                // alternatywa dla powyższego

                $ext = $image->extension;


                // generate a unique file name

                $model->avatar = Yii::$app->security->generateRandomString().".{$ext}";


                // the path to save file, you can set an uploadPath

                // in Yii::$app->params (as used in example below)

                $path = Yii::getAlias('@webroot/') . Yii::$app->params['uploadPath'] . $model->avatar;

            }

            

            if($model->save()){

                

                ($image !== null) ? $image->saveAs($path) : '';

                

                return $this->redirect(['view', 'id'=>$model->_id]);

            } else {

                // error in saving model

            }


        } else {

            return $this->render('create', [

                'model' => $model,

            ]);

        }

    }



to tak na szybko. Zobacz czy Ci coś konsola zwraca.

Wyśiwetla się view, nazwa pliku dodaje się do bazy, ale plik nie jest zapisywany w katalogu uploads.

Widok (_form):


<div class="person-form">


    

    <?php $form = ActiveForm::begin(); ?>


    <?= $form->field($model, 'filename')->widget(FileInput::classname(), [

    'options'=>['accept'=>'image/*'],

    'pluginOptions'=>['allowedFileExtensions'=>['jpg','gif','png']

    ]]); ?>




    <?= $form->field($model, 'name')->textInput(['maxlength' => 128]) ?>


    <div class="form-group">

        <?= Html::submitButton($model->isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>

    </div>


    <?php ActiveForm::end(); ?>


</div>

Kontroler (akcja Create), ,iałem dodane use yii\web\UploadedFile


public function actionCreate()

    {

        $model = new Person();


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


            // get the uploaded file instance. for multiple file uploads

            // the following data will return an array

            $image = UploadedFile::getInstance($model, 'image');


            if ($image !== null) {


                // store the source file name

                $model->filename = $image->name;

                $ext = end(explode(".", $image->name));

                

                // alternatywa dla powyższego

                $ext = $image->extension;


                // generate a unique file name

                $model->avatar = Yii::$app->security->generateRandomString().".{$ext}";


                // the path to save file, you can set an uploadPath

                // in Yii::$app->params (as used in example below)

                $path = Yii::getAlias('@webroot/') . Yii::$app->params['uploadPath'] . $model->avatar;

            }

            

            if($model->save()){

                

                ($image !== null) ? $image->saveAs($path) : '';

                

                return $this->redirect(['view', 'id'=>$model->id]);

            } else {

                // error in saving model

            }


        } else {

            return $this->render('create', [

                'model' => $model,

            ]);

        }

    }

Wydaje mi się, że problem jest po stronie $path, brak / jeżeli w params masz samo uploads

to zmień:




[size=2]$path = Yii::getAlias('@webroot/') . Yii::$app->params['uploadPath'] . $model->avatar;[/size]



na to:




$path = Yii::getAlias('@webroot/') . Yii::$app->params['uploadPath'] . '/' . $model->avatar;



Poniżej zamieszczam kod, który u mnie działa

models\Person





<?php


namespace frontend\models;


use Yii;


/**

 * This is the model class for table "Person".

 *

 * @property integer $id

 * @property string $name

 * @property string $avatar

 * @property string $filename

 */

class Person extends \yii\db\ActiveRecord

{

    public $image;

    /**

 	* @inheritdoc

 	*/

    public static function tableName()

    {

        return 'Person';

    }


    /**

 	* @inheritdoc

 	*/

    public function rules()

    {

        return [

            ['image', 'safe'],

            [['image'], 'file', 'extensions'=>'jpg, gif, png'],

            [['name', 'avatar', 'filename'], 'string']

        ];

    }


    /**

 	* @inheritdoc

 	*/

    public function attributeLabels()

    {

        return [

            'id' => 'ID',

            'name' => 'Name',

            'avatar' => 'Avatar',

            'filename' => 'Filename',

        ];

    }

}




controllers\PersonController





  public function actionCreate()

    {

        $model = new Person();


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


            // pobranie instancji

            $image = UploadedFile::getInstance($model, 'image');


            // jeżeli plik został wybrany

            if ($image !== null) {


                $model->filename = $image->name;


                // wygenerowanie unikatowej nazwy

                $model->avatar = Yii::$app->security->generateRandomString().".{$image->extension}";


                // utworzenie ścieżki

                $path = Yii::getAlias('@webroot/') . Yii::$app->params['uploadPath'] . '/' . $model->avatar;

            }


            // walidacja i zapis do bazy

            if ($model->save()) {

                // jeżeli wybrano plik zapis pliku

                ($image !== null) ? $image->saveAs($path) : '';


                return $this->redirect(['view', 'id' => $model->id]);

            } else {


            }

        } else {

            return $this->render('create', [

                'model' => $model,

            ]);

        }

    }



views\person\_form





<?php


use yii\helpers\Html;

use kartik\widgets\ActiveForm;

use kartik\widgets\FileInput;

?>


<div class="person-form">


    <?php $form = ActiveForm::begin(['options'=>['enctype'=>'multipart/form-data']]); ?>


    <?= $form->field($model, 'name')->textInput() ?>


    <?= $form->field($model, 'filename') ?>


    <?= $form->field($model, 'image')->widget(FileInput::className(), [

        'options'=>['accept'=>'image/*'],

        'pluginOptions'=>['allowedFileExtensions'=>['jpg','gif','png']]

    ]) ?>


    <div class="form-group">

        <?= Html::submitButton($model->isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>

    </div>


    <?php ActiveForm::end(); ?>


</div>




to tyle

Dzięki za przesłany kod. Podmieniłem go we wszystkich 3 plikach. Musiałem dodać pole avatar w tabeli, żeby było identycznie i otrzymuję błąd:

Undefined index: uploadPath

Parametr dodałem wg jednego z poprzednich postów, czyli we frontend/config/params.php mam coś takiego:


<?php

return [

    'adminEmail' => 'admin@example.com',

    'uploadPath' => 'uploads',

];

Jeżeli wkleiłeś mój kod to powinien działać, ja go testowałem. Z błędu wynika, że system nie może znaleźć tego parametru, więc albo masz gdzieś literówkę albo twój kontroler nie jest w frontend tylko backend.

I w tym właśnie był problem. Teraz działa. Dzięki wielkie za pomoc.

Wszystko to robiłem w backend - czyli dodawanie treści, załączników do bazy. Myślałem, że będzie lepiej z punktu bezpieczeństwa aplikacji jeśli katalog do którego uzytkownicy będą zapisywać pliki (a później pobierać) umieszczę we frontend. Nie pomyślałem, że może nie być dostępu do tego katalogu z części backend.

Musisz pamiętać, że pliki parametrów są osobne dla frontend i backend, jeżeli jakiś parametr ma być dostępne w oby tych obszarach to należy go zdefiniować w common/config/params…

Należy również mieć świadomość jak są one wczytywane, zobacz sekcja "Configuration and environments"

Ok. Dziękuję za pomoc. Pozdrawiam.