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

Ezobjectwrapperbundle Laravel Package

kaliop/ezobjectwrapperbundle

Kaliop eZObjectWrapperBundle provides a lightweight object wrapper for eZ Publish/Platform content in Symfony applications. It enables lazy loading and simpler access to content fields, attributes, and relations, helping developers integrate legacy eZ content into cleaner templates and controllers.

View on GitHub
Deep Wiki
Context7

Getting Started

The bundle is a thin, read-oriented object-wrapper layer for eZ Publish 5 / eZ Platform (and, at v4, presumably Ibexa): it maps an eZ/Ibexa Content object to a PHP class of your own, so controllers and Twig templates can talk in domain terms ($article->getTitle(), article.children) instead of $content->getFieldValue('title')->text and raw ContentService calls.

Minimal steps:

  1. composer require kaliop/ezobjectwrapperbundle:^4.0. The v4.0.0 release note is a single line ("useful f.e. for rendering previews of content"), so read CHANGELOG.md / UPGRADE*.md in the tag before installing — a 1.x → 4.0.0 jump almost certainly carries namespace, config-key and service-ID changes, and possibly an Ibexa namespace migration.
  2. Register the bundle in config/bundles.php (or app/AppKernel.php on older Symfony), e.g. new Kaliop\eZObjectWrapperBundle\KaliopEzObjectWrapperBundle() — verify the FQCN in the tag, majors like this one often rename the bundle class/namespace.
  3. Map at least one content type to a wrapper class:
# config/packages/kaliop_ez_object_wrapper.yaml
kaliop_ez_object_wrapper:
    content_types:
        article:
            wrapper: App\Wrapper\Article
  1. Create the wrapper class extending the bundle's base entity (Kaliop\eZObjectWrapperBundle\Core\Entity) and override only the getters you actually need.
  2. Fetch a wrapper through the bundle's repository service instead of the raw ContentService.

Where to look first, in order:

  • The tag's CHANGELOG.md/UPGRADE.md — with a one-line release note, this is the only real source of truth about what v4 changed.
  • Core/Entity.php — the base wrapper: what it gives you for free (getContent(), getContentInfo(), getLocation(), getFieldValue(), child/parent helpers, magic accessors), and what preview support was added around it.
  • Core/Repository.php (or whatever the loader/service class is named in your installed version) — the getById / getByRemoteId / getByLocationId entry points, how the content-type→wrapper map is consulted, and any new version/draft-aware lookup.
  • DependencyInjection/Configuration.php — the authoritative config schema; the README's YAML is often out of date relative to what the TreeBuilder accepts, and majors are exactly where keys get renamed.
  • DependencyInjection/CompilerPass (if present) — how wrapper classes get registered/tagged; this is your extension point.

First use case worth wiring up: pick one content type (article), write a wrapper with a handful of typed getters, resolve it by location ID in a controller, and render it in a Twig template. That exercises the mapping, the loader, languages and field value types all at once — the four things that bite later. Then re-render the same template against a draft version, which is the workflow v4.0.0 is pointing at.

// src/Wrapper/Article.php
namespace App\Wrapper;

use Kaliop\eZObjectWrapperBundle\Core\Entity;

class Article extends Entity
{
    public function getTitle(): string
    {
        return $this->getFieldValue('title')->text;
    }

    public function getIntro(): string
    {
        $xml = $this->getFieldValue('intro')->xml;

        return $xml ? strip_tags($xml->saveXML()) : '';
    }

    public function getPublishedAt(): ?\DateTime
    {
        return $this->getContent()->versionInfo->publishedDate;
    }
}
// in a controller
$article = $this->get('ezobjectwrapper.repository')->getByLocationId($locationId);

If the service ID differs in your version, bin/console debug:container | grep -i wrapper will tell you immediately; that is usually faster than reading the extension class.

Implementation Patterns

Typed getters over bare field access. Treat the wrapper as a view model: one method per field, returning scalars/DateTime/arrays rather than eZ FieldType value objects. This is the single biggest reason to use the package — templates stop needing ez_field_value() and field identifiers stop leaking into markup. Keep a couple of getFieldValue('x') calls in the wrapper as an escape hatch for one-off fields, but don't let templates call them.

Wrapper resolution at the controller boundary. Resolve wrappers in controllers or in a small service layer, then pass them to Twig as plain variables. Do not resolve them inside templates — you lose all error handling and you re-hit the repository on every render.

Preview rendering (the v4.0.0 use case). The release note's one concrete signal is previews, so treat "wrap the current user's draft, not the published version" as a first-class flow: in an editor/preview controller, load the content in the version being edited, wrap it with the same wrapper class the front end uses, and render the identical Twig template. That is the whole value — WYSIWYG fidelity without a second set of templates. Concretely: (a) resolve from a version/draft-aware loader path rather than getByLocationId, (b) check the repo's version-aware method names in Core/Repository.php before assuming they exist, (c) bypass HTTP cache/Twig cache for the preview route, and (d) gate the route behind ROLE_EDIT-style access checks, since draft content may be unpublished and/or in a non-public language.

Trees and navigation. The base class's children/parent helpers are where the wrapper pays for itself: a mapped folder wrapper returning Article[]/Folder[] children means menu rendering becomes a recursive Twig macro over plain objects. Guard the recursion with a depth limit (see Gotchas).

Relations. For relation/relation-list fields, get destination IDs from the field value and re-resolve through the wrapper repository so the related items come back wrapped:

/** @return Article[] */
public function getRelatedArticles(): array
{
    $ids = $this->getFieldValue('related')->destinationContentIds;

    return $ids ? $this->repository->findByIds($ids) : [];
}

