redkite-cms/redkite-cms-bundle
RedKite CMS Bundle delivers core CMS features for RedKite, an open-source PHP/Symfony content management system. It provides page and content management, a modular architecture, and tools for building editable websites.
Accept up front that this is not something you composer require into a Laravel app. It's a Symfony 2-era bundle depending on symfony/http-kernel 2.x, Propel, Twig 1, and Assetic. Laravel 10/11 pull Symfony 6/7 components, so the dependency graph won't resolve — and --ignore-platform-reqs just moves the failure to runtime. The realistic "first use case" is one of two things:
Where to look first, in this order:
RedKiteCmsBundle.php — the bundle boot/config lifecycle; tells you which services and routes get registered.Resources/config/*.yml — the service container definitions. This is the fastest map of the domain: block managers, page managers, content repositories, theme loaders.Page, Block, Language, Seo and their join tables. This is what you actually want to copy.Resources/views/ — the frontend editing UI (Twig 1). Read it for the UX contract: how a block is selected, edited, saved, and re-rendered in place.DependencyInjection/Configuration.php — the config tree; shows what was tunable (languages, themes, block types).Concrete first step for a Laravel dev: open the Propel schema (or the generated om/ and map/ classes) and transcribe it into Eloquent migrations. That one exercise gives you ~80% of the value of this repo.
1. Reference-to-migration pattern (the one you'll actually use).
Sketch the tables from the Propel model, then write Laravel migrations against them:
Schema::create('pages', function (Blueprint $t) {
$t->id();
$t->foreignId('site_id')->index();
$t->string('slug')->index();
$t->string('locale', 5)->index();
$t->boolean('is_published')->default(false);
$t->timestamps();
$t->unique(['site_id', 'locale', 'slug']);
});
Schema::create('blocks', function (Blueprint $t) {
$t->id();
$t->string('type'); // 'text', 'image', 'menu', custom
$t->json('payload'); // replaces RedKite's per-type block data
$t->timestamps();
});
Schema::create('page_block', function (Blueprint $t) {
$t->foreignId('page_id')->constrained()->cascadeOnDelete();
$t->foreignId('block_id')->constrained()->cascadeOnDelete();
$t->string('slot'); // 'main', 'sidebar', 'footer'
$t->unsignedInteger('position');
$t->timestamps();
});
2. Port the block-registry idea to the Laravel container.
RedKite registers block types as tagged Symfony services. The Laravel equivalent is a config-driven map plus a small resolver:
// config/blocks.php
return [
'text' => App\Blocks\TextBlock::class,
'image' => App\Blocks\ImageBlock::class,
];
// app/Blocks/BlockManager.php
public function resolve(string $type): BlockRenderer
{
$class = config("blocks.{$type}");
abort_unless($class, 422, "Unknown block type [{$type}]");
return app($class);
}
This keeps templates and the editor dropdown in sync via one source of truth — the same guarantee RedKite's tagged services gave it.
3. In-place editing UX pattern.
RedKite's selling point is editing on the rendered page, not in an admin panel. Reproduce it in Laravel with a route-level switch rather than a separate app:
Route::middleware(['web', 'can:edit-content'])
->get('/_edit/{page}', [EditorController::class, 'show']);
The public page and the editor render the same Blade view; a @can('edit-content') check injects wrapper attributes and Livewire/Alpine edit affordances. That avoids the classic mistake of maintaining two templates that drift.
4. Strangler pattern for a live legacy instance.
If a working RedKite deployment holds content and you're moving to Laravel:
php artisan redkite:import consumes that JSON into Eloquent, idempotently keyed on (site, locale, slug).5. Feature-flag the cutover, not the code. Put migrated pages behind a content_source flag per site, so you can flip back per-tenant during the transition without a deploy.
composer require redkite-cms/redkite-cms-bundle in a Laravel app and get a wall of constraint errors, that is the assessment result. Don't spend a day fighting it.declare(strict_types=1). You generally can't lift code — you read it and rewrite. Budget accordingly; "just copy the block renderer" rarely works.SELECT *-to-JSON dump per table, plus the page/block join table, is a few hours of work now and a panic later.bin/console in the old container, not php artisan. Keep the two toolchains mentally separated; mixing them up wastes more time than the port itself.How can I help you explore Laravel packages today?