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

Resource Repository Bundle Laravel Package

fsi/resource-repository-bundle

A Symfony bundle that integrates FSi's resource repository library, offering reusable repository services for Doctrine entities with built-in CRUD, pagination, and filtering to reduce boilerplate and keep persistence logic out of controllers.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a small, single-purpose Symfony bundle: it gives you a Doctrine-backed key/value store where the "value" is a PHP resource object. There is no Flex recipe and very little documentation, so the first hour is best spent in the source, not the README.

  1. Install and register the bundle manually:
composer require fsi/resource-repository-bundle
// config/bundles.php
FsOpen\Bundle\ResourceRepositoryBundle\FsOpenResourceRepositoryBundle::class => ['all' => true],
  1. Confirm Doctrine picks up the bundle's entity mapping (this is the step that silently fails):
bin/console doctrine:mapping:info
bin/console doctrine:schema:update --dump-sql   # or generate a migration

If the bundle's resource entity is not listed, your doctrine.orm.auto_mapping is probably disabled — register the bundle's mapping explicitly under doctrine.orm.mappings before going further.

  1. Read, in this order: Model/AbstractResource.php, Repository/ResourceRepository.php, the Doctrine entity, and Tests/. Those four files are effectively the whole public API.

  2. First use case — a global settings blob for an admin panel. Subclass the abstract resource and give it a stable name:

namespace App\Resource;

use FsOpen\Bundle\ResourceRepositoryBundle\Model\AbstractResource;

class SettingsResource extends AbstractResource
{
    private string $siteName = 'My App';
    private bool $maintenanceMode = false;

    public function getResourceName(): string { return 'app_settings'; }
    // getters/setters...
}
$repo = $this->container->get('fsi_resource_repository.repository');

$settings = $repo->get('app_settings') ?? new SettingsResource();
$settings->setSiteName('Acme');
$repo->persist($settings);

That round-trip — instantiate, populate, persist, fetch by name — is the entire happy path. Verify method names against the installed version before copying snippets; the API is small and has changed little, but there is no upgrade guide to lean on.

Implementation Patterns

Wrap the repository, never inject it directly. The service id is stringly-typed and the return type is loosely typed (null for missing keys). Put a typed service in front of it:

final class SettingsProvider
{
    public function __construct(
        private object $repository,          // fsi_resource_repository.repository
        private CacheInterface $cache,
    ) {}

    public function get(): SettingsResource
    {
        return $this->cache->get('app_settings', function () {
            return $this->repository->get('app_settings') ?? new SettingsResource();
        });
    }

    public function save(SettingsResource $settings): void
    {
        $this->repository->persist($settings);
        $this->cache->delete('app_settings');
    }
}

Use a DI alias or services.yaml binding so application code only sees SettingsProvider.

Pick one granularity and stick to it. Two viable shapes:

  • One resource per aggregate: app_settings, invoice_defaults, mailer_config. Simple, cacheable, easy to reason about.
  • One bucket per entity: name keys like invoice_42_metadata containing an array or a small DTO. Handy for arbitrary per-record attributes, but you lose any ability to query them.

Do not mix the two; the naming scheme is your only index.

Treat persist as a read-modify-write transaction. The pattern is fetch → mutate → persist, which is inherently racy. Wrap it in $em->transactional() or serialize writes through a single command/consumer if the resource is hot (settings toggled by admins during traffic is the classic lost-update case).

Admin forms map cleanly. Bind a Symfony form to your resource class, persist in the controller after isValid(). Because the resource is a plain object, data_class works without any custom mapper — this is where the bundle earns its keep versus hand-rolled settings tables.

Fixtures and seeds. Create resources idempotently (get() ?? new ...) so doctrine:fixtures:load can run repeatedly. Never INSERT rows by hand unless you know the serialization format the repository expects.

Testing. Boot a kernel with SQLite in-memory, create the schema, and assert a round-trip for every resource class you add. This is the cheapest insurance against serialization drift.

Migration path. If you already have an app_settings table, import each row into a resource in one data migration, then drop the table only after a release where reads go exclusively through the repository.

Gotchas and Tips

  • The serialized payload is a schema you don't control. Renaming a resource class, moving namespaces, or changing property visibility can break hydration of existing rows. Keep resource classes in a stable namespace, avoid private property renames, and consider a version field for future migrations.
  • get() returns null for unknown keys (and may throw for malformed payloads). Always null-coalesce, and add a try/catch around fetches of resources written by older code.
  • No querying inside values. This is a blob store. Anything you need to filter, sort, or join on belongs in a real entity — don't grow a settings blob into a pseudo-table.
  • No built-in caching or invalidation. Every get() is a DB hit. Decorate the repository service (it's a normal service, so decorates: works) or wrap it as shown above.
  • Column size. Check the generated column type for the value field. A TEXT column caps out around 64KB on MySQL; long JSON payloads will truncate or error. Verify with doctrine:schema:update --dump-sql before storing anything sizable.
  • Mapping conflicts. The bundle ships its own entity; if you already have an entity with the same table name or you disable auto-mapping, you'll get "table already exists" or "entity not found" at boot. Inspect doctrine:mapping:info first whenever something feels wrong.
  • Debugging is DB-first. Serialized values are unreadable in the profiler; query the table directly (SELECT * FROM ...) and unserialize/json_decode the payload in bin/console doctrine:query:sql or a tinker session.
  • Bus factor is essentially one. Three stars, zero dependents. Pin the version in composer.json, read the diff before upgrading, and assume you may need to fork — MIT licensing makes vendoring or a maintained fork a realistic option.
  • Extension points are limited but sufficient: subclass AbstractResource, decorate the repository service, and override the entity class if the bundle's configuration exposes it. There is no event system, so cross-cutting concerns (audit logging, cache invalidation) go in your decorator.
  • Recent release, low adoption. The release date suggests someone still tags versions, but there's no community to lean on for answers — budget time for reading source rather than searching for documentation.
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.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log