Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Simple Html Dom Bundle Laravel Package

iloh/simple-html-dom-bundle

Symfony bundle integrating the simple-html-dom PHP library, exposing HTML parsing and DOM manipulation as a service. Parse, traverse, and edit HTML documents with a familiar API, simplifying scraping and document processing in Symfony applications.

View on GitHub
Deep Wiki
Context7

Getting Started

What you're actually installing. This is a Symfony bundle (iloh/simple-html-dom-bundle) wrapping the legacy simple_html_dom library. In Laravel there is no auto-discovery, no service provider, no facade, no config publish. The bundle's only real job — registering a service in a Symfony container — does not happen in a Laravel app. So the practical starting point is to decide whether you're adopting the wrapper or the underlying library. In almost every Laravel scenario you adopt the library (or the maintained fork) and skip the bundle.

First move: read composer.json in the repo before running composer require. With a 0-star, single-maintainer wrapper around a SourceForge-era library, its Symfony constraints are almost certainly capped at 2.x/3.x/4.x. On Laravel 10–12 (Symfony 6/7 components) that constraint will block resolution. Options in preference order:

  1. Use the maintained fork — composer require voku/simple_html_dom. Same find() API, PHP 8 clean, actively patched. This is the shortest path to "day-one productive" if you specifically want the simple_html_dom ergonomics.
  2. Require the underlying parser directly — composer require simplehtmldom/simplehtmldom (or vendor it) and accept you own the PHP 8 patches.
  3. Install the bundle anyway with a repositories entry + --with-all-dependencies and loosened constraints — only if you have a concrete reason (you're porting a legacy Symfony app). Expect to spend the first day just getting Composer to resolve.

First use case — scrape one page and extract a list. The simple_html_dom API is jQuery-flavoured and reads cleanly, which is why it's tempting:

use voku\helper\HtmlDomParser; // maintained fork; use simple_html_dom for the original

$html = HtmlDomParser::file_get_html('https://example.com/blog');

foreach ($html->find('article.post h2 a') as $node) {
    echo $node->plaintext . ' -> ' . $node->href . PHP_EOL;
}

$html->clear(); // free the DOM tree explicitly

Where to look first if you're truly integrating the bundle: vendor/iloh/simple-html-dom-bundle/Resources/config/services.yml. Whatever service ID it registers is what you'd re-create by hand in a Laravel service provider. The class is usually a thin stateless wrapper — you can instantiate it with new. If it references Symfony DI interfaces, you'll be hand-wiring those too.

Don't build features against \simple_html_dom directly on day one. Wrap it in your own interface first (see below). This is the single most expensive decision to defer.

Implementation Patterns

Bind an internal contract, not the library. The whole lever here is that the parser should sit behind your own boundary so the abandoned dependency is swappable in one place. Define an interface in your app namespace:

// app/Support/Html/HtmlExtractor.php
namespace App\Support\Html;

interface HtmlExtractor
{
    /** @return array<int, array{title: string, href: string}> */
    public function extractLinks(string $html, string $selector): array;
}

Then a SimpleHtmlDomExtractor that uses the library, and a DomCrawlerExtractor that uses symfony/dom-crawler + symfony/css-selector. Bind the interface in a service provider so callers depend on the contract.

Register the parser in a service provider — this is the thing the Symfony bundle would have done for you if you were in Symfony:

// app/Providers/AppServiceProvider.php
public function register(): void
{
    $this->app->singleton(HtmlExtractor::class, function () {
        return new SimpleHtmlDomExtractor(); // or DomCrawlerExtractor
    });
}

Because simple_html_dom is stateful per document, do not register a parser instance that holds a document as a singleton — register the stateless extractor/service, and instantiate a fresh DOM per call inside it. Otherwise you leak documents across requests/queue jobs.

Queue the scraping work. Parsing is I/O + CPU that doesn't belong in a request. Standard pattern:

class ScrapePageJob implements ShouldQueue
{
    public function __construct(public string $url, public string $selector) {}

    public function handle(HtmlExtractor $extractor): void
    {
        $html = Http::timeout(15)->get($this->url)->body();
        $links = $extractor->extractLinks($html, $this->selector);
        ScrapeResult::updateOrCreate(['url' => $this->url], ['payload' => $links]);
    }
}

Use $this->release(60) on rate limits, and set memory_limit per worker — simple_html_dom builds a userland object graph and is memory-hungry on large pages. Short-lived workers (--max-jobs, --max-time) are the practical mitigation.

Cache aggressively. Every scrape is a network call plus a parse. Cache raw HTML by URL with a short TTL, and cache extracted results longer:

$html = Cache::remember("html:{$url}", now()->addMinutes(10),
    fn () => Http::get($url)->body());

Selector patterns you'll write daily — $dom->find('a.title', 0) for the first match, $dom->find('table tbody tr') for rows, ->plaintext for cleaned text, ->href/->src for attributes. Iterating a find() result is the workhorse:

$rows = [];
foreach ($dom->find('table.price-table tbody tr') as $tr) {
    $cells = $tr->find('td');
    $rows[] = [
        'name'  => $cells[0]->plaintext ?? null,
        'price' => $cells[1]->plaintext ?? null,
    ];
}

Call $dom->clear() in a finally block after every parse (where the library exposes it) — this is the cheapest defence against the known memory growth, and it becomes a code-review habit rather than an afterthought.

Use it for lenient extraction, DomCrawler for correctness. A useful split: if you're reading controlled/internal HTML, use DomCrawler (libxml-backed, faster, maintained). If the source is third-party and full of malformed markup, the legacy parser's forgiving behaviour is sometimes what you actually want — but masterminds/html5 gives you HTML5-compliant leniency without inheriting the abandoned dependency. Reach for simple_html_dom only when its specific find() semantics matter.

Gotchas and Tips

PHP 8.x will bite you. The underlying library predates modern PHP: dynamic property deprecations, strlen(null) notices, and in older forks each()-era patterns. If you're on PHP 8.2+, expect deprecation noise in logs and occasional fatal errors on edge-case markup. The maintained fork (voku/simple_html_dom) exists precisely to fix this — use it if you want the API. Otherwise budget the first spike for patching.

No Laravel integration = you are the integration. Laravel does not discover Symfony bundles. There's no register(), no facade, no config file. Any "install and go" expectation will be wrong. Your service provider is the integration layer, and it must handle the singleton/stateful issue above.

Memory growth is real. The library builds a full DOM as PHP objects and has documented leak behaviour on large or deeply nested documents. Consequences in production:

  • Batch jobs grow per-item memory → cap worker lifetime.
  • Long-running queue workers (horizon, queue:work without --max-time) accumulate → always set --max-jobs/--max-time.
  • Enforce a document-size guard before parsing (strlen($html) < 2_000_000), and reject or stream large pages.

Silent parse degradation. The parser's leniency means malformed HTML can yield wrong results rather than errors — elements silently missing, text truncated at a bad tag. Never trust it in a correctness-critical path (prices, IDs, legal copy) without asserting on extracted shapes and alerting when a selector returns zero nodes unexpectedly. A "result empty" metric per scraper is worth the five minutes.

Composer resolution breakage. The bundle's Symfony caps conflict with modern Laravel's transitive Symfony components. Symptoms: Your requirements could not be resolved, or a symfony/* downgrade that silently breaks something else. Use composer why-not / composer prohibits to trace it, and prefer requiring the library directly. If you must use the exact bundle, alias it in repositories and record the decision in an ADR so the next person knows why.

Supply-chain posture. You are trusting the bundle author and an unmaintained upstream, with no CI, no dependents, no CVE process. Pin exact versions ("1.0.0" not "^1.0"), run composer audit in CI, and treat the dependency as fork-ready. The extension point you actually want isn't in the package — it's your HtmlExtractor interface, and the moment to add it is before the first feature ships on it.

Debugging a bad extraction is easier if you dump the raw HTML and the parsed tree side by side: dd($dom->find($selector)); often shows the parser's view diverging from view-source:. Also check $dom->find($selector, 0) (first node) versus iterating the full node list — off-by-one indexing on empty results is a common source of "works in dev, nulls in prod."

Extension points are thin. There's no plugin system, no config surface, no error translation layer. If you want caching, retries, proxy support, or structured error reporting around parsing, you build it in your own service — which is a strong argument for wrapping this from day one rather than sprinkling $dom->find() through the codebase.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator