GeoAnalysisViewDataService   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 182
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 69
dl 0
loc 182
rs 10
c 0
b 0
f 0
wmc 22

9 Methods

Rating   Name   Duplication   Size   Complexity  
A insertGetId() 0 10 2
A update() 0 13 3
A delete() 0 5 1
A colorsDecoder() 0 15 4
A enabledFilter() 0 4 2
A all() 0 12 1
A updateStatus() 0 5 2
A viewMapper() 0 26 6
A find() 0 4 1
1
<?php
2
3
/**
4
 * webtrees-lib: MyArtJaub library for webtrees
5
 *
6
 * @package MyArtJaub\Webtrees
7
 * @subpackage GeoDispersion
8
 * @author Jonathan Jaubart <[email protected]>
9
 * @copyright Copyright (c) 2021-2022, Jonathan Jaubart
10
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public License, version 3
11
 */
12
13
declare(strict_types=1);
14
15
namespace MyArtJaub\Webtrees\Module\GeoDispersion\Services;
16
17
use Fisharebest\Webtrees\Registry;
18
use Fisharebest\Webtrees\Tree;
19
use Illuminate\Contracts\Container\BindingResolutionException;
20
use Illuminate\Database\Capsule\Manager as DB;
21
use Illuminate\Support\Collection;
22
use MyArtJaub\Webtrees\Common\GeoDispersion\Config\MapColorsConfig;
23
use MyArtJaub\Webtrees\Contracts\GeoDispersion\GeoAnalysisInterface;
24
use MyArtJaub\Webtrees\Module\GeoDispersion\Views\AbstractGeoAnalysisView;
25
use MyArtJaub\Webtrees\Module\GeoDispersion\Views\GeoAnalysisMap;
26
use Spatie\Color\Exceptions\InvalidColorValue;
27
use Closure;
28
use stdClass;
29
30
/**
31
 * Service for accessing Geographical dispersion analysis views configuration data.
32
 */
