campo/random-user-agent
Campo Random User Agent is a Laravel/PHP package that generates realistic random user-agent strings on demand. Rotate browser identities in scrapers, tests, or request simulations using simple helpers and configurable lists of desktop and mobile agents.
What changed in 1.3.0. A single community contribution (@deminy) memoizes the agent list so it is loaded once rather than per call. This is a non-breaking internal performance change: no API change, no dataset change, no dependency-constraint change announced. It is the first release since 2018-06-13. That fact revises our maintenance characterization but does not touch the deciding factor in the prior assessment — dataset freshness — which is exactly what the release notes are silent on.
Architecture fit. Unchanged and still favorable. campo/random-user-agent remains a single-responsibility leaf utility: no persistence, no domain events, no queue or transport coupling, configuration surface limited to an optional dataset. Architecturally cheap to add, architecturally cheap to remove. Realistic use cases remain narrow and adjacent to the core product: synthetic traffic generation, load-test/QA fixtures, crawler and HTTP-client header randomization, scraping rotation. If the need is detection rather than generation, this is still the wrong package. The dataset is the asset; the API is commodity.
Integration feasibility. Marginally improved, but the three friction points still require validation, and one of them now has a sharper question attached:
composer.json to PHP 8 — but the notes don't say so, and we must not assume. Verify composer show campo/random-user-agent constraints on 1.3.0 before the spike; if modernized, the fork requirement in the prior assessment dissolves and extraction gets cheaper.file_get_contents/json_decode, with a residual possibility of a remote fetch. Memoization reduces the frequency of that I/O but does not eliminate the dependency — it converts per-call I/O into per-process I/O. Under PHP-FPM that is still once per request; only under Octane and queue workers does it amortize. This must be read in source, not inferred.Technical risk. The dominant risk is reclassified from abandonment to unsupported-but-not-dead. Zero dependents, a composite score of 1.45, and a seven-year gap followed by one performance PR mean: no downstream validation, no one to surface breakage on our behalf, and no evidenced security-triage process. One merged community PR is not a support relationship and does not constitute a maintenance cadence we can plan against. Three second-order risks persist:
Sec-CH-UA headers — degrading scraping success and poisoning fixtures and analytics, silently. A performance release on a stale dataset is optimization of the wrong axis.composer.lock remains a standing compliance and audit finding, and 1.3.0 does not change that classification.Key questions.
illuminate/support constraints, or is the ^5.6 || ^7.0-era constraint still present?random()-style call — does any consumer need filtering by browser, OS, or device?Recommendation. Directionally unchanged: treat this as a dataset acquisition, not a library adoption. 1.3.0 improves efficiency in a component whose efficiency was never the constraint, and leaves untouched the one attribute that determines whether the output is fit for purpose. Extract the corpus into an internal data file behind an owned interface; do not carry the package in production.
Two amendments: (a) extract from 1.3.0 rather than a 2018 tarball, assuming the spike confirms the corpus is byte-identical or better — record provenance and retain the MIT notice; (b) if the spike shows 1.3.0 resolves cleanly on PHP 8.2 / Laravel 11, the "fork and own" fallback is no longer needed, which makes the extraction path strictly cheaper than before. The gate remains: nothing carrying this dependency in composer.lock merges to main.
Stack fit. Unchanged in substance. Still a Laravel 5.x-era artifact until the 1.3.0 constraints are read; out-of-the-box compatibility with Laravel 10/11 + PHP 8.2 should be assumed broken until proven. The strategic inversion stands: fit the capability into the stack and leave the package out. "Return a plausible random UA string, optionally filtered" is a twenty-line class over a JSON file. 1.3.0's load-once optimization is roughly what we would write ourselves in that class — which is a mild endorsement of the extraction plan, not a reason to keep the dependency.
Migration path. Sequence unchanged, with the memoization lesson folded in:
UserAgentProvider::random(array $filters = []): string, plus pool(): array for test assertions.illuminate constraints, static-vs-instance memoization, and whether a failed load is cached.resources/data/user-agents.json (MIT, attribution in NOTICE); evaluate age distribution against real target endpoints.BundledUserAgentProvider with its own single, deterministic load — read once at construction, never on the hot path, and never cache a failure (fail loud, or re-attempt next call).Compatibility. Verify 1.3.0's composer.json requires first; it is the cheapest possible check and may resolve the largest prior unknown. Do not reach for --ignore-platform-reqs on a shared branch under any outcome — it masks real conflicts across the graph. Confirm whether the agent list is resolved via a path relative to the package directory, since that breaks under optimized/read-only deployments or when the vendor directory is pruned (and, post-1.3.0, a failure there is memoized). Also confirm behavior under Octane and queue workers: memoized-per-process is fine for a static file, but verify no assumption that the list is immutable-across-deploy.
Sequencing. Spike (0.5–1 day: install 1.3.0, read the load path, resolve under target PHP/Laravel, generate 1,000 UAs, inspect age distribution, test against one real target) → dataset adequacy decision → extract → contract + adapter swap → dependency removal. Explicit gate: nothing carrying campo/random-user-agent in composer.lock merges to main. Total elapsed effort remains under a week; the extraction path is reversible.
Maintenance. Code maintenance drops to zero once extracted; ownership rises to 100% either way. The 1.3.0 release slightly softens the "no upstream at all" framing — the repo can accept a community PR — but a seven-year gap followed by one contribution is not a cadence anyone can staff against, and no commitment to PHP 8.4 deprecations, filter bugs, or dataset refresh has been demonstrated. The recurring cost is still the dataset: realistically 1–2 engineer-days per year plus a documented cadence, because staleness degrades output quality silently and nobody files a ticket for "our fake user agents look 2018."
Support. No evidenced issue triage, no security advisories, zero dependents, and — critically — 1.3.0's own release notes say nothing about the corpus. Relying on upstream for the thing that actually matters (fresh UA strings) is not a support relationship that exists. Internal ownership — a named team, a wiki page, a stated refresh policy — must be established before adoption. One perf PR does not change this.
Scaling. Still negligible and now marginally better. A single load plus in-process random indexing is effectively O(1) with no per-call I/O and no shared state; it will not appear on a latency budget at any realistic volume, and it is safe under PHP-FPM, Octane, and queue workers. 1.3.0 removes redundant reads, which matters only in tight loops (bulk fixture generation, high-volume synthetic traffic) where repeated file reads would have shown up — a real but minor win. If the source turns out to be remote, memoization converts per-call latency and rate-limit exposure into per-process, which is an improvement but not a fix: a cold worker still blocks on a third party, and that remains disqualifying for the production path.
Failure modes. Re-enumerated, with the new one flagged:
--ignore-platform-reqs workaround that destabilizes the wider graph. Partially de-risked if 1.3.0 modernized constraints — verify.Ramp-up. Hours. Any engineer reads the implementation in a single sitting; 1.3.0 does not change that. The risk remains the "it works, don't touch it" trap — and a fresh patch release makes that trap more likely, because it creates the appearance of active maintenance on a dataset that may not have been refreshed at all. Mitigate with the freshness test and a named refresh owner.
How can I help you explore Laravel packages today?