Laravel Eloquent Model Dynamic Stored Properties from and to a single serialized field - laravel

Using Eloquent L5.1
I'd like to not have to define a set of properties for a model. Instead having everything stored within the database as a serialized column.
In other words any property that is set to this model should before save be removed from the object and added to an array to be serialized and then saved in db to a "data" column.
In turn after retrieving the object from db the model should be hydrated with the de-serialized parameters from the data column.
I could override a good number of Illuminate\Database\Eloquent\Model methods to accomplish this and I'm willing to do so, was curious if anyone had an example of this.
An example of this might be for configuration objects that have virtually unlimited unknown values that have multiple itterations for different objects or for different users.

Put these accessors and mutators in your Model:
class YourModel extends Model
{
public function getDataAttribute($data)
{
return collect(json_decode($data,true));
}
public function setDataAttribute($data)
{
$data = (is_array($data) || is_object($data)) ? json_encode($data) : $data;
return $this->attributes['data'] = $data;
}
}
Now when you can pass data as json string, array or object
YourModel::create(['data'=>'{"working":"ok"}']);
YourModel::create(['data'=>['working'=>'ok']]);
It will work in all three cases.

Related

Extending Eloquent Models with dynamic global scopes

I have a MySQL table that receives many different Jotform reports in JSON payloads. This has helped tremendously in capturing queryable data quickly without adding to the front-end developer's workload.
I created an eloquent model for the table. I now would like to be able to create models that extend it for each Jotform we create. I feel like it will increase the readability of my code drastically.
My eloquent model is called RawDataReport. It has created_at, updated_at, data, and report name columns in the table. I want to create the model ShiftInspectionReport extending the RawDataReport.
I have two JotForm reports one is called Shift Inspection Report and one is called Need Inspection Report. Both are part of the ShiftInspectionReport model.
So I need to query the RawDataReports table for any reports matching those names. I frequently need to query the RawDataReports report_name column with either one or more report names.
To help with this I created a local scope to query the report name which accepts either a string report name or an array of string report names. Here is the local scope on the RawDataReports model.
protected function scopeReportName($query, $report_name): \Illuminate\Database\Eloquent\Builder
{
if (is_array($report_name)) {
return $query->orWhere(function ($query) USE ($report_name) {
ForEach($report_name as $report) {
if (is_string($report) === false) {
throw new \Exception('$report_name must be an array of strings or a string');
}
$query->where('report_name', $report);
}
});
} else {
if (is_string($report_name) === false) {
throw new \Exception('$report_name must be an array of strings or a string');
}
return $query->where('report_name', $report_name);
}
}
EDIT - after comments I simplified the reportName scope
protected function scopeReportName($query,array $report_name): \Illuminate\Database\Eloquent\Builder
{
return $query->whereIn('report_name',$report_name);
}
Now in my ShiftInspectionReport model, I'd like to add a global scope that can use that local scope and pass in the $report_name. But according to this article, Laravel 5 Global Scope with Dynamic Parameter, it doesn't look like Laravel global scopes allow you to use dynamic variables.
I could just create a local scope in ShiftInspectionReport but the readability would look like
$reports = ShiftInspectionReport::shiftInspectionReport()->startDate('2021-05-15')->get()
when I'd really like to be able to just call
ShiftInspectionReport::startDate('2021-05-15')->get()
Any suggestions or comments would be appreciated.
Thank you
Thanks to IGP I figured out that I can just call the local scope right from my boot function.
My extended class looks like this now and it works.
class ShiftInspection extends RawDataReport
{
use HasFactory;
protected static function booted()
{
static::addGlobalScope('shift_inspection_report', function(\Illuminate\Database\Eloquent\Builder $builder) {
$builder->reportName(['Shift Safety Inspection','Need Safety Inspection']);
});
}
}

Laravel, related model reuturened names

I have a model that has a related model
class A extends Model{
public function niceName()
{
return this->hasOne('App\NiceName2' ...);
}
In the controller when I retrieve data with submodel the result is like
a[nice_name_2] (using the table name) and I would like it to be a[NiceName2].
Is there a way to have an alias for the returned result? In cakePHP i know there is propertyName to set this on relations. Laravel has a similar feature?
Thanks
Laravel uses the convention of camelCase for method names and snake_case for attributes. I'm not sure there's an easy way around this.
When Laravel serializes the data, it converts relationships to snake_case, by convention. So NiceName2 would become nice_name2 when you execute toArray() or when the model is serialized (either in a JSON response or otherwise).
How this works is:
When you access $model->nice_name2 it converts the property name back to niceName2 to check for a relationship method with that name. When serializing, it converts the relationship niceName2 to the attribute name nice_name2.

The best way to operate output data models to build custom JSON?

I have some nested models with nested models.
best way to operate output data models to build custom JSON?
Now It is uncomfortable to iterate each model, add each others to separated array and after join by key.
Out of the box, you can add protected $hidden or protected $visible arrays as properties on the model class which will automatically hide or only make certain columns visible in JSON responses.
https://laravel.com/docs/5.4/eloquent-serialization#hiding-attributes-from-json
Additionally, you can use mutators to modify certain attributes when converting your models to JSON.
https://laravel.com/docs/5.4/eloquent-mutators#introduction
You can also add additional attributes not in your database to your JSON responses when converting a model to JSON.
https://laravel.com/docs/5.4/eloquent-serialization#appending-values-to-json
Example
class User extends Model {
protected $appends = ['is_admin'];
public function getIsAdminAttribute()
{
return $this->attributes['admin'] == 'yes';
}
}

Adding data to an eloquent collection?

Im getting various data out of my database.
Product::with('users');
I also have toe execute a complex raw query to get back some information. This is returned as an array.
In my method I would like to get products with users and then add on the data from my raw query to this collection, but this data comes back as an array. Something like:
Product::with('users');
Product->extraData = $rawQuery;
How can I add the raw query output to my Product Collection?
By using Eloquent Facade like Product:: you will get an Eloquent Model object as a result or an Eloquent Collection object as a result, including results retrieved via the get method or accessed via a relationship.
Now, if i understand correctly, you need to add a single extraData property to Eloquent Collection model alongside with Collection items? Or you need to add extraData for each Product ?
If you need to add additional property to Eloquent Collection object, maybe it is a good idea to use a Custom Collection. Please read this section: http://laravel.com/docs/5.1/eloquent-collections#custom-collections .
<?php namespace App;
use App\CollectionWithExtraData;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public function newCollection(array $models = [])
{
return new CollectionWithExtraData($models);
}
}
And maybe your CollectionWithExtraData can have let's say a
public function setExtraData() {
}
or
public $extraData = array();
If you need extraData for each Product Eloquent Model, just create a new attribute within your Eloquent Model, make it public and set your extra data when needed. Make use of setExtraData() method and $extraData property from above

Whats the right way to store JSON in a database with Laravel?

I have JSON data I want to store in a database. Ideally, the data should be automatically decoded when I retreive from the database and automatically encoded when I store in the database.
How can I do this?
Use Accessors and Mutators, for an attribute called foobar add these two functions in your Eloquent model:
public function getFoobarAttribute($value){
return json_decode($value);
}
public function setFoobarAttribute($value){
$this->attributes['foobar'] = json_encode($value);
}

Resources