Library / SDK
sebastianbergmann/comparator avatar
sebastianbergmann/comparator

comparator: a PHP value comparison that reports failure as an exception

Provides the functionality to compare PHP values for equality.

7,043 stars74 forksPHPBSD-3-Clause

At a glance

What is it?
A small PHP component that picks a comparator for a pair of values at runtime, then throws a typed ComparisonFailure instead of returning a boolean. Its README runs to about a hundred words, so most integration questions only get answered from the repository itself.
Who is it for?
Adopt sebastian/comparator when you are writing a test helper or a diffing layer in PHP and the values you compare are objects whose equality is not a one line operator. Skip it when every comparison is between scalars, where PHP's own operators cost nothing and this package is a dependency plus a runtime lookup.
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 November 13, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • sebastianbergmann/comparator7,043 ★ · -2
Star history
Repository2026-09-242026-11-13
sebastianbergmann/comparator7k7k

-2 Stars in the last 50 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The Factory picks a comparator for the pair you hand it

The usage example in the README touches exactly two classes from this library. A `Factory` answers `getComparatorFor($date1, $date2)`, taking both values as its only arguments, and hands back an object that carries an `assertEquals` method. The same two values are then passed again to that method, so the choice of comparator is derived from the values themselves rather than from a class name you look up yourself.

What comes back is not a boolean. `assertEquals` throws `SebastianBergmann\Comparator\ComparisonFailure` when the values differ, and the README catches it by class name:

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

Two details matter here. The factory is handed both values because it has to decide what kind of comparison applies, and the failure arrives as an exception, so it sits inside the try block you already wrote instead of forcing a return value check at every call site.

Two DateTime objects an hour apart, same instant

The example builds two `DateTime` values on the same date, 2013-03-29: `04:13:35` in `America/New_York` and `03:13:35` in `America/Chicago`. The clock readings differ by an hour and the timezone objects differ, yet the assertion is written to pass and the README prints "Dates match". The comparison is not being made on the printed local time.

Notice what the code does not use. There is no `==`, no `===` and no string comparison anywhere in it. Equality is decided by the object the factory returned, which is the entire contribution this package makes to a codebase.

The gap is that the README never states the rule it applies. It does not define what equality means for `DateTime`, it does not enumerate the value types the factory handles, it does not say what `getComparatorFor` does when it has no comparator for a pair, and it documents no way to register one of your own. If your data holds types beyond the built in set, budget time in `src/` before you commit to this.

composer require --dev decides whether it reaches production

Two install commands are given, and the difference between them is not cosmetic. As a runtime dependency:

bash
composer require sebastian/comparator

As a development dependency:

bash
composer require --dev sebastian/comparator

The README frames the second one as the choice for when you only need the library while working on a project, and it names running your project's test suite as the example case. Composer treats the two entries differently: a runtime requirement in `composer.json` is installed for everyone who requires your package, while a `require-dev` entry is skipped under `--no-dev`. Putting the first in a library therefore ships the comparison code onto every downstream install. Putting the second in an application and then calling `Factory` from application code leaves you with a class that is absent from a deployment that installed production dependencies correctly.

Neither mistake announces itself at build time. One shows up as a package you did not mean to expose, the other as a missing class at the moment the code path first runs.

phpstan.neon and phpunit.xml hold the checks the README never mentions

The repository layout says more about how this library is worked on than the prose does. `phpunit.xml` is the test suite configuration, `phpstan.neon` the static analysis configuration and `.php-cs-fixer.dist.php` the code style configuration, all sitting at the top level beside `composer.json`. `.phive/` and `tools/` are there too, which is where Phive keeps PHAR based tool installs instead of declaring them in `composer.json`.

`build.xml` and `build/` add an Ant build on top of that. `renovate.json` covers dependency updates, `SECURITY.md` a disclosure policy, `ChangeLog.md` the history and `.github/` the workflows. The README links to GitHub Actions badges and to a Codecov coverage page, so the intent to run tests and publish coverage is visible from the badge row alone.

None of it is documented in sentences. There is no statement of which PHP versions are supported and no minimum version anywhere in the README. If you are pinning this against a particular PHP release, the constraint you need sits in `composer.json`.

