Including related model data in Page API request - php

I've got a relatively simple setup running using SilverStripe 3.2.1 with the restfulserver addon and using a variety of widgets which are associated to a Page using the elemental addon.
When I make a GET request via the API to retrieve some of Page #1's data, I can see the associated ElementAreaID:
# GET /api/v1/Page/1.json?fields=Title,URLSegment,Content,ElementArea
{
"Title": "Welcome",
"URLSegment": "home",
"Content": "A bunch of HTML here from all the widgets in the page...",
"ElementArea": {
"className": "ElementalArea",
"href": "http://ss.local:3000/api/v1/ElementalArea/11.json",
"id": "11"
}
}
If I follow the links through the ElementalArea API calls it will list out all of the elements in my page:
# GET /api/v1/ElementalArea/11.json
{
"ID": "11",
"Widgets": [
{
"className": "Widget",
"href": "http://ss.local:3000/api/v1/Widget/9.json",
"id": 9
},
{
"className": "Widget",
"href": "http://ss.local:3000/api/v1/Widget/8.json",
"id": 8
},
...
]
}
And if I follow those API paths it will serve up the contents of the latest version of each of the Widgets.
My question is how can I include certain fields from the Widget DataObjects within the original Page field list?
I'd like ideally to have the Content field from each Widget be returned in an array with the initial Page API request.
For reference:
A Page has one ElementArea
An ElementArea has many Widgets
A Widget contains content that I want for my Page

Preamble: It doesn't seem there's currently a way to output array-like datastructures with the RESTful server module (except for relations of course). The proposed solution is a hack that abuses how JSONDataFormatter formats output.
Since JSONDataFormatter uses forTemplate to render a field before converting it to JSON, we can create our own Object renderer that returns an array instead of a string via forTemplate. This might look like this:
class FlatJSONDataList extends ViewableData
{
protected $list;
public function __construct(array $list)
{
parent::__construct();
$this->list = $list;
}
public function forTemplate()
{
return $this->list;
}
}
Then in your Page, it should be sufficient to have an additional method, like so:
public function getWidgetContents()
{
return FlatJSONDataList::create(
$this->ElementArea()->Widgets()->column('Content')
);
}
Then you can include WidgetContents in your Field-list to get all widget Content fields in an array:
GET /api/v1/Page/1.json?fields=Title,URLSegment,Content,WidgetContents

Related

How to get array in array with laravel and mysql database?

I am using Laravel (PHP) and MySQL for my backend. I am creating methods for setting and getting information from the database. Those information are being send as a json to the frontend.
I can send table information like:
[
{
"id": 4,
"name": "Max"
},
{
"id": 5,
"name": "Eric"
}
]
For this I am using laravel methods like: DB::table('user')->select('user.id', 'user.name')->get();
However my friend who is doing the frontend want the following json code:
[
{
"id": 4,
"name": "Max"
"specific_user_price":{
"S":5.00,
"M":6.00,
"XL":8.00
}
},
{
"id": 5,
"name": "Eric"
"specific_user_price":{
"S":5.50,
"M":10.00,
"XL":15.00
}
}
]
"specific_user_price is a table and "S", "M", "XL" are columns which have different values depending on the user. I do not know how I can create specific_user_price as an array in a query. I can use group_concat but he needs the json like displayed above.
My idea was to create additional columns in user "size S price", "size M price" and "size XL price". However my friend want those values as an own array group, because some users only habe access to one size, so he would get null values.
Any ideas which method in PHP or Laravel I can use for that? Or is there a MySQL method for creating such thing in a query?
Firstly use Models, way easier to work with out of the box. Define your User model like this, with a relationship for the price.
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public function specificUserPrice() {
return $this->hasOne(UserPrice::class);
}
}
You also need to have the UserPrice model defined.
use Illuminate\Database\Eloquent\Model;
class SpecificUserPrice extends Model
{
}
Laravel automatically transforms models, you can get away with the following code in the controller.
public function index()
{
return User::with('specificUserPrice')->get();
}
DB::table('user')->select(['id', 4],['name','max'])->get();
DB::table('user')[0]->get()
This gets you the first element in the array

Laravel 7.x - Using API Resources to Generate Pagination Links While Preserving Query String With Special Characters

