jonnyw/php-phantomjs
PHP PhantomJS is a headless browser wrapper for PHP that lets you load pages, run JavaScript, capture screenshots, and export to PDF. It provides a clean, extensible API over the PhantomJS binary, supports custom scripts, and works with any PHP project.
Architecture fit
jonnyw/php-phantomjs is a thin PHP wrapper that shells out to a native PhantomJS binary using symfony/process, assembling the CLI invocation from Twig-templated "procedures." It ships a Laravel service provider and facade (PhantomJs), so it drops into a Laravel service container cleanly. Architecturally it is a renderer adapter, not a component of application domain logic — it produces a response payload (HTML, PDF, screenshot) and has no business rules. That makes it replaceable behind an interface, which is the only reason it is tolerable at all. The design assumes a host-installed binary whose path, version, and ABI are managed outside Composer; that assumption is the core weakness in any containerized or platform-managed deployment.
Integration feasibility
Feasible in a legacy context and hostile in a modern one. The package's last release (2018-04-03) pins Symfony process/config/filesystem at 2.x–4.x eras, so on Laravel 9+ (Symfony 6) Composer resolution is likely to fail or force downgrades of shared transitive dependencies — this needs a composer why-not check before anything else. On Laravel 5.5–8 with PHP 7.x it will install. The real blocker is not PHP: it is obtaining a working PhantomJS 2.1.1 binary. That binary is not built for Apple Silicon, is unreliable on modern glibc/libssl 1.1+, and has no supported distribution. Integration effort is small; sustaining the integration is the cost.
Technical risk Critical. Every risk vector is unowned:
file:// local-read exposure with no meaningful sandbox.Key questions
Client calls scattered across controllers and jobs?Stack fit Best fit: a Laravel 5.5–8 / PHP 7.x application that already has PhantomJS provisioned (or can be pinned to a legacy Debian base image) and needs PDF or screenshot generation in a background job. Poor fit: greenfield Laravel 10+, PHP 8.2+, ARM CI runners, or anything requiring SOC2-style dependency hygiene.
Migration path Do not integrate the package directly. Introduce an application-owned boundary first:
App\Contracts\HtmlRenderer (or PdfRenderer / Screenshotter) with a single render(string $url, array $options): RenderedDocument.PhantomJsRenderer wrapping JonnyW\PhantomJs\Client — configure binary path, timeout, and viewport from config/rendering.php, never at call sites.Client/facade usages and rewrite them against the interface in one pass.BrowsershotRenderer over headless Chrome, or a Gotenberg/Puppeteer HTTP sidecar) in parallel.Compatibility
Verify explicitly: PHP version constraint, Symfony major version, and whether bin/phantomjs is expected local or system-wide in the installed tag. Confirm the Twig version constraint too — Twig 1.x/2.x pins frequently collide in modern Laravel trees. Treat the package as unusable the moment any of these require a downgrade of a shared dependency.
Sequencing
Maintenance Zero upstream maintenance. The package is archived and PhantomJS is archived; any defect is yours. Budget for vendoring the package (or forking under an internal namespace) to patch PHP 8 deprecations, and for owning a pinned binary image indefinitely. This is a permanent headcount tax on a non-differentiating feature.
Support No vendor, no maintainer, no issue triage. The 0-dependents signal means no community to answer questions — internal tribal knowledge only. Any new engineer inheriting the system will need a written runbook covering binary location, version, required OS libraries, and known failure signatures.
Scaling
PhantomJS is single-process, heavy (~100–250 MB RSS per render), and leaks memory across renders; it is unsuitable for long-lived workers. Scale horizontally by spawning a process per job, capping concurrency at the worker level (Laravel Horizon maxProcesses + a render-specific queue), and enforcing hard timeouts. Do not attempt pooling or reuse. Throughput will be an order of magnitude below headless Chrome and will degrade silently under memory pressure.
Failure modes
Expect: proc_open errors from missing libssl1.0/glibc mismatches; zombie or orphaned processes on timeout; empty or truncated PDFs with a 0 exit code; opaque CLI stderr surfacing as generic exceptions; Twig template errors on options the wrapper doesn't support; and silent hangs on JS-heavy pages because the "wait for" procedure never fires. Every one of these needs a distinct alert, because the default symptom — "no PDF" — does not localize the cause. Wrap all renders with an explicit timeout, an exit-code plus file-size assertion, and a dead-letter path.
Ramp-up
Hours to first render; weeks to production confidence. The API is small and well-documented for its era, but debugging is largely reading raw PhantomJS output. Mitigate by front-loading the golden-file test corpus and a diagnostic command (php artisan render:debug --url=...) that dumps the full command string, exit code, stdout, and stderr.
How can I help you explore Laravel packages today?