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

Zend File Laravel Package

zendframework/zend-file

Zend File is a legacy Zend Framework component for working with files, offering file transfer adapters for HTTP and FTP uploads, filename filters, and validators for checking file size, extension, and MIME type.

Deep Wiki
Context7

Getting Started

zendframework/zend-file is the standalone Zend Framework file component. In a Laravel application it is not a replacement for Illuminate\Http\UploadedFile or Storage; it is a lower-level upload/file-processing toolkit built around $_FILES, validators, and filters. It is archived (last release 2019), so for any new code prefer the maintained fork:

composer require laminas/laminas-file

Use Zend\File\... only if you are maintaining a legacy ZF application or a package that already depends on it. The namespace maps directly: Laminas\File\Transfer\Adapter\Http, Laminas\Validator\File\Size, etc.

Look first at:

  • Zend\File\Transfer\Adapter\Http — the upload adapter.
  • Zend\Validator\File\* — upload, size, extension, MIME, image, hash, count, and related validators.
  • Zend\Filter\File\Rename — the file rename/move filter.
  • Zend\File\ClassFileLocator — class discovery helper, less relevant in Laravel because Composer handles autoloading.

A minimal first use case is receiving an upload into a local directory with size and extension checks:

use Zend\File\Transfer\Adapter\Http;
use Zend\Validator\File\Extension;
use Zend\Validator\File\Size;

public function store()
{
    $upload = new Http(); // reads $_FILES by default
    $upload->setDestination(storage_path('app/private/uploads'));

    $upload->addValidator(new Size(['max' => '8MB']));
    $upload->addValidator(new Extension([
        'extension' => ['jpg', 'jpeg', 'png', 'pdf'],
    ]));

    if (! $upload->receive()) {
        return back()->withErrors($upload->getMessages());
    }

    $path = $upload->getFileName();

    return response()->json(['path' => $path]);
}

The destination directory must already exist and be writable. receive() returns a boolean; getMessages() returns an array of validation errors keyed by file field.

Implementation Patterns

Wrap the adapter in a service. Do not scatter new Http() calls through controllers. Inject configuration and normalize results:

namespace App\Services;

use Zend\File\Transfer\Adapter\Http;
use Zend\Validator\File\Extension;
use Zend\Validator\File\Size;

class ZendUploadService
{
    public function __construct(private array $config) {}

    public function receive(string $field, string $destination): array
    {
        $upload = new Http();
        $upload->setDestination($destination);
        $upload->addValidator(new Size(['max' => $this->config['max_size']]));
        $upload->addValidator(new Extension([
            'extension' => $this->config['allowed_extensions'],
        ]));

        if (! $upload->receive($field)) {
            return ['ok' => false, 'errors' => $upload->getMessages()];
        }

        return [
            'ok' => true,
            'path' => $upload->getFileName($field),
            'info' => $upload->getFileInfo($field),
        ];
    }
}

Feed the adapter from Laravel instead of relying on $_FILES when possible. The Http adapter accepts a $_FILES-shaped array, which makes it easier to isolate from superglobals:

$files = [];

foreach ($request->allFiles() as $key => $file) {
    if (is_array($file)) {
        // Flatten or loop; zend-file expects the standard nested $_FILES shape.
        continue;
    }

    $files[$key] = [
        'name'     => $file->getClientOriginalName(),
        'type'     => $file->getClientMimeType(),
        'tmp_name' => $file->getRealPath(),
        'error'    => $file->getError(),
        'size'     => $file->getSize(),
    ];
}

$upload = new \Zend\File\Transfer\Adapter\Http($files);

This is useful for request-agnostic code, but see the testing caveat in Gotchas.

Use zend-file as a validation layer, not a storage layer. A good Laravel pattern is to receive into a temporary directory, then move the file through Laravel’s filesystem abstraction:

$tmp = storage_path('app/tmp');
$upload = new Http();
$upload->setDestination($tmp);

if (! $upload->receive('avatar')) {
    throw new \RuntimeException('Upload failed');
}

$localPath = $upload->getFileName('avatar');

Storage::disk('s3')->putFileAs(
    'uploads/avatars',
    new \Illuminate\Http\File($localPath),
    basename($localPath)
);

@unlink($localPath);

This lets you keep S3, local, and other disks consistent while still using zend-file validators.

Chain validators for defense in depth. For user uploads, combine extension, size, MIME, and image validators where appropriate:

$upload->addValidator(new \Zend\Validator\File\Size([
    'min' => '10kB',
    'max' => '5MB',
]));

$upload->addValidator(new \Zend\Validator\File\Extension([
    'extension' => ['jpg', 'png'],
]));

