Laravel/Eloquent - Order By a Model's Relationship Without Using Table Names - php

I have a Person eloquent model that belongsTo an Address. My Laravel version is 4.2.5 and I am using PostgreSQL.
class Person extends Eloquent {
public function address() {
return $this->belongsTo('Address');
}
}
My aim is to get a collection of Person resources that are sorted by the address_1 field of their related Address model.
I can accomplish this by referencing table names as show below, but I want to do it instead with Eloquent relationships, since I do not want to deal with tables for abstraction purposes.
Person::join('addresses', 'persons.id', '=', 'addresses.person_id')
->orderBy('address_1', 'asc')->get();
I have attempted the following Eloquent method without success.
Person::with('address')->whereHas('address', function($q)
{
$q->orderBy('address_1', 'asc');
})->get();
This query fails with the error message:
Grouping error: 7 ERROR: column \"addresses.address_1\" must appear in the
GROUP BY clause or be used in an aggregate function
In response to this, I tried adding this line above the orderBy statement which causes the query to succeed, but the ordering has no effect on the resulting Person collection.
$q->groupBy('address_1');
I would much appreciate a solution where I do not have to reference table names if it is possible. I have exhausted all resources on this subject, but surely this is a common use case.

Here you go:
$person = new Person;
$relation = $person->address();
$table = $relation->getRelated()->getTable();
$results = $person->join(
$table, $relation->getQualifiedForeignKey(), '=', $relation->getQualifiedOtherKeyName()
)->orderBy($table.'.address_1', 'asc')
->get();

Related

Laravel Where Clause To Search In Columns Which Are Not In The Database

I have a table organisations with a number of columns, namely id, name, created_at and updated_at.
In my Organisation model, I added a custom attribute by adding the following code in the model:
// Add custom attributes
protected $appends = [
'device_count',
];
and:
public function getDeviceCountAttribute()
{
// Count organisation's devices
$device_count = Device::where('organisation_id', '=', $this->id)->count();
return $device_count;
}
In my controller, I am trying to search (using the where clause) by the device_count attribute, but I'm getting an error since this is not a real column in my database table.
This is how I'm searching:
$organisations = Organisation::query();
$organisations = $organisations->where('device_count', '=', 0);
$organisations = $organisations->get();
This is the error:
[2020-10-14 12:29:27] local.ERROR: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'device_count' in 'field list'...
Is there an alternative to the where clause in order to search by device_count please?
You can't query against attributes like that, they only exist on the model and do not exist in the database. However, you don't need to create a getter for that.
Laravel has a withCount() method you can use instead of that attribute - which is the correct approach of obtaining relational counts.
So a query with that attribute would look like
$organisations = Organisation::withCount('devices')->get();
The resulting models will now have a device_count attribute on them, that you can use in your views.
Then in your query, to filter those records that has at least a certain relational count, you can do
$organisations = Organisation::has('devices', '=', 0)->get();
Since you're looking for those that do not have any relation, you can replace it with doesntHave(). The above snippet is included to show how you can query against a specific set of relational counts.
To query only those that don't have any related devices, simply do
$organisations = Organisation::doesntHave('devices')->get();
This assumes that you have defined a devices relation on your Organisation class.
public function devices() {
return $this->hasMany(App\Models\Device::class); // Or App\Device::class if your models are not in the Models namespace/directory
}
The approaches above are the Laravel-way of doing it - and you should therefor remove your getDeviceCountAttribute() method and the corresponding device_count from $appends.
As per your requirement you want to get those Organisations which doesnot have any devices
For that, you need to use doesntHave method.
Make a relationship in the Organisation model.
public function devices() {
return $this->hasMany('App\Models\Device','organisation_id','id');
}
Now call the mothed in controller/
$organisations = Organisation::doesntHave('devices')->get();
it will return you those organisations which devices are not exists.

How to query a relationship through a relationship collection

I have a user and a department, a user can belong to many departments. I'm trying to define a scope on the user model to only show users of departments that the current user is a part of:
public function scopeVisible($query) {
$user = Auth::user();
return $user->departments()->users();
}
I've looked at the documentation and can't seem to find a way to do this.
I tried mapping but got strange errors where a method call would be on a query builder instead of a collection even though the object is a collection.
As Laravel docs says you can use whereHas to add customized constraints for more specific queries:
If you need even more power, you may use the whereHas and orWhereHas
methods to put "where" conditions on your has queries. These methods
allow you to add customized constraints to a relationship constraint.
Then this should work:
public function scopeVisible($query) {
$user_id = Auth::user()->id;
return $query->whereHas('departaments', function($query) use ($user_id) {
$query->where('user_id', '=', $user_id);
});
}
The error you are getting about "the method call must be on a query builder instead of a collection" it's because on your code you are returning the result of $user->departments()->users(), which is a collection of Departments, you should return the query builder instead.

