Vor etwa 5-6 Jahren hatte ich meine “PHP sollte mehr wie Java sein” Phase und habe viel mit Sachen wie String Objekten und Überladen von Methoden experimentiert, was meistens fiese Workarounds erforderte und die meisten Dinge stellten sich auf lange Sicht nicht als sehr praktikabel heraus.
Aber ein Package aus der Zeit gefällt mir immer noch ziemlich gut, und zwar ComparatorTools, was immerhin Platz 2 in den monatlichen PHPclasses.org Innovation Awards belegte. Es stellt Comparable
und Comparator
Interfaces zur Verfügung sowie Funktionen, analog zu den Core Array-Funktionen, die mit diesen arbeiten können.
Interfaces
Die Interfaces ähneln den entsprechenden Java interfaces, außer dass wir keine Generics in PHP haben, so dass nicht garantiert werden kann, dass die verglichenen Objekte den selben Typ haben. Dies muss zur Laufzeit in der Implementierung geprüft werden, sofern nötig. Ein Exception-Typ für diese Fälle ist verfügbar:
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); }
Funktionen
Sortierfunktionen sowie Funktionen, die Vergleich auf Gleichheit benötigen wurden mit einem ähnlichen Interface wie die Core Array-Funktionen implementiert:
osort(array &$array, $comparator = null)
Sortiert ein Array von Objekten aufgrund ihres Comparable Interfaces oder eines Comparators-
orsort(array &$array, $comparator = null)
Sortiert ein Array von Objekten aufgrund ihres Comparable Interfaces oder eines Comparators in umgekehrter Reihenfolge -
oasort(array &$array, $comparator = null)
Sortiert ein Array von Objekten aufgrund ihres Comparable Interfaces oder eines Comparators und behält Index-Assoziationen bei -
oarsort(array &$array, $comparator = null)
Sortiert ein Array von Objekten aufgrund ihres Comparable Interfaces oder eines Comparators in umgekehrter Reihenfolge und behält Index-Assoziationen bei -
array_omultisort(array &$arrays, Comparator $comparator = null)
Sortiert mehrere Arrays von Objekten aufgrund ihres Comparable Interfaces oder eines Comparators -
array_ounique(array &$array, Comparator $comparator = null)
Entfernt doppelte Objekte von einem Array aufgrund ihres Comparable Interfaces oder eines Comparators -
array_odiff(array $array, array $array2 /*, [array $array3, [...]], Comparator $comparator = null*/)
Berechnet die Differenz von Arrays aufgrund ihres Comparable Interfaces oder eines Comparators -
array_ointersect(array $array, array $array2 /*, [array $array3, [...]], Comparator $comparator = null*/)
Berechnet die Schnittmenge von Arrays aufgrund ihres Comparable Interfaces oder eines Comparators
Dieses prozedurale Interface war nur ein Wrapper für das 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
Ich hatte den Code seit Jahren nicht angefasst und der Stil war recht überholt. Seinerzeit war PHP 5.2 der Stand der Technik, 5.3 noch recht neu und längst nicht weithin verbreitet, PSR-0 war noch weit weg. Jetzt war es Zeit zum Aufpolieren.
Einige neueren PHP Features würden sich dabei als nützlich erweisen:
- Namespaces, natürlich (PHP >=5.3)
__invoke()
magische Methode, um Comparators callable zu machn, so dass sie direkt als Vergleichs-Callback genutzt werden können- Für das prozedurale Interface, importieren von Funktionen mit
use function
(PHP >=5.6) und Dereferenzieren von “new” (PHP >=5.4) - Variadic Methods für ein schöneres Interface von array_multisort, ohne Array von Referenzen (PHP >=5.6)
Da ich das Package definitiv in Projekten nutzen werde, die noch nicht zu PHP 5.6 migriert werden können und ich mir sicher bin, dass andere das selbe Probem haben werden, habe ich mich für zwei separate Branches entschieden, 1.x und 2.x. Version 2.x wird all die coolen neuen Features nutzen, während 1.x eine modernisierte Version des vorigen 0.9 Releases ist, das alles enthält, was PHP 5.4 kompatibel ist. Für Version 2.0 ist keine Abwärtskompatibilität garantiert (nach Semantic Versioning).
Neues Interface
Funktionen wurden Autoloader-freundlich ersetzt durch statische Methoden:
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);
Zusätzlich haben die Standard-Implementierungen ComparableComparator
und ObjectComparator
eine callback
Factory Methode erhalten, die eine aufrufbare (callable) Version ihrer selbst zurückgeben, die direkt in jeder Funktion verwendet werden kann, die einen Vergleichs-Callback als Argument entgegennehmen (wie usort()
):
usort($array, \SGH\Comparable\Comparator\ComparableComparator::callback())
Nützliche Comparatoren
Das vorige Release enthielt Comparators für SplFileInfo
Objekte als Beispiel. Mit Version 1.0 könnten diese so aussehen:
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()); } }
Zusammen mit dem SortedIterator
könnte so ein Comparator genutzt werden, Sortierreihenfolge zu einem RecursiveDirectoryIterator
hinzuzufügen:
$iterator = \SGH\Comparable\SortFunctions::sortedIterator( new RecursiveDirectoryIterator(__DIR__, FilesystemIterator::SKIP_DOTS), true); foreach ($iterator as $fileInfo) { echo $file->getFileName(), "\n"; }
Beachte, dass der “cloneItems” Parameter auf true
gesetzt wurde, weil die Dateisystem-Iteratoren in jeder Iteration sich selbst in verschiedenen Stati zurückgeben (mehr dazu). Es funktioniert aus irgendeinem Grund immer noch nicht mit einem normalen DirectoryIterator
aber wenn man den RecursiveDirectoryIterator
ohne umgebenden RecursiveIteratorIterator
nutzt, iteriert er nicht-rekursiv über das gegebene Verzeichnis.
Mehr Infos
- Mehr lesen auf GitHub: README.md
- Source Code
- Packagist Key für Composer:
sgh/comparable
RFC
Es gibt einen alten RFC, solch ein Comparable Interface Teil des PHP Cores zu machen, der kürzlich reanimiert wurde: https://wiki.php.net/rfc/comparable (aktueller PR: https://github.com/php/php-src/pull/1097). Es wäre schön, das als natives Feature in PHP 7.x zu sehen, aber so lange es das nicht gibt, kann man bereits das Interface von meinem Package nutzen. Ich war übrigens von Anfang an darauf vorbereitet:
/* * just in case that a Comparable SPL Interface comes someday */ if (interface_exists('Comparable')) { return; }
Mit dem Namespace ist das nun nicht mehr notwendig. Dennoch, wenn Du SGH\Comparable\Comparable
implementierst, ist es es kompatibel zu dem Interface das im RFC vorgeschlagen wurde, so dass es jederzeit mit einem nativen Comparable
ersetzt werden kann, sobald der RFC akzeptiert wurde.
One Reply to “Comparable Interface für PHP”
Comments are closed.
Der Ansatz ist ja auch nicht verkehrt. PHP bietet inzwischen viele Sachen die Java schon lange kann. Ich hatte da vor einiger Zeit auch mal damit experimentiert: http://blog.ebene7.com/2012/08/31/sortieren-mit-php5-closures-und-dem-java-comparator-pattern/