Completed
Pull Request — master (#329)
by Claus
02:07
created

ViewHelperResolver   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 334
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 42
lcom 1
cbo 1
dl 0
loc 334
rs 8.295
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A getNamespaces() 0 4 1
A addNamespaces() 0 6 2
A resolvePhpNamespaceFromFluidNamespace() 0 16 4
A setNamespaces() 0 7 3
B addNamespace() 0 10 7
A isNamespaceValid() 0 12 3
A isNamespaceValidOrIgnored() 0 16 4
B isNamespaceIgnored() 0 16 6
A resolveViewHelperClassName() 0 8 2
A createViewHelperInstance() 0 5 1
A createViewHelperInstanceFromClassName() 0 4 1
A getArgumentDefinitionsForViewHelper() 0 4 1
C resolveViewHelperName() 0 47 7

How to fix   Complexity   

Complex Class

Complex classes like ViewHelperResolver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ViewHelperResolver, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace TYPO3Fluid\Fluid\Core\ViewHelper;
3
4
/*
5
 * This file belongs to the package "TYPO3 Fluid".
6
 * See LICENSE.txt that was shipped with this package.
7
 */
8
9
use TYPO3Fluid\Fluid\Core\Parser\Exception as ParserException;
10
use TYPO3Fluid\Fluid\Core\Parser\Patterns;
11
12
/**
13
 * Class ViewHelperResolver
14
 *
15
 * Responsible for resolving instances of ViewHelpers and for
16
 * interacting with ViewHelpers; to translate ViewHelper names
17
 * into actual class names and resolve their ArgumentDefinitions.
18
 *
19
 * Replacing this class in for example a framework allows that
20
 * framework to be responsible for creating ViewHelper instances
21
 * and detecting possible arguments.
22
 */
23
class ViewHelperResolver
24
{
25
26
    /**
27
     * @var array
28
     */
29
    protected $resolvedViewHelperClassNames = [];
30
31
    /**
32
     * Namespaces requested by the template being rendered,
33
     * in [shortname => phpnamespace] format.
34
     *
35
     * @var array
36
     */
37
    protected $namespaces = [
38
        'f' => ['TYPO3Fluid\\Fluid\\ViewHelpers']
39
    ];
40
41
    /**
42
     * @return array
43
     */
44
    public function getNamespaces()
45
    {
46
        return $this->namespaces;
47
    }
48
49
    /**
50
     * Add a PHP namespace where ViewHelpers can be found and give
51
     * it an alias/identifier.
52
     *
53
     * The provided namespace can be either a single namespace or
54
     * an array of namespaces, as strings. The identifier/alias is
55
     * always a single, alpha-numeric ASCII string.
56
     *
57
     * Calling this method multiple times with different PHP namespaces
58
     * for the same alias causes that namespace to be *extended*,
59
     * meaning that the PHP namespace you provide second, third etc.
60
     * are also used in lookups and are used *first*, so that if any
61
     * of the namespaces you add contains a class placed and named the
62
     * same way as one that exists in an earlier namespace, then your
63
     * class gets used instead of the earlier one.
64
     *
65
     * Example:
66
     *
67
     * $resolver->addNamespace('my', 'My\Package\ViewHelpers');
68
     * // Any ViewHelpers under this namespace can now be accessed using for example {my:example()}
69
     * // Now, assuming you also have an ExampleViewHelper class in a different
70
     * // namespace and wish to make that ExampleViewHelper override the other:
71
     * $resolver->addNamespace('my', 'My\OtherPackage\ViewHelpers');
72
     * // Now, since ExampleViewHelper exists in both places but the
73
     * // My\OtherPackage\ViewHelpers namespace was added *last*, Fluid
74
     * // will find and use My\OtherPackage\ViewHelpers\ExampleViewHelper.
75
     *
76
     * Alternatively, setNamespaces() can be used to reset and redefine
77
     * all previously added namespaces - which is great for cases where
78
     * you need to remove or replace previously added namespaces. Be aware
79
     * that setNamespaces() also removes the default "f" namespace, so
80
     * when you use this method you should always include the "f" namespace.
81
     *
82
     * @param string $identifier
83
     * @param string|array $phpNamespace
84
     * @return void
85
     */
86
    public function addNamespace($identifier, $phpNamespace)
87
    {
88
        if (!array_key_exists($identifier, $this->namespaces) || $this->namespaces[$identifier] === null) {
89
            $this->namespaces[$identifier] = $phpNamespace === null ? null : (array) $phpNamespace;
90
        } elseif (is_array($phpNamespace)) {
91
            $this->namespaces[$identifier] = array_unique(array_merge($this->namespaces[$identifier], $phpNamespace));
92
        } elseif (isset($this->namespaces[$identifier]) && !in_array($phpNamespace, $this->namespaces[$identifier])) {
93
            $this->namespaces[$identifier][] = $phpNamespace;
94
        }
95
    }
96
97
    /**
98
     * Wrapper to allow adding namespaces in bulk *without* first
99
     * clearing the already added namespaces. Utility method mainly
100
     * used in compiled templates, where some namespaces can be added
101
     * from outside and some can be added from compiled values.
102
     *
103
     * @param array $namespaces
104
     * @return void
105
     */
106
    public function addNamespaces(array $namespaces)
107
    {
108
        foreach ($namespaces as $identifier => $namespace) {
109
            $this->addNamespace($identifier, $namespace);
110
        }
111
    }
112
113
    /**
114
     * Resolves the PHP namespace based on the Fluid xmlns namespace,
115
     * which can be either a URL matching the Patterns::NAMESPACEPREFIX
116
     * and Patterns::NAMESPACESUFFIX rules, or a PHP namespace. When
117
     * namespace is a PHP namespace it is optional to suffix it with
118
     * the "\ViewHelpers" segment, e.g. "My\Package" is as valid to
119
     * use as "My\Package\ViewHelpers" is.
120
     *
121
     * @param string $fluidNamespace
122
     * @return string
123
     */
124
    public function resolvePhpNamespaceFromFluidNamespace($fluidNamespace)
125
    {
126
        $namespace = $fluidNamespace;
127
        $suffixLength = strlen(Patterns::NAMESPACESUFFIX);
128
        $phpNamespaceSuffix = str_replace('/', '\\', Patterns::NAMESPACESUFFIX);
129
        $extractedSuffix = substr($fluidNamespace, 0 - $suffixLength);
130
        if (strpos($fluidNamespace, Patterns::NAMESPACEPREFIX) === 0 && $extractedSuffix === Patterns::NAMESPACESUFFIX) {
131
            // convention assumed: URL starts with prefix and ends with suffix
132
            $namespace = substr($fluidNamespace, strlen(Patterns::NAMESPACEPREFIX));
133
        }
134
        $namespace = str_replace('/', '\\', $namespace);
135
        if (substr($namespace, 0 - strlen($phpNamespaceSuffix)) !== $phpNamespaceSuffix) {
136
            $namespace .= $phpNamespaceSuffix;
137
        }
138
        return $namespace;
139
    }
140
141
    /**
142
     * Set all namespaces as an array of ['identifier' => ['Php\Namespace1', 'Php\Namespace2']]
143
     * namespace definitions. For convenience and legacy support, a
144
     * format of ['identifier' => 'Only\Php\Namespace'] is allowed,
145
     * but will internally convert the namespace to an array and
146
     * allow it to be extended by addNamespace().
147
     *
148
     * Note that when using this method the default "f" namespace is
149
     * also removed and so must be included in $namespaces or added
150
     * after using addNamespace(). Or, add the PHP namespaces that
151
     * belonged to "f" as a new alias and use that in your templates.
152
     *
153
     * Use getNamespaces() to get an array of currently added namespaces.
154
     *
155
     * @param array $namespaces
156
     * @return void
157
     */
158
    public function setNamespaces(array $namespaces)
159
    {
160
        $this->namespaces = [];
161
        foreach ($namespaces as $identifier => $phpNamespace) {
162
            $this->namespaces[$identifier] = $phpNamespace === null ? null : (array) $phpNamespace;
163
        }
164
    }
165
166
    /**
167
     * Validates the given namespaceIdentifier and returns FALSE
168
     * if the namespace is unknown, causing the tag to be rendered
169
     * without processing.
170
     *
171
     * @param string $namespaceIdentifier
172
     * @return boolean TRUE if the given namespace is valid, otherwise FALSE
173
     */
174
    public function isNamespaceValid($namespaceIdentifier)
175
    {
176
        if (strpos($namespaceIdentifier, '.')) {
177
            return true;
178
        }
179
180
        if (!array_key_exists($namespaceIdentifier, $this->namespaces)) {
181
            return false;
182
        }
183
184
        return $this->namespaces[$namespaceIdentifier] !== null;
185
    }
186
187
    /**
188
     * Validates the given namespaceIdentifier and returns FALSE
189
     * if the namespace is unknown and not ignored
190
     *
191
     * @param string $namespaceIdentifier
192
     * @return boolean TRUE if the given namespace is valid, otherwise FALSE
193
     */
194
    public function isNamespaceValidOrIgnored($namespaceIdentifier)
195
    {
196
        if ($this->isNamespaceValid($namespaceIdentifier) === true) {
197
            return true;
198
        }
199
200
        if (array_key_exists($namespaceIdentifier, $this->namespaces)) {
201
            return true;
202
        }
203
204
        if ($this->isNamespaceIgnored($namespaceIdentifier)) {
205
            return true;
206
        }
207
208
        return false;
209
    }
210
211
    /**
212
     * @param string $namespaceIdentifier
213
     * @return boolean
214
     */
215
    public function isNamespaceIgnored($namespaceIdentifier)
216
    {
217
        if (array_key_exists($namespaceIdentifier, $this->namespaces) && $this->namespaces[$namespaceIdentifier] === null) {
218
            return true;
219
        }
220
        foreach (array_keys($this->namespaces) as $existingNamespaceIdentifier) {
221
            if (strpos($existingNamespaceIdentifier, '*') === false) {
222
                continue;
223
            }
224
            $pattern = '/' . str_replace(['.', '*'], ['\\.', '[a-zA-Z0-9\.]*'], $existingNamespaceIdentifier) . '/';
225
            if (preg_match($pattern, $namespaceIdentifier) === 1) {
226
                return true;
227
            }
228
        }
229
        return false;
230
    }
231
232
    /**
233
     * Resolves a ViewHelper class name by namespace alias and
234
     * Fluid-format identity, e.g. "f" and "format.htmlspecialchars".
235
     *
236
     * Looks in all PHP namespaces which have been added for the
237
     * provided alias, starting in the last added PHP namespace. If
238
     * a ViewHelper class exists in multiple PHP namespaces Fluid
239
     * will detect and use whichever one was added last.
240
     *
241
     * If no ViewHelper class can be detected in any of the added
242
     * PHP namespaces a Fluid Parser Exception is thrown.
243
     *
244
     * @param string $namespaceIdentifier
245
     * @param string $methodIdentifier
246
     * @return string|NULL
247
     * @throws ParserException
248
     */
249
    public function resolveViewHelperClassName($namespaceIdentifier, $methodIdentifier)
250
    {
251
        if (!isset($this->resolvedViewHelperClassNames[$namespaceIdentifier][$methodIdentifier])) {
252
            $this->resolvedViewHelperClassNames[$namespaceIdentifier][$methodIdentifier] =
253
                $this->resolveViewHelperName($namespaceIdentifier, $methodIdentifier);
254
        }
255
        return $this->resolvedViewHelperClassNames[$namespaceIdentifier][$methodIdentifier];
256
    }
257
258
    /**
259
     * Can be overridden by custom implementations to change the way
260
     * classes are loaded when the class is a ViewHelper - for
261
     * example making it possible to use a DI-aware class loader.
262
     *
263
     * @param string $namespace
264
     * @param string $viewHelperShortName
265
     * @return ViewHelperInterface
266
     */
267
    public function createViewHelperInstance($namespace, $viewHelperShortName)
268
    {
269
        $className = $this->resolveViewHelperClassName($namespace, $viewHelperShortName);
270
        return $this->createViewHelperInstanceFromClassName($className);
271
    }
272
273
    /**
274
     * Wrapper to create a ViewHelper instance by class name. This is
275
     * the final method called when creating ViewHelper classes -
276
     * overriding this method allows custom constructors, dependency
277
     * injections etc. to be performed on the ViewHelper instance.
278
     *
279
     * @param string $viewHelperClassName
280
     * @return ViewHelperInterface
281
     */
282
    public function createViewHelperInstanceFromClassName($viewHelperClassName)
283
    {
284
        return new $viewHelperClassName();
285
    }
286
287
    /**
288
     * Return an array of ArgumentDefinition instances which describe
289
     * the arguments that the ViewHelper supports. By default, the
290
     * arguments are simply fetched from the ViewHelper - but custom
291
     * implementations can if necessary add/remove/replace arguments
292
     * which will be passed to the ViewHelper.
293
     *
294
     * @param ViewHelperInterface $viewHelper
295
     * @return ArgumentDefinition[]
296
     */
297
    public function getArgumentDefinitionsForViewHelper(ViewHelperInterface $viewHelper)
298
    {
299
        return $viewHelper->prepareArguments();
300
    }
301
302
    /**
303
     * Resolve a viewhelper name.
304
     *
305
     * @param string $namespaceIdentifier Namespace identifier for the view helper.
306
     * @param string $methodIdentifier Method identifier, might be hierarchical like "link.url"
307
     * @return string The fully qualified class name of the viewhelper
308
     */
309
    protected function resolveViewHelperName($namespaceIdentifier, $methodIdentifier)
310
    {
311
        $explodedViewHelperName = explode('.', $methodIdentifier);
312
        if (count($explodedViewHelperName) > 1) {
313
            $className = implode('\\', array_map('ucfirst', $explodedViewHelperName));
314
        } else {
315
            $className = ucfirst($explodedViewHelperName[0]);
316
        }
317
        $classNames = [
318
            $className . 'ViewHelper',
319
            $className
320
        ];
321
322
        if ($this->namespaces[$namespaceIdentifier] ?? false) {
323
            $namespaces = (array) $this->namespaces[$namespaceIdentifier];
324
        } else {
325
            $namespacePrefix = $this->namespaces[$namespaceIdentifier] = str_replace('.', '\\', $namespaceIdentifier);
326
            $namespaces = [
327
                $namespacePrefix . '\\ViewHelpers',
328
                $namespacePrefix
329
            ];
330
        }
331
332
        $checked = [];
333
        foreach (array_reverse($namespaces) as $namespace) {
334
            $namespace = rtrim($namespace, '\\');
335
            foreach ($classNames as $className) {
336
                $name = $namespace . '\\' . $className;
337
                if (class_exists($name) && is_a($name, ViewHelperInterface::class, true)) {
338
                    return $name;
339
                }
340
                $checked[] = $name;
341
            }
342
        }
343
344
        throw new ParserException(
345
            sprintf(
346
                'The ViewHelper "%s:%s" could not be resolved. Fluid checked for "%s" but none of those classes exist.',
347
                $namespaceIdentifier,
348
                $methodIdentifier,
349
                implode(', ', $checked)
350
            ),
351
            1407060572
352
        );
353
354
        return $name;
0 ignored issues
show
Unused Code introduced by
return $name; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
355
    }
356
}
357