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

martyrer/simple-html-dom-bundle

A Symfony bundle that integrates PHP Simple HTML DOM into your application, giving you a convenient service for parsing and manipulating HTML using jQuery-like selectors. Load pages, traverse the DOM, and extract or edit elements with straightforward, readable syntax.

View on GitHub
Deep Wiki
Context7

Getting Started

Know what you're installing before you install anything. This package is a Symfony bundle, not a Laravel package. It ships a Bundle subclass plus DI service definitions for simple_html_dom (S.C. Chen / sunra, global functions, no namespace, no PSR-4). Laravel has no PackageManifest entry, no service provider, no facade, no config to publish — so composer require will either fail on symfony/framework-bundle constraints or install dead weight you can't autoload into the container. From a Laravel codebase, treat the evaluation target as the underlying library, not the bundle.

If you still want to run it (e.g., in a Symfony side-service or a scratch container to compare against your current stack), register the bundle the way Symfony expects:

// config/bundles.php (Symfony 5+)
return [
    Martyrer\SimpleHtmlDomBundle\SimpleHtmlDomBundle::class => ['all' => true],
];

Then confirm what it actually registered by reading, in order:

  1. vendor/martyrer/simple-html-dom-bundle/DependencyInjection/*Extension.php — the real service ID (if any).
  2. Resources/config/services.* — the alias name you should type-hint.
  3. The bundled simple_html_dom.php header — the upstream version it vendored in.

Many bundles of this shape do nothing but require the library file so the global functions exist. Verify before assuming a container service exists.

The canonical "does it work" check:

$html = str_get_html('<div class="price">9.99</div>');
echo $html->find('div.price', 0)->plaintext; // "9.99"
$html->clear();

If this prints 9.99, the wrapper did its one job. Everything past this line is you learning simple_html_dom, not the bundle.

Laravel-native starting point. If your real question is "how do we scrape/parse HTML in a Laravel app," skip the bundle entirely and start from one of these:

  • voku/simple_html_dom — a maintained, namespaced, Composer-installable fork of the same library, with the same selector API. Drop-in for existing call sites.
  • symfony/dom-crawler + symfony/css-selector — already a common transitive dep, actively maintained, faster, better error semantics.
  • PHP 8.4+ native Dom\HTMLDocument — no dependency at all for straightforward extraction.

Implementation Patterns

Bind the library yourself, one class, one binding. If you do adopt simple_html_dom (for its tolerance of badly formed markup), wrap it in a small app-owned class and register it in a service provider. That is roughly thirty minutes of work and gives you a seam to swap the parser later.

// app/Services/HtmlExtractor.php
namespace App\Services;

class HtmlExtractor
{
    public function fromHtml(string $html): \simple_html_dom
    {
        $dom = str_get_html($html);
        if ($dom === false) {
            throw new \RuntimeException('Unparseable HTML document');
        }
        return $dom;
    }

    public function fromUrl(string $url): \simple_html_dom
    {
        $response = \Illuminate\Support\Facades\Http::timeout(10)
            ->withHeaders(['User-Agent' => 'Mozilla/5.0 (compatible; App/1.0)'])
            ->retry(2, 200)
            ->get($url);

        $response->throw();
        return $this->fromHtml($response->body());
    }
}

// app/Providers/AppServiceProvider.php → register()
$this->app->singleton(HtmlExtractor::class);

Never call file_get_html(). It wraps file_get_contents — no timeout, no user agent, no redirect policy, no TLS verification control, no error handling worth the name, and it puts SSRF/local-file reach squarely in your request path. Fetch with Http:: or Guzzle, hand the body to str_get_html. The split also lets you swap the HTTP layer (proxies, rate-limiters, caches) independently of the parser.

Use the selector API the way it's meant to be used; don't reimplement CSS.

$dom = $extractor->fromHtml($body);

// Grab first match, guard for null.
$title = optional($dom->find('h1.title', 0))->plaintext;

// Iterate collections.
foreach ($dom->find('table#results tr') as $row) {
    $cells = array_map(fn($c) => trim($c->plaintext), $row->find('td'));
    // ...
}

$dom->clear(); // critical — see Gotchas

Rely on find() for selection, plaintext for text, ->href / ->src / ->attr for attributes. Do not build your own regex pass on top; you will lose to encoding and nesting edge cases the library already handles (badly, but better than regex).

Isolate the call site from the parser type. Return arrays/plain PHP structures out of your extractor, not simple_html_dom objects. That single discipline is what makes the eventual migration to DomCrawler or native Dom\HTMLDocument a one-file change per feature instead of a rewrite across twenty services.

public function prices(string $html): array
{
    $dom = $this->fromHtml($html);
    try {
        return collect($dom->find('div.price'))
            ->map(fn($n) => (float) trim($n->plaintext))
            ->all();
    } finally {
        $dom->clear();
    }
}

Integration tips for job-driven workflows:

  • Parse inside a ShouldQueue job, one document per job, with $timeout and $tries set explicitly.
  • Cap document size before str_get_html (strlen($body) > 2_000_000 → reject) — this is the single cheapest protection against worker OOM.
  • Cache fetched HTML in the cache/filesystem layer when scraping repeated pages; do not rely on the library's caching, which is per-process and easy to misconfigure.
  • Add per-host rate limiting via RateLimiter::for('scraper-'.$host, ...) — an unbounded foreach over URLs is a self-inflicted outage.

Gotchas and Tips

Symfony bundle in a Laravel codebase. The class extends Symfony\Component\HttpKernel\Bundle\Bundle. Laravel cannot auto-discover it and there is no ServiceProvider to register. If you --ignore-platform-reqs your way past the Composer conflict, you still have symfony/framework-bundle constraints fighting against Laravel's symfony/* v6/v7. Do not work around Composer to make a wrapper fit. If you need the library, composer require voku/simple_html_dom or vendor the file.

Memory is the real ceiling, not CPU. simple_html_dom builds a full object tree, commonly 10–100× the source document. The failure mode is a queue worker silently hitting memory_limit and dying mid-job, producing no error row — just a stuck job. Mitigations, in order:

  1. Hard cap on document size before parsing.
  2. $dom->clear() in a finally block, every time.
  3. max_execution_time and job timeouts set below the worker's memory-kill threshold.

null and false are returned inconsistently. str_get_html() returns false on failure; find($selector, 0) returns null when nothing matches. A find(...)->plaintext chain therefore throws a null-property error far away from the parse site, in code that looks unrelated. Always guard: optional($dom->find('h1', 0))->plaintext ?? '', or wrap the fetch/parse in a typed extractor that throws on ambiguity.

PHP 8 compatibility is not guaranteed. The legacy simple_html_dom codebase was written pre-PHP 7 and regularly trips on PHP 8 strict typing, deprecations, and #[\ReturnTypeWillChange] failures. Before assuming it "just works" on PHP 8.1–8.4:

  • Grep the bundled file for each(, create_function, and {}-style array access — all removed in PHP 8.
  • Run one fixture through the parser under your actual PHP version inside a container.
  • If patched, vendor the patched file and pin it; do not let Composer pull a "new" version that silently reverts your patch.

Global function and class collisions. The library defines str_get_html, file_get_html, and the global simple_html_dom class in the root namespace. Anything else in the autoload set that defines the same names — another scraper, a legacy helper, a polyfill — will fatal on include with no useful message. If you must coexist, wrap all access behind your own service and consider autoload.files discipline.

Encoding will corrupt data silently. simple_html_dom predates robust UTF-8 handling. On non-ASCII content, mojibake frequently passes through and only surfaces downstream (search, matching, exports). Force charset explicitly:

$dom = str_get_html($body, true, true, DEFAULT_TARGET_CHARSET, false);
$dom->load($body) && null;
// Or normalize first with mb_convert_encoding($body, 'UTF-8', 'auto')

Then assert in tests: parse a document with curly quotes / accented characters and verify round-trip. Without that fixture you will not catch the corruption until a customer does.

Selectors are not full CSS. simple_html_dom supports a practical subset (tag, class, id, attribute, descendant, comma-OR, [attr=value]). It does not support full CSS4 pseudo-classes (:has(), :nth-child(of ...), :is()), and behavior on :nth-child and attribute operators differs from the spec. Anything you write that works here should be considered simple_html_dom dialect, not CSS — and re-tested if you ever migrate to DomCrawler or native DOM.

Debugging: log the raw body, not the DOM. When a parse fails or a selector returns nothing, the DOM object is unhelpful — no pretty-printed tree, no error line. Always stash the raw HTML (S3, log, a storage/app/scrape/ fixture folder) before parsing so you can reproduce. This also gives you characterization fixtures for an eventual migration off the library.

Extension points are minimal by design. The bundle is a shim; there is essentially nothing to extend. The real extension point is your own extractor class — that's where you add retries, size caps, caching, and selector helpers. Do not try to monkey-patch the bundle; it will be overwritten on the next composer update, if the maintainer ever ships one (they likely won't — zero stars, zero dependents, no release cadence to speak of).

On the "reject vs. adopt" call. This package is a convenience wrapper around a decaying parser with no ecosystem validation. If HTML extraction is a real product need, put the ~1–2 engineer-days into DomCrawler or native Dom\HTMLDocument behind an owned interface. If it's a one-off migration script, script it with voku/simple_html_dom and throw it away when the job finishes. In both cases, the bundle itself is not in the answer.

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect