Laravel Lazy Loading Reference $this->property in Load() custom constrain - orm

I have a relation on a model which I need to lazy load on my controller but I need to do a query constraint using a property from the original model.
So I have something like:
$Users = User::all();
$Users->load(['disputes' => function($query){
$query->where('property', $this->property );
}]);
But it returns Undefined property $account_number_tu.
How can I make sure $this is referencing each $User in $Users when doing a load()?
I tried doing this constraint within the model but
$this->property;
returns null.

Under the hood, load() initializes an instance of the Builder on the User model and then passing the params you passed to load to the with method on builder. With this you can do everything in the load method that you can with the with method.
To answer your question you can do:
$Users = User::all();
$Users->load(['disputes' => function($query) {
return $query->select('disputes.*')
->leftJoin('disputes', 'disputes.user_id', '=', 'users.id')
->whereRaw('users.property = disputes.property');
}]);
Which would be the same as:
$Users = User::with(['disputes' => function($query) {
return $query->select('disputes.*')
->leftJoin('disputes', 'disputes.user_id', '=', 'users.id')
->whereRaw('users.property = disputes.property');
}]);

Related

Remove global scopes while applying another scope in Laravel

In my multi-tenant application, Institutions can only see the Assignments they create. Assignments become templates if they are marked as is_template. If a template is marked as is_shared then all institutions should be able to see it.
To achieve the multi-tenancy part, I have a global InstitutionScope that works as expected:
public function apply(Builder $builder, Model $model)
{
$builder->where($model->getTable() . '.institution_id', auth()->user()->institution_id);
}
I also have a local not_template scope on the Assignment model, which again works well:
static::addGlobalScope('not_template', function (Builder $builder) {
$builder->where('is_template', '=', false);
});
I'm now trying to create the scope for the Template model, which extends the Assignment model. This is what I have so far:
static::addGlobalScope('template', function (Builder $builder) {
$builder->withoutGlobalScopes([InstitutionScope::class, 'not_template'])
->where('is_shared', '=', true)
->orWhere(function($query) {
$query->where('institution_id', auth()->user()->institution_id)
->where('is_template', true);
});
});
However, this scope does not work - specifically, it does not remove the other two scopes, so the sql query generated to get all templates is
select * from "assignments" where "assignments"."institution_id" = ? and ("is_shared" = ? or ("institution_id" = ? and "is_template" = ?))
Is there a way of removing other scopes when applying a global scope to a model?
Edit to add - the InsitutionScope is applied to the Assignments by a BelongsToInstitution trait, which is the inherited by the extended Template model

Can't insert into database with save()

