Complicated JSON with Cakephp 3 - php

I am working on a project which involves passing data "profiles" via JSON to a web application. I am using Cakephp 3.0 and am very new to it. I store the profiles in a mysql database and can easily query for the data and put it into a basic JSON format with each row being a separate value in the JSON:
Controller.php:
....
public function getProfileData()
{
$uid = $this->Auth->user('id');
$this->loadComponent('RequestHandler');
$this->set('profile', $this->MapDisplay->find(
'all',
['conditions' =>
['MapDisplay.user_id =' => $uid]
]
)
);
$this->set('_serialize', ['profile']);
}
....
get_profile_data.ctp:
<?= json_encode($profile); ?>
Which returns something like this:
{
"profile": [
{
"alert_id": 1,
"alert_name": "Test",
"user_id": 85,
"initialized_time": "2017-03-24T00:00:00",
"forecasted_time": "2017-03-24T00:10:00",
"minimum_dbz_forecast": 0,
"maximum_dbz_forecast": 10,
"average_dbz_forecast": 5,
"confidence_in_forecast": 0.99,
"alert_lat": 44.3876,
"alert_lon": -68.2039
},
{
"alert_id": 1,
"alert_name": "Test",
"user_id": 85,
"initialized_time": "2017-03-24T00:00:00",
"forecasted_time": "2017-03-24T00:20:00",
"minimum_dbz_forecast": 5,
"maximum_dbz_forecast": 15,
"average_dbz_forecast": 10,
"confidence_in_forecast": 0.99,
"alert_lat": 44.3876,
"alert_lon": -68.2039
},
{
"alert_id": 2,
"alert_name": "Test2",
"user_id": 85,
"initialized_time": "2017-03-24T00:00:00",
"forecasted_time": "2017-03-24T00:10:00",
"minimum_dbz_forecast": 10,
"maximum_dbz_forecast": 20,
"average_dbz_forecast": 15,
"confidence_in_forecast": 0.99,
"alert_lat": 44.5876,
"alert_lon": -68.1039
},
{
"alert_id": 2,
"alert_name": "Test2",
"user_id": 85,
"initialized_time": "2017-03-24T00:00:00",
"forecasted_time": "2017-03-24T00:20:00",
"minimum_dbz_forecast": 15,
"maximum_dbz_forecast": 25,
"average_dbz_forecast": 35,
"confidence_in_forecast": 0.99,
"alert_lat": 44.5876,
"alert_lon": -68.1039
]
}
I am hoping to A) Easily call individual profiles instead of searching for unique profile ids and B) Only have to load one JSON file to get all profile contents. An output of something like this would be more ideal:
{
"profile": [
{
"alert_id": 1,
"alert_name": "Test",
"initialized_time":"2017-03-24T00:00:00",
"alert_lat": 44.3876,
"alert_lon": -68.2039,
"profile_data": [
{
"forecasted_time": "2017-03-24T00:10:00",
"minimum_dbz_forecast": 0,
"maximum_dbz_forecast": 10,
"average_dbz_forecast": 5,
"confidence_in_forecast": 0.99
},
{
"forecasted_time": "2017-03-24T00:20:00",
"minimum_dbz_forecast": 5,
"maximum_dbz_forecast": 15,
"average_dbz_forecast": 10,
"confidence_in_forecast": 0.99
}
]
},
{
"alert_id": 2,
"alert_name": "Test2",
"initialized_time": "2017-03-24T00:00:00",
"alert_lat": 44.5876,
"alert_lon": -68.1039,
"profile_data": [
{
"forecasted_time": "2017-03-24T00:10:00",
"minimum_dbz_forecast": 10,
"maximum_dbz_forecast": 20,
"average_dbz_forecast": 15,
"confidence_in_forecast": 0.99
},
{
"forecasted_time": "2017-03-24T00:20:00",
"minimum_dbz_forecast": 15,
"maximum_dbz_forecast": 25,
"average_dbz_forecast": 35,
"confidence_in_forecast": 0.99
}
]
}
]
}
How would I go about querying my database and populating this JSON structure? Are there any Cakephp tools that help do this? Does reframing the JSON into this structure seem to make sense?
Thanks in advance!

