icecave/parity

A customizable deep comparison library.

3.0.1 2021-02-04 05:51 UTC

This package is auto-updated.

Last update: 2024-03-04 12:28:51 UTC


README

Build Status Code Coverage Latest Version

Parity is a deep comparison library for PHP.

composer require icecave/parity

Rationale

PHP does not provide a way to reliably and strictly compare values of heterogeneous types. Most of the built-in comparison operators perform often undesired type-juggling. The one exception is the strict equality operator, which has the further caveat that it can only compare objects by their identity. No type-strict mechanism is provided for comparing objects by their properties; nor are there any type-strict versions of the relative comparison operators (less-than, greater-than, etc).

Parity aims to fill the void by providing a comparison engine with the following features:

  • Type-strict comparison of arrays and objects by their elements
  • Recursion-safe comparison of objects
  • Natural, type-strict comparison semantics for built-in types
  • Powerful mechanisms for classes to customize comparison behavior

Example

The Parity comparison engine is accessed via static methods on the Parity facade class. These methods accept any types and are guaranteed to produce a deterministic comparison result1. Some basic examples are shown below using integers.

use Icecave\Parity\Parity;

// The compare() method provides a strcmp-style comparison, and hence can be
// used as a sorting function for operations such as usort()
assert(Parity::compare(1, 2) < 0);

// The following methods are convenience methods, implemented on top of compare().
assert(Parity::isEqualTo(1, 2) === false);
assert(Parity::isNotEqualTo(1, 2) === true);
assert(Parity::isNotEqualTo(1, 2) === true);
assert(Parity::isLessThan(1, 2) === true);
assert(Parity::isLessThanOrEqualTo(1, 2) === true);
assert(Parity::isGreaterThan(1, 2) === false);
assert(Parity::isGreaterThanOrEqualTo(1, 2) === false);

Concepts

Comparable

The core concept of Parity is the comparable. A comparable is any object that provides its own behavior for comparison with other values. The following refinements of the comparable concept are supported by the comparison engine:

  • Restricted Comparable: A comparable that can be queried as to which values it may be compared to.
  • Self Comparable: A comparable that may only be compared to other objects of exactly the same type.
  • Sub Class Comparable: A comparable that may only be compared to other objects of the same or a derived type.
  • Any Comparable: A comparable that may be freely compared to values of any other type.

Comparator

A Comparator defines comparison behavior for values other than itself. Parity provides the following comparator implementations:

Algorithm Resolution

The following process is used by Parity::compare($A, $B) to determine which comparison algorithm to use:

Use $A->compare($B) if:

  1. $A is Any Comparable; or
  2. $A is Restricted Comparable and $A->canCompare($B) returns true; or
  3. $A is Self Comparable and $A is exactly the same type as $B; or
  4. $A is Sub Class Comparable and $B is an instance of the class where $A->compare() is implemented

If none of the conditions above are true, the comparison is tried in reverse with $A on the right hand side and $B on the left; the result is also inverted. If still no comparison is possible, Parity falls back to a strictly-typed deep comparison.

When comparing scalar types, integers and doubles (PHP's only true numeric types) are treated as though they were the same type, such that the expression 3 < 3.5 < 4 holds true. Numeric strings are not compared in this way.

Caveats

  1. Comparison of recursive objects is not a truly deterministic operation as objects are compared by their object hash where deeper comparison would otherwise result in infinite recursion.