Yii2: Use scopes of realtion in with() of ActiveRecord - php

In Yii1 I could do something like this:
$posts=Post::model()->with(array(
'comments'=>array(
'scopes'=>array('recently','approved')
),
))->findAll();
Is there a way to call a scope of a relation in the callback function of with() in Yii2?
Customer::find()->with([
'orders' => function ($query) {
$query->andWhere('status = 1');
},
'country',
])->all();

A clean solution is by overriding the model's find() method to use a custom ActiveQuery class :
class Order extends yii\db\ActiveRecord
{
public static function find()
{
return new OrderQuery(get_called_class());
}
}
class OrderQuery extends yii\db\ActiveQuery
{
public function payed()
{
return $this->andWhere(['status' => 1]);
}
}
then you can use it like :
$customers = Customer::find()->with([
'orders' => function($q) {
$q->payed();
}
])->all();
You can also chain many of them like Yii 1 scopes.
In this post you'll find more examples on how to use ActiveQuery class to build Named Scopes :
Yii2 : ActiveQuery Example and what is the reason to generate ActiveQuery class separately in Gii?

Related

Set a eager loading function in my eloquent Object

To avoid duplicate code, I would like to create a function eagerLoading() in my eloquent model. Here is my code:
Model Product:
public function scopeActive($query)
{
return $query->where('active', 1);
}
public function eagerLoading($query)
{
return $query->with([
'owners',
'attributes',
'prices' => function ($query)
{
$query->orderBy('created_at', 'desc');
$query->distinct('type');
}
]);
}
MyController:
$products = Product::active()->eagerLoading()->paginate(100);
return $this->response->withPaginator($products, $this->productTransformer);
But when using this, I have this error : Call to undefined method Illuminate\Database\Query\Builder::eagerLoading().
How should I use my function?
Your eagerLoading() method is just another scope, like your scopeActive() method. In order to do what you want, you need to rename it to scopeEagerLoading().
Right now, Product::active() is returning an Eloquent Query Builder. You are then trying to call eagerLoading() on that, and the method doesn't exist. By prefixing the method with scope, it tells the query builder to call the method on the Model for which it is querying.
From the documentation:
"To define a scope, simply prefix an Eloquent model method with scope."
Check documentation at: https://laravel.com/docs/5.1/eloquent#query-scopes
So you need to rename your method to have "scope" at the beginning.
Change public function eagerLoading($query) to be public function scopeEagerLoading($query)

Yii2 : ActiveQuery Example and what is the reason to generate ActiveQuery class separately in Gii?

Could you provide an example usage. Description will be highly appreciated. I can not find a good example for it.
The Active Query represents a DB query associated with an Active Record class. It is usually used to override the default find() method of a specific model where it will be used to generate the query before sending to DB :
class OrderQuery extends ActiveQuery
{
public function payed()
{
return $this->andWhere(['status' => 1]);
}
public function big($threshold = 100)
{
return $this->andWhere(['>', 'subtotal', $threshold]);
}
}
If you worked before with Yii 1 then this is what replaces Yii 1.x Named Scopes in Yii2. All you have to do is to override the find() method in your model class to use the ActiveQuery class above :
// This will be auto generated by gii if 'Generate ActiveQuery' is selected
public static function find()
{
return new \app\models\OrderQuery(get_called_class());
}
Then you can use it this way :
$payed_orders = Order::find()->payed()->all();
$very_big_orders = Order::find()->big(999)->all();
$big_payed_orders = Order::find()->big()->payed()->all();
A different use case of the same ActiveQuery class defined above is by using it when defining relational data in a related model class like:
class Customer extends \yii\db\ActiveRecord
{
...
public function getPayedOrders()
{
return $this->hasMany(Order::className(),['customer_id' => 'id'])->payed();
}
}
Then you can eager load customers with their respective payed orders by doing :
$customers = Customer::find()->with('payedOrders')->all();

What is the best way to filter collection in Laravel?

I am thinking a best solution at the same time a lazy solution to filter collection in eloquent result in Laravel. I want to filter all my $videos collection in all my controllers. Is that possible to do without rewriting the controllers and instead put it in the model?
Here is my filter code:
$videos = $videos->filter(function( $video ){
return $video->isPublished();
});
Use query scopes. You can learn from here. And in your case, it would be something like this:
class Video extends Eloquent {
public function scopePublished($query)
{
return $query->where('published', '1');
}
}
class VideosController extends BaseController {
public function showPublishedVideos()
{
return View::make('published_videos')
->with('videos', Video::published()->take(10)->get());
}
}

Laravel 4 passing database query from model to controller - error call to member function on non-object

Confused about why this isn't working, trying to pass a database query function to my controller. I am receiving the error Call to a member function getCompanyData() on a non-object
Review.php (Model)
class Review extends Eloquent {
public function getCompanyData($company)
{
return $this->select('head', 'body', 'logo', 'name')
->where('company', '=', $company)
->firstOrFail();
}
}
ReviewController.php
class ReviewController extends BaseController {
protected $review;
public function __construct(Review $review)
{
$this->beforeFilter('csrf', array('on' => 'post'));
$this->review = $review;
}
public function show($company)
{
$data = $this->review->getCompanyData($company);
return View::make('reviews.show', compact('data'));
}
}
I'm not sure about the problem but since you mentioned you want be able to call Review::method() but don't want to declare the method as static, so, in this case, it's possible to call using static :: syntax a non-static method declared in an Eloquent Model in Laravel using scope like this:
class Review extends Eloquent {
public function scopeGetCompanyData($query, $company)
{
return $query->select('head', 'body', 'logo', 'name')
->where('company', '=', $company);
}
}
Now you can call the getCompanyData method from your controller like this way:
$data = Review::getCompanyData($company)->firstOrFail();
So review is not an object, and you are trying to call its method like it is object. It is probably array. I see you provided Request $request reference and you expect it to be and object. It sometimes happens to me while passing values to view. Try to track type of review.
Update:
Some other things to consider are: Why did you pass Review through constructor, when it is model that extends Eloquent and it should be visible ? Does Review really extends Eloquent ?

