Returning on a recursive function in php - php

I'm developing a function to parse 2 xml files. It compares them node by node and then if the nodes are different, the function should return one of them. But it isn't returning anything.
$xml = simplexml_load_file("file1.xml");
$xml2 = simplexml_load_file("file2.xml");
$result = parseNode($xml, $xml2);
print_r($result);
echo $result;
function parseNode($node1, $node2) {
for ($i = 0; $i < count($node1->children()); $i++) {
$child1 = $node1->children();
$child2 = $node2->children();
if ($child1[$i]->getName() != $child2[$i]->getName()) {
return $child1[$i];
} else {
parseNode($child1[$i], $child2[$i]);
}
}
}

return parseNode($child1[$i], $child2[$i]);

Well, you can do it with a simple conditional statement...
$xml = simplexml_load_file("file1.xml");
$xml2 = simplexml_load_file("file2.xml");
$result = parseNode($xml, $xml2);
print_r($result);
echo $result;
function parseNode($node1, $node2) {
$child1 = $node1->children();
$child2 = $node2->children();
$numChildren = count($child1);
for ($i = 0; $i < $numChildren; $i++) {
if ($child1[$i]->getName() != $child2[$i]->getName()) {
return $child1[$i];
} else {
$test = parseNode($child1[$i], $child2[$i]);
if ($test) return $test;
}
}
return false;
}

You could also loop over the XML structures using recursive iterators to simplify your parseNodes() function.
$xml = simplexml_load_string("<root><foo/><bar><baz/></bar></root>", "SimpleXMLIterator");
$xml2 = simplexml_load_string("<root><foo/><bar><baz/></bar><bat/></root>", "SimpleXMLIterator");
$result = parseNode($xml, $xml2);
echo $result;
function parseNode($a, $b) {
$mit = new MultipleIterator(MultipleIterator::MIT_NEED_ANY|MultipleIterator::MIT_KEYS_NUMERIC);
$mit->attachIterator(new RecursiveIteratorIterator($a, RecursiveIteratorIterator::SELF_FIRST));
$mit->attachIterator(new RecursiveIteratorIterator($b, RecursiveIteratorIterator::SELF_FIRST));
foreach ($mit as $node) {
// One has more nodes than another!
if ( ! isset($node[0], $node[1])) {
return 'Curse your sudden but inevitable betrayal!';
}
// Nodes have different names
if ($node[0]->getName() !== $node[1]->getName()) {
return $node[0];
}
}
// No differences in names and order
return FALSE;
}
Setting up the MultipleIterator is pretty verbose (mostly due to the über-long class names) but the logic is darned simple.

There's no return in the recursive call. Hence, no result.
EDIT Do not up-vote this. ircmaxell is right. I have removed the exception part of my answer.

Related

PHP array_filter weird behavior

I'm using array_filter with multiple parameters but it's not doing the filter correctly. Here where it's supposed to return an array with only "arts,crafts,designs" as an element, it's returning an empty array. The only $askedcat parameter it works for is "arts". I can't find out what the issue is.
I've tried not using an array_filter and instead just looping over the array, and I get the same problem.
<?php
class CategoryFilter {
public $categoryAskedFor;
function __construct($askedCat) {
$this->categoryAskedFor = $askedCat;
}
function categoryCallback($projectCategoryString) {
$project_category_array = explode(",", $projectCategoryString);
if(in_array($this->categoryAskedFor, $project_category_array)) return true;
return false;
}
}
$verifiedProjects = ["arts", "arts,crafts,designs", "film", "film,theater"];
$askedCat = "crafts";
$newArr = array_filter($verifiedProjects, array(new CategoryFilter($askedCat), "categoryCallback"));
for ($i = 0; $i < count($newArr); $i++) {
echo $newArr[$i] . "<br>";
}
I expect the output here to be arts,crafts,design<br> but it's only <br> meaning the array is empty.
There are many way to achieve this but let me show you two way here
WAY #1
If you using the for loop to retrieve the array value then need to have numeric key and as per your code you need array_values function to manage that
<?php
class CategoryFilter {
public $categoryAskedFor;
function __construct($askedCat) {
$this->categoryAskedFor = $askedCat;
}
function categoryCallback($projectCategoryString) {
$project_category_array = explode(",", $projectCategoryString);
if(in_array($this->categoryAskedFor, $project_category_array)) return true;
return false;
}
}
$verifiedProjects = ["arts", "arts,crafts,designs", "film", "film,theater"];
$askedCat = "crafts";
$newArr = array_filter($verifiedProjects, array(new CategoryFilter($askedCat), "categoryCallback"));
$newArr = array_values($newArr);
for ($i = 0; $i < count($newArr); $i++) {
echo $newArr[$i] . "<br>";
}
WAY # 2
If you don't want to use the array_values here then you need to manage the foreach loop instead of for loop
<?php
class CategoryFilter {
public $categoryAskedFor;
function __construct($askedCat) {
$this->categoryAskedFor = $askedCat;
}
function categoryCallback($projectCategoryString) {
$project_category_array = explode(",", $projectCategoryString);
if(in_array($this->categoryAskedFor, $project_category_array)) return true;
return false;
}
}
$verifiedProjects = ["arts", "arts,crafts,designs", "film", "film,theater"];
$askedCat = "crafts";
$newArr = array_filter($verifiedProjects, array(new CategoryFilter($askedCat), "categoryCallback"));
foreach ($newArr as $value) {
echo $value . "<br>";
}
The way you're looping over your resulting array is wrong, because array_filter will preserve the array keys. The 0 index may not be there in the filtered version (and in your case, it actually isn't).
Use foreach instead:
foreach ($newArr as $value) {
echo $value, '<br>';
}
array_filter will remove elements but will not reset the keys. use array_values to reset the keys first.
$newArr = array_values($newArr);
for ($i = 0; $i < count($newArr); $i++) {
echo $newArr[$i] . "<br>";
}
You will get as per your output
$askedCat = 'crafts';
$verifiedProjects = ["arts", "arts,crafts,designs", "film", "film,theater"];
$newArr = array_filter($verifiedProjects, function ($item) use ($askedCat) {
if (stripos($item, $askedCat) !== false) {
return true;
}
return false;
});
foreach ($newArr as $value) {
echo $value . "<br>";
}

Data Not Being Parsed Correctly

I have a simple data format that goes as follows:
stuff/stuff/stuff
An example would be:
data/test/hello/hello2
In order to retrieve a certain piece of data, one would use my parser, which tries to do the following:
In data/test/hello/hello2
You want to retrieve the data under data/test (which is hello). My parser's code is below:
function getData($data, $pattern)
{
$info = false;
$dataLineArray = explode("\n", $data);
foreach($dataLineArray as &$line)
{
if (strpos($line,$pattern) !== false) {
$lineArray = explode("/", $line);
$patternArray = explode("/", $pattern);
$iteration = 0;
foreach($lineArray as &$lineData)
{
if($patternArray[$iteration] == $lineData)
{
$iteration++;
}
else
{
$info = $lineData;
}
}
}
}
return $info;
}
However, it always seems to return the last item, which in this case is hello2:
echo getData("data/test/hello/hello2", "data/test");
Gives Me;
hello2
What am I doing wrong?
If you want the first element after the pattern, put break in the loop:
foreach($lineArray as $lineData)
{
if($patternArray[$iteration] == $lineData)
{
$iteration++;
}
elseif ($iteration == count($patternArray))
{
$info = $lineData;
break;
}
}
I also check $iteration == count($patternArray) so that it won't return intermediate elements, e.g.
/data/foo/test/hello/hello2
will return hello rather than foo.
P.S. There doesn't seem to be any reason to use references instead of ordinary variables in your loops, since you never assign to the reference variables.

PHP Notice: Undefined property: Tpl::$lbName

