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

Cabinet Laravel Package

andrewelkins/cabinet

Cabinet is a Laravel package for managing file uploads and attachments. It lets you associate uploaded files with Eloquent models, handle storage and retrieval, and add file management to your app through a simple, expressive API.

View on GitHub
Deep Wiki
Context7

Getting Started

Cabinet is a thin wrapper around Flysystem that gives Laravel 4 a single-file-upload API with a local or S3 backend. It is a legacy package (last release Feb 2015, Laravel 4 only) — treat this section as instructions for maintaining or extending an existing L4 app, not for new projects.

1. Install

// composer.json
"require": {
    "andrewelkins/cabinet": "~1.0"
}
composer update andrewelkins/cabinet

2. Register the service provider and alias in app/config/app.php:

'providers' => [
    // ...
    'AndrewElkins\Cabinet\CabinetServiceProvider',
],

'aliases' => [
    // ...
    'Cabinet' => 'AndrewElkins\Cabinet\Facades\Cabinet',
],

3. Publish and edit config

php artisan config:publish andrewelkins/cabinet

That writes to app/config/packages/andrewelkins/cabinet/config.php. Set your driver and paths:

return [
    'driver' => 'local',                              // or 's3'
    'path'   => storage_path('uploads'),

    's3' => [
        'key'    => getenv('AWS_KEY'),
        'secret' => getenv('AWS_SECRET'),
        'bucket' => getenv('AWS_BUCKET'),
        'region' => 'us-east-1',
    ],
];

4. Where to look first

  • app/config/packages/andrewelkins/cabinet/config.php — driver, path, S3 credentials, and any visibility options.
  • vendor/andrewelkins/cabinet/src/ — the driver classes. This is the source of truth if the README lags behind your pinned version; method names (put, get, remove, url) are small enough to confirm in one file.

5. First successful upload

The package's surface is intentionally small: put() returns an opaque file id, get() returns a file object, remove() deletes, url() produces a public URL for the local/S3 backend.

// app/controllers/UploadController.php
public function store()
{
    $file = Input::file('upload');

    if (! $file || ! $file->isValid()) {
        return Redirect::back()->withErrors('Upload failed.');
    }

    $id = Cabinet::put($file);   // persist this id
    return Redirect::to('uploads')->with('file_id', $id);
}

public function show($id)
{
    return Response::make(Cabinet::get($id)->getContent())
        ->header('Content-Type', Cabinet::get($id)->getMimeType());
}

Store the returned id on your model (e.g. avatar_id), not the original filename — Cabinet generates the storage key itself.

Implementation Patterns

Attach uploads to an Eloquent model. Keep a file_id (or avatar_id, attachment_id) column and wrap retrieval in an accessor so views never touch the facade directly:

class User extends Eloquent
{
    public function getAvatarAttribute()
    {
        return $this->avatar_id ? Cabinet::get($this->avatar_id) : null;
    }

    public function getAvatarUrlAttribute()
    {
        return $this->avatar_id ? Cabinet::url($this->avatar_id) : '/img/default.png';
    }
}

Validate before you store. Cabinet does not validate. Do it in the controller or a form request:

$rules = ['upload' => 'required|file|max:5120|mimes:jpg,jpeg,png,pdf'];
$v = Validator::make(Input::all(), $rules);
if ($v->fails()) { /* bail before Cabinet::put() */ }

Serve private files through a route. With the local driver, Cabinet::url() only works if path is inside public/. For anything user-private, keep path under storage/ and stream through a controller that does its own Auth::check() and ownership check before calling Cabinet::get($id).

Swap drivers per environment. Because the config is environment-aware, use local in app/config/local/packages/andrewelkins/cabinet/config.php and s3 in app/config/production/.... Both drivers expose the same facade, so controller code does not change. This is the single biggest reason to use Cabinet over ad-hoc move_uploaded_file calls.

Post-process images before storing. Cabinet doesn't transform files. If you need thumbnails, read the file, run Intervention, then store the derived versions yourself:

$image = Image::make($file->getRealPath())->resize(200, null, function ($c) { $c->aspectRatio(); });
$thumbId = Cabinet::put($image->encode('jpg'));

Keep a small table (uploads with original_id, thumb_id, mime, bytes, original_name) so you can track derived assets and know what to delete.

Clean up on delete. Hook model events so orphaned blobs don't accumulate:

User::deleting(function ($user) {
    if ($user->avatar_id) { Cabinet::remove($user->avatar_id); }
});

Test against a temp directory. Point the config path at storage_path('testing/uploads') in your test bootstrap and File::cleanDirectory() in setUp(). There is no Storage::fake() in L4, so this is the closest equivalent.

One facade call per request where possible. Cabinet::get($id) returns an in-memory file object; call it once, assign to a variable, and reuse it for getContent(), getMimeType(), and getSize().

Gotchas and Tips

  • The package is abandoned and Laravel 4 is EOL. If you are starting anything new, use Laravel's Storage facade (L5+) instead — the API is a superset of what Cabinet provides. Only adopt Cabinet if you're already pinned to an L4 codebase.
  • Form must be enctype="multipart/form-data". A missing enctype produces null from Input::file() with no error; the most common "Cabinet is broken" report is actually a form-encoding bug.
  • Check isValid() before put(). Symfony's UploadedFile::isValid() catches PHP upload errors (size, partial upload, missing tmp dir). Cabinet will happily throw on an invalid file otherwise.
  • PHP/nginx limits fail silently. upload_max_filesize, post_max_size, and client_max_body_size all need raising for large files. When Input::file() is null but the form is correct, this is almost always the cause.
  • Cabinet::url() is only meaningful for the local driver when path is web-accessible. Otherwise build a route that streams the file. Do not paper over this by publishing your storage/ directory.
  • The returned id carries no extension or original filename. Cabinet generates the storage key. If users need "download as invoice.pdf" behaviour, store the original name and mime type in your own table — you cannot recover them from Cabinet later.
  • Transactions are not atomic with the filesystem. Upload first, then save the DB row; if the save fails, call Cabinet::remove($id) in a catch block. In a DB transaction, do the file removal in an after-commit callback or accept a small orphan window and run a cleanup job.
  • Composer conflicts come from the AWS SDK pin. Cabinet pins an older aws/aws-sdk-php. Adding any new AWS-backed package will often conflict. Run composer why aws/aws-sdk-php and consider dropping Cabinet's S3 driver in favour of a direct Storage/Flysystem call rather than upgrading the whole SDK.
  • Config is published to app/config/packages/..., not app/config/. The facade reads the namespaced config. Editing app/config/cabinet.php does nothing — a classic L4 footgun.
  • Ensure storage/uploads (or your configured path) is writable by the web user. A permission error surfaces as a generic exception from Flysystem, not a friendly upload message.
  • S3 visibility is not configurable per call in the default config. If you need private objects, set the bucket policy or S3 object ACL in config; Cabinet::url() will otherwise hand back a URL that 403s. Public-read URLs are a common accidental data exposure — audit what user uploads end up in.
  • Extension points are thin, and that's the intended design. For custom behaviour (image hashing, deduping, virus scanning), wrap Cabinet in your own small repository class (App\Uploads\Storage) and call the facade from there. That also gives you one seam to swap out when you eventually migrate to L5's Storage.
  • Debugging: start with dd(Cabinet::get($id)) to confirm the file object resolves, then dd(Cabinet::url($id)) for the resolved path/URL. If get() returns null, the id is stale or the driver config is wrong — check app/config/packages/... under the active environment, not just the default file.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource