Web starter . Server-rendered

Dotkernel Frontend

A web starter skeleton on the Mezzio microframework and Laminas components, for the applications people log into. User accounts, a working contact form and a content page ship as proof of concept - real, running features whose only job is to show you where your own code goes.

PHP version for dotkernel/frontend Latest release of dotkernel/frontend License of dotkernel/frontend OSS lifecycle for dotkernel/frontend
Runtime Mezzio + Laminas
Pattern Action controllers
Templating Twig
What you could build on it
Blog
Marketplace
Social feed
What it is

The web application half of the stack

Frontend is the skeleton for a server-rendered application with users in it. Where Light gives you pages, Frontend gives you the machinery around them: sessions, authentication, registration, validated forms, flash messages between redirects, and role-based access to individual controller actions.

The shipped features - contact us, a generic content page, user accounts - are deliberately presented as building blocks rather than a finished product. They exist to showcase the file architecture and to be copied.

Extending the power of
User accounts, from registration to account deletion

User accounts, from registration to account deletion

Action controllers, not request handlers

Action controllers, not request handlers

CSRF and reCAPTCHA on public forms

CSRF and reCAPTCHA on public forms

GDPR anonymization out of the box

GDPR anonymization out of the box

What ships wired up

Everything a logged-in site needs first

These are the parts you would otherwise spend your first two weeks assembling, already working together in a fresh install.

Users . Accounts

Registration & login

The whole account lifecycle, already routed.

Login, registration and account management, including activation, password reset, avatar upload, profile details, password change and account deletion. Password reset and account activation emails are part of the flow, which is why the only personal details on a user profile are a name and an email address.

Security . Access control

Guards per action

Permissions applied to individual controller actions.

dot-rbac-guard and dot-rbac read roles and permissions from authorization.global.php, then authorization-guards.global.php maps rules onto a route and a named list of its actions - or an empty list to cover all of them. Fine-grained without being scattered.

Security . Forms

CSRF tokens

A new token per render, validated on submit.

Built from the laminas-form CSRF element, a laminas-session CSRF validator in the input filter, and the formElement view helper in the template. Tokens expire after a configurable timeout - one hour by default - and are never reusable between forms.

Public . Contact

Contact form with reCAPTCHA

A public form that does not become a spam relay.

The contact form uses score-based Google reCAPTCHA, with the site key, secret key and score threshold read from local configuration, and the message recipients - to, cc and bcc addresses - configured alongside them. The contact page will not render until the keys are set; whitelist localhost while developing, and take it out again for production.

UX . Feedback

Flash messages

The message survives the redirect.

dot-flashmessenger carries session messages across redirects - the small piece that makes post-then-redirect flows feel finished. dot-session extends laminas-session underneath, configured in session.global.php.

Wiring . DI

Attribute injection

Constructor injection declared on the constructor.

dot-dependency-injection reads an #[Inject] attribute and resolves each listed dependency - a service, the whole config array, or a single key by dot notation. Register the class against AttributedServiceFactory and stop writing factories.

Data . Persistence

Doctrine ORM

Migrations and fixtures, driven from the CLI.

Doctrine through roave/psr-container-doctrine, with UUIDs as a field type via ramsey/uuid-doctrine. Migrations live in data/doctrine/migrations; bin/doctrine fixtures:execute seeds the default roles.

Delivery . HTTP

Headers & CORS

Response headers declared globally or per route.

dot-response-header sets custom headers for all routes or for individual routes from response-header.global.php, while mezzio-cors handles origins, headers and cookies from cors.global.php.

Content . Presentation

Menus, templates, i18n

Navigation from configuration, translation when you need it.

dot-navigation defines and parses menus inside templates from configuration. dot-twigrenderer adds Dotkernel's Twig extensions, and laminas-i18n is present for a complete translation suite. The Plugin module carries dynamic forms and templates.

Compliance

Account anonymization, not just deletion

Under the GDPR, a company recording personal data from EU citizens must delete it on request - or anonymize it, which the European Commission accepts as an alternative. Frontend implements the second option, because deleting a user row is rarely what your foreign keys want.

On the user profile, the skeleton stores only what it needs to run those flows: first name, last name and the email address used as the identity, for password reset and account activation. Anonymizing replaces exactly those; contact form messages and remember-me records are kept as they are.

What anonymization changes

  • First and last name become anonymous plus the current date and time in dmYHis format - for example anonymous23092026155300.
  • The email becomes the same value plus whatever you set in userAnonymizeAppend - anonymous23092026155300@example.com.
  • The account status is set to deleted; the row itself is kept.
  • On account deletion, the avatar image and its database record are deleted.

Point userAnonymizeAppend at a domain you control and it doubles as a catch-all address, if your mail provider supports one. Leave it empty and the local part stands alone.

Structure

Five modules, PSR-4 throughout

Each module keeps its controllers, entities, repositories and services together, alongside a ConfigProvider and a RoutesDelegator - so a new feature has an obvious shape before you write it.

User

Login, registration and account management - the largest module, and the one worth reading first.

App

Core functionality: authentication, rendering and error reporting.

Contact

The contact us form, from validation through to the outgoing mail.

Page

Displaying a page - the minimal case, for static copy.

Plugin

Plugin functionality for dynamic forms and templates.

Module contents

Controller, Entity, Repository and Service folders, plus Form, Fieldset, InputFilter, EventListener, Factory, Middleware or Enum as needed.

Installation

From clone to welcome page

The documentation walks through every command with its expected output. This is the sequence.

1 . Clone into an empty folder

Git refuses a non-empty directory, and you need write permissions on it.

git clone https://github.com/dotkernel/frontend.git .

2 . Install dependencies

From the CLI, so the prompts stay interactive. Decline the config provider injection - Frontend ships its own.

composer install

3 . Enable development mode

Sets debug on, configuration caching off, and clears any existing cache.

composer development-enable

4 . Prepare the config files

composer install has already created local.php and mail.global.php, and development mode created development.local.php; fill in the database and reCAPTCHA details in local.php, and copy mail.global.php to mail.local.php for the sender and SMTP details, so credentials stay out of git.

5 . Migrate and seed

Migrations build the schema and are logged so none runs twice; the fixtures populate the default user roles.

php vendor/bin/doctrine-migrations migrate
php bin/doctrine fixtures:execute

6 . Fix permissions and open it

Three writable paths cover almost every first-run error.

chmod -R 777 data log public/uploads

Two local-only notes worth keeping: session.cookie_secure has to be false in your own local.php - never in local.php.dist, where it stays true for production - and a stale data/cache/config-cache.php is loaded regardless of ConfigAggregator::ENABLE_CACHE, so clear it with bin/clear-config-cache.php when services go missing. Duplicating local.test.php.dist gives your tests an in-memory database.

Requirements

What the server needs

Component Requirement
Operating system A *nix based system is strongly recommended for production.
PHP 8.2 or 8.3, mod_php or FCGI (FPM). memory_limit at least 128M; upload_max_filesize and post_max_size at least 100M depending on your data.
Web server Apache 2.2+ with mod_rewrite and .htaccess support (AllowOverride All); a default .htaccess ships in public/. On Nginx, translate it into server configuration.
Database Tested with MariaDB 11.4 LTS, 11.8 LTS and 12.3 LTS, and with PostgreSQL 13 and above, my.cnf needs mysql_native_password=ON.
Required extensions curl, gettext, intl, json, mbstring, the CLI SAPI for cron jobs, and Composer on $PATH.
Recommended extensions opcache; pdo_mysql for MySQL or MariaDB; dom and simplexml for markup; gd and exif for images; zlib, zip, bz2 for compression; sqlite3 for the test suite.

Note that Frontend still supports MySQL - unlike API and Admin v7, which require native UUID support and therefore PostgreSQL 13+ or MariaDB 11.4+.

Choosing

Where Frontend sits

Frontend stands on its own, outside the Headless Platform. It is the right starting point when your users log in and your pages are rendered on the server - and the wrong one in both directions from there.

Smaller . Minimal

Light

No users, no database, no forms to protect.

The smallest complete Mezzio application - routing, pipeline and Twig, six direct dependencies. Right for a presentation site.

Different . HTTP surface

API

Your frontend is someone else's framework.

A REST API on a PSR-15 middleware pipeline, with OAuth 2.0, RBAC, HAL payloads and an OpenAPI 3.0 specification wired up on install.

Bigger . Platform

Admin

You need a back office over a shared domain.

Table-based record management with RBAC guards, CSRF-protected forms and 2FA, over a Core module shared with API and Queue.

Alongside . Async work

Queue

Move the registration email off the request.

Background workers on Symfony Messenger - a TCP listener, Valkey streams, retries and a dead letter queue for what still fails.

Tooling . AI context

Dotboost

Teach your AI tools this architecture.

Drop-in Claude Code configuration - ten commands, seventeen skills and permission guardrails that keep your secrets out of the context window.

Open source, in production

Accounts, forms and compliance - already handled.

Dotkernel Frontend is developed and led by the dev team at Apidemia and released as open source for the community. Try the demo to see the account flows before you install anything.