XKCDCacheApp::addServices()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 16
ccs 6
cts 6
cp 1
rs 9.9332
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: famoser
5
 * Date: 28/11/2016
6
 * Time: 19:10
7
 */
8
9
namespace Famoser\XKCDCache;
10
11
12
use Closure;
13
use Exception;
14
use Famoser\XKCDCache\Controllers\ApiController;
15
use Famoser\XKCDCache\Controllers\ComicController;
16
use Famoser\XKCDCache\Controllers\DownloadController;
17
use Famoser\XKCDCache\Controllers\PublicController;
18
use Famoser\XKCDCache\Exceptions\ServerException;
19
use Famoser\XKCDCache\Framework\ContainerBase;
20
use Famoser\XKCDCache\Models\Response\Base\BaseResponse;
21
use Famoser\XKCDCache\Services\CacheService;
22
use Famoser\XKCDCache\Services\DatabaseService;
23
use Famoser\XKCDCache\Services\Interfaces\LoggingServiceInterface;
24
use Famoser\XKCDCache\Services\LoggingService;
25
use Famoser\XKCDCache\Services\SettingService;
26
use Famoser\XKCDCache\Services\XKCDService;
27
use Famoser\XKCDCache\Types\ServerError;
28
use Interop\Container\ContainerInterface;
29
use InvalidArgumentException;
30
use Psr\Http\Message\ResponseInterface;
31
use Psr\Http\Message\ServerRequestInterface;
32
use Slim\App;
33
use Slim\Container;
34
use Slim\Http\Environment;
35
use Slim\Http\Response;
36
use Slim\Views\Twig;
37
use Slim\Views\TwigExtension;
38
use Throwable;
39
40
/**
41
 * the sync api application, in one neat class :)
42
 *
43
 * @package Famoser\XKCDCache
44
 */
