Laravel Eloquent HasMany / HasOne Additional On Clauses - php

Good morning,
Apologies if this has been ask before but we are unable to find any answer to an issue we are having.
We are working with a legacy database that is not owned by us (read-only) and are attempting to use Eloquent (Models) in Laravel to solve some common issues.
Is it possible to setup Eloquent's Eager-loading to create additional ON clauses to the HasMany / HasOne relationship builder?
Please see below of what we are trying to achieve without raw queries.
public function policy()
{
return $this->hasMany(Policy::class, 'Group', 'Group')
// This breaks as `on` isn't defined on Eloquent\Builder. Is this concept possible? Multiple on clauses
->on('Reference', 'Reference');
}
In our controller we have attempted the following which also fails.
Vehicle::with([
'policy' => function ($query) {
// Model isn't instantiated yet, but we need an additional on clause here
$query->on('Reference', 'Reference');
}
]);
Can the above be achieved or do we have to revert back to using raw queries?
Thank you in advance for any help.

You can use the Compoships package:
class Vehicle extends Model
{
use \Awobaz\Compoships\Compoships;
public function policy()
{
return $this->hasMany(Policy::class, ['Group', 'Reference'], ['Group', 'Reference']);
}
}

I am assuming the query already exists as raw SQL and what you are trying to achieve is to convert it to using eloquent. If that is the case you may find it quicker an deasier to use the built in raw query.

Related

Showing relational data of a Filament resource

I've started using Filament PHP for creating a Laravel based intranet application but stumbled across a question I couldn't answer myself using the official documentation:
What's the easiest way to show relational data inside the view page of a resource?
I have two resources ClientResource and ProjectResource which results in two Laravel relationships:
Client model:
public function projects(): HasMany
{
return $this->hasMany(Project::class);
}
Project model:
public function client(): BelongsTo
{
return $this->belongsTo(Client::class);
}
I have implemented a BelongsToSelect field inside project resource to assign a client:
Components\BelongsToSelect::make('client')
->relationship('client', 'first_name')
->required(),
Everything works fine so far, but (obviously) all I can see on the project's view page is the disabled select field showing the customer's first name. I'd like to have all related fields listed. Have I missed something crucial in the documentation or what's the best way to approach this?
I've had a look into the RelationManager but it seems there is only a belongsToMany relationship (no belongsTo).
This can also be done with a select component by specifying a relation like this:
Select::make('client_id')
->relationship('client', 'first_name');
You also have access to the eloquent query builder instance to play with like so:
Select::make('client_id')
->relationship('client', 'first_name',
fn (Builder $query) => $query->where('status', 'actif'))
);
Document reference: https://filamentphp.com/docs/2.x/forms/fields#populating-automatically-from-a-relationship

What's the advantage of using Laravel Eloquent Relationship queries?

So I'm trying to improve my queries by implementing eloquent relationships.
Let's say I have a threads table and each thread have replies
What I need is a JSON object of the information of threads with array of all the replies.
Currently, I can achieve what I want with this.
public function show($threadId){
$threads = ThreadView::find($threadId);
$threads->replies = ReplyView::where('threadId', $threadId)->get();
return response()->json($threads, 200);
}
With eloquent relationship, I can achieve the same result with something like this.
public function show($threadId) {
$threads= ThreadView::find($threadId);
$threads->replies= ThreadView::find($threadId)->Replies;
return response()->json($threads, 200);
}
Is there an advantage to using relationship? They both look the same to me.
Using Eloquent you can do this even shorter:
public function show($threadId) {
$threads = ThreadView::with('replies')->find($threadId);
return response()->json($threads, 200);
}
The with function will eager load relations without you having to write the query.
That is the advantage of Eloquent, it writes the queries for you. No need to create join queries or extra queries for you.
public function show($threadId) {
$threads= ThreadView::with('replies')->find($threadId);
return response()->json($threads, 200);
}
The main advantage is that using relationships like this, the first example does 2 selects (it grabs 100% of ThreadView then 100% of ReplyView), while the second example only does 1 query.
The second advantage is that Eloquent does a Really Good Job at lazy loading compared to others and it is much better to use if you are not an expert in SQL queries.
Ditching Eloquent models and using the DB Builder directly will be approximately 500x faster, and I find the code cleaner. That may be what you're looking for.
But if you use models, use the relationships!

