Eloquent relationship with a where clause - oop

I need to do a where condition on the code field from my intermediate table. My two models are;
class Agreement extends Eloquent {
protected $table = 'agreements';
public function clients(){
return $this->belongsToMany('Client', 'client_agreements')->withPivot('start_date', 'expire_date', 'code');
}
}
And;
class Client extends Eloquent {
public function agreements(){
return $this->belongsToMany('Agreement', 'client_agreements')->withPivot('start_date', 'expire_date', 'id', 'code');
}
}
My controller is currently;
public function show($code, $client_id)
{
//
$client = Client::with('agreements')->find($client_id);
$client_agreement = $client->agreements;
}
I think I need to expand the $client->agreements; code to include a where condition on the $code. I have tried so many different combinations but just keep returning the same Call to undefined method error.
I've tried things like;
$client_agreement = $client->agreements->where('code', '=', $code);
$client_agreement = $client->agreements->code->find($code);
$client_agreement = $client->agreements->pivot->code->find($code);
I always get the same error. I'm not that great on objects so maybe I'm looking at this all wrong. How is it done?

You need to access ->relation() not ->relation (which is dynamic property call):
$client_agreement = $client->agreements()
->wherePivot('code', '=', $code)
->first();

Related

How to get the default $id from another table using laravel model relationship?

I am facing the problem whereby I don't know the syntax of letting the id of my property model equals to property_id value in property_doc table.
In PropertyDoc model
public function property()
{
return $this->belongsTo(Properties::class, 'property_id');
}
In Properties model
public function property_id()
{
return $this->hasMany(PropertyDoc::class, 'property_id');
}
In PropertyController
public function StoreInfoProperty(Request $request)
{
$propertyInfo = new PropertyDoc;
$propertyInfo->property_id = $property_id;
}
I am stuck at retrieving the default id value in properties database to be equal to the property_id in property_docs database. Thank you.
You should change the naming of the relationship, see my example below:
In Properties model
public function propertyDocs()
{
return $this->hasMany(PropertyDoc::class, 'property_id', 'id');
}
In PropertyDoc model
public function property()
{
return $this->belongsTo(Properties::class, 'property_id', 'id');
}
In controller
public function StoreInfoProperty(Request $request)
{
$propertyDoc = PropertyDoc::with(['property'])->where('...logic here');
$property_id = $propertyDoc->property->id;
}
hope can help you and happy coding !

Laravel eager loading a HasMany Relationship with all the ID present

There are 2 models Property and Relationship. I am trying to eagerload Relationship from Property whereIn relationship.id = array of ids.
My code is as follows:
Property Model
public function relationship_items()
{
return $this->hasMany(Relationship::class, 'property_id', 'id');
}
Relationship Model
public function property()
{
return $this->belongsTo(Property::class, 'id', 'property_id');
}
Eloquent
$properties = Property::when($request->relationship_id, function ($q) use ($request) {
$q->whereHas("relationship_items", function ($q) use ($request) {
$q->whereIn("relationship_id", $request->relationship_id);
});
})->get();
In my whereIn function, I think mysql is trying to do where in id = ([....]), etc. Because of this, If I input ids [1,2,3], eloquent will fetch relationship data even if it has only id "1". I want to only get all relationships if and only if it has all the ids.

Delete unused groups in Laravel 8

I have a group model and I want to delete groups that don't have any member.
How can I get empty groups with eloquent or SQL query ?
class Group extends Model
{
use HasFactory;
protected $fillable = [
'group_name',
'description'
];
public function users(){
return $this->hasMany(User::class);
}
}
And this is the User model code:
class User extends Model implements AuthenticatableContract, AuthorizableContract
{
use SoftDeletes, Authenticatable, Authorizable, HasFactory, Notifiable;
public function getNameAttribute()
{
return $this->last_name.' '.$this->first_name;
}
public function group(){
return $this->belongsTo(Group::class);
}
}
You can use whereDoesntHave:
Group::whereDoesntHave('users')->delete();
You can make sure that you are deleting the correct groups by running this statement instead:
dump(Group::whereDoesntHave('users')->get());
I think whereDoesntHave work in you situation.
Group::query()->whereDoesntHave('users')->delete();

Laravel Convert Mysql query to Eloquent

I'm new to Laravel and I can write simple eloquent queries but have no idea how to convert this query to eloquent. Can anyone give any idea, is it possible to convert this to eloquent or I have to write raw query?
"Select categories.id, categories.name, Sum(likes.liked) as liked
FROM categories, likes
WHERE likes.material_id IN (SELECT category_material.material_id
FROM category_material
WHERE category_material.category_id = categories.id)
GROUP BY categories.id";
Here my Models
class Material extends Model
{
public function categories(){
return $this->belongsToMany(Category::class,'category_material');
}
public function likes(){
return $this->hasMany(Like::class);
}
////////////////////////////////////////////////////////////
class Like extends Model
{
protected $table = 'likes';
public function user(){
return $this->belongsTo(User::class);
}
public function material(){
return $this->belongsTo(Material::class);
}
//////////////////////////////////////////////////////
class Category extends Model
{
public function materials(){
return $this->belongsToMany(Material::class,'category_material');
}
You can define a likes relationship in your Category model like so:
public function likes()
{
return $this->belongsToMany(Like::class, 'category_material', 'category_id', 'material_id', 'id', 'material_id');
}
Then to achieve what you're after with Eloquent you can use a mixture of has() and withCount, however, we're going to modify the withCount call to return a sum() instead:
$catrgories = Category::has('likes')->withCount([
'likes as liked' => function ($query) {
$query->select(DB::raw('SUM(likes.liked)'));
},
])->get();
If you're wanting to return categories that don't have any likes you can remove the has() method, and introduce the COALESCE() function to your raw query:
$catrgories = Category::withCount([
'likes as liked' => function ($query) {
$query->select(DB::raw('COALESCE(SUM(likes.liked), 0)'));
},
])->get();
Alternatively, you could simply load the necessary relationships and then use that fact that Eloquent returns collection to get the value after you've retrieved the results from the database:
$categories = Category::with('materials.likes')->get()->map(function ($item) {
$item->setAttribute('liked', $item->materials->map(function ($item) {
return $item->likes->map->sum('liked')->sum();
})->first());
$item->unsetRelation('materials');
return $item;
});
This would mean that you don't have to add the custom relationship.

Override the condition from ActiveQuery->init()

On Yii2 i have this code in ProjectQuery
class ProjectQuery extends \yii\db\ActiveQuery
{
public function init()
{
$this->andOnCondition(['deleted' => 0]);
parent::init();
}
Obviously the deleted condition must always apply, but there could be cases where this isn't true (for example an option for the user to see his deleted projects). How can i override this condition? Do i have to use something different from init() ?
(note, i want to apply this condition to all kind of queries normally, that's why i used init() on ProjectQuery and not the ProjectSearch class)
You can still use init() but to override the 0 you should bind a parameter.
public function init()
{
$this->andOnCondition('deleted = :deleted', [':deleted' => 0]);
parent::init();
}
So to create a query that only shows the deleted projects write something like this:
$query = Project::find()->addParams([':deleted' => 1]);
To show all projects, deleted and not deleted, you could add a function to the ProjectQuery object to modify it accordingly.
public function includeDeleted() {
$this->orOnCondition(['deleted' => 1]);
return $this;
}
And then write your query like:
$query = Project::find()->includeDeleted();
You can use onCondition() to override existing on conditions:
public function init() {
$this->andOnCondition('deleted = :deleted', [':deleted' => 0]);
parent::init();
}
public function includeDeleted() {
$this->onCondition(null);
// remove unused param from old ON condition
unset($this->params[':deleted']);
return $this;
}
You can use where() in the same way if you want to override conditions added by where(), andWhere and orWhere().
Assuming that you have a class Project where you have overwritten the find() method to return a ProjectQuery instance, the following might be another option. I also assume that you regularely query for undeleted items, and not so often but explicitly for all/deleted items.
Another option could be to add another method to the Project class and remove the default initialization in the ProjectQuery class.
class ProjectQuery extends \yii\db\ActiveQuery
{
public function init()
{
parent::init();
}
...
}
And:
class Project extends \yii\db\ActiveRecord {
...
public static function find()
{
return (new ProjectQuery(get_called_class()))
->andOnCondition(['deleted' => 0]);
}
public static function findAllProjects() // or find any better name for this
{
return new ProjectQuery(get_called_class());
}
}
Now, whenever you want to query explicitly all projects you would need to use this extra method Project::findAllProjects(). So in normal circumstances you won't have to remember that you have to modify the query in some way. No danger, that this could be forgotten.
It is still not 100% satisfying, since one could use find() and add ->andOnCondition(['deleted' => 1]) which would mean no records can be found. However, regarding security this is not so critical and the problem is found easily, I guess.