In my model I added $casts field because I need Laravel to give me a float. My database is storing it as a string.
So I did this in my model Score.php:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Score extends Model
{
protected $fillable = [
'value', 'comment'
];
protected $casts = [
'user_id' => 'int',
'value' => 'float'
];
This is great, it casts it. However it is going wayyyy too many decimal places. I need it to go only 1 decimal place, to the tenths.
My json endpoint data looks like this:
{
"id": 2,
"name": "Zed",
"avatar_path": null,
"score": {
"id": 2,
"value": 9.9000000000000003552713678800500929355621337890625,
"comment": null,
"user_id": 2,
"created_at": "2018-05-03 07:03:37",
"updated_at": "2018-05-03 07:03:41"
}
},
I want that 9.9...... to just be 9.9 is this possible?
In my schema, I did i do it right to create it as $table->decimal('value', 3, 1):
Schema::create('scores', function (Blueprint $table) {
$table->increments('id');
$table->decimal('value', 3, 1)->nullable(); //////// is this correct?
How about creating a mutator/accessor?
In your model add
public function getValueAttribute($value) {
return round($value, 1);
}
for extra info visit the related Laravel docs
If you are 100% sure you only need one decimal place, than the database approach is good too, but this way you can store the raw value for future use, and still respond with the currently desired format
Related
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"
}
]
To keep it simple, let's take an example of stackoverflow's questions page where there is a paginated list of questions and each question has some tags attached to it.
A Tag model is in Many to Many relation with Question model i.e.
A tag can be assigned to many questions.
A question can be assigned to many tags.
For this relation I created an relational model named QuestionTag (and table for it) that has the relation with both Tag and Question. Then I used laravel's hasManyThrough relation to get a list of tags assigned to a question through the QuestionTag model as such:
class QuestionTag extends Model
{
public function question()
{
return $this->belongsTo(Question::class, 'question_id', 'id');
}
public function tag()
{
return $this->belongsTo(Tag::class, 'tag_id', 'id');
}
}
class Question extends Model
{
public function tags()
{
return $this->hasManyThrough(Tag::class, QuestionTag::class, 'question_id', 'id', 'id', 'tag_id');
}
}
And I created QuestionResource for returning the expected paginated results of questions as such:
QuestionResource
class QuestionResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'subject' => $this->subject,
'body' => $this->body,
'tags' => $this->tags // this will call the has many through relations as expected.
];
}
}
Result
{
"current_page": 1,
"data": [
{
"id": 1,
"subject": "Lorem ipsum dolor sir amet!",
"body": "...",
tags: [
{
"id": 1,
"name": "tag1",
},
// ...and so on
]
},
// ...and so on
],
"first_page_url": "http://127.0.0.1:8000/uv1/questions?page=1",
"from": 1,
"last_page": 1,
"last_page_url": "http://127.0.0.1:8000/uv1/questions?page=1",
"next_page_url": null,
"path": "http://127.0.0.1:8000/uv1/questions",
"per_page": "15",
"prev_page_url": null,
"to": 5,
"total": 5
}
At last, on the index function, I returned the paginated list of questions from the QuestionController's index function as such:
public function index(Request $request)
{
$perPage = $request->input('perPage') ?? 15;
// execute the query.
$crawlers = Question::paginate($perPage);
return QuestionResource::collection($crawlers);
}
It returned what I wanted but when I increased the per_page size to 100 or more, it is returning this error:
Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes)
I found many solutions that suggests to increase the memory in php.ini(memory_limit = 2048M) but it feels like we are bruteforcing to acheive the outcome. There will be some point when again the memory_limit will fail to return the same when I keep on increasing the per_page size.
Is there any optimal way in laravel to get the same expected result(instead of above mentioned error) with the desired output without increasing the memory size?
I used Inner Join to achieve this and used MySQL's JSON_ARRAYAGG(JSON_OBJECT()) along with it in SELECT statement to create a concatenated json string of tags and later convert it to json array using php json_decode(). Little tacky but it returned the result fast and I could load thousands of records within milliseconds.
My QuestionController now looks like this:
public function index(Request $request)
{
$perPage = $request->input('perPage') ?? 15;
// execute the query.
$crawlers = Question::query()
->select(['questions.*', DB::raw('JSON_ARRAYAGG(JSON_OBJECT("id", `tags`.`id`, "name", `tags`.`name`)) AS `tags`')])
->join('question_tags', 'questions.id', 'question_tags.question_id')
->join('tags', 'question_tags.tag_id', 'tags.id')
->groupBy('questions.id')
->paginate($perPage);
return QuestionResource::collection($crawlers);
}
And I removed the join relations from the models and changed my QuestionResource as such:
class QuestionResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'subject' => $this->subject,
'body' => $this->body,
'tags' => json_decode($this->tags ?? '[]', false, 512, JSON_THROW_ON_ERROR), // convert string JSON to array
];
}
}
Currently, I've implemented this approach but I'm still open to better solutions. :)
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
I want to load specific attributes with user model :
$user = $user->only(['id', 'user_name', 'created']);
but created_at gets an object of date_time something like this:
{
"id": 1,
"user_name": "foo",
"created_at": {
"date": "2018-12-30 11:34:57.000000",
"timezone_type": 3,
"timezone": "...."
},
}
how can i get string type of created_at with only method?
Thanks
There are multiple ways to achieve this.
Either you could set a $casts[] array on your model as such:
protected $casts = [
'created_at' => 'string'
];
which means, everytime you will try to access created_at, it will return a string representation.
Or, you could also define an Accessor such as
public function getCreatedAtAttribute()
{
return (string) $this->created_at;
}
However, I don't usually like to mess with the created_at and updated_at attributes. So usually I'd do something like this:
public function getFormattedCreatedAtAttribute()
{
return $this->created_at->toFormattedDateString();
}
and then in your particular case, it would be:
$user = $user->only(['id', 'user_name', 'formatted_created_at']);
you can try this,
'create_at' => now();
or
'crated_at' => Carbon::now();
'updated_at' => Carbon::now();
Using Laravel 5.4, I have a query that correctly returns a relationship. Using the "with" clause in the query, I am attempting to return only selected columns from the relationship in my controller.
When I add the select to the with clause, relationship returns an empty array. Oddly enough, if I add a different parameter, such as a groupBy or join the query DOES return results. So something about my setup dislikes the select on the query.
Thus far I have tried:
using selectRaw
using select(DB::raw)
tried defining this as a separate relationship on my model.
Nothing has worked this far. Sql log looks good when I dump it.
Here is my model:
// MODEL
namespace App;
use Illuminate\Database\Eloquent\Model;
use DB;
class ClassBlocks extends Model
{
public $timestamps = false;
public function schedule(){
return $this->hasMany('App\ClassSchedules', 'class_block_id', 'id');
}
}
And here is my controller:
//CONTROLLER
use App;
use DateTime;
use Illuminate\Http\Request;
class ProgramsController extends Controller
{
public function filterClass(Request $request, App\ClassBlocks $block)
{
$block = $block->newQuery();
// Attempt to eager load relationship
// Returns results when "select" disabled
$block->with([
'schedule' => function($query){
$query->select('time_start');
$query->groupBy('day');
},
]);
return $block->get();
}
}
Here is a sample result with select enabled (schedule returns empty):
[
{
"id": 13,
"program_id": "1",
"class_group_id": "1",
"schedule": [
]
}
]
And here is a result with select disabled (returns relationship when select disabled):
[
{
"id": 13,
"program_id": "1",
"class_group_id": "1",
"schedule": [
{
"id": 338,
"class_group_id": "1",
"program_id": "1",
"class_block_id": "13",
"date": "06/13/2017",
"day": "Tuesday",
"instructor_id": "1",
"time_start": "6:30am",
"time_end": "6:30am"
},
{
"id": 339,
"class_group_id": "1",
"program_id": "1",
"class_block_id": "13",
"date": "06/14/2017",
"day": "Wednesday",
"instructor_id": "2",
"time_start": "6:30am",
"time_end": "6:30am"
}
]
},
]
Any insight would be greatly appreciated.
The problem here is:
$query->select('time_start');
Laravel need to have column that is connection between 2 records. In this case you should probably use:
$query->select('time_start', 'class_block_id');
to make it work.
Obviously you will have class_block_id in response this way. If you really don't want it, probably you should create some transformer that will return exactly you want in response.