About 5-6 years ago I had my “PHP should be more like Java” phase and experimented a lot with things like string objects and method overloading, which usually required hackish workarounds and most things did not turn out to be very practical in the long run.

But there is one package I still like very much, namely ComparatorTools, which got to be place 2 in the monthly PHPclasses.org innovation awards after all. It provides Comparable and Comparator interfaces and functions similar to the core array functions, that can work with these.

Interfaces

The interfaces resemble the corresponding Java interfaces, except that we do not have Generics in PHP, so it is cannot be guaranteed that compared objects have the same type. This has to be checked at runtime in the implementation, if needed. An exception type for these cases is provided:

interface Comparable { /** * @param object $object * @return numeric negative value if $this < $object, positive if $this > $object, 0 otherwise (if objects are considered equal) * @throws ComparatorException if objects are not comparable to each other */ public function compareTo($object); }

interface Comparator { /** * @param object $object1 * @param object $object2 * @return numeric Negative value if $object1 < $object2, positive if $object1 > $object2, 0 otherwise * @throws ComparatorException if objects are not comparable to each other */ public function compare($object1, $object2); }

Functions

Sorting functions as well as functions that need equality comparison were implemented with a similar interface as the core array functions:

osort(array &$array, $comparator = null) Sort an array of objects by their Comparable Interface or a Comparator

Sort an array of objects by their Comparable Interface or a Comparator orsort(array &$array, $comparator = null) Sort an array of objects by their Comparable Interface or a Comparator in reverse order

Sort an array of objects by their Comparable Interface or a Comparator in reverse order oasort(array &$array, $comparator = null) Sort an array of objects by their Comparable Interface or a Comparator and maintain index association

Sort an array of objects by their Comparable Interface or a Comparator and maintain index association oarsort(array &$array, $comparator = null) Sort an array of objects by their Comparable Interface or a Comparator in reverse order and maintain index association

Sort an array of objects by their Comparable Interface or a Comparator in reverse order and maintain index association array_omultisort(array &$arrays, Comparator $comparator = null) Sort multiple arrays of objects by their Comparable Interface or a Comparator

Sort multiple arrays of objects by their Comparable Interface or a Comparator array_ounique(array &$array, Comparator $comparator = null) Remove duplicate objects from an array based on their Comparable Interface or a Comparator

Remove duplicate objects from an array based on their Comparable Interface or a Comparator array_odiff(array $array, array $array2 /*, [array $array3, [...]], Comparator $comparator = null*/) Compute the difference of arrays based on the Comparable Interface of contained objects or a Comparator

Compute the difference of arrays based on the Comparable Interface of contained objects or a Comparator array_ointersect(array $array, array $array2 /*, [array $array3, [...]], Comparator $comparator = null*/) Compute the intersection of arrays based on the Comparable Interface of contained objects or a Comparator

This procedural interface was just a convenience wrapper for the OOP interface:

/** * Returns a new ObjectSorter instance * * @return ObjectSorter */ function object_sorter() { return new ObjectSorter(); } /** * Sort an array of objects by their Comparable Interface or a Comparator * in reverse order and maintain index association * * @param array $array Array of objects, comparable by $comparator * @param Comparator $comparator A comparator. If null, the default * ComparableComparator will be used. * @return Returns TRUE on success or FALSE on failure. */ function oarsort(array &$array, $comparator = null) { return object_sorter() ->setMaintainKeys(true) ->setReverse(true) ->setComparator($comparator) ->sort($array); }

Version 1.0

I hadn’t touched the code for years and the style was quite outdated. Back then, PHP 5.2 was state of the art, 5.3 quite new and still not widely adopted, PSR-0 was far away. Now it was time for a revamping.

There are some newer PHP features that would come in handy:

Namespaces, naturally (PHP >=5.3)

__invoke() magic method to make comparators callable, so they can be used as compare callback immediately

magic method to make comparators callable, so they can be used as compare callback immediately For the procedural interface, function importing with use function (PHP >=5.6) and dereferencing “new” (PHP >=5.4)

