Get data from multiple sql tables using eloquent with laravel - php

What i want to achieve?
Show user which pacts he is following.
What I am trying
I have designed two tables which are 'pacts' & 'pacts_follwers'
Table 'pacts' has the details of all the pacts
Table 'pacts_follwers' has details of users following a particular pact.
These links will give you the images of both the tables
For Schema Refer Images
So how to get pacts that user is following.
What I have tried?
Sql Query
SELECT pacts.*, (SELECT pactsid FROM pacts_follwers WHERE pacts.id = pacts_follwers.pactsid
and pacts_follwers.userid = 2 ) as pactID FROM `pacts`
Sql query Result
This query will give pactId some value, where the value is null means the user is not following that pact. If this is the solution then i would need Eloquent for this which i am unable to make.
1st table pacts
id
title
about
created_at
updated_at
pactsImage
2nd table pacts_follwers
id
pactsid
userid
created_at
updated_at
Controller Code
$pacts = DB::select("SELECT pacts.*, (SELECT pactsid FROM pacts_follwers WHERE pacts.id =
pacts_follwers.pactsid and pacts_follwers.userid = ".Auth::id()." ) as pactID FROM `pacts`");

You need to setup hasManyThrough relationship for User and Pact.
class User extends Model {
public function pacts() {
return $this->hasManyThrough(
Pact::class,
PactFollower::class
'userid',
'pactsid'
);
}
}
I don't fully understand if you want to achieve "get user's all pacts" or "if pact is followed by user". Either way, you need to setup related relationships.
Or really simple (and not efficient way)
class Pact extends Model {
public function followers() {
return $this->hasMany(PactFollower::class, 'pactsid')
}
}
Now you can use something like
$userIdsForPact = Pact::followers()->pluck('userid');
if ($userIdsForPact->has($user->id)) {
// your operation
}
Edit: For "if pact is followed by user", you need to setup belongsToThrough relationship. It doesn't come out of the box with Laravel but staudenmeir/belongs-to-through package should serve you well.
After setting the relationship properly, you can use something like this.
Pact::with('user')->get();
Or add some methods in your Pact model:
public function followedByUser($user) {
return $this->users->has($user);
}

Related

How to use custom SELECT with JOINs and GROUP BY in Laravel model?

