Format date before query database: casts and mutatores - laravel

The front end of my application consumes the following date format: d-m-Y h:m:s
To achieve this , I use protected $casts in the model:
protected $casts = [
'created_at' => 'datetime: d-m-Y h:m:s',
'updated_at' => 'datetime: d-m-Y h:m:s',
];
Works well (for output).
The problem is when, for example, I want to get data from the database within a date range.
In this case, the date is sent to the server side in the following format 01-02-2022.
For the query to work correctly, the date must previously be in the format 2022-02-01.
I tried to solve it with a mutator, but it is not working.
use Illuminate\Support\Carbon;
protected $fillable = [
...
'created_at'
];
public function setCreatedAtAttribute($value)
{
$this->attributes['created_at'] = Carbon::createFromFormat('d-m-Y', $value)->format('Y-m-d');
}

This is a common design pattern for me, as Laravel conveniently allows us to overload newQuery() in a 'BaseModel' class, and extend from this class in all our other Models:
you may Override Illuminate\Database\Eloquent\Model 's query()!
/**
* Overrides Illuminate\Database\Eloquent\Model 's query()
*
* #return mixed
*/
public function newQuery($excludeDeleted = true)
{
$query = parent::newQuery($excludeDeleted)->where('created_at',$this->getCreatedAt());
return $query;
}

Related

Laravel Nova Actions BelongsTo field not working

I have this simple action:
/**
* Perform the action on the given models.
*
* #param \Laravel\Nova\Fields\ActionFields $fields
* #param \Illuminate\Support\Collection $models
* #return mixed
*/
public function handle(ActionFields $fields, Collection $models)
{
foreach ($models as $model) {
$model->update([
'user_id' => $fields->user
]);
}
}
/**
* Get the fields available on the action.
*
* #return array
*/
public function fields()
{
return [
BelongsTo::make('User', 'user', User::class),
];
}
At first, it seems fine, but when I select User from BelongsTo relation and try to save exception is throwing:
Argument 1 passed to Laravel\Nova\Fields\BelongsTo::getRelationForeignKeyName() must be an instance of Illuminate\Database\Eloquent\Relations\Relation, instance of Illuminate\Support\Fluent given, called in /Users/rd/Sites/bns-crm/vendor/laravel/nova/src/Fields/BelongsTo.php on line 212
Yes i know i'm late but - here's a solution for this:
Use a Select-Field instead of BelongsTo and Pluck your options to build Key-Value pairs:
public function fields()
{
return [
Select::make('debitor')->options(\App\Models\Debitor::pluck('Name', 'id'))
];
}
Then in the handle you should geht the ids in $fields:
public function handle(ActionFields $fields, Collection $models) {
Log::info($fields);
}
Maybe I'm late, but, for the ones like me wanting to use the BelongsTo searchable field because the model they want to search in contains too much records to pack them in a normal Select field here is the solution I found:
Create a class in App\Nova\Fields with this code:
<?php
namespace App\Nova\Fields;
use Laravel\Nova\Fields\BelongsTo;
use Laravel\Nova\Http\Requests\NovaRequest;
class BelongsToForActions extends BelongsTo
{
public function fillForAction(NovaRequest $request, $model)
{
$attribute = $this->attribute;
if ($request->exists($attribute)) {
$value = $request[ $attribute ];
$model->{$attribute} = $this->isNullValue($value) ? null : $value;
}
}
}
Then use it like you would use a normal BelongsTo field. Just remember to fill the 3 arguments on the make, so, for example:
BelongsToForActions::make('User', 'relation', \App\Nova\User::class)->searchable()
Remember that 'relation' must exist.
Check your namespaces. Did you imported right class? User class must be resource class
public function fields()
{
return [
BelongsTo::make('User', 'user', User::class),
];
}
I actually fixed this by mocking the key value pair used in this relationship.
First I build an array with the ID column as key and the name column as value.
$clients = Client::all()
->keyBy('id')
->map(fn($client): string => $client['name'])
->toArray();
Then I use the Select nova field to display it.
Select::make('Klant', 'client')
->searchable()
->options($clients)
->rules('required'),

Get specific columns having with() function in laravel

