ViewModels
A Yuga ViewModel is a special class that represents the data model used in a specific view.
What is a ViewModel?, What makes it different from a controller?
In a yuga
application, every view has a code behind file
which in this case is our view model, View models
can work as controllers
but the main difference lies in how they couple with views, a controller
can return any view and it doesn't interact with the view itself while a view model
interacts directly with the view and returns only that view. In fact, you don't tell it the view it has to return, It already knows it.
A view model in a yuga application binds a form to a real database model
such that the developer doesn't need to do the mapping of form fields to a model them selves.
It has automatic validation
of form fields. This can turn out to be a time saver, if custom validation is needed, a validate
method is provided to a model to which the view model is bound and the view model will run that method instead of the default.
While you can do pretty much everything from within a controller, a view model simplifies your work by taking away tasks like
validation
andform-model
binding.
Basic ViewModels
Defining ViewModels
Below is an example of a basic ViewModel class. Note that the ViewModel extends the base ViewModel class that comes with Yuga. The base class provides a few convenience methods such as the onPost, onLoad, onGet
methods, which can be used whenever those events occur.
namespace App\ViewModels;
class UserViewModel extends App
{
/**
* Create a new UserViewModel ViewModel instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Handle any form data that has been submited
*/
public function onPost($model)
{
}
/**
* Load or / manupulate data when its a get request
*/
public function onGet()
{
}
/**
* Load or / manupulate data before the page loads and feed it to the page
*/
public function onLoad()
{
}
}
What's in the App class that the above class is extending? Let's find out
namespace App\ViewModels;
use Yuga\View\ViewModel;
use Yuga\Views\Widgets\Menu\Menu;
abstract class App extends ViewModel
{
protected $applicationMenu;
public function __construct()
{
parent::__construct();
$this->name = 'Yuga Framework';
$this->getSite()->setTitle('Welcom to ' . $this->name)
->addCss(assets('yuga/bootstrap/css/bootstrap.min.css'))
->addCss(assets('yuga/css/yuga.css'))
->addJs(assets('yuga/js/jQuery/jquery-2.2.3.min.js'))
->addJs(assets('yuga/bootstrap/js/bootstrap.min.js'))
->addJs(assets('yuga/js/yuga.client.js'));
$this->makeMenu();
}
protected function makeMenu()
{
$this->applicationMenu = new Menu;
$this->applicationMenu->addClass('nav navbar-nav');
if (\Auth::authRoutesExist()) {
if (\Auth::guest()) {
$this->applicationMenu->addItem('Login', route('login'))->addClass('nav-item')->addLinkAttribute('class', 'nav-link');
$this->applicationMenu->addItem('Register', route('register'))->addClass('nav-item')->addLinkAttribute('class', 'nav-link');
} else {
$this->applicationMenu->addItem('Logout', route('/logout'))->addClass('nav-item')->addLinkAttribute('class', 'nav-link');
}
}
}
protected function printMenu()
{
return $this->applicationMenu;
}
}
The route that corresponds to this UserViewModel
is as below:
Route::get('add-users', App\ViewModels\UserViewModel::class);
Constructor Injection
The Yuga service container is used to resolve all Yuga ViewModels. As a result, you are able to type-hint any dependencies your ViewModel may need in its constructor. The declared dependencies will automatically be resolved and injected into the ViewModel instance:
namespace App\ViewModels;
use App\Models\User;
class UserViewModel extends App
{
/**
* The user model instance.
*/
protected $user;
/**
* Create a new UserViewModel ViewModel instance.
*
* @param User $user
* @return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
}
Creating ViewModels using yuga console command
ViewModels can be created using the php yuga make:viewmodel
command
i.e php yuga make:viewmodel UserViewModel
would produce the following scaffold:
namespace App\ViewModels;
class UserViewModel extends App
{
/**
* Create a new UserViewModel ViewModel instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Handle any form data that has been submited
*/
public function onPost()
{
}
/**
* Load or / manupulate data when its a get request
*/
public function onGet()
{
}
/**
* Load or / manupulate data before the page loads and feed it to the page
*/
public function onLoad()
{
}
}
Model binding to the ViewModel
Think of this as an easier way of mapping every form value to an appropriate Object attribute or property. In yuga
, this works like magic.
When a form is submitted, the ViewModel
looks for the bound Model from the scope and maps every form field to a property on that Model
and finally tries to run a validator
to every field on the form to make sure that every form field is not empty for starters.
The default bound model is \Yuga\Models\ElegantModel::class
But of course the table that is bound to this model is elegant_models
which basically doesn't make any sense for every form, so how do we bind a model to a form, Well, there're two ways of doing this,
You may skip binding to the form yourself and instead set a table to be bound to the
ElegantModel
class, this is done as below:$this->setTable("my_table");
inside of the view-model's constructor.Or you can bind any other model you would like to use instead of
ElegantModel
as below:
<?php
namespace App\ViewModels;
class UserViewModel extends App
{
/**
* Create a new UserViewModel ViewModel instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Handle any form data that has been submited
*/
public function onPost()
{
}
/**
* Load or / manupulate data when its a get request
*/
public function onGet()
{
}
/**
* Load or / manupulate data before the page loads and feed it to the page
*/
public function onLoad()
{
// this will change the bound model to what is defined
$this->setModel(['form' => new App\Models\User]);
}
}
Basic Structure
When a form is submitted and it is a post request method, the onPost
method is run and so this is where your code for form manipulation should reside.
Example of a view (My.php
)
<div class="row">
<div class="col-md-6 col-md-offset-3 main-users-form-border">
<div class="panel panel-default">
<div class="panel-heading">Users</div>
<div class="panel-body">
<?=$this->form()->start('user', 'post')->addClass('yuga-form'); ?>
<?=$this->showSuccessMessage()?>
<?=$this->validatedField('first_name')?>
<?=$this->validatedField('last_name')?>
<div class="form-group">
<div class="col-md-8 col-md-offset-4">
<?=$this->form()->button('Save', 'submit')->addClass('btn btn-primary'); ?>
</div>
</div>
<div class="clearfix"></div><br />
<?=$this->showErrors()?>
<?=$this->form()->end() ?>
</div>
</div>
</div>
</div>
The code behind to the above view is below: (MyViewModel.php
)
<?php
namespace App\ViewModels;
use Yuga\Models\ElegantModel;
class MyViewModel extends App
{
/**
* Create a new MyViewModel ViewModel instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Handle any form data that has been submited
*/
public function onPost($model)
{
}
/**
* Load or / manupulate data when its a get request
*/
public function onGet()
{
}
/**
* Load or / manupulate data before the page loads and feed it to the page
*/
public function onLoad()
{
}
}
The route corresponding to the above ViewModel
could be any route but Let’s say that it's:
Route::all("/my-view-model", App\ViewModels\MyViewModel::class);
The model
parameter in the onPost
method in the MyViewModel
ViewModel
, is the bound model to the form inside the My.php
html file. It can only be an instance of Yuga\Database\Elegant\Model
for it to work well with the ViewModel
.
By default, the bound model is Yuga\Models\ElegantModel
/**
* Handle any form data that has been submited
*/
public function onPost($model)
{
$model->save();
}
Like the code above, you just have to call the save method to the model since it’s an instance of Yuga\Database\Elegant\Model
, When the save method is called, It will try to insert
or update
the database table depending on the bound model
Form Validation
Forms in view-models are validated automatically for required field validations. This means, if a form is submitted to the server, the framework will look for the form, validate it (making sure every field has a value basically) then bind it to the appropriate model, then provide it as an Argument to the onPost
method as below:
/**
* Handle any form data that has been submited
* This $model argument is a valid Database Model
*/
public function onPost($model)
{
}
You can just call the save
method on the $model
variable since $model
is already a valid Elegant Model.
Sometimes you want to customize how the validation is done, well, yuga has your back already, you just have to do the following in your model
<?php
namespace App\Models;
use Yuga\Database\Elegant\Model as Elegant;
class User extends Elegant
{
/**
* Use this method to run a customized validation on a model bound to a view-model's form
*/
public function validate(): ?array
{
return [
'email' => 'required|email|unique' // blah blah
];
}
}
If the above model is the one bound to the form, yuga will run its validate
method instead of the default. Be warned though, when you run a custom validation, you will have to do it for every form field not just on the one field you want to customize.
Last updated
Was this helpful?