45
class XKCDCacheApp extends App
46
{
47
    /**
48
     * Create new application
49
     *
50
     * @param array $configuration an associative array of app settings
51
     * @throws InvalidArgumentException when no container is provided that implements ContainerInterface
52
     */
53 17
    public function __construct($configuration)
54
    {
55
        //construct parent with container
56 17
        parent::__construct(
57 17
            $this->constructContainer(
58 17
                $configuration
59
            )
60
        );
61
62
        //add routes
63 17
        $this->group('', $this->getWebAppRoutes());
64 17
        $this->group('/1.0', $this->getApiRoutes());
65 17
    }
66
67
    /**
68
     * override the environment (to mock requests for example)
69
     *
70
     * @param Environment $environment
71
     */
72 11
    public function overrideEnvironment(Environment $environment)
73
    {
74 11
        $this->getContainer()['environment'] = $environment;
75 11
    }
76
77
    /**
78
     * get the web app routes
79
     *
80
     * @return Closure
81
     */
82 17
    private function getWebAppRoutes()
83
    {
84
        return function () {
85 17
            $this->get('/', PublicController::class . ':index')->setName('index');
86
87 17
            $this->group(
88 17
                '/comics',
89
                function () {
90 17
                    $this->get('/', ComicController::class . ':index')->setName('comics_index');
91 17
                    $this->get('/failed', ComicController::class . ':failed')->setName('comics_failed');
92
93 17
                    $this->get('/show/{id}', ComicController::class . ':show')->setName('comics_show');
94 17
                    $this->get('/refresh/{id}', ComicController::class . ':refresh')->setName('comic_new');
95 17
                }
96
            );
97 17
        };
98
    }
99
100
    /**
101
     * get the api routes
102
     *
103
     * @return Closure
104
     */
105 17
    private function getApiRoutes()
106
    {
107
        return function () {
108 17
            $this->get('/refresh', ApiController::class . ':refresh')->setName('api_refresh');
109 17
            $this->get('/refresh/force', ApiController::class . ':refreshNoLimit')->setName('api_refresh_force');
110 17
            $this->get('/status', ApiController::class . ':status')->setName('api_status');
111
112 17
            $this->group(
113 17
                '/download',
114
                function () {
115 17
                    $this->get('/zip', DownloadController::class . ':downloadZip')->setName('download_zip');
116 17
                    $this->get('/json', DownloadController::class . ':downloadJson')->setName('download_json');
117 17
                }
118
            );
119 17
        };
120
    }
121
122
    /**
123
     * create the container
124
     *
125
     * @param $configuration
126
     * @return Container
127
     */
128 17
    private function constructContainer($configuration)
129
    {
130 17
        $container = new Container($configuration);
131
132
        //add services
133 17
        $this->addServices($container);
134
135
        //construct base container to get services now needed to configure other services
136 17
        $baseContainer = new ContainerBase($container);
137
138
        //add error handlers
139 17
        $this->addHandlers($container, $baseContainer);
140
141
        //add view
142
        $container['view'] = function (Container $container) use ($baseContainer) {
143 8
            $settings = $baseContainer->getSettingService();
144 8
            $view = new Twig(
145 8
                $settings->getTemplatePath(),
146
                [
147 8
                    'cache' => $settings->getCachePath(),
148 8
                    'debug' => $settings->getDebugMode()
149
                ]
150
            );
151 8
            $view->addExtension(
152 8
                new TwigExtension(
153 8
                    $container['router'],
154 8
                    $container['request']->getUri()
155
                )
156
            );
157
158 8
            return $view;
159
        };
160
161 17
        return $container;
162
    }
163
164
    /**
165
     * add the error handlers to the container
166
     *
167
     * @param Container $container
168
     * @param ContainerBase $containerBase
169
     */
170 17
    private function addHandlers(Container $container, ContainerBase $containerBase)
171
    {
172 17
        $errorHandler = $this->createErrorHandlerClosure($container, $containerBase);
173
174
        //third argument: \Throwable
175 17
        $container['phpErrorHandler'] = $errorHandler;
176
        //third argument: \Exception
177 17
        $container['errorHandler'] = $errorHandler;
178
179 17
        $container['notAllowedHandler'] = $this->createNotFoundHandlerClosure($container, $containerBase, ServerError::METHOD_NOT_ALLOWED);
180 17
        $container['notFoundHandler'] = $this->createNotFoundHandlerClosure($container, $containerBase, ServerError::NODE_NOT_FOUND);
181 17
    }
182
183
    /**
184
     * checks if a specific request is done by the api library
185
     *
186
     * @param ServerRequestInterface $request
187
     * @return bool
188
     */
189 3
    private function isApiRequest(ServerRequestInterface $request)
190
    {
191 3
        return strpos($request->getUri()->getPath(), '/1.0/') === 0;
192
    }
193
194
    /**
195
     * creates a closure which has no third argument
196
     *
197
     * @param ContainerInterface $container
198
     * @param ContainerBase $containerBase
199
     * @param $apiError
200
     * @return Closure
201
     */
202 17
    private function createNotFoundHandlerClosure(ContainerInterface $container, ContainerBase $containerBase, $apiError)
203
    {
204
        return function () use ($container, $apiError, $containerBase) {
205
            return function (ServerRequestInterface $request, Response $response) use ($container, $apiError, $containerBase) {
206 2
                $response = $response->withStatus(404);
207
208
                /* @var LoggingServiceInterface $logger */
209 2
                $logger = $containerBase->getLoggingService();
210 2
                $logger->log(
211 2
                    "[" . date("c") . "]: not found / not allowed " . $request->getUri()
212
                );
213
214 2
                if ($this->isApiRequest($request)) {
215
                    $resp = new BaseResponse();
216
                    $resp->successful = false;
217
                    $resp->error_message = ServerError::toString($apiError);
218
                    return $response->withJson($resp);
219
                }
220
221
                /** @var Twig $view */
222 2
                $view = $container['view'];
223 2
                return $view->render($response, 'public/not_found.html.twig', []);
224 2
            };
225 17
        };
226
    }
227
228
    /**
229
     * creates a closure which accepts \Exception and \Throwable as third argument
230
     *
231
     * @param ContainerInterface $container
232
     * @param ContainerBase $containerBase
233
     * @return Closure
234
     */
235 17
    private function createErrorHandlerClosure(ContainerInterface $container, ContainerBase $containerBase)
236
    {
237
        return function () use ($container, $containerBase) {
238
            return function (ServerRequestInterface $request, Response $response, $error = null) use ($container, $containerBase) {
239 1
                $response = $response->withStatus(500);
240
241 1
                if ($error instanceof Exception || $error instanceof Throwable) {
242 1
                    $errorString = $error->getFile() . ' (' . $error->getLine() . ')\n' .
243 1
                        $error->getCode() . ': ' . $error->getMessage() . '\n' .
244 1
                        $error->getTraceAsString();
245
                } else {
246
                    $errorString = 'unknown error type occurred :/. Details: ' . print_r($error);
247
                }
248
249
                /* @var LoggingServiceInterface $logger */
250 1
                $logger = $containerBase->getLoggingService();
251 1
                $logger->log(
252 1
                    "[" . date("c") . "]: " . $errorString
253
                );
254
255
                //return json if api request
256 1
                if ($this->isApiRequest($request)) {
257 1
                    $resp = new BaseResponse();
258 1
                    $resp->successful = false;
259 1
                    if ($containerBase->getSettingService()->getDebugMode() || !($error instanceof ServerException)) {
260 1
                        $resp->error_message = $errorString;
261
                    } else {
262
                        $resp->error_message = $error->getMessage();
263
                    }
264
265 1
                    return $response->withJson($resp);
266
                } else {
267
                    //general error page
268
                    $args = [];
269
                    if ($containerBase->getSettingService()->getDebugMode()) {
270
                        $args['error'] = $errorString;
271
                    } else {
272
                        $args['error'] = "";
273
                    }
274
275
                    /** @var Twig $view */
276
                    $view = $container['view'];
277
                    return $view->render($response, 'public/server_error.html.twig', $args);
278
                }
279 1
            };
280 17
        };
281
    }
282
283
    /**
284
     * add all services to the container
285
     *
286
     * @param Container $container
287
     */
288 19
    private function addServices(Container $container)
289
    {
290
        $container[ContainerBase::LOGGING_SERVICE_KEY] = function (Container $container) {
291 6
            return new LoggingService($container);
292
        };
293
        $container[ContainerBase::DATABASE_SERVICE_KEY] = function (Container $container) {
294 19
            return new DatabaseService($container);
295
        };
296
        $container[ContainerBase::SETTING_SERVICE_KEY] = function (Container $container) {
297 19
            return new SettingService($container);
298
        };
299
        $container[ContainerBase::XKCD_SERVICE_KEY] = function (Container $container) {
300 5
            return new XKCDService($container);
301
        };
302
        $container[ContainerBase::CACHE_SERVICE_KEY] = function (Container $container) {
303 5
            return new CacheService($container);
304
        };
305 17
    }
306
}
307