HtmlElement::getClass()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 2
eloc 2
nc 2
nop 0
1
<?php
2
/**
3
 * Sake
4
 *
5
 * @link      http://github.com/sandrokeil/HtmlElement for the canonical source repository
6
 * @copyright Copyright (c) 2014-2017 Sandro Keil
7
 * @license   http://github.com/sandrokeil/HtmlElement/blob/master/LICENSE.txt New BSD License
8
 */
9
10
namespace Sake\HtmlElement\View\Helper;
11
12
use Zend\Escaper\Escaper;
13
use Zend\View\Helper\AbstractHelper;
14
15
/**
16
 * HtmlElement view helper
17
 *
18
 * This view helper creates a html tag object which can be configured and rendered.
19
 */
20
class HtmlElement extends AbstractHelper
21
{
22
    /**
23
     * Tag type e.g. div
24
     *
25
     * @var string
26
     */
27
    protected $tag;
28
29
    /**
30
     * Tag content
31
     *
32
     * @var string
33
     */
34
    protected $text = '';
35
36
    /**
37
     * Array of attribute / value pair
38
     *
39
     * @var array
40
     */
41
    protected $attributes = array();
42
43
    /**
44
     * Escape html
45
     *
46
     * @var bool
47
     */
48
    protected $renderHtml = false;
49
50
    /**
51
     * List of self closing tags for valid html
52
     *
53
     * @var array
54
     */
55
    protected $singletonTags = array(
56
        'area',
57
        'base',
58
        'br',
59
        'col',
60
        'command',
61
        'embed',
62
        'hr',
63
        'img',
64
        'input',
65
        'keygen',
66
        'link',
67
        'meta',
68
        'param',
69
        'source',
70
        'track',
71
        'wbr',
72
    );
73
74
    /**
75
     * Html/text escaper
76
     *
77
     * @var Escaper
78
     */
79
    protected $escaper;
80
81
    /**
82
     * Should be html attributes escaped
83
     *
84
     * @var bool
85
     */
86
    protected $escapeHtmlAttribute = true;
87
88
    /**
89
     * Should be text escaped
90
     *
91
     * @var bool
92
     */
93
    protected $escapeText = true;
94
95
    /**
96
     * Create html object with provided settings.
97
     *
98
     * @param string $tag Html tag
99
     * @param string $text Html content
100
     * @param array $attributes Html attributes
101
     * @param bool $renderHtml Escape html
102
     * @return HtmlElement
103
     */
104
    public function __invoke($tag, $text = '', array $attributes = array(), $renderHtml = false)
105
    {
106
        $html = clone $this;
107
108
        $html->setTag($tag);
109
        $html->setText($text);
110
        $html->setAttributes($attributes);
111
        $html->enableHtml($renderHtml);
112
        return $html;
113
    }
114
115
    /**
116
     * Renders element. Triggers an error instead of throw an exception, because it is not allowed here.
117
     *
118
     * @return string
119
     */
120
    public function __toString()
121
    {
122
        try {
123
            $html = $this->render();
124
        } catch (\Exception $exception) {
125
            trigger_error($exception, E_USER_WARNING);
126
            $html = '';
127
        }
128
        return $html;
129
    }
130
131
    /**
132
     * Alias for __toString
133
     *
134
     * @return string
135
     */
136
    public function toString()
137
    {
138
        return $this->__toString();
139
    }
140
141
    /**
142
     * Returns tag
143
     *
144
     * @return string
145
     */
146
    public function getTag()
147
    {
148
        return $this->tag;
149
    }
150
151
    /**
152
     * Returns the current text (content) of tag
153
     *
154
     * @return string
155
     */
156
    public function getText()
157
    {
158
        return $this->text;
159
    }
160
161
    /**
162
     * Returns an array of html attribute / value pair for the current html element
163
     *
164
     * @return array
165
     */
166
    public function getAttributes()
167
    {
168
        return $this->attributes;
169
    }
170
171
    /**
172
     * Sets an array of html attributes / value pair
173
     *
174
     * @param array $attributes Html attributes
175
     * @return HtmlElement
176
     */
177
    public function setAttributes(array $attributes)
178
    {
179
        $this->attributes = $attributes;
180
        return $this;
181
    }
182
183
    /**
184
     * Sets html tag
185
     *
186
     * @param string $tag
187
     * @return HtmlElement
188
     */
189
    public function setTag($tag)
190
    {
191
        $this->tag = (string) $tag;
192
        return $this;
193
    }
194
195
    /**
196
     * Sets tag text (content)
197
     *
198
     * @param string $text
199
     * @return HtmlElement
200
     */
201
    public function setText($text)
202
    {
203
        $this->text = (string) $text;
204
        return $this;
205
    }
206
207
    /**
208
     * Appends text
209
     *
210
     * @param string $text
211
     * @return HtmlElement
212
     */
213
    public function appendText($text)
214
    {
215
        $this->text .= (string) $text;
216
        return $this;
217
    }
218
219
    /**
220
     * Returns css classes
221
     *
222
     * @return string
223
     */
224
    public function getClass()
225
    {
226
        return isset($this->attributes['class']) ? $this->attributes['class'] : '';
227
    }
228
229
    /**
230
     * Sets a css class for current element. This method overrides all existing css classes. To append css classes
231
     * use appendClass()
232
     *
233
     * @param string $class Css class
234
     * @return HtmlElement
235
     */
236
    public function setClass($class)
237
    {
238
        $this->attributes['class'] = $class;
239
        return $this;
240
    }
241
242
    /**
243
     * Appends a css class to current element.
244
     *
245
     * @param string $class Css class
246
     * @return HtmlElement
247
     */
248
    public function appendClass($class)
249
    {
250
        if (!isset($this->attributes['class'])) {
251
            $this->attributes['class'] = '';
252
        } else {
253
            $this->attributes['class'] .= ' ';
254
        }
255
        $this->attributes['class'] .= (string) $class;
256
        return $this;
257
    }
258
259
    /**
260
     * Returns id attribute of current element
261
     *
262
     * @return string
263
     */
264
    public function getId()
265
    {
266
        return isset($this->attributes['id']) ? $this->attributes['id'] : '';
267
    }
268
269
    /**
270
     * Sets id attribute for current element
271
     *
272
     * @param string $id Name
273
     * @return HtmlElement
274
     */
275
    public function setId($id)
276
    {
277
        $this->attributes['id'] = (string) $id;
278
        return $this;
279
    }
280
281
    /**
282
     * Return true or false to indicate escaping of text
283
     *
284
     * @return bool
285
     */
286
    public function useHtml()
287
    {
288
        return $this->renderHtml;
289
    }
290
291
    /**
292
     * Turn on / off html rendering text
293
     *
294
     * @param bool $renderHtml Render html or escape content
295
     * @return HtmlElement
296
     */
297
    public function enableHtml($renderHtml)
298
    {
299
        $this->renderHtml = (bool) $renderHtml;
300
        return $this;
301
    }
302
303
    /**
304
     * Builds html attributes
305
     *
306
     * @return string
307
     */
308
    protected function buildAttributes()
309
    {
310
        $attributes = '';
311
312
        foreach ($this->attributes as $key => $value) {
313
            $attributes .= ' ' . $key . '="' . $this->escapeHtmlAttribute($value) . '"';
314
        }
315
        return $attributes;
316
    }
317
318
    /**
319
     * Renders html element
320
     *
321
     * @return string
322
     */
323
    public function render()
324
    {
325
        if (in_array($this->tag, $this->singletonTags)) {
326
            // strict html 5 is omitted for backward compatibility, html 5 is still valid
327
            return '<' . $this->tag . $this->buildAttributes() . ' />';
328
        }
329
330
        if (false === $this->renderHtml) {
331
            $text = $this->escapeText($this->text);
332
        } else {
333
            $text = $this->text;
334
        }
335
        return '<' . $this->tag . $this->buildAttributes() . '>' . $text . '</' . $this->tag . '>';
336
    }
337
338
    /**
339
     * Returns if html attributes should be escaped
340
     *
341
     * @return boolean
342
     */
343
    public function isEscapeHtmlAttribute()
344
    {
345
        return $this->escapeHtmlAttribute;
346
    }
347
348
    /**
349
     * Enable/disable escaping of html attributes
350
     *
351
     * @param boolean $escapeHtmlAttributes
352
     * @return HtmlElement
353
     */
354
    public function setEscapeHtmlAttribute($escapeHtmlAttributes)
355
    {
356
        $this->escapeHtmlAttribute = (bool) $escapeHtmlAttributes;
357
        return $this;
358
    }
359
360
    /**
361
     * Returns if text should be escaped
362
     *
363
     * @return boolean
364
     */
365
    public function isEscapeText()
366
    {
367
        return $this->escapeText;
368
    }
369
370
    /**
371
     * Enable/disable escaping of text
372
     *
373
     * @param boolean $escapeText
374
     * @return HtmlElement
375
     */
376
    public function setEscapeText($escapeText)
377
    {
378
        $this->escapeText = (bool) $escapeText;
379
        return $this;
380
    }
381
382
    /**
383
     * Returns escaper for html attributes, if no one is set, escaper of view will be used
384
     *
385
     * @return string
386
     */
387
    protected function escapeHtmlAttribute($value)
388
    {
389
        if (false === $this->escapeHtmlAttribute) {
390
            return $value;
391
        }
392
        return $this->getEscaper()->escapeHtmlAttr($value);
393
    }
394
395
    /**
396
     * Returns escaper for html, if no one is set, escaper of view will be used
397
     *
398
     * @return string
399
     */
400
    protected function escapeText($value)
401
    {
402
        if (false === $this->escapeText) {
403
            return $value;
404
        }
405
        return $this->getEscaper()->escapeHtml($value);
406
    }
407
408
    /**
409
     * Sets escaper for html
410
     *
411
     * @param Escaper $helperEscapeHtml
412
     * @return HtmlElement
413
     */
414
    public function setEscaper(Escaper $helperEscapeHtml)
415
    {
416
        $this->escaper = $helperEscapeHtml;
417
        return $this;
418
    }
419
420
    /**
421
     * Returns escaper for html, if no one is set, lazy loads escaper from view
422
     *
423
     * @return Escaper
424
     */
425
    protected function getEscaper()
426
    {
427
        if (null === $this->escaper) {
428
            $this->escaper = $this->getView()->plugin('escapehtml')->getEscaper();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Zend\View\Renderer\RendererInterface as the method plugin() does only exist in the following implementations of said interface: Zend\View\Renderer\PhpRenderer.

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...
429
        }
430
        return $this->escaper;
431
    }
432
}
433