I am having an issue inserting a record into the database. I am a beginner with the Yii framework, so I may have made some stupid mistakes.
This is from the SiteController
public function actionCreatePost(){
$model = new PostForm();
$post = new Post();
if ($model->load(Yii::$app->request->post()) && $model->validate()) {
$post->body = $model->body;
$post->title = $model->title;
$post->save();
return $this->redirect('index');
}else {
return $this->render('createPost', ['model' => $model]);
}
}
This is from the Post class
public function behaviors()
{
return [
[
'class' => TimestampBehavior::className(),
'createdAtAttribute' => 'created_at',
'updatedAtAttribute' => 'updated_at',
'value' => new Expression('NOW()'),
],
[
'class' => BlameableBehavior::className(),
'createdByAttribute' => 'id_author',
]
];
}
The issue is that you have created a PostForm class for the form (which is correct) but you are then trying to load the response into the Post class - a completely different class. This won’t work without modification.
If you have a look at the response:
var_dump(Yii:$app->request->post());
You will see the form data is located within the PostForm key. Yii will therefore only load the data into the PostForm class.
The correct solution is therefore to create a savePost() function within the PostForm eg:
public function savePost(){
$model = new Post();
$model->propertyABC = $this->propertyABC
...etc...
$model->save();
So the action would appear as follows:
$model = new PostForm();
If($model->load(Yii::$app->request->post()) && $model->validate()){
$model->savePost();
The other option is to rename the key from PostForm to Post. Yii will then load the data but this is not the best approach as it is a bit obscure.
Hope that helps
I would guess the issue is with the validation.
I can see several issues I will point out. First, I cannot figure out why are you creating a new PostForm, loading the data in it and verifying it, just to dump some values in a new Post and save it. Are there some functions, you are running in the PostForm model, that are triggered by load or verify? If that is not the case, I would suggest dropping one of the models, and using only the other. Usually, that is the Form model. It serves as a link between the ActiveForm and the model handling everything. You can do everything in the createPost() function in the Form model, and then in the controller it will look like
if ($model->load(Yii::$app->request->post())) {
$model->save();
return $this->redirect('index');
}
Second of all, you can dump post->getErrors() before the save to see if there are any errors with the validation. What you can also do, is call $post->save(false) instead. If you pass false to it, it will not trigger $post->validate(), and some errors can be neglected. Please, let me know if there is anything unclear.

How to chain Laravel 5.1 (one-to-many) Eloquent ORM relationship?

Currently i am having to manually access the relationship of each event.
see below:
$events = Events::where('status', '!=', 'passed')->orderBy('created_at', 'desc')->take(20)->get();
foreach ($events as $event) {
$responses = Events::findOrFail($event->id)->responses()->where('user_id', '=', '1')->first();//TODO user_id hardcoded
if($responses) echo "The event has a response that belongs to the user";
else echo "The event doesnt have a response for that user";
}
Is there a way to chain the "Events::" Eloquent ORM completley?
Let me know if you need to see the the models although it's pretty standard.
tried ::with from http://laravel.com/docs/5.1/eloquent-relationships ?
Using eager loading constraints, it'll looked like
$events = Events::with(['response' => function($query) use($user_id){
$query->where('user_id', '=', $user_id);
}
])->where('status', '!=', 'passed')
->orderBy('created_at', 'desc')
->take(20)->get();
However, i'm not sure how to take the first element of the constrained eager load. Try to fiddle with it more.
Laravel Eager Load with dynamic constraints

Yii DAO implementation.

If I only use DAO to save data in Yii , can I use the rules function of the model ? Of course I would not be declaring AR instance . Can I still validate using rules function ?
Create a new model class extending CModel.
Create the rules method as normal.
You can then do
$model = new MyModel();
$model->myAttribute = 'value';
if ($model->validate())
{
Yii::app()->db->createCommand()
->update(
'MyTable',
array('myAttribute' => $model->myAttribute),
'key=:id',
array(':id' => 'key')
);
}
Yes, you can use without problems.
Here are some links that can help you:
http://www.yiiframework.com/doc/guide/1.1/en/database.dao
http://www.yiiframework.com/forum/index.php/topic/25825-dao-vs-activerecord-methods/
http://www.sheldmandu.com/php/php-mvc-frameworks/yii-dao-vs-active-record-performance

call action of another controller

i have a grid view and i would like to get value of column from another action controller.
at now i have this in controller 1
array(
'name'=>'title',
'value'=>array($this,'Action2'),
),
and i get this error:
controller1 and its behaviors do not have a method or closure named "Action2".
if i replace $this with "controller2"
array(
'name'=>'title',
'value'=>array('controller2','Action2'),
),
i get this error
call_user_func_array() [<a href='function.call-user-func-array'>function.call-user-func-array</a>]: First argument is expected to be a valid callback, 'controller2::action2' was given
maybe this is bad practice but is this feasible?
It's bad practice to use controller actions this way. Better place your code in model's method. But if you still want to do this, here is one way:
'value' => function() {
list($controller) = Yii::app()->createController('controllerId');
return $controller->actionTest();
}
Here is another:
'value' => function() {
$controller = new TestController('test');
return $controller->actionTest();
}
You can use this solution:
Yii::app()->runController('category/view/id/1');