I have a class Tpl to mount template with this function (template.php)
function Set($var, $value){
$this->$var = $value;
}
A php file that call the function, example (form.php):
$t->Set("lbAddress","Address");
And a html file with the template with tags (template.html)
<tr><td>[lbAdress]</td></tr>
To print the html I have this function (template.php) - the notice points to this function
function Show_Temp($ident = ""){
// create array
$arr = file($this->file);
if( $ident == "" ){
$c = 0;
$len = count($arr);
while( $c < $len ){
$temp = str_replace("[", "$" . "this->", $arr[$c]);
$temp = str_replace("]", "", $temp);
$temp = addslashes($temp);
eval("\$x = \"$temp\";");
echo $x;
$c++;
}
} else {
$c = 0;
$len = count($arr);
$tag = "*=> " . $ident;
while( $c < $len ){
if( trim($arr[$c]) == $tag ){
$c++;
while( (substr(#$arr[$c], 0 ,3) != "*=>" ) && ($c < $len) ){
$temp = str_replace("[", "$" . "this->", $arr[$c]);
$temp = str_replace("]", "", $temp);
$temp = addslashes($temp);
eval("\$x= \"$temp\";"); //this is the line 200
echo $x;
$c++;
}
$c = $len;
}
$c++;
}
}
}
If the template .html have a line [lbName] and I don't have the line $t->Set("lbName","Name"); at the php code, I receive the error PHP Notice: Undefined property: Tpl::$lbName in ../template.php(200) : eval()'d code on line 1. The solution that I found is add lines like $t->Set("lbName","");, but if I have 50 tags in HTML that I don't use in PHP, I have to add all 50 $t->Set("tag_name","");. The error occurred after migrate to the PHP 5.
Can someone help me? Thanks
Perhaps a better way still would be not to rely on dynamic evaluation through eval (it's generally best to avoid eval where possible), but to replace [lbName] with the value stored in the object directly as and when needed. If you can replace [lbName] with $this->lbName, surely you can also replace it with the value of lBName that you've looked up on-the-fly?
To answer your original question, however:
If I understand correctly, you're setting the values like this:
$t->Set('foo', 'bar');
And – effectively – getting them like this:
$t->foo;
If so, you could implement a __get method to intercept the property references and provide your own logic for retrieving the value; e.g.:
public function __get($key)
{
// You can adapt this logic to suit your needs.
if (isset($this->$key))
{
return $this->$key;
}
else
{
return null;
}
}
In this case, you'd probably be better off using an associative array as the backing store, and then using __get and __set to access it; e.g.:
class Template
{
private $values = array();
public function __get($key)
{
if (array_key_exists[$key, $this->values])
{
return $this->values[$key];
}
else
{
return null;
}
}
public function __set($key, $value)
{
$this->values[$key] = $value;
}
}

Get the first element of an array that is defined and not null

I would like to assign a variable that is the first not null element from another set of variables. Much like the conditional assignment in ruby ||=. For example:
<?php
$result = null;
$possibleValue1 = null;
// $possibleValue2 not defined
$possibleValue3 = 'value3';
if (isset($possibleValue1) && !is_null($possibleValue1)) {
$result = $possibleValue1;
} else if (isset($possibleValue2) && !is_null($possibleValue2)) {
$result = $possibleValue2;
} else if (isset($possibleValue3) && !is_null($possibleValue3)) {
$result = $possibleValue3;
}
Is there a way to do this simply in php, like so (if possible, I would like to avoid creating a function and just use functions from the php library):
$result = firstNotNull(array($possibleValue1, $possibleValue2, $possibleValue3));
I think the shortest way is:
$result = current(array_filter(array($possibleValue1, $possibleValue2, $possibleValue3)));
If all $possibleValues are definitely set:
$possibleValues = array($possibleValue1, $possibleValue2, ...);
If they may not be set:
$possibleValues = compact('possibleValue1', 'possibleValue2', ...);
Then:
$result = reset(array_filter($possibleValues, function ($i) { return $i !== null; }));
Do not know about such a function in PHP but why not creating Your own?
function getFirstNotNullValue($values = array()) {
foreach($values as $val)
if($val) return $val;
return false;
}

PHP - determine last function-call from chained function-calls

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;
}
}

Categories