Laravel: why Eloquent Models methods should be invoked as fields - php

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

Related

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

How can I mock a different model to retrieve a polymorphic relationship?

I have 3 data models, one of which extends the other:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Opinion extends Model
{
public function reactions()
{
return $this->morphMany('App\Models\Reaction', 'reactable');
}
...
}
namespace App\Models\Activity;
use App\Models\Opinion;
class ActivityOpinion extends Opinion
{
...
}
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Reaction extends Model
{
public function reactable()
{
return $this->morphTo();
}
...
}
The App\Models\Opinion model has a polymorphic relationship with the App\Models\Reaction model. I can retrieve all of the App\Models\Opinion reactions no problem, so I know the relationship works great.
My question is, how can I retrieve the same set of reactions from the App\Models\Activity\ActivityOpinion model? Because right now, it is looking for App\Models\Activity\ActivityOpinion as the relationship but I need it to look for App\Models\Opinion. Is it possible to mock another model in a polymorphic relationship?
This is because in a Polymorphic Relationship in the stored data (if leaved as default) the relationship type gets the class namespace (sort of) to specify wich model needs to be returned. That's why when you try to access to your reactions() relationship from ActivityOpinion it will look up for the App\ActivityOpinion value in the reactable_type.
You can customize the morph class to search in the model addind this:
Opinion.php
protected $morphClass = 'reaction';
This should be enough, if not, add it also in the ActivityOpinion model.
Note
This could breake some things when trying to search results using Eloquent. Check this other answer in order to address this possible inconviniance.
Update
I've just found out that you could do all this even easier with MorphMap. From the docs:
Custom Polymorphic Types
By default, Laravel will use the fully qualified class name to store
the type of the related model. For instance, given the one-to-many
example above where a Comment may belong to a Post or a Video,
the default commentable_type would be either App\Post or
App\Video, respectively. However, you may wish to decouple your
database from your application's internal structure. In that case, you
may define a "morph map" to instruct Eloquent to use a custom name for
each model instead of the class name:
use Illuminate\Database\Eloquent\Relations\Relation;
Relation::morphMap([
'posts' => 'App\Post',
'videos' => 'App\Video',
]);
You may register the morphMap in the boot function of your
AppServiceProvider or create a separate service provider if you
wish.

In Laravel what is the difference between use User; and /User

i want to know the difference between
use User;
and
/User
in laravel.
In my project i see in a controller say UserController it does this
It adds
use User;
at the top of the controller and in function it uses
User::find($id);
to get query results.
And some other Controller say CompanyController it does not use
use User;
However it does this
/User::find($id);
and gets the same result.
I am confused with its usage. Which should i follow, what does each type do, can ayone please explain.
You should use use User; in combination with User::find($id);
When you have the slash in front of it you basically combine the 2 above into one.
in depth:
The User class (according to your example) appears to have no namespace, yet the file you use it in has one.
In other words, the User class is defined in the root namespace.
Normally you can use classes from the root namespace without use or /, but not when the file you use it in has a namespace.
So you will have to specify the namespace of the object you are trying to use, either with use User; or /User::
While both act identical, most developers opt to use the use variant.
Both have same behavior but different to use.
Use User;
User::all();
is basically a reference to that model class that can be used in any class to get its static methods.
/user::all();
doesn't need to call the reference, this access the model directly so both are basically same.
IN your case you are making a reference to that model class
use User;
User::find(2);
it will call model to find the matching user;
where in Company Controller you didn't use any reference to model. you're accessing that model by just '/'. laravel provides this just for ease.

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.

Categories