I have an API written in Laravel that utilizes Spatie Laravel Query Builder package to query a table and then return a JSON response using an API Resource. Since this method acts sort of like a search feature as well, any pagination links should also include the additional parameters sent with the request. Here is an example:
MessageController.php
class MessageController extends Controller
{
public function index(Request $request)
{
$messages = QueryBuilder::for(Message::class)
->allowedFilters([
AllowedFilter::exact('id'),
AllowedFilter::scope('unread'),
])
->paginate(30);
return MessageResource::collection($messages->appends($request->except('page')));
}
}
This works great until you start to introduce special characters such a [ or ] into the url parameters. For example, we might send a request to api/messages?filter[unread]=true and we want the response to have a links section like the following:
What We Want
... Other JSON Data ...
"links": {
"first": "https://test.test/api/messages?filter[unread]=true&page=1",
"last": "https://test.test/api/messages?filter[unread]=true&page=1",
"prev": null,
"next": null
},
... Other JSON Data ...
What We're Getting
... Other JSON Data ...
"links": {
"first": "https://test.test/api/messages?filter%5Bunread%5D=true&page=1",
"last": "https://test.test/api/messages?filter%5Bunread%5D=true&page=1",
"prev": null,
"next": null
}, ... Other JSON Data ...
Is there an easy way to make it so that those special characters remain intact?

How to set Updates to this data - Laravel/Lumen

I have a web application where the user shall be able to change MariaDB records via a GUI inside the browser.
In this GUI, the user sees a list of the records visible for him. Whatever he can see, he has permission to delete or change as well.
To change a DB record, he simply has to click the fields he wants to change inside the list and then press the "send" button.
In his HTTP request, the id associated with the record will be transmitted so the backend can identify the respective record inside the DB and apply the changes.
Now, Im rather new to Laravel/Lumen. To fetch the list the user can apply changes to in the first place, I have the following code:
$join = coretable::with($permittedTables)->get();
The $permittedTables is an array of tablenames, so any number or combination of tables might be joined to coretable.
For example, a fetch can look like this:
[{
"Internal_key": "TESTKEY_1",
"extensiontable_itc": {
"description": "EXTENSION_iTC_1"
},
"extensiontable_sysops": {
"description": "EXTENSION_SYSOPS_1"
}
}, {
"Internal_key": "TESTKEY_2",
"extensiontable_itc": {
"description": "EXTENSION_ITC_2"
},
"extensiontable_sysops": {
"description": "EXTENSION_SYSOPS_2"
}
}, {
"Internal_key": "TESTKEY_3",
"extensiontable_itc": {
"description": "EXTENSION_ITC_3"
},
"extensiontable_sysops": {
"description": "EXTENSION_SYSOPS_3"
}
}, {
"Internal_key": "TESTKEY_4",
"extensiontable_itc": {
"description": "EXTENSION_ITC_4"
},
"extensiontable_sysops": {
"description": "EXTENSION_SYSOPS_4"
}
}, {
"Internal_key": "TESTKEY_5",
"extensiontable_itc": {
"description": "EXTENSION_ITC_5"
},
"extensiontable_sysops": {
"description": "EXTENSION_SYSOPS_5"
}
}]
Now, I wondered if I could just reuse the code I've created to fetch the data for setting the data.
So I used the above shown $join and tried to determine the datarecords I want to change.
The attempt looks like this:
$join = $join->find("TESTKEY_1");
The find however doesnt return anything.
Considering the structure of the results I'm querying here, is this approach even feasible? Or should I build some new code, fetching the results in a different structure, better suited to have changes applied to it?
Still, is there a way to search these fetchresults for a subset of data and then apply changes to this subset (and persist those changes to the DB, of course)?
$join = $join->find("TESTKEY_1");
find method works only for ids.
e.g. $join = $join->find(1); //1 is id (primary_key) in here.
if you want to find the record by TESTKEY_1
$join = $join->where("Internal_key","TESTKEY_1")->get();
// will return array of all records having Internal_key TESTKEY_1
if you want only the first record to be fetched
$join = $join->where("Internal_key","TESTKEY_1")->first();
// will return only first having Internal_key TESTKEY_1 as an array.

Modify cake association result array

