phemellc / yii2-settings

Yii2 Settings Module
Other
151 stars 74 forks source link

Exception on getting empty value which was saved as an object in db #50

Closed demogorgorn closed 8 years ago

demogorgorn commented 8 years ago

Hello! I'm getting an exception while trying to get an empty value which was saved as an object in db (field "type")

web.php:

'modules' => [
        'settings' => [
          'class' => 'pheme\settings\Module',
          'sourceLanguage' => 'ru'
        ],
    ],
'settings' => [
      'class' => 'pheme\settings\components\Settings'
],

controller:

          'social-service' => [
                'class' => 'pheme\settings\SettingsAction',
                'modelClass' => 'app\models\SocialService',
                'viewName' => 'social-service'   // The form we need to render
            ],

view:


<?php

use yii\helpers\Html;
use yii\widgets\ActiveForm;
use yii\helpers\Url;

/* @var $this yii\web\View */
/* @var $model app\models\Page */
/* @var $form yii\widgets\ActiveForm */

?>

<?php $form = ActiveForm::begin(['id' => 'social-service-form']); ?>
<?= $form->field($model, 'twitter')->textInput([
                                 'type' => 'text'
                            ])  ?>
<?= $form->field($model, 'google')->textInput([
                                 'type' => 'text'
                            ])  ?>
<?= $form->field($model, 'facebook')->textInput([
                                 'type' => 'text'
                            ])  ?>
<?= $form->field($model, 'youtube')->textInput([
                                 'type' => 'text'
                            ])  ?>
<?= $form->field($model, 'linkedin')->textInput([
                                 'type' => 'text'
                            ])  ?>
<?= $form->field($model, 'pinterest')->textInput([
                                 'type' => 'text'
                            ])  ?>
<?= $form->field($model, 'vimeo')->textInput()  ?>
<?= $form->field($model, 'instagram')->textInput([
                                 'type' => 'text'
                            ])  ?>
<?= $form->field($model, 'vk')->textInput([
                                 'type' => 'text'
                            ])  ?>
<?= $form->field($model, 'ok')->textInput([
                                 'type' => 'text'
                            ])  ?>

<div class="form-group">
        <?= Html::submitButton('Save', ['class' => 'btn btn-success']) ?>
    </div>

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

model:

class SocialService extends Model 
{

    public $twitter;
    public $google;
    public $facebook;
    public $youtube;
    public $linkedin;
    public $pinterest;
    public $vimeo;
    public $instagram;
    public $vk;
    public $ok;

    public function rules()
    {
        return [
            [['twitter','google','facebook','youtube','linkedin','pinterest','vimeo','instagram','vk','ok'], 'string'],
        ];
    }

    public function fields()
    {
            return ['twitter','google','facebook','youtube','linkedin','pinterest','vimeo','instagram','vk','ok'];
    }

    public function attributes()
    {
            return ['twitter','google','facebook','youtube','linkedin','pinterest','vimeo','instagram','vk','ok'];
    }
}

When I'm trying to save empty fields (e.g. user don't want to fill some of them), I'm getting following error:


 PHP Recoverable Error – yii\base\ErrorException
Object of class stdClass could not be converted to string

