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

Think Csv Laravel Package

whereof/think-csv

View on GitHub
Deep Wiki
Context7

Getting Started

Before writing any code, confirm what you actually installed. The package name (whereof/think-csv) and the repository name (tp5er/think-csvs) disagree, which is common for forks/renames of small ThinkPHP packages, so the vendor directory may not be where you expect:

composer require whereof/think-csv
composer show whereof/think-csv   # confirm the install path and version
ls vendor/whereof/think-csv/src   # READ THIS before trusting the README

At 1 star and 0 dependents there is effectively no community documentation trail, so the source is the documentation. Read three files first, in this order:

  1. The service class (typically src/Service.php) — tells you whether it registers a singleton, a config, or a facade alias.
  2. The facade (typically src/Facade/Csv.php) — tells you the public method surface.
  3. src/config/csv.php (if present) — delimiter, enclosure, charset, BOM, temp path.

If the service is not auto-discovered (ThinkPHP 5.1 style), register it manually in config/app.php:

'services' => [
    \Whereof\ThinkCsv\Service::class,
],

First use case: stream a modest export from a ThinkPHP model in a controller. The idiomatic shape for this family of packages is a fluent builder that accepts a collection/array of rows plus a header definition, then returns a download response:

// app/controller/User.php
public function export()
{
    $users = \app\model\User::where('status', 1)->select();

    return \Whereof\ThinkCsv\Facade\Csv::header(['ID', 'Name', 'Email', 'Created'])
        ->rows($users->toArray())
        ->download('users-' . date('Ymd') . '.csv');
}

Verify the exact method names against the facade — header/rows/download are the conventional trio, but a package this small may only expose create($data)->export(). Whatever the surface is, treat it as a thin wrapper over fputcsv and plan to bypass it for anything large (see below).

Implementation Patterns

Keep the column mapping on the model, not the controller. The single biggest day-to-day win is making export columns a first-class property of the model so headers, ordering, and formatting do not drift between screens:

// app/model/User.php
class User extends Model
{
    public static function csvColumns(): array
    {
        return [
            'id'         => 'ID',
            'name'       => 'Name',
            'email'      => 'Email',
            'created_at' => 'Created',
        ];
    }
}

Then a reusable exporter maps rows through that definition. Never feed toArray() straight into the writer — appended accessors, hidden-field leakage, and relation arrays all end up in the CSV.

Stream, don't hydrate, past a few thousand rows. The package-level API almost certainly materialises the whole dataset into memory. For real reports, skip it and write directly to php://output while chunking the query:

public function export()
{
    $columns = User::csvColumns();

    return \think\Response::create(function () use ($columns) {
        $out = fopen('php://output', 'w');
        fwrite($out, "\xEF\xBB\xBF");                       // UTF-8 BOM for Excel
        fputcsv($out, array_values($columns), ',', '"', '\\');

        User::where('status', 1)->chunk(1000, function ($users) use ($out, $columns) {
            foreach ($users as $user) {
                $row = [];
                foreach (array_keys($columns) as $key) {
                    $row[] = $user->{$key};
                }
                fputcsv($out, $row, ',', '"', '\\');
            }
        });

        fclose($out);
    }, 200, ['Content-Type' => 'text/csv']);
}

Put exports behind a service class (app/export/UserExport.php) rather than a controller method. You get one place to change delimiters, one place to test, and the option of running the same code from a queue job or a CLI command (php think user:export) without duplication.

Queue the heavy ones. ThinkPHP's queue integration (think-queue) pairs well here: the job writes to runtime/export/{uuid}.csv and the browser polls a status endpoint, then downloads. This is the difference between a 3-second request and a 90-second one that times out behind Nginx.

Compose with the response object, not echo. Always return a think\Response (or use download()), so middleware, CORS headers, and request logging still apply.

Gotchas and Tips

  • Excel and UTF-8. Without the \xEF\xBB\xBF BOM, Chinese/accented characters render as mojibake the moment a user double-clicks the file. Given the package's Chinese-language origins, expect this to be either handled for you or a config flag — check before shipping.

  • Excel mangles long identifiers. Phone numbers, order IDs, and 18-digit national IDs become 1.23E+18. Prefix with a tab ("\t" . $value) or force text formatting.

  • CSV formula injection. Values beginning with =, +, -, or @ execute as formulas in Excel. Sanitise any user-supplied string column:

    if (in_array($value[0] ?? '', ['=', '+', '-', '@'], true)) {
        $value = "'" . $value;
    }
    
  • fputcsv escape parameter. PHP 8.4 deprecates the default $escape. Pass it explicitly ('\\') now, or the package's internals will start emitting deprecation notices on upgrade even though your code is clean.

  • Vendor/namespace mismatch. If use Whereof\ThinkCsv\... fails, run composer dump-autoload and inspect vendor/composer/autoload_psr4.php — a renamed fork frequently leaves the PSR-4 prefix pointing at Tp5er\ThinkCsv.

  • Facade alias collisions. Registering Csv as a global class alias can clash with another exporter. Use the fully-qualified facade, or override the alias in config.

  • Config caching. In production ThinkPHP caches config/, so changes to csv.php need php think clear or a deploy restart.

  • Version pinning. With one star and zero dependents there is no ecosystem acting as a regression suite. Pin an exact version ("whereof/think-csv": "1.0.*" → exact) and, if the package is under ~300 lines, consider vendoring the writer into app/export/ — it removes supply-chain risk and lets you fix bugs yourself.

  • Testing. Assert on the streamed body rather than on the facade return value:

    ob_start();
    $response->send();
    $csv = ob_get_clean();
    $this->assertStringContainsString('ID,Name,Email', $csv);
    
  • Extension points worth knowing: the config file (delimiter/enclosure/charset/BOM/temp dir) is the safest customisation surface; if the package binds a writer class in the container, rebinding it lets you swap in your own implementation without forking. Anything beyond that — chunking, queueing, column mapping — is better handled in your own export layer than by patching a low-traffic dependency.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
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