Passed
Push — master ( 10b09c...54e166 )
by Schlaefer
04:38
created

Phile::process()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 33
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 5

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 33
ccs 20
cts 20
cp 1
rs 8.439
cc 5
eloc 20
nc 5
nop 2
crap 5
1
<?php
2
/**
3
 * @author  PhileCMS
4
 * @link    https://philecms.com
5
 * @license http://opensource.org/licenses/MIT
6
 */
7
8
namespace Phile;
9
10
use Phile\Core\Config;
11
use Phile\Core\Container;
12
use Phile\Core\Event;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Phile\Event.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
13
use Phile\Core\RequestHandler;
14
use Phile\Core\Response;
15
use Phile\Core\Router;
16
use Phile\Model\Page;
17
use Phile\Repository\Page as Repository;
18
use Interop\Http\Server\MiddlewareInterface;
19
use Interop\Http\Server\RequestHandlerInterface;
20
use Psr\Http\Message\ResponseInterface;
21
use Psr\Http\Message\ServerRequestInterface;
22
23
/**
24
 * Phile Core class
25
 */
26
class Phile implements MiddlewareInterface
27
{
28
    /** @var Config Phile configuration */
29
    protected $config;
30
31
    /** @var Event event-bus */
32
    protected $eventBus;
33
34
    /** @var array callbacks run at bootstrap */
35
    protected $bootstrapConfigs = [];
36
37
    /** @var array callbacks run on middleware-setup */
38
    protected $middlewareConfigs = [];
39
40
    /**
41
     * Constructor sets-up base Phile environment
42
     *
43
     * @param Event $eventBus
44
     * @param Config $config
45
     */
46 28
    public function __construct(Event $eventBus, Config $config)
47
    {
48 28
        $this->eventBus = $eventBus;
49 28
        $this->config = $config;
50 28
    }
51
52
    /**
53
     * Adds bootstrap-setup
54
     */
55 28
    public function addBootstrap(callable $bootstrap)
56
    {
57 28
        $this->bootstrapConfigs[] = $bootstrap;
58 28
        return $this;
59
    }
60
61
    /**
62
     * Adds middleware-setup
63
     */
64 28
    public function addMiddleware(callable $middleware)
65
    {
66 28
        $this->middlewareConfigs[] = $middleware;
67 28
        return $this;
68
    }
69
70
    /**
71
     * Performs bootstrap
72
     */
73 27
    public function bootstrap()
74
    {
75 27
        foreach ($this->bootstrapConfigs as $config) {
76 27
            call_user_func_array($config, [$this->eventBus, $this->config]);
77
        }
78 27
        return $this;
79
    }
80
81
    /**
82
     * Processes request
83
     */
84 4
    public function dispatch($request)
85
    {
86 4
        $this->bootstrap();
87 4
        $this->config->lock();
88
89 4
        $requestHandler = new RequestHandler(new Response);
90 4
        foreach ($this->middlewareConfigs as $config) {
91 4
            call_user_func_array($config, [$requestHandler, $this->eventBus, $this->config]);
92
        }
93
94 4
        return $requestHandler->handle($request);
95
    }
96
97
    /**
98
     * Implements PSR-15 middle-ware process-handler
99
     */
100 4
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler)
101
    {
102 4
        $router = new Router($request->getServerParams());
103 4
        Container::getInstance()->set('Phile_Router', $router);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Psr\Container\ContainerInterface as the method set() does only exist in the following implementations of said interface: Phile\Core\Container.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
104
105
        // BC: send response in after_init_core event
106 4
        $response = new Response;
107 4
        $response->setCharset($this->config->get('charset'));
108 4
        $this->eventBus->trigger('after_init_core', ['response' => &$response]);
109 4
        if ($response instanceof ResponseInterface) {
110 1
            return $response;
111
        }
112
113 4
        $page = $this->resolveCurrentPage($router);
114 4
        if ($page instanceof ResponseInterface) {
115 2
            return $page;
116
        }
117
118 3
        $html = $this->renderHtml($page);
0 ignored issues
show
Bug introduced by
It seems like $page defined by $this->resolveCurrentPage($router) on line 113 can be null; however, Phile\Phile::renderHtml() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
119 3
        if ($html instanceof ResponseInterface) {
120 1
            return $html;
121
        }
122
123 2
        $charset = $this->config->get('charset');
124 2
        $response = (new Response)->createHtmlResponse($html)
125 2
            ->withHeader('Content-Type', 'text/html; charset=' . $charset);
126
127 2
        if ($page->getPageId() == $this->config->get('not_found_page')) {
128 1
            $response = $response->withStatus(404);
129
        }
130
131 2
        return $response;
132
    }
133
134
    /**
135
     * Resolves request into the current page
136
     */
137 4
    protected function resolveCurrentPage(Router $router)
138
    {
139 4
        $pageId = $router->getCurrentUrl();
140 4
        $response = null;
141 4
        $this->eventBus->trigger(
142 4
            'request_uri',
143 4
            ['uri' => $pageId, 'response' => &$response]
144
        );
145 4
        if ($response instanceof ResponseInterface) {
146 1
            return $response;
147
        }
148
149 4
        $repository = new Repository();
150 4
        $page = $repository->findByPath($pageId);
151 4
        $found = $page instanceof Page;
152
153 4
        if ($found && $pageId !== $page->getPageId()) {
154 1
            $url = $router->urlForPage($page->getPageId());
155 1
            return (new Response)->createRedirectResponse($url, 301);
156
        }
157
158 3
        if (!$found) {
159 1
            $page = $repository->findByPath($this->config->get('not_found_page'));
160 1
            $this->eventBus->trigger('after_404');
161
        }
162
163 3
        $this->eventBus->trigger(
164 3
            'after_resolve_page',
165 3
            ['pageId' => $pageId, 'page' => &$page, 'response' => &$response]
166
        );
167 3
        if ($response instanceof ResponseInterface) {
168 1
            return $response;
169
        }
170
171 3
        return $page;
172
    }
173
174
    /**
175
     * Renders page into output format (HTML)
176
     */
177 3
    protected function renderHtml(Page $page)
178
    {
179 3
        $this->eventBus->trigger('before_init_template');
180 3
        $engine = ServiceLocator::getService('Phile_Template');
181
182 3
        $coreVars = $this->config->getTemplateVars();
183 3
        $templateVars = Registry::get('templateVars') + $coreVars;
184 3
        Registry::set('templateVars', $templateVars);
185
186 3
        $response = null;
187 3
        $this->eventBus->trigger(
188 3
            'before_render_template',
189 3
            ['templateEngine' => &$engine, 'response' => &$response]
190
        );
191 3
        if ($response instanceof ResponseInterface) {
192 1
            return $response;
193
        }
194
195 2
        $engine->setCurrentPage($page);
196 2
        $html = $engine->render();
197
198 2
        $this->eventBus->trigger(
199 2
            'after_render_template',
200 2
            ['templateEngine' => &$engine, 'output' => &$html]
201
        );
202
203 2
        return $html;
204
    }
205
}
206