Passed
Pull Request — master (#59)
by Alexander
22:15 queued 08:16
created

BaseController::handle()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 16
c 1
b 0
f 0
dl 0
loc 27
rs 8.8333
cc 7
nc 9
nop 1
1
<?php
2
3
namespace App\LazyRendering\Http;
4
5
use Generator;
6
use Psr\Container\ContainerInterface as Container;
7
use Psr\Http\Message\ResponseFactoryInterface as ResponseFactory;
8
use Psr\Http\Message\ResponseInterface as Response;
9
use Psr\Http\Message\ServerRequestInterface as Request;
10
use Psr\Http\Message\StreamFactoryInterface;
11
use Psr\Http\Server\MiddlewareInterface;
12
use Psr\Http\Server\RequestHandlerInterface;
13
use Throwable;
14
use Yiisoft\Http\Method;
15
use Yiisoft\Injector\Injector;
16
use Yiisoft\Router\UrlGeneratorInterface;
17
use Yiisoft\Widget\WidgetFactory;
18
19
abstract class BaseController implements MiddlewareInterface
20
{
21
    protected ResponseFactory $responseFactory;
22
    protected Request $request;
23
    protected UrlGeneratorInterface $urlGenerator;
24
    /** @var null|mixed Layout definition with method render() */
25
    protected $pageLayout = null;
26
    protected StreamFactoryInterface $streamFactory;
27
28
    private Container $container;
29
30
    /**
31
     * baseController constructor.
32
     * @param Container $container
33
     * @param mixed[] $options
34
     */
35
    public function __construct(Container $container)
36
    {
37
        $this->container = $container;
38
        $this->responseFactory = $this->container->get(ResponseFactory::class);
39
        $this->urlGenerator = $this->container->get(UrlGeneratorInterface::class);
40
        $this->streamFactory = $this->container->get(StreamFactoryInterface::class);
41
        WidgetFactory::initialize($container);
42
    }
43
44
    /**
45
     * @param Request $request
46
     * @param Response $response
47
     * @param array $args
48
     * @return Response
49
     * @throws Throwable
50
     */
51
    public function process(Request $request, RequestHandlerInterface $handler): Response
52
    {
53
        $this->flushAllOutputBuffers();
54
        return $this->handle($request) ?? $handler->handle($request);
55
    }
56
57
    public function handle(Request $request): ?Response
58
    {
59
        $this->request = $request;
60
        $actionMethod = $this->getActionMethod($request);
61
        if ($actionMethod === null || !method_exists($this, $actionMethod)) {
62
            return null;
63
        }
64
        $data = (new Injector($this->container))->invoke([$this, $actionMethod]);
65
66
        $response = $data instanceof Response ? $data : $this->prepareResponse($data);
67
68
        // Force Buffering (classic mode)
69
        if (($this->request->getQueryParams()['forceBuffering'] ?? 0) === '1') {
70
            // Buffering from generator
71
            $content = $response->getBody()->getContents();
72
            $stream = $this->streamFactory->createStream($content);
73
            return $response->withBody($stream);
74
        }
75
76
        if (($this->request->getQueryParams()['forceBuffering'] ?? 0) === '2') {
77
            $stream = $response->getBody();
78
            if (!$stream instanceof GeneratorStream) {
79
                throw new \Exception('Combined mode not supported');
80
            }
81
            $stream->setReadMode(GeneratorStream::READ_MODE_FIRST_YIELD);
82
        }
83
        return $response;
84
    }
85
86
    protected function prepareResponse(iterable $page): Response
87
    {
88
        if (!$page instanceof Generator) {
89
            $page = (static function (iterable $iterable) {
90
                yield from $iterable;
91
            })($page);
92
        }
93
        // Add layout rendering
94
        if ($this->pageLayout !== null) {
95
            if (is_string($this->pageLayout)) {
96
                $this->pageLayout = $this->container->get($this->pageLayout);
97
            } elseif (!is_object($this->pageLayout) || !method_exists($this->pageLayout, 'render')) {
98
                throw new \RuntimeException('Bad Layout definition');
99
            }
100
            $page = (new Injector($this->container))->invoke([$this->pageLayout, 'render'], [$page, $this->request]);
101
        }
102
        $stream = new GeneratorStream($page);
103
        return $this->responseFactory->createResponse()->withBody($stream);
104
    }
105
106
    private function flushAllOutputBuffers(): void
107
    {
108
        for ($bufferingLevel = ob_get_level(), $i = 0; $i < $bufferingLevel; ++$i) {
109
            ob_end_flush();
110
        }
111
    }
112
113
    private function getActionMethod(Request $request): ?string
114
    {
115
        $method = strtoupper($request->getMethod());
116
        $page = $request->getAttribute('page', 'index');
117
        $action =
118
            $request->getAttribute('action') ??
119
            $request->getParsedBody()['action'] ??
120
            $request->getQueryParams()['action'] ??
121
            null;
122
123
        if ($action !== null || $method === Method::POST) {
124
            $action = $action ?? $page;
125
            return 'action' . str_replace(' ', '', ucwords($action));
126
        }
127
128
        if ($method === Method::GET) {
129
            return 'page' . str_replace(' ', '', ucwords($page));
130
        }
131
132
        return null;
133
    }
134
}
135