sebastian/comparator
sebastian/comparator compares PHP values for equality with type-aware comparators. Use the Factory to select the right comparator and get helpful ComparisonFailure details when assertions fail—ideal for test suites and tooling.
Strengths:
ComparisonFailure exceptions align with Laravel’s error-handling patterns (e.g., AssertionFailedError in Pest).DateTime, DateInterval, DOMNode, Closure, and BcMath\Number address Laravel’s common pain points (e.g., Carbon instances, API response validation, or financial calculations).CarbonComparator, CollectionComparator), enabling future-proofing for Laravel-specific types without forking the package.Gaps:
Carbon, Collection, Model). This is a design choice, not a flaw—it’s intentional to keep the package framework-agnostic.--dev), isolating it to testing environments. No runtime dependencies or autoload conflicts.Factory class dynamically selects comparators, reducing boilerplate. Example:
$factory = new \SebastianBergmann\Comparator\Factory;
$comparator = $factory->getComparatorFor($carbon1, $carbon2);
assertEquals() or custom matchers.assertSame() (which fails for identical models due to spl_object_id()) with canonicalized comparisons:
$factory->getComparatorFor($users->toArray(), $expected)->assertEquals();
$comparator = $factory->getComparatorFor($response->json(), $expected)
->withConfiguration(new \SebastianBergmann\Comparator\Configuration([
'ignoreCase' => true,
'ignoreWhitespace' => true,
]));
$actual = DB::table('users')->get()->toArray();
$comparator = $factory->getComparatorFor($actual, $expected)->assertEquals();
dev-master branch during evaluation.Carbon) must handle Laravel’s quirks (e.g., timezone-aware comparisons). Mitigation: Start with the package’s defaults and extend only where needed.Carbon, Collection, Model) need custom comparators, and what are their edge cases?Carbon instances with different timezones but identical timestamps be compared?assertEquals() calls, or only specific cases (e.g., complex data structures)?assertJsonFragment)?Carbon) if the package evolves?StrictUnifiedDiffOutputBuilder for better readability.json_encode($a) === json_encode($b)) with type-aware assertions in PHPUnit/Pest.assertEquals() with enhanced features (e.g., canonicalization, custom comparators).DateTime comparisons natively (no need for Carbon::parse() hacks).$this->assertEquals(
$users->toArray(),
$expected,
'',
false, // Canonicalize arrays
$factory->getComparatorFor($users->toArray(), $expected)
);
$response = $this->getJson('/api/users');
$comparator = $factory->getComparatorFor($response->json(), $expected)
->withConfiguration(new Configuration(['ignoreWhitespace' => true]));
$comparator->assertEquals();
$actual = DB::table('users')->pluck('email')->toArray();
$comparator = $factory->getComparatorFor($actual, $expected)->assertEquals();
composer require --dev sebastian/comparator
use SebastianBergmann\Comparator\Factory;
$factory = new Factory;
$comparator = $factory->getComparatorFor($a, $b);
$comparator->assertEquals($a, $b);
CarbonComparator).trait UsesComparator
{
protected function assertComparatorEquals($actual, $expected, $message = '', $canonicalize = false, $comparator = null) {
$factory = new Factory;
$comparator = $comparator ?? $factory->getComparatorFor($actual, $expected);
if ($canonicalize) {
$comparator = $comparator->withConfiguration(new Configuration(['canonicalize' => true]));
}
$comparator->assertEquals($actual, $expected, $message);
}
}
CollectionComparator, ModelComparator) by subclassing Comparator:
class CarbonComparator extends \SebastianBergmann\Comparator\Comparator
{
public function compare($expected, $actual, $description) {
// Custom Carbon-specific logic
}
}
Factory:
$factory->registerComparatorForType('Carbon\Carbon', function () {
return new CarbonComparator();
});
How can I help you explore Laravel packages today?