Laravel's eloquent post-processing query - php

I am using PostgreSQL + PostGIS setup, and I want to fetch geom column from database as GeoJSON. I know I can do that using ST_AsGeoJSON() function. So i use this code to fetch all countries:
$countries = Country::select(["id", "name", DB::raw("ST_AsGeoJSON(geom) AS geom")])->get()
However, I end up with $countries[i]->geom being a string so I need to have for loop to do this:
$countries[i]->geom = json_decode($countries[i]->geom);
I would like to move this line of code to the Eloquent Model so I do not have to worry if I forget to decode column or not. Is there a method I should override or any way to add this special functionality to my Country model?

Add the following to your model
protected $casts = [
'geom' => 'array',
];
https://laravel.com/docs/5.3/eloquent-mutators#array-and-json-casting

You can use mutator/accessor to override or to decode the values in your model. So in your model do the following.
public function getGeomAttribute()
{
return json_decode($this->attributes['geom']);
}
With this you can access geom at different part of your application without decoding it.

Related

Laravel: Eloquent relationship from JSON

Still practicing in Laravel. At this time, I have the a page controller that finds all the sections available to the corresponding page (simple HTML sections) and includes them in the blade like this:
#foreach(#sections as #section)
#include('sections.' . $section->filename)
Then I have a sections table with data for the corresponding section, as well as columns column with a JSON data inside, that looks like this:
{"header":"Section column 1", "text":"bruh?", "image_id":""},
{"header":"Section column 2", "text":"bruh?", "image_id":""},
{"header":"Section column 3", "text":"bruh?", "image_id":"7"}
As you can see, there is image_id that should call for filename from the images table using Eloquent relationship (I assume it's gonna be One to Many).
Yes, I know that storing JSON inside the table is not the best solution (especially if you want to cross-request data from it), but it is the best I came up with.
So, the question(s) is: how can I get the filename of the image using just ID from a JSON object or is there any different solution, except JSON?
P.S. columns table and JSON format is used for defining some properties, since some of my sections have 3-9 text columns (col-md-3), and each of them has a different header, text and image, so I store them inside JSON.
P.P.S. My JSON format is valid, I just removed some formatting in order to keep this question clear.
Laravel has no native support for JSON relationships.
I've created a package for this: https://github.com/staudenmeir/eloquent-json-relations
class Section extends Model
{
use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;
protected $casts = [
'columns' => 'json',
];
public function image()
{
return $this->belongsTo(Image::class, 'columns->image_id');
}
}
class Image extends Model
{
use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;
public function sections()
{
return $this->hasMany(Section::class, 'columns->image_id');
}
}
{{ $section->image->filename }}

Use one method to all messages from db

I have a tables which I want to use one or more methods to all result from db. This method can change #hashtags to links, bbcode to html tags, etc. Let's assume that I have this query:
$query = Comments::orderBy('created_at', 'desc')->get();
Comments table has a comment column where user can use emoticons bbcode, etc. This query returns a few of results. How I can use method on column? If I have one result is simple:
$query = Posts::find(1);
$desc = myMethod($query->desc);
I assumne you want to format your eloquent attributes? have a look at https://laravel.com/docs/5.8/eloquent-mutators
and if you don't want to save your formatted data on db. you can just use the Accessor by defining it. for example please see below
// on your model you just need to create new methods
// with `get` as prefix and `Attribute` as suffix
public function getBbcToHtmlAttribute() {
return myMethod($query->desc);
}
please note that get and Attribute on your method name is required (e.g getMyNewAccessorAttribute`
after that you need to add casts property. if you already have casts property you just add it to the array if you dont have casts property you need to define it.
protected $casts = [
'BbcToHtml'
];
and then you can use it anywhere on your model instance.
$query = Posts::find(1);
$desc = $query->bbctohtml;
As MarkB has said, you would need to use a mutator, create a helper class that can take any input and produce the correct html code for your bbcode. Then you would create a mutator on your model and use that instead of the field where the bbcode text is.

Access keys in json type column for Eloquent collections?

A table column in my database is saved as json type. I want to display a keyed value in the saved json object on my blade templates but I'm unsure how.
Let's say I have $table->json('meta') in my table schema for Newsletter model, with meta column having e.g. {"foo": "bar"} as value.
How would I retrieve something like $newsletter->meta->foo? Since $newsletter->meta returns string instead of json object by default on Laravel 5.5 requiring a trivial json_decode to convert it.
A cleaner solution besides json_decode on every call, would be to use an accessor on that column e.g. getMetaAttribute but that's still messy. I want automatic json column to PHP object detection, how can I make this happen?
You can declare a protected $casts array inside your model in which you can instruct Eloquent to automatically convert types of model attributes. In your case that would look like this:
/*
* #property string $meta - json is actually just a string
*/
class Newsletter extends Model {
protected $casts = [
'meta' => 'array',
];
}
// Now you can use `$meta` as array:
$newsletter = Newsletter::find(1);
$foo = array_get($newsletter->meta, 'foo');
But this still does not convert it into an object. Although object is mentioned in the docs as being a valid cast type, I can not tell you exactly what it does.
As a note, these $casts are not bi-directional. If you want to set meta on a Newsletter instance, you'd have to create your own facilities to convert any object or array into a json string. Eloquent allows you to define mutators on your model to get the job done.

How to refresh values in laravel models where enums are replaced with values?

So I came across a weird issue while writing tests in laravel using factories. So this is a test I wrote:
/**
#test
*/
public function document_belongs_to_a_patent()
{
$patent = factory(Patent::class)->create();
$document = factory(Document::class)->create([
'documentable_id' => $patent->id,
'documentable_type' => 'patent'
]);
$this->assertArraySubset($patent->toArray(), $document->documentable->toArray());
}
So this should work, right because both should return the same thing and patent array should be equal or a subset of documentable array. But it was failing when I realised that there is an enum field in Patent model to which I am passing the value 1 but it was converted to the enum equivalent value in the database and when I tried document->documentable->toArray() it came back with the enum value rather than 1 which got me thinking how can make the model factory return the actual enum value and not the index number.
Top of the head I just fetched the patent just after creating it via the factory like so:
$patent = Patent::find($patent->id);
And it works well but it seems inconsistent. Is there a way to refresh models. I know we can refresh relationships of models but is there a way to do for the models themselves?
If you're strictly needing the change for API, you can do something cheeky with mutators like this.
https://laravel.com/docs/5.1/eloquent-serialization
Add this property. It tells Laravel that for special outputs only, it needs to append a non-database property.
protected $appends = ['documentable_type_name'];
Then you need some ways of knowing the language for the enum. You need an array, a #lang definition, etc. Here's a protected property solution that I am quite fond of in simple situations.
protected static $documentable_types = [ 'divorce', 'patent' ];
And then create this mutator on your Documentable model.
public function getDocumentableTypeName()
{
if ($this->documentable_type)
{
return static::$documentable_types[ $this->documentable_type ];
}
return null;
}
This changes your JSON output to look like this:
{
docuemntable_id : 555,
documentable_type : 1,
documentable_type_name : 'patent'
}
You can also hide the document_type field by adding this.
protected $hidden = ['documentable_type'];
And Laravel magic takes care of the rest. Hope that helps.

How to save multiple request parameters in laravel 5.2 ?

I am just learning laravel now. And I have this problem. I have passed 2 request parameters to my controller function. First request parameter holds an object value, but I converted it to a serialized form since the field of my table where it will be saved has a text datatype. The second request parameter holds a overall_total calculated value and it has a float datatype field. My problem is, how would I store it in my database? I have tried to use the create function but it returns an error. Some forums regarding this are not so clear. I just can't figure it out yet. Can somebody help me with this? Here are my codes.
function store
public function store(Request $request){
$serialize_po = serialize($request['purchase_orders']);
$overall_total = $request['overall_total'];
$purchase_orders_save = PurchaseOrder::create(?);
}
How would I save 2 parameters using create? or is there other way I can saved it?
Inside of $request['purchase_orders'] is shown in the image below
Inside of $request['overall_total'] is just a number. E.g. 310
My Database Table Structure is shown below
The create() function in Laravel accepts an associative array, where the array keys are the names of the columns, and the array values are the corresponding values for the columns.
So your store function might look something like this:
public function store(Request $request){
$serialize_po = serialize($request['purchase_orders']);
$overall_total = $request['overall_total'];
$purchase_orders_save = PurchaseOrder::create([
'purchase_orders' => $serialize_po,
'overall_total' => $overall_total
]);
}
One other thing to note is that as a safety precaution, Laravel does not allow the properties of a model to be filled in this fashion out of the box. You will need to list in your model using the $fillable property which keys you will allow to be filled by passing in an associative array. If you don't, you'll likely get a MassAssignmentException.
So in your model, you will likely need to have at least the following:
class PurchaseOrder extends Model
{
protected $fillable = ['purchase_orders', 'overall_total'];
}
There are more options and ways to do this, but that is the basic and typical scenario.
More info on using the create() method and handling mass assignment is available in the documentation: https://laravel.com/docs/5.2/eloquent#mass-assignment
Considering your model name is PurchaseOrder, you first need to create a new instance of it. Then, use the save method.
public function store(Request $request)
{
$purchaseOrder = new PurchaseOrder;
$purchaseOrder->overall_total = $request['overall_total'];
$purchaseOrder->purchase_orders = serialize($request['purchase_orders']);
$purchaseOrder->save();
}
See https://laravel.com/docs/5.2/eloquent#basic-inserts for more info.
Something like this :
DB::table('purchaseOrder')->insert(
['purchase_orders' => $serialize_po,'overall_total' => $overall_total]
);
See doc if you want to explore more.

Categories