0

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!

Barry
  • 3,303
  • 7
  • 23
  • 42
priMo-ex3m
  • 1,072
  • 2
  • 15
  • 29

3 Answers3

0

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];
}
Masa Sakano
  • 1,921
  • 20
  • 32
0

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.

thisiskelvin
  • 4,136
  • 1
  • 10
  • 17
0

Laravel collection is really helpful in this kinds of scenario you can break your codes into parts making it readable at the same time attaining a functional programing looks.

    $data = file_get_contents(storage_path('hours.txt'));
    $data = collect(json_decode($data, true));
    $batch = 5;
    $data = $this->initMap($data);
    $data = $this->finMap($data, $data->count(), $batch);
    if ($data->count() > 0){
      BusinessHour::insert($data->toArray());
    };

Here's the initial map function, what it do is it just prepare all the necesarry data so it can be processed later.

    public function initMap($data){
        return $data->flatmap(function($data, $bId) {
            return collect($data)
            ->map(function($data, $day) use ($bId) {
                return collect([
                    'business_id' => $bId,
                    'day_of_week' => $day,
                    'open_periods_mins' => $data['period'][0]['open'],
                    'close_periods_mins' => $data['period'][0]['close']
                ]);
            });
        });
    }

And here is the finalization of the data. This compute if the data needs to be save base on the $batch variable, if not remaining data will still be store at the end.

PS: Code are base on the OP initial code and I use laravel 8.

  public function finMap($data, $total, $batch){
        $this->insertData = [];
        return $data->map(function($data, $index) use ($total, $batch) {
            if (($total - $index) > ($total % $batch)){
                $this->insertData[] = collect($data);
                if (($index % $batch) == 0){
                    BusinessHour::insert($this->insertData);
                }
            }else{
                return $data;
            }
        })->filter(function($data){
            return $data;
        });
    }
Angelito Tan
  • 151
  • 1
  • 6