How to get past [#attributes] in SimpleXMLElement? [duplicate] - php

This question already has answers here:
simplexml_load_file parse [#attributes] => Array
(3 answers)
Closed 9 years ago.
The goal: Using PHP/CodeIgniter, I need to get a list of users and their custom field names and values in a usable array. If I can just get to the items, I can do what I need. Please see OUTPUT with notes for "// <<< " so you can see where I'm stuck. I just can't get past the dang [#attributes].
I'm using PHP to connect to Redmine using ActiveResource.php and their built in REST API. I want to get a list of all the users and am getting the OUTPUT below.
Models/Employees_model.php:
<?php
require_once ('ActiveResource.php');
class Employees_model extends ActiveResource {
var $site = 'http://user:password#localhost:8080/redmine/';
var $element_name = 'user';
var $request_format = 'xml';
function __construct() {
parent::__construct();
}
}
?>
controllers/employees.php:
public function employees () {
$employees = new Employees_model();
$data['employeeList'] = $employees->find('all');
$this->load->view('customers/ajaxEmployees', $data);
}
view/ajaxEmployees.php:
<?php
//I can get the following with no problem
echo $employee->id . "<br/>";
echo $employee->firstname . "<br/>";
echo $employee->lastname . "<br/>";
//This is where I'm stuck (see OUTPUT for [#attributes] and "// <<<" notes)
echo $employee->custom_fields->custom_field;
?>
OUTPUT:
Array
(
[0] => Employees_model Object
(
[id] =>
[site] => http://user:password#localhost:8080/redmine/
[element_name] => user
[request_format] => xml
[extra_params] =>
[user] =>
[password] =>
[element_name_plural] => users
[_data] => Array
(
[id] => 16
[login] => jschmoe
[firstname] => Joe
[lastname] => Schmoe
[mail] => joe#example.com
[created_on] => 2012-08-24T01:58:21Z
[last_login_on] => SimpleXMLElement Object
(
)
[custom_fields] => SimpleXMLElement Object
(
[#attributes] => Array
(
[type] => array
)
[custom_field] => Array
(
[0] => SimpleXMLElement Object
(
[#attributes] => Array
(
[name] => Quality Control //<<< I need this
[id] => 2
)
[value] => 1 //<<< I need this to know that QualityControl = 1
)
[1] => SimpleXMLElement Object
(
[#attributes] => Array
(
[name] => Technical Contractor // <<< I need this
[id] => 3
)
[value] => 0 // <<< I need this to know that TechnicalContractor = 0
)
[2] => SimpleXMLElement Object
(
[#attributes] => Array
(
[name] => Content //<<< I need this
[id] => 4
)
[value] => 0 // <<< I need this to know Content = 1
)
)
)
)
)
)
Thank you all so much for your help. After wasting many hours searching around, trying everything I came across and everything I could think of - I figured I'd better wave my little white flag. :(

$employee->custom_fields->custom_field; is an array, and you can foreach over it to get each name attribute and its corresponding value using SimpleXMLElement::attributes().
foreach ($employee->custom_fields->custom_field as $cf) {
// Loop over the custom field nodes and read the name of each
switch ($cf->attributes()->name) {
// Load a variable on each matching name attribute
// or do whatever you need with them.
case "Quality Control":
$quality_control = $cf->value;
break;
case "Technical Contractor":
$technical_contractor = $cf->value;
break;
case "Content":
$content = $cf->value;
break;
}
}
Or if you don't know in advance all the ones you'll need, load them into an array:
$employee_attrs = array();
foreach ($employee->custom_fields->custom_field as $cf) {
// Add an array key of the name, whose value is the value
$attrs[$cf->attributes()->name] = $cf->value;
}
var_dump($employee_attrs);

The attributes() function is what you're looking for I think. For example
foreach ($employee->custom_fields->custom_field as $line) {
echo "[" . $line->attributes()->name . "]" . $line->value . "\n";
}
Also you might have to use this instead:
foreach ($employee->_data->custom_fields->custom_field as $line) {
(not sure, try both)

Related

Get data from OMDb API with xml

I'l try to get the movie title and info from the omdb API. This is my code:
<?php
$enter = $_GET["enter"];
$content = file_get_contents("https://www.omdbapi.com/?s=$enter&r=xml");
$xml = simplexml_load_string($content);
if($xml) {
echo "<h2>" .$xml->title. "</h2>";
}
else
{
echo "Nothing found. Add the info manualy";
}
?>
The "enter" value is from the search form with AJAX. He create only an empty h2 tag. How can i get also the data from the API?
Thank you,
Julian
You should familiarize yourself with the structure of the xml to know how to access its elements. print_r(get_object_vars($xml)) will show you a structure like this:
Array
(
[#attributes] => Array
(
[totalResults] => 3651
[response] => True
)
[result] => Array
(
[0] => SimpleXMLElement Object
(
[#attributes] => Array
(
[title] => World War Z
[year] => 2013
[imdbID] => tt0816711
[type] => movie
[poster] => https://images-na.ssl-images-amazon.com/images/M/MV5BMTg0NTgxMjIxOF5BMl5BanBnXkFtZTcwMDM0MDY1OQ##._V1_SX300.jpg
)
)
[1] => SimpleXMLElement Object
(
[#attributes] => Array
(
[title] => Captain America: Civil War
[year] => 2016
[imdbID] => tt3498820
[type] => movie
[poster] => https://images-na.ssl-images-amazon.com/images/M/MV5BMjQ0MTgyNjAxMV5BMl5BanBnXkFtZTgwNjUzMDkyODE#._V1_SX300.jpg
)
)
...
...
...
[9] => SimpleXMLElement Object
(
[#attributes] => Array
(
[title] => War
[year] => 2007
[imdbID] => tt0499556
[type] => movie
[poster] => https://images-na.ssl-images-amazon.com/images/M/MV5BMTgzNTA4MTc3OF5BMl5BanBnXkFtZTcwOTA0ODk0MQ##._V1_SX300.jpg
)
)
)
)
So you receive an array with results where you need to pick from. Alternatively if you know the exact title the API has the t=title option which only returns a single result (see documentation).
So assuming you use the s=title option which returns multiple results, you can use something like this to pick information from the first result:
<?php
$enter = $_GET["enter"];
$content = file_get_contents("https://www.omdbapi.com/?s=$enter&r=xml");
$xml = simplexml_load_string($content);
# show the structure of the xml
# print_r(get_object_vars($xml));
if($xml) {
print "<h2>" .$xml->result[0]['title']. "</h2>";
print "<br>imdbID=" . $xml->result[0]['imdbID'] ;
} else {
echo "Nothing found. Add the info manualy";
}
?>

Extracting a property from an array of an array of objects

I've got an object, containing an array of objects, containing an array of values:
stdClass Object (
[devices] => Array (
[0] => stdClass Object (
[location] => 1
[delegate] =>
[type] => 1
[id] => 1234
[IP] => 1.2.3.4
[name] => host1
[owner] => user6
[security] => 15
)
[1] => stdClass Object (
[location] => 2
[delegate] =>
[type] => 1
[id] => 4321
[IP] => 4.3.2.1
[name] => host2
[owner] => user9
[security] => 15
)
)
)
I want to extract just the id and name into an array in the form of:
$devices['id'] = $name;
I considered using the array_map() function, but couldn't work out how to use it... Any ideas?
This will generate you a new array like I think you want
I know you says that delegate is an object but the print does not show it that way
$new = array();
foreach($obj->devices as $device) {
$new[][$device->id] = $device->name;
}
Would something like this not work? Untested but it cycles through the object structure to extract what I think you need.
$devices = myfunction($my_object);
function myfunction($ob){
$devices = array();
if(isset($ob->devices)){
foreach($ob->devices as $d){
if(isset($d->delegate->name && isset($d->delegate->id))){
$devices[$d->delegate->id] = $d->delegate->name;
}
}
}
return($devices);
}
Im usually using this function to generate all child and parent array stdclass / object, but still make key same :
function GenNewArr($arr=array()){
$newarr = array();
foreach($arr as $a=>$b){
$newarr[$a] = is_object($b) || is_array($b) ? GenNewArr($b) : $b ;
}
return $newarr;
}

Retrieving correct record from multidimentional array

I'm having a mental freeze moment. If I have an array in the following format:
$myData = Array
(
[0] => stdClass Object
(
[id] => 1
[busID] => 5
[type] => SMS
[number] => 5128888888
)
[1] => stdClass Object
(
[id] => 2
[busID] => 5
[type] => APP
[number] => 5125555555
)
[2] => stdClass Object
(
[id] => 4
[busID] => 5
[type] => APP
[number] => 5129999988
[verified] => 1
[default] => 0
)
)
And I only have an var for ID of the record, how do I retrieve the rest of the detail for that set.
$myID = 2;
// get number 5125555555 and it's type
echo $myData[][$myID]['number']; // ???
The way you have your data arranged your going to have to loop through your array to identify the object corresponding to $myID.
foreach($myData as $object) if($object->id == $myID) echo $object->number;
The alternative is to arrange your $myData as an associative array with the id field as the key. Then you could access it simply with $myData[$myID]->number.
Actually it's an array that contains StdClass objects , try looping over $myData and access each attribute :
foreach ( $myData as $data )
{
print_r($data->id);
// ...
}
You can avoid loop by using following logic:
<?php
$myID = 2;
$myData = json_decode(json_encode($myData),1); // Convert Object to Array
$id_arr = array_column($myData, 'id'); // Create an array with All Ids
$idx = array_search($myID, $id_arr);
if($idx !== false)
{
echo $myData[$idx]['type'] . ' -- ' . $myData[$idx]['number'];
}
?>
Working Demo
Note: array_column is supported from PHP 5.5.
For lower versions you can use this beautiful library https://github.com/ramsey/array_column/blob/master/src/array_column.php
You can create a custom function to achieve this, you need to pass the array and id whose details you want and the function will return the array with matching id, like below
function detailsById($myData,$id){
foreach($myData as $data){
if($data->id == $id){
return $data;
}
}
}
Just call this function with your array and id..
$data=detailsById($myData,2);
echo "<pre>";print_r($data);
This will give you :
stdClass Object
(
[id] => 2
[busID] => 5
[type] => APP
[number] => 5125555555
)
And further to print 'number' and 'type' use $data array
$data['type'];
$data['number'];

in_array is not working even though i see a match

I'm trying to check if there is a duplicate value in a foreach loop.
Here's my attempt which ain't working:
$popup_array = array();
foreach($xml->config->popup as $popup_item)
{
$duplicate_test = $popup_item->attributes()->name;
if (in_array_r($duplicate_test, $popup_array)){
echo "match found for " . $duplicate_test;
}
echo "item: " . $duplicate_test . "<br />";
$popup_array[$i] = $duplicate_test;
$i++;
}
Now i can clearly see there is 2 duplicates, here is what i see at end when i print_r as you can see 2 x default and 2 x lost and the echo also shows default and lost so the in_array is not working and im not sure why:
[0] => SimpleXMLElement Object
(
[0] => Default
)
[1] => SimpleXMLElement Object
(
[0] => Default
)
[2] => SimpleXMLElement Object
(
[0] => pipe
)
[3] => SimpleXMLElement Object
(
[0] => raised
)
[4] => SimpleXMLElement Object
(
[0] => steal
)
[5] => SimpleXMLElement Object
(
[0] => lost
)
[6] => SimpleXMLElement Object
(
[0] => lost
)
[7] => SimpleXMLElement Object
(
[0] => teach
)
[8] => SimpleXMLElement Object
(
[0] => terrain
)
Is there an error in my code? Is it something to do with the simpleXMLEelement and thats turned it into a multidimensional array and i need to search a different way. If i loop through the array and do this:
$popup_length = count($popup_array);
for($x=0;$x<$popup_length;$x++)
{
echo $popup_array[$x];
echo "<br>";
}
It returns:
Default
Default
pipe
raised
steal
lost
lost
teach
terrain
I think, it should be like this
$popup_array = array();
foreach($xml->config->popup as $popup_item)
{
$duplicate_test = (string) $popup_item->attributes()->name;
if (!in_array($duplicate_test, $popup_array)){
$popup_array[] = $duplicate_test;
}
else {
echo "match found for " . $duplicate_test;
}
}
You should check if not in array and then push/add it inside $popup_array, no need to use $i as an index for the array. Also check SimpleXMLElement::attributes.
This value returns an object, not a string:
$popup_item->attributes()->name
So, the objects probably differ by some XML attribute other than name. Try casting to a string, so that your array index is just the name:
$duplicate_test = (string) $popup_item->attributes()->name;
use array_diff
$arr = array(1=>'word',2=>'otherword',3 =>'Hello' ,4=>'hello', 5=>'KKKKK');
//Case Sensitive
$withoutDuplicates = array_unique(array_map("strtoupper", $arr));
$duplicates = array_diff($arr, $withoutDuplicates);
print_r($duplicates);
will print :
Array
(
[3] => Hello
[4] => hello
)

CakePHP Model Query Return Data Formating

I'm looking for a way to make it so cake returns all database data in the same format/structure... Currently it returns two different types of format depending on the relationship.
If a model 'B' is associated with the current model 'A' being queried it will then place model associations for 'B' underneath it as you can see in [User] below. I want it so that all queries use that structure.
example:
$this->find('all', ....
returns:
Array
(
[0] => Array
(
[UserGroup] => Array
(
[id] => 53
[user_id] => 100003332014851
[media_id] =>
[name] => john
[description] => qwasdfad
)
[User] => Array
(
[id] => 100003332014851
[session_id] => ssm2qbrotmm13ho1ipm8ii2492
[username] =>
[password] => -1
[Planner] => Array
(
)
[Purchase] => Array
(
)
[Listing] => Array
(
)
)
)
I want this to look like:
Array
(
[0] => Array
(
[UserGroup] => Array
(
[id] => 53
[user_id] => 100003332014851
[media_id] =>
[name] => john
[description] => qwasdfad
[User] => Array
(
[id] => 100003332014851
[session_id] => ssm2qbrotmm13ho1ipm8ii2492
[username] =>
[password] => -1
[Planner] => Array
(
)
[Purchase] => Array
(
)
[Listing] => Array
(
)
)
)
)
)
In CakePHP, the find() method return data like your first format. But If you want to format like second one then you have to process it by hand (try to avoid this if possible)
$data = $this->find('all');
$assocs = Set::extract('/User', $data); // extracting all `User` array
foreach($assocs as $key => $assoc) {
unset($data[$key]['User']); // removing the associate `User` from `$data`
$data[$key]['UserGroup']['User'] = $assoc['User']; // adding associate under `UserGroup`
}
ended up doing this... it changes the output to what we need. The top level item does not have a header which is fine I just adjusted our scripts for that... maybe this will help somebody else if they need a custom idea
also no guarantee this covers all possible results but so far it works with all the queries we have.
class AppModel extends Model {
function afterFind($results, $primary) {
//if this is a primary, structure like a secondary so entire site is same format
if ($primary) {
$class = get_class($this);
//simple fix for primary
foreach ($results as $key => $result) {
$result = $this->formatData($result, $class);
$results[$key] = $result;
}
}
return $results;
}
function formatData($result, $class) {
$array = array();
if (isset($result[$class])) {
$array = $result[$class];
unset($result[$class]);
}
$array += $result;
return $array;
}
You can also use contain in this case along with find as UserGroup.User for your desired result

Categories