ViewModel concept in Yii?

In ASP.NET MVC there is such thing like ViewModel.
It works like this: To view we could bound only one model and this model called ViewModel. Access to data entities goes through this model.

Why it is good?

  1. It helps with separation of concerns.
    Whan application growing it starts happening a lot in view or in controller.
  2. IDE friendly.

Why it is bad?

  1. We need to create and maintain additional class
  2. In ViewModel should be only model related code, needed for view, not models itself. Unexperienced user can put general code for model processing in ViewModel.

I suppose this pattern could exist in the form of concept, for example, this approach cloud be described in manual and ViewModel cloud be optionally generated by Gii

it’s not the same like a ModelForm?

https://www.yiiframework.com/doc/guide/2.0/en/input-forms

Why not write an own generator for gii?

It is form model.

This is a render method from one of my projects. Only $model is bound to form.

        $result = $this->render($view, [
                'model' => $model,
                'source' => $source,
                'searchType' => $searchType,
                'dataProvider' => $dataProvider,
                'soundex' => $soundex,
                'similarDataProvider' => $similarDataProvider,
                'messages' => $messages,
                'allpersonsResult' => $allpersonsResult
            ]
        );

What bad it leads to a mess in a view file.
Of course we could say it is a bad design, but you start from 1 model, and keep adding functionaly during years and it grows to a monster. Symfony for example force you to use EntityRepositories to avoid such problem.

The real problem is, in my opinion, that there is no place for code for advanced application screen. We could have few forms (interconnected or not) plus output area with customisation features and advanced hierarchical structure. And this concept does not fit Form Model because it not about form it about View in general.

This is application area. If you don’t like to pass multiple variables to view, you can create entity to group them and passs it to view:

        $result = $this->render($view, new MyViewEntity([
                'model' => $model,
                'source' => $source,
                'searchType' => $searchType,
                'dataProvider' => $dataProvider,
                'soundex' => $soundex,
                'similarDataProvider' => $similarDataProvider,
                'messages' => $messages,
                'allpersonsResult' => $allpersonsResult
         ]));

And then use $data->model in view. You don’t need any special tools from framework for that.

2 Likes

Yes I can and I do, but this a point with framework - to provide solutions and learn good practices.

Moreover I believe you did not catch idea completely, the idea is not to group models

View model, additionally to holding data, contains view logic such as formatting values or checking for permissions to render menu item. That is entirely possible with current Yii 1, Yii 2 or Yii 3.

The missing part is, indeed, docs that this is possible and why/when it’s a good idea.

@olegl84 would be great to get a wiki article for 2.0 explaining the concept. We could probably make it part of the 3.0 extra guide if we’ll decide to do something like “patterns book”.

Created an issue not to forget about it:

Good, I will contribute about View Model.

1 Like
2 Likes

I’m using the following approach on my current project (suggestions appreciated).

// Interface definition
interface ViewModel
{
    /**
     * Must return a array of params suitable to the view rendering
     */
    public function params(): array;
}

// Example of concrete implementation:
class AccountViewModel implements ViewModel
{
    private $account;

    public function __construct(Account $account)
    {
        $this->account = $account;
    }

    public function params(): array
    {
        return [
            'model' => $this->model,
            'banks' => $this->getBanks(),
            'user' => Yii::$app->user->identity,
            ...
        ];
    }

    public function getBanks(): array
    {
        // Some logic to return the bank list
    }
}

// Example in controller:
class AccountController extends Controller
{
    ...
    public function actionCreate()
    {
        $model = new Account();
        ...
        return $this->render('form', (new AccountViewModel($model))->params());
    }
    
    public function actionUpdate(int $id)
    {
        $model = $this->findModel($id);
        ...
        return $this->render('form', (new AccountViewModel($model))->params());
    }
}

I don’t think an interface is necessary. Views are view-model specific so there’s no need for a common way of data retrieval.

The rest is okay.


You can use this package for this. It is tested and production ready

It would really be nice if the gii model generator would create the models in a /models/generated folder with a name like PageGenerated.php and, only if the PageGenerated.php does not already exist, create a Page.php in /models/ that overrides PageGenerated.php. That way it would be easy to override functions in PageGenerated.php, without having to worry about a rerun of gii wiping out any manually applied modifications.