in D:\OpenServer\domains\localhost\vendor\yiisoft\yii2\helpers\BaseHtml.php

     * See [[renderTagAttributes()]] for details on how attributes are being rendered.
     * @return string the generated input tag
     */
    public static function input($type, $name = null, $value = null, $options = [])
    {
        if (!isset($options['type'])) {
            $options['type'] = $type;
        }
        $options['name'] = $name;
                                                               $options['value'] = $value === null ? null : (string) $value;
        return static::tag('input', '', $options);

as I've noticed it's all because of:

BaseSetting.php

 public function setSetting($section, $key, $value, $type = null)
    {
        $model = static::findOne(['section' => $section, 'key' => $key]);

        if ($model === null) {
            $model = new static();
            $model->active = 1;
        }
        $model->section = $section;
        $model->key = $key;
        $model->value = strval($value);

        if ($type !== null) {
            $model->type = $type;
        } else {
            $t = gettype($value);
            if ($t == 'string') {
                $error = false;
                try {
                    Json::decode($value);
                } catch (InvalidParamException $e) {
                    $error = true;
                }
                /*if (!$error) {
                    $t = 'object';
                }*/
            }
            $model->type = $t;
        }

        return $model->save();
    }

So, the question is how to save this form correctly?

arisk commented 8 years ago

Hi. Your model class is not extending from BaseSetting so I don't see how that could be the problem. Are you actually using BaseSetting anywhere? Also could you show your controller code? By the way since you're not using the model or view, it's better not to use the module at all and implement your own logic for saving the data. There's nothing that's stopping you from using the component without the module.

demogorgorn commented 8 years ago

It's using BaseSetting:

In controller (posted above) I'm using SettingAction. SettingAction uses settings component (line 39 of SettingsAction). Setting component actively uses variable $modelClass which equals to "pheme\settings\models\BaseSetting" (line 23 of Settings.php component).

I've posted the controller code already but will post again:

class DefaultController extends Controller
{
    public $layout='god';

    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
/*                'only' => ['logout'],*/
                'rules' => [
                    [
                        'actions' => ['index','create','view','update','delete','logout','fileman','social-service'],
                        'allow' => true,
                        'roles' => ['@'],
                    ],
                    [
                        'actions' => ['login'],
                        'allow' => true,
                        'roles' => ['?'],
                    ],
                ],
            ],
            'verbs' => [
                'class' => VerbFilter::className(),
                'actions' => [
                    'delete' => ['post'],
                ],
            ],
        ];
    }

    public function actions()
    {
        return [
            'error' => [
                'class' => 'yii\web\ErrorAction',
            ],
            'social-service' => [
                'class' => 'pheme\settings\SettingsAction',
                'modelClass' => 'app\models\SocialService',
                //'scenario' => 'site', // Change if you want to re-use the model for multiple setting form.
                'viewName' => 'social-service'   // The form we need to render
            ],
            'captcha' => [
                'class' => 'yii\captcha\CaptchaAction',
                'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
            ],
        ];
    }

    /**
     * Lists all Page models.
     * @return mixed
     */
    public function actionIndex()
    {
        $dataProvider = new ActiveDataProvider([
            'query' => Special::find(),
        ]);

        return $this->render('index', [
            'dataProvider' => $dataProvider,
        ]);
    }

    public function actionFileman()
    {
        $this->layout = 'god-fw';
        return $this->render('fileman');
    }

    /**
     * Displays a single Page model.
     * @param integer $id
     * @return mixed
     */
    public function actionView($id)
    {
        return $this->render('view', [
            'model' => $this->findModel($id),
        ]);
    }

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

        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->redirect(['index']);
        } else {
            return $this->render('create', [
                'model' => $model,
            ]);
        }
    }

    /**
     * Updates an existing Page model.
     * If update is successful, the browser will be redirected to the 'view' page.
     * @param integer $id
     * @return mixed
     */
    public function actionUpdate($id)
    {
        $model = $this->findModel($id);

        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->redirect(['index']);
        } else {
            return $this->render('update', [
                'model' => $model,
            ]);
        }
    }

    /**
     * Deletes an existing Page model.
     * If deletion is successful, the browser will be redirected to the 'index' page.
     * @param integer $id
     * @return mixed
     */
    public function actionDelete($id)
    {
        $this->findModel($id)->delete();

        return $this->redirect(['index']);
    }

    /**
     * Finds the Page model based on its primary key value.
     * If the model is not found, a 404 HTTP exception will be thrown.
     * @param integer $id
     * @return Page the loaded model
     * @throws NotFoundHttpException if the model cannot be found
     */
    protected function findModel($id)
    {
        if (($model = Page::findOne($id)) !== null) {
            return $model;
        } else {
            throw new NotFoundHttpException('The requested page does not exist.');
        }
    }

    public function actionLogin()
    {
        if (!\Yii::$app->user->isGuest) {
            /*return $this->goHome();*/
            return $this->redirect(['index']);
        }

        $model = new LoginForm();
        if ($model->load(Yii::$app->request->post()) && $model->login()) {
            /*return $this->goBack();*/
            return $this->redirect(['index']);
        }
        return $this->render('login', [
            'model' => $model,
        ]);
    }

    public function actionLogout()
    {
        Yii::$app->user->logout();

        return $this->goHome();
    }
}

Thank you for helping!

demogorgorn commented 8 years ago

By the way since you're not using the model or view, it's better not to use the module at all and implement your own logic for saving the data.

But I'm using the model, and the view. In the first message I've posted the controller, the view, the model. As I've understand correctly.

I'm only trying to do as it's described in the readme. That's all! Thanks again

arisk commented 8 years ago

I just finished testing your code and I couldn't reproduce the problem. Basically it inserts an empty string setting in the database. There's something we're missing here.

screen shot 2016-05-18 at 3 31 06 pm
arisk commented 8 years ago

I think I found the problem. Hold on a few for a fix.

demogorgorn commented 8 years ago

Ok! Thank you! I'm also looking the trouble in code but except the BaseSetting.php setSetting method I don't know where to look

demogorgorn commented 8 years ago

BaseSetting.php

public function setSetting($section, $key, $value, $type = null)
    {
        ...

            $t = gettype($value);
            if ($t == 'string') {
                $error = false;
                try {
                    Json::decode($value);
                } catch (InvalidParamException $e) {
                    $error = true;
                }

     **!!! WHY THIS CHECK IS HERE?**
                if (!$error) {
                    $t = 'object';
                }
            }
            $model->type = $t;
        }

        ..
    }
``
arisk commented 8 years ago

Basically it's there for JSON objects. The problem was that settype was converting it to a PHP object. Hence the error on the input form. Please run composer update and try again. Your problem should be fixed now.

demogorgorn commented 8 years ago

Thank you very much! Trying to update now

demogorgorn commented 8 years ago

All is working! Thank you very much!

arisk commented 8 years ago

Glad I could help

schmunk42 commented 7 years ago

https://github.com/phemellc/yii2-settings/commit/396bfcd0e359647ffde2ae3f5185e01e5b33996c breaks object completely for me.

I am accessing (because there was no other way?) object values via scalar which is no longer available now.

$json = Yii::$app->settings->get('availablePhpClasses', 'widgets', []);
echo $json->scalar;

Any chance to make this BC?

schmunk42 commented 7 years ago

@demogorgorn Please check that https://github.com/phemellc/yii2-settings/issues/65 breaks nothing for you.