Part of an array is not accessed through foreach - php

I am creating a bot for telegram using Botman and I have an issue when trying to get a value from an API response.
This is my class that proccesses the response and returns the message to the user, you can see the API adress.
class TaxiService {
protected $client;
const TAXI_ENDPOINT = 'http://35.204.38.8:4000/api/v1/taxis/%s';
/**
* DogService constructor
*
* #return void
*/
public function __construct(){
$this->client = new Client();
}
public function hireTaxi(){
try {
$lon="Hello";
$endpoint = sprintf(self::TAXI_ENDPOINT, 'Madrid');
$response = json_decode(
$this->client->get($endpoint)->getBody()
);
foreach($response as $key => $value){
foreach($value as $key2 => $value2){
if(is_array($value2)){
foreach($value2 as $key3 => $value3){
$lon = $value3;
}
}
}
}
return $lon;
} catch (Exception $e) {
// If anything goes wrong, we will be sending the user this error message.
return 'An unexpected error occurred. Please try again later.';
}
}
}
This is the response I get when calling the API.
[{"state":"free","name":"Opel","location":
{"lon":1.399,"lat":38.88},"city":"Madrid"},
{"state":"free","name":"Skoda4","location":
{"lon":1.3123,"lat":38.123},"city":"Madrid"},
{"state":"free","name":"Hyundai","location":
{"lon":1.2313,"lat":38.41},"city":"Madrid"}]
When I iterate trough the Array given by ´json_decode´ the ´$long´ variable returns ´Hello´, it doesn't set any value when looping trough the array.
What I want to get is one of the values in the location position of the JSON.
I have tested my function with this site: http://sandbox.onlinephpfunctions.com/ and it gives me a value belonging to the JSON which is what I want but it seems the function is not working in my application.

you need to change:
$response = json_decode(
$this->client->get($endpoint)->getBody()
);
to:
$response = json_decode(
$this->client->get($endpoint)->getBody(), true
);
When TRUE, returned objects will be converted into associative arrays.

You're overwriting the value of $lon on each iteration, which probably isn't what you want. If you just want the lon from the first record that was returned, then you don't need to iterate over everything, just explicitly take the first one:
$response = json_decode(
$this->client->get($endpoint)->getBody()
);
return $response[0]->location->lon;
Or even:
return json_decode($this->client->get($endpoint)->getBody())[0]->location->lon;

neoChiri,
Looks like a scope issue. You would get a $long is not defined error if the variable doesn't exist. So, some variable you created later on in the code is masking the original value.
Hope this helps.

Related

Restful api using Slim php returns empty if array contains arrays