Thanks to user ndm, I realized there were a few problems with my approach. I thought having all data in one table would simplify things, but in reality it would make things more complicated and require redundant data storage (eg, a latitude and longitude value stored for every profile entry, instead of just once in a separate table).
ndm also mentioned
You'd just have to set up the associations properly, and contain the associated >table in your find, and in case the property name for the association would be >profile_data, you wouldn't even have to modify the results at all.
After altering the Table Model file, I had this for a new "ProfileDataTable.php" file:
class ProfileDataTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('profile_data');
$this->setDisplayField('title');
$this->setPrimaryKey('alert_id');
$this->addBehavior('Timestamp');
$this->belongsTo('AlertData', [
'foreignKey' => 'alert_id'
]);
}
}
And this for a new "AlertDataTable.php" file:
class AlertDataTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('alert_data');
$this->setDisplayField('title');
$this->setPrimaryKey('alert_id');
$this->addBehavior('Timestamp');
$this->hasMany('ProfileData', [
'foreignKey' => 'alert_id'
]);
}
}
The important lines here being "belongsTo" and "hasMany".
I then was able to alter my query and use "contain" to easily link the two tables together and get the JSON formatted exactly how I wanted:
$this->AlertData->find(
'all',
['conditions' =>
['AlertData.user_id =' => $uid],
'contain' =>
['ProfileData']
]
);

Related

Reset value on map / transform fucntion failed - Laravel

