How to access JSON data in a twig template? - php

I'm using Symfony2 and making a php curl request to an api. I'm looking to make sure the results are being returned in the correct form: as a json string (not a php object or something else). The returned json string is consumed by renderAPIResults() that uses twig to "render" a JsonResponse. Is this possible?
Can I "render" the response in renderAPIResults or do I need to return a JsonResponse and have the twig template render it, having been called from apiAction()?
Is there any built in Symfony2 functionality wherein if one names a template results.json.twig vs. results.html.twig, or is this naming convention is just idiomatic?
I have been able to get the results into results.html.twig by rendering a Response() in the renderAPIResults() method but I have only been able to console.log() the results from a .js file when returning the results as a JsonResponse(). I have not been able to parse these results in any shape or form in the results.json.twig template when trying to somehow 'render' a JsonResponse.
//DefaultController.php
public function curlRestRequest($apiQueryString, $jsonDecode = FALSE, array $post_data = null, $service_url = null){
if(!$service_url) {
$service_url = 'http://website.com/rest';
}
$curl = curl_init($service_url.$apiQueryString);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data);
$curl_response = curl_exec($curl);
if ($curl_response === false) {
$info = curl_getinfo($curl);
curl_close($curl);
die('error occured during curl exec. Additioanl info: ' . var_export($info));
}
curl_close($curl);
if($jsonDecode)
{
$curl_response = json_decode($curl_response);
}
return $curl_response;
}
public function renderAPIResults($data, $asObject = FALSE, $template)
{
if(!$template) {
throw new Exception('Must provide a template name before rendering!');
}
if($asObject != TRUE) {
// we want to cast to array since specified as OBJECT = FALSE, removing all instances of objects
$dataArray = json_decode(json_encode($data), true);
return $this->render($template, array('data' => $dataArray));
} else { // Is JSON Decoded, if it is an object
//just return json data, parse it in javascript instead of with template
$response = new JsonResponse();
$response->setData(array(
'data' => $data
));
return $response;
//return $this->renderView($template, array('data' => $data));
//or
//$content = $this->render($template, array('data' => $data));
//return new JsonResponse($content);
}
}
public function apiAction($type, $query){
$apiQueryString = '/search/' . $type . '?query=' . $query;
//make a curl request
$requestedResults = ($this->curlRestRequest($apiQueryString, TRUE));
//return option three: (dont use any response object, and just pass, just pass curl-results directly)
//note, currently, when set as true, its not rendered in the template, the template is not used
$view = $this->renderAPIResults($requestedResults, TRUE, 'ApiBundle:API:results.json.twig');
return $view;
}
This is the twig template:
//results.json.twig
{{ dump(data) }}
Basically, im asking:
How do I utilize JsonResponse() in the renderAPIResults() method to render the results.json.twig in that same method, returning this rendered template so the template itself can loop over the json results?
Can I use JsonResponse() to render a template this way? Or do I have to use only the Response() method when rendering?
If I can't use JsonResponse to render, can I parse the results directly in twig using its native language? i.e. {{ dump(results) }} or do I need to include javascript inside scripts tags and process these results first?
EDIT: I think i found a solution to the problem.
when you json_decode($string,TRUE), if its a deeply nested array, then the nested components don't end up as a perfect nested array as i assumed it would, only the top level ones do. Is this a natural side effect of json_decode, or bad json, or what am i doing wrong?
I think this post help shed some light on this. Accessing JSON array after json_decode/multidimensional array
So it appears the problem i was having was the JSON data wasnt completely clean.
i.e.
$requestedResults = ($this->curlRestRequest($apiQueryString, TRUE));
print_r($requestedResults);
//yields Array
(
[category] =>
[executionTime] => 759
[facets] =>
[resultCount] => 8
[searchCount] => 0
[searchInfo] =>
[searchResults] => Array
(
[0] => Array
(
[description] => Gives instructions for 3 different in-class games.
[taxonomyDataSet] => {"course":[],"topic":[],"unit":[],"lesson":[],"subject":[],"curriculum":{"curriculumName":["Common Core State Standards - Math","Common Core State Standards - Math"],"curriculumCode":["CCSS.M.8.G.C.9","CCSS.M.7.G.B.6"],"curriculumDesc":["Solve real-world and mathematical problems involving area, volume and surface area of two- and three-dimensional objects composed of triangles, quadrilaterals, polygons, cubes, and right prisms.","Know the formulas for the volumes of cones, cylinders, and spheres and use them to solve real-world and mathematical problems."]}}
[thumbnail] => slides/thumbnail.jpg
[title] => Game Suggestions for Volume
)
)
which the "curriculumCode" for e.g. wasnt accessible, or anything in the taxonomyDataSet wasnt accessible, via twig, and i had to massage the data a bit to clean it.
$requestedResults = ($this->curlRestRequest($apiQueryString, TRUE));
foreach ($requestedResults['searchResults'] as $resource) {
$title = $resource["title"];
$description = $resource["description"];
$thumbnailUrl = $resource["thumbnails"]['url'];
$taxonomyDataSet = json_decode($resource['taxonomyDataSet'],TRUE);
$standard = $taxonomyDataSet['curriculum']['curriculumCode'];
if(! file_exists($thumbnailUrl))
{
$thumbnailUrl = NULL;
}
$results[] = array($title,$description,$thumbnailUrl, $standard);
}
print_r($results);
//yields
Array
(
[0] => Array
(
[0] => Game Suggestions for Volume
[1] => Gives instructions for 3 different in-class games.
[2] =>
[3] => Array
(
[0] => CCSS.M.8.G.C.9
[1] => CCSS.M.7.G.B.6
)
))
Why Am i having to take this extra step, isn't there a better way to properly iterate over a JSON string so this doesnt happen?
I would MUCH prefer keeping it an object, then simple return it like this in the controller return $requestedResults->searchResults
where i can safely iterate it in TWIG without all the data massaging.
Edit, ok looking into this once again deeper, i believe that the company's API data is to blame, it looks like one particular portion of the results is double json_encoded, and this make sense why the data is not accessible until i massage it. is it me, or does it appear that this is whats happening?

I’m not sure if I understood the question, but let’s go:
In my opinion, you should process your API result before send it to the view, that means you should parse the JSON string into PHP objects, create an array of it, and then iterate on twig as normal.
So, focus your effort on develop a piece of code that turns your JSON string into an array of objects; and don’t pass logic to the view.

Consider using FOSRestBundle, it will handle your request and return correct JSON responses "automatically". It is simple to configure basic usage and then you have to only follow conventions to achieve simple and valid REST API.
You can use Guzzle as API client for retrieving data. You can write some client class and provide methods implementing some logic (method for each resource). Then in those methods you simply use, for example, $response = $guzzleClient->get($url, $options) and under $reponse->json() you have all the data which you can process and serve in your own API response.
You should register that API client as service and use it in own controllers to retrieve data, then process it and return expected result from controller's action. You can handle it then by FOSRestBundle+JMSSerializerBundle to make JSON response (you can return XML too, it's a matter of using allowed formats and requesting resource with format parameter provided - for example getting /api/users.xml would return XML file, while /api/users.json will result in JSON).
This is wide topic and it's nearly impossible to describe all the stuff you have to do in order to achieve what you want, but I believe it will help you to get right resolution.

Related

php: how to parse graphQl query string to get the operation names

I would like to allow certain graphQl operations only for certain api users based on a confguration. Our stack is Symfony 6.2 + overblog/GraphQLBundle.
My current approach is to check in the authenticate method of a custom authenticator, if the current operation is cleared in the allowed operations config of the user. For this I would like to parse the graphql query into a kind of array, that I can interpret easily.
Anybody knows how this can be done? I was scanning the underlying webonyx/graphql-php library, but can not see how they do it.
As a simple example:
query myLatestPosts($followablesToFilter: [FollowableInput], $limit: Int, $offset: Int) {
my_latest_posts(followablesToFilter: $followablesToFilter, limit: $limit, offset: $offset) {
...PostFragment
__typename
}
my_roles
}
From this I would like to retrieve the operations my_latest_posts and my_roles.
Update 1
it's probably possible to write a simple lexer utilising preg_split - I'm just hesitating, as I'm sure someone has done this already... The spec appears to be well defined.
Alright, so it turned out webonyx/graphql-php has indeed all the lowlevel function needed for this :D. Especially the Visitor is very useful here.
With this code you can drill down the query to get the operation names (or selections, as they are called in the code)
This works for me:
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\Parser;
use GraphQL\Language\Visitor;
// whatever comes before
// ...
$graphQlRequest = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR);
$operations = [];
Visitor::visit(Parser::parse($graphQlRequest['query']), [
NodeKind::OPERATION_DEFINITION => function ($node) use (&$operations) {
// $node contains the whole graphQL query in a structured way
$selections = array_map(function ($selection) {
return $selection['name']['value'];
}, $node->toArray()['selectionSet']['selections']);
foreach ($selections as $selection) {
$operations[] = $selection;
}
return Visitor::stop();
}]
);
print_r($operations);
The result of $operations is then for my example above:
Array
(
[0] => my_latest_posts
[1] => my_roles
)
And this information is all I need to decide weather the user should have access to the endpoint or not.

How do I work with an array object in PHP?

I have a Laravel site I am modifying, but there are some parts of the PHP code I don't quite understand, which are "array objects" or "object arrays". You see, I don't even know what to call them and so can't find a tutorial or basic data on it. Below is the code that I am dealing with:
private function parseMetric($result, $view)
{
$data = collect([]);
$result->each(function($item) use ($data, $view) {
if (isset($item->metric->{$view})) {
$data->push((object)[
'label' => $item->metric->{$view},
'value' => $item->metric->count
]);
}
});
...
From what I can tell, this creates an object out of $result. If I json_encode this and echo it out I get this:
[{"label":"1k-25k","value":14229},
{"label":"1mm+","value":1281},
{"label":"25k-50k","value":398},
{"label":"50k-75k","value":493},
{"label":"75k-100k","value":3848},
{"label":"100k-150k","value":9921},
{"label":"150k-200k","value":4949},
{"label":"200k-250k","value":3883},
{"label":"250k-300k","value":2685},
{"label":"300k-350k","value":2744},
{"label":"350k-500k","value":4526},
{"label":"500k-1mm","value":8690}]
Now this is obviously an array of arrays... or is it? Is it an array of objects? Or is it an object containing arrays? But the most important question is, how do I access and move or change the individual objects/arrays in this object? For example, I want to take the second object/array, which is:
{"label":"1mm+","value":1281}
and move it to the end. How do I do that? How do I find it? I used the following piece of code to find it which is pretty clunky:
$pos = strpos(json_encode($result), '1mm+');
if($pos){
Log::debug('Enrich 73, I found it!!!!!!!!!!!!!!!!!!!!!!!!!!!');
}
And once I find it, how do I move that array/object to the end of the whole object?
And finally, where can I find some kind of tutorial, or documentation, that describes this construct and how to work with it?
There is no need to json_encode the data. Since the data is an instance of Laravel Collection, you can manipulate it like so
$item = $data->firstWhere('label', '1mm+'); // get the item
$data = $data->filter(fn($value, $key) => $value->label !== '1mm+') // remove $item from $data
->push($item); // move $item to the end of data
Acording to Laravel documnentation for Collections, you can try something like this :
To find index of element with name = "1mm+" :
$index = $datas->search(function ($item, $key) {
return $item['name'] == "1mm+";
});
to get an element at a given index :
$element = $datas->get($index);
to Move element at index 3 to the end :
$index = 3
$elementToMove = $data->splice($index, 1);
$datas->push($elementToMove);
Here is a link to the document used : https://laravel.com/docs/8.x/collections

How can I convert plain nested array to collection of entity objects?

I have a PHP plain array which I need converted to it's original entity. Example:
class Contact
{
protected $name;
getName(){}
setName(){}
}
This gets send back and forth via an API, and at some point I have that contact as an array element:
$example = ['name'=>'Foo Bar'];
I would like that back as an Contact class. At the moment, I can do that via a serialize/deserialize, but I'm hoping there is a more efficient method for this:
foreach($examples as $example) {
$temp = $this->serializer->serialize($example, 'json');
$contact = $this->serializer->deserialize($temp, Contact::class, 'json');
}
This works, and $contact is now instance of Contact. But I have to perform this on 100 items in one go, possibly more.
I'm thinking of creating a toObject() method, which assigns the values by keys, but that doesn't seem a lot better.
Is there way to accomplish this without writing my own logic or doing the extra serializing step?
Please note: I get the data array, I cant get the 'raw' json. Please take that 'as is'.
Denormalizing from raw JSON input
If you are getting the information from an API, you could probably do away with the JSON conversion and deal with the input directly, since most likely the API is not sending you a native array, but a JSON you are converting to an array at some point
The Serializer component can handle arrays as well, directly.
Assuming an input JSON like this:
$data = '[
{
"name": "Mary"
},
{
"name": "Jane",
},
{
"name": "Alice"
}
]';
You could call deserialize() saying you expect Contact[] in your input data:
$contacts = $serializer->deserialize($data, Contact::class . '[]', 'json');
This would get you a Contact array in one single step.
Denormalizing from array to object
If for some reason the original input is not available or not readily unserializable and you really need to convert from an array to an object one by one, and your objects have setters like the ones you show in your question; you could simply use the GetSetMethodNormalizer (one of the normalizers than the Serializer component uses internally).
E.g.:
$contacts = [
['name' => 'Mary'],
['name' => 'Jane'],
['name' => 'Alice'],
];
$normalizer = new GetSetMethodNormalizer();
foreach($contacts as $arrayContact){
$contact = $normalizer->denormalize(Contact::class, $arrayContact);
// do something with $contact;
}

How can I parse a JSON object in PHP? How can I retrieve the values of some specific fields of this JSON object?

