Laravel Eloquent Models __construct method to call relations - eloquent

I want to have my model automatically call its relations when instantiated. As of now my model looks like this:
class AdminLog extends Model{
public function __construct(){
$this->belongsTo('App\User', 'admin_id');
}
}
but when i try to do dd(AdminLog::get()->first());, it doesnt show any relations.
Edit#1: tried adding parent::__construct(); inside the model's __construct method but it didn't work.

belongsTo() defines a relationship, it doesn't load it.
First you need to define the relationship, then you can load it at any point using the load method.
class AdminLog extends Model {
public function user() {
return $this->belongsTo(\App\User::class, 'admin_id');
}
}
$log = AdminLog::first();
$log->load('user');
It is possible to load inside the constructor, but I would highly recommend against that. If you have 20 AdminLog objects then it will query the database 20 times, once for each object. That's inefficient.
What you should do instead is use eager loading. This will query the users table just once for all 20 admin logs. There are many ways to do this, here is an example:
$logs = AdminLog::take(20)
->with('user')
->get();
dd($logs->toArray());

Related

What is the correct name of an relationship which is passed to Eloqent `Model::load()`?

I have to models Photo and SizeVariant. A photo has many size variants, a size variant has exactly one photo as its owner. The tables of the DB follow Eloquents naming scheme and are called photos and size_variants.
My models look like that (shortened down):
class Photo extends Model {
protected $with = [ 'size_variants' ];
public function sizeVariants(): HasMany {
return $this->hasMany(SizeVariant::class);
}
}
class SizeVariant extends Model {
protected $with = ['photo'];
protected $touches = ['photo'];
public function photo(): BelongsTo {
return $this->belongsTo(Photo::class);
}
}
However, when I try to call $photo->load('size_variants) on an existing $photo object, I receive an Illuminate\Database\Eloquent\RelationNotFoundException exception with message Call to undefined relationship [size_variants] on model [App\Models\Photo].
What is the right name of the relationship? I thought the naming scheme would follow Laravels usual pattern and Laravel magically maps between snake and camel case.
I already tried the all combinations of capitalizing, snake and camel case and plural vs. singular, i.e. $photo->load('size_variants'), $photo->load('size_variant'), $photo->load('sizeVariants'), $photo->load('sizeVariant'), $photo->load('SizeVariants') and $photo->load('SizeVariant').
Your relation name that is passed in with or load contexts should match the name of the relation you defined on your model, which in your case should be sizeVariants.
On another note, I would also be careful with always eager loading both relations by defining the protected $with property on both models. You'll end up getting timeouts because: your Photo model would be eager loading the SizeVariant models, which would eager load the Photo model, which would eager load the SizeVariant models, which would eager load the Photo model, etc.
You should remove the protected $with property and use with on your queries or load on your models whenever you need the related models instead to avoid this.

One to many polymorphic relationship

It's a common situation but I somehow couldn't manage to find a reasonable solution so far. Basically I need the following:
I have the model House which contains a couple of other models (e.g. Brick, 'Stick', 'Chair' etc.). Each one of them has a belongsTo relation to the House (so basically in the sticks, chairs and bricks table there is a house_id column). Each model contains a public function house() method which returns the house they're part of. Now comes the problem that I'd like to get all of a house's supplies by a method, let's say for example public function supplies(), which should return a collection of instances of the classes Stick, Chair and Brick. Is there any convenient way to achieve that? Could I for example unite those classes by a trait and somehow point to that trait in the house's method or maybe with a BaseClass, let's say Supply, that all the supplies should inherit? Any recommendances? Any help is greatly appreciated and thanks in advance!
There is no built-in support for this in Laravel.
You can define separate HasMany relationships and then merge them with an accessor:
class House extends Model {
public function bricks() {
return $this->hasMany(Brick::class);
}
public function chairs() {
return $this->hasMany(Chair::class);
}
public function sticks() {
return $this->hasMany(Stick::class);
}
public function getSuppliesAttribute() {
return $this->bricks->toBase()->merge($this->chairs)->merge($this->sticks);
}
}
$supplies = $house->supplies;
+++ UPDATE +++
I've created a package for merging relationships using views:
https://github.com/staudenmeir/laravel-merged-relations
First, create the merge view in a migration:
use Staudenmeir\LaravelMergedRelations\Facades\Schema;
Schema::createMergeView(
'supplies', [(new House)->bricks(), (new House)->chairs(), (new House)->sticks()]
);
Then define the relationship:
class House extends Model
{
use \Staudenmeir\LaravelMergedRelations\Eloquent\HasMergedRelationships;
public function supplies()
{
return $this->mergedRelation('supplies');
}
}
Use it like any other relationship:
House::find($id)->supplies;
House::find($id)->supplies()->paginate();
House::with('supplies')->get();

Mass CRUD REST edit/update controller

I am trying to create a RESTful CRUD controller with a little but significant difference that might be in conflict with REST idea but anyway:
I am trying to mass edit items like so /photos/{photo}/edit where item id parameters are like /photos/0&2&7/edit
What is the proper way to establish that in Laravel 5.3?
Is there a way to use some method injections or at least to receive a collection of parameters in the controller method ?
public function edit($id) {
//.......
}
Appreciate your kind help, BR
Using Eloquent you can do whereIn, so you just need to explode the photo parameter so that all the ids are in an array:
public function edit($ids) {
$photo_ids = explode('&', $ids);
$images = Image::whereIn('id', $photo_ids)->get();
}
You can switch out statically accessing the Image model like I did in this example, you can just method inject or dependency inject the image model, let me know if you'd like assistance with dependency/method injection.
Hey i guess you are trying Model binding so you have to use like this
public function edit(Photo $photo) {
//.......
}
Your route should like this
Route::model('photos','App\Photo');
Route::resource('photos','PhotoController');
or you can try this way
your route and function like this
Route::resource('photos','PhotoController');
public function edit($id) {
$photo = Photo::findorFail($id);
}

Is splitting an index action into multiple ones a restful-friendly approach?

I need to display two different index pages to two different user groups. For example, a regular user should see one page, and a privileged user - another one. I see two ways of approaching this issue:
One index action with conditionals:
public function index()
{
// view for privileged users
if(request()->user()->hasRole('privileged')){
return view('index_privileged');
}
// view for regular users
if(request()->user()->hasRole('regular')){
return view('index_regular');
}
return redirect('/');
}
Multiple actions:
public function index_privileged()
{
return view('index_privileged');
}
public function index_regular()
{
return view('index_regular');
}
Which approach is more "restful-friendly" and generally better?
I'm a big fan of light controllers. This might be a little overboard for a simple problem but if something like this pops up again, you'd already have everything all setup.
With that said, it might be best to create a PrivilegedUser class and a RegularUser class and give them both an index method which returns their respective views. Code them both to an interface UserInterface and make sure they both implement that.
Here is what those looked like in my test.
class RegularUser implements UserInterface
{
public function index()
{
return view('index_regular');
}
}
class PrivilegedUser implements UserInterface
{
public function index()
{
return view('index_privileged');
}
}
interface UserInterface
{
public function index();
}
Then you can add a listener which should run for the event Illuminate\Auth\Events\Login. Laravel will fire this event for you automatically when someone logs in. This goes into the file EventServiceProvider.php.
protected $listen = [
'Illuminate\Auth\Events\Login' => [
'App\Listeners\AuthLoginListener',
],
];
Now you can run php artisan event:generate to generate the new listener. Here is what my listener looks like, it should work for you.
namespace App\Listeners;
use Illuminate\Auth\Events\Login;
use Illuminate\Foundation\Application;
class AuthLoginListener
{
/**
* Create the event listener.
*
* #param Application $app
*/
public function __construct(Application $app)
{
$this->app = $app;
}
/**
* Handle the event.
*
* #param Login $event
* #return void
*/
public function handle(Login $event)
{
if ($event->user->hasRole('privileged')) {
$this->app->bind('App\Repositories\UserInterface', 'App\Repositories\PrivilegedUser');
} else if ($event->user->hasRole('regular')) {
$this->app->bind('App\Repositories\UserInterface', 'App\Repositories\RegularUser');
}
}
}
Essentially what this is doing is telling Laravel to load up a certain class based on the type of user that just logged in. The User instance is available through the Login object which was automatically passed in by Laravel.
Now that everything is setup, we barely have to do anything in our controller and if you need to do more things that are different depending on the user, just add them to the RegularUser or PrivilegedUser class. If you get more types of users, simply write a new class for them that implements the interface, add an additional else if to your AuthLoginListener and you should be good to go.
To use this, in your controller, you'd do something like the following...
// Have Laravel make our user class
$userRepository = App::make('App\Repositories\UserInterface');
return $userRepository->index()->with('someData', $data);
Or even better, inject it as a dependency.
use App\Repositories\UserInterface;
class YourController extends Controller
{
public function index(UserInterface $user)
{
return $user->index();
}
}
Edit:
I just realized I forgot the part where you wanted to return redirect('/'); if no condition was met. You could create a new class GuestUser (I know this sounds like an oxymoron) which implements UserInterface but instead of using the AuthLoginListener, I'd bind it in a service provider when Laravel boots. This way Laravel will always have something to return when it needs an implementation of UserInterface in the event it needs this class if no one is logged in.
Well, its more like a refactoring "issue" than a rest-friendly issue. Check this guideline and you can see that most of the things that makes an api friendly is concerned to the url.
But, lets answer what you are asking. The thing you wanna do is a refactoring method but it is not only the move method but something like the extract variable.
The second option would make the code more readable, either ways are right but the second is more developer friendly. It enhances the code readability from any developer. I would recommend using the second option.
Refactoring is never enough, but read something like this, it will help you a lot writing more readable codes.

Can't extend my class to Eloquent while it's already extended to BaseController

I have a ListingsController that extends BaseController which is fine, my app works.
But now I intend to create a new ContactController and create a relation between it and ListingController.
Each listing should have 1 contact and there's a contact_id field in listings table.
The problem is, to use that, I need to extend my class to Eloquent and it's already extended to BaseController and if I change that my app crashes down.
Is there a solution for this?
Thanks.
As SUB0DH said, relationships should be declared in the Model, not in the Controller, you'd want to have something like this in your Listing model:
public function contact()
{
return $this->hasOne('Contact');
}
and in your Contact model you can link the contact to a listing by using
public function user()
{
return $this->belongsTo('User');
}
Be sure to read http://laravel.com/docs/eloquent#one-to-one