Completed
Push — master ( 1c0463...6ee2a7 )
by Rafael
34:09
created

AbstractWidgetViewHelper::getControllerContext()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4.128

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 12
cts 15
cp 0.8
rs 9.2
c 0
b 0
f 0
cc 4
eloc 9
nc 6
nop 0
crap 4.128
1
<?php
2
namespace ApacheSolrForTypo3\Solr\Widget;
3
4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use ApacheSolrForTypo3\Solr\Mvc\Controller\SolrControllerContext;
18
use ApacheSolrForTypo3\Solr\Widget\WidgetRequest as SolrFluidWidgetRequest;
19
use TYPO3\CMS\Extbase\Mvc\Web\Response;
20
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
21
use TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\RootNode;
22
use TYPO3\CMS\Fluid\Core\ViewHelper\Facets\ChildNodeAccessInterface;
23
use TYPO3\CMS\Fluid\Core\Widget\AbstractWidgetViewHelper as AbstractCoreWidgetViewHelper;
24
use TYPO3\CMS\Fluid\Core\Widget\AjaxWidgetContextHolder;
25
use TYPO3\CMS\Fluid\Core\Widget\Exception\MissingControllerException;
26
use TYPO3\CMS\Fluid\Core\Widget\WidgetRequest as CoreWidgetRequest;
27
use TYPO3\CMS\Fluid\Core\Widget\WidgetContext;
28
29
30
/**
31
 * Class AbstractWidgetViewHelper
32
 *
33
 * This is almost a exact copy of \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper
34
 * because we need to switch the request object to overrule the widget prefix for url params
35
 * todo: find cleaner way to let widget listen to tx_solr[] instead of tx_solr[@widget]
36
 *
37
 * @author Frans Saris <[email protected]>
38
 * @author Timo Hund <[email protected]>
39
 * @package ApacheSolrForTypo3\Solr\Widget
40
 */
41
abstract class AbstractWidgetViewHelper extends AbstractCoreWidgetViewHelper implements ChildNodeAccessInterface
42
{
43
44
    /**
45
     * The Controller associated to this widget.
46
     * This needs to be filled by the individual subclass by an @inject
47
     * annotation.
48
     *
49
     * @var AbstractWidgetController
50
     * @api
51
     */
52
    protected $controller;
53
54
    /**
55
     * If set to TRUE, it is an AJAX widget.
56
     *
57
     * @var boolean
58
     * @api
59
     */
60
    protected $ajaxWidget = false;
61
62
    /**
63
     * @var AjaxWidgetContextHolder
64
     */
65
    private $ajaxWidgetContextHolder;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
66
67
    /**
68
     * @var ObjectManagerInterface
69
     */
70
    protected $objectManager;
71
72
    /**
73
     * @var \TYPO3\CMS\Extbase\Service\ExtensionService
74
     * @inject
75
     */
76
    protected $extensionService;
77
78
    /**
79
     * @var \TYPO3\CMS\Fluid\Core\Widget\WidgetContext
80
     */
81
    private $widgetContext;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
82
83
    /**
84
     * @param AjaxWidgetContextHolder $ajaxWidgetContextHolder
85
     * @return void
86 30
     */
87
    public function injectAjaxWidgetContextHolder(AjaxWidgetContextHolder $ajaxWidgetContextHolder)
88 30
    {
89 30
        $this->ajaxWidgetContextHolder = $ajaxWidgetContextHolder;
90
    }
91
92
    /**
93
     * @param ObjectManagerInterface $objectManager
94
     * @return void
95 30
     */
96
    public function injectObjectManager(ObjectManagerInterface $objectManager)
97 30
    {
98 30
        $this->objectManager = $objectManager;
99 30
        $this->widgetContext = $this->objectManager->get(WidgetContext::class);
100
    }
101
102
    /**
103
     * Initialize the arguments of the ViewHelper, and call the render() method of the ViewHelper.
104
     *
105
     * @return string the rendered ViewHelper.
106 30
     */
107
    public function initializeArgumentsAndRender()
108 30
    {
109 30
        $this->validateArguments();
110 30
        $this->initialize();
111 30
        $this->initializeWidgetContext();
112
        return $this->callRenderMethod();
113
    }
114
115
    /**
116
     * @todo The fallback on $this->controllerContext is only needed for TYPO3 8 backwards compatibility and can be dropped when TYPO3 8 is not supported anymore
117
     * @return SolrControllerContext
118
     * @throws \InvalidArgumentException
119 30
     */
120
    protected function getControllerContext()
121 30
    {
122 30
        $controllerContext = null;
123 30
        if (!is_null($this->controllerContext)) {
124 30
            $controllerContext = $this->controllerContext;
125 30
        } elseif (method_exists($this->renderingContext, 'getControllerContext')) {
126 30
            $controllerContext = $this->renderingContext->getControllerContext();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TYPO3Fluid\Fluid\Core\Re...nderingContextInterface as the method getControllerContext() does only exist in the following implementations of said interface: Nimut\TestingFramework\R...RenderingContextFixture, TYPO3\CMS\Fluid\Core\Rendering\RenderingContext, TYPO3\CMS\Fluid\Tests\Un...RenderingContextFixture, TYPO3\CMS\Fluid\Tests\Un...RenderingContextFixture.

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...
127 30
        }
128 30
129 30
        if (!$controllerContext instanceof SolrControllerContext) {
130 30
            throw new \InvalidArgumentException('No valid SolrControllerContext found', 1512998673);
131 30
        }
132
133
        return $controllerContext;
134 30
    }
135
136
    /**
137
     * Initialize the Widget Context, before the Render method is called.
138
     *
139
     * @return void
140
     */
141
    private function initializeWidgetContext()
0 ignored issues
show
Bug introduced by
Consider using a different method name as you override a private method of the parent class.

Overwriting private methods is generally fine as long as you also use private visibility. It might still be preferable for understandability to use a different method name.

Loading history...
142
    {
143 30
        $this->widgetContext->setWidgetConfiguration($this->getWidgetConfiguration());
144
        $this->initializeWidgetIdentifier();
145 30
        $this->widgetContext->setControllerObjectName(get_class($this->controller));
146 30
147 30
        $extensionName = $this->getControllerContext()->getRequest()->getControllerExtensionName();
148
        $pluginName = $this->getControllerContext()->getRequest()->getPluginName();
149
        $this->widgetContext->setParentExtensionName($extensionName);
150 30
        $this->widgetContext->setParentPluginName($pluginName);
151 30
        $pluginNamespace = $this->extensionService->getPluginNamespace($extensionName, $pluginName);
152
        $this->widgetContext->setParentPluginNamespace($pluginNamespace);
153
        $this->widgetContext->setWidgetViewHelperClassName(get_class($this));
154
155
        if ($this->ajaxWidget === true) {
156
            $this->ajaxWidgetContextHolder->store($this->widgetContext);
157
        }
158
    }
159 30
160
    /**
161 30
     * Stores the syntax tree child nodes in the Widget Context, so they can be
162
     * rendered with <f:widget.renderChildren> lateron.
163
     *
164
     * @param array $childNodes The SyntaxTree Child nodes of this ViewHelper.
165
     * @return void
166
     */
167
    public function setChildNodes(array $childNodes)
168
    {
169
        $rootNode = $this->objectManager->get(RootNode::class);
170
        foreach ($childNodes as $childNode) {
171 30
            $rootNode->addChildNode($childNode);
172
        }
173 30
        $this->widgetContext->setViewHelperChildNodes($rootNode, $this->renderingContext);
174
    }
175
176
    /**
177
     * Generate the configuration for this widget. Override to adjust.
178
     *
179
     * @return array
180 30
     * @api
181 30
     */
182 30
    protected function getWidgetConfiguration()
183 30
    {
184 30
        return $this->arguments;
185 30
    }
186
187
    /**
188
     * Initiate a sub request to $this->controller. Make sure to fill $this->controller
189
     * via Dependency Injection.
190
     *
191
     * @return \TYPO3\CMS\Extbase\Mvc\ResponseInterface the response of this request.
192
     * @throws MissingControllerException
193
     */
194 30
    protected function initiateSubRequest()
195
    {
196 30
        if (!$this->controller instanceof AbstractWidgetController) {
197 30
            if (isset($this->controller)) {
198 30
                throw new MissingControllerException('initiateSubRequest() can not be called if there is no valid controller extending TYPO3\\CMS\\Fluid\\Core\\Widget\\AbstractWidgetController. Got "' . get_class($this->controller) . '" in class "' . get_class($this) . '".', 1289422564);
199
            }
200
            throw new MissingControllerException('initiateSubRequest() can not be called if there is no controller inside $this->controller. Make sure to add a corresponding injectController method to your WidgetViewHelper class "' . get_class($this) . '".', 1284401632);
201
        }
202 30
            /** @var $subRequest \ApacheSolrForTypo3\Solr\Widget\WidgetRequest */
203
        $subRequest = $this->objectManager->get(SolrFluidWidgetRequest::class);
204 30
        $subRequest->setWidgetContext($this->widgetContext);
205
206
        $this->passArgumentsToSubRequest($subRequest);
207
        $subResponse = $this->objectManager->get(Response::class);
208
        $this->controller->processRequest($subRequest, $subResponse);
209
        return $subResponse;
210
    }
211
212
    /**
213
     * Pass the arguments of the widget to the subrequest.
214 30
     *
215
     * @param CoreWidgetRequest $subRequest
216 30
     * @return void
217 30
     */
218
    private function passArgumentsToSubRequest(CoreWidgetRequest $subRequest)
0 ignored issues
show
Bug introduced by
Consider using a different method name as you override a private method of the parent class.

Overwriting private methods is generally fine as long as you also use private visibility. It might still be preferable for understandability to use a different method name.

Loading history...
219
    {
220
        $arguments = $this->getControllerContext()->getRequest()->getArguments();
221
222
        if (isset($arguments)) {
223
            if (isset($arguments['action'])) {
224
                $subRequest->setControllerActionName($arguments['action']);
225
                unset($arguments['action']);
226
            }
227
            $subRequest->setArguments($arguments);
228
        }
229
    }
230
231
    /**
232
     * The widget identifier is unique on the current page, and is used
233
     * in the URI as a namespace for the widget's arguments.
234
     *
235
     * @return string the widget identifier for this widget
236
     * @return void
237
     * @todo clean up, and make it somehow more routing compatible.
238
     */
239
    private function initializeWidgetIdentifier()
0 ignored issues
show
Bug introduced by
Consider using a different method name as you override a private method of the parent class.

Overwriting private methods is generally fine as long as you also use private visibility. It might still be preferable for understandability to use a different method name.

Loading history...
240
    {
241
        $this->widgetContext->setWidgetIdentifier('');
242
    }
243
}
244