Completed
Push — master ( 936f9a...d65c6b )
by Ondřej
02:46
created

Composite::offsetGet()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
namespace Ivory\Value;
4
5
use Ivory\Exception\IncomparableException;
6
use Ivory\Value\Alg\IComparable;
7
use Ivory\Value\Alg\ComparisonUtils;
8
9
/**
10
 * A composite value of several attributes.
11
 *
12
 * The attribute values are accessible as dynamic properties. E.g., attribute "foo" is accessed using `$val->foo`.
13
 * Besides, the composite value is `Traversable` - its attribute names and values are returned during traversal.
14
 *
15
 * Note the composite value is immutable, i.e., once constructed, its values cannot be changed.
16
 *
17
 * @internal Ivory design note: Although Composite seems similar to Tuple and using the same class for both might look
18
 * reasonable, there is a significant difference: in Composite, each attribute name is unique, whereas in Tuple,
19
 * multiple columns might have a same name. Moreover, it is customary to access tuples with array indices, while
20
 * composite attributes should only be accessed through attribute names. Hence, two different classes.
21
 *
22
 * @see http://www.postgresql.org/docs/9.4/static/rowtypes.html
23
 */
24
class Composite implements IComparable, \IteratorAggregate
25
{
26
    /** @var array map: attribute name => value */
27
    private $values;
28
29
    /**
30
     * Creates a new composite value out of a map of attribute names to the corresponding values.
31
     *
32
     * Attributes not mentioned in the given map will be considered as `null`.
33
     *
34
     * @param array $valueMap map: attribute name => value; unspecified attributes get a <tt>null</tt> value
35
     * @return Composite
36
     */
37
    public static function fromMap(array $valueMap): Composite
38
    {
39
        return new Composite($valueMap);
40
    }
41
42
    private function __construct(array $valueMap)
43
    {
44
        $this->values = $valueMap;
45
    }
46
47
    /**
48
     * @return array ordered map: attribute name => attribute value
49
     */
50
    public function toMap(): array
51
    {
52
        return $this->values;
53
    }
54
55
    //region dynamic properties
56
57
    /**
58
     * @param string $name attribute name
59
     * @return mixed value of the given attribute, or <tt>null</tt> if no such attribute is defined on this value
60
     */
61
    public function __get($name)
62
    {
63
        return ($this->values[$name] ?? null);
64
    }
65
66
    /**
67
     * @param string $name attribute name
68
     * @return bool whether the attribute is defined and non-null on this value
69
     */
70
    public function __isset($name)
71
    {
72
        return isset($this->values[$name]);
73
    }
74
75
    //endregion
76
77
    //region IComparable
78
79
    public function equals($other): bool
80
    {
81
        if (!$other instanceof Composite) {
82
            return false;
83
        }
84
85
        $otherValues = $other->values;
86
87
        if (count($this->values) != count($otherValues)) {
88
            return false;
89
        }
90
91
        foreach ($this->values as $name => $thisVal) {
92
            if (!ComparisonUtils::equals($thisVal, ($otherValues[$name] ?? null))) {
93
                return false;
94
            }
95
        }
96
97
        return true;
98
    }
99
100
    public function compareTo($other): int
101
    {
102
        if ($other === null) {
103
            throw new \InvalidArgumentException();
104
        }
105
        if (!$other instanceof Composite) {
106
            throw new IncomparableException('$other is not a ' . Composite::class);
107
        }
108
109
        $otherValues = $other->values;
110
111
        if (count($this->values) != count($otherValues)) {
112
            throw new IncomparableException('$other contains different attributes');
113
        }
114
115
        foreach ($this->values as $name => $thisVal) {
116
            $otherVal = ($otherValues[$name] ?? null);
117
            $cmp = ComparisonUtils::compareValues($thisVal, $otherVal);
118
            if ($cmp != 0) {
119
                return $cmp;
120
            }
121
        }
122
123
        return 0;
124
    }
125
126
    //endregion
127
128
    //region IteratorAggregate
129
130
    public function getIterator()
131
    {
132
        return new \ArrayIterator($this->values);
133
    }
134
135
    //endregion
136
}
137