Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Comparator Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require --dev sebastian/comparator
    

    Add as a dev dependency to avoid bloating production builds.

  2. First Use Case: Replace ad-hoc assertSame() or json_encode() comparisons in tests with type-aware assertions. Example:

    use SebastianBergmann\Comparator\Factory;
    use SebastianBergmann\Comparator\ComparisonFailure;
    
    $factory = new Factory();
    $comparator = $factory->getComparatorFor($actual, $expected);
    
    try {
        $comparator->assertEquals($actual, $expected);
        $this->assertTrue(true); // Pass
    } catch (ComparisonFailure $e) {
        $this->fail($e->getMessage()); // Fail with diff
    }
    
  3. Key Entry Points:

    • Factory: Central class to instantiate comparators.
    • ComparisonFailure: Exception with rich diff output.
    • Predefined Comparators: DateTimeComparator, ArrayComparator, ClosureComparator, etc. (see Implementation Patterns).
  4. Where to Look First:


Implementation Patterns

Core Workflows

1. Basic Equality Assertions

Replace Laravel’s assertEquals() or assertSame() with type-aware comparisons:

// Before (flaky for objects/arrays)
$this->assertEquals($user->toArray(), $expectedArray);

// After (type-aware, handles object arrays)
$comparator = (new Factory())->getComparatorFor($user->toArray(), $expectedArray);
$this->assertTrue($comparator->assertEquals($user->toArray(), $expectedArray));

2. Canonicalized Array Comparisons

For order-agnostic testing (e.g., API responses, Eloquent collections):

$comparator = (new Factory())->getComparatorFor($actualArray, $expectedArray, 0.0, true);
$comparator->assertEquals($actualArray, $expectedArray);
// true = canonicalize (order-independent)

3. DateTime/Carbon Comparisons

Handle timezones and precision:

$comparator = (new Factory())->getComparatorFor(
    Carbon::parse('2023-01-01 12:00:00', 'America/New_York'),
    Carbon::parse('2023-01-01 11:00:00', 'America/Chicago')
);
$comparator->assertEquals($dt1, $dt2); // Works with timezone offsets

4. Custom Comparator Integration

Extend for Laravel-specific types (e.g., Carbon, Collection):

use SebastianBergmann\Comparator\Comparator;
use SebastianBergmann\Comparator\ComparatorInterface;

class CarbonComparator implements ComparatorInterface
{
    public function assertEquals($expected, $actual, $description = '', $delta = 0.0, $canonicalize = false)
    {
        // Custom Carbon logic (e.g., timezone normalization)
        if (!$expected->eq($actual)) {
            throw new ComparisonFailure($expected, $actual, $description);
        }
    }
}

// Register in Factory
$factory = new Factory();
$factory->registerComparator('Carbon\Carbon', new CarbonComparator());

5. Diff Output in Tests

Leverage rich diffs for debugging:

try {
    $comparator->assertEquals($actual, $expected);
} catch (ComparisonFailure $e) {
    $this->fail($e->getDiff());
    // Outputs unified diff (e.g., for arrays/strings)
}

Laravel-Specific Patterns

Eloquent Model Testing

// Compare models with relationships (order-agnostic)
$comparator = (new Factory())->getComparatorFor(
    $user->load('posts')->toArray(),
    $expected,
    0.0,
    true // Canonicalize
);
$comparator->assertEquals($actual, $expected);

API Response Validation

$response = $this->getJson('/api/users');
$comparator = (new Factory())->getComparatorFor(
    $response->json(),
    $expectedSchema,
    0.0,
    true // Ignore array order
);
$comparator->assertEquals($response->json(), $expectedSchema);

Database Seeder Validation

$seededUsers = User::all()->toArray();
$comparator = (new Factory())->getComparatorFor($seededUsers, $expectedUsers, 0.0, true);
$comparator->assertEquals($seededUsers, $expectedUsers);

Closure/Callback Testing

$closure1 = fn() => 'result';
$closure2 = fn() => 'result';
$comparator = (new Factory())->getComparatorFor($closure1, $closure2);
$comparator->assertEquals($closure1, $closure2); // Works since v7.1.0

Gotchas and Tips

Pitfalls

  1. Object Array Sorting:

    • Issue: Arrays of identical objects may fail due to spl_object_id() sorting (fixed in v8.1.2).
    • Fix: Use canonicalize = true for order-agnostic comparisons:
      $comparator->assertEquals($actual, $expected, '', 0.0, true);
      
  2. DateTime Precision:

    • Issue: Microsecond precision may cause false negatives.
    • Fix: Use DateTimeComparator with a delta tolerance:
      $comparator = (new Factory())->getComparatorFor($dt1, $dt2, 1.0); // 1-second tolerance
      
  3. Non-Serializable Diffs:

    • Issue: ComparisonFailure may fail to serialize if stack traces contain non-serializable objects (fixed in v8.2.1).
    • Fix: Avoid custom exceptions in comparators or use getDiff() instead of throwing.
  4. XML/HTML Comparisons:

    • Issue: DOMNodeComparator may crash on malformed XML or ignore comments (fixed in v7.1.8).
    • Fix: Pre-canonicalize XML strings or use SimpleXMLElement for simpler cases.
  5. Closure Comparisons:

    • Issue: Closures are compared by value (not reference) since v8.2.1, which may break assumptions.
    • Fix: Test closures by invoking them or use assertSame() for reference equality.
  6. PHP 8.5+ Warnings:

    • Issue: Deprecated SplObjectStorage methods may trigger warnings (suppressed in v7.1.4+).
    • Fix: Update to the latest version (v8.x) for compatibility.

Debugging Tips

  1. Inspect Diffs:

    • Use $e->getDiff() to get a unified diff for arrays/strings:
      catch (ComparisonFailure $e) {
          $this->fail($e->getDiff());
      }
      
  2. Enable Canonicalization:

    • Set canonicalize = true to ignore array order/keys:
      $comparator->assertEquals($actual, $expected, '', 0.0, true);
      
  3. Custom Comparator Logging:

    • Add debug output in custom comparators:
      class CustomComparator implements ComparatorInterface {
          public function assertEquals($expected, $actual, $description = '', $delta = 0.0, $canonicalize = false) {
              error_log("Comparing: " . print_r($expected, true) . " vs " . print_r($actual, true));
              // ...
          }
      }
      
  4. Performance Tuning:

    • Avoid deep comparisons in performance-critical paths (e.g., loops). Cache comparators:
      $factory = new Factory();
      $arrayComparator = $factory->getComparatorFor([], []);
      
  5. Laravel-Specific Quirks:

    • Carbon: Use CarbonComparator or normalize timezones before comparison:
      $dt1->setTimezone('UTC');
      $dt2->setTimezone('UTC');
      
    • Collections: Convert to arrays first:
      $comparator->assertEquals($collection->toArray(), $expected);
      

Extension Points

  1. Register Custom Comparators:

    $factory = new Factory();
    $factory->registerComparator('App\Models\User', new UserComparator());
    
  2. Override Default Comparators:

    $factory = new Factory();
    $factory->registerComparator('DateTime', new CustomDateTimeComparator());
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata
splash/openapi