Library / SDK
sebastianbergmann/comparator avatar
sebastianbergmann/comparator

sebastian/comparator: PHP Value Equality for Test Assertions

Provides the functionality to compare PHP values for equality.

7,045 stars74 forksPHPBSD-3-Clause

At a glance

What is it?
A small Composer library that decides whether two PHP values are equal, with per-type comparators, a factory that picks the right one, and a ComparisonFailure exception that carries the diff. It is built for test tooling, not for application logic.
Who is it for?
Adopt sebastian/comparator if you are writing an assertion library, a test double framework, or a diff-producing test runner in PHP and need type-aware equality without reimplementing it. Do not adopt it as a general-purpose validation or business-rule comparator in production code: it is a development-time dependency by the README's own framing, and ComparisonFailure is an assertion-shaped exception, not a domain error.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What sebastian/comparator actually decides

The README states the scope in one sentence: the component "provides the functionality to compare PHP values for equality." That is narrower than it sounds. It does not sort, order, hash, or serialize. It answers a boolean question with a side channel.

The side channel is the point. The documented entry point is assertEquals, and when the two values differ it raises ComparisonFailure rather than returning false. A caller that only wants a yes or no still has to catch an exception, and a caller that wants to report why two values differ gets an object to inspect. That design choice tells you who the library is for: code that produces failure messages. PHPUnit's assertion layer is the obvious consumer, and the README's own example prints "Dates don't match" inside a catch block, which is exactly the shape of a test runner's output path.

If you need a comparator for sorting arrays with usort, this is the wrong package. The name overlaps with java.util.Comparator and with the Minecraft redstone component, and neither has anything to do with this repository.

The Factory and the per-type comparator registry

The mechanism visible in the README is a two-step lookup. You construct a Factory, hand it two values, and ask for a comparator:

$factory->getComparatorFor($date1, $date2)

What comes back is not a generic function but an object with its own assertEquals method, chosen according to the types of the two arguments. That is the whole architecture: a registry of type-specific comparators behind one dispatch point. The README does not enumerate the registry, so the set of types with dedicated comparators is something you have to read src/ to learn. The repository layout confirms the split between src/, tests/, and a tools/ directory, which is consistent with a library that carries its own test suite and development tooling rather than shipping a CLI.

The practical consequence is that equality is not one rule. Two DateTime objects are compared according to their instants, not their wall-clock strings, which is why the README's example is worth reading closely. The two values in it are written as 04:13:35 in America/New_York and 03:13:35 in America/Chicago. Those are the same moment expressed in two zones. The example is constructed so that a naive string comparison fails and a timezone-aware comparison succeeds. That is the clearest statement in the README about what type-aware comparison buys you.

The trade-off is that dispatch happens on runtime types. If your own value objects are not covered by a registered comparator, you are relying on whatever the fallback does, and the README is silent on what that fallback is. That silence is the first thing to close by reading the source.

Installing it and running the README example

Installation is Composer only. The README gives two forms, and the second is the one most projects want, because the library is framed as something you need during development:

bash
composer require --dev sebastian/comparator

If the library is genuinely part of your runtime rather than your test suite, the README also gives the non-dev form:

bash
composer require sebastian/comparator

After either command, Composer's autoloader picks up the SebastianBergmann\Comparator namespace with no extra configuration. The README shows no service provider, no bootstrap file, and no environment variables, which matches a library of this size.

The first real use is the README's DateTime example. It constructs two DateTime objects that denote the same instant in different timezones, asks the factory for a comparator, and prints a message depending on whether ComparisonFailure is thrown:

php
<?php
use SebastianBergmann\Comparator\Factory;
use SebastianBergmann\Comparator\ComparisonFailure;

$date1 = new DateTime('2013-03-29 04:13:35', new DateTimeZone('America/New_York'));
$date2 = new DateTime('2013-03-29 03:13:35', new DateTimeZone('America/Chicago'));

$factory = new Factory;
$comparator = $factory->getComparatorFor($date1, $date2);

try {
    $comparator->assertEquals($date1, $date2);
    print "Dates match";
} catch (ComparisonFailure $failure) {
    print "Dates don't match";
}

Run that and you should see "Dates match", because the two timestamps are the same instant. Change one of the times by a minute and the catch block runs instead. The README does not document additional constructor arguments for Factory, so treat the no-argument form as the documented one.

ComparisonFailure is not a validation error

The exception type is the sharpest limitation. ComparisonFailure belongs to the vocabulary of test assertions. Its name says a comparison failed, not that input was invalid or a business rule was violated. If you throw it from application code, your error handling has to distinguish it from the exceptions your domain actually raises, and any framework that maps exception classes to HTTP statuses will need a rule for it.

