tourze/symfony-temp-file-bundle
Symfony bundle for managing temporary files: create, track, and automatically clean up temp files through a simple service. Integrates with Symfony's DI container and Filesystem component, giving scratch data a predictable location and lifecycle instead of scattered tempnam() calls.
tourze/symfony-temp-file-bundle is a small Symfony bundle from the Tourze ecosystem that provides a temp-file service: you ask it for a file (CSV export, generated PDF, downloaded attachment, intermediate upload chunk), it creates it in a managed temp location and hands back the path/handle, and it owns cleanup so your storage/app doesn't fill with orphans.
Be aware up front: this is a Symfony bundle, not a Laravel package. There is no Illuminate\Support\ServiceProvider, no extra.laravel.providers, and no auto-discovery. Installing it gives you a Bundle class, a DI extension, and one or two plain PHP service classes. In a Laravel app you either (a) instantiate the service class directly, or (b) write a two-line service provider that binds it, and skip the Symfony bundle machinery entirely.
Minimal path:
composer require tourze/symfony-temp-file-bundle:^1.1
Then look, in this order:
src/ — the bundle class and, more importantly, the actual temp-file service class(es). This is the only code you'll call; everything else is Symfony plumbing you can ignore.DependencyInjection/ and the service-definition file (Resources/config/services.php or config/services.php, depending on the release) — this tells you the real constructor arguments and whether the service is shared/singleton.composer.json — check the symfony/* constraints. If it requires symfony/http-kernel + symfony/framework-bundle at a version incompatible with your Laravel-adjacent stack (or with laravel/framework's Symfony constraints), you'll get resolution conflicts.tests/ — usually the most reliable documentation of intended usage in Tourze packages.First use case that pays for itself immediately: a queued export job that writes a temp CSV, mails/downloads it, and lets the temp layer reap the file instead of you hand-rolling tempnam() + unlink() in a finally block.
$path = app(TempFileService::class)->create('csv'); // or similar factory method
// write, hand off, forget
Bind it in a provider rather than loading the bundle. The bundle's build()/load() methods only make sense under a Symfony kernel; calling them from Laravel is a no-op at best. Bind the concrete service:
// app/Providers/TempFileServiceProvider.php
public function register(): void
{
$this->app->singleton(TempFileService::class, function () {
return new TempFileService(sys_get_temp_dir()); // match the real ctor
});
}
Wrap it in your own tiny abstraction. One interface — TempFileFactory with make(string $prefix): TempFile — means your jobs depend on your contract, not on a Tourze class that may be renamed between minor releases. You can then swap in a Storage-disk-backed implementation for tests or for S3.
Controller download with delete-after-send. The natural pairing is Laravel's streamed response with a cleanup callback, so the file survives exactly as long as the transfer:
return response()->streamDownload(function () use ($path) {
readfile($path);
}, 'export.csv')->deleteFileAfterSend(true);
Queue jobs. Create the temp file inside handle(), never in the constructor (serialized job payloads can't carry a file handle, and the file may be reaped before the job runs). Keep the path, not the object, in any state you persist.
Scheduled reap as a belt-and-braces. Register Schedule::call(fn () => app(TempFileService::class)->cleanup())->hourly() if the package exposes a cleanup entry point; if it only cleans on destruct, you need this for crash-interrupted requests.
Testing. Because the files are not on a Laravel disk, Storage::fake() is irrelevant. Point the service at a per-test directory (sys_get_temp_dir().'/test-'.Str::random()) in setUp(), assert the file exists after the call, and assert it's gone after cleanup — that's the behaviour you actually care about.
Version pinning. Pin ^1.1 and commit the lock file. Tourze publishes many small packages on a fast, largely automated cadence; treating minor bumps as reviewable-but-low-risk and reading the diff is the sane posture.
composer update tourze/symfony-temp-file-bundle --with-dependencies -W on a branch and read git diff 1.0.0..1.1.0 -- src/. Realistically this is dependency churn and test/docs tweaks (the 1.x line has no deprecation surface to break), but "realistically" is not "verified".config/app.php providers. Do not expect php artisan vendor:publish to find anything. There is no config file to publish, and none of Laravel's container conventions apply to the bundle's extension.TMPDIR/sys_get_temp_dir() values, and open_basedir may block the queue path. If a job succeeds but the file "vanishes", this is the cause — log sys_get_temp_dir() in both contexts once.exit, killed workers, and Octane's long-lived workers all break the assumption that the object goes out of scope promptly. Under Octane a singleton holding temp files will happily live for the whole process lifetime — prefer per-request resolution ($this->app->make(...) inside the method, not constructor injection into a singleton).Storage with an explicit delete(), where the lifecycle is yours and observable.readfile(): failed to open stream. Set a group-writable temp dir or keep creation and consumption in the same process.realpath() returns false once the file is deleted — assert with file_exists() on the raw path, not realpath().app/Support/. For a package this small, forking is often cheaper than fighting the Symfony DI assumptions — and it removes a dependency from your audit surface.How can I help you explore Laravel packages today?