Laravel Array & JSON Casting to Algolia - json

I am trying to send some data along to Algolia through the toSearchableArray. Any strings I have stored in my DB are sending along fine, but I hit a roadblock when trying to push nested JSON data along—the information is being sent as a string with characters escaped.
This is a sample of the nested object that I am storing in my table (MySQL with a JSON data type):
[
{
"id": 19,
"name": "Mathematics",
"short": "Math"
},
{
"id": 23,
"name": "Science",
"short": "Science"
},
{
"id": 14,
"name": "Health and Life Skills",
"short": "Health"
}
]
My model looks like this:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Resource extends Model
{
use Searchable;
protected $primaryKey = 'objectID';
public function toSearchableArray()
{
$data = $this->toArray();
$data['grades'] = explode(';', $data['grades']);
$data['units'] = explode(';', $data['units']);
return $data;
}
}
I get an output that looks like this:
array:22 [
"objectID" => 1
"name" => "Resource #1"
"slug" => "resource-1"
"write_up" => """
This is an example write up.
"""
"author" => "johnny"
"type_name" => "Lesson Plan"
"language" => "English"
"grades" => array:3 [
0 => "Kindergarten"
1 => "Grade 1"
2 => "Grade 4"
]
"subjects" => "[{"id": 19, "name": "Mathematics", "short": "Math"}, {"id": 23, "name": "Science", "short": "Science"}, {"id": 14, "name": "Health and Life Skills", "short": "Health"}]"
"units" => array:2 [
0 => "Unit A"
1 => "Unit B"
]
"main_image" => "https://dummyimage.com/250x325/000000/fff.png&text=Just+a+Test"
"loves" => 88
"downloads" => 280
"created_at" => "2018-01-01 13:26:47"
"updated_at" => "2018-01-02 10:10:32"
]
As you can see, the 'subjects' attribute is being stored as a string. I know there is attribute casting in 5.5 (I am running 5.5), but I am not too clear on how I would implement the example they have for Array & JSON Casting in my work above. https://laravel.com/docs/5.5/eloquent-mutators#attribute-casting
Would anyone be willing to show me an example?

I'd rely on Attribute Casting for this, add a $casts property in your model and it will be done automatically.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Resource extends Model
{
use Searchable;
protected $primaryKey = 'objectID';
protected $casts = [
'subjects' => 'array',
];
public function toSearchableArray()
{
// Same function as you posted
}
}
You can also do it manually in your toSearchableArray method with $data['subjects'] = json_decode($this->subjects, true);
I answered with more details on this other posts: https://discourse.algolia.com/t/laravel-array-json-casting-to-algolia/4125/2

Related

Laravel: Resources with json field

I am on Laravel 7.x and I have two models (CustomerOrder composed of many CustomerOrderLines) with parent - child relationship. Parent (CustomerOrder) model has a json type field among its fields.
CustomerOrderResource.php:
return [
'id' => $this->id,
'wfx_oc_no' => $this->wfx_oc_no,
'qty_json' => json_decode($this->qty_json)
];
CustomerOrderLineResource.php:
return [
'id' => $this->id,
'description' => $this->description,
'customer-order' => $this->customerOrder
];
CustomerOrder->GET request returns properly formatted data as:
"data": {
"id": 11,
"wfx_oc_no": 12,
"qty_json": {
"L": "20",
"M": "30",
"S": "20",
"XL": "100"
}
}
But for CustomerOrderLine->GET, the response is as:
"data": {
"id": 15,
"description": "test desc",
"customer-order": {
"id": 11,
"wfx_oc_no": 12,
"qty_json": "{\"L\": \"20\", \"M\": \"30\", \"S\": \"20\", \"XL\": \"100\"}"
}
}
json field is not properly formatted. It seems it doesn't go through Resource class. Please let me know, how can I get this fixed?
FYI
CustomerOrderLine.php:
public function parent()
{
return $this->belongsTo(CustomerOrder::class);
}
Finally managed to get it solved using json cast. The field was included in $casts array in the model.

Laravel API resource (Problem with creating exact API)

I want to create an API in the below format. But I am unable to do it. I am using the API resource. I have tried using different queries but I am not getting the exact solution.Please help me.
Thank you
what I have tried
$marks = StudentMarksResource::collection(StudentsMark::whereIn('academic_id',$ids)->whereIn('student_id',$studentid)->get());
my API resource file
public function toArray($request)
{
return [
'exam_type' => $this->exam_type,
'details' => [
/* 'class_id' => Course::find($this->class_id),
'batch_id' => Batch::find($this->batch_id),
'student_id' => Student::find($this->student_id), */
'subject' => $this->subject,
'marks' => $this->marks,
'marksgrade' => $this->marksgrade,
'total' => $this->total,
'grade' => $this->grade,
'percentage' => $this->percentage,
'year' => $this->year,
],
];
}
what I want is this
data": [
{
"exam_type": "Unit Test 1",
"details": {
//subject1 details
},
{
//subject2 details
},
{
//subject3 details
},
"exam_type": "Unit Test 2",
"details": {
//subject1 details
},
{
//subject2 details
},
{
//subject3 details
},
},
what I am getting for the above code
"data": [
{
"exam_type": "Unit Test 1",
"details": {
//subject1 marks details
}
},
{
"exam_type": "Unit Test 1",
"details": {
//subject2 marks details
}
},
Here is something you can try:
First get exam types
$exam_types = StudentsMark::select('exam_type', 'student_id', 'academic_id')->whereIn('academic_id',$ids)->whereIn('student_id',$studentid)->get();
$marks = StudentMarksResource::collection($exam_types);
Now inside resource that's when you can retrieve the details
#do not forget to import StudentsMark
#and you can make a resource for details, just to make clean code
return [
'exam_type' => $this->exam_type,
'details' => StudentsMark::where('academic_id', $this->academic_id)
->where('student_id', $this->student_id)
->get();
];

Laravel using avg() function with foreign key

I want to get the average rates for each product.
I have a rates table which has a foreign key to product table,
the rates table is similar to this
when I try to get products with this code:
$stocks = Stocks::with('images:url,color', 'tags:tag', 'sizes', 'rates')
->get()
->pluckDistant('tags', 'tag')
->pluckDistant('sizes', 'size');
it returns this
[
{
"id": 10,
"name": "name",
"image": "1564964985mI7jTuQEZxD49SGTce6Qntl7U8QDnc8uhVxedyYN.jpeg",
"images": [
{
"url": "1564964985mI7jTuQEZxD49SGTce6Qntl7U8QDnc8uhVxedyYN.jpeg",
"color": ""
},
{
"url": "1564964985EV20c1jGvCVCzpCv2Gy9r5TnWM0hMpCBsiRbe8pI.png",
"color": ""
},
{
"url": "1564964985iFcMox6rjsUaM8CHil5oQ9HkrsDqTrqLNY1cXCRX.png",
"color": ""
}
],
"tags": [
"عطور"
],
"sizes": [],
"rates": [
{
"id": 1,
"stocks_id": 10,
"rate": 2
},
{
"id": 2,
"stocks_id": 10,
"rate": 4
}
],
}
]
How can I get the average of rates as "rates":3 using the eloquent relations to get them all by sql without php proccessing?
You could leverage something like Appending. Say you have a Product model which has a OneToMany relationship with Rate model.
Your Product model would look something like this:
class Product extends Model
{
protected $with = ['rates'];
protected $appends = ['average_rate'];
public function getAverageRateAttribute()
{
return $this->attributes['average_rate'] = $this->rates->avg('rate');
}
public function rates() {
return $this->hasMany(Rate::class);
}
}
Now anytime you query your products from the database, you'll have the rate appended with the result.
array:7 [▼
"id" => 1
"created_at" => "2019-08-12 14:08:09"
"updated_at" => "2019-08-12 14:08:09"
"average_rate" => 4.5
"rates" => array:2 [▶]
]
However, be aware of causing n+1 problem. If you're using this approach make sure to always eager load your rates.
You could just use join and use aggregate function on rates table.
Stocks::with('images:url,color', 'tags:tag', 'sizes')
->join('rates', 'rates.stocks_id', '=', 'stocks.id')
->selectRaw('stocks.*')
->selectRaw('AVG(rates.rate) as average_rating')
->selectRaw('tags.*')
->selectRaw('sizes.*')
->selectRaw('images.*')
->groupBy('stocks.id')
->get()

Getting a selected property from a getter

I have an array of containers in my state and I'm trying to setup a getter that splits it into active and inactive containers.
containers: [{
id: '1',
name: 'test container',
image: 'some image',
state: 'running',
status: 'Running'
}, {
id: '2',
name: 'another test container',
image: 'some image',
state: 'stopped',
status: 'Running'
}]
I'm using this to get the array below.
export const x = state => _.partition(state.containers, c => c.state === 'running');
The problem with this is I want it split and assigned to activeContainers and stoppedContainers and then exported.
[
[
{
"id": "1",
"name": "test container",
"image": "some image",
"state": "running",
"status": "Running"
}
],
[
{
"id": "2",
"name": "another test container",
"image": "some image",
"state": "stopped",
"status": "Running"
}
]
]
I've tried using ES6's deconstruction but I think I'm missing something or putting the deconstruction in the wrong place for it to work with Vuex.
export const [activeContainers = [], stoppedContainers = []] = state => _.partition(state.containers, c => c.state === 'running');
Returning functions from a getter
As per the comments go by in the question, it is indeed true that you can't have two properties mapped by a getter. Reading more that you didn't want another getter for the activeContainers and stoppedContainers
After going through the link you shared, I found a way you could still have something very close to a parameterized getter.
Here you can see it in full effect.
getters = {
getContainer: (state) => {
const [activeContainer = [], inactiveContainer = []] = _.partition(state.containers, c => c.state === 'running')
return (container) => {
return (container === 'activeContainer') // returned function
? activeContainer
: inactiveContainer
}
}
}
Here in this getter, I am returning a function, which can accept parameters and get something very close to what you desire.

Deserialize multidimensional JSON API Response with JSMSerializerBundle

I work with Symfony2/JSMSerializerBundle.
Serializing flat json objects to PHP objects works great. But the API I use, gives a multidimensional Json response:
{
"Webmessage": {
"#version": "1.0",
"Header": {
"Country": "NL",
"Language": "NL"
},
"Content": {
"Filters": {
"Sizes": {
"Size": [
{
"#id": "241",
"#text": "3,5"
},
{
"#id": "55",
"#text": "36"
}
]
},
"Colours": {
"Colour": [
{
"#id": "159",
"#text": "wit"
},
{
"#id": "54",
"#text": "zwart"
}
]
}
}
}
}
}
As deserialized PHP I want something like this:
Array
(
[sizes] => Array
(
[0] => AppBundle\Entity\Filter Object
(
[id:AppBundle\Entity\Filter:private] => 1
[text:AppBundle\Entity\Filter:private] => Heren
)
[1] => AppBundle\Entity\Filter Object
(
[id:AppBundle\Entity\Filter:private] => 2
[text:AppBundle\Entity\Filter:private] => Dames
)
)
[colour] => Array
(
[0] => AppBundle\Entity\Filter Object
(
[id:AppBundle\Entity\Filter:private] =>56
[text:AppBundle\Entity\Filter:private] => Black
)
[1] => AppBundle\Entity\Filter Object
(
[id:AppBundle\Entity\Filter:private] => 212
[text:AppBundle\Entity\Filter:private] => Yellow
)
)
)
Who has tips how i can do this?
Thanks!
Maybe you can decode it first and then use the Normalizer to create the entities. Something like this:
$array= json_decode($json, true);
$valueToDenormalize = $array['value'];
$normalizer = new GetSetMethodNormalizer();
$entity = $normalizer->denormalize($valueToDenormalize, 'Your\Class');
Be aware, I have not tried this. I don't know if the normalizer will work this way, but I know it's used to normalize and denormalize between arrays and Symfony's entities.
For further investigation you could take a look at the Serializer docs:
http://symfony.com/doc/current/components/serializer.html
Or the Normalizer:
http://api.symfony.com/2.3/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.html
Here's something about json_encode:
http://php.net/manual/en/function.json-decode.php