how can do a multiple has() /orHas() in laravel 5.4 orm

First of all I should say I want to Use laravel ORM not query builder and not the Raw sql methods. so here is my question.
I have a model called bots that has belongTo(User::class) relation and 6 hasMany(TextPost::class) - hasMany(PhotoPost::class) and so on. I want to get bots that has at least one record in any of the hasMany(...) relations. so if a bot has a record in TextPost it should be returned and if a bot has no post in none of the hasMany(...) relations it should not be returned.
so far I have something like this in my User model
public function broadCasters()
{
return $this->bots->has('text_post')->orHas('photo_post')->orHas('media_post')->orHas('video_post')->orHas('audio_post')->orHas('document_post');
}
but its not going to work and says Call to a member function orHas() on boolean
so I wanted to change it to something like this:
public function broadCasters(){
return $this->bots->where(function($query){
return $query->has('TextPost')->orHas('PhotoPost') ...;
});
}
but this is not gonna work either and says Missing argument 2 for Illuminate\Support\Collection::where(). what should I do?
I use laravel 5.4. bu I saw this Question whereHas on multiple relationships - laravel 5.4 and its pretty close to what I want but no answers yet!!
also this question is another of my questions too, Combining AND/OR eloquent query in Laravel
but its not working either.
AND NOW THIS IS EXACTLY WHAT I WANT BUT NOT WORKING, I THINK BECAUSE ITS FOR LARAVEL 4 AND I USE LARAVEL 5.4
Laravel eloquent multiple has and where clause
enter code hereIf you user $this->bots, the result will be laravel collection array. You need to use $this->bots() like this :
public function broadCasters(){
return $this->bots()->where(function($query){
return $query->has('TextPost')->orHas('PhotoPost') ...;
})->get();
}

Laravel 5.2 Eloquent hasOne relationship doesn't work

I have relationship in my chat model
public function user() {
return $this->hasOne(User::class,'id','writers_id');
}
And want to use it in my controller. I tryed many ways, but no one worked. Just giving last try example (result - blank page).
print_r($chat = Chat::where('id', 1)->first()->user);
Can anyone help me? Thanks! I don't really understand that Eloquent, used simple DB query maker before, however someone said that I should do every database stuff in model. Is it correct? Sorry about my poor English!
First as mentioned by #Vohuman
return $this->hasOne('App\User', 'id', 'writers_id');
And when using it:
Chat::where('id', 1)->first()->user()->first()->attribute
Look here: https://laravel.com/docs/master/eloquent-relationships#one-to-one
In App\Chat.php:
public function writer()
{
return $this->hasOne('App\User','id','writers_id');
}
to define the relation. Then in whatever controller:
At top:
use App\User;
and inside the method:
$writer = User::find($user_id)->writer;
to debug it add one line in front of the above line.
dd($user_id,User::find($user_id),$writer);

Laravel 5 issue with wherePivot

