Completed
Push — develop ( 058245...bd9d6f )
by Narcotic
12s
created

MainController::getAdditionalRoutes()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 0
cts 19
cp 0
rs 9.3142
c 0
b 0
f 0
cc 3
eloc 14
nc 2
nop 1
crap 12
1
<?php
2
/**
3
 * controller for start page
4
 */
5
6
namespace Graviton\CoreBundle\Controller;
7
8
use Graviton\CoreBundle\Event\HomepageRenderEvent;
9
use Graviton\ProxyBundle\Service\ApiDefinitionLoader;
10
use Graviton\RestBundle\HttpFoundation\LinkHeader;
11
use Graviton\RestBundle\HttpFoundation\LinkHeaderItem;
12
use Graviton\RestBundle\Service\RestUtilsInterface;
13
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
14
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
15
use Symfony\Component\HttpFoundation\Request;
16
use Symfony\Component\HttpFoundation\Response;
17
use Symfony\Component\Routing\Router;
18
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
19
20
/**
21
 * MainController
22
 *
23
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
24
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
25
 * @link     http://swisscom.ch
26
 */
27
class MainController
28
{
29
    /**
30
     * @var Router
31
     */
32
    private $router;
33
34
    /**
35
     * @var Response
36
     */
37
    private $response;
38
39
    /**
40
     * @var RestUtilsInterface
41
     */
42
    private $restUtils;
43
44
    /**
45
     * @var EngineInterface
46
     */
47
    private $templating;
48
49
    /**
50
     * @var EventDispatcherInterface
51
     */
52
    private $eventDispatcher;
53
54
    /**
55
     * @var ApiDefinitionLoader
56
     */
57
    private $apiLoader;
58
59
    /**
60
     * @var array
61
     */
62
    private $addditionalRoutes;
63
64
    /**
65
     * @var array
66
     */
67
    private $pathWhitelist;
68
69
    /**
70
     * @var array
71
     */
72
    private $proxySourceConfiguration;
73
74
    /**
75
     * @param Router                   $router                   router
76
     * @param Response                 $response                 prepared response
77
     * @param RestUtilsInterface       $restUtils                rest-utils from GravitonRestBundle
78
     * @param EngineInterface          $templating               templating-engine
79
     * @param EventDispatcherInterface $eventDispatcher          event dispatcher
80
     * @param ApiDefinitionLoader      $apiLoader                loader for third party api definition
81
     * @param array                    $additionalRoutes         custom routes
82
     * @param array                    $pathWhitelist            serviec path that always get aded to the main page
83
     * @param array                    $proxySourceConfiguration Set of sources to be recognized by the controller
84
     */
85
    public function __construct(
86
        Router $router,
87
        Response $response,
88
        RestUtilsInterface $restUtils,
89
        EngineInterface $templating,
90
        EventDispatcherInterface $eventDispatcher,
91
        ApiDefinitionLoader $apiLoader,
92
        $additionalRoutes = [],
93
        $pathWhitelist = [],
94
        array $proxySourceConfiguration = []
95
    ) {
96
        $this->router = $router;
97
        $this->response = $response;
98
        $this->restUtils = $restUtils;
99
        $this->templating = $templating;
100
        $this->eventDispatcher = $eventDispatcher;
101
        $this->apiLoader = $apiLoader;
102
        $this->addditionalRoutes = $additionalRoutes;
103
        $this->pathWhitelist = $pathWhitelist;
104
        $this->proxySourceConfiguration = $proxySourceConfiguration;
105
    }
106
107
    /**
108
     * create simple start page.
109
     *
110
     * @return Response $response Response with result or error
111
     */
112
    public function indexAction()
113
    {
114
        $response = $this->response;
115
116
        $mainPage = new \stdClass();
117
        $mainPage->services = $this->determineServices(
118
            $this->restUtils->getOptionRoutes()
119
        );
120
121
        $mainPage->thirdparty = $this->registerThirdPartyServices();
122
123
        $response->setContent(json_encode($mainPage));
124
        $response->setStatusCode(Response::HTTP_OK);
125
        $response->headers->set('Link', $this->prepareLinkHeader());
126
127
        // todo: make sure, that the correct content type is set.
128
        // todo: this should be covered by a kernel.response event listener?
129
        $response->headers->set('Content-Type', 'application/json');
130
131
        return $this->render(
132
            'GravitonCoreBundle:Main:index.json.twig',
133
            array('response' => $response->getContent()),
134
            $response
135
        );
136
    }
137
138
    /**
139
     * Renders a view.
140
     *
141
     * @param string   $view       The view name
142
     * @param array    $parameters An array of parameters to pass to the view
143
     * @param Response $response   A response instance
0 ignored issues
show
Documentation introduced by
Should the type for parameter $response not be null|Response?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
144
     *
145
     * @return Response A Response instance
146
     */
147
    public function render($view, array $parameters = array(), Response $response = null)
148
    {
149
        return $this->templating->renderResponse($view, $parameters, $response);
150
    }
151
152
    /**
153
     * Determines what service endpoints are available.
154
     *
155
     * @param array $optionRoutes List of routing options.
156
     *
157
     * @return array
158
     */
159
    protected function determineServices(array $optionRoutes)
160
    {
161
        $router = $this->router;
162
        foreach ($this->addditionalRoutes as $route) {
163
            $optionRoutes[$route] = null;
164
        }
165
166
        $services = array_map(
167
            function ($routeName) use ($router) {
168
                list($app, $bundle, $rest, $document) = explode('.', $routeName);
169
170
                $schemaRoute = implode('.', [$app, $bundle, $rest, $document, 'canonicalSchema']);
171
172
                return [
173
                    '$ref' => $router->generate($routeName, [], UrlGeneratorInterface::ABSOLUTE_URL),
174
                    'profile' => $router->generate($schemaRoute, [], UrlGeneratorInterface::ABSOLUTE_URL),
175
                ];
176
            },
177
            array_keys($optionRoutes)
178
        );
179
180
        $sortArr = [];
181
        foreach ($services as $key => $val) {
182
            if ($this->isRelevantForMainPage($val) && !in_array($val['$ref'], $sortArr)) {
183
                $sortArr[$key] = $val['$ref'];
184
            } else {
185
                unset($services[$key]);
186
            }
187
        }
188
189
        // get additional routes
190
        $additionalRoutes = $this->getAdditionalRoutes($sortArr);
191
192
        $services = array_merge($services, $additionalRoutes);
193
194
        array_multisort($sortArr, SORT_ASC, $services);
195
        return $services;
196
    }
197
198
    /**
199
     * gets the additional routes that can be injected by listeners/subscribers
200
     *
201
     * @param array $sortArr array needed for sorting
202
     *
203
     * @return array additional routes
204
     */
205
    private function getAdditionalRoutes(array &$sortArr)
206
    {
207
        $additionalRoutes = [];
208
        $event = new HomepageRenderEvent();
209
        $routes = $this->eventDispatcher->dispatch(HomepageRenderEvent::EVENT_NAME, $event)->getRoutes();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\EventDispatcher\Event as the method getRoutes() does only exist in the following sub-classes of Symfony\Component\EventDispatcher\Event: Graviton\CoreBundle\Event\HomepageRenderEvent. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
210
211
        if (!empty($routes)) {
212
            $baseRoute = $this->router->match("/");
213
            $baseUrl = $this->router->generate($baseRoute['_route'], [], UrlGeneratorInterface::ABSOLUTE_URL);
214
            foreach ($routes as $route) {
215
                $thisUrl = $baseUrl.$route['$ref'];
216
                $additionalRoutes[] = [
217
                    '$ref' => $thisUrl,
218
                    'profile' => $baseUrl.$route['profile']
219
                ];
220
                $sortArr[$thisUrl] = $thisUrl;
221
            }
222
        }
223
224
        return $additionalRoutes;
225
    }
226
227
    /**
228
     * Prepares the header field containing information about pagination.
229
     *
230
     * @return string
231
     */
232
    protected function prepareLinkHeader()
233
    {
234
        $links = new LinkHeader(array());
235
        $links->add(
236
            new LinkHeaderItem(
237
                $this->router->generate('graviton.core.rest.app.all', array (), UrlGeneratorInterface::ABSOLUTE_URL),
238
                array ('rel' => 'apps', 'type' => 'application/json')
239
            )
240
        );
241
242
        return (string) $links;
243
    }
244
245
    /**
246
     * tells if a service is relevant for the mainpage
247
     *
248
     * @param array $val value of service spec
249
     *
250
     * @return boolean
251
     */
252
    private function isRelevantForMainPage($val)
253
    {
254
        return true;
255
256
        return (substr($val['$ref'], -1) === '/')
257
            || in_array(parse_url($val['$ref'], PHP_URL_PATH), $this->pathWhitelist);
258
    }
259
260
    /**
261
     * Resolves all third party routes and add schema info
262
     *
263
     * @param array $thirdApiRoutes list of all routes from an API
264
     *
265
     * @return array
266
     */
267
    protected function determineThirdPartyServices(array $thirdApiRoutes)
268
    {
269
        $definition = $this->apiLoader;
270
        $mainRoute = $this->router->generate(
271
            'graviton.core.static.main.all',
272
            array(),
273
            UrlGeneratorInterface::ABSOLUTE_URL
274
        );
275
        $services = array_map(
276
            function ($apiRoute) use ($mainRoute, $definition) {
277
278
                return array (
279
                    '$ref' => $mainRoute.$apiRoute,
280
                    'profile' => $mainRoute."schema/".$apiRoute."/item",
281
                );
282
            },
283
            $thirdApiRoutes
284
        );
285
286
        return $services;
287
    }
288
289
    /**
290
     * Finds configured external apis to be exposed via G2.
291
     *
292
     * @return array
293
     */
294
    private function registerThirdPartyServices()
295
    {
296
        $services = [];
297
298
        // getenv()... it's a workaround for run all tests on travis! will be removed!
299
        if (getenv('USER') !== 'travis' && getenv('HAS_JOSH_K_SEAL_OF_APPROVAL') !== true) {
300
            foreach (array_keys($this->proxySourceConfiguration) as $source) {
301
                foreach ($this->proxySourceConfiguration[$source] as $thirdparty => $option) {
302
                    $this->apiLoader->resetDefinitionLoader();
303
                    $this->apiLoader->setOption($option);
304
                    $this->apiLoader->addOptions($this->decideApiAndEndpoint($option));
305
                    $services[$thirdparty] = $this->determineThirdPartyServices(
306
                        $this->apiLoader->getAllEndpoints(false, true)
307
                    );
308
                }
309
            }
310
        }
311
312
        return $services;
313
    }
314
315
    /**
316
     * get API name and endpoint from the url (third party API)
317
     *
318
     * @param array $config Configuration information ['prefix', 'serviceEndpoint']
319
     *
320
     * @return array
321
     */
322
    protected function decideApiAndEndpoint(array $config)
323
    {
324
        if (array_key_exists('serviceEndpoint', $config)) {
325
            return array (
326
                "apiName" => $config['prefix'],
327
                "endpoint" => $config['serviceEndpoint'],
328
            );
329
        }
330
331
        return [];
332
    }
333
334
    /**
335
     * Return OPTIONS results.
336
     *
337
     * @param Request $request Current http request
338
     *
339
     * @return Response $response Result of the action
340
     */
341
    public function optionsAction(Request $request)
342
    {
343
        $response = $this->response;
344
        $response->setStatusCode(Response::HTTP_NO_CONTENT);
345
        $request->attributes->set('corsMethods', 'GET, OPTIONS');
346
347
        return $response;
348
    }
349
}
350