I am absolutely new in PHP and moreover in Laravel framework (I don't know if Laravel provides some utility class for this kind of tasks). I came from Java.
So I have the following problem:
Into a class I perform a call to a REST web service, something like this:
$response = $client->get('http://localhost:8080/Extranet/login',
[
'auth' => [
'dummy#gmail.com',
'pswd'
]
]);
$dettagliLogin = json_decode($response->getBody());
\Log::info('response: '.(json_encode($dettagliLogin)));
$response->getBody() contains the returned JSON object, this is the output of the previous \Log::info():
{
"id":5,
"userName":"Dummy User",
"email":"dummy#gmail.com",
"enabled":true
}
So I have the following problems:
1) What exactly returns the json_decode() function? I really can't understand because PHP is not strongly typed and I have not a declared return type.
This is the method signature:
function json_decode($json, $assoc = false, $depth = 512, $options = 0)
and in the related doc it says #return mixed. What exactly means "mixed"?
2) Anyway the main problem is: I have to use the content of the previous returned JSON object and put these value into the related field of an array like this:
$attributes = array(
'id' => HERE THE id FIELD VALUE OF MY JSON OBJECT,
'username' => HERE THE email FIELD VALUE OF MY JSON OBJECT',
'name' => HERE THE userName FIELD VALUE OF MY JSON OBJECT,
);
So I think that I have to parse the value of the $response->getBody() or of the json_decode($response->getBody()) to obtain these values. But how exactly can I do it? What is the neater way to do it? Does the Laravel framework provide some utility to do it?
For better understanding, let's first describe - what's JSON?
It's a way of representing objects (arrays, objects, etc) in a string.
1) What exactly returns the json_decode() function? I really can't
understand because PHP is not strongly typed and I have not a declared
return type. This is the method signature:
function json_decode($json, $assoc = false, $depth = 512, $options =
0) and in the related doc it says #return mixed. What exatly means
mixed?
json_deocde converts the JSON string into the original "structure" it represent.
#return mixed means that the returned value of json_decode can be any type of variable. If the JSON represent an array - it would be an array type, if it represent an object - it would be an object type.
2) Anyway the main problem is: I have to use the content of the
previous returned JSON object and put these value into the related
field of an array like this:
$attributes = array(
'id' => HERE THE id FIELD VALUE OF MY JSON OBJECT,
'username' => HERE THE email FIELD VALUE OF MY JSON OBJECT',
'name' => HERE THE userName FIELD VALUE OF MY JSON OBJECT,
);
In order to make sure which type of variable your JSON represent, you can use var_dump(json_decode($json));. Anyway, it's a class object.
Therefore:
$object = json_decode($json);
$attributes = array(
'id' => $object->id,
'username' => $object->email,
'name' => $object->userName,
);
If you json string is an object (not an array) it will return an object (of type stdClass). Mixed means it can be multiple things, so if it was a json array, you'd get an array.
Best thing to do is use json_decode, and then var_dump (or var_export) to see what you actually get.

What is register_rest_route's callback's $data variable?

This is really bizarre. I've spent at least 5 hours trying to figure out what this $data variable truly is and what is in it. I think the question is a little confusing, so let me explain what I'm talking about:
The callback for register_rest_route is a function that takes one parameter, $data, which seems to be a magical object. I've been trying to follow the source to figure out if there's something more to this. Could someone explain? It could be very illuminating for the community (OK, or at least me).
register_rest_route( 'custom/v1', '/customer/', array(
'methods' => 'POST',
'callback' => 'get_stuff',
) );
So, my function get_stuff looks like this:
get_stuff( $data ) {
return array(
'is object? ' . is_object( $data ), // returns 1
'is array? ' . is_array( $data ), // returns blank; nothing
get_object_vars( $data ), // returns an empty array
$data, // returns {}; an empty object
$data['assignee'], // returns POST'd data as expected
$data['number'], // returns POST'd data as expected
$data->number // returns null; I thought this was an object? Why doesn't this work?
}
What the is $data?
For the longest time, I was trying to return the entire $data object, just to test/debug/play, and was getting an empty object, until I tried getting one of the properties. I'm thoroughly confused because it doesn't behave like an object, but apparently is one. I can't seem to get it in its entirety, but only if I specify a property. Can someone clear this up?
The return type is a WP_REST_REQUEST object, so in order to get the body of the request you will have to invoke the get_body method.
function get_stuff( $data ) {
$result = $data->get_body(); //returns a string of the body
return $result;
}
However, if you're like me, you'll most likely be sending JSON in a POST request, so invoke the parse_json method to get an object/array.
function get_val($data){
$json_result = json_decode($data->get_body(), true); //note second param is for setting this to an associative array
$some_value = $json_result["some_var"];
return $some_value;
}
Here's some helpful links:
WP_REST_REQUEST docs: https://developer.wordpress.org/reference/classes/wp_rest_request/
Parsing JSON:
https://developer.wordpress.org/reference/functions/json_decode/

Categories