Category Model definition is:
class Category extends Model
{
public $implement = ['#RainLab.Translate.Behaviors.TranslatableModel'];
public $translatable = ['name'];
/**
* #var string The database table used by the model.
*/
public $table = 'shop_category';
/**
* #var array Validation rules
*/
public $rules = [
];
public $attachOne = [
'icon' => 'System\Models\File'
];
public $hasMany =[
'shops' => ['ItScholarBd\Api\Models\Shop', 'key' => 'shop_category_id']
];
}
I want to get category with shop. It works fine if I retrieve all columns but if I try to retrieve specific column it is not working. I want to get only the following columns:
id, name of each table. I also apply a condition where Shop.status = 1. I am trying as follows:
$this['categoryWithShop'] = Category::select('id,name')->with(array('shops'=>function($query){
$query->where('status','=',0);
$query->select('id','name');
}))->get();
Any idea?
Try this,
$this['categoryWithShop'] = Category::select('id,name')->with(array('shops'=>function($query){
$query->where('status','=',0);
$query->select('id','name', 'status');
}))->get();
In select query you need to pass that all columns which is needed in query.
try this chaining method and select('id,name') this is wrong you need to fix this as select('id','name')
ref link https://laravel.com/docs/8.x/queries#selects
$this['categoryWithShop'] = Category::select('id','name')->with(['shops' => function ($query) {
$query->select('id', 'name')->where('status', '=', 0);
}])->get();

Laravel - formatting all dates simultaneously

I'm currently working on a POC to showcase that it's going to be fairly painless to create an API with Laravel, the catch being that the database is already set in stone.
One problem I've run into is that they've used custom created at and updated at column names, e.g. for a car table, the created_at column would be car_time and the updated date would be cardata_time, and these are all saved as unix timestamps.
I know you can set the CREATED_AT and UPDATED_AT columns for each model. I want to go another step and return all dates in ISO 8601 format.
I've inserted a class between my models and Model called MasterModel and I want to do something like
protected function getCreatedAtAttribute($value)
{
$format = "Y-m-d\TH:i:s\Z";
$datetime = new DateTime($value);
return $datetime->format($format);
}
to make all created at dates be in that format. The problem is that I the custom created at and updated columns mean that this never gets called.
Is there a way for me to identify the created at and updated at columns in such a way that I can use a single method to updated all created at dates at the same time?
UPDATE: I realize my original question was not clear enough - I need to identify all fields that are dates, not just created_at and updated_at, and have them formatted a certain way. They will always be unix timestamps. Not sure how I'd go about this.
Here an answer that will expand on #caddy dz answer who happen to be sitting with me.
 All the things that need to be known
Deactivation of auto management of timestamps
public $timestamps = false; // <-- deactivate the automatic handling
Change table attributes names
const CREATED_AT = 'creation_date'; // <--- change the names
const UPDATED_AT = 'last_update';
source doc:
https://laravel.com/docs/5.8/eloquent#eloquent-model-conventions
By default, Eloquent expects created_at and updated_at columns to
exist on your tables. If you do not wish to have these columns
automatically managed by Eloquent, set the $timestamps property on
your model to false:
 Creating the accessors
class User extends Model
{
/**
* Get the user's first name.
*
* #param string $value
* #return string
*/
public function getFirstNameAttribute($value)
{
// do whatever you want here (change and mutate the value)
return ucfirst($value);
}
}
First thing to know, is that the accessors are a global concept for
eloquent and can be writing for all attributes and not just
getCreatedAtAttribute or getUpdatedAtAttribute.
Second thing to know is that whatever the name of the column, that is
in camle case (firstName) or with _ (first_name) eloquent know to
match to it. The format of the accessor should be
get[NameOfATtribute]Attribute in pascal case (camle case but first
letter too in uppercase).
Three the method argument hold the value of the column in
question. Bellow a snippet that show how it's used
$user = App\User::find(1);
$firstName = $user->first_name; //|=> first_name => getFirstNameAttribute(columnVal)
The resolution is clear.
first_name (column name) => getFirstNameAttribute(columnValue)
All the snippets are from the doc: https://laravel.com/docs/5.8/eloquent-mutators#accessors-and-mutators
 Let's apply all of that
First we need to not use $table->timestamps() in the migration so we make the changment to the bellow.
Schema::create('cars', function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamp('cardata_time', 0)->nullable();
$table->timestamp('car_time', 0)->nullable();
});
Then we apply the modification on our model:
- we deactivate the auto handling of timestamps.
- Override the timestamps columns names.
- And create the accessors.
Here depend on what we want. If we want to only do the above here a snippet that show that:
// deactivate auto timestamps management
public $timestamps = false;
// change the columns names
const CREATED_AT = 'car_time';
const UPDATED_AT = 'cardata_time';
// creating the accessors (respect the naming)
protected function getCarTimeAttribute($value) //car_time => CarTime
{
// <-- do whatever you want here (example bellow)
$format = "Y-m-d\TH:i:s\Z";
$datetime = new DateTime($value);
return $datetime->format($format);
}
protected function getCardataTimeAttribute($value) //cardata_time => CardataTime
{
// <-- do whatever you want here
$format = "Y-m-d\TH:i:s\Z";
$datetime = new DateTime($value);
return $datetime->format($format);
}
 Doing it with renaming the attributes completely
