So I have the following XML chain:
$xml->assessment->outcomes_processing->outcomes->decvar->attributes()->cutvalue
XML example
In Some XML-files $xml->assessment->outcomes_processing->outcomes->... does exist. But here only $xml->assessment exist.
<questestinterop>
<assessment ident="nedolat78356769204914" title="welkom woordenschat 4">
<qtimetadata>
<qtimetadatafield>
<fieldlabel>qmd_assessmenttype</fieldlabel>
<fieldentry>Assessment</fieldentry>
</qtimetadatafield>
</qtimetadata>
<section ident="nedolat78356769204915" title="Woordenschat">
<selection_ordering>
<selection/>
<order order_type="Sequential"/>
</selection_ordering>
<item ident="QTIEDIT:FIB:34932158" title="Oefening 1">
...
</item>
</section>
</assessment>
</questestinterop>
At the moment I am receiving the error: "Trying to get property of non-object". Which is logic because the node from outcomes doesn't exist.
So I tried a solution with isset() which obviously isn't working either.
isset($xml->assessment->outcomes_processing->outcomes->decvar->attributes()->cutvalue) ? ... : null
This gives me the same error. Because the node outcomes doesn't exist, so he throws me the error immediately. The solution could be for checking each node individually with isset(), of course with a function because otherwise I have a lot of checking to do...
This is my function, which is failed to work because the string '->' could not be processed as php code:
// xml: xml structure form SimpleXML
// chain: assessment->outcomes_processing->outcomes->decvar->attributes()->cutvalue
function checkIfXMLChainExists($xml, $chain) {
$xmlChain = '';
$i = 0;
$nodes = explode('->', $chain);
$numItems = count($nodes);
// experimenting with eval() to treat '->' as ->; failed to work
$a = '$xml->assessment->outcomes_processing';
$t = eval($a);
echo $t;
print_r($t);
foreach ($nodes as $node) {
$xmlChain .= '->' . $node;
if (isset($xml->$xmlChain)) { // Node exists
if ($i+1 == $numItems) {
return $xml->$xmlChain;
}
} else { // Node does not exists
return 'does not exist';
}
$i++;
}
Does anyone has some ideas because I don't have any inspiration at the moment :(
Thanks in advance
Returns the data referenced by the chain, or NULL if it doesn't exist.
function getDataIfExists () {
// We accept an unknown number of arguments
$args = func_get_args();
if (!count($args)) {
trigger_error('getDataIfExists() expects a minimum of 1 argument', E_USER_WARNING);
return NULL;
}
// The object we are working with
$baseObj = array_shift($args);
// Check it actually is an object
if (!is_object($baseObj)) {
trigger_error('getDataIfExists(): first argument must be an object', E_USER_WARNING);
return NULL;
}
// Loop subsequent arguments, check they are valid and get their value(s)
foreach ($args as $arg) {
$arg = (string) $arg;
if (substr($arg, -2) == '()') { // method
$arg = substr($arg, 0, -2);
if (!method_exists($baseObj, $arg)) return NULL;
$baseObj = $baseObj->$arg();
} else { // property
if (!isset($baseObj->$arg)) return NULL;
$baseObj = $baseObj->$arg;
}
}
// If we get here $baseObj will contain the item referenced by the supplied chain
return $baseObj;
}
// Call it like this:
$subObj = getDataIfExists($xml, 'assessment', 'outcomes_processing', 'outcomes', 'decvar', 'attributes()', 'cutvalue');
Related
I am trying to modify phpgraphlib so that I can generate a legend when multiple bar colors are used. I added an array with colors as optional parameter in generateLegend() but it doesn't seem to work. I don't know what's wrong. I have no prior experience with PHP, but it seemed to me passing an array as optional parameter must be possible
Here is my code:
protected function generateLegend(array $colors = array())
{
// here is some code
if($this->bool_multi_color_bars) {
// gets here
if (!empty($colors)) {
// doesn't get here
$index = 0;
foreach($colors as $key => $item) {
// here is some code that creates the colored boxes
$index++;
}
}
}
}
And here is the code that calls the function:
$colors = array();
foreach($data as $key => $value) {
if($value < 3) {
$colors[$key] = 'green';
}
elseif($value < 8) {
$colors[$key] = 'orange';
}
else {
$colors[$key] = 'red';
}
}
$graph->setBarColors($colors);
$graph->setLegend(true, $colors);
$graph->createGraph();
EDIT: generateLegend() is called with the follwing code:
if ($this->bool_legend) {
$this->generateLegend();
}
For the sake of readability I left most of the code out, but I can see that the method is called (therefore I added the comments where the code does get and not)
I'm not sure, what you actually want. What you currently see is the expected behavior of empty. An array without elements is empty.
var_dump(empty([])); // true
If you want to test, if the optional param was actually set, you could use func_num_args.
if (func_num_args() > 0) {
// $colors was set
}
Or use an other default argument and test against the type.
function foo(array $bar = null) {
if ($bar === null) {
// ...
}
}
I get some data externally but gets this error because the variable is "empty":
Undefined property: stdClass::$summary in /Applications/MAMP/htdocs/jobportal/functions.php on line 68
I have tried to build a function to help me:
$summary = convert_empty($user->summary);
function convert_empty($data) {
if(isset($data)) {
return $data;
} else {
return ".";
}
}
But the error is still there. I have tried isset, empty, and defined. I think I miss another point here - since none of it is working.
That means that the object $user doesn't have a summary member variable defined.
$summary = isset($user->summary) ? convert_empty($user->summary) : NULL;
Or
$summary = isset($user->summary) ? convert_empty(isset($user->summary) ? $user->summary : NULL);
Now you won't see the warning and $summary will be set to NULL, assuming that you're expecting $summary to be NULL in this situation in which $user->summary is undefined.
The second one allows your convert_empty to figure it out.
The issue is not in your function, but how you call it. The error is that you're trying to access ->summary but doesn't exists. You could use something like this:
$summary = convert_empty($user, 'summary');
function convert_empty($data, $key) {
if (isset($data->$key))
return $data->$key;
return ".";
}
Note that you should also test if $data is an object too.
if (is_object($data) && isset($data->$key)) { ... }
Or, without a function using conditional ternary operator :
$summary = isset($user->summary) ? $user->summary : '.';
EDIT for a deeper use :
convert_empty($user, 'positions', 'values', $i, 'title');
function convert_empty($obj) {
$error = '.';
$args = func_get_args();
array_shift($args); // remove $obj
$ref = $obj ;
foreach ($args as $arg) {
if (is_array($ref)) {
if (!isset($ref[$arg])) return $error ;
$ref = $ref[$arg] ;
}
elseif (is_object($ref)) {
if (!isset($ref->$arg)) return $error ;
$ref = $ref->$arg ;
}
}
return $ref ;
}
Using PHP, I would like to write a function that accomplishes what is shown by this pseudo code:
function return_value($input_string='array:subArray:arrayKey')
{
$segments = explode(':',$input_string);
$array_depth = count(segments) - 1;
//Now the bit I'm not sure about
//I need to dynamically generate X number of square brackets to get the value
//So that I'm left with the below:
return $array[$subArray][$arrayKey];
}
Is the above possible? I'd really appreciate some pointer on how to acheive it.
You can use a recursive function (or its iterative equivalent since it's tail recursion):
function return_value($array, $input_string) {
$segments = explode(':',$input_string);
// Can we go next step?
if (!array_key_exists($segments[0], $array)) {
return false; // cannot exist
}
// Yes, do so.
$nextlevel = $array[$segments[0]];
if (!is_array($nextlevel)) {
if (1 == count($segments)) {
// Found!
return $nextlevel;
}
// We can return $nextlevel, which is an array. Or an error.
return false;
}
array_shift($segments);
$nextsegments = implode(':', $segments);
// We can also use tail recursion here, enclosing the whole kit and kaboodle
// into a loop until $segments is empty.
return return_value($nextlevel, $nextsegments);
}
Passing one object
Let's say we want this to be an API and pass only a single string (please remember that HTTP has some method limitation in this, and you may need to POST the string instead of GET).
The string would need to contain both the array data and the "key" location. It's best if we send first the key and then the array:
function decodeJSONblob($input) {
// Step 1: extract the key address. We do this is a dirty way,
// exploiting the fact that a serialized array starts with
// a:<NUMBEROFITEMS>:{ and there will be no "{" in the key address.
$n = strpos($input, ':{');
$items = explode(':', substr($input, 0, $n));
// The last two items of $items will be "a" and "NUMBEROFITEMS"
$ni = array_pop($items);
if ("a" != ($a = array_pop($items))) {
die("Something strange at offset $n, expecting 'a', found {$a}");
}
$array = unserialize("a:{$ni}:".substr($input, $n+1));
while (!empty($items)) {
$key = array_shift($items);
if (!array_key_exists($key, $array)) {
// there is not this item in the array.
}
if (!is_array($array[$key])) {
// Error.
}
$array = $array[$key];
}
return $array;
}
$arr = array(
0 => array(
'hello' => array(
'joe','jack',
array('jill')
)));
print decodeJSONblob("0:hello:1:" . serialize($arr));
print decodeJSONblob("0:hello:2:0" . serialize($arr));
returns
jack
jill
while asking for 0:hello:2: would get you an array { 0: 'jill' }.
you could use recursion and array_key_exists to walk down to the level of said key.
function get_array_element($key, $array)
{
if(stripos(($key,':') !== FALSE) {
$currentKey = substr($key,0,stripos($key,':'));
$remainingKeys = substr($key,stripos($key,':')+1);
if(array_key_exists($currentKey,$array)) {
return ($remainingKeys,$array[$currentKey]);
}
else {
// handle error
return null;
}
}
elseif(array_key_exists($key,$array)) {
return $array[$key];
}
else {
//handle error
return null;
}
}
Use a recursive function like the following or a loop using references to array keys
<?php
function lookup($array,$lookup){
if(!is_array($lookup)){
$lookup=explode(":",$lookup);
}
$key = array_shift($lookup);
if(!isset($array[$key])){
//throw exception if key is not found so false values can also be looked up
throw new Exception("Key does not exist");
}else{
$val = $array[$key];
if(count($lookup)){
return lookup($val,$lookup);
}
return $val;
}
}
$config = array(
'db'=>array(
'host'=>'localhost',
'user'=>'user',
'pass'=>'pass'
),
'data'=>array(
'test1'=>'test1',
'test2'=>array(
'nested'=>'foo'
)
)
);
echo "Host: ".lookup($config,'db:host')."\n";
echo "User: ".lookup($config,'db:user')."\n";
echo "More levels: ".lookup($config,'data:test2:nested')."\n";
Output:
Host: localhost
User: user
More levels: foo
When using chained functions, is there a way to determine if the current call is the last in the chain?
For example:
$oObject->first()->second()->third();
I want to check in first, second and third if the call is the last in the chain so it saves me to write a result-like function to always add to the chain. In this example the check should result true in third.
No, not in any way that's sane or maintainable.
You'll have to add a done() method or similar.
As far as i know it's impossible, i'd suggest to use finishing method like this:
$oObject->first()
->second()
->third()
->end(); // like this
If you want to execute a function on the last chain (without addional exec or done on the chain).
The code below will obtain the full chain from the source code and return the data after the last chain.
<?php
$abc = new Methods;
echo($abc->minus(12)->plus(32)); // output: -12+32
echo(
$abc->plus(84)
->minus(63)
); // output: +84-63
class Methods{
private $data = '';
private $chains = false;
public function minus($val){
$this->data .= '-'.$val;
return $this->exec('minus');
}
public function plus($val){
$this->data .= '+'.$val;
return $this->exec('plus');
}
private function exec($from){
// Check if this is the first chain
if($this->chains === false){
$this->getChains();
}
// Remove the first chain as it's
// already being called
if($this->chains[0] === $from){
array_shift($this->chains);
}
else
die("Can't parse your chain");
// Check if this is the last chain
if(count($this->chains) === 0){
$copy = $this->data;
// Clear data
$this->chains = false;
$this->data = '';
return $copy;
}
// If not then continue the chain
return $this;
}
private function getChains(){
$temp = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
// Find out who called the function
for ($i=0; $i < count($temp); $i++) {
if($temp[$i]['function'] === 'exec'){
$temp = $temp[$i + 1];
break;
}
}
// Prepare variable
$obtained = '';
$current = 1;
// Open that source and find the chain
$handle = fopen($temp['file'], "r");
if(!$handle) return false;
while(($text = fgets($handle)) !== false){
if($current >= $temp['line']){
$obtained .= $text;
// Find break
if(strrpos($text, ';') !== false)
break;
}
$current++;
}
fclose($handle);
preg_match_all('/>(\w.*?)\(/', $obtained, $matches);
$this->chains = $matches[1];
return true;
}
}
I'm looking for a nice way of doing the magicFunction():
$indexes = array(1, 3, 0);
$multiDimensionalArray = array(
'0',
array('1-0','1-1','1-2',array('2-0','2-1','2-2')),
array('1-4','1-5','1-6')
);
$result = magicFunction($indexes, $multiDimensionalArray);
// $result == '2-0', like if we did $result = $multiDimensionalArray[1][3][0];
Thanks.
You can solve this recursively.
$indexes = array(1, 3, 0);
$multiDimensionalArray = array(
'0',
array('1-0','1-1','1-2',array('2-0','2-1','2-2')),
array('1-4','1-5','1-6')
);
$result = magicFunction($indexes, $multiDimensionalArray);
function magicFunction($indices, $array) {
// Only a single index is left
if(count($indices) == 1) {
return $array[$indices[0]];
} else if(is_array($indices)) {
$index = array_shift($indices);
return magicFunction($indices, $array[$index]);
} else {
return false; // error
}
}
print $result;
This function will go down the $multiDimensionalArray as long as there are indices available and then return the last value accesses by the index. You will need to add some error handling though, e.g. what happens if you call the function with $indexes = array(1,2,3,4);?
This is a recursive version. Will return null if it cannot find a value with the given index route.
function magicFunction($indexes, $array) {
if (count($indexes) == 1) {
return isset($array[$indexes[0]]) ? $array[$indexes[0]] : null;
} else {
$index=array_shift($indexes);
return isset($array[$index]) ? magicFunction($indexes, $array[$index]) : null;
}
}
You can just step trough based on your keys (no need to do any recursion, just a simple foreach):
$result = &$multiDimensionalArray;
foreach($indexes as $index)
{
if (!isset($result[$index]))
break; # Undefined index error
$result = &$result[$index];
}
If you put it inside a function, then it won't keep the reference if you don't want to. Demo.
I think something like this should do the trick for you.
function magicFuntion($indexes, $marray)
{
if(!is_array($indexes) || count($indexes) == 0 || !is_array($marray))
return FALSE;
$val = '';
$tmp_arr = $marray;
foreach($i in $indexes) {
if(!is_int($i) || !is_array($tmp_arr))
return FALSE;
$val = $tmp_arr[$i];
$tmp_arr = $tmp_arr[$i];
}
return $val;
}
Try this :P
function magicFunction ($indexes,$mdArr){
if(is_array($mdArr[$indexes[0]] && $indexes)){
magicFunction (array_slice($indexes,1),$mdArr[$indexes[0]]);
}
else {
return $mdArr[$indexes[0]];
}
}
My own attempt ; found it simpler by not doing it recursively, finally.
function magicFunction($arrayIndexes, $arrayData)
{
if (!is_array($arrayData))
throw new Exception("Parameter 'arrayData' should be an array");
if (!is_array($arrayIndexes))
throw new Exception("Parameter 'arrayIndexes' should be an array");
foreach($arrayIndexes as $index)
{
if (!isset($arrayData[$index]))
throw new Exception("Could not find value in multidimensional array with indexes '".implode(', ', $arrayIndexes)."'");
$arrayData = $arrayData[$index];
}
return $arrayData;
}
Exceptions may be somewhat "violent" here, but at least you can know exactly what's going on when necessary.
Maybe, for sensitive hearts, a third $defaultValue optional parameter could allow to provide a fallback value if one of the indexes isn't found.