The README does not document what ComparisonFailure exposes beyond being catchable. It does not say whether it carries the expected value, the actual value, a diff, or a formatted message. That matters if you intend to build reporting on top of it, because the whole reason to choose an exception over a boolean is to get that payload. The README is silent, so the answer lives in src/ and in the ChangeLog.

The second limitation is coverage. The README documents DateTime behaviour and nothing else. There is no list of supported types, no statement about floats and epsilon comparison, and no statement about object identity versus structural equality. For a library whose entire job is equality, that is a thin contract. You can read the tests/ directory to infer the intended semantics, but inference is not documentation, and equality semantics that you guessed are equality semantics that can shift under you on a minor release.

How this differs from PHPUnit's assertSame and from hand-rolled checks

The nearest real alternative is not another comparator library. It is the assertion API most PHP developers already have installed. PHPUnit exposes assertSame for strict identity and assertEquals for loose, type-aware comparison, and this package is the machinery behind that second family of assertions in PHPUnit's own stack. The difference in approach is where the type logic lives. With assertSame you get one rule, applied uniformly, and the caller decides what "same" means. With sebastian/comparator the rule is selected per value pair, so DateTime, arrays, and scalars can each be judged by their own semantics.

That is a real gain when you are comparing timestamps, because strict identity on two DateTime objects is not what anyone means by "the same time". It is a liability when you want a single predictable rule across your whole codebase, because the comparison you get depends on the runtime types of the arguments rather than on the call site.

The second alternative is writing the check yourself: normalize both values, compare, throw. That is a few lines for scalars and a research project for objects with cycles, floats, and nested arrays. The honest framing is that this package exists because that research project is tedious, and it is worth using exactly when your values are complex enough that hand-rolling would be worse.

Maintenance, releases, and the BSD-3-Clause licence

The repository is not archived, and the last push was on 2026-09-17, five days before the date of writing. Release cadence is visible in the tags: 8.2.1 on 2026-05-21, 8.3.0 on 2026-06-05, 8.4.0 on 2026-08-07. That is a minor release roughly every two to three months, with patch releases in between. The version numbers are major-version 8, which means the project has been willing to break compatibility across major lines.

The upgrade cost is therefore concentrated at major boundaries, not at minor ones. The presence of ChangeLog.md, renovate.json, and a .phive/ directory in the repository root suggests the maintainers track dependency updates and pin their own tooling, which reduces the chance of a minor release arriving with a surprise. It does not eliminate the risk that a comparator's semantics change in a patch release, and the README gives you no compatibility statement to check against.

The licence is BSD-3-Clause. That is a permissive licence, which in practice means you can use the library in closed-source projects and only need to preserve the copyright notice and the licence text. It is not a copyleft licence, so it does not reach into your own source files. This is a description of what the licence identifier means, not legal advice; if your organisation has rules about third-party licences, the LICENSE file in the repository is the text that governs.

Editorial conclusion

Adopt sebastian/comparator if you are writing an assertion library, a test double framework, or a diff-producing test runner in PHP and need type-aware equality without reimplementing it. Do not adopt it as a general-purpose validation or business-rule comparator in production code: it is a development-time dependency by the README's own framing, and ComparisonFailure is an assertion-shaped exception, not a domain error. Before wiring it in, verify which comparator the factory selects for your own value types, since the README documents DateTime handling and nothing about your custom classes.

Frequently asked questions

How do I install sebastian/comparator?

Add it with Composer. The README gives composer require sebastian/comparator for a runtime dependency and composer require --dev sebastian/comparator when you only need it for a test suite.

How do I use sebastian/comparator to compare two DateTime values?

Construct a Factory, call getComparatorFor with the two DateTime objects, and call assertEquals on the returned comparator inside a try block, catching ComparisonFailure to handle a mismatch. The README's example compares the same instant written in America/New_York and America/Chicago and prints "Dates match".

What does sebastian/comparator throw when two values are not equal?

It throws SebastianBergmann\Comparator\ComparisonFailure from the comparator's assertEquals method. The README catches that exception but does not document what data the exception carries.

Which PHP types does sebastian/comparator support?

The README only documents DateTime comparison and states the general purpose of comparing PHP values for equality. It does not list the registered comparators, so the supported types have to be read from the src/ directory.

What licence does sebastian/comparator use?

The repository is licensed under BSD-3-Clause, a permissive licence that requires preserving the copyright notice and licence text.

Official sources

  1. Issues
  2. License: BSD-3-Clause
  3. README
  4. Releases
  5. sebastianbergmann/comparator on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/sebastianbergmann-comparator.svg)](https://hysenlabs.com/projects/sebastianbergmann-comparator)