Total Complexity | 58 |
Total Lines | 398 |
Duplicated Lines | 0 % |
Coverage | 95.5% |
Changes | 1 | ||
Bugs | 0 | Features | 0 |
Complex classes like SpatialTrait often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use SpatialTrait, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
36 | trait SpatialTrait |
||
37 | { |
||
38 | /* |
||
39 | * The attributes that are spatial representations. |
||
40 | * To use this Trait, add the following array to the model class |
||
41 | * |
||
42 | * @var array |
||
43 | * |
||
44 | * protected $spatialFields = []; |
||
45 | */ |
||
46 | |||
47 | /** |
||
48 | * @var array |
||
49 | */ |
||
50 | public $geometries = []; |
||
51 | |||
52 | /** |
||
53 | * @var array |
||
54 | */ |
||
55 | protected $stRelations = [ |
||
56 | 'Within', |
||
57 | 'Crosses', |
||
58 | 'Contains', |
||
59 | 'Disjoint', |
||
60 | 'Equals', |
||
61 | 'Intersects', |
||
62 | 'Overlaps', |
||
63 | 'Touches', |
||
64 | ]; |
||
65 | |||
66 | /** |
||
67 | * Create a new Eloquent query builder for the model. |
||
68 | * |
||
69 | * @param \Illuminate\Database\Query\Builder $query |
||
70 | * |
||
71 | * @return \LaravelSpatial\Eloquent\Builder |
||
72 | */ |
||
73 | 48 | public function newEloquentBuilder($query) |
|
74 | { |
||
75 | 48 | return new Builder($query); |
|
76 | } |
||
77 | |||
78 | /** |
||
79 | * @inheritDoc |
||
80 | */ |
||
81 | 11 | public function setRawAttributes(array $attributes, $sync = false) |
|
82 | { |
||
83 | 11 | $spatial_fields = $this->getSpatialFields(); |
|
84 | |||
85 | 11 | foreach ($attributes as $attribute => &$value) { |
|
86 | 11 | if (is_string($value) && in_array($attribute, $spatial_fields, true) && strlen($value) >= 15) { |
|
87 | 11 | $connection = $this->getConnection(); |
|
1 ignored issue
–
show
|
|||
88 | |||
89 | // MySQL adds 4 NULL bytes at the start of the binary |
||
90 | 11 | if ($connection instanceof MySqlConnection && strpos($value, "\0\0\0\0") === 0) { |
|
91 | 5 | $value = substr($value, 4); |
|
92 | 6 | } elseif ($connection instanceof PostgresConnection) { |
|
93 | 5 | $value = pack('H*', $value); |
|
94 | } |
||
95 | |||
96 | try { |
||
97 | 11 | $value = GeoJson::jsonUnserialize(json_decode(geoPHP::load($value, 'wkb')->out('json'), false)); |
|
98 | } catch (Exception $e) { |
||
99 | 11 | throw new SpatialParseException("Can't parse WKB {$value}: {$e->getMessage()}", $e->getCode(), $e); |
|
100 | } |
||
101 | } |
||
102 | } |
||
103 | |||
104 | 11 | return parent::setRawAttributes($attributes, $sync); |
|
105 | } |
||
106 | |||
107 | /** |
||
108 | * @return array |
||
109 | */ |
||
110 | 50 | public function getSpatialFields(): array |
|
111 | { |
||
112 | 50 | if (property_exists($this, 'spatialFields') && !empty($this->spatialFields)) { |
|
113 | 47 | return $this->spatialFields; |
|
114 | } |
||
115 | |||
116 | 3 | throw new SpatialFieldsNotDefinedException(__CLASS__ . ' has to define $spatialFields'); |
|
117 | } |
||
118 | |||
119 | /** |
||
120 | * @param \GeoJSON\Geometry\Geometry $value |
||
121 | * |
||
122 | * @return string |
||
123 | */ |
||
124 | 46 | protected function toWkt(Geometry $value): string |
|
125 | { |
||
126 | try { |
||
127 | 46 | $wkt = geoPHP::load(json_decode(json_encode($value->jsonSerialize()), false), 'json')->out('wkt'); |
|
128 | } catch (Exception $e) { |
||
129 | throw new SpatialParseException('Unable to data to geometry.', 0, $e); |
||
130 | } |
||
131 | |||
132 | 46 | return ($this->getConnection() instanceof PostgresConnection ? 'SRID=4326;' : '') . $wkt; |
|
133 | } |
||
134 | |||
135 | /** |
||
136 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
137 | * |
||
138 | * @return bool |
||
139 | */ |
||
140 | 31 | protected function performInsert(EloquentBuilder $query) |
|
141 | { |
||
142 | 31 | foreach ($this->attributes as $key => $value) { |
|
143 | 31 | if ($value instanceof Geometry && $this->isColumnAllowed($key)) { |
|
144 | 29 | $this->geometries[$key] = $value; // Preserve the geometry objects prior to the insert |
|
145 | 29 | $this->attributes[$key] = $this->getConnection()->raw("ST_GeomFromText('{$this->toWkt($value)}')"); |
|
146 | } |
||
147 | } |
||
148 | |||
149 | 29 | $insert = parent::performInsert($query); |
|
150 | |||
151 | 29 | foreach ($this->geometries as $key => $value) { |
|
152 | 29 | $this->attributes[$key] = $value; // Retrieve the geometry objects so they can be used in the model |
|
153 | } |
||
154 | |||
155 | 29 | return $insert; // Return the result of the parent insert |
|
156 | } |
||
157 | |||
158 | /** |
||
159 | * @param $geometryColumn |
||
160 | * |
||
161 | * @return bool |
||
162 | */ |
||
163 | 48 | public function isColumnAllowed($geometryColumn): bool |
|
164 | { |
||
165 | 48 | if (!in_array($geometryColumn, $this->getSpatialFields(), true)) { |
|
166 | throw new SpatialFieldsNotDefinedException(sprintf('%s is not a valid spatial column.', $geometryColumn)); |
||
167 | } |
||
168 | |||
169 | 46 | return true; |
|
170 | } |
||
171 | |||
172 | /** |
||
173 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
174 | * @param $geometryColumn |
||
175 | * @param $geometry |
||
176 | * @param $distance |
||
177 | * |
||
178 | * @return \Illuminate\Database\Eloquent\Builder |
||
179 | */ |
||
180 | 4 | public function scopeDistance($query, $geometryColumn, $geometry, $distance): EloquentBuilder |
|
181 | { |
||
182 | 4 | if ($this->isColumnAllowed($geometryColumn)) { |
|
183 | 4 | $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : ''; |
|
184 | 4 | $query->whereRaw("ST_Distance({$geometryColumn}, ST_GeomFromText(?)) <= ?", [ |
|
185 | 4 | $this->toWkt($geometry), |
|
186 | 4 | $distance, |
|
187 | ]); |
||
188 | } |
||
189 | |||
190 | 4 | return $query; |
|
191 | } |
||
192 | |||
193 | /** |
||
194 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
195 | * @param $geometryColumn |
||
196 | * @param $geometry |
||
197 | * @param $distance |
||
198 | * |
||
199 | * @return \Illuminate\Database\Eloquent\Builder |
||
200 | */ |
||
201 | 3 | public function scopeDistanceExcludingSelf($query, $geometryColumn, $geometry, $distance): EloquentBuilder |
|
213 | } |
||
214 | |||
215 | /** |
||
216 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
217 | * @param $geometryColumn |
||
218 | * @param $geometry |
||
219 | * |
||
220 | * @return \Illuminate\Database\Eloquent\Builder |
||
221 | */ |
||
222 | 4 | public function scopeDistanceValue($query, $geometryColumn, $geometry): EloquentBuilder |
|
223 | { |
||
224 | 4 | if ($this->isColumnAllowed($geometryColumn)) { |
|
225 | 4 | $columns = $query->getQuery()->columns; |
|
226 | |||
227 | 4 | if (!$columns) { |
|
228 | 3 | $query->select('*'); |
|
229 | } |
||
230 | |||
231 | 4 | $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : ''; |
|
232 | 4 | $query->selectRaw("ST_Distance({$geometryColumn}, ST_GeomFromText(?)) as distance", [ |
|
233 | 4 | $this->toWkt($geometry), |
|
234 | ]); |
||
235 | } |
||
236 | |||
237 | 4 | return $query; |
|
238 | } |
||
239 | |||
240 | /** |
||
241 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
242 | * @param $geometryColumn |
||
243 | * @param $geometry |
||
244 | * @param $distance |
||
245 | * |
||
246 | * @return \Illuminate\Database\Eloquent\Builder |
||
247 | */ |
||
248 | 4 | public function scopeDistanceSphere($query, $geometryColumn, $geometry, $distance): EloquentBuilder |
|
249 | { |
||
250 | 4 | $distFunc = $this->getConnection() instanceof PostgresConnection ? 'ST_DistanceSphere' : 'ST_Distance_Sphere'; |
|
251 | |||
252 | 4 | if ($this->isColumnAllowed($geometryColumn)) { |
|
253 | 4 | $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : ''; |
|
254 | 4 | $query->whereRaw("{$distFunc}({$geometryColumn}, ST_GeomFromText(?)) <= ?", [ |
|
255 | 4 | $this->toWkt($geometry), |
|
256 | 4 | $distance, |
|
257 | ]); |
||
258 | } |
||
259 | |||
260 | 4 | return $query; |
|
261 | } |
||
262 | |||
263 | /** |
||
264 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
265 | * @param $geometryColumn |
||
266 | * @param $geometry |
||
267 | * @param $distance |
||
268 | * |
||
269 | * @return \Illuminate\Database\Eloquent\Builder |
||
270 | */ |
||
271 | 3 | public function scopeDistanceSphereExcludingSelf($query, $geometryColumn, $geometry, $distance): EloquentBuilder |
|
285 | } |
||
286 | |||
287 | /** |
||
288 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
289 | * @param $geometryColumn |
||
290 | * @param $geometry |
||
291 | * |
||
292 | * @return \Illuminate\Database\Eloquent\Builder |
||
293 | */ |
||
294 | 4 | public function scopeDistanceSphereValue($query, $geometryColumn, $geometry): EloquentBuilder |
|
295 | { |
||
296 | 4 | $distFunc = $this->getConnection() instanceof PostgresConnection ? 'ST_DistanceSphere' : 'ST_Distance_Sphere'; |
|
297 | |||
298 | 4 | if ($this->isColumnAllowed($geometryColumn)) { |
|
299 | 4 | $columns = $query->getQuery()->columns; |
|
300 | |||
301 | 4 | if (!$columns) { |
|
302 | 3 | $query->select('*'); |
|
303 | } |
||
304 | |||
305 | 4 | $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : ''; |
|
306 | 4 | $query->selectRaw("{$distFunc}({$geometryColumn}, ST_GeomFromText(?)) as distance", [ |
|
307 | 4 | $this->toWkt($geometry), |
|
308 | ]); |
||
309 | } |
||
310 | |||
311 | 4 | return $query; |
|
312 | } |
||
313 | |||
314 | /** |
||
315 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
316 | * @param $geometryColumn |
||
317 | * @param $geometry |
||
318 | * @param $relationship |
||
319 | * |
||
320 | * @return \Illuminate\Database\Eloquent\Builder |
||
321 | */ |
||
322 | 9 | public function scopeComparison($query, $geometryColumn, $geometry, $relationship): EloquentBuilder |
|
323 | { |
||
324 | 9 | if ($this->isColumnAllowed($geometryColumn)) { |
|
325 | 9 | $relationship = ucfirst(strtolower($relationship)); |
|
326 | |||
327 | 9 | if (!in_array($relationship, $this->stRelations, true)) { |
|
328 | throw new UnknownSpatialRelationFunction($relationship); |
||
329 | } |
||
330 | |||
331 | 9 | $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : ''; |
|
332 | 9 | $query->whereRaw("ST_{$relationship}(`{$geometryColumn}`, ST_GeomFromText(?))", [ |
|
333 | 9 | $this->toWkt($geometry), |
|
334 | ]); |
||
335 | } |
||
336 | |||
337 | 9 | return $query; |
|
338 | } |
||
339 | |||
340 | /** |
||
341 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
342 | * @param $geometryColumn |
||
343 | * @param $polygon |
||
344 | * |
||
345 | * @return \Illuminate\Database\Eloquent\Builder |
||
346 | */ |
||
347 | 1 | public function scopeWithin($query, $geometryColumn, $polygon): EloquentBuilder |
|
350 | } |
||
351 | |||
352 | /** |
||
353 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
354 | * @param $geometryColumn |
||
355 | * @param $geometry |
||
356 | * |
||
357 | * @return \Illuminate\Database\Eloquent\Builder |
||
358 | */ |
||
359 | 1 | public function scopeCrosses($query, $geometryColumn, $geometry): EloquentBuilder |
|
360 | { |
||
361 | 1 | return $this->scopeComparison($query, $geometryColumn, $geometry, 'crosses'); |
|
362 | } |
||
363 | |||
364 | /** |
||
365 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
366 | * @param $geometryColumn |
||
367 | * @param $geometry |
||
368 | * |
||
369 | * @return \Illuminate\Database\Eloquent\Builder |
||
370 | */ |
||
371 | 1 | public function scopeContains($query, $geometryColumn, $geometry): EloquentBuilder |
|
372 | { |
||
373 | 1 | return $this->scopeComparison($query, $geometryColumn, $geometry, 'contains'); |
|
374 | } |
||
375 | |||
376 | /** |
||
377 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
378 | * @param $geometryColumn |
||
379 | * @param $geometry |
||
380 | * |
||
381 | * @return \Illuminate\Database\Eloquent\Builder |
||
382 | */ |
||
383 | 1 | public function scopeDisjoint($query, $geometryColumn, $geometry): EloquentBuilder |
|
384 | { |
||
385 | 1 | return $this->scopeComparison($query, $geometryColumn, $geometry, 'disjoint'); |
|
386 | } |
||
387 | |||
388 | /** |
||
389 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
390 | * @param $geometryColumn |
||
391 | * @param $geometry |
||
392 | * |
||
393 | * @return \Illuminate\Database\Eloquent\Builder |
||
394 | */ |
||
395 | 1 | public function scopeEquals($query, $geometryColumn, $geometry): EloquentBuilder |
|
396 | { |
||
397 | 1 | return $this->scopeComparison($query, $geometryColumn, $geometry, 'equals'); |
|
398 | } |
||
399 | |||
400 | /** |
||
401 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
402 | * @param $geometryColumn |
||
403 | * @param $geometry |
||
404 | * |
||
405 | * @return \Illuminate\Database\Eloquent\Builder |
||
406 | */ |
||
407 | 1 | public function scopeIntersects($query, $geometryColumn, $geometry): EloquentBuilder |
|
410 | } |
||
411 | |||
412 | /** |
||
413 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
414 | * @param $geometryColumn |
||
415 | * @param $geometry |
||
416 | * |
||
417 | * @return \Illuminate\Database\Eloquent\Builder |
||
418 | */ |
||
419 | 1 | public function scopeOverlaps($query, $geometryColumn, $geometry): EloquentBuilder |
|
422 | } |
||
423 | |||
424 | /** |
||
425 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
426 | * @param $geometryColumn |
||
427 | * @param $geometry |
||
428 | * |
||
429 | * @return \Illuminate\Database\Eloquent\Builder |
||
430 | */ |
||
431 | 1 | public function scopeDoesTouch($query, $geometryColumn, $geometry): EloquentBuilder |
|
434 | } |
||
435 | } |
||
436 |