Issues (29)

lib/Phile/Phile.php (4 issues)

1
<?php
2
/**
3
 * @author  PhileCMS
4
 * @link    https://philecms.github.io
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
This use statement conflicts with another class in this namespace, Phile\Event. Consider defining an alias.

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\Response;
14
use Phile\Core\Router;
15
use Phile\Http\MiddlewareQueue;
16
use Phile\Model\Page;
17
use Phile\Repository\Page as Repository;
18
use Psr\Http\Message\ResponseInterface;
19
use Psr\Http\Message\ServerRequestInterface;
20
use Psr\Http\Server\MiddlewareInterface;
21
use Psr\Http\Server\RequestHandlerInterface;
22
use Phile\Core\ServiceLocator;
0 ignored issues
show
This use statement conflicts with another class in this namespace, Phile\ServiceLocator. Consider defining an alias.

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...
23
24
/**
25
 * Phile Core class
26
 */
27
class Phile implements MiddlewareInterface
28
{
29
    /** @var Config Phile configuration */
30
    protected $config;
31
32
    /** @var Event event-bus */
33
    protected $eventBus;
34
35
    /** @var array callbacks run at bootstrap */
36
    protected $bootstrapConfigs = [];
37
38
    /** @var array callbacks run on middleware-setup */
39
    protected $middlewareConfigs = [];
40
41
    /**
42
     * Constructor sets-up base Phile environment
43
     */
44 35
    public function __construct(Event $eventBus, Config $config)
45
    {
46 35
        $this->eventBus = $eventBus;
47 35
        $this->config = $config;
48
    }
49
50
    /**
51
     * Adds bootstrap-setup
52
     */
53 35
    public function addBootstrap(callable $bootstrap): self
54
    {
55 35
        $this->bootstrapConfigs[] = $bootstrap;
56 35
        return $this;
57
    }
58
59
    /**
60
     * Adds middleware-setup
61
     */
62 35
    public function addMiddleware(callable $middleware): self
63
    {
64 35
        $this->middlewareConfigs[] = $middleware;
65 35
        return $this;
66
    }
67
68
    /**
69
     * Performs bootstrap
70
     */
71 34
    public function bootstrap(): self
72
    {
73 34
        foreach ($this->bootstrapConfigs as $config) {
74 34
            call_user_func_array($config, [$this->eventBus, $this->config]);
75
        }
76 34
        return $this;
77
    }
78
79
    /**
80
     * Populates Phile controlled middle-ware-queue
81
     */
82 8
    public function middleware(MiddlewareQueue $queue): MiddlewareQueue
83
    {
84 8
        foreach ($this->middlewareConfigs as $config) {
85 8
            call_user_func_array($config, [$queue, $this->eventBus, $this->config]);
86
        }
87 8
        return $queue;
88
    }
89
90
    /**
91
     * Run a request through Phile and create a response
92
     */
93 8
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
94
    {
95 8
        $this->config->lock();
96
97 8
        $router = new Router($request->getServerParams());
98 8
        Container::getInstance()->set('Phile_Router', $router);
99
100 8
        $response = $this->triggerEventWithResponse('after_init_core');
101 7
        if ($response) {
102 1
            return $response;
103
        }
104
105 7
        $page = $this->resolveCurrentPage($router);
106 7
        if ($page instanceof ResponseInterface) {
107 2
            return $page;
108
        }
109
110 6
        $notFound = $page->getPageId() == $this->config->get('not_found_page');
111 6
        if ($notFound && !$this->config->get('handle_not_found')) {
112 1
            return $handler->handle($request);
113
        }
114
115 5
        $html = $this->renderHtml($page);
116 5
        if ($html instanceof ResponseInterface) {
117 1
            return $html;
118
        }
119
120 5
        return $this->createResponse($html, $notFound ? 404 : 200);
121
    }
122
123
    /**
124
     * Resolves request into the current page
125
     *
126
     * @return Page|ResponseInterface
127
     */
128 7
    protected function resolveCurrentPage(Router $router)
129
    {
130 7
        $pageId = $router->getCurrentUrl();
131 7
        $response = $this->triggerEventWithResponse('request_uri', ['uri' => $pageId]);
132 7
        if ($response) {
133 1
            return $response;
134
        }
135
136 7
        $repository = new Repository();
137 7
        $page = $repository->findByPath($pageId);
138 7
        $found = $page instanceof Page;
139
140 7
        if ($found && $pageId !== $page->getPageId()) {
141 1
            $url = $router->urlForPage($page->getPageId());
142 1
            return (new Response)->createRedirectResponse($url, 301);
143
        }
144
145 6
        if (!$found) {
146 5
            $page = $repository->findByPath($this->config->get('not_found_page'));
147 5
            $this->eventBus->trigger('after_404');
148
        }
149
150 6
        $response = $this->triggerEventWithResponse(
151
            'after_resolve_page',
152 6
            ['pageId' => $pageId, 'page' => &$page]
153
        );
154 6
        if ($response) {
155 1
            return $response;
156
        }
157
158 6
        return $page;
159
    }
160
161
    /**
162
     * Renders page into output format (HTML)
163
     *
164
     * @return string|ResponseInterface
165
     */
166 5
    protected function renderHtml(Page $page)
167
    {
168 5
        $this->eventBus->trigger('before_init_template');
169 5
        $engine = ServiceLocator::getService('Phile_Template');
170
171 5
        $coreVars = $this->config->getTemplateVars();
172 5
        $templateVars = Registry::get('templateVars') + $coreVars;
173 5
        Registry::set('templateVars', $templateVars);
174
175 5
        $response = $this->triggerEventWithResponse(
176
            'before_render_template',
177 5
            ['templateEngine' => &$engine]
178
        );
179 5
        if ($response) {
180 1
            return $response;
181
        }
182
183 5
        $engine->setCurrentPage($page);
184 5
        $html = $engine->render();
185
186 5
        $this->eventBus->trigger(
187
            'after_render_template',
188 5
            ['templateEngine' => &$engine, 'output' => &$html]
189
        );
190
191 5
        return $html;
192
    }
193
194
    /**
195
     * Creates response
196
     *
197
     * @throws Exception
198
     */
199 5
    protected function createResponse(string $output, int $status): ResponseInterface
200
    {
201 5
        $charset = $this->config->get('charset');
202 5
        $response = (new Response)
203 5
            ->createHtmlResponse($output)
204 5
            ->withHeader('Content-Type', 'text/html; charset=' . $charset)
205 5
            ->withStatus($status);
206
207 5
        $response = $this->triggerEventWithResponse(
208
            'after_response_created',
209 5
            ['response' => &$response]
210
        );
211
212 4
        if (!($response instanceof ResponseInterface)) {
213 1
            throw new Exception('Response not valid.', 1523630697);
0 ignored issues
show
Deprecated Code introduced by
The class Phile\Exception has been deprecated: since 1.4 will be removed ( Ignorable by Annotation )

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

213
            throw /** @scrutinizer ignore-deprecated */ new Exception('Response not valid.', 1523630697);
Loading history...
214
        }
215
216 3
        return $response;
217
    }
218
219
    /**
220
     * Triggers event injecting a response parameter and returning it if set
221
     *
222
     * @throws Exception
223
     */
224 8
    protected function triggerEventWithResponse(string $eventName, array $eventData = []): ?ResponseInterface
225
    {
226 8
        if (empty($eventData['response'])) {
227 8
            $response = null;
228 8
            $eventData = array_merge(['response' => &$response], $eventData);
229
        }
230
231 8
        $this->eventBus->trigger($eventName, $eventData);
232
233 7
        if ($eventData['response'] !== null && !($eventData['response'] instanceof ResponseInterface)) {
234 1
            throw new Exception(
0 ignored issues
show
Deprecated Code introduced by
The class Phile\Exception has been deprecated: since 1.4 will be removed ( Ignorable by Annotation )

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

234
            throw /** @scrutinizer ignore-deprecated */ new Exception(
Loading history...
235 1
                "No valid response in \$eventData['response'] for event $eventName.",
236
                1523630698
237
            );
238
        }
239
240 7
        return $eventData['response'];
241
    }
242
}
243