Avoiding too many nested conditionals in PHP

Status
Not open for further replies.

Lucidity

New member
Jul 30, 2007
418
9
0
So I think I'm missing some fundamental principle of software design. In an effort to build in better handling of errors and unexpected results, I keep finding myself burying my code in deeper and deeper sets of nested conditionals. Especially with cURL apps and scrapers where I commonly have 15 actions in a routine where each successive action should only be executed based on the previous action's successful outcome.

One work around I sometimes use when working within a function or method is to have multiple return paths, but I'd kind of like to avoid that.

A simplified example of what I mean:

Code:
$page = download_page($url1);
if ($page['SUCCESS'] == true)
{
    $data = regex_functions($page['FILE']);
    
    if ($data == $expectedData)
    {
        $page = download_page($url2);
        
        if ($page['SUCCESS'])
        {
            $data = regex_functions($page['FILE']);
            
            if ($data = $expectedData)
            {
                ...
            }
            else
            {
                echo "Could not retriee expected data from page 2";
            }
        }
        else
        {
            echo "Could not download url 2";   
        }
    }
    else
    {
        echo "Could not retrieve expected data from page 1";
    }
}
else
{
    echo "Could not download url 1";
}
And this could go on and on. I'm thinking there has to be a more elegant and logical way to approach this.
 


case statements

That would just replace one if, elseif, else sequence, wouldn't it? There would still be nesting involved for more than one level deep unless I'm not taking your meaning.

Test for errors before hand, collect them, if there are none, do "...". If there are, show the errors.

Edit: for that particular case.

What if you can't test for certain errors unless certain other conditions hold true? For instance, the only way to check for errors relating to url2 is to download it, but I may need information from page 1 for url parameters in url 2.
 
case whatever
<condition1> do this
<condition2> do this
... etc..
end case

and you can nest them

case whatever
<condition1> case whatever2
<conditiona> doSomething
<conditionb> doSomething2
...etc...
end case
<condion2> doSomethingelse
...etc...
end case
 
Combine case and if.

PHP:
  <?php
switch ($i) {
    case "apple":
        echo "i is apple";
        break;
    case "bar":
        echo "i is bar";
        break;
    case "cake":
        echo "i is cake";
        break;
}
?>
 
I would say , case and usage of operands. For example if you aren't use any nested alternatives...

Code:
if(this)
{ 
  if(that)
   { 
     if(andthis) { } 
   }
}

Could easily become
Code:
if((this) && (that) && (andthis)) { }

something else to think about when you're looking for a specific match of multiple conditions.
 
I'm probably just a dumbass, but I'm not seeing how you can use case statements to reduce conditional nesting. Isn't that just replacing one semantic form of an if statement with another semantic form?

Just to be sure I'm communicating my point, my issue is not that I have single blocks that are too long, but that I have too many blocks nested inside one another.

I can rewrite my first example with case statements instead of if statements with the exact same amount of nesting and even less readable code:

Code:
$page = download_page($url1);
switch ($page['SUCCESS']) 
{
    case 1;
    $data = regex_functions($page['FILE']);
    
    switch ($data)
    {
        case $expectedData;
        $page = download_page($url2);
        
        switch ($page['SUCCESS'])
        {
            case 1;
            $data = regex_functions($page['FILE']);
            
            switch ($data)
            {
                case $expectedData;
                ...
                break;
                
                default:
                echo "Could not retriee expected data from page 2";
                break;
            }
            break;
            
            case 0;
            echo "Could not download url 2";
            break;
        }
        break;
        
        default: 
        echo "Could not retrieve expected data from page 1";
        break;
        
    }
    break;
    
    case 0;
    echo "Could not retrieve expected data from page 1";
    break;    
}
 
That would just replace one if, elseif, else sequence, wouldn't it? There would still be nesting involved for more than one level deep unless I'm not taking your meaning.



What if you can't test for certain errors unless certain other conditions hold true? For instance, the only way to check for errors relating to url2 is to download it, but I may need information from page 1 for url parameters in url 2.

You could probably use the Chain of Responsibility pattern(or at least draw inspiration from it) for this. But it would probably be overkill, which means using it would probably please a lot of consultants.
I'd just put the shit in nested if statements and if you don't like the depth, put it in a function so you don't have to look at it as much.
 
I'm probably just a dumbass, but I'm not seeing how you can use case statements to reduce conditional nesting. Isn't that just replacing one semantic form of an if statement with another semantic form?

Just to be sure I'm communicating my point, my issue is not that I have single blocks that are too long, but that I have too many blocks nested inside one another.

use subroutine and function calls..
structured programming..

break the program up..
 
I would say , case and usage of operands. For example if you aren't use any nested alternatives...

Code:
if(this)
{ 
  if(that)
   { 
     if(andthis) { } 
   }
}
Could easily become
Code:
if((this) && (that) && (andthis)) { }
something else to think about when you're looking for a specific match of multiple conditions.

The thing is, I run into situations where not all conditions are known in the beginning to be able to form compound logical operations in a single conditional statement.

For example:

I need to go to page 1 to find out how to get page 2, I need to go to page 2 to find out how to get to page 3, I need to go page 3 to find out how to get to page 4, and I shouldn't try to go to page N unless I was succesfully able to go to page N-1.

If I only had to perform the same function on every page, I could put all this into a loop. But most often, I need to do something different at every step along the way, and so I end up with massive nesting monstrosities.
 
You could probably use the Chain of Responsibility pattern(or at least draw inspiration from it) for this. But it would probably be overkill, which means using it would probably please a lot of consultants.
I'd just put the shit in nested if statements and if you don't like the depth, put it in a function so you don't have to look at it as much.

lol, thanks I'll check that out.
 
The thing is, I run into situations where not all conditions are known in the beginning to be able to form compound logical operations in a single conditional statement.

For example:

I need to go to page 1 to find out how to get page 2, I need to go to page 2 to find out how to get to page 3, I need to go page 3 to find out how to get to page 4, and I shouldn't try to go to page N unless I was succesfully able to go to page N-1.

If I only had to perform the same function on every page, I could put all this into a loop. But most often, I need to do something different at every step along the way, and so I end up with massive nesting monstrosities.



Well obviously the advice given was not "the solution" that you'd get if you paid someone to do it for you, but merely an acknowledgement of the conditional methods to help you figure it out yourself since you've provided only a 'simplified' version of what you're wishing to accomplish.
 
You could solve this by breaking that code up into functions.. something like this:

Code:
function processPages($url_list)
{
    foreach ($url_list as $url)
    {
        if (downloadFile($url) == false)
        {
            echo "Could not retrive expected data from " . $url;
            break;
        }
    }
}

function downLoadFile($url, $expectedData)
{
    $page = download_page($url);
    
    if ($page['SUCCESS'] == true)
    {
        $data = regex_functions($page['FILE']);
        if ($data == $expectedData)
        {
            return processData($data);
        }
    }
    return false;
}

function processData($data)
{
    //do something and return true or false depending on whether the process was successful or not
}

If you are pulling the URL's from the pages as you process them you could change processPages to be recursive and kick it off with an array of seed URL's in processPages
 
Personally, I like the idea about using try statements and collecting errors, but here's another way:

You could do the validations from within the functions and let the functions issue an echo statement if validation fails, then return NULL.

You would still have the same nesting, but without the else.

Code:
if(myFunction()) 
{
  if(myOtherFunction())
  {
     if(myFinalFunction()){}
  }
}


function myFunction($goodData = '')
{
  $result = FALSE;
  if($goodData) $result = TRUE;
  else echo 'goodData was not.';
  return $result;

}
If you need the functions to return data, just declare a null variable and if it validates, put the data in the variable. Then the function would return either the data or NULL.

For PHP 5+, you can also combine the alternative syntax with the traditional syntax to make it more readable (edit: probably would look better if I had used real tabs).


Code:
if ($condition) :
  do_this()
  echo 'can use multiple lines';
else : 
  if($condition2)
  {
     do_something_else();
     echo 'then write about it.';
  }
endif;

But really, all code you come across out there is nested pretty deep, like 5-10 levels. You just don't realize it because it is broken down into functions.
 
Nothing wrong with a good ol' nested IF statement in my book. Your example doesn't look that bad to worry about. If it was twice the size, then yes, I'd use functions.

If it works and you can read it, then it is good enough IMHO
 
If your question is how can you write as many nested conditions as possible in an elegant manner then the answer is simple. They way you design and develop your code needs to change


As an example with your code the following repeated twice with the only difference being the value of the variable $url

Code:
$page = download_page($url2);
        
        if ($page['SUCCESS'])
        {
            $data = regex_functions($page['FILE']);
            
            if ($data = $expectedData)
            {
                ...
            }
            else
            {
                echo "Could not retriee expected data from page 2";
            }
        }
        else
        {
            echo "Could not download url 2";   
        }
Straight away it tells us it needs to be its own function and i guess this is where you should start. looking for patterns that are identical, reduce them to a single function.
 
Status
Not open for further replies.