$upload->addValidator(new \Zend\Validator\File\MimeType([
    'mimeType' => ['image/jpeg', 'image/png'],
]));

$upload->addValidator(new \Zend\Validator\File\IsImage());

Use the rename filter when you need deterministic or randomized names:

use Zend\Filter\File\Rename;

$upload->addFilter(new Rename([
    'target'    => storage_path('app/private/uploads'),
    'randomize' => true,
]));

Prefer generating the target name yourself and keeping the extension, especially if downstream code depends on it.

Handle multi-file uploads explicitly. Use the Count validator and inspect getFileInfo():

$upload->addValidator(new \Zend\Validator\File\Count(['min' => 1, 'max' => 5]));
$upload->receive(); // all files
$info = $upload->getFileInfo();

Integrate with Laravel validation rather than replacing it. A common split is:

  • Laravel validation handles required fields, request shape, and user-facing messages.
  • zend-file handles specialized checks such as CRC32, hash equality, image validity, or MIME inspection.
$request->validate([
    'avatar' => ['required', 'file', 'max:8192'],
]);

// Then run zend-file checks for deeper inspection.

Use zend-file validators directly without the adapter when you already have a path from UploadedFile::getRealPath():

$validator = new \Zend\Validator\File\IsImage();

if (! $validator->isValid($request->file('avatar')->getRealPath())) {
    // handle $validator->getMessages()
}

This avoids most superglobal and adapter-lifecycle issues.

Gotchas and Tips

The package is archived. Do not build new Laravel features on zendframework/zend-file. Use laminas/laminas-file, or better, use Laravel’s native upload and validation stack unless you specifically need a zend-file validator or are maintaining legacy code.

PHP version compatibility is the first thing to test. The last zendframework release was in 2019; its supported PHP range predates modern PHP 8.x releases. The Laminas fork has newer support. Run the test suite on your actual PHP version before committing to it.

$_FILES coupling is the biggest practical pitfall. The Http adapter is designed around PHP’s upload superglobal. That breaks down in:

  • API tests and CLI contexts where $_FILES is empty.
  • Livewire, Inertia, or custom request pipelines.
  • Queue workers processing serialized uploads.
  • Requests where the file arrives through a non-standard mechanism.

Prefer constructing the adapter with a files array or using validators directly against a known path.

isUploaded() may reject synthetic files. In test suites, files created with UploadedFile::fake() or temp files may not pass is_uploaded_file() checks inside the adapter. If you must test adapter behavior, either:

  • Test individual validators and filters directly.
  • Subclass the adapter and override the upload-origin check.
  • Use integration tests against a real HTTP request.

Destinations are local absolute paths, not Laravel disks. setDestination() does not understand Storage::disk('s3'). It expects a local directory that already exists. Create it first:

$destination = storage_path('app/private/uploads');

if (! is_dir($destination)) {
    mkdir($destination, 0755, true);
}

Also check writability; the adapter will fail with messages that can be less obvious than Laravel’s filesystem exceptions.

Validation and filtering order matters. The adapter typically validates before applying filters. If you rely on a rename filter to sanitize a name, remember that validators may run against the original uploaded name first. Design validator options accordingly.

Do not trust client-supplied MIME types. The type value in $_FILES comes from the client. Use MimeType or IsImage validators that inspect file contents, and even then treat uploads as untrusted. Store user uploads outside the public web root or behind a controlled download route.

getMessages() format differs from Laravel’s validator. The adapter returns nested arrays keyed by file field and validator. If you surface them through Laravel’s withErrors(), normalize them first:

$messages = collect($upload->getMessages())
    ->flatten()
    ->values()
    ->all();

The rename filter can overwrite files. Set overwrite => false or generate a collision-resistant target name. Default behavior depends on options and target shape, so test both single-file and multi-file cases.

Multi-file naming is easy to get wrong. When receiving multiple files, getFileName() may return an array per field. Inspect getFileInfo() and handle nested arrays rather than assuming a single string.

Extension points are straightforward. Implement Zend\Validator\ValidatorInterface or extend the provided validators for custom checks, and implement Zend\Filter\FilterInterface for custom transformations. Register them with addValidator() and addFilter(). This is the cleanest way to add domain-specific checks such as virus scanning or image dimension limits.

ClassFileLocator is rarely useful in Laravel. It scans PHP files for class declarations, which Composer’s autoloader already handles. Only reach for it if you are building a plugin/module discovery system that must inspect arbitrary directories at runtime.

Bottom line: treat zend-file as a narrowly scoped validation and upload utility for legacy or specialized cases. For day-to-day Laravel work, use UploadedFile, Laravel validation rules, and Storage; reach for zend-file or Laminas-file only when you need a specific validator or are maintaining an existing Zend Framework integration.

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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle