Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
4.60% covered (danger)
4.60%
4 / 87
16.67% covered (danger)
16.67%
1 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
LocationsController
4.60% covered (danger)
4.60%
4 / 87
16.67% covered (danger)
16.67%
1 / 6
442.26
0.00% covered (danger)
0.00%
0 / 1
 index
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getLocations
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 getData
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 getLocationsFromAPCu
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 getLocationsFromRedis
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
30
 getLocationsFromDB
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2
3namespace App\Http\Controllers;
4
5use Illuminate\Http\Request;
6use Illuminate\Http\JsonResponse;
7use Illuminate\Support\Facades\DB;
8use Illuminate\Support\Facades\Redis;
9
10class 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}