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();
Related
I am using Laravel Job Batching feature and I have dashboard where I display the progress of Batch(Processed, Failed, Pending jobs … etc.).
Each user has it's own dashboard and I want to display the progress of Batch based on logged in user, but I can't see any relationship with User model with batch table job_batches.
Is it possible to somehow make relationship with those tables? or any alternative?
Thanks
That is possible, but there is a lot of hoops to go through. This could also be a question about, a general approach to extending functionality of Laravel.
Some quick assumption is that you use some sort of Authentication when creating the batches, so you can do Auth::user()->id.
Create your user_id for the job_batches table with a migration.
Schema::table('job_batches', function (Blueprint $table) {
$table->unsignedBigInteger('user_id')->after('name')->nullable();
$table->foreign('user_id')->references('id')->on('users');
});
Laravel uses a BatchRepository to create the Batches in the job_batches table, extend this and add our logic to insert Users into the row. I have added the custom repository, to App\Repositories namespace. In general use the current logic and update the user_id after the core Laravel logic has been executed.
<?php
namespace App\Repostories;
use Illuminate\Bus\DatabaseBatchRepository;
use Illuminate\Bus\PendingBatch;
use Illuminate\Support\Facades\Auth;
class BatchRepository extends DatabaseBatchRepository
{
public function store(PendingBatch $batch)
{
$batch = parent::store($batch); // TODO: Change the autogenerated stub
$this->connection->table($this->table)
->where('id', $batch->id)->update([
'user_id' => Auth::user()->id,
]);
return $batch;
}
}
For Laravel to use your new class, you need to extend the current class in the Container. Third parameter is the table name, assuming you are using the default table. This is done in a provider. Either put it in existing provider, or create a new one, remembers to register it.
use Illuminate\Bus\BatchFactory;
use Illuminate\Bus\BatchRepository;
use Illuminate\Database\Connection;
use App\Repostories\BatchRepository as CustomBatchRepository;
...
public function register()
{
$this->app->extend(BatchRepository::class, function () {
return new CustomBatchRepository(resolve(BatchFactory::class), resolve(Connection::class), 'job_batches');
});
}
Tested with the following snippet, this will add user_id to the table rows.
Bus::batch([new TestJob(), new TestJob()])->dispatch();
The relationship
BatchRepositories returns a Batch that is not an Eloquent Model. So i would suggest creating your own Eloquent model for relationship purposes and make logic to convert it into the Batch when you want to have the batch functionality at hand eg. finished().
Firstly Eloquent Model for your Batch.php. Meanwhile also preparing the toBatch() functionality, to convert Eloquent model to Batch class.
namespace App;
use Illuminate\Bus\BatchRepository;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Batch extends Model
{
use HasFactory;
protected $table = 'job_batches';
public function toBatch()
{
return resolve(BatchRepository::class)->toBatch($this);
}
}
Create your relationship method on your User.php.
public function batches()
{
return $this->hasMany(Batch::class);
}
I tested the relationship setup with the following snippet, which worked.
User::first()->batches->first()->toBatch();
Secondly imagine having multiple batches, you would be able to get the Batch classes with higher order functions easily. Or else use them as a proper relationship.
User::first()->batches->map->toBatch();
Note
Be careful to import correct Batch and BatchRepository classes. I added imports to secure you include the correct ones, also the following snippet to the provider, makes you able to instantiate my custom batch repository.
use App\Repostories\BatchRepository as CustomBatchRepository;
$this->app->bind(CustomBatchRepository::class, function () {
return new CustomBatchRepository(resolve(BatchFactory::class), resolve(Connection::class), 'job_batches');
});
At your own risk, you can see my solution, in a rough testing ground created for this question. There is a controller and relationship on the user. Not certain if there is leftovers for other StackoverFlow projects.
Only I modify this for when is a job executed locally:
$this->connection->table($this->table)
->where('id', $batch->id)->update([
'user_id' => Auth::user()->id ?? null,
]);
I'm currently trying to use Laravel Relationships to access my achievements Model using User model, I use this relationship code:
public function achievements()
{
return $this->hasMany('App\Models\User\Achievement');
}
I can easily make some eloquent queries, however I can't access any method that I created there, I can't access this method:
class Achievement extends Model
{
public function achievementsAvailableToClaim(): int
{
// Not an eloquent query
}
}
Using the following code:
Auth::user()->achievements()->achievementsAvailableToClaim();
I believe that I am using this Laravel function in the wrong way, because of that I tried something else without using relationship:
public function achievements()
{
return new \App\Models\User\Achievement;
}
But that would have a performance problem, because would I be creating a new class instance every time I use the achievements function inside user model?
What would be the right way of what I'm trying to do?
it's not working because your eloquent relationship is a hasMany so it return a collection. you can not call the related model function from a collection.
you can var dump it on tinker to understand more what i mean.
You can use laravel scopes.Like local scopes allow you to define common sets of constraints that you may easily re-use throughout your application.
In your case you use this like, Define scope in model:
public function scopeAchievementsAvailableToClaim()
{
return $query->where('achivement_avilable', true);
}
And you can use this like :
Auth::user()->achievements()->achievementsAvailableToClaim();
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.
I have a package media library by spatie. I need to get table name of the model.
I know that I can do this:
public function getPath(Media $media) {
$name = (new $media->model())->getTable()
}
But this creates a new query. I don't need to create an extra query on database. In table media, I have a column a model_type, where records can be like this: App\ModelName. Maybe I can get names of the model without a query?
There is an answer in laravel framework github:
https://github.com/laravel/framework/issues/1436 .
So it seems you will need to extend Media model.
Example from github
class BaseModel extends Eloquent {
public static function getTableName()
{
return with(new static)->getTable();
}
}
class User extends BaseModel {
}
User::getTableName();
I don't think "new model()" created a query on the database, it just spawns a new object instance of the model class. I don't know the library by heart, but given that it's a Spatie library, it probably functions very similar like Eloquent does, which has the same behaviour.
I am currently working on a web app that has been set up using the Repository/Service Layer Design Pattern, i.e. I have service layer that does any necessary business logic before running any methods within the repository. I have facades for each one of my models which access their respective service layers, and this has been fine for the most part. However, now that I am trying to set up Eloquent relationships, the facades seem to be causing a massive headache as I am not sure which direction I should be going.
Take the following code:
class Account extends Eloquent {
// Our table name
protected $table = "accounts";
// Our primary key
protected $primaryKey = "id";
/**
* Role Relationship
*
* Returns a list of roles associated with
* this account
*/
public function roles() {
return $this->hasMany('Role');
}
}
This will not work as is, because instead of using the entity class of Role, it is using the Role Facade. I have figured out a workaround for this, by setting an alias for the Entity with a slightly different name, such as RoleEntity so that
public function roles() {
return $this->hasMany('RoleEntity');
}
will work, however this doesn't seem like the most optimal solution.
My question is, is the practice ok? Or better yet, should this be happening at all? And if not, how do I fix it/where did I go wrong?
You have two classes with the same name in the same namespace. Use different namespaces so you can use the same class names.
I usually use \Models to locate my models classes.
At the top of each model file:
namespace Models;
In your controller or any part of your app:
\Models\Role::first();
Note that changing the namespace on your model will require you to add the namespaces of other classes i.e. Str, Eloquent, Url, Redirect, etc.
use Eloquent;
use URL;
In your model, you also have to pass the namespaces in the relationship functions, i.e.:
public function roles() {
return $this->hasMany('\Models\Role');
}