Weave 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

Php Sequence Matcher Laravel Package

jfcherng/php-sequence-matcher

PHP 8.4+ longest sequence matcher inspired by Python difflib. Compare strings or arrays to find matching blocks and similarities for diffing, change detection, and text analysis. Lightweight library extracted from php-diff with improvements.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require jfcherng/php-sequence-matcher
    

    Requires PHP 8.4+ (compatible with Laravel 11+).

  2. First Use Case: Compare two strings for similarity ratio (0.0 to 1.0):

    use Jfcherng\SequenceMatcher\SequenceMatcher;
    
    $matcher = new SequenceMatcher();
    $ratio = $matcher->ratio("Laravel", "Laravul"); // ~0.857
    
  3. Where to Look First:

    • README.md for core methods (ratio(), getOpCodes()).
    • Tests for edge-case examples (Unicode, empty strings).
    • Release Notes for breaking changes (e.g., PHP 8.3+ requirement).

Implementation Patterns

Usage Patterns

  1. Basic Comparisons:

    $matcher = new SequenceMatcher();
    $ratio = $matcher->ratio($a, $b); // Similarity score (0.0–1.0)
    $isSimilar = $ratio >= 0.7; // Custom threshold
    
  2. Laravel Integration:

    • Service Binding (in AppServiceProvider):
      $this->app->singleton(SequenceMatcher::class, fn() => new SequenceMatcher());
      
    • Facade (optional, via Macroable):
      SequenceMatcher::macro('isSimilar', fn($a, $b, $threshold = 0.7) =>
          app(SequenceMatcher::class)->ratio($a, $b) >= $threshold
      );
      
  3. Eloquent Observers: Track field changes by comparing old() and new() attributes:

    class PostObserver {
        public function saved(Model $post) {
            if ($post->isDirty('content')) {
                $similarity = app(SequenceMatcher::class)
                    ->ratio($post->getOriginal('content'), $post->content);
                logger()->info("Content similarity: {$similarity}");
            }
        }
    }
    
  4. API Responses: Return similarity scores in JSON:

    return response()->json([
        'data' => $model,
        'similarity' => app(SequenceMatcher::class)->ratio($model->content, $referenceContent),
    ]);
    
  5. Queue Jobs: Offload heavy comparisons (e.g., large arrays):

    class CompareLargeDataJob implements ShouldQueue {
        public function handle() {
            $matcher = new SequenceMatcher();
            $result = $matcher->getOpCodes($largeArrayA, $largeArrayB);
            // Store results in DB/cache
        }
    }
    

Workflows

  1. Data Migration: Compare legacy and new database records:

    $legacy = LegacyModel::find($id)->toArray();
    $new = NewModel::find($id)->toArray();
    $similarity = app(SequenceMatcher::class)->ratio(json_encode($legacy), json_encode($new));
    
  2. Testing: Assert API responses match expected outputs:

    $response = $this->get('/api/data');
    $expected = ['key' => 'value'];
    $similarity = app(SequenceMatcher::class)->ratio($response->json(), $expected);
    $this->assertGreaterThan(0.9, $similarity);
    
  3. Content Moderation: Flag duplicate submissions:

    $submissions = Submission::all()->pluck('content');
    $newSubmission = request('content');
    $duplicates = collect($submissions)
        ->filter(fn($content) => app(SequenceMatcher::class)->ratio($content, $newSubmission) > 0.9);
    

Integration Tips

  1. Normalization: Preprocess data for consistent comparisons:

    $normalizedA = Str::lower(json_encode($a, JSON_UNESCAPED_UNICODE));
    $normalizedB = Str::lower(json_encode($b, JSON_UNESCAPED_UNICODE));
    $ratio = app(SequenceMatcher::class)->ratio($normalizedA, $normalizedB);
    
  2. Caching: Cache results for repeated comparisons:

    $cacheKey = "similarity:{$aHash}:{$bHash}";
    return cache()->remember($cacheKey, now()->addHours(1), fn() =>
        app(SequenceMatcher::class)->ratio($a, $b)
    );
    
  3. Batch Processing: Use Laravel Collections for bulk operations:

    $results = collect($itemsA)
        ->crossJoin($itemsB)
        ->map(fn($pair) => [
            'a' => $pair[0],
            'b' => $pair[1],
            'similarity' => app(SequenceMatcher::class)->ratio($pair[0], $pair[1]),
        ]);
    
  4. Custom Scoring: Extend the matcher for domain-specific logic:

    class CustomMatcher extends SequenceMatcher {
        public function weightedRatio(array $a, array $b, array $weights) {
            $total = 0;
            foreach ($a as $key => $value) {
                $weight = $weights[$key] ?? 1;
                $total += $weight * $this->ratio($value, $b[$key] ?? '');
            }
            return $total / array_sum($weights);
        }
    }
    

Gotchas and Tips

Pitfalls

  1. Memory Usage:

    • Issue: Large sequences (e.g., 1MB+ strings/arrays) may cause Allowed memory exhausted.
    • Fix: Chunk data or use a queue:
      $chunkSize = 1000;
      $chunksA = array_chunk($largeArrayA, $chunkSize);
      $chunksB = array_chunk($largeArrayB, $chunkSize);
      $results = [];
      foreach (array_map(null, $chunksA, $chunksB) as $chunkAAndB) {
          $results[] = app(SequenceMatcher::class)->ratio($chunkAAndB[0], $chunkAAndB[1]);
      }
      
  2. Unicode Handling:

    • Issue: Non-ASCII characters (e.g., "café") may not compare as expected.
    • Fix: Normalize with Str::ascii() or mb_convert_encoding():
      $normalized = mb_convert_encoding($string, 'UTF-8', 'UTF-8');
      
  3. Floating-Point Precision:

    • Issue: Ratios like 0.7000000000000001 may fail strict comparisons.
    • Fix: Round results:
      $rounded = round($matcher->ratio($a, $b), 2);
      
  4. Object Comparison:

    • Issue: Directly comparing objects (e.g., Eloquent models) fails.
    • Fix: Convert to arrays/JSON:
      $ratio = app(SequenceMatcher::class)->ratio(
          json_encode($modelA->toArray()),
          json_encode($modelB->toArray())
      );
      
  5. Performance with Arrays:

    • Issue: Comparing large arrays (e.g., 10,000+ elements) is slow.
    • Fix: Use getOpCodes() for granular control:
      $opcodes = $matcher->getOpCodes($arrayA, $arrayB);
      // Process opcodes manually for better performance
      

Debugging

  1. Inspect Opcodes: Debug mismatches by examining operation codes:

    $opcodes = $matcher->getOpCodes($a, $b);
    foreach ($opcodes as $op) {
        logger()->debug("Op: {$op[0]}, A: {$op[1]}, B: {$op[2]}");
    }
    
    • 1 = equal ('eq').
    • 2 = delete ('del').
    • 4 = insert ('ins').
    • 8 = replace ('rep').
  2. Benchmarking: Compare performance with Python’s difflib:

    # Python benchmark
    from difflib import SequenceMatcher
    import time
    start = time.time()
    for _ in range(1000):
        SequenceMatcher(None, "long_string" * 100, "long_string" * 100 + "x").ratio()
    print(time.time() - start)
    
  3. Edge Cases: Test these scenarios:

    • Empty strings: ratio("", "") → 1.0.
    • Identical sequences: ratio("abc", "abc") → `1.
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.
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk