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

Pagepart Bundle Laravel Package

kunstmaan/pagepart-bundle

Kunstmaan PagePartBundle is a Symfony bundle for building pages from reusable page parts. It offers configurable content blocks, admin management, and flexible templating for modular CMS pages.

View on GitHub
Deep Wiki
Context7

Getting Started

kunstmaan/pagepart-bundle is a Symfony 2-era component of the Kunstmaan CMS. It does not give you a CMS on its own — it gives you the page/block composition layer: a page is a collection of PagePartRef rows pointing at small Doctrine entities ("pageparts"), each rendered by its own Twig view. The last tagged release was 2014, so read everything below as Symfony 2.3–2.8, PHP 5.4+, Doctrine annotations.

Minimal setup

composer require kunstmaan/pagepart-bundle
// app/AppKernel.php
$bundles = array(
    // ...
    new Kunstmaan\PagePartBundle\KunstmaanPagePartBundle(),
);
app/console doctrine:schema:update --force   # creates page_part_ref + your pagepart tables

Where to look first (in the vendor directory, roughly in this order):

  1. Helper/PagePartInterface.php and Helper/HasPagePartsInterface.php — the whole contract in two files.
  2. Entity/AbstractPagePart.php — the mapped superclass you extend for every new block.
  3. Entity/PagePartRef.php — the join object that actually owns page → pagepart, the context (region) and the sequencenr (ordering).
  4. PagePartAdmin/PagePartAdminConfigurator.php — how the admin UI is told which pageparts a page template accepts.
  5. Twig/Extension/PagePartTwigExtension.php and Resources/views/ — how a pagepart becomes HTML.
  6. DependencyInjection/Configuration.php — the exact config key (typically kunstmaan_page_part:) and its options.

First use case: a text block on the homepage

// src/Acme/SiteBundle/Entity/TextPagePart.php
namespace Acme\SiteBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Kunstmaan\PagePartBundle\Entity\AbstractPagePart;

/**
 * @ORM\Entity
 * @ORM\Table(name="acme_text_page_parts")
 */
class TextPagePart extends AbstractPagePart
{
    /** @ORM\Column(type="text", nullable=true) */
    private $body;

    public function getBody() { return $this->body; }
    public function setBody($body) { $this->body = $body; return $this; }

    public function getDefaultView()
    {
        return 'AcmeSiteBundle:PagePart/TextPagePart:view.html.twig';
    }

    public function getDefaultAdminType()
    {
        return new \Acme\SiteBundle\Form\TextPagePartAdminType();
    }
}
{# src/Acme/SiteBundle/Resources/views/PagePart/TextPagePart/view.html.twig #}
<div class="text-pagepart">{{ pagepart.body|raw }}</div>
{# page template: iterate the pageparts for a region/context #}
{% for pagepart in page.pageparts %}
    {{ render_pagepart(pagepart) }}
{% endfor %}

The Twig function name and whether you pass a view explicitly are the two things that changed most across versions — open Twig/Extension/PagePartTwigExtension.php and read the getFunctions() block before writing your template; that twenty-line method will save you an hour of guessing.

Implementation Patterns

One page template class per layout. Page templates implement PageTemplateInterface and return one PagePartAdminConfigurator per region. That configurator is the single source of truth for "what can a webmaster drop into the header?" — it is consumed by the admin, not by the frontend. Keep the context names (main, header, footer, sidebar) identical between configurator and template; they are string keys, not constants.

public function getPagePartAdminConfigurations()
{
    $main = new PagePartAdminConfigurator();
    $main->setContext('main');
    $main->addPagePart('text',  'Acme\SiteBundle\Entity\TextPagePart');
    $main->addPagePart('image', 'Acme\SiteBundle\Entity\ImagePagePart');

    $footer = new PagePartAdminConfigurator();
    $footer->setContext('footer');
    $footer->addPagePart('text', 'Acme\SiteBundle\Entity\TextPagePart');

    return array($main, $footer);
}

PagePartRef is the real model. A pagepart entity has no idea which page it is on. Attaching it means creating a ref:

$ref = new PagePartRef();
$ref->setPage($pageTranslation);          // refs hang off the page translation, not the page
$ref->setPagePart($textPagePart);
$ref->setContext('main');
$ref->setSequencenr(3);
$em->persist($ref);
$em->flush();

Because the ref is separate, the same TextPagePart instance can legitimately appear on several pages — useful for shared disclaimers, dangerous for anything the editor expects to be private to one page. Decide this deliberately; most projects should create a fresh entity per placement.

Separate data from presentation, literally. Give each pagepart a view path in getDefaultView() and treat the Twig file as the only place HTML lives. Business logic (link resolution, image URL generation, filtering) belongs in the entity getter or a Twig extension — the pagepart view should be dumb enough that a frontend developer can rewrite it without touching PHP.

Reuse the admin form types, don't build parallel UI. getDefaultAdminType() returns a Symfony form type; extend the bundle's abstract admin type so field ordering, tabs and the "save/delete" chrome stay consistent. Custom admin templates go under Resources/views/PagePartAdmin/, not in your app's generic form theme.

Rendering outside the page template. For sitemaps, RSS, search indexing or emails, don't duplicate the loop — resolve the pageparts via the page's accessor and render through the bundle's renderer service (the same one the Twig extension delegates to). This keeps escaping and view resolution identical between web and CLI output.

Extension point checklist when a pagepart doesn't fit: new context in the configurator → new form type → new Twig view → new entity. In that order. If you find yourself editing PagePartRef or the admin controller, you are almost certainly solving the wrong problem.

Gotchas and Tips

  • It is a 2014 bundle. No Symfony 3/4/5 compatibility, no PHP 7+ typed signatures, no maintained security surface. With zero dependents on record, treat it as "vendored legacy code": pin exact versions, do not composer update it casually, and budget a fork if you need fixes. The high opportunity score mostly reflects how much room there is for a modern replacement, not how ready it is.
  • Dangling refs on delete. Removing a pagepart entity directly leaves a page_part_ref row pointing at nothing, and the template loop explodes (or silently renders nothing, which is worse). Delete the ref, or configure cascade/orphanRemoval on the page side and verify with a query: SELECT COUNT(*) FROM page_part_ref r LEFT JOIN ... WHERE ... IS NULL.
  • Context typos fail silently. 'main' vs 'Main' vs ' content' yields an empty region with no error. Add a smoke test that asserts each configured context renders at least one known pagepart for a fixture page.
  • Ordering lives on the ref, not the entity. sequencenr is an integer maintained by the admin drag-and-drop. Bulk operations (imports, content migrations) must set it explicitly, or you get pageparts in unpredictable primary-key order.
  • Refs are per page translation. Switching language does not switch pageparts — each translation needs its own ref set. Decide early whether translators copy blocks or share entities, because retrofitting is painful.
  • N+1 by default. Looping page.pageparts and touching pagepart.body will lazy-load one query per ref per part. Use a fetch-join on the ref repository for listing pages; it is the single biggest performance lever in this bundle.
  • View override order is your friend. Project-level views under app/Resources/KunstmaanPagePartBundle/views/ override bundle views without touching vendor. Use it for one-off tweaks instead of subclassing entities.
  • Schema drift after adding a pagepart. New entity = new table; doctrine:schema:update in dev is fine, but generate a real migration before deploy, and remember the page_part_ref table itself must already exist.
  • Doctrine inheritance traps. If you plan a single page_part table with STI, AbstractPagePart must stay a mapped superclass — adding @Entity to it, or a second inheritance level, produces duplicate-column errors that are annoying to unpick.
  • Debugging tip. When a pagepart "doesn't show up", check in this order: row exists in page_part_ref with the right context → the context is in the page template's configurator → getDefaultView() resolves to a file that exists → the Twig function is being called at all. That's ~90% of issues.
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