Architecture fit. dcat/laravel-wherehasin is a thin, additive extension to Eloquent's query builder. It registers whereHasIn, orWhereHasIn, and whereDoesntHaveIn macros that rewrite the correlated exists (select ...) clause generated by whereHas into an uncorrelated where <foreign_key> in (select <id> from ... where ...) form. For a TPM, this is attractive because it is a drop-in at the call site: no schema changes, no new services, no changes to models, relationships, or the repository/query-object layer beyond swapping one method name. It sits entirely inside the persistence layer and does not leak into domain code, so it is reversible per call site and can be adopted incrementally.
Integration feasibility. Composer-only install; the only real friction is version compatibility. The package's release history stops at 2021-07-30, and its supported Illuminate range targets Laravel 5.5 through 8.x. On Laravel 9+/PHP 8.1+ installs will either fail dependency resolution or require a fork/ignore-platform-reqs, which is not acceptable for a production dependency. The practical entry criteria are therefore: pin to a specific commit, verify against our exact Laravel/PHP/DB matrix, and be prepared to vendor the ~small source into an internal package regardless.
Technical risk.
hasMany/hasOne/belongsTo shape with a single local/foreign key. Polymorphic relations, belongsToMany with pivot tables, custom whereHas closures that reference the parent table, and self-referencing relations with aliases are where IN-subquery rewrites commonly diverge from exists semantics. These divergences produce wrong rows silently, which is a far worse failure mode than a slow query.IN (subquery) beats exists is a MySQL-5.x-era generalization. MySQL 8 semi-join and subquery materialization frequently make whereHas equal or better, and a large IN list can regress. Any adoption must be justified by EXPLAIN output on our data, not by the benchmark in the README.0 dependents Packagist signal (likely an undercount, since the Dcat Admin ecosystem pulls it transitively, but still not a reassuring health indicator).Key questions.
EXPLAIN show for the current whereHas on MySQL 8? If the optimizer already semi-joins, the package buys us nothing.ignore-platform-reqs?whereHas on our schema?Stack fit. PHP/Laravel + MySQL. It is compatible with any Laravel app that meets the version constraint and requires no infrastructure, queue, cache, or driver changes. It also composes cleanly with query-object/repository patterns, which is where I would confine it — do not let whereHasIn appear in controllers or ad-hoc scopes.
Migration path. Treat it as a query-level optimization, not a package rollout:
whereHas call sites.acme/laravel-wherehasin) with the Laravel 9+ constraint widened and the macro registration moved to a service provider we control.whereHas and whereHasIn for each candidate relation and asserts identical ID sets.EXPLAIN before/after.Compatibility. Restrict supported relation types to simple one-to-one/one-to-many with default keys. Block pivots and morphs at the API level (a wrapper that throws on unsupported relation types is better than a silent wrong result). Verify behavior under withTrashed(), global scopes, and whereHas closures that reference the outer table.
Sequencing. (1) Validate optimizer behavior → (2) fork and widen constraints → (3) differential tests → (4) pilot 3–5 call sites in a non-critical path → (5) measure, then expand or abandon. Budget one sprint; if step 1 shows the MySQL 8 optimizer already handles the target queries, stop and close the evaluation.
Maintenance. Low code surface, but effectively zero upstream maintenance. Assume we own the fork indefinitely: PHP/Laravel compatibility patches, security review of new macro behavior, and re-validation on every framework major. Cost is small but permanent and must be budgeted, not assumed away.
Support. No commercial support, no SLAs, issue tracker largely dormant and non-English. Internal support burden falls on the backend platform team; document the supported relation matrix so application engineers don't misuse it.
Scaling. Gains are workload-dependent and do not scale predictably: the rewrite helps most where the inner subquery is highly selective and the outer table is large, and can hurt where the IN list is huge or the optimizer would have chosen a semi-join. This is a per-query tuning artifact, not a capacity-planning lever — do not count it toward a scaling roadmap.
Failure modes.
Ramp-up. Near-zero for engineers already fluent in Eloquent; roughly half a day to teach the supported/unsupported relation matrix and the EXPLAIN-first rule. The real ramp-up is on the platform side: whoever owns the fork needs to be comfortable reading the package's small source and Laravel's relationship internals.
Recommendation. Adopt narrowly and conditionally: only if EXPLAIN on MySQL 8 shows a real win for a named set of queries, only behind an internal fork, and only with differential tests in place. If the optimizer baseline already passes, the correct decision is to skip the dependency and instead fix the underlying schema/indexing — which addresses the root cause with no long-term ownership cost.
How can I help you explore Laravel packages today?