Three releases between 2026-05-21 and 2026-08-07

The release history is short and recent. Tag 8.2.1 was published on 2026-05-21, 8.3.0 on 2026-06-05 and 8.4.0 on 2026-08-07. The last push to `main` landed on 2026-11-02, nearly three months after the newest tag, so work on the default branch has not reached a release yet.

Two of those three tags are minor bumps in a span of about eleven weeks. The README says nothing about what changed in any of them: there is no upgrade section, no migration note and no compatibility table. `ChangeLog.md` is in the repository, and release pages exist for 8.2.1, 8.3.0 and 8.4.0, so both places have to be read directly.

Because the tag numbers resemble PHP version numbers, the safe move before pinning is to read the constraint in `composer.json` rather than assuming a tag maps to your runtime. Leaving the requirement at `composer require sebastian/comparator` and letting Composer resolve it avoids the mapping question entirely.

No type list, no custom comparator hook, no diff output

Here is the short list of things an integrating reader needs and does not get. There is no list of comparable types. There is no documented method for adding a comparator to the `Factory`, so a type this library does not cover looks like a dead end rather than an extension point. There is no static helper for producing a comparison report either: the only API in the README is `getComparatorFor` followed by `assertEquals`, which either returns quietly or throws.

That shapes what you can build on top of it. A quiet return gives you a pass or fail signal and nothing more, so an assertion helper built on this API cannot tell a reader what differed without a second mechanism. Likewise there is no documented way to pull a textual description of the difference out of a `ComparisonFailure`, and the README does not enumerate what that object carries, so a diff view for a test failure reporter has to be assembled from the exception itself.

None of this makes the component defective. It is a small library whose documentation assumes the reader will open `src/`, which is a defensible trade, but it should be a choice you make with your eyes open.

Booleans versus a ComparisonFailure you can catch by class

The alternative is to write the comparison yourself and keep the result. `Factory` and `ComparisonFailure` exist so a mismatch arrives as a typed exception you can catch by class name, inside the try block you already have, rather than as a `false` you must test at every call site. In a test helper that performs hundreds of comparisons, that is most of what the dependency buys you.

The cost is that you give up the operator. `==`, `===` and `<=>` are always available in PHP, need no dependency and add no class loading. When your comparisons are all between scalars, those operators are the cheaper answer and this package is overhead layered on top of them.

The case where it earns its keep is the awkward one: values whose equality is not a one line operator, exactly like the two `DateTime` objects in the README example, where the local times differ and the instants do not.

Editorial conclusion

Adopt sebastian/comparator when you are writing a test helper or a diffing layer in PHP and the values you compare are objects whose equality is not a one line operator. Skip it when every comparison is between scalars, where PHP's own operators cost nothing and this package is a dependency plus a runtime lookup. Before pinning a version, read the PHP constraint in composer.json and the entries in ChangeLog.md, since the README documents neither, and decide separately whether the absence of a documented way to register your own comparator on the Factory is something you can live with.

Frequently asked questions

What does sebastian/comparator actually compare?

PHP values, for equality. The README shows a Factory handing a comparator for two values through getComparatorFor, and that comparator exposing an assertEquals method. No list of supported types is given.

How do I install sebastian/comparator for a test suite?

Use composer require --dev sebastian/comparator, which the README gives for when you need the library during development and names the test suite as the case. A runtime dependency uses composer require sebastian/comparator instead.

Which PHP versions does sebastian/comparator 8.4.0 support?

The README states no version requirement. The PHP constraint lives in the composer.json file at the top level of the repository, alongside build.xml and phpstan.neon.

Can I register my own comparator on sebastian/comparator's Factory?

Nothing in the README describes a way to do it. The example shows only new Factory, getComparatorFor and assertEquals, with no registration method, so custom value types mean reading the src directory.

What happens when sebastian/comparator finds a mismatch?

assertEquals throws SebastianBergmann\Comparator\ComparisonFailure. The README catches that class by name and prints a message, which is the only failure behaviour the documentation describes.

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)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/sebastianbergmann-comparator.svg)](https://hysenlabs.com/projects/sebastianbergmann-comparator)

Was this useful?