从foreach到Laravel集合重构代码

时间:2018-10-17 15:30:25

标签: php laravel collections

我有一个这样的JSON文件

{
    "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
                }
            ]
        }
    }

并且我有一个action正在将这个JSON解码为数组,并逐步传递foreach-es,获取该数据,然后将其存储在数据库中。 / p>

一个人说我可以重构所有动作,所以我不会绝对(或近似)任何foreach或if。他还说这称为representative/functional编程。

因此,我发现了这个概念以及所有这些东西,但也弄不清楚该怎么做。 我的imperial代码:

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

我不知道如何逐步parse并使用Laravel Collections或其他任何方法declarative paradigm将其全部切掉。

有人可以出于教学目的解释/重写该代码吗? 谢谢!

3 个答案:

答案 0 :(得分:0)

我不确定是否有适用于您的情况的通用方法,因为您将信息处理到最终数组中的方法不是通用的(例如,字符串“ period”或数字键都不在最终输出中的任何地方都没有使用 period 数组,而使用了其他键,并且最后两个子元素组合成一个记录,等等。

在您的示例中,这里有一些技巧性的方法来准备$data,其中this answer中的方式为“ PHP在串联键的同时将嵌套数组转换为单个数组?”被采用。 这样,它仅使用一个foreach(考虑到最终转换是非通用的,我认为不可避免要进行一级循环)。请注意,它假定输入JSON没有不规则结构。

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

答案 1 :(得分:0)

您可以使用collect()帮助器来实现此目的,该帮助器会将数组添加到集合实例并允许您使用其方法。

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

首先,我们获得$processingFile并将其解码为$data变量。 $insertData被创建为一个新的空数组,将在以后使用。

然后将$data变量包装在collect()帮助器中。对于每个business,我们都会通过参考变量$insertData。这是为了在集合闭包之外更新变量。

在每个business中,我们有days,因此我们收集了$business(实际上是days),并且对于每个day,都经过{{ 1}}和$businessKey变量的引用。

此后,我们使用普通的$insertData用新数据更新foreach数组。

在该过程结束时,然后将所有数据$insertData insert()放入记录中。

我希望这会有所帮助。

答案 2 :(得分:0)

Laravel 集合在这种情况下非常有用,您可以将代码分解成部分使其可读,同时获得功能性编程外观。

    $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());
    };

这是初始的地图函数,它只是准备所有必要的数据,以便以后处理。

    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']
                ]);
            });
        });
    }

这里是数据的最终确定。此计算是否需要根据 $batch 变量保存数据,如果不需要,剩余的数据仍将保存在最后。

PS:代码基于 OP 初始代码,我使用 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;
        });
    }