When i tried to change the value of id to encrypted Id is using map function and transform function, it get zero value no string value or alpha numeric getting replaced .
but it is mandatory to encrypt all id's for API
Please Help me .
Function
function getLatestArticle($profileId)
{
$data = Article::wherehas('articleTrans',function($query){
$query->where('status', ApiConstants::PUBLISHED_STATUS);
})->with(['articleTrans'=>function($q){
$q->select('id','article_id','language_id','title','short_description','description','main_image');
$q->where('status', ApiConstants::PUBLISHED_STATUS);
}])->latest()->paginate(ApiConstants::D_PAGE_C);
$data->getCollection()->transform(function ($value) {
$value->id = encrypt($value->id);
return $value;
});
return $data;
}
Collection
"latest_data": [
{
"id": 0,
"profile_id": 3,
"name": "Test",
"trending": 0,
"new_arrived": 0,
"featured": 0,
"reading_time": null,
"has_series": 0,
"status": 0,
"created_by": 1,
"updated_by": 1,
"deleted_at": null,
"created_at": "2022-03-24T10:27:16.000000Z",
"updated_at": "2022-03-31T11:41:14.000000Z",
"created_from": 1,
"article_trans": {
"id": 8,
"article_id": 12,
"language_id": 1,
"title": "Test",
"short_description": "Test",
"description": "<p><strong>TestTestTestTestTestTestTest </strong></p>\r\n\r\n<p><strong>TestTestTestTestTestTestTestTestTestTestTestTestTest</strong></p>",
"main_image": "1648117636_AXwwVY6JSmNTxmXIiRqGlXiePTl70chCkmMDlehp.jpeg",
"image_url": "http://127.0.0.1:8000/storage/admin/article/image/1648117636_AXwwVY6JSmNTxmXIiRqGlXiePTl70chCkmMDlehp.jpeg"
}
}
Its not good solution to temporarily modify entity objects only for change API response format.
Better solution is using DTO/Transformer classes. Good example of transformers implementation is https://fractal.thephpleague.com/transformers/
In this way you can split presentation and model.
Using any packages is optional, you can write simple transformer classes like this:
// app/Transformers/ArticleTransformer.php
final class ArticleTransformer
{
public static function transform(Article $article): array
{
return [
'id' => encrypt($article->id),
// any fields
];
/*
// or if you need change only one property
return array_replace(
$article->toArray(), // or $article->jsonSerialize()
[
'id' => encrypt($article->id),
],
);
*/
}
public static function transformCollection(Collection $articles): Collection
{
return $collection->map(fn (Article $article): array => self::transform($article));
}
}
Then use this transformer:
return [
'latest_data' => ArticleTransformer::transformCollection($latestArticles),
];
Not very good, but should work:
Override jsonSerialize/toArray method of model, in returning modify your id.

assertJson only looks at the first list within repsonse. (PHP,Laravel)

Greeting,
I am trying to produce this test to test my schools API
$suburb = "BobsTown";
$postcode = 2000;
$response = $this->json('GET',"http://hostname/api/schools/$postcode/$suburb");
$response->
assertStatus(200)->
assertJson([[
["school_name" => "BobsTown High School",
"school_type" => "Secondary"
],
[[
"boys-enrolments" => "439",
"girls-enrolments" => "381",
"indigenous-enrolments" => "12"
]]
]]
);
The actual repose of the API is as follows.
{
{
{
{
"calendar_year": "2019",
"school_name": "BobsTown Public School",
"suburb": "BobsTown",
"postcode": 2000,
"school_type": "Primary",
},
{
{
"girls-enrolments": 318,
"boys-enrolments": 359,
"indigenous-enrolments": 16,
}
}
},
{
{
"calendar_year": "2019",
"school_name": "BobsTown Valley Public School",
"suburb": "BobsTown",
"postcode": 2000,
"school_type": "Primary",
},
{
{
"girls-enrolments": 281,
"boys-enrolments": 269,
"indigenous-enrolments": 12,
}
}
},
{
{
"calendar_year": "2019",
"school_name": "BobsTown High School",
"suburb": "BobsTown",
"postcode": 2000,
"school_type": "Secondary",
},
{
{
"girls-enrolments": 381,
"boys-enrolments": 439,
"indigenous-enrolments": 12,
}
}
}
}
This test code works correctly when the school being tested is the first in the list however, as you can see BobsTown High School is the last in the list, the test only seems to check the first item in the list.
phpunit returns this error.
--- Expected
+++ Actual
## ##
...
- 'school_name' => 'BobsTown High School',
+ 'school_name' => 'BobsTown Public School',
...
I am using assertJson incorrectly? My assumption was it returns true is the assertion is true anywhere in the response.
I was able to get the functionality I wanted using assertJsonFragment()
public function testSchools4()
{
$suburb = "BobsTown";
$postcode = 2000;
$response = $this->json('GET', "http://hostname/api/schools/$postcode/$suburb");
$response->assertStatus(200)->assertJsonFragment(
[
"school_name" => "BobsTown High School",
"school_type" => "Secondary"
]
)->assertJsonFragment([
"girls-enrolments" => 381,
"boys-enrolments" => 439,
"indigenous-enrolments" => 12
]);
}
}

how to sort a merged object in laravel

i have 2 objects that i use merge on them like below :
$data = $city->merge($accommodation);
return AccCitySearchResource::collection($data);
and in my resource
return [
'id' => $this->id,
'name' => $this->name,
'english_name' => $this->english_name,
'accommodation_type_id' =>$this->accommodation_type_id,
'grade_stars' =>$this->grade_stars,
'hits' =>$this->hits,
'accommodation_count' => $this->accommodation_count,
];
now all i want is that my $data first shows all cities and then shows all accommodations but now it shows all together is there any way to sort them ??
EDIT
This is what i get now
{
id: 5,
name: "hotel",
accommodation_type_id: 1,
grade_stars: 4,
hits: 22,
accommodation_count: null
},
{
id: 7,
name: "city",
accommodation_type_id: null,
grade_stars: null,
hits: null,
accommodation_count: 0
},
{
id: 10,
name: "hotel",
accommodation_type_id: 1,
grade_stars: 2,
hits: 0,
accommodation_count: null
},
And this is what i want
{
id: 5,
name: "hotel",
accommodation_type_id: 1,
grade_stars: 4,
hits: 22,
accommodation_count: null
},
{
id: 10,
name: "hotel",
accommodation_type_id: 1,
grade_stars: 2,
hits: 0,
accommodation_count: null
},
{
id: 7,
name: "city",
accommodation_type_id: null,
grade_stars: null,
hits: null,
accommodation_count: 0
},
just to mention as you see all hotels comes first and then all cities come .
The recommended way in the documentation is to create your own resource collection if you need to customise the default behavior:
php artisan make:resource AccCitySearchCollection
Then you can customise your collection:
class AccCitySearchCollection{
public function toArray($request)
{
return [
'data' => $this->collection->sortBy('name')
];
}
}
You can then return a collection instance:
$data = $city->merge($accommodation);
return new AccCitySearchCollection($data);
You can use sortBy Collection method
Created Resource Collection for model.
Then in toArray method write:
/**
* Transform the resource collection into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'data' => $this->collection->sortByDesc(function($d){
return (int) ($d['name'] == 'hotel');
}),
];
}
This will sort your data as you want.
Hope this answer helps you
Maybe something like this ... But if you are getting your data from a database , maybe you could join and order the query instead..
$data = $city->merge($accommodation)->sortByDesc('some field of first object here');

Find difference between sets of objects

I have 2 objects and would like to find the difference between them. Return an array or object of the differences only. The two objects look as following.
{
"new": {
"crc_code": "00",
"serial_number": "239-03",
"reason": "Ir/b4c - no center rib",
"project_id": 9,
"wafer_id": 1,
"equipment_status_code_id": 7,
"plate_container_id": null,
"supplier_id": 1,
"container_slot_id": null,
"plate_quality_id": 1
},
"old": {
"crc_code": "00",
"serial_number": "239-03",
"reason": "Ir/b4c - no center rib",
"project_id": 9,
"wafer_id": 1,
"equipment_status_code_id": 2,
"plate_container_id": null,
"supplier_id": 1,
"container_slot_id": null,
"plate_quality_id": 2
}
}
What's the best way to go about this?
Update People asking what I have tried already?
Something along these lines.
array_udiff($new, $old, function ($obj_a, $obj_b) {
return strcmp($obj_a, $obj_b);
}
);
But not getting there..
Use array_diff_assoc:
return array_diff_assoc($obj->new, $obj->old);
// convert object to array
$arr = json_decode(json_encode($obj), true);
// get the diff
$diff = array_diff($arr['new'], $arr['old']);
// result
array:1 [
"equipment_status_code_id" => 7
]

Check if relationship of relationship if empty laravel 5.6?

I got a table encuesta that has a relationship encuesta_pregunta, the table encuesta_pregunta has a relationship to encuesta_respuesta, it return this
"id": 4,
//...table info...
"encuesta_preguntas": [
{
"id": 10,
"encuesta_id": 4,
"pregunta_id": 5,
//...table info....
"encuesta_respuestas": [
//this relationship can be empty
]
},
{
"id": 11,
"encuesta_id": 4,
"pregunta_id": 3,
//...table info....
"encuesta_respuestas": [
]
},
{
"id": 12,
"encuesta_id": 4,
"pregunta_id": 2,
//...table info....
"encuesta_respuestas": [
]
}
]
}
is there a way to checheck from without looping throught each encuesta_preguntas to know if encuesta_respuesta is empty?
o get the response above like this
$encuesta = Encuesta::with(['encuestaPreguntas' => function($preguntas) {
$preguntas->with(['encuestaRespuestas' => function($respuestas) {
$respuestas->where('user_id','=',auth()->id());
}]);
}])->where('fecha_inicio','<=',date('Y-m-d'))
->where('fecha_fin','>',date('Y-m-d'))
->first();
First. You can do it with Laravel accesors
public function getHasEncuestasAttribute()
{
return $this->encuesta_pregunta != [];
}
When you do $model->has_encuestas it will become true if the relationship isn't empty.
Second. Using relationship method has or doesntHave. If you want to get all the encuesta that have empty relationship you can call Encuesta::doesntHave('encuesta_pregunta') or the opposite that dont have empty relationship Encuesta::has('encuesta_pregunta').

Categories