33
class GeoAnalysisViewDataService
34
{
35
    /**
36
     * Find a Geographical dispersion analysis view by ID
37
     *
38
     * @param Tree $tree
39
     * @param int $id
40
     * @return AbstractGeoAnalysisView|NULL
41
     */
42
    public function find(Tree $tree, int $id, bool $include_disabled = false): ?AbstractGeoAnalysisView
43
    {
44
        return $this->all($tree, $include_disabled)
45
            ->first(fn(AbstractGeoAnalysisView $view): bool => $view->id() === $id);
46
    }
47
48
    /**
49
     * Get all Geographical dispersion analysis views, with or without the disabled ones.
50
     *
51
     * {@internal It would ignore any view for which the class could not be loaded by the container}
52
     *
53
     * @param Tree $tree
54
     * @param bool $include_disabled
55
     * @return Collection<AbstractGeoAnalysisView>
56
     */
57
    public function all(Tree $tree, bool $include_disabled = false): Collection
58
    {
59
        return Registry::cache()->array()->remember(
60
            'all-geodispersion-views',
61
            function () use ($tree, $include_disabled): Collection {
62
                return DB::table('maj_geodisp_views')
63
                    ->select('maj_geodisp_views.*')
64
                    ->where('majgv_gedcom_id', '=', $tree->id())
65
                    ->get()
66
                    ->map($this->viewMapper($tree))
67
                    ->filter()
68
                    ->filter($this->enabledFilter($include_disabled));
69
            }
70
        );
71
    }
72
73
    /**
74
     * Insert a geographical dispersion analysis view object in the database.
75
     *
76
     * @param AbstractGeoAnalysisView $view
77
     * @return int
78
     */
79
    public function insertGetId(AbstractGeoAnalysisView $view): int
80
    {
81
        return DB::table('maj_geodisp_views')
82
            ->insertGetId([
83
                'majgv_gedcom_id' => $view->tree()->id(),
84
                'majgv_view_class' => get_class($view),
85
                'majgv_status' => $view->isEnabled() ? 'enabled' : 'disabled',
86
                'majgv_descr' => mb_substr($view->description(), 0, 248),
87
                'majgv_analysis' => get_class($view->analysis()),
88
                'majgv_place_depth' => $view->placesDepth()
89
            ]);
90
    }
91
92
    /**
93
     * Update a geographical dispersion analysis view object in the database.
94
     *
95
     * @param AbstractGeoAnalysisView $view
96
     * @return int
97
     */
98
    public function update(AbstractGeoAnalysisView $view): int
99
    {
100
        return DB::table('maj_geodisp_views')
101
            ->where('majgv_id', '=', $view->id())
102
            ->update([
103
                'majgv_gedcom_id' => $view->tree()->id(),
104
                'majgv_view_class' => get_class($view),
105
                'majgv_status' => $view->isEnabled() ? 'enabled' : 'disabled',
106
                'majgv_descr' => mb_substr($view->description(), 0, 248),
107
                'majgv_analysis' => get_class($view->analysis()),
108
                'majgv_place_depth' => $view->placesDepth(),
109
                'majgv_top_places' => $view->numberTopPlaces(),
110
                'majgv_colors' => $view instanceof GeoAnalysisMap ? json_encode($view->colors()) : null
111
            ]);
112
    }
113
114
    /**
115
     * Update the status of a geographical dispersion analysis view object in the database.
116
     *
117
     * @param AbstractGeoAnalysisView $view
118
     * @param bool $status
119
     * @return int
120
     */
121
    public function updateStatus(AbstractGeoAnalysisView $view, bool $status): int
122
    {
123
        return DB::table('maj_geodisp_views')
124
            ->where('majgv_id', '=', $view->id())
125
            ->update(['majgv_status' => $status ? 'enabled' : 'disabled']);
126
    }
127
128
    /**
129
     * Delete a geographical dispersion analysis view object from the database.
130
     *
131
     * @param AbstractGeoAnalysisView $view
132
     * @return int
133
     */
134
    public function delete(AbstractGeoAnalysisView $view): int
135
    {
136
        return DB::table('maj_geodisp_views')
137
            ->where('majgv_id', '=', $view->id())
138
            ->delete();
139
    }
140
141
    /**
142
     * Get the closure to create a AbstractGeoAnalysisView object from a row in the database.
143
     * It returns null if the classes stored in the DB cannot be loaded through the Laravel container,
144
     * or if the types do not match with the ones expected.
145
     *
146
     * @param Tree $tree
147
     * @return Closure(\stdClass $row):?AbstractGeoAnalysisView
148
     */
149
    private function viewMapper(Tree $tree): Closure
150
    {
151
        return function (stdClass $row) use ($tree): ?AbstractGeoAnalysisView {
152
            try {
153
                $geoanalysis = app($row->majgv_analysis);
154
                if (!($geoanalysis instanceof GeoAnalysisInterface)) {
155
                    return null;
156
                }
157
158
                $view = app()->makeWith($row->majgv_view_class, [
159
                    'id'                    =>  (int) $row->majgv_id,
160
                    'tree'                  =>  $tree,
161
                    'enabled'               =>  $row->majgv_status === 'enabled',
162
                    'description'           =>  $row->majgv_descr,
163
                    'geoanalysis'           =>  $geoanalysis,
164
                    'depth'                 =>  (int) $row->majgv_place_depth,
165
                    'detailed_top_places'   =>  (int) $row->majgv_top_places
166
                ]);
167
168
                if ($row->majgv_colors !== null && $view instanceof GeoAnalysisMap) {
169
                    $view = $view->withColors($this->colorsDecoder($row->majgv_colors));
170
                }
171
172
                return $view instanceof AbstractGeoAnalysisView ? $view : null;
173
            } catch (BindingResolutionException $ex) {
174
                return null;
175
            }
176
        };
177
    }
178
179
    /**
180
     * Create a MapColorsConfig object from a JSON column value.
181
     * Returns null if the JSON string is invalid, or if the colors are not valid.
182
     *
183
     * @param string $colors_config
184
     * @return MapColorsConfig|NULL
185
     */
186
    private function colorsDecoder(string $colors_config): ?MapColorsConfig
187
    {
188
        $colors = json_decode($colors_config, true);
189
        if (!is_array($colors) && count($colors) !== 4) {
190
            return null;
191
        }
192
        try {
193
            return new MapColorsConfig(
194
                \Spatie\Color\Factory::fromString($colors['default'] ?? ''),
195
                \Spatie\Color\Factory::fromString($colors['stroke'] ?? ''),
196
                \Spatie\Color\Factory::fromString($colors['maxvalue'] ?? ''),
197
                \Spatie\Color\Factory::fromString($colors['hover'] ?? '')
198
            );
199
        } catch (InvalidColorValue $ex) {
200
            return null;
201
        }
202
    }
203
204
    /**
205
     * Get a closure to filter views by enabled/disabled status
206
     *
207
     * @param bool $include_disabled
208
     *
209
     * @return Closure(AbstractGeoAnalysisView $view):bool
210
     */
211
    private function enabledFilter(bool $include_disabled): Closure
212
    {
213
        return function (AbstractGeoAnalysisView $view) use ($include_disabled): bool {
214
            return $include_disabled || $view->isEnabled();
215
        };
216
    }
217
}
218