1

I have a nested loop to iterate over a data structure comprised of different object types. I would like to use this loop structure for multiple purposes.

I found a discussion about how to reuse nested loops in C++. Is there a way in PHP to reuse this loop structure without copying the loop into a new function for each different case?

Here is my code:

foreach ($this->orders as $order) { // OrderObject
foreach ($order->items as $itemGroup) { // ItemPrice, ItemFees
    foreach ($itemGroup as $item) { // PrincipalItem, PriceItem, FeeItem
        switch (get_class($item)) {
            case 'PrincipalItem':
                $revenue += $item->getAmountAfterTax() * $item->quantity;
                break;
            case 'PriceItem':
                $revenue += $item->getAmountAfterTax();
                break;
            case 'FeeItem':
                $fees += $item->amount;
                break;
        }
    }
}
}

In the first instance above, I am summing various values. In another instance, I would like to output data as a CSV. The same loop will accomplish this, I just need to change the lines inside the switch cases.

Community
  • 1
  • 1
Chrysippus
  • 119
  • 1
  • 11
  • anonymous functions maybe? just pass an instance of `$this` into it when you want to use it – Jaquarh Nov 20 '16 at 21:33
  • Isn't that what `functions` are for? – Nytrix Nov 20 '16 at 21:36
  • @nytrix Can you explain further? – Chrysippus Nov 20 '16 at 21:50
  • @iakkam refer to my answer below. – Nytrix Nov 20 '16 at 21:56
  • I want to be able to use this loop structure but change which statements get executed inside. For example, `$revenue += $item->getAmountAfterTax() * $item->quantity;` could become `$this->convert2CSV($item);` I want to reuse the loop in performing various functions on the data. – Chrysippus Nov 20 '16 at 22:06
  • What's the benefit of "reusing" three nested loops? During the time you spent writing this question, reading the comments and answers you can write the three nested loops hundreds of times. If you want to improve something then think how easy is for another developer to understand the code you write. Is it easier or harder to understand the purpose of the nested loops if you create a function out of them? I think it is harder. The flow of the three nested loops already is difficult to understand. – axiac Nov 21 '16 at 19:58
  • @axiac I'm an amateur programmer so I wanted to know whether there are advanced techniques I can use in this situation. For example, I know there are such structures as Iterators and perhaps an Iterator could be useful in this case. Secondly, if you think three nested loops is difficult to understand, please suggest a better way. Thank you. – Chrysippus Nov 21 '16 at 23:27

2 Answers2

1

The switch (get_class($item)) part of your code is an incorrect implementation of the OOP concept called polymorphism.