If what you want is to use another accessing name. Then what my friend #caddy dz did is the way to go. Which happen to be sitting with me. And dared me to expand upon the answer. (hhhh)
You will need to know
$appends and $hidden
Part of the serialization API.
https://laravel.com/docs/master/eloquent-serialization#appending-values-to-json
https://laravel.com/docs/master/eloquent-serialization#hiding-attributes-from-json
$appends allow us to add attributes to the model. That don't exists on the table. We need also to create an accessors for them.
class User extends Model
{
/**
* The accessors to append to the model's array form.
*
* #var array
*/
protected $appends = ['is_admin'];
// ........
/**
* Get the administrator flag for the user.
*
* #return bool
*/
public function getIsAdminAttribute()
{
return $this->attributes['admin'] == 'yes';
}
}
and
$hidden allow us to remove and limit the attribute from the models. Like with the password field.
Doc examples:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = ['password'];
}
And from that what we need to do is to hide the attributes holding the time, that want to be changed to somehting else.
// remove the old attributes names
protected $hidden = ['car_time', 'cardata_time']; // renaming those
// append the new one \/ \/ <- to those
protected $appends = ['car_crated_at', 'cardata_created_at']; // names just for illustration
protected function getCarCreatedAtAttribute($value) // car_created_at => CarCreatedAt
{
$format = "Y-m-d\TH:i:s\Z";
$datetime = new DateTime($value);
return $datetime->format($format);
}
protected function getCardataCreatedAtAttribute($value) // cardata_created_at => CardataCreatedAt
{
$format = "Y-m-d\TH:i:s\Z";
$datetime = new DateTime($value);
return $datetime->format($format);
}
 Applying it for different models
The basic idea is to create a base model then extend it when you create your model.
Formatting all time attributes of the model without exception
If what you want is to apply the formatting for all the time attributes within the model.
Then override serializeDate() method. In practice write a trait, and then you can apply it. Otherwise a base model.
The answer bellow cover it well:
https://stackoverflow.com/a/41569026/7668448
And historically This thread is interesting :
https://github.com/laravel/framework/issues/21703
Serializing in Carbon level
In the documentation laravel 5.7 and up (what i checked [doc only]) :
https://laravel.com/docs/master/eloquent-serialization#date-serialization
We can change the formatting at the level of carbon serialization. But it happen that there was a bug in the past. Normally fixed but i didn't try it. Bug was in 5.7 and fixed in 5.7 if i'm not wrong. The git link above discuss it.
Snippet:
class AppServiceProvider extends ServiceProvider
{
/**
* Perform post-registration booting of services.
*
* #return void
*/
public function boot()
{
Carbon::serializeUsing(function ($carbon) {
return $carbon->format('U');
});
}
___THE_END ^ ^
Not sure what you're asking but if you have cardata_time and car_time in your table defined like this
Schema::create('cars', function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamp('cardata_time', 0)->nullable();
$table->timestamp('car_time', 0)->nullable();
});
And a MasterModel like so
/**
* Indicates if the model should be timestamped.
*
* #var bool
*/
public $timestamps = false;
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
/**
* The accessors to append to the model's array form.
*
* #var array
*/
protected $appends = ['created_at', 'updated_at'];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = ['car_time', 'cardata_time'];
protected function getCreatedAtAttribute($value)
{
$format = "Y-m-d\TH:i:s\Z";
$datetime = new DateTime($value);
return $datetime->format($format);
}
protected function getUpdatedAtAttribute($value)
{
$format = "Y-m-d\TH:i:s\Z";
$datetime = new DateTime($value);
return $datetime->format($format);
}
Results:
{
"id": 1,
"created_at": "2019-09-02T20:31:38Z",
"updated_at": "2019-09-02T20:31:38Z"
}
As in the documentation. The first approach. This requires the dates, to be defined in the $dates property. This will only be triggered if the Model is serialized.
public class YourModel extends Model
{
protected $dateFormat = "Y-m-d\TH:i:s\Z";
}
You can also define it in a provider in the boot method. Which will trigger when a Carbon date is serialized.
public function boot()
{
Carbon::serializeUsing(function ($carbon) {
return $carbon->format("Y-m-d\TH:i:s\Z");
});
}

Date field must cast to 'date' in Eloquent model