Before I start, I want to let you know I'm really a noob in PHP and this is the first API I'm making.
It works pretty good if I want to echo one array of information (for example food details), but when I try to do the same with multiple items it returns empty.
I've checked the variable values in debug. It's fine in debug and I see an array which contains multiple sub arrays.
My code
$app->get('/allfoods', 'authenticate', function () use ($app) {
global $user_id;
$db = new FoodHandler();
// In here i get foods with their details via mysql
$result = $db->GetAllFoods();
$response = array();
$response["error"] = false;
$response["foods"] = array();
// looping through result and preparing food array
while ($row = $result->fetch_assoc()) {
$tmp = array();
$tmp['food_id'] = $row['food_id'];
$tmp['food_name'] = $row['food_name'];
$tmp['food_desc'] = $row['food_desc'];
$tmp['food_category'] = $row['food_category'];
$tmp['food_creationDate'] = $row['food_creationDate'];
array_push($response["foods"], $tmp);
}
echoRespnse(200, $response);});
My output function (which works great if there is no array in my array)
function echoRespnse($status_code, $response) {
$app = \Slim\Slim::getInstance();
// Http response code
$app->status($status_code);
// setting response content type to json
$app->contentType('application/json');
echo json_encode($response);
}
$app->run();?>
What is my setup?
Localhost wamp with php 7.2.4
Apache 2.4.33
Mysql 5.7.21
I'm also using Postman to send my request (I also tried it in C#, both give back empty content)
I see several issues with your code. First, there is a problem with your route definition. When defining a route, you should pass two arguments to the get method: a pattern (a string,/allfoods in your case) and an instance of Clousure (a callable, your route callback, the anonymous function in your case.) More details in official docs.
So, first thing is to remove the authenticate string from method parameters and change your route definition to this:
$app->get('/allfoods', function ($request, $response, $args) {
// Body of the function goes here
});
Please note I also removed the use ($app) as you have access to application instance uising $this keyword, so no need for that (described in official docs as well).
Second thing is about generating the response. When using Slim framework it is always a good idea to return the $response object instead of echoing response (read more in official docs). Thisgives you some advantages, for example the helper method whitJson helps you whit generating JSON output.
To refine your whole code in a more Slim-ish way:
$app->get('/allfoods', function ($request, $response, $args) {
global $user_id;
$db = new FoodHandler();
// In here i get foods with their details via mysql
$result = $db->GetAllFoods();
$data= array();
$data["error"] = false;
$data["foods"] = array();
// looping through result and preparing food array
while ($row = $result->fetch_assoc()) {
$tmp = array();
$tmp['food_id'] = $row['food_id'];
$tmp['food_name'] = $row['food_name'];
$tmp['food_desc'] = $row['food_desc'];
$tmp['food_category'] = $row['food_category'];
$tmp['food_creationDate'] = $row['food_creationDate'];
array_push($data["foods"], $tmp);
}
// Return JSON data using helper method
return $response->withJson($data);
}
And you won't need the echoResponse function anymore.

Converting Request Object into JSON in Laravel 5.2

I have below code that save the country information in Database. Below code works fine. There is no problem in that.
private function SaveChanges(\App\Http\Requests\CountryRequest $request) {
if($request['CountryID'] == 0) {
$Country = new \App\Models\CountryModel();
}
else {
$Country = $this->GetCountry($request['CountryID']);
}
$Country->Country = $request['Country'];
$Country->CountryCode = $request['CountryCode'];
$Country->save();
return redirect()->route($this->AllCountries);
}
Now, I decided to shift the working of above method inside a new class like below. Here I am reading the JSON data
class CountryData {
public function CreateCountry($CountryObject) {
$obj = json_decode($CountryObject);
$Country = new \App\Models\CountryModel();
$Country->Country = $CountryObject->Country;
$Country->CountryCode = $CountryObject->CountryCode;
$Country->save();
return true;
}
}
and the original function is changed like below. Sending the Request parameter in the form of JSON.
private function SaveChanges(\App\Http\Requests\CountryRequest $request) {
$data = array(
'Country' => $request['Country'],
'CountryCode' => $request['CountryCode'],
'CountryID' => $request['CountryID']
);
if($request['CountryID'] == 0) {
$result = (new \CountryData())->CreateCountry( json_encode($data) );
}
return redirect()->route($this->AllCountries);
}
Question: Is my approach correct to send converted request object to JSON object and reading in an another Class .
I am doing that so that I can create a new controller and call the CreateCountry from class CountryData to return JSON data for an Android App.
Well, I don't think it's a good approach. Your CountryData class acts as a service, so I think it hasn't have to know anything about JSON, that is part of the interface between your business logic and the external side of your system (Android app, web interface, etc.)
Your new Controller may receive JSON objects and answer with JSON objects, but it must convert the JSON received to your business classes, then pass them to your services, in this case CountryData (not a good name, though).
So the logic should be:
Controller:
- receive request data
- call service and save or whatever
- encode to JSON
- send the response in JSON format
So your business classes don't know anything about JSON.
A not fully code solution is provided as an idea, but it lacks error management, and more work to do. It's based on some Laravel 5 features. Also I don't know if you're using REST or what kind of request are you doing...
use App\Http\Controllers\Controller;
class CountryController() extends Controller {
public function store(\App\Http\Requests\CountryRequest $request) {
// TODO manage errors
$countryModel = $this->createOrUpdateCountry($request);
// Laravel way to response as JSON
return redirect()->json($this->country2Array($countryModel);
}
private function createOrUpdateCountry(\App\Http\Requests\CountryRequest $request) {
$countryId = $request['CountryID'];
if($id == 0) {
$countryModel = new \App\Models\CountryModel();
} else {
$countryModel = $this->GetCountry($countryId);
}
$countryModel->Country = $request['Country'];
$countryModel->CountryCode = $request['CountryCode'];
// You must have an initialised instance of CountryDAO
// TODO manage errors
$countryDAO->saveOrUpdate($countryModel);
return $countryModel;
}
private function country2Array($countryModel) {
$data = array(
'country' => $countryModel->Country,
'countryCode' => $countryModel->CountryCode,
'countryId' => $countryModel->CountryID
);
return $data;
}
}
/**
* Formerly CountryData
*/
class CountryDAO {
public function saveOrUpdate($countryModel) {
// TODO Manage errors or DB exceptions
// I'd put the DB save access/responsability here instead of in CountryModel
$countryModel->save();
return true;
}
}
First of you should not do any conversions to objects and so on.
Second, since the request object should be an array as shown on your example I suggest you to use the "fill" method of Laravel, instead of looping on hand all of the request elements.
Your code for saving the request should be as follows:
class CountryData {
public function CreateCountry($requestData) {
$Country = new \App\Models\CountryModel();
$country->fill($requestData);
$Country->save();
return true;
}
}
The "fill" method loops all of the array keys and tries to set them into the object instance if it has those keys as properties. If there are any extra fields, they are trimmed and you wont get any errors.
Cheers! :)

Amazon MWS (PHP) - Report Request API functions return without data, no error thrown

I am currently working with the Amazon MWS to integrate some features into wordpress via a plugin. I am using the client libraries provided by amazon found here:
https://developer.amazonservices.com/api.html?group=bde&section=reports&version=latest
Using these client libraries and the sample php files included I have set up my plugin to make two API calls. The first is requestReport
public function requestInventoryReport() {
AWI_Amazon_Config::defineCredentials(); // Defines data for API Call
$serviceUrl = "https://mws.amazonservices.com";
$config = array (
'ServiceURL' => $serviceUrl,
'ProxyHost' => null,
'ProxyPort' => -1,
'MaxErrorRetry' => 3,
);
$service = new MarketplaceWebService_Client(
AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY,
$config,
APPLICATION_NAME,
APPLICATION_VERSION);
$request = new MarketplaceWebService_Model_RequestReportRequest();
$request->setMerchant(MERCHANT_ID);
$request->setReportType('_GET_MERCHANT_LISTINGS_DATA_');
self::invokeRequestReport($service, $request);
}
private function invokeRequestReport(MarketplaceWebService_Interface $service, $request) {
try {
$response = $service->requestReport($request);
if ($response->isSetRequestReportResult()) {
// Print Out Data
}
} catch (MarketplaceWebService_Exception $ex) {
// Print Out Error
}
}
and the second is getReportRequestList which has code similar to the first function. I am able to run these functions without any errors. The issue that I am having is that $response->isSetRequestReportResult() returns false. From my understanding and looking into the response object, this would suggest that the response object does not have the result. (Upon printing out the response object I can see that the FieldValue of the result array is NULL.) The call, however, does not throw an error but neither does it have the result.
I did some digging through the code and found that the result does actually get returned from the api call but never gets set to the return object when the library attempts to parse it from XML. I've tracked the error down to this block of code (This code is untouched by me and directly from the amazon mws reports library).
private function fromDOMElement(DOMElement $dom)
{
$xpath = new DOMXPath($dom->ownerDocument);
$xpath->registerNamespace('a', 'http://mws.amazonaws.com/doc/2009-01-01/');
foreach ($this->fields as $fieldName => $field) {
$fieldType = $field['FieldType'];
if (is_array($fieldType)) {
if ($this->isComplexType($fieldType[0])) {
// Handle Data
} else {
// Handle Data
}
} else {
if ($this->isComplexType($fieldType)) {
// Handle Data
} else {
$element = $xpath->query("./a:$fieldName/text()", $dom);
$data = null;
if ($element->length == 1) {
switch($this->fields[$fieldName]['FieldType']) {
case 'DateTime':
$data = new DateTime($element->item(0)->data,
new DateTimeZone('UTC'));
break;
case 'bool':
$value = $element->item(0)->data;
$data = $value === 'true' ? true : false;
break;
default:
$data = $element->item(0)->data;
break;
}
$this->fields[$fieldName]['FieldValue'] = $data;
}
}
}
}
}
The data that should go into the RequestReportResult exists at the beginning of this function as a node in the dom element. The flow of logic takes it into the last else statement inside the foreach. The code runs its query and returns $element however $element->length = 13 in my case which causes it to fail the if statement and never set the data to the object. I have also looked into $element->item(0) to see what was in it and it appears to be a dom object itself matching the original dom object but with a bunch of empty strings.
Now, I'm new to working with the MWS and my gut feeling is that I am missing a parameter somewhere in my api call that is messing up how the data is returned and is causing this weird error, but I'm out of ideas at this point. If anyone has any ideas or could point me in the right direction, I would greatly appreciate it.
Thanks for your time!
** Also as a side note, Amazon Scratchpad does return everything properly using the same parameters that I am using in my code **
These works for me, check if you are missing anything.
For RequestReportRequest i am doing this:
$request = new MarketplaceWebService_Model_RequestReportRequest();
$marketplaceIdArray = array("Id" => array($pos_data['marketplace_id']));
$request->setMarketplaceIdList($marketplaceIdArray);
$request->setMerchant($pos_data['merchant_id']);
$request->setReportType($this->report_type);
For GetReportRequestList i am doing this:
$service = new MarketplaceWebService_Client($pos_data['aws_access_key'], $pos_data['aws_secret_access_key'], $pos_data['config'], $pos_data['application_name'], $pos_data['application_version']);
$report_request = new MarketplaceWebService_Model_GetReportRequestListRequest();
$report_request->setMerchant($pos_data["merchant_id"]);
$report_type_request = new MarketplaceWebService_Model_TypeList();
$report_type_request->setType($this->report_type);
$report_request->setReportTypeList($report_type_request);
$report_request_status = $this->invokeGetReportRequestList($service, $report_request, $report_requestID);

PHP json_encode errors messages for JQuery

I have the following method in my PHP class that processes messages and sends it back to JQuery. It works fine if there is only a single message to send back but if there is more than one, it sends them back as separate json objects. The messages are sent back ok but JQuery gives me an error. The messages look like this:
{"error":true,"msg":"Message 1 here..."}{"error":true,"msg":"Message 2 here"}
My PHP method looks like this:
private function responseMessage($bool, $msg) {
$return['error'] = $bool;
$return['msg'] = $msg;
if (isset($_POST['plAjax']) && $_POST['plAjax'] == true) {
echo json_encode($return);
}
...
}
I don't know how to change this so multiple error messages are put into a single json encoded message but also work if it is just a single message.
Can you help?
Thanks
Looks like you need to be appending onto an array and then when all messages have been added, output the JSON. Currently, your function outputs JSON any time it is called:
// Array property to hold all messages
private $messages = array();
// Call $this->addMessage() to add a new messages
private function addMessage($bool, $msg) {
// Append a new message onto the array
$this->messages[] = array(
'error' => $bool,
'msg' => $msg
);
}
// Finally, output the responseMessage() to dump out the complete JSON.
private function responseMessage() {
if (isset($_POST['plAjax']) && $_POST['plAjax'] == true) {
echo json_encode($this->messages);
}
...
}
The output JSON will be an array of objects resembling:
[{"error":true,"msg":"Message 1 here..."},{"error":true,"msg":"Message 2 here"}]
You can send the errors as an array.
$errors = Array();
// some code
$errors[] = ...; // create an error instead of directly outputting it
// more code
echo json_encode($errors);
This will result in something like:
[{"error":true,"msg":"Message 1 here..."},{"error":true,"msg":"Message 2 here"}]
Sounds like a design issue. You need to build up an object like $response = array(); and then every time an error needs to be added just append it. $response[] = $errorData; then when ur finished just json_encode($response);

check if object exists in Cloud Files (PHP API)

I've just started working with the PHP API for Rackspace Cloud Files. So far so good-- but I am using it as sort of a poor man's memcache, storing key/value pairs of serialized data.
My app attempts to grab the existing cached object by its key ('name' in the API language) using something like this:
$obj = $this->container->get_object($key);
The problem is, if the object doesn't exist, the API throws a fatal error rather than simply returning false. The "right" way to do this by the API would probably be to do a
$objs = $this->container->list_objects();
and then check for my $key value in that list. However, this seems way more time/CPU intensive than just returning false from the get_object request.
Is there a way to do a "search for object" or "check if object exists" in Cloud Files?
Thanks
I sent them a pull request and hope it'll get included.
https://github.com/rackspace/php-cloudfiles/pull/35
My pull-request includes an example, for you it would be similar to this:
$object = new CF_Object($this->container, 'key');
if ($object->exists() === false) {
echo "The object '{$object->name}' does not exist.";
}
I have more general way to check if object exists:
try {
$this->_container->get_object($path);
$booExists = true;
} catch (Exception $e) {
$booExists = false;
}
If you dump the $object, you'll see that content_length is zero. Or, last modified will be a zero length string.
Example:
$object = new CF_Object($container, 'thisdocaintthere.pdf');
print_r($object->content_length);
There is also, deep in the dumped parent object, a 404 that will return, but it's private, so you'd need to some hackin' to get at it.
To see this, do the following:
$object = new CF_Object($container, 'thisdocaintthere.pdf');
print_r($object->container->cfs_http);
You'll see inside that object a response_status that is 404
[response_status:CF_Http:private] => 404
I know I'm a little late to the party, but hopefully this will help someone in the future: you can use the objectExists() method to test if an object is available.
public static function getObject($container, $filename, $expirationTime = false)
{
if ($container->objectExists($filename)) {
$object = $container->getPartialObject($filename);
// return a private, temporary url
if ($expirationTime) {
return $object->getTemporaryUrl($expirationTime, 'GET');
}
// return a public url
return $object->getPublicUrl();
}
// object does not exist
return '';
}
Use like...
// public CDN file
$photo = self::getObject($container, 'myPublicfile.jpg');
// private file; temporary link expires after 60 seconds
$photo = self::getObject($container, 'myPrivatefile.jpg', 60);
If you do not want to import opencloud to perform this check you can use the following:
$url = 'YOUR CDN URL';
$code = FALSE;
$options['http'] = array(
'method' => "HEAD",
'ignore_errors' => 1,
'max_redirects' => 0
);
$body = file_get_contents($url, NULL, stream_context_create($options));
sscanf($http_response_header[0], 'HTTP/%*d.%*d %d', $code);
if($code!='200') {
echo 'failed';
} else {
echo 'exists';
}

Categories