├── .gitignore ├── .php_cs ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── phpunit.xml ├── src ├── AnyComparable.php ├── Comparator │ ├── Comparator.php │ ├── DeepComparator.php │ ├── ObjectIdentityComparator.php │ ├── ParityComparator.php │ ├── PhpComparator.php │ └── StrictPhpComparator.php ├── Exception │ └── NotComparableException.php ├── ExtendedComparable.php ├── ExtendedComparableTrait.php ├── Parity.php ├── RestrictedComparable.php ├── SelfComparable.php └── SubClassComparable.php └── test ├── src ├── ChildObject.php ├── Comparator │ ├── SelfComparableImpl.php │ ├── SelfComparableSubClass.php │ ├── SubClassComparableImpl.php │ └── SubClassComparableSubClass.php ├── ExtendedComparableTraitUser.php └── ParentObject.php └── suite ├── Comparator ├── DeepComparatorTest.php ├── ObjectIdentityComparatorTest.php ├── ParityComparatorTest.php ├── PhpComparatorTest.php └── StrictPhpComparatorTest.php ├── Exception └── NotComparableExceptionTest.php ├── ExtendedComparableTraitTest.php └── ParityTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /artifacts/ 2 | /vendor/ 3 | .phpunit.result.cache 4 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | in(__DIR__) 5 | ->exclude([ 6 | 'artifacts', 7 | 'vendor', 8 | ]); 9 | 10 | return PhpCsFixer\Config::create() 11 | ->setFinder($finder) 12 | ->setCacheFile(__DIR__ . '/artifacts/.php_cs.cache') 13 | ->setRules([ 14 | '@PSR2' => true, 15 | 16 | 'array_syntax' => ['syntax' => 'short'], 17 | 'binary_operator_spaces' => false, 18 | 'blank_line_after_opening_tag' => true, 19 | 'blank_line_before_return' => true, 20 | 'braces' => false, 21 | 'cast_spaces' => true, 22 | 'class_definition' => ['singleLine' => false], 23 | 'concat_space' => ['spacing' => 'one'], 24 | 'declare_equal_normalize' => true, 25 | 'function_typehint_space' => true, 26 | 'hash_to_slash_comment' => true, 27 | 'heredoc_to_nowdoc' => true, 28 | 'include' => true, 29 | 'linebreak_after_opening_tag' => true, 30 | 'lowercase_cast' => true, 31 | 'method_separation' => true, 32 | 'native_function_casing' => true, 33 | 'new_with_braces' => true, 34 | 'no_blank_lines_after_class_opening' => true, 35 | 'no_blank_lines_after_phpdoc' => true, 36 | 'no_empty_comment' => true, 37 | 'no_empty_phpdoc' => true, 38 | 'no_empty_statement' => true, 39 | 'no_extra_consecutive_blank_lines' => true, 40 | 'no_leading_import_slash' => true, 41 | 'no_leading_namespace_whitespace' => true, 42 | 'no_mixed_echo_print' => true, 43 | 'no_multiline_whitespace_before_semicolons' => true, 44 | 'no_short_bool_cast' => true, 45 | 'no_singleline_whitespace_before_semicolons' => true, 46 | 'no_spaces_around_offset' => true, 47 | 'no_trailing_comma_in_singleline_array' => true, 48 | 'no_unneeded_control_parentheses' => true, 49 | 'no_unused_imports' => true, 50 | 'no_whitespace_before_comma_in_array' => true, 51 | 'no_whitespace_in_blank_line' => true, 52 | 'normalize_index_brace' => true, 53 | 'object_operator_without_whitespace' => true, 54 | 'ordered_imports' => true, 55 | 'phpdoc_align' => true, 56 | 'phpdoc_indent' => true, 57 | 'phpdoc_no_package' => true, 58 | 'phpdoc_scalar' => true, 59 | 'phpdoc_to_comment' => true, 60 | 'phpdoc_trim' => true, 61 | 'phpdoc_types' => true, 62 | 'pre_increment' => true, 63 | 'return_type_declaration' => true, 64 | 'self_accessor' => true, 65 | 'short_scalar_cast' => true, 66 | 'single_blank_line_before_namespace' => true, 67 | 'single_quote' => true, 68 | 'space_after_semicolon' => true, 69 | 'standardize_not_equals' => true, 70 | 'ternary_operator_spaces' => true, 71 | 'trailing_comma_in_multiline_array' => true, 72 | 'trim_array_spaces' => true, 73 | 'unary_operator_spaces' => true, 74 | 'whitespace_after_comma_in_array' => true, 75 | ]); 76 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: php 3 | php: 4 | - '7.3' 5 | - '7.3' 6 | - '7.4' 7 | - '8.0' 8 | matrix: 9 | fast_finish: true 10 | allow_failures: 11 | - php: nightly 12 | before_install: 13 | - phpenv config-rm xdebug.ini || true 14 | - "[[ $GITHUB_TOKEN ]] && composer config --global github-oauth.github.com $GITHUB_TOKEN" 15 | install: composer install --no-interaction 16 | script: phpdbg -qrr vendor/bin/phpunit 17 | after_script: bash <(curl -s https://codecov.io/bash) 18 | env: 19 | global: 20 | secure: H0ZWjPYUbKpxE9nR0wA5S3oWG827F7daF473DGi40Afxej5h7kAWe2gcHHZrFxDwXR65RpcRw/kcL9hXld1aNtehZRES7wiktFLqExQTQn/Z5EYal8Jx9qFpq6Wb2Pvs0sF+9DO0QUNiDeVjt7g/fNsahAZ/MKzObIH0swNJOmg= 21 | cache: 22 | directories: 23 | - "$HOME/.composer/cache/files" 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Parity Changelog 2 | 3 | ### 3.0.1 (2021-02-04) 4 | 5 | Please note, as of this release this project intends to support only those 6 | versions of PHP that are listed under the "Currently Supported Versions" section 7 | of https://www.php.net/supported-versions.php. This means that support for older 8 | PHP versions may be dropped in minor or patch releases. 9 | 10 | - Drop support for PHP 7.2 11 | - Add support for PHP 8 12 | 13 | ### 3.0.0 (2020-08-25) 14 | 15 | This release contains several backwards compatibility breaks that will effect 16 | more advanced usage of Parity, however the common usage via `Parity::compare()` 17 | is unchanged. 18 | 19 | - **[BC]** Drop support for PHP 7.1 20 | - **[BC]** Add parameter and return type hints 21 | - **[BC]** Remove `PackageInfo` class 22 | - **[BC]** Rename `ComparatorInterface` to `Comparator` 23 | - **[BC]** Rename `AnyComparableInterface` to `AnyComparable` 24 | - **[BC]** Rename `ExtendedComparableInterface` to `ExtendedComparable` 25 | - **[BC]** Rename `SelfComparableInterface` to `SelfComparable` 26 | - **[BC]** Rename `SubClassComparableInterface` to `SubClassComparable` 27 | - **[BC]** Remove `AbstractExtendedComparable` class, use `ExtendedComparableTrait` instead 28 | 29 | ### 2.0.0 (2018-11-06) 30 | 31 | - **[BC]** Drop support for PHP 5.x and PHP 7.0 32 | - **[NEW]** Added `Parity::min[Sequence]()`, `max[Sequence]()` (thanks [@koden-km](https://github.com/jmalloc)) 33 | 34 | ### 1.0.0 (2014-01-17) 35 | 36 | * Stable release (no API changes since 1.0.0-alpha.2). 37 | 38 | ### 1.0.0-alpha.2 (2013-10-22) 39 | 40 | - **[BC]** `SelfComparableInterface` now requires the comparison operands to be the exactly same type. 41 | - **[NEW]** Added `SubClassComparableInterface`, similar to the previous behavior of `SelfComparableInterface` 42 | 43 | ### 1.0.0-alpha.1 (2013-10-20) 44 | 45 | - **[BC]** Removed `deep` parameter from `Parity::compare()` and related convenience methods 46 | - **[BC]** Removed `ComparableInterface` and `Comparator` 47 | - **[BC]** Removed `AbstractComparable` (replaced with `AbstractExtendedComparable` which does not implement any comparable interface) 48 | - **[NEW]** Added `AnyComparableInterface` and `SelfComparableInterface` 49 | - **[NEW]** Added `ComparatorInterface::__invoke()` 50 | - **[NEW]** Added `ObjectIdentityComparator`, `StrictPhpComparator` and `PhpComparator` 51 | - **[NEW]** Added `ParityComparator` which compares numeric types in a more natural manner 52 | - **[IMPROVED]** Comparators are now use other comparators to handle unsupported comparisons (composition rather than extension) 53 | - **[IMPROVED]** Deep comparison no longer compares arrays by size first, allows for more natural ordering based on elements 54 | 55 | ### 0.1.0 (2013-05-29) 56 | 57 | - Initial release 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013 James Harris 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Parity 2 | 3 | [![Build Status](http://img.shields.io/travis/icecave/parity/master.svg?style=flat-square)](https://travis-ci.org/icecave/parity) 4 | [![Code Coverage](https://img.shields.io/codecov/c/github/icecave/parity/master.svg?style=flat-square)](https://codecov.io/github/icecave/parity) 5 | [![Latest Version](http://img.shields.io/packagist/v/icecave/parity.svg?style=flat-square&label=semver)](https://semver.org) 6 | 7 | **Parity** is a deep comparison library for PHP. 8 | 9 | composer require icecave/parity 10 | 11 | ## Rationale 12 | 13 | PHP does not provide a way to reliably and strictly compare values of heterogeneous types. Most of the built-in 14 | comparison operators perform often undesired [type-juggling](http://php.net/manual/en/language.types.type-juggling.php). 15 | The one exception is the [strict equality operator](http://php.net/manual/en/language.operators.comparison.php), which 16 | has the further caveat that it can only compare objects by their identity. No type-strict mechanism is provided for 17 | comparing objects by their properties; nor are there any type-strict versions of the relative comparison operators 18 | (less-than, greater-than, etc). 19 | 20 | **Parity** aims to fill the void by providing a comparison engine with the following features: 21 | 22 | * Type-strict comparison of arrays and objects by their elements 23 | * Recursion-safe comparison of objects 24 | * Natural, type-strict comparison semantics for built-in types 25 | * Powerful mechanisms for classes to customize comparison behavior 26 | 27 | ## Example 28 | 29 | The **Parity** comparison engine is accessed via static methods on the `Parity` facade class. These methods accept any 30 | types and are guaranteed to produce a deterministic comparison result[1](#caveat1). Some basic examples are 31 | shown below using integers. 32 | 33 | ```php 34 | use Icecave\Parity\Parity; 35 | 36 | // The compare() method provides a strcmp-style comparison, and hence can be 37 | // used as a sorting function for operations such as usort() 38 | assert(Parity::compare(1, 2) < 0); 39 | 40 | // The following methods are convenience methods, implemented on top of compare(). 41 | assert(Parity::isEqualTo(1, 2) === false); 42 | assert(Parity::isNotEqualTo(1, 2) === true); 43 | assert(Parity::isNotEqualTo(1, 2) === true); 44 | assert(Parity::isLessThan(1, 2) === true); 45 | assert(Parity::isLessThanOrEqualTo(1, 2) === true); 46 | assert(Parity::isGreaterThan(1, 2) === false); 47 | assert(Parity::isGreaterThanOrEqualTo(1, 2) === false); 48 | ``` 49 | 50 | ## Concepts 51 | 52 | ### Comparable 53 | 54 | The core concept of **Parity** is the *comparable*. A comparable is any object that provides its own behavior for 55 | comparison with other values. The following refinements of the comparable concept are supported by the comparison engine: 56 | 57 | * [Restricted Comparable](src/RestrictedComparable.php): A comparable that can be queried as to which values it may be compared to. 58 | * [Self Comparable](src/SelfComparable.php): A comparable that may only be compared to other objects of exactly the same type. 59 | * [Sub Class Comparable](src/SubClassComparable.php): A comparable that may only be compared to other objects of the same or a derived type. 60 | * [Any Comparable](src/AnyComparable.php): A comparable that may be freely compared to values of any other type. 61 | 62 | ### Comparator 63 | 64 | A *[Comparator](src/Comparator/Comparator.php)* defines comparison behavior for values other 65 | than itself. **Parity** provides the following comparator implementations: 66 | 67 | * [Parity Comparator](src/Comparator/ParityComparator.php): Implements the logic surrounding the comparable concepts described in the section above. 68 | * [Deep Comparator](src/Comparator/DeepComparator.php): Performs deep comparison of arrays and objects. Object comparison is recursion-safe. 69 | * [Object Identity Comparator](src/Comparator/ObjectIdentityComparator.php): Compares objects by identity. 70 | * [Strict PHP Comparator](src/Comparator/StrictPhpComparator.php): Approximates PHP's strict comparison for the full suite of comparison operations. 71 | * [PHP Comparator](src/Comparator/PhpComparator.php): Exposes the standard PHP comparison behavior as a Parity comparator. 72 | 73 | ## Algorithm Resolution 74 | 75 | The following process is used by `Parity::compare($A, $B)` to determine which comparison algorithm to use: 76 | 77 | Use `$A->compare($B)` if: 78 | 79 | 1. `$A` is [Any Comparable](src/AnyComparable.php); or 80 | 2. `$A` is [Restricted Comparable](src/RestrictedComparable.php) and `$A->canCompare($B)` returns `true`; or 81 | 3. `$A` is [Self Comparable](src/SelfComparable.php) and `$A` is exactly the same type as `$B`; or 82 | 4. `$A` is [Sub Class Comparable](src/SubClassComparable.php) and `$B` is an instance of the class where `$A->compare()` is implemented 83 | 84 | If none of the conditions above are true, the comparison is tried in reverse with `$A` on the right hand side and `$B` 85 | on the left; the result is also inverted. If still no comparison is possible, **Parity** falls back to a strictly-typed 86 | deep comparison. 87 | 88 | When comparing scalar types, integers and doubles (PHP's only true numeric types) are treated as though they were the 89 | same type, such that the expression `3 < 3.5 < 4` holds true. Numeric strings are **not** compared in this way. 90 | 91 | ## Caveats 92 | 93 | 1. Comparison of recursive objects is not a truly deterministic operation as objects are compared 94 | by their [object hash](http://php.net/manual/en/function.spl-object-hash.php) where deeper comparison would otherwise 95 | result in infinite recursion. 96 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "icecave/parity", 3 | "description": "A customizable deep comparison library.", 4 | "keywords": [ 5 | "compare", 6 | "comparison", 7 | "equal", 8 | "equality", 9 | "less", 10 | "greater", 11 | "sort", 12 | "sorting" 13 | ], 14 | "homepage": "https://github.com/icecave/parity", 15 | "license": "MIT", 16 | "authors": [ 17 | { 18 | "name": "James Harris", 19 | "email": "mailjamesharris@gmail.com", 20 | "homepage": "https://github.com/jmalloc" 21 | } 22 | ], 23 | "require": { 24 | "php": ">=7.3", 25 | "icecave/repr": "^4" 26 | }, 27 | "require-dev": { 28 | "eloquent/liberator": "^2", 29 | "friendsofphp/php-cs-fixer": "^2", 30 | "eloquent/phony": "^5", 31 | "eloquent/phony-phpunit": "^7", 32 | "phpunit/phpunit": "^9" 33 | }, 34 | "autoload": { "psr-4": { "Icecave\\Parity\\": "src" } }, 35 | "autoload-dev": { "psr-4": { "Icecave\\Parity\\": "test/src" } }, 36 | "config": { 37 | "sort-packages": true, 38 | "platform": { 39 | "php": "7.3" 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | test/suite 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | src 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/AnyComparable.php: -------------------------------------------------------------------------------- 1 | $value | $result > 0 | 20 | * +--------------------+---------------+ 21 | * 22 | * @param mixed $value The value to compare. 23 | * 24 | * @return int The result of the comparison. 25 | */ 26 | public function compare($value): int; 27 | } 28 | -------------------------------------------------------------------------------- /src/Comparator/Comparator.php: -------------------------------------------------------------------------------- 1 | $value | $result > 0 | 19 | * +--------------------+---------------+ 20 | * 21 | * @param mixed $lhs The first value to compare. 22 | * @param mixed $rhs The second value to compare. 23 | * 24 | * @return int The result of the comparison. 25 | */ 26 | public function compare($lhs, $rhs): int; 27 | 28 | /** 29 | * An alias for compare(). 30 | * 31 | * @param mixed $lhs The first value to compare. 32 | * @param mixed $rhs The second value to compare. 33 | * 34 | * @return int The result of the comparison. 35 | */ 36 | public function __invoke($lhs, $rhs): int; 37 | } 38 | -------------------------------------------------------------------------------- /src/Comparator/DeepComparator.php: -------------------------------------------------------------------------------- 1 | fallbackComparator = $fallbackComparator; 26 | $this->relaxClassComparisons = $relaxClassComparisons; 27 | } 28 | 29 | /** 30 | * Fetch the fallback comparator. 31 | * 32 | * @return Comparator The comparator to use when the operands are not arrays or objects. 33 | */ 34 | public function fallbackComparator(): Comparator 35 | { 36 | return $this->fallbackComparator; 37 | } 38 | 39 | /** 40 | * Compare two values, yielding a result according to the following table: 41 | * 42 | * +--------------------+---------------+ 43 | * | Condition | Result | 44 | * +--------------------+---------------+ 45 | * | $this == $value | $result === 0 | 46 | * | $this < $value | $result < 0 | 47 | * | $this > $value | $result > 0 | 48 | * +--------------------+---------------+ 49 | * 50 | * A deep comparison is performed if both operands are arrays, or both are 51 | * objects; otherwise, the fallback comparator is used. 52 | * 53 | * @param mixed $lhs The first value to compare. 54 | * @param mixed $rhs The second value to compare. 55 | * 56 | * @return int The result of the comparison. 57 | */ 58 | public function compare($lhs, $rhs): int 59 | { 60 | $visitationContext = []; 61 | 62 | return $this->compareValue($lhs, $rhs, $visitationContext); 63 | } 64 | 65 | /** 66 | * An alias for compare(). 67 | * 68 | * @param mixed $lhs The first value to compare. 69 | * @param mixed $rhs The second value to compare. 70 | * 71 | * @return int The result of the comparison. 72 | */ 73 | public function __invoke($lhs, $rhs): int 74 | { 75 | return $this->compare($lhs, $rhs); 76 | } 77 | 78 | /** 79 | * @param mixed $lhs 80 | * @param mixed $rhs 81 | * @param mixed &$visitationContext 82 | * 83 | * @return int The result of the comparison. 84 | */ 85 | protected function compareValue($lhs, $rhs, &$visitationContext): int 86 | { 87 | if (is_array($lhs) && is_array($rhs)) { 88 | return $this->compareArray($lhs, $rhs, $visitationContext); 89 | } elseif (is_object($lhs) && is_object($rhs)) { 90 | return $this->compareObject($lhs, $rhs, $visitationContext); 91 | } 92 | 93 | return $this->fallbackComparator()->compare($lhs, $rhs); 94 | } 95 | 96 | /** 97 | * @param array $lhs 98 | * @param array $rhs 99 | * @param mixed &$visitationContext 100 | * 101 | * @return int The result of the comparison. 102 | */ 103 | protected function compareArray(array $lhs, array $rhs, &$visitationContext): int 104 | { 105 | reset($lhs); 106 | reset($rhs); 107 | 108 | while (true) { 109 | $lk = key($lhs); 110 | $rk = key($rhs); 111 | 112 | if ($lk === null && $rk === null) { 113 | break; 114 | } elseif ($lk === null) { 115 | return -1; 116 | } elseif ($rk === null) { 117 | return +1; 118 | } 119 | 120 | $cmp = $this->compareValue($lk, $rk, $visitationContext); 121 | if ($cmp !== 0) { 122 | return $cmp; 123 | } 124 | 125 | $lv = current($lhs); 126 | $rv = current($rhs); 127 | 128 | $cmp = $this->compareValue($lv, $rv, $visitationContext); 129 | if ($cmp !== 0) { 130 | return $cmp; 131 | } 132 | 133 | next($lhs); 134 | next($rhs); 135 | } 136 | 137 | return 0; 138 | } 139 | 140 | /** 141 | * @param object $lhs 142 | * @param object $rhs 143 | * @param mixed &$visitationContext 144 | * 145 | * @return int The result of the comparison. 146 | */ 147 | protected function compareObject($lhs, $rhs, &$visitationContext): int 148 | { 149 | if ($lhs === $rhs) { 150 | return 0; 151 | } elseif ($this->isNestedComparison($lhs, $rhs, $visitationContext)) { 152 | return strcmp( 153 | spl_object_hash($lhs), 154 | spl_object_hash($rhs) 155 | ); 156 | } elseif (!$this->relaxClassComparisons) { 157 | $diff = strcmp(get_class($lhs), get_class($rhs)); 158 | if ($diff !== 0) { 159 | return $diff; 160 | } 161 | } 162 | 163 | return $this->compareArray( 164 | $this->objectProperties($lhs, $visitationContext), 165 | $this->objectProperties($rhs, $visitationContext), 166 | $visitationContext 167 | ); 168 | } 169 | 170 | /** 171 | * @param object $object 172 | * @param mixed &$visitationContext 173 | * 174 | * @return array 175 | */ 176 | protected function objectProperties($object, &$visitationContext): array 177 | { 178 | $properties = []; 179 | $reflector = new ReflectionObject($object); 180 | 181 | while ($reflector) { 182 | foreach ($reflector->getProperties() as $property) { 183 | if ($property->isStatic()) { 184 | continue; 185 | } 186 | 187 | $key = sprintf( 188 | '%s::%s', 189 | $property->getDeclaringClass()->getName(), 190 | $property->getName() 191 | ); 192 | 193 | $property->setAccessible(true); 194 | $properties[$key] = $property->getValue($object); 195 | } 196 | 197 | $reflector = $reflector->getParentClass(); 198 | } 199 | 200 | return $properties; 201 | } 202 | 203 | /** 204 | * @param mixed $lhs 205 | * @param mixed $rhs 206 | * @param mixed &$visitationContext 207 | * 208 | * @return bool 209 | */ 210 | protected function isNestedComparison($lhs, $rhs, &$visitationContext): bool 211 | { 212 | $key = spl_object_hash($lhs) . ':' . spl_object_hash($rhs); 213 | 214 | if (array_key_exists($key, $visitationContext)) { 215 | return true; 216 | } 217 | 218 | $visitationContext[$key] = true; 219 | 220 | return false; 221 | } 222 | 223 | private $fallbackComparator; 224 | private $relaxClassComparisons; 225 | } 226 | -------------------------------------------------------------------------------- /src/Comparator/ObjectIdentityComparator.php: -------------------------------------------------------------------------------- 1 | fallbackComparator = $fallbackComparator; 16 | } 17 | 18 | /** 19 | * Fetch the fallback comparator. 20 | * 21 | * @return Comparator The comparator to use for non-objects. 22 | */ 23 | public function fallbackComparator(): Comparator 24 | { 25 | return $this->fallbackComparator; 26 | } 27 | 28 | /** 29 | * Compare two values, yielding a result according to the following table: 30 | * 31 | * +--------------------+---------------+ 32 | * | Condition | Result | 33 | * +--------------------+---------------+ 34 | * | $this == $value | $result === 0 | 35 | * | $this < $value | $result < 0 | 36 | * | $this > $value | $result > 0 | 37 | * +--------------------+---------------+ 38 | * 39 | * If either of the operands is not an object the fallback comparator is 40 | * used. 41 | * 42 | * @param mixed $lhs The first value to compare. 43 | * @param mixed $rhs The second value to compare. 44 | * 45 | * @return int The result of the comparison. 46 | */ 47 | public function compare($lhs, $rhs): int 48 | { 49 | if (!is_object($lhs) || !is_object($rhs)) { 50 | return $this->fallbackComparator()->compare($lhs, $rhs); 51 | } 52 | 53 | return strcmp( 54 | spl_object_hash($lhs), 55 | spl_object_hash($rhs) 56 | ); 57 | 58 | } 59 | 60 | /** 61 | * An alias for compare(). 62 | * 63 | * @param mixed $lhs The first value to compare. 64 | * @param mixed $rhs The second value to compare. 65 | * 66 | * @return int The result of the comparison. 67 | */ 68 | public function __invoke($lhs, $rhs): int 69 | { 70 | return $this->compare($lhs, $rhs); 71 | } 72 | 73 | private $fallbackComparator; 74 | } 75 | -------------------------------------------------------------------------------- /src/Comparator/ParityComparator.php: -------------------------------------------------------------------------------- 1 | fallbackComparator = $fallbackComparator; 23 | $this->compareImplementationClasses = []; 24 | } 25 | 26 | /** 27 | * Fetch the fallback comparator. 28 | * 29 | * @return Comparator The comparator to use when the operands do not provide their own comparison algorithm. 30 | */ 31 | public function fallbackComparator(): Comparator 32 | { 33 | return $this->fallbackComparator; 34 | } 35 | 36 | /** 37 | * Compare two values, yielding a result according to the following table: 38 | * 39 | * +--------------------+---------------+ 40 | * | Condition | Result | 41 | * +--------------------+---------------+ 42 | * | $this == $value | $result === 0 | 43 | * | $this < $value | $result < 0 | 44 | * | $this > $value | $result > 0 | 45 | * +--------------------+---------------+ 46 | * 47 | * If either of the operands implements one of the Parity comparator 48 | * interfaces and is able to perform the comparison to the other operand 49 | * its compare() method is used to perform the comparison. If neither 50 | * operand provides a suitable implementation, the fallback comparator is 51 | * used. 52 | * 53 | * @param mixed $lhs The first value to compare. 54 | * @param mixed $rhs The second value to compare. 55 | * 56 | * @return int The result of the comparison. 57 | */ 58 | public function compare($lhs, $rhs): int 59 | { 60 | if ($this->canCompare($lhs, $rhs)) { 61 | return $lhs->compare($rhs); 62 | } elseif ($this->canCompare($rhs, $lhs)) { 63 | return -$rhs->compare($lhs); 64 | } 65 | 66 | return $this->fallbackComparator()->compare($lhs, $rhs); 67 | } 68 | 69 | /** 70 | * An alias for compare(). 71 | * 72 | * @param mixed $lhs The first value to compare. 73 | * @param mixed $rhs The second value to compare. 74 | * 75 | * @return int The result of the comparison. 76 | */ 77 | public function __invoke($lhs, $rhs): int 78 | { 79 | return $this->compare($lhs, $rhs); 80 | } 81 | 82 | /** 83 | * Check if one value can be compared to another. 84 | * 85 | * @param mixed $lhs The first value to compare. 86 | * @param mixed $rhs The second value to compare. 87 | * 88 | * @return bool 89 | */ 90 | protected function canCompare($lhs, $rhs): bool 91 | { 92 | if ($lhs instanceof AnyComparable) { 93 | return true; 94 | } elseif ($lhs instanceof RestrictedComparable && $lhs->canCompare($rhs)) { 95 | return true; 96 | } elseif ($lhs instanceof SelfComparable) { 97 | return is_object($rhs) 98 | && get_class($lhs) === get_class($rhs); 99 | } elseif ($lhs instanceof SubClassComparable) { 100 | $className = $this->compareImplementationClass($lhs); 101 | 102 | return $rhs instanceof $className; 103 | } 104 | 105 | return false; 106 | } 107 | 108 | /** 109 | * @param mixed $value 110 | * 111 | * @return string 112 | */ 113 | protected function compareImplementationClass($value): string 114 | { 115 | $className = get_class($value); 116 | 117 | if (array_key_exists($className, $this->compareImplementationClasses)) { 118 | return $this->compareImplementationClasses[$className]; 119 | } 120 | 121 | $reflector = new ReflectionMethod($value, 'compare'); 122 | $declaringClassName = $reflector->getDeclaringClass()->getName(); 123 | $this->compareImplementationClasses[$className] = $declaringClassName; 124 | 125 | return $declaringClassName; 126 | } 127 | 128 | private $fallbackComparator; 129 | private $compareImplementationClasses; 130 | } 131 | -------------------------------------------------------------------------------- /src/Comparator/PhpComparator.php: -------------------------------------------------------------------------------- 1 | $value | $result > 0 | 19 | * +--------------------+---------------+ 20 | * 21 | * @param mixed $lhs The first value to compare. 22 | * @param mixed $rhs The second value to compare. 23 | * 24 | * @return int The result of the comparison. 25 | */ 26 | public function compare($lhs, $rhs): int 27 | { 28 | if ($lhs < $rhs) { 29 | return -1; 30 | } elseif ($rhs < $lhs) { 31 | return +1; 32 | } 33 | 34 | return 0; 35 | } 36 | 37 | /** 38 | * An alias for compare(). 39 | * 40 | * @param mixed $lhs The first value to compare. 41 | * @param mixed $rhs The second value to compare. 42 | * 43 | * @return int The result of the comparison. 44 | */ 45 | public function __invoke($lhs, $rhs): int 46 | { 47 | return $this->compare($lhs, $rhs); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Comparator/StrictPhpComparator.php: -------------------------------------------------------------------------------- 1 | relaxNumericComparisons = $relaxNumericComparisons; 21 | } 22 | 23 | /** 24 | * Compare two values, yielding a result according to the following table: 25 | * 26 | * +--------------------+---------------+ 27 | * | Condition | Result | 28 | * +--------------------+---------------+ 29 | * | $this == $value | $result === 0 | 30 | * | $this < $value | $result < 0 | 31 | * | $this > $value | $result > 0 | 32 | * +--------------------+---------------+ 33 | * 34 | * @param mixed $lhs The first value to compare. 35 | * @param mixed $rhs The second value to compare. 36 | * 37 | * @return int The result of the comparison. 38 | */ 39 | public function compare($lhs, $rhs): int 40 | { 41 | $lhsType = $this->transformTypeName($lhs); 42 | $rhsType = $this->transformTypeName($rhs); 43 | $cmp = strcmp($lhsType, $rhsType); 44 | 45 | if ($cmp !== 0) { 46 | return $cmp; 47 | } elseif ($lhs < $rhs) { 48 | return -1; 49 | } elseif ($rhs < $lhs) { 50 | return +1; 51 | } 52 | 53 | return 0; 54 | } 55 | 56 | /** 57 | * An alias for compare(). 58 | * 59 | * @param mixed $lhs The first value to compare. 60 | * @param mixed $rhs The second value to compare. 61 | * 62 | * @return int The result of the comparison. 63 | */ 64 | public function __invoke($lhs, $rhs): int 65 | { 66 | return $this->compare($lhs, $rhs); 67 | } 68 | 69 | /** 70 | * @param mixed $value 71 | * 72 | * @return string The effective type name to use when comparing types. 73 | */ 74 | private function transformTypeName($value): string 75 | { 76 | if (is_object($value)) { 77 | return 'object:' . get_class($value); 78 | } elseif (is_integer($value) && $this->relaxNumericComparisons) { 79 | return 'double'; 80 | } 81 | 82 | return gettype($value); 83 | } 84 | 85 | private $relaxNumericComparisons; 86 | } 87 | -------------------------------------------------------------------------------- /src/Exception/NotComparableException.php: -------------------------------------------------------------------------------- 1 | $value. 35 | */ 36 | public function isGreaterThan($value): bool; 37 | 38 | /** 39 | * @param mixed $value The value to compare. 40 | * 41 | * @return bool True if $this <= $value. 42 | */ 43 | public function isLessThanOrEqualTo($value): bool; 44 | 45 | /** 46 | * @param mixed $value The value to compare. 47 | * 48 | * @return bool True if $this >= $value. 49 | */ 50 | public function isGreaterThanOrEqualTo($value): bool; 51 | } 52 | -------------------------------------------------------------------------------- /src/ExtendedComparableTrait.php: -------------------------------------------------------------------------------- 1 | compare($value) === 0; 18 | } 19 | 20 | /** 21 | * @param mixed $value The value to compare. 22 | * 23 | * @return bool True if $this != $value. 24 | */ 25 | public function isNotEqualTo($value): bool 26 | { 27 | return $this->compare($value) !== 0; 28 | } 29 | 30 | /** 31 | * @param mixed $value The value to compare. 32 | * 33 | * @return bool True if $this < $value. 34 | */ 35 | public function isLessThan($value): bool 36 | { 37 | return $this->compare($value) < 0; 38 | } 39 | 40 | /** 41 | * @param mixed $value The value to compare. 42 | * 43 | * @return bool True if $this > $value. 44 | */ 45 | public function isGreaterThan($value): bool 46 | { 47 | return $this->compare($value) > 0; 48 | } 49 | 50 | /** 51 | * @param mixed $value The value to compare. 52 | * 53 | * @return bool True if $this <= $value. 54 | */ 55 | public function isLessThanOrEqualTo($value): bool 56 | { 57 | return $this->compare($value) <= 0; 58 | } 59 | 60 | /** 61 | * @param mixed $value The value to compare. 62 | * 63 | * @return bool True if $this >= $value. 64 | */ 65 | public function isGreaterThanOrEqualTo($value): bool 66 | { 67 | return $this->compare($value) >= 0; 68 | } 69 | 70 | /** 71 | * @param mixed $value The value to compare. 72 | * 73 | * @return int 74 | */ 75 | abstract protected function compare($value): int; 76 | } 77 | -------------------------------------------------------------------------------- /src/Parity.php: -------------------------------------------------------------------------------- 1 | $value | $result > 0 | 21 | * +--------------------+---------------+ 22 | * 23 | * @param mixed $lhs The first value to compare. 24 | * @param mixed $rhs The second value to compare. 25 | * 26 | * @return int The result of the comparison. 27 | */ 28 | public static function compare($lhs, $rhs): int 29 | { 30 | return self::comparator()->compare($lhs, $rhs); 31 | } 32 | 33 | /** 34 | * @param mixed $lhs The first value to compare. 35 | * @param mixed $rhs The second value to compare. 36 | * 37 | * @return bool True if $lhs == $rhs. 38 | */ 39 | public static function isEqualTo($lhs, $rhs): bool 40 | { 41 | return static::compare($lhs, $rhs) === 0; 42 | } 43 | 44 | /** 45 | * @param mixed $lhs The first value to compare. 46 | * @param mixed $rhs The second value to compare. 47 | * 48 | * @return bool True if $lhs != $rhs. 49 | */ 50 | public static function isNotEqualTo($lhs, $rhs): bool 51 | { 52 | return static::compare($lhs, $rhs) !== 0; 53 | } 54 | 55 | /** 56 | * @param mixed $lhs The first value to compare. 57 | * @param mixed $rhs The second value to compare. 58 | * 59 | * @return bool True if $lhs < $rhs. 60 | */ 61 | public static function isLessThan($lhs, $rhs): bool 62 | { 63 | return static::compare($lhs, $rhs) < 0; 64 | } 65 | 66 | /** 67 | * @param mixed $lhs The first value to compare. 68 | * @param mixed $rhs The second value to compare. 69 | * 70 | * @return bool True if $lhs > $rhs. 71 | */ 72 | public static function isGreaterThan($lhs, $rhs): bool 73 | { 74 | return static::compare($lhs, $rhs) > 0; 75 | } 76 | 77 | /** 78 | * @param mixed $lhs The first value to compare. 79 | * @param mixed $rhs The second value to compare. 80 | * 81 | * @return bool True if $lhs <= $rhs. 82 | */ 83 | public static function isLessThanOrEqualTo($lhs, $rhs): bool 84 | { 85 | return static::compare($lhs, $rhs) <= 0; 86 | } 87 | 88 | /** 89 | * @param mixed $lhs The first value to compare. 90 | * @param mixed $rhs The second value to compare. 91 | * 92 | * @return bool True if $lhs >= $rhs. 93 | */ 94 | public static function isGreaterThanOrEqualTo($lhs, $rhs): bool 95 | { 96 | return static::compare($lhs, $rhs) >= 0; 97 | } 98 | 99 | /** 100 | * @param mixed $lhs The first value to compare. 101 | * @param mixed $rhs,... The second (and more) value(s) to compare. 102 | * 103 | * @return mixed 104 | */ 105 | public static function min($lhs, $rhs) 106 | { 107 | return self::minSequence(func_get_args()); 108 | } 109 | 110 | /** 111 | * @param mixed $lhs The first value to compare. 112 | * @param mixed $rhs,... The second (and more) value(s) to compare. 113 | * 114 | * @return mixed 115 | */ 116 | public static function max($lhs, $rhs) 117 | { 118 | return self::maxSequence(func_get_args()); 119 | } 120 | 121 | /** 122 | * @param iterable $sequence The sequence to find the minimum value in. 123 | * @param mixed $default The default miniumum value. 124 | * 125 | * @return mixed The minimum value in the sequence. 126 | */ 127 | public static function minSequence(iterable $sequence, $default = null) 128 | { 129 | $minAssigned = false; 130 | $min = null; 131 | 132 | foreach ($sequence as $value) { 133 | if (!$minAssigned) { 134 | $minAssigned = true; 135 | $min = $value; 136 | } elseif (static::isLessThan($value, $min)) { 137 | $min = $value; 138 | } 139 | } 140 | 141 | if (!$minAssigned) { 142 | return $default; 143 | } 144 | 145 | return $min; 146 | } 147 | 148 | /** 149 | * @param iterable $sequence The sequence to find the maximum value in. 150 | * @param mixed $default The default maxiumum value. 151 | * 152 | * @return mixed The maximum value in the sequence. 153 | */ 154 | public static function maxSequence(iterable $sequence, $default = null) 155 | { 156 | $maxAssigned = false; 157 | $max = null; 158 | 159 | foreach ($sequence as $value) { 160 | if (!$maxAssigned) { 161 | $maxAssigned = true; 162 | $max = $value; 163 | } elseif (static::isGreaterThan($value, $max)) { 164 | $max = $value; 165 | } 166 | } 167 | 168 | if (!$maxAssigned) { 169 | return $default; 170 | } 171 | 172 | return $max; 173 | } 174 | 175 | /** 176 | * Get the internal Parity comparator. 177 | * 178 | * The comparator returned by this method in such as way as to enforce the 179 | * documented rules of Parity's comparison engine. 180 | * 181 | * @return Comparator 182 | */ 183 | public static function comparator(): Comparator 184 | { 185 | if (null === self::$comparator) { 186 | self::$comparator = new ParityComparator( 187 | new DeepComparator( 188 | new StrictPhpComparator() 189 | ) 190 | ); 191 | } 192 | 193 | return self::$comparator; 194 | } 195 | 196 | private static $comparator; 197 | } 198 | -------------------------------------------------------------------------------- /src/RestrictedComparable.php: -------------------------------------------------------------------------------- 1 | $value | $result > 0 | 20 | * +--------------------+---------------+ 21 | * 22 | * @param mixed $value The value to compare. 23 | * 24 | * @return int The result of the comparison. 25 | * @throws Exception\NotComparableException Indicates that the implementation does not know how to compare $this to $value. 26 | */ 27 | public function compare($value): int; 28 | 29 | /** 30 | * Check if $this is able to be compared to another value. 31 | * 32 | * A return value of false indicates that calling $this->compare($value) 33 | * will throw an exception. 34 | * 35 | * @param mixed $value The value to compare. 36 | * 37 | * @return bool True if $this can be compared to $value. 38 | */ 39 | public function canCompare($value): bool; 40 | } 41 | -------------------------------------------------------------------------------- /src/SelfComparable.php: -------------------------------------------------------------------------------- 1 | $value | $result > 0 | 20 | * +--------------------+---------------+ 21 | * 22 | * @param object $value The object to compare. 23 | * 24 | * @return int The result of the comparison. 25 | * @throws Exception\NotComparableException if $value is not the same type as $this. 26 | */ 27 | public function compare($value): int; 28 | } 29 | -------------------------------------------------------------------------------- /src/SubClassComparable.php: -------------------------------------------------------------------------------- 1 | $value | $result > 0 | 21 | * +--------------------+---------------+ 22 | * 23 | * @param object $value The object to compare. 24 | * 25 | * @return int The result of the comparison. 26 | * @throws Exception\NotComparableException if $value is not the same type as $this. 27 | */ 28 | public function compare($value): int; 29 | } 30 | -------------------------------------------------------------------------------- /test/src/ChildObject.php: -------------------------------------------------------------------------------- 1 | foo = $foo; 10 | $this->bar = $bar; 11 | } 12 | 13 | public static $staticProperty = 'staticPropertyValue'; 14 | private $foo; 15 | private $bar; 16 | } 17 | -------------------------------------------------------------------------------- /test/suite/Comparator/DeepComparatorTest.php: -------------------------------------------------------------------------------- 1 | fallbackComparator = Phony::partialMock(PhpComparator::class); 17 | $this->comparator = new DeepComparator($this->fallbackComparator->get()); 18 | } 19 | 20 | public function testInvoke() 21 | { 22 | $this->assertSame( 23 | 0, 24 | call_user_func( 25 | $this->comparator, 26 | [1, 2, 3], 27 | [1, 2, 3] 28 | ) 29 | ); 30 | } 31 | 32 | public function testCompareWithObjectReferences() 33 | { 34 | $value = (object) ['foo' => 'bar']; 35 | 36 | $result = $this->comparator->compare($value, $value); 37 | 38 | // Even though there are strings in the object, the fallback comparator 39 | // should never be used because we are comparing references to the same 40 | // object. 41 | $this->fallbackComparator->noInteraction(); 42 | 43 | $this->assertSame(0, $result); 44 | } 45 | 46 | public function testCompareWithEmptyArrays() 47 | { 48 | $result = $this->comparator->compare([], []); 49 | 50 | $this->fallbackComparator->noInteraction(); 51 | 52 | $this->assertSame(0, $result); 53 | } 54 | 55 | public function testCompareWithArrays() 56 | { 57 | $this->assertSame( 58 | 0, 59 | $this->comparator->compare( 60 | [1, 2, 3], 61 | [1, 2, 3] 62 | ) 63 | ); 64 | } 65 | 66 | public function testCompareWithAssociativeArrays() 67 | { 68 | $this->assertSame( 69 | 0, 70 | $this->comparator->compare( 71 | ['a' => 1, 'b' => 2, 'c' => 3], 72 | ['a' => 1, 'b' => 2, 'c' => 3] 73 | ) 74 | ); 75 | } 76 | 77 | public function testCompareWithArraysThatDifferBySize() 78 | { 79 | $this->assertLessThan( 80 | 0, 81 | $this->comparator->compare( 82 | [1, 2], 83 | [1, 2, 3] 84 | ) 85 | ); 86 | 87 | $this->assertGreaterThan( 88 | 0, 89 | $this->comparator->compare( 90 | [1, 2, 3], 91 | [1, 2] 92 | ) 93 | ); 94 | } 95 | 96 | public function testCompareWithArraysThatDifferBySizeAndContent() 97 | { 98 | $this->assertLessThan( 99 | 0, 100 | $this->comparator->compare( 101 | [1, 2, 3], 102 | [1, 3] 103 | ) 104 | ); 105 | 106 | $this->assertGreaterThan( 107 | 0, 108 | $this->comparator->compare( 109 | [1, 3], 110 | [1, 2, 3] 111 | ) 112 | ); 113 | } 114 | 115 | public function testCompareWithArraysThatDifferByKeys() 116 | { 117 | $this->assertLessThan( 118 | 0, 119 | $this->comparator->compare( 120 | ['a' => 1], 121 | ['b' => 1] 122 | ) 123 | ); 124 | 125 | $this->assertGreaterThan( 126 | 0, 127 | $this->comparator->compare( 128 | ['b' => 1], 129 | ['a' => 1] 130 | ) 131 | ); 132 | } 133 | 134 | public function testCompareWithObjects() 135 | { 136 | $this->assertSame( 137 | 0, 138 | $this->comparator->compare( 139 | (object) ['a' => 1, 'b' => 2, 'c' => 3], 140 | (object) ['a' => 1, 'b' => 2, 'c' => 3] 141 | ) 142 | ); 143 | } 144 | 145 | public function testCompareWithObjectsThatDifferBySize() 146 | { 147 | $this->assertLessThan( 148 | 0, 149 | $this->comparator->compare( 150 | (object) ['a' => 1, 'b' => 2], 151 | (object) ['a' => 1, 'b' => 2, 'c' => 3] 152 | ) 153 | ); 154 | 155 | $this->assertGreaterThan( 156 | 0, 157 | $this->comparator->compare( 158 | (object) ['a' => 1, 'b' => 2, 'c' => 3], 159 | (object) ['a' => 1, 'b' => 2] 160 | ) 161 | ); 162 | } 163 | 164 | public function testCompareWithObjectsThatDifferBySizeAndContent() 165 | { 166 | $this->assertLessThan( 167 | 0, 168 | $this->comparator->compare( 169 | (object) ['a' => 1, 'b' => 2, 'c' => 3], 170 | (object) ['a' => 1, 'b' => 3] 171 | ) 172 | ); 173 | 174 | $this->assertGreaterThan( 175 | 0, 176 | $this->comparator->compare( 177 | (object) ['a' => 1, 'b' => 3], 178 | (object) ['a' => 1, 'b' => 2, 'c' => 3] 179 | ) 180 | ); 181 | } 182 | 183 | public function testCompareWithObjectsThatDifferClassName() 184 | { 185 | $this->assertLessThan( 186 | 0, 187 | $this->comparator->compare( 188 | new DateTime(), 189 | new stdClass() 190 | ) 191 | ); 192 | 193 | $this->assertGreaterThan( 194 | 0, 195 | $this->comparator->compare( 196 | new stdClass(), 197 | new DateTime() 198 | ) 199 | ); 200 | } 201 | 202 | public function testCompareWithObjectsWithRelaxedClassComparisons() 203 | { 204 | $this->comparator = new DeepComparator($this->fallbackComparator->get(), true); 205 | 206 | $this->assertSame( 207 | 0, 208 | $this->comparator->compare( 209 | new ParentObject(1, 2), 210 | new ChildObject(1, 2) 211 | ) 212 | ); 213 | } 214 | 215 | public function testCompareWithObjectsDifferentInnerClassTypes() 216 | { 217 | $obj1 = new stdClass(); 218 | $obj1->foo = new stdClass(); 219 | 220 | $obj2 = new stdClass(); 221 | $obj2->foo = new ParentObject(0, 0); 222 | 223 | $this->assertSame(0, $this->comparator->compare($obj1, $obj1)); 224 | $this->assertSame(0, $this->comparator->compare($obj2, $obj2)); 225 | 226 | $this->assertLessThan(0, $this->comparator->compare($obj2, $obj1)); 227 | $this->assertGreaterThan(0, $this->comparator->compare($obj1, $obj2)); 228 | } 229 | 230 | public function testCompareWithObjectsParentAndDerived() 231 | { 232 | $obj1 = new ParentObject(0, 0); 233 | $obj2 = new ChildObject(0, 0); 234 | 235 | $this->assertSame(0, $this->comparator->compare($obj1, $obj1)); 236 | $this->assertSame(0, $this->comparator->compare($obj2, $obj2)); 237 | 238 | $this->assertLessThan(0, $this->comparator->compare($obj2, $obj1)); 239 | $this->assertGreaterThan(0, $this->comparator->compare($obj1, $obj2)); 240 | } 241 | 242 | public function testCompareWithObjectsHavingSharedInnerObject() 243 | { 244 | $shared = new ParentObject('foo', 'bar'); 245 | 246 | $obj1 = new ParentObject(111, $shared); 247 | $obj2 = new ParentObject(222, $shared); 248 | 249 | $obj3 = new ChildObject(333, $shared); 250 | $obj4 = new ChildObject(444, $shared); 251 | 252 | $this->assertSame(0, $this->comparator->compare($obj1, $obj1)); 253 | $this->assertSame(0, $this->comparator->compare($obj3, $obj3)); 254 | 255 | $this->assertLessThan(0, $this->comparator->compare($obj1, $obj2)); 256 | $this->assertGreaterThan(0, $this->comparator->compare($obj2, $obj1)); 257 | 258 | $this->assertLessThan(0, $this->comparator->compare($obj3, $obj4)); 259 | $this->assertGreaterThan(0, $this->comparator->compare($obj4, $obj3)); 260 | } 261 | 262 | public function testCompareWithSimpleRecursion() 263 | { 264 | $obj1 = new stdClass(); 265 | $obj1->foo = $obj1; 266 | $obj1->bar = 1; 267 | 268 | $obj2 = new stdClass(); 269 | $obj2->foo = $obj2; 270 | $obj2->bar = 2; 271 | 272 | // The first property compared is infinitely recusive, so just the hash will be used. 273 | // Since the hash's wont match the 'bar' property will not be compared. 274 | if (spl_object_hash($obj1) < spl_object_hash($obj2)) { 275 | $this->assertLessThan(0, $this->comparator->compare($obj1, $obj2)); 276 | $this->assertGreaterThan(0, $this->comparator->compare($obj2, $obj1)); 277 | } else { 278 | $this->assertLessThan(0, $this->comparator->compare($obj2, $obj1)); 279 | $this->assertGreaterThan(0, $this->comparator->compare($obj1, $obj2)); 280 | } 281 | } 282 | 283 | public function testCompareWithSimpleObjectsDoubleRecursion() 284 | { 285 | $obj1 = new stdClass(); 286 | $obj1->recurse = new stdClass(); 287 | $obj1->recurse->recurse = $obj1; 288 | $obj1->value = 1; 289 | 290 | $obj2 = new stdClass(); 291 | $obj2->recurse = new stdClass(); 292 | $obj2->recurse->recurse = $obj2; 293 | $obj2->value = 2; 294 | 295 | // The first property compared is infinitely recusive, so just the hash will be used. 296 | // Since the hash's wont match the 'value' property will not be compared. 297 | if (spl_object_hash($obj1) < spl_object_hash($obj2)) { 298 | $this->assertLessThan(0, $this->comparator->compare($obj1, $obj2)); 299 | $this->assertGreaterThan(0, $this->comparator->compare($obj2, $obj1)); 300 | } else { 301 | $this->assertLessThan(0, $this->comparator->compare($obj2, $obj1)); 302 | $this->assertGreaterThan(0, $this->comparator->compare($obj1, $obj2)); 303 | } 304 | } 305 | 306 | public function testCompareWithSimpleObjectsBothHavingObject1AsFirstProperty() 307 | { 308 | $obj1 = new stdClass(); 309 | $obj1->foo = $obj1; 310 | $obj1->bar = 1; 311 | 312 | $obj2 = new stdClass(); 313 | $obj2->foo = $obj1; 314 | $obj2->bar = 2; 315 | 316 | $this->assertLessThan(0, $this->comparator->compare($obj1, $obj2)); 317 | $this->assertGreaterThan(0, $this->comparator->compare($obj2, $obj1)); 318 | } 319 | 320 | public function testCompareWithObjectCycle() 321 | { 322 | $obj1 = new stdClass(); 323 | $obj1->foo = new ParentObject('foo1', $obj1); 324 | 325 | $obj2 = new stdClass(); 326 | $obj2->foo = new ParentObject('foo2', $obj2); 327 | 328 | $obj3 = new stdClass(); 329 | $obj3->foo = new ChildObject('bar3', $obj1); 330 | 331 | $obj4 = new stdClass(); 332 | $obj4->foo = new ChildObject('bar4', $obj2); 333 | 334 | $this->assertLessThan(0, $this->comparator->compare($obj1, $obj2)); 335 | $this->assertGreaterThan(0, $this->comparator->compare($obj2, $obj1)); 336 | 337 | $this->assertLessThan(0, $this->comparator->compare($obj3, $obj4)); 338 | $this->assertGreaterThan(0, $this->comparator->compare($obj4, $obj3)); 339 | } 340 | 341 | public function testCompareWithObjectsHavingInternalArraysAndObjects() 342 | { 343 | $shared = new ChildObject('foo', 'bar'); 344 | 345 | $obj1 = new ParentObject(['a', 'b'], [$shared, 'foo']); 346 | $obj2 = new ParentObject(['a', 'b'], [$shared, 'foo']); 347 | $obj3 = new ParentObject(['x', 'y'], [$shared, 'foo']); 348 | 349 | $this->assertSame(0, $this->comparator->compare($obj1, $obj1)); 350 | $this->assertSame(0, $this->comparator->compare($obj1, $obj2)); 351 | $this->assertSame(0, $this->comparator->compare($obj3, $obj3)); 352 | 353 | $this->assertLessThan(0, $this->comparator->compare($obj1, $obj3)); 354 | $this->assertGreaterThan(0, $this->comparator->compare($obj3, $obj1)); 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /test/suite/Comparator/ObjectIdentityComparatorTest.php: -------------------------------------------------------------------------------- 1 | fallbackComparator = Phony::mock(Comparator::class); 14 | $this->fallbackComparator->compare->returns(-1); 15 | 16 | $this->comparator = new ObjectIdentityComparator($this->fallbackComparator->get()); 17 | } 18 | 19 | public function testInvoke() 20 | { 21 | $this->assertSame( 22 | -1, 23 | call_user_func( 24 | $this->comparator, 25 | [1, 2, 3], 26 | [1, 2, 3] 27 | ) 28 | ); 29 | } 30 | 31 | public function testCompare() 32 | { 33 | $obj1 = new stdClass(); 34 | $obj2 = new stdClass(); 35 | 36 | $this->assertSame(0, $this->comparator->compare($obj1, $obj1)); 37 | $this->assertSame(0, $this->comparator->compare($obj2, $obj2)); 38 | 39 | // The first property compared is infinitely recusive, so just the hash will be used. 40 | // Since the hash's wont match the 'bar' property will not be compared. 41 | if (spl_object_hash($obj1) < spl_object_hash($obj2)) { 42 | $this->assertLessThan(0, $this->comparator->compare($obj1, $obj2)); 43 | $this->assertGreaterThan(0, $this->comparator->compare($obj2, $obj1)); 44 | } else { 45 | $this->assertLessThan(0, $this->comparator->compare($obj2, $obj1)); 46 | $this->assertGreaterThan(0, $this->comparator->compare($obj1, $obj2)); 47 | } 48 | } 49 | 50 | public function testCompareWithFallback() 51 | { 52 | $lhs = new stdClass(); 53 | 54 | $result = $this->comparator->compare($lhs, 20); 55 | 56 | $this->fallbackComparator->compare->calledWith($lhs, 20); 57 | 58 | $this->assertSame($result, -1); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /test/suite/Comparator/ParityComparatorTest.php: -------------------------------------------------------------------------------- 1 | fallbackComparator = Phony::mock(Comparator::class); 19 | $this->fallbackComparator->compare->returns(-1); 20 | 21 | $this->comparator = new ParityComparator($this->fallbackComparator->get()); 22 | } 23 | 24 | public function testInvoke() 25 | { 26 | $this->assertSame( 27 | -1, 28 | call_user_func( 29 | $this->comparator, 30 | [1, 2, 3], 31 | [1, 2, 3] 32 | ) 33 | ); 34 | } 35 | 36 | public function testCompareInversion() 37 | { 38 | $lhs = Phony::mock(AnyComparable::class); 39 | $lhs->compare->returns(-1); 40 | 41 | $this->assertSame(-1, $this->comparator->compare($lhs->get(), 10)); 42 | $this->assertSame(+1, $this->comparator->compare(10, $lhs->get())); 43 | } 44 | 45 | public function testCompareWithFallback() 46 | { 47 | $result = $this->comparator->compare(10, 20); 48 | 49 | $this->fallbackComparator->compare->calledWith(10, 20); 50 | 51 | $this->assertSame($result, -1); 52 | } 53 | 54 | public function testCompareWithAnyComparable() 55 | { 56 | $comparable = Phony::mock(AnyComparable::class); 57 | $comparable->compare->returns(-10); 58 | 59 | $result = $this->comparator->compare($comparable->get(), 10); 60 | 61 | $comparable->compare->calledWith(10); 62 | $this->fallbackComparator->noInteraction(); 63 | 64 | $this->assertSame($result, -10); 65 | } 66 | 67 | public function testCompareWithRestrictedComparable() 68 | { 69 | $comparable = Phony::mock(RestrictedComparable::class); 70 | $comparable->compare->returns(-10); 71 | $comparable->canCompare->returns(true); 72 | 73 | $result = $this->comparator->compare($comparable->get(), 10); 74 | 75 | Phony::inOrder( 76 | $comparable->canCompare->calledWith(10), 77 | $comparable->compare->calledWith(10) 78 | ); 79 | 80 | $this->fallbackComparator->noInteraction(); 81 | 82 | $this->assertSame($result, -10); 83 | } 84 | 85 | public function testCompareWithRestrictedComparableAndUnsupportedOperand() 86 | { 87 | $comparable = Phony::mock(RestrictedComparable::class); 88 | $comparable->compare->returns(-10); 89 | $comparable->canCompare->returns(false); 90 | 91 | $result = $this->comparator->compare($comparable->get(), 10); 92 | 93 | $comparable->canCompare->calledWith(10); 94 | $comparable->compare->never()->called(); 95 | $this->fallbackComparator->compare->calledWith($comparable, 10); 96 | 97 | $this->assertSame($result, -1); 98 | } 99 | 100 | public function testCompareWithSelfComparable() 101 | { 102 | $lhsComparable = Phony::mock(SelfComparable::class); 103 | $lhsComparable->compare->returns(-10); 104 | 105 | $rhsComparable = Phony::mock(SelfComparable::class); 106 | 107 | $result = $this->comparator->compare($lhsComparable->get(), $rhsComparable->get()); 108 | 109 | $lhsComparable->compare->calledWith($rhsComparable); 110 | $this->fallbackComparator->noInteraction(); 111 | 112 | $this->assertSame($result, -10); 113 | } 114 | 115 | public function testCompareWithSelfComparableAndSubClass() 116 | { 117 | $lhsComparable = new SelfComparableImpl(); 118 | $rhsComparable = new SelfComparableSubClass(); 119 | 120 | $result = $this->comparator->compare($lhsComparable, $rhsComparable); 121 | 122 | $this->fallbackComparator->compare->calledWith($lhsComparable, $rhsComparable); 123 | 124 | $this->assertSame($result, -1); 125 | } 126 | 127 | public function testCompareWithSelfComparableAndNonObject() 128 | { 129 | $comparable = Phony::mock(SelfComparable::class); 130 | 131 | $result = $this->comparator->compare($comparable->get(), 10); 132 | 133 | $comparable->compare->never()->called(); 134 | $this->fallbackComparator->compare->calledWith($comparable, 10); 135 | 136 | $this->assertSame($result, -1); 137 | } 138 | 139 | public function testCompareWithSelfComparableAndUnrelatedType() 140 | { 141 | $comparable = Phony::mock(SelfComparable::class); 142 | 143 | $result = $this->comparator->compare($comparable->get(), new stdClass()); 144 | 145 | $comparable->compare->never()->called(); 146 | $this->fallbackComparator->compare->calledWith($comparable, new stdClass()); 147 | 148 | $this->assertSame($result, -1); 149 | } 150 | 151 | public function testCompareWithSubClassComparable() 152 | { 153 | $lhsComparable = Phony::mock(SubClassComparable::class); 154 | $lhsComparable->compare->returns(-10); 155 | 156 | $rhsComparable = Phony::mock(SubClassComparable::class); 157 | 158 | $result = $this->comparator->compare($lhsComparable->get(), $rhsComparable->get()); 159 | 160 | $lhsComparable->compare->calledWith($rhsComparable); 161 | $this->fallbackComparator->noInteraction(); 162 | 163 | $this->assertSame($result, -10); 164 | } 165 | 166 | public function testCompareWithSubClassComparableAndSubClass() 167 | { 168 | $lhsComparable = new SubClassComparableImpl(); 169 | $rhsComparable = new SubClassComparableSubClass(); 170 | 171 | $result = $this->comparator->compare($lhsComparable, $rhsComparable); 172 | 173 | $this->fallbackComparator->noInteraction(); 174 | 175 | $this->assertSame($result, -10); 176 | } 177 | 178 | public function testCompareWithSubClassComparableUsesCache() 179 | { 180 | $lhsComparable = Phony::mock(SubClassComparable::class); 181 | $lhsComparable->compare->returns(-10); 182 | 183 | $rhsComparable = Phony::mock(SubClassComparable::class); 184 | 185 | $this->assertSame(-10, $this->comparator->compare($lhsComparable->get(), $rhsComparable->get())); 186 | $this->assertSame(-10, $this->comparator->compare($lhsComparable->get(), $rhsComparable->get())); 187 | 188 | $this->assertSame( 189 | Liberator::liberate($this->comparator)->compareImplementationClasses[get_class($lhsComparable->get())], 190 | get_class($lhsComparable->get()) 191 | ); 192 | } 193 | 194 | public function testCompareWithSubClassComparableAndNonObject() 195 | { 196 | $comparable = Phony::mock(SubClassComparable::class); 197 | 198 | $result = $this->comparator->compare($comparable->get(), 10); 199 | 200 | $comparable->compare->never()->called(); 201 | $this->fallbackComparator->compare->calledWith($comparable, 10); 202 | 203 | $this->assertSame($result, -1); 204 | } 205 | 206 | public function testCompareWithSubClassComparableAndUnrelatedType() 207 | { 208 | $comparable = Phony::mock(SubClassComparable::class); 209 | 210 | $result = $this->comparator->compare($comparable->get(), new stdClass()); 211 | 212 | $comparable->compare->never()->called(); 213 | $this->fallbackComparator->compare->calledWith($comparable, new stdClass()); 214 | 215 | $this->assertSame($result, -1); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /test/suite/Comparator/PhpComparatorTest.php: -------------------------------------------------------------------------------- 1 | comparator = new PhpComparator(); 12 | } 13 | 14 | public function testCompare() 15 | { 16 | $this->assertLessThan(0, $this->comparator->compare(10, 20)); 17 | $this->assertGreaterThan(0, $this->comparator->compare(20, 10)); 18 | $this->assertSame(0, $this->comparator->compare(10, 10)); 19 | } 20 | 21 | public function testInvoke() 22 | { 23 | $this->assertLessThan(0, call_user_func($this->comparator, 10, 20)); 24 | $this->assertGreaterThan(0, call_user_func($this->comparator, 20, 10)); 25 | $this->assertSame(0, call_user_func($this->comparator, 10, 10)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/suite/Comparator/StrictPhpComparatorTest.php: -------------------------------------------------------------------------------- 1 | comparator = new StrictPhpComparator(); 14 | } 15 | 16 | public function testCompare() 17 | { 18 | $this->assertLessThan(0, $this->comparator->compare(10, 20)); 19 | $this->assertGreaterThan(0, $this->comparator->compare(20, 10)); 20 | $this->assertSame(0, $this->comparator->compare(10, 10)); 21 | } 22 | 23 | public function testCompareWithoutRelaxedNumericComparisons() 24 | { 25 | $this->comparator = new StrictPhpComparator(false); 26 | 27 | $this->assertGreaterThan(0, $this->comparator->compare(1, 2.5)); 28 | $this->assertLessThan(0, $this->comparator->compare(2.5, 3)); 29 | $this->assertGreaterThan(0, $this->comparator->compare(1, 1.0)); 30 | } 31 | 32 | public function testCompareRelaxedNumericComparisons() 33 | { 34 | $this->assertLessThan(0, $this->comparator->compare(1, 2.5)); 35 | $this->assertLessThan(0, $this->comparator->compare(2.5, 3)); 36 | $this->assertSame(0, $this->comparator->compare(1, 1.0)); 37 | } 38 | 39 | public function testCompareWithObjects() 40 | { 41 | $this->assertSame(0, $this->comparator->compare(new stdClass(), new stdClass())); 42 | $this->assertGreaterThan(0, $this->comparator->compare(new stdClass(), new DateTime())); 43 | } 44 | 45 | public function testInvoke() 46 | { 47 | $this->assertLessThan(0, call_user_func($this->comparator, 10, 20)); 48 | $this->assertGreaterThan(0, call_user_func($this->comparator, 20, 10)); 49 | $this->assertSame(0, call_user_func($this->comparator, 10, 10)); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/suite/Exception/NotComparableExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertSame('Can not compare [1, 2, 3] to [4, 5, 6].', $exception->getMessage()); 19 | $this->assertSame($previous, $exception->getPrevious()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/suite/ExtendedComparableTraitTest.php: -------------------------------------------------------------------------------- 1 | trait = Phony::partialMock(ExtendedComparableTraitUser::class)->get(); 13 | 14 | $this->less = -1; 15 | $this->same = 0; 16 | $this->more = 1; 17 | } 18 | 19 | public function testIsEqualTo() 20 | { 21 | $this->assertFalse($this->trait->isEqualTo($this->less)); 22 | $this->assertTrue($this->trait->isEqualTo($this->same)); 23 | $this->assertFalse($this->trait->isEqualTo($this->more)); 24 | } 25 | 26 | public function testIsNotEqualTo() 27 | { 28 | $this->assertTrue($this->trait->isNotEqualTo($this->less)); 29 | $this->assertFalse($this->trait->isNotEqualTo($this->same)); 30 | $this->assertTrue($this->trait->isNotEqualTo($this->more)); 31 | } 32 | 33 | public function testIsLessThan() 34 | { 35 | $this->assertFalse($this->trait->isLessThan($this->less)); 36 | $this->assertFalse($this->trait->isLessThan($this->same)); 37 | $this->assertTrue($this->trait->isLessThan($this->more)); 38 | } 39 | 40 | public function testIsGreaterThan() 41 | { 42 | $this->assertTrue($this->trait->isGreaterThan($this->less)); 43 | $this->assertFalse($this->trait->isGreaterThan($this->same)); 44 | $this->assertFalse($this->trait->isGreaterThan($this->more)); 45 | } 46 | 47 | public function testIsLessThanOrEqualTo() 48 | { 49 | $this->assertFalse($this->trait->isLessThanOrEqualTo($this->less)); 50 | $this->assertTrue($this->trait->isLessThanOrEqualTo($this->same)); 51 | $this->assertTrue($this->trait->isLessThanOrEqualTo($this->more)); 52 | } 53 | 54 | public function testIsGreaterThanOrEqualTo() 55 | { 56 | $this->assertTrue($this->trait->isGreaterThanOrEqualTo($this->less)); 57 | $this->assertTrue($this->trait->isGreaterThanOrEqualTo($this->same)); 58 | $this->assertFalse($this->trait->isGreaterThanOrEqualTo($this->more)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /test/suite/ParityTest.php: -------------------------------------------------------------------------------- 1 | value = 0; 13 | $this->less = -1; 14 | $this->same = 0; 15 | $this->more = 1; 16 | $this->object = new stdClass(); 17 | } 18 | 19 | public function testCompare() 20 | { 21 | $this->assertGreaterThan(0, Parity::compare($this->value, $this->less)); 22 | $this->assertSame(0, Parity::compare($this->value, $this->same)); 23 | $this->assertLessThan(0, Parity::compare($this->value, $this->more)); 24 | } 25 | 26 | public function testIsEqualTo() 27 | { 28 | $this->assertFalse(Parity::isEqualTo($this->value, $this->less)); 29 | $this->assertTrue(Parity::isEqualTo($this->value, $this->same)); 30 | $this->assertFalse(Parity::isEqualTo($this->value, $this->more)); 31 | } 32 | 33 | public function testIsNotEqualTo() 34 | { 35 | $this->assertTrue(Parity::isNotEqualTo($this->value, $this->less)); 36 | $this->assertFalse(Parity::isNotEqualTo($this->value, $this->same)); 37 | $this->assertTrue(Parity::isNotEqualTo($this->value, $this->more)); 38 | } 39 | 40 | public function testIsLessThan() 41 | { 42 | $this->assertFalse(Parity::isLessThan($this->value, $this->less)); 43 | $this->assertFalse(Parity::isLessThan($this->value, $this->same)); 44 | $this->assertTrue(Parity::isLessThan($this->value, $this->more)); 45 | } 46 | 47 | public function testIsGreaterThan() 48 | { 49 | $this->assertTrue(Parity::isGreaterThan($this->value, $this->less)); 50 | $this->assertFalse(Parity::isGreaterThan($this->value, $this->same)); 51 | $this->assertFalse(Parity::isGreaterThan($this->value, $this->more)); 52 | } 53 | 54 | public function testIsLessThanOrEqualTo() 55 | { 56 | $this->assertFalse(Parity::isLessThanOrEqualTo($this->value, $this->less)); 57 | $this->assertTrue(Parity::isLessThanOrEqualTo($this->value, $this->same)); 58 | $this->assertTrue(Parity::isLessThanOrEqualTo($this->value, $this->more)); 59 | } 60 | 61 | public function testIsGreaterThanOrEqualTo() 62 | { 63 | $this->assertTrue(Parity::isGreaterThanOrEqualTo($this->value, $this->less)); 64 | $this->assertTrue(Parity::isGreaterThanOrEqualTo($this->value, $this->same)); 65 | $this->assertFalse(Parity::isGreaterThanOrEqualTo($this->value, $this->more)); 66 | } 67 | 68 | public function testMin() 69 | { 70 | $this->assertSame($this->less, Parity::min($this->value, $this->less)); 71 | $this->assertSame($this->less, Parity::min($this->less, $this->value)); 72 | $this->assertSame($this->less, Parity::min($this->value, $this->same, $this->less, $this->more)); 73 | } 74 | 75 | public function testMax() 76 | { 77 | $this->assertSame($this->more, Parity::max($this->value, $this->more)); 78 | $this->assertSame($this->more, Parity::max($this->more, $this->value)); 79 | $this->assertSame($this->more, Parity::max($this->value, $this->same, $this->less, $this->more)); 80 | } 81 | 82 | /** 83 | * @dataProvider minSequenceData 84 | */ 85 | public function testMinSequence($sequence, $default, $expected) 86 | { 87 | $this->assertSame( 88 | $expected, 89 | Parity::minSequence($sequence, $default) 90 | ); 91 | } 92 | 93 | public function minSequenceData() 94 | { 95 | $value = 0; 96 | $less = -1; 97 | $same = 0; 98 | $more = 1; 99 | $object = new stdClass(); 100 | 101 | $sequenceEmpty = []; 102 | 103 | $sequenceObjectAndNull = [ 104 | $object, 105 | null, 106 | ]; 107 | 108 | $sequenceNumber = [ 109 | $value, 110 | $less, 111 | $same, 112 | $more, 113 | ]; 114 | 115 | $sequenceMixed = [ 116 | $value, 117 | $less, 118 | $same, 119 | $more, 120 | $object, 121 | true, 122 | null, 123 | ]; 124 | 125 | return [ 126 | 'Empty sequence, default null' => [$sequenceEmpty, null, null], 127 | 'Empty sequence, default object' => [$sequenceEmpty, $object, $object], 128 | 'Empty sequence, default value' => [$sequenceEmpty, $value, $value], 129 | 130 | 'Object and null sequence, default null' => [$sequenceObjectAndNull, null, null], 131 | 'Object and null sequence, default object' => [$sequenceObjectAndNull, $object, null], 132 | 'Object and null sequence, default value' => [$sequenceObjectAndNull, $value, null], 133 | 134 | 'Number sequence, default null' => [$sequenceNumber, null, $less], 135 | 'Number sequence, default object' => [$sequenceNumber, $object, $less], 136 | 'Number sequence, default value' => [$sequenceNumber, $value, $less], 137 | 138 | 'Mixed sequence, default null' => [$sequenceMixed, null, null], 139 | 'Mixed sequence, default object' => [$sequenceMixed, $object, null], 140 | 'Mixed sequence, default value' => [$sequenceMixed, $value, null], 141 | ]; 142 | } 143 | 144 | /** 145 | * @dataProvider maxSequenceData 146 | */ 147 | public function testMaxSequence($sequence, $default, $expected) 148 | { 149 | $this->assertSame( 150 | $expected, 151 | Parity::maxSequence($sequence, $default) 152 | ); 153 | } 154 | 155 | public function maxSequenceData() 156 | { 157 | $value = 0; 158 | $less = -1; 159 | $same = 0; 160 | $more = 1; 161 | $object = new stdClass(); 162 | 163 | $sequenceEmpty = []; 164 | 165 | $sequenceObjectAndNull = [ 166 | $object, 167 | null, 168 | ]; 169 | 170 | $sequenceNumber = [ 171 | $value, 172 | $less, 173 | $same, 174 | $more, 175 | ]; 176 | 177 | $sequenceMixed = [ 178 | $value, 179 | $less, 180 | $same, 181 | $more, 182 | $object, 183 | true, 184 | null, 185 | ]; 186 | 187 | return [ 188 | 'Empty sequence, default null' => [$sequenceEmpty, null, null], 189 | 'Empty sequence, default object' => [$sequenceEmpty, $object, $object], 190 | 'Empty sequence, default value' => [$sequenceEmpty, $value, $value], 191 | 192 | 'Object and null sequence, default null' => [$sequenceObjectAndNull, null, $object], 193 | 'Object and null sequence, default object' => [$sequenceObjectAndNull, $object, $object], 194 | 'Object and null sequence, default value' => [$sequenceObjectAndNull, $value, $object], 195 | 196 | 'Number sequence, default null' => [$sequenceNumber, null, $more], 197 | 'Number sequence, default object' => [$sequenceNumber, $object, $more], 198 | 'Number sequence, default value' => [$sequenceNumber, $value, $more], 199 | 200 | 'Mixed sequence, default null' => [$sequenceMixed, null, $object], 201 | 'Mixed sequence, default object' => [$sequenceMixed, $object, $object], 202 | 'Mixed sequence, default value' => [$sequenceMixed, $value, $object], 203 | ]; 204 | } 205 | 206 | public function testComparitor() 207 | { 208 | $comparator = Parity::comparator(); 209 | $this->assertInstanceOf(__NAMESPACE__ . '\Comparator\ParityComparator', $comparator); 210 | 211 | $comparator = $comparator->fallbackComparator(); 212 | $this->assertInstanceOf(__NAMESPACE__ . '\Comparator\DeepComparator', $comparator); 213 | 214 | $comparator = $comparator->fallbackComparator(); 215 | $this->assertInstanceOf(__NAMESPACE__ . '\Comparator\StrictPhpComparator', $comparator); 216 | } 217 | } 218 | --------------------------------------------------------------------------------