Hi I'm using laravel nova for create an admin panel. And I'm trying to use Date Field.
This is my migration,
$table->date('day')->nullable();
This is my my nova resource,
public function fields(Request $request)
{
return [
ID::make()->sortable(),
Date::make(__('Day'), 'day'),
];
}
This is my model,
class Menu extends Model
{
use HasTranslations;
public $translatable = ['name','description'];
protected $fillable = [
'name','description','typology', 'cost', 'day', 'active', 'buffet'
];
This is my error,
Date field must cast to 'date' in Eloquent model.
Do I need to do anything in resource ?
In your model class you just need to add the following lines which describe to laravel that you must cast the day field into a date object (Carbon):
//Casts of the model dates
protected $casts = [
'day' => 'date'
];
Check here someone has the same issue.
EDIT:
I have seen that your day column is set to nullable i think that your field Nova should be to like in this post :
public function fields(Request $request)
{
return [
ID::make()->sortable(),
Date::make(__('Day'), 'day')->nullable(),
];
}
And we need to change the model like this,
protected $casts = ['day' => 'date'];

Problem relationship many to many on Laravel

I'm new to Laravel, I'm having this problem:
I have 2 tables, platos and ingredientes, these have a many to many relationship, and for this I use a third table called ingredientes_platos.
To save the relationship many to many I tried with the following:
$platos->ingredientes()->attach($input['ingredientes']);
but it gives the following error:
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '151-3' for key 'PRIMARY' (SQL: insert into ingredientes_platos (platos_id, ingredientes_id, norma_bruta) values (151, 3, ))
Looking a bit in the documentation, I could solve with sync instead of attach, but this does not solve my problem, because in addition to saving the id's of the relationship I need to save other attributes in the pivot table.
It is important to note that if I try to save this data in a table other than ingredients_platos, I do not get this problem and the data is saved correctly no matter which method I use.
I appreciate your attention, I hope you can help me.
These are the models for the three tables:
Table Platos:
public $table = 'platos';
protected $dates = ['deleted_at'];
public $fillable = [
'Grupo',
'Nombre',
'Procedimiento',
'Cantidad',
'Unidad',
'Precio'
];
/**
* The attributes that should be casted to native types.
*
* #var array
*/
protected $casts = [
'Grupo' => 'integer',
'Nombre' => 'string',
'Procedimiento' => 'string',
'Cantidad' => 'integer',
'Unidad' => 'integer',
'Precio' => 'double'
];
/**
* Validation rules
*
* #var array
*/
public static $rules = [
'Grupo' => 'required',
'Nombre' => 'required'
];
public function ingredientes()
{
return $this->belongsToMany(Ingredientes::class);
}
public function grupo_platos()
{
return $this->hasOne('App\Models\Grupo_Platos', 'id', 'Grupo');
}
}
Table Ingredientes:
public $table = 'ingredientes';
protected $dates = ['deleted_at'];
public $fillable = [
'Grupo',
'Nombre',
'Descripcion',
'Kcal',
'Proteinas',
'Grasas',
'Unidad',
'Precio'
];
/**
* The attributes that should be casted to native types.
*
* #var array
*/
protected $casts = [
'Grupo' => 'integer',
'Nombre' => 'string',
'Descripcion' => 'string',
'Kcal' => 'double',
'Proteinas' => 'double',
'Grasas' => 'double',
'Unidad' => 'integer',
'Precio' => 'double'
];
/**
* Validation rules
*
* #var array
*/
public static $rules = [
'Nombre' => 'required'
];
public function platos()
{
return $this->belongsToMany(Platos::class);
}
}
Table Ingredientes_Platos:
public $table = 'ingredientes_platos';
public $fillable = [
'platos_id',
'ingredientes_id',
'norma_bruta',
'norma_neta',
'unidad_id'
];
public $timestamps = false;
}
Platos Controller:
public function store(CreatePlatosRequest $request)
{
$input = $request->all();
$platos = $this->platosRepository->create($input);
$id = $platos->id;
$ingredientes = $input['ingredientes'];
$norma_b = $input['norma_b'];
$t = sizeof($ingredientes);
$i=0;
for ($i = 0; $i < $t; $i++) {
$pivot = new Ingredientes_Platos;
$pivot->platos_id = $platos['id'];
$pivot->ingredientes_id = $ingredientes[$i];
$pivot->norma_bruta = $norma_b[$i];
$pivot->save();
}
Flash::success('Plato agregado correctamente.');
return redirect(route('platos.index'));
}
If you need one type to "belong" to another type multiple times, and vice versa, then the generic many-to-many relationship probably isn't a good fit for what you're trying to do. Not just because of the primary key constraint, but because you'll have cases in which multiple row results are returned for the relationship, and the many-to-many relationship won't know how to aggregate or group these. You may want to come up with a third type of Model that's something like an "attribute" that has relationships to the other two models and helps you accomplish your goal.
Thank you very much, my problem was solved. It happens that in addition to Laravel, I'm using InfyOM and this was creating a conflict when I was trying to manually add the records to the pivot table, because this line was already responsible for adding the registry to the pivot table: $ dishes = $ this-> dishesRepository-> create ($ input);
// Many to Many relationship in category Model to Post model coding here
namespace App;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
public function posts()
{
return $this->belongsToMany('App\Post')->withTimestamps();
}
}
// Many to Many relationship in Post Model to Category model coding here
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
public function categories(){
return $this->belongsToMany('App\Category')->withTimestamps();
}
}

Resources