trejder
trejder

Reputation: 17505

REST controller based on yii\rest\ActiveController does not accept any input data when trying to modify any record

tl;dr My Yii 2 app responds correctly to REST requests for fetching data (i.e. GET, but fails completely when I am trying to create some record (i.e. use POST HTTP verb or similar). What can be wrong?


I have created a blank Yii 2 project based on Yii 2 Advanced Project Template:

composer create-project --prefer-dist yiisoft/yii2-app-advanced yii-application
php init
php yii migrate

I have created a REST UserController for already existing User model:

namespace app\controllers;
use yii\rest\ActiveController;

class UserController extends ActiveController
{
    public $modelClass = 'app\models\User';
}

I have configured URL rules:

'urlManager' => [
    'enablePrettyUrl' => true,
    'enableStrictParsing' => true,
    'showScriptName' => false,
    'rules' => [
        ['class' => 'yii\rest\UrlRule', 'controller' => 'user'],
    ],
]

I have also enabled JSON input, if that matters:

'request' => [
    'parsers' => [
        'application/json' => 'yii\web\JsonParser',
    ]
]

And my application is working only for reading data, meaning that endpoints / requests like GET /users or GET /users/12 are processed correctly.

But, when I try to create a new post for the first time using JSON:

curl -i -H "Accept:application/json" -H "Content-Type:application/json" \
    -XPOST "http://localhost/users" \
    -d '{"username": "example", "email": "[email protected]"}'

I can clearly see that my request's JSON ({"username": "example", "email": "[email protected]"}) gets totally ignored and user is created with empty username and email fields.

And when I try to invoke the above curl code for the second or following time, I am getting Integrity constraint violation exception with error message:

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '' for key 'username'\nThe SQL being executed was: INSERT INTO user (status, created_at, updated_at) VALUES (9, 1654291700, 1654291700)

Which also clearly proves that my input JSON is ignored.

This is by no means related to JSON input. I have tried to create a record using URL-encoded data to no avail:

enter image description here

What am I missing?

Upvotes: 0

Views: 321

Answers (1)

Michal Hynčica
Michal Hynčica

Reputation: 6179

The issue in this case is not in parsing JSON body but rather in loading data into model.

The yii\rest\CreateAction uses Model::load() method to load data into attributes. But load() method only loads data into attributes that are considered "safe".

As "safe" are considered attributes that are returned in attributes list for current scenario by Model::scenarios() method. As stated in documentation for scenarios() method:

The default implementation of this method will return all scenarios found in the rules() declaration.

So, if there are no validation rules in model, no attribute will be considered safe. Therefore, no attribute will be loaded.

There are two options how to make model load your data. You can override scenarios() method to return list of your attributes for scenario default.

public function scenarios(): array
{
    return [
        \yii\base\Model::SCENARIO_DEFAULT => ['your', 'attributes', ...],
        //or you can also use attributes() method here
        //\yii\base\Model::SCENARIO_DEFAULT => $this->attributes(),
    ];
}

Or, you can set proper validation rules in rules() method. Because you are dealing with user inputs, setting proper validation rules is probably better idea.

Upvotes: 1

Related Questions