Laravel relationship manytomany paginate issue

I have 3 tables / models
User (has an id)
Articles (has an id )
UserArticles (has an id, article_id and user_id)
I am a little confused on how I would set up the relationship so that I will be able to get all articles connected to a user so I can set up a call like so in my controller:
$articles = User::find(Auth::user()->id)->articles->paginate(20);
I figured this was a manytomany relationship so I am playing around with this code inside the User model:
public function articles()
{
return $this->belongsToMany('App\Article', 'user_saved_articles', 'user_id', 'article_id');
}
Now this works, as long as I don't call paginate() on the controller function I'd like to use above. Here is where my real issue lies now, so it works with
$articles = User::find(Auth::user()->id)->articles;
but with this:
$articles = User::find(Auth::user()->id)->articles->paginate(20);
it comes up with the error:
FatalErrorException in UserController.php line 217:
Call to undefined method Illuminate\Database\Eloquent\Collection::paginate()
I can't figure out why I can't paginate on this, as I can with all my other queries.
If you call an eloquent relation as an attribute User::find(Auth::user()->id)->articles it will automatically execute the SQL, and return a Collection. The error is telling you that you have a Collection, which you can't call paginate() on.
If you want to reference the relationship to add more statements you need to call it as a function User::find(Auth::user()->id)->articles(). This will return a QueryBuilder instance that you can add statements to, and/or paginate.
So this should work:
$articles = User::find(Auth::user()->id)->articles()->paginate(20);

Laravel, How to use where conditions for relation's column?

