Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
AttributeWithValue
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
4 / 4
12
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 confirmParameterCount
n/a
0 / 0
n/a
0 / 0
0
 getValue
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setValue
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
8
 render
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace pvc\html\attribute;
4
5use pvc\html\err\InvalidAttributeValueException;
6use pvc\html\err\UnsetAttributeValueException;
7use pvc\interfaces\html\attribute\AttributeWithValueInterface;
8use pvc\interfaces\html\attribute\DataType;
9use pvc\interfaces\validator\ValTesterInterface;
10use pvc\validator\val_tester\null_empty\IsNotEmptyTester;
11
12/**
13 * @phpstan-import-type ValueType from AttributeWithValueInterface
14 */
15abstract class AttributeWithValue extends Attribute
16{
17    /**
18     * @var DataType
19     */
20    protected DataType $dataType;
21
22    /**
23     * @var bool
24     * many (most?) attributes values are not case-sensitive, but some are.  A good example is the 'id'
25     * attribute.
26     */
27    protected bool $isCaseSensitive;
28
29
30    /**
31     * @var ValTesterInterface<ValueType>
32     *
33     * Attributes usually accept values whose validity can be determined in a context free manner.  But there
34     * are a few values which are context-sensitive.  For example, the 'for' attribute has value(s) which are id(s)
35     * of other elements within the same structural block.  The ValTester object is a context free tester, because
36     * it knows nothing about other attributes and elements which are outside its own scope.
37     */
38    protected ValTesterInterface $tester;
39
40    /**
41     * @var array<ValueType>
42     * multivalued attributes and custom attributes can have more than one
43     * parameter.  All values are stored in array for all attribute types.  For
44     * singlevalued attributes and void attributes, the length of the array is 1.
45     */
46    protected array $values;
47
48    /**
49     * @param  string  $name
50     * @param  DataType $dataType
51     * @param  bool  $caseSensitive
52     * @param  ValTesterInterface<ValueType>|null  $tester
53     */
54    public function __construct(
55        string $name,
56        DataType $dataType,
57        ?bool $caseSensitive = null,
58        ?ValTesterInterface $tester = null,
59    ) {
60        parent::__construct($name);
61        $this->dataType = $dataType;
62        $this->isCaseSensitive = $caseSensitive ?? false;
63        $this->tester = $tester ?? new IsNotEmptyTester();
64    }
65
66    /**
67     * @param array<ValueType> $values
68     *
69     * @return void
70     *
71     * all attributes must have a value (void attributes have a value of
72     * true) if they are to be rendered.  Subclasses for single-valued and
73     * void attributes confirm the parameter count is exactly 1.
74     */
75    abstract protected function confirmParameterCount(array $values): void;
76
77    /**
78     * @return array<ValueType>|ValueType|null
79     *
80     * most attributes are single-valued.  Keeping the getter in this class makes
81     * testing the abstract class easier (at the moment).  It is overridden
82     * in the multivalued attribute class.
83     */
84    public function getValue()
85    {
86        return $this->values[0] ?? null;
87    }
88    /**
89     * @param  ValueType  ...$values
90     *
91     * @return void
92     * @throws InvalidAttributeValueException
93     */
94    public function setValue(...$values): void
95    {
96        /**
97         * confirm the parameter count.
98         */
99        $this->confirmParameterCount($values);
100
101        /**
102         * confirm the data type and value.  String values are potentially subject
103         * to case conversion, so build a new array of (possibly case-converted)
104         * values before setting the $value attribute in this object
105         */
106        $newValues = [];
107
108        $callback = match ($this->dataType) {
109            DataType::Integer => 'is_integer',
110            DataType::String => 'is_string',
111        };
112
113        foreach ($values as $value) {
114
115            if (!$callback($value)) {
116                throw new InvalidAttributeValueException($this->name, $value);
117            }
118
119            /**
120             * if the value is not case-sensitive, set it to lower case
121             */
122            if (is_string($value) && !$this->isCaseSensitive) {
123                $value = strtolower($value);
124            }
125            /**
126             * value must be validated by the tester.
127             */
128            if (!$this->tester->testValue($value)) {
129                throw new InvalidAttributeValueException($this->getName(), $value);
130            }
131            $newValues[] = $value;
132        }
133        $this->values = $newValues;
134    }
135    /**
136     * @return string
137     */
138    function render(): string
139    {
140        if (null === $this->getValue()) {
141            throw new UnsetAttributeValueException($this->getName());
142        }
143        return $this->name . '=\'' . implode(' ', $this->values) . '\'';
144    }
145}