Issues (20)

src/Services/ControllerService.php (4 issues)

1
<?php
2
3
namespace Gvera\Services;
4
5
use Gvera\Cache\Cache;
6
use Gvera\Controllers\GvController;
7
use Gvera\Exceptions\InvalidControllerException;
8
use Gvera\Exceptions\NotFoundException;
9
use Gvera\Helpers\annotations\AnnotationUtil;
10
use Gvera\Helpers\dependencyInjection\DIContainer;
11
use JetBrains\PhpStorm\Pure;
12
use PHPUnit\Runner\Exception;
13
14
/**
15
 * @class
16
 * This class will be in charge of generating the controller's lifecycle
17
 */
18
class ControllerService
19
{
20
    const CONTROLLERS_PREFIX = 'Gvera\\Controllers\\';
21
    const CONTROLLERS_KEY = 'gv_controllers';
22
23
    private string $method = 'index';
24
    private string $controllerFinalName;
25
    
26
    private ?string $uriData;
27
    private array $controllerAutoloadingNames;
28
29
    private DIContainer $diContainer;
30
31
    /**
32
     * @param $diContainer
33
     * @param $uriData
34
     * @throws \Exception
35
     * @return void
36
     */
37
    public function startControllerLifecycle($diContainer, $uriData)
38
    {
39
        $this->diContainer = $diContainer;
40
41
        if (!$uriData) {
42
            $this->redirectToDefault($diContainer);
43
            return;
44
        }
45
46
        $this->uriData = $uriData;
47
        $this->generateRegularControllerLifecycle();
48
    }
49
50
    /**
51
     * @param $controllerName
52
     * @param $methodName
53
     * @throws \Exception
54
     * @return void
55
     */
56
    public function generateSpecificControllerLifeCycle($controllerName, $methodName): void
57
    {
58
        $this->generateControllerLifecycle($controllerName, $methodName);
59
    }
60
61
    /**
62
     * @param $diContainer
63
     * @throws \Exception
64
     * @return void
65
     */
66
    public function redirectToDefault($diContainer): void
67
    {
68
        $this->diContainer = $diContainer;
69
        $response = $diContainer->get('httpResponse');
70
        $response->redirect('/' . GvController::DEFAULT_CONTROLLER);
71
    }
72
73
    /**
74
     * @throws \Exception
75
     * @return void
76
     */
77
    private function generateRegularControllerLifecycle(): void
78
    {
79
        $uriPath = $this->uriData;
80
81
        if (!isset($uriPath)) {
82
            throw new Exception('Url is malformed');
83
        }
84
85
        $uriArray = explode('/', $uriPath);
86
        $apiVersions = $this->getApiVersions();
87
88
        $this->generateControllerLifeCycleBasedOnGivenData($uriArray, $apiVersions);
89
    }
90
91
    /**
92
     * @return array
93
     * if there are versions (sub-controllers in the controllers directory, get them and return the auto-loading names)
94
     */
95
    private function getApiVersions():array
96
    {
97
        $versions = [];
98
        foreach ($this->controllerAutoloadingNames as $key => $controller) {
99
            if (is_array($controller)) {
100
                $versions[$key] = $controller;
101
            }
102
        }
103
104
        return $versions;
105
    }
106
107
    /**
108
     * @param $uriArray
109
     * @param $apiVersions
110
     * @throws \Exception
111
     */
112
    private function generateControllerLifeCycleBasedOnGivenData($uriArray, $apiVersions): void
113
    {
114
        //if a version apply, go through that specific path
115
        $version = array_key_exists($uriArray[1], $apiVersions) ? $uriArray[1] : null;
116
        $controllerName = $uriArray[1] ?? null;
117
        $methodName = $uriArray[2] ?? null;
118
119
        if (isset($version)) {
120
            $controllerName = $uriArray[2] ?? null;
121
            $methodName = $uriArray[3] ?? null;
122
        }
123
124
        //if it doesn't go through the regular path
125
        $this->generateControllerLifecycle(
126
            $controllerName ?? GvController::DEFAULT_CONTROLLER,
127
            $methodName ?? GvController::DEFAULT_METHOD,
128
            $version
129
        );
130
    }
131
132
    /**
133
     * @param $controller
134
     * @param $method
135
     * @param null $version
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $version is correct as it would always require null to be passed?
Loading history...
136
     * @throws \Exception
137
     */
138
    private function generateControllerLifecycle($controller, $method, $version = null): void
139
    {
140
        $this->controllerFinalName = $this->getControllerFinalName($controller, $version);
141
        $this->method = $this->getMethodFinalName($method);
142
        $controller = $this->getValidControllerClassName($this->controllerFinalName, $version);
143
        $this->initializeControllerInstance($controller);
144
    }
145
146
    /**
147
     * @param string|null $rawName
148
     * @param string|null $version
149
     * @return string
150
     * If no Controller/Method is specified it will fall back to the default controller (Index controller)
151
     */
152
    private function getControllerFinalName(string $rawName = null, ?string $version = null):string
153
    {
154
        if (empty($rawName)) {
155
            return GvController::DEFAULT_CONTROLLER;
156
        }
157
158
        $autoloadedNames = $this->controllerAutoloadingNames;
159
        if (isset($version)) {
160
            $autoloadedNames = $this->controllerAutoloadingNames[$version];
161
        }
162
163
        return $this->getAutoloadedControllerName($autoloadedNames, $rawName);
164
    }
165
166
    /**
167
     * @param $autoloadedArray
168
     * @param $name
169
     * @return string
170
     */
171
    private function getAutoloadedControllerName($autoloadedArray, $name): string
172
    {
173
        $lowercaseRawName = strtolower($name);
174
        if (!array_key_exists($lowercaseRawName, $autoloadedArray)) {
175
            return $name;
176
        }
177
178
        return $autoloadedArray[$lowercaseRawName];
179
    }
180
181
    /**
182
     * @param null $methodName
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $methodName is correct as it would always require null to be passed?
Loading history...
183
     * @return string
184
     */
185
    private function getMethodFinalName($methodName = null): string
186
    {
187
        //remove http get params if are present
188
        $methodName = explode('?', $methodName)[0];
0 ignored issues
show
$methodName of type null is incompatible with the type string expected by parameter $string of explode(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

188
        $methodName = explode('?', /** @scrutinizer ignore-type */ $methodName)[0];
Loading history...
189
190
        //if there's no method assigned then return the default method call.
191
        return ($methodName == '') ? GvController::DEFAULT_METHOD : $methodName;
192
    }
193
194
    /**
195
     * @param $controllerName
196
     * @param $version
197
     * @return string|null
198
     * @throws InvalidControllerException
199
     * @throws NotFoundException
200
     */
201
    private function getValidControllerClassName($controllerName, $version): ?string
202
    {
203
        if ($controllerName == "GvController") {
204
            throw new InvalidControllerException('GvController is not a valid controller');
205
        }
206
207
        $versionPath = isset($version) ? $version . "\\" : "";
208
209
        if (class_exists(self::CONTROLLERS_PREFIX . $versionPath . $controllerName)) {
210
            return self::CONTROLLERS_PREFIX . $versionPath . $controllerName;
211
        }
212
213
        throw new NotFoundException("Resource not found");
214
    }
215
216
    /**
217
     * @param $controllerFullName
218
     * @throws \Exception
219
     */
220
    private function initializeControllerInstance($controllerFullName)
221
    {
222
        $controllerInstance = new $controllerFullName($this->diContainer, $this->controllerFinalName, $this->method);
223
        if (!is_a($controllerInstance, GvController::class)) {
224
            throw new InvalidControllerException(
225
                'The controller that you are trying to instantiate should be extending GvController',
226
                ["controller class" => get_class($controllerInstance)]
227
            );
228
        }
229
230
        $annotationUtil = $this->diContainer->get('annotationUtil');
231
        $allowedHttpMethods = $annotationUtil->getAnnotationContentFromMethod(
232
            get_class($controllerInstance),
233
            $this->method,
234
            AnnotationUtil::HTTP_ANNOTATION
235
        );
236
237
        $controllerInstance->init($allowedHttpMethods);
238
    }
239
240
    /**
241
     * Set the value of controllerAutoloadingNames
242
     *
243
     * @param $controllerAutoloadingNames
244
     * @return  self
245
     */
246
    public function setControllerAutoloadingNames($controllerAutoloadingNames): ControllerService
247
    {
248
        $this->controllerAutoloadingNames = $controllerAutoloadingNames;
249
250
        return $this;
251
    }
252
253
    /**
254
     * Set the value of diContainer
255
     *
256
     * @param $diContainer
257
     * @return  self
258
     */
259
    public function setDiContainer($diContainer): ControllerService
260
    {
261
        $this->diContainer = $diContainer;
262
263
        return $this;
264
    }
265
266
    /**
267
     * Get the value of controllerFinalName
268
     * @return string
269
     */
270
    public function getControllerName(): string
271
    {
272
        return $this->controllerFinalName;
273
    }
274
275
    /**
276
     * Get the value of method
277
     * @return string
278
     */
279
    public function getMethodName(): string
280
    {
281
        return $this->method;
282
    }
283
284
    /**
285
     * @param $scanDirectory
286
     * @return array
287
     * @throws \Exception
288
     */
289
    public function autoloadControllers($scanDirectory):array
290
    {
291
        if (Cache::getCache()->exists(self::CONTROLLERS_KEY)) {
292
            return Cache::getCache()->load(self::CONTROLLERS_KEY);
293
        }
294
295
        $controllersDir = scandir($scanDirectory);
296
        $loadedControllers = [];
297
        foreach ($controllersDir as $autoloadingName) {
298
            $loadedControllers = $this->loadControllers($scanDirectory, $autoloadingName, $loadedControllers);
299
        }
300
        Cache::getCache()->save(self::CONTROLLERS_KEY, $loadedControllers);
301
        return $loadedControllers;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $loadedControllers could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
302
    }
303
304
    /**
305
     * @param $scanDirectory
306
     * @param $autoloadingName
307
     * @param $loadedControllers
308
     * @return mixed|null
309
     * @throws \ReflectionException
310
     */
311
    private function loadControllers($scanDirectory, $autoloadingName, $loadedControllers)
312
    {
313
        $routeManager = $this->diContainer->get('routeManager');
314
        if (in_array($autoloadingName, $routeManager->getExcludeDirectories())) {
315
            return null;
316
        }
317
318
        if (is_dir($scanDirectory . $autoloadingName)) {
319
            $autoloadedSubDir = $this->autoloadControllers($scanDirectory . $autoloadingName);
320
            $loadedControllers[$autoloadingName] = $autoloadedSubDir;
321
            return $loadedControllers;
322
        }
323
324
        $correctName = str_replace(".php", "", $autoloadingName);
325
        $loadedControllers[strtolower($correctName)] = $correctName;
326
        return $loadedControllers;
327
    }
328
}
329