What is $model in Yii2? - model-view-controller

I'm new to MVC and Yii Framework. The $model variable seems very confusing to me.
Where is it declared in the fist place? Where does it come from?
When I work with GridView I see that some functions take $model as a parameter. Neither model nor model search of this GridView declares $model variable anywhere. Yet it is widely used in all sorts of data management. It just doesn't make sense to me.
So I need a simple, straight forward, "for dummies" explanation of $model variable in Yii Framework v2. Help in clarifying these questions is much appreciated:
1. What is the origin of $model variable?
2. How to identify what model of the app is the $model variable representing when it's used in view files?
3. There are sometimes multiple $model variables in a single view file. Do all of them represent one model class? How to distinguish them when used for multiple classes?
Thanks.

If you are unsure where $model, $searchModel and other variables come from, you are most likely searching for them on the view file where they are used.
They are created on the controller that renders said view, just like any other object/variable.
From the controller, you can render a view and pass objects/variables the following way:
// MyController.php
...
public function actionMyAction($id) {
// Create and manipulate $model and $searchModel
...
/*
* First param is the name of the view to be rendered
* Second param is an Associative Array with params
* that will be made available to the view.
*/
return $this->render('my-view-name', [
'model' => $model,
'searchModel' => $searchModel,
]);
}

The $model is a var as the others ...normally in the yii2 samples contain an instance of a model class (as tiplically an active record=)
assuming you have a class
class Category extends \yii\db\ActiveRecord
{
........
a tipical code could be
$model = Category::findOne($id);
where Category::findOne($id) find an instance form database using $id as primary key and assign the result to $model ..
then you can access to the instance attribute (eg:attribute1) using
$model->attribute1
you can take a look at this guide
http://www.yiiframework.com/doc-2.0/guide-index.html
http://www.yiiframework.com/doc-2.0/guide-structure-models.html
http://www.yiiframework.com/doc-2.0/guide-db-active-record.html

Related

Populating $attributes with values from the previous model?

I've got a Laravel project (actually a Laravel Nova project) that involves entering a lot of data. To save some time I'd like to pre-fill some of the fields in my form, based on the logged in user's last entry.
I can pre-fill fields via the $attributes variable on my model, called Product, like so:
protected $attributes = [
'category' => 'ABC'
];
And I can do this for more dynamic data in the constructor like so:
function __construct() {
$this->attributes['category'] = Str::random();
parent::__construct();
}
But I'm not quite sure how I'd go about this when I want to retrieve what the user entered last time. For example, I'd like to do this:
function __construct() {
$user = auth()->user()->id;
$last = Product::where('created_by', $user)->latest()->first();
$this->attributes['category'] = $last['category'] ?? null;
}
However that ends up in an infinite loop. Same if I call $this->where('created_by' ...
Is there a way I can set $attributes of a new Product based on the last Product created by the user?
Nova fields have resolveUsing method, so in your case if you want to populate Text field:
Text::make('Category')->resolveUsing(function () {
return optional(auth()->user()->products()->latest()->first())->category;
})
I found the solution in the Nova Defaultable package.
Once you add the necessary traits, you can just add ->defaultLast() to a resource field and it'll default to the last set value. This also works for relationships which is perfect for my use case.

Laravel calling model from controller? Is this the right approach?

I am trying to split some code and let model handle all database stuff, such as create, retrieve etc.
At the moment all of that code was in the controller and I quickly realized that code is very messy so instead I wanted to do something like this:
public function incompletedEntity(EntityRequestPartial $request)
{
$partial_entity = EntityTrash::saveEntity();
}
And my model:
public static function saveEntity(Request $request)
{
$entity = new EntityTrash();
$entity->lat = $request->input('lat');
$entity->lng = $request->input('lng');
$entity->slug = $request->input('name');
$user_id = Auth::id();
$entity->name = $request->input('name');
$entity->type = $request->input('type');
$entity->email = $request->input('email');
$entity->tags = $request->input('tags');
$entity->slug = $user_id;
$entity->building_name = $request->input('building_name');
$entity->address = $request->input('address');
$entity->town = $request->input('town');
$entity->postcode = $request->input('postcode');
$entity->telephone = $request->input('telephone');
$entity->save();
}
However, I cannot call that function because I am not passing an argument from the controller, what is the right approach for this? Should I do $request->input in controller and assign it to an array maybe? and deal with it in the controller?
If you wish to split out the code so that controllers don't touch models, I would recommend that you look into the repository pattern.
The idea behind this would be that in your controller you have
$this->entityRepository->create($data);
You could either pass in an array, or each individual property
$this->entityRepository->create($lat, $lng, $name, $type..etc);
This way, your controller can retrieve all of the data and validate it.
$data = $request->only(['lat', 'lng', 'name', 'type', 'email', 'tags', ..etc]);
$validator = Validator::make($data, ['name' => ['required']]);
Obviously you can use request validation or whatever you're comfortable with.
This way, your controller is responsible for receiving data and validating it. It can then pass it on blindly to the repository knowing that it is valid, and trusting that the repository will do its job.
As a side note, I highly recommend that you do not have your models interact with the Request object directly. The Illuminate\Http\Request class belongs to the HTTP layer, and the model belongs to the persistence layer. These two layers should never know of the existence of the other.
Generally speaking, your model should never depend on the Request object. Models can be used for any access of a database, or other storage area. Many of those access points may not be HTTP at all.
Your HTTP controllers should always be used to pass information from an HTTP request to a deeper layer of your application (e.g. a Model). So if you are using the Request object anywhere, it should be in your HTTP controllers.
I think it's important for you to review why the controller layer is there and why we don't just have Models and Views.
I would recommend you to take a look at the official Laravel Documentation first: https://laravel.com/docs and the Controller / Eloquent Model chapters. Moreover, Adam Wathans talk Github from the current Laracon is a good read for further refactoring.
First, make use of the CRUD pattern from the Laravel Controller in addition with the Eloquent Model. Then pass the validated data to your model via the create/save method.
<?php
namespace App\Http\Controllers;
use App\EntityTrash;
use App\Http\Controllers\Controller;
class EntityTrashController extends Controller
{
public function create(EntityRequestPartial $request)
{
return EntityTrash::create($request->only(['lat', 'lng', 'name', 'type', 'email', ...]));
}
}
Finally, maybe the repository pattern can improve your code as well.

Laravel, how cast object to new Eloquent Model?

I get via Request a Json Object.
I clearly parse this object in order to check if it may fit the destination model.
Instead of assigning property by property. Is there a quick way to populate the model with the incoming object?
If you have an array of arrays, then you can use the hydrate() method to cast it to a collection of the specified model:
$records = json_decode($apiResult, true);
SomeModel::hydrate($records);
If you just have a single record, then you can just pass that array to the model’s constructor:
$model = new SomeModel($record);
Just pass your object casted to array as Model constructor argument
$model = new Model((array) $object);
Internally this uses fill() method, so you may first need to add incoming attributes to $fillable property or first create model and then use forceFill().
You should convert that object to array and use fill($attributes) method.
As method name says, it will fill object with provided values. Keep in mind that it will not persist to database, You have to fire save() method after that.
Or if You want to fill and persist in one method - there is create($attributes) which runs fill($attributes) and save() under the hood.
You can use Mass Assignment feature of Laravel,
You model would look like this:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['name', 'email', 'phone'];
}
And the process of populating the Model would be like this:
// This would be your received json data converted to array
// use 'json_decode($json, true)' to convert json data to array
$json_arr = [
'name' => 'User Name',
'email' => 'email#example.com',
'phone' => '9999999999'
];
$user = new \App\User($json_arr);
Hope this helps!
Castings may fail due to several reasons. A safe way is to add a static function to the model to generate from both array or object. feels like an extension to the model.
public static function generateFromObject($object)
{
$myModel = new MyModel();
foreach($object as $k => $v)
$myModel->{$k} = $v; //for arrays $myModel[$k] = $v;
return $myModel;
}
and you can use anywhere like,
$myModel = MyModel::generateFromObject($myObjectOrArray)->save();

Can you create a new Model instance without saving it to the database

I want to create a whole bunch of instances of a model object in Laravel, then pick the optimal instance and save it to the database. I know that I can create an instance with Model::create([]), but that saves to the database. If possible I'd like to create a bunch of models, then only "create" the one that is best.
Is this possible?
I am using Laravel 5.0
You create a new model simply by instantiating it:
$model = new Model;
You can then save it to the database at a later stage:
$model->save();
You can create instances with Model::make(). It works the same way as create but it doesn't save it.
Whether or not this is best practice is another matter entirely.
Yes, it is possible different ways: you can use the mass assignment without saving.
Please remember to set first the $fillable property in your model.
WAY #1: using the method fill
$model = new YourModel;
$model->fill([
'field' => 'value',
'another_field' => 'another_value'
]);
$model->save();
WAY #2: using the constructor
$model = new YourModel([
'field' => 'value',
'another_field' => 'another_value'
]);
$model->save();
In YourModel set the $fillable property with the fileds allowed for mass assignment:
class YourModel extends Model
{
protected $fillable = ['field', 'another_field'];
// ...
}
Laravel documentation: https://laravel.com/docs/9.x/eloquent#mass-assignment
there is also a method you can call it statically to get new instance:
$modelInstance = $modelName::newModelInstance();
it takes array $attributes = [] as a parameter

Data getting lost while passing from controller to view in Laravel

In one of my controller, I'm passing the result obtained from an Eloquent query to a view. But no matter how I pass the result to the view, I'm not getting the result in the view.
The controller:
class ProductCategoriesController extends BaseController
{
public function __construct()
{
parent::__construct();
// Add location hinting for views
View::addNamespace('product-categories', app_path() . "/MyVendor/ProductsManager/Views/admin/product-categories");
}
public function index()
{
$categories = ProductCategory::all();
return View::make('product-categories::index')
->with('title', 'All Product Categories')
->with('categories', $categories);
}
...
}
If I view the dd the value of $categories inside the index controller, I get the proper Eloquent collection, like following:
object(Illuminate\Database\Eloquent\Collection)[655]
protected 'items' =>
array (size=1)
0 =>
object(MyVendor\ProductsManager\Models\ProductCategory)[653]
protected 'table' => string 'product_categories' (length=18)
protected 'guarded' => ...
But if I dd the value of $categories inside the index view, I get an empty Eloquent collection, like following:
object(Illuminate\Database\Eloquent\Collection)[655]
protected 'items' =>
array (size=1)
0 => null
I am doing similar approach in other controllers and they work fine. I don't know what the problem with this controller is. I have been using Laravel for quite some time now and never had this problem before. Maybe I'm missing something, or there's something wrong with the recent package updates. Whatever it is, it's driving me crazy. Any ideas on what the problem might be?
P.S. I'm using Laravel Framework version 4.2.6
I finally figured out what the problem was. I was using robclancy/presenter package to wrap and render objects in the view. For it, the model had to implement a PresentableInterface interface. Implementing the interface required the method to have a getPresenter method. I had added the getPresenter method in my model, but I forgot to return the presenter from that method. Returning the presenter solved the problem.
The code inside the getPresenter method is the code that I forgot to write.
public function getPresenter()
{
return new ProductCategoryPresenter($this);
}
Thank you guys for trying to help.

Resources