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

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.

Related

What is the real query behind Laravel's Model::belongsTo() method?

For example, let's take a look at this model in a laravel project:
<?php
//...
class Post extends Model
{
//...
public function user()
{
return $this->belongsTo(User::class); // User model is previously defined
}
}
Now I can get the user (author) info of a specific post:
Post::find(1)->user()->username;
Of course laravel has to run some DB queries to get the data; and I wanna know what is the query behind this belongsTo() method.
You cant use the toSql() method to check query will run on laravel illuminate,
$sql = Person::query()->with('user')->find(1)->toSql();
dd($sql);
try use this
$result=Post::with('user')->find(1);
dd($result);
and look the response
belongs to is meaning to relation between tables
if the relation is one to one use belongsTo and hasOne
if the relation is one to many use belongsTo and hasMany

Laravel Job Batching table relationship with User model?

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

Laravel: why Eloquent Models methods should be invoked as fields

I am fairly new at Laravel, I have stumbled up on a very interesting concept in Laravel Eloquent Models. When I create a model lets say Tweet.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Tweet extends Model
{
use HasFactory;
public function user(){
return $this->belongsTo(User::class);
}
}
Then when I want to call the user method normally Tweet::first()->user() it dose not return the required result instead it returns (which I assume it is a closure or a reference):
Illuminate\Database\Eloquent\Relations\BelongsTo
While it works perfectly when I treated it as a filed and invoked it without the parenthesis Tweet::first()->user then I will get the actual data which is the uses's information.
Would you please let me know what is this concept, how it works, and how can we replicate it with our code.
As explained by #lagbox the difference is described in the manual under Relationship methods vs. dynamic properties
For more detail:
Tweet::first()->user() will return a query which can be used to retrieve the user (or be further refined) e.g. you can do Tweet::first()->user()->where('name', 'Bob')->first() to retrieve the user associated with the first tweet if the user's name is Bob. This makes no sense in this context but makes sense in the reverse direction where you can e.g. do User::first()->tweets()->whereDate('created_at', Carbon::today()) to get all of today's tweets.
When you do Tweet::first()->user this tries to get the relationship user if it's loaded or lazy loads it if it's not loaded. For more information on how to optimise this loading you can check Eager loading in the manual

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;

Using Laravel Eloquents HasManyThrough relation with multiple relations through polymorphism

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

Categories