zdrojowa/cms-kernel
A Laravel CMS kernel package providing core building blocks for content-driven applications, with a lightweight foundation for extending and wiring up CMS features inside your PHP projects.
Do a five-minute viability check before you install anything. This package's last release was October 2019 and it has a single star. Read composer.json on the master branch and look at the require block: if it pins illuminate/support to ^5.8 or ^6.0, it will not install cleanly on Laravel 10/11 without --ignore-platform-reqs or a fork. Confirm the constraint and whether the repo has a tests/ directory and a CHANGELOG. If either is missing, treat this as "vendored source I own" rather than "dependency I consume" — copy the useful parts into app/Cms/ and move on. That decision shapes everything below.
If you do install it:
composer require zdrojowa/cms-kernel
Then check vendor/zdrojowa/cms-kernel/composer.json for extra.laravel.providers. A 2019-era package may rely on package discovery (Laravel 5.5+) or may not declare it at all. If it is absent, register the provider manually in config/app.php (or bootstrap/providers.php on L11). Do not assume vendor:publish works — check src/Providers/ for a publishes() call first, and whether it uses tags.
Where to look first, in order:
src/ top-level directory listing. The description promises "modular kernel", "content and rendering hooks", and "service-provider bootstrap" — those three concerns will map to roughly three namespaces. Find them before writing a line of code; the README will not help you.src/Contracts/ (or src/Contracts equivalent) — these interfaces define the seam you are allowed to depend on. Everything else is internal.Kernel, Registry, Module, Renderer, or Hook class. That is your real API.register() and boot() — tells you what is a singleton, what is deferred, and what view namespace or route group is registered globally.First use case. Do not wire it into a real page. Create a throwaway route:
Route::get('/_cms-smoke', function () {
$kernel = app(\Zdrojowa\CmsKernel\Kernel::class);
return response()->json([
'modules' => array_keys($kernel->modules()), // confirm registration works
'renderers' => array_keys($kernel->renderers()),
]);
});
That single response tells you whether autoloading, provider registration, and the registry bootstrapping all work — before any content type, hook, or Blade view is involved. Delete the route afterwards.
Treat the kernel as a registry, not a framework. The valuable part of a CMS kernel is the registry: modules declare content types and renderers, and the kernel routes requests and renders output. Your job is to register modules, not to fight the kernel for control of the request lifecycle. Keep content-type logic in module classes and let the kernel orchestrate.
One module class per content type, registered from one place. Typical shape:
// app/Cms/Modules/ArticleModule.php
class ArticleModule
{
public function key(): string { return 'article'; }
public function routes(): void { /* kernel-provided route group */ }
public function render($model): string { return view('cms.article', compact('model'))->render(); }
}
// app/Providers/CmsServiceProvider.php
public function boot(Kernel $kernel): void
{
$kernel->registerModule(new ArticleModule());
}
Keep ArticleModule free of request(), auth(), and container lookups — inject those. Modules that reach into globals become untestable fast.
Use the rendering hooks instead of hardcoded @includes. If the kernel exposes a render pipeline (hooks around content resolution and view selection), route all content output through it, even where a plain Blade include would do. You get consistent ordering, and later you can insert caching or a fallback renderer in one place rather than in fifty views. If the hook API is missing or undocumented, add your own thin Renderer wrapper in app/Cms/ and call the kernel from there — do not edit vendor files.
Put the kernel behind an application-owned facade. This is the highest-leverage pattern given the package's age:
// app/Cms/CmsGateway.php
final class CmsGateway
{
public function __construct(private Kernel $kernel) {}
public function renderContent(string $type, $model): string { /* kernel calls */ }
public function contentTypes(): array { /* kernel calls */ }
}
Your controllers and Blade views depend on CmsGateway, never on Zdrojowa\CmsKernel\*. When (not if) you fork, replace, or drop the kernel, you edit one class.
Resolve from the container; skip facades. If the kernel registers a facade alias, use it only inside Blade. In PHP, type-hint the kernel or the gateway. It makes feature tests trivial and keeps you free of static state.
Register routes deliberately. A CMS kernel usually wants to own routes for content types (/articles/{slug} etc.). Let it own those, and keep application routes (/dashboard, /api/*) explicit in routes/web.php. Never let the kernel's catch-all come last-but-one — it will swallow your 404 handling. Verify order with php artisan route:list.
Test the gateway, not the kernel. In feature tests, bind a fake gateway into the container that returns canned HTML. Reserve kernel-level tests for a small suite asserting your modules register and render. You are not going to get upstream test utilities from a 2019 package.
This is effectively unmaintained — plan the exit before you start. No commits since 2019 means no Laravel upgrade path and no security fixes. Pin exactly ("zdrojowa/cms-kernel": "dev-master" or a commit hash) and keep the gateway seam described above. If the kernel turns out to be 400 lines of registry code, reimplementing it in app/Cms/ over an afternoon is usually cheaper than carrying the dependency for a year.
Check for a config file before assuming one exists. Many small kernels have zero configuration and hardcode a view namespace. If there is no config/ directory in the package, then vendor:publish will do nothing and there is no config('cms-kernel.*') to modify — you change behavior by rebinding contracts or extending classes, not by config. Knowing this saves an hour of confused searching.
Config cache and env(). The standard Laravel rule applies with extra force on an old package: if the package config file calls env(), it must be published and cached at deploy time; env() returns null once config:cache has run without it. Never call env() in your own code that touches kernel settings.
Deferred providers and boot ordering. If the kernel provider is deferred, Kernel::registerModule() in your provider's register() may run before the kernel's own bindings exist. Register modules in boot(), or force ordering with an explicit $app->register(KernelServiceProvider::class) in AppServiceProvider::register(). Symptom: "Target [Kernel] is not instantiable" only on cold requests, fine once tinker has warmed the container.
Double registration. Calling registerModule() twice (once in a provider, once in a test bootstrap, once via a package auto-discovery duplicate) yields duplicated routes or duplicated render output. Guard with $kernel->hasModule($key) or register from exactly one place and assert on the registry in a test.
View namespace collisions. Kernels commonly register a view namespace like cms::. If another package or your own code uses the same namespace, view('cms::article') resolves unpredictably. Run php artisan view:clear and check app('view')->getFinder()->getHints() when a "view not found" contradicts the filesystem.
Debug by dumping the registry, not the rendering. When a page renders blank, the problem is almost always that no module/renderer is registered for that content type. Dump the registry first:
dd(app(\Zdrojowa\CmsKernel\Kernel::class));
If the registry is populated and output is still empty, the renderer is returning '' — check the view path and whether the renderer expects a model or an array.
Extension points worth preferring: rebinding a contract in src/Contracts/ from your own provider (the supported seam), and adding a renderer via whatever extend()/registerRenderer() method the kernel exposes. Avoid subclassing kernel internals — with no maintainer, upstream will never accommodate your override, and you will be the sole owner of a diverging fork.
Only build on this if the registry actually saves you work. Read the source, count the classes. If the "kernel" is under a few hundred lines, the honest assessment is that the abstractions you need — content-type registry, renderer pipeline, module provider — are an afternoon of code in your own namespace, with tests, on a Laravel version you control. Adopt the package only if its renderer or routing logic is genuinely non-trivial and you cannot reproduce it cheaply.
How can I help you explore Laravel packages today?