重构从foreach到Laravel Collections的代码

I have a JSON file like that

{
    "20":{
        "0":{
            "period":[
                {
                    "open": 350,
                    "close": 600
                },
                {
                    "open": 660,
                    "close": 900
                }
            ]
        },
        "1":{
            "period":[
                {
                    "open": 350,
                    "close": 600
                },
                {
                    "open": 660,
                    "close": 900
                }
            ]
        },
        "2":{
            "period":[
                {
                    "open": 350,
                    "close": 600
                },
                {
                    "open": 660,
                    "close": 900
                }
            ]
        },
        "3":{
            "period":[
                {
                    "open": 350,
                    "close": 600
                },
                {
                    "open": 660,
                    "close": 900
                }
            ]
        },
        "4":{
            "period":[
                {
                    "open": 350,
                    "close": 600
                },
                {
                    "open": 660,
                    "close": 900
                }
            ]
        },
        "5":{
            "period":[
                {
                    "open": 350,
                    "close": 600
                },
                {
                    "open": 660,
                    "close": 900
                }
            ]
        },
        "6":{
            "period":[
                {
                    "open": 350,
                    "close": 600
                },
                {
                    "open": 660,
                    "close": 900
                }
            ]
        }
    },
    "21":{
        "0":{
            "period":[
                {
                    "open": 350,
                    "close": 600
                },
                {
                    "open": 660,
                    "close": 900
                }
            ]
        },
        "1":{
            "period":[
                {
                    "open": 350,
                    "close": 600
                },
                {
                    "open": 660,
                    "close": 900
                }
            ]
        },
        "2":{
            "period":[
                {
                    "open": 350,
                    "close": 600
                },
                {
                    "open": 660,
                    "close": 900
                }
            ]
        },
        "3":{
            "period":[
                {
                    "open": 350,
                    "close": 600
                },
                {
                    "open": 660,
                    "close": 900
                }
            ]
        },
        "4":{
            "period":[
                {
                    "open": 350,
                    "close": 600
                },
                {
                    "open": 660,
                    "close": 900
                }
            ]
        },
        "5":{
            "period":[
                {
                    "open": 350,
                    "close": 600
                },
                {
                    "open": 660,
                    "close": 900
                }
            ]
        },
        "6":{
            "period":[
                {
                    "open": 350,
                    "close": 600
                },
                {
                    "open": 660,
                    "close": 900
                }
            ]
        }
    }

and I have an action that is is decoding this JSON to array, and pass with foreach-es step by step, get that data and then store it in Database.

One guy said me that it is possible to refactor all that action, so I will not have absolutely (or approximative) any foreach or if. Also he said that it is called representative/functional programming.

So, I found that concept and all that stuff, but also can't figure out how to do it. My imperial code:

$processingFile = file_get_contents(storage_path('hours.txt'));
$decodedFile = json_decode($processingFile, true);

$data = [];
$i = 0;
$batch = 10000;

foreach ($decodedFile as $business => $days) {
    foreach ($days as $dayOfWeek => $periods) {
        if (count($periods)) {
            foreach ($periods['period'] as $key => $value) { 
                $i++;  
                $tmp = [
                    'business_id' => $business,
                    'day_of_week' => $dayOfWeek,
                    'open_periods_mins' => $value['open'],
                    'close_periods_mins' => $value['close'],
                ];
                array_push($data, $tmp);
                if($i === $batch) {
                    BusinessHour::insert($data);
                    $data = [];
                    $i = 0;
                }
            }
        }
    }
}

if( count($data) ) {
    BusinessHour::insert($data); 
}

I don't know how to parse step by step and cut it all in functions using Laravel Collections or whatever... declarative paradigm.

Can someone explain / rewrite that code for teaching purpose? Thanks!

I am not sure if there are any universal ways that are applicable to your case, given the way you manipulate the information into the final array is not universal (for example, neither the string "period" nor the numerical keys in the period array is not used anywhere in the final output while the other keys are used, and the final two child elements are combined into one record, etc).

Here is a bit techy way to prepare $data in your example, where the way in this answer to "PHP convert nested array to single array while concatenating keys?" is adopted. With this, it uses only one foreach (I think one-level loop is inevitable given the fiinal conversion is non-universal). Note it assumes the input JSON has no irregular structures.

$string = http_build_query($decodedFile);
$string = urldecode($string);
$string = str_replace(
              array('[',']'),
              array('_','') , 
              $string
          );
parse_str($string, $flat_array);

$data = [];
$tmp = [];
foreach ($flat_array as $ky => $val) {
    $ary = explode('_', $ky);
    $tmp[$ary[4] . '_periods_mins'] = $val;
    if ($ary[4] == 'close') {
        array_push($data, $tmp);
        $tmp = [];
        continue;
    }
    $tmp['business_id'] = $ary[0];
    $tmp['day_of_week'] = $ary[1];
}

You are able to achieve this using the collect() helper, which will add an array to a collection instance and allow you to use its methods.

$processingFile = file_get_contents(storage_path('hours.txt'));
$data = json_decode($processingFile, true);
$insertData = [];

collect($data)
    ->each(function ($business, $businessKey) use (&$insertData) {
        collect($business)
            ->each(function ($day, $dayKey) use ($businessKey, &$insertData) {
                foreach ($day['period'] as $period) {
                    $insertData[] = [
                        'business_id'        => $businessKey,
                        'days_of_week'       => $dayKey,
                        'open_periods_mins'  => $period['open'],
                        'close_periods_mins' => $period['close'],
                    ];
                }
            });
    });

if (count($insertData)) {
    BusinessHour::insert($insertData);
}

Firstly, we get the $processingFile and decode it to a $data variable. $insertData is created as a new empty array which will be used for later.

Then then wrap the $data variable in a collect() helper. For each business we pass through the reference $insertData variable. This is needed in order to update the variable outside of the collection closures.

Within each business, we have days so we collect $business (which is actually days) and for each day, pass through the $businessKey and the reference of the $insertData variable.

After this, we use a normal foreach to update the $insertData array with new data.

At the end of the process, you then insert() all data into records on the BusinessHour model.

I hope this helps.