I'm using Laravel and having a small problem with Eloquent ORM.. I can get this working simply with SQL query using a JOIN but I can't seem to get it working with Eloquent!
This is what I want, I have two tabels. one is 'Restaurants' and other is 'Restaurant_Facilities'.
The tables are simple.. and One-To-One relations. like there is a restaurant table with id, name, slug, etc and another table called restaurant_facilities with id, restaurant_id, wifi, parking, etc
Now what I want to do is.. load all restaurants which have wifi = 1 or wifi = 0..
How can i do that with Eloquent ? I have tried eager loading, pivot tables, with(), collections() and nothing seems to work!
The same problem I have for a Many-To-Many relation for cuisines!
I have the same restaurant table and a cuisine table and a restaurant_cuisine_connection table..
but how do I load all restaurants inside a specific cuisine using it's ID ?
This works.
Cuisine::find(6)->restaurants()->get();
but I wanna load this from Restaurant:: model not from cuisines.. because I have many conditions chained together.. its for a search and filtering / browse page.
Any ideas or ways ? I've been struggling with this for 3 days and still no answer.
Example Models :
class Restaurant extends Eloquent {
protected $table = 'restaurants';
public function facilities() {
return $this->hasOne('Facilities');
}
}
class Facilities extends Eloquent {
protected $table = 'restaurants_facilities';
public function restaurant() {
return $this->belongsTo('Restaurant');
}
}
PS :
This seems to be working.. but this is not Eloquent way right ?
Restaurant::leftJoin(
'cuisine_restaurant',
'cuisine_restaurant.restaurant_id',
'=', 'restaurants.id'
)
->where('cuisine_id', 16)
->get();
Also what is the best method to find a count of restaurants which have specific column value without another query ? like.. i have to find the total of restaurants which have parking = 1 and wifi = 1 ?
Please help on this.
Thank you.
I don't see anything wrong with doing the left join here, if you have to load from the Restaurant model. I might abstract it away to a method on my Restaurant model, like so:
class Restaurant extends Eloquent {
protected $table = 'restaurants'; // will be default in latest L4 beta
public function facility()
{
return $this->hasOne('Facility');
}
// Or, better, make public, and inject instance to controller.
public static function withWifi()
{
return static::leftJoin(
'restaurant_facilities',
'restaurants.id', '=', 'restaurant_facilities.restaurant_id'
)->where('wifi', '=', 1);
}
}
And then, from your routes:
Route::get('/', function()
{
return Restaurant::withWifi()->get();
});
On the go - haven't tested that code, but I think it should work. You could instead use eager loading with a constraint, but that will only specify whether the facility object is null or not. It would still return all restaurants, unless you specify a where clause.
(P.S. I'd stick with the singular form of Facility. Notice how hasOne('Facilities') doesn't read correctly?)
I stumbled across this post while trying to improve my REST API methodology when building a new sharing paradigm. You want to use Eager Loading Constraints. Let's say you have an api route where your loading a shared item and it's collection of subitems such as this:
/api/shared/{share_id}/subitem/{subitem_id}
When hitting this route with a GET request, you want to load that specific subitem. Granted you could just load that model by that id, but what if we need to validate if the user has access to that shared item in the first place? One answer recommended loading the inversed relationship, but this could lead to a confusing and muddled controller very quickly. Using constraints on the eager load is a more 'eloquent' approach. So we'd load it like this:
$shared = Shared::where('id', $share_id)
->with([ 'subitems' => function($query) use ($subitem_id) {
$query->where('subitem_id', $subitem_id)
}]);
So where only want the subitem that has that id. Now we can check if it was found or not by doing something like this:
if ($shared->subitems->isEmpty())
Since subitems is a collection (array of subitems) we return the subitem[0] with this:
return $shared->subitems[0];
Use whereHas to filter by any relationship. It won't join the relation but it will filter the current model by a related property. Also look into local scopes to help with situations like this https://laravel.com/docs/5.3/eloquent#local-scopes
Your example would be:
Restaurant::whereHas('facilities', function($query) {
return $query->where('wifi', true);
})->get();
Restaurant::whereHas('cuisines', function($query) use ($cuisineId) {
return $query->where('id', $cuisineId);
})->get();
To achieve the same thing with local scopes:
class Restaurant extends Eloquent
{
// Relations here
public function scopeHasWifi($query)
{
return $query->whereHas('facilities', function($query) {
return $query->where('wifi', true);
});
}
public function scopeHasCuisine($query, $cuisineId)
{
return $query->whereHas('cuisines', function($query) use ($cuisineId) {
return $query->where('id', $cuisineId);
});
}
}
For local scopes you DO NOT want to define them as static methods on your model as this creates a new instance of the query builder and would prevent you from chaining the methods. Using a local scope will injects and returns the current instance of the query builder so you can chain as many scopes as you want like:
Restaurant::hasWifi()->hasCuisine(6)->get();
Local Scopes are defined with the prefix scope in the method name and called without scope in the method name as in the example abover.
Another solution starring whereHas() function:
$with_wifi = function ($query) {
$query->where('wifi', 1);
};
Facilities::whereHas('restaurant', $with_wifi)
Nice and tidy.
Do you absolutely have to load it from the Restaurant model? In order to solve the problem, I usually approach it inversely.
Facilities::with('restaurant')->where('wifi' ,'=', 0)->get();
This will get all the restaurant facilities that match your conditions, and eager load the restaurant.
You can chain more conditions and count the total like this..
Facilities::with('restaurant')
->where('wifi' ,'=', 1)
->where('parking','=', 1)
->count();
This will work with cuisine as well
Cuisine::with('restaurant')->where('id','=',1)->get();
This grabs the cuisine object with the id of 1 eager loaded with all the restaurants that have this cuisine

Eloquent how to search in relation's values

I have the following tables with the following fields:
person: id, lastname, firstname
phoneboookentry: id, name
I have the following two models:
class Person extends Eloquent
{
public static $table = 'personphonebookentries';
public function phonebookentry()
{
return $this->has_one('Phonebookentry', 'id');
}
}
class Phonebookentry extends Eloquent
{
public function person()
{
return $this->belongs_to('Person', 'id');
}
}
I can't figure out how to get all Persons that have a specific firstname OR a specific number.
I've tried :
Person::or_where('firstname', 'LIKE', '_name_')->phonebookentry()->or_where('number', '=', '_number_')->get();
I need help on finding out how to get that done :)
Thanks for any help!
In this case a simple relation not gonna help as you want to constrain the People results based on the relation which is a second query.
With this schema you would need to do a join, something like this in case you add a person_id to the phoneboookentry table:
Person::join('phoneboookentry', 'person.id', '=', 'phoneboookentry.person_id')
->where('person.firstname', 'LIKE', "%$name%")
->or_where('phoneboookentry.number', '=', $number)
->get('person.*');
But for every one-to-one relationship it is strongly adevised to think about the schema, there is a high probability that you'll be better off merging the tables. Overnormalization is not helping usually.
Also a few miscellaneous points:
You have "personphonebookentries" as a table, but you said you have person table.
Specifying id as a foreign key is not a good thing, keeping the two table in sync just creates problems.
It is unnecesary and wrong to use or_where() everywhere, just use it where it when it's neccesary.
From the point when you run any query or relational method on a model you are working on a query object, you can't execute relational methods on query objects. (phonebookentry()) You have to have a result model.

Categories