Laravel Job Batching table relationship with User model? - php

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,
]);

Related

Trouble understanding laravel eloquent models

It's been a some time since I've programmed with Laravel and I'm stumped by the relations I need in order to create a foreign key -link with 2 models.
I have a database where there's a table "company" containing companies, and I also have a table called "projects", which contains projects.
So the Projects- table contains a column called "employercompany" with a foreign key constraint to the company-table.
I'm trying to print out the company's name in a project page in laravel with
{{$project->employercompany->name}}
But keep getting "Trying to get property of non-object"
My model pages look like this:
//Company
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Company extends Model
{
public function projects()
{
return $this->hasMany('App\Projects', 'employercompany', 'id');
}
}
and
// Projects
namespace App;
use Illuminate\Database\Eloquent\Model;
class Projects extends Model
{
public function employercompany()
{
return $this->belongsTo('App\Company');
}
}
I know this is an easy problem but I just can't wrap my head around it
*****EDIT*****
I found the solution. Thanks Radical for providing some insight.
I ended up changing the employercompany column to company_id and, all the others as such too.
After that I fiddled around what I'm guessing fixed the thing was that I changed my database search query from
DB::table('projects')->get();
into
Project::all();
Don't know if that was the change needed but it sure feels like it was.
When defining a belongsTo relation like you have done, Laravel will try and 'guess' the keys you are using based on the class name. In this case, it will look for a column company_id on your Projects model since your model is called Company.
Like you have done for the projects() method on your Company model, you should tell Laravel that you are using the employercompany column to reference the Company model:
public function employercompany()
{
return $this->belongsTo('App\Company', 'employercompany');
}
For more information, see the relevant documentation here.
In addition, to make things easier, it might be worthwhile to try - if possible - to adhere to what Laravel 'expects' your database columns to be called, so situations like this are resolved automatically.
It should be like
$project = Project::find($id); //id of project
$compnany_name = ($project->employercompany()->get())->name;

How do I use an intermediary to connect tables in Laravel?

I have recently started working with Laravel and am looking to really start digging into it and all of its power. I am currently trying to do something fairly simple, pair a player with their corresponding stats. I have 3 classes and corresponding tables in my database:
Player
Stats
PlayerStatsLink
The PlayerStatsLink table has a 'player_id' and a 'stats_id' column that connects each player to their corresponding stats. Where I am getting somewhat confused is how to use the belongsTo and hasOne methods in this situation with the link. Right now here is what my classes look like:
Player.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Player extends Model
{
public function stats()
{
return $this->hasOne('App\Stats');
}
}
Stats.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Stats extends Model
{
public function players()
{
return $this->belongsTo('App\Player');
}
}
I am confused as to how to incorporate my link class into the mix, even though I'm pretty sure it is the right thing to do based on what I have learned about databases and structures. I have considered adding a player_id column to the Stats table to make what I have right now work, but I'm not sure if that is the right way to do this.
What I would ideally like to be able to do to access this data after the PlayerStatsLink has connected a player with corresponding stats is something like this:
{{ $player->stats->points }}
Okay let us assume you have 3 models: User, Stat and GameLog. I suggest you to use singular name for your models to avoid any confusion.
let us start with creating them:
php artisan make:model --migration Stat
to create both Stat model and stats table.
php artisan make:model --migration GameLog
to create both GameLog model and game_logs table.
This is the easiest way to create models and tables and bind them together and avoid typos. User model and corresponding table is present with fresh laravel installation.
in you User model:
public function gameLogs(){
return $this->hasMany(GameLog::class);
}
public function stats(){
return $this->hasManyThrough(Stat::class, GameLog::class);
}
in GameLog model:
public function user(){
return $this->belongsTo(User::class); // each gamelog belongs to just one user
}
public function stat(){
return $this->hasOne(Stat::class);
}
then in your Stat model:
public function gameLog(){
return $this->belongsTo(GameLog::class);
}
Please remember to construct you database also. If you faced problem in it please let me know and I will help you with.
Now if you want to query a User gamelogs you simply need to:
$user = User::find(1);
$gamelogs = $user->gameLogs;
since $user has many gamelogs you need to iterate trough them:
foreach(gamelogs as gamelog){
//do your logic
}
also if you want to load stat relationship of gamelog and use it then please read section eager-loading in laravel documents and learn about that.
I hope the explanation is clear and enough. If not please let me know. But I have a suggestion for a better database structure. I think it is better to merge stats table and game_logs table together and omit the model GameLog or Stat. Practically it is exactly the same.

how to run custom code after every $model->save() function in yii2

I want to execute my custom code after or before every $model->save() in Yii2.
I want to perform this globally like using components, etc.
I want to create a user activity log to store how many times a user insert or update any rows in database table, so for this I want to run some code when ever data inserted or update in tables.
Any help or suggestion will appreciated.
As #patryk mentioned ActiveRecord has beforeSave and afterSave methods.
I use something like the following to store a created date for new records (and updated date when existing records are updated). The code in the example is, of course, trivial but it allows you to use any arbitrary code you need, see the layout and how to split code for 'new' records and existing.
This overridden method can be added to any model class which extends ActiveRecord to allow the parent beforeSave to be called correctly also.
/**
* #inheritdoc
*/
public function beforeSave($insert)
{
if ($insert) {
// This is a new instance of modelClass, run your 'insert' code here.
$this->created_date = time();
}
// Anything else will be run any time a model is saved.
$this->updated_date = time();
return parent::beforeSave($insert);
}
edited to add:
if the code to be run is the same for each model you could create a trait and use the trait in each model to allow you to change the behaviour in one place. Or create a custom ActiveRecord class to override the beforeSave method for each subclass.
Create new class(MyActiveRecord) which extends \yii\db\ActiveRecord
Use extends MyActiveRecord to all your project models
Ex:
class MyActiveRecord extends \yii\db\ActiveRecord
{
public function afterSave($insert, $changedAttributes){
//This will called after every model saved
return parent::beforeSave($insert,$changedAttributes);
}
}
In your project other models
class Customer extends app\models\MyActiveRecord
{
}
Yii2 ActiveRecord class has beforeSave and afterSave methods. https://github.com/yiisoft/yii2/blob/master/framework/db/BaseActiveRecord.php#L926
But maybe it would be better to do such operation on database triggers?

Laravel 5.1 - Dry on same characteristics models

In my Laravel 5.1 App I have a lot of aux Models with the same structure. I was thinking in the posibility of make one model and controller for using all of them, but I cannot figure how to do.
I explain, all the database aux tables have the fields ID and name, and are made for CRUD operations and for filling the forms all over the App.
Is possible to specify the table on the methods implemented by Laravel? I mean, stablish the table on construct, on get(), etc. This would made the work a much more simple if I could do AuxTable::create("sex") or even in requests like $request->auxtable("studies")->get().
Am I explaining?
you can do it with single model like below in Model class there is a method called setTable($table) which can set the table name you want to use so consider below
use Illuminate\Database\Eloquent\Model;
class AuxTable implements Model {
//other class properties
}
in your controller use the model like below
class SampleController extends BaseController {
public function index() {
$model = new AuxTable;
$model->setTable('sex');
$model->get();
}
}
this should do the trick

Laravel modify Auth::user() query?

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();

Categories