Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
4.60% |
4 / 87 |
|
16.67% |
1 / 6 |
CRAP | |
0.00% |
0 / 1 |
LocationsController | |
4.60% |
4 / 87 |
|
16.67% |
1 / 6 |
442.26 | |
0.00% |
0 / 1 |
index | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getLocations | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
12 | |||
getData | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
20 | |||
getLocationsFromAPCu | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
20 | |||
getLocationsFromRedis | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
30 | |||
getLocationsFromDB | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
30 |
1 | <?php |
2 | |
3 | namespace App\Http\Controllers; |
4 | |
5 | use Illuminate\Http\Request; |
6 | use Illuminate\Http\JsonResponse; |
7 | use Illuminate\Support\Facades\DB; |
8 | use Illuminate\Support\Facades\Redis; |
9 | |
10 | class LocationsController extends Controller |
11 | { |
12 | public const DISTRICT = "district"; |
13 | public const MUNICIPALITY = "municipality"; |
14 | public const PARISH = "parish"; |
15 | public const LOCATIONS = "locations"; |
16 | public const SOURCE = "source"; |
17 | public const LOCATION_PREFIX = "location_pt"; |
18 | |
19 | /** |
20 | * @param Request $request |
21 | * @return JsonResponse |
22 | */ |
23 | public function index(Request $request): JsonResponse |
24 | { |
25 | $user = $request->user(); |
26 | return response()->json([ |
27 | 'result' => compact('user') |
28 | ]); |
29 | } |
30 | |
31 | /** |
32 | * @param Request $request |
33 | * @return JsonResponse |
34 | */ |
35 | public function getLocations(Request $request): JsonResponse |
36 | { |
37 | $user = ['user' => ['role' => null]]; |
38 | $locations = ['locations' => null]; |
39 | |
40 | if ($request->has('level')) |
41 | { |
42 | $level = $request->get('level'); |
43 | $code = $request->has('options') |
44 | ? $request->get('options') |
45 | : null; |
46 | |
47 | $locations = $this->getData($level, $code); |
48 | } |
49 | |
50 | return response()->json([ |
51 | 'result' => array_merge($locations, $user) |
52 | ]); |
53 | } |
54 | |
55 | /** |
56 | * @param string $level |
57 | * @param string|null $code |
58 | * @return array |
59 | */ |
60 | private function getData(string $level, string $code = null) : array |
61 | { |
62 | // Level validation |
63 | if (!in_array($level, [self::DISTRICT, self::MUNICIPALITY, self::PARISH])) |
64 | { |
65 | throw new \InvalidArgumentException( |
66 | "Invalid level. Valid levels: district, municipality, parish" |
67 | ); |
68 | } |
69 | |
70 | // Try to get data from APCu. If found, return |
71 | $result = $this->getLocationsFromAPCu($level, $code); |
72 | if (!empty($result[self::LOCATIONS])) |
73 | { |
74 | return $result; |
75 | } |
76 | |
77 | // Try to get data from Redis. If found warm up APCu and return |
78 | $result = $this->getLocationsFromRedis($level, $code); |
79 | if (!empty($result[self::LOCATIONS])) |
80 | { |
81 | return $result; |
82 | } |
83 | |
84 | // Try to get data from DataBase. If found warm up APCu and Redis and return |
85 | return $this->getLocationsFromDB($level, $code); |
86 | } |
87 | |
88 | /** |
89 | * @param $level |
90 | * @param $code |
91 | * @return array|null |
92 | */ |
93 | private function getLocationsFromAPCu($level, $code) : array | null |
94 | { |
95 | // Start register time |
96 | $startTime = microtime(true); |
97 | |
98 | // Initialize the result array |
99 | $result[self::LOCATIONS] = []; |
100 | |
101 | // Define the cache key pattern |
102 | $keyPattern = "/^" . self::LOCATION_PREFIX . "_{$level}_{$code}/"; |
103 | |
104 | try |
105 | { |
106 | // Iterate over APCu cache keys matching the pattern |
107 | foreach (new \APCUIterator($keyPattern) as $counter) { |
108 | $result[self::LOCATIONS][] = $counter['value']; |
109 | } |
110 | |
111 | // Sort locations and show process time |
112 | if ($result[self::LOCATIONS]) |
113 | { |
114 | // Sort the locations array by name |
115 | sort($result[self::LOCATIONS]); |
116 | |
117 | // End register time |
118 | $endTime = microtime(true); |
119 | |
120 | // Total time of process |
121 | $processingTime = number_format($endTime - $startTime, 6); |
122 | $result[self::SOURCE] = "APCu ( $processingTime Sec )"; |
123 | } |
124 | |
125 | } catch (\Exception $e) { |
126 | |
127 | // Handle exceptions |
128 | echo "Error: " . $e->getMessage(); |
129 | die; |
130 | } |
131 | |
132 | return $result; |
133 | } |
134 | |
135 | /** |
136 | * @param $level |
137 | * @param $code |
138 | * @return array|null |
139 | */ |
140 | private function getLocationsFromRedis($level, $code) : array | null |
141 | { |
142 | // Start register time |
143 | $startTime = microtime(true); |
144 | |
145 | // Initialize the result array |
146 | $result[self::LOCATIONS] = []; |
147 | |
148 | // Go to Redis DB 2 |
149 | Redis::select(2); |
150 | $redis = Redis::connection(); |
151 | |
152 | try |
153 | { |
154 | // Check if Redis connection is successful |
155 | if ($redis->ping()) |
156 | { |
157 | // Build the pattern for keys |
158 | $pattern = self::LOCATION_PREFIX . "_{$level}_{$code}*"; |
159 | |
160 | // Retrieve keys matching the pattern |
161 | $keys = $redis->keys($pattern); |
162 | |
163 | // Iterate over keys and fetch values |
164 | foreach ($keys as $key) |
165 | { |
166 | $value = Redis::get($key); |
167 | $result[self::LOCATIONS][] = $value; |
168 | |
169 | // Save in APCu |
170 | apcu_store($key, $value); |
171 | } |
172 | |
173 | // Sort locations |
174 | if ($result[self::LOCATIONS]) |
175 | { |
176 | // Sort the locations array by name |
177 | sort($result[self::LOCATIONS]); |
178 | |
179 | // End register time |
180 | $endTime = microtime(true); |
181 | |
182 | // Total time of process |
183 | $processingTime = number_format($endTime - $startTime, 6); |
184 | $result[self::SOURCE] = "Redis ( $processingTime Sec )"; |
185 | } |
186 | |
187 | } else { |
188 | |
189 | // If Redis connection fails, throw an exception |
190 | throw new \Exception( |
191 | "Failed to establish connection with Redis." |
192 | ); |
193 | } |
194 | } catch (\Exception $e) { |
195 | |
196 | // Handle exceptions |
197 | echo "Error: " . $e->getMessage(); |
198 | die; |
199 | } |
200 | |
201 | return $result; |
202 | } |
203 | |
204 | /** |
205 | * @param $level |
206 | * @param $code |
207 | * @return array|null |
208 | */ |
209 | private function getLocationsFromDB($level, $code): array | null |
210 | { |
211 | // Start register time |
212 | $startTime = microtime(true); |
213 | |
214 | // Initialize the result array |
215 | $result[self::LOCATIONS] = []; |
216 | |
217 | // Start query the database for location data |
218 | $dbResult = DB::table("locations_pt") |
219 | ->select("{$level}_name", "{$level}_code") |
220 | ->distinct(); |
221 | |
222 | // Case level != "district", apply a filter based on the previews level |
223 | // Ex: if level == municipality, the filter is district |
224 | // Ex: if level == parish, the filter is municipality |
225 | if ($level != self::DISTRICT) |
226 | { |
227 | // Determine the filter level based on the current level |
228 | $filterLevel = ($level == self::MUNICIPALITY) |
229 | ? self::DISTRICT |
230 | : (($level == self::PARISH) |
231 | ? self::MUNICIPALITY |
232 | : ""); |
233 | |
234 | // Add the filter to the query |
235 | $dbResult = $dbResult->where("{$filterLevel}_code", '=', $code); |
236 | } |
237 | |
238 | // Order the results by the name field |
239 | $dbResult->orderBy("{$level}_name"); |
240 | |
241 | // Execute the query and convert the results to an array |
242 | $dbResult = $dbResult->get()->toArray(); |
243 | |
244 | // Save retrieved data to caches |
245 | foreach ($dbResult as $value) |
246 | { |
247 | // Encode the value to JSON |
248 | $propertyValue = $result[self::LOCATIONS][] = json_encode($value); |
249 | $propertyKey = "{$level}_code"; |
250 | |
251 | // Save in Redis |
252 | Redis::set(self::LOCATION_PREFIX . "_{$level}_{$value->$propertyKey}", $propertyValue); |
253 | |
254 | // Save in APCu |
255 | apcu_store(self::LOCATION_PREFIX . "_{$level}_{$value->$propertyKey}", $propertyValue); |
256 | } |
257 | |
258 | // Sort the locations array by name |
259 | sort($result[self::LOCATIONS]); |
260 | |
261 | // End register time |
262 | $endTime = microtime(true); |
263 | |
264 | // Total time of process |
265 | $processingTime = number_format($endTime - $startTime, 6);; |
266 | |
267 | // Set the data source |
268 | $result[self::SOURCE] = "Database ( $processingTime Sec )"; |
269 | |
270 | // Return the result |
271 | return $result; |
272 | } |
273 | } |