M
M
Maxim Weber2019-04-06 11:53:09
Yii
Maxim Weber, 2019-04-06 11:53:09

How to split an array of fields in Model::fields() in yii2 for different cases?

Problem
Suppose there is a REST service represented by two resources: a list of records, information about the record. It is necessary that the list of entries contain only the id and title fields, and the information about the entry: id, title, text, image, author.
How can I solve my problem in the most beautiful way? What do you advise?
How I'm trying to solve this (you don't have to read much below)
I know that I can use the fields get-parameter in the url and list the fields that interest me in it. But I do not like this approach because the url is not as concise as I would like.
Yii2 models have fields() which is used by the serializer in the controller and determines the list of fields to be returned to the client. To separate the list of fields for different usage scenarios, I decided to use the scenario field of the model. This works great exactly until the moment when I use ActiveDataProvider () to which I cannot transfer this script in any way.
In code it looks something like this:

class Post 
{
    const SCENARIO_INDEX = 'index';
    const SCENARIO_VIEW = 'view';

    public function fields()
    {
        $fields = parent::fields();

        if ($this->scenario === self::SCENARIO_INDEX) {
            $fields = ['id', 'title'];
        }
        if ($this->scenario === self::SCENARIO_VIEW) {
            $fields = ['id', 'title', 'text', 'image', 'author'];
        }

        return $fields;
    }
}

class PostController extends \yii\rest\Controller
{
    public function actionIndex()
    {
        return new ActiveDataProvider([
            'query' => Post::find(),
            // 'scenario' => Post::SCENARIO_INDEX, // Это так не работает :(
        ]);
    }
    
    public function actionView($id)
    {
        $post = Post::findOne((int) $id);
        if (!$post) {
            throw new NotFoundHttpException();
        }
        $post->scenario = Post::SCENARIO_VIEW;

        return $post;
    }
}

Three solutions came to my mind:
1. Own implementation of ActiveDataProvider with scripting support.
2. Do not use scripts. Create Post descendants (for example, PostForIndex, PostForView) in which to redefine fields() according to my needs.
3. Do not use scripts. Set get parameters in the controller:
\Yii::$app->request->setQueryParams(['fields' => 'id, title']);

Answer the question

In order to leave comments, you need to log in

2 answer(s)
S
Sergei Iamskoi, 2019-04-06
@syamskoy

The script can be set, but it looks like this:

$activeDataProvider = new ActiveDataProvider([
            'query' => Post::find(),
            // 'scenario' => Post::SCENARIO_INDEX, // Это так не работает :(
        ]);
foreach($activeDataProvider->getModels() as $model) {
    $model->scenario = Post::SCENARIO_GUEST;
}

If the return of the required fields is not a part of security, but simply a convenience, then it is still better to request the necessary ones through the url. If this is access sharing, then instead of scripts, you can use RBAC rules to return the required fields. If it too does not approach - that already inheritance and redefinition. But there is one more option, though it is not related to scripts in any way: you can specify the necessary fields in the select: Post::find()->select(['id']).

M
Maxim Timofeev, 2019-04-08
@webinar

1. I would make 2 models, not scenarios. If there are common methods, you can have a common ancestor. Accordingly, there will be two controllers, each with its own model and there will be no confusion.
2. You can override prepareDataProvider:
https://www.yiiframework.com/doc/api/2.0/yii-rest-...

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question