The correct way to do it is to define an interface that declares a method (let's name it updateAmounts()) then implement the interface in all classes you can have as $item in the inner loop. Each implementation of the updateAmounts() method contains the code from corresponding case of the switch statement.

Something like this:

// This class is a Value Object.
// It doesn't need behaviour and it's OK to have public properties
class Amounts
{
    public $revenue = 0;
    public $fees    = 0;
}

interface HasAmounts
{
    /**
     * Update the revenue and the fees in the passed Amounts object
     * using the data stored in this object.
     */
    public function updateAmounts(Amounts $a);
}

class PrincipalItem implements HasAmounts
{
    public function updateAmounts(Amounts $a)
    {
        $a->revenue += $this->getAmountAfterTax() * $this->quantity;
    }
}

class PriceItem implements HasAmounts
{
    public function updateAmounts(Amounts $a)
    {
        $a->revenue += $this->getAmountAfterTax();
    }
}

class FeeItem implements HasAmounts
{
    public function updateAmounts(Amounts $a)
    {
        $a->fees += $this->amount;
    }
}

Now the nested loops look like this:

$amounts = new Amounts();
foreach ($this->orders as $order) { // OrderObject
    foreach ($order->items as $itemGroup) { // ItemPrice, ItemFees
        foreach ($itemGroup as $item) { // PrincipalItem, PriceItem, FeeItem
            $item->updateAmounts($amounts);
        }
    }
}
echo('Revenue: '.$amounts->revenue."\n");
echo('Fees: '.$amounts->fees."\n");

If the classes PrincipalItem, PriceItem and FeeItem already extend the same base class (or have a common ancestor) then the method can be added (as abstract or with an empty implementation) to this class (and the interface is not needed any more).


After the above transformation of the code, do you still want to find a way to reuse the nested foreach block?

The foreach statements don't look worth reusing now. This happens because the code that is worth reusing was moved where it belongs; the code should stay together with the data it handles, in the class.

Further refactoring can be operated on the code by moving each foreach in the class that contains the data it operates on.

For example:

class OrderObject implements HasAmounts
{
    public function updateAmounts(Amounts $a)
    {
        foreach ($this->items as $itemGroup) { // ItemPrice, ItemFees
            foreach ($itemGroup as $item) { // PrincipalItem, PriceItem, FeeItem
                $item->updateAmounts($amounts);
            }
        }
    }
}

If $itemGroup is an object of some class (or more) then the inner foreach can be moved to each of these classes (in its implementation of the updateAmounts() method) and this way the code stays in the same class with the data it processes (it's called encapsulation and it is another important property of the OOP code).

Now the calling code now looks like this:

$amounts = new Amounts();
foreach ($this->orders as $order) { // OrderObject
    $order->updateAmounts($amounts);
}
echo('Revenue: '.$amounts->revenue."\n");
echo('Fees: '.$amounts->fees."\n");

Look, ma! No more nested foreach loops

I'm afraid I tore apart your pack of nested foreach loops and there is nothing to reuse from it.

But wait! Your classes now contain behaviour (they probably used to be just amorphous containers of data) and this is better than writing all-purpose code (as it seems it was your goal of reusing the foreach loops). Because this is what OOP is supposed to be: data and code packed together.

axiac
  • 68,258
  • 9
  • 99
  • 134
  • Thank you so much for this answer. I learned a lot. I'm sorry for the delay in replying ... I was actually thinking about it and didn't have time until now to fix my code. – Chrysippus Jan 15 '17 at 22:17
0

In case that you want to use a certain piece of code multiple time, you make functions. Those can then be executed. Here is the manual on functions.

Example:

<?php
function example($itemGroup,$orders,$items){

    foreach ($orders as $order) { // OrderObject
        foreach ($items as $itemGroup) { // ItemPrice, ItemFees
            foreach ($itemGroup as $item) { // PrincipalItem, PriceItem, FeeItem
                switch (get_class($item)) {
                    case 'PrincipalItem':
                        $revenue += $item->getAmountAfterTax() * $item->quantity;
                        break;
                    case 'PriceItem':
                        $revenue += $item->getAmountAfterTax();
                        break;
                    case 'FeeItem':
                        $fees += $item->amount;
                        break;
                }
            }
        }
    }
}

example($value,$oders,$items);
?>

You can now call this example anywhere. In case you are writing in OOP please refer to the manual.

As mentioned before, you can also store functions in a variable, those are called Anonymous functions. Here is the reference to the manual

Example

<?php   
$var = function($variable){
    echo "this is a function with a ".$variable;
}

$var("variable");
?>

You can call those functions like showed in the code.

All this leads to your actuall answer, you can fuse those together, like so.

Example

<?php

function test( callable $function){
    $function();
}

test(function(){
    echo "some code";
})

?>

In your case:

<?php
function example($itemGroup,$orders,$items,$function()){

    foreach ($orders as $order) { // OrderObject
        foreach ($items as $itemGroup) { // ItemPrice, ItemFees
            foreach ($itemGroup as $item) { // PrincipalItem, PriceItem, FeeItem
                $function();
            }
        }
    }

}

example($itemGroup,$orders,$items,function(){

    switch (get_class($item)) {
        case 'PrincipalItem':
            $revenue += $item->getAmountAfterTax() * $item->quantity;
            break;
        case 'PriceItem':
            $revenue += $item->getAmountAfterTax();
            break;
        case 'FeeItem':
            $fees += $item->amount;
            break;
    }

}
?>
Nytrix
  • 1,139
  • 11
  • 23
  • Thanks for your reply. I understand functions, but I'm not sure this answer helps. Your function contains my nested loop but retains the lines inside the switch/case statements that I'd like to change depending on my purpose. The lines I'd like to change are ones like `$revenue += $item->getAmountAfterTax() * $item->quantity;` – Chrysippus Nov 20 '16 at 22:01
  • @iakkam your question was how to repeat a certain piece of code. I gave you the answer. What is it you want to change then? – Nytrix Nov 20 '16 at 22:02
  • I want to reuse the loop but execute different statements inside the switch/case ... for example, `$revenue += $item->getAmountAfterTax() * $item->quantity;` could become something totally different like `$this->convert2CSV($item);` – Chrysippus Nov 20 '16 at 22:03
  • @iakkam has this solved your problem or not? I have tested this method and it works fine for me. – Nytrix Nov 22 '16 at 00:47