Dernière version : 1.0.0-bêta
Bibliothèque PHP 5.4+ pour représenter les relations d'équivalence et les stratégies de hachage et de tri des valeurs.
Les relations d'équivalence sont utiles pour établir une méthode généralisée de comparaison des valeurs par rapport aux exigences spécifiques à un domaine, ainsi que pour représenter des critères personnalisés permettant de comparer des valeurs dans des contextes délimités, spécialement pour une utilisation dans des collections.
Des fonctionnalités complémentaires, telles que le hachage et le tri, sont également couvertes par cette bibliothèque, ce qui en fait un ajout précieux à votre ceinture d'outils de développement.
L'API est largement documentée dans le code source. De plus, une version HTML est également disponible pour une visualisation plus pratique dans le navigateur.
Utilisez Composer pour installer le package :
$ composer require phpcommon/comparison
Une relation est un outil mathématique permettant de décrire des associations entre des éléments d'ensembles. Les relations sont largement utilisées en informatique, notamment dans les bases de données et les applications de planification.
Contrairement à la plupart des langages modernes, PHP ne prend pas en charge la surcharge d’opérateurs, historiquement évitée en tant que choix de conception. En d’autres termes, il n’est pas possible de remplacer le comportement par défaut des opérateurs natifs, tels que égal, identique, supérieur à, inférieur à, etc. Par exemple, Java fournit l’interface Comparable, tandis que Python propose des méthodes magiques.
L'importance d'un tel concept devient plus évidente dans les situations où la notion d'équivalence ou d'ordre varie selon le sujet de comparaison ou le contexte, comme discuté dans les sections suivantes.
En mathématiques, une relation d'équivalence est une relation binaire réflexive , symétrique et transitive . Dans le domaine informatique, il existe cependant une autre propriété dont il faut tenir compte : la cohérence . La cohérence signifie qu'une relation ne doit pas produire des résultats différents pour la même entrée.
Une relation d'équivalence omniprésente est la relation d'égalité entre les éléments d'un ensemble quelconque. D'autres exemples incluent :
Pour les besoins de cette bibliothèque, une relation d'équivalence peut être générique ou spécifique à un type. Les relations spécifiques au type sont définies en implémentant des interfaces Equatable
ou Equivalence
, tandis que les équivalences génériques doivent implémenter cette dernière.
L'interface Equatable
définit une méthode généralisée qu'une classe implémente pour créer une méthode spécifique au type permettant de déterminer l'égalité des instances.
Pour illustrer, considère une classe Money
, qui vise à représenter des valeurs monétaires. Cette classe est un bon candidat pour implémenter l'interface Equatable
, car Money
est un objet de valeur, c'est-à-dire que la notion d'égalité de ces objets n'est pas basée sur l'identité. Au lieu de cela, deux instances de Money
sont égales si elles ont les mêmes valeurs. Ainsi, tandis que Money::USD(5) === Money::USD(5)
renvoie false
, Money::USD(5)->equals(Money::USD(5))
renvoie true
.
Voici la classe mentionnée précédemment :
final class Money implements Equatable
{
private $ amount ;
private $ currency ;
public function __construct ( $ amount , $ currency )
{
$ this -> amount = ( int ) $ amount ;
$ this -> currency = ( string ) $ currency ;
}
public function equals ( Equatable $ other )
{
if (! $ other instanceof self) {
return false ;
}
return $ this -> amount === $ other -> amount && $ this -> currency === $ other -> currency ;
}
}
Il existe cependant de nombreux cas où il devient nécessaire de disposer d'un moyen non standard ou externe pour comparer deux valeurs. Le cas d'utilisation le plus évident de ces relations personnalisées est peut-être celui de leur utilisation avec des collections, mais il est également utile pour fournir ces fonctionnalités à des valeurs scalaires ou à une classe existante qui ne peut pas les fournir elle-même, car elle appartient à un package tiers ou à une classe construite. en PHP.
Supposons que vous développiez un logiciel pour aider les hôpitaux à gérer les dons de sang. L'une des exigences stipule qu'une infirmière ne peut pas prélever du sang sur des donneurs ayant le même groupe sanguin. Une relation pour ce scénario ressemblerait à ceci :
use PhpCommon Comparison Equivalence ;
class BloodGroupEquivalence implements Equivalence
{
public function equals ( Equatable $ other )
{
return get_class ( $ other ) === static ::class;
}
public function equivalent ( $ left , $ right )
{
if (! $ left instanceof Person) {
UnexpectedTypeException:: forType (Person::class, $ left );
}
if (! $ right instanceof Person) {
return false ;
}
return $ left -> getBloodType () === $ right -> getBloodType ();
}
}
Cette relation détermine si deux personnes sont du même groupe sanguin :
$ equivalence = new BloodGroupEquivalence ();
$ donors = new BloodDonors ( $ equivalence );
$ james = new Person ( ' James ' , ' A ' );
$ john = new Person ( ' John ' , ' A ' );
// James and John are considered equivalent once they are of the same blood group
var_dump ( $ equivalence -> equivalent ( $ james , $ john )); // Outputs bool(true)
// Initially, none of them are present in the collection
var_dump ( $ volunteers -> contains ( $ james )); // Outputs bool(false)
var_dump ( $ volunteers -> contains ( $ john )); // Outputs bool(false)
// Add James to the set of volunteers
$ donors -> add ( $ james );
// Now, considering only the blood group of each donor for equality, both of
// them are considered present in the collection
$ donors -> contains ( $ james ); // Outputs bool(true)
$ donors -> contains ( $ john ); // Outputs bool(true)
Puisque BloodGroupEquivalence
établit une relation d'équivalence entre les personnes en fonction de leur groupe sanguin, toute tentative d'ajouter John à la collection sera ignorée, car James est déjà présent et ils sont du même groupe sanguin.
Cela peut paraître un peu compliqué pour une simple exigence au début, mais dans des cas réels, cela peut être utilisé pour comparer l'équivalence entre des groupes sanguins compatibles, afin de diviser les donneurs en groupes.
Cette bibliothèque fournit des relations d'équivalence génériques dans le cadre de la bibliothèque standard, comme décrit ci-dessous.
Compare deux valeurs d'identité.
Cette relation est basée sur l'opérateur identique. Dans la plupart des cas, deux valeurs sont considérées comme équivalentes si elles ont le même type et la même valeur, à quelques exceptions près :
NAN
est inégal à toutes les autres valeurs, y compris elle-même.Le tableau suivant résume la manière dont les opérandes des différents types sont comparés :
$A $B | NUL | Booléen | Entier | Flotter | Chaîne | Ressource | Tableau | Objet |
---|---|---|---|---|---|---|---|---|
NUL | true | false | false | false | false | false | false | false |
Booléen | false | $A === $B | false | false | false | false | false | false |
Entier | false | false | $A === $B | false | false | false | false | false |
Flotter | false | false | false | $A === $B | false | false | false | false |
Chaîne | false | false | false | false | $A === $B | false | false | false |
Ressource | false | false | false | false | false | $A === $B | false | false |
Tableau | false | false | false | false | false | false | $A === $B | false |
Objet | false | false | false | false | false | false | false | $A === $B |
Compare deux valeurs d'égalité.
L'équivalence de valeur se comporte exactement comme l'équivalence d'identité, sauf qu'elle délègue la comparaison entre les objets Equatable
aux objets comparés. De plus, des relations externes peuvent être spécifiées pour comparer les valeurs d'un type particulier. Ceci est utile dans les cas où il est souhaitable de remplacer le comportement par défaut pour un type spécifique, mais de conserver tous les autres. Il est également utile pour définir une relation pour les objets de classes appartenant à des packages tiers ou intégrés à PHP.
Les règles suivantes sont utilisées pour déterminer si deux valeurs sont considérées comme équivalentes :
NAN
est inégal à toutes les autres valeurs, y compris elle-même.Equatable
et l'expression $left->equals($right)
est évaluée à true
.$relation->equivalent($left, $right)
est évaluée à true
.Le tableau suivant résume la manière dont les opérandes des différents types sont comparés :
$A $B | NUL | Booléen | Entier | Flotter | Chaîne | Ressource | Tableau | Objet | Équatable |
---|---|---|---|---|---|---|---|---|---|
NUL | true | false | false | false | false | false | false | false | false |
Booléen | false | $A === $B | false | false | false | false | false | false | false |
Entier | false | false | $A === $B | false | false | false | false | false | false |
Flotter | false | false | false | $A === $B | false | false | false | false | false |
Chaîne | false | false | false | false | $A === $B | false | false | false | false |
Ressource | false | false | false | false | false | $A === $B | false | false | false |
Tableau | false | false | false | false | false | false | eq($A, $B) | false | false |
Objet | false | false | false | false | false | false | false | $A === $B | false |
Équatable | false | false | false | false | false | false | false | false | $A‑>equals($B) |
Où eq()
désigne une fonction qui compare chaque paire d'entrées correspondantes de manière récursive, selon les règles décrites ci-dessus.
Cette relation permet également de remplacer la logique d'équivalence pour une classe particulière sans qu'il soit nécessaire de créer une nouvelle relation. Par exemple, supposons que vous souhaitiez comparer des instances de DateTime
en fonction de leurs valeurs, mais conserver le comportement par défaut pour les autres types. Cela peut être accompli en spécifiant une relation personnalisée à utiliser chaque fois qu'une instance de DateTime
est comparée à une autre valeur :
use PhpCommon Comparison Hasher ValueHasher as ValueEquivalence ;
use PhpCommon Comparison Hasher DateTimeHasher as DateTimeEquivalence ;
use DateTime ;
$ relation = new ValueEquivalence ([
DateTime::class => new DateTimeEquivalence ()
]);
$ date = ' 2017-01-01 ' ;
$ timezone = new DateTimeZone ( ' Pacific/Nauru ' );
$ left = new DateTime ( $ date , $ timezone );
$ right = new DateTime ( $ date , $ timezone );
// Outputs bool(true)
var_dump ( $ relation -> equivalent ( $ left , $ right ));
Compare deux valeurs pour l'égalité sémantique.
Une équivalence sémantique est prévue pour les versions futures. Cela permettrait la comparaison de valeurs qui semblent sémantiquement similaires, même si elles sont de types différents. C'est similaire au fonctionnement de la comparaison lâche en PHP, mais dans des conditions plus restrictives, de telle sorte que les propriétés de réflexivité, de symétrie et de transitivité soient conservées.
Compare deux instances DateTime
en fonction de leur date, heure et fuseau horaire.
Cette relation considère que deux instances de DateTime
sont équivalentes si elles ont la même date, heure et fuseau horaire :
use PhpCommon Comparison Hasher IdentityHasher as IdentityEquivalence ;
use PhpCommon Comparison Hasher DateTimeHasher as DateTimeEquivalence ;
use DateTime ;
$ identity = new IdentityEquivalence ();
$ value = new DateTimeEquivalence ();
$ date = ' 2017-01-01 ' ;
$ timezone = new DateTimeZone ( ' Pacific/Nauru ' );
$ left = new DateTime ( $ date , $ timezone );
$ right = new DateTime ( $ date , $ timezone );
// Outputs bool(false)
var_dump ( $ identity -> equivalent ( $ left , $ right ));
// Outputs bool(true)
var_dump ( $ value -> equivalent ( $ left , $ right ));
En PHP, les clés de tableau ne peuvent être représentées que sous forme de nombres et de chaînes. Cependant, il existe plusieurs cas dans lesquels le stockage de types complexes sous forme de clés s'avère utile. Prenons comme exemple des classes qui représentent différents types de nombres ou de chaînes, tels que des objets GMP, des chaînes Unicode, etc. Il serait également pratique de pouvoir utiliser de tels objets comme clés de tableau.
Pour combler cette lacune, cette bibliothèque introduit les interfaces Hashable
et Hasher
, qui spécifient un protocole pour fournir des codes de hachage pour les valeurs. Ces interfaces n'exigent pas que les implémenteurs fournissent des fonctions de hachage parfaites. Autrement dit, deux valeurs qui ne sont pas équivalentes peuvent avoir le même code de hachage. Cependant, pour déterminer si deux valeurs avec le même code de hachage sont effectivement égales, les notions de hachage et d’équivalence doivent être combinées de manière complémentaire. Cela explique pourquoi Hasher
et Hashable
étendent respectivement Equivalence
et Equatable
.
Un mot d'avertissement
Un code de hachage est destiné à une insertion et une recherche efficaces dans des collections basées sur une table de hachage et à des vérifications rapides des inégalités. Un code de hachage n'est pas une valeur permanente. Pour cette raison:
- Ne sérialisez pas les valeurs de code de hachage et ne les stockez pas dans des bases de données.
- N'utilisez pas le code de hachage comme clé pour récupérer un objet d'une collection à clé.
- N'envoyez pas de codes de hachage entre domaines d'application ou processus. Dans certains cas, les codes de hachage peuvent être calculés par processus ou par domaine d'application.
- N'utilisez pas le code de hachage à la place d'une valeur renvoyée par une fonction de hachage cryptographique si vous avez besoin d'un hachage cryptographiquement fort.
- Ne testez pas l'égalité des codes de hachage pour déterminer si deux objets sont égaux, une fois que des valeurs inégales peuvent avoir des codes de hachage identiques.
Il existe des cas où il peut être souhaitable de définir une logique de hachage personnalisée pour une classe afin de répondre au mieux à vos besoins. Par exemple, supposons que vous disposiez d’une classe Point pour représenter un point 2D :
namespace PhpCommon Comparison Equatable ;
final class Point implements Equatable
{
private $ x ;
private $ y ;
public function __construct ( $ x , $ y )
{
$ this -> x = ( int ) $ x ;
$ this -> y = ( int ) $ y ;
}
public function equals ( Equatable $ point )
{
if (! $ point instanceof Point) {
return false ;
}
return $ this -> x === $ point -> x && $ this -> y === $ point -> y ;
}
}
Un Point
contient les coordonnées x et y d'un point. Selon la définition de la classe, deux points sont considérés comme égaux s’ils ont les mêmes coordonnées. Cependant, si vous avez l'intention de stocker des instances de Point
dans une carte basée sur le hachage, par exemple parce que vous souhaitez associer des coordonnées à des étiquettes, vous devez alors vous assurer que votre classe produit des codes de hachage cohérents avec la logique utilisée pour déterminer quand deux les points sont considérés comme égaux :
namespace PhpCommon Comparison Equatable ;
final class Point implements Hashable
{
private $ x ;
private $ y ;
public function __construct ( $ x , $ y )
{
$ this -> x = ( int ) $ x ;
$ this -> y = ( int ) $ y ;
}
public function equals ( Equatable $ point )
{
if (! $ point instanceof Point) {
return false ;
}
return $ this -> x === $ point -> x && $ this -> y === $ point -> y ;
}
public function getHash ()
{
return 37 * ( 31 + $ this -> $ x ) + $ this -> $ x ;
}
}
De cette façon, la méthode getHash()
fonctionne conformément à la méthode equals()
, même si l’algorithme de hachage n’est peut-être pas idéal. La mise en œuvre d’algorithmes efficaces pour la génération de codes de hachage dépasse le cadre de ce guide. Cependant, il est recommandé d'utiliser un algorithme rapide qui produit des résultats raisonnablement différents pour des valeurs inégales, et de déplacer la logique de comparaison lourde vers Equatable::equals()
.
Notez que les objets hachables doivent soit être immuables, soit vous devez faire preuve de discipline pour ne pas les modifier après avoir été utilisés dans des structures basées sur le hachage.
Un Hasher fournit une fonctionnalité de hachage pour les types primitifs et les objets des classes qui n'implémentent pas Hashable
.
La méthode hash()
introduite par cette interface est destinée à fournir un moyen d'effectuer des contrôles d'inéquivalence rapides et une insertion et une recherche efficaces dans les structures de données basées sur le hachage. Cette méthode est toujours cohérente avec equivalent()
, ce qui signifie que pour toute référence $x
et $y
, si equivalent($x, $y)
, alors hash($x) === hash($y)
. Cependant, si equivalence($x, $y)
est évalué à false
, hash($x) === hash($y)
peut toujours être vrai. C'est pourquoi la méthode hash()
convient aux contrôles d'inéquivalence , mais pas aux contrôles d'équivalence .
Toutes les implémentations d' Equivalence
incluses dans cette bibliothèque fournissent également une fonctionnalité de hachage. Plus d’informations sur la façon dont les valeurs sont hachées peuvent être trouvées dans la documentation de l’implémentation respective.
Suivant la même logique que les concepts évoqués précédemment, Comparable
et Comparator
sont des interfaces permettant de fournir respectivement des stratégies de tri naturelles et personnalisées. Les deux interfaces spécifient une relation d'ordre total, une relation réflexive , antisymétrique et transitive .
Cette interface impose un ordonnancement total sur les objets de chaque classe qui l'implémente. Cet ordre est appelé ordre naturel de la classe, et la méthode Comparable::compareTo()
est appelée sa méthode de comparaison naturelle .
L'exemple suivant montre comment une classe peut définir l'ordre naturel de ses instances :
use PhpCommon Comparison UnexpectedTypeException ;
final class BigInteger implements Comparable
{
private $ value ;
public function __construct ( $ value )
{
$ this -> value = ( string ) $ value ;
}
public function compareTo ( Comparable $ other )
{
if (! $ other instanceof self) {
throw UnexpectedTypeException:: forType (BigInteger::class, $ other );
}
return bccomp ( $ this -> value , $ other -> value );
}
}
Le but d'un Comparator
est de vous permettre de définir une ou plusieurs stratégies de comparaison qui ne sont pas la stratégie de comparaison naturelle d'une classe. Idéalement, un Comparator
doit être implémenté par une classe différente de celle pour laquelle il définit la stratégie de comparaison. Si vous souhaitez définir une stratégie de comparaison naturelle pour une classe, vous pouvez implémenter Comparable
à la place.
Les comparateurs peuvent être transmis à une méthode de tri d'une collection pour permettre un contrôle précis de son ordre de tri. Il peut également être utilisé pour contrôler l'ordre de certaines structures de données, telles que des ensembles triés ou des cartes triées. Par exemple, considérons le comparateur suivant qui classe les chaînes en fonction de leur longueur :
use PhpCommon Comparison Comparator ;
class StringLengthComparator implements Comparator
{
public function compare ( $ left , $ right )
{
return strlen ( $ left ) <=> strlen ( $ right );
}
}
$ comparator = new StringLengthComparator ();
// Outputs int(-1)
var_dump ( $ comparator -> compare ( ' ab ' , ' a ' ));
Cette implémentation représente l'une des nombreuses façons possibles de trier les chaînes. D'autres stratégies incluent le tri par ordre alphabétique, lexicographique, etc.
Veuillez consulter CHANGELOG pour plus d'informations sur ce qui a changé récemment.
$ composer test
Consultez la documentation des tests pour plus de détails.
Les contributions au package sont toujours les bienvenues !
Veuillez consulter CONTRIBUTION et CONDUITE pour plus de détails.
Si vous découvrez des problèmes liés à la sécurité, veuillez envoyer un e-mail à [email protected] au lieu d'utiliser le suivi des problèmes.
Tout le contenu de ce package est sous licence MIT.