dflydev/ant-path-matcher
PHP implementation of Ant-style path matching. Compile glob-like patterns using **, * and ? wildcards into matchers, then test paths against them. Useful for routing, file filtering, and access rules where simple prefix checks fall short.
Architecture fit
dflydev/ant-path-matcher is a headless, framework-agnostic utility: it compiles Ant-style glob patterns (? = one character, * = zero or more characters within a path segment, ** = zero or more path segments) and matches them against string paths. It has no I/O, no configuration, no service container coupling, and no global state. That makes it architecturally "free" — it can be bound behind a one-method interface in our domain or support layer without leaking a third-party type into application code.
The realistic use cases in a Laravel codebase are narrow but real:
/api/**/health, **/_debug/**).App\Modules\**\Listeners\*).The critical architectural question is that Laravel already ships this: Str::is() accepts wildcard patterns, and Symfony\Component\Finder\Glob::toRegex() provides glob semantics. The only genuine capability gap is segment-awareness — in Ant semantics * does not cross /, while Laravel's Str::is('*.php', 'a/b/c.php') returns true because it naively expands * to .*. If our rules need "match one segment only" semantics, this package earns its place; if not, we are adding a dependency for ergonomics.
Integration feasibility
High. Installation is a single composer require dflydev/ant-path-matcher; there is no service provider, no publishable config, no migration, no facade, and no Laravel package discovery. Integration is roughly 20–40 lines: an application-owned PathMatcher interface, an adapter delegating to Dflydev\AntPathMatcher\AntPathMatcher, and a container binding. Testing is trivial because the unit is a pure function — no database, HTTP, or clock to fake.
The absence of Laravel auto-discovery is a mild positive here: it means the package cannot silently alter framework behavior, and it keeps our coupling surface to a single class.
Technical risk
| Risk | Severity | Notes |
|---|---|---|
| Maintenance abandonment | High | 6 stars, 0 dependents, last release Jan 2023. Effectively a single-maintainer library with no downstream pressure to fix bugs. |
| No community triage | High | Zero dependents means edge-case bugs (Windows separators, trailing slashes, unicode) are unlikely to be reported or fixed upstream. |
| PHP version drift | Medium | A Jan-2023 release is unlikely to be validated against PHP 8.3/8.4; implicit-nullable and dynamic-property deprecations are plausible. Must be verified, not assumed. |
| ReDoS / pattern injection | Medium | Ant patterns are compiled to regex. If patterns are ever sourced from tenant config or user input, catastrophic backtracking and pathological patterns become an availability risk. |
Semantic mismatch with Str::is |
Medium | Migrating existing *-based rules to Ant semantics silently tightens matching (no crossing /), which can flip a deny-rule to a no-op — a security-relevant regression class. |
| Vendor lock-in | Low | ~200 lines of deterministic logic; a clean-room reimplementation is a plausible one-day task. |
Net: low technical risk, non-trivial sustainability risk. The code is small enough that the real exposure is ownership, not correctness.
Key questions
Str::is() cannot — i.e., do we actually need segment-scoped *?Str::is() rules need to keep their old, looser semantics? If so, we need two matchers, not one./ vs \ separator handling on Windows deploy targets a requirement?Stack fit
Fits cleanly into Laravel 9–12 / PHP 8.1+. It is a leaf dependency with no transitive graph to speak of, so it will not conflict with framework or Symfony versions. It composes naturally with the container and with Laravel's config repository, so the recommended shape is:
App\Support\PathMatcher interface (our type, not theirs).AntPathMatcherAdapter implementing it via the vendor class.config/ and cached with the config cache.Migration path
Str::is(), fnmatch(), preg_match() or glob()-based path rule and record its current effective semantics (* crossing separators is the common surprise).Str::is() — no behavior change, no new dependency.This sequencing decouples "we adopted a pattern-matching abstraction" from "we adopted this specific package," which is the whole point given the sustainability profile.
Compatibility
* and ? stop at /; Laravel's * does not. This is the single most likely source of production surprise and must be covered by the differential fixtures above./, trailing /, doubled separators, and Windows \ if any deploy target is Windows-based.PathMatcher/StringMatcher types from the vendor namespace across our boundary.Sequencing
Phase 1: spike + differential harness against Str::is() (1–2 days). Phase 2: interface + adapter + container binding, feature-flagged. Phase 3: migrate call sites one at a time, highest-risk rules (security deny-lists) last and with explicit review. Phase 4: delete the flag, document the semantics in an ADR, and record the fork-maintenance decision.
Maintenance
Low ongoing code maintenance, moderate ownership burden. There is nothing to configure and nothing to upgrade aggressively, but we should assume upstream will not ship PHP-version fixes. Concretely: pin a specific version, add a CI job that runs the dependency's test surface (or our own coverage of it) on every PHP minor we adopt, and record a standing decision to fork into app/Support if the package fails on a future PHP release. Treat this as a vendored utility with an owner, not as a maintained OSS dependency.
Support
No vendor support, no active issue triage. Support burden falls entirely on the owning team. Mitigate by keeping the surface to one interface, so the blast radius of a swap is a single file. Onboarding cost is genuinely low — the semantics are documented Ant conventions that most engineers recognize.
Scaling
Negligible runtime cost: boolean regex evaluation per pattern-path pair, no I/O, no allocation-heavy work. Two caveats at scale:
match() call compiles. If a request evaluates dozens of rules, cache compiled results per (pattern, path) or precompile at boot when config is built.Memory and CPU impact are immaterial relative to framework boot.
Failure modes
* semantics becomes narrower under Ant semantics and stops matching paths it used to block. This is a security regression with no error signal — hence the differential fixtures and ordering deny-lists last.Ramp-up
Under a day for any PHP engineer. The domain knowledge required is Ant glob semantics, which is the only non-obvious part and should be captured in a short ADR alongside the differential test fixtures. No infrastructure, no credentials, no external services to provision — the onboarding story is a genuine strength of this package.
How can I help you explore Laravel packages today?