Passed
Push — master ( d10e8c...c6bdb0 )
by Joni
03:28
created

Attribute::castValues()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 17
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 13
dl 0
loc 17
ccs 13
cts 13
cp 1
rs 9.8333
c 0
b 0
f 0
cc 3
nc 2
nop 1
crap 3
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace Sop\X501\ASN1;
6
7
use Sop\ASN1\Type\Constructed\Sequence;
8
use Sop\ASN1\Type\Constructed\Set;
9
use Sop\ASN1\Type\UnspecifiedType;
10
use Sop\X501\ASN1\AttributeValue\AttributeValue;
11
use Sop\X501\ASN1\Feature\TypedAttribute;
12
13
/**
14
 * Implements *Attribute* ASN.1 type.
15
 *
16
 * @see https://www.itu.int/ITU-T/formal-language/itu-t/x/x501/2012/InformationFramework.html#InformationFramework.Attribute
17
 */
18
class Attribute implements \Countable, \IteratorAggregate
19
{
20
    use TypedAttribute;
21
22
    /**
23
     * Attribute values.
24
     *
25
     * @var AttributeValue[]
26
     */
27
    protected $_values;
28
29
    /**
30
     * Constructor.
31
     *
32
     * @param AttributeType  $type      Attribute type
33
     * @param AttributeValue ...$values Attribute values
34
     */
35 6
    public function __construct(AttributeType $type, AttributeValue ...$values)
36
    {
37
        // check that attribute values have correct oid
38 6
        array_walk($values,
39
            function (AttributeValue $value) use ($type) {
40 5
                if ($value->oid() !== $type->oid()) {
41 1
                    throw new \LogicException('Attribute OID mismatch.');
42
                }
43 6
            });
44 5
        $this->_type = $type;
45 5
        $this->_values = $values;
46 5
    }
47
48
    /**
49
     * Initialize from ASN.1.
50
     *
51
     * @param Sequence $seq
52
     *
53
     * @return self
54
     */
55 1
    public static function fromASN1(Sequence $seq): self
56
    {
57 1
        $type = AttributeType::fromASN1($seq->at(0)->asObjectIdentifier());
58 1
        $values = array_map(
59
            function (UnspecifiedType $el) use ($type) {
60 1
                return AttributeValue::fromASN1ByOID($type->oid(), $el);
61 1
            }, $seq->at(1)->asSet()->elements());
62 1
        return new self($type, ...$values);
63
    }
64
65
    /**
66
     * Convenience method to initialize from attribute values.
67
     *
68
     * @param AttributeValue ...$values One or more values
69
     *
70
     * @throws \LogicException
71
     *
72
     * @return self
73
     */
74 5
    public static function fromAttributeValues(AttributeValue ...$values): self
75
    {
76
        // we need at least one value to determine OID
77 5
        if (!count($values)) {
78 1
            throw new \LogicException('No values.');
79
        }
80 4
        $oid = reset($values)->oid();
81 4
        return new self(new AttributeType($oid), ...$values);
82
    }
83
84
    /**
85
     * Get first value of the attribute.
86
     *
87
     * @throws \LogicException
88
     *
89
     * @return AttributeValue
90
     */
91 3
    public function first(): AttributeValue
92
    {
93 3
        if (!count($this->_values)) {
94 1
            throw new \LogicException('Attribute contains no values.');
95
        }
96 2
        return $this->_values[0];
97
    }
98
99
    /**
100
     * Get all values.
101
     *
102
     * @return AttributeValue[]
103
     */
104 1
    public function values(): array
105
    {
106 1
        return $this->_values;
107
    }
108
109
    /**
110
     * Generate ASN.1 structure.
111
     *
112
     * @return Sequence
113
     */
114 1
    public function toASN1(): Sequence
115
    {
116 1
        $values = array_map(
117
            function (AttributeValue $value) {
118 1
                return $value->toASN1();
119 1
            }, $this->_values);
120 1
        $valueset = new Set(...$values);
121 1
        return new Sequence($this->_type->toASN1(), $valueset->sortedSetOf());
122
    }
123
124
    /**
125
     * Cast attribute values to another AttributeValue class.
126
     *
127
     * This method is generally used to cast UnknownAttributeValue values
128
     * to specific objects when class is declared outside this package.
129
     *
130
     * The new class must be derived from AttributeValue and have the same OID
131
     * as current attribute values.
132
     *
133
     * @param string $cls AttributeValue class name
134
     *
135
     * @throws \LogicException
136
     *
137
     * @return self
138
     */
139 3
    public function castValues(string $cls): self
140
    {
141 3
        $refl = new \ReflectionClass($cls);
142 3
        if (!$refl->isSubclassOf(AttributeValue::class)) {
143 1
            throw new \LogicException(
144 1
                "{$cls} must be derived from " . AttributeValue::class . '.');
145
        }
146 2
        $oid = $this->oid();
147 2
        $values = array_map(
148
            function (AttributeValue $value) use ($cls, $oid) {
149 2
                $value = $cls::fromSelf($value);
150 2
                if ($value->oid() !== $oid) {
151 1
                    throw new \LogicException('Attribute OID mismatch.');
152
                }
153 1
                return $value;
154 2
            }, $this->_values);
155 1
        return self::fromAttributeValues(...$values);
156
    }
157
158
    /**
159
     * @see \Countable::count()
160
     *
161
     * @return int
162
     */
163 1
    public function count(): int
164
    {
165 1
        return count($this->_values);
166
    }
167
168
    /**
169
     * @see \IteratorAggregate::getIterator()
170
     *
171
     * @return \ArrayIterator
172
     */
173 1
    public function getIterator(): \ArrayIterator
174
    {
175 1
        return new \ArrayIterator($this->_values);
176
    }
177
}
178