-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathParityComparator.php
More file actions
130 lines (115 loc) · 3.97 KB
/
ParityComparator.php
File metadata and controls
130 lines (115 loc) · 3.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
<?php
namespace Icecave\Parity\Comparator;
use Icecave\Parity\AnyComparable;
use Icecave\Parity\RestrictedComparable;
use Icecave\Parity\SelfComparable;
use Icecave\Parity\SubClassComparable;
use ReflectionMethod;
/**
* A comparator that dispatches comparison operations to the appropriate
* algorithm based on the comparable interfaces that the operands implement.
*/
class ParityComparator implements Comparator
{
/**
* @param Comparator $fallbackComparator The comparator to use when the operands do not provide their own comparison algorithm.
*/
public function __construct(Comparator $fallbackComparator)
{
$this->fallbackComparator = $fallbackComparator;
$this->compareImplementationClasses = [];
}
/**
* Fetch the fallback comparator.
*
* @return Comparator The comparator to use when the operands do not provide their own comparison algorithm.
*/
public function fallbackComparator(): Comparator
{
return $this->fallbackComparator;
}
/**
* Compare two values, yielding a result according to the following table:
*
* +--------------------+---------------+
* | Condition | Result |
* +--------------------+---------------+
* | $this == $value | $result === 0 |
* | $this < $value | $result < 0 |
* | $this > $value | $result > 0 |
* +--------------------+---------------+
*
* If either of the operands implements one of the Parity comparator
* interfaces and is able to perform the comparison to the other operand
* its compare() method is used to perform the comparison. If neither
* operand provides a suitable implementation, the fallback comparator is
* used.
*
* @param mixed $lhs The first value to compare.
* @param mixed $rhs The second value to compare.
*
* @return int The result of the comparison.
*/
public function compare($lhs, $rhs): int
{
if ($this->canCompare($lhs, $rhs)) {
return $lhs->compare($rhs);
} elseif ($this->canCompare($rhs, $lhs)) {
return -$rhs->compare($lhs);
}
return $this->fallbackComparator()->compare($lhs, $rhs);
}
/**
* An alias for compare().
*
* @param mixed $lhs The first value to compare.
* @param mixed $rhs The second value to compare.
*
* @return int The result of the comparison.
*/
public function __invoke($lhs, $rhs): int
{
return $this->compare($lhs, $rhs);
}
/**
* Check if one value can be compared to another.
*
* @param mixed $lhs The first value to compare.
* @param mixed $rhs The second value to compare.
*
* @return bool
*/
protected function canCompare($lhs, $rhs): bool
{
if ($lhs instanceof AnyComparable) {
return true;
} elseif ($lhs instanceof RestrictedComparable && $lhs->canCompare($rhs)) {
return true;
} elseif ($lhs instanceof SelfComparable) {
return is_object($rhs)
&& get_class($lhs) === get_class($rhs);
} elseif ($lhs instanceof SubClassComparable) {
$className = $this->compareImplementationClass($lhs);
return $rhs instanceof $className;
}
return false;
}
/**
* @param mixed $value
*
* @return string
*/
protected function compareImplementationClass($value): string
{
$className = get_class($value);
if (array_key_exists($className, $this->compareImplementationClasses)) {
return $this->compareImplementationClasses[$className];
}
$reflector = new ReflectionMethod($value, 'compare');
$declaringClassName = $reflector->getDeclaringClass()->getName();
$this->compareImplementationClasses[$className] = $declaringClassName;
return $declaringClassName;
}
private $fallbackComparator;
private $compareImplementationClasses;
}