I am working with Laravel 5 and I am having issue getting ->wherePivot() to work on a Many-to-Many relationship. When I dd() the SQL it looks like Eloquent is looking for records in the pivot table with a `pose_state`.`pose_id` is null`.
I am hoping it is a simple error and not a bug. Any ideas are appreciated.
Database Structure
pose
id
name
type
state
id
name
machine_name
pose_state
pose_id
state_id
status
Models
Pose
<?php namespace App;
use DB;
use App\State;
use Illuminate\Database\Eloquent\Model;
class Pose extends Model {
public function states()
{
return $this->belongsToMany('App\State')
->withPivot('status_id')
->withTimestamps();
}
public function scopeWithPendingReviews()
{
return $this->states()
->wherePivot('status_id',10);
}
}
State
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class State extends Model {
public function poses()
{
return $this->belongsToMany('Pose')
->withPivot('status_id')
->withTimestamps();
}
}
PosesController function
public function listPosesForReview(){
$poses = Pose::withPendingReviews()->get();
dd($poses->toArray() );
}
SQL
select
`states`.*, `pose_state`.`pose_id` as `pivot_pose_id`,
`pose_state`.`state_id` as `pivot_state_id`,
`pose_state`.`status_id` as `pivot_status_id`,
`pose_state`.`created_at` as `pivot_created_at`,
`pose_state`.`updated_at` as `pivot_updated_at`
from
`states` inner join `pose_state` on `states`.`id` = `pose_state`.`state_id`
where
`pose_state`.`pose_id` is null and `pose_state`.`status_id` = ?
EDIT
When I updated my code to removing the scope it worked. Thanks #Deefour for putting me on the right path! Maybe scope has something else to that I am missing.
public function pendingReviews()
{
return $this->states()
->wherePivot('status_id','=', 10);
}
YET ANOTHER EDIT
I finally got this to work. The solution above was giving me duplicate entries. No idea why this works, but it does, so I will stick with it.
public function scopeWithStatusCode($query, $tag)
{
$query->with(['states' => function($q) use ($tag)
{
$q->wherePivot('status_id','=', $tag);
}])
->whereHas('states',function($q) use ($tag)
{
$q->where('status_id', $tag);
});
}
I think your implementation of scopeWithPendingReviews() is an abuse of the intended use of scopes.
A scope should be thought of as a reusable set of conditions to append to an existing query, even if that query is simply
SomeModel::newQuery()
The idea is that a pre-existing query would be further refined (read: 'scoped') by the conditions within the scope method, not to generate a new query, and definitely not to generate a new query based on an associated model.
By default, the first and only argument passed to a scope method is the query builder instance itself.
Your scope implementation on your Pose model was really a query against the states table as soon as you did this
$this->states()
This is why your SQL appears as it does. It's also a clear indicator you're misusing scopes. A scope might instead look like this
public function scopeWithPendingReviews($query) {
$query->join('pose_state', 'poses.id', '=', 'pose_state.pose.id')
->where('status_id', 10);
}
Unlike your new pendingReviews() method which is returning a query based on the State model, this scope will refine a query on the Pose model.
Now you can use your scope as you originally intended.
$poses = Pose::withPendingReviews();
which could be translated into the more verbose
$poses = Pose::newQuery()->withPendingReviews();
Notice also the scope above doesn't return a value. It's accepting the existing query builder object and adding onto it.
The other answer to this question is filled with misinformation.
You cannot use wherePivot() as is claims.
Your use of withTimestamps() is not at all related to your problem
You don't have to do any "custom work" to get timestamps working. Adding the withTimestamps() call as you did is all that is needed. Just make sure you have a created_at and updated_at column in your join table.
I think that your implementation of scopes is fine, the problem I see is just a typo. Your schema shows that the field is called status but your where condition is referring to a status_id
Try:
->wherePivot('status', 10);
Also, the withTimestamps() method is causing issues. You don't have timestamps in your schema for the pivot (as I can see) so you shouldn't be putting these in the your relation definitions as it's trying to fetch the timestamps relating to when the relation was created/updated. You can do this if you set up your pivot table schema to have the timestamp fields, but I think you'll have to do some custom work to get the timestamps to save properly.
This worked for me (Laravel 5.3):
$task = App\Models\PricingTask::find(1);
$task->products()->wherePivot('taggable_type', 'product')->get();
You can also have this problem (return no results) if the column you are using in wherePivot hasn't been added to withPivot.

Categories