ruwork/object-store-bundle
Symfony bundle that wires a configurable object store into your app. Register storage services, persist and retrieve objects through a clean API, and keep storage concerns decoupled from domain logic. Suitable for files or serialized data with simple configuration.
This is a Symfony bundle, not a Laravel package. There is no service provider, no facade, no config/object_store.php. If you're working in Laravel, the only realistic path is to pull the underlying services out of the bundle manually — Symfony bundles ship with DependencyInjection\Extension and Bundle classes that Laravel's container knows nothing about. Plan accordingly before you install it.
The package is archived and unmaintained (0 stars, no dependents), so treat it as a source-code reference rather than a dependency you'll composer require and forget about.
Minimal steps (Symfony project):
composer require ruwork/object-store-bundle — but pin the commit; the repo is archived, so don't rely on tags resolving cleanly forever.config/bundles.php:
Ruwork\ObjectStoreBundle\RuworkObjectStoreBundle::class => ['all' => true],
config/packages/ruwork_object_store.yaml. Look at DependencyInjection/Configuration.php in the bundle first — that file is the documentation for every option (driver, storage path/bucket, credential keys, metadata persistence).bin/console debug:container | grep -i object_store. The bundle exposes its store as an autowirable service; note the exact FQCN or alias here, since that's what you'll inject everywhere else.Where to look first: DependencyInjection/Configuration.php for config shape, then Resources/config/services.* (or config/services.* in newer Symfony layouts) for the service IDs and aliases. Skip the README — on a zero-star archived repo it's usually a stub.
First realistic use case: accept an uploaded file in a controller, hand it to the store, get back an opaque object identifier, and persist that identifier against a Doctrine entity. Everything else (retrieval, URLs, cleanup) builds from that loop.
Store-and-reference (the core loop). The object store should hide where bytes physically live (local disk, S3, whatever the driver chooses). Your domain entities should hold only the returned identifier — never a filesystem path, never a bucket key. This is the one architectural decision that matters, because it's what lets you swap drivers later without a data migration:
public function upload(Request $request, ObjectStore $store): Response
{
$file = $request->files->get('attachment');
// Returns an opaque identifier — treat the type as a string-ish value object
$id = $store->store($file);
$attachment = new Attachment($id); // Entity holds only the ID
$this->em->persist($attachment);
$this->em->flush();
}
Inject the interface, not the concrete store. Autowire whichever service the bundle aliases (check debug:container), but type-hint the store's interface (or wrap it in your own thin FileStore interface) so tests can substitute an in-memory implementation. Given the bundle is frozen, wrapping it is also your insurance policy against a future replacement.
Symfony controller / service integration. This is a plain DI bundle — constructor-inject the store into services and controllers and you're done. There's no request-scoped magic. That means it composes cleanly with Symfony Messenger: a ProcessUpload message carrying the identifier works fine, as long as the store is a stateless service.
Doctrine metadata is a separate concern. If the bundle persists metadata (size, mime type, original name) into its own table, keep that table for infrastructure only — your domain entity should still own its reference to the object ID. Don't let the bundle's schema leak into your domain model.
Cleanup as a command. Archived bundles don't do garbage collection for you. Write a RemoveOrphansCommand that diffs stored object IDs against your entity references and deletes the stragglers. This is the pattern that pays for itself fastest.
In Laravel specifically: you'll skip the bundle entirely and instantiate the underlying store class yourself in a service provider, passing config from config/filesystems.php. Expect to read the constructor signature by hand — there's no Configuration.php tree to lean on in that context.
composer.json constraints before committing; a stale symfony/* range will fight your framework version.Storage facade. Laravel developers reach for Storage::put() reflexively. Don't. The bundle's abstraction is ID-based, not path-based, and mixing mental models (storing a path from one and an ID from the other) creates orphaned files nothing can clean up.bundles.php. Usually it's a missing Resources/config/services.yaml import, or the alias only existing under a specific environment. Run debug:container --show-private to see hidden services.local and s3 both existing in the tree, only one active). Don't set both; the extension typically doesn't validate mutual exclusion and you'll get the "last one wins" behavior silently.phpstan baseline or a thin adapter class so a future swap is a single-file change.decorate that interface (Symfony's decorates: config) to add logging, virus scanning, or metrics without forking. This is the safest way to get value from an unmaintained dependency.league/flysystem-bundle is actively maintained, has 1st-party Laravel adapters, and covers everything this bundle presumably offers. Ruwork's value here is only if you're already inside a RuVents codebase — for greenfield work, it isn't a defensible choice.How can I help you explore Laravel packages today?