Cache resolved relations on the instance (private $relatedCache) so a template that loops twice doesn't double-fetch.

Inheritance for shared fields. If several content types share title/intro/image, put those getters on an AbstractPage extends Entity and have Article, LandingPage, etc. extend it. Map each concrete content type to its concrete class. Interfaces are useful too, if you want to type-hint across types in services.

Interfaces + typed return on the loader. Because the loader returns whatever the map says, wrap the call in your own small helper (ContentResolver::articleByLocationId()) that asserts the class and gives your IDE and static analysis something to work with. This also isolates the service ID across upgrades — worth doing now, given the 1.x→4.x jump.

Language handling. Wrappers read through the eZ repository, so getFieldValue() respects the language set on the repository/language mask. Set languages before resolving ($repository->setLanguages([$lang])) or use sudo() with a language mask; otherwise you'll see empty strings for translated fields in the admin-preview or in a non-default-language siteaccess. Preview flows make this twice as likely to bite, because the draft's language may not match the current siteaccess.

Writes stay outside. The wrapper is read-only by design — and preview support does not change that: rendering a draft is still a read, saving one still goes through ContentService::updateContent with a typed ContentUpdateStruct. Mixing persistence into wrappers is the fastest way to make this package unmaintainable.

Testing. Unit-test wrappers by constructing them with a mocked Content (or a ContentService mock returning a stub Content) — the getters are pure mapping logic, so they're trivially testable. Integration-test the loader mapping with an eZ fixture content tree; that is where missing/incorrect content-type identifiers surface. Add one test that wraps a draft version and renders the preview template — that is the regression that v4.0.0's feature invites.

Gotchas and Tips

  • A one-line release note is itself the risk. v4.0.0 exists (so the "last release 2017" assumption is dead), but "useful f.e. for rendering previews of content" tells you nothing about namespace changes, renamed config keys, dropped Symfony/eZ versions, PHP minimum, or whether it now targets Ibexa. Diff the tag against 1.x and read composer.json (require constraints + license) before you commit. Pin to ^4.0 and commit the lockfile; do not let a floating constraint pull a 4.x patch into production untested.
  • Major-version jumps from a low-traffic package. With single-digit star counts and zero dependents, a 4.0.0 gets very little external battle-testing. Budget an afternoon to vendor/fork Core/ into your app if the upgrade path is unclear — at this size of dependency, owning ~10 files is cheaper than owning an upgrade.
  • Licence check on upgrade. The previous line was GPL-2.0. If v4 changed licence (or didn't), confirm it before distributing — this is a licensing decision, not a footnote.
  • Namespace drift. Older versions target eZ\Publish\* / ez_content; Ibexa-era code uses Ibexa\* / ibexa_content. A v4 released after the rename may have moved wholesale, in which case your app-side wrappers, service IDs and Twig function names all need the same find-and-replace. grep -r "eZ\\\\Publish" vendor/kaliop/ settles it in seconds.
  • Method-name collisions with the base class. Magic getter resolution loses to real methods: if you have a field literally called content, location, parent or children, calling getContent() returns the eZ object, not your field. Always use getFieldValue('content') explicitly for those, or rename the identifier.
  • Return types are value objects, not primitives. getFieldValue('intro') returns TextBlock\Value; a relation-list returns RelationList\Value. If you skip the unwrapping step in your getters, every consumer has to know eZ field-type internals — which defeats the purpose.
  • N+1 on children. getChildren() per item in a loop re-issues location searches. For listings, resolve a batch of locations with a single search first, then wrap the results, or memoize the child collections on the wrapper instance.
  • Circular references. Parent↔children and bidirectional relations produce wrapper graphs Twig can happily recurse forever (and var_dump/json_encode will explode). Add an explicit $depth argument on tree helpers or a visited-set, and implement __toString()/jsonSerialize() with hard limits.
  • Object identity. Two wrappers for the same content are different PHP objects; == will fail. Compare getContentInfo()->id (or a getCacheKey() you add) when you need deduplication — and for previews, key on content id + version number, or a preview of v3 and the published v2 will collide in any cache you put in front of them.
  • Never cache a preview. Whatever HTTP/Twig/ESI cache fronts your site, the preview route must bypass it (and send Cache-Control: private, no-store). Preview content is per-user, per-draft and permission-sensitive; the fastest way to leak unpublished copy is a shared fragment cache.
  • Permissions and sudo(). The repository uses the current user. Anonymous requests on restricted content either return empty fields or throw NotFoundException/UnauthorizedException. Decide per controller between catching and 404-ing, and use sudo() deliberately (and narrowly) for things like sitemaps or previews of content the current editor can read but the anonymous user cannot.
  • Config cache / debug. After changing the content-type map, run cache:clear; stale container config silently keeps wrapping with the old class. debug:container plus debug:event-dispatcher are the two fastest ways to see what the compiler pass actually registered — and after a major upgrade they are the quickest way to discover a renamed service ID.
  • Not-found semantics. The loader typically throws eZ API\Repository\Exceptions\NotFoundException; there is usually no find*-returns-null variant. Wrap it once in your own resolver so every controller doesn't repeat the try/catch.
  • Extension points if you outgrow it: decorate the loader service to add caching or fallback wrapper classes; add a compiler pass to register wrappers from bundle config rather than YAML; and if you need query-style fetching (search, filters, facets), keep using LocationService/SearchService and only wrap the resulting IDs — the package is a decoration layer, not a query layer.
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.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment