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: *
- bacon/bacon-qr-code: ^3.0
- doctrine/doctrine-bundle: ^3.2.2
- doctrine/orm: ^3.6.1
- jeremykendall/php-domain-parser: ^6.4
- predis/predis: ^3.3
- scheb/2fa-bundle: ^8.5
- scheb/2fa-totp: ^8.5
- 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
This package is auto-updated.
Last update: 2026-04-29 02:42:27 UTC
README
symfonicat/core is the full Symfony application for Symfonicat. Public routing, admin CRUD, Doctrine entities, webpack wiring, Docker/FrankenPHP, module runtime, application shells, and Electron packaging all live in this repository.
Install
For local development, point the seeded hosts at your Docker host:
127.0.0.1 example.com
127.0.0.1 project1.example.com
# clone repo git clone https://github.com/symfonicat/core symfonicat # or # composer w/php8.4 on host composer create-project symfonicat/core symfonicat # init cd symfonicat docker compose up -d
On startup the php container installs Composer dependencies, bootstraps the schema, seeds local defaults, runs npm install, and runs npm run build. The Docker image also installs n globally and runs n latest, so the Node/Electron toolchain inside the container is current.
After the stack is up:
docker exec -it php bin/console symfonicat:admin:create <username> docker exec -it php bin/console symfonicat:schema:update
Public Runtime
Runtime resolution is layered:
DomainServiceresolves the base host.ProjectServiceresolves the project subdomain when present.RoutingRuleSubscriberapplies redirect, route, domain, project, and application rules.ApplicationServiceloads application shells either from regex path rules or route-bound application rules.- The public controllers render the domain, project, or application shell when a Symfony route has not already taken over.
The default public routes are:
/for the domain shell/{path}for the project shell when a project subdomain is active/application/{id}/{path}as the internal application entry route used to render an application and redirect client-side history to its public rule-backed path
Symfonicat-owned Symfony route and table names are prefixed with symfonicat_.
Routing Rules
Supported rule types:
domain: render the domain shell for a matching regex pathproject: disable the project catch-all for a matching regex path so Symfony routes can handle itapplication: either match regex arguments and render an application shell, or bind an application to a named Symfony routeredirect: redirect a whole domain or project to another domain, project host, orproject.domainpairroute: render a named Symfony route for the root of a domain or project
Application rules use applicationType:
arguments: use the regex argument listroute: useRoutingRule.routeas the Symfony route name
Env
Env is grouped by EnvParent, with leaf keys stored in Env.
Runtime precedence is:
- application env
- domain env
- project env
- electron env, but only for Electron requests
Project values overwrite domain values, domain values overwrite application values, and Electron values overwrite the merged result only when the current request is running in Electron.
The same grouped structure is emitted directly into window.env:
window.env = { colors: { primary: 'blue' } }
Scoped env forms on domains, projects, and applications filter the env dropdown by the selected env parent and restore the saved parent when editing existing rows. Electron rows use the same scoped env collection UI, and Electron env values override all lower layers for Electron requests only.
There is also EnvService for env lookups. Use ->get($id, $entity) or ->all($entity) and omit $entity to let the other retrieval services pull the correct env values for the URL you're using it for.
Twig
Twig, for the env helper, uses dotted lookups such as:
{{ env('colors.primary') }}
There is also a path_application helper that works like this:
{# https://example.com/symfony/*/test #} {{ path_application(application) }} {# https://example.com/symfony/PARAM/test #} {{ path_application(application, ['PARAM']) }} {# https://example.com/symfony/*/test/somepath/testpath #} {{ path_application(application, 'somepath/testpath') }} {# https://example.com/symfony/PARAM/test/somepath/testpath #} {{ path_application(application, 'somepath/testpath', ['PARAM']) }} {# application ID also works #} {{ path_application('test', 'somepath/testpath', ['PARAM']) }}
Assets
Webpack entry discovery is driven by symfonicat:data:webpack, with database-backed rows and filesystem fallback:
assets/applications/{id}->applications/{id}assets/domains/{id}->domains/{id}assets/projects/{id}->projects/{id}assets/modules/{id}->modules/{id}
Public assets live on:
assets/symfonicat.jsassets/stimulus.jsassets/controllers.jsonassets/controllers/
Admin-only JavaScript belongs on the admin asset stack, assets/*_admin*.
Module Runtime
Backend module controllers live under /m/{id} and should extend Symfonicat\Controller\AbstractModuleController.
Frontend helpers from assets/module.js support:
''.json(payload)''.json(path, payload)''.html(payload)''.html(path, payload)''.log(...args)
Application shells expose a signed application request context through application_helper(), which writes window.application plus debug logs into the base layout script block. Application-backed module requests send the application id plus signed headers so /m/{id} can execute when the module is attached to that application.
The base layout also writes window.electron from the Twig electron global. That value is a boolean flag indicating whether the current request is running in Electron mode.
Admin
Admin is isolated from any host user system and uses its own Admin entity plus Symfony security and TOTP MFA at /admin.
Modules do not have admin CRUD. Module rows are synchronized from assets/modules/{id}/package.json.
symfonicat:admin:create prompts for the password with hidden input, so Docker usage should include -it.
Electron
Each Electron row has:
nametype(domain,project, orapplication)- one matching relation field, except
projectrows which carry bothprojectanddomain - an optional favicon upload stored at
public/electron/favicon/{type}/{targetId}.png - an
envcollection using the sameEnvParent+Envselectors as domains, projects, and applications
For project Electron rows, targetId is projectId.domainId.
That same projectId.domainId target id is used for both override templates and build output paths.
The admin form shows only the relation field that matches the selected type, except project rows which show both the project and domain selectors.
Electron build templates live under:
templates/electron/domain/main.twig.jstemplates/electron/project/main.twig.jstemplates/electron/application/main.twig.jstemplates/electron/{type}/overrides/{targetId}.twig.js
For project Electron rows specifically:
- override templates live at
templates/electron/project/overrides/{projectId}.{domainId}.twig.js - generated files live under
electron/project/{projectId}.{domainId}/
Build Electron outputs with:
docker exec php bin/console symfonicat:electron:build docker exec php bin/console symfonicat:electron:build <name>
For each Electron row, the command renders the override template if present, otherwise the type-specific main *.twig.js template, writes electron/{type}/{targetId}/app.js, writes a local package.json with a fixed Electron version derived from the root package, and runs electron-builder into electron/{type}/{targetId}/build. For project Electron rows that means electron/project/{projectId}.{domainId}/.... The generated Electron package points at the matching domain host, project.domain host, or application path and appends ?electron to the start URL. Those build directories are generated outputs and are ignored by Git.
Electron requests keep using the Twig Electron globals. When a request is flagged as Electron, the extension loads the matching Electron row for the active application, project, or domain and exposes its favicon to the base layout.
Sync and Bootstrap
symfonicat:bootstrap seeds local defaults, including:
localhostexample.comproject1testapplicationanalyticsmoduleExample TestElectron row bound toexample.com/symfonicat/*/test*application routing rule- grouped sample env values under
colors.primary, including an Electron override ofyellowfor the seededExample TestElectron row
symfonicat:schema:update synchronizes:
- modules from
assets/modules/{id}/package.json - applications from
assets/applications/{id} - projects from
assets/projects/{id}
Run schema sync with an interactive terminal when confirmations may be needed:
docker exec -it php bin/console symfonicat:schema:update