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

Flow Php Server Laravel Package

flowjs/flow-php-server

Server-side PHP companion for Flow.js that handles chunked file uploads, validates chunks, manages temporary storage, and assembles complete files for reliable resumable uploads.

Deep Wiki
Context7

Getting Started

flowjs/flow-php-server is the PHP half of the flow.js resumable-upload stack. flow.js chunks a file in the browser, POSTs each chunk to your endpoint, and asks GET whether a chunk already exists before sending it. This package validates each chunk, stores it under a temp directory keyed by the upload identifier, and merges the chunks into one file when the last one arrives.

Install it with Composer:

composer require flowjs/flow-php-server

The only hard requirement is a writable temp directory (the hash extension is used for chunk identifiers). First place to look is src/Flow/ — File.php is the workhorse, Request.php parses the flow* parameters, and Config.php holds the temp-dir/hash settings. The README on GitHub has a working endpoint you can copy.

A minimal, framework-free endpoint looks like this — it is the canonical first use case:

use Flow\Config;
use Flow\Request;
use Flow\File;

$config = new Config();
$config->setTempDir(__DIR__ . '/tmp');   // writable, outside the webroot

$request = new Request();
$file    = new File($config, $request);

if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    // flow.js asks "do you already have this chunk?"
    if ($file->checkChunk()) {
        header('HTTP/1.1 200 Ok');
    } else {
        header('HTTP/1.1 204 No Content');
    }
    exit;
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if ($file->validateChunk()) {
        $file->saveChunk();
    } else {
        header('HTTP/1.1 400 Bad Request');
        exit;
    }

    if ($file->validateFile()) {               // all chunks present
        $file->save($destinationPath);         // merge to a final path
    }
    exit;
}

Once that responds correctly, swap the $destinationPath for something derived from your application data (e.g. a UUID-based storage path) rather than trusting flowFilename.

Implementation Patterns

Single route, two verbs. In Laravel this maps cleanly to one controller action handling both the checkChunk (GET) and saveChunk (POST) paths. Keep the status codes exactly right — flow.js uses 200 vs 204 to decide whether to re-send a chunk, so a stray 200 with a body for a missing chunk will make the client skip data.

Route::match(['get', 'post'], '/uploads/chunk', ChunkController::class)
    ->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);

Wire the temp directory through config, not hard-codes. In a Laravel app, point Config::setTempDir() at storage_path('app/flow-tmp') (or a Storage disk path). Keeping it on the same filesystem volume as the final destination makes the merge-and-move cheap.

$config = new \Flow\Config();
$config->setTempDir(storage_path('app/flow-tmp'));

$request = new \Flow\Request();  // reads the flow* params from the request superglobals
$file    = new \Flow\File($config, $request);

If your version of Request accepts an override array, you can pass $request->all() from Laravel explicitly, which is handy for testing; otherwise it reads $_GET/$_POST/$_FILES, which Laravel populates anyway.

Decide the final name yourself. $request->getFileName() gives you the client's filename, but the identifier (getIdentifier()) is the stable key across retries. A common pattern is to use the identifier as the temp grouping and a UUID or hashed name as the destination, persisting the mapping in your own uploads table:

if ($file->validateFile()) {
    $dest = storage_path("app/uploads/{$uuid}");
    $file->save($dest);                 // merge
    Upload::create([...]);             // record original filename, size, mime
}

Resumed and parallel uploads. flow.js fires several chunks concurrently; the package handles this because each chunk is written to its own file inside the identifier's temp folder, and the merge only runs when validateFile() succeeds. This means your endpoint must be stateless — do not assume chunks arrive in order, and do not keep per-request state in the session.

Chunk cleanup. After a successful save(), the temp chunk files are no longer needed. Add a scheduled command that sweeps directories under the temp dir older than N hours; abandoned uploads (closed tabs, failed mobile connections) otherwise accumulate forever.

Guarding the endpoint. Chunk requests are ordinary multipart POSTs, so authenticate as usual (middleware), but exempt them from CSRF or attach the token to the flow.js request — otherwise every resumed chunk fails with a 419 and the client retries endlessly.

Gotchas and Tips

  • saveChunk() after validateChunk() only. saveChunk() will happily write a chunk that failed validation if you call it unconditionally. The validateChunk() guard is what enforces the chunk size and the identifier/filename sanity checks.
  • checkChunk() returns false on a fresh upload, so returning 204 No Content for that case is correct and expected — the client then POSTs the chunk.
  • Never use flowFilename for a filesystem path. It is attacker-controlled. Store the original name in your database, but write to disk under an ID you generated.
  • Temp dir must be writable and must not be web-accessible. A chunk folder inside public/ is a data-leak waiting to happen, and a read-only temp dir produces confusing "chunk not saved" errors that look like client failures.
  • upload_max_filesize / post_max_size (plus nginx client_max_body_size) apply per chunk, not per file. Set the flow.js chunkSize comfortably beneath those limits (e.g. 1–5 MB). If a chunk is rejected by PHP before your code runs, flow.js sees a non-2xx and retries — producing an infinite retry loop that looks like a hang.
  • Hash algorithm mismatch. The temp folder name is derived from the upload identifier; if you change the hash algorithm in Config between deploy and resume, previously uploaded chunks become invisible and the client restarts the whole file. Keep the setting stable, or at least version it.
  • Concurrency and locking. Two tabs uploading the same identifier can race during save(). The package throws a FileLockException in that situation; catch it and return a retryable status (5xx with a delay) rather than a 200, so the client doesn't mark the upload complete.
  • validateFile() is the "all chunks present" check, not a virus/type check. It confirms every chunk arrived; it says nothing about the merged file's MIME type or content. Follow the merge with your own validation (mime sniffing, size checks, scanning) before promoting the file into permanent storage.
  • save() merges into the destination path you give it. If the path exists, behavior depends on the version — write to a temp name and rename() atomically into place to avoid a half-merged file being served during the merge.
  • The merged file is on the same volume as the temp dir in the simple setup; if you move it across volumes (e.g. to S3), do the merge locally first, then upload and delete — Storage::putFile can stream it but not merge chunks for you.
  • Testing: you can exercise the flow without a browser by POSTing chunks with flowChunkNumber, flowTotalChunks, flowTotalSize, flowIdentifier, flowFilename and a file part, then a final POST that triggers the merge. That's the fastest way to reproduce a reported "upload stuck at 99%" bug.
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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo