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.
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.
User accounts, from registration to account deletion
Action controllers, not request handlers
CSRF and reCAPTCHA on public forms
GDPR anonymization out of the box
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
anonymousplus the current date and time indmYHisformat - for exampleanonymous23092026155300. - 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.
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.
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.
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+.
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.
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.
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.
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.
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.
Go deeper
How to Use Twig Markdown to Generate HTML Pages at Runtime
How to wire twig/markdown-extra with league/commonmark so a Twig application converts Markdown files to HTML on every request, instead of pre-compiling pages at build time.
Read more ->What "Production Ready" Means for Dotkernel API
Dotkernel API describes itself as production ready. Here is exactly what that covers — and the infrastructure layer (rate limiting, gateway, federated identity, error tracking, health checks, caching) that stays yours to add before launch.
Read more ->How to build this website starting from Dotkernel Light
How this blog itself came together: starting from the Dotkernel Light starter project, then following the Tutorial 101 to add Doctrine ORM and load real data into the database.
Read more ->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.