difane/difane-contentpart-bundle
Installation Add the bundle via Composer:
composer require difane/difane-contentpart-bundle
Enable it in config/bundles.php:
Difane\ContentPartBundle\DifaneContentPartBundle::class => ['all' => true],
Database Migration Run migrations to create the required tables:
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
First Content Part
Create a basic content part class (e.g., src/ContentPart/MyFirstPart.php):
namespace App\ContentPart;
use Difane\ContentPartBundle\Model\ContentPartInterface;
class MyFirstPart implements ContentPartInterface
{
private $title;
private $content;
// Getters, setters, and validation logic
}
Register the Content Part
Add it to the service configuration in config/packages/difane_content_part.yaml:
difane_content_part:
parts:
my_first_part:
class: App\ContentPart\MyFirstPart
label: 'My First Part'
Admin Integration
Access the admin interface at /admin/content-parts to manage instances of your part.
Create a Page Entity
Extend Difane\ContentPartBundle\Model\PageInterface in your page entity (e.g., src/Entity/Page.php):
namespace App\Entity;
use Difane\ContentPartBundle\Model\PageInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
class Page implements PageInterface
{
private $contentParts;
public function __construct()
{
$this->contentParts = new ArrayCollection();
}
// Implement PageInterface methods (addContentPart, removeContentPart, etc.)
}
Inject Content Parts in Twig
Use the content_part Twig function in your template:
{% for part in page.contentParts %}
{{ content_part(part) }}
{% endfor %}
Define Part Structure
Create a base class for shared functionality (e.g., src/ContentPart/BasePart.php):
namespace App\ContentPart;
use Difane\ContentPartBundle\Model\ContentPartInterface;
use Symfony\Component\Validator\Constraints as Assert;
abstract class BasePart implements ContentPartInterface
{
/**
* @Assert\NotBlank
*/
protected $title;
// Common fields and validation
}
Extend for Specific Use Cases
Example: A HeroSection part:
namespace App\ContentPart;
use App\ContentPart\BasePart;
class HeroSection extends BasePart
{
/**
* @Assert\NotBlank
*/
private $subtitle;
/**
* @Assert\NotBlank
*/
private $imageUrl;
// Custom getters/setters
}
Register in Configuration
Update config/packages/difane_content_part.yaml:
difane_content_part:
parts:
hero_section:
class: App\ContentPart\HeroSection
label: 'Hero Section'
form_type: App\Form\HeroSectionType # Optional: Custom form
Twig Integration
content_part(part) to render a part in templates.{{ part.title }}).{% block hero_section_content %}
<div class="hero">
<h1>{{ part.title }}</h1>
<p>{{ part.subtitle }}</p>
<img src="{{ part.imageUrl }}">
</div>
{% endblock %}
Form Customization
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class HeroSectionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('subtitle')
->add('imageUrl', TextType::class, [
'attr' => ['placeholder' => 'https://example.com/image.jpg']
]);
}
}
difane_content_part.yaml under form_type.Page-Level Configuration
difane_content_part:
parts:
hero_section:
allowed_pages: ['homepage', 'landing_page'] # Custom logic in service
Event Listeners
ContentPartPrePersist):
namespace App\EventListener;
use Difane\ContentPartBundle\Event\ContentPartEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class ContentPartSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
ContentPartEvent::PRE_PERSIST => 'onPrePersist',
];
}
public function onPrePersist(ContentPartEvent $event)
{
$part = $event->getContentPart();
if ($part instanceof \App\ContentPart\HeroSection) {
$part->setCreatedAt(new \DateTime());
}
}
}
Lazy-Load Parts Use a service to dynamically register parts (e.g., from a database):
namespace App\Service;
use Difane\ContentPartBundle\Manager\ContentPartManagerInterface;
use Doctrine\ORM\EntityManagerInterface;
class DynamicContentPartLoader
{
public function __construct(
private ContentPartManagerInterface $manager,
private EntityManagerInterface $em
) {}
public function loadPartsFromDatabase()
{
$parts = $this->em->getRepository(\App\Entity\DynamicPart::class)->findAll();
foreach ($parts as $partConfig) {
$this->manager->registerPart(
$partConfig->getName(),
$partConfig->getClass(),
$partConfig->getLabel()
);
}
}
}
Conditional Rendering Use Twig tests to conditionally render parts:
{% if part is renderable %}
{{ content_part(part) }}
{% endif %}
Define renderable in a Twig extension:
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class ContentPartExtension extends AbstractExtension
{
public function getFunctions()
{
return [
new TwigFunction('renderable', [$this, 'isRenderable']),
];
}
public function isRenderable($part)
{
return $part->isActive() && $part->getTitle() !== null;
}
}
Circular Dependencies
ContentPartManager.Validation Errors
namespace App\Form\Handler;
use Difane\ContentPartBundle\Form\ContentPartHandlerInterface;
use Symfony\Component\Form\FormInterface;
class CustomContentPartHandler implements ContentPartHandlerInterface
{
public function handle(FormInterface $form, $contentPart)
{
if ($form->isSubmitted() && !$form->isValid()) {
// Log errors or flash a message
$this->addFlash('error', 'Validation failed');
}
return parent::handle($form, $contentPart);
}
}
services.yaml:
Difane\ContentPartBundle\Form\ContentPartHandler: '@App\Form\Handler\CustomContentPartHandler'
Performance with Many Parts
$qb = $this->em->createQueryBuilder()
->select('p')
->from(\App\Entity\Page::class, 'p')
->join('p.contentParts', 'cp')
->where('cp.active = :active')
->setParameter('active', true);
Twig Caching Issues
How can I help you explore Laravel packages today?