Let's talk
  • [ Ibexa DXP ]
  • [ Tech ]

Ibexa bez chaosu w kodzie: jak Kaliop Content Decorator Bundle porządkuje pracę z contentem

Stanisław Klimaszewski

Opublikowano 9 czerwca 2026

Po ponad dekadzie pracy z eZ Platform i Ibexą widziałem wiele projektów, które zaczynały bardzo podobnie: kilka typów treści, kilka kontrolerów, trochę logiki w szablonach Twig, kilka zapytań do SearchService. Wszystko działało. 

Do czasu.

Problem pojawia się wtedy, gdy projekt rośnie. Typów treści jest coraz więcej, logika biznesowa zaczyna powtarzać się w kontrolerach, szablonach, komendach i serwisach, a dostęp do pól wygląda coraz częściej jak ręczna praca na surowych obiektach Ibexy. W kodzie zaczynają pojawiać się stringi z identyfikatorami pól, warunki zależne od typów treści i metody, które wiedzą zdecydowanie za dużo o samej strukturze CMS’a.

Kaliop Content Decorator Bundle powstał właśnie po to, żeby ten problem rozwiązać.

Nie przez zastępowanie API Ibexy. Nie przez budowanie frameworka obok frameworka. Tylko przez dodanie cienkiej, typowanej warstwy, która pozwala traktować content jako model domenowy, a nie jako anonimowy kontener danych.

Content w Ibexie to dane. W projekcie potrzebujemy modelu.

Ibexa daje nam bardzo elastyczny model contentu. Content, Location, SearchService, ContentService, field definitions, languages, permissions, versions: to wszystko jest bardzo mocne i sprawdzone.

Ale z perspektywy aplikacji biznesowej rzadko chcemy wszędzie operować na generycznym Content.

Jeżeli mamy artykuł, produkt, autora, landing page lub case study, to w kodzie chcemy pracować na pojęciach domenowych:

$article->getTitle();
$article->getLead();
$article->getAuthorName();
$article->getMainImage();
$article->isPromoted();

a nie na powtarzanym w wielu miejscach dostępie do pól:

$content->getFieldValue('title');
$content->getFieldValue('lead');
$content->getFieldValue('author');

Różnica wydaje się kosmetyczna tylko na początku. W praktyce to właśnie ta różnica decyduje, czy po roku projekt jest łatwy do utrzymania, czy każda zmiana w content modelu wymaga przeszukiwania połowy codebase’u.

Co robi Kaliop Content Decorator Bundle?

Bundle pozwala mapować typy treści Ibexy na własne klasy PHP, czyli dekoratory contentu. Przykładowo content type article może być reprezentowany przez klasę Article:

use Kaliop\ContentDecorator\Attribute\Decorator;
use Kaliop\Contracts\ContentDecorator\Model\ContentDecorator;

#[Decorator(repositoryClass: ArticleRepository::class, 
contentTypes: ['article'])]
final class Test extends ContentDecorator
{
   public function getTitle(): string
   {
       return (string) 
$this->getContent()->getFieldValue('title');
   }
   public function isPromoted(): bool
   {
       return (bool) 
$this->getContent()->getFieldValue('promoted');
   }
}

Od tego momentu kod aplikacyjny nie musi wiedzieć, jak Ibexa przechowuje pola, jakie są identyfikatory fieldów i jak wygląda struktura typu danych. Ta wiedza zostaje zamknięta w jednym miejscu - w modelu.

To jest główny benefit bundle’a: separacja odpowiedzialności.

Separacja, która realnie zmienia projekt

W typowym projekcie opartym o Ibexę logika dotycząca contentu bardzo łatwo rozlewa się po aplikacji.

Kontroler ładuje content. Twig sprawdza pola. Serwis buduje URL. Command robi podobne zapytanie jeszcze raz. Inny serwis filtruje po content type. Po kilku miesiącach nikt nie jest pewien, gdzie właściwie znajduje się logika dla danego typu treści.

