marquine/php-etl
Marquine PHP ETL is a flexible Extract, Transform, Load library for PHP and Laravel. Build clean, chainable pipelines from reusable extractors, transformers, and loaders to move and reshape data between sources and destinations, with an extensible API for maintainable data workflows.
Architecture fit. marquine/php-etl is a framework-agnostic, Composer-installed PHP library built around the familiar extract → transform → load pipeline, with a fluent builder API and generator-based (streaming) data flow. That makes it a natural fit for the "bounded data movement" problems that show up in nearly every Laravel product: CSV/XLSX imports, ERP or partner-feed ingestion, nightly exports, backfills, and one-off data migrations. It is a library, not a platform — it does not schedule, orchestrate, retry, or observe anything. Treat it as the in-process transformation engine inside a Laravel command or queued job, not as a replacement for Airflow/dbt/Sidekiq-class tooling.
Integration feasibility. High for a spike: composer require marquine/php-etl, define extractors/transformers/loaders, and run inside php artisan. The API surface is small and readable, so a working prototype is measured in hours, not days. The real feasibility question is lifecycle, not code: 181 stars, zero tracked dependents, and (based on the public repo) effectively a single-maintainer project. That is a bus-factor and long-term-support risk, offset by MIT licensing — we can fork and maintain privately if needed, which caps the downside.
Technical risk.
Key questions to resolve before committing.
Loader implementations?WithoutOverlapping/batch primitives, or does it assume a synchronous CLI context?Verdict. Opportunity is genuinely high — the PHP/Laravel ecosystem lacks a lightweight, well-shaped ETL primitive, and most teams hand-roll this badly. Risk is concentrated in maturity and maintenance, not in architectural incompatibility. Recommend a time-boxed spike plus a hardening sprint, with an explicit "adopt / wrap / replace" decision gate.
Stack fit. Clean. The package is PSR-friendly and Laravel-agnostic, so it slots into an existing Laravel app without touching framework internals. The idiomatic placement is:
app/DataPipelines/ (or a small internal Etl namespace), one class per feed.app/Console/Commands/ for scheduled/manual runs and app/Jobs/ for user-triggered or chunked work.config/ and validated at boot, not inline in the pipeline.Migration path. Wrap, don't leak. Define a narrow internal contract — essentially run(SourceSpec, TransformSpec, TargetSpec): RunResult — and implement it with marquine/php-etl. Domain code depends on the contract, not on Marquine\Etl\* types. This buys three things: the ability to swap implementations (custom iterators, league/csv + Laravel Excel, or a future fork) without touching call sites; a single choke point for metrics, retries, and audit logging; and insulation from upstream breaking changes.
For teams currently on ad-hoc imports or Laravel Excel-only flows, migrate one feed at a time behind the contract, running old and new implementations in parallel on the same inputs and diffing outputs before cutover.
Compatibility. Verify PHP version against our runtime (Laravel 10/11 typically imply PHP 8.1+); pin the package to a specific minor and record it in the dependency policy. Check that extractor dependencies (spreadsheet readers, XML parsers) don't pull in conflicting versions of libraries we already use, and that extension requirements (ext-pdo, ext-mbstring, ext-xml, ext-zip) are satisfied in every environment including CI and queue workers.
Sequencing.
Maintenance burden. Low code volume, but non-zero ownership. Because upstream is a single-maintainer project with minimal community, plan on owning a vendored fork within 12 months. That is cheap for a library this size — the fork cost is roughly one engineer-week to stand up CI, pin dependencies, and merge any pending patches — but it must be a budgeted line item, not a surprise.
Support model. Community-only, no vendor SLA. Internalize this: designate one engineer as the package owner, maintain a short internal runbook, and keep a fork branch warm. Escalation for production incidents is our own on-call, which means the observability layer described above is not optional.
Scaling characteristics. Single-process, streaming, CPU- and I/O-bound. Horizontal scale comes from partitioning inputs and fanning out across queue workers, not from the library itself. Expect throughput in the thousands-to-low-hundreds-of-thousands of rows per minute per worker depending on transformation complexity and DB write patterns; measure before promising SLAs. The load stage is almost always the bottleneck — batch inserts, disable per-row ORM hydration, and commit in chunks. There is no built-in distributed coordination, so exactly-once semantics across retries require an explicit idempotency design.
Failure modes to instrument and mitigate.
Ramp-up. Low for the fluent API — a mid-level Laravel developer can write a working pipeline in a day. Moderate for the parts that actually matter: generator semantics, lazy vs. eager transformations, chunking, transaction boundaries, and error-policy design. Budget a half-day internal walkthrough plus a reference implementation that new engineers can copy. The bigger ramp-up cost is operational literacy — teaching the team to read row-count metrics, dead-letter queues, and memory profiles rather than treating ETL as fire-and-forget.
Net operational posture. Adopt with eyes open: high opportunity, low adoption cost, capped downside thanks to MIT and a thin codebase, but a maintenance obligation we must explicitly resource. Not appropriate as the backbone of a company-wide data platform; very appropriate as the workhorse inside a Laravel monolith that needs reliable, observable imports and exports.
How can I help you explore Laravel packages today?