Laravel DRY nested eager loading - php

Suppose I got a model with a few relations:
class Author extends Model
{
public function shortStories()
{
return $this->hasMany(ShortStory::class);
}
public function essays()
{
return $this->hasMany(Essay::class);
}
public function books()
{
return $this->hasMany(Book::class);
}
}
Now suppose I got two more models that want to eager load this model with its relations:
class Publisher extends Model
{
public function scopeWithAuthor($query)
{
$query->with('author.shortStories', 'author.essays', 'author.books');
}
}
class Reviewer extends Model
{
public function scopeWithAuthor($query)
{
$query->with('author.shortStories', 'author.essays', 'author.books');
}
}
Problem - if the relations of author change I now need to reflect this in multiple locations.
My question - how do I achieve this in DRY style?
I know that I could add a protected $with to the Author class but then it would always load the relation, not just when required.
With that in mind, one solution I have come up with is to extend the Author model like so:
class AuthorWithRelations extends Author
{
protected $with = ['shortStories', 'essays', 'books'];
}
This then allows me to refactor the scopes of the other models like so:
class Publisher extends Model
{
public function scopeWithAuthor($query)
{
$query->with('authorWithRelations');
}
}
class Reviewer extends Model
{
public function scopeWithAuthor($query)
{
$query->with('authorWithRelations');
}
}
This works well enough but really I was wondering if Laravel provides a better / inbuilt approach to this?

Related

Touching Eloquent model via polymorphic relationship

Laravel 5.7. I have two models, Foo and Content. Their relationship is polymorphic because Content can also be related to other models:
class Foo extends Model
{
public function contents()
{
return $this->morphToMany('App\Content');
}
}
class Content extends Model
{
use LastModified;
public function foos()
{
return $this->morphedByMany('App\Foo');
}
}
I want to touch the Foo model whenever the Content model is updated. So I use a LastModified trait for the Content model:
trait LastModified
{
protected static function bootLastModified()
{
static::updating(function ($model) {
static::updateLastModified($model);
});
}
protected static function updateLastModified($model)
{
$foos = $model->foos;
if (count($foos) > 0) {
foreach ($foos as $foo) {
$foo->touch();
}
}
}
}
My problem is that $model->foos returns the correct Foo models, but with the wrong ids. Instead of the Foo's own model id, each Foo has the id of the pivot table row. This means that the wrong Foo row is touched.
Laravel has built-in functionality for touching parent timestamps.
On the content model, you can add a property telling which relationships should be touched when the given model is updated.
The following should work:
class Content extends Model
{
protected $touches = ['foos'];
public function foos()
{
return $this->morphedByMany('App\Foo');
}
}
Edit: Since you are using a static updated event you should manually call $model->touchOwners() from the static::updated

information about related models in Laravel

Hello I´m writing an API and I want to display more information about the related model.
Routes.php
Route::resource('makes', 'MakesController');
MakesController.php
class MakesController extends Controller
{
public function index()
{
$data = Make::all();
return response()->json($data);
}
}
This returns only information about the makes (id, name)
but how can I display also how many models has each make?
I have defined these two models
class Make extends Model
{
public function models()
{
return $this->hasMany('App\CarModel');
}
}
class CarModel extends Model
{
public function make()
{
return $this->belongsTo('App\Make');
}
}
You can define $visible field in the Make model's class like this:
protected $visible = ['models'];
This will automatically appends the related model's array to array/json.
You can also use an optional way with makeVisible method:
class MakesController extends Controller
{
public function index()
{
$data = Make::all();
return response()->makeVisible('models')->json($data);
}
}

Laravel5.2 with count

I have the following problem,
I have four models:
MainArea |Institute|institute_level|students with the following reations:
class MainArea extends Model
{
public function institute(){
return $this->belongsTo('\App\Institute');
}
public function places(){
return $this->hasMany('\App\Place');
}
}
class Institute extends Model
{
public function insLevel(){
return $this->hasMany('\App\InstituteLevel');
}
}
class InstituteLevel extends Model
{
public function students(){
return $this->hasMany('\App\Student','applying_level');
}
}
class Student extends Model
{
public function instituteLevel(){
return $this->belongsTo('\App\InstituteLevel','institute_level_id');
}
public function place(){
return $this->belongsTo('\App\Place');
}
}
Now, I want to get all areas with the count of students registered in it, in the same institute? it should be something like this.
$areas=\App\MainArea::withCount('institute.insLevel.students')->where('institute_id',$id)->get();
Any suggestions ?
Unfortunately, "withCount()" doesn't support nested relationships as of Laravel 5.2.
Here's the comment from the contributor in a Github Pull Request.

Laravel 4, Retrive morphed Parent Model, querying child

I didn't know how to ask the question so, the question is not as clear as the description. But here it goes.
I want to query the parent Model, by quering the child Model. Here is the basic Models:
//Gig.php
class Gig extends Eloquent
{
public function gigable()
{
return $this->morphTo('profile');
}
}
//Band.php
class Band extends Eloquent {
public function gigs()
{
return $this->morphMany('Gig','profile');
}
}
//Musician.php
class Musician extends Eloquent {
public function gigs()
{
return $this->morphMany('Gig','profile');
}
}
Now, I know I can do something like this:
Gig::find(1)->gigable
Is there a way that I can retrieve gigables in one query
and for example for my api I can return all the gigable at once.
Something like:
Gig::where('id','>',1)->gigable
I know thats doesn't work.
Such as in reverse of this I can do something like,
Band::with('gigs')->where('created_at','<',Carbon::now())->get();
Use as follows
//Gig.php
class Gig extends Eloquent {
public function gigable()
{
return $this->morphTo();
}
}
//Band.php
class Band extends Eloquent {
public function gigs()
{
return $this->morphMany('Gig','gigable');
}
}
//Musician.php
class Musician extends Eloquent {
public function gigs()
{
return $this->morphMany('Gig','gigable');
}
}
Then you will be able to get the gigs this way:
$band = Band::with('gigs')->where('created_at','<',Carbon::now())->get();
$gigs = $band->gigs;
If this is what you wanted... your question is unclear

Table prefix configuration in Phalcon Model

I would like to set up a table prefix for all my models, since this is how it is designed in the database.
How can I achieve that?
You can override the getSource method to set the prefix:
class Users extends Phalcon\Mvc\Model
{
public function getSource()
{
return 'my_' . 'users';
}
}
Or, you can set a base model class to set the table prefix for all models:
class BaseModel extends Phalcon\Mvc\Model
{
public function getSource()
{
return 'my_' . strtolower(get_class($this));
}
}
and extend all models from that
class Users extends BaseModel
{
}
or in PHP 5.4 you can create a trait:
trait CustomPrefix
{
public function getSource()
{
return 'my_' . strtolower(get_class($this));
}
}
then in your model:
class Users extends Phalcon\Mvc\Model
{
use CustomPrefix;
}
Source
Also, if you have tables with underscores "_" you could write it like that:
public function getSource()
{
return 'my_'.strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', get_class($this)));
}
you can also add all your settings to initialize function. If you have any connection between models like one-one many-many one-many you will define them also in initialize method.
class Robots extends \Phalcon\Mvc\Model
{
public function initialize()
{
$this->setSource("the_robots");
}
}

Categories