MapDataExportCSV::handle()   F
last analyzed

Complexity

Conditions 12
Paths 432

Size

Total Lines 110
Code Lines 62

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 62
nc 432
nop 1
dl 0
loc 110
rs 3.7002
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2022 webtrees development team
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\Http\RequestHandlers;
21
22
use Fisharebest\Webtrees\PlaceLocation;
23
use Fisharebest\Webtrees\Services\MapDataService;
24
use Illuminate\Database\Capsule\Manager as DB;
25
use Psr\Http\Message\ResponseInterface;
26
use Psr\Http\Message\ServerRequestInterface;
27
use Psr\Http\Server\RequestHandlerInterface;
28
use RuntimeException;
29
use stdClass;
30
31
use function addcslashes;
32
use function array_map;
33
use function array_merge;
34
use function array_pad;
35
use function array_reverse;
36
use function array_shift;
37
use function array_unshift;
38
use function count;
39
use function fopen;
40
use function fputcsv;
41
use function max;
42
use function preg_replace;
43
use function response;
44
use function rewind;
45
use function stream_get_contents;
46
47
/**
48
 * Export geographic data.
49
 */
50
class MapDataExportCSV implements RequestHandlerInterface
51
{
52
    /** @var MapDataService */
53
    private $map_data_service;
54
55
    /**
56
     * Dependency injection.
57
     *
58
     * @param MapDataService $map_data_service
59
     */
60
    public function __construct(MapDataService $map_data_service)
61
    {
62
        $this->map_data_service = $map_data_service;
63
    }
64
65
    /**
66
     * @param ServerRequestInterface $request
67
     *
68
     * @return ResponseInterface
69
     */
70
    public function handle(ServerRequestInterface $request): ResponseInterface
71
    {
72
        $parent_id = $request->getAttribute('parent_id');
73
74
        if ($parent_id === null) {
75
            $parent = new PlaceLocation('');
76
        } else {
77
            $parent = $this->map_data_service->findById((int) $parent_id);
78
        }
79
80
        for ($tmp = $parent, $hierarchy = []; $tmp->id() !== null; $tmp = $tmp->parent()) {
81
            $hierarchy[] = $tmp->locationName();
82
        }
83
84
        // Create the file name
85
        $filename = preg_replace('/[^\p{L}]+/u', '-', $hierarchy[0] ?? 'Global') . '.csv';
86
87
        // Recursively search for child places
88
        $places = [];
89
        $queue  = [[
90
            $parent->id(), array_reverse($hierarchy), $parent->latitude(), $parent->longitude()
91
        ]];
92
93
        while ($queue !== []) {
94
            [$id, $hierarchy, $latitude, $longitude] = array_shift($queue);
95
96
            if ($latitude !== null && !$longitude !== null) {
97
                $places[] = (object) [
98
                    'hierarchy' => $hierarchy,
99
                    'latitude'  => $latitude,
100
                    'longitude' => $longitude,
101
                ];
102
            }
103
104
            $query = DB::table('place_location');
105
            // Data for the next level.
106
107
            if ($id === null) {
108
                $query->whereNull('parent_id');
109
            } else {
110
                $query->where('parent_id', '=', $id);
111
            }
112
113
            $rows = $query
114
                ->orderBy('place', 'DESC')
115
                ->select(['id', 'place', 'latitude', 'longitude'])
116
                ->get();
117
118
            $next_level = count($hierarchy);
119
120
            foreach ($rows as $row) {
121
                $hierarchy[$next_level] = $row->place;
122
                array_unshift($queue, [$row->id, $hierarchy, $row->latitude, $row->longitude]);
123
            }
124
        }
125
126
        // Pad all locations to the length of the longest.
127
        $max_level = 0;
128
        foreach ($places as $place) {
129
            $max_level = max($max_level, count($place->hierarchy));
130
        }
131
132
        $places = array_map(function (stdClass $place) use ($max_level): array {
133
            return array_merge(
134
                [
135
                    count($place->hierarchy) - 1,
136
                ],
137
                array_pad($place->hierarchy, $max_level, ''),
138
                [
139
                    $this->map_data_service->writeLongitude((float) $place->longitude),
140
                    $this->map_data_service->writeLatitude((float) $place->latitude),
141
                    '',
142
                    '',
143
                ]
144
            );
145
        }, $places);
146
147
        // Create the header line for the output file (always English)
148
        $header = [
149
            'Level',
150
        ];
151
152
        for ($i = 0; $i < $max_level; $i++) {
153
            $header[] = 'Place' . $i;
154
        }
155
156
        $header[] = 'Longitude';
157
        $header[] = 'Latitude';
158
        $header[] = 'Zoom';
159
        $header[] = 'Icon';
160
161
        $resource = fopen('php://temp', 'wb+');
162
163
        if ($resource === false) {
164
            throw new RuntimeException('Failed to create temporary stream');
165
        }
166
167
        fputcsv($resource, $header, MapDataService::CSV_SEPARATOR);
168
169
        foreach ($places as $place) {
170
            fputcsv($resource, $place, MapDataService::CSV_SEPARATOR);
171
        }
172
173
        rewind($resource);
174
175
        $filename = addcslashes($filename, '"');
176
177
        return response(stream_get_contents($resource))
178
            ->withHeader('Content-Type', 'text/csv; charset=UTF-8')
179
            ->withHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
180
    }
181
}
182