I want to use sophisticated SELECT query with JOINs and GROUP BY in Laravel model.
Сoncretely I want to make a messager in my application. Here is table "messages" with all messages. Now I want to create model called "Dialog". Keep in mind here is no table "dialogs", a dialog is a result of joining and grouping.
Example of query:
SELECT
cl.name AS client_name,
COUNT(m.id) AS messages_count,
MAX(m.created_at) AS last_message,
COUNT(m.id) > SUM(m.viewed_by_client) AS has_new_for_client,
COUNT(m.id) > SUM(m.viewed_by_user) AS has_new_for_user
FROM messages AS m
INNER JOIN clients AS c ON m.client_id = c.id
GROUP BY c.id
Of cource I can use raw SQL queries. But I want to use Eloquent relations later with all its benefits. For example:
$dialog->client->full_name
$dialog->client->order->ordered_items
I had an idea to create a VIEW in database from my query and to use this view as a fake table in the model. But it seems to me not ideal solution.
So, how can I use JOINs and GROUP BY in Eloquent when I do not have a real table for model entities? Or may be some different solutions for my task?
You can have a database table without an Eloquent model but not the other way around. That said, there's no rule against making more than 1 model per table. Not really standard practice though.
I experimented with making a model that would inherit from another model but the boot method didn't work as expected so I dropped it.
I think you could get all the information you take from that query with accessors in your Client model. Since your query has no where clause, a scope is not really necessary but it could also be done with that.
OPTION 1: Accessors
# App\Client
class Client extends Model
{
// Standard Eloquent relationship
public function messages()
{
return $this->hasMany(App\Message::class);
}
// Accessor $client->client_name
public function getClientNameAttribute()
{
return $this->name;
}
// Accessor $client->last_message
public function getLastMessageAttribute()
{
// Load relationship only if it hasn't been loaded yet
if(!$this->relationshipLoaded('messages'))
$this->load('messages');
// use max() method from collection to get the results
return $this->messages->max('created_at');
}
// Accessor $client->has_new_for_client
public function getHasNewForClientAttribute()
{
// Load relationship only if it hasn't been loaded yet
if(!$this->relationshipLoaded('messages'))
$this->load('messages');
return $this->messages->count() > $this->messages->sum('viewed_by_client');
}
// Accessor $client->has_new_for_user
public function getHasNewForUserAttribute()
{
// Load relationship only if it hasn't been loaded yet
if(!$this->relationshipLoaded('messages'))
$this->load('messages');
return $this->messages->count() > $this->messages->sum('viewed_by_user');
}
}
And then you can access all the properties dynamically
$dialog = Client::withCount('messages')->find($id);
$dialog->client_name;
$dialog->messages_count;
$dialog->has_new_for_client;
$dialog->has_new_for_user;
$dialog->last_message;
However if you're converting $dialog to an array or json format, accessors will be lost unless you append them. In the same way, you can hide the attributes you don't want to show.
This can be done globally for the model
protected $appends = ['client_name', 'has_new_for_client', 'has_new_for_user', 'last_message'];
protected $hidden = ['name'];
or locally for the query
$dialog->setHidden(['name']);
$dialog->setAppends(['client_name', 'has_new_for_client', 'has_new_for_user', 'last_message'];
OPTION 2: Query scopes
# App\Client
class Client extends Model
{
public function scopeDialog($query)
{
$query->select('name as client_name')
->withCount('messages') // the default name will be messages_count
->selectRaw('max(m.created_at) as last_message')
->selectRaw('count(m.id) > sum(m.viewed_by_client) as has_new_for_client')
->selectRaw('count(m.id) > sum(m.viewed_by_user) as has_new_for_user')
->join('messages as m', 'm.client_id', 'clients.id')
->groupBy('clients.id');
}
}
And then just call it like you would any scope Client::dialog()->...
OPTION 3: Just use whatever methods are already available instead of writing more logic
$dialog = Client::with('messages')->find($id);
// client_name
$dialog->name
// messages_count
$dialog->messages->count()
// last_message
$dialog->messages->max('created_at')
// has_new_for_client
($dialog->messages->count('id') > $dialog->messages->count('viewed_by_client'))
// has_new_for_user
($dialog->messages->count('id') > $dialog->messages->count('viewed_by_user'))
Create dialogs table and put 'dialog_id' column into the messages table. Each message has a dialog and a client. Create relationships in each model. So you can access attributes over models as you want. By doing this, this code works;
$dialog->client->full_name
$dialog->client->order->ordered_items
I am trying to detail example about how to get User Model's Accessor in another model with using relationship
Suppose, we have User table & Comment Table...
Now, Suppose I appends User's Profile Full URL in User model using "getProfilePhotoUrlAttribute" Method. when I call User model eloquent then it's appends User Profile Image automatically.
but Now I wants to get that user's profile Full URL in with Comments then we can't access Accessor using Join because with join we can join only out DataBase's Table Columns. If we have profile_photo_path column & doesn't have profile_photo_url named column as we define accessor function name then we can't access using jjoin. in this case we wants to use Relationship method
For example:-
Case :- 1 You wants to Get the user's comments with User details
In this case, User have one or more than one comments So we need to use One TO Many Relation
App/Models/User.php file
/**
* The accessors to append to the model's array form.
*
* #var array
*/
protected $appends = [
'profile_photo_url',
];
/**
* Get the URL to the user's profile photo.
*
* #return string
*/
public function getProfilePhotoUrlAttribute()
{
... here return full profile URL (concat profile_path with storage/public location path)...
}
/**
* Get the user's comments with User details.
*
* One To Many relation
*/
public function comments()
{
return $this->hasMany(Comment::class);
}
Now then, use Model eloquent Query like below
$user = User::with('comments')->where('id', '=', '2')->get();
echo '<pre>';
print_r($user->toarray());
Case :- 2 You wants to Get the user details of the all comments.
In this case, we need to use Many TO One Relation
App/Models/Comment.php file
/**
* Get the user details of the comments.
*
* One To Many (Inverse) / Belongs To
*/
public function user()
{
return $this->belongsTo(User::class);
}
then use Model eloquent Query like below
$comments = Comment::where('deal_id', '=', '45')->get();
print_r($comments->toarray());
foreach ($comments as $comment) {
print_r($comment->user->toarray());
echo $comment->user->profile_photo_url;
echo "<br/>";
}
NOTE:- I used Latest version - it is Laravel 8, So Syntax may vary as per your Laravel Version
For More Detail with Output Data check here my answer on another question
& you can check it in Laravel Official Documentation

Using eloquent relation

I am trying to understand how to effectively use Eloquent relationships to have some high level functions in the model.
I have a subscription app with 2 tables, 'users' and 'subscriptions'.
This is a legacy system so I cannot just change things in any way I want.
Table users (model App\User)
id
email
active (0/1)
join_date
address etc
phone
Table subscriptions (model App\Subscription)
id
user_id
box_id (what the person is subscribed to get)
amount
Users are marked active or not active.
I would like to have a static method on the Subscription model that will give me all the active subscriptions. This data is then fed into other parts of the application.
This is derived by joining subscriptions to users and filtering based on the active column.
The query is like this:
SELECT users.*, subscriptions.*
FROM subscriptions
JOIN users ON users.id = subscriptions.user_id
WHERE users.active = 1
Subscription model
class Subscription extends Model
{
public static function allActive()
{
// This works except it doesn't use the eloquent relationship
return static::where('users.active', 1)
->join('users', 'users.id', '=', 'subscriptions.user_id')
->select('users.*','subscriptions.*')
->get();
}
public function user()
{
return $this->belongsTo(User::class);
}
}
User model
class User extends Authenticatable
{
use Notifiable;
public function subscriptions()
{
return $this->hasMany(Subscription::class);
}
}
I would use it like this:
$subscriptions = \App\Subscription::allActive()->toArray();
print_r($subscriptions);
I have 2 questions.
How do I rewrite the allActive function to use the relationship I already defined? Any solution should generate SQL with a JOIN.
In the returned data, how do I separate the columns from the two separate tables so that it is clear which table the data came from?
Given the relationships you have wired up, to get only active subscriptions from the model class you will have to do it this way:
class Subscription extends Model
{
public static function allActive()
{
$activeSubcriptions = Subscription::whereHas('user', function($query){
$query->where('active', 1) //or you could use true in place of 1
})->get();
return $activeSubcriptions;
}
public function user()
{
return $this->belongsTo(User::class);
}
}
Thats working with closures in Laravel, quite an efficient way of writing advanced eloquent queries.
In the callback function you will do pretty much anything with the $query object, its basically working on the User model since you mentioned it as the first parameter of the ->whereHas
Note that that variable has to have EXACTLY the same name used in declaring the relationship
The above i suppose answers your first question, however its highly recommended that you do most of this logic in a controller file
To answer question 2, when you execute that get() it will return Subscription objects array so to access the info based on columns you will have to go like:
$subscriptions = \App\Subscription::allActive();
foreach($subscriptions as $subscription){
$amount = $subscription->amount; //this you access directly since we working with the subscription object
$box_id = $subscription->box_id;
//when accessing user columns
$email = $subscription->user->email; //you will have to access it via the relationship you created
$address = $subscription->user->address;
}

collection to string in laravel 5.2 query builder

I want to make query in laravel 5.2 to fetch agencies table which has a foreign key with organizations table using agencies.organization_id=organizations.id. Now Users table has also foreign key with organizations table using users.organization_id=organizations.id. Now how to fetch agency table that which agencies are linked with users_id.
public function postagency(Request $request) {
$user_id = $request->user_id;
$org_id = User::where('id', $user_id)->pluck('organization_id')->first();
$postagencies = agency::where('organization_id', $org_id);
echo $postagencies;
}
For what I understand is that an user can only be under one organisation and an organisation has many agencies. If not please say so and I will alter my answer.
First of all set your relationships inside your models. An example would be:
// User.php
public function organization()
{
return $this->belongsTo('App\Organization'); // App\Organization can be changed depending on the used namespace
}
More info can be found here. If you need some more examples just ask.
After you have created these relationships you can retrieve your agency like this:
$user= User::find($request->user_id);
if (!$user) ... // Check if user exists
$agencies = $user->organisation->agencies;
If I need to explain things in more detail just ask. Hope this helps :)

