jaeger/phpquery-single
phpQuery-single is a single-file PHP port of jQuery's DOM API, letting you parse, query, and manipulate HTML/XML using familiar CSS selectors and chainable methods. No extensions or extra dependencies required—just drop it in and go.
Architecture fit. phpQuery is a 2010-era PHP port of jQuery's DOM API: a global pq() function, static class state, and no namespaces, PSR-4 structure, service provider, facade, or contracts. That is structurally at odds with a Laravel application's container-managed, testable architecture. Its legitimate fit is narrow: as a leaf dependency inside an isolated HTML-parsing/scraping layer, behind an adapter interface, never referenced directly from controllers, jobs, or domain services. The static/global state is also a hazard under Laravel Octane, where worker memory persists across requests.
Integration feasibility. Mechanically trivial — composer require and it works, with no configuration, migrations, or bootstrapping. The single-file distribution is actually a modest plus: it is easy to vendor, diff, and patch in-house. In practice it will almost never be a deliberate direct choice; it arrives transitively via jaeger/querylist, which lists it as a dependency. The dataset's "0 dependents" is almost certainly a registry-metadata gap rather than evidence of no consumers — worth confirming against the lockfile before drawing conclusions.
Technical risk. The dominant risk is stewardship, not code quality. Upstream phpQuery has been effectively unmaintained since 0.9.5 (~2010); this fork is a Composer-packaging convenience, not a maintained parser. Consequences:
Key questions for the team.
symfony/dom-crawler + symfony/css-selector satisfy the same requirement with a maintained codebase?:has(), CSS4)?Stack fit. Works with Laravel 10–12 on PHP 8.1+, but only acceptably when confined to an HtmlParser-style contract with a phpQuery-backed implementation. Treat pq() as an implementation detail; nothing outside the adapter should know it exists. This preserves the option to swap in DomCrawler later without touching call sites.
Migration path. Two scenarios:
symfony/dom-crawler + symfony/css-selector first. Adopt phpQuery-single only if QueryList is already mandated by an existing codebase or a specific feature (its jQuery-like manipulation API) is genuinely required.composer.json, commit the lockfile, and add characterization tests that pin observed behavior so upstream drift is caught rather than discovered in production.Compatibility. Audit for PHP 8 breakage early — removed functions, dynamic-property deprecations, and implicit nullable parameters are the usual offenders in code of this vintage. CSS selector coverage is jQuery-1.2-era; document the gaps so developers do not write :has() and get silent misses. No namespacing means no autoload conflict risk, but also no ability to run two versions side by side.
Sequencing.
Maintenance. Functionally, this becomes vendored code — assume we own it. Budget recurring effort for PHP-version upgrades and upstream querylist releases that bump the pinned fork. This is low-effort in a quiet year and a sharp, unbudgeted spike during a framework upgrade.
Support. Community-only, no SLA, no CVE feed, no security advisories channel. An internal runbook covering "what to do when parsing breaks" and a named owner are non-optional. Note the license (MIT-lineage) is permissive enough for vendoring and patching.
Scaling. Parsing is CPU- and memory-bound per document and does not parallelize within a request. Mitigations: hard-cap input document size, run parsing in queue workers with tuned memory_limit, and avoid the Octane request path if static state proves to leak across requests. Throughput scales horizontally via workers, not vertically via the library.
Failure modes to instrument.
libxml_use_internal_errors(true).Ramp-up. Fast for anyone with jQuery experience — the API is deliberately familiar. The cost is in unlearning assumptions: no live DOM, no event model, global state, and PHP 8-era caveats. Budget roughly half a day of onboarding plus adapter documentation, and make the adapter the reference example so new engineers copy the safe pattern rather than calling pq() directly.
How can I help you explore Laravel packages today?