I am in a problem of modifying the cake association hierarchy,I am new to cakephp , below is the snippet of code.
Current Code
$this->Cateogory->find('all');
The output of the above expression is of the below form :
[
{
"Category": {
"id": "37",
"title": "Inner Title",
"color": "#ffffff",
"phone-number": ""
},
"CategoryHas": [],
"PhoneNumberhas": []
}
]
Requirement :-
I am creating API for third party consumption , now the third party has given the output format as below. I want the the output to be of below form.
[
{
"Category": {
"id": "37",
"title": "Inner Title",
"color": "#ffffff",
"phone-number": "",
"CategoryHas": [],
"PhoneNumberhas": []
}
}
]
I am searching on this problem for past 1 day and I consider that I need to make a custom PHP function to solve the problem.
Thanks in advance :
Just use a loop
To convert the array of the first format to an array of the second format - just use a loop e.g.:
foreach ($stuff as &$row) {
$row['Category']['CategoryHas'] = $row['CategoryHas'];
$row['Category']['PhoneNumberhas'] = $row['PhoneNumberhas'];
unset($row['CategoryHas'], $row['PhoneNumberhas']);
}
This would be appropriate to put in your controller if it's only needed in one place.
If you always, always want your results in that format, use an afterFind method (or a behavior) to implement the same logic:
// In the category model
public function afterFind($results, $primary = false) {
foreach ($results as &$row) {
if (isset($row['CategoryHas'])) {
$row['Category']['CategoryHas'] = $row['CategoryHas'];
}
if (isset($row['PhoneNumberhas'])) {
$row['Category']['PhoneNumberhas'] = $row['PhoneNumberhas'];
}
unset($row['CategoryHas'], $row['PhoneNumberhas']);
}
return $results;
}
Be wary of choosing this option - it means that your models act unconventionally, potentially meaning plugins don't work and other developers (or you, tomorrow) are confused by the way the models act.
Your association names are pretty strange. Why are they not better named? You can use proper names internally and reformat it when sending it.
If this is just about getting the API response into a silly format that doesn't provide additional benefit use JSON views and change the structure of the array in the view as needed. This section of the book explains it in detail, read it. Example taken from the book:
// Controller code
class CategoriesController extends AppController {
public function index() {
// ... get the categories
$this->set(compact('categories'));
}
}
// View code - app/View/Posts/json/index.ctp
foreach ($categories &$row) {
if (isset($row['CategoryHas'])) {
$row['Category']['CategoryHas'] = $row['CategoryHas'];
}
if (isset($row['PhoneNumberhas'])) {
$row['Category']['PhoneNumberhas'] = $row['PhoneNumberhas'];
}
unset($row['CategoryHas'], $row['PhoneNumberhas']);
}
echo json_encode(compact('categories'));
I would convince the client and bring some arguments for a proper response format and structure. Most clients usually don't know what they want. Sometimes you have smart asses who think they need something specific but can explain why. Check JSend or http://jsonapi.org/ on how to implement a proper API response format. Understand the reasons for these formats and bring these arguments to the client.

Include a search box that can look through a JSON and return drop down list

I'm using a JSON file to populate some drop down menus for various conversions. The converter has grown somewhat and I'd like to have the option of including a search bar.
So a user can search for Milliampere instead of navigating to the 'Current' category within "Electricity" etc etc. I'd just like to make it a bit easier for them.
My JSON only has two fields, the name and value of the drop down, example below.
The value is a string in the fashion that I understand so that Milliampere is milliAmpere and various others all using camel case. I don't think it would be suitable to run the search on this field as the values may differ from the name.
The name field in this instance is Milliampere(mA) so I need for the search to be able to look at part of the string and not do a full match ignoring case as the liklihood is the search string would be milliamp or milliampere or even milliamperes.
From there the next step is either to populate the drop downs whilst on that page or to return a list of possible options if there are many.
Is this possible at all and if so can you please guide me in the right direction?
Many thanks!
"current":[
{
"value" : "ampere",
"name" : "Ampere(A)"
},
{
"value" : "kiloAmpere",
"name" : "Kiloampere(kA)"
}]
You can use this JS lib - DefiantJS (defiantjs.com), which extends the global object JSON with a new method: "search". With this method, you can search a JSON structure with XPath expressions, like this:
var data = {
"current": [
{ "value": "ampere", "name": "Ampere(A)" },
{ "value": "kiloAmpere", "name": "Kiloampere(kA)" },
{ "value": "milliAmpere", "name": "milliAmpere" },
{ "value": "Milliampere", "name": "Milliampere" }
]
},
res = JSON.search( data, '//name[contains(translate(., "MA", "ma"), "millia")]/..' );
console.log( res[0].name );
console.log( res[1].name );
The "translate" method in the expression extends the search to include uppercase and lowercase versions of the letters "ma". If you want to cover all the alphabetic letter, then add all of them.
Here is a working fiddle;
http://jsfiddle.net/hbi99/6ssS9/

Categories