laravel5.1 retrieve not directly related attributes using eloquent

I've currently no idea how to get this done in a smart way. I would like to prevent writing tons of querys.
First my table design:
users:
|id|username|
tickets:
|id|user_id|
ticket_replies:
id|ticket_id|user_id|
files:
|id|ticket_replie_id|name
my controllers:
user:
public function tickets()
{
return $this->hasMany('App\ticket');
}
ticket:
public function ticket_replie()
{
return $this->hasMany('App\ticket_replie', 'ticket_id', 'id');
}
ticket_replie:
public function file()
{
return $this->hasOne('App\File', 'ticket_replie_id', 'id');
}
The relation is like following, a user has many tickets. Each ticket has many ticket_replies. A ticket_replie hasOne attachment (file).
Now I need to retrieve the name of a file for a given ticket & ticket_replie_id.
In my controller I use this at the moment:
$ticket = Auth::user()->tickets()->where('tickets.id', $id)->where('files.ticket_replie_id', $attachment_id)->firstOrFail();
Laravel generates me this query & error:
select * from `tickets` where `tickets`.`user_id` = 1 and `tickets`.`user_id` is not null and `tickets`.`id` = 43 and `files`.`ticket_replie_id` = 39 limit 1
Column not found: 1054 Unknown column 'files.ticket_replie_id' in 'where clause
The query must be something like:
select * from `tickets`, `files` where `tickets`.`user_id` = 1 and `tickets`.`user_id` is not null and `tickets`.`id` = 43 and `files`.`ticket_replie_id` = 39 limit 1
When I run this query in my database, it returns the needed informations. Is my way to retrieve the information okay? Where's my fault, because at the moment the query generated by Eloquent isn't working as described above. In case there's a easier way, just tell me.
It seems like eloquent isn't able to get a relation between ticket and file. How to tell eloquent there's a relation using ticket_replie?
If you already know the ticket_replie_id all you have to do is:
File::where('ticket_replie_id', $ticket_replie_id)->first();
In a more general situation, you would refer to Eager Loading.