Kaliop Content Decorator Bundle pozwala to uporządkować:

  • model dekoratora przechowuje logikę związaną z pojedynczym obiektem contentu,
  • repozytorium przechowuje logikę wyszukiwania i ładowania kolekcji,
  • kontrolery i serwisy pracują na typowanych obiektach,
  • Twig dostaje gotowy model zamiast surowego contentu,
  • field identifiery i szczegóły Ibexy nie rozchodzą się po całej aplikacji.

Dzięki temu Article wie, jak zachowuje się artykuł. ArticleRepository wie, jak znaleźć artykuły. Kontroler wie tylko o tym, czego potrzebuje.
To jest dokładnie ten rodzaj separacji, który w większych projektach robi ogromną różnicę.

Dedykowane repozytoria zamiast zapytań rozsianych po kodzie

Bundle wspiera również dedykowane repozytoria dla dekoratorów w sposób analogiczny do repozytoriów wykorzystywanych przy encjach Doctrine.
Zamiast budować zapytania do SearchService w kontrolerach lub serwisach, możemy zamknąć je w repozytorium konkretnego modelu:

final class Test extends AbstractContentRepository
{
   public function findLatestPublished(int $limit = 10): array
   {
       return $this->findBy(
           criteria: [
               new Criterion\Visibility(Criterion\Visibility::VISIBLE),
           ],
           sortClauses: [
               new SortClause\DatePublished(Query::SORT_DESC),
           ],
           limit: $limit,
       );
   }
}

Użycie po stronie aplikacji jest wtedy banalnie proste:

$articleRepository = 
$contentDecoratorManager->getRepository(Article::class);

$articles = $articleRepository->findLatestPublished();

To podejście ma kilka praktycznych korzyści.

Kod odpowiedzialny za wyszukiwanie contentu znajduje się w jednym miejscu. Metody mają nazwy biznesowe, a nie techniczne. Wyniki są od razu udekorowane, więc dalsza część aplikacji pracuje na obiektach Article[ ], a nie na tablicy generycznych Content.

Mniej logiki w kontrolerach i Twigach

Jednym z najczęstszych symptomów starzejącego się projektu Ibexa jest przenoszenie logiki do warstwy prezentacji.
Twig zaczyna wiedzieć, które pole odpowiada za lead. Kontroler zaczyna wiedzieć, jak sprawdzić, czy dany content jest promowany. Serwis SEO zaczyna znać strukturę kilku content type’ów naraz.

Dekoratory pozwalają to odwrócić.

Template nie musi znać szczegółów Ibexy:

<h1>{{ article.title }}</h1>

{% if article.promoted %}
    <span>Promowany</span>
    
{% endif %}

Model może ukryć szczegóły implementacyjne i wystawić czytelne API. To poprawia nie tylko estetykę kodu, ale też jego odporność na zmiany.

Jeżeli zmieni się identyfikator pola, logika wyliczania zwracanej wartości czy fallback językowy - aktualizujemy model. Nie przeszukujemy całego projektu.

Typowanie i statyczna analiza

W projektach PHP 8+ typowanie nie jest dodatkiem. To narzędzie utrzymania jakości.

Praca na generycznych obiektach Content ogranicza możliwości statycznej analizy. IDE i PHPStan nie wiedzą, że dany content jest artykułem, produktem albo autorem. Dla nich to nadal ten sam typ.

Dekoratory zmieniają sytuację.

Jeżeli metoda zwraca Article, możemy korzystać z autouzupełniania, typów zwracanych, PHPStan i refaktoryzacji na poziomie IDE. To zmniejsza liczbę błędów, przyspiesza development i ułatwia wejście w projekt nowym osobom.

Warto również podkreślić, że ścisłe typowanie staje się kluczowe w dobie agentów AI i modeli LLM. Modele te, aby generować kod wysokiej jakości lub poprawnie przeprowadzać refaktoryzację, potrzebują jasnych kontraktów danych. Dzięki typowaniu LLM-y lepiej rozumieją strukturę obiektów, co znacząco zmniejsza ryzyko halucynacji w kodzie i pozwala na skuteczniejsze wspomaganie programistów w automatyzacji zadań.

Injectory: zależności bez robienia z modeli serwisów

Dekoratory nie są serwisami Symfony - i to bardzo dobrze.

