symfonicat / core
Symfonicat Symfony application with admin, routing, module runtime, Electron tooling, and FrankenPHP starter infrastructure.
Requires
- php: >=8.4
- ext-ctype: *
- ext-iconv: *
- ext-redis: *
- doctrine/doctrine-bundle: ^3.2.2
- doctrine/orm: ^3.6.1
- jeremykendall/php-domain-parser: ^6.4
- predis/predis: ^3.3
- symfony/asset: 8.0.*
- symfony/console: 8.0.*
- symfony/dotenv: 8.0.*
- symfony/expression-language: 8.0.*
- symfony/flex: ^2
- symfony/form: 8.0.*
- symfony/framework-bundle: 8.0.*
- symfony/http-client: 8.0.*
- symfony/mercure-bundle: ^0.4.2
- symfony/messenger: 8.0.*
- symfony/polyfill-intl-idn: ^1.33
- symfony/process: 8.0.*
- symfony/property-access: 8.0.*
- symfony/property-info: 8.0.*
- symfony/redis-messenger: 8.0.*
- symfony/runtime: 8.0.*
- symfony/security-bundle: 8.0.*
- symfony/string: 8.0.*
- symfony/twig-bundle: 8.0.*
- symfony/ux-turbo: ^2.32
- symfony/validator: 8.0.*
- symfony/webpack-encore-bundle: ^2.4
- symfony/yaml: 8.0.*
- twig/extra-bundle: ^3.22.2
- twig/twig: ^3.22.2
Requires (Dev)
- phpunit/phpunit: ^12.5.4
- symfony/browser-kit: 8.0.*
- symfony/css-selector: 8.0.*
Conflicts
README
symfonicat/core is the full Symfonicat Symfony application: admin, public runtime, webpack, Electron, and the FrankenPHP-oriented starter shell all live here
Install
composer create-project symfonicat/core symfonicat
cd symfonicat
docker compose up -d
npm install
npm run dev
You don't need to run doctrine:schema:create to get the UI up locally. On first container boot, the php service bootstraps the local stack:
- synchronizes the Doctrine schema
- seeds a
localhostdomain row - seeds and enables the
analyticsmodule forlocalhost
After the containers are up, create an admin:
docker exec php bin/console symfonicat:admin:create <username> <password>
Philosophy
- only supports one subdomain layer
- a subdomain (
Project) is usually a second-tier section of a site, so Symfonicat treats it as a first-class runtime shell Projectentities exist in the database for Symfony-native referencing, modularity, extension, and trackingProjectentities can be prepared for Electron applications- when a
Projectis active, the public runtime uses a catch-all route so the rest of the URL can be client-side routed - if no
Projectis active, theDomainruntime is loaded - if you are on a
Domainand there is a path, Symfony handles it - if you are on a
Domain, there is a path, and a matchingRoutingRuleexists for the first path segment, Symfonicat can redirect to the correctDomainand let Symfony continue from there
Included
- the public frontend runtime for domains, projects, modules, and routing rules
- the separate
/adminruntime and admin templates - the internal
SymfonicatBundleservice/entity/template organization - shared frontend assets under
assets - the webpack helper webpack.symfonicat.js
- the Electron desktop shell under electron
- drop-in FrankenPHP infrastructure files such as compose.yaml and Caddyfile
Runtime Model
Symfonicat resolves requests in layers.
- A request arrives on the base domain or a subdomain.
- Subscribers resolve the active
Domain,Project, andRoutingRule. - The public controller decides whether to render a domain shell or a project shell.
- If there is no resolved project and the domain request has a path, the request continues through the normal Symfony application routes.
- Encore entrypoints are selected from the current database-backed domain, project, and module state.
The public entry routes live in MainController.php:
/renders the domain shell when there is no resolved project./{path}renders the project shell when a project is resolved onto the request.- domain paths without a resolved project are left for the Symfony app route table, such as
/admin,/m/{slug}, or other application routes.
Resolution is driven primarily by subdomain and routing-rule context. The key runtime pieces are:
Module System
The module system is database-backed and route-aware.
- A
Modulerecord is identified by slug. - Modules can be attached to a
Projector aDomain. - Backend module endpoints live under
/m/{slug}. - Frontend module entrypoints build under
modules/{slug}.
The runtime guard is in AbstractModuleController.php. A module controller only runs when the current request context actually has that module attached.
That gives you a clean rule:
- attach a module to a project when it should run only inside that project
- attach a module to a domain when it should run at the domain level without a project
The current concrete server example is AnalyticsController.php, which exposes POST /m/analytics.
Client-Side Routing and Assets
Symfonicat keeps frontend routing simple: the server resolves the page shell, then project/module entrypoints attach behavior.
The project shell in project/main.html.twig loads:
- a project entrypoint named
projects/{project.slug} - zero or more module entrypoints named
modules/{module.slug}
The shared webpack helper webpack.symfonicat.js discovers entries from:
symfonicat:data:webpack- or, if that command is unavailable during build time, the filesystem under
assets/domains,assets/projects, andassets/modules
Frontend bootstrap is split into a few small pieces:
- app.js is the shared app entry
- stimulus.js starts Stimulus through
@symfony/stimulus-bridge - controllers.json enables Symfony UX controllers
- Turbo is started through the
symfony--ux-turbo--turbo-corecontroller mounted on the<body>in the base layouts
Browser-side module requests are intentionally simple and live in module.js. The conventions are:
- all module requests are
POST - all module requests begin with
/m/{module} - if a path is provided, the final URL becomes
/m/{module}/{path} .json(...)expects a JSON response body.html(...)expects a raw HTML response body
Examples:
const parsedJson = await 'analytics'.json({ working: true }); const parsedJsonWithPath = await 'analytics'.json('path/secondpath', { working: true }); const parsedHtml = await 'frame'.html({ working: true }); const parsedHtmlWithPath = await 'frame'.html('path/secondpath', { working: true });
Those helpers are available through the shared app.js bootstrap. Module entrypoints do not need to import ./module themselves.
Admin Area
`` The admin runtime is separate from any public app user system.
- admin auth is HTTP basic
- admin credentials live in the separate
symfonicat_admintable
Redis
The Docker stack provides Redis at the standard REDIS_URL environment variable:
REDIS_URL=redis://redis:6379 MESSENGER_TRANSPORT_DSN=redis://redis:6379/symfonicat_messages MESSENGER_FAILED_TRANSPORT_DSN=redis://redis:6379/symfonicat_failed_messages MESSENGER_CONSUMER_NAME=symfonicat
Symfony uses Redis for shared runtime infrastructure:
cache.appusescache.adapter.redis_tag_awarecache.systemstays on Symfony's default local adapter for container and warmup data- sessions use
RedisSessionHandlerwith asymfonicat_session:key prefix cache.symfonicatis available as a one-hour application cache pool for Symfonicat runtime data- Messenger uses Redis streams for the
asyncandfailedtransports Symfony\Component\Mercure\Updatemessages dispatched through Messenger are routed toasync
Run a Redis-backed Messenger worker when you need async message consumption:
docker exec php bin/console messenger:consume async docker exec php bin/console messenger:stats docker exec php bin/console messenger:failed:show
Key console commands
Scaffolding:
bin/console make:module <Name> <slug>creates a module controller, module service, andassets/modules/{slug}/index.jsentrypoint
Admin management:
symfonicat:admin:createcreates or updates an admin accountsymfonicat:admin:deletedeletes an admin account by emailsymfonicat:bootstrapwaits for PostgreSQL, synchronizes the schema, and seedslocalhostfor local development
Runtime data export:
symfonicat:data:webpackoutputs domain, project, and module data for Encore entry discoverysymfonicat:data:electronoutputs project metadata for the Electron layersymfonicat:data:dnsoutputs project/domain data for DNS-related workflows
Electron support:
symfonicat:electron:prepareprepares Electron project directoriessymfonicat:electron:devprepares Electron directories for local developmentsymfonicat:electron:buildruns the Electron build flowsymfonicat:electron:packagepackages Electron bundles
Ops and diagnostics:
symfonicat:public-suffix:refreshdownloadspublic_suffix_list.datsymfonicat:test:mercurepublishes a Turbo Stream test eventsymfonicat:test:redischecks Redis connectivity
Infrastructure
This repo ships app-level infrastructure files directly:
- compose.yaml for FrankenPHP, Mercure, PostgreSQL, and Redis
- Caddyfile for the FrankenPHP/Caddy setup
- electron.js plus the electron directory for desktop packaging