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

Flexmodel Bundle Laravel Package

flexmodel/flexmodel-bundle

FlexModel Bundle offers a flexible model layer for Laravel/PHP applications, enabling dynamic attributes, relationships, and configurable schemas with less migration overhead. A practical fit for rapid prototyping and customizable data structures.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and enable the bundle

composer require flexmodel/flexmodel-bundle

This pulls in flexmodel/flexmodel (the actual model/adapter/indexer library) plus its adapters. If you're on Symfony 4+ Flex, the bundle should be auto-registered; on Symfony 3 add it manually:

// app/AppKernel.php
$bundles = [
    // ...
    new FlexModel\Bundle\FlexModelBundle(),
];

2. Read the bundle's Configuration.php first

The single most useful file in the repo is src/DependencyInjection/Configuration.php. It is the authoritative list of config keys, adapter/indexer types, and option names for the version you installed. The README is sparse and the package was archived in 2018, so treat the config tree as the real documentation.

vendor/bin/console config:dump-reference flex_model   # exact root key: verify in Configuration.php
vendor/bin/console debug:container flex_model         # see the registered manager/adapter services

3. Write the minimal config

# app/config/config.yml
flex_model:
    adapter:
        default:
            type: memory          # swap for dynamodb / redis / elasticsearch
    indexer:
        default:
            type: memory
    hydrator:
        default:
            type: reflection

The memory adapter/indexer combination is the correct starting point: it lets you build and unit-test your model classes with zero infrastructure, then flip one config value per environment.

4. First use case: a model that persists itself

Extend AbstractModel (which carries ModelManagerAwareTrait) and implement whatever indexable/adapter interface your version requires:

namespace App\Model;

use FlexModel\Model\AbstractModel;
use FlexModel\Model\IndexableInterface;

class Product extends AbstractModel implements IndexableInterface
{
    private $id;
    private $name;
    private $price;

    public function getId() { return $this->id; }
    public function getName() { return $this->name; }
    public function setName($name) { $this->name = $name; return $this; }
    // getters/setters allow the ReflectionHydrator to round-trip the object

    public function getIndexName(): string { return 'app'; }
    public function getIndexType(): string { return 'product'; }
    public function isIndexable(): bool { return true; }
    public function toIndexableArray(): array { return ['name' => $this->name, 'price' => $this->price]; }
}

Then make models ModelManagerAware in the container — the bundle's compiler pass wires the manager into anything tagged/implementing the aware interface (confirm the tag name in the bundle's compiler pass):

services:
    App\Model\Product:
        tags: [flex_model.model_manager_aware]

From there, $product->save() / $product->delete() operate against the configured adapter, and the indexer is updated alongside — the whole point of the library is that the calling code never mentions DynamoDB or Elasticsearch.

Implementation Patterns

Environment-split adapters. Keep memory in config_test.yml and the real backend in config_prod.yml. Because the manager is built from config, integration tests that exercise save()/find() run in milliseconds with no AWS or ES dependency. This is the highest-leverage pattern with the package and the main reason to adopt it.

One model = one index type, one table per model. FlexModel expects the adapter and indexer to be keyed by model. Decide your naming convention (getIndexName() returning a shared app-wide index, getIndexType() per model; table names derived from the class name or prefix) before writing models, because changing it later means re-provisioning DynamoDB tables and re-indexing Elasticsearch.

Repositories/services as the entry point. Don't let controllers touch models directly. Wrap lookup/query logic in a service that injects the model manager and returns hydrated models:

class ProductRepository
{
    public function __construct(private ModelManager $modelManager) {}

    public function find(string $id): ?Product
    {
        $adapter = $this->modelManager->getAdapter();
        $data = $adapter->fetch(Product::class, ['id' => $id]);
        return $data ? $this->modelManager->getHydrator()->hydrate(Product::class, $data) : null;
    }
}

Search reads go through the indexer, not the adapter. For list/filter screens, use the indexer (Elasticsearch) and hydrate the returned IDs via the adapter, or hydrate directly from indexed fields. Mixing the two paths is normal, but be explicit in the repository about which is used.

Custom adapters are a plain service. To add a backend (or a decorator that logs/retries), implement ModelAdapterInterface (and ModelIndexerInterface for indexing) and register it, then reference it by name in flex_model.adapter.<name>. No bundle changes needed — this is the intended extension seam.

Tagged auto-wiring for models. When you have dozens of model classes, tag the whole App\Model\ namespace rather than listing each one:

services:
    App\Model\:
        resource: '../src/Model'
        tags: [flex_model.model_manager_aware]

Hydrator choice per model. reflection (no setters required, slower), property_accessor (respects getters/setters, good when models have computed fields), and closure (fastest, but you hand-write the mapping) are all exposed via config. Start with reflection, and only switch if you profile a hot path.

Gotchas and Tips

  • The package is archived (last release Jan 2018). It targets Symfony 3-era APIs and PHP 7.1. Before adopting, verify it boots on your Symfony version — service definition syntax, ContainerAwareCommand, and autowiring behaviour have all drifted since. Forking it into a private repo is a realistic option.
  • Config key naming is the #1 time sink. The bundle wraps a library whose option names (table_prefix, index_name, ES hosts, AWS region/version) are passed straight through to adapters. config:dump-reference is your friend; typos in adapter options surface as runtime exceptions inside the adapter, not as config validation errors.
  • DynamoDB needs tables to exist. The adapter does not create schema. Provision tables (and GSIs for any query patterns your repository uses) via CloudFormation/Terraform or a local dynamodb-local container in dev. Missing-table errors look like generic adapter failures.
  • Elasticsearch mapping drift. Index types in ES 6+ were deprecated and removed in 7; a 2018 bundle will almost certainly assume the old index/type API. If you're on a modern ES cluster, pin ES 5/6 or plan on a custom indexer.
  • isIndexable() returning false is silent. Models will save to the adapter and simply not appear in searches. If a record "exists but can't be found", check this method and toIndexableArray() before debugging the adapter.
  • toIndexableArray() must be JSON-serialisable. Dates, value objects, and entities with cycles will break indexing. Flatten to scalars/ISO-8601 strings in that method.
  • Memory adapter doesn't validate your model. It will happily store data the real adapter rejects (unset partition keys, wrong types). Keep at least one integration test per model against the real backend, or a smoke test in CI.
  • No data collector / profiler panel. There is no bundle-provided debugging tooling. For diagnostics, decorate the adapter/indexer services with a logging decorator, or log inside your repository.
  • Credentials come from the AWS SDK chain, not the bundle. Rely on env vars / IAM roles; don't put keys in flex_model config. In tests, set dummy creds plus a local endpoint so the SDK client constructs.
  • Compiler pass ordering. If a model is defined in another bundle or created dynamically, it may miss the model-manager injection. Register such services explicitly and confirm with debug:container that the manager argument is present.
  • Upgrade path is manual. With zero dependents and no maintainer, expect to own the code. Budget for a fork; the library side (flexmodel/flexmodel) is the part worth keeping, and the bundle is thin enough to replace with ~50 lines of your own service definitions if it stops booting.
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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core