Laravel 5 - API Resources with nested relations - php

First of all I've started to learn Laravel few weeks ago so sorry if I'm not using the right words while I'll explain my problem.
I'm trying to write some API to retrieve all posts in my DB and inside post's info I'd like to retrieve user's info related to every post (like username, id etc.)
User Model:
public function post()
{ return $this->hasMany(Post::class); }
Post Model:
public function user()
{ return $this->belongsTo(User::class); }
then in my PostResource I try to return the post's data from DB
Post Resource:
public function toArray($request)
{
return [
'id' => $this->id,
'user_id' => $this->user_id,
'user_info' => HERE_I_WANT_USER[USERNAME,ID ETC.]
'body' => $this->name
];
}
and in my PostController I've this function that return my collection:
public function show() {
return PostResource::collection ( Post::get() );
}
So every post is linked to every author thanks to "user_id" value.
Here is my question: What are the steps to achieve this?
I've already red Laravel Doc "https://laravel.com/docs/5.5/eloquent-resources#conditional-relationships" making and UserResource and doing the same steps that I did before, but I'm not able to retrieve any data because my user's info return empty.
I'd like to understand better what are the steps.

You would just call it like it is part of the Post model. Something similar to this:
return [
'id' => $this->id,
'user_id' => $this->user_id,
'user_info' => $this->user
'body' => $this->name
];
I'm assuming that $this is a Post Model. If its not, then you'll want to find it and use it instead like $post->user. Additionally, the name of the method will be whatever your relationship function is called (with out the parenthesis), so if that ever changes, you'll have to update this. Finally, this will return a User Model, so you can interact with it like a normal model ($post->user->username or whatever).
Here is the Laravel Relationship documentaion for further reference.

Related

Avoiding n queries on API list call for database-calculated model attributes

I am cleaning up a quite messy php laravel 8 project right now.
Currently my ressource looks like this:
class MyModelSimpleResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'my_calculated_attribute' => MyModel::whereRaw('fk_id = ? AND model_type = "xyz"', [$this->id])->count(),
];
}
}
My problem is that on calling the API endpoint, for every record of MyModel it creates a separate query to calculate my_calculated_attribute.
I know that you can define foreign key constrains in the model like and query like this:
MyModel::with('myFkModel')->get()
This works great for foreign keys. But how can I avoid n queries when I need this my_calculated_attribute.
Thanks a lot!
PS: I know that raw queries are a bad idea and I know that the resource is supposed to transform data and not query it. 😅
Ok, I figured it out. I don't only can define foreign keys in my model! I just defined a new relationship:
public function myFkModels(): HasMany
{
return $this->hasMany(MyFkModel::class);
}
public function myFkWithCondition(): HasMany
{
return $this->myFkModels()->where('model_type ', 'xyz');
}
This I can put in my with statement like this:
MyModel::with('myFkModels', 'myFkWithCondition')->get()

how to add an existing_model as many-to-many relation to a dummy_mode in laravel

I am trying to make a testcase within laravel.
I have a fake User model (which dosent exists in DB), and creating it using faker->make,
and a real Role model which exists in DB,
these two have a many-to-many relationship
in my testcase, i am going to associate them like here :
public function testAccess()
{
$user = factory(\App\User::class)->make();
$supervisionControllerRole = \App\Role::where('name', 'supervision_controller')->first();
$user->roles->add($supervisionControllerRole);
}
since i dont want to save the relation in database, i am using add() instead of attach():
$user->roles()->attach($supervisionControllerRole->id);
//resulting database modification.
Problem
my problem is, when i am trying to get the relation from the model its ok.
var_dump($user->roles->first());
but when i am trying to get the relation Within The Model, it dosent works.
like here in my User Model:
public function hasRole($roleName)
{
$role_id = Cache::tags(['role_id'])->remember($roleName, 24*3600, function () use ($roleName) {
return \App\Role::where('name', $roleName)->first()->id;
});
return $this->roles()->where('role_id', $role_id)->exists();
}
It will returns false and trying $this->roles->count() results 0
from inside of the model.
My definitions
in User model:
public function roles()
{
return $this->belongsToMany("App\Role", "role_user")->whereNull("deleted_at")->using("App\RoleUser");
}
User Factory:
$factory->define(User::class, function (Faker $faker) {
return [
'id' => $faker->randomNumber(),
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'email_verified_at' => now(),
'password' => Str::random(80), // password
'remember_token' => Str::random(10),
];
});
Whenever you call a relationship with parentheses, such as
return $this->roles()->where('role_id', $role_id)->exists();
^^
you're accessing a Builder query instance which will return info from the database. But your data is not in the database, so of course it won't find anything when it looks there.
When you directly add() the relationship (vs attach()), you're inserting into a Collection instance, which as you know doesn't affect the database. This information is saved on the model only and stored in memory. Hence when you do
var_dump($user->roles->first());
it finds the information since it's already in memory. (You should also be able to call $user->roles->count() here and get a non-zero value.)
And since it's in a relationship of the model vs a direct attribute, I don't even think it would update the database if you were to save() the model.
You can use the contains method to perform the first step if you are not storing in the database:
return $this->roles->contains($role_id);

Return the last record in a One to many Eloquent Relation using Laravel

Assuming there existed a One To Many relation where a User has Many Jobs, and the last record in the job table is the current job of the user. What is a better way of returning the users with their last jobs?
Here is what I have tried.
User Class
public function ejob(){
return $this->hasMany(Ejob::class);
}
Ejob Class
public function user(){
return $this->belongsTo(User::class);
}
API Controller Method
public function index()
{
return UserResource::collection((
User::with(
$this->particulars() // I want the last record from this line
)->orderBy('id', 'desc')->get() ));
}
Particulars Method
// I want the last record from this
private function particulars(){
return
[
'ejob.company:id,name',
'ejob.job:id,title',
'ejob.department:id,name',
'ejob.reporting:id,surname,first_name,other_name',
'ejob.employmentstatus:id,name',
'country:id,name',
'gender:id,name',
'state:id,name'
];
}
User Resource
public function toArray($request)
{
//return parent::toArray($request);
return [
'data' => [
'id' => $this->id,
'surname' => $this->surname,
'first_name' => $this->first_name,
'other_name' => $this->other_name,
'email' => $this->email,
'phone_number' => $this->phone_number,
'birthday' => $this->birthday->format('d-m-Y'),
'age'=> $this->birthday->age,
'ejob' => $this->whenLoaded('ejob'),
];
}
Currently, this returns a user with all related records from the ejobs table but I want just the last job.
You could define another relationship method for the same relationship but define it as a Has One instead of a Has Many:
public function currentJob()
{
return $this->hasOne(Ejob::class, ...)->latest();
// order by by how ever you need it ordered to get the latest
}
Then you could eager load that instead of the ejob relationship where needed.
You can use first() instead of get(). So it'll get a single model instance.
get() method give a collection and first() method give you a single model instance.
User::with(
$this->particulars()
)->orderBy('id', 'desc')->first()
Or you can use latest() to get the last inserted record.
User::with(
$this->particulars()
)->latest()->first()
->latest() fetches the most recent set of data from the Database. In short, it sorts the data fetched, using the created_at column to chronologically order the data.
Edit:-
As you wanted to get the last record of the relationship you can do as below.
User::with('ejob', function($query) {
return $query->latest()->first();
})->get();
// in your case
public function currentJob()
{
return $this->hasOne(Ejob::class, ...)->latestOfMany();
// order by by how ever you need it ordered to get the latest
}
// another example
public function latestPerformance()
{
return $this->hasOne(Performance::class)->latestOfMany();
}
You can group it by GROUP BY and then return all results.
There you will see job for each User

Laravel resource api to get all entries with where clause of referenced table

So, this problem is a bit complex and I don't know if I'm even on the right path to the solution anymore.
I have a few tables in a database which I want to serve as an api. I created a resource for a table which returns fields from different tables.
I have the following files:
Http/Table.php (The model)
Http/Resources/TableResource.php (The resource)
Http/Controller/Tablecontroller.php (The controller)
routes/api.php (The route file)
The resource file for the table looks like this:
public function toArray($request)
{
'id' => $this->id,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'name' => $this->name,
'website' => $this->website,
'categories' => CategoryResource::collection($this->categories),
'photos' => PhotoResource::collection($this->photos),
}
As you can see, the resource is referencing different resources and creates a single object for it.
My controller has two functions to serve all data and data with a specific id.
It looks like this:
public function showAll()
{
return TableResource::collection(TableApi::all());
}
public function show($id)
{
return new TableResource(TableApi::find($id));
}
And finally I made some routes:
Route::get('/table', 'TableController#showAll');
Route::get('/table/{id}', 'TableController#show');
Everything works until this point.
Now the following scenario: I want data from the table not only filtered by the id, but by the name of one of the referenced tables. So basically I want all entries where the category->name is "Test".
Is this even possible with my approach?
If TableApi is your model and you declared a relationship with the Category model, you can do something like:
TableApi::where('id', $id)->whereHas('categories', function($query) {
$query->where('category.name', 'Test')
})->first();
You can include the related Category and Photo models using with().
TableApi::where('id', $id)->whereHas('categories', function($query) {
$query->where('category.name', 'Test')
})->with(['categories', 'photos'])->first();
You can read more about querying relationships existence in the Laravel documentation.

Laravel CommentsController not enough arguments passing user_id

So I have a Posts model that has many comments and belongs to a user, so when I want to add a comment, which belongs to a post and a user, I must give it a user a id, and this is what I tried.
use App\Posts;
use App\Comment;
class CommentsController extends Controller
{
public function store(Posts $post)
{
$this->validate(request(), ['body' => 'required|min:2']);
$post->addComment(request([
'body' => request('body'),
'user_id' => auth()->user()]));
}
}
But what I am getting is
Type error: Too few arguments to function App\Posts::addComment(), 1
passed 2 expected.
The addcoment method, from the posts model:
public function addComment($body, User $userid)
{
$this->comments()->create(compact('body', 'userid'));
return back();
}
Following this tutorial https://laracasts.com/series/laravel-from-scratch-2017/episodes/19, but the tutor skipped this step.
Your method addComment($body, User $userid) needs 2 arguments!
You should try something like this :
$post->addComment(request('body'),auth()->user());
OR (I'm not sure for this one) This one below will not work.
$post->addComment(request(['body' => request('body')],auth()->user());

Categories