(PHP >=5.6) and dereferencing “new” (PHP >=5.4) Variadic methods for a nicer interface of array_multisort, without the need for references (PHP >=5.6)

Since I definitly will use this in projects that cannot yet migrate to PHP 5.6 and I am sure, others will have the same problem as well, I opted for two separate branches, 1.x and 2.x. Version 2.x will have the cool new stuff and 1.x is a modernized version of the previous 0.9 release that includes everything compatible to PHP 5.4 at most. Version 2.0 is not granted to be backwards compatible with version 1.0 (following Semantic Versioning).

New interface

Functions were replaced with static methods, which is more autoloader-friendly:

SGH\Comparable\SortFunctions

public static function sort(array &$array, Comparator $comparator = null); public static function asort(array &$array, Comparator $comparator = null); public static function rsort(array &$array, Comparator $comparator = null); public static function arsort(array &$array, Comparator $comparator = null); public static function multisort(array &$arrays, Comparator $comparator = null); public static function sortedIterator(\Traversable $iterator, Comparator $comparator = null, $cloneItems = false);

SGH\Comparable\SetFunctions

public static function objectsDiff(array $array1, array $array2, array $..., Comparator $comparator = null); public static function objectsIntersect(array $array1, array $array2, array $..., Comparator $comparator = null); public static function objectsUnique(array $array); public static function diff(array $array1, array $array2, array $..., Comparator $comparator = null) public static function intersect(array $array1, array $array2, array $..., Comparator $comparator = null); public static function diff_assoc(array $array1, array $array2, array $..., Comparator $comparator = null); public static function intersect_assoc(); public static function unique(array $array, Comparator $comparator = null);

Additionally, the default comparator implementations ComparableComparator and ObjectComparator have a callback factory method that returns a callable version of themselves to be used directly in any function that takes a comparison callback as argument (like usort() ):

usort($array, \SGH\Comparable\Comparator\ComparableComparator::callback())

Useful Comparators

The previous release contained comparators for SplFileInfo objects as an example. With Version 1.0 they could look like this:

use SGH\Comparable\Comparator; use SGH\Comparable\ComparatorException; class SplFileInfoByNameComparator implements Comparator { protected function checkTypes($object1, $object2) { if (!$object1 instanceof \SplFileInfo) { throw new ComparatorException('$object1 (type: ' . gettype($object1) . ') is no instance of SplFileInfo.'); } if (!$object2 instanceof \SplFileInfo) { throw new ComparatorException('$object2 (type: ' . gettype($object2) . ') is no instance of SplFileInfo.'); } } /** * @param SplFileInfo $object1 * @param SplFileInfo $object2 */ public function compare($object1, $object2) { $this->checkTypes($object1, $object2); return strcmp($object1->getFileName(), $object2->getFileName()); } }

Together with the SortedIterator , it could be used to add sort order to a RecursiveDirectoryIterator :

$iterator = \SGH\Comparable\SortFunctions::sortedIterator( new RecursiveDirectoryIterator(__DIR__, FilesystemIterator::SKIP_DOTS), true); foreach ($iterator as $fileInfo) { echo $file->getFileName(), "

"; }

Note that the “cloneItems” parameter has been set to true , because the file system iterators return themselves in a different state on each iteration (more info). It still does not work with a normal DirectoryIterator for some reason, but using the RecursiveDirectoryIterator without wrapping it with a RecursiveIteratorIterator iterates non-recursively over the given directory.

More info

Read more at GitHub: README.md

Browse source

Packagist key for composer: sgh/comparable

RFC

There is an old RFC to make such a Comparable interface part of the PHP core, that recently has been reanimated: https://wiki.php.net/rfc/comparable (current PR: https://github.com/php/php-src/pull/1097). It would be great to see this as native feature in PHP 7.x, but as long as it isn’t, you can already use the interface from my package. I actually was prepared for this since the beginning:

/* * just in case that a Comparable SPL Interface comes someday */ if (interface_exists('Comparable')) { return; }