teknoo / east-ux
Symfony bundle, following the #East programming philosophy, build on Teknoo/East-Foundation and Teknoo/East-Common, providing the application-level UI pieces of an East application: Symfony UX Twig and Live Components (windows, forms, lists, menu bar, ...), their Stimulus controllers and stylesheet,
Requires
- php: ^8.4
- symfony/form: ^7.4||^8.1
- symfony/framework-bundle: ^7.4||^8.1
- symfony/http-foundation: ^7.4||^8.1
- symfony/security-http: ^7.4||^8.1
- symfony/service-contracts: ^3.6
- symfony/translation-contracts: ^3.6.1
- symfony/ux-live-component: ^3
- symfony/ux-twig-component: ^3
- teknoo/east-common: ^4.8
- teknoo/east-foundation: ^9.4
- twig/twig: ^3.24
Requires (Dev)
- behat/behat: ^4.0
- behat/phpunit-assertions-extension: ^1.0
- laminas/laminas-diactoros: ^3.8
- phpstan/extension-installer: ^1.4.3
- phpstan/phpstan: ^2.2.12
- phpunit/phpunit: ^13.3.2
- roave/security-advisories: dev-latest
- squizlabs/php_codesniffer: ^4.0.1
- symfony/asset: ^7.4||^8.1
- symfony/asset-mapper: ^7.4||^8.1
- symfony/cache: ^7.4||^8.1
- symfony/config: ^7.4||^8.1
- symfony/console: ^7.4||^8.1
- symfony/css-selector: ^7.4||^8.1
- symfony/dependency-injection: ^7.4||^8.1
- symfony/dom-crawler: ^7.4||^8.1
- symfony/error-handler: ^7.4.17||^8.1.5
- symfony/event-dispatcher: ^7.4||^8.1
- symfony/event-dispatcher-contracts: ^3.6
- symfony/expression-language: ^7.4||^8.1
- symfony/filesystem: ^7.4||^8.1
- symfony/http-kernel: ^7.4||^8.1
- symfony/password-hasher: ^7.4||^8.1
- symfony/polyfill-php83: ^1.31
- symfony/property-access: ^7.4||^8.1
- symfony/property-info: ^7.4||^8.1
- symfony/psr-http-message-bridge: ^7.4||^8.1
- symfony/routing: ^7.4||^8.1
- symfony/security-bundle: ^7.4||^8.1
- symfony/security-core: ^7.4||^8.1
- symfony/security-csrf: ^7.4||^8.1
- symfony/stimulus-bundle: ^3
- symfony/translation: ^7.4||^8.1
- symfony/twig-bridge: ^7.4||^8.1
- symfony/twig-bundle: ^7.4||^8.1
- symfony/validator: ^7.4||^8.1
- symfony/var-dumper: ^7.4||^8.1
- symfony/yaml: ^7.4||^8.1
- teknoo/bridge-phpdi-symfony: ^7.2
Suggests
- symfony/asset-mapper: Required to serve the Stimulus controllers and the stylesheet of the components without a build step (the bundle maps its assets as `@teknoo/east-ux`).
- symfony/stimulus-bundle: Required to load the Stimulus controllers of the components from the controllers.json of the application.
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-22 12:59:55 UTC
README
Symfony bundle, following the #East programming philosophy, built on Teknoo/East-Foundation and Teknoo/East-Common, providing the application-level UI pieces of an East application:
- Symfony UX Twig and Live Components, with their templates: windows (
Window,FormWindow,ListWindow,LoginWindow),ObjectList,Pagination,ConfirmModal,CardandMenuBar; - their Stimulus controllers and a structural stylesheet, served by AssetMapper without build step (or consumed with npm);
- a component renderer endpoint, to load a
FormWindowdynamically, validating what a request may render; - a menu service, fed by the menu generators of the application.
The Live Components are re-rendered by the East recipe of their page: East Foundation's router maps a
POST /_components/<Name> back to the route of the component's originalPath and runs its recipe again, and East
Common's LiveComponentBuilder copies the view parameters of the recipe into the component. East UX only uses this
bridge: the router and LiveTrait stay in East Foundation, the LiveComponentBuilder and the recipe steps in East
Common.
Documentation
- Components: props, blocks and behaviour of each component.
- Live Components and the East bridge: re-renders through the recipes, the component renderer, security, limitations.
- Stimulus controllers: values of each controller and contract with the page.
- Menu: menu generators, sections, items and the menu service.
- Theming: stylesheet, form theme, template overrides and translations.
Installation & Requirements
This library requires:
* PHP 8.4+
* A PHP autoloader (Composer is recommended)
* Teknoo/East-Foundation 9.3+
* Teknoo/East-Common 4.6+
* Symfony 7.4+ or 8.1+ (FrameworkBundle, Form, Security HTTP), with SecurityBundle registered in the application
* Symfony UX TwigComponent 3 and LiveComponent 3
* Twig 3.24+
* Optional: Symfony AssetMapper and StimulusBundle, to serve the controllers and the stylesheet without build step
Before the release on Packagist
teknoo/east-ux is not published on Packagist yet: an application requires it from its Git repository. Add the
repositories entry and the requirement together, in the application's composer.json:
{
"repositories": [
{
"type": "vcs",
"url": "git@gitlab.teknoo.software:teknoosoftware/east-ux.git"
}
],
"require": {
"teknoo/east-ux": "^1.0@dev"
},
"config": {
"gitlab-domains": [
"gitlab.teknoo.software"
]
}
}
- The
mainbranch is aliased1.0.x-dev:^1.0@devresolves to it today and to the1.0.0tag once it exists (with"minimum-stability": "stable"and"prefer-stable": true, the tag wins). The constraint never changes; only therepositoriesentry is removed once the package is on Packagist. - With a GitLab token (the
COMPOSER_AUTHvariable, orauth.json:{"gitlab-token": {"gitlab.teknoo.software": "<token>"}}), Composer reaches the GitLab API and downloads the dist archive: the usualcomposer install --prefer-distworks. Use a project access token in the CI. - Composer 2.10 no longer installs a package from source when the download of its dist archive fails (its
source-fallbackoption isfalseby default, and deprecated). Before the library is pushed to GitLab, or for a developer with an SSH access to the repository but without GitLab API token, install withcomposer install --prefer-install=auto(orcomposer update --prefer-install=auto): dev versions, such as1.0.x-dev, are installed from source with git, every other package from its dist archive. - Before the library is pushed, the SSH URL can point at a local checkout of the library with a git rewrite scoped to
the Composer process, never written to a configuration file:
GIT_CONFIG_COUNT=1,GIT_CONFIG_KEY_0=url.<path of the local checkout>.insteadOfandGIT_CONFIG_VALUE_0=git@gitlab.teknoo.software:teknoosoftware/east-ux.git, with--no-interactionand--prefer-install=auto.composer.jsonandcomposer.lockkeep the GitLab URL and the commit hashes.
Once released
composer require teknoo/east-ux
Symfony configuration
Bundles
// config/bundles.php return [ // ... Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], Teknoo\DI\SymfonyBridge\DIBridgeBundle::class => ['all' => true], Teknoo\East\FoundationBundle\EastFoundationBundle::class => ['all' => true], Teknoo\East\CommonBundle\TeknooEastCommonBundle::class => ['all' => true], Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true], Symfony\UX\TwigComponent\TwigComponentBundle::class => ['all' => true], Symfony\UX\LiveComponent\LiveComponentBundle::class => ['all' => true], Teknoo\East\UXBundle\TeknooEastUXBundle::class => ['all' => true], ];
East Foundation and East Common are configured as their own documentation explains (di_bridge.definitions, ...).
East UX has no PHP-DI definition today: its infrastructures/symfony/config/di.php and twig.php are empty, listing
them in di_bridge.definitions is optional.
The bundle prepends:
twig_component.defaultswithTeknoo\East\UXBundle\Components\mapped to@TeknooEastUX/components/: the components keep their short names (<twig:FormWindow>,component('ListWindow', {...})). The application still configurestwig_component(anonymous_template_directoryand its owndefaults, both required by TwigComponentBundle);- when AssetMapper is available,
framework.asset_mapper.pathswith the package'sassets/distdirectory, as the namespace@teknoo/east-ux.
Bundle configuration
# config/packages/teknoo_east_ux.yaml teknoo_east_ux: home_route: 'app_home' # brand link of the MenuBar form_theme: '@App/form/fields.html.twig' # form theme of the FormWindow renderable_components: ['FormWindow'] # components the component renderer may render renderable_forms: # form types a request may name as FormWindow's formName - 'App\Form\Type\ItemType'
| Key | Default | Meaning |
|---|---|---|
home_route |
null |
Route of the brand link of MenuBar; without it, the brand links to / |
form_theme |
null |
Default of the formTheme prop of FormWindow; without it, no theme is set on the form and the global form themes apply |
renderable_components |
['FormWindow'] |
Components the component renderer may render (only those with an entry in ComponentRenderer::ALLOWED_PROPS, today FormWindow) |
renderable_forms |
[] |
Form types a request to the component renderer may name as formName |
A configured value can not be an empty string. The lists of every configuration are merged (and deduplicated): an
extension whose lists open a FormWindow adds its form types from its bundle's prepend():
public function prepend(ContainerBuilder $container): void { $container->prependExtensionConfig( 'teknoo_east_ux', ['renderable_forms' => [CustomerType::class]], ); }
The default ['FormWindow'] of renderable_components applies only when no configuration sets this key: a
configuration that sets it lists FormWindow itself.
The values are available as the parameters teknoo_east_ux.home_route, teknoo_east_ux.form_theme,
teknoo_east_ux.renderable_components and teknoo_east_ux.renderable_forms.
Routes and access control
# config/routes/east_ux.yaml: the component renderer answers on `POST /component/render` east_ux: resource: '@TeknooEastUXBundle/config/routes.yaml' prefix: '/component' # config/routes/ux_live_component.yaml: the Live Component endpoint, re-routed by East Foundation live_component: resource: '@LiveComponentBundle/config/routes.php' prefix: '/_components'
# config/packages/security.yaml security: access_control: # ... - { path: '^/component', roles: [IS_AUTHENTICATED_FULLY], requires_channel: https } - { path: '^/_components', roles: [IS_AUTHENTICATED_FULLY], requires_channel: https }
The component renderer is reached only by an authenticated user, and checks what it renders: the component, the props
(a request never sets objectInstance or originalPath), the form type, and a formPath that the access map must
grant to the user. East Foundation re-runs, for a /_components request, the recipe of the route named by the signed
originalPath, after checking the access rules of that route with the firewall (East Foundation 9.4): requiring
authentication on ^/_components and signing only paths the user may reach keep a user away from the recipes their
roles do not reach. The details are in documentation/live-components.md.
Components
| Component | Kind | Purpose |
|---|---|---|
Window |
Twig | Generic window (title bar, separator, pane), base of the other windows' templates |
FormWindow |
Live | Window holding a Symfony form, as a modal loaded dynamically or as a full page |
ListWindow |
Live | Paginated list of objects with an optional search form and a "New" button |
LoginWindow |
Twig | Login form of Symfony's form_login, error in #login-error |
MenuBar |
Twig | Navigation bar built from the menu generators |
ObjectList |
Twig | Table of objects, one item component per row |
Pagination |
Twig | Page buttons bound to the page prop of ListWindow |
ConfirmModal |
Twig | Bootstrap confirmation modal, used by ListWindow for deletions |
Card |
Twig | Bootstrap card; its content is escaped unless contentIsHtml is set |
Every template can be overridden in templates/bundles/TeknooEastUXBundle/components/<Name>.html.twig. Texts are
translation keys of the east_ux domain, with an English catalogue; labels supplied by the application (menu entries,
list headers, sub-form labels) keep its default domain. See documentation/components.md
and documentation/theming.md.
The East bridge
A page is rendered by an East recipe, and the Live Components it holds receive the view parameters of this recipe:
# config/routes/item.yaml app_item_list: path: '/items/' methods: ['GET', 'POST'] defaults: _controller: 'teknoo.east.common.endpoint.crud.list' loader: '@App\Loader\ItemLoader' defaultOrderDirection: 'ASC' itemsPerPage: 20 template: '@@App/item/list.html.twig' errorTemplate: '@@App/Error/<error>.html.twig'
{# templates/item/list.html.twig #} <twig:ListWindow title="{{ 'app.item.title.list'|trans }}" objectsCollection="{{ objectsCollection }}" pageCount="{{ pageCount }}" page="{{ page }}" itemComponent="ItemRow" tableHeaders="{{ ['app.item.table.name', 'app.item.table.actions'] }}" newFormRoute="app_item_new" newFormName="App\\Form\\Type\\ItemType" /> <div id="dynamic-modals"></div>
When a page button is clicked, the ListWindow is re-rendered: POST /_components/ListWindow runs the recipe of
/items/ again for the new page, and the component receives the new objectsCollection and pageCount. The "New"
button asks the component renderer for a FormWindow on /items/new, appends it to #dynamic-modals and opens it:
the window is re-rendered by the creation recipe, which builds the form. Components never load objects themselves.
See documentation/live-components.md.
Menu
namespace App\Menu; use Teknoo\East\Common\Contracts\User\UserInterface; use Teknoo\East\UX\Contracts\Menu\MenuGeneratorInterface; use Teknoo\East\UX\Menu\Location; use Teknoo\East\UX\Menu\MenuItem; use Teknoo\East\UX\Menu\MenuService; use function in_array; class AdminMenu implements MenuGeneratorInterface { public function generate(MenuService $menuService, Location $location, UserInterface $user, array $roles): void { if (Location::TopLeft === $location && in_array('ROLE_ADMIN', $roles, true)) { $menuService->getSection('app.menu.administration', $location) ->addItem(new MenuItem(name: 'app.menu.users', route: 'app_admin_user_list')); } } }
# config/services.yaml services: App\Menu\AdminMenu: tags: ['teknoo.east.ux.menu.generator']
<twig:MenuBar :user="app.user ? app.user.wrappedUser : null" />
The menu service caches the sections per user and location in its instance, and is reset between two requests
(kernel.reset). See documentation/menu.md.
Assets
AssetMapper and StimulusBundle
assets/package.json is read by Symfony Flex (the package has the symfony-ux keyword) and by StimulusBundle. When
the library is installed or updated, Flex writes the @teknoo/east-ux entries into the application's
assets/controllers.json (the seven controllers) and importmap.php (@teknoo/east-ux/styles/east-ux.css, and
@hotwired/stimulus and bootstrap when they are missing), each file only if it already exists: commit both files
first, then require the library and commit the entries Flex adds. A later composer install leaves them unchanged.
The application imports the stylesheet in its assets/app.js:
import './stimulus_bootstrap.js'; import 'bootstrap'; import 'bootstrap/dist/css/bootstrap.min.css'; import '@teknoo/east-ux/styles/east-ux.css'; import './styles/app.css';
bin/console debug:asset-map lists the assets of the library under @teknoo/east-ux/ (for example
@teknoo/east-ux/controllers/form_window_controller.js and @teknoo/east-ux/styles/east-ux.css).
npm
The same assets/package.json is a valid npm package. In an application still on npm (a package.json and no
importmap.php), Flex adds "@teknoo/east-ux": "file:vendor/teknoo/east-ux/assets" to package.json when the
library is installed or updated (in devDependencies, or where the key already is: move it to dependencies, the
browser loads the package at runtime), and writes the seven controllers into assets/controllers.json if that file
exists. Run npm install after Composer. The application imports the controllers from
@teknoo/east-ux/dist/controllers/, registers them with their identifiers (form-window,
form-window-dynamic-loader, list-window-refresh, window-close, window-drag, delete-item, clipboard) and
loads @teknoo/east-ux/dist/styles/east-ux.css. Without a bundler, its import map needs the prefix entry
"@teknoo/east-ux/": "<public URL of node_modules>/@teknoo/east-ux/".
See documentation/stimulus.md for the controllers and the contract with the page
(#dynamic-modals, delete-confirm-modal, the list-window:refresh event, the X-Requested-With header and the CSRF
token).
Support this project
This project is free and will remain free. It is fully supported by commercial activities of SASU Teknoo Software and EIRL Richard DELOGE. If you like it and help me maintain it and evolve it, don't hesitate to support me on Patreon or Github.
Thanks :) Richard.
Credits
EIRL Richard Déloge - https://deloge.io - Lead developer. SASU Teknoo Software - https://teknoo.software
About Teknoo Software
Teknoo Software is a PHP software editor, founded by Richard Déloge, as part of EIRL Richard Déloge. Teknoo Software's goals : Provide to our partners and to the community a set of high quality services or software, sharing knowledge and skills.
License
East UX is licensed under the 3-Clause BSD License - see the LICENSE file for details.
Contribute :)
You are welcome to contribute to this project. Fork it on Github