Completed
Pull Request — master (#470)
by Claus
06:03
created

AbstractViewHelper::getArguments()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
namespace TYPO3Fluid\Fluid\Core\ViewHelper;
4
5
/*
6
 * This file belongs to the package "TYPO3 Fluid".
7
 * See LICENSE.txt that was shipped with this package.
8
 */
9
10
use Closure;
11
use TYPO3Fluid\Fluid\Component\AbstractComponent;
12
use TYPO3Fluid\Fluid\Component\Argument\ArgumentCollection;
13
use TYPO3Fluid\Fluid\Component\Argument\ArgumentDefinition;
14
use TYPO3Fluid\Fluid\Component\ComponentInterface;
15
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
16
17
/**
18
 * The abstract base class for all view helpers.
19
 */
20
abstract class AbstractViewHelper extends AbstractComponent
21
{
22
    /**
23
     * @var RenderingContextInterface
24
     */
25
    protected $renderingContext;
26
27
    /**
28
     * @var Closure
29
     */
30
    protected $renderChildrenClosure = null;
31
32
    protected $escapeOutput = true;
33
34
    /**
35
     * Execute via Component API implementation.
36
     *
37
     * @param RenderingContextInterface $renderingContext
38
     * @return mixed
39
     */
40
    public function evaluate(RenderingContextInterface $renderingContext)
41
    {
42
        $this->renderingContext = $renderingContext;
43
        $this->getArguments()->setRenderingContext($renderingContext);
44
        return $this->callRenderMethod();
45
    }
46
47
    public function onOpen(RenderingContextInterface $renderingContext): ComponentInterface
48
    {
49
        $this->getArguments()->setRenderingContext($renderingContext);
50
        return parent::onOpen($renderingContext);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return parent::onOpen($renderingContext); (TYPO3Fluid\Fluid\Core\Vi...lper\AbstractViewHelper) is incompatible with the return type declared by the interface TYPO3Fluid\Fluid\Compone...ponentInterface::onOpen of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
51
    }
52
53
    public function getArguments(): ArgumentCollection
54
    {
55
        if ($this->arguments === null) {
56
            $this->arguments = new ArgumentCollection();
57
            $this->initializeArguments();
58
        }
59
        return $this->arguments;
60
    }
61
62
    /**
63
     * Register a new argument. Call this method from your ViewHelper subclass
64
     * inside the initializeArguments() method.
65
     *
66
     * @param string $name Name of the argument
67
     * @param string $type Type of the argument
68
     * @param string $description Description of the argument
69
     * @param boolean $required If TRUE, argument is required. Defaults to FALSE.
70
     * @param mixed $defaultValue Default value of argument
71
     * @return AbstractViewHelper $this, to allow chaining.
72
     * @throws Exception
73
     */
74
    protected function registerArgument(string $name, string $type, string $description, bool $required = false, $defaultValue = null): self
75
    {
76
        $this->getArguments()->addDefinition(new ArgumentDefinition($name, $type, $description, $required, $defaultValue));
77
        return $this;
78
    }
79
80
    /**
81
     * Overrides a registered argument. Call this method from your ViewHelper subclass
82
     * inside the initializeArguments() method if you want to override a previously registered argument.
83
     *
84
     * @param string $name Name of the argument
85
     * @param string $type Type of the argument
86
     * @param string $description Description of the argument
87
     * @param boolean $required If TRUE, argument is required. Defaults to FALSE.
88
     * @param mixed $defaultValue Default value of argument
89
     * @return AbstractViewHelper $this, to allow chaining.
90
     * @throws Exception
91
     * @see registerArgument()
92
     * @deprecated Will be removed in Fluid 4.0
93
     */
94
    protected function overrideArgument(string $name, string $type, string $description, bool $required = false, $defaultValue = null): self
95
    {
96
        $this->getArguments()->addDefinition(new ArgumentDefinition($name, $type, $description, $required, $defaultValue));
97
        return $this;
98
    }
99
100
    /**
101
     * Called when being inside a cached template.
102
     *
103
     * @param Closure $renderChildrenClosure
104
     * @return void
105
     */
106
    public function setRenderChildrenClosure(Closure $renderChildrenClosure)
107
    {
108
        $this->renderChildrenClosure = $renderChildrenClosure;
109
    }
110
111
    /**
112
     * Call the render() method and handle errors.
113
     *
114
     * @return mixed the rendered ViewHelper
115
     * @throws Exception
116
     */
117
    protected function callRenderMethod()
118
    {
119
        if (method_exists($this, 'render')) {
120
            return call_user_func([$this, 'render']);
121
        }
122
        if (method_exists($this, 'renderStatic')) {
123
            // Method is safe to call - will not recurse through ViewHelperInvoker via the default
124
            // implementation of renderStatic() on this class.
125
            return call_user_func_array([static::class, 'renderStatic'], [$this->arguments->getArrayCopy(), $this->buildRenderChildrenClosure(), $this->arguments->getRenderingContext()]);
126
        }
127
        return $this->renderChildren();
128
    }
129
130
    /**
131
     * Helper method which triggers the rendering of everything between the
132
     * opening and the closing tag.
133
     *
134
     * @return mixed The finally rendered child nodes.
135
     */
136
    protected function renderChildren()
137
    {
138
        if ($this->renderChildrenClosure !== null) {
139
            $closure = $this->renderChildrenClosure;
140
            return $closure();
141
        }
142
        return $this->evaluateChildren($this->renderingContext);
143
    }
144
145
    /**
146
     * Helper which is mostly needed when calling renderStatic() from within
147
     * render().
148
     *
149
     * No public API yet.
150
     *
151
     * @return Closure
152
     */
153
    protected function buildRenderChildrenClosure()
154
    {
155
        $self = clone $this;
156
        $renderChildrenClosure = function () use ($self) {
157
            return $self->renderChildren();
158
        };
159
        return $renderChildrenClosure;
160
    }
161
162
    /**
163
     * Initialize all arguments. You need to override this method and call
164
     * $this->registerArgument(...) inside this method, to register all your arguments.
165
     *
166
     * @return void
167
     */
168
    protected function initializeArguments()
169
    {
170
    }
171
172
    public function allowUndeclaredArgument(string $argumentName): bool
173
    {
174
        return false;
175
    }
176
177
    protected function hasArgument(string $argumentName): bool
178
    {
179
        return $this->getArguments()->getRaw($argumentName) !== null;
180
    }
181
}
182