Laravel Eloquent Query absence through shared relationship - php

I have a relationship like this
Requests
=> id
public function proposals(){
return $this->hasMany(Proposal::class)
}
Proposals
=> request_id
=> company_id
public function request(){
return $this->belongsTo(Request::class)
}
public function proposals(){
return $this->belongsTo(Company::class)
}
Companies
=> id
public function proposals(){
return $this->hasMany(Proposal::class)
}
I've tought in something like this:
$request->with('proposals')->whereDoesntHave('company', function($query){
$query->where('company_id', '<>', 1);
})->get()
But it didn't work out.
In this scenario how can I retrieve all the requests that one company has not sent a proposal?
EDIT
A short version of my DB Schema
A User can have many Companies, a User can make Requests, a Company respond to this Request through a Proposal

What I am seeing here from your schema is a pivot table relationship.
A Request can belong to many Companies, and a Company can have many Requests. The relationship between the two is the Proposal, which is your pivot table.
So firstly, you want add the correct relationships to your models
class Request
{
public function companies()
{
// You will need to probably set the foreign and local keys as extra parameters here
return $this->belongsToMany(Company::class, 'proposals')->using(Proposal::class);
}
}
and
class Company
{
public function requests()
{
// You will need to probably set the foreign and local keys as extra parameters here
return $this->belongsToMany(Request::class, 'proposals')->using(Proposal::class);
}
}
Finally, you can get the proposal using the pivot which will be a Proposal object.
To address your original query, you would just do:
$requests = Request::with(['companies' => function($q) {
$q->where('companyid', '!=', 1);
}]);

Related

Laravel whereHas getting the relationship

I'm doing a Laravel application to manage events. An Attendant and and Event have a belongsToMany relationship. An attendant has multiple events and viceversa.
The pivot column has an extra field called uuid, this is like the ticket id to the event.
I need to validate the entrance of an Attendant to an Event. So the Attendant scans a QR code with the uuid of the pivot table.
So I have this function
public function validateTicket(Request $request, $ticket) {
$event = Event::fromTicket($ticket);
\Log::info($event);
return new EventResource($event);
}
And the static method fromTicket is:
public static function fromTicket($ticket) {
return static::whereHas('attendants', function($q) use ($ticket) {
$q->where('uuid', $ticket);
})->first();
}
The problem is that I need the relationship too, this because I need to register the hour of entrance of an Attendant, and this entrance could happen many times.
Is there a way to return the relationship too during the whereHas query?
Thanks in advance.
No because it needs the original models relationship to get the pivot table, fear not there is a solution that is very close to the original. Instead include all attendants and do a condition in the include and only the attendent you need will be present.
public static function fromTicket($ticket) {
return static::whereHas('attendants',
function($query) use ($ticket) {
$query->where('uuid', $ticket);
})->with(['attendants' => function ($query) use ($ticket) {
$query->where('uuid', $ticket);
}])->first();
}
This will include the attendants, but only the one with the correct ticket, so now you can do $event->attendants->first(), this is two queries still quite optimized but not optimal, my easiest solution for how you portrayed your problem.

Laravel get model with relation's pivot condition

I need to get all appeals, that have appeal_stage.expiration_date less than NOW().
Now I have following solution:
public function scopeExpired($query) {
$query->join('appeal_stage', 'appeals.id', 'appeal_stage.appeal_id')
->where('appeal_stage.expiration_date', '<=', new Expression('NOW()'));
}
but resulted model dump shows that joined table is recognized as pivot table:
So, I want to ask - Is there some more convenient way to perform this request?
My suggestions is use Illuminate\Database\Eloquent\Relations\Pivot somehow, bu I do not quiet understand, how Pivot can be used here.
UPD 1
Models has next relations:
public function stages()
{
return $this->belongsToMany(Stage::class)->withPivot('prolongated_count', 'expiration_date')->withTimestamps();
}
public function appeals() {
return $this->belongsToMany(Appeal::class);
}
You should be able to do something like this:
$appeal->stages()->wherePivot('expiration_date', '<', $now)->get()
You should create relationship in appeal model
public function stages()
{
return $this->belongsToMany(Stage::class,'appeal_stage','appeal_id','stage_id')->wherePivot('expiration_date','<',Carbon::now())->withTimestamps();
}
In belongs To Many relationship second argument is your Pivot table name

Laravel -- Flatten data appended via `with`

I've got two models, User and Seminar. In English, the basic idea is that a bunch of users attend any number of seminars. Additionally, exactly one user may volunteer to speak at each of the seminars.
My implementation consists of a users table, a seminars table, and a seminar_user pivot table.
The seminar_user table has a structure like this:
seminar_id | user_id | speaking
-------------|-----------|---------
int | int | bool
The relationships are defined as follows:
/** On the Seminar model */
public function members()
{
return $this->belongsToMany(User::class);
}
/** On the User model */
public function seminars()
{
return $this->belongsToMany(Seminar::class);
}
I am struggling to figure out how to set up a "relationship" which will help me get a Seminar's speaker. I have currently defined a method like this:
public function speaker()
{
return $this->members()->where('speaking', true);
}
The reason I'd like this is because ultimately, I'd like my API call to look something like this:
public function index()
{
return Seminar::active()
->with(['speaker' => function ($query) {
$query->select('name');
}])
->get()
->toJson();
}
The problem is that since the members relationship is actually a belongsToMany, even though I know there is only to ever be a single User where speaking is true, an array of User's will always be returned.
One workaround would be to post-format the response before sending it off, by first setting a temp $seminars variable, then going through a foreach and setting each $seminar['speaker'] = $seminar['speaker'][0] but that really stinks and I feel like there should be a way to achieve this through Eloquent itself.
How can I flatten the data that is added via the with call? (Or rewrite my relationship methods)
Try changing your speaker function to this
public function speaker()
{
return $this->members()->where('speaking', true)->first();
}
This will always give you an Item as opposed to a Collection that you currently receive.
You can define a new relation on Seminar model as:
public function speaker()
{
return $this->belongsToMany(User::class)->wherePivot('speaking', true);
}
And your query will be as:
Seminar::active()
->with(['speaker' => function ($query) {
$query->select('name');
}])
->get()
->toJson();
Docs scroll down to Filtering Relationships Via Intermediate Table Columns