Get array of Eloquent model's relations

I'm trying to get an array of all of my model's associations. I have the following model:
class Article extends Eloquent
{
protected $guarded = array();
public static $rules = array();
public function author()
{
return $this->belongsTo('Author');
}
public function category()
{
return $this->belongsTo('Category');
}
}
From this model, I'm trying to get the following array of its relations:
array(
'author',
'category'
)
I'm looking for a way to pull this array out from the model automatically.
I've found this definition of a relationsToArray method on an Eloquent model, which appears to return an array of the model's relations. It seems to use the $this->relations attribute of the Eloquent model. However, this method returns an empty array, and the relations attribute is an empty array, despite having my relations set up correctly.
What is $this->relations used for if not to store model relations? Is there any way that I can get an array of my model's relations automatically?
It's not possible because relationships are loaded only when requested either by using with (for eager loading) or using relationship public method defined in the model, for example, if a Author model is created with following relationship
public function articles() {
return $this->hasMany('Article');
}
When you call this method like:
$author = Author::find(1);
$author->articles; // <-- this will load related article models as a collection
Also, as I said with, when you use something like this:
$article = Article::with('author')->get(1);
In this case, the first article (with id 1) will be loaded with it's related model Author and you can use
$article->author->name; // to access the name field from related/loaded author model
So, it's not possible to get the relations magically without using appropriate method for loading of relationships but once you load the relationship (related models) then you may use something like this to get the relations:
$article = Article::with(['category', 'author'])->first();
$article->getRelations(); // get all the related models
$article->getRelation('author'); // to get only related author model
To convert them to an array you may use toArray() method like:
dd($article->getRelations()->toArray()); // dump and die as array
The relationsToArray() method works on a model which is loaded with it's related models. This method converts related models to array form where toArray() method converts all the data of a model (with relationship) to array, here is the source code:
public function toArray()
{
$attributes = $this->attributesToArray();
return array_merge($attributes, $this->relationsToArray());
}
It merges model attributes and it's related model's attributes after converting to array then returns it.
use this:
class Article extends Eloquent
{
protected $guarded = array();
public static $rules = array();
public $relationships = array('Author', 'Category');
public function author() {
return $this->belongsTo('Author');
}
public function category() {
return $this->belongsTo('Category');
}
}
So outside the class you can do something like this:
public function articleWithAllRelationships()
{
$article = new Article;
$relationships = $article->relationships;
$article = $article->with($relationships)->first();
}
GruBhub, thank you very much for your comments. I have corrected the typo that you mentioned.
You are right, it is dangerous to run unknown methods, hence I added a rollback after such execution.
Many thanks also to phildawson from laracasts, https://laracasts.com/discuss/channels/eloquent/get-all-model-relationships
You can use the following trait:
<?php
namespace App\Traits;
use Illuminate\Database\Eloquent\Relations\Relation;
trait EloquentRelationshipTrait
{
/**
* Get eloquent relationships
*
* #return array
*/
public static function getRelationships()
{
$instance = new static;
// Get public methods declared without parameters and non inherited
$class = get_class($instance);
$allMethods = (new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC);
$methods = array_filter(
$allMethods,
function ($method) use ($class) {
return $method->class === $class
&& !$method->getParameters() // relationships have no parameters
&& $method->getName() !== 'getRelationships'; // prevent infinite recursion
}
);
\DB::beginTransaction();
$relations = [];
foreach ($methods as $method) {
try {
$methodName = $method->getName();
$methodReturn = $instance->$methodName();
if (!$methodReturn instanceof Relation) {
continue;
}
} catch (\Throwable $th) {
continue;
}
$type = (new \ReflectionClass($methodReturn))->getShortName();
$model = get_class($methodReturn->getRelated());
$relations[$methodName] = [$type, $model];
}
\DB::rollBack();
return $relations;
}
}
Then you can implement it in any model.
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
use App\Traits\EloquentRelationshipTrait;
class User extends Authenticatable
{
use Notifiable, HasApiTokens, EloquentRelationshipTrait;
Finally with (new User)->getRelationships() or User::getRelationships() you will get:
[
"notifications" => [
"MorphMany",
"Illuminate\Notifications\DatabaseNotification",
],
"readNotifications" => [
"MorphMany",
"Illuminate\Notifications\DatabaseNotification",
],
"unreadNotifications" => [
"MorphMany",
"Illuminate\Notifications\DatabaseNotification",
],
"clients" => [
"HasMany",
"Laravel\Passport\Client",
],
"tokens" => [
"HasMany",
"Laravel\Passport\Token",
],
]
I have published a package in order to get all eloquent relationships from a model. Such package contains the helper "rel" to do so.
Just run (Composer 2.x is required!):
require pablo-merener/eloquent-relationships
If you are on laravel 9, you are able to run artisan command model:show

Categories