Laravel belongsToMany function

I'm using Laravel to create a basic site with the following function: A user can follow certain topics. I have a 'users' table, a 'topics' table, and as a pivot table, I have a 'following' table.
users
----
user_id (primary)
user_first_name
etc..
following
---------
follow_id
user_id (foreign)
topic_id (foreign)
topics
------
topic_id (primary)
topic_name
etc...
I'm trying to create a page that displays all of the topics, but for the topics that the current user is following, I need to show an overlay on the box.
I have a User Model with the following function:
public function follows() {
return $this->belongsToMany('Topic', 'following', 'user_id', 'user_id');
}
However, I'm not too sure where to go from here (or whether this is right!)
Would be hugely grateful for any help.
First of all, you made a mistake on your follows method.
You have the same variable name on local and foreign id 'user_id'.
Then,
Did you already add a topic to an user ?
If yes, it would be great if you do the same as in your User model on the Topic model
public function followedBy()
{
return $this->belongsToMany('User', 'following', 'topic_id', 'user_'id');
}
From here, you can add a following topic to users by doing
$user->following()->attach($topic_id);
Or
$user->following()->attach([$first_topic, $second, $third, ...]);
You can use the sync method too, but that will delete all previous relationship between user and the topics which are not in the array.
To retrieve all information you can simply do the following:
foreach ($user->following as $topic) {};
/!\ Do not add parentheses to following otherwise you will get a QueryBuilder instead of a collection of the topics. /!\
If you want to add more filters (for example only active topics)
foreach ($user->following()->active()->get() as $topic) {}
Notice that here I added the parentheses which are necessaries because I do not directly want the topics but a QueryBuilder to filter the results.
Call the ->get() method when you are done filtering.
(This suppose you have a method called scopeActive() in your model)
See Laravel scope to do so : http://laravel.com/docs/eloquent#query-scopes
You can do the opposite on the topic side by doing :
foreach ($topic->followedBy as $user) {}
PS: sorry for my English, If you misunderstood something. Not my native language.
You sustain the following function in User Model
public function follows() {
return $this->belongsToMany('Topic', 'following');
}
and use below statement to retrieve the all topics of any user
$topics = User::find(1)->follows;
Where 1 is the user id for particular user.
1 In your setup you use non-default primary keys (topic_id / user_id / follow_id instead of id) so be sure to set:
protected $primaryKey = 'topic_id';
on each of your models accordingly.
2 I would suggest renaming the relation - follows is ambiguous, unless you have literally 3 models there.
3 Your relation for your current setup, like already suggested by #ChainList:
public function follows()
{
return $this->belongsToMany('Topic', 'following', 'user_id', 'topic_id');
}
4 In order to check if a user already follows given topic, do this:
// I Assume logged in user
$user = Auth::user();
$topics = Topic::all();
// somewhere in your view
#foreach ($topics as $topic)
{{ $topic->name }}
#if ($user->follows->contains($topic->id))
Already following this topic
#else
Follow this topic (put a link here or whatever)
#endif
#endforeach
With this you run just a single query for user's topics, when you call $user->follows for the first time.
My suggestion would be:
// leave id as primary key, it will make your life easier
// rename relation to
public function topics()
{
// rename table name to something meaningful
return $this->belongsToMany('Topic', 'topic_user'); // laravel convention
// or if you like topics_followers
}
Almost there, the follows() relationship needs to be like this:
public function follows() {
return $this->belongsToMany('Topic', 'following', 'user_id', 'topic_id');
}
Then you should be able to grab the topics associated to the current user by doing this:
$topicsUserIsFollowing = Auth::user()->follows;
To start following a topic, you can do this (assuming you have the topic's ID):
Auth::user()->follows()->attach($topic_id);
Edit
If you want to see if a topic is followed by someone, then in your Topic model put a function like this:
public function isFollowedBy($user)
{
return $user->follows->contains($this->topic_id);
}
So then you can do something like this:
$currentUser = Auth::user();
$topics = Topics::all();
foreach($topics as $topic) {
echo $topic->topic_name;
if( $topic->isFollowedBy($currentUser) ){
echo ' - [Already following this topic]';
} else {
echo ' - [Follow this topic]';
}
}
You'd want to put the loop in your view, this is just for illustration

Categories