Laravel 5.2 hasManyThrough relationship issue

I have 3 tables: orders, codes, events
I want to be able to pull all events that an order has, but there's an intermediary table that acts as a pivot table. I've been trying to use hasManyThrough and belongsToMany (along with withPivot) without any luck.
Examples:
public function events()
{
return $this->belongsToMany('events'); // tried this, fails
return $this->hasManyThrough('events', 'codes'); // tried this, fails
return $this->hasManyThrough('events', 'codes', 'event_id', 'id'); // tried this, fails
}
Any pointers would be great!
That's a belongsToMany setup. First, the first parameter is the name of the related class. Second, since your pivot table doesn't follow the Laravel naming conventions, you need to specify the name of the pivot table in your relationship definition:
public function events()
{
// first parameter is the name of the related class
// second parameter is pivot table name
return $this->belongsToMany(Event::class, 'codes');
}
With this setup, you can do:
// get an order
$order = Order::first();
// has all the events related to an order
$events = $order->events;
There are many ways to do this. I will show a one you can get it done.
In Order.php model
public function codes(){
return $this->has('App\Http\Code');
}
In Code.php model
public function orders(){
return $this->belongsTo('App\Http\Order');
}
public function events(){
return $this->hasMany('App\Http\Event');
}
In Event.php model
public function codes(){
return $this->belongsTo('App\Http\Code');
}
Then in you Controller, call them to get required data.
In your case you can do it like below:
$orders = Order::with(['codes' => function($q){
$q->with('events');
})->get();
May be you can get them with nested manner(not sure about this because i didn't tried before posting):
$orders = Order::with('codes.events')->get();
put return $orders; in your controller to see the query.
Enjoy!

Friendship system with Laravel : Many to Many relationship

I'm trying to create a Friendship system with Laravel (I'm starting with it) but I'm blocked with relationships. Here's the thing : there is one table Users and one table Friends which contains the following columns :
friends: id, user_id, friend_id, accepted.
It looks like a Many to Many so here's what I set on User class :
class User extends Eloquent {
function friends()
{
return $this->belongsToMany('User');
}
}
But when I try a :
$friends = User::find($id)->friends()->get()
I have this error :
Base table or view not found: 1146 Table 'base.user_user' doesn't exist
I would like to get a list of the Friends of a user, no matters if the user sent the invitation or received it. So the user can ba on user_id or on friend_id and then I retrieve the data of the other user depending of that column.
Any idea? Thank's!
EDIT : Here's the code I use :
$usersWithFriends = User::with('friendsOfMine', 'friendOf')->get();
$user = User::find(Auth::id())->friends;
foreach($user as $item) {
echo $item->first()->pivot->accepted;
}
tldr; you need 2 inverted relationships to make it work, check SETUP and USAGE below
First off the error - this is how your relation should look like:
function friends()
{
return $this->belongsToMany('User', 'friends', 'user_id', 'friend_id')
// if you want to rely on accepted field, then add this:
->wherePivot('accepted', '=', 1);
}
Then it will work without errors:
$user->friends; // collection of User models, returns the same as:
$user->friends()->get();
SETUP
However you would like the relation to work in both ways. Eloquent doesn't provide a relation of that kind, so you can instead use 2 inverted relationships and merge the results:
// friendship that I started
function friendsOfMine()
{
return $this->belongsToMany('User', 'friends', 'user_id', 'friend_id')
->wherePivot('accepted', '=', 1) // to filter only accepted
->withPivot('accepted'); // or to fetch accepted value
}
// friendship that I was invited to
function friendOf()
{
return $this->belongsToMany('User', 'friends', 'friend_id', 'user_id')
->wherePivot('accepted', '=', 1)
->withPivot('accepted');
}
// accessor allowing you call $user->friends
public function getFriendsAttribute()
{
if ( ! array_key_exists('friends', $this->relations)) $this->loadFriends();
return $this->getRelation('friends');
}
protected function loadFriends()
{
if ( ! array_key_exists('friends', $this->relations))
{
$friends = $this->mergeFriends();
$this->setRelation('friends', $friends);
}
}
protected function mergeFriends()
{
return $this->friendsOfMine->merge($this->friendOf);
}
USAGE
With such setup you can do this:
// access all friends
$user->friends; // collection of unique User model instances
// access friends a user invited
$user->friendsOfMine; // collection
// access friends that a user was invited by
$user->friendOf; // collection
// and eager load all friends with 2 queries
$usersWithFriends = User::with('friendsOfMine', 'friendOf')->get();
// then
$users->first()->friends; // collection
// Check the accepted value:
$user->friends->first()->pivot->accepted;
It's oviously a problem in your DB and also definition of the relation. Many-to-Many relation type expects you to use and intermediate table. Here's what you have to do :
Create a user_friend (id, user_id, friend_id) table in your schema.
Remove unnecessary fields from user and friend tables.
Create proper foreign keys . user.id-> user_friend.user_id , friend.id -> user_friend.friend_id
Better define full relation on the User and Friend models,
for example :
class User extends Eloquent {
function friends()
{
return $this->belongsToMany('User', 'user_friend', 'user_id', 'friend_id');
}
}
You can read much more in Laravel docs, HERE

Categories