zendframework/zend-dom
Zend\Dom provides tools for parsing and querying HTML and XML documents, including CSS selector support translated to XPath via Zend\Dom\Query. A legacy Zend Framework component for DOM inspection and extraction.
Architecture fit. zendframework/zend-dom is a single-purpose, framework-agnostic component: Zend\Dom\Query wraps DOMDocument/DOMXPath and adds CSS-selector-to-XPath translation, with execute() returning a NodeList of DOMNode objects. Nothing in it assumes Zend Framework's MVC, service manager, or DI container, so it drops into a Laravel codebase without adapter work. That is also its limitation — Laravel has no DOM abstraction of its own, and the de facto choice in the ecosystem is symfony/dom-crawler (+ symfony/css-selector), which is already present in most Laravel installs via transitive dependencies (BrowserKit, HTTP-client testing, mail/HTML tooling). Choosing zend-dom means adding a second, older DOM API alongside one the team likely already has.
Integration feasibility. Mechanically trivial: composer require zendframework/zend-dom, no service provider, no config, no facade. The API surface is small enough to wrap behind an interface in a day. The real friction is Composer-level, not code-level — see risk below.
Technical risk — this is the dominant factor. The package is archived and its last release was 2019-06-18. Zend Framework was donated to the Linux Foundation and continues as Laminas; zendframework/zend-dom is the abandoned name for what is now laminas/laminas-dom. Concretely:
install/update, which creates recurring noise and audit findings. Security scanners and SBOM tooling will surface it; a TPM should expect a ticket to be filed against it within one audit cycle.Key questions.
Zend\Dom usage in the codebase (legacy ZF2/ZF3 code carried into Laravel), or is this a greenfield evaluation? The answer flips the recommendation from "don't" to "migrate."symfony/dom-crawler, laminas/laminas-dom, or plain DOMXPath).Stack fit. Poor-but-workable. It installs cleanly and needs only ext-dom + ext-libxml, both standard. But it duplicates capability Laravel projects usually already have and introduces a namespace (Zend\) that signals "pre-2019 Zend" to every reviewer.
Migration path. Because the API is identical, the correct move is to adopt the successor rather than the archived artifact:
laminas/laminas-dom — the maintained continuation. Namespace changes from Zend\Dom to Laminas\Dom; classes, method signatures, and behavior are otherwise the same. This is a sed-level rename. Note that laminas/laminas-zendframework-bridge exists to alias old namespaces, but it is a transitional crutch for third-party transitive dependencies, not something to build new code on.symfony/dom-crawler + symfony/css-selector — the stronger recommendation if the need is HTML scraping/querying. Maintained by Symfony, released on a predictable cadence, already adjacent to Laravel's dependency tree, and it handles encoding, relative-URL resolution, and form interaction (which zend-dom does not). Its filter()/each() API is CSS-first, so most zend-dom call sites translate directly.DOMDocument + DOMXPath — acceptable if the need is a single narrow query. Do not add a dependency to avoid twenty lines of code.Compatibility. Watch ext-dom/ext-libxml in CI images (some minimal php:alpine builds omit them), and pin PHP constraints explicitly if zend-dom is retained, because its composer.json predates current PHP. The CSS-selector parser is a subset — pseudo-classes like :nth-child and attribute operators are only partially supported — so verify every selector actually used in the codebase, not just the ones in the docs.
Sequencing.
grep -r "Zend\\\\Dom") and classify by use case.zendframework/zend-dom from composer.json and confirm no transitive dependency still pulls it (check composer why).If a hard constraint forces retention, wrap it behind your own interface immediately and isolate it to one module so a future swap is a single-file change.
Maintenance. Effectively zero upstream maintenance — that is the headline. There will be no bug fixes, no PHP-version compatibility releases, and no responses to issues. The BSD-3-Clause license means we can fork and patch, but that converts a commodity dependency into an owned one, which is almost never justified for a component with a maintained equivalent. Budget for a permanent "keep or replace" line item at every architecture review.
Support. Community-only, and the community has moved to Laminas. Internal engineers searching for a fix will find Laminas answers, which mostly apply — but "mostly" is a poor support model for production parsing code.
Scaling. The component is CPU- and memory-bound like all DOMDocument work: the entire document is materialized in memory as a node tree, typically 5–10× the source size. Fine for sitemaps and email templates; a liability for large or untrusted HTML. Throughput scales horizontally with workers, not within a process — if parsing appears in a hot request path, push it to a queue. NodeList is lazy over the XPath result, so iteration cost is modest, but retaining DOMNode references keeps the whole document alive and defeats PHP's cycle collector more often than teams expect.
Failure modes.
NodeList rather than an exception. Callers that don't distinguish "no match" from "parse failure" will silently process zero records — the classic version of this bug is a scraper that reports success while ingesting nothing.DOMDocument::loadHTML assumes ISO-8859-1 absent a meta charset declaration; non-ASCII text becomes mojibake unless you force UTF-8 (BOM prefix or mb_convert_encoding). This is the single most common production defect with this class of library and there is no upstream fix coming.Query::execute() accepts a selector string; if any part is user-supplied, you are interpolating into XPath. Treat selectors as code, not data.LIBXML_NOENT/external entity loading enabled. The component inherits whatever flags the caller passes — enforce LIBXML_NONET centrally.Ramp-up. Very low. The API is essentially setDocumentHtml(), execute($selector), iterate the NodeList — a competent PHP engineer is productive in under an hour. The real ramp-up cost is organizational: explaining to reviewers, auditors, and future maintainers why an archived 2019 package was chosen over a maintained alternative. That conversation costs more than the integration.
TPM recommendation. Do not adopt zendframework/zend-dom for new work. Route existing usage to laminas/laminas-dom (rename-only) or, preferably, symfony/dom-crawler + symfony/css-selector (better fit for a Laravel stack, maintained, already adjacent to the dependency tree). The 0.8 opportunity score is best read as an opportunity to consolidate away from this package, not to standardize on it.
How can I help you explore Laravel packages today?