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

Extractor Laravel Package

mmoreram/extractor

mmoreram/extractor is a lightweight PHP library for pulling data out of files in multiple formats—CSV, JSON, XML, and more—into clean arrays via a simple, extensible extractor API. A handy, dependency-light tool for Symfony or any PHP project that needs reliable file parsing without the overhead.

View on GitHub
Deep Wiki
Context7

Getting Started

Check you can install it before you plan anything around it. The last release was 2018-12-17, so the composer.json constraints almost certainly target PHP 7.x. On a modern Laravel app (10/11, PHP 8.2+) this is the first thing that will bite you:

composer require mmoreram/extractor --dry-run
# if it fails:
composer why-not mmoreram/extractor

If Composer refuses, you have two options: --ignore-platform-reqs in a sandbox to prove the code actually runs (usually it does — it's plain PHP), or fork with a widened php constraint. Do this investigation in a branch, not in composer.json on main.

Where to look first. The package is small enough to read end to end in ten minutes. Open src/ and identify three things:

  1. The extractor contract — the single interface every format implements.
  2. The factory/resolver that picks an implementation from a file extension or MIME type.
  3. The concrete CSV/JSON/XML extractors and what their extract() method actually returns (array of rows? array of associative arrays? keys preserved?).

Confirm the real class and method names against src/ — the names in this assessment are indicative, and with no release since 2018 the README may have drifted. Read the tests too; they are the best documentation of the return shape.

First use case. Don't build a feature around it. Pick the ugliest one-off parse you already have — a supplier CSV dropped in storage/app/imports, or a JSON feed you json_decode in a controller — and re-express it in a throwaway php artisan tinker session:

$extractor = /* whatever the factory call is */;
$rows = $extractor->extract(storage_path('app/imports/suppliers.csv'));

dump(count($rows), $rows[0]);

If the shape of $rows is clean and predictable, the abstraction is earning its place. If you find yourself normalising wildly different shapes in every call site, stop and use league/csv + json_decode directly — you'd get the same result with no dependency risk.

Implementation Patterns

Wrap it, don't sprinkle it. The package is framework-agnostic, which means Laravel knows nothing about it. Give it one home:

namespace App\Support\Extraction;

interface SourceExtractor
{
    /** @return array<int, array<string, mixed>> */
    public function extract(string $location): array;
}
final class PackageSourceExtractor implements SourceExtractor
{
    public function extract(string $location): array
    {
        return $this->factory->build($location)->extract($location);
    }
}

Bind the interface in a service provider and inject that everywhere. This is the single most valuable move: it isolates the abandoned dependency behind one file, so replacing it later is a one-class change rather than a grep across the codebase.

Resolve through the container, not new. Register the extractor factory as a singleton in a SourceExtractionServiceProvider. That keeps it testable via $this->app->instance(...) and lets you swap in a fake for feature tests.

Separate "fetch" from "parse". The uniform local-file-or-URL API is convenient and dangerous. In production, treat remote sources as untrusted input and put your own layer in front:

// config/extraction.php
return [
    'allowed_hosts' => ['feeds.partner.com'],
    'timeout' => 10,
    'max_bytes' => 20 * 1024 * 1024,
];

Validate the host against the allowlist before handing the URL to the extractor, and cache the download separately so a parse bug doesn't mean re-fetching a partner feed on every retry.

Long-running imports belong in a job. Never parse a multi-megabyte file inside a request. Pattern:

final class ImportSuppliers implements ShouldQueue
{
    public function handle(SourceExtractor $extractor): void
    {
        $rows = $extractor->extract(Storage::path($this->path));

        collect($rows)->chunk(500)->each(function ($chunk) {
            Supplier::upsert($chunk->all(), ['external_id'], ['name', 'price']);
        });
    }
}

Keep the upsert key stable and idempotent — imports get retried, and ShouldQueue without idempotency is how you get duplicate rows.

Cache the parsed result, keyed by content hash. For remote JSON/XML feeds polled on a schedule:

$key = 'feed:suppliers:' . md5_file(Storage::path($path));

$rows = Cache::remember($key, now()->addHours(6), fn () => $extractor->extract($path));

This makes the extraction cost proportional to change, not to poll frequency, and gives you a cheap diff signal for the next run.

Test with local fixtures, always. The package's framework-agnosticism makes this easy and you should exploit it: ship small tests/Fixtures/*.csv|json|xml files, use Storage::fake() and Http::fake() in feature tests, and assert on the array shape rather than on internals. One test per format, plus one malformed-file test asserting you throw a domain exception rather than leaking a package exception to the controller.

Gotchas and Tips

The dependency is the risk, not the code. 21 stars, zero dependents, no release in seven years. Budget for the possibility that you are the maintainer now. The wrapper interface above is what makes that survivable; a hand-rolled fallback for CSV/JSON is roughly thirty lines with fgetcsv() and json_decode(), so the package's real value is the XML path and the uniform API — judge it on that, not on the whole surface.

Arrays mean memory. Everything comes back fully materialised. A 200 MB CSV will OOM your worker long before the database does. Above a few megabytes, use league/csv's streaming reader for CSV and XMLReader for XML, and let this package handle only the small/medium files where the convenience is real. Set memory_limit deliberately on the queue worker that runs imports.

Remote URLs are an SSRF surface. If the location string can ever be influenced by a user (a "paste a feed URL" field), you have an internal-network request primitive. Allowlist hosts, block private IP ranges, disable redirects or cap them, and set an explicit timeout. Do not assume the extractor does any of this — it predates most of the conversation.

Encoding and CSV quirks. Expect UTF-8 BOMs at the head of the first field, Windows-1252 from legacy exports, ; delimiters from European Excel, and CRLF line endings. Sanitise on the way out of your wrapper (ltrim($field, "\xEF\xBB\xBF"), mb_convert_encoding) rather than patching call sites. Sniff the delimiter from the first line if the extractor doesn't.

XML external entities. The 2018-era XML path may not pass LIBXML_NONET / disable entity loading. If you parse any XML you didn't generate, verify this in src/ and, if necessary, pre-validate or re-parse defensively. This is worth an actual code read, not a guess.

Debug the raw payload, not the parsed array. When a row is wrong, the instinct is to dump the output. Dump the input first — the file on disk, the response body — then the array. Nine times out of ten the extractor is fine and you saved a stale file or got an HTML error page from the feed instead of JSON.

Null and missing keys are silent. Array output has no schema, so $row['price'] becomes null on a renamed column and you find out in production. Assert the expected keys in your wrapper (or in a validate() step in the importer) and fail loudly, once, at the boundary.

Extension points. A custom format means implementing the extractor contract and registering it with the factory/resolver. Do this only for formats you own the spec of — for anything standard, a maintained parser is a better bet than an extension of a package nobody is patching.

Long-lived workers and Octane. Nothing here is stateful, but keep it that way: don't cache the extractor instance per-file or store the last result on the singleton. Resolve, use, discard — inside the method.

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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin