Storing Data using Laravel Helpers Arr::add() - Laravel 5.6 - laravel

I am storing data in my controller like below
Controller
public function store(Request $request, $patient_id)
{
$auth = auth();
$patient_info = this->patient->store(Arr::add($request->all(),
'patient_id' => $patient_id, 'user_id' => $auth->id()));
dd($patient_info);
}
Model
class Patient extends Model
{
protected $fillable = ['name','patient_id','user_id'];
}
Results
"patient": {
"name": "Mohammed",
"patient_id": "1",
"updated_at": "2019-05-11 18:52:32",
"created_at": "2019-05-11 18:52:32",
"id": 1
}
The data is stored accurately in my database but without user_id as shown in the response. However, i have included user_id in the Arr:add(). What could i be doing wrong in my code please ?
PS: Beginner in laravel

Make sure you are logged in, otherwise $auth->id() will return null.
You can check if user is logged in with this helper auth()->check() which returns bool value.
Also make sure you have user_id column in your Patient table

Related

How to select custom attributes fields with relation in Laravel?

I have tables emails and email_attachments as follows:
Schema::create('emails', function (Blueprint $table) {
$table->id();
$table->string('email')->nullable(false);
$table->string('subject')->nullable(false);
$table->text('body')->nullable(false);
$table->timestamps();
});
Schema::create('email_attachments', function (Blueprint $table) {
$table->id();
$table->foreignId('email_id')->nullable(false);
$table->string('file_name')->nullable(false);
$table->string('file_path')->nullable(false);
$table->foreign('email_id')->references('id')->on('emails');
});
And my models classes Email and EmailAttachment as follows:
class Email extends Model
{
use HasFactory;
protected $fillable = ['email', 'subject', 'body'];
function attachments()
{
return $this->hasMany(EmailAttachment::class);
}
}
class EmailAttachment extends Model
{
use HasFactory;
public $timestamps = false;
protected $fillable = ['email_id', 'file_name', 'file_path'];
protected $appends = ['url'];
function email()
{
return $this->belongsTo(Email::class);
}
public function getUrlAttribute()
{
return Storage::disk('public')->url($this->file_name);
}
}
Doing this return Email::with('attachments')->get(); I get everything as response:
{
"id": 1,
"email": "me#me.com",
"subject": "My Subject",
"body": "<html><body><h1>My test message.</h1></body></html>",
"created_at": "2021-02-26T23:32:08.000000Z",
"updated_at": "2021-02-26T23:32:08.000000Z",
"attachments": [
{
"id": 1,
"email_id": 1,
"file_name": "foo.jpg",
"file_path": "/home/user/laravel/storage/app/foo.jpg",
"url": "http://127.0.0.1:8000/storage/foo.jpg"
}
]
}
And this return Email::with('attachments:file_name')->get(); I get attachments as an empty array:
{
"id": 1,
"email": "me#me.com",
"subject": "My Subject",
"body": "<html><body><h1>My test message.</h1></body></html>",
"created_at": "2021-02-26T23:32:08.000000Z",
"updated_at": "2021-02-26T23:32:08.000000Z",
"attachments": []
}
How can I get only this?
{
"email": "me#me.com",
"subject": "My Subject",
"body": "<html><body><h1>My test message.</h1></body></html>",
"attachments": [
{
"url": "http://127.0.0.1:8000/storage/foo.jpg"
}
]
}
I already did this tries:
return Email::with('attachments:url')->get(['email', 'subject', 'body']);
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'url' in 'field list' (SQL: select `url` from `email_attachments` where `email_attachments`.`email_id` in (0))"
return Email::with('attachments')->get(['email', 'subject', 'body', 'url']);
"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'url' in 'field list' (SQL: select `email`, `subject`, `body`, `url` from `emails`)"
return Email::with('attachments:url')->get(['email', 'subject', 'body', 'url']);
"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'url' in 'field list' (SQL: select `email`, `subject`, `body`, `url` from `emails`)"
PS.: I'm using Laravel 8.29.0.
There are several ways you can achieve this, usually, in other frameworks, these objects tend to be called "DTO objects" ( Data Transfer Objects )
For Laravel, you could simply get the resulting query from the database, and perform simple array transformations on it using the collections() API.
For example:
$collection = Email::with('attachments')->get();
$collection->map(function ($record) {
return ['email' => $record->email, 'subject' => $record->subject, 'attachments' => $record->attachments ];
});
Now you would just return this as the response:
$response = $collection->map(...);
return response()->json($response, 200);
From your controller.
Though you should be aware that these transformations happen in PHP and not SQL side of things, but the user won't know which field you've returned from the database. https://laravel.com/docs/8.x/queries#select-statements
You can add ->select() anywhere between the other queries and ->get() because until that point you're still in the query builder mode ( You're making your SQL query )
So you could do this:
Email::with('attachments')->select('my_field','my_field2')->get();
Keep in mind that this works for any query builder methods. https://laravel.com/docs/8.x/queries
Finally, you may look into API resources, as they provide a way you can format your responses. The documentation will explain it with far more better job then me: https://laravel.com/docs/8.x/eloquent-resources
When loading specific fields in eager loading you should also select the identifier of the record which is used to map the related records. So in your case email (hasMany) attachments via foreign key email_id which points to Email
See Eager Loading Specific Columns
When using this feature, you should always include the id column and any relevant foreign key columns in the list of columns you wish to retrieve.
Email::with('attachments:email_id,file_name')->get();
Now this will give you 2 fields from attachments relation
"attachments": [
{
"email_id": 1,
"file_name": "foo.jpg"
}
]
So if you need url attribute which is a custom attribute and not physically present in your table you will need to trick the query builder to produce such SQL query with url attribute included, initially with null value and later on when this query output is transformed by laravel data transformation your url's accessor getUrlAttribute() will fill this attribute with your expected output
Email::with(['attachments' => function ($query) {
$query->select(['email_id', 'file_name', \DB::raw('null as url')]);
}])->get();
Now attachments collection will have following data in it
"attachments": [
{
email_id": 1,
"file_name": "foo.jpg",
"url": "http://127.0.0.1:8000/storage/foo.jpg"
}
]

How to get data from laravel relationship in single object?

IN USER MODEL this is my relation
public function User() {
return $this->belongsTo('App\models\Users','UserId');
}
IN WALLET MODEL this is my relation
public function Wallet() {
return $this->HasOne('App\Models\Wallet','UserId','Id');
}
but when i am running the query
$user = Users::with([
'Wallet' => function($query){
$query->select('test_userwallet.UserId','test_userwallet.CoinBalance');
}
])->get()->toArray();
i am getting the data in a object like this
{
"Id": 1,
"UID": "8oDI617ZlsInXtUkRpMqVKo5J4XPzI12567",
"CountryCode": "91",
"Status": "active",
"TimeStamp": "2021-02-12 06:43:08",
"wallet": {
"UserId": 1,
"CoinBalance": 6
}
which is totally fine but i am guessing is there any way or method by which i can get the data in this format
{
"Id": 1,
"UID": "8oDI617ZlsInXtUkRpMqVKo5J4XPzI12567",
"CountryCode": "91",
"Status": "active",
"TimeStamp": "2021-02-12 06:43:08",
"UserId": 1,
"CoinBalance": 6
}
like in single object as i am working in apis so i want to do like that
note : only using query or eloquent
you can do this using join:
$user = Users::query()->leftJoin('test_userwallet','test_userwallet.UserId','users.id')
->select(['test_userwallet.Id','UID','CountryCode','Status','TimeStamp','UserId','CoinBalance'])
->get()->toArray();
You've specified only using query or eloquent.
I would say the best way would be to return your endpoint response as a Resource from your controllers.
But if you must do it as a property on the model. You can use the $appends array and an accessor. See docs This will add the properties to the model any time it is serialised like when it's returned in a response.
protected $appends = [
'coin_balance',
];
public function getCoinBalance Attribute()
{
return $this->Wallet->coin_balance;
}

Laravel 5.6 Many to Many relationship - accessing child fields in view

I have a Many to Many relationship between User and Project. I am trying to list a users projects but can't access the child fields in a view:
Models
// Project
public function users() {
return $this->belongsToMany('App\User')->withTimestamps();
}
// User
public function projects() {
return $this->belongsToMany('App\Project')->withTimestamps();
}
Intermediate table: project_user
user_id, project_id, timestamps
Controller
$projects = User::with('projects')->where('id', auth()->user()->id)->get();
return view('home')->with('projects', $projects);
View
#foreach($projects as $project)
- {{ $project->name}}
<br>
#endforeach
This returns no errors and no results
If I try $projects->projects as $project I get "projects" not available to this collection.
If I return $projects in the controller I get:
[
{
"id": 1,
"first": "User",
"last": "Name",
"organization": "Organization",
"phone": "5555555555",
"email": "test#example.com",
"created_at": "2018-03-22 20:16:20",
"updated_at": "2018-03-22 20:16:20",
"projects": [
{
"id": 10,
"name": "Project One for User One",
"description": "Project Description",
"created_at": "2018-03-22 20:16:20",
"updated_at": "2018-03-22 20:16:20",
"pivot": {
"user_id": 1,
"project_id": 10,
"created_at": "2018-03-22 20:16:20",
"updated_at": "2018-03-22 20:16:20"
}
},
...
How can I access the child fields name and description?
First, you do not have to query for the user, as it is already authenticated. If you use something like the Debugbar package, you can see that it will query the user for the current session.
So, to fetch the currently authenticated user, you can simply use:
$user = auth()->user(); // you can als use this in the view if you want.
In the controller, your code:
$projects = User::with('projects')->where('id', auth()->user()->id)- >get();
Will do a query to fetch all users with id = auth()->user()->id and it will eagerload all projects of those users (<- plural !!!).
So the $projects variable contains all the users with that id and it will attach all the projects in a subsequent query. Hence it is giving you an array of user objects, instead of the projects that you want. Which makes sense, since you are querying the User table.
Personally, I would do something like this in the controller:
$user = auth()->user();
$projects = $user->projects->get(); // doing this here will allow you to change get() to paginate() if you want.
return ('home')->with(['projects' => $projects]); // < either use compact as in the docs, or an associative array
Now in the view $projects will contain a Collection of projects, not users, and you can simply do:
#foreach($projects as $project)
- {{ $project->name}}
<br>
#endforeach

Constraining a nested 3rd level relationship

I'm building an api using eager loading so i can simply return the user model with its deep relations and it automatically be converted as json. Here's the set up.
users
id
..
clients
id
..
user_clients
id
user_id
client_id
..
campaigns
id
..
client_campaigns
id
client_id
campaign_id
..
campaign_activities
id
campaign_id
..
client_campaign_activity_templates
id
campaign_activity_id
client_id *(templates are unique per client)*
..
I've setup the models' relationships.
User
public function clients() {
return $this->belongsToMany('App\Client','user_clients');
}
Client
public function campaigns() {
return $this->belongsToMany('App\Campaign','client_campaigns');
}
Campaign
public function activities() {
return $this->hasMany('App\CampaignActivity');
}
CampaignActivity
public function templates() {
return $this->hasMany('App\ClientCampaignActivityTemplate')
}
I have a simple api endpoint to provide a JSON of a User object including its deep relations using eager loading.
public function getLoggedInUser(Request $request) {
return \App\User::with('clients.campaigns.activities.templates')->find($request->user()->id);
}
Testing this using postman, I can get the user including its deep relations.
{
"user": {
"id": 1,
"name": "user1",
"clients": [
{
"id": 1,
"name": "client1",
"campaigns": [
{
"id": 1,
"name": "campaign1",
"activities": [
{
"id": 1,
"name": "activity1",
"templates": [
{
"id": 1,
"name": "template1 for client1",
"client_id": 1,
"body": "this is a template.",
}, {
"id": 2,
"name": "template1 for client2",
"client_id": 2,
"body": "This is a template for client2"
}
]
}, {
"id": 2,
"name": "activity2",
"templates": []
}, {
"id": 3,
"name": "activity3",
"templates": []
}
]
}
]
}
]
}
}
However, on the user->clients->campaigns->activities->templates level, it will list all the templates for that activity. I know based on the code of the relationships of the models above that it's supposed to behave like that.
So the question is How would you filter the templates to filter for both campaign_activity_id and client_id?
I've been experimenting on how to filter the templates so it will only list templates for that activity AND for that client as well. I have a working solution but it's N+1, I'd prefer eloquent approach if possible. I've been scouring with other questions, answers and comments for a closely similar problem, but I had no luck, hence I'm posting this one and seek for your thoughts. Thank you
I think what you need are eager loading constraints.
public function getLoggedInUser(Request $request) {
return \App\User::with('clients.campaigns.activities.templates',
function($query) use($request) {
$client_ids = Client::whereHas('users', function($q) use($request){
$q->where('id', $request->user()->id);
})->pluck('id');
$query->whereIn('templates.client_id', $client_ids);
})->find($request->user()->id);
}
Not tested but it should only require one additional query.
What I am doing is: define a constraint for your eager loading, namely only show those templates that have a client_id that is in the list (pluck) of Client IDs with a relation to the User.
Try using closures to filter through related models:
$users = App\User::with([
'clients' => function ($query) {
$query->where('id', $id);
},
'clients.campaigns' => function ($query) {
$query->where('id', $id);
}
])->get();
Here's my working solution, but I'm still interested if you guys have a better approach of doing this.
On the CampaignActivity model, I added a public property client_id and modified the relationship code to
CampaignActivity
public $client_id = 0
public function templates() {
return $this->hasMany('App\ClientCampaignActivityTemplate')->where('client_id', $this->client_id);
}
and on my controller, limit the eager loading to activities only (actually, there are more sqls executed using eager loading[9] in this case vs just iterating[7], and also eager loading doesn't make sense anymore because we're iterating lol)
public function getLoggedInUser(Request $request) {
foreach ($user->clients as $client)
foreach( $client->campaigns as $campaign)
foreach ($campaign->activities as $activity) {
$activity->client_id = $client->id;
$activity->templates; //to load the values
}
return $user;
}

Eloquent Eager Loading with $append Attribute

I'm kinda stuck with this here and don't know how to move forward with this one.
I have two Models: user and child and they are in a Relationship.
( Keep in mind that this only illustrate the problem )
class Child extends Model{
protected $primaryKey = 'id_child';
public $appends = ['is_alive'];
public function user(){
return $this->belongsTo('Models\User','id_user');
}
public function getIsAliveAttribute(){
if (!is_null($this->lifetime_updated_at))
return (Carbon::parse($this->lifetime_updated_at)->addMinute($this->lifetime) >= Carbon::now());
else
return false;
}
}
class User extends Model{
protected $primaryKey = 'id_user';
public $appends = ['is_alive'];
public function childs(){
return $this->hasMany('Models\Child','id_user');
}
public function getIsAliveAttribute(){
if (!is_null($this->lifetime_updated_at))
return (Carbon::parse($this->lifetime_updated_at)->addMinute($this->lifetime) >= Carbon::now());
else
return false;
}
}
Now I want to use Eager Loading in the Controller to retrieve my Childs data from User.
But my User Model Object comes from an MiddleWare in my Application. So I only have the User Model Object to use and I don't want to Query the User again using "with()".
$user = User::where('name','DoctorWho')->first();
return user->childs()->find(3);
What this operation returns:
{
"id_child": 3,
"name": "JayZ",
"last_name": "Etc",
"lifetime": 1,
"lifetime_updated_at": null,
"created_at": "2017-05-29 21:40:02",
"updated_at": "2017-05-29 21:40:02",
"active": 1
}
What I needed ( With Attribute Appended)
{
"id_child": 3,
"name": "JayZ",
"last_name": "Etc",
"lifetime": 1,
"lifetime_updated_at": null,
"created_at": "2017-05-29 21:40:02",
"updated_at": "2017-05-29 21:40:02",
"active": 1,
"is_alive": true
}
Is even possible to retrieve Child Data with Appended Attributes using Eager Loading ?
Notes: This user object come from an Middleware, I must use the User Model Object to get its child's with the appended attribute.
Thanks in Advance,
LosLobos
The thing is I was doing something wrong that is not related to Eloquent acessing the model like that does come with the appended attributes!
What you're doing here is not eager loading. Your childs relationship is wrong. Should be return $this->hasMany('Models\Child', 'id_user'); based on the model and the other relationship defined.
Here are some ways you can access the child information. These by default should respect the $appends property and load the field.
$childId = 3;
$user = User::with('childs')
->where('name', 'DoctorWho')
->first();
return $user->childs()->where('id_child', $childId)->first();
// Or
$user = User::with(['childs' => function ($query) use ($childId) {
$query->where('id_child', $childId);
}])
->where('name', 'DoctorWho')
->first();
return $user->childs->first();
// Or
$child = Child::whereHas('user', function ($query) {
$query->where('name', 'DoctorWho');
})
->find(3);
return $child;
Edit :
If you already have the user model then you can do this.
$child = Child::where('id_user', $user->id_user)
->where('id_child', 3)
->first();
return $child;

Resources