Model contentu nie powinien stawać się ciężkim serwisem aplikacyjnym. Czasem jednak potrzebuje dostępu do wybranych zależności: translatora, loggera, generatora URL, repository Ibexy, config resolver’a itp.

Bundle rozwiązuje to przez injectory. Dzięki temu dekorator może otrzymać potrzebne zależności, ale nadal pozostaje modelem tworzonym wokół konkretnego obiektu Content.
To rozsądny kompromis między czystym modelem a realiami projektów Ibexa, gdzie część logiki contentowej naturalnie wymaga integracji z usługami platformy.

Cache tam, gdzie ma sens

Bundle cache’uje udekorowane instancje w pamięci w ramach requestu. Dzięki temu ten sam content nie musi być dekorowany wielokrotnie.
Dodatkowo wybrane metody dekoratora można oznaczyć atrybutem #[Cacheable], jeżeli wykonują kosztowniejsze obliczenia lub wywołują żądania do zewnętrznych API:

use Kaliop\ContentDecorator\Attribute\Cacheable;

#[Cacheable]
public function getRelatedArticles(): array
{
    // kosztowniejsza logika
}

To szczególnie przydatne w projektach, gdzie jeden content jest używany w wielu miejscach renderowania strony: breadcrumb, listing, komponenty, SEO, dane strukturalne, menu czy bloki Page Buildera.

Integracja z codzienną pracą w Ibexie

Bundle nie próbuje ukryć Ibexy. To ważne.

Nadal korzystamy z typów treści, lokalizacji, kryteriów wyszukiwania, systemu uprawnień itd. Różnica polega na tym, że niskopoziomowe elementy API są zamknięte w warstwie, która ma jasną odpowiedzialność.

Do dyspozycji mamy między innymi:

  • mapowanie typów treści na klasy PHP,
  • domyślną klasę dekoratora dla generycznego contentu,
  • dedykowane repozytoria per model,
  • ContentDecoratorManager do ładowania i dekorowania contentu oraz locationów,
  • dekorowanie wielu obiektów naraz,
  • adapter Pagerfanta zwracający udekorowane wyniki,
  • integrację z Doctrine przez typy decorated_content i decorated_content_list do wykorzystania podczas tworzenia relacji między encjami Doctrine a modelem contentowym Ibexy,
  • eventy po dekoracji contentu,
  • cache instancji i cache wybranych metod.

To są funkcje, które pasują do realnych projektów, a nie tylko do prostych przykładów.

Największy benefit: kod zaczyna odzwierciedlać domenę

Najważniejszą wartością tego bundle’a nie jest sam fakt, że można napisać $article->getTitle().

Najważniejsze jest to, że projekt zaczyna mieć strukturę zgodną z domeną.

Artykuły mają swoje modele i repozytoria. Produkty mają swoje modele i repozytoria. Landing page’e mają swoje modele i repozytoria. Logika przestaje być anonimowa. Zaczyna być nazwana, typowana i możliwa do testowania.

To ma ogromne znaczenie w projektach utrzymywanych przez lata.

Bo w Ibexie problemem rzadko jest samo załadowanie contentu. Problemem jest utrzymanie porządku, kiedy content model rośnie, wymagania biznesowe się zmieniają, a zespół musi szybko i bezpiecznie dostarczać kolejne funkcje.

Podsumowanie

Kaliop Content Decorator Bundle jest dla zespołów, które chcą pisać aplikacje Ibexa w sposób bardziej przewidywalny, typowany i łatwy do utrzymania.

Nie zastępuje API Ibexy. Uzupełnia je o warstwę, której często brakuje w większych projektach: warstwę modeli contentu i dedykowanych repozytoriów.

Jeżeli w Twoim projekcie widzisz dużo surowych obiektów Content, powtarzające się identyfikatory pól, zapytania do SearchService rozsiane po kontrolerach i logikę contentową w szablonach, to jest dokładnie ten moment, w którym warto wprowadzić dekoratory.

Jest to jeden z tych wzorców, które po wdrożeniu trudno porzucić. Nie dlatego, że są efektowne. Dlatego, że po prostu robią porządek tam, gdzie w projektach Ibexa porządek jest najbardziej potrzebny.

 

Podobne treści