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

Content Decorator Bundle Laravel Package

kaliop/content-decorator-bundle

A Symfony bundle from Kaliop that adds a decoration layer for content objects, letting you attach custom presentation methods and computed values without changing the underlying content model. Helps keep templates clean by moving view-specific logic into reusable decorators.

View on GitHub
Deep Wiki
Context7

Getting Started

This bundle sits between Ibexa's raw content API and your application code. Instead of passing Content / ContentInfo / field collections around, you get typed model objects and a repository that returns them. Day to day, that means less instanceof and getFieldValue() plumbing in controllers and more ordinary PHP objects you can type-hint, test and serialize.

1. Pick the right version line first. The platform constraints are hard, not advisory:

Line Ibexa Symfony PHP
2.x 5.0 7.4 LTS 8.3+
1.x 4.6 5.4 LTS 8.1+

Install with composer require kaliop/content-decorator-bundle, then register the bundle in config/bundles.php. If you are on Ibexa 4.6 / Symfony 5.4, pin the 1.x line explicitly — main targets the 2.x stack.

2. Read the source in this order (the README is thin, the code is the documentation):

  • src/contracts — the stable API surface. This is what you should type-hint against. Start here.
  • src/bundle — the DI extension and Configuration class. This tells you the actual config keys, service IDs and how decorators get registered.
  • src/lib — mappers, factories, repositories, events. Useful for understanding behaviour, but treat as implementation detail.

Use bin/console debug:container --tag=<bundle tag> and debug:container <RepositoryInterface> to find the repository service ID rather than guessing.

3. First use case: decorate one content type. Do not migrate everything at once. Pick a single content type (say article), define one typed model for it, wire it through the bundle config, and have exactly one controller or query path consume it. Verify the mapping in a functional test before touching a second content type.

Implementation Patterns

Model-per-content-type, immutable. Each decorated model should be a small, readonly-ish object exposing typed properties or getters for the fields you actually use. Resist the urge to mirror every field — the mapping layer is the cost you are paying, so map what the domain needs. Keep Ibexa value objects (e.g. ContentType, relations) out of your public model surface where practical, or at least behind contracts.

Repository as the single entry point. The value of this package collapses if half your code uses the decorated repository and half uses the raw Ibexa content service. Establish a rule: anything that needs a domain object goes through the decorator repository; anything that needs raw content (admin screens, low-level sync jobs) is explicitly marked as bypassing it. Mixing the two produces objects that look alike but behave differently.

Wire, don't subclass. Registration happens through bundle configuration, not by extending package internals. Your application owns its model classes; the bundle owns the mapping. When you need new behaviour in the package itself, the contribution path is defined: add it in src/lib, wire it in src/bundle, and only touch src/contracts if the public API genuinely must grow.

Keep controllers thin. The typical controller becomes: inject repository → query by identifier/location/type → receive typed model → hand to a responder or template. Business logic that used to be field extraction moves into model methods or a domain service, which is the point of the abstraction.

Use events for cross-cutting post-processing. The bundle ships events from src/lib. Prefer a listener that augments or validates a mapped object over duplicating mapping logic in every caller — and prefer it over forking the mapper.

Test at two levels. Unit-test your model classes with plain PHPUnit (no kernel). For the mapping, write functional tests that boot the kernel and assert on real fixtures — mapping bugs are almost always about field edge cases (empty fields, missing translations, multi-value attributes), which unit tests with mocks will not catch. Where the mapper can be instantiated directly, test it standalone to keep the suite fast.

Multi-site / multi-version awareness. If you run several siteaccesses or content type variants, keep decoration config explicit per context rather than relying on defaults inherited from the bundle's Configuration. Explicit config survives upgrades; implicit defaults do not.

Gotchas and Tips

The version coupling is the biggest operational risk. A major line is locked to a specific Ibexa/Symfony/PHP triple. Upgrading Ibexa will likely force an upgrade of this bundle, and the AGENTS notes explicitly say maintainers will not widen constraints without approval. Plan the bundle upgrade as part of your platform upgrade, not after it.

Contracts are stable; lib is not. Build your integration against src/contracts. SemVer forbids BC breaks in minor/patch releases, but the layering exists precisely so that implementation classes in src/lib can be reshaped. If you find yourself type-hinting a concrete mapper or repository implementation from src/lib, you have created upgrade debt.

Adoption is minimal — 2 stars, 0 dependents. Treat this as a single-maintainer dependency. Practical mitigations: pin exact versions, keep an application-side interface in front of the repository so you can swap the implementation, read the mapper code you depend on before upgrading, and be prepared to fork. It is MIT licensed, so forking is a viable fallback rather than a last resort.

Config quirks to check on day one. Symfony's config component will reject unknown keys, but silently ignored or defaulted values are common in bundle extensions. After wiring, dump the resolved container parameters and confirm your decorator registrations actually landed. Also remember to clear the container cache after config changes — a stale compiled container is the most common "my decorator isn't applied" cause.

Missing and empty fields. Decide early whether an absent field yields null, a default object, or an exception, and confirm which the mapper actually does. Content objects in the wild frequently have unset fields, empty rich text, or untranslated attributes; a mapper that assumes presence will fail on production content, not on your fixtures.

Mapping cost. Decoration is not free — every query through the repository pays for object construction. Avoid decorating content types you only ever display as a title and link, and watch for N+1 behaviour when models pull in relations or locations lazily. If a listing endpoint gets slow, the fix is usually to select less, not to optimise the mapper.

Don't bypass and re-enter. Fetching raw content and manually constructing a model "just this once" is how the invariant breaks. If you need a decorated object in a context that has raw content, add a path through the repository rather than hand-assembling.

If you contribute back, run the required checks before opening a PR: composer validate --strict, composer check-cs, composer phpstan, composer deptrac. Deptrac enforces the bundle/contracts/lib split, so a layering violation will fail CI rather than review. Target main for features and the relevant maintenance branch (e.g. 1.x) for fixes to older lines, and update README.md plus release notes whenever behaviour changes.

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