I know that hasOneThrough has been introduced to the latest Laravel build, however I can't really upgrade right now. What is the best way to do this?
I have got a users table, which I can't change the structure off, but I need to assign a role to these users, so I have created a pivot table, I want to add a method to my User model to get the role, and the user can only have ONE.
Tables:
users user_roles user_assigned_roles
I could use hasManyThrough, but this would expect many and I want to return a single model rather than a collection .
You could manage this with a combination of one-to-one & one-to-many relationships, and then access the role through an accessor:
`users` 1 --- 1 `user_assigned_roles` m ---- 1 `user_roles`
So, in a UserAssignedRole model:
/** UserAssignedRole.php */
public function role()
{
return $this->belongsTo('App\UserRole');
}
Then in your User model:
/** User.php */
public function assigned_role()
{
return $this->hasOne('App\UserAssignedRole');
}
// defining an accessor for your role:
public function getRoleAttribute()
{
return $this->assigned_role->role; // <--- Access the role of 'UserAssignedRole'
}
So in your controller (or wherever you want) you could do:
/** UsersController.php */
public function myFunction()
{
$user = User::find(1);
$role = $user->role; // <--
dd($role->name);
//
}
PS1: I strongly suggest you to upgrade to the latest version of Laravel to make use of the new features and also for security reasons, fixes etc.
PS2: There is package called Eloquent Has Many Deep by Jonas Staudenmeir that manages this kind of relationship (and more) for you.
Related
I have default user model. Now I have another table for Administrators. Is there a way to return user fields (since admin is extending user) from admin model ? I found this example but its when admin_id is present in user model. I have one-to-one relation here.
class Admin extends User
{
protected $table = 'users';
public static function boot()
{
parent::boot();
static::addGlobalScope(function ($query) {
$query->where('is_admin', true);
});
}
}
This is the example I found. I'm not sure how can I return user fields from my admin model when its on different table.
The point is I want to be able to do something like this (call methods from users):
Admin::first()->posts()
Where posts method is not on Admin class but on user class.
Edit:
To explain it better. I have two tables, users and admins. They are connected in one-to-one relationship so I can do something like this:
$admin = Admin::first();
$posts = $admin->user()->posts();
but since Admin should have all fields from users table and one more field from admins table I'm looking for a way to do this:
$admin = Admin::first();
$posts = $admin->posts();
I don't want to add admin or something to users table, I still want to use admins table since I will need more fields there later.
If both tables have an equal id, use a trait to define your relationships:
trait UserRelationships {
public function posts() {
return $this->hasMany(Post::class, 'user_id');
}
}
class Admin {
use UserRelationships;
}
class User {
use UserRelationships;
}
You'll just have to be sure to explicitly declare the foreign key name in the relationship.
You could also extend the User model and override the $table property but this may present problems for various reasons since User properties exist on the user relationship and not on the Admin model.
I got a rather simple application where a user can report other users comments and recipes. I use a polymorphic relation to store the reports. Which works fine; however, I am now trying to get the offences that a user has made.
Getting users reports is not a problem, this can be done directly using user->reports() but I would very much like to get the reports in which other people has reported said user. I can get this to work using either the hasManyThrough relation or queries BUT only on one model at a time.
ex.
public function offenses() {
return $this->hasManyThrough('Recipe', 'Reports');
}
Or
->with('user.recipe.reports')
The problem is that my reportable object is not just recipes, it could be comments, images, etc. So instead of having to use multiple functions, the logical way would be to parse the relationship between hasManyThrough various parameters somehow.
Theoretically looking like this:
public function offenses() {
return $this->hasManyThrough(['Recipe', 'RecipeComments'], 'Reports');
}
Is this in any way possible? With some undocumented syntax? If not is there any clever workarounds/hacks?
Possible solution?
Would an acceptable solution be to add another column on my report table and only add offender_id like this?
ID | User_id | Offender_id | Reportable_type | Reportable_id
That would mean I could just make a relation on my user model that connects offences through that column. But would this be considered redundant? Since I already have the offender through the reportable model?
Models
Polymorphic Model
class Report extends Model {
public function reportable() {
return $this->morphTo();
}
public function User() {
return $this->belongsTo('App\User');
}
}
Recipe Model
class Recipe extends Model {
public function user() {
return $this->belongsTo('App\User');
}
public function reports() {
return $this->morphMany('App\Report', 'reportable');
}
}
Comment Model
class RecipeComment extends Model {
public function user() {
return $this->belongsTo('App\User');
}
public function reports() {
return $this->morphMany('App\Report', 'reportable');
}
}
Using your current model, you can receive all the reported models for a user by the following code:
$recipeCommentReport = RecipeComment::whereHas('reports',function($q){
return $q->where('user_id','=',Auth::user()->id)
});
$recipeReport = Recipe::whereHas('reports',function($q){
return $q->where('user_id','=',Auth::user()->id)
});
//Get all reports into one
$reports = $recipeReport->merge([$recipeCommentReport]);
This is messy at best, because:
We can't sort the results, given that we are using two separate db queries.
If you have other models who have a report relationship, just imagine the chaos.
The best solution, is as you have figured out above:
Add an offender_id column to your report table.
It is cleaner and follows the DRY principle.
Typical Case Scenarios
Get all Recipe Comment reports for User
Report::where('offender_id','=',Auth::check()->id)
->where('reportable_type','=','RecipeComment')
->get();
Count offense by type for User
Report::where('offender_id','=',Auth::check()->id)
->grouBy('reportable_type')
->select(Db::raw('count(*)'),'reportable_type')
->get();
I want to add some joins onto my Auth::user() query. How do I do this without creating a brand new query? I just want to be able to make the default call of Auth::user() different than:
SELECT * FROM `users` WHERE `id` = ?
to
SELECT * FROM users INNER JOIN user_icons ON user_icons.ID = users.iconid WHERE `id` = ?
I'm using the default model User class.
Laravel provides a way for you to extend the Auth functionality. First, you need to create a class that implements the Illuminate\Auth\UserProviderInterface. Once you have your class, you call Auth::extend() to configure Auth with your new class.
For your case, the easiest thing for you to do would be to create a class that extends Illuminate\Auth\EloquentUserProvider. You'll want to update the retrieveBy* methods to add in your custom joins. For example:
class MyEloquentUserProvider extends Illuminate\Auth\EloquentUserProvider {
public function retrieveById($identifier) {
return $this->createModel()->newQuery()->join(/*join params here*/)->find($identifier);
}
public function retrieveByToken($identifier, $token) {
// your code with join added here
}
public function retrieveByCredentials(array $credentials)
// your code with join added here
}
}
Once your class is fleshed out, you need to tell Auth to use it:
Auth::extend('eloquent', function($app) {
return new MyEloquentUserProvider($app['hash'], $app['config']['auth.model']);
});
The first parameter to the Auth::extend method is the name of the auth driver being used as defined in app/config/auth.php. If you want, you can create a new driver (e.g. 'myeloquent'), but you'd need to update your Auth::extend statement and your app/config/auth.php driver.
Once all this is done, Auth::user() will end up calling your MyEloquentUserProvider::retrieveById method.
Fair warning: I have not actually done this myself, and none of this is personally tested. You will probably want to check out the documentation (L4.1 docs, L4.2 docs) and look at the Laravel code.
Other notes:
People have already chimed in that this is probably not what you want to do. However, the this information may be helpful to you and others looking to extend Auth for some other reason.
Considering your inner join, if a user does not have an associated user_icons record, Auth::user() will not return a record anymore, and the user probably won't be able to log in at all.
If you have 1:n relation:
Add a "icons" table to you database with a foreign key "user_id".
Add a "Icon" Model to your models.
<?php
class Icon extends Eloquent{
...
}
?>
In Model Class "User" add a function:
public function icons() {
return $this->hasMany('Icon');
}
Now you can do this:
$userIcons = Auth::user()->icons();
I have two models in my Laravel 4.2 web application, User and Group. A user can be a member of many groups, and a group can have many members. Both models are thus joined with a many-to-many relationship:
<?php
class User extends Eloquent {
public function groups()
{
return $this->belongsToMany('Group');
}
}
class Group extends Eloquent {
public function users()
{
return $this->belongsToMany('User');
}
}
?>
One of my API resources is /groups, which lists all groups available within the app:
<?php
$groups = Group::with('users')->all();
?>
This works, however in the JSON response each user contains all fields from the users table (excluding of course those in the $hidden attribute). I would like this relationship to return only a specific set of fields instead of the whole table.
In other relationship types I can easily achieve this with the following statement (assume now that users may belong to only one group):
<?php
public function users()
{
return $this->hasMany('User')->select(['id', 'first_name', 'last_name']);
}
?>
However the above does not seem to work with many-to-many relationships. I came across this question which apparently refers to the same issue and it looks like this was not possible in Laravel 4.1. The author of the chosen answer, tptcat, provides a link to an issue on Laravel's Github issue tracker, but the link is no longer working and I couldn't figure whether this issue is still open in 4.2.
Has anybody come across this and successfully managed to solve it?
{
return $this->belongsToMany('User')->select(array('id', 'name'));
}
use this
The all method takes in an array of column names as a parameter.
If you look at the source, it takes * (which means everything) by default.
https://github.com/laravel/framework/blob/4.2/src/Illuminate/Database/Eloquent/Model.php#L624-L629
You can pass in the columns that you needed and it should return the results only with the specified columns.
<?php
$groups = Group::with('users')->all(array('first_column', 'third_column'));
Use like this.
<?php
class User extends Eloquent {
public function groups()
{
return $this->belongsToMany('Group')->select(array('id', 'name'));
}
}
class Group extends Eloquent {
public function users()
{
return $this->belongsToMany('User')->select(array('id', 'name'));
}
}
?>
Instead of selecting column in relationship, you can select column as below:
$groups = Group::with('users:id,first_name,last_name')->all();
And when you are selecting column in relationship, make sure that you are selected foreign key of relation table
Okay so I'm building a pretty large application in Laravel. Users manage their own virtual soccer teams. I have a users table then I have a teams table with team specefic things like name, level,and arena, etc. For the arena though I decided to add a arenas table and then add a arena_id column in the teams table instead of just adding the arena name to the teams table.
so here is the basic relantionship:
User hasOne Team
Team hasOne User
Team hasOne Arena
Arena hasOne Team
so if I wanted to get the arena for a user I call the method
$user = User::with('team')->where('username', '=', $username)->first();
$user->team->arena->arena_name;
and everything works fine; however I feel there is a much cleaner or simpler way of doing this. Is there or is this fine for the aplication?
There is nothing wrong with the way you are doing it. That is a perfectly good way of doing it for your needs. However something that might help is creating a getArenaFromUsername() method in the User model. Your User model would look something like this:
<?php
class User extends \Eloquent {
protected $fillable = [];
public function getArenaFromUsername($username)
{
$user = User::with('team')->where('username', '=', $username)->first();
return $user->team->arena->arena_name;
}
}
So then to get the arena name from a controller you just do:
$user = new User;
$arena = $user->getArenaFromUsername($username);
-----------------------------------------OR-----------------------------------------------
Or use dependency injection by doing the following in your controller using the same method we just created in the model:
protected $user;
public function __construct(User $user)
{
$this->user = $user;
}
then to use it you can use one line in any method in your controller like so:
$this->user->getArenaFromUsername($username);
These are all different ways of abstracting your query to make it more reusable and cleaner to call in your controller. Don't be afraid to make public methods in your model to call.
A couple things.
You can eager load the sub-relationship like so:
User::with('team', 'team.arena')...
You can also create an accessor function (http://laravel.com/docs/eloquent#accessors-and-mutators) on your User model to make it a first-class property on the User object:
// accessed via $user->arena
public function getArenaAttribute {
return $this->team->arena;
}
// accessed via $user->arenaName
public function getArenaNameAttribute {
return $this->team->arena->arena_name;
}