From eee57a7a87d9ad21dfdd7ff7f20f9c417b9ed832 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Mon, 23 Jun 2025 15:48:38 +0200 Subject: [PATCH 01/53] Update passing-data-to-your-hookables.md Fix indication in line on example code. --- docs/twig-hooks/passing-data-to-your-hookables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/twig-hooks/passing-data-to-your-hookables.md b/docs/twig-hooks/passing-data-to-your-hookables.md index 72f967f3f..13b920cff 100644 --- a/docs/twig-hooks/passing-data-to-your-hookables.md +++ b/docs/twig-hooks/passing-data-to-your-hookables.md @@ -35,7 +35,7 @@ The context data from these two sources are merged and passed with the metadata ``` {% endcode %} -So, as we see at `line 8` we define the `index.form` hook. But also, we pass the `form` with using the `with` keyword. Thanks to it, we are able to pass multiple data to hookables that will hook into the `index.form` hook. +So, as we see at `line 11` we define the `index.form` hook. But also, we pass the `form` with using the `with` keyword. Thanks to it, we are able to pass multiple data to hookables that will hook into the `index.form` hook. {% hint style="info" %} `with { form }` is a short-hand for `with { form: form }`, so the key for our `FormView` in the context data bag will be `form.` From bd1d33771ff4b33ea5332afc8e7412420404fd5c Mon Sep 17 00:00:00 2001 From: Florian Merle Date: Sun, 17 Aug 2025 20:39:49 +0200 Subject: [PATCH 02/53] add metatags hook --- docs/SUMMARY.md | 1 + docs/cookbook/admin_panel.md | 1 + docs/cookbook/admin_panel/metatags.md | 51 +++++++++++++++++++++++++++ src/AdminUi/templates/base.html.twig | 5 ++- 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 docs/cookbook/admin_panel/metatags.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 77bf032e7..e11ebe7f3 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -12,6 +12,7 @@ * [Customizing the menu](cookbook/admin_panel/menu.md) * [Configuring the security access](cookbook/admin_panel/security.md) * [Customizing the page titles](cookbook/admin_panel/page_titles.md) + * [Customizing the metatags](cookbook/admin_panel/metatags.md) * [How to use in a DDD architecture](cookbook/ddd_architecture.md) * [Architecture overview](cookbook/ddd_architecture/overview.md) * [Resource configuration](cookbook/ddd_architecture/resource_configuration.md) diff --git a/docs/cookbook/admin_panel.md b/docs/cookbook/admin_panel.md index e84be7da6..70fcd495f 100644 --- a/docs/cookbook/admin_panel.md +++ b/docs/cookbook/admin_panel.md @@ -6,3 +6,4 @@ * [Customizing the menu](admin_panel/menu.md) * [Configuring the security access](admin_panel/security.md) * [Customizing the page titles](admin_panel/page_titles.md) +* [Customizing the metatags](admin_panel/metatags.md) diff --git a/docs/cookbook/admin_panel/metatags.md b/docs/cookbook/admin_panel/metatags.md new file mode 100644 index 000000000..c5daba1ca --- /dev/null +++ b/docs/cookbook/admin_panel/metatags.md @@ -0,0 +1,51 @@ +# Customizing the metatags + +## Adding metatags + +To add new `` meta tags, you can use the `sylius_admin#metatags` hook. This is useful for adding a favicon or SEO meta tags, for example. +You can register your own Twig template for meta tags via YAML or PHP. + +{% tabs %} +{% tab title="YAML" %} +{% code title="config/packages/sylius_bootstrap_admin_ui.yaml" lineNumbers="true" %} +```yaml +# ... +sylius_twig_hooks: + hooks: + # ... + 'sylius_admin.base#metatags': + favicon: + template: 'favicon.html.twig' + seo_metatags: + template: 'seo_metatags.html.twig' +``` +{% endcode %} +{% endtab %} + +{% tab title="PHP" %} +{% code title="config/packages/sylius_bootstrap_admin_ui.php" lineNumbers="true" %} +```php +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + +return static function (ContainerConfigurator $containerConfigurator): void { + // ... + + // Define your own Twig template for the favicon. + $containerConfigurator->extension('sylius_twig_hooks', [ + 'hooks' => [ + 'sylius_admin.base#metatags' => [ + 'favicon' => [ + 'template' => 'favicon.html.twig', + ], + 'seo_metatags' => [ + 'template' => 'seo_metatags.html.twig', + ], + ], + ], + + ]); +}; +``` +{% endcode %} +{% endtab %} +{% endtabs %} diff --git a/src/AdminUi/templates/base.html.twig b/src/AdminUi/templates/base.html.twig index 33093e9b3..eca6001d3 100644 --- a/src/AdminUi/templates/base.html.twig +++ b/src/AdminUi/templates/base.html.twig @@ -9,7 +9,10 @@ {% block title %}{% apply striptags %}{% hook generic_hook ~ '#base_title' %}{% endapply %}{% endblock %} - {% block metatags %}{% endblock %} + {% block metatags %} + {% hook generic_hook ~ '#metatags' %} + {% endblock %} + {% block stylesheets %} {% hook generic_hook ~ '#stylesheets' %} {% endblock %} From 433dbb54dfc70a8f1367e210a4f3f7a43f72e005 Mon Sep 17 00:00:00 2001 From: Florian Merle Date: Sun, 17 Aug 2025 20:58:27 +0200 Subject: [PATCH 03/53] sort main and bulk actions by position --- .../crud/common/content/header/title_block/actions.html.twig | 2 +- .../shared/crud/index/content/grid/data_table.html.twig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BootstrapAdminUi/templates/shared/crud/common/content/header/title_block/actions.html.twig b/src/BootstrapAdminUi/templates/shared/crud/common/content/header/title_block/actions.html.twig index e911cf720..6e38f56a1 100644 --- a/src/BootstrapAdminUi/templates/shared/crud/common/content/header/title_block/actions.html.twig +++ b/src/BootstrapAdminUi/templates/shared/crud/common/content/header/title_block/actions.html.twig @@ -3,7 +3,7 @@
{% if resources.definition.actionGroups.main is defined %} - {% for action in resources.definition.getEnabledActions('main') %} + {% for action in resources.definition.getEnabledActions('main')|sylius_sort_by('position') %} {{ sylius_grid_render_action(resources, action, null) }} {% endfor %} {% endif %} diff --git a/src/BootstrapAdminUi/templates/shared/crud/index/content/grid/data_table.html.twig b/src/BootstrapAdminUi/templates/shared/crud/index/content/grid/data_table.html.twig index 670a2179d..0ef4a3e63 100644 --- a/src/BootstrapAdminUi/templates/shared/crud/index/content/grid/data_table.html.twig +++ b/src/BootstrapAdminUi/templates/shared/crud/index/content/grid/data_table.html.twig @@ -11,7 +11,7 @@
{% if data|length > 0 and definition.actionGroups.bulk is defined and definition.getEnabledActions('bulk')|length > 0 %}
- {% for action in definition.getEnabledActions('bulk') %} + {% for action in definition.getEnabledActions('bulk')|sylius_sort_by('position') %} {{ sylius_grid_render_bulk_action(resources, action, null) }} {% endfor %}
From cd3e184a512b751239c055adeda0de7bf815cff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Thu, 25 Sep 2025 10:46:33 +0200 Subject: [PATCH 04/53] [Docs] Remove unnecessary parts of the getting started --- docs/getting-started.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 4309562d4..e03335c03 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -23,7 +23,7 @@ Go to your project directory and run the following command: ```bash composer require -W \ - doctrine/orm "^2.16" \ + doctrine/orm \ doctrine/doctrine-bundle \ pagerfanta/doctrine-orm-adapter \ symfony/asset-mapper \ @@ -39,12 +39,6 @@ composer require -W \ Type "a" or "p" to configure the packages via Symfony Flex. -### Install missing tom-select assets - -```bash -symfony console importmap:require tom-select/dist/css/tom-select.default.css -``` - ### Run your web server ```bash From cc727198621c4de175d76c656eb8ef166332b7f6 Mon Sep 17 00:00:00 2001 From: Kevin Kaniaburka Date: Tue, 5 Aug 2025 13:12:50 +0200 Subject: [PATCH 05/53] [Docs] Add "Configure the routes' name" section to resource/configure_your_operations.md --- docs/resource/configure_your_operations.md | 46 +++++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/docs/resource/configure_your_operations.md b/docs/resource/configure_your_operations.md index 0ba40cc3f..eaf84038c 100644 --- a/docs/resource/configure_your_operations.md +++ b/docs/resource/configure_your_operations.md @@ -447,7 +447,7 @@ use Sylius\Resource\Metadata\Update; #[AsResource( routePrefix: 'admin', operations: [ - new Index(), + new Index(routePrefix: ''), new Create(), new Update(), new Delete(), @@ -463,13 +463,55 @@ class Book implements ResourceInterface | Name | Method | Path | |----------------------|-----------------|--------------------------| -| app_book_index | GET | /admin/books/ | +| app_book_index | GET | /books/ | | app_book_create | GET, POST | /admin/books/new | | app_book_update | GET, PUT, PATCH | /admin/books/{id}/edit | | app_book_delete | DELETE | /admin/books/{id} | | app_book_bulk_delete | DELETE | /admin/books/bulk_delete | | app_book_show | GET | /admin/books/{id} | +### Configure the routes' name + +It customizes the route name for individual operations. + +{% code title="src/Entity/Book.php" lineNumbers="true" %} +```php +namespace App\Entity; + +use Sylius\Resource\Model\ResourceInterface; +use Sylius\Resource\Metadata\AsResource; +use Sylius\Resource\Metadata\BulkDelete; +use Sylius\Resource\Metadata\Create; +use Sylius\Resource\Metadata\Delete; +use Sylius\Resource\Metadata\Index; +use Sylius\Resource\Metadata\Show; +use Sylius\Resource\Metadata\Update; + +#[AsResource( + operations: [ + new Index(routeName: 'library_book_list'), + new Create(routeName: 'library_book_add'), + new Update(), + new Delete(), + new BulkDelete(), + new Show(), + ], +) +class Book implements ResourceInterface +{ +} +``` +{% endcode %} + +| Name | Method | Path | +|----------------------|-----------------|--------------------------| +| library_book_list | GET | /books/ | +| library_book_add | GET, POST | /books/new | +| app_book_update | GET, PUT, PATCH | /books/{id}/edit | +| app_book_delete | DELETE | /books/{id} | +| app_book_bulk_delete | DELETE | /books/bulk_delete | +| app_book_show | GET | /books/{id} | + ### Configure the section It changes the route name for each operation. From 51db8599dcc2f4a10f0b1cd7b24dbd4f2c916e95 Mon Sep 17 00:00:00 2001 From: Kevin Kaniaburka Date: Mon, 18 Aug 2025 11:38:35 +0200 Subject: [PATCH 06/53] [Docs] Add clear explanation in code example for custom routePrefix in operations Co-authored-by: Estelle Gaits <74190794+stlgaits@users.noreply.github.com> --- docs/resource/configure_your_operations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/resource/configure_your_operations.md b/docs/resource/configure_your_operations.md index eaf84038c..24ece1b0c 100644 --- a/docs/resource/configure_your_operations.md +++ b/docs/resource/configure_your_operations.md @@ -447,7 +447,7 @@ use Sylius\Resource\Metadata\Update; #[AsResource( routePrefix: 'admin', operations: [ - new Index(routePrefix: ''), + new Index(routePrefix: ''), // you can also customize the route prefix at the operation level too for extra flexibility new Create(), new Update(), new Delete(), From 857ae4b1c1a900f1eb9f34a664951045b7dc3ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=BCrner?= Date: Sat, 28 Jun 2025 13:07:55 +0200 Subject: [PATCH 07/53] Add missing translating --- src/UiTranslations/translations/messages.de.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/UiTranslations/translations/messages.de.yaml b/src/UiTranslations/translations/messages.de.yaml index 77bf5acfc..d7c55a947 100644 --- a/src/UiTranslations/translations/messages.de.yaml +++ b/src/UiTranslations/translations/messages.de.yaml @@ -6,6 +6,7 @@ sylius: remember_me: 'Angemeldet bleiben' ui: actions: 'Aktionen' + add_new_entry: '' administration_panel_login: 'Administrations-Bereich Anmeldung' all: 'Alle' are_your_sure_you_want_to_perform_this_action: 'Möchten Sie diese Aktion wirklich durchführen?' @@ -17,24 +18,29 @@ sylius: details: 'Details' edit: 'Bearbeiten' empty: 'Leer' + equal: 'Gleich' ends_with: 'Endet mit' error: 'Fehler' - equal: 'Gleich' filter: 'Filter' filters: 'Filter' from: 'Von' hello: 'Hallo' in: 'In' + login_to_your_account: 'Einloggen' logout: 'Abmelden' new: 'Neu' no_label: 'Nein' no_results: 'Keine Ergebnisse für diese Anfrage gefunden' + no_results_adjust_your_search: 'Passen Sie Ihre Suche an und versuchen Sie es erneut.' not_contains: 'Enthält nicht' not_empty: 'Nicht leer' not_equal: 'Nicht gleich' not_in: 'Nicht in' + pagination: + number_of_results: 'Zeigt %from% - %to% von %total% Einträgen' reset: 'Zurücksetzen' search: 'Suche' + search_menu: 'Menü durchsuchen' search_products: 'Artikel suchen' show: 'Anzeigen' starts_with: 'Beginnt mit' @@ -45,3 +51,4 @@ sylius: value: 'Wert' warning: 'Warnung' yes_label: 'Ja' + From 7daa65fa301588d7cd2855c4aa76f035e35b8a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=BCrner?= Date: Sat, 28 Jun 2025 13:10:44 +0200 Subject: [PATCH 08/53] Add translation --- src/UiTranslations/translations/messages.de.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UiTranslations/translations/messages.de.yaml b/src/UiTranslations/translations/messages.de.yaml index d7c55a947..27526b503 100644 --- a/src/UiTranslations/translations/messages.de.yaml +++ b/src/UiTranslations/translations/messages.de.yaml @@ -6,7 +6,7 @@ sylius: remember_me: 'Angemeldet bleiben' ui: actions: 'Aktionen' - add_new_entry: '' + add_new_entry: 'Neuen Eintrag hinzufügen' administration_panel_login: 'Administrations-Bereich Anmeldung' all: 'Alle' are_your_sure_you_want_to_perform_this_action: 'Möchten Sie diese Aktion wirklich durchführen?' From efe836c84913876180c7fd3910cadb811d5743a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=BCrner?= Date: Sat, 28 Jun 2025 13:28:50 +0200 Subject: [PATCH 09/53] Adjust typo --- src/UiTranslations/translations/messages.de.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UiTranslations/translations/messages.de.yaml b/src/UiTranslations/translations/messages.de.yaml index 27526b503..d07405c5b 100644 --- a/src/UiTranslations/translations/messages.de.yaml +++ b/src/UiTranslations/translations/messages.de.yaml @@ -31,7 +31,7 @@ sylius: new: 'Neu' no_label: 'Nein' no_results: 'Keine Ergebnisse für diese Anfrage gefunden' - no_results_adjust_your_search: 'Passen Sie Ihre Suche an und versuchen Sie es erneut.' + no_results_adjust_your_search: 'Passen Sie Ihre Anfrage an und versuchen Sie es erneut.' not_contains: 'Enthält nicht' not_empty: 'Nicht leer' not_equal: 'Nicht gleich' From 4f91da82c8772290ca75ee76c830c3c9cdd3767a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Tue, 10 Jun 2025 09:33:10 +0200 Subject: [PATCH 10/53] WCAG improvements --- src/BootstrapAdminUi/assets/styles/_body.scss | 3 +++ .../assets/styles/_buttons.scss | 4 ++++ src/BootstrapAdminUi/assets/styles/_form.scss | 4 ++++ .../assets/styles/_navbar.scss | 21 ++++++++++++++++--- src/BootstrapAdminUi/assets/styles/main.scss | 1 + src/BootstrapAdminUi/public/app.css | 6 +++--- src/BootstrapAdminUi/public/app.rtl.css | 6 +++--- .../header/title_block/actions.html.twig | 1 + templates/component/new_talks.html.twig | 16 +++++++------- 9 files changed, 46 insertions(+), 16 deletions(-) create mode 100644 src/BootstrapAdminUi/assets/styles/_buttons.scss diff --git a/src/BootstrapAdminUi/assets/styles/_body.scss b/src/BootstrapAdminUi/assets/styles/_body.scss index 5bd6d4835..ecce06584 100644 --- a/src/BootstrapAdminUi/assets/styles/_body.scss +++ b/src/BootstrapAdminUi/assets/styles/_body.scss @@ -10,6 +10,9 @@ * { --tblr-breadcrumb-item-active-color: var(--tblr-gray-500); --tblr-breadcrumb-divider-color: var(--tblr-gray-300); + --tblr-code-color: #36393B; + --tblr-blue-rgb: 17, 81, 141; + --tblr-green-rgb: 0, 97, 16; } [data-bs-theme=dark] { diff --git a/src/BootstrapAdminUi/assets/styles/_buttons.scss b/src/BootstrapAdminUi/assets/styles/_buttons.scss new file mode 100644 index 000000000..ef66bfdf9 --- /dev/null +++ b/src/BootstrapAdminUi/assets/styles/_buttons.scss @@ -0,0 +1,4 @@ +.btn:not(.btn-sm) { + min-height: 44px; + min-width: 44px; +} diff --git a/src/BootstrapAdminUi/assets/styles/_form.scss b/src/BootstrapAdminUi/assets/styles/_form.scss index 2496a070c..fed144e8d 100644 --- a/src/BootstrapAdminUi/assets/styles/_form.scss +++ b/src/BootstrapAdminUi/assets/styles/_form.scss @@ -55,6 +55,10 @@ textarea.form-control { } } +.form-control, .form-select { + min-height: 44px; +} + .form-control:focus { box-shadow: none; } diff --git a/src/BootstrapAdminUi/assets/styles/_navbar.scss b/src/BootstrapAdminUi/assets/styles/_navbar.scss index 588164c44..a52c6b283 100644 --- a/src/BootstrapAdminUi/assets/styles/_navbar.scss +++ b/src/BootstrapAdminUi/assets/styles/_navbar.scss @@ -11,18 +11,33 @@ &.navbar-nav { gap: 0.5rem; } +} - &.navbar-brand-image { - height: 4rem; - } +.navbar input { + min-height: 44px; +} + +.navbar-brand-image { + height: 3rem; } .navbar-collapse a.nav-link, .navbar-collapse a.dropdown-item { transition: all .1s; + min-height: 44px; &:hover { color: var(--tblr-primary) !important; background: transparent; } } +.nav-button { + min-height: 44px; + min-width: 44px; +} +.navbar-collapse .nav-link:focus-visible { + outline: solid 2px rgb(153, 200, 255); +} +.navbar-plus-badge{ + width: 50px; +} diff --git a/src/BootstrapAdminUi/assets/styles/main.scss b/src/BootstrapAdminUi/assets/styles/main.scss index 1efcd9974..0f3ca3b16 100644 --- a/src/BootstrapAdminUi/assets/styles/main.scss +++ b/src/BootstrapAdminUi/assets/styles/main.scss @@ -12,6 +12,7 @@ @import "@tabler/core/src/scss/tabler.scss"; @import "body"; +@import "buttons"; @import "accordion"; @import "alert"; @import "datatable"; diff --git a/src/BootstrapAdminUi/public/app.css b/src/BootstrapAdminUi/public/app.css index 8f3c2a927..773ef909b 100644 --- a/src/BootstrapAdminUi/public/app.css +++ b/src/BootstrapAdminUi/public/app.css @@ -20,7 +20,7 @@ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - */*{--tblr-breadcrumb-item-active-color:var(--tblr-gray-500);--tblr-breadcrumb-divider-color:var(--tblr-gray-300)}[data-bs-theme=dark],body[data-bs-theme=dark] [data-bs-theme=light]{--tblr-bg-surface:#1e2433}@font-face{font-family:Inter;font-weight:1 999;src:url(fonts/Inter-VariableFont_slnt,wght.853e0197.ttf) format("truetype-variations")}body{--bs-body-bg:#f6f8fb;--bs-tertiary-bg:#f6f8fb;--bs-body-color:#212529;font-feature-settings:"cv03","cv04","cv11"}a{text-underline-offset:.25em}a.link-reset{text-decoration:none}.btn-collapse.collapsed .icon-chevron-right{display:inline-flex}.btn-collapse.collapsed .icon-chevron-down,.btn-collapse:not(.collapsed) .icon-chevron-right{display:none}.btn-collapse:not(.collapsed) .icon-chevron-down{display:inline-flex}.breadcrumb-item a{text-decoration:none}.breadcrumb-item a:hover{color:#22b99a}.switch-collapse,body[data-bs-theme=dark] html[data-bs-theme=light] [data-theme-switch=dark],html[data-bs-theme=dark] [data-theme-switch=dark],html[data-bs-theme=light] [data-theme-switch=light]{display:none}label:has(input:checked)~.switch-collapse{display:block}/*! + */*{--tblr-breadcrumb-item-active-color:var(--tblr-gray-500);--tblr-breadcrumb-divider-color:var(--tblr-gray-300);--tblr-code-color:#36393b;--tblr-blue-rgb:17,81,141;--tblr-green-rgb:0,97,16}[data-bs-theme=dark],body[data-bs-theme=dark] [data-bs-theme=light]{--tblr-bg-surface:#1e2433}@font-face{font-family:Inter;font-weight:1 999;src:url(fonts/Inter-VariableFont_slnt,wght.853e0197.ttf) format("truetype-variations")}body{--bs-body-bg:#f6f8fb;--bs-tertiary-bg:#f6f8fb;--bs-body-color:#212529;font-feature-settings:"cv03","cv04","cv11"}a{text-underline-offset:.25em}a.link-reset{text-decoration:none}.btn-collapse.collapsed .icon-chevron-right{display:inline-flex}.btn-collapse.collapsed .icon-chevron-down,.btn-collapse:not(.collapsed) .icon-chevron-right{display:none}.btn-collapse:not(.collapsed) .icon-chevron-down{display:inline-flex}.breadcrumb-item a{text-decoration:none}.breadcrumb-item a:hover{color:#22b99a}.switch-collapse,body[data-bs-theme=dark] html[data-bs-theme=light] [data-theme-switch=dark],html[data-bs-theme=dark] [data-theme-switch=dark],html[data-bs-theme=light] [data-theme-switch=light]{display:none}label:has(input:checked)~.switch-collapse{display:block}.btn:not(.btn-sm){min-height:44px;min-width:44px}/*! * This file is part of the Sylius package. * * (c) Sylius Sp. z o.o. @@ -62,7 +62,7 @@ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - */textarea.form-control{height:12rem;min-height:8rem}.accordion-item:has(.accordion-error),.list-group-item.active:has(.tab-error),.list-group-item:has(.tab-error){border-left:2px solid #ff0017}.form-select:disabled{color:var(--tblr-gray-600)}.input-group{box-shadow:none}.input-group .form-check{flex-grow:1}.input-group .input-group-text{color:var(--tblr-gray-600)}.form-control:focus{box-shadow:none}/*! + */textarea.form-control{height:12rem;min-height:8rem}.accordion-item:has(.accordion-error),.list-group-item.active:has(.tab-error),.list-group-item:has(.tab-error){border-left:2px solid #ff0017}.form-select:disabled{color:var(--tblr-gray-600)}.input-group{box-shadow:none}.input-group .form-check{flex-grow:1}.input-group .input-group-text{color:var(--tblr-gray-600)}.form-control,.form-select{min-height:44px}.form-control:focus{box-shadow:none}/*! * This file is part of the Sylius package. * * (c) Sylius Sp. z o.o. @@ -76,7 +76,7 @@ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - */.sylius.navbar-nav{gap:.5rem}.sylius.navbar-brand-image{height:4rem}.navbar-collapse a.dropdown-item,.navbar-collapse a.nav-link{transition:all .1s}.navbar-collapse a.dropdown-item:hover,.navbar-collapse a.nav-link:hover{background:transparent;color:var(--tblr-primary)!important}svg.icon *{stroke-width:1.5}/*! + */.sylius.navbar-nav{gap:.5rem}.navbar input{min-height:44px}.navbar-brand-image{height:3rem}.navbar-collapse a.dropdown-item,.navbar-collapse a.nav-link{min-height:44px;transition:all .1s}.navbar-collapse a.dropdown-item:hover,.navbar-collapse a.nav-link:hover{background:transparent;color:var(--tblr-primary)!important}.nav-button{min-height:44px;min-width:44px}.navbar-collapse .nav-link:focus-visible{outline:2px solid #99c8ff}.navbar-plus-badge{width:50px}svg.icon *{stroke-width:1.5}/*! * This file is part of the Sylius package. * * (c) Sylius Sp. z o.o. diff --git a/src/BootstrapAdminUi/public/app.rtl.css b/src/BootstrapAdminUi/public/app.rtl.css index 4479da89a..cbd0b8f57 100644 --- a/src/BootstrapAdminUi/public/app.rtl.css +++ b/src/BootstrapAdminUi/public/app.rtl.css @@ -20,7 +20,7 @@ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - */*{--tblr-breadcrumb-item-active-color:var(--tblr-gray-500);--tblr-breadcrumb-divider-color:var(--tblr-gray-300)}[data-bs-theme=dark],body[data-bs-theme=dark] [data-bs-theme=light]{--tblr-bg-surface:#1e2433}@font-face{font-family:Inter;font-weight:1 999;src:url(fonts/Inter-VariableFont_slnt,wght.853e0197.ttf) format("truetype-variations")}body{--bs-body-bg:#f6f8fb;--bs-tertiary-bg:#f6f8fb;--bs-body-color:#212529;font-feature-settings:"cv03","cv04","cv11"}a{text-underline-offset:.25em}a.link-reset{text-decoration:none}.btn-collapse.collapsed .icon-chevron-right{display:inline-flex}.btn-collapse.collapsed .icon-chevron-down,.btn-collapse:not(.collapsed) .icon-chevron-right{display:none}.btn-collapse:not(.collapsed) .icon-chevron-down{display:inline-flex}.breadcrumb-item a{text-decoration:none}.breadcrumb-item a:hover{color:#22b99a}.switch-collapse,body[data-bs-theme=dark] html[data-bs-theme=light] [data-theme-switch=dark],html[data-bs-theme=dark] [data-theme-switch=dark],html[data-bs-theme=light] [data-theme-switch=light]{display:none}label:has(input:checked)~.switch-collapse{display:block}/*! + */*{--tblr-breadcrumb-item-active-color:var(--tblr-gray-500);--tblr-breadcrumb-divider-color:var(--tblr-gray-300);--tblr-code-color:#36393b;--tblr-blue-rgb:17,81,141;--tblr-green-rgb:0,97,16}[data-bs-theme=dark],body[data-bs-theme=dark] [data-bs-theme=light]{--tblr-bg-surface:#1e2433}@font-face{font-family:Inter;font-weight:1 999;src:url(fonts/Inter-VariableFont_slnt,wght.853e0197.ttf) format("truetype-variations")}body{--bs-body-bg:#f6f8fb;--bs-tertiary-bg:#f6f8fb;--bs-body-color:#212529;font-feature-settings:"cv03","cv04","cv11"}a{text-underline-offset:.25em}a.link-reset{text-decoration:none}.btn-collapse.collapsed .icon-chevron-right{display:inline-flex}.btn-collapse.collapsed .icon-chevron-down,.btn-collapse:not(.collapsed) .icon-chevron-right{display:none}.btn-collapse:not(.collapsed) .icon-chevron-down{display:inline-flex}.breadcrumb-item a{text-decoration:none}.breadcrumb-item a:hover{color:#22b99a}.switch-collapse,body[data-bs-theme=dark] html[data-bs-theme=light] [data-theme-switch=dark],html[data-bs-theme=dark] [data-theme-switch=dark],html[data-bs-theme=light] [data-theme-switch=light]{display:none}label:has(input:checked)~.switch-collapse{display:block}.btn:not(.btn-sm){min-height:44px;min-width:44px}/*! * This file is part of the Sylius package. * * (c) Sylius Sp. z o.o. @@ -62,7 +62,7 @@ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - */textarea.form-control{height:12rem;min-height:8rem}.accordion-item:has(.accordion-error),.list-group-item.active:has(.tab-error),.list-group-item:has(.tab-error){border-right:2px solid #ff0017}.form-select:disabled{color:var(--tblr-gray-600)}.input-group{box-shadow:none}.input-group .form-check{flex-grow:1}.input-group .input-group-text{color:var(--tblr-gray-600)}.form-control:focus{box-shadow:none}/*! + */textarea.form-control{height:12rem;min-height:8rem}.accordion-item:has(.accordion-error),.list-group-item.active:has(.tab-error),.list-group-item:has(.tab-error){border-right:2px solid #ff0017}.form-select:disabled{color:var(--tblr-gray-600)}.input-group{box-shadow:none}.input-group .form-check{flex-grow:1}.input-group .input-group-text{color:var(--tblr-gray-600)}.form-control,.form-select{min-height:44px}.form-control:focus{box-shadow:none}/*! * This file is part of the Sylius package. * * (c) Sylius Sp. z o.o. @@ -76,7 +76,7 @@ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - */.sylius.navbar-nav{gap:.5rem}.sylius.navbar-brand-image{height:4rem}.navbar-collapse a.dropdown-item,.navbar-collapse a.nav-link{transition:all .1s}.navbar-collapse a.dropdown-item:hover,.navbar-collapse a.nav-link:hover{background:transparent;color:var(--tblr-primary)!important}svg.icon *{stroke-width:1.5}/*! + */.sylius.navbar-nav{gap:.5rem}.navbar input{min-height:44px}.navbar-brand-image{height:3rem}.navbar-collapse a.dropdown-item,.navbar-collapse a.nav-link{min-height:44px;transition:all .1s}.navbar-collapse a.dropdown-item:hover,.navbar-collapse a.nav-link:hover{background:transparent;color:var(--tblr-primary)!important}.nav-button{min-height:44px;min-width:44px}.navbar-collapse .nav-link:focus-visible{outline:2px solid #99c8ff}.navbar-plus-badge{width:50px}svg.icon *{stroke-width:1.5}/*! * This file is part of the Sylius package. * * (c) Sylius Sp. z o.o. diff --git a/src/BootstrapAdminUi/templates/shared/crud/common/content/header/title_block/actions.html.twig b/src/BootstrapAdminUi/templates/shared/crud/common/content/header/title_block/actions.html.twig index 6e38f56a1..554329491 100644 --- a/src/BootstrapAdminUi/templates/shared/crud/common/content/header/title_block/actions.html.twig +++ b/src/BootstrapAdminUi/templates/shared/crud/common/content/header/title_block/actions.html.twig @@ -2,6 +2,7 @@
+ {% hook 'actions' %} {% if resources.definition.actionGroups.main is defined %} {% for action in resources.definition.getEnabledActions('main')|sylius_sort_by('position') %} {{ sylius_grid_render_action(resources, action, null) }} diff --git a/templates/component/new_talks.html.twig b/templates/component/new_talks.html.twig index 7143803fd..e3a532e0a 100644 --- a/templates/component/new_talks.html.twig +++ b/templates/component/new_talks.html.twig @@ -20,14 +20,16 @@
{% set firstSpeaker = talk.speakers.first %} - {% if firstSpeaker.avatar %} - {% set avatar_path = vich_uploader_asset(firstSpeaker.avatar) %} + {% if firstSpeaker %} + {% if firstSpeaker.avatar %} + {% set avatar_path = vich_uploader_asset(firstSpeaker.avatar) %} - {{ avatar.default(avatar_path, 'img-thumbnail') }} - {% else %} - - {{ firstSpeaker.firstName|first }}{{ firstSpeaker.lastName|first }} - + {{ avatar.default(avatar_path, 'img-thumbnail') }} + {% else %} + + {{ firstSpeaker.firstName|first }}{{ firstSpeaker.lastName|first }} + + {% endif %} {% endif %}
From e7047bcc4960741b8824cf87eec41f4673f42958 Mon Sep 17 00:00:00 2001 From: Florian Merle Date: Sun, 27 Apr 2025 20:40:57 +0200 Subject: [PATCH 11/53] Add link script --- composer.json | 1 + link | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100755 link diff --git a/composer.json b/composer.json index 075290b27..deac5a7ac 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,7 @@ "symfony/config": "^6.4 || ^7.0", "symfony/dependency-injection": "^6.4 || ^7.0", "symfony/expression-language": "^6.4 || ^7.0", + "symfony/filesystem": "^6.4 || ^7.0", "symfony/http-client": "^6.4 || ^7.0", "symfony/http-kernel": "^6.4 || ^7.0", "symfony/security-bundle": "^6.4 || ^7.0", diff --git a/link b/link new file mode 100755 index 000000000..b2181a231 --- /dev/null +++ b/link @@ -0,0 +1,97 @@ +#!/usr/bin/env php + + * See https://github.com/symfony/symfony/blob/7.3/link + * + * (c) Fabien Potencier + */ + +$copy = false !== $k = array_search('--copy', $argv, true); +$copy && array_splice($argv, $k, 1); +$rollback = false !== $k = array_search('--rollback', $argv, true); +$rollback && array_splice($argv, $k, 1); +$pathToProject = $argv[1] ?? getcwd(); + +if (!is_dir("$pathToProject/vendor/sylius")) { + echo 'Links dependencies of a project to a local clone of the main Sylius/Stack GitHub repository.'.PHP_EOL.PHP_EOL; + echo "Usage: $argv[0] /path/to/the/project".PHP_EOL; + echo ' Use `--copy` to copy dependencies instead of symlink'.PHP_EOL.PHP_EOL; + echo ' Use `--rollback` to rollback'.PHP_EOL.PHP_EOL; + echo "The directory \"$pathToProject\" does not exist or the dependencies are not installed, did you forget to run \"composer install\" in your project?".PHP_EOL; + exit(1); +} + +$stackPackages = []; + +$filesystem = new Filesystem(); +$directories = glob(__DIR__.'/src/*', GLOB_ONLYDIR | GLOB_NOSORT); + +foreach ($directories as $dir) { + if ($filesystem->exists($composer = "$dir/composer.json")) { + $stackPackages[json_decode($filesystem->readFile($composer), flags: JSON_THROW_ON_ERROR)->name] = $dir; + } +} + +foreach (glob("$pathToProject/vendor/sylius/*", GLOB_ONLYDIR | GLOB_NOSORT) as $dir) { + $package = 'sylius/'.basename($dir); + + if (!isset($stackPackages[$package])) { + continue; + } + + if ($rollback) { + $filesystem->remove($dir); + echo "\"$package\" has been rollback from \"$stackPackages[$package]\".".PHP_EOL; + continue; + } + + if (!$copy && is_link($dir)) { + echo "\"$package\" is already a symlink, skipping.".PHP_EOL; + continue; + } + + $sfDir = ('\\' === DIRECTORY_SEPARATOR || $copy) ? $stackPackages[$package] : $filesystem->makePathRelative($stackPackages[$package], dirname(realpath($dir))); + + $filesystem->remove($dir); + + if ($copy) { + $filesystem->mirror($sfDir, $dir); + echo "\"$package\" has been copied from \"$stackPackages[$package]\".".PHP_EOL; + } else { + $filesystem->symlink($sfDir, $dir); + echo "\"$package\" has been linked to \"$stackPackages[$package]\".".PHP_EOL; + } +} + +foreach (glob("$pathToProject/var/cache/*", GLOB_NOSORT) as $cacheDir) { + $filesystem->remove($cacheDir); +} + +if ($rollback) { + echo PHP_EOL."Rollback done, do not forget to run \"composer install\" in your project \"$pathToProject\".".PHP_EOL; +} From b1de79cc224cd80e8639b15e754fcac42713cdec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Fri, 26 Sep 2025 09:26:15 +0200 Subject: [PATCH 12/53] [Docs][TwigExtra] Fix sylius_sort_by example --- docs/twig-extra/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/twig-extra/getting-started.md b/docs/twig-extra/getting-started.md index 5adef3736..ed96f80da 100644 --- a/docs/twig-extra/getting-started.md +++ b/docs/twig-extra/getting-started.md @@ -38,7 +38,7 @@ $books = [ ```twig {% raw %}
    -{% for book in books|sort_by('name') %} +{% for book in books|sylius_sort_by('name') %}
  • {{ book.name }}
  • {% endif %}
From 488fb5d51c56c11c5c57a299e5e0439a4f56a034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Tue, 23 Sep 2025 10:45:11 +0200 Subject: [PATCH 13/53] Fix data table template when no grid is attached to the index operation --- app/Entity/Book.php | 4 ++++ .../index/content/grid/data_table.html.twig | 4 ++-- .../crud/index/content/grid/filters.html.twig | 2 +- .../index/content/grid/no_results.html.twig | 2 +- tests/Functional/BookTest.php | 22 +++++++++++++++++++ 5 files changed, 30 insertions(+), 4 deletions(-) diff --git a/app/Entity/Book.php b/app/Entity/Book.php index 0c7b2ccf2..324824eda 100644 --- a/app/Entity/Book.php +++ b/app/Entity/Book.php @@ -36,6 +36,10 @@ new Create(), new Update(), new Index(grid: BookGrid::class), + new Index( + template: '@SyliusAdminUi/crud/index.html.twig', + shortName: 'withoutGrid', + ), new Delete(), new BulkDelete(), new Show(), diff --git a/src/BootstrapAdminUi/templates/shared/crud/index/content/grid/data_table.html.twig b/src/BootstrapAdminUi/templates/shared/crud/index/content/grid/data_table.html.twig index 0ef4a3e63..ea2a7efa0 100644 --- a/src/BootstrapAdminUi/templates/shared/crud/index/content/grid/data_table.html.twig +++ b/src/BootstrapAdminUi/templates/shared/crud/index/content/grid/data_table.html.twig @@ -2,8 +2,8 @@ {% import '@SyliusBootstrapAdminUi/shared/helper/pagination.html.twig' as pagination %} {% set resources = hookable_metadata.context.resources %} -{% set data = resources.data %} -{% set definition = resources.definition %} +{% set data = resources.data|default([]) %} +{% set definition = resources.definition|default(null) %} {% if data|length > 0 %}
diff --git a/src/BootstrapAdminUi/templates/shared/crud/index/content/grid/filters.html.twig b/src/BootstrapAdminUi/templates/shared/crud/index/content/grid/filters.html.twig index 0e52b5cd1..079bdb901 100644 --- a/src/BootstrapAdminUi/templates/shared/crud/index/content/grid/filters.html.twig +++ b/src/BootstrapAdminUi/templates/shared/crud/index/content/grid/filters.html.twig @@ -6,7 +6,7 @@ {% set path = path(app.request.attributes.get('_route'), app.request.attributes.all('_route_params')) %} {% set are_criteria_set = app.request.query.has('criteria') %} -{% if resources.definition.enabledFilters is not empty %} +{% if resources.definition.enabledFilters|default([]) is not empty %}
{% set content %}
diff --git a/src/BootstrapAdminUi/templates/shared/crud/index/content/grid/no_results.html.twig b/src/BootstrapAdminUi/templates/shared/crud/index/content/grid/no_results.html.twig index 3214bcafd..d8b77b8bb 100644 --- a/src/BootstrapAdminUi/templates/shared/crud/index/content/grid/no_results.html.twig +++ b/src/BootstrapAdminUi/templates/shared/crud/index/content/grid/no_results.html.twig @@ -1,6 +1,6 @@ {% set resources = hookable_metadata.context.resources %} -{% if resources.data|length == 0 %} +{% if resources.data is defined and resources.data|length == 0 %}
{% hook 'no_results' %} diff --git a/tests/Functional/BookTest.php b/tests/Functional/BookTest.php index 160a0fdbc..576365dc8 100644 --- a/tests/Functional/BookTest.php +++ b/tests/Functional/BookTest.php @@ -98,6 +98,28 @@ public function testBrowsingBooks(): void self::assertSelectorExists('tr.item:last-child [data-bs-title=Delete]'); } + public function testBrowsingBooksWithoutGrid(): void + { + BookFactory::new() + ->withTitle('The Shining') + ->withAuthorName('Stephen King') + ->create() + ; + + BookFactory::new() + ->withTitle('Carrie') + ->withAuthorName('Stephen King') + ->create() + ; + + $this->client->request('GET', '/admin/books/withoutGrid'); + + self::assertResponseIsSuccessful(); + + // Validate Header + self::assertSelectorTextContains('[data-test-page-title]', 'Books'); + } + public function testSortingBooks(): void { BookFactory::new() From ae08768460005ae006694f7d2c15279655001c2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Fri, 26 Sep 2025 09:35:00 +0200 Subject: [PATCH 14/53] [Docs][TwigExtra] Fix another sort_by to sylius_sort_by --- docs/twig-extra/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/twig-extra/getting-started.md b/docs/twig-extra/getting-started.md index ed96f80da..8fc2e8104 100644 --- a/docs/twig-extra/getting-started.md +++ b/docs/twig-extra/getting-started.md @@ -71,7 +71,7 @@ You just need to encapsulate the key with `[]`. ```twig {% raw %}
    -{% for book in books|sort_by('[name]') %} +{% for book in books|sylius_sort_by('[name]') %}
  • {{ book.name }}
  • {% endif %}
From 8be0d50070baaa0c2f238c24f99d0a1fc02d2267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Fri, 26 Sep 2025 09:57:00 +0200 Subject: [PATCH 15/53] [Docs] Improve documentation about resource --- docs/cookbook/admin_panel/basic_operations.md | 6 ++++ docs/resource/configure_your_resource.md | 33 ++++++++++++++----- docs/resource/create_new_resource.md | 20 ++++------- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/docs/cookbook/admin_panel/basic_operations.md b/docs/cookbook/admin_panel/basic_operations.md index 43461499b..0bde45fce 100644 --- a/docs/cookbook/admin_panel/basic_operations.md +++ b/docs/cookbook/admin_panel/basic_operations.md @@ -1,5 +1,11 @@ # Basic operations +In this cookbook, we assume that you have already created a `Book` resource. + +{% hint style="info" %} +Learn more on how to [create a Sylius resource](../../resource/create_new_resource.md). +{% endhint %} + ## List of resources
diff --git a/docs/resource/configure_your_resource.md b/docs/resource/configure_your_resource.md index 7c63e8ce3..c9a2c10df 100644 --- a/docs/resource/configure_your_resource.md +++ b/docs/resource/configure_your_resource.md @@ -59,15 +59,30 @@ $ bin/console sylius:debug:resource 'App\Entity\Book' ``` ``` -+--------------------+------------------------------------------------------------+ -| name | book | -| application | app | -| driver | doctrine/orm | -| classes.model | App\Entity\Book | -| classes.controller | Sylius\Bundle\ResourceBundle\Controller\ResourceController | -| classes.factory | Sylius\Component\Resource\Factory\Factory | -| classes.form | Sylius\Bundle\ResourceBundle\Form\Type\DefaultResourceType | -+--------------------+------------------------------------------------------------+ +Resource Metadata +----------------- + + ------------------------ ------------------- + Option Value + ------------------------ ------------------- + alias "app.book" + section null + formType null + templatesDir null + routePrefix null + name "book" + pluralName null + applicationName "app" + identifier null + normalizationContext null + denormalizationContext null + validationContext null + class "App\Entity\Book" + driver null + vars null + ------------------------ ------------------- + + [...] ``` By default, the alias for your Sylius resource will be `app.book`, which combines the application name and the resource name diff --git a/docs/resource/create_new_resource.md b/docs/resource/create_new_resource.md index 795d2631c..e1d4c4123 100644 --- a/docs/resource/create_new_resource.md +++ b/docs/resource/create_new_resource.md @@ -126,10 +126,8 @@ class BookRepository extends ServiceEntityRepository ``` {% endcode %} -The generated code is not compatible with Sylius Resource yet, so we need to make a few changes. - -* First, your repository should implement the `Sylius\Component\Resource\Repository\RepositoryInterface` interface -* Then, add the `Sylius\Bundle\ResourceBundle\Doctrine\ORM\ResourceRepositoryTrait` trait +The generated code is not fully compatible with Sylius Resource yet, so we need to make a few change. +Please add the `createPaginator` method using the `Sylius\Bundle\ResourceBundle\Doctrine\ORM\CreatePaginatorTrait` trait Your repository should look like this: @@ -142,20 +140,14 @@ namespace App\Repository; use App\Entity\Book; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; -use Sylius\Bundle\ResourceBundle\Doctrine\ORM\ResourceRepositoryTrait; -use Sylius\Resource\Doctrine\Persistence\RepositoryInterface; +use Sylius\Bundle\ResourceBundle\Doctrine\ORM\CreatePaginatorTrait; /** - * @extends ServiceEntityRepository - * - * @method Book|null find($id, $lockMode = null, $lockVersion = null) - * @method Book|null findOneBy(array $criteria, array $orderBy = null) - * @method Book[] findAll() - * @method Book[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + * [...] */ -class BookRepository extends ServiceEntityRepository implements RepositoryInterface +class BookRepository extends ServiceEntityRepository { - use ResourceRepositoryTrait; + use CreatePaginatorTrait; public function __construct(ManagerRegistry $registry) { From bdc6baed910af30eb6ebdb1b2696a4ecbd0fcd68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Thu, 25 Sep 2025 16:38:43 +0200 Subject: [PATCH 16/53] [AdminUi] Use admin dashboard route instead of admin ui configuration --- src/AdminUi/src/Symfony/DependencyInjection/Configuration.php | 1 + .../templates/shared/crud/common/sidebar/logo.html.twig | 4 +--- .../shared/crud/create/content/header/breadcrumbs.html.twig | 4 +--- .../shared/crud/show/content/header/breadcrumbs.html.twig | 4 +--- .../shared/crud/update/content/header/breadcrumbs.html.twig | 4 +--- 5 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/AdminUi/src/Symfony/DependencyInjection/Configuration.php b/src/AdminUi/src/Symfony/DependencyInjection/Configuration.php index 64fcf157e..72e081c6b 100644 --- a/src/AdminUi/src/Symfony/DependencyInjection/Configuration.php +++ b/src/AdminUi/src/Symfony/DependencyInjection/Configuration.php @@ -35,6 +35,7 @@ private function addRoutingConfiguration(ArrayNodeDefinition $rootNode): void $rootNode ->children() ->arrayNode('routing') + ->setDeprecated('sylius/admin-ui', '0.9', 'This configuration is not used anymore, use the Admin Ui routes instead.') ->useAttributeAsKey('name') ->validate() ->always(static function ($values): array { diff --git a/src/BootstrapAdminUi/templates/shared/crud/common/sidebar/logo.html.twig b/src/BootstrapAdminUi/templates/shared/crud/common/sidebar/logo.html.twig index 2fb171b2e..082c692ad 100644 --- a/src/BootstrapAdminUi/templates/shared/crud/common/sidebar/logo.html.twig +++ b/src/BootstrapAdminUi/templates/shared/crud/common/sidebar/logo.html.twig @@ -1,7 +1,5 @@ -{% set dashboard_path = hookable_metadata.context.routing.dashboard_path|default('/admin') %} -

- + {% hook 'logo' %}

diff --git a/src/BootstrapAdminUi/templates/shared/crud/create/content/header/breadcrumbs.html.twig b/src/BootstrapAdminUi/templates/shared/crud/create/content/header/breadcrumbs.html.twig index 21372dcca..22dbe025f 100644 --- a/src/BootstrapAdminUi/templates/shared/crud/create/content/header/breadcrumbs.html.twig +++ b/src/BootstrapAdminUi/templates/shared/crud/create/content/header/breadcrumbs.html.twig @@ -14,10 +14,8 @@ ) %} -{% set dashboard_path = hookable_metadata.context.routing.dashboard_path|default('/admin') %} - {{ breadcrumbs([ - { name: 'sylius.ui.dashboard', url: dashboard_path, active: false }, + { name: 'sylius.ui.dashboard', url: path('sylius_admin_ui_dashboard'), active: false }, { name: resource_name, url: index_url, active: false }, { name: 'sylius.ui.new'|trans, active: true} ]) }} diff --git a/src/BootstrapAdminUi/templates/shared/crud/show/content/header/breadcrumbs.html.twig b/src/BootstrapAdminUi/templates/shared/crud/show/content/header/breadcrumbs.html.twig index 9b5a8d685..ab88f6dbc 100644 --- a/src/BootstrapAdminUi/templates/shared/crud/show/content/header/breadcrumbs.html.twig +++ b/src/BootstrapAdminUi/templates/shared/crud/show/content/header/breadcrumbs.html.twig @@ -28,10 +28,8 @@ {% set resource_show = { name: resource_show_name, active: true} %} {% endif %} -{% set dashboard_path = hookable_metadata.context.routing.dashboard_path|default('/admin') %} - {{ breadcrumbs([ - { name: 'sylius.ui.dashboard', url: dashboard_path, active: false }, + { name: 'sylius.ui.dashboard', url: path('sylius_admin_ui_dashboard'), active: false }, { name: resource_index, url: index_url, active: false }, resource_show, action diff --git a/src/BootstrapAdminUi/templates/shared/crud/update/content/header/breadcrumbs.html.twig b/src/BootstrapAdminUi/templates/shared/crud/update/content/header/breadcrumbs.html.twig index e05fa6ebc..caf3b7697 100644 --- a/src/BootstrapAdminUi/templates/shared/crud/update/content/header/breadcrumbs.html.twig +++ b/src/BootstrapAdminUi/templates/shared/crud/update/content/header/breadcrumbs.html.twig @@ -24,10 +24,8 @@ {% set resource_edit = { name: resource_edit_name, active: true} %} {% endif %} -{% set dashboard_path = hookable_metadata.context.routing.dashboard_path|default('/admin') %} - {{ breadcrumbs([ - { name: 'sylius.ui.dashboard', url: dashboard_path, active: false }, + { name: 'sylius.ui.dashboard', url: path('sylius_admin_ui_dashboard'), active: false }, { name: resource_index, url: index_url, active: false }, resource_edit, { name: 'sylius.ui.edit'|trans, active: false } From f67f14d16ef2234af5636668eac9c667a00678ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Mon, 25 Aug 2025 10:32:06 +0200 Subject: [PATCH 17/53] Allow configuring primary Twig hook for CRUD templates --- src/AdminUi/templates/crud/create.html.twig | 5 +++-- src/AdminUi/templates/crud/index.html.twig | 5 +++-- src/AdminUi/templates/crud/show.html.twig | 5 +++-- src/AdminUi/templates/crud/update.html.twig | 5 +++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/AdminUi/templates/crud/create.html.twig b/src/AdminUi/templates/crud/create.html.twig index 3522e2c47..39fc882af 100644 --- a/src/AdminUi/templates/crud/create.html.twig +++ b/src/AdminUi/templates/crud/create.html.twig @@ -1,9 +1,10 @@ {% extends '@SyliusAdminUi/base.html.twig' %} -{% set prefixes = [ +{% set prefixes = configuration.vars.hook_prefix is defined ? [configuration.vars.hook_prefix] %} +{% set prefixes = prefixes|default({})|merge([ 'sylius_admin.%resource_name%'|replace({'%resource_name%': resource_name|default(metadata.name)}), 'sylius_admin.common' -] %} +]) %} {% block title %} {% apply striptags %} diff --git a/src/AdminUi/templates/crud/index.html.twig b/src/AdminUi/templates/crud/index.html.twig index e0413ec3c..f58afa798 100644 --- a/src/AdminUi/templates/crud/index.html.twig +++ b/src/AdminUi/templates/crud/index.html.twig @@ -1,9 +1,10 @@ {% extends '@SyliusAdminUi/base.html.twig' %} -{% set prefixes = [ +{% set prefixes = configuration.vars.hook_prefix is defined ? [configuration.vars.hook_prefix] %} +{% set prefixes = prefixes|default({})|merge([ 'sylius_admin.%resource_name%'|replace({'%resource_name%': resource_name|default(metadata.name)}), 'sylius_admin.common' -] %} +]) %} {% block title %} {% apply striptags %} diff --git a/src/AdminUi/templates/crud/show.html.twig b/src/AdminUi/templates/crud/show.html.twig index dbf495749..f05bfd86c 100644 --- a/src/AdminUi/templates/crud/show.html.twig +++ b/src/AdminUi/templates/crud/show.html.twig @@ -1,9 +1,10 @@ {% extends '@SyliusAdminUi/base.html.twig' %} -{% set prefixes = [ +{% set prefixes = configuration.vars.hook_prefix is defined ? [configuration.vars.hook_prefix] %} +{% set prefixes = prefixes|default({})|merge([ 'sylius_admin.%resource_name%'|replace({'%resource_name%': resource_name|default(metadata.name)}), 'sylius_admin.common' -] %} +]) %} {% block title %} {% apply striptags %} diff --git a/src/AdminUi/templates/crud/update.html.twig b/src/AdminUi/templates/crud/update.html.twig index ed825c7c4..3db8e694e 100644 --- a/src/AdminUi/templates/crud/update.html.twig +++ b/src/AdminUi/templates/crud/update.html.twig @@ -1,9 +1,10 @@ {% extends '@SyliusAdminUi/base.html.twig' %} -{% set prefixes = [ +{% set prefixes = configuration.vars.hook_prefix is defined ? [configuration.vars.hook_prefix] %} +{% set prefixes = prefixes|default({})|merge([ 'sylius_admin.%resource_name%'|replace({'%resource_name%': resource_name|default(metadata.name)}), 'sylius_admin.common' -] %} +]) %} {% block title %} {% apply striptags %} From f54cc8665daa1ea3aff56608b589d33f17dea843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Fri, 26 Sep 2025 10:14:25 +0200 Subject: [PATCH 18/53] Prepare 0.9 --- src/AdminUi/composer.json | 4 ++-- src/BootstrapAdminUi/composer.json | 8 ++++---- src/TwigExtra/composer.json | 2 +- src/TwigHooks/composer.json | 2 +- src/UiTranslations/composer.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/AdminUi/composer.json b/src/AdminUi/composer.json index 09daa0710..de3388d47 100644 --- a/src/AdminUi/composer.json +++ b/src/AdminUi/composer.json @@ -5,7 +5,7 @@ "require": { "php": "^8.1", "knplabs/knp-menu-bundle": "^3.0", - "sylius/twig-hooks": "^0.8", + "sylius/twig-hooks": "^0.9", "symfony/http-kernel": "^6.4 || ^7.0", "symfony/security-bundle": "^6.4 || ^7.0", "symfony/security-http": "^6.4 || ^7.0", @@ -54,7 +54,7 @@ "require": "7.1.*" }, "branch-alias": { - "dev-main": "0.8-dev" + "dev-main": "0.9-dev" } }, "config": { diff --git a/src/BootstrapAdminUi/composer.json b/src/BootstrapAdminUi/composer.json index 0d35139e1..fcf8c726f 100644 --- a/src/BootstrapAdminUi/composer.json +++ b/src/BootstrapAdminUi/composer.json @@ -5,11 +5,11 @@ "require": { "php": "^8.1", "pagerfanta/twig": "^4.6", - "sylius/admin-ui": "^0.8", + "sylius/admin-ui": "^0.9", "sylius/grid-bundle": "^1.13", "sylius/resource-bundle": "^1.11", - "sylius/twig-extra": "^0.8", - "sylius/twig-hooks": "^0.8", + "sylius/twig-extra": "^0.9", + "sylius/twig-hooks": "^0.9", "symfony/asset": "^6.4 || ^7.0", "symfony/http-kernel": "^6.4 || ^7.0", "symfony/http-client": "^6.4 || ^7.0", @@ -59,7 +59,7 @@ "require": "7.1.*" }, "branch-alias": { - "dev-main": "0.8-dev" + "dev-main": "0.9-dev" } }, "minimum-stability": "dev", diff --git a/src/TwigExtra/composer.json b/src/TwigExtra/composer.json index 375f46d1b..0f5fb0492 100644 --- a/src/TwigExtra/composer.json +++ b/src/TwigExtra/composer.json @@ -55,7 +55,7 @@ "require": "7.1.*" }, "branch-alias": { - "dev-main": "0.8-dev" + "dev-main": "0.9-dev" } } } diff --git a/src/TwigHooks/composer.json b/src/TwigHooks/composer.json index 157a262b0..c89dd0ca0 100644 --- a/src/TwigHooks/composer.json +++ b/src/TwigHooks/composer.json @@ -66,7 +66,7 @@ "require": "7.1.*" }, "branch-alias": { - "dev-main": "0.8-dev" + "dev-main": "0.9-dev" } } } diff --git a/src/UiTranslations/composer.json b/src/UiTranslations/composer.json index cd6a1f890..1f395a836 100644 --- a/src/UiTranslations/composer.json +++ b/src/UiTranslations/composer.json @@ -40,7 +40,7 @@ "require": "7.1.*" }, "branch-alias": { - "dev-main": "0.8-dev" + "dev-main": "0.9-dev" } } } From 04ed94342ba73be2dfb2fa1ac4c44fc80b6b5b7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Mon, 29 Sep 2025 14:43:44 +0200 Subject: [PATCH 19/53] Fix missing dashboard path replacement --- .../shared/crud/index/content/header/breadcrumbs.html.twig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/BootstrapAdminUi/templates/shared/crud/index/content/header/breadcrumbs.html.twig b/src/BootstrapAdminUi/templates/shared/crud/index/content/header/breadcrumbs.html.twig index dc15931b8..4ae7c2202 100644 --- a/src/BootstrapAdminUi/templates/shared/crud/index/content/header/breadcrumbs.html.twig +++ b/src/BootstrapAdminUi/templates/shared/crud/index/content/header/breadcrumbs.html.twig @@ -9,9 +9,7 @@ {% set title = hookable_metadata.configuration.title %} {% endif %} -{% set dashboard_path = hookable_metadata.context.routing.dashboard_path|default('/admin') %} - {{ breadcrumbs([ - { 'name': 'sylius.ui.dashboard', 'url': dashboard_path, 'active': false }, + { 'name': 'sylius.ui.dashboard', 'url': path('sylius_admin_ui_dashboard'), 'active': false }, { 'name': title, 'active': true }, ]) }} From 2906cb11d73364727ecb56b350e5257abd9c122f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Tue, 30 Sep 2025 11:38:38 +0200 Subject: [PATCH 20/53] =?UTF-8?q?[Docs]=C2=A0Fix=20a=20typo=20in=20create?= =?UTF-8?q?=20new=20resource=20chapter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/resource/create_new_resource.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/resource/create_new_resource.md b/docs/resource/create_new_resource.md index e1d4c4123..a8561b3d7 100644 --- a/docs/resource/create_new_resource.md +++ b/docs/resource/create_new_resource.md @@ -126,7 +126,7 @@ class BookRepository extends ServiceEntityRepository ``` {% endcode %} -The generated code is not fully compatible with Sylius Resource yet, so we need to make a few change. +The generated code is not fully compatible with Sylius Resource yet, so we need to make a few changes. Please add the `createPaginator` method using the `Sylius\Bundle\ResourceBundle\Doctrine\ORM\CreatePaginatorTrait` trait Your repository should look like this: From d74fd5637277fad173047cf4719964b00df81eb0 Mon Sep 17 00:00:00 2001 From: Estelle Gaits Date: Thu, 2 Oct 2025 13:24:56 +0000 Subject: [PATCH 21/53] GITBOOK-22: Estelle's Oct 2 changes - Fix typos & broken code blocks --- ...han-div-class-field-greater-than-for....md | 28 +++++++++ .../passing-data-to-your-hookables.md | 61 ++++++------------- 2 files changed, 47 insertions(+), 42 deletions(-) create mode 100644 docs/.gitbook/includes/less-than-div-class-field-greater-than-for....md diff --git a/docs/.gitbook/includes/less-than-div-class-field-greater-than-for....md b/docs/.gitbook/includes/less-than-div-class-field-greater-than-for....md new file mode 100644 index 000000000..b4571ff18 --- /dev/null +++ b/docs/.gitbook/includes/less-than-div-class-field-greater-than-for....md @@ -0,0 +1,28 @@ +--- +title:
{{ for... +--- + +{% tabs %} +{% tab title="property access via hookable_metadata" %} +
<div class="field">
+  {{ form_row(hookable_metadata.context.form.some_field) }}
+ </div>
+
+{% endtab %} + +{% tab title="variable binding" %} +
<div class="field">
+  {% set context = hookable_metadata.context %}
+  {{ form_row(context.form.some_field) }}
+ </div>
+
+{% endtab %} + +{% tab title="utility function" %} +
<div class="field">
+  {% set context = get_hookable_context() %}
+  {{ form_row(context.form.some_field) }}
+ </div>
+
+{% endtab %} +{% endtabs %} diff --git a/docs/twig-hooks/passing-data-to-your-hookables.md b/docs/twig-hooks/passing-data-to-your-hookables.md index 13b920cff..89479ca4d 100644 --- a/docs/twig-hooks/passing-data-to-your-hookables.md +++ b/docs/twig-hooks/passing-data-to-your-hookables.md @@ -1,69 +1,42 @@ # Passing data to your hookables -One of the most powerful aspects of hooks & hookables is an ability to pass their data down to the children. We can have two sources of the context data: +One of the most powerful aspects of hooks & hookables is the ability to pass data down to children elements. We can have two sources of context data: * Hook-level defined data * Hookable-level defined data -The context data from these two sources are merged and passed with the metadata to the hookable template or component, so we can use them. +Context data from these two sources is merged and passed to the **hookable** template or component together with the metadata , so we can access them. -
+
-
+### Example -
+|

Let's assume we want to render a form in our index.html.twig template via a form variable containing a FormView instance.

Here, we define an index.form hook, and we can pass it the form's context data thanks to the with keyword.

| +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -### Example +This means that we can technically pass down multiple pieces of data to hookables that will hook into `index.form`. {% code title="index.html.twig" lineNumbers="true" fullWidth="false" %} ```twig -{# - # we assume there is a `form` variable holding a `FormView` instance passed - # from the controller - #} -
{{ form_start(form) }} - {{ form_errors(form) }} - {{ form_widget(form._token) }} + {{ form_errors(form }} + {{ form_widget(form._token) }} + + {% hook 'index.form' with { form } %} - {% raw %} -{% hook 'index.form' with { form } %} -{% endraw %} - {{ form_end(form, {render_rest: false}) }} + {{ form_end(form, {render_rest: false} }}
``` {% endcode %} -So, as we see at `line 11` we define the `index.form` hook. But also, we pass the `form` with using the `with` keyword. Thanks to it, we are able to pass multiple data to hookables that will hook into the `index.form` hook. - {% hint style="info" %} `with { form }` is a short-hand for `with { form: form }`, so the key for our `FormView` in the context data bag will be `form.` {% endhint %} -Now let's create a Twig template rendering some field from our form, and let's make it a hookable. +Now let's create a Twig template that renders a field from our form and let's make it a hookable. We have 3 possible options to do this : -{% code title="index/some_field.html.twig" lineNumbers="true" %} -```twig -
- {{ form_row(hookable_metadata.context.form.some_field) }} - - {# we can also write it like #} - - {% raw %} -{% set context = hookable_metadata.context %} - - {{ form_row(context.form.some_field) }} - - {# or #} - - {% set context = get_hookable_context() %} -{% endraw %} - - {{ form_row(context.form.some_field) }} -
-``` -{% endcode %} +{% include "../.gitbook/includes/less-than-div-class-field-greater-than-for....md" %} {% hint style="info" %} You can access the context data in multiple ways, so you can pick the one you like the most. Available options are: @@ -72,6 +45,10 @@ You can access the context data in multiple ways, so you can pick the one you li * getting the context data bag via the Twig function like `get_hookable_context().` {% endhint %} +### Override behavior + +When the same context data key is defined at both the **hook** and **hookable** levels, the **hookable-level** value takes precedence. + {% hint style="info" %} -Sometimes you might want to override some data that are defined at the hook-level. It is possible by defining the same context data key on the hookable level. If the same context data key is defined at both hook-level and hookable-level the hookable-level one is used. +You can use this to override hook-level data by redefining the key at the hookable level. {% endhint %} From ac055e94b9ad57dcb771cc0797e13a74b8510e35 Mon Sep 17 00:00:00 2001 From: Rachel Snijders Date: Wed, 1 Oct 2025 12:00:57 +0200 Subject: [PATCH 22/53] Added Dutch translations --- .../translations/messages.nl.yaml | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/UiTranslations/translations/messages.nl.yaml diff --git a/src/UiTranslations/translations/messages.nl.yaml b/src/UiTranslations/translations/messages.nl.yaml new file mode 100644 index 000000000..7a9141b36 --- /dev/null +++ b/src/UiTranslations/translations/messages.nl.yaml @@ -0,0 +1,55 @@ +sylius: + form: + login: + username: 'Gebruikersnaam' + password: 'Wachtwoord' + remember_me: 'Onthoud mij' + ui: + actions: Acties + add_new_entry: Nieuw item toevoegen + administration_panel_login: 'Administratiepaneel login' + all: Alles + are_your_sure_you_want_to_perform_this_action: 'Weet je zeker dat je daze actie wilt uitvoeren?' + cancel: Annuleren + contains: Bevat + create: Creëer + dashboard: Dashboard + date_filter: '%label% | %altLabel%' + delete: Verwijderen + details: Details + edit: Bewerken + empty: 'Leeg' + equal: 'Gelijk aan' + ends_with: 'Eindigt met' + error: Fout + filter: Filter + filters: Filters + from: Van + hello: Hallo + in: 'In' + info: Info + login: Inloggen + login_to_your_account: 'Inloggen op jouw account' + logout: Uitloggen + new: Nieuw + no_label: Nee + no_results: Geen resultaten gevonden + no_results_adjust_your_search: 'Stel je zoekopdracht bij en probeer opnieuw' + not_contains: 'Bevat niet' + not_empty: 'Niet leeg' + not_equal: 'Niet gelijk aan' + not_in: 'Niet in' + pagination: + number_of_results: 'Toont %from% tot %to% van %total% resultaten' + reset: Resetten + search: Zoeken + search_menu: 'Zoek in menu' + show: Tonen + starts_with: 'Begint met' + success: Succes + this_form_contains_errors: 'Dit formulier bevat fouten.' + to: Tot + update: Bijwerken + value: Waarde + warning: Waarschuwing + yes_label: Ja From 7c0841cbbb5447b27ccd0e8f30a9b5471716dbec Mon Sep 17 00:00:00 2001 From: Estelle Gaits Date: Thu, 9 Oct 2025 08:50:03 +0000 Subject: [PATCH 23/53] GITBOOK-23: Estelle's Oct 2 changes - entity autocompletes --- docs/SUMMARY.md | 3 +- docs/cookbook/admin_panel.md | 2 + .../admin_panel/using-autocompletes.md | 260 ++++++++++++++++++ 3 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 docs/cookbook/admin_panel/using-autocompletes.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index e11ebe7f3..a5584df97 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -13,6 +13,7 @@ * [Configuring the security access](cookbook/admin_panel/security.md) * [Customizing the page titles](cookbook/admin_panel/page_titles.md) * [Customizing the metatags](cookbook/admin_panel/metatags.md) + * [Using autocompletes](cookbook/admin_panel/using-autocompletes.md) * [How to use in a DDD architecture](cookbook/ddd_architecture.md) * [Architecture overview](cookbook/ddd_architecture/overview.md) * [Resource configuration](cookbook/ddd_architecture/resource_configuration.md) @@ -40,7 +41,7 @@ * [Providers](resource/providers.md) * [Processors](resource/processors.md) * [Responders](resource/responders.md) - * [Legacy Resource Documentation](resource/legacy/index.md) + * [Legacy Resource Documentation](resource/legacy/index.md) * [Configuration](resource/legacy/configuration.md) * [Services](resource/legacy/services.md) * [Routing](resource/legacy/routing.md) diff --git a/docs/cookbook/admin_panel.md b/docs/cookbook/admin_panel.md index 70fcd495f..4f69ab2c1 100644 --- a/docs/cookbook/admin_panel.md +++ b/docs/cookbook/admin_panel.md @@ -7,3 +7,5 @@ * [Configuring the security access](admin_panel/security.md) * [Customizing the page titles](admin_panel/page_titles.md) * [Customizing the metatags](admin_panel/metatags.md) +* [Using autocompletes](admin_panel/using-autocompletes.md) + diff --git a/docs/cookbook/admin_panel/using-autocompletes.md b/docs/cookbook/admin_panel/using-autocompletes.md new file mode 100644 index 000000000..e559ea556 --- /dev/null +++ b/docs/cookbook/admin_panel/using-autocompletes.md @@ -0,0 +1,260 @@ +# Using autocompletes + +The [SyliusBootstrapAdminUi](../../bootstrap-admin-ui/getting-started.md) package uses[ Symfony UX ](https://ux.symfony.com/)under the hood. Thus, [UX autocomplete](https://symfony.com/bundles/ux-autocomplete/current/index.html) is already setup and configured in your admin panel. This means that any simple `ChoiceType` filter form can be turned into an autocomplete simply by setting `autocomplete` to `true` in form options. + +```php +public function configureOptions(OptionsResolver $resolver): void +{ + $resolver->setDefaults([ + 'choices' => $this->getChoices(), + 'placeholder' => 'sylius.ui.all', + 'autocomplete' => true, + ]); +} + +public function getParent(): string +{ + return ChoiceType::class; +} + +``` + + However, if your autocomplete filter requires fetching data from another entity, you will need to use a `BaseEntityAutocompleteType` in order to fetch your options via AJAX. + +All you need to start leveraging this functionality is a bit of routing config. + +### Configure the entity autocomplete route + +{% tabs %} +{% tab title="PHP" %} +{% code title="config/routes/ux_autocomplete.php" %} +```php +add('ux_entity_autocomplete_admin', '/admin/autocomplete/{alias}') + ->controller('ux.autocomplete.entity_autocomplete_controller') + ; +}; + +``` +{% endcode %} +{% endtab %} + +{% tab title="YAML" %} +{% code title="config/routes/ux_autocomplete.yaml" %} +```yaml +# ... +ux_entity_autocomplete_admin: + path: '/admin/autocomplete/{alias}' + controller: 'ux.autocomplete.entity_autocomplete_controller' +``` +{% endcode %} +{% endtab %} +{% endtabs %} + +This adds a new `ux_entity_autocomplete_admin` AJAX route dedicated to your autocompletes. + +### Add a grid filter with entity autocomplete + +First, you need to create an [entity autocomplete field ](https://symfony.com/bundles/ux-autocomplete/current/index.html#usage-in-a-form-with-ajax). + +{% code title="src/Form/SpeakerAutocompleteType.php" lineNumbers="true" %} +```php +setDefaults([ + 'class' => Speaker::class, + 'choice_label' => 'fullName', + ]); + } + + public function getParent(): string + { + return BaseEntityAutocompleteType::class; + } +} +``` +{% endcode %} + +Then, you need to create your grid filter. + +{% tabs %} +{% tab title="SyliusGridBundle v1.13 (latest)" %} +1\) Create your filter class. + +{% code title="src/Grid/Filter/SpeakerFilter.php" lineNumbers="true" %} +```php +entityFilter->apply($dataSource, $name, $data, $options); + } + + public static function getFormType(): string + { + return SpeakerAutocompleteType::class; + } + + public static function getType(): string + { + return self::class; // this will allow us to use FQCN instead of a string key. + } + + } +``` +{% endcode %} + +2\) then configure the Twig template this filter will use. + +PHP config : + +{% code title="config/packages/sylius_grid.php +" %} +```php +extension('sylius_grid', [ + 'templates' => [ + 'filter' => [ + SpeakerFilter::class => '@SyliusBootstrapAdminUi/shared/grid/filter/select.html.twig', + ], + ], + ]); +}; +``` +{% endcode %} + +YAML config: + +{% code title="config/packages/sylius_grid.yaml" %} +```yaml +sylius_grid: + # ... + templates: + filter: + 'App\Grid\Filter\SpeakerFilter': '@SyliusBootstrapAdminUi/shared/grid/filter/entity.html.twig' +``` +{% endcode %} +{% endtab %} + +{% tab title="SyliusGridBundle v1.14@alpha" %} +{% code title="src/Grid/Filter/SpeakerFilter.php" lineNumbers="true" %} +```php +entityFilter->apply($dataSource, $name, $data, $options); + } + } +``` +{% endcode %} +{% endtab %} +{% endtabs %} + +Now that the filter is configured, you can use it inside any grid. + +{% code title="src/Grid/TalkGrid.php" lineNumbers="true" %} +```php +addFilter( + Filter::create(name: 'speaker', type: SpeakerFilter::class) + ->setLabel('app.ui.speaker') + ->setOptions(['fields' => ['speakers.id']]), + ) + // ... + ; + } + + // ... +} +``` +{% endcode %} From a87f2baa617b3178639b439ae75cc1b9f7970316 Mon Sep 17 00:00:00 2001 From: Estelle Gaits Date: Wed, 15 Oct 2025 10:40:14 +0000 Subject: [PATCH 24/53] GITBOOK-25: Estelle's Oct 15 changes - custom filters with attributes --- docs/grid/custom_filter.md | 152 +++++++++++++++++++++++++++---------- 1 file changed, 112 insertions(+), 40 deletions(-) diff --git a/docs/grid/custom_filter.md b/docs/grid/custom_filter.md index 56d0e23b0..affe21213 100644 --- a/docs/grid/custom_filter.md +++ b/docs/grid/custom_filter.md @@ -4,6 +4,85 @@ Sylius Grids come with built-in filters, but there are use-cases where you need To add a new filter, we need to create an appropriate class and form type. +{% tabs %} +{% tab title="SyliusGridBundle v1.14 - using attributes" %} +{% code title="src/Grid/Filter/SuppliersStatisticsFilter.php" lineNumbers="true" %} +```php +getQueryBuilder(); + $queryBuilder + ->andWhere('stats = :stats') + ->setParameter(':stats', $data['stats']) + ; + + // You can leverage the ExpressionBuilder to apply driver-agnostic filters to the data source. + // Combined with restrict(), it provides query builder–style functionalities for grid filters. + $dataSource->restrict($dataSource->getExpressionBuilder()->equals('stats', $data['stats'])); + } +} +``` +{% endcode %} + +And the form type: + +{% code title="src/Form/Type/Filter/SuppliersStatisticsFilterType.php" lineNumbers="true" %} +```php +add( + 'stats', + ChoiceType::class, + ['choices' => range($options['range'][0], $options['range'][1])] + ); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver + ->setDefaults([ + 'range' => [0, 10], + ]) + ->setAllowedTypes('range', ['array']) + ; + } +} +``` +{% endcode %} +{% endtab %} + +{% tab title="SyliusGridBundle v1.13" %} {% code title="src/Grid/Filter/SuppliersStatisticsFilter.php" lineNumbers="true" %} ```php getQueryBuilder(); $queryBuilder ->andWhere('stats = :stats') @@ -28,8 +106,6 @@ class SuppliersStatisticsFilter implements ConfigurableFilterInterface ; // For driver abstraction you can use the expression builder. ExpressionBuilder is a kind of query builder. - // $data['stats'] contains the submitted value! - // here is an example $dataSource->restrict($dataSource->getExpressionBuilder()->equals('stats', $data['stats'])); } @@ -85,15 +161,15 @@ class SuppliersStatisticsFilterType extends AbstractType Create a template for the filter, similar to the existing ones: -{% code title="templates/Grid/Filter/suppliers_statistics.html.twig" lineNumbers="true" %} +{% code title="templates/grid/filter/suppliers_statistics.html.twig" lineNumbers="true" %} ```twig -{% form_theme form '@SyliusUi/Form/theme.html.twig' %} + +
{{ form_row(form) }} ``` {% endcode %} - If you use autoconfiguration, the filter is automatically registered as a grid filter. But if you don't use autoconfiguration, let's register your new filter type as a service. @@ -105,30 +181,12 @@ services: tags: ['sylius.grid_filter'] ``` {% endcode %} +{% endtab %} +{% endtabs %} -Now you can use your new filter type in the grid configuration! +Now you can use your new filter type in any grid configuration! {% tabs %} -{% tab title="YAML" %} -{% code title="config/packages/sylius_grid.yaml" lineNumbers="true" %} -```yaml -sylius_grid: - grids: - app_tournament: - driver: doctrine/orm - resource: app.tournament - filters: - stats: - type: suppliers_statistics - form_options: - range: [0, 100] - templates: - filter: - suppliers_statistics: '@App/Grid/Filter/suppliers_statistics.html.twig' -``` -{% endcode %} -{% endtab %} - {% tab title="PHP" %} {% code title="config/packages/sylius_grid.php" lineNumbers="true" %} ```php @@ -165,16 +223,14 @@ namespace App\Grid; use App\Entity\Tournament; use Sylius\Bundle\GridBundle\Builder\GridBuilderInterface; use Sylius\Bundle\GridBundle\Grid\AbstractGrid; -use Sylius\Bundle\GridBundle\Grid\ResourceAwareGridInterface; -final class TournamentGrid extends AbstractGrid implements ResourceAwareGridInterface +#[AsGrid( + name: 'app_tournament', + resourceClass: Tournament::class, +)] +final class TournamentGrid extends AbstractGrid { - public static function getName(): string - { - return 'app_tournament'; - } - - public function buildGrid(GridBuilderInterface $gridBuilder): void + public function __invoke(GridBuilderInterface $gridBuilder): void { $gridBuilder ->addFilter( @@ -183,13 +239,29 @@ final class TournamentGrid extends AbstractGrid implements ResourceAwareGridInte ) ; } - - public function getResourceClass(): string - { - return Tournament::class; - } } ``` {% endcode %} {% endtab %} + +{% tab title="YAML" %} +{% code title="config/packages/sylius_grid.yaml" lineNumbers="true" %} +```yaml +sylius_grid: + grids: + app_tournament: + driver: doctrine/orm + resource: app.tournament + filters: + stats: + type: suppliers_statistics + form_options: + range: [0, 100] + + templates: # only needed if you didn't use AsFilter attribute + filter: + suppliers_statistics: '@App/Grid/Filter/suppliers_statistics.html.twig' +``` +{% endcode %} +{% endtab %} {% endtabs %} From e3fab3b5c4898a55ca06e595edc3735f900e9f83 Mon Sep 17 00:00:00 2001 From: Estelle Gaits Date: Wed, 15 Oct 2025 15:47:32 +0000 Subject: [PATCH 25/53] GITBOOK-24: Estelle's Oct 15 changes --- .../admin_panel/using-autocompletes.md | 77 ++++++++++--------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/docs/cookbook/admin_panel/using-autocompletes.md b/docs/cookbook/admin_panel/using-autocompletes.md index e559ea556..798853dac 100644 --- a/docs/cookbook/admin_panel/using-autocompletes.md +++ b/docs/cookbook/admin_panel/using-autocompletes.md @@ -104,7 +104,42 @@ final class SpeakerAutocompleteType extends AbstractType Then, you need to create your grid filter. {% tabs %} -{% tab title="SyliusGridBundle v1.13 (latest)" %} +{% tab title="SyliusGridBundle v1.14" %} +{% code title="src/Grid/Filter/SpeakerFilter.php" lineNumbers="true" %} +```php +entityFilter->apply($dataSource, $name, $data, $options); + } + } +``` +{% endcode %} +{% endtab %} + +{% tab title="SyliusGridBundle v1.13 " %} 1\) Create your filter class. {% code title="src/Grid/Filter/SpeakerFilter.php" lineNumbers="true" %} @@ -185,40 +220,6 @@ sylius_grid: ``` {% endcode %} {% endtab %} - -{% tab title="SyliusGridBundle v1.14@alpha" %} -{% code title="src/Grid/Filter/SpeakerFilter.php" lineNumbers="true" %} -```php -entityFilter->apply($dataSource, $name, $data, $options); - } - } -``` -{% endcode %} -{% endtab %} {% endtabs %} Now that the filter is configured, you can use it inside any grid. @@ -236,12 +237,14 @@ use Sylius\Bundle\GridBundle\Builder\Filter\Filter; use Sylius\Bundle\GridBundle\Builder\GridBuilderInterface; use Sylius\Bundle\GridBundle\Grid\AbstractGrid; use Sylius\Bundle\GridBundle\Grid\ResourceAwareGridInterface; +use Sylius\Component\Grid\Attribute\AsGrid; -final class TalkGrid extends AbstractGrid implements ResourceAwareGridInterface +#[AsGrid(resourceClass: Talk::class)] +final class TalkGrid extends AbstractGrid { // ... - public function buildGrid(GridBuilderInterface $gridBuilder): void + public function __invoke(GridBuilderInterface $gridBuilder): void { $gridBuilder // ... From 05d60d35717e37461a5f2297a775324d47f05445 Mon Sep 17 00:00:00 2001 From: Estelle Gaits Date: Thu, 16 Oct 2025 12:55:10 +0000 Subject: [PATCH 26/53] GITBOOK-27: Estelle's Oct 15 changes - your 1st grid revamp with attributes --- docs/grid/your_first_grid.md | 933 ++++++++++++++++++----------------- 1 file changed, 488 insertions(+), 445 deletions(-) diff --git a/docs/grid/your_first_grid.md b/docs/grid/your_first_grid.md index 801d8180d..f797060b6 100644 --- a/docs/grid/your_first_grid.md +++ b/docs/grid/your_first_grid.md @@ -1,10 +1,6 @@ -Creating Your First Grid -======================== +# Creating your first grid -In order to use grids, you need to register your entity as a Sylius -resource. Let us assume you have a Supplier model in your application, -which represents a supplier of goods in your shop and has several -fields, including _name_, _description_ and _enabled_. +In order to use grids, you need to register your entity as a Sylius resource. Let us assume you have a Supplier model in your application, which represents a supplier of goods in your shop and has several fields, including _name_, _description_ and _enabled_. In order to make it a Sylius resource, you need to add the `AsResource` attribute and implement `ResourceInterface`. @@ -23,51 +19,74 @@ class Supplier implements ResourceInterface ``` {% endcode %} -That's it! Your class is now a resource. In order to learn what it -means, please refer to the -[SyliusResourceBundle](../resource/index.md) -documentation. +That's it! Your class is now a resource. In order to learn what it means, please refer to the [SyliusResourceBundle](../resource/index.md) documentation. ## Grid Maker You can create your grid using the [Symfony Maker bundle](https://symfony.com/bundles/SymfonyMakerBundle/current/index.html). +{% hint style="info" %} +Since SyliusGridBundle 1.14, this command supports any PHP class, including Sylius Resources too ! +{% endhint %} + ```shell $ bin/console make:grid ``` +{% hint style="info" %} +This command will generate a grid with a field entry for each of your PHP class properties except `id`. +{% endhint %} + ## Grid Definition Now we can configure our first grid: {% tabs %} -{% tab title="YAML" %} -{% code title="config/packages/sylius_grid.yaml" lineNumbers="true" %} -```yaml -sylius_grid: - grids: - app_admin_supplier: - driver: - name: doctrine/orm - options: - class: App\Entity\Supplier - fields: - name: - type: string - label: app.ui.name - enabled: - type: twig - label: app.ui.enabled - options: - template: '@SyliusBootstrapAdminUi/shared/grid/field/boolean.html.twig' # This will be a checkbox field +{% tab title="PHP service" %} +{% code title="/src/Grid/AdminSupplierGrid.php" lineNumbers="true" %} +```php +addField( + StringField::create('name') + ->setLabel('app.ui.name') + ) + ->addField( + TwigField::create('enabled', '@SyliusBootstrapAdminUi/shared/grid/field/boolean.html.twig') + ->setLabel('app.ui.enabled') + ) + ; + } +} ``` {% endcode %} - {% endtab %} -{% tab title="PHP" %} +{% tab title="PHP config file (legacy)" %} {% code lineNumbers="true" %} ```php +addFilter( + StringFilter::create('name') + ->setLabel('Name') + ) + ->addFilter( + BooleanFilter::create('enabled') + ->setLabel('Enabled') + ) + ; + } +} ``` {% endcode %} - {% endtab %} -{% tab title="PHP" %} +{% tab title="PHP (legacy)" %} {% code lineNumbers="true" %} ```php addFilter( - StringFilter::create('name') - ->setLabel('Name') - ) - ->addFilter( - BooleanFilter::create('enabled') - ->setLabel('Enabled') - ) - ; - } - - public function getResourceClass(): string + public function __invoke(GridBuilderInterface $gridBuilder): void { - return Supplier::class; + $gridBuilder->setRepositoryMethod('mySupplierGridQuery'); } } ``` {% endcode %} - {% endtab %} -{% endtabs %} - -How will it look like in the admin panel? -![image](../.gitbook/assets/grid_filters.png) +{% tab title="PHP (legacy)" %} +{% code title="config/packages/sylius_grid.php" lineNumbers="true" %} +```php +addGrid(GridBuilder::create('app_admin_supplier', Supplier::class) + ->setRepositoryMethod('mySupplierGridQuery') + ) +}; +``` +{% endcode %} +{% endtab %} -{% tabs %} {% tab title="YAML" %} {% code title="config/packages/sylius_grid.yaml" lineNumbers="true" %} ```yaml @@ -319,125 +401,126 @@ sylius_grid: method: mySupplierGridQuery ``` {% endcode %} - {% endtab %} +{% endtabs %} -{% tab title="PHP" %} -{% code title="config/packages/sylius_grid.php" lineNumbers="true" %} -```php -addGrid(GridBuilder::create('app_admin_supplier', Supplier::class) - ->setRepositoryMethod('mySupplierGridQuery') - ) -}; -``` -{% endcode %} +#### Custom data provider -OR +Here is a simple example of a custom data provider. You're obviously free to actually fetch your data in whatever way suits your need, by calling a repository of any kind (Doctrine, API, in-memory,..) directly or call a query bus for instance. -{% code title="src/Grid/AdminSupplierGrid.php" lineNumbers="true" %} +{% code title="src/Grid/Provider/SupplierGridProvider.php" lineNumbers="true" %} ```php setRepositoryMethod('mySupplierGridQuery') - ; + $page = (int) $parameters->get('page', 1); + $itemsPerPage = (int) $parameters->get('limit', 10); + $criteria = $parameters->get('criteria'); + + $paginator = $this->getSuppliersPaginator($page, $itemsPerPage, $criteria); + + $data = []; + + foreach ($paginator as $row) { + $data[] = new SupplierResource( + enabled: $row['enabled'], + name: $row['name'], + // ... + ); + } + + return new Pagerfanta(new FixedAdapter($paginator->count(), $data)); } - public function getResourceClass(): string + public function getSuppliersPaginator(int $page, int $itemsPerPage, ?array $criteria): PagerfantaInterface { - return Supplier::class; + $supplierRepository = $this->supplierRepository; + + if (!empty($criteria['country'] ?? null)) { + $supplierRepository = $supplierRepository->withCountryCode($criteria['country']); + } + + return $supplierRepository->withPagination($page, $itemsPerPage)->paginator(); } } ``` {% endcode %} -{% endtab %} -{% endtabs %} +Then, this example Doctrine repository uses a JOIN statement on our related Address Entity. -### *Note* +{% code title="src/Repository/SupplierRepository.php" lineNumbers="true" %} +```php +addGrid(GridBuilder::create('app_admin_supplier', Supplier::class) - ->addFilter( - StringFilter::create('country', ['address.country'], 'contains') - ->setLabel('origin') - ) - ) -}; + $this->queryBuilder + ->innerJoin(sprintf('%s.address', self::ALIAS), self::ADDRESS_ALIAS); + } + + public function withCountryCode(string $countryCode): static + { + return $this->filter(static function (QueryBuilder $queryBuilder) use ($countryCode): void { + $queryBuilder + ->andWhere($queryBuilder->expr()->eq(sprintf('%s.countryCode', self::ADDRESS_ALIAS), ':countryCode')) + ->setParameter('countryCode', $countryCode) + ; + }); + } + + // .... + +} ``` {% endcode %} -OR +#### Adding your entity filter to your grid +Then you can simply insert your filter inside the grid. + +{% tabs %} +{% tab title="PHP " %} {% code title="src/Grid/AdminSupplierGrid.php" lineNumbers="true" %} ```php addFilter( @@ -468,58 +552,62 @@ final class AdminSupplierGrid extends AbstractGrid implements ResourceAwareGridI ) ; } - - public function getResourceClass(): string - { - return Supplier::class; - } + + // ... } ``` {% endcode %} - -{% endtab %} -{% endtabs %} - -Default Sorting ---------------- - -You can define by which field you want the grid to be sorted and how. - -{% tabs %} -{% tab title="YAML" %} -{% code title="config/packages/sylius_grid.yaml" lineNumbers="true" %} -```yaml -sylius_grid: - grids: - app_admin_supplier: - # ... - sorting: - name: asc - # ... -``` -{% endcode %} - {% endtab %} -{% tab title="PHP" %} +{% tab title="PHP (legacy)" %} {% code title="config/packages/sylius_grid.php" lineNumbers="true" %} ```php addGrid(GridBuilder::create('app_admin_supplier', Supplier::class) - ->orderBy('name', 'asc') + ->addFilter( + StringFilter::create('country', ['address.country'], 'contains') + ->setLabel('origin') + ) ) }; ``` {% endcode %} +{% endtab %} -OR +{% tab title="YAML" %} +{% code title="config/packages/sylius_grid.php" lineNumbers="true" %} +```yaml +sylius_grid: + grids: + app_admin_supplier: + # ... + filters: + # ... + country: + type: string + label: origin + options: + fields: [address.country] + form_options: + type: contains +``` +{% endcode %} +{% endtab %} +{% endtabs %} +## Default Sorting + +You can define by which field you want the grid to be sorted and how using `orderBy()` . + +{% tabs %} +{% tab title="PHP service" %} {% code title="src/Grid/AdminSupplierGrid.php" lineNumbers="true" %} ```php orderBy('name', 'asc') - ; - } - - public function getResourceClass(): string - { - return Supplier::class; + $gridBuilder->orderBy(name: 'name', direction: 'asc'); } } ``` {% endcode %} -{% endtab %} -{% endtabs %} - -Then in the fields section, indicate that the field can be used for sorting: - -{% tabs %} -{% tab title="YAML" %} -{% code title="config/packages/sylius_grid.php" lineNumbers="true" %} -```yaml -sylius_grid: - grids: - app_admin_supplier: - # ... - fields: - name: - type: string - label: sylius.ui.name - sortable: ~ - # ... -``` -{% endcode %} {% endtab %} -{% tab title="PHP" %} +{% tab title="PHP (legacy)" %} {% code title="config/packages/sylius_grid.php" lineNumbers="true" %} ```php addGrid(GridBuilder::create('app_admin_supplier', Supplier::class) - ->addField( - StringField::create('name') - ->setLabel('sylius.ui.name') - ->setSortable(true) - ) + ->orderBy('name', 'asc') ) }; ``` {% endcode %} +{% endtab %} -OR +{% tab title="YAML" %} +{% code title="config/packages/sylius_grid.yaml" lineNumbers="true" %} +```yaml +sylius_grid: + grids: + app_admin_supplier: + # ... + sorting: + name: asc + # ... +``` +{% endcode %} +{% endtab %} +{% endtabs %} +Then in the fields section, indicate that the field can be used for sorting with `setSortable()`: + +{% tabs %} +{% tab title="PHP" %} {% code title="src/Grid/AdminSupplierGrid.php" lineNumbers="true" %} ```php addField( @@ -635,68 +707,58 @@ final class AdminSupplierGrid extends AbstractGrid implements ResourceAwareGridI ; } - public function getResourceClass(): string - { - return Supplier::class; - } } ``` {% endcode %} - -{% endtab %} -{% endtabs %} - -If your field is not of a "simple" type, e.g. a twig template with a -specific path, you can enable sorting with the following definition: - -{% tabs %} -{% tab title="YAML" %} -{% code title="config/packages/sylius_grid.php" lineNumbers="true" %} -```yaml -sylius_grid: - grids: - app_admin_supplier: - # ... - fields: - # ... - origin: - type: twig - options: - template: "@App/Grid/Fields/myCountryFlags.html.twig" - path: address.country - label: app.ui.country - sortable: address.country - # ... -``` -{% endcode %} - {% endtab %} -{% tab title="PHP" %} +{% tab title="PHP (legacy)" %} {% code title="config/packages/sylius_grid.php" lineNumbers="true" %} ```php addGrid(GridBuilder::create('app_admin_supplier', Supplier::class) ->addField( - TwigField::create('name', '@App/Grid/Fields/myCountryFlags.html.twig') - ->setPath('address.country') - ->setLabel('app.ui.country') - ->setSortable(true, 'address.country') + StringField::create('name') + ->setLabel('sylius.ui.name') + ->setSortable(true) ) ) }; ``` {% endcode %} -OR +{% endtab %} + +{% tab title="YAML" %} +{% code title="config/packages/sylius_grid.php" lineNumbers="true" %} +```yaml +sylius_grid: + grids: + app_admin_supplier: + # ... + fields: + name: + type: string + label: sylius.ui.name + sortable: ~ + # ... +``` +{% endcode %} +{% endtab %} +{% endtabs %} + +If your field is not of a "simple" type, e.g. a Twig template with a specific path, you can enable sorting with the following definition: + +{% tabs %} +{% tab title="PHP" %} {% code title="src/Grid/AdminSupplierGrid.php" lineNumbers="true" %} ```php addField( - TwigField::create('name', '@App/Grid/Fields/myCountryFlags.html.twig') + TwigField::create('name', '@App/Grid/Fields/my_country_flags.html.twig') ->setPath('address.country') ->setLabel('app.ui.country') ->setSortable(true, 'address.country') ) ; - } - - public function getResourceClass(): string - { - return Supplier::class; - } + } } ``` {% endcode %} - -{% endtab %} -{% endtabs %} - -Pagination ----------- - -You can limit how many items are visible on each page by providing an -array of integers into the `limits` parameter. The first element of the -array will be treated as the default, so by configuring: - -{% tabs %} -{% tab title="YAML" %} -{% code title="config/packages/sylius_grid.php" lineNumbers="true" %} -```yaml -sylius_grid: - grids: - app_admin_supplier: - # ... - limits: [30, 12, 48] - # ... -``` -{% endcode %} - {% endtab %} -{% tab title="PHP" %} +{% tab title="PHP (legacy)" %} {% code title="config/packages/sylius_grid.php" lineNumbers="true" %} ```php addGrid(GridBuilder::create('app_admin_supplier', Supplier::class) - ->setLimits([30, 12, 48]) + ->addField( + TwigField::create('name', '@App/Grid/Fields/myCountryFlags.html.twig') + ->setPath('address.country') + ->setLabel('app.ui.country') + ->setSortable(true, 'address.country') + ) ) }; ``` {% endcode %} +{% endtab %} + +{% tab title="YAML" %} +{% code title="config/packages/sylius_grid.php" lineNumbers="true" %} +```yaml +sylius_grid: + grids: + app_admin_supplier: + # ... + fields: + # ... + origin: + type: twig + options: + template: "@App/Grid/Fields/myCountryFlags.html.twig" + path: address.country + label: app.ui.country + sortable: address.country + # ... +``` +{% endcode %} +{% endtab %} +{% endtabs %} -OR +## Pagination + +You can limit how many items are visible on each page by providing an array of integers into the `limits` parameter. The first element of the array will be treated as the default. +{% hint style="info" %} +Pagination limits are set by default to 10, 25 and 50 items per page. In order to turn it off, configure `limits: ~` . +{% endhint %} + +{% tabs %} +{% tab title="PHP" %} {% code title="src/Grid/AdminSupplierGrid.php" lineNumbers="true" %} ```php setLimits([30, 12, 48]) - ; - } - - public function getResourceClass(): string + public function __invoke(GridBuilderInterface $gridBuilder): void { - return Supplier::class; + $gridBuilder->setLimits([30, 12, 48]); } } ``` {% endcode %} - {% endtab %} {% endtabs %} -you will see thirty suppliers per page, also you will have the -possibility to change the number of elements to either 12 or 48. - -### *Note* - -Pagination limits are set by default to 10, 25 and 50 items per page. -In order to turn it off, configure limits: \~. - -Actions Configuration ---------------------- +## Actions Configuration Next step is adding some actions to the grid: create, update and delete. @@ -878,69 +929,15 @@ These new operations are now available: Then we need to add these operations into our Grid using Actions. -### *Note* +{% hint style="info" %} +There are two types of actions that can be added to a grid: -There are two types of actions that can be added to a grid: `main` -which "influence" the whole grid (like adding new objects) and `item` -which influence one row of the grid (one object) like editing or -deleting. +* `main` actions affect the entire grid, such as adding new items or deleting items in bulk +* and `item` actions which apply to a single row of the grid (one object), such as editing or deleting. +{% endhint %} {% tabs %} -{% tab title="YAML" %} -{% code title="config/packages/sylius_grid.php" lineNumbers="true" %} -```yaml -sylius_grid: - grids: - app_admin_supplier: - # ... - actions: - main: - create: - type: create - item: - update: - type: update - delete: - type: delete -``` -{% endcode %} - -{% endtab %} - {% tab title="PHP" %} -{% code title="config/packages/sylius_grid.php" lineNumbers="true" %} -```php -addGrid(GridBuilder::create('app_admin_supplier', Supplier::class) - ->addActionGroup( - MainActionGroup::create( - CreateAction::create() - ) - ) - ->addActionGroup( - ItemActionGroup::create( - UpdateAction::create(), - DeleteAction::create() - ) - ) - ) -}; -``` -{% endcode %} - -OR - {% code title="src/Grid/AdminSupplierGrid.php" lineNumbers="true" %} ```php addActionGroup( @@ -982,15 +978,62 @@ final class AdminSupplierGrid extends AbstractGrid implements ResourceAwareGridI ) ; } - - public function getResourceClass(): string - { - return Supplier::class; - } } ``` {% endcode %} +{% endtab %} + +{% tab title="PHP (legacy)" %} +{% code title="config/packages/sylius_grid.php" lineNumbers="true" %} +```php +addGrid(GridBuilder::create('app_admin_supplier', Supplier::class) + ->addActionGroup( + MainActionGroup::create( + CreateAction::create() + ) + ) + ->addActionGroup( + ItemActionGroup::create( + UpdateAction::create(), + DeleteAction::create() + ) + ) + ) +}; +``` +{% endcode %} +{% endtab %} +{% tab title="YAML" %} +{% code title="config/packages/sylius_grid.php" lineNumbers="true" %} +```yaml +sylius_grid: + grids: + app_admin_supplier: + # ... + actions: + main: + create: + type: create + item: + update: + type: update + delete: + type: delete +``` +{% endcode %} {% endtab %} {% endtabs %} From 06a529cdf525acf4b9bc00393735973d40d55157 Mon Sep 17 00:00:00 2001 From: Estelle Gaits Date: Thu, 16 Oct 2025 13:15:21 +0000 Subject: [PATCH 27/53] GITBOOK-28: Estelle's Oct 16 changes - replace PHP legacy with "config file" for clarity in tabs --- docs/grid/your_first_grid.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/grid/your_first_grid.md b/docs/grid/your_first_grid.md index f797060b6..4f14c22c9 100644 --- a/docs/grid/your_first_grid.md +++ b/docs/grid/your_first_grid.md @@ -42,7 +42,7 @@ This command will generate a grid with a field entry for each of your PHP class Now we can configure our first grid: {% tabs %} -{% tab title="PHP service" %} +{% tab title="PHP (recommended)" %} {% code title="/src/Grid/AdminSupplierGrid.php" lineNumbers="true" %} ```php Date: Thu, 16 Oct 2025 13:47:37 +0200 Subject: [PATCH 28/53] [Docs] Add Resource Lifecycle --- docs/SUMMARY.md | 1 + docs/resource/index.md | 1 + docs/resource/lifecycle.md | 63 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 docs/resource/lifecycle.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index a5584df97..90cf34719 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -32,6 +32,7 @@ * [Resource Bundle documentation](resource/index.md) * [Installation](resource/installation.md) + * [Resource Lifecycle](resource/lifecycle.md) * [Create new resource](resource/create_new_resource.md) * [Configure your resource](resource/configure_your_resource.md) * [Configure your operations](resource/configure_your_operations.md) diff --git a/docs/resource/index.md b/docs/resource/index.md index d0accdee1..be0ffabe7 100644 --- a/docs/resource/index.md +++ b/docs/resource/index.md @@ -19,6 +19,7 @@ So far we support: * [Installation](installation.md) # New documentation +* [Resource lifecycle](lifecycle.md) * [Create a new resource](create_new_resource.md) * [Configure your resource](configure_your_resource.md) * [Configure your operations](configure_your_operations.md) diff --git a/docs/resource/lifecycle.md b/docs/resource/lifecycle.md new file mode 100644 index 000000000..9ac98e242 --- /dev/null +++ b/docs/resource/lifecycle.md @@ -0,0 +1,63 @@ +# Resource Lifecycle + +Each operation on a resource follows a well-defined lifecycle. +This flow ensures clear separation of concerns between reading data, applying business logic, and producing the final response. + +```mermaid +flowchart TD + A[Request] --> B[Routing & Metadata] + B --> C[Provider] + C --> D[Processor] + D --> E[Responder] + E --> F[Response] + + subgraph ProviderPhase [Provider Phase] + direction LR + C1[Load existing resource] + C2[Create new instance] + C3[Hydrate resource from request] + C4[Validate resource] + end + + subgraph ProcessorPhase [Processor Phase] + direction LR + D1[Apply business logic] + D2[Persist resource] + D3[Dispatch domain events] + end + + subgraph ResponderPhase [Responder Phase] + direction LR + E1[Render template or redirect] + E2[Return HTTP response] + end + + C --> ProviderPhase + D --> ProcessorPhase + E --> ResponderPhase + + style ProviderPhase fill:#f4f8ff,stroke:#93c5fd,stroke-width:1px + style ProcessorPhase fill:#fef9c3,stroke:#facc15,stroke-width:1px + style ResponderPhase fill:#fef2f2,stroke:#f87171,stroke-width:1px +``` + +## Step-by-step explanation + +### Routing & Metadata +The request is matched to a `Resource` and an `Operation` using metadata collected from attributes (eg `#[AsResource]`). +This step defines which provider, processor, and responder should handle the request. + +### Provider Phase +The provider is responsible for loading or creating the resource object, hydrating it from the request, and validating it before any business logic is applied. + +**Example:** load from Doctrine, create a new instance, populate from request data, validate, or fetch from an external API. + +### Processor Phase +The processor applies the business logic of the operation. + +**Example:** persist the entity, execute domain services, or dispatch events. + +You can use the default Doctrine processor, or implement your own for DDD use cases. + +### Responder Phase +The responder produces the final output — it can render a Twig template or redirect to another route. From 7c65d9705c7b75158864ce8cc825b80e0d98ead4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Thu, 16 Oct 2025 10:06:35 +0200 Subject: [PATCH 29/53] [Docs] Rework Sylius resource introduction --- docs/resource/index.md | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/docs/resource/index.md b/docs/resource/index.md index be0ffabe7..5aa97e6f5 100644 --- a/docs/resource/index.md +++ b/docs/resource/index.md @@ -1,18 +1,27 @@ # SyliusResourceBundle -There are plenty of things you need to handle for every single Resource in your web application. +The **Sylius Resource Bundle** provides a powerful and extensible foundation for exposing your **business resources** (entities, aggregates, etc.) in a declarative way. +Rather than generating controllers or relying on rigid admin generators, it offers a flexible architecture that lets you focus on your domain model while the bundle handles the boilerplate. -Several "Admin Generators" are available for Symfony, but we needed something really simple, that will allow us to have reusable controllers -but preserve the performance and standard Symfony workflow. We did not want to generate any code or write "Admin" class definitions in PHP. -The big goal was to have exactly the same workflow as with writing controllers manually but without actually creating them! +A *Resource* is any business object you want to expose — for example, a `Product`, `Order`, or `UserProfile`. +You declare a resource by implementing the `ResourceInterface` and annotating it with the `#[AsResource(...)]` attribute. +This declarative metadata system automatically wires routes, controllers, and templates for your resource, while keeping everything fully configurable. -Another idea was not to limit ourselves to a single persistence backend. -``Resource`` component provides us with generic purpose persistence services and you can use this bundle with multiple persistence backends. -So far we support: +Each resource can define a set of **operations** — actions that can be performed on it. +Typical operations include `index`, `show`, `create`, `update`, and `delete`, but you can also define custom domain-specific ones. +The bundle orchestrates each operation through a well-defined lifecycle involving **providers**, **processors**, and **responders**: -* Doctrine ORM -* Doctrine MongoDB ODM -* Doctrine PHPCR ODM +- **Providers** are responsible for loading or creating the resource object and **validating it** (ensuring the object is consistent before any business logic is applied). + - Example: load from Doctrine, create a new instance, hydrate from request data, validate the object, or fetch from an external API. +- **Processors** handle the business logic or persistence layer (e.g. saving, executing domain services, dispatching events). +- **Responders** produce the final response (e.g. rendering a template). + +This architecture allows you to use the bundle in two main ways: + +- **Rapid Application Mode (RAD)** – perfect for quick CRUD setup with Doctrine ORM. You define your entity, mark it as a resource, and everything just works. +- **DDD / Advanced Mode** – where you control how data is provided and processed by writing your own providers and processors. This approach fits perfectly into Domain-Driven Design applications. + +In short, the Sylius Resource Bundle is both **declarative** and **extensible** — define your resources and their operations, and let the framework handle the rest, while still giving you full control over the domain logic when you need it. ## Resource system for Symfony applications. From a9ca0dc40e006d17b3c0fa9584401a22e0ba348e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Mon, 20 Oct 2025 14:35:29 +0200 Subject: [PATCH 30/53] Apply suggestions from code review Co-authored-by: Estelle Gaits <74190794+stlgaits@users.noreply.github.com> --- docs/resource/index.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/resource/index.md b/docs/resource/index.md index 5aa97e6f5..a262b62c9 100644 --- a/docs/resource/index.md +++ b/docs/resource/index.md @@ -4,11 +4,9 @@ The **Sylius Resource Bundle** provides a powerful and extensible foundation for Rather than generating controllers or relying on rigid admin generators, it offers a flexible architecture that lets you focus on your domain model while the bundle handles the boilerplate. A *Resource* is any business object you want to expose — for example, a `Product`, `Order`, or `UserProfile`. -You declare a resource by implementing the `ResourceInterface` and annotating it with the `#[AsResource(...)]` attribute. -This declarative metadata system automatically wires routes, controllers, and templates for your resource, while keeping everything fully configurable. Each resource can define a set of **operations** — actions that can be performed on it. -Typical operations include `index`, `show`, `create`, `update`, and `delete`, but you can also define custom domain-specific ones. +Typical operations include `index`, `show`, `create`, `update`, and `delete`, but you can also define custom, domain-specific operations too. The bundle orchestrates each operation through a well-defined lifecycle involving **providers**, **processors**, and **responders**: - **Providers** are responsible for loading or creating the resource object and **validating it** (ensuring the object is consistent before any business logic is applied). @@ -19,7 +17,7 @@ The bundle orchestrates each operation through a well-defined lifecycle involvin This architecture allows you to use the bundle in two main ways: - **Rapid Application Mode (RAD)** – perfect for quick CRUD setup with Doctrine ORM. You define your entity, mark it as a resource, and everything just works. -- **DDD / Advanced Mode** – where you control how data is provided and processed by writing your own providers and processors. This approach fits perfectly into Domain-Driven Design applications. +- **Domain-Driven Design / Advanced Mode** – where you control how data is provided and processed by writing your own providers and processors. In short, the Sylius Resource Bundle is both **declarative** and **extensible** — define your resources and their operations, and let the framework handle the rest, while still giving you full control over the domain logic when you need it. From 15f041eb33d8f94216d22a9a754085256a8aa8f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Fri, 24 Oct 2025 09:15:01 +0200 Subject: [PATCH 31/53] [Docs] Collapse menu items --- docs/cookbook/admin_panel/menu.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/cookbook/admin_panel/menu.md b/docs/cookbook/admin_panel/menu.md index 12c32d210..c14e306be 100644 --- a/docs/cookbook/admin_panel/menu.md +++ b/docs/cookbook/admin_panel/menu.md @@ -88,3 +88,18 @@ final readonly class MenuBuilder implements MenuBuilderInterface } } ``` + +{% hint style="success" %} +**🧠 Collapse your custom menu by default** + +It's possible to expand your parent menu category on page load by default. For that, you have to set the `setExtra` attribute like this: + +```php +$newSubmenu = $menu + ->addChild('new') + ->setLabel('Custom Admin Menu') + ->setExtra('always_open', true); +``` + +However, ensure that you set the attribute in the parent menu, not in one of the child menu items. +{% endhint %} From 3c6198a62d7a8a1c5e2d53835c68ddd3556dc4dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Fri, 24 Oct 2025 09:55:02 +0200 Subject: [PATCH 32/53] Update docs/cookbook/admin_panel/menu.md Co-authored-by: Simon Krull --- docs/cookbook/admin_panel/menu.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/cookbook/admin_panel/menu.md b/docs/cookbook/admin_panel/menu.md index c14e306be..65b0aed62 100644 --- a/docs/cookbook/admin_panel/menu.md +++ b/docs/cookbook/admin_panel/menu.md @@ -95,9 +95,10 @@ final readonly class MenuBuilder implements MenuBuilderInterface It's possible to expand your parent menu category on page load by default. For that, you have to set the `setExtra` attribute like this: ```php -$newSubmenu = $menu - ->addChild('new') - ->setLabel('Custom Admin Menu') +$library = $menu + ->addChild('library') + ->setLabel('app.ui.library') + ->setLabelAttribute('icon', 'tabler:books') ->setExtra('always_open', true); ``` From 7f1cceb13dbd5bede2b18c2d5b38a8a22c09c6bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Wed, 22 Oct 2025 10:02:50 +0200 Subject: [PATCH 33/53] [Docs][BootstrapAdminUi] Copy usage with Sylius resource package --- docs/bootstrap-admin-ui/getting-started.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/bootstrap-admin-ui/getting-started.md b/docs/bootstrap-admin-ui/getting-started.md index 1cc1d1df6..0bf578283 100644 --- a/docs/bootstrap-admin-ui/getting-started.md +++ b/docs/bootstrap-admin-ui/getting-started.md @@ -21,6 +21,27 @@ CRUD templates are split into configurable blocks. You can add new blocks, disable existing ones, or reorder them using the [TwigHooks package](../twig-hooks/getting-started.md). +*Usage with Sylius Resource package* + +{% code title="src/Entity/Speaker.php" lineNumbers="true" %} +```php +namespace App\Entity; + +use Sylius\Resource\Metadata\AsResource; +use Sylius\Resource\Model\ResourceInterface; + +#[AsResource( + // We still use the Sylius admin ui templates dir. + templatesDir: '@SyliusAdminUi/crud', +)] +class Speaker implements ResourceInterface +{ + // ... +} + +``` +{% endcode %} + ### Create This package sets up the template content needed to create a new resource. From 8800868cad8cd942967d043df98172379bd5337d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Fri, 24 Oct 2025 10:05:08 +0200 Subject: [PATCH 34/53] Update docs/bootstrap-admin-ui/getting-started.md Co-authored-by: Estelle Gaits <74190794+stlgaits@users.noreply.github.com> --- docs/bootstrap-admin-ui/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/bootstrap-admin-ui/getting-started.md b/docs/bootstrap-admin-ui/getting-started.md index 0bf578283..0e8925f47 100644 --- a/docs/bootstrap-admin-ui/getting-started.md +++ b/docs/bootstrap-admin-ui/getting-started.md @@ -21,7 +21,7 @@ CRUD templates are split into configurable blocks. You can add new blocks, disable existing ones, or reorder them using the [TwigHooks package](../twig-hooks/getting-started.md). -*Usage with Sylius Resource package* +### Usage with the Sylius Resource package {% code title="src/Entity/Speaker.php" lineNumbers="true" %} ```php From f2d534e9351bb5193c01ad7dd7bf553738dddb8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Fri, 24 Oct 2025 15:38:27 +0200 Subject: [PATCH 35/53] [BootstrapAdminUi] add a footer --- .../config/app/twig_hooks/common/create.php | 4 ++ .../config/app/twig_hooks/common/index.php | 4 ++ .../config/app/twig_hooks/common/show.php | 4 ++ .../config/app/twig_hooks/common/update.php | 4 ++ .../crud/common/content/footer.html.twig | 42 +++++++++++++++++++ .../translations/messages.de.yaml | 3 ++ .../translations/messages.en.yaml | 7 ++++ .../translations/messages.es.yaml | 3 ++ .../translations/messages.fr.yaml | 7 ++++ .../translations/messages.nl.yaml | 7 ++++ .../translations/messages.pl.yaml | 7 ++++ 11 files changed, 92 insertions(+) create mode 100644 src/BootstrapAdminUi/templates/shared/crud/common/content/footer.html.twig diff --git a/src/BootstrapAdminUi/config/app/twig_hooks/common/create.php b/src/BootstrapAdminUi/config/app/twig_hooks/common/create.php index 4ae3262be..7ebb509e3 100644 --- a/src/BootstrapAdminUi/config/app/twig_hooks/common/create.php +++ b/src/BootstrapAdminUi/config/app/twig_hooks/common/create.php @@ -41,6 +41,10 @@ 'content' => [ 'template' => '@SyliusBootstrapAdminUi/shared/crud/common/content.html.twig', ], + 'footer' => [ + 'template' => '@SyliusBootstrapAdminUi/shared/crud/common/content/footer.html.twig', + 'priority' => -100, + ], ], 'sylius_admin.common.create.content' => [ diff --git a/src/BootstrapAdminUi/config/app/twig_hooks/common/index.php b/src/BootstrapAdminUi/config/app/twig_hooks/common/index.php index 515b25bdf..ba393a3b1 100644 --- a/src/BootstrapAdminUi/config/app/twig_hooks/common/index.php +++ b/src/BootstrapAdminUi/config/app/twig_hooks/common/index.php @@ -53,6 +53,10 @@ 'grid' => [ 'template' => '@SyliusBootstrapAdminUi/shared/crud/index/content/grid.html.twig', ], + 'footer' => [ + 'template' => '@SyliusBootstrapAdminUi/shared/crud/common/content/footer.html.twig', + 'priority' => -100, + ], ], 'sylius_admin.common.index.content.header' => [ diff --git a/src/BootstrapAdminUi/config/app/twig_hooks/common/show.php b/src/BootstrapAdminUi/config/app/twig_hooks/common/show.php index 8f52883ef..4bb26885e 100644 --- a/src/BootstrapAdminUi/config/app/twig_hooks/common/show.php +++ b/src/BootstrapAdminUi/config/app/twig_hooks/common/show.php @@ -41,6 +41,10 @@ 'content' => [ 'template' => '@SyliusBootstrapAdminUi/shared/crud/common/content.html.twig', ], + 'footer' => [ + 'template' => '@SyliusBootstrapAdminUi/shared/crud/common/content/footer.html.twig', + 'priority' => -100, + ], ], 'sylius_admin.common.show.content' => [ diff --git a/src/BootstrapAdminUi/config/app/twig_hooks/common/update.php b/src/BootstrapAdminUi/config/app/twig_hooks/common/update.php index 5ea178680..efa2a2384 100644 --- a/src/BootstrapAdminUi/config/app/twig_hooks/common/update.php +++ b/src/BootstrapAdminUi/config/app/twig_hooks/common/update.php @@ -41,6 +41,10 @@ 'content' => [ 'template' => '@SyliusBootstrapAdminUi/shared/crud/common/content.html.twig', ], + 'footer' => [ + 'template' => '@SyliusBootstrapAdminUi/shared/crud/common/content/footer.html.twig', + 'priority' => -100, + ], ], 'sylius_admin.common.update.content' => [ diff --git a/src/BootstrapAdminUi/templates/shared/crud/common/content/footer.html.twig b/src/BootstrapAdminUi/templates/shared/crud/common/content/footer.html.twig new file mode 100644 index 000000000..cc6afca39 --- /dev/null +++ b/src/BootstrapAdminUi/templates/shared/crud/common/content/footer.html.twig @@ -0,0 +1,42 @@ + diff --git a/src/UiTranslations/translations/messages.de.yaml b/src/UiTranslations/translations/messages.de.yaml index d07405c5b..2e0df115a 100644 --- a/src/UiTranslations/translations/messages.de.yaml +++ b/src/UiTranslations/translations/messages.de.yaml @@ -16,6 +16,7 @@ sylius: dashboard: 'Dashboard' delete: 'Löschen' details: 'Details' + documentation: 'Dokumentation' edit: 'Bearbeiten' empty: 'Leer' equal: 'Gleich' @@ -38,10 +39,12 @@ sylius: not_in: 'Nicht in' pagination: number_of_results: 'Zeigt %from% - %to% von %total% Einträgen' + report_it: 'Hier melden' reset: 'Zurücksetzen' search: 'Suche' search_menu: 'Menü durchsuchen' search_products: 'Artikel suchen' + see_issue: 'Fehler gefunden' show: 'Anzeigen' starts_with: 'Beginnt mit' success: 'Erfolgreich' diff --git a/src/UiTranslations/translations/messages.en.yaml b/src/UiTranslations/translations/messages.en.yaml index 653f134d0..5bf9ddee8 100644 --- a/src/UiTranslations/translations/messages.en.yaml +++ b/src/UiTranslations/translations/messages.en.yaml @@ -9,14 +9,17 @@ sylius: add_new_entry: Add a new entry administration_panel_login: 'Administration panel login' all: All + all_rights_reserved: 'All rights reserved' are_your_sure_you_want_to_perform_this_action: 'Are you sure you want to perform this action?' cancel: Cancel + copyright: Copyright contains: Contains create: Create dashboard: Dashboard date_filter: '%label% | %altLabel%' delete: Delete details: Details + documentation: 'Documentation' edit: Edit empty: 'Empty' equal: 'Equal' @@ -28,6 +31,7 @@ sylius: hello: Hello in: 'In' info: Info + license: 'License' login: Login login_to_your_account: 'Login to your account' logout: Logout @@ -41,10 +45,13 @@ sylius: not_in: 'Not in' pagination: number_of_results: 'Showing %from% to %to% of %total% entries' + report_it: 'Report it' reset: Reset search: Search search_menu: 'Search menu' + see_issue: 'See an issue' show: Show + source_code: 'Source code' starts_with: 'Starts with' success: Success this_form_contains_errors: 'This form contains errors.' diff --git a/src/UiTranslations/translations/messages.es.yaml b/src/UiTranslations/translations/messages.es.yaml index 9affb2968..8db99595a 100644 --- a/src/UiTranslations/translations/messages.es.yaml +++ b/src/UiTranslations/translations/messages.es.yaml @@ -15,6 +15,7 @@ sylius: dashboard: 'Panel general' delete: 'Eliminar' details: 'Detalles' + documentation: 'Documentación' edit: 'Editar' empty: 'Vacío' ends_with: 'Termina en' @@ -35,8 +36,10 @@ sylius: not_empty: 'No está vacío' not_equal: 'No igual' not_in: 'No en' + report_it: 'Informar' reset: 'Restablecer' search: 'Búsqueda' + see_issue: 'Ver incidencia' show: 'Mostrar' starts_with: 'Comienza por' success: 'Operación realizada correctamente' diff --git a/src/UiTranslations/translations/messages.fr.yaml b/src/UiTranslations/translations/messages.fr.yaml index 0806ab64c..88743beab 100644 --- a/src/UiTranslations/translations/messages.fr.yaml +++ b/src/UiTranslations/translations/messages.fr.yaml @@ -9,14 +9,17 @@ sylius: add_new_entry: 'Nouvel item' administration_panel_login: 'Connexion à l''espace d''administration' all: 'Tous' + all_rights_reserved: Tous droits réservés are_your_sure_you_want_to_perform_this_action: 'Êtes-vous sûr(e) de vouloir effectuer cette action ?' cancel: 'Annuler' + copyright: Copyright contains: 'Contient' create: 'Créer' dashboard: 'Tableau de bord' date_filter: '%label% | %altLabel%' delete: 'Supprimer' details: 'Détails' + documentation: Documentation edit: 'Modifier' empty: 'Vide' ends_with: 'Se termine par' @@ -28,6 +31,7 @@ sylius: hello: 'Bonjour' in: 'Dans' info: 'Info' + license: Licence login: 'Connexion' login_to_your_account: 'Connexion à votre compte' logout: 'Déconnexion' @@ -41,10 +45,13 @@ sylius: not_in: 'Pas dans' pagination: number_of_results: 'Affiche de %from% à %to% sur %total% items' + report_it: Signalez le reset: 'Réinitialiser' search: 'Rechercher' search_menu: 'Recherche menu' + see_issue: Vous avez trouvé un bug show: 'Afficher' + source_code: Code source starts_with: 'Commence par' success: 'Succès' this_form_contains_errors: 'Ce formulaire contient des erreurs.' diff --git a/src/UiTranslations/translations/messages.nl.yaml b/src/UiTranslations/translations/messages.nl.yaml index 7a9141b36..f3d6d92f9 100644 --- a/src/UiTranslations/translations/messages.nl.yaml +++ b/src/UiTranslations/translations/messages.nl.yaml @@ -9,14 +9,17 @@ sylius: add_new_entry: Nieuw item toevoegen administration_panel_login: 'Administratiepaneel login' all: Alles + all_rights_reserved: 'Alle rechten voorbehouden' are_your_sure_you_want_to_perform_this_action: 'Weet je zeker dat je daze actie wilt uitvoeren?' cancel: Annuleren contains: Bevat + copyright: 'Copyright' create: Creëer dashboard: Dashboard date_filter: '%label% | %altLabel%' delete: Verwijderen details: Details + documentation: 'Documentatie' edit: Bewerken empty: 'Leeg' equal: 'Gelijk aan' @@ -28,6 +31,7 @@ sylius: hello: Hallo in: 'In' info: Info + license: 'Licentie' login: Inloggen login_to_your_account: 'Inloggen op jouw account' logout: Uitloggen @@ -41,9 +45,12 @@ sylius: not_in: 'Niet in' pagination: number_of_results: 'Toont %from% tot %to% van %total% resultaten' + report_it: 'Meld het' reset: Resetten search: Zoeken search_menu: 'Zoek in menu' + see_issue: 'Bekijk probleem' + source_code: 'Broncode' show: Tonen starts_with: 'Begint met' success: Succes diff --git a/src/UiTranslations/translations/messages.pl.yaml b/src/UiTranslations/translations/messages.pl.yaml index 778fd892e..5fa3ac0e2 100644 --- a/src/UiTranslations/translations/messages.pl.yaml +++ b/src/UiTranslations/translations/messages.pl.yaml @@ -8,13 +8,16 @@ sylius: actions: 'Akcje' administration_panel_login: 'Logowanie do panelu administracyjnego' all: 'Wszystko' + all_rights_reserved: 'Wszystkie prawa zastrzeżone' are_your_sure_you_want_to_perform_this_action: 'Czy na pewno chcesz wykonać tę akcję?' cancel: 'Anuluj' + copyright: 'Prawa autorskie' contains: 'Zawiera' create: 'Utwórz' dashboard: 'Panel' delete: 'Usuń' details: 'Szczegóły' + documentation: 'Dokumentacja' edit: 'Edytuj' empty: 'Brak' ends_with: 'Kończy się na' @@ -26,6 +29,7 @@ sylius: hello: 'Witaj' in: 'W' info: 'Info' + license: 'Licencja' login: 'Zaloguj' login_to_your_account: 'Zaloguj się do swojego konta' logout: 'Wyloguj' @@ -36,9 +40,12 @@ sylius: not_empty: 'Nie pusty' not_equal: 'Różne od' not_in: 'Nie w' + report_it: 'Zgłoś' reset: 'Resetuj' search: 'Szukaj' + see_issue: 'Zobacz problem' show: 'Pokaż' + source_code: 'Kod źródłowy' starts_with: 'Rozpoczyna się od' success: 'Sukces' this_form_contains_errors: 'Ten formularz zawiera błędy.' From d0eecc3b4b83ccd268de5b6b3390336c3ed78ba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Fri, 26 Sep 2025 10:24:06 +0200 Subject: [PATCH 36/53] Prepare 0.10 --- src/AdminUi/composer.json | 4 ++-- src/BootstrapAdminUi/composer.json | 8 ++++---- src/TwigExtra/composer.json | 2 +- src/TwigHooks/composer.json | 2 +- src/UiTranslations/composer.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/AdminUi/composer.json b/src/AdminUi/composer.json index de3388d47..cab1dfcf5 100644 --- a/src/AdminUi/composer.json +++ b/src/AdminUi/composer.json @@ -5,7 +5,7 @@ "require": { "php": "^8.1", "knplabs/knp-menu-bundle": "^3.0", - "sylius/twig-hooks": "^0.9", + "sylius/twig-hooks": "^0.10", "symfony/http-kernel": "^6.4 || ^7.0", "symfony/security-bundle": "^6.4 || ^7.0", "symfony/security-http": "^6.4 || ^7.0", @@ -54,7 +54,7 @@ "require": "7.1.*" }, "branch-alias": { - "dev-main": "0.9-dev" + "dev-main": "0.10-dev" } }, "config": { diff --git a/src/BootstrapAdminUi/composer.json b/src/BootstrapAdminUi/composer.json index fcf8c726f..fbb805357 100644 --- a/src/BootstrapAdminUi/composer.json +++ b/src/BootstrapAdminUi/composer.json @@ -5,11 +5,11 @@ "require": { "php": "^8.1", "pagerfanta/twig": "^4.6", - "sylius/admin-ui": "^0.9", + "sylius/admin-ui": "^0.10", "sylius/grid-bundle": "^1.13", "sylius/resource-bundle": "^1.11", - "sylius/twig-extra": "^0.9", - "sylius/twig-hooks": "^0.9", + "sylius/twig-extra": "^0.10", + "sylius/twig-hooks": "^0.10", "symfony/asset": "^6.4 || ^7.0", "symfony/http-kernel": "^6.4 || ^7.0", "symfony/http-client": "^6.4 || ^7.0", @@ -59,7 +59,7 @@ "require": "7.1.*" }, "branch-alias": { - "dev-main": "0.9-dev" + "dev-main": "0.10-dev" } }, "minimum-stability": "dev", diff --git a/src/TwigExtra/composer.json b/src/TwigExtra/composer.json index 0f5fb0492..4f0878396 100644 --- a/src/TwigExtra/composer.json +++ b/src/TwigExtra/composer.json @@ -55,7 +55,7 @@ "require": "7.1.*" }, "branch-alias": { - "dev-main": "0.9-dev" + "dev-main": "0.10-dev" } } } diff --git a/src/TwigHooks/composer.json b/src/TwigHooks/composer.json index c89dd0ca0..f617c945a 100644 --- a/src/TwigHooks/composer.json +++ b/src/TwigHooks/composer.json @@ -66,7 +66,7 @@ "require": "7.1.*" }, "branch-alias": { - "dev-main": "0.9-dev" + "dev-main": "0.10-dev" } } } diff --git a/src/UiTranslations/composer.json b/src/UiTranslations/composer.json index 1f395a836..d44e05a9e 100644 --- a/src/UiTranslations/composer.json +++ b/src/UiTranslations/composer.json @@ -40,7 +40,7 @@ "require": "7.1.*" }, "branch-alias": { - "dev-main": "0.9-dev" + "dev-main": "0.10-dev" } } } From ad167dc99c33f16c2a47923a0e35727a651b4126 Mon Sep 17 00:00:00 2001 From: Estelle Gaits Date: Wed, 29 Oct 2025 10:56:10 +0000 Subject: [PATCH 37/53] GITBOOK-29: Estelle's Oct 29 changes - update code examples with attributes in cookbook --- docs/cookbook/admin_panel/grids.md | 134 +++++++++++------------------ 1 file changed, 50 insertions(+), 84 deletions(-) diff --git a/docs/cookbook/admin_panel/grids.md b/docs/cookbook/admin_panel/grids.md index f69ce94ff..7a455dbad 100644 --- a/docs/cookbook/admin_panel/grids.md +++ b/docs/cookbook/admin_panel/grids.md @@ -1,18 +1,10 @@ # Customizing your grids -
- -
Overview of an admin dashboard
- -
- -
+
Overview of an admin dashboard
Based on the grid generated by default, our goal here is to obtain a nicely customized grid with autocomplete filters and more! -
Overview of an admin dashboard
- -
+
Overview of an admin dashboard
Let's imagine we have the following grid. @@ -34,21 +26,20 @@ use Sylius\Bundle\GridBundle\Builder\Field\DateTimeField; use Sylius\Bundle\GridBundle\Builder\Field\StringField; use Sylius\Bundle\GridBundle\Builder\GridBuilderInterface; use Sylius\Bundle\GridBundle\Grid\AbstractGrid; -use Sylius\Bundle\GridBundle\Grid\ResourceAwareGridInterface; +use Sylius\Component\Grid\Attribute\AsGrid; -final class TalkGrid extends AbstractGrid implements ResourceAwareGridInterface +#[AsGrid( + name: 'app_talk', + resourceClass: Talk::class, +)] +final class TalkGrid extends AbstractGrid { public function __construct() { // TODO inject services if required } - public static function getName(): string - { - return 'app_talk'; - } - - public function buildGrid(GridBuilderInterface $gridBuilder): void + public function __invoke(GridBuilderInterface $gridBuilder): void { $gridBuilder // see https://github.com/Sylius/SyliusGridBundle/blob/master/docs/field_types.md @@ -95,22 +86,13 @@ final class TalkGrid extends AbstractGrid implements ResourceAwareGridInterface ) ; } - - public function getResourceClass(): string - { - return Talk::class; - } } ``` -{% endcode %} +{% endcode %} ## Fields -
- -
Overview of an admin dashboard
- -
+
Overview of an admin dashboard
Let's clean up our grid and remove unnecessary fields. @@ -132,16 +114,15 @@ use Sylius\Bundle\GridBundle\Builder\Field\DateTimeField; use Sylius\Bundle\GridBundle\Builder\Field\StringField; use Sylius\Bundle\GridBundle\Builder\GridBuilderInterface; use Sylius\Bundle\GridBundle\Grid\AbstractGrid; -use Sylius\Bundle\GridBundle\Grid\ResourceAwareGridInterface; +use Sylius\Component\Grid\Attribute\AsGrid; -final class TalkGrid extends AbstractGrid implements ResourceAwareGridInterface +#[AsGrid( + name: 'app_talk', + resourceClass: Talk::class, +)] +final class TalkGrid extends AbstractGrid { - public static function getName(): string - { - return 'app_talk'; - } - - public function buildGrid(GridBuilderInterface $gridBuilder): void + public function __invoke(GridBuilderInterface $gridBuilder): void { $gridBuilder ->addField( @@ -172,30 +153,17 @@ final class TalkGrid extends AbstractGrid implements ResourceAwareGridInterface ) ; } - - public function getResourceClass(): string - { - return Talk::class; - } } ``` {% endcode %} -We have removed the "description", "endsAt" and "track" grid fields. +We have removed the `description`, `endsAt` and `track` grid fields. -
- -
Overview of an admin dashboard
- -
+
Overview of an admin dashboard
## Adding the speaker avatar using Twig field -
- -
Overview of an admin dashboard
- -
+
Overview of an admin dashboard
Now, let's add the speaker avatar into our talk grid. @@ -207,14 +175,18 @@ namespace App\Grid; use Sylius\Bundle\GridBundle\Builder\Field\TwigField; use Sylius\Bundle\GridBundle\Builder\GridBuilderInterface; use Sylius\Bundle\GridBundle\Grid\AbstractGrid; -use Sylius\Bundle\GridBundle\Grid\ResourceAwareGridInterface; +use Sylius\Component\Grid\Attribute\AsGrid; // ... -final class TalkGrid extends AbstractGrid implements ResourceAwareGridInterface +#[AsGrid( + name: 'app_talk', + resourceClass: Talk::class, +)] +final class TalkGrid extends AbstractGrid { // ... - public function buildGrid(GridBuilderInterface $gridBuilder): void + public function __invoke(GridBuilderInterface $gridBuilder): void { $gridBuilder ->addField( @@ -230,22 +202,17 @@ final class TalkGrid extends AbstractGrid implements ResourceAwareGridInterface } ``` -templates/talk/grid/speaker_avatar.html.twig -{% import '@SyliusBootstrapAdminUi/shared/helper/avatar.html.twig' as avatar %} - -{% set avatar_path = data.avatar.path is defined ? vich_uploader_asset(data.avatar) : null %} - +{% code title="templates/talk/grid/speaker_avatar.html.twig" lineNumbers="true" %} +```php {{ avatar.default(avatar_path, 'img-thumbnail') }} +``` +{% endcode %} ## Filters ### Adding an autocomplete filter -
- -
Overview of an admin dashboard
- -
+
Overview of an admin dashboard
We'd like to filter our talks by a specific speaker. @@ -296,11 +263,16 @@ declare(strict_types=1); namespace App\Grid\Filter; use App\Form\SpeakerAutocompleteType; +use Sylius\Component\Grid\Attribute\AsFilter; use Sylius\Component\Grid\Data\DataSourceInterface; use Sylius\Component\Grid\Filter\EntityFilter; -use Sylius\Component\Grid\Filtering\ConfigurableFilterInterface; +use Sylius\Component\Grid\Filtering\FilterInterface; -final class SpeakerFilter implements ConfigurableFilterInterface +#[AsFilter( + formType: SpeakerAutocompleteType::class, + template: '@SyliusBootstrapAdminUi/shared/grid/filter/select.html.twig', +)] +final class SpeakerFilter implements FilterInterface { public function __construct( private readonly EntityFilter $entityFilter, @@ -312,21 +284,11 @@ final class SpeakerFilter implements ConfigurableFilterInterface // We simply reuse the logic of the built-in EntityFilter provided by the Sylius Grid package. $this->entityFilter->apply($dataSource, $name, $data, $options); } - - public static function getFormType(): string - { - return SpeakerAutocompleteType::class; - } - - public static function getType(): string - { - return self::class; - } } ``` {% endcode %} -Then, we add our SpeakerFilter to our grid. +Then, we add our `SpeakerFilter` to our grid. {% code title="src/Grid/TalkGrid.php" lineNumbers="true" %} ```php @@ -341,22 +303,26 @@ use Sylius\Bundle\GridBundle\Builder\Filter\Filter; use Sylius\Bundle\GridBundle\Builder\GridBuilderInterface; use Sylius\Bundle\GridBundle\Grid\AbstractGrid; use Sylius\Bundle\GridBundle\Grid\ResourceAwareGridInterface; +use Sylius\Component\Grid\Attribute\AsGrid; // ... -final class TalkGrid extends AbstractGrid implements ResourceAwareGridInterface +#[AsGrid( + name: 'app_talk', + resourceClass: Talk::class, +)] +final class TalkGrid extends AbstractGrid { // ... - public function buildGrid(GridBuilderInterface $gridBuilder): void + public function __invoke(GridBuilderInterface $gridBuilder): void { $gridBuilder ->addFilter( Filter::create(name: 'speaker', type: SpeakerFilter::class) ->setLabel('app.ui.speaker') - ->setOptions(['fields' => ['speaker.id']]), // TODO, we need to check if this is necessary - ) + ); + // ... - ; } // ... From 0ca573a6a232eea47edcc1cda40f171b6503ac3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Wed, 29 Oct 2025 12:04:49 +0100 Subject: [PATCH 38/53] =?UTF-8?q?[Docs]=C2=A0Fix=20speaker=20autocomplete?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/cookbook/admin_panel/grids.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/cookbook/admin_panel/grids.md b/docs/cookbook/admin_panel/grids.md index 7a455dbad..bf8e10819 100644 --- a/docs/cookbook/admin_panel/grids.md +++ b/docs/cookbook/admin_panel/grids.md @@ -320,6 +320,7 @@ final class TalkGrid extends AbstractGrid ->addFilter( Filter::create(name: 'speaker', type: SpeakerFilter::class) ->setLabel('app.ui.speaker') + ->setOptions(['fields' => ['speaker.id']]) ); // ... From f8d236e58648fc974edac1f1476b0ecba89ee0d2 Mon Sep 17 00:00:00 2001 From: Simon Krull Date: Sat, 1 Nov 2025 14:13:40 +0100 Subject: [PATCH 39/53] TASK: [AdminUi] add missing german translations --- translations/flashes.de.yaml | 3 +++ translations/messages.de.yaml | 44 +++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 translations/flashes.de.yaml create mode 100644 translations/messages.de.yaml diff --git a/translations/flashes.de.yaml b/translations/flashes.de.yaml new file mode 100644 index 000000000..aa73d64f9 --- /dev/null +++ b/translations/flashes.de.yaml @@ -0,0 +1,3 @@ +app: + conference: + create: Die Konferenz wurde erfolgreich erstellt. diff --git a/translations/messages.de.yaml b/translations/messages.de.yaml new file mode 100644 index 000000000..5e8a37503 --- /dev/null +++ b/translations/messages.de.yaml @@ -0,0 +1,44 @@ +app: + ui: + archival: Archivierung + author: Autor + author_name: Name des Autors + avatar: Avatar + biz: Unternehmen + book: Buch + books: Bücher + browsing_speakers: Referenten durchsuchen + company_name: Firmenname + conference: Konferenz + conferences: Konferenzen + configuration: Konfiguration + edit_conference: Konferenz bearbeiten + ends_at: Endet am + first_name: Vorname + last_name: Nachname + library: Bibliothek + managing_your_conferences: Ihre Konferenzen verwalten + managing_your_speakers: Ihre Referenten verwalten + name: Name + new_conference: Neue Konferenz + new_speakers: Neue Referenten + new_talks: Neue Vorträge + past_event: Vergangenes Ereignis + show_all: Alle anzeigen + show_talks: Vorträge anzeigen + speaker: Referent + speakers: Referenten + starts_at: Beginnt am + statistics: + day: Tag + lifetime: Gesamtzeitraum + month: Monat + week: Woche + 2weeks: 2 Wochen + year: Jahr + talk: Vortrag + talks: Vorträge + tech_one: Technik#1 + tech_two: Technik#2 + title: Titel + track: Themenbereich From 260204bdc0e023ab2398aabd480c17e0bd08f0f7 Mon Sep 17 00:00:00 2001 From: Simon Krull Date: Sat, 1 Nov 2025 14:20:13 +0100 Subject: [PATCH 40/53] TASK: add translations for german CH and german AT --- translations/flashes.de_AT.yaml | 3 +++ translations/flashes.de_CH.yaml | 3 +++ translations/messages.de_AT.yaml | 44 ++++++++++++++++++++++++++++++++ translations/messages.de_CH.yaml | 44 ++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+) create mode 100644 translations/flashes.de_AT.yaml create mode 100644 translations/flashes.de_CH.yaml create mode 100644 translations/messages.de_AT.yaml create mode 100644 translations/messages.de_CH.yaml diff --git a/translations/flashes.de_AT.yaml b/translations/flashes.de_AT.yaml new file mode 100644 index 000000000..aa73d64f9 --- /dev/null +++ b/translations/flashes.de_AT.yaml @@ -0,0 +1,3 @@ +app: + conference: + create: Die Konferenz wurde erfolgreich erstellt. diff --git a/translations/flashes.de_CH.yaml b/translations/flashes.de_CH.yaml new file mode 100644 index 000000000..aa73d64f9 --- /dev/null +++ b/translations/flashes.de_CH.yaml @@ -0,0 +1,3 @@ +app: + conference: + create: Die Konferenz wurde erfolgreich erstellt. diff --git a/translations/messages.de_AT.yaml b/translations/messages.de_AT.yaml new file mode 100644 index 000000000..5e8a37503 --- /dev/null +++ b/translations/messages.de_AT.yaml @@ -0,0 +1,44 @@ +app: + ui: + archival: Archivierung + author: Autor + author_name: Name des Autors + avatar: Avatar + biz: Unternehmen + book: Buch + books: Bücher + browsing_speakers: Referenten durchsuchen + company_name: Firmenname + conference: Konferenz + conferences: Konferenzen + configuration: Konfiguration + edit_conference: Konferenz bearbeiten + ends_at: Endet am + first_name: Vorname + last_name: Nachname + library: Bibliothek + managing_your_conferences: Ihre Konferenzen verwalten + managing_your_speakers: Ihre Referenten verwalten + name: Name + new_conference: Neue Konferenz + new_speakers: Neue Referenten + new_talks: Neue Vorträge + past_event: Vergangenes Ereignis + show_all: Alle anzeigen + show_talks: Vorträge anzeigen + speaker: Referent + speakers: Referenten + starts_at: Beginnt am + statistics: + day: Tag + lifetime: Gesamtzeitraum + month: Monat + week: Woche + 2weeks: 2 Wochen + year: Jahr + talk: Vortrag + talks: Vorträge + tech_one: Technik#1 + tech_two: Technik#2 + title: Titel + track: Themenbereich diff --git a/translations/messages.de_CH.yaml b/translations/messages.de_CH.yaml new file mode 100644 index 000000000..5e8a37503 --- /dev/null +++ b/translations/messages.de_CH.yaml @@ -0,0 +1,44 @@ +app: + ui: + archival: Archivierung + author: Autor + author_name: Name des Autors + avatar: Avatar + biz: Unternehmen + book: Buch + books: Bücher + browsing_speakers: Referenten durchsuchen + company_name: Firmenname + conference: Konferenz + conferences: Konferenzen + configuration: Konfiguration + edit_conference: Konferenz bearbeiten + ends_at: Endet am + first_name: Vorname + last_name: Nachname + library: Bibliothek + managing_your_conferences: Ihre Konferenzen verwalten + managing_your_speakers: Ihre Referenten verwalten + name: Name + new_conference: Neue Konferenz + new_speakers: Neue Referenten + new_talks: Neue Vorträge + past_event: Vergangenes Ereignis + show_all: Alle anzeigen + show_talks: Vorträge anzeigen + speaker: Referent + speakers: Referenten + starts_at: Beginnt am + statistics: + day: Tag + lifetime: Gesamtzeitraum + month: Monat + week: Woche + 2weeks: 2 Wochen + year: Jahr + talk: Vortrag + talks: Vorträge + tech_one: Technik#1 + tech_two: Technik#2 + title: Titel + track: Themenbereich From 8495e527d68b53d36388f495cd5fe107e1e1e360 Mon Sep 17 00:00:00 2001 From: Estelle Gaits Date: Wed, 5 Nov 2025 10:10:19 +0000 Subject: [PATCH 41/53] GITBOOK-31: Estelle's Nov 5 changes - Creating your first grid --- docs/.gitbook/assets/img_1.png | Bin 141963 -> 0 bytes .../assets/managing_speakers_subheader.png | Bin 15642 -> 0 bytes docs/.gitbook/assets/speakers_icon.png | Bin 12195 -> 0 bytes docs/grid/your_first_grid.md | 90 +++++++----------- 4 files changed, 37 insertions(+), 53 deletions(-) delete mode 100644 docs/.gitbook/assets/img_1.png delete mode 100644 docs/.gitbook/assets/managing_speakers_subheader.png delete mode 100644 docs/.gitbook/assets/speakers_icon.png diff --git a/docs/.gitbook/assets/img_1.png b/docs/.gitbook/assets/img_1.png deleted file mode 100644 index 0069f40bf228ce317ed62a20311a6d57093d9158..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 141963 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�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 diff --git a/docs/.gitbook/assets/managing_speakers_subheader.png b/docs/.gitbook/assets/managing_speakers_subheader.png deleted file mode 100644 index 5eac4e94cef3b6c69572f6c88a10ae77b0013a56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15642 zcmd6u1ydbO)3C7wm*6hJ-Q9w_yN6)GorAl(I~)ie++Bhn91ia8?#{>kJ%8ezs@<91 z>fNg8+1{P$z9vFhQ3@FW9{~aa0$D~{TonQWvgNZK1o!px`~iVt`B}j@i^{0OeHL#x z)9_Cj&qYGpMa|ya1z_xC24P`mZ)?WrY~o~QX6O9d-sJ+iTj*1X_CF;tCo^LgOM5#~ zbxT__2wO8JCQ=q2QY&LSHzrax7B)^&RyIC%Rz4QC`8;?72nbRL8F3MH_w2JZfD7ha z-{94Sjm(80Sw3a{13Ibrk0XOjQqn(vgkdV+!buf=z_fo4SH&!%nc=7Dq$ax!rYJ<8 z@!DX!AX(y&=D7UZ<0*XLfFfglv6`{eIl0OYFfV!)jfp0O3nazG8|bU@|F89mA>IQY zND3z@1C#wJ|4jbBB_3TN@w^fF;%}rDkLyh}cpzpr+Q@~KgkFun62QyOz4@F>XIqHG z8G1Vys*OYORrv`9qkvJ>ULPv&{(YA3e8Z*ujF@ND^y8K6@|(=7lcJIwOr62+*YTG29pgi8A6Sqazt;n(MJ%7!MBQ3nLWh&*0+3P7VkP$C;W=f1(-JO7-- zo@eromApDhe_SO*q1rIIc?Xs-QwcN2K~+ZIrXgC(Aa=_ui=*p!I%n%=*KmUqefa}> zy+nf zSNEt_KA-r5&C$F{AeP2r=6#VJR3R~)53E?y5-}ns@Bh@U84VSZ!u1NDv7)rlm0Gdi z4j6iWObAWvx)zjB<|*vXwpHsmKYqpAkHHlv6K(@G|M9&e&$TXv;piuNPn0ma_|C$G ziV@cooz4}v(B78Mfmt3SJPd2(GcjSUlRkHM(E6}{?Zp3b%@WVf`!HV4@XQ;}Q3P8F z+ch$_@T@#JcKPrJ7p3Yk9g80-g0ZHj+}4juF-siSR)mO&?(-YV=)*fP9Lh^K&O3&w zxqCaV%{fDO3OZz80P&eo{Nt6Bt?6}`cAT_pl@f6CNE-CPYpWQ!zL)9rIPF(UFb5I;@H%i67q5^hABJrNy{fhK!8hRKRM> z8{q27E3V%mDju7uJFYjgDx;t>XxsZ8LwbTyQZv)!!Z1tnF1uD$H9z<6cZK4XxhiYb z_}g!DBY9PIiY-9hFnG9htirMUK#ZI_s_r3}6fUl7$UVHysyIe(Mj1oKy#r;K`m#Pk zevN+GWX8t4dn!81hfP#s++H{h{ONSd2Lw=K5$2>meJ(}^_ z^@Ma+>^&MwWSucD5UNq%wdJky#1}Q;Jp_>H)Oo$(;_~A{Ndw<_+$GiTLJcov>tEAd z0G-TNO9+;_o-hm@goipJ z-r6w^yByeIxjSUp&8Vbs6wE;>&mC>AT1_83h#!|46`g-NCk2ZOvO}FLqj5#Sb1hyT+vZhdj8w^+hlDN=nMhSe9g3!H z#CibcV-M{DxhTp*RSvw+(bDJD6_}jv-jq`QRmG0Jn0R{qh)8lazJTQ3_G3yMC+gd; z-@D6q433`@9N2{BCvLebsXb0WQDfW%Vbv9wI*Op95fiesFj zf7s(~1%DszghbJ$wVW)352No?2VU~9RuL2Ww{p(^^>#uq-Mtm7So%t?HNLfEZk5M5 zZi9|{y`5h-u$K&zn0$w~)=F08hF4M?TH0T zO#pgnUwysd)`&kKycPIXw4hx0r6{D$+=4$5&rl&tY?tvnMtykP2!)WWsF|J8T4>iJ zPiE?a4HgQA%@Udx=cZ-EyF5)q^F_SM5W;974?02$50{Oi+(Y7<>gX#SwLKL&P=&yY zS`c725MQjZwOWc*X`LbVPQ>(8l5_(cH(dgU+hy9Kc(QYbKAlKOQPhzNPWT$;m!NE{$Fhiu8EuUJ{ z`|M!#aTkkiBN_u$;XZlaowtw^PoK_Jf_TO4{@F*y$(6f>i`%{$FeHm#g6HRTS ze2&b^Z5%RAT9nt<^1%)!m^U!A0Zz6csImL!gY!~)Yd^X*XA$+apEEgu&+RgZ=efWUlvhA4A)$Luw?$=x~Z(K$lA}244JMR8*ZP$&$gs z?Sy#s(mBpnVyX?!N+Zdx>WpW$%wNcLqA@ZG^RUAbt%ZA-1q}@Y>#La7u-p|y`i`9K z!$+lrs%(*E2kM61b;rXG^9reBmo_tS%uypJni(0S31NoJLAnOUi)x}pRoisw#1xc= zQL5o>I}rsX9#8L@yfZCkwx@KiN?7T-vdM93n)=d^(PDDYP$4l=`tsKk`HBgS?9x1>TxeMj z#&E2+y_Q?H5!O%<0Eaxuhzf0sxz+ss>MLYsV0646hUQ-Xio(Ksz$U_dae$wV5+fO= z4nDE>*N-1g8C`%wlB4SG=*7ggOh{0s8{JTKQskozC4kx6MoM(Pm8Ns9kjK*2)ya!* z!~_*DE@_)sn8c)HD|wm=NqE9jsEv(N?EWFFi;|thjvO9g+CN9qTAUr|HX&b~9#N=33G#Y^|hQ z?V^QVu=$bJqY?EBrY(y!x#0$&qXVD>As<#F`j zm8TB}C_4y*Q;vL~l+$Tzk1qL4+6_!MfR*ZXMJHp`8Bd^JsY>{Y2*fZQY*7(y3ro6v zf~5YS%qL?M*xmAXHj=tCE0ZB!Fo^4JEJ8&THR4SC8T`;gQ?gHeNSI;+tN z#Sm)Q$Fl{N1}BLpmR8i6O_sqrriAbjj)lh>+KX+|W9`JmR1oNzjRfF|OFnLNbolm! z=rS?ej)c-qfk1D)%n8RZ{KPbiIGjTQRai42=V0tOfBoE_>>h z3stvf@@N$>Rm4mBzhzS7H`NT*8w=vMW<~mA1P_HL8$H?!8yy)>#b;%UR+VAcHBtF> zK8lg4C8x++)qbg(nE&X_2$NTFO zrKguh+sGTB1^dl>x==fbRfWC)@fCHJboz3e>dA&~PDPP)fmFVxTHg~TYK*8^;F1wD z6t+(-nii4F!>e#sQcbnF#5R$Jgd(1JkXz9|SL zhW9&NA=%qPNvVVa?$=}j1(!M`Ce&ul-peQ*eUE>8=7Rl|~ zdq;}VLyj+|>k*`vJ(J!%mbeAx7vx=kSzafVXMXk*MBDC!rWDav=6VFF#H66MtA73^ z#Q(rP@WhWfSg&F-BlclY>PY!Y5;p$<(@c$$;-IxU3te#j?p=0teXQm>K}f5T|H!8Y zlekI1h`3XP`+E`6=pPBcc&*zRvbMRH8m;t4(~>v~XuvuNk*xqQgv{Autdbo(UHIUO zROS27-hnP5rp0D?tj_(Q!QI;IMe-Au!<3PaQ&4^_I^@Hm>MyVh=0)_<_z`ne{+HY4 zoz}`eGzZo)q(Kvd!VZD@id~3MU)3g@`?WLwqbj4JI=qmDA;%0$!=Lqx5l`zSnq-1 z75l|ekT0V`6rP=cx&yy)4-_!T)?}o1DvmF7{jLUylbZ(bUtP2&Rt4IS=$+~9V93L4 z;#yP(XlM{jEYoh>xJnaKg3aj-=h8r(*W}>!_s<{!KAmFuxJ>N#M`g@^Va?a0{yN^Q z0JJ-KNKiuTJF-enSMXOsJTejSV?J=VDfgit5s_H^!*EE*9+;1{jr%ecMCC!kRr3f z4XV>y`{10VzNUYOPi}_H0_RrocX$UnSNBL=$g=bssQy%(q4C1M;CSnDj9$jKZ#;so zSj%r^RpXfV2LQg5b-#-CL=ywKSYP(U5sUzTYV!QO1@q`7jrv-bCB9|3ALQIoZiPY( z`2(IjHA5W0l-ENu+#dE|WG6c8%oK6cO~~)isI_~|7TUK>m|MsvbcNRqrUtMtE zLy@(XQ$84}cliyye7*$WwaEfQI-*yCb6XV-mpX0uZ;8u=h+~`632&s?pZ)a+04bFb z#^QSxTF*X$hy;?J9OgEaR2p69@{&;127>ZFa{Qq!Pi#{I1oU$ScXp}ufjZrH2)Yn5 z3Z1@hzg5P+jq}&WHnu*|FbMu$EJSH?A~T$)tPDNBM=_UwirzV*v?R}Hof+#Uk2!6m zFccck2uEsgf=p;XU^}MPr{|bYt?kT$acjEjb_Rt{a%camxg_zA@ciEFmyQLH(LIHC|Y9 zN4UjJxmIw%efR6(;B2Fb(^YxQMt(M)Jot%7^(sQ%{-;MM z)7E%B{(ZK-m9WO0lQh3g&V&lT7e>vm`+2YwjnK&af+8o&`ncUCLIFk-TNSD-(pSap4&^0dSd!84Sf{5zHN zC^SlDPo%7;LLA|auj-)CQII;Ox`3&!O2f#T@$4h!Sc~ca{rLr(Yl9_w@Hh%2gS7XU z`vyaeO(usud%+!~MAg*aa11rIzx!*+^{X{b%paKnyWH#n5+ff&Y&(RP33 znj^+TFz?F}h2hH<-&0*`i>;i_TLX!jUS{6yxj1#4#%eS;fGfFYQ$P2nMg$5$l*yr} z^cP|YITK4bjE`CervLue0J0}ai{V79Gyb%B1F134jyNX4M1^&n`P1N010xXuk#MG+ zGjOl*4A4W7hDdFnXSvcgqr7KyDvtnmE_+x-&{R0{4}$uk^b8sh_*kx9of#RHFg_J_ ziNf*mmh0uY;Mnl-`saWc7~D2NDx}Yw)s-8HUy^OQRM`APdn;5@JCc#j?>3E=i9qJlT8^ap(E^s3lDsqe`l>wLfB0z*E4|Rf+YvfsLTNWtJ+}Qb0fUB zuMk0Jw$yOu1rJ{#2k9Nf*>kXcplq*#@kT_mruP`4LM5`GI8j-N@O}z67n)`%rQJ%J z4ene*M3?Ljxx>K=-b7;_JXOyr`ztH#yK*z0uY8HI47){TdJ%1L{z=Zvwc9aEHgRB5 z^89=J{xBo?;zn1Gyp!+Tb*66$2xjrg=eODIi34KCDacvY=qM}}z4C4Ymb6F)lWFat zS7&g|UL}zy&cpK@zs7KY>~}J8vL<-TH70~g9E4ID7*y3dll1FFJP;Z8|xjCY|N)xrj@njoA%;027E?%WU1w@Gm6Z%GdVOQr=CT zup$u>(oG7u*Xwli%*0L$EUIP*E4lJUk2ZS_(7VwEHgDuXiA^if_etv(HkK`rG=ztb z%M6kW^iP%Izc@ip1FH*WSqzDqL`$eA5h07iR}e;g6^tT^Wuz58&x=r-Vw zg#9!1DI#NK&|~!&hg8f(q){q_a`x5ZY9^A z{o&}^0b1GX_SZgvV2uWZ6J&r_TzqM@RR|q^tkG~odv9eS6FyxNOr8E!IE^DoO}arh zkb>usVMtS$x%cdwMKWYF#VZ3K@)`nHt(^6RAIZWF+V5-IBg@o4SS#}D(uOfExgb8I zLkUe>-1i9$x;{y>5hQ!z*eBspM8k22;W=z}{?q>Mc2++Tplgn86tLtXeCZY2Q2ESB zc7rQrB2>=OUVqohQb}$uFjq;_anO1uI9F+o3yJYqrrSH>H<3SW^Z!IYqoQ zQC`%xw=x^w51kScwV?)a#sGbnj2<}&e|b-7v}P9w^kegP7a9Ujwe7iLtclQ2F`W7c z5V_&j8}mZ_&Lia?_Q;-9Vhg=`*xak)iUl}edwUxKtbm{#fd`&}iVA3onQz-JICbqu zmCLy-&@J^L*>a|kTFGGhe&W~S4f~V(IF~PKEJe|exUxzNw|zgu zs+nf?9Q}(o8hW2x(jM@oE-$b~S?u?L(e|(OCJK2hMR(mI$DL))Dvf8Hh~DYGOT#G) zFV18_JQ_G^hKU+>3JhIgEdRANdwNi2?Ao#xJ8u!2PEya7M0M?V{AtcEy?m@wrdM5J?;?}urQXUwq!DLau zw&$~dK;s#da)TJLD06Qb2Yzz&r;;kgn?dKtl`U;S zMW5VBvsR134<$!Mwr@E1wfj*ir`;Mq?#B(rfDFdX9*}G0>6KJv8lj;L-!nRrF`bal zQhMvK4N)|FoUq*It3ncKT>P^W#s3oH)BXG7L#>;2wxb;o&BT?SM1>cCG4;gKVBp<~=|1idBNG)=ehpKxtAd*Oo2lku{Rc8dN?oK6B!byM{>ltHK+ux*$V`x4;_P|0jLsgj zv2>@bKmL`ou<+la&g;dkbp;$qk{Kt_-4WHA`P`V1w5;OFW?O2(w-1lMf!%O_E_wSj zP)@@&G5g9v<3}_~JV<_$;i#;nC|=()Kptn(o_*+Y1vsxJ2~3b}Betny2QlO%{HYlN zcOD;n21nX=O8r;6mx(gb+|KAeRJLZ3gpJC*MrTVBlXaJgz)pKFT6j-yKpaM#;-pmO zpd*7K8M;}GPDlt)6SaMpSF*M9!#6F4g3-v$MdojWFJIy@(|PAh8vRhe#t}<&7o%)9 z1xjU4=Y+jxqJ2$<0l##;`?bE>0Atf>WY>%J(CszbS3jM0G?WvyWZ)f{^X)_V=dUx# zq!Po;0xa-lb1DR$!M0)73VxsP`;R09+aObLfWLWYzMw5`0FE;Vv8#tO1_{CX^)<`H zu!Q_QSN4aLr~{g#ME>5wMzY}};IBbzemvpP_4=fZ0dJCHr>@$<1g6B1;JFiP#)ww3TnKq0f(B>IJ}{v|kKJ7y?tHUn^7xcDEME#aR zN}AR}jv_g!R|0icVw=#kkNUAozf`>6!?CCb$8+Vu+3TaJf;cjCsnZrAHOB=;NA>St zj^9j5W)LdQDiWk&f23tgClV807y_A63X@?48kO5;+ztsEh@QV5I>|)0{F}2*<8l5; z&p+9p#5}qiV*BeVQ|L5%`8pT1l1nI*hiI?)ck&!wbmROs08}7|-%3Az$LK;F;xywDO)Eai{S3 z1t01(mU)sBeCYjfiOkBU)y{8(ljDW)TuYFl@1LFb(d;rMZuo?vfA z3b-qd)ffL<$etb9vf&R_%TAIMy;Wd>4NKHUqN#MSUd$yHqrQ9}A<4k|sls(dx#dY? zsSzB?bc~7;vfhqy&2-A_F@o%GD$_SeDQE`zi^nKuA$4fN|Ggg?IYQ_$TWA*|9$?0v9 zdQd6-AmXL zOyLKQT2YT>r}VJNv$lt$Z>v~TO8^sT?w7iZcpOdd9;JMbJ+d;ngHtWUC5KU#9hIX2 zf3h>WO0lIp3?d|)0GHYlUMw}IxAA7Ru)5q_j}^rR)|4L;TfG_Om;~o9Ryb3|PCl-+ zua~OaZFhKMm#oZJ-Dceza~L$=+3P!e6C2-zU&Z0!vlNx4=UGfd=R>diwn# ze;n*C5o*1@hScmIUDG)l++b_ec_mtSwmDc?p!4}~m0-v1Ejf1{EiWP` z1;SJjE=|^kfA4FQ<@OXlGuoS~pPBwLnst)Fvq>7G$I&ApyYDHD3o0_80Y!2#E9RBE z9?@gEBm0Hn0J?{{7Z9c_a&~SGzSfkRK%5<~6pTfZ9rj!H&dB9jk!NuJ`q>>CYEt%R zx-`xGdSQuhVv79a+~MavyZ**I4AF5$lGh2;P>>z@%Cbp9is3ay629uZM<}X!w#n}4 zhRob3rv69nLzm5H=#j6sOrF)X062`q^m-qcqfH!Tl0as|i0|7}FQPSX(D2O=FF}f} zd$lgucT???HF{NEseX1^AIMfsBOJN?ai`WKpy zPd`VJRL1ldeR6tOh#Ah1*HqjI7VZsU17BxR+|R`z1iqdIiG!B2D*Py8E6R?g;oY0` z2X1Tltlol5r5hxCh^^WnW5y&}+>3C5x+jY9(ZZ6v*9*NOm1TUCYK!kL<^}r7-t6bM zjh(0rgNq#OSXx`CM z^;3ftjGv2@o$dxGLQc2B;@i2GFspMfu9y1vAJ#{2o7oHI3gbMut-eRaQRSzYAz&~& zI*d|^8H`;L^F|rNJL`CMl&vZcJuUGzH@|H$XwxRBTWwE;fyV8FfLg=PP)Cz z^j%StNzWJ@%o-(Z>aupNMW6k0^$trWu0Zdho>nnQdH*W(Ym(sIZYAP~9ly?Op6|O= z7dONgJLq+b^Cg0gR(i;simmxUUWTic9#|zlQW-9n+@@?)1Vf8|6MDO&5{3HwBTd$T zGv*@lvyTV(o0rzr2egzK3qra5;G5&uPH~)(-65dKNv1P^k5l0 zb1<0?JS!qQ`_Fpyu|t6W#%BX3?i>H=-@8ackpu(3_Kc22=1#bl>oe8T#68fjesJ|S z{@XCTc%h_CPS@t!)GUFtg?eW3HWk+875uFw+1obY-nF!x(U-+tGk00DSMmMJJ__-F z>5j=7;TKg6`11Sr1`ffovfe}j$I6u#aEIzOP1#BYWM58``wMTW>sn6Arr;$?#;qt{>-EX^yzlLv`HR zw^hByTwJh}c~D7mw-xt^?bhjXS16}kYBHP2tb{pT>SumYM0X(%EgbW1ppzq9J)Umx z;r<#+C{iuiG{-%k$UFY=fP5mTb$zt_1|wCZW@DvvMn!Zacukpmv1i+&Rm)Z+r)B*o0z^>jj#;+3;RAx zb&jxMlLlnlZuqSOY{i;P?p!t!KdSQu_!m**URtq91u#{8x1bGUe1g}86)eQ3V(WlI zUYsU*QQU_6T!*HD@FA@>N|8T(yr0T8>}_GeQ&TVWDOI$aX~9{qxPCDXY2>)n1tHX&e5ecs%6x*IHq~^o;g?{*&DtT z%v=n5aGA9=ZwPDJZ++9i9V!zHSqZ-(^t5=R#Y|#aDCb z^{#@2Ke7Z=U{cMY6i>d+m;g5H)3Rj5{TlDmYnq$U$fCJ4492@ zGFI}qUmzHISimv2WsF}nnE<$i@?i{w2D6pVG9p9u$qomS+V$alX0W3ZRZvy-e{-(V z4Hrsl-LXR{q{tp;t;`+fk9}(O?hG74G0Ca*&kWyWPe_v%g`!HhanE=$yG&f4KWJd>?F8`G2=evSs@7XJzyR%fXKL-ZwTBl$+S%MD!$yd6F~%JA~`z~}{@o+y~9)5GY~!SKrX-3OfpS?uw0)nNhd_K^GRT5xxe z3XFbLQ##~9bK+mx9$B+qFCik_-D77Xvs3xVpN{>!PK^UC>Dn~~Z&P&D<3zF%`)^s) zpt)3UPLN%<@hs;@TDQTE|MLHGlB`iq#*a&N_Sc59ESp_@$Uo!DQ4tBgLQoq0Zlli$Br=xp>v|{*=(*_g2_|wmvEanix|Im_RZsVWx*sSy9ncISgRf2G% z(D;xb=}Nu-jL4w&1SFTj8Up{n5j;+znOb|f0nLrBZ+>YD#iDqCNGIzCeJ5lKYlP21 z`N$m-+MJ;aj*2UK`)C(1l54N~D8becCa6!tCL|7$GJave!R8`~ZZrK^^;QVSlOb@ihHz@A zfb7=aonvlt_w5Y(7Tvo(li+R%vX>G!(?f3VZ|yp0f2pC)zZJLp#1kEq<5JB@)LJ~4 zMSi@|y?udSNbSU%DyOX4w$Aw?;y(OohjM#My}gf*qwDwcyLGwV5jNlkAZPcyjA{9# zeTl#}c8~l4PT|W$4)y0W+#;Lg1(5nl<+cxX;W?>hIhXFhp|}zTfPvltM;iv~8=dQ3 zWtQ{JIQP_mD=E(bud67y^+sR?lMj5&-$jK)5c1kTDf?)X!>MWcXLn&TFX7!iGbvDN zzNvOOMnU&_UnU?FJWwr2kUrzXR?AOsvA5cf1 z^QC#Q(H-gy>h(7POFHcaa4)TypLIXX{q|61nIqty*_KKMwXMb;clk_^>rAKjQ7Ff9 zXCdaFz4y@57{ba;$=;d-&fs-Ek%y(TYDhPh(=SCpTL&On!2c7)_I<|}%RLoeiCy#X zye>J7V>Q^T(U1D0W89q#(zJ5I7Vn<@XS5;^_5@E{MV@@I)w&!#Gn#di*d7E z-Vy%!9Y`d{fX6iZiPTU2^KJleBM#md`n1Qo5WG{3Dp6|SDkg*48WNT!ANn?`$(3$N zhN2q-3bhu0`V30=8nQ&F^o6pUA0x!V+HG}eX`yNFg}Vr8b^8TW1?^vXIe~OtKo;7Z zXF8sbFVkC~e;qu2hzoM-aFjzuCrFz1nFHa}Z-*Q=Gy^|;A5pM77D+zbY=1Af;@W%T zNbj3`;KFTnpVp%CKmgR}R~0JXJ^yvLc657n0@OH(dmyTRxDo!H$~yp>9T;bv2fdcH znEUi{t$Xaz_xeb(noMLNv8;i5nNiRP6J(#v1cH~JMn_qbSNqq@3d zO&kc`2i&Yl&R>Ir!aS`vVr@kgJDt^&YLBFQv_6MxQu^;h0I!GoifTqcT+{I{E*9qr z8GS=TP_FQOon0b<)IOt`QKm7`vv`xAbGWYk`*1DB%hS08EXufhl14tIlqgPnMp^Zn zv|F=I#mYT_wYgh~!;M}?J?}bNBDW6v9_M^Xvq`U4eL7)0s4b+N$CEX5%uBU9U9Y=q zl%SjM`u=*!1)w5&<*L~w6ts4n&tTXc!)Vm4nAL(M|GWuNq>=0#S1VY^d-2{9*LmUE zD-KApFJU5YIO3SU_{b47iW99h;{xVjvj>L^rK!MVPpi%rZRAPnueAcTiTvf~uM7xl zPX{)e%(1X%oy@?xOwGPIamBXo~eoy zQe3jeM8@TEM2k67sC78$A2nG{!f;C9Sqn-oDKxA$oNaeYFDRqb(?oL#Pa0vmwX}%M zb_*wY$YAqamk({Iiqgu_MDhhdPU@UntaTN7c?Hi^7M+7Tp8g2mUwZNbK8)DzXgDr2_7h?b$F(>k#aaW;qJW(ORJX2t6{Te23F+pl^eX+#Ihkie4 zaV?9??Asat&ybSc>qg0TGp(1*o5dc+cfIk_YB@*dW@yLNiF$}l79k;J9FO0X!nTvJ zwwddT!gu&GO^)k_z7-Jh!an)q?;*E+x2Do9LyTalxM?4QSjPm13(oMS(S>^IZC)yF z>4VEy_Le(6DRx%`Jvp2t_D@7F@BzcNmif$18l}NX4QDPSa*wtK;P}^Jim|qCN=;ci zx}p^n^IL1V(Un>Fq}E0zOOnnG-*;9b#BsEPa*~^dS)|TRM9&DTxqJ;qDw}9jthavz zLBm++={hK-qyikZez)Ls_#1&dx0dp7%5qkTskjpFkSXkcG`D&SPuX>`4}qR<>$eD$ zv|}rs))B7z#a9pop2iJX^nK27Hd(>qS`j-DzmUkJU8N(l!4G4pf6QOt+u?(t3to&NRmF zIae1`5q5%YGY#7w$)p12;yr@fm?%nYEno}p+7kq3L9dt`G9jeQqnO0x>kv~$e8y4* zI$}lmMo?$Ha^W$bR2_DVqQc(yPYQc4Ca+?lKua3n`hq1b`<|?>pe>;6A*p4_Ow_j4V=Iw40 ziTU&ZLVV-u8&NxcrSYOhYJwT%xY1K;QIG+hZF^x&j|y^ggDyo1JQg6RP_^K1gE1R# zS?cYfW|oh2_ohRf#4X#{X#!+YtbOg)Tb23*H5S)|Y>UPKm(2WQ=M_8bbI!rz(M|Tp z4x-_+6{ksBwYe<)bQA`$(y#Dq73!tS{>>^(;?+Us?z8U|13#YK*IgGe-Q4Hc$oV)D z|I`^_JD!-^j9(+d7A`lJAUemqv9y?HwmJ3}78+6!62=7ON5>n%D(!Jmka zd3&_0xqp;)=)GS+-zq%s^ULz5Q?or;tBlA9_8HB+q>-*z$xijJg@pI-jlGSuFr~FQ zEhW~58NE9xgIj{(6f1N_LY1F{Jbl4kFjMyj=`rJV#=|w>x~M?SSpE4 z2NT5^eaO2ngQN3af>o8xAt{MvKlV-!josX*PQN1}Y< zm8C#xZGee`)(ahhNz{FH?I{+lo6@OBi9gfr3u(%G!OHp&ZbPIbGt<`EV1WDuhQHqr z=Jl4vjM_CiBqf>H+sqp*drzaSn+P#H9yemClaBtIaT15Z7m-o5mP_@GRLq06sdhln z^;hlx5!o*#HRqKHNS^z)n$(Z1_IvFvtort<&(Y@ZD~u6O`&eJ&+;bW;I`J09pM{_l zd-uyMyQyu#|c}A_9f;P z*S+I*J$e$NP^YmPFOo_s8YmrOAU;~jT7QCnUDOwhc(LYd+HEibA_w2fSxEShfL3l% zy=DCMLWBYLB^loC97IbMU#flTlOfyI#_&Ekf(9qdN`A2bz4=gpRx`3&Lcvc_FKQiT zbbX$82TK_%TSsOLczd+?@LRQJm`3h&$zZI)q--*iljHCK=p4^cOf$+PU7LXN`nyiZ zGH|{c-KdxZu;&>k)T!_SrHf?G^CM;pI)yr^>je4h>uMm0U#_xk!JG2$iTbg&JEAcdR55KI1V8LWa%-SfX1lKQ_*wv^u-{x6Bo!bMBSy?b{#{-3*(QOvYu%oP+M a=sw$UpT~b?MlI>TN=8Cayh`+E;Qs?U-JT@? diff --git a/docs/.gitbook/assets/speakers_icon.png b/docs/.gitbook/assets/speakers_icon.png deleted file mode 100644 index 99c8deeb548c7790cfb80920a3d4d24e68dad833..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12195 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 diff --git a/docs/grid/your_first_grid.md b/docs/grid/your_first_grid.md index 4f14c22c9..a2b0d11ad 100644 --- a/docs/grid/your_first_grid.md +++ b/docs/grid/your_first_grid.md @@ -107,52 +107,6 @@ return static function (GridConfig $grid) { }; ``` {% endcode %} - -OR - -{% code title="src/Grid/AdminSupplierGrid.php" lineNumbers="true" %} -```php -addField( - StringField::create('name') - ->setLabel('app.ui.name') - ) - ->addField( - TwigField::create('enabled', '@SyliusBootstrapAdminUi/shared/grid/field/boolean.html.twig') - ->setLabel('app.ui.enabled') - ) - ; - } - - public function getResourceClass(): string - { - return Supplier::class; - } -} -``` -{% endcode %} {% endtab %} {% tab title="YAML" %} @@ -733,12 +687,10 @@ return static function (GridConfig $grid) { }; ``` {% endcode %} - - {% endtab %} {% tab title="YAML" %} -{% code title="config/packages/sylius_grid.php" lineNumbers="true" %} +{% code title="config/packages/sylius_grid.yaml" lineNumbers="true" %} ```yaml sylius_grid: grids: @@ -820,7 +772,7 @@ return static function (GridConfig $grid) { {% endtab %} {% tab title="YAML" %} -{% code title="config/packages/sylius_grid.php" lineNumbers="true" %} +{% code title="config/packages/sylius_grid.yaml" lineNumbers="true" %} ```yaml sylius_grid: grids: @@ -850,7 +802,7 @@ Pagination limits are set by default to 10, 25 and 50 items per page. In order t {% endhint %} {% tabs %} -{% tab title="PHP" %} +{% tab title="PHP (recommended)" %} {% code title="src/Grid/AdminSupplierGrid.php" lineNumbers="true" %} ```php setLimits([30, 12, 48]); + $gridBuilder->setLimits([12, 24, 48]); } } ``` {% endcode %} {% endtab %} + +{% tab title="YAML" %} +{% code title="config/packages/sylius_grid.yaml" %} +```yaml +sylius_grid: + grids: + app_admin_supplier: + limits: + - 12 + - 24 + - 48 +``` +{% endcode %} +{% endtab %} + +{% tab title="PHP config file" %} +{% code title="config/packages/sylius_grid.php" %} +```php +addGrid( + GridBuilder::create('app_admin_supplier', Supplier::class) + ->setLimits([12, 24, 48]) + ); +}; +``` +{% endcode %} +{% endtab %} {% endtabs %} ## Actions Configuration From a180f6242f69a681208483cc0d1f8eb9b8e987e5 Mon Sep 17 00:00:00 2001 From: Simon Krull Date: Wed, 5 Nov 2025 14:15:38 +0000 Subject: [PATCH 42/53] GITBOOK-32: adjust templateDir path in Creating your first grid page --- docs/grid/your_first_grid.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/grid/your_first_grid.md b/docs/grid/your_first_grid.md index a2b0d11ad..8d91a7306 100644 --- a/docs/grid/your_first_grid.md +++ b/docs/grid/your_first_grid.md @@ -168,6 +168,10 @@ class Supplier implements ResourceInterface ``` {% endcode %} +{% hint style="info" %} +Note: When you are in a Sylius project, the `templatesDir` path is: `@SyliusAdmin/shared/crud` +{% endhint %} + This will generate the following path: ```shell From 11fd005a2b6a3015280707d6da6365628bdb805a Mon Sep 17 00:00:00 2001 From: Simon Krull Date: Wed, 5 Nov 2025 20:11:54 +0000 Subject: [PATCH 43/53] GITBOOK-33: Add templateDir notice to basic operations page --- docs/cookbook/admin_panel/basic_operations.md | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/cookbook/admin_panel/basic_operations.md b/docs/cookbook/admin_panel/basic_operations.md index 0bde45fce..663c03b73 100644 --- a/docs/cookbook/admin_panel/basic_operations.md +++ b/docs/cookbook/admin_panel/basic_operations.md @@ -8,11 +8,7 @@ Learn more on how to [create a Sylius resource](../../resource/create_new_resour ## List of resources -
- -
List of books
- -
+
List of books
Create a grid for your resource using Symfony's Maker Bundle. @@ -25,7 +21,7 @@ bin/console cache:clear # To refresh grid's cache Magic! Here is the generated grid. -````php +```php final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface { public function __construct() @@ -76,7 +72,7 @@ final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface { return Book::class; } -```` +``` Configure the `index` operation in your resource. @@ -104,6 +100,10 @@ class Book implements ResourceInterface } ``` +{% hint style="info" %} +Note: When you are in a Sylius project, the `templatesDir` path is: `@SyliusAdmin/shared/crud` +{% endhint %} + Use the Symfony `debug:router` command to check the results. ```shell @@ -121,11 +121,7 @@ Your route should look like this: ## Resource creation page -
- -
Book creation page
- -
+
Book creation page
Create a form type for your resource. @@ -159,6 +155,10 @@ class Book implements ResourceInterface } ``` +{% hint style="info" %} +Note: When you are in a Sylius project, the `templatesDir` path is: `@SyliusAdmin/shared/crud` +{% endhint %} + Use the Symfony `debug:router` command to check the results. ```shell @@ -176,13 +176,9 @@ Your route should look like this: ## Resource edition page -
- -
Book edition page
- -
+
Book edition page
-Ensure you already created the Symfony form type in the [previous section](#resource-creation-page). +Ensure you already created the Symfony form type in the [previous section](basic_operations.md#resource-creation-page). Configure the `update` operation in your resource. @@ -210,6 +206,10 @@ class Book implements ResourceInterface } ``` +{% hint style="info" %} +Note: When you are in a Sylius project, the `templatesDir` path is: `@SyliusAdmin/shared/crud` +{% endhint %} + Use the Symfony `debug:router` command to check the results. ```shell @@ -227,11 +227,7 @@ Your route should look like this: ## Resource details page -
- -
Book details page
- -
+
Book details page
Configure the `show` operation in your resource. @@ -258,6 +254,10 @@ class Book implements ResourceInterface } ``` +{% hint style="info" %} +Note: When you are in a Sylius project, the `templatesDir` path is: `@SyliusAdmin/shared/crud` +{% endhint %} + Use the Symfony `debug:router` command to check the results. ```shell From f3aed7dcddf3df468afb315df924946f2796005b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Mon, 17 Nov 2025 17:42:10 +0100 Subject: [PATCH 44/53] [BootstrapAdminUi] Use translation domain on menu --- .../crud/common/sidebar/menu/menu.html.twig | 10 ++-- .../src/Menu/AdminMenuBuilder.php | 58 +++++++++++++++++++ .../.application/translations/menu.en.yaml | 3 + .../translations/messages.en.yaml | 3 + .../tests/Functional/TemplatesTest.php | 12 ++-- 5 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 src/BootstrapAdminUi/tests/Functional/.application/src/Menu/AdminMenuBuilder.php create mode 100644 src/BootstrapAdminUi/tests/Functional/.application/translations/menu.en.yaml create mode 100644 src/BootstrapAdminUi/tests/Functional/.application/translations/messages.en.yaml diff --git a/src/BootstrapAdminUi/templates/shared/crud/common/sidebar/menu/menu.html.twig b/src/BootstrapAdminUi/templates/shared/crud/common/sidebar/menu/menu.html.twig index 1ec465fa5..24ea978d9 100644 --- a/src/BootstrapAdminUi/templates/shared/crud/common/sidebar/menu/menu.html.twig +++ b/src/BootstrapAdminUi/templates/shared/crud/common/sidebar/menu/menu.html.twig @@ -1,4 +1,4 @@ -{% extends 'knp_menu.html.twig' %} +{% extends '@KnpMenu/menu.html.twig' %} {% block root %}
-{% endraw %} ``` +{% endcode %} {% hint style="info" %} Note that you can also [replace the default title](page_titles.md). From 0f243fda5349ac1bbff1fe883929a188170e0717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Mon, 22 Dec 2025 17:27:07 +0100 Subject: [PATCH 51/53] Add support for Symfony 8 --- .github/workflows/ci.yaml | 38 +- app/Factory/BookFactory.php | 6 +- app/Factory/ConferenceFactory.php | 6 +- app/Factory/SpeakerFactory.php | 6 +- app/Factory/TalkFactory.php | 6 +- app/Factory/UserFactory.php | 6 +- composer.json | 66 +- config/packages/doctrine.yaml | 4 - config/reference.php | 1685 +++++++++++++++++ config/routes/framework.yaml | 2 +- config/routes/sylius_resource.yaml | 4 +- config/routes/web_profiler.yaml | 4 +- ecs.php | 6 + importmap.php | 8 + monorepo-builder.php | 2 +- phpstan-baseline.neon | 14 +- phpunit.xml.dist | 1 - src/AdminUi/composer.json | 36 +- src/AdminUi/phpunit.xml.dist | 1 - .../config/packages/doctrine.yaml | 4 - .../.application/config/reference.php | 1492 +++++++++++++++ .../.application/config/routes/framework.yaml | 2 +- .../config/routes/sylius_resource.yaml | 4 +- src/BootstrapAdminUi/composer.json | 33 +- src/BootstrapAdminUi/phpunit.xml.dist | 1 - .../config/packages/doctrine.yaml | 4 - .../.application/config/reference.php | 1552 +++++++++++++++ .../.application/config/routes/framework.yaml | 2 +- .../config/routes/sylius_resource.yaml | 4 +- src/TwigExtra/composer.json | 22 +- src/TwigExtra/phpunit.xml.dist | 1 - .../.application/config/routes/framework.yaml | 2 +- src/TwigHooks/composer.json | 34 +- src/TwigHooks/phpunit.xml.dist | 1 - .../.application/config/routes/framework.yaml | 2 +- src/UiTranslations/composer.json | 20 +- src/UiTranslations/phpunit.xml.dist | 1 - .../.application/config/routes/framework.yaml | 2 +- symfony.lock | 33 + tests/Functional/BookTest.php | 2 +- tests/Functional/ConferenceTest.php | 2 +- tests/Functional/DashboardTest.php | 2 +- tests/Functional/LegacyBookTest.php | 4 +- tests/Functional/SpeakerTest.php | 2 +- tests/Functional/TalkTest.php | 2 +- tests/Translations/FrenchTranslatedUiTest.php | 2 +- 46 files changed, 4945 insertions(+), 188 deletions(-) create mode 100644 config/reference.php create mode 100644 src/AdminUi/tests/Functional/.application/config/reference.php create mode 100644 src/BootstrapAdminUi/tests/Functional/.application/config/reference.php diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 00f7ec19e..556d88624 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -14,12 +14,13 @@ jobs: strategy: fail-fast: false matrix: - php: [ "8.1", "8.2", "8.3" ] + php: [ "8.2", "8.3" ] postgres: ["14.6"] - symfony: [ "^6.4", "^7.0" ] - exclude: - - php: "8.1" - symfony: "^7.0" + symfony: [ "^6.4", "^7.4" ] + include: + - php: '8.4' + postgres: "14.6" + symfony: "^8.0" name: "PHP ${{ matrix.php }} / Symfony ${{ matrix.symfony }}" env: APP_ENV: test @@ -33,7 +34,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: "${{ matrix.php }}" - tools: symfony + tools: 'symfony, composer:v2, flex' coverage: none - name: Shutdown default MySQL @@ -46,10 +47,7 @@ jobs: postgresql password: "postgres" - name: "Restrict packages' versions" - run: | - composer global config --no-plugins allow-plugins.symfony/flex true - composer global require --no-progress --no-scripts --no-plugins "symfony/flex" - composer config extra.symfony.require "${{ matrix.symfony }}" + run: composer config extra.symfony.require "${{ matrix.symfony }}" - name: Get Composer cache directory id: composer-cache @@ -102,9 +100,7 @@ jobs: - name: "Restrict packages' versions (Admin Ui)" run: | - (cd src/AdminUi/ && composer global config --no-plugins allow-plugins.symfony/flex true) - (cd src/AdminUi/ && composer global config --no-plugins allow-plugins.symfony/runtime true) - (cd src/AdminUi/ && composer global require --no-progress --no-scripts --no-plugins "symfony/flex") + (cd src/AdminUi/ && composer config --no-plugins allow-plugins.symfony/runtime true) (cd src/AdminUi/ && composer config extra.symfony.require "${{ matrix.symfony }}") - name: "Install dependencies (Admin Ui)" @@ -115,9 +111,7 @@ jobs: - name: "Restrict packages' versions (Bootstrap Admin Ui)" run: | - (cd src/BootstrapAdminUi/ && composer global config --no-plugins allow-plugins.symfony/flex true) - (cd src/BootstrapAdminUi/ && composer global config --no-plugins allow-plugins.symfony/runtime true) - (cd src/BootstrapAdminUi/ && composer global require --no-progress --no-scripts --no-plugins "symfony/flex") + (cd src/BootstrapAdminUi/ && composer config --no-plugins allow-plugins.symfony/runtime true) (cd src/BootstrapAdminUi/ && composer config extra.symfony.require "${{ matrix.symfony }}") - name: "Install dependencies (Bootstrap Admin Ui)" @@ -128,9 +122,7 @@ jobs: - name: "Restrict packages' versions (Twig Hooks)" run: | - (cd src/TwigHooks/ && composer global config --no-plugins allow-plugins.symfony/flex true) - (cd src/TwigHooks/ && composer global config --no-plugins allow-plugins.symfony/runtime true) - (cd src/TwigHooks/ && composer global require --no-progress --no-scripts --no-plugins "symfony/flex") + (cd src/TwigHooks/ && composer config --no-plugins allow-plugins.symfony/runtime true) (cd src/TwigHooks/ && composer config extra.symfony.require "${{ matrix.symfony }}") - name: "Install dependencies (Twig Hooks)" @@ -141,9 +133,7 @@ jobs: - name: "Restrict packages' versions (Twig Extra)" run: | - (cd src/TwigExtra/ && composer global config --no-plugins allow-plugins.symfony/flex true) - (cd src/TwigExtra/ && composer global config --no-plugins allow-plugins.symfony/runtime true) - (cd src/TwigExtra/ && composer global require --no-progress --no-scripts --no-plugins "symfony/flex") + (cd src/TwigExtra/ && composer config --no-plugins allow-plugins.symfony/runtime true) (cd src/TwigExtra/ && composer config extra.symfony.require "${{ matrix.symfony }}") - name: "Install dependencies (Twig Extra)" @@ -154,9 +144,7 @@ jobs: - name: "Restrict packages' versions (Ui Translations)" run: | - (cd src/UiTranslations/ && composer global config --no-plugins allow-plugins.symfony/flex true) - (cd src/UiTranslations/ && composer global config --no-plugins allow-plugins.symfony/runtime true) - (cd src/UiTranslations/ && composer global require --no-progress --no-scripts --no-plugins "symfony/flex") + (cd src/UiTranslations/ && composer config --no-plugins allow-plugins.symfony/runtime true) (cd src/UiTranslations/ && composer config extra.symfony.require "${{ matrix.symfony }}") - name: "Install dependencies (Ui Translations)" diff --git a/app/Factory/BookFactory.php b/app/Factory/BookFactory.php index 1afe2f9e3..1a7a91f74 100644 --- a/app/Factory/BookFactory.php +++ b/app/Factory/BookFactory.php @@ -14,12 +14,12 @@ namespace App\Factory; use App\Entity\Book; -use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory; +use Zenstruck\Foundry\Persistence\PersistentObjectFactory; /** - * @extends PersistentProxyObjectFactory + * @extends PersistentObjectFactory */ -final class BookFactory extends PersistentProxyObjectFactory +final class BookFactory extends PersistentObjectFactory { public static function class(): string { diff --git a/app/Factory/ConferenceFactory.php b/app/Factory/ConferenceFactory.php index c425f6e49..df919a849 100644 --- a/app/Factory/ConferenceFactory.php +++ b/app/Factory/ConferenceFactory.php @@ -14,12 +14,12 @@ namespace App\Factory; use App\Entity\Conference; -use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory; +use Zenstruck\Foundry\Persistence\PersistentObjectFactory; /** - * @extends PersistentProxyObjectFactory + * @extends PersistentObjectFactory */ -final class ConferenceFactory extends PersistentProxyObjectFactory +final class ConferenceFactory extends PersistentObjectFactory { public static function class(): string { diff --git a/app/Factory/SpeakerFactory.php b/app/Factory/SpeakerFactory.php index bc82edf47..b1fd53c31 100644 --- a/app/Factory/SpeakerFactory.php +++ b/app/Factory/SpeakerFactory.php @@ -17,12 +17,12 @@ use App\Entity\SpeakerAvatar; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpFoundation\File\UploadedFile; -use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory; +use Zenstruck\Foundry\Persistence\PersistentObjectFactory; /** - * @extends PersistentProxyObjectFactory + * @extends PersistentObjectFactory */ -final class SpeakerFactory extends PersistentProxyObjectFactory +final class SpeakerFactory extends PersistentObjectFactory { public static function class(): string { diff --git a/app/Factory/TalkFactory.php b/app/Factory/TalkFactory.php index fe068ee7b..51b849670 100644 --- a/app/Factory/TalkFactory.php +++ b/app/Factory/TalkFactory.php @@ -19,13 +19,13 @@ use App\Enum\Track; use function Zenstruck\Foundry\lazy; use Zenstruck\Foundry\Persistence\Exception\NotEnoughObjects; -use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory; +use Zenstruck\Foundry\Persistence\PersistentObjectFactory; use Zenstruck\Foundry\Persistence\Proxy; /** - * @extends PersistentProxyObjectFactory + * @extends PersistentObjectFactory */ -final class TalkFactory extends PersistentProxyObjectFactory +final class TalkFactory extends PersistentObjectFactory { public static function class(): string { diff --git a/app/Factory/UserFactory.php b/app/Factory/UserFactory.php index 7f182fa2d..bc3b093e0 100644 --- a/app/Factory/UserFactory.php +++ b/app/Factory/UserFactory.php @@ -15,12 +15,12 @@ use App\Entity\User; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; -use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory; +use Zenstruck\Foundry\Persistence\PersistentObjectFactory; /** - * @extends PersistentProxyObjectFactory + * @extends PersistentObjectFactory */ -final class UserFactory extends PersistentProxyObjectFactory +final class UserFactory extends PersistentObjectFactory { public function __construct( private UserPasswordHasherInterface $userPasswordHasher, diff --git a/composer.json b/composer.json index deac5a7ac..2d9da6f00 100644 --- a/composer.json +++ b/composer.json @@ -15,27 +15,27 @@ ], "require": { "php": "^8.1", - "doctrine/dbal": "^3", - "doctrine/doctrine-bundle": "^2.12", - "doctrine/orm": "^2.0", + "doctrine/dbal": "^3 || ^4", + "doctrine/doctrine-bundle": "^2.13 || ^3.0 || ^4.0", + "doctrine/orm": "^3.3 || ^4.0", "knplabs/knp-menu-bundle": "^3.0", "laminas/laminas-stdlib": "^3.18", "pagerfanta/doctrine-orm-adapter": "^4.6", "pagerfanta/twig": "^4.6", - "sylius/grid-bundle": "^1.13", - "sylius/resource-bundle": "^1.11", - "symfony/asset": "^6.4 || ^7.0", - "symfony/asset-mapper": "^6.4 || ^7.0", - "symfony/config": "^6.4 || ^7.0", - "symfony/dependency-injection": "^6.4 || ^7.0", - "symfony/expression-language": "^6.4 || ^7.0", - "symfony/filesystem": "^6.4 || ^7.0", - "symfony/http-client": "^6.4 || ^7.0", - "symfony/http-kernel": "^6.4 || ^7.0", - "symfony/security-bundle": "^6.4 || ^7.0", - "symfony/security-http": "^6.4 || ^7.0", - "symfony/stopwatch": "^6.4 || ^7.0", - "symfony/twig-bundle": "^6.4 || ^7.0", + "sylius/grid-bundle": "^1.13 || ^1.15@alpha", + "sylius/resource-bundle": "^1.13 || ^1.14@alpha", + "symfony/asset": "^6.4 || ^7.4 || ^8.0", + "symfony/asset-mapper": "^6.4 || ^7.4 || ^8.0", + "symfony/config": "^6.4 || ^7.4 || ^8.0", + "symfony/dependency-injection": "^6.4 || ^7.4 || ^8.0", + "symfony/expression-language": "^6.4 || ^7.4 || ^8.0", + "symfony/filesystem": "^6.4 || ^7.4 || ^8.0", + "symfony/http-client": "^6.4 || ^7.4 || ^8.0", + "symfony/http-kernel": "^6.4 || ^7.4 || ^8.0", + "symfony/security-bundle": "^6.4 || ^7.4 || ^8.0", + "symfony/security-http": "^6.4 || ^7.4 || ^8.0", + "symfony/stopwatch": "^6.4 || ^7.4 || ^8.0", + "symfony/twig-bundle": "^6.4 || ^7.4 || ^8.0", "symfony/ux-autocomplete": "^2.17", "symfony/ux-icons": "^2.20", "symfony/ux-live-component": "^2.17", @@ -44,26 +44,26 @@ "webmozart/assert": "^1.9" }, "require-dev": { - "doctrine/doctrine-fixtures-bundle": "^3.6", - "matthiasnoback/symfony-config-test": "^5.1", - "matthiasnoback/symfony-dependency-injection-test": "^5.1", + "doctrine/doctrine-fixtures-bundle": "^4.3", + "matthiasnoback/symfony-config-test": "^6.1", + "matthiasnoback/symfony-dependency-injection-test": "^6.1.0", "phpstan/phpstan": "^1.10", "phpstan/phpstan-symfony": "^1.3", - "phpunit/phpunit": "^9.6", + "phpunit/phpunit": "^10.5", "sylius-labs/coding-standard": "^4.0", - "symfony/browser-kit": "^6.4 || ^7.0", - "symfony/console": "^6.4 || ^7.0", - "symfony/css-selector": "^6.4 || ^7.0", - "symfony/debug-bundle": "^6.4 || ^7.0", - "symfony/dom-crawler": "^6.4 || ^7.0", - "symfony/dotenv": "^6.4 || ^7.0", + "symfony/browser-kit": "^6.4 || ^7.4 || ^8.0", + "symfony/console": "^6.4 || ^7.4 || ^8.0", + "symfony/css-selector": "^6.4 || ^7.4 || ^8.0", + "symfony/debug-bundle": "^6.4 || ^7.4 || ^8.0", + "symfony/dom-crawler": "^6.4 || ^7.4 || ^8.0", + "symfony/dotenv": "^6.4 || ^7.4 || ^8.0", "symfony/flex": "^2.4", - "symfony/framework-bundle": "^6.4 || ^7.0", + "symfony/framework-bundle": "^6.4 || ^7.4 || ^8.0", "symfony/maker-bundle": "^1.61", - "symfony/runtime": "^6.4 || ^7.0", - "symfony/translation": "^6.4 || ^7.0", - "symfony/web-profiler-bundle": "^6.4 || ^7.0", - "symfony/yaml": "^6.4 || ^7.0", + "symfony/runtime": "^6.4 || ^7.4 || ^8.0", + "symfony/translation": "^6.4 || ^7.4 || ^8.0", + "symfony/web-profiler-bundle": "^6.4 || ^7.4 || ^8.0", + "symfony/yaml": "^6.4 || ^7.4 || ^8.0", "symplify/monorepo-builder": "11.2.*", "vich/uploader-bundle": "^2.4", "zenstruck/foundry": "^2.0" @@ -109,7 +109,7 @@ }, "extra": { "symfony": { - "require": "7.1.*" + "require": "8.0.*" } }, "scripts": { diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 2942e16f8..cb21798e6 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -7,11 +7,7 @@ doctrine: #server_version: '16' profiling_collect_backtrace: '%kernel.debug%' - use_savepoints: true orm: - auto_generate_proxy_classes: true - enable_lazy_ghost_objects: true - report_fields_where_declared: true validate_xml_mapping: true naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware auto_mapping: true diff --git a/config/reference.php b/config/reference.php new file mode 100644 index 000000000..0d1559a7a --- /dev/null +++ b/config/reference.php @@ -0,0 +1,1685 @@ + [ + * 'App\\' => [ + * 'resource' => '../src/', + * ], + * ], + * ]); + * ``` + * + * @psalm-type ImportsConfig = list + * @psalm-type ParametersConfig = array|null>|null> + * @psalm-type ArgumentsType = list|array + * @psalm-type CallType = array|array{0:string, 1?:ArgumentsType, 2?:bool}|array{method:string, arguments?:ArgumentsType, returns_clone?:bool} + * @psalm-type TagsType = list>> // arrays inside the list must have only one element, with the tag name as the key + * @psalm-type CallbackType = string|array{0:string|ReferenceConfigurator,1:string}|\Closure|ReferenceConfigurator|ExpressionConfigurator + * @psalm-type DeprecationType = array{package: string, version: string, message?: string} + * @psalm-type DefaultsType = array{ + * public?: bool, + * tags?: TagsType, + * resource_tags?: TagsType, + * autowire?: bool, + * autoconfigure?: bool, + * bind?: array, + * } + * @psalm-type InstanceofType = array{ + * shared?: bool, + * lazy?: bool|string, + * public?: bool, + * properties?: array, + * configurator?: CallbackType, + * calls?: list, + * tags?: TagsType, + * resource_tags?: TagsType, + * autowire?: bool, + * bind?: array, + * constructor?: string, + * } + * @psalm-type DefinitionType = array{ + * class?: string, + * file?: string, + * parent?: string, + * shared?: bool, + * synthetic?: bool, + * lazy?: bool|string, + * public?: bool, + * abstract?: bool, + * deprecated?: DeprecationType, + * factory?: CallbackType, + * configurator?: CallbackType, + * arguments?: ArgumentsType, + * properties?: array, + * calls?: list, + * tags?: TagsType, + * resource_tags?: TagsType, + * decorates?: string, + * decoration_inner_name?: string, + * decoration_priority?: int, + * decoration_on_invalid?: 'exception'|'ignore'|null, + * autowire?: bool, + * autoconfigure?: bool, + * bind?: array, + * constructor?: string, + * from_callable?: CallbackType, + * } + * @psalm-type AliasType = string|array{ + * alias: string, + * public?: bool, + * deprecated?: DeprecationType, + * } + * @psalm-type PrototypeType = array{ + * resource: string, + * namespace?: string, + * exclude?: string|list, + * parent?: string, + * shared?: bool, + * lazy?: bool|string, + * public?: bool, + * abstract?: bool, + * deprecated?: DeprecationType, + * factory?: CallbackType, + * arguments?: ArgumentsType, + * properties?: array, + * configurator?: CallbackType, + * calls?: list, + * tags?: TagsType, + * resource_tags?: TagsType, + * autowire?: bool, + * autoconfigure?: bool, + * bind?: array, + * constructor?: string, + * } + * @psalm-type StackType = array{ + * stack: list>, + * public?: bool, + * deprecated?: DeprecationType, + * } + * @psalm-type ServicesConfig = array{ + * _defaults?: DefaultsType, + * _instanceof?: InstanceofType, + * ... + * } + * @psalm-type ExtensionType = array + * @psalm-type FrameworkConfig = array{ + * secret?: scalar|null|Param, + * http_method_override?: bool|Param, // Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests. // Default: false + * allowed_http_method_override?: list|null, + * trust_x_sendfile_type_header?: scalar|null|Param, // Set true to enable support for xsendfile in binary file responses. // Default: "%env(bool:default::SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER)%" + * ide?: scalar|null|Param, // Default: "%env(default::SYMFONY_IDE)%" + * test?: bool|Param, + * default_locale?: scalar|null|Param, // Default: "en" + * set_locale_from_accept_language?: bool|Param, // Whether to use the Accept-Language HTTP header to set the Request locale (only when the "_locale" request attribute is not passed). // Default: false + * set_content_language_from_locale?: bool|Param, // Whether to set the Content-Language HTTP header on the Response using the Request locale. // Default: false + * enabled_locales?: list, + * trusted_hosts?: list, + * trusted_proxies?: mixed, // Default: ["%env(default::SYMFONY_TRUSTED_PROXIES)%"] + * trusted_headers?: list, + * error_controller?: scalar|null|Param, // Default: "error_controller" + * handle_all_throwables?: bool|Param, // HttpKernel will handle all kinds of \Throwable. // Default: true + * csrf_protection?: bool|array{ + * enabled?: scalar|null|Param, // Default: null + * stateless_token_ids?: list, + * check_header?: scalar|null|Param, // Whether to check the CSRF token in a header in addition to a cookie when using stateless protection. // Default: false + * cookie_name?: scalar|null|Param, // The name of the cookie to use when using stateless protection. // Default: "csrf-token" + * }, + * form?: bool|array{ // Form configuration + * enabled?: bool|Param, // Default: true + * csrf_protection?: array{ + * enabled?: scalar|null|Param, // Default: null + * token_id?: scalar|null|Param, // Default: null + * field_name?: scalar|null|Param, // Default: "_token" + * field_attr?: array, + * }, + * }, + * http_cache?: bool|array{ // HTTP cache configuration + * enabled?: bool|Param, // Default: false + * debug?: bool|Param, // Default: "%kernel.debug%" + * trace_level?: "none"|"short"|"full"|Param, + * trace_header?: scalar|null|Param, + * default_ttl?: int|Param, + * private_headers?: list, + * skip_response_headers?: list, + * allow_reload?: bool|Param, + * allow_revalidate?: bool|Param, + * stale_while_revalidate?: int|Param, + * stale_if_error?: int|Param, + * terminate_on_cache_hit?: bool|Param, + * }, + * esi?: bool|array{ // ESI configuration + * enabled?: bool|Param, // Default: false + * }, + * ssi?: bool|array{ // SSI configuration + * enabled?: bool|Param, // Default: false + * }, + * fragments?: bool|array{ // Fragments configuration + * enabled?: bool|Param, // Default: false + * hinclude_default_template?: scalar|null|Param, // Default: null + * path?: scalar|null|Param, // Default: "/_fragment" + * }, + * profiler?: bool|array{ // Profiler configuration + * enabled?: bool|Param, // Default: false + * collect?: bool|Param, // Default: true + * collect_parameter?: scalar|null|Param, // The name of the parameter to use to enable or disable collection on a per request basis. // Default: null + * only_exceptions?: bool|Param, // Default: false + * only_main_requests?: bool|Param, // Default: false + * dsn?: scalar|null|Param, // Default: "file:%kernel.cache_dir%/profiler" + * collect_serializer_data?: true|Param, // Default: true + * }, + * workflows?: bool|array{ + * enabled?: bool|Param, // Default: false + * workflows?: array, + * definition_validators?: list, + * support_strategy?: scalar|null|Param, + * initial_marking?: list, + * events_to_dispatch?: list|null, + * places?: list, + * }>, + * transitions: list, + * to?: list, + * weight?: int|Param, // Default: 1 + * metadata?: list, + * }>, + * metadata?: list, + * }>, + * }, + * router?: bool|array{ // Router configuration + * enabled?: bool|Param, // Default: false + * resource: scalar|null|Param, + * type?: scalar|null|Param, + * default_uri?: scalar|null|Param, // The default URI used to generate URLs in a non-HTTP context. // Default: null + * http_port?: scalar|null|Param, // Default: 80 + * https_port?: scalar|null|Param, // Default: 443 + * strict_requirements?: scalar|null|Param, // set to true to throw an exception when a parameter does not match the requirements set to false to disable exceptions when a parameter does not match the requirements (and return null instead) set to null to disable parameter checks against requirements 'true' is the preferred configuration in development mode, while 'false' or 'null' might be preferred in production // Default: true + * utf8?: bool|Param, // Default: true + * }, + * session?: bool|array{ // Session configuration + * enabled?: bool|Param, // Default: false + * storage_factory_id?: scalar|null|Param, // Default: "session.storage.factory.native" + * handler_id?: scalar|null|Param, // Defaults to using the native session handler, or to the native *file* session handler if "save_path" is not null. + * name?: scalar|null|Param, + * cookie_lifetime?: scalar|null|Param, + * cookie_path?: scalar|null|Param, + * cookie_domain?: scalar|null|Param, + * cookie_secure?: true|false|"auto"|Param, // Default: "auto" + * cookie_httponly?: bool|Param, // Default: true + * cookie_samesite?: null|"lax"|"strict"|"none"|Param, // Default: "lax" + * use_cookies?: bool|Param, + * gc_divisor?: scalar|null|Param, + * gc_probability?: scalar|null|Param, + * gc_maxlifetime?: scalar|null|Param, + * save_path?: scalar|null|Param, // Defaults to "%kernel.cache_dir%/sessions" if the "handler_id" option is not null. + * metadata_update_threshold?: int|Param, // Seconds to wait between 2 session metadata updates. // Default: 0 + * }, + * request?: bool|array{ // Request configuration + * enabled?: bool|Param, // Default: false + * formats?: array>, + * }, + * assets?: bool|array{ // Assets configuration + * enabled?: bool|Param, // Default: true + * strict_mode?: bool|Param, // Throw an exception if an entry is missing from the manifest.json. // Default: false + * version_strategy?: scalar|null|Param, // Default: null + * version?: scalar|null|Param, // Default: null + * version_format?: scalar|null|Param, // Default: "%%s?%%s" + * json_manifest_path?: scalar|null|Param, // Default: null + * base_path?: scalar|null|Param, // Default: "" + * base_urls?: list, + * packages?: array, + * }>, + * }, + * asset_mapper?: bool|array{ // Asset Mapper configuration + * enabled?: bool|Param, // Default: true + * paths?: array, + * excluded_patterns?: list, + * exclude_dotfiles?: bool|Param, // If true, any files starting with "." will be excluded from the asset mapper. // Default: true + * server?: bool|Param, // If true, a "dev server" will return the assets from the public directory (true in "debug" mode only by default). // Default: true + * public_prefix?: scalar|null|Param, // The public path where the assets will be written to (and served from when "server" is true). // Default: "/assets/" + * missing_import_mode?: "strict"|"warn"|"ignore"|Param, // Behavior if an asset cannot be found when imported from JavaScript or CSS files - e.g. "import './non-existent.js'". "strict" means an exception is thrown, "warn" means a warning is logged, "ignore" means the import is left as-is. // Default: "warn" + * extensions?: array, + * importmap_path?: scalar|null|Param, // The path of the importmap.php file. // Default: "%kernel.project_dir%/importmap.php" + * importmap_polyfill?: scalar|null|Param, // The importmap name that will be used to load the polyfill. Set to false to disable. // Default: "es-module-shims" + * importmap_script_attributes?: array, + * vendor_dir?: scalar|null|Param, // The directory to store JavaScript vendors. // Default: "%kernel.project_dir%/assets/vendor" + * precompress?: bool|array{ // Precompress assets with Brotli, Zstandard and gzip. + * enabled?: bool|Param, // Default: false + * formats?: list, + * extensions?: list, + * }, + * }, + * translator?: bool|array{ // Translator configuration + * enabled?: bool|Param, // Default: true + * fallbacks?: list, + * logging?: bool|Param, // Default: false + * formatter?: scalar|null|Param, // Default: "translator.formatter.default" + * cache_dir?: scalar|null|Param, // Default: "%kernel.cache_dir%/translations" + * default_path?: scalar|null|Param, // The default path used to load translations. // Default: "%kernel.project_dir%/translations" + * paths?: list, + * pseudo_localization?: bool|array{ + * enabled?: bool|Param, // Default: false + * accents?: bool|Param, // Default: true + * expansion_factor?: float|Param, // Default: 1.0 + * brackets?: bool|Param, // Default: true + * parse_html?: bool|Param, // Default: false + * localizable_html_attributes?: list, + * }, + * providers?: array, + * locales?: list, + * }>, + * globals?: array, + * domain?: string|Param, + * }>, + * }, + * validation?: bool|array{ // Validation configuration + * enabled?: bool|Param, // Default: true + * enable_attributes?: bool|Param, // Default: true + * static_method?: list, + * translation_domain?: scalar|null|Param, // Default: "validators" + * email_validation_mode?: "html5"|"html5-allow-no-tld"|"strict"|Param, // Default: "html5" + * mapping?: array{ + * paths?: list, + * }, + * not_compromised_password?: bool|array{ + * enabled?: bool|Param, // When disabled, compromised passwords will be accepted as valid. // Default: true + * endpoint?: scalar|null|Param, // API endpoint for the NotCompromisedPassword Validator. // Default: null + * }, + * disable_translation?: bool|Param, // Default: false + * auto_mapping?: array, + * }>, + * }, + * serializer?: bool|array{ // Serializer configuration + * enabled?: bool|Param, // Default: false + * enable_attributes?: bool|Param, // Default: true + * name_converter?: scalar|null|Param, + * circular_reference_handler?: scalar|null|Param, + * max_depth_handler?: scalar|null|Param, + * mapping?: array{ + * paths?: list, + * }, + * default_context?: list, + * named_serializers?: array, + * include_built_in_normalizers?: bool|Param, // Whether to include the built-in normalizers // Default: true + * include_built_in_encoders?: bool|Param, // Whether to include the built-in encoders // Default: true + * }>, + * }, + * property_access?: bool|array{ // Property access configuration + * enabled?: bool|Param, // Default: true + * magic_call?: bool|Param, // Default: false + * magic_get?: bool|Param, // Default: true + * magic_set?: bool|Param, // Default: true + * throw_exception_on_invalid_index?: bool|Param, // Default: false + * throw_exception_on_invalid_property_path?: bool|Param, // Default: true + * }, + * type_info?: bool|array{ // Type info configuration + * enabled?: bool|Param, // Default: true + * aliases?: array, + * }, + * property_info?: bool|array{ // Property info configuration + * enabled?: bool|Param, // Default: true + * with_constructor_extractor?: bool|Param, // Registers the constructor extractor. // Default: true + * }, + * cache?: array{ // Cache configuration + * prefix_seed?: scalar|null|Param, // Used to namespace cache keys when using several apps with the same shared backend. // Default: "_%kernel.project_dir%.%kernel.container_class%" + * app?: scalar|null|Param, // App related cache pools configuration. // Default: "cache.adapter.filesystem" + * system?: scalar|null|Param, // System related cache pools configuration. // Default: "cache.adapter.system" + * directory?: scalar|null|Param, // Default: "%kernel.share_dir%/pools/app" + * default_psr6_provider?: scalar|null|Param, + * default_redis_provider?: scalar|null|Param, // Default: "redis://localhost" + * default_valkey_provider?: scalar|null|Param, // Default: "valkey://localhost" + * default_memcached_provider?: scalar|null|Param, // Default: "memcached://localhost" + * default_doctrine_dbal_provider?: scalar|null|Param, // Default: "database_connection" + * default_pdo_provider?: scalar|null|Param, // Default: null + * pools?: array, + * tags?: scalar|null|Param, // Default: null + * public?: bool|Param, // Default: false + * default_lifetime?: scalar|null|Param, // Default lifetime of the pool. + * provider?: scalar|null|Param, // Overwrite the setting from the default provider for this adapter. + * early_expiration_message_bus?: scalar|null|Param, + * clearer?: scalar|null|Param, + * }>, + * }, + * php_errors?: array{ // PHP errors handling configuration + * log?: mixed, // Use the application logger instead of the PHP logger for logging PHP errors. // Default: true + * throw?: bool|Param, // Throw PHP errors as \ErrorException instances. // Default: true + * }, + * exceptions?: array, + * web_link?: bool|array{ // Web links configuration + * enabled?: bool|Param, // Default: false + * }, + * lock?: bool|string|array{ // Lock configuration + * enabled?: bool|Param, // Default: false + * resources?: array>, + * }, + * semaphore?: bool|string|array{ // Semaphore configuration + * enabled?: bool|Param, // Default: false + * resources?: array, + * }, + * messenger?: bool|array{ // Messenger configuration + * enabled?: bool|Param, // Default: false + * routing?: array, + * }>, + * serializer?: array{ + * default_serializer?: scalar|null|Param, // Service id to use as the default serializer for the transports. // Default: "messenger.transport.native_php_serializer" + * symfony_serializer?: array{ + * format?: scalar|null|Param, // Serialization format for the messenger.transport.symfony_serializer service (which is not the serializer used by default). // Default: "json" + * context?: array, + * }, + * }, + * transports?: array, + * failure_transport?: scalar|null|Param, // Transport name to send failed messages to (after all retries have failed). // Default: null + * retry_strategy?: string|array{ + * service?: scalar|null|Param, // Service id to override the retry strategy entirely. // Default: null + * max_retries?: int|Param, // Default: 3 + * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 + * multiplier?: float|Param, // If greater than 1, delay will grow exponentially for each retry: this delay = (delay * (multiple ^ retries)). // Default: 2 + * max_delay?: int|Param, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 + * jitter?: float|Param, // Randomness to apply to the delay (between 0 and 1). // Default: 0.1 + * }, + * rate_limiter?: scalar|null|Param, // Rate limiter name to use when processing messages. // Default: null + * }>, + * failure_transport?: scalar|null|Param, // Transport name to send failed messages to (after all retries have failed). // Default: null + * stop_worker_on_signals?: list, + * default_bus?: scalar|null|Param, // Default: null + * buses?: array, + * }>, + * }>, + * }, + * scheduler?: bool|array{ // Scheduler configuration + * enabled?: bool|Param, // Default: false + * }, + * disallow_search_engine_index?: bool|Param, // Enabled by default when debug is enabled. // Default: true + * http_client?: bool|array{ // HTTP Client configuration + * enabled?: bool|Param, // Default: true + * max_host_connections?: int|Param, // The maximum number of connections to a single host. + * default_options?: array{ + * headers?: array, + * vars?: array, + * max_redirects?: int|Param, // The maximum number of redirects to follow. + * http_version?: scalar|null|Param, // The default HTTP version, typically 1.1 or 2.0, leave to null for the best version. + * resolve?: array, + * proxy?: scalar|null|Param, // The URL of the proxy to pass requests through or null for automatic detection. + * no_proxy?: scalar|null|Param, // A comma separated list of hosts that do not require a proxy to be reached. + * timeout?: float|Param, // The idle timeout, defaults to the "default_socket_timeout" ini parameter. + * max_duration?: float|Param, // The maximum execution time for the request+response as a whole. + * bindto?: scalar|null|Param, // A network interface name, IP address, a host name or a UNIX socket to bind to. + * verify_peer?: bool|Param, // Indicates if the peer should be verified in a TLS context. + * verify_host?: bool|Param, // Indicates if the host should exist as a certificate common name. + * cafile?: scalar|null|Param, // A certificate authority file. + * capath?: scalar|null|Param, // A directory that contains multiple certificate authority files. + * local_cert?: scalar|null|Param, // A PEM formatted certificate file. + * local_pk?: scalar|null|Param, // A private key file. + * passphrase?: scalar|null|Param, // The passphrase used to encrypt the "local_pk" file. + * ciphers?: scalar|null|Param, // A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...) + * peer_fingerprint?: array{ // Associative array: hashing algorithm => hash(es). + * sha1?: mixed, + * pin-sha256?: mixed, + * md5?: mixed, + * }, + * crypto_method?: scalar|null|Param, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants. + * extra?: array, + * rate_limiter?: scalar|null|Param, // Rate limiter name to use for throttling requests. // Default: null + * caching?: bool|array{ // Caching configuration. + * enabled?: bool|Param, // Default: false + * cache_pool?: string|Param, // The taggable cache pool to use for storing the responses. // Default: "cache.http_client" + * shared?: bool|Param, // Indicates whether the cache is shared (public) or private. // Default: true + * max_ttl?: int|Param, // The maximum TTL (in seconds) allowed for cached responses. Null means no cap. // Default: null + * }, + * retry_failed?: bool|array{ + * enabled?: bool|Param, // Default: false + * retry_strategy?: scalar|null|Param, // service id to override the retry strategy. // Default: null + * http_codes?: array, + * }>, + * max_retries?: int|Param, // Default: 3 + * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 + * multiplier?: float|Param, // If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries). // Default: 2 + * max_delay?: int|Param, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 + * jitter?: float|Param, // Randomness in percent (between 0 and 1) to apply to the delay. // Default: 0.1 + * }, + * }, + * mock_response_factory?: scalar|null|Param, // The id of the service that should generate mock responses. It should be either an invokable or an iterable. + * scoped_clients?: array, + * headers?: array, + * max_redirects?: int|Param, // The maximum number of redirects to follow. + * http_version?: scalar|null|Param, // The default HTTP version, typically 1.1 or 2.0, leave to null for the best version. + * resolve?: array, + * proxy?: scalar|null|Param, // The URL of the proxy to pass requests through or null for automatic detection. + * no_proxy?: scalar|null|Param, // A comma separated list of hosts that do not require a proxy to be reached. + * timeout?: float|Param, // The idle timeout, defaults to the "default_socket_timeout" ini parameter. + * max_duration?: float|Param, // The maximum execution time for the request+response as a whole. + * bindto?: scalar|null|Param, // A network interface name, IP address, a host name or a UNIX socket to bind to. + * verify_peer?: bool|Param, // Indicates if the peer should be verified in a TLS context. + * verify_host?: bool|Param, // Indicates if the host should exist as a certificate common name. + * cafile?: scalar|null|Param, // A certificate authority file. + * capath?: scalar|null|Param, // A directory that contains multiple certificate authority files. + * local_cert?: scalar|null|Param, // A PEM formatted certificate file. + * local_pk?: scalar|null|Param, // A private key file. + * passphrase?: scalar|null|Param, // The passphrase used to encrypt the "local_pk" file. + * ciphers?: scalar|null|Param, // A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...). + * peer_fingerprint?: array{ // Associative array: hashing algorithm => hash(es). + * sha1?: mixed, + * pin-sha256?: mixed, + * md5?: mixed, + * }, + * crypto_method?: scalar|null|Param, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants. + * extra?: array, + * rate_limiter?: scalar|null|Param, // Rate limiter name to use for throttling requests. // Default: null + * caching?: bool|array{ // Caching configuration. + * enabled?: bool|Param, // Default: false + * cache_pool?: string|Param, // The taggable cache pool to use for storing the responses. // Default: "cache.http_client" + * shared?: bool|Param, // Indicates whether the cache is shared (public) or private. // Default: true + * max_ttl?: int|Param, // The maximum TTL (in seconds) allowed for cached responses. Null means no cap. // Default: null + * }, + * retry_failed?: bool|array{ + * enabled?: bool|Param, // Default: false + * retry_strategy?: scalar|null|Param, // service id to override the retry strategy. // Default: null + * http_codes?: array, + * }>, + * max_retries?: int|Param, // Default: 3 + * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 + * multiplier?: float|Param, // If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries). // Default: 2 + * max_delay?: int|Param, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 + * jitter?: float|Param, // Randomness in percent (between 0 and 1) to apply to the delay. // Default: 0.1 + * }, + * }>, + * }, + * mailer?: bool|array{ // Mailer configuration + * enabled?: bool|Param, // Default: false + * message_bus?: scalar|null|Param, // The message bus to use. Defaults to the default bus if the Messenger component is installed. // Default: null + * dsn?: scalar|null|Param, // Default: null + * transports?: array, + * envelope?: array{ // Mailer Envelope configuration + * sender?: scalar|null|Param, + * recipients?: list, + * allowed_recipients?: list, + * }, + * headers?: array, + * dkim_signer?: bool|array{ // DKIM signer configuration + * enabled?: bool|Param, // Default: false + * key?: scalar|null|Param, // Key content, or path to key (in PEM format with the `file://` prefix) // Default: "" + * domain?: scalar|null|Param, // Default: "" + * select?: scalar|null|Param, // Default: "" + * passphrase?: scalar|null|Param, // The private key passphrase // Default: "" + * options?: array, + * }, + * smime_signer?: bool|array{ // S/MIME signer configuration + * enabled?: bool|Param, // Default: false + * key?: scalar|null|Param, // Path to key (in PEM format) // Default: "" + * certificate?: scalar|null|Param, // Path to certificate (in PEM format without the `file://` prefix) // Default: "" + * passphrase?: scalar|null|Param, // The private key passphrase // Default: null + * extra_certificates?: scalar|null|Param, // Default: null + * sign_options?: int|Param, // Default: null + * }, + * smime_encrypter?: bool|array{ // S/MIME encrypter configuration + * enabled?: bool|Param, // Default: false + * repository?: scalar|null|Param, // S/MIME certificate repository service. This service shall implement the `Symfony\Component\Mailer\EventListener\SmimeCertificateRepositoryInterface`. // Default: "" + * cipher?: int|Param, // A set of algorithms used to encrypt the message // Default: null + * }, + * }, + * secrets?: bool|array{ + * enabled?: bool|Param, // Default: true + * vault_directory?: scalar|null|Param, // Default: "%kernel.project_dir%/config/secrets/%kernel.runtime_environment%" + * local_dotenv_file?: scalar|null|Param, // Default: "%kernel.project_dir%/.env.%kernel.runtime_environment%.local" + * decryption_env_var?: scalar|null|Param, // Default: "base64:default::SYMFONY_DECRYPTION_SECRET" + * }, + * notifier?: bool|array{ // Notifier configuration + * enabled?: bool|Param, // Default: false + * message_bus?: scalar|null|Param, // The message bus to use. Defaults to the default bus if the Messenger component is installed. // Default: null + * chatter_transports?: array, + * texter_transports?: array, + * notification_on_failed_messages?: bool|Param, // Default: false + * channel_policy?: array>, + * admin_recipients?: list, + * }, + * rate_limiter?: bool|array{ // Rate limiter configuration + * enabled?: bool|Param, // Default: false + * limiters?: array, + * limit?: int|Param, // The maximum allowed hits in a fixed interval or burst. + * interval?: scalar|null|Param, // Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). + * rate?: array{ // Configures the fill rate if "policy" is set to "token_bucket". + * interval?: scalar|null|Param, // Configures the rate interval. The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). + * amount?: int|Param, // Amount of tokens to add each interval. // Default: 1 + * }, + * }>, + * }, + * uid?: bool|array{ // Uid configuration + * enabled?: bool|Param, // Default: false + * default_uuid_version?: 7|6|4|1|Param, // Default: 7 + * name_based_uuid_version?: 5|3|Param, // Default: 5 + * name_based_uuid_namespace?: scalar|null|Param, + * time_based_uuid_version?: 7|6|1|Param, // Default: 7 + * time_based_uuid_node?: scalar|null|Param, + * }, + * html_sanitizer?: bool|array{ // HtmlSanitizer configuration + * enabled?: bool|Param, // Default: false + * sanitizers?: array, + * block_elements?: list, + * drop_elements?: list, + * allow_attributes?: array, + * drop_attributes?: array, + * force_attributes?: array>, + * force_https_urls?: bool|Param, // Transforms URLs using the HTTP scheme to use the HTTPS scheme instead. // Default: false + * allowed_link_schemes?: list, + * allowed_link_hosts?: list|null, + * allow_relative_links?: bool|Param, // Allows relative URLs to be used in links href attributes. // Default: false + * allowed_media_schemes?: list, + * allowed_media_hosts?: list|null, + * allow_relative_medias?: bool|Param, // Allows relative URLs to be used in media source attributes (img, audio, video, ...). // Default: false + * with_attribute_sanitizers?: list, + * without_attribute_sanitizers?: list, + * max_input_length?: int|Param, // The maximum length allowed for the sanitized input. // Default: 0 + * }>, + * }, + * webhook?: bool|array{ // Webhook configuration + * enabled?: bool|Param, // Default: false + * message_bus?: scalar|null|Param, // The message bus to use. // Default: "messenger.default_bus" + * routing?: array, + * }, + * remote-event?: bool|array{ // RemoteEvent configuration + * enabled?: bool|Param, // Default: false + * }, + * json_streamer?: bool|array{ // JSON streamer configuration + * enabled?: bool|Param, // Default: false + * }, + * } + * @psalm-type TwigConfig = array{ + * form_themes?: list, + * globals?: array, + * autoescape_service?: scalar|null|Param, // Default: null + * autoescape_service_method?: scalar|null|Param, // Default: null + * cache?: scalar|null|Param, // Default: true + * charset?: scalar|null|Param, // Default: "%kernel.charset%" + * debug?: bool|Param, // Default: "%kernel.debug%" + * strict_variables?: bool|Param, // Default: "%kernel.debug%" + * auto_reload?: scalar|null|Param, + * optimizations?: int|Param, + * default_path?: scalar|null|Param, // The default path used to load templates. // Default: "%kernel.project_dir%/templates" + * file_name_pattern?: list, + * paths?: array, + * date?: array{ // The default format options used by the date filter. + * format?: scalar|null|Param, // Default: "F j, Y H:i" + * interval_format?: scalar|null|Param, // Default: "%d days" + * timezone?: scalar|null|Param, // The timezone used when formatting dates, when set to null, the timezone returned by date_default_timezone_get() is used. // Default: null + * }, + * number_format?: array{ // The default format options for the number_format filter. + * decimals?: int|Param, // Default: 0 + * decimal_point?: scalar|null|Param, // Default: "." + * thousands_separator?: scalar|null|Param, // Default: "," + * }, + * mailer?: array{ + * html_to_text_converter?: scalar|null|Param, // A service implementing the "Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface". // Default: null + * }, + * } + * @psalm-type TwigComponentConfig = array{ + * defaults?: array, + * anonymous_template_directory?: scalar|null|Param, // Defaults to `components` + * profiler?: bool|Param, // Enables the profiler for Twig Component (in debug mode) // Default: "%kernel.debug%" + * controllers_json?: scalar|null|Param, // Deprecated: The "twig_component.controllers_json" config option is deprecated, and will be removed in 3.0. // Default: null + * } + * @psalm-type SyliusTwigHooksConfig = array{ + * enable_autoprefixing?: bool|Param, // Default: false + * hook_name_section_separator?: scalar|null|Param, // Default: false + * supported_hookable_types?: array, + * hooks?: array, + * props?: array, + * configuration?: array, + * priority?: int|Param, // Default: null + * }>>, + * } + * @psalm-type SyliusTwigExtraConfig = array{ + * twig_ux?: array{ + * anonymous_component_template_prefixes?: array, + * }, + * } + * @psalm-type SyliusAdminUiConfig = array{ + * routing?: array, + * } + * @psalm-type SyliusUiTranslationsConfig = array + * @psalm-type SyliusBootstrapAdminUiConfig = array + * @psalm-type WebProfilerConfig = array{ + * toolbar?: bool|array{ // Profiler toolbar configuration + * enabled?: bool|Param, // Default: false + * ajax_replace?: bool|Param, // Replace toolbar on AJAX requests // Default: false + * }, + * intercept_redirects?: bool|Param, // Default: false + * excluded_ajax_paths?: scalar|null|Param, // Default: "^/((index|app(_[\\w]+)?)\\.php/)?_wdt" + * } + * @psalm-type DebugConfig = array{ + * max_items?: int|Param, // Max number of displayed items past the first level, -1 means no limit. // Default: 2500 + * min_depth?: int|Param, // Minimum tree depth to clone all the items, 1 is default. // Default: 1 + * max_string_length?: int|Param, // Max length of displayed strings, -1 means no limit. // Default: -1 + * dump_destination?: scalar|null|Param, // A stream URL where dumps should be written to. // Default: null + * theme?: "dark"|"light"|Param, // Changes the color of the dump() output when rendered directly on the templating. "dark" (default) or "light". // Default: "dark" + * } + * @psalm-type LiveComponentConfig = array{ + * secret?: scalar|null|Param, // The secret used to compute fingerprints and checksums // Default: "%kernel.secret%" + * } + * @psalm-type StimulusConfig = array{ + * controller_paths?: list, + * controllers_json?: scalar|null|Param, // Default: "%kernel.project_dir%/assets/controllers.json" + * } + * @psalm-type DoctrineConfig = array{ + * dbal?: array{ + * default_connection?: scalar|null|Param, + * types?: array, + * driver_schemes?: array, + * connections?: array, + * mapping_types?: array, + * default_table_options?: array, + * schema_manager_factory?: scalar|null|Param, // Default: "doctrine.dbal.default_schema_manager_factory" + * result_cache?: scalar|null|Param, + * replicas?: array, + * }>, + * }, + * orm?: array{ + * default_entity_manager?: scalar|null|Param, + * enable_native_lazy_objects?: bool|Param, // Deprecated: The "enable_native_lazy_objects" option is deprecated and will be removed in DoctrineBundle 4.0, as native lazy objects are now always enabled. // Default: true + * controller_resolver?: bool|array{ + * enabled?: bool|Param, // Default: true + * auto_mapping?: bool|Param, // Deprecated: The "doctrine.orm.controller_resolver.auto_mapping.auto_mapping" option is deprecated and will be removed in DoctrineBundle 4.0, as it only accepts `false` since 3.0. // Set to true to enable using route placeholders as lookup criteria when the primary key doesn't match the argument name // Default: false + * evict_cache?: bool|Param, // Set to true to fetch the entity from the database instead of using the cache, if any // Default: false + * }, + * entity_managers?: array, + * }>, + * }>, + * }, + * connection?: scalar|null|Param, + * class_metadata_factory_name?: scalar|null|Param, // Default: "Doctrine\\ORM\\Mapping\\ClassMetadataFactory" + * default_repository_class?: scalar|null|Param, // Default: "Doctrine\\ORM\\EntityRepository" + * auto_mapping?: scalar|null|Param, // Default: false + * naming_strategy?: scalar|null|Param, // Default: "doctrine.orm.naming_strategy.default" + * quote_strategy?: scalar|null|Param, // Default: "doctrine.orm.quote_strategy.default" + * typed_field_mapper?: scalar|null|Param, // Default: "doctrine.orm.typed_field_mapper.default" + * entity_listener_resolver?: scalar|null|Param, // Default: null + * fetch_mode_subselect_batch_size?: scalar|null|Param, + * repository_factory?: scalar|null|Param, // Default: "doctrine.orm.container_repository_factory" + * schema_ignore_classes?: list, + * validate_xml_mapping?: bool|Param, // Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.14 and will be mandatory in ORM 3.0. See https://github.com/doctrine/orm/pull/6728. // Default: false + * second_level_cache?: array{ + * region_cache_driver?: string|array{ + * type?: scalar|null|Param, // Default: null + * id?: scalar|null|Param, + * pool?: scalar|null|Param, + * }, + * region_lock_lifetime?: scalar|null|Param, // Default: 60 + * log_enabled?: bool|Param, // Default: true + * region_lifetime?: scalar|null|Param, // Default: 3600 + * enabled?: bool|Param, // Default: true + * factory?: scalar|null|Param, + * regions?: array, + * loggers?: array, + * }, + * hydrators?: array, + * mappings?: array, + * dql?: array{ + * string_functions?: array, + * numeric_functions?: array, + * datetime_functions?: array, + * }, + * filters?: array, + * }>, + * identity_generation_preferences?: array, + * }>, + * resolve_target_entities?: array, + * }, + * } + * @psalm-type BabdevPagerfantaConfig = array{ + * default_view?: scalar|null|Param, // Default: "default" + * default_twig_template?: scalar|null|Param, // Default: "@BabDevPagerfanta/default.html.twig" + * exceptions_strategy?: array{ + * out_of_range_page?: "to_http_not_found"|"custom"|Param, // Default: "to_http_not_found" + * not_valid_current_page?: "to_http_not_found"|"custom"|Param, // Default: "to_http_not_found" + * }, + * } + * @psalm-type SyliusResourceConfig = array{ + * resources?: array, + * settings?: array{ + * paginate?: mixed, // Default: null + * limit?: mixed, // Default: null + * allowed_paginate?: list, + * default_page_size?: int|Param, // Default: 10 + * default_templates_dir?: scalar|null|Param, // Default: null + * sortable?: bool|Param, // Default: false + * sorting?: mixed, // Default: null + * filterable?: bool|Param, // Default: false + * criteria?: mixed, // Default: null + * state_machine_component?: scalar|null|Param, // Default: null + * }, + * translation?: bool|array{ + * enabled?: bool|Param, // Default: true + * locale_provider?: scalar|null|Param, // Default: "sylius.translation_locale_provider.immutable" + * }, + * drivers?: list<"doctrine/orm"|"doctrine/mongodb-odm"|"doctrine/phpcr-odm"|Param>, + * mapping?: array{ + * imports?: list, + * paths?: list, + * }, + * authorization_checker?: scalar|null|Param, // Default: "sylius.resource_controller.authorization_checker.disabled" + * routing_path_bc_layer?: bool|Param, + * path_segment_name_generator?: scalar|null|Param, // Specify a path name generator to use. // Default: "sylius.metadata.path_segment_name_generator.dash" + * } + * @psalm-type ZenstruckFoundryConfig = array{ + * auto_refresh_proxies?: bool|null|Param, // Deprecated: Since 2.0 auto_refresh_proxies defaults to true and this configuration has no effect. // Whether to auto-refresh proxies by default (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#auto-refresh) // Default: null + * enable_auto_refresh_with_lazy_objects?: bool|null|Param, // Enable auto-refresh using PHP 8.4 lazy objects (cannot be enabled if PHP < 8.4). // Default: null + * faker?: array{ // Configure the faker used by your factories. + * locale?: scalar|null|Param, // The default locale to use for faker. // Default: null + * seed?: scalar|null|Param, // Deprecated: The "faker.seed" configuration is deprecated and will be removed in 3.0. Use environment variable "FOUNDRY_FAKER_SEED" instead. // Random number generator seed to produce the same fake values every run. // Default: null + * service?: scalar|null|Param, // Service id for custom faker instance. // Default: null + * }, + * instantiator?: array{ // Configure the default instantiator used by your object factories. + * use_constructor?: bool|Param, // Use the constructor to instantiate objects. // Default: true + * allow_extra_attributes?: bool|Param, // Whether or not to skip attributes that do not correspond to properties. // Default: false + * always_force_properties?: bool|Param, // Whether or not to skip setters and force set object properties (public/private/protected) directly. // Default: false + * service?: scalar|null|Param, // Service id of your custom instantiator. // Default: null + * }, + * global_state?: list, + * persistence?: array{ + * flush_once?: bool|Param, // Flush only once per call of `PersistentObjectFactory::create()` in userland. // Default: false + * }, + * orm?: array{ + * auto_persist?: bool|Param, // Deprecated: Since 2.4 auto_persist defaults to true and this configuration has no effect. // Automatically persist entities when created. // Default: true + * reset?: array{ + * connections?: list, + * entity_managers?: list, + * mode?: \Zenstruck\Foundry\ORM\ResetDatabase\ResetDatabaseMode::SCHEMA|\Zenstruck\Foundry\ORM\ResetDatabase\ResetDatabaseMode::MIGRATE|Param, // Reset mode to use with ResetDatabase trait // Default: "schema" + * migrations?: array{ + * configurations?: list, + * }, + * }, + * }, + * mongo?: array{ + * auto_persist?: bool|Param, // Deprecated: Since 2.4 auto_persist defaults to true and this configuration has no effect. // Automatically persist documents when created. // Default: true + * reset?: array{ + * document_managers?: list, + * }, + * }, + * make_factory?: array{ + * default_namespace?: scalar|null|Param, // Default namespace where factories will be created by maker. // Default: "Factory" + * add_hints?: bool|Param, // Add "beginner" hints in the created factory. // Default: true + * }, + * make_story?: array{ + * default_namespace?: scalar|null|Param, // Default namespace where stories will be created by maker. // Default: "Story" + * }, + * } + * @psalm-type KnpMenuConfig = array{ + * providers?: array{ + * builder_alias?: bool|Param, // Default: true + * }, + * twig?: array{ + * template?: scalar|null|Param, // Default: "@KnpMenu/menu.html.twig" + * }, + * templating?: bool|Param, // Default: false + * default_renderer?: scalar|null|Param, // Default: "twig" + * } + * @psalm-type SyliusGridConfig = array{ + * drivers?: list<"doctrine/orm"|"doctrine/phpcr-odm"|Param>, + * templates?: array{ + * filter?: array, + * action?: array, + * bulk_action?: array, + * }, + * grids?: array, + * }, + * sorting?: array, + * limits?: list, + * fields?: array, + * }>, + * filters?: array, + * form_options?: list, + * default_value?: mixed, + * }>, + * actions?: array, + * }>>, + * removals?: array, + * }>, + * } + * @psalm-type VichUploaderConfig = array{ + * default_filename_attribute_suffix?: scalar|null|Param, // Default: "_name" + * db_driver: scalar|null|Param, + * storage?: scalar|null|Param, // Default: "file_system" + * use_flysystem_to_resolve_uri?: bool|Param, // Default: false + * twig?: scalar|null|Param, // twig requires templating // Default: true + * form?: scalar|null|Param, // Default: true + * metadata?: array{ + * cache?: scalar|null|Param, // Default: "file" + * type?: scalar|null|Param, // Default: "attribute" + * file_cache?: array{ + * dir?: scalar|null|Param, // Default: "%kernel.cache_dir%/vich_uploader" + * }, + * auto_detection?: bool|Param, // Default: true + * directories?: list, + * }, + * mappings?: array, + * } + * @psalm-type MakerConfig = array{ + * root_namespace?: scalar|null|Param, // Default: "App" + * generate_final_classes?: bool|Param, // Default: true + * generate_final_entities?: bool|Param, // Default: false + * } + * @psalm-type SecurityConfig = array{ + * access_denied_url?: scalar|null|Param, // Default: null + * session_fixation_strategy?: "none"|"migrate"|"invalidate"|Param, // Default: "migrate" + * expose_security_errors?: \Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::None|\Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::AccountStatus|\Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::All|Param, // Default: "none" + * erase_credentials?: bool|Param, // Default: true + * access_decision_manager?: array{ + * strategy?: "affirmative"|"consensus"|"unanimous"|"priority"|Param, + * service?: scalar|null|Param, + * strategy_service?: scalar|null|Param, + * allow_if_all_abstain?: bool|Param, // Default: false + * allow_if_equal_granted_denied?: bool|Param, // Default: true + * }, + * password_hashers?: array, + * hash_algorithm?: scalar|null|Param, // Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms. // Default: "sha512" + * key_length?: scalar|null|Param, // Default: 40 + * ignore_case?: bool|Param, // Default: false + * encode_as_base64?: bool|Param, // Default: true + * iterations?: scalar|null|Param, // Default: 5000 + * cost?: int|Param, // Default: null + * memory_cost?: scalar|null|Param, // Default: null + * time_cost?: scalar|null|Param, // Default: null + * id?: scalar|null|Param, + * }>, + * providers?: array, + * }, + * entity?: array{ + * class: scalar|null|Param, // The full entity class name of your user class. + * property?: scalar|null|Param, // Default: null + * manager_name?: scalar|null|Param, // Default: null + * }, + * memory?: array{ + * users?: array, + * }>, + * }, + * ldap?: array{ + * service: scalar|null|Param, + * base_dn: scalar|null|Param, + * search_dn?: scalar|null|Param, // Default: null + * search_password?: scalar|null|Param, // Default: null + * extra_fields?: list, + * default_roles?: list, + * role_fetcher?: scalar|null|Param, // Default: null + * uid_key?: scalar|null|Param, // Default: "sAMAccountName" + * filter?: scalar|null|Param, // Default: "({uid_key}={user_identifier})" + * password_attribute?: scalar|null|Param, // Default: null + * }, + * }>, + * firewalls: array, + * security?: bool|Param, // Default: true + * user_checker?: scalar|null|Param, // The UserChecker to use when authenticating users in this firewall. // Default: "security.user_checker" + * request_matcher?: scalar|null|Param, + * access_denied_url?: scalar|null|Param, + * access_denied_handler?: scalar|null|Param, + * entry_point?: scalar|null|Param, // An enabled authenticator name or a service id that implements "Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface". + * provider?: scalar|null|Param, + * stateless?: bool|Param, // Default: false + * lazy?: bool|Param, // Default: false + * context?: scalar|null|Param, + * logout?: array{ + * enable_csrf?: bool|null|Param, // Default: null + * csrf_token_id?: scalar|null|Param, // Default: "logout" + * csrf_parameter?: scalar|null|Param, // Default: "_csrf_token" + * csrf_token_manager?: scalar|null|Param, + * path?: scalar|null|Param, // Default: "/logout" + * target?: scalar|null|Param, // Default: "/" + * invalidate_session?: bool|Param, // Default: true + * clear_site_data?: list<"*"|"cache"|"cookies"|"storage"|"executionContexts"|Param>, + * delete_cookies?: array, + * }, + * switch_user?: array{ + * provider?: scalar|null|Param, + * parameter?: scalar|null|Param, // Default: "_switch_user" + * role?: scalar|null|Param, // Default: "ROLE_ALLOWED_TO_SWITCH" + * target_route?: scalar|null|Param, // Default: null + * }, + * required_badges?: list, + * custom_authenticators?: list, + * login_throttling?: array{ + * limiter?: scalar|null|Param, // A service id implementing "Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface". + * max_attempts?: int|Param, // Default: 5 + * interval?: scalar|null|Param, // Default: "1 minute" + * lock_factory?: scalar|null|Param, // The service ID of the lock factory used by the login rate limiter (or null to disable locking). // Default: null + * cache_pool?: string|Param, // The cache pool to use for storing the limiter state // Default: "cache.rate_limiter" + * storage_service?: string|Param, // The service ID of a custom storage implementation, this precedes any configured "cache_pool" // Default: null + * }, + * x509?: array{ + * provider?: scalar|null|Param, + * user?: scalar|null|Param, // Default: "SSL_CLIENT_S_DN_Email" + * credentials?: scalar|null|Param, // Default: "SSL_CLIENT_S_DN" + * user_identifier?: scalar|null|Param, // Default: "emailAddress" + * }, + * remote_user?: array{ + * provider?: scalar|null|Param, + * user?: scalar|null|Param, // Default: "REMOTE_USER" + * }, + * login_link?: array{ + * check_route: scalar|null|Param, // Route that will validate the login link - e.g. "app_login_link_verify". + * check_post_only?: scalar|null|Param, // If true, only HTTP POST requests to "check_route" will be handled by the authenticator. // Default: false + * signature_properties: list, + * lifetime?: int|Param, // The lifetime of the login link in seconds. // Default: 600 + * max_uses?: int|Param, // Max number of times a login link can be used - null means unlimited within lifetime. // Default: null + * used_link_cache?: scalar|null|Param, // Cache service id used to expired links of max_uses is set. + * success_handler?: scalar|null|Param, // A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface. + * failure_handler?: scalar|null|Param, // A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface. + * provider?: scalar|null|Param, // The user provider to load users from. + * secret?: scalar|null|Param, // Default: "%kernel.secret%" + * always_use_default_target_path?: bool|Param, // Default: false + * default_target_path?: scalar|null|Param, // Default: "/" + * login_path?: scalar|null|Param, // Default: "/login" + * target_path_parameter?: scalar|null|Param, // Default: "_target_path" + * use_referer?: bool|Param, // Default: false + * failure_path?: scalar|null|Param, // Default: null + * failure_forward?: bool|Param, // Default: false + * failure_path_parameter?: scalar|null|Param, // Default: "_failure_path" + * }, + * form_login?: array{ + * provider?: scalar|null|Param, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|null|Param, + * failure_handler?: scalar|null|Param, + * check_path?: scalar|null|Param, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|null|Param, // Default: "/login" + * username_parameter?: scalar|null|Param, // Default: "_username" + * password_parameter?: scalar|null|Param, // Default: "_password" + * csrf_parameter?: scalar|null|Param, // Default: "_csrf_token" + * csrf_token_id?: scalar|null|Param, // Default: "authenticate" + * enable_csrf?: bool|Param, // Default: false + * post_only?: bool|Param, // Default: true + * form_only?: bool|Param, // Default: false + * always_use_default_target_path?: bool|Param, // Default: false + * default_target_path?: scalar|null|Param, // Default: "/" + * target_path_parameter?: scalar|null|Param, // Default: "_target_path" + * use_referer?: bool|Param, // Default: false + * failure_path?: scalar|null|Param, // Default: null + * failure_forward?: bool|Param, // Default: false + * failure_path_parameter?: scalar|null|Param, // Default: "_failure_path" + * }, + * form_login_ldap?: array{ + * provider?: scalar|null|Param, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|null|Param, + * failure_handler?: scalar|null|Param, + * check_path?: scalar|null|Param, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|null|Param, // Default: "/login" + * username_parameter?: scalar|null|Param, // Default: "_username" + * password_parameter?: scalar|null|Param, // Default: "_password" + * csrf_parameter?: scalar|null|Param, // Default: "_csrf_token" + * csrf_token_id?: scalar|null|Param, // Default: "authenticate" + * enable_csrf?: bool|Param, // Default: false + * post_only?: bool|Param, // Default: true + * form_only?: bool|Param, // Default: false + * always_use_default_target_path?: bool|Param, // Default: false + * default_target_path?: scalar|null|Param, // Default: "/" + * target_path_parameter?: scalar|null|Param, // Default: "_target_path" + * use_referer?: bool|Param, // Default: false + * failure_path?: scalar|null|Param, // Default: null + * failure_forward?: bool|Param, // Default: false + * failure_path_parameter?: scalar|null|Param, // Default: "_failure_path" + * service?: scalar|null|Param, // Default: "ldap" + * dn_string?: scalar|null|Param, // Default: "{user_identifier}" + * query_string?: scalar|null|Param, + * search_dn?: scalar|null|Param, // Default: "" + * search_password?: scalar|null|Param, // Default: "" + * }, + * json_login?: array{ + * provider?: scalar|null|Param, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|null|Param, + * failure_handler?: scalar|null|Param, + * check_path?: scalar|null|Param, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|null|Param, // Default: "/login" + * username_path?: scalar|null|Param, // Default: "username" + * password_path?: scalar|null|Param, // Default: "password" + * }, + * json_login_ldap?: array{ + * provider?: scalar|null|Param, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|null|Param, + * failure_handler?: scalar|null|Param, + * check_path?: scalar|null|Param, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|null|Param, // Default: "/login" + * username_path?: scalar|null|Param, // Default: "username" + * password_path?: scalar|null|Param, // Default: "password" + * service?: scalar|null|Param, // Default: "ldap" + * dn_string?: scalar|null|Param, // Default: "{user_identifier}" + * query_string?: scalar|null|Param, + * search_dn?: scalar|null|Param, // Default: "" + * search_password?: scalar|null|Param, // Default: "" + * }, + * access_token?: array{ + * provider?: scalar|null|Param, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|null|Param, + * failure_handler?: scalar|null|Param, + * realm?: scalar|null|Param, // Default: null + * token_extractors?: list, + * token_handler: string|array{ + * id?: scalar|null|Param, + * oidc_user_info?: string|array{ + * base_uri: scalar|null|Param, // Base URI of the userinfo endpoint on the OIDC server, or the OIDC server URI to use the discovery (require "discovery" to be configured). + * discovery?: array{ // Enable the OIDC discovery. + * cache?: array{ + * id: scalar|null|Param, // Cache service id to use to cache the OIDC discovery configuration. + * }, + * }, + * claim?: scalar|null|Param, // Claim which contains the user identifier (e.g. sub, email, etc.). // Default: "sub" + * client?: scalar|null|Param, // HttpClient service id to use to call the OIDC server. + * }, + * oidc?: array{ + * discovery?: array{ // Enable the OIDC discovery. + * base_uri: list, + * cache?: array{ + * id: scalar|null|Param, // Cache service id to use to cache the OIDC discovery configuration. + * }, + * }, + * claim?: scalar|null|Param, // Claim which contains the user identifier (e.g.: sub, email..). // Default: "sub" + * audience: scalar|null|Param, // Audience set in the token, for validation purpose. + * issuers: list, + * algorithms: list, + * keyset?: scalar|null|Param, // JSON-encoded JWKSet used to sign the token (must contain a list of valid public keys). + * encryption?: bool|array{ + * enabled?: bool|Param, // Default: false + * enforce?: bool|Param, // When enabled, the token shall be encrypted. // Default: false + * algorithms: list, + * keyset: scalar|null|Param, // JSON-encoded JWKSet used to decrypt the token (must contain a list of valid private keys). + * }, + * }, + * cas?: array{ + * validation_url: scalar|null|Param, // CAS server validation URL + * prefix?: scalar|null|Param, // CAS prefix // Default: "cas" + * http_client?: scalar|null|Param, // HTTP Client service // Default: null + * }, + * oauth2?: scalar|null|Param, + * }, + * }, + * http_basic?: array{ + * provider?: scalar|null|Param, + * realm?: scalar|null|Param, // Default: "Secured Area" + * }, + * http_basic_ldap?: array{ + * provider?: scalar|null|Param, + * realm?: scalar|null|Param, // Default: "Secured Area" + * service?: scalar|null|Param, // Default: "ldap" + * dn_string?: scalar|null|Param, // Default: "{user_identifier}" + * query_string?: scalar|null|Param, + * search_dn?: scalar|null|Param, // Default: "" + * search_password?: scalar|null|Param, // Default: "" + * }, + * remember_me?: array{ + * secret?: scalar|null|Param, // Default: "%kernel.secret%" + * service?: scalar|null|Param, + * user_providers?: list, + * catch_exceptions?: bool|Param, // Default: true + * signature_properties?: list, + * token_provider?: string|array{ + * service?: scalar|null|Param, // The service ID of a custom remember-me token provider. + * doctrine?: bool|array{ + * enabled?: bool|Param, // Default: false + * connection?: scalar|null|Param, // Default: null + * }, + * }, + * token_verifier?: scalar|null|Param, // The service ID of a custom rememberme token verifier. + * name?: scalar|null|Param, // Default: "REMEMBERME" + * lifetime?: int|Param, // Default: 31536000 + * path?: scalar|null|Param, // Default: "/" + * domain?: scalar|null|Param, // Default: null + * secure?: true|false|"auto"|Param, // Default: null + * httponly?: bool|Param, // Default: true + * samesite?: null|"lax"|"strict"|"none"|Param, // Default: "lax" + * always_remember_me?: bool|Param, // Default: false + * remember_me_parameter?: scalar|null|Param, // Default: "_remember_me" + * }, + * }>, + * access_control?: list, + * attributes?: array, + * route?: scalar|null|Param, // Default: null + * methods?: list, + * allow_if?: scalar|null|Param, // Default: null + * roles?: list, + * }>, + * role_hierarchy?: array>, + * } + * @psalm-type UxIconsConfig = array{ + * icon_dir?: scalar|null|Param, // The local directory where icons are stored. // Default: "%kernel.project_dir%/assets/icons" + * default_icon_attributes?: mixed, // Default attributes to add to all icons. // Default: {"fill":"currentColor"} + * icon_sets?: array, + * }>, + * aliases?: list, + * iconify?: bool|array{ // Configuration for the remote icon service. + * enabled?: bool|Param, // Default: true + * on_demand?: bool|Param, // Whether to download icons "on demand". // Default: true + * endpoint?: scalar|null|Param, // The endpoint for the Iconify icons API. // Default: "https://api.iconify.design" + * }, + * ignore_not_found?: bool|Param, // Ignore error when an icon is not found. Set to 'true' to fail silently. // Default: false + * } + * @psalm-type WhiteOctoberPagerfantaConfig = array{ // Deprecated: The "white_october_pagerfanta" configuration node is deprecated, migrate your configuration to the "babdev_pagerfanta" configuration node. + * exceptions_strategy?: array{ + * out_of_range_page?: scalar|null|Param, // Default: "to_http_not_found" + * not_valid_current_page?: scalar|null|Param, // Default: "to_http_not_found" + * }, + * default_view?: scalar|null|Param, // Default: "default" + * } + * @psalm-type ConfigType = array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * twig?: TwigConfig, + * twig_component?: TwigComponentConfig, + * sylius_twig_hooks?: SyliusTwigHooksConfig, + * sylius_twig_extra?: SyliusTwigExtraConfig, + * sylius_admin_ui?: SyliusAdminUiConfig, + * sylius_ui_translations?: SyliusUiTranslationsConfig, + * sylius_bootstrap_admin_ui?: SyliusBootstrapAdminUiConfig, + * live_component?: LiveComponentConfig, + * stimulus?: StimulusConfig, + * doctrine?: DoctrineConfig, + * babdev_pagerfanta?: BabdevPagerfantaConfig, + * sylius_resource?: SyliusResourceConfig, + * knp_menu?: KnpMenuConfig, + * sylius_grid?: SyliusGridConfig, + * vich_uploader?: VichUploaderConfig, + * security?: SecurityConfig, + * ux_icons?: UxIconsConfig, + * white_october_pagerfanta?: WhiteOctoberPagerfantaConfig, + * "when@dev"?: array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * twig?: TwigConfig, + * twig_component?: TwigComponentConfig, + * sylius_twig_hooks?: SyliusTwigHooksConfig, + * sylius_twig_extra?: SyliusTwigExtraConfig, + * sylius_admin_ui?: SyliusAdminUiConfig, + * sylius_ui_translations?: SyliusUiTranslationsConfig, + * sylius_bootstrap_admin_ui?: SyliusBootstrapAdminUiConfig, + * web_profiler?: WebProfilerConfig, + * debug?: DebugConfig, + * live_component?: LiveComponentConfig, + * stimulus?: StimulusConfig, + * doctrine?: DoctrineConfig, + * babdev_pagerfanta?: BabdevPagerfantaConfig, + * sylius_resource?: SyliusResourceConfig, + * zenstruck_foundry?: ZenstruckFoundryConfig, + * knp_menu?: KnpMenuConfig, + * sylius_grid?: SyliusGridConfig, + * vich_uploader?: VichUploaderConfig, + * maker?: MakerConfig, + * security?: SecurityConfig, + * ux_icons?: UxIconsConfig, + * }, + * "when@prod"?: array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * twig?: TwigConfig, + * twig_component?: TwigComponentConfig, + * sylius_twig_hooks?: SyliusTwigHooksConfig, + * sylius_twig_extra?: SyliusTwigExtraConfig, + * sylius_admin_ui?: SyliusAdminUiConfig, + * sylius_ui_translations?: SyliusUiTranslationsConfig, + * sylius_bootstrap_admin_ui?: SyliusBootstrapAdminUiConfig, + * live_component?: LiveComponentConfig, + * stimulus?: StimulusConfig, + * doctrine?: DoctrineConfig, + * babdev_pagerfanta?: BabdevPagerfantaConfig, + * sylius_resource?: SyliusResourceConfig, + * knp_menu?: KnpMenuConfig, + * sylius_grid?: SyliusGridConfig, + * vich_uploader?: VichUploaderConfig, + * security?: SecurityConfig, + * ux_icons?: UxIconsConfig, + * }, + * "when@test"?: array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * twig?: TwigConfig, + * twig_component?: TwigComponentConfig, + * sylius_twig_hooks?: SyliusTwigHooksConfig, + * sylius_twig_extra?: SyliusTwigExtraConfig, + * sylius_admin_ui?: SyliusAdminUiConfig, + * sylius_ui_translations?: SyliusUiTranslationsConfig, + * sylius_bootstrap_admin_ui?: SyliusBootstrapAdminUiConfig, + * web_profiler?: WebProfilerConfig, + * live_component?: LiveComponentConfig, + * stimulus?: StimulusConfig, + * doctrine?: DoctrineConfig, + * babdev_pagerfanta?: BabdevPagerfantaConfig, + * sylius_resource?: SyliusResourceConfig, + * zenstruck_foundry?: ZenstruckFoundryConfig, + * knp_menu?: KnpMenuConfig, + * sylius_grid?: SyliusGridConfig, + * vich_uploader?: VichUploaderConfig, + * security?: SecurityConfig, + * ux_icons?: UxIconsConfig, + * }, + * ..., + * }> + * } + */ +final class App +{ + /** + * @param ConfigType $config + * + * @psalm-return ConfigType + */ + public static function config(array $config): array + { + return AppReference::config($config); + } +} + +namespace Symfony\Component\Routing\Loader\Configurator; + +/** + * This class provides array-shapes for configuring the routes of an application. + * + * Example: + * + * ```php + * // config/routes.php + * namespace Symfony\Component\Routing\Loader\Configurator; + * + * return Routes::config([ + * 'controllers' => [ + * 'resource' => 'routing.controllers', + * ], + * ]); + * ``` + * + * @psalm-type RouteConfig = array{ + * path: string|array, + * controller?: string, + * methods?: string|list, + * requirements?: array, + * defaults?: array, + * options?: array, + * host?: string|array, + * schemes?: string|list, + * condition?: string, + * locale?: string, + * format?: string, + * utf8?: bool, + * stateless?: bool, + * } + * @psalm-type ImportConfig = array{ + * resource: string, + * type?: string, + * exclude?: string|list, + * prefix?: string|array, + * name_prefix?: string, + * trailing_slash_on_root?: bool, + * controller?: string, + * methods?: string|list, + * requirements?: array, + * defaults?: array, + * options?: array, + * host?: string|array, + * schemes?: string|list, + * condition?: string, + * locale?: string, + * format?: string, + * utf8?: bool, + * stateless?: bool, + * } + * @psalm-type AliasConfig = array{ + * alias: string, + * deprecated?: array{package:string, version:string, message?:string}, + * } + * @psalm-type RoutesConfig = array{ + * "when@dev"?: array, + * "when@prod"?: array, + * "when@test"?: array, + * ... + * } + */ +final class Routes +{ + /** + * @param RoutesConfig $config + * + * @psalm-return RoutesConfig + */ + public static function config(array $config): array + { + return $config; + } +} diff --git a/config/routes/framework.yaml b/config/routes/framework.yaml index 0fc74bbac..bc1feace1 100644 --- a/config/routes/framework.yaml +++ b/config/routes/framework.yaml @@ -1,4 +1,4 @@ when@dev: _errors: - resource: '@FrameworkBundle/Resources/config/routing/errors.xml' + resource: '@FrameworkBundle/Resources/config/routing/errors.php' prefix: /_error diff --git a/config/routes/sylius_resource.yaml b/config/routes/sylius_resource.yaml index 0c8ade2c2..e4c326435 100644 --- a/config/routes/sylius_resource.yaml +++ b/config/routes/sylius_resource.yaml @@ -2,6 +2,6 @@ sylius_crud_routes: resource: 'sylius.routing.loader.crud_routes_attributes' type: service -sylius_routes: - resource: 'sylius.routing.loader.routes_attributes' +sylius_resource_routes: + resource: 'sylius.symfony.routing.loader.resource' type: service diff --git a/config/routes/web_profiler.yaml b/config/routes/web_profiler.yaml index 8d85319fd..b3b7b4b0e 100644 --- a/config/routes/web_profiler.yaml +++ b/config/routes/web_profiler.yaml @@ -1,8 +1,8 @@ when@dev: web_profiler_wdt: - resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml' + resource: '@WebProfilerBundle/Resources/config/routing/wdt.php' prefix: /_wdt web_profiler_profiler: - resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml' + resource: '@WebProfilerBundle/Resources/config/routing/profiler.php' prefix: /_profiler diff --git a/ecs.php b/ecs.php index f05b3cb3d..e95ce5c4a 100644 --- a/ecs.php +++ b/ecs.php @@ -11,6 +11,12 @@ __DIR__ . '/src', ]); + $ecsConfig->skip([ + __DIR__ . '/config/reference.php', + __DIR__ . '/src/AdminUi/tests/Functional/.application/config/reference.php', + __DIR__ . '/src/BootstrapAdminUi/tests/Functional/.application/config/reference.php', + ]); + $ecsConfig->import('vendor/sylius-labs/coding-standard/ecs.php'); $ecsConfig->ruleWithConfiguration(HeaderCommentFixer::class, [ diff --git a/importmap.php b/importmap.php index 3b49af095..ea1cce89e 100644 --- a/importmap.php +++ b/importmap.php @@ -45,4 +45,12 @@ '@symfony/ux-live-component' => [ 'path' => './vendor/symfony/ux-live-component/assets/dist/live_controller.js', ], + 'tom-select/dist/css/tom-select.bootstrap4.css' => [ + 'version' => '2.4.3', + 'type' => 'css', + ], + 'tom-select/dist/css/tom-select.bootstrap5.css' => [ + 'version' => '2.4.3', + 'type' => 'css', + ], ]; diff --git a/monorepo-builder.php b/monorepo-builder.php index 98505c2cd..3298891c6 100644 --- a/monorepo-builder.php +++ b/monorepo-builder.php @@ -22,7 +22,7 @@ ComposerJsonSection::REQUIRE_DEV => [ 'phpstan/phpstan' => '^1.10', 'phpstan/phpstan-symfony' => '^1.3', - 'symfony/debug-bundle' => '^6.4 || ^7.0', + 'symfony/debug-bundle' => '^6.4 || ^7.4 || ^8.0', 'symfony/flex' => '^2.4', 'symplify/monorepo-builder' => '11.2.*', ], diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 33f9b132a..8b23813b6 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,6 +1,16 @@ parameters: ignoreErrors: - - message: "#^Unsafe usage of new static\\(\\)\\.$#" + message: "#^Call to method arrayNode\\(\\) on an unknown class Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeBuilder\\\\.$#" count: 1 - path: src/TwigHooks/src/Hookable/AbstractHookable.php + path: src/TwigExtra/src/Symfony/DependencyInjection/Configuration.php + + - + message: "#^Call to method arrayNode\\(\\) on an unknown class Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeBuilder\\\\.$#" + count: 2 + path: src/TwigHooks/src/DependencyInjection/Configuration.php + + - + message: "#^Call to method booleanNode\\(\\) on an unknown class Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeBuilder\\\\.$#" + count: 1 + path: src/TwigHooks/src/DependencyInjection/Configuration.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 57dd47cab..03391d70c 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -6,7 +6,6 @@ backupGlobals="false" colors="true" bootstrap="tests/bootstrap.php" - convertDeprecationsToExceptions="false" > diff --git a/src/AdminUi/composer.json b/src/AdminUi/composer.json index cab1dfcf5..4a555b3f6 100644 --- a/src/AdminUi/composer.json +++ b/src/AdminUi/composer.json @@ -6,27 +6,27 @@ "php": "^8.1", "knplabs/knp-menu-bundle": "^3.0", "sylius/twig-hooks": "^0.10", - "symfony/http-kernel": "^6.4 || ^7.0", - "symfony/security-bundle": "^6.4 || ^7.0", - "symfony/security-http": "^6.4 || ^7.0", - "symfony/twig-bundle": "^6.4 || ^7.0", - "sylius/resource-bundle": "^1.11", + "symfony/http-kernel": "^6.4 || ^7.4 || ^8.0", + "symfony/security-bundle": "^6.4 || ^7.4 || ^8.0", + "symfony/security-http": "^6.4 || ^7.4 || ^8.0", + "symfony/twig-bundle": "^6.4 || ^7.4 || ^8.0", + "sylius/resource-bundle": "^1.13 || ^1.14@alpha", "twig/twig": "^2.15 || ^3.0" }, "require-dev": { - "doctrine/doctrine-bundle": "^2.12", - "doctrine/orm": "^2.0", - "matthiasnoback/symfony-dependency-injection-test": "^5.1", - "phpunit/phpunit": "^9.6", - "sylius/grid-bundle": "^1.13", - "symfony/browser-kit": "^6.4 || ^7.0", - "symfony/config": "^6.4 || ^7.0", - "symfony/console": "^6.4 || ^7.0", - "symfony/css-selector": "^6.4 || ^7.0", - "symfony/dotenv": "^6.4 || ^7.0", - "symfony/framework-bundle": "^6.4 || ^7.0", - "symfony/runtime": "^6.4 || ^7.0", - "symfony/yaml": "^6.4 || ^7.0" + "doctrine/doctrine-bundle": "^2.13 || ^3.0 || ^4.0", + "doctrine/orm": "^3.3 || ^4.0", + "matthiasnoback/symfony-dependency-injection-test": "^6.1.0", + "phpunit/phpunit": "^10.5", + "sylius/grid-bundle": "^1.13 || ^1.15@alpha", + "symfony/browser-kit": "^6.4 || ^7.4 || ^8.0", + "symfony/config": "^6.4 || ^7.4 || ^8.0", + "symfony/console": "^6.4 || ^7.4 || ^8.0", + "symfony/css-selector": "^6.4 || ^7.4 || ^8.0", + "symfony/dotenv": "^6.4 || ^7.4 || ^8.0", + "symfony/framework-bundle": "^6.4 || ^7.4 || ^8.0", + "symfony/runtime": "^6.4 || ^7.4 || ^8.0", + "symfony/yaml": "^6.4 || ^7.4 || ^8.0" }, "suggest": { "sylius/bootstrap-admin-ui": "^0.5" diff --git a/src/AdminUi/phpunit.xml.dist b/src/AdminUi/phpunit.xml.dist index 324062d0e..b82c87ad6 100644 --- a/src/AdminUi/phpunit.xml.dist +++ b/src/AdminUi/phpunit.xml.dist @@ -5,7 +5,6 @@ xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd" colors="true" bootstrap="tests/Functional/.application/config/bootstrap.php" - convertDeprecationsToExceptions="true" > diff --git a/src/AdminUi/tests/Functional/.application/config/packages/doctrine.yaml b/src/AdminUi/tests/Functional/.application/config/packages/doctrine.yaml index d42c52d6d..171cd6588 100644 --- a/src/AdminUi/tests/Functional/.application/config/packages/doctrine.yaml +++ b/src/AdminUi/tests/Functional/.application/config/packages/doctrine.yaml @@ -7,11 +7,7 @@ doctrine: #server_version: '16' profiling_collect_backtrace: '%kernel.debug%' - use_savepoints: true orm: - auto_generate_proxy_classes: true - enable_lazy_ghost_objects: true - report_fields_where_declared: true validate_xml_mapping: true naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware auto_mapping: true diff --git a/src/AdminUi/tests/Functional/.application/config/reference.php b/src/AdminUi/tests/Functional/.application/config/reference.php new file mode 100644 index 000000000..3e0f5f1b7 --- /dev/null +++ b/src/AdminUi/tests/Functional/.application/config/reference.php @@ -0,0 +1,1492 @@ + [ + * 'App\\' => [ + * 'resource' => '../src/', + * ], + * ], + * ]); + * ``` + * + * @psalm-type ImportsConfig = list + * @psalm-type ParametersConfig = array|null>|null> + * @psalm-type ArgumentsType = list|array + * @psalm-type CallType = array|array{0:string, 1?:ArgumentsType, 2?:bool}|array{method:string, arguments?:ArgumentsType, returns_clone?:bool} + * @psalm-type TagsType = list>> // arrays inside the list must have only one element, with the tag name as the key + * @psalm-type CallbackType = string|array{0:string|ReferenceConfigurator,1:string}|\Closure|ReferenceConfigurator|ExpressionConfigurator + * @psalm-type DeprecationType = array{package: string, version: string, message?: string} + * @psalm-type DefaultsType = array{ + * public?: bool, + * tags?: TagsType, + * resource_tags?: TagsType, + * autowire?: bool, + * autoconfigure?: bool, + * bind?: array, + * } + * @psalm-type InstanceofType = array{ + * shared?: bool, + * lazy?: bool|string, + * public?: bool, + * properties?: array, + * configurator?: CallbackType, + * calls?: list, + * tags?: TagsType, + * resource_tags?: TagsType, + * autowire?: bool, + * bind?: array, + * constructor?: string, + * } + * @psalm-type DefinitionType = array{ + * class?: string, + * file?: string, + * parent?: string, + * shared?: bool, + * synthetic?: bool, + * lazy?: bool|string, + * public?: bool, + * abstract?: bool, + * deprecated?: DeprecationType, + * factory?: CallbackType, + * configurator?: CallbackType, + * arguments?: ArgumentsType, + * properties?: array, + * calls?: list, + * tags?: TagsType, + * resource_tags?: TagsType, + * decorates?: string, + * decoration_inner_name?: string, + * decoration_priority?: int, + * decoration_on_invalid?: 'exception'|'ignore'|null, + * autowire?: bool, + * autoconfigure?: bool, + * bind?: array, + * constructor?: string, + * from_callable?: CallbackType, + * } + * @psalm-type AliasType = string|array{ + * alias: string, + * public?: bool, + * deprecated?: DeprecationType, + * } + * @psalm-type PrototypeType = array{ + * resource: string, + * namespace?: string, + * exclude?: string|list, + * parent?: string, + * shared?: bool, + * lazy?: bool|string, + * public?: bool, + * abstract?: bool, + * deprecated?: DeprecationType, + * factory?: CallbackType, + * arguments?: ArgumentsType, + * properties?: array, + * configurator?: CallbackType, + * calls?: list, + * tags?: TagsType, + * resource_tags?: TagsType, + * autowire?: bool, + * autoconfigure?: bool, + * bind?: array, + * constructor?: string, + * } + * @psalm-type StackType = array{ + * stack: list>, + * public?: bool, + * deprecated?: DeprecationType, + * } + * @psalm-type ServicesConfig = array{ + * _defaults?: DefaultsType, + * _instanceof?: InstanceofType, + * ... + * } + * @psalm-type ExtensionType = array + * @psalm-type FrameworkConfig = array{ + * secret?: scalar|null, + * http_method_override?: bool, // Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests. // Default: false + * allowed_http_method_override?: list|null, + * trust_x_sendfile_type_header?: scalar|null, // Set true to enable support for xsendfile in binary file responses. // Default: "%env(bool:default::SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER)%" + * ide?: scalar|null, // Default: "%env(default::SYMFONY_IDE)%" + * test?: bool, + * default_locale?: scalar|null, // Default: "en" + * set_locale_from_accept_language?: bool, // Whether to use the Accept-Language HTTP header to set the Request locale (only when the "_locale" request attribute is not passed). // Default: false + * set_content_language_from_locale?: bool, // Whether to set the Content-Language HTTP header on the Response using the Request locale. // Default: false + * enabled_locales?: list, + * trusted_hosts?: list, + * trusted_proxies?: mixed, // Default: ["%env(default::SYMFONY_TRUSTED_PROXIES)%"] + * trusted_headers?: list, + * error_controller?: scalar|null, // Default: "error_controller" + * handle_all_throwables?: bool, // HttpKernel will handle all kinds of \Throwable. // Default: true + * csrf_protection?: bool|array{ + * enabled?: scalar|null, // Default: null + * stateless_token_ids?: list, + * check_header?: scalar|null, // Whether to check the CSRF token in a header in addition to a cookie when using stateless protection. // Default: false + * cookie_name?: scalar|null, // The name of the cookie to use when using stateless protection. // Default: "csrf-token" + * }, + * form?: bool|array{ // Form configuration + * enabled?: bool, // Default: true + * csrf_protection?: array{ + * enabled?: scalar|null, // Default: null + * token_id?: scalar|null, // Default: null + * field_name?: scalar|null, // Default: "_token" + * field_attr?: array, + * }, + * }, + * http_cache?: bool|array{ // HTTP cache configuration + * enabled?: bool, // Default: false + * debug?: bool, // Default: "%kernel.debug%" + * trace_level?: "none"|"short"|"full", + * trace_header?: scalar|null, + * default_ttl?: int, + * private_headers?: list, + * skip_response_headers?: list, + * allow_reload?: bool, + * allow_revalidate?: bool, + * stale_while_revalidate?: int, + * stale_if_error?: int, + * terminate_on_cache_hit?: bool, + * }, + * esi?: bool|array{ // ESI configuration + * enabled?: bool, // Default: false + * }, + * ssi?: bool|array{ // SSI configuration + * enabled?: bool, // Default: false + * }, + * fragments?: bool|array{ // Fragments configuration + * enabled?: bool, // Default: false + * hinclude_default_template?: scalar|null, // Default: null + * path?: scalar|null, // Default: "/_fragment" + * }, + * profiler?: bool|array{ // Profiler configuration + * enabled?: bool, // Default: false + * collect?: bool, // Default: true + * collect_parameter?: scalar|null, // The name of the parameter to use to enable or disable collection on a per request basis. // Default: null + * only_exceptions?: bool, // Default: false + * only_main_requests?: bool, // Default: false + * dsn?: scalar|null, // Default: "file:%kernel.cache_dir%/profiler" + * collect_serializer_data?: true, // Default: true + * }, + * workflows?: bool|array{ + * enabled?: bool, // Default: false + * workflows?: array, + * definition_validators?: list, + * support_strategy?: scalar|null, + * initial_marking?: list, + * events_to_dispatch?: list|null, + * places?: list, + * }>, + * transitions: list, + * to?: list, + * weight?: int, // Default: 1 + * metadata?: list, + * }>, + * metadata?: list, + * }>, + * }, + * router?: bool|array{ // Router configuration + * enabled?: bool, // Default: false + * resource: scalar|null, + * type?: scalar|null, + * default_uri?: scalar|null, // The default URI used to generate URLs in a non-HTTP context. // Default: null + * http_port?: scalar|null, // Default: 80 + * https_port?: scalar|null, // Default: 443 + * strict_requirements?: scalar|null, // set to true to throw an exception when a parameter does not match the requirements set to false to disable exceptions when a parameter does not match the requirements (and return null instead) set to null to disable parameter checks against requirements 'true' is the preferred configuration in development mode, while 'false' or 'null' might be preferred in production // Default: true + * utf8?: bool, // Default: true + * }, + * session?: bool|array{ // Session configuration + * enabled?: bool, // Default: false + * storage_factory_id?: scalar|null, // Default: "session.storage.factory.native" + * handler_id?: scalar|null, // Defaults to using the native session handler, or to the native *file* session handler if "save_path" is not null. + * name?: scalar|null, + * cookie_lifetime?: scalar|null, + * cookie_path?: scalar|null, + * cookie_domain?: scalar|null, + * cookie_secure?: true|false|"auto", // Default: "auto" + * cookie_httponly?: bool, // Default: true + * cookie_samesite?: null|"lax"|"strict"|"none", // Default: "lax" + * use_cookies?: bool, + * gc_divisor?: scalar|null, + * gc_probability?: scalar|null, + * gc_maxlifetime?: scalar|null, + * save_path?: scalar|null, // Defaults to "%kernel.cache_dir%/sessions" if the "handler_id" option is not null. + * metadata_update_threshold?: int, // Seconds to wait between 2 session metadata updates. // Default: 0 + * }, + * request?: bool|array{ // Request configuration + * enabled?: bool, // Default: false + * formats?: array>, + * }, + * assets?: bool|array{ // Assets configuration + * enabled?: bool, // Default: true + * strict_mode?: bool, // Throw an exception if an entry is missing from the manifest.json. // Default: false + * version_strategy?: scalar|null, // Default: null + * version?: scalar|null, // Default: null + * version_format?: scalar|null, // Default: "%%s?%%s" + * json_manifest_path?: scalar|null, // Default: null + * base_path?: scalar|null, // Default: "" + * base_urls?: list, + * packages?: array, + * }>, + * }, + * asset_mapper?: bool|array{ // Asset Mapper configuration + * enabled?: bool, // Default: true + * paths?: array, + * excluded_patterns?: list, + * exclude_dotfiles?: bool, // If true, any files starting with "." will be excluded from the asset mapper. // Default: true + * server?: bool, // If true, a "dev server" will return the assets from the public directory (true in "debug" mode only by default). // Default: true + * public_prefix?: scalar|null, // The public path where the assets will be written to (and served from when "server" is true). // Default: "/assets/" + * missing_import_mode?: "strict"|"warn"|"ignore", // Behavior if an asset cannot be found when imported from JavaScript or CSS files - e.g. "import './non-existent.js'". "strict" means an exception is thrown, "warn" means a warning is logged, "ignore" means the import is left as-is. // Default: "warn" + * extensions?: array, + * importmap_path?: scalar|null, // The path of the importmap.php file. // Default: "%kernel.project_dir%/importmap.php" + * importmap_polyfill?: scalar|null, // The importmap name that will be used to load the polyfill. Set to false to disable. // Default: "es-module-shims" + * importmap_script_attributes?: array, + * vendor_dir?: scalar|null, // The directory to store JavaScript vendors. // Default: "%kernel.project_dir%/assets/vendor" + * precompress?: bool|array{ // Precompress assets with Brotli, Zstandard and gzip. + * enabled?: bool, // Default: false + * formats?: list, + * extensions?: list, + * }, + * }, + * translator?: bool|array{ // Translator configuration + * enabled?: bool, // Default: true + * fallbacks?: list, + * logging?: bool, // Default: false + * formatter?: scalar|null, // Default: "translator.formatter.default" + * cache_dir?: scalar|null, // Default: "%kernel.cache_dir%/translations" + * default_path?: scalar|null, // The default path used to load translations. // Default: "%kernel.project_dir%/translations" + * paths?: list, + * pseudo_localization?: bool|array{ + * enabled?: bool, // Default: false + * accents?: bool, // Default: true + * expansion_factor?: float, // Default: 1.0 + * brackets?: bool, // Default: true + * parse_html?: bool, // Default: false + * localizable_html_attributes?: list, + * }, + * providers?: array, + * locales?: list, + * }>, + * globals?: array, + * domain?: string, + * }>, + * }, + * validation?: bool|array{ // Validation configuration + * enabled?: bool, // Default: true + * enable_attributes?: bool, // Default: true + * static_method?: list, + * translation_domain?: scalar|null, // Default: "validators" + * email_validation_mode?: "html5"|"html5-allow-no-tld"|"strict", // Default: "html5" + * mapping?: array{ + * paths?: list, + * }, + * not_compromised_password?: bool|array{ + * enabled?: bool, // When disabled, compromised passwords will be accepted as valid. // Default: true + * endpoint?: scalar|null, // API endpoint for the NotCompromisedPassword Validator. // Default: null + * }, + * disable_translation?: bool, // Default: false + * auto_mapping?: array, + * }>, + * }, + * serializer?: bool|array{ // Serializer configuration + * enabled?: bool, // Default: false + * enable_attributes?: bool, // Default: true + * name_converter?: scalar|null, + * circular_reference_handler?: scalar|null, + * max_depth_handler?: scalar|null, + * mapping?: array{ + * paths?: list, + * }, + * default_context?: list, + * named_serializers?: array, + * include_built_in_normalizers?: bool, // Whether to include the built-in normalizers // Default: true + * include_built_in_encoders?: bool, // Whether to include the built-in encoders // Default: true + * }>, + * }, + * property_access?: bool|array{ // Property access configuration + * enabled?: bool, // Default: true + * magic_call?: bool, // Default: false + * magic_get?: bool, // Default: true + * magic_set?: bool, // Default: true + * throw_exception_on_invalid_index?: bool, // Default: false + * throw_exception_on_invalid_property_path?: bool, // Default: true + * }, + * type_info?: bool|array{ // Type info configuration + * enabled?: bool, // Default: true + * aliases?: array, + * }, + * property_info?: bool|array{ // Property info configuration + * enabled?: bool, // Default: true + * with_constructor_extractor?: bool, // Registers the constructor extractor. // Default: true + * }, + * cache?: array{ // Cache configuration + * prefix_seed?: scalar|null, // Used to namespace cache keys when using several apps with the same shared backend. // Default: "_%kernel.project_dir%.%kernel.container_class%" + * app?: scalar|null, // App related cache pools configuration. // Default: "cache.adapter.filesystem" + * system?: scalar|null, // System related cache pools configuration. // Default: "cache.adapter.system" + * directory?: scalar|null, // Default: "%kernel.share_dir%/pools/app" + * default_psr6_provider?: scalar|null, + * default_redis_provider?: scalar|null, // Default: "redis://localhost" + * default_valkey_provider?: scalar|null, // Default: "valkey://localhost" + * default_memcached_provider?: scalar|null, // Default: "memcached://localhost" + * default_doctrine_dbal_provider?: scalar|null, // Default: "database_connection" + * default_pdo_provider?: scalar|null, // Default: null + * pools?: array, + * tags?: scalar|null, // Default: null + * public?: bool, // Default: false + * default_lifetime?: scalar|null, // Default lifetime of the pool. + * provider?: scalar|null, // Overwrite the setting from the default provider for this adapter. + * early_expiration_message_bus?: scalar|null, + * clearer?: scalar|null, + * }>, + * }, + * php_errors?: array{ // PHP errors handling configuration + * log?: mixed, // Use the application logger instead of the PHP logger for logging PHP errors. // Default: true + * throw?: bool, // Throw PHP errors as \ErrorException instances. // Default: true + * }, + * exceptions?: array, + * web_link?: bool|array{ // Web links configuration + * enabled?: bool, // Default: false + * }, + * lock?: bool|string|array{ // Lock configuration + * enabled?: bool, // Default: false + * resources?: array>, + * }, + * semaphore?: bool|string|array{ // Semaphore configuration + * enabled?: bool, // Default: false + * resources?: array, + * }, + * messenger?: bool|array{ // Messenger configuration + * enabled?: bool, // Default: false + * routing?: array, + * }>, + * serializer?: array{ + * default_serializer?: scalar|null, // Service id to use as the default serializer for the transports. // Default: "messenger.transport.native_php_serializer" + * symfony_serializer?: array{ + * format?: scalar|null, // Serialization format for the messenger.transport.symfony_serializer service (which is not the serializer used by default). // Default: "json" + * context?: array, + * }, + * }, + * transports?: array, + * failure_transport?: scalar|null, // Transport name to send failed messages to (after all retries have failed). // Default: null + * retry_strategy?: string|array{ + * service?: scalar|null, // Service id to override the retry strategy entirely. // Default: null + * max_retries?: int, // Default: 3 + * delay?: int, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 + * multiplier?: float, // If greater than 1, delay will grow exponentially for each retry: this delay = (delay * (multiple ^ retries)). // Default: 2 + * max_delay?: int, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 + * jitter?: float, // Randomness to apply to the delay (between 0 and 1). // Default: 0.1 + * }, + * rate_limiter?: scalar|null, // Rate limiter name to use when processing messages. // Default: null + * }>, + * failure_transport?: scalar|null, // Transport name to send failed messages to (after all retries have failed). // Default: null + * stop_worker_on_signals?: list, + * default_bus?: scalar|null, // Default: null + * buses?: array, + * }>, + * }>, + * }, + * scheduler?: bool|array{ // Scheduler configuration + * enabled?: bool, // Default: false + * }, + * disallow_search_engine_index?: bool, // Enabled by default when debug is enabled. // Default: true + * http_client?: bool|array{ // HTTP Client configuration + * enabled?: bool, // Default: true + * max_host_connections?: int, // The maximum number of connections to a single host. + * default_options?: array{ + * headers?: array, + * vars?: array, + * max_redirects?: int, // The maximum number of redirects to follow. + * http_version?: scalar|null, // The default HTTP version, typically 1.1 or 2.0, leave to null for the best version. + * resolve?: array, + * proxy?: scalar|null, // The URL of the proxy to pass requests through or null for automatic detection. + * no_proxy?: scalar|null, // A comma separated list of hosts that do not require a proxy to be reached. + * timeout?: float, // The idle timeout, defaults to the "default_socket_timeout" ini parameter. + * max_duration?: float, // The maximum execution time for the request+response as a whole. + * bindto?: scalar|null, // A network interface name, IP address, a host name or a UNIX socket to bind to. + * verify_peer?: bool, // Indicates if the peer should be verified in a TLS context. + * verify_host?: bool, // Indicates if the host should exist as a certificate common name. + * cafile?: scalar|null, // A certificate authority file. + * capath?: scalar|null, // A directory that contains multiple certificate authority files. + * local_cert?: scalar|null, // A PEM formatted certificate file. + * local_pk?: scalar|null, // A private key file. + * passphrase?: scalar|null, // The passphrase used to encrypt the "local_pk" file. + * ciphers?: scalar|null, // A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...) + * peer_fingerprint?: array{ // Associative array: hashing algorithm => hash(es). + * sha1?: mixed, + * pin-sha256?: mixed, + * md5?: mixed, + * }, + * crypto_method?: scalar|null, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants. + * extra?: array, + * rate_limiter?: scalar|null, // Rate limiter name to use for throttling requests. // Default: null + * caching?: bool|array{ // Caching configuration. + * enabled?: bool, // Default: false + * cache_pool?: string, // The taggable cache pool to use for storing the responses. // Default: "cache.http_client" + * shared?: bool, // Indicates whether the cache is shared (public) or private. // Default: true + * max_ttl?: int, // The maximum TTL (in seconds) allowed for cached responses. Null means no cap. // Default: null + * }, + * retry_failed?: bool|array{ + * enabled?: bool, // Default: false + * retry_strategy?: scalar|null, // service id to override the retry strategy. // Default: null + * http_codes?: array, + * }>, + * max_retries?: int, // Default: 3 + * delay?: int, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 + * multiplier?: float, // If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries). // Default: 2 + * max_delay?: int, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 + * jitter?: float, // Randomness in percent (between 0 and 1) to apply to the delay. // Default: 0.1 + * }, + * }, + * mock_response_factory?: scalar|null, // The id of the service that should generate mock responses. It should be either an invokable or an iterable. + * scoped_clients?: array, + * headers?: array, + * max_redirects?: int, // The maximum number of redirects to follow. + * http_version?: scalar|null, // The default HTTP version, typically 1.1 or 2.0, leave to null for the best version. + * resolve?: array, + * proxy?: scalar|null, // The URL of the proxy to pass requests through or null for automatic detection. + * no_proxy?: scalar|null, // A comma separated list of hosts that do not require a proxy to be reached. + * timeout?: float, // The idle timeout, defaults to the "default_socket_timeout" ini parameter. + * max_duration?: float, // The maximum execution time for the request+response as a whole. + * bindto?: scalar|null, // A network interface name, IP address, a host name or a UNIX socket to bind to. + * verify_peer?: bool, // Indicates if the peer should be verified in a TLS context. + * verify_host?: bool, // Indicates if the host should exist as a certificate common name. + * cafile?: scalar|null, // A certificate authority file. + * capath?: scalar|null, // A directory that contains multiple certificate authority files. + * local_cert?: scalar|null, // A PEM formatted certificate file. + * local_pk?: scalar|null, // A private key file. + * passphrase?: scalar|null, // The passphrase used to encrypt the "local_pk" file. + * ciphers?: scalar|null, // A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...). + * peer_fingerprint?: array{ // Associative array: hashing algorithm => hash(es). + * sha1?: mixed, + * pin-sha256?: mixed, + * md5?: mixed, + * }, + * crypto_method?: scalar|null, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants. + * extra?: array, + * rate_limiter?: scalar|null, // Rate limiter name to use for throttling requests. // Default: null + * caching?: bool|array{ // Caching configuration. + * enabled?: bool, // Default: false + * cache_pool?: string, // The taggable cache pool to use for storing the responses. // Default: "cache.http_client" + * shared?: bool, // Indicates whether the cache is shared (public) or private. // Default: true + * max_ttl?: int, // The maximum TTL (in seconds) allowed for cached responses. Null means no cap. // Default: null + * }, + * retry_failed?: bool|array{ + * enabled?: bool, // Default: false + * retry_strategy?: scalar|null, // service id to override the retry strategy. // Default: null + * http_codes?: array, + * }>, + * max_retries?: int, // Default: 3 + * delay?: int, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 + * multiplier?: float, // If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries). // Default: 2 + * max_delay?: int, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 + * jitter?: float, // Randomness in percent (between 0 and 1) to apply to the delay. // Default: 0.1 + * }, + * }>, + * }, + * mailer?: bool|array{ // Mailer configuration + * enabled?: bool, // Default: false + * message_bus?: scalar|null, // The message bus to use. Defaults to the default bus if the Messenger component is installed. // Default: null + * dsn?: scalar|null, // Default: null + * transports?: array, + * envelope?: array{ // Mailer Envelope configuration + * sender?: scalar|null, + * recipients?: list, + * allowed_recipients?: list, + * }, + * headers?: array, + * dkim_signer?: bool|array{ // DKIM signer configuration + * enabled?: bool, // Default: false + * key?: scalar|null, // Key content, or path to key (in PEM format with the `file://` prefix) // Default: "" + * domain?: scalar|null, // Default: "" + * select?: scalar|null, // Default: "" + * passphrase?: scalar|null, // The private key passphrase // Default: "" + * options?: array, + * }, + * smime_signer?: bool|array{ // S/MIME signer configuration + * enabled?: bool, // Default: false + * key?: scalar|null, // Path to key (in PEM format) // Default: "" + * certificate?: scalar|null, // Path to certificate (in PEM format without the `file://` prefix) // Default: "" + * passphrase?: scalar|null, // The private key passphrase // Default: null + * extra_certificates?: scalar|null, // Default: null + * sign_options?: int, // Default: null + * }, + * smime_encrypter?: bool|array{ // S/MIME encrypter configuration + * enabled?: bool, // Default: false + * repository?: scalar|null, // S/MIME certificate repository service. This service shall implement the `Symfony\Component\Mailer\EventListener\SmimeCertificateRepositoryInterface`. // Default: "" + * cipher?: int, // A set of algorithms used to encrypt the message // Default: null + * }, + * }, + * secrets?: bool|array{ + * enabled?: bool, // Default: true + * vault_directory?: scalar|null, // Default: "%kernel.project_dir%/config/secrets/%kernel.runtime_environment%" + * local_dotenv_file?: scalar|null, // Default: "%kernel.project_dir%/.env.%kernel.runtime_environment%.local" + * decryption_env_var?: scalar|null, // Default: "base64:default::SYMFONY_DECRYPTION_SECRET" + * }, + * notifier?: bool|array{ // Notifier configuration + * enabled?: bool, // Default: false + * message_bus?: scalar|null, // The message bus to use. Defaults to the default bus if the Messenger component is installed. // Default: null + * chatter_transports?: array, + * texter_transports?: array, + * notification_on_failed_messages?: bool, // Default: false + * channel_policy?: array>, + * admin_recipients?: list, + * }, + * rate_limiter?: bool|array{ // Rate limiter configuration + * enabled?: bool, // Default: false + * limiters?: array, + * limit?: int, // The maximum allowed hits in a fixed interval or burst. + * interval?: scalar|null, // Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). + * rate?: array{ // Configures the fill rate if "policy" is set to "token_bucket". + * interval?: scalar|null, // Configures the rate interval. The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). + * amount?: int, // Amount of tokens to add each interval. // Default: 1 + * }, + * }>, + * }, + * uid?: bool|array{ // Uid configuration + * enabled?: bool, // Default: false + * default_uuid_version?: 7|6|4|1, // Default: 7 + * name_based_uuid_version?: 5|3, // Default: 5 + * name_based_uuid_namespace?: scalar|null, + * time_based_uuid_version?: 7|6|1, // Default: 7 + * time_based_uuid_node?: scalar|null, + * }, + * html_sanitizer?: bool|array{ // HtmlSanitizer configuration + * enabled?: bool, // Default: false + * sanitizers?: array, + * block_elements?: list, + * drop_elements?: list, + * allow_attributes?: array, + * drop_attributes?: array, + * force_attributes?: array>, + * force_https_urls?: bool, // Transforms URLs using the HTTP scheme to use the HTTPS scheme instead. // Default: false + * allowed_link_schemes?: list, + * allowed_link_hosts?: list|null, + * allow_relative_links?: bool, // Allows relative URLs to be used in links href attributes. // Default: false + * allowed_media_schemes?: list, + * allowed_media_hosts?: list|null, + * allow_relative_medias?: bool, // Allows relative URLs to be used in media source attributes (img, audio, video, ...). // Default: false + * with_attribute_sanitizers?: list, + * without_attribute_sanitizers?: list, + * max_input_length?: int, // The maximum length allowed for the sanitized input. // Default: 0 + * }>, + * }, + * webhook?: bool|array{ // Webhook configuration + * enabled?: bool, // Default: false + * message_bus?: scalar|null, // The message bus to use. // Default: "messenger.default_bus" + * routing?: array, + * }, + * remote-event?: bool|array{ // RemoteEvent configuration + * enabled?: bool, // Default: false + * }, + * json_streamer?: bool|array{ // JSON streamer configuration + * enabled?: bool, // Default: false + * }, + * } + * @psalm-type TwigConfig = array{ + * form_themes?: list, + * globals?: array, + * autoescape_service?: scalar|null, // Default: null + * autoescape_service_method?: scalar|null, // Default: null + * cache?: scalar|null, // Default: true + * charset?: scalar|null, // Default: "%kernel.charset%" + * debug?: bool, // Default: "%kernel.debug%" + * strict_variables?: bool, // Default: "%kernel.debug%" + * auto_reload?: scalar|null, + * optimizations?: int, + * default_path?: scalar|null, // The default path used to load templates. // Default: "%kernel.project_dir%/templates" + * file_name_pattern?: list, + * paths?: array, + * date?: array{ // The default format options used by the date filter. + * format?: scalar|null, // Default: "F j, Y H:i" + * interval_format?: scalar|null, // Default: "%d days" + * timezone?: scalar|null, // The timezone used when formatting dates, when set to null, the timezone returned by date_default_timezone_get() is used. // Default: null + * }, + * number_format?: array{ // The default format options for the number_format filter. + * decimals?: int, // Default: 0 + * decimal_point?: scalar|null, // Default: "." + * thousands_separator?: scalar|null, // Default: "," + * }, + * mailer?: array{ + * html_to_text_converter?: scalar|null, // A service implementing the "Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface". // Default: null + * }, + * } + * @psalm-type SecurityConfig = array{ + * access_denied_url?: scalar|null, // Default: null + * session_fixation_strategy?: "none"|"migrate"|"invalidate", // Default: "migrate" + * expose_security_errors?: \Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::None|\Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::AccountStatus|\Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::All, // Default: "none" + * erase_credentials?: bool, // Default: true + * access_decision_manager?: array{ + * strategy?: "affirmative"|"consensus"|"unanimous"|"priority", + * service?: scalar|null, + * strategy_service?: scalar|null, + * allow_if_all_abstain?: bool, // Default: false + * allow_if_equal_granted_denied?: bool, // Default: true + * }, + * password_hashers?: array, + * hash_algorithm?: scalar|null, // Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms. // Default: "sha512" + * key_length?: scalar|null, // Default: 40 + * ignore_case?: bool, // Default: false + * encode_as_base64?: bool, // Default: true + * iterations?: scalar|null, // Default: 5000 + * cost?: int, // Default: null + * memory_cost?: scalar|null, // Default: null + * time_cost?: scalar|null, // Default: null + * id?: scalar|null, + * }>, + * providers?: array, + * }, + * memory?: array{ + * users?: array, + * }>, + * }, + * ldap?: array{ + * service: scalar|null, + * base_dn: scalar|null, + * search_dn?: scalar|null, // Default: null + * search_password?: scalar|null, // Default: null + * extra_fields?: list, + * default_roles?: list, + * role_fetcher?: scalar|null, // Default: null + * uid_key?: scalar|null, // Default: "sAMAccountName" + * filter?: scalar|null, // Default: "({uid_key}={user_identifier})" + * password_attribute?: scalar|null, // Default: null + * }, + * entity?: array{ + * class: scalar|null, // The full entity class name of your user class. + * property?: scalar|null, // Default: null + * manager_name?: scalar|null, // Default: null + * }, + * }>, + * firewalls: array, + * security?: bool, // Default: true + * user_checker?: scalar|null, // The UserChecker to use when authenticating users in this firewall. // Default: "security.user_checker" + * request_matcher?: scalar|null, + * access_denied_url?: scalar|null, + * access_denied_handler?: scalar|null, + * entry_point?: scalar|null, // An enabled authenticator name or a service id that implements "Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface". + * provider?: scalar|null, + * stateless?: bool, // Default: false + * lazy?: bool, // Default: false + * context?: scalar|null, + * logout?: array{ + * enable_csrf?: bool|null, // Default: null + * csrf_token_id?: scalar|null, // Default: "logout" + * csrf_parameter?: scalar|null, // Default: "_csrf_token" + * csrf_token_manager?: scalar|null, + * path?: scalar|null, // Default: "/logout" + * target?: scalar|null, // Default: "/" + * invalidate_session?: bool, // Default: true + * clear_site_data?: list<"*"|"cache"|"cookies"|"storage"|"executionContexts">, + * delete_cookies?: array, + * }, + * switch_user?: array{ + * provider?: scalar|null, + * parameter?: scalar|null, // Default: "_switch_user" + * role?: scalar|null, // Default: "ROLE_ALLOWED_TO_SWITCH" + * target_route?: scalar|null, // Default: null + * }, + * required_badges?: list, + * custom_authenticators?: list, + * login_throttling?: array{ + * limiter?: scalar|null, // A service id implementing "Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface". + * max_attempts?: int, // Default: 5 + * interval?: scalar|null, // Default: "1 minute" + * lock_factory?: scalar|null, // The service ID of the lock factory used by the login rate limiter (or null to disable locking). // Default: null + * cache_pool?: string, // The cache pool to use for storing the limiter state // Default: "cache.rate_limiter" + * storage_service?: string, // The service ID of a custom storage implementation, this precedes any configured "cache_pool" // Default: null + * }, + * x509?: array{ + * provider?: scalar|null, + * user?: scalar|null, // Default: "SSL_CLIENT_S_DN_Email" + * credentials?: scalar|null, // Default: "SSL_CLIENT_S_DN" + * user_identifier?: scalar|null, // Default: "emailAddress" + * }, + * remote_user?: array{ + * provider?: scalar|null, + * user?: scalar|null, // Default: "REMOTE_USER" + * }, + * login_link?: array{ + * check_route: scalar|null, // Route that will validate the login link - e.g. "app_login_link_verify". + * check_post_only?: scalar|null, // If true, only HTTP POST requests to "check_route" will be handled by the authenticator. // Default: false + * signature_properties: list, + * lifetime?: int, // The lifetime of the login link in seconds. // Default: 600 + * max_uses?: int, // Max number of times a login link can be used - null means unlimited within lifetime. // Default: null + * used_link_cache?: scalar|null, // Cache service id used to expired links of max_uses is set. + * success_handler?: scalar|null, // A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface. + * failure_handler?: scalar|null, // A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface. + * provider?: scalar|null, // The user provider to load users from. + * secret?: scalar|null, // Default: "%kernel.secret%" + * always_use_default_target_path?: bool, // Default: false + * default_target_path?: scalar|null, // Default: "/" + * login_path?: scalar|null, // Default: "/login" + * target_path_parameter?: scalar|null, // Default: "_target_path" + * use_referer?: bool, // Default: false + * failure_path?: scalar|null, // Default: null + * failure_forward?: bool, // Default: false + * failure_path_parameter?: scalar|null, // Default: "_failure_path" + * }, + * form_login?: array{ + * provider?: scalar|null, + * remember_me?: bool, // Default: true + * success_handler?: scalar|null, + * failure_handler?: scalar|null, + * check_path?: scalar|null, // Default: "/login_check" + * use_forward?: bool, // Default: false + * login_path?: scalar|null, // Default: "/login" + * username_parameter?: scalar|null, // Default: "_username" + * password_parameter?: scalar|null, // Default: "_password" + * csrf_parameter?: scalar|null, // Default: "_csrf_token" + * csrf_token_id?: scalar|null, // Default: "authenticate" + * enable_csrf?: bool, // Default: false + * post_only?: bool, // Default: true + * form_only?: bool, // Default: false + * always_use_default_target_path?: bool, // Default: false + * default_target_path?: scalar|null, // Default: "/" + * target_path_parameter?: scalar|null, // Default: "_target_path" + * use_referer?: bool, // Default: false + * failure_path?: scalar|null, // Default: null + * failure_forward?: bool, // Default: false + * failure_path_parameter?: scalar|null, // Default: "_failure_path" + * }, + * form_login_ldap?: array{ + * provider?: scalar|null, + * remember_me?: bool, // Default: true + * success_handler?: scalar|null, + * failure_handler?: scalar|null, + * check_path?: scalar|null, // Default: "/login_check" + * use_forward?: bool, // Default: false + * login_path?: scalar|null, // Default: "/login" + * username_parameter?: scalar|null, // Default: "_username" + * password_parameter?: scalar|null, // Default: "_password" + * csrf_parameter?: scalar|null, // Default: "_csrf_token" + * csrf_token_id?: scalar|null, // Default: "authenticate" + * enable_csrf?: bool, // Default: false + * post_only?: bool, // Default: true + * form_only?: bool, // Default: false + * always_use_default_target_path?: bool, // Default: false + * default_target_path?: scalar|null, // Default: "/" + * target_path_parameter?: scalar|null, // Default: "_target_path" + * use_referer?: bool, // Default: false + * failure_path?: scalar|null, // Default: null + * failure_forward?: bool, // Default: false + * failure_path_parameter?: scalar|null, // Default: "_failure_path" + * service?: scalar|null, // Default: "ldap" + * dn_string?: scalar|null, // Default: "{user_identifier}" + * query_string?: scalar|null, + * search_dn?: scalar|null, // Default: "" + * search_password?: scalar|null, // Default: "" + * }, + * json_login?: array{ + * provider?: scalar|null, + * remember_me?: bool, // Default: true + * success_handler?: scalar|null, + * failure_handler?: scalar|null, + * check_path?: scalar|null, // Default: "/login_check" + * use_forward?: bool, // Default: false + * login_path?: scalar|null, // Default: "/login" + * username_path?: scalar|null, // Default: "username" + * password_path?: scalar|null, // Default: "password" + * }, + * json_login_ldap?: array{ + * provider?: scalar|null, + * remember_me?: bool, // Default: true + * success_handler?: scalar|null, + * failure_handler?: scalar|null, + * check_path?: scalar|null, // Default: "/login_check" + * use_forward?: bool, // Default: false + * login_path?: scalar|null, // Default: "/login" + * username_path?: scalar|null, // Default: "username" + * password_path?: scalar|null, // Default: "password" + * service?: scalar|null, // Default: "ldap" + * dn_string?: scalar|null, // Default: "{user_identifier}" + * query_string?: scalar|null, + * search_dn?: scalar|null, // Default: "" + * search_password?: scalar|null, // Default: "" + * }, + * access_token?: array{ + * provider?: scalar|null, + * remember_me?: bool, // Default: true + * success_handler?: scalar|null, + * failure_handler?: scalar|null, + * realm?: scalar|null, // Default: null + * token_extractors?: list, + * token_handler: string|array{ + * id?: scalar|null, + * oidc_user_info?: string|array{ + * base_uri: scalar|null, // Base URI of the userinfo endpoint on the OIDC server, or the OIDC server URI to use the discovery (require "discovery" to be configured). + * discovery?: array{ // Enable the OIDC discovery. + * cache?: array{ + * id: scalar|null, // Cache service id to use to cache the OIDC discovery configuration. + * }, + * }, + * claim?: scalar|null, // Claim which contains the user identifier (e.g. sub, email, etc.). // Default: "sub" + * client?: scalar|null, // HttpClient service id to use to call the OIDC server. + * }, + * oidc?: array{ + * discovery?: array{ // Enable the OIDC discovery. + * base_uri: list, + * cache?: array{ + * id: scalar|null, // Cache service id to use to cache the OIDC discovery configuration. + * }, + * }, + * claim?: scalar|null, // Claim which contains the user identifier (e.g.: sub, email..). // Default: "sub" + * audience: scalar|null, // Audience set in the token, for validation purpose. + * issuers: list, + * algorithms: list, + * keyset?: scalar|null, // JSON-encoded JWKSet used to sign the token (must contain a list of valid public keys). + * encryption?: bool|array{ + * enabled?: bool, // Default: false + * enforce?: bool, // When enabled, the token shall be encrypted. // Default: false + * algorithms: list, + * keyset: scalar|null, // JSON-encoded JWKSet used to decrypt the token (must contain a list of valid private keys). + * }, + * }, + * cas?: array{ + * validation_url: scalar|null, // CAS server validation URL + * prefix?: scalar|null, // CAS prefix // Default: "cas" + * http_client?: scalar|null, // HTTP Client service // Default: null + * }, + * oauth2?: scalar|null, + * }, + * }, + * http_basic?: array{ + * provider?: scalar|null, + * realm?: scalar|null, // Default: "Secured Area" + * }, + * http_basic_ldap?: array{ + * provider?: scalar|null, + * realm?: scalar|null, // Default: "Secured Area" + * service?: scalar|null, // Default: "ldap" + * dn_string?: scalar|null, // Default: "{user_identifier}" + * query_string?: scalar|null, + * search_dn?: scalar|null, // Default: "" + * search_password?: scalar|null, // Default: "" + * }, + * remember_me?: array{ + * secret?: scalar|null, // Default: "%kernel.secret%" + * service?: scalar|null, + * user_providers?: list, + * catch_exceptions?: bool, // Default: true + * signature_properties?: list, + * token_provider?: string|array{ + * service?: scalar|null, // The service ID of a custom remember-me token provider. + * doctrine?: bool|array{ + * enabled?: bool, // Default: false + * connection?: scalar|null, // Default: null + * }, + * }, + * token_verifier?: scalar|null, // The service ID of a custom rememberme token verifier. + * name?: scalar|null, // Default: "REMEMBERME" + * lifetime?: int, // Default: 31536000 + * path?: scalar|null, // Default: "/" + * domain?: scalar|null, // Default: null + * secure?: true|false|"auto", // Default: null + * httponly?: bool, // Default: true + * samesite?: null|"lax"|"strict"|"none", // Default: "lax" + * always_remember_me?: bool, // Default: false + * remember_me_parameter?: scalar|null, // Default: "_remember_me" + * }, + * }>, + * access_control?: list, + * attributes?: array, + * route?: scalar|null, // Default: null + * methods?: list, + * allow_if?: scalar|null, // Default: null + * roles?: list, + * }>, + * role_hierarchy?: array>, + * } + * @psalm-type TwigComponentConfig = array{ + * defaults?: array, + * anonymous_template_directory?: scalar|null, // Defaults to `components` + * profiler?: bool, // Enables the profiler for Twig Component (in debug mode) // Default: "%kernel.debug%" + * controllers_json?: scalar|null, // Deprecated: The "twig_component.controllers_json" config option is deprecated, and will be removed in 3.0. // Default: null + * } + * @psalm-type SyliusTwigHooksConfig = array{ + * enable_autoprefixing?: bool, // Default: false + * hook_name_section_separator?: scalar|null, // Default: false + * supported_hookable_types?: array, + * hooks?: array, + * props?: array, + * configuration?: array, + * priority?: int, // Default: null + * }>>, + * } + * @psalm-type SyliusResourceConfig = array{ + * resources?: array, + * settings?: array{ + * paginate?: mixed, // Default: null + * limit?: mixed, // Default: null + * allowed_paginate?: list, + * default_page_size?: int, // Default: 10 + * default_templates_dir?: scalar|null, // Default: null + * sortable?: bool, // Default: false + * sorting?: mixed, // Default: null + * filterable?: bool, // Default: false + * criteria?: mixed, // Default: null + * state_machine_component?: scalar|null, // Default: null + * }, + * translation?: bool|array{ + * enabled?: bool, // Default: true + * locale_provider?: scalar|null, // Default: "sylius.translation_locale_provider.immutable" + * }, + * drivers?: list<"doctrine/orm"|"doctrine/mongodb-odm"|"doctrine/phpcr-odm">, + * mapping?: array{ + * imports?: list, + * paths?: list, + * }, + * authorization_checker?: scalar|null, // Default: "sylius.resource_controller.authorization_checker.disabled" + * routing_path_bc_layer?: bool, + * path_segment_name_generator?: scalar|null, // Specify a path name generator to use. // Default: "sylius.metadata.path_segment_name_generator.dash" + * } + * @psalm-type SyliusAdminUiConfig = array{ + * routing?: array, + * } + * @psalm-type SyliusGridConfig = array{ + * drivers?: list<"doctrine/orm"|"doctrine/phpcr-odm">, + * templates?: array{ + * filter?: array, + * action?: array, + * bulk_action?: array, + * }, + * grids?: array, + * }, + * sorting?: array, + * limits?: list, + * fields?: array, + * }>, + * filters?: array, + * form_options?: list, + * default_value?: mixed, + * }>, + * actions?: array, + * }>>, + * removals?: array, + * }>, + * } + * @psalm-type DoctrineConfig = array{ + * dbal?: array{ + * default_connection?: scalar|null, + * types?: array, + * driver_schemes?: array, + * connections?: array, + * mapping_types?: array, + * default_table_options?: array, + * schema_manager_factory?: scalar|null, // Default: "doctrine.dbal.default_schema_manager_factory" + * result_cache?: scalar|null, + * replicas?: array, + * }>, + * }, + * orm?: array{ + * default_entity_manager?: scalar|null, + * enable_native_lazy_objects?: bool, // Deprecated: The "enable_native_lazy_objects" option is deprecated and will be removed in DoctrineBundle 4.0, as native lazy objects are now always enabled. // Default: true + * controller_resolver?: bool|array{ + * enabled?: bool, // Default: true + * auto_mapping?: bool, // Deprecated: The "doctrine.orm.controller_resolver.auto_mapping.auto_mapping" option is deprecated and will be removed in DoctrineBundle 4.0, as it only accepts `false` since 3.0. // Set to true to enable using route placeholders as lookup criteria when the primary key doesn't match the argument name // Default: false + * evict_cache?: bool, // Set to true to fetch the entity from the database instead of using the cache, if any // Default: false + * }, + * entity_managers?: array, + * }>, + * }>, + * }, + * connection?: scalar|null, + * class_metadata_factory_name?: scalar|null, // Default: "Doctrine\\ORM\\Mapping\\ClassMetadataFactory" + * default_repository_class?: scalar|null, // Default: "Doctrine\\ORM\\EntityRepository" + * auto_mapping?: scalar|null, // Default: false + * naming_strategy?: scalar|null, // Default: "doctrine.orm.naming_strategy.default" + * quote_strategy?: scalar|null, // Default: "doctrine.orm.quote_strategy.default" + * typed_field_mapper?: scalar|null, // Default: "doctrine.orm.typed_field_mapper.default" + * entity_listener_resolver?: scalar|null, // Default: null + * fetch_mode_subselect_batch_size?: scalar|null, + * repository_factory?: scalar|null, // Default: "doctrine.orm.container_repository_factory" + * schema_ignore_classes?: list, + * validate_xml_mapping?: bool, // Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.14 and will be mandatory in ORM 3.0. See https://github.com/doctrine/orm/pull/6728. // Default: false + * second_level_cache?: array{ + * region_cache_driver?: string|array{ + * type?: scalar|null, // Default: null + * id?: scalar|null, + * pool?: scalar|null, + * }, + * region_lock_lifetime?: scalar|null, // Default: 60 + * log_enabled?: bool, // Default: true + * region_lifetime?: scalar|null, // Default: 3600 + * enabled?: bool, // Default: true + * factory?: scalar|null, + * regions?: array, + * loggers?: array, + * }, + * hydrators?: array, + * mappings?: array, + * dql?: array{ + * string_functions?: array, + * numeric_functions?: array, + * datetime_functions?: array, + * }, + * filters?: array, + * }>, + * identity_generation_preferences?: array, + * }>, + * resolve_target_entities?: array, + * }, + * } + * @psalm-type WhiteOctoberPagerfantaConfig = array{ // Deprecated: The "white_october_pagerfanta" configuration node is deprecated, migrate your configuration to the "babdev_pagerfanta" configuration node. + * exceptions_strategy?: array{ + * out_of_range_page?: scalar|null, // Default: "to_http_not_found" + * not_valid_current_page?: scalar|null, // Default: "to_http_not_found" + * }, + * default_view?: scalar|null, // Default: "default" + * } + * @psalm-type ConfigType = array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * twig?: TwigConfig, + * security?: SecurityConfig, + * twig_component?: TwigComponentConfig, + * sylius_twig_hooks?: SyliusTwigHooksConfig, + * sylius_resource?: SyliusResourceConfig, + * sylius_admin_ui?: SyliusAdminUiConfig, + * sylius_grid?: SyliusGridConfig, + * doctrine?: DoctrineConfig, + * white_october_pagerfanta?: WhiteOctoberPagerfantaConfig, + * "when@dev"?: array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * twig?: TwigConfig, + * security?: SecurityConfig, + * twig_component?: TwigComponentConfig, + * sylius_twig_hooks?: SyliusTwigHooksConfig, + * sylius_resource?: SyliusResourceConfig, + * sylius_admin_ui?: SyliusAdminUiConfig, + * sylius_grid?: SyliusGridConfig, + * doctrine?: DoctrineConfig, + * }, + * "when@prod"?: array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * twig?: TwigConfig, + * security?: SecurityConfig, + * twig_component?: TwigComponentConfig, + * sylius_twig_hooks?: SyliusTwigHooksConfig, + * sylius_resource?: SyliusResourceConfig, + * sylius_admin_ui?: SyliusAdminUiConfig, + * sylius_grid?: SyliusGridConfig, + * doctrine?: DoctrineConfig, + * }, + * "when@test"?: array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * twig?: TwigConfig, + * security?: SecurityConfig, + * twig_component?: TwigComponentConfig, + * sylius_twig_hooks?: SyliusTwigHooksConfig, + * sylius_resource?: SyliusResourceConfig, + * sylius_admin_ui?: SyliusAdminUiConfig, + * sylius_grid?: SyliusGridConfig, + * doctrine?: DoctrineConfig, + * }, + * ..., + * }> + * } + */ +final class App +{ + /** + * @param ConfigType $config + * + * @psalm-return ConfigType + */ + public static function config(array $config): array + { + return AppReference::config($config); + } +} + +namespace Symfony\Component\Routing\Loader\Configurator; + +/** + * This class provides array-shapes for configuring the routes of an application. + * + * Example: + * + * ```php + * // config/routes.php + * namespace Symfony\Component\Routing\Loader\Configurator; + * + * return Routes::config([ + * 'controllers' => [ + * 'resource' => 'routing.controllers', + * ], + * ]); + * ``` + * + * @psalm-type RouteConfig = array{ + * path: string|array, + * controller?: string, + * methods?: string|list, + * requirements?: array, + * defaults?: array, + * options?: array, + * host?: string|array, + * schemes?: string|list, + * condition?: string, + * locale?: string, + * format?: string, + * utf8?: bool, + * stateless?: bool, + * } + * @psalm-type ImportConfig = array{ + * resource: string, + * type?: string, + * exclude?: string|list, + * prefix?: string|array, + * name_prefix?: string, + * trailing_slash_on_root?: bool, + * controller?: string, + * methods?: string|list, + * requirements?: array, + * defaults?: array, + * options?: array, + * host?: string|array, + * schemes?: string|list, + * condition?: string, + * locale?: string, + * format?: string, + * utf8?: bool, + * stateless?: bool, + * } + * @psalm-type AliasConfig = array{ + * alias: string, + * deprecated?: array{package:string, version:string, message?:string}, + * } + * @psalm-type RoutesConfig = array{ + * "when@dev"?: array, + * "when@prod"?: array, + * "when@test"?: array, + * ... + * } + */ +final class Routes +{ + /** + * @param RoutesConfig $config + * + * @psalm-return RoutesConfig + */ + public static function config(array $config): array + { + return $config; + } +} diff --git a/src/AdminUi/tests/Functional/.application/config/routes/framework.yaml b/src/AdminUi/tests/Functional/.application/config/routes/framework.yaml index 0fc74bbac..bc1feace1 100644 --- a/src/AdminUi/tests/Functional/.application/config/routes/framework.yaml +++ b/src/AdminUi/tests/Functional/.application/config/routes/framework.yaml @@ -1,4 +1,4 @@ when@dev: _errors: - resource: '@FrameworkBundle/Resources/config/routing/errors.xml' + resource: '@FrameworkBundle/Resources/config/routing/errors.php' prefix: /_error diff --git a/src/AdminUi/tests/Functional/.application/config/routes/sylius_resource.yaml b/src/AdminUi/tests/Functional/.application/config/routes/sylius_resource.yaml index 0c8ade2c2..e4c326435 100644 --- a/src/AdminUi/tests/Functional/.application/config/routes/sylius_resource.yaml +++ b/src/AdminUi/tests/Functional/.application/config/routes/sylius_resource.yaml @@ -2,6 +2,6 @@ sylius_crud_routes: resource: 'sylius.routing.loader.crud_routes_attributes' type: service -sylius_routes: - resource: 'sylius.routing.loader.routes_attributes' +sylius_resource_routes: + resource: 'sylius.symfony.routing.loader.resource' type: service diff --git a/src/BootstrapAdminUi/composer.json b/src/BootstrapAdminUi/composer.json index fbb805357..8f7617725 100644 --- a/src/BootstrapAdminUi/composer.json +++ b/src/BootstrapAdminUi/composer.json @@ -6,29 +6,29 @@ "php": "^8.1", "pagerfanta/twig": "^4.6", "sylius/admin-ui": "^0.10", - "sylius/grid-bundle": "^1.13", - "sylius/resource-bundle": "^1.11", + "sylius/grid-bundle": "^1.13 || ^1.15@alpha", + "sylius/resource-bundle": "^1.13 || ^1.14@alpha", "sylius/twig-extra": "^0.10", "sylius/twig-hooks": "^0.10", - "symfony/asset": "^6.4 || ^7.0", - "symfony/http-kernel": "^6.4 || ^7.0", - "symfony/http-client": "^6.4 || ^7.0", - "symfony/security-bundle": "^6.4 || ^7.0", + "symfony/asset": "^6.4 || ^7.4 || ^8.0", + "symfony/http-kernel": "^6.4 || ^7.4 || ^8.0", + "symfony/http-client": "^6.4 || ^7.4 || ^8.0", + "symfony/security-bundle": "^6.4 || ^7.4 || ^8.0", "symfony/ux-autocomplete": "^2.17", "symfony/ux-icons": "^2.20", "symfony/ux-live-component": "^2.17" }, "require-dev": { - "doctrine/doctrine-bundle": "^2.12", - "doctrine/orm": "^2.0", - "phpunit/phpunit": "^9.6", - "symfony/browser-kit": "^6.4 || ^7.0", - "symfony/console": "^6.4 || ^7.0", - "symfony/css-selector": "^6.4 || ^7.0", - "symfony/dotenv": "^6.4 || ^7.0", - "symfony/framework-bundle": "^6.4 || ^7.0", - "symfony/runtime": "^6.4 || ^7.0", - "symfony/yaml": "^6.4 || ^7.0" + "doctrine/doctrine-bundle": "^2.13 || ^3.0 || ^4.0", + "doctrine/orm": "^3.3 || ^4.0", + "phpunit/phpunit": "^10.5", + "symfony/browser-kit": "^6.4 || ^7.4 || ^8.0", + "symfony/console": "^6.4 || ^7.4 || ^8.0", + "symfony/css-selector": "^6.4 || ^7.4 || ^8.0", + "symfony/dotenv": "^6.4 || ^7.4 || ^8.0", + "symfony/framework-bundle": "^6.4 || ^7.4 || ^8.0", + "symfony/runtime": "^6.4 || ^7.4 || ^8.0", + "symfony/yaml": "^6.4 || ^7.4 || ^8.0" }, "autoload": { "psr-4": { @@ -53,6 +53,7 @@ "type": "path", "url": "../**" } + ], "extra": { "symfony": { diff --git a/src/BootstrapAdminUi/phpunit.xml.dist b/src/BootstrapAdminUi/phpunit.xml.dist index 5e1e6bf28..682d7a860 100644 --- a/src/BootstrapAdminUi/phpunit.xml.dist +++ b/src/BootstrapAdminUi/phpunit.xml.dist @@ -5,7 +5,6 @@ xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd" colors="true" bootstrap="tests/Functional/.application/config/bootstrap.php" - convertDeprecationsToExceptions="true" > diff --git a/src/BootstrapAdminUi/tests/Functional/.application/config/packages/doctrine.yaml b/src/BootstrapAdminUi/tests/Functional/.application/config/packages/doctrine.yaml index d42c52d6d..171cd6588 100644 --- a/src/BootstrapAdminUi/tests/Functional/.application/config/packages/doctrine.yaml +++ b/src/BootstrapAdminUi/tests/Functional/.application/config/packages/doctrine.yaml @@ -7,11 +7,7 @@ doctrine: #server_version: '16' profiling_collect_backtrace: '%kernel.debug%' - use_savepoints: true orm: - auto_generate_proxy_classes: true - enable_lazy_ghost_objects: true - report_fields_where_declared: true validate_xml_mapping: true naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware auto_mapping: true diff --git a/src/BootstrapAdminUi/tests/Functional/.application/config/reference.php b/src/BootstrapAdminUi/tests/Functional/.application/config/reference.php new file mode 100644 index 000000000..6f5c20aa9 --- /dev/null +++ b/src/BootstrapAdminUi/tests/Functional/.application/config/reference.php @@ -0,0 +1,1552 @@ + [ + * 'App\\' => [ + * 'resource' => '../src/', + * ], + * ], + * ]); + * ``` + * + * @psalm-type ImportsConfig = list + * @psalm-type ParametersConfig = array|null>|null> + * @psalm-type ArgumentsType = list|array + * @psalm-type CallType = array|array{0:string, 1?:ArgumentsType, 2?:bool}|array{method:string, arguments?:ArgumentsType, returns_clone?:bool} + * @psalm-type TagsType = list>> // arrays inside the list must have only one element, with the tag name as the key + * @psalm-type CallbackType = string|array{0:string|ReferenceConfigurator,1:string}|\Closure|ReferenceConfigurator|ExpressionConfigurator + * @psalm-type DeprecationType = array{package: string, version: string, message?: string} + * @psalm-type DefaultsType = array{ + * public?: bool, + * tags?: TagsType, + * resource_tags?: TagsType, + * autowire?: bool, + * autoconfigure?: bool, + * bind?: array, + * } + * @psalm-type InstanceofType = array{ + * shared?: bool, + * lazy?: bool|string, + * public?: bool, + * properties?: array, + * configurator?: CallbackType, + * calls?: list, + * tags?: TagsType, + * resource_tags?: TagsType, + * autowire?: bool, + * bind?: array, + * constructor?: string, + * } + * @psalm-type DefinitionType = array{ + * class?: string, + * file?: string, + * parent?: string, + * shared?: bool, + * synthetic?: bool, + * lazy?: bool|string, + * public?: bool, + * abstract?: bool, + * deprecated?: DeprecationType, + * factory?: CallbackType, + * configurator?: CallbackType, + * arguments?: ArgumentsType, + * properties?: array, + * calls?: list, + * tags?: TagsType, + * resource_tags?: TagsType, + * decorates?: string, + * decoration_inner_name?: string, + * decoration_priority?: int, + * decoration_on_invalid?: 'exception'|'ignore'|null, + * autowire?: bool, + * autoconfigure?: bool, + * bind?: array, + * constructor?: string, + * from_callable?: CallbackType, + * } + * @psalm-type AliasType = string|array{ + * alias: string, + * public?: bool, + * deprecated?: DeprecationType, + * } + * @psalm-type PrototypeType = array{ + * resource: string, + * namespace?: string, + * exclude?: string|list, + * parent?: string, + * shared?: bool, + * lazy?: bool|string, + * public?: bool, + * abstract?: bool, + * deprecated?: DeprecationType, + * factory?: CallbackType, + * arguments?: ArgumentsType, + * properties?: array, + * configurator?: CallbackType, + * calls?: list, + * tags?: TagsType, + * resource_tags?: TagsType, + * autowire?: bool, + * autoconfigure?: bool, + * bind?: array, + * constructor?: string, + * } + * @psalm-type StackType = array{ + * stack: list>, + * public?: bool, + * deprecated?: DeprecationType, + * } + * @psalm-type ServicesConfig = array{ + * _defaults?: DefaultsType, + * _instanceof?: InstanceofType, + * ... + * } + * @psalm-type ExtensionType = array + * @psalm-type DoctrineConfig = array{ + * dbal?: array{ + * default_connection?: scalar|null, + * types?: array, + * driver_schemes?: array, + * connections?: array, + * mapping_types?: array, + * default_table_options?: array, + * schema_manager_factory?: scalar|null, // Default: "doctrine.dbal.default_schema_manager_factory" + * result_cache?: scalar|null, + * replicas?: array, + * }>, + * }, + * orm?: array{ + * default_entity_manager?: scalar|null, + * enable_native_lazy_objects?: bool, // Deprecated: The "enable_native_lazy_objects" option is deprecated and will be removed in DoctrineBundle 4.0, as native lazy objects are now always enabled. // Default: true + * controller_resolver?: bool|array{ + * enabled?: bool, // Default: true + * auto_mapping?: bool, // Deprecated: The "doctrine.orm.controller_resolver.auto_mapping.auto_mapping" option is deprecated and will be removed in DoctrineBundle 4.0, as it only accepts `false` since 3.0. // Set to true to enable using route placeholders as lookup criteria when the primary key doesn't match the argument name // Default: false + * evict_cache?: bool, // Set to true to fetch the entity from the database instead of using the cache, if any // Default: false + * }, + * entity_managers?: array, + * }>, + * }>, + * }, + * connection?: scalar|null, + * class_metadata_factory_name?: scalar|null, // Default: "Doctrine\\ORM\\Mapping\\ClassMetadataFactory" + * default_repository_class?: scalar|null, // Default: "Doctrine\\ORM\\EntityRepository" + * auto_mapping?: scalar|null, // Default: false + * naming_strategy?: scalar|null, // Default: "doctrine.orm.naming_strategy.default" + * quote_strategy?: scalar|null, // Default: "doctrine.orm.quote_strategy.default" + * typed_field_mapper?: scalar|null, // Default: "doctrine.orm.typed_field_mapper.default" + * entity_listener_resolver?: scalar|null, // Default: null + * fetch_mode_subselect_batch_size?: scalar|null, + * repository_factory?: scalar|null, // Default: "doctrine.orm.container_repository_factory" + * schema_ignore_classes?: list, + * validate_xml_mapping?: bool, // Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.14 and will be mandatory in ORM 3.0. See https://github.com/doctrine/orm/pull/6728. // Default: false + * second_level_cache?: array{ + * region_cache_driver?: string|array{ + * type?: scalar|null, // Default: null + * id?: scalar|null, + * pool?: scalar|null, + * }, + * region_lock_lifetime?: scalar|null, // Default: 60 + * log_enabled?: bool, // Default: true + * region_lifetime?: scalar|null, // Default: 3600 + * enabled?: bool, // Default: true + * factory?: scalar|null, + * regions?: array, + * loggers?: array, + * }, + * hydrators?: array, + * mappings?: array, + * dql?: array{ + * string_functions?: array, + * numeric_functions?: array, + * datetime_functions?: array, + * }, + * filters?: array, + * }>, + * identity_generation_preferences?: array, + * }>, + * resolve_target_entities?: array, + * }, + * } + * @psalm-type FrameworkConfig = array{ + * secret?: scalar|null, + * http_method_override?: bool, // Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests. // Default: false + * allowed_http_method_override?: list|null, + * trust_x_sendfile_type_header?: scalar|null, // Set true to enable support for xsendfile in binary file responses. // Default: "%env(bool:default::SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER)%" + * ide?: scalar|null, // Default: "%env(default::SYMFONY_IDE)%" + * test?: bool, + * default_locale?: scalar|null, // Default: "en" + * set_locale_from_accept_language?: bool, // Whether to use the Accept-Language HTTP header to set the Request locale (only when the "_locale" request attribute is not passed). // Default: false + * set_content_language_from_locale?: bool, // Whether to set the Content-Language HTTP header on the Response using the Request locale. // Default: false + * enabled_locales?: list, + * trusted_hosts?: list, + * trusted_proxies?: mixed, // Default: ["%env(default::SYMFONY_TRUSTED_PROXIES)%"] + * trusted_headers?: list, + * error_controller?: scalar|null, // Default: "error_controller" + * handle_all_throwables?: bool, // HttpKernel will handle all kinds of \Throwable. // Default: true + * csrf_protection?: bool|array{ + * enabled?: scalar|null, // Default: null + * stateless_token_ids?: list, + * check_header?: scalar|null, // Whether to check the CSRF token in a header in addition to a cookie when using stateless protection. // Default: false + * cookie_name?: scalar|null, // The name of the cookie to use when using stateless protection. // Default: "csrf-token" + * }, + * form?: bool|array{ // Form configuration + * enabled?: bool, // Default: true + * csrf_protection?: array{ + * enabled?: scalar|null, // Default: null + * token_id?: scalar|null, // Default: null + * field_name?: scalar|null, // Default: "_token" + * field_attr?: array, + * }, + * }, + * http_cache?: bool|array{ // HTTP cache configuration + * enabled?: bool, // Default: false + * debug?: bool, // Default: "%kernel.debug%" + * trace_level?: "none"|"short"|"full", + * trace_header?: scalar|null, + * default_ttl?: int, + * private_headers?: list, + * skip_response_headers?: list, + * allow_reload?: bool, + * allow_revalidate?: bool, + * stale_while_revalidate?: int, + * stale_if_error?: int, + * terminate_on_cache_hit?: bool, + * }, + * esi?: bool|array{ // ESI configuration + * enabled?: bool, // Default: false + * }, + * ssi?: bool|array{ // SSI configuration + * enabled?: bool, // Default: false + * }, + * fragments?: bool|array{ // Fragments configuration + * enabled?: bool, // Default: false + * hinclude_default_template?: scalar|null, // Default: null + * path?: scalar|null, // Default: "/_fragment" + * }, + * profiler?: bool|array{ // Profiler configuration + * enabled?: bool, // Default: false + * collect?: bool, // Default: true + * collect_parameter?: scalar|null, // The name of the parameter to use to enable or disable collection on a per request basis. // Default: null + * only_exceptions?: bool, // Default: false + * only_main_requests?: bool, // Default: false + * dsn?: scalar|null, // Default: "file:%kernel.cache_dir%/profiler" + * collect_serializer_data?: true, // Default: true + * }, + * workflows?: bool|array{ + * enabled?: bool, // Default: false + * workflows?: array, + * definition_validators?: list, + * support_strategy?: scalar|null, + * initial_marking?: list, + * events_to_dispatch?: list|null, + * places?: list, + * }>, + * transitions: list, + * to?: list, + * weight?: int, // Default: 1 + * metadata?: list, + * }>, + * metadata?: list, + * }>, + * }, + * router?: bool|array{ // Router configuration + * enabled?: bool, // Default: false + * resource: scalar|null, + * type?: scalar|null, + * default_uri?: scalar|null, // The default URI used to generate URLs in a non-HTTP context. // Default: null + * http_port?: scalar|null, // Default: 80 + * https_port?: scalar|null, // Default: 443 + * strict_requirements?: scalar|null, // set to true to throw an exception when a parameter does not match the requirements set to false to disable exceptions when a parameter does not match the requirements (and return null instead) set to null to disable parameter checks against requirements 'true' is the preferred configuration in development mode, while 'false' or 'null' might be preferred in production // Default: true + * utf8?: bool, // Default: true + * }, + * session?: bool|array{ // Session configuration + * enabled?: bool, // Default: false + * storage_factory_id?: scalar|null, // Default: "session.storage.factory.native" + * handler_id?: scalar|null, // Defaults to using the native session handler, or to the native *file* session handler if "save_path" is not null. + * name?: scalar|null, + * cookie_lifetime?: scalar|null, + * cookie_path?: scalar|null, + * cookie_domain?: scalar|null, + * cookie_secure?: true|false|"auto", // Default: "auto" + * cookie_httponly?: bool, // Default: true + * cookie_samesite?: null|"lax"|"strict"|"none", // Default: "lax" + * use_cookies?: bool, + * gc_divisor?: scalar|null, + * gc_probability?: scalar|null, + * gc_maxlifetime?: scalar|null, + * save_path?: scalar|null, // Defaults to "%kernel.cache_dir%/sessions" if the "handler_id" option is not null. + * metadata_update_threshold?: int, // Seconds to wait between 2 session metadata updates. // Default: 0 + * }, + * request?: bool|array{ // Request configuration + * enabled?: bool, // Default: false + * formats?: array>, + * }, + * assets?: bool|array{ // Assets configuration + * enabled?: bool, // Default: true + * strict_mode?: bool, // Throw an exception if an entry is missing from the manifest.json. // Default: false + * version_strategy?: scalar|null, // Default: null + * version?: scalar|null, // Default: null + * version_format?: scalar|null, // Default: "%%s?%%s" + * json_manifest_path?: scalar|null, // Default: null + * base_path?: scalar|null, // Default: "" + * base_urls?: list, + * packages?: array, + * }>, + * }, + * asset_mapper?: bool|array{ // Asset Mapper configuration + * enabled?: bool, // Default: true + * paths?: array, + * excluded_patterns?: list, + * exclude_dotfiles?: bool, // If true, any files starting with "." will be excluded from the asset mapper. // Default: true + * server?: bool, // If true, a "dev server" will return the assets from the public directory (true in "debug" mode only by default). // Default: true + * public_prefix?: scalar|null, // The public path where the assets will be written to (and served from when "server" is true). // Default: "/assets/" + * missing_import_mode?: "strict"|"warn"|"ignore", // Behavior if an asset cannot be found when imported from JavaScript or CSS files - e.g. "import './non-existent.js'". "strict" means an exception is thrown, "warn" means a warning is logged, "ignore" means the import is left as-is. // Default: "warn" + * extensions?: array, + * importmap_path?: scalar|null, // The path of the importmap.php file. // Default: "%kernel.project_dir%/importmap.php" + * importmap_polyfill?: scalar|null, // The importmap name that will be used to load the polyfill. Set to false to disable. // Default: "es-module-shims" + * importmap_script_attributes?: array, + * vendor_dir?: scalar|null, // The directory to store JavaScript vendors. // Default: "%kernel.project_dir%/assets/vendor" + * precompress?: bool|array{ // Precompress assets with Brotli, Zstandard and gzip. + * enabled?: bool, // Default: false + * formats?: list, + * extensions?: list, + * }, + * }, + * translator?: bool|array{ // Translator configuration + * enabled?: bool, // Default: true + * fallbacks?: list, + * logging?: bool, // Default: false + * formatter?: scalar|null, // Default: "translator.formatter.default" + * cache_dir?: scalar|null, // Default: "%kernel.cache_dir%/translations" + * default_path?: scalar|null, // The default path used to load translations. // Default: "%kernel.project_dir%/translations" + * paths?: list, + * pseudo_localization?: bool|array{ + * enabled?: bool, // Default: false + * accents?: bool, // Default: true + * expansion_factor?: float, // Default: 1.0 + * brackets?: bool, // Default: true + * parse_html?: bool, // Default: false + * localizable_html_attributes?: list, + * }, + * providers?: array, + * locales?: list, + * }>, + * globals?: array, + * domain?: string, + * }>, + * }, + * validation?: bool|array{ // Validation configuration + * enabled?: bool, // Default: true + * enable_attributes?: bool, // Default: true + * static_method?: list, + * translation_domain?: scalar|null, // Default: "validators" + * email_validation_mode?: "html5"|"html5-allow-no-tld"|"strict", // Default: "html5" + * mapping?: array{ + * paths?: list, + * }, + * not_compromised_password?: bool|array{ + * enabled?: bool, // When disabled, compromised passwords will be accepted as valid. // Default: true + * endpoint?: scalar|null, // API endpoint for the NotCompromisedPassword Validator. // Default: null + * }, + * disable_translation?: bool, // Default: false + * auto_mapping?: array, + * }>, + * }, + * serializer?: bool|array{ // Serializer configuration + * enabled?: bool, // Default: false + * enable_attributes?: bool, // Default: true + * name_converter?: scalar|null, + * circular_reference_handler?: scalar|null, + * max_depth_handler?: scalar|null, + * mapping?: array{ + * paths?: list, + * }, + * default_context?: list, + * named_serializers?: array, + * include_built_in_normalizers?: bool, // Whether to include the built-in normalizers // Default: true + * include_built_in_encoders?: bool, // Whether to include the built-in encoders // Default: true + * }>, + * }, + * property_access?: bool|array{ // Property access configuration + * enabled?: bool, // Default: true + * magic_call?: bool, // Default: false + * magic_get?: bool, // Default: true + * magic_set?: bool, // Default: true + * throw_exception_on_invalid_index?: bool, // Default: false + * throw_exception_on_invalid_property_path?: bool, // Default: true + * }, + * type_info?: bool|array{ // Type info configuration + * enabled?: bool, // Default: true + * aliases?: array, + * }, + * property_info?: bool|array{ // Property info configuration + * enabled?: bool, // Default: true + * with_constructor_extractor?: bool, // Registers the constructor extractor. // Default: true + * }, + * cache?: array{ // Cache configuration + * prefix_seed?: scalar|null, // Used to namespace cache keys when using several apps with the same shared backend. // Default: "_%kernel.project_dir%.%kernel.container_class%" + * app?: scalar|null, // App related cache pools configuration. // Default: "cache.adapter.filesystem" + * system?: scalar|null, // System related cache pools configuration. // Default: "cache.adapter.system" + * directory?: scalar|null, // Default: "%kernel.share_dir%/pools/app" + * default_psr6_provider?: scalar|null, + * default_redis_provider?: scalar|null, // Default: "redis://localhost" + * default_valkey_provider?: scalar|null, // Default: "valkey://localhost" + * default_memcached_provider?: scalar|null, // Default: "memcached://localhost" + * default_doctrine_dbal_provider?: scalar|null, // Default: "database_connection" + * default_pdo_provider?: scalar|null, // Default: null + * pools?: array, + * tags?: scalar|null, // Default: null + * public?: bool, // Default: false + * default_lifetime?: scalar|null, // Default lifetime of the pool. + * provider?: scalar|null, // Overwrite the setting from the default provider for this adapter. + * early_expiration_message_bus?: scalar|null, + * clearer?: scalar|null, + * }>, + * }, + * php_errors?: array{ // PHP errors handling configuration + * log?: mixed, // Use the application logger instead of the PHP logger for logging PHP errors. // Default: true + * throw?: bool, // Throw PHP errors as \ErrorException instances. // Default: true + * }, + * exceptions?: array, + * web_link?: bool|array{ // Web links configuration + * enabled?: bool, // Default: false + * }, + * lock?: bool|string|array{ // Lock configuration + * enabled?: bool, // Default: false + * resources?: array>, + * }, + * semaphore?: bool|string|array{ // Semaphore configuration + * enabled?: bool, // Default: false + * resources?: array, + * }, + * messenger?: bool|array{ // Messenger configuration + * enabled?: bool, // Default: false + * routing?: array, + * }>, + * serializer?: array{ + * default_serializer?: scalar|null, // Service id to use as the default serializer for the transports. // Default: "messenger.transport.native_php_serializer" + * symfony_serializer?: array{ + * format?: scalar|null, // Serialization format for the messenger.transport.symfony_serializer service (which is not the serializer used by default). // Default: "json" + * context?: array, + * }, + * }, + * transports?: array, + * failure_transport?: scalar|null, // Transport name to send failed messages to (after all retries have failed). // Default: null + * retry_strategy?: string|array{ + * service?: scalar|null, // Service id to override the retry strategy entirely. // Default: null + * max_retries?: int, // Default: 3 + * delay?: int, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 + * multiplier?: float, // If greater than 1, delay will grow exponentially for each retry: this delay = (delay * (multiple ^ retries)). // Default: 2 + * max_delay?: int, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 + * jitter?: float, // Randomness to apply to the delay (between 0 and 1). // Default: 0.1 + * }, + * rate_limiter?: scalar|null, // Rate limiter name to use when processing messages. // Default: null + * }>, + * failure_transport?: scalar|null, // Transport name to send failed messages to (after all retries have failed). // Default: null + * stop_worker_on_signals?: list, + * default_bus?: scalar|null, // Default: null + * buses?: array, + * }>, + * }>, + * }, + * scheduler?: bool|array{ // Scheduler configuration + * enabled?: bool, // Default: false + * }, + * disallow_search_engine_index?: bool, // Enabled by default when debug is enabled. // Default: true + * http_client?: bool|array{ // HTTP Client configuration + * enabled?: bool, // Default: true + * max_host_connections?: int, // The maximum number of connections to a single host. + * default_options?: array{ + * headers?: array, + * vars?: array, + * max_redirects?: int, // The maximum number of redirects to follow. + * http_version?: scalar|null, // The default HTTP version, typically 1.1 or 2.0, leave to null for the best version. + * resolve?: array, + * proxy?: scalar|null, // The URL of the proxy to pass requests through or null for automatic detection. + * no_proxy?: scalar|null, // A comma separated list of hosts that do not require a proxy to be reached. + * timeout?: float, // The idle timeout, defaults to the "default_socket_timeout" ini parameter. + * max_duration?: float, // The maximum execution time for the request+response as a whole. + * bindto?: scalar|null, // A network interface name, IP address, a host name or a UNIX socket to bind to. + * verify_peer?: bool, // Indicates if the peer should be verified in a TLS context. + * verify_host?: bool, // Indicates if the host should exist as a certificate common name. + * cafile?: scalar|null, // A certificate authority file. + * capath?: scalar|null, // A directory that contains multiple certificate authority files. + * local_cert?: scalar|null, // A PEM formatted certificate file. + * local_pk?: scalar|null, // A private key file. + * passphrase?: scalar|null, // The passphrase used to encrypt the "local_pk" file. + * ciphers?: scalar|null, // A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...) + * peer_fingerprint?: array{ // Associative array: hashing algorithm => hash(es). + * sha1?: mixed, + * pin-sha256?: mixed, + * md5?: mixed, + * }, + * crypto_method?: scalar|null, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants. + * extra?: array, + * rate_limiter?: scalar|null, // Rate limiter name to use for throttling requests. // Default: null + * caching?: bool|array{ // Caching configuration. + * enabled?: bool, // Default: false + * cache_pool?: string, // The taggable cache pool to use for storing the responses. // Default: "cache.http_client" + * shared?: bool, // Indicates whether the cache is shared (public) or private. // Default: true + * max_ttl?: int, // The maximum TTL (in seconds) allowed for cached responses. Null means no cap. // Default: null + * }, + * retry_failed?: bool|array{ + * enabled?: bool, // Default: false + * retry_strategy?: scalar|null, // service id to override the retry strategy. // Default: null + * http_codes?: array, + * }>, + * max_retries?: int, // Default: 3 + * delay?: int, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 + * multiplier?: float, // If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries). // Default: 2 + * max_delay?: int, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 + * jitter?: float, // Randomness in percent (between 0 and 1) to apply to the delay. // Default: 0.1 + * }, + * }, + * mock_response_factory?: scalar|null, // The id of the service that should generate mock responses. It should be either an invokable or an iterable. + * scoped_clients?: array, + * headers?: array, + * max_redirects?: int, // The maximum number of redirects to follow. + * http_version?: scalar|null, // The default HTTP version, typically 1.1 or 2.0, leave to null for the best version. + * resolve?: array, + * proxy?: scalar|null, // The URL of the proxy to pass requests through or null for automatic detection. + * no_proxy?: scalar|null, // A comma separated list of hosts that do not require a proxy to be reached. + * timeout?: float, // The idle timeout, defaults to the "default_socket_timeout" ini parameter. + * max_duration?: float, // The maximum execution time for the request+response as a whole. + * bindto?: scalar|null, // A network interface name, IP address, a host name or a UNIX socket to bind to. + * verify_peer?: bool, // Indicates if the peer should be verified in a TLS context. + * verify_host?: bool, // Indicates if the host should exist as a certificate common name. + * cafile?: scalar|null, // A certificate authority file. + * capath?: scalar|null, // A directory that contains multiple certificate authority files. + * local_cert?: scalar|null, // A PEM formatted certificate file. + * local_pk?: scalar|null, // A private key file. + * passphrase?: scalar|null, // The passphrase used to encrypt the "local_pk" file. + * ciphers?: scalar|null, // A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...). + * peer_fingerprint?: array{ // Associative array: hashing algorithm => hash(es). + * sha1?: mixed, + * pin-sha256?: mixed, + * md5?: mixed, + * }, + * crypto_method?: scalar|null, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants. + * extra?: array, + * rate_limiter?: scalar|null, // Rate limiter name to use for throttling requests. // Default: null + * caching?: bool|array{ // Caching configuration. + * enabled?: bool, // Default: false + * cache_pool?: string, // The taggable cache pool to use for storing the responses. // Default: "cache.http_client" + * shared?: bool, // Indicates whether the cache is shared (public) or private. // Default: true + * max_ttl?: int, // The maximum TTL (in seconds) allowed for cached responses. Null means no cap. // Default: null + * }, + * retry_failed?: bool|array{ + * enabled?: bool, // Default: false + * retry_strategy?: scalar|null, // service id to override the retry strategy. // Default: null + * http_codes?: array, + * }>, + * max_retries?: int, // Default: 3 + * delay?: int, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 + * multiplier?: float, // If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries). // Default: 2 + * max_delay?: int, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 + * jitter?: float, // Randomness in percent (between 0 and 1) to apply to the delay. // Default: 0.1 + * }, + * }>, + * }, + * mailer?: bool|array{ // Mailer configuration + * enabled?: bool, // Default: false + * message_bus?: scalar|null, // The message bus to use. Defaults to the default bus if the Messenger component is installed. // Default: null + * dsn?: scalar|null, // Default: null + * transports?: array, + * envelope?: array{ // Mailer Envelope configuration + * sender?: scalar|null, + * recipients?: list, + * allowed_recipients?: list, + * }, + * headers?: array, + * dkim_signer?: bool|array{ // DKIM signer configuration + * enabled?: bool, // Default: false + * key?: scalar|null, // Key content, or path to key (in PEM format with the `file://` prefix) // Default: "" + * domain?: scalar|null, // Default: "" + * select?: scalar|null, // Default: "" + * passphrase?: scalar|null, // The private key passphrase // Default: "" + * options?: array, + * }, + * smime_signer?: bool|array{ // S/MIME signer configuration + * enabled?: bool, // Default: false + * key?: scalar|null, // Path to key (in PEM format) // Default: "" + * certificate?: scalar|null, // Path to certificate (in PEM format without the `file://` prefix) // Default: "" + * passphrase?: scalar|null, // The private key passphrase // Default: null + * extra_certificates?: scalar|null, // Default: null + * sign_options?: int, // Default: null + * }, + * smime_encrypter?: bool|array{ // S/MIME encrypter configuration + * enabled?: bool, // Default: false + * repository?: scalar|null, // S/MIME certificate repository service. This service shall implement the `Symfony\Component\Mailer\EventListener\SmimeCertificateRepositoryInterface`. // Default: "" + * cipher?: int, // A set of algorithms used to encrypt the message // Default: null + * }, + * }, + * secrets?: bool|array{ + * enabled?: bool, // Default: true + * vault_directory?: scalar|null, // Default: "%kernel.project_dir%/config/secrets/%kernel.runtime_environment%" + * local_dotenv_file?: scalar|null, // Default: "%kernel.project_dir%/.env.%kernel.runtime_environment%.local" + * decryption_env_var?: scalar|null, // Default: "base64:default::SYMFONY_DECRYPTION_SECRET" + * }, + * notifier?: bool|array{ // Notifier configuration + * enabled?: bool, // Default: false + * message_bus?: scalar|null, // The message bus to use. Defaults to the default bus if the Messenger component is installed. // Default: null + * chatter_transports?: array, + * texter_transports?: array, + * notification_on_failed_messages?: bool, // Default: false + * channel_policy?: array>, + * admin_recipients?: list, + * }, + * rate_limiter?: bool|array{ // Rate limiter configuration + * enabled?: bool, // Default: false + * limiters?: array, + * limit?: int, // The maximum allowed hits in a fixed interval or burst. + * interval?: scalar|null, // Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). + * rate?: array{ // Configures the fill rate if "policy" is set to "token_bucket". + * interval?: scalar|null, // Configures the rate interval. The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). + * amount?: int, // Amount of tokens to add each interval. // Default: 1 + * }, + * }>, + * }, + * uid?: bool|array{ // Uid configuration + * enabled?: bool, // Default: false + * default_uuid_version?: 7|6|4|1, // Default: 7 + * name_based_uuid_version?: 5|3, // Default: 5 + * name_based_uuid_namespace?: scalar|null, + * time_based_uuid_version?: 7|6|1, // Default: 7 + * time_based_uuid_node?: scalar|null, + * }, + * html_sanitizer?: bool|array{ // HtmlSanitizer configuration + * enabled?: bool, // Default: false + * sanitizers?: array, + * block_elements?: list, + * drop_elements?: list, + * allow_attributes?: array, + * drop_attributes?: array, + * force_attributes?: array>, + * force_https_urls?: bool, // Transforms URLs using the HTTP scheme to use the HTTPS scheme instead. // Default: false + * allowed_link_schemes?: list, + * allowed_link_hosts?: list|null, + * allow_relative_links?: bool, // Allows relative URLs to be used in links href attributes. // Default: false + * allowed_media_schemes?: list, + * allowed_media_hosts?: list|null, + * allow_relative_medias?: bool, // Allows relative URLs to be used in media source attributes (img, audio, video, ...). // Default: false + * with_attribute_sanitizers?: list, + * without_attribute_sanitizers?: list, + * max_input_length?: int, // The maximum length allowed for the sanitized input. // Default: 0 + * }>, + * }, + * webhook?: bool|array{ // Webhook configuration + * enabled?: bool, // Default: false + * message_bus?: scalar|null, // The message bus to use. // Default: "messenger.default_bus" + * routing?: array, + * }, + * remote-event?: bool|array{ // RemoteEvent configuration + * enabled?: bool, // Default: false + * }, + * json_streamer?: bool|array{ // JSON streamer configuration + * enabled?: bool, // Default: false + * }, + * } + * @psalm-type TwigConfig = array{ + * form_themes?: list, + * globals?: array, + * autoescape_service?: scalar|null, // Default: null + * autoescape_service_method?: scalar|null, // Default: null + * cache?: scalar|null, // Default: true + * charset?: scalar|null, // Default: "%kernel.charset%" + * debug?: bool, // Default: "%kernel.debug%" + * strict_variables?: bool, // Default: "%kernel.debug%" + * auto_reload?: scalar|null, + * optimizations?: int, + * default_path?: scalar|null, // The default path used to load templates. // Default: "%kernel.project_dir%/templates" + * file_name_pattern?: list, + * paths?: array, + * date?: array{ // The default format options used by the date filter. + * format?: scalar|null, // Default: "F j, Y H:i" + * interval_format?: scalar|null, // Default: "%d days" + * timezone?: scalar|null, // The timezone used when formatting dates, when set to null, the timezone returned by date_default_timezone_get() is used. // Default: null + * }, + * number_format?: array{ // The default format options for the number_format filter. + * decimals?: int, // Default: 0 + * decimal_point?: scalar|null, // Default: "." + * thousands_separator?: scalar|null, // Default: "," + * }, + * mailer?: array{ + * html_to_text_converter?: scalar|null, // A service implementing the "Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface". // Default: null + * }, + * } + * @psalm-type SecurityConfig = array{ + * access_denied_url?: scalar|null, // Default: null + * session_fixation_strategy?: "none"|"migrate"|"invalidate", // Default: "migrate" + * expose_security_errors?: \Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::None|\Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::AccountStatus|\Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::All, // Default: "none" + * erase_credentials?: bool, // Default: true + * access_decision_manager?: array{ + * strategy?: "affirmative"|"consensus"|"unanimous"|"priority", + * service?: scalar|null, + * strategy_service?: scalar|null, + * allow_if_all_abstain?: bool, // Default: false + * allow_if_equal_granted_denied?: bool, // Default: true + * }, + * password_hashers?: array, + * hash_algorithm?: scalar|null, // Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms. // Default: "sha512" + * key_length?: scalar|null, // Default: 40 + * ignore_case?: bool, // Default: false + * encode_as_base64?: bool, // Default: true + * iterations?: scalar|null, // Default: 5000 + * cost?: int, // Default: null + * memory_cost?: scalar|null, // Default: null + * time_cost?: scalar|null, // Default: null + * id?: scalar|null, + * }>, + * providers?: array, + * }, + * entity?: array{ + * class: scalar|null, // The full entity class name of your user class. + * property?: scalar|null, // Default: null + * manager_name?: scalar|null, // Default: null + * }, + * memory?: array{ + * users?: array, + * }>, + * }, + * ldap?: array{ + * service: scalar|null, + * base_dn: scalar|null, + * search_dn?: scalar|null, // Default: null + * search_password?: scalar|null, // Default: null + * extra_fields?: list, + * default_roles?: list, + * role_fetcher?: scalar|null, // Default: null + * uid_key?: scalar|null, // Default: "sAMAccountName" + * filter?: scalar|null, // Default: "({uid_key}={user_identifier})" + * password_attribute?: scalar|null, // Default: null + * }, + * }>, + * firewalls: array, + * security?: bool, // Default: true + * user_checker?: scalar|null, // The UserChecker to use when authenticating users in this firewall. // Default: "security.user_checker" + * request_matcher?: scalar|null, + * access_denied_url?: scalar|null, + * access_denied_handler?: scalar|null, + * entry_point?: scalar|null, // An enabled authenticator name or a service id that implements "Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface". + * provider?: scalar|null, + * stateless?: bool, // Default: false + * lazy?: bool, // Default: false + * context?: scalar|null, + * logout?: array{ + * enable_csrf?: bool|null, // Default: null + * csrf_token_id?: scalar|null, // Default: "logout" + * csrf_parameter?: scalar|null, // Default: "_csrf_token" + * csrf_token_manager?: scalar|null, + * path?: scalar|null, // Default: "/logout" + * target?: scalar|null, // Default: "/" + * invalidate_session?: bool, // Default: true + * clear_site_data?: list<"*"|"cache"|"cookies"|"storage"|"executionContexts">, + * delete_cookies?: array, + * }, + * switch_user?: array{ + * provider?: scalar|null, + * parameter?: scalar|null, // Default: "_switch_user" + * role?: scalar|null, // Default: "ROLE_ALLOWED_TO_SWITCH" + * target_route?: scalar|null, // Default: null + * }, + * required_badges?: list, + * custom_authenticators?: list, + * login_throttling?: array{ + * limiter?: scalar|null, // A service id implementing "Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface". + * max_attempts?: int, // Default: 5 + * interval?: scalar|null, // Default: "1 minute" + * lock_factory?: scalar|null, // The service ID of the lock factory used by the login rate limiter (or null to disable locking). // Default: null + * cache_pool?: string, // The cache pool to use for storing the limiter state // Default: "cache.rate_limiter" + * storage_service?: string, // The service ID of a custom storage implementation, this precedes any configured "cache_pool" // Default: null + * }, + * x509?: array{ + * provider?: scalar|null, + * user?: scalar|null, // Default: "SSL_CLIENT_S_DN_Email" + * credentials?: scalar|null, // Default: "SSL_CLIENT_S_DN" + * user_identifier?: scalar|null, // Default: "emailAddress" + * }, + * remote_user?: array{ + * provider?: scalar|null, + * user?: scalar|null, // Default: "REMOTE_USER" + * }, + * login_link?: array{ + * check_route: scalar|null, // Route that will validate the login link - e.g. "app_login_link_verify". + * check_post_only?: scalar|null, // If true, only HTTP POST requests to "check_route" will be handled by the authenticator. // Default: false + * signature_properties: list, + * lifetime?: int, // The lifetime of the login link in seconds. // Default: 600 + * max_uses?: int, // Max number of times a login link can be used - null means unlimited within lifetime. // Default: null + * used_link_cache?: scalar|null, // Cache service id used to expired links of max_uses is set. + * success_handler?: scalar|null, // A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface. + * failure_handler?: scalar|null, // A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface. + * provider?: scalar|null, // The user provider to load users from. + * secret?: scalar|null, // Default: "%kernel.secret%" + * always_use_default_target_path?: bool, // Default: false + * default_target_path?: scalar|null, // Default: "/" + * login_path?: scalar|null, // Default: "/login" + * target_path_parameter?: scalar|null, // Default: "_target_path" + * use_referer?: bool, // Default: false + * failure_path?: scalar|null, // Default: null + * failure_forward?: bool, // Default: false + * failure_path_parameter?: scalar|null, // Default: "_failure_path" + * }, + * form_login?: array{ + * provider?: scalar|null, + * remember_me?: bool, // Default: true + * success_handler?: scalar|null, + * failure_handler?: scalar|null, + * check_path?: scalar|null, // Default: "/login_check" + * use_forward?: bool, // Default: false + * login_path?: scalar|null, // Default: "/login" + * username_parameter?: scalar|null, // Default: "_username" + * password_parameter?: scalar|null, // Default: "_password" + * csrf_parameter?: scalar|null, // Default: "_csrf_token" + * csrf_token_id?: scalar|null, // Default: "authenticate" + * enable_csrf?: bool, // Default: false + * post_only?: bool, // Default: true + * form_only?: bool, // Default: false + * always_use_default_target_path?: bool, // Default: false + * default_target_path?: scalar|null, // Default: "/" + * target_path_parameter?: scalar|null, // Default: "_target_path" + * use_referer?: bool, // Default: false + * failure_path?: scalar|null, // Default: null + * failure_forward?: bool, // Default: false + * failure_path_parameter?: scalar|null, // Default: "_failure_path" + * }, + * form_login_ldap?: array{ + * provider?: scalar|null, + * remember_me?: bool, // Default: true + * success_handler?: scalar|null, + * failure_handler?: scalar|null, + * check_path?: scalar|null, // Default: "/login_check" + * use_forward?: bool, // Default: false + * login_path?: scalar|null, // Default: "/login" + * username_parameter?: scalar|null, // Default: "_username" + * password_parameter?: scalar|null, // Default: "_password" + * csrf_parameter?: scalar|null, // Default: "_csrf_token" + * csrf_token_id?: scalar|null, // Default: "authenticate" + * enable_csrf?: bool, // Default: false + * post_only?: bool, // Default: true + * form_only?: bool, // Default: false + * always_use_default_target_path?: bool, // Default: false + * default_target_path?: scalar|null, // Default: "/" + * target_path_parameter?: scalar|null, // Default: "_target_path" + * use_referer?: bool, // Default: false + * failure_path?: scalar|null, // Default: null + * failure_forward?: bool, // Default: false + * failure_path_parameter?: scalar|null, // Default: "_failure_path" + * service?: scalar|null, // Default: "ldap" + * dn_string?: scalar|null, // Default: "{user_identifier}" + * query_string?: scalar|null, + * search_dn?: scalar|null, // Default: "" + * search_password?: scalar|null, // Default: "" + * }, + * json_login?: array{ + * provider?: scalar|null, + * remember_me?: bool, // Default: true + * success_handler?: scalar|null, + * failure_handler?: scalar|null, + * check_path?: scalar|null, // Default: "/login_check" + * use_forward?: bool, // Default: false + * login_path?: scalar|null, // Default: "/login" + * username_path?: scalar|null, // Default: "username" + * password_path?: scalar|null, // Default: "password" + * }, + * json_login_ldap?: array{ + * provider?: scalar|null, + * remember_me?: bool, // Default: true + * success_handler?: scalar|null, + * failure_handler?: scalar|null, + * check_path?: scalar|null, // Default: "/login_check" + * use_forward?: bool, // Default: false + * login_path?: scalar|null, // Default: "/login" + * username_path?: scalar|null, // Default: "username" + * password_path?: scalar|null, // Default: "password" + * service?: scalar|null, // Default: "ldap" + * dn_string?: scalar|null, // Default: "{user_identifier}" + * query_string?: scalar|null, + * search_dn?: scalar|null, // Default: "" + * search_password?: scalar|null, // Default: "" + * }, + * access_token?: array{ + * provider?: scalar|null, + * remember_me?: bool, // Default: true + * success_handler?: scalar|null, + * failure_handler?: scalar|null, + * realm?: scalar|null, // Default: null + * token_extractors?: list, + * token_handler: string|array{ + * id?: scalar|null, + * oidc_user_info?: string|array{ + * base_uri: scalar|null, // Base URI of the userinfo endpoint on the OIDC server, or the OIDC server URI to use the discovery (require "discovery" to be configured). + * discovery?: array{ // Enable the OIDC discovery. + * cache?: array{ + * id: scalar|null, // Cache service id to use to cache the OIDC discovery configuration. + * }, + * }, + * claim?: scalar|null, // Claim which contains the user identifier (e.g. sub, email, etc.). // Default: "sub" + * client?: scalar|null, // HttpClient service id to use to call the OIDC server. + * }, + * oidc?: array{ + * discovery?: array{ // Enable the OIDC discovery. + * base_uri: list, + * cache?: array{ + * id: scalar|null, // Cache service id to use to cache the OIDC discovery configuration. + * }, + * }, + * claim?: scalar|null, // Claim which contains the user identifier (e.g.: sub, email..). // Default: "sub" + * audience: scalar|null, // Audience set in the token, for validation purpose. + * issuers: list, + * algorithms: list, + * keyset?: scalar|null, // JSON-encoded JWKSet used to sign the token (must contain a list of valid public keys). + * encryption?: bool|array{ + * enabled?: bool, // Default: false + * enforce?: bool, // When enabled, the token shall be encrypted. // Default: false + * algorithms: list, + * keyset: scalar|null, // JSON-encoded JWKSet used to decrypt the token (must contain a list of valid private keys). + * }, + * }, + * cas?: array{ + * validation_url: scalar|null, // CAS server validation URL + * prefix?: scalar|null, // CAS prefix // Default: "cas" + * http_client?: scalar|null, // HTTP Client service // Default: null + * }, + * oauth2?: scalar|null, + * }, + * }, + * http_basic?: array{ + * provider?: scalar|null, + * realm?: scalar|null, // Default: "Secured Area" + * }, + * http_basic_ldap?: array{ + * provider?: scalar|null, + * realm?: scalar|null, // Default: "Secured Area" + * service?: scalar|null, // Default: "ldap" + * dn_string?: scalar|null, // Default: "{user_identifier}" + * query_string?: scalar|null, + * search_dn?: scalar|null, // Default: "" + * search_password?: scalar|null, // Default: "" + * }, + * remember_me?: array{ + * secret?: scalar|null, // Default: "%kernel.secret%" + * service?: scalar|null, + * user_providers?: list, + * catch_exceptions?: bool, // Default: true + * signature_properties?: list, + * token_provider?: string|array{ + * service?: scalar|null, // The service ID of a custom remember-me token provider. + * doctrine?: bool|array{ + * enabled?: bool, // Default: false + * connection?: scalar|null, // Default: null + * }, + * }, + * token_verifier?: scalar|null, // The service ID of a custom rememberme token verifier. + * name?: scalar|null, // Default: "REMEMBERME" + * lifetime?: int, // Default: 31536000 + * path?: scalar|null, // Default: "/" + * domain?: scalar|null, // Default: null + * secure?: true|false|"auto", // Default: null + * httponly?: bool, // Default: true + * samesite?: null|"lax"|"strict"|"none", // Default: "lax" + * always_remember_me?: bool, // Default: false + * remember_me_parameter?: scalar|null, // Default: "_remember_me" + * }, + * }>, + * access_control?: list, + * attributes?: array, + * route?: scalar|null, // Default: null + * methods?: list, + * allow_if?: scalar|null, // Default: null + * roles?: list, + * }>, + * role_hierarchy?: array>, + * } + * @psalm-type TwigComponentConfig = array{ + * defaults?: array, + * anonymous_template_directory?: scalar|null, // Defaults to `components` + * profiler?: bool, // Enables the profiler for Twig Component (in debug mode) // Default: "%kernel.debug%" + * controllers_json?: scalar|null, // Deprecated: The "twig_component.controllers_json" config option is deprecated, and will be removed in 3.0. // Default: null + * } + * @psalm-type SyliusTwigHooksConfig = array{ + * enable_autoprefixing?: bool, // Default: false + * hook_name_section_separator?: scalar|null, // Default: false + * supported_hookable_types?: array, + * hooks?: array, + * props?: array, + * configuration?: array, + * priority?: int, // Default: null + * }>>, + * } + * @psalm-type SyliusBootstrapAdminUiConfig = array + * @psalm-type SyliusGridConfig = array{ + * drivers?: list<"doctrine/orm"|"doctrine/phpcr-odm">, + * templates?: array{ + * filter?: array, + * action?: array, + * bulk_action?: array, + * }, + * grids?: array, + * }, + * sorting?: array, + * limits?: list, + * fields?: array, + * }>, + * filters?: array, + * form_options?: list, + * default_value?: mixed, + * }>, + * actions?: array, + * }>>, + * removals?: array, + * }>, + * } + * @psalm-type SyliusResourceConfig = array{ + * resources?: array, + * settings?: array{ + * paginate?: mixed, // Default: null + * limit?: mixed, // Default: null + * allowed_paginate?: list, + * default_page_size?: int, // Default: 10 + * default_templates_dir?: scalar|null, // Default: null + * sortable?: bool, // Default: false + * sorting?: mixed, // Default: null + * filterable?: bool, // Default: false + * criteria?: mixed, // Default: null + * state_machine_component?: scalar|null, // Default: null + * }, + * translation?: bool|array{ + * enabled?: bool, // Default: true + * locale_provider?: scalar|null, // Default: "sylius.translation_locale_provider.immutable" + * }, + * drivers?: list<"doctrine/orm"|"doctrine/mongodb-odm"|"doctrine/phpcr-odm">, + * mapping?: array{ + * imports?: list, + * paths?: list, + * }, + * authorization_checker?: scalar|null, // Default: "sylius.resource_controller.authorization_checker.disabled" + * routing_path_bc_layer?: bool, + * path_segment_name_generator?: scalar|null, // Specify a path name generator to use. // Default: "sylius.metadata.path_segment_name_generator.dash" + * } + * @psalm-type SyliusTwigExtraConfig = array{ + * twig_ux?: array{ + * anonymous_component_template_prefixes?: array, + * }, + * } + * @psalm-type KnpMenuConfig = array{ + * providers?: array{ + * builder_alias?: bool, // Default: true + * }, + * twig?: array{ + * template?: scalar|null, // Default: "@KnpMenu/menu.html.twig" + * }, + * templating?: bool, // Default: false + * default_renderer?: scalar|null, // Default: "twig" + * } + * @psalm-type BabdevPagerfantaConfig = array{ + * default_view?: scalar|null, // Default: "default" + * default_twig_template?: scalar|null, // Default: "@BabDevPagerfanta/default.html.twig" + * exceptions_strategy?: array{ + * out_of_range_page?: "to_http_not_found"|"custom", // Default: "to_http_not_found" + * not_valid_current_page?: "to_http_not_found"|"custom", // Default: "to_http_not_found" + * }, + * } + * @psalm-type SyliusAdminUiConfig = array{ + * routing?: array, + * } + * @psalm-type UxIconsConfig = array{ + * icon_dir?: scalar|null, // The local directory where icons are stored. // Default: "%kernel.project_dir%/assets/icons" + * default_icon_attributes?: mixed, // Default attributes to add to all icons. // Default: {"fill":"currentColor"} + * icon_sets?: array, + * }>, + * aliases?: list, + * iconify?: bool|array{ // Configuration for the remote icon service. + * enabled?: bool, // Default: true + * on_demand?: bool, // Whether to download icons "on demand". // Default: true + * endpoint?: scalar|null, // The endpoint for the Iconify icons API. // Default: "https://api.iconify.design" + * }, + * ignore_not_found?: bool, // Ignore error when an icon is not found. Set to 'true' to fail silently. // Default: false + * } + * @psalm-type WhiteOctoberPagerfantaConfig = array{ // Deprecated: The "white_october_pagerfanta" configuration node is deprecated, migrate your configuration to the "babdev_pagerfanta" configuration node. + * exceptions_strategy?: array{ + * out_of_range_page?: scalar|null, // Default: "to_http_not_found" + * not_valid_current_page?: scalar|null, // Default: "to_http_not_found" + * }, + * default_view?: scalar|null, // Default: "default" + * } + * @psalm-type ConfigType = array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * doctrine?: DoctrineConfig, + * framework?: FrameworkConfig, + * twig?: TwigConfig, + * security?: SecurityConfig, + * twig_component?: TwigComponentConfig, + * sylius_twig_hooks?: SyliusTwigHooksConfig, + * sylius_bootstrap_admin_ui?: SyliusBootstrapAdminUiConfig, + * sylius_grid?: SyliusGridConfig, + * sylius_resource?: SyliusResourceConfig, + * sylius_twig_extra?: SyliusTwigExtraConfig, + * knp_menu?: KnpMenuConfig, + * babdev_pagerfanta?: BabdevPagerfantaConfig, + * sylius_admin_ui?: SyliusAdminUiConfig, + * ux_icons?: UxIconsConfig, + * white_october_pagerfanta?: WhiteOctoberPagerfantaConfig, + * "when@dev"?: array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * doctrine?: DoctrineConfig, + * framework?: FrameworkConfig, + * twig?: TwigConfig, + * security?: SecurityConfig, + * twig_component?: TwigComponentConfig, + * sylius_twig_hooks?: SyliusTwigHooksConfig, + * sylius_bootstrap_admin_ui?: SyliusBootstrapAdminUiConfig, + * sylius_grid?: SyliusGridConfig, + * sylius_resource?: SyliusResourceConfig, + * sylius_twig_extra?: SyliusTwigExtraConfig, + * knp_menu?: KnpMenuConfig, + * babdev_pagerfanta?: BabdevPagerfantaConfig, + * sylius_admin_ui?: SyliusAdminUiConfig, + * ux_icons?: UxIconsConfig, + * }, + * "when@prod"?: array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * doctrine?: DoctrineConfig, + * framework?: FrameworkConfig, + * twig?: TwigConfig, + * security?: SecurityConfig, + * twig_component?: TwigComponentConfig, + * sylius_twig_hooks?: SyliusTwigHooksConfig, + * sylius_bootstrap_admin_ui?: SyliusBootstrapAdminUiConfig, + * sylius_grid?: SyliusGridConfig, + * sylius_resource?: SyliusResourceConfig, + * sylius_twig_extra?: SyliusTwigExtraConfig, + * knp_menu?: KnpMenuConfig, + * babdev_pagerfanta?: BabdevPagerfantaConfig, + * sylius_admin_ui?: SyliusAdminUiConfig, + * ux_icons?: UxIconsConfig, + * }, + * "when@test"?: array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * doctrine?: DoctrineConfig, + * framework?: FrameworkConfig, + * twig?: TwigConfig, + * security?: SecurityConfig, + * twig_component?: TwigComponentConfig, + * sylius_twig_hooks?: SyliusTwigHooksConfig, + * sylius_bootstrap_admin_ui?: SyliusBootstrapAdminUiConfig, + * sylius_grid?: SyliusGridConfig, + * sylius_resource?: SyliusResourceConfig, + * sylius_twig_extra?: SyliusTwigExtraConfig, + * knp_menu?: KnpMenuConfig, + * babdev_pagerfanta?: BabdevPagerfantaConfig, + * sylius_admin_ui?: SyliusAdminUiConfig, + * ux_icons?: UxIconsConfig, + * }, + * ..., + * }> + * } + */ +final class App +{ + /** + * @param ConfigType $config + * + * @psalm-return ConfigType + */ + public static function config(array $config): array + { + return AppReference::config($config); + } +} + +namespace Symfony\Component\Routing\Loader\Configurator; + +/** + * This class provides array-shapes for configuring the routes of an application. + * + * Example: + * + * ```php + * // config/routes.php + * namespace Symfony\Component\Routing\Loader\Configurator; + * + * return Routes::config([ + * 'controllers' => [ + * 'resource' => 'routing.controllers', + * ], + * ]); + * ``` + * + * @psalm-type RouteConfig = array{ + * path: string|array, + * controller?: string, + * methods?: string|list, + * requirements?: array, + * defaults?: array, + * options?: array, + * host?: string|array, + * schemes?: string|list, + * condition?: string, + * locale?: string, + * format?: string, + * utf8?: bool, + * stateless?: bool, + * } + * @psalm-type ImportConfig = array{ + * resource: string, + * type?: string, + * exclude?: string|list, + * prefix?: string|array, + * name_prefix?: string, + * trailing_slash_on_root?: bool, + * controller?: string, + * methods?: string|list, + * requirements?: array, + * defaults?: array, + * options?: array, + * host?: string|array, + * schemes?: string|list, + * condition?: string, + * locale?: string, + * format?: string, + * utf8?: bool, + * stateless?: bool, + * } + * @psalm-type AliasConfig = array{ + * alias: string, + * deprecated?: array{package:string, version:string, message?:string}, + * } + * @psalm-type RoutesConfig = array{ + * "when@dev"?: array, + * "when@prod"?: array, + * "when@test"?: array, + * ... + * } + */ +final class Routes +{ + /** + * @param RoutesConfig $config + * + * @psalm-return RoutesConfig + */ + public static function config(array $config): array + { + return $config; + } +} diff --git a/src/BootstrapAdminUi/tests/Functional/.application/config/routes/framework.yaml b/src/BootstrapAdminUi/tests/Functional/.application/config/routes/framework.yaml index 0fc74bbac..bc1feace1 100644 --- a/src/BootstrapAdminUi/tests/Functional/.application/config/routes/framework.yaml +++ b/src/BootstrapAdminUi/tests/Functional/.application/config/routes/framework.yaml @@ -1,4 +1,4 @@ when@dev: _errors: - resource: '@FrameworkBundle/Resources/config/routing/errors.xml' + resource: '@FrameworkBundle/Resources/config/routing/errors.php' prefix: /_error diff --git a/src/BootstrapAdminUi/tests/Functional/.application/config/routes/sylius_resource.yaml b/src/BootstrapAdminUi/tests/Functional/.application/config/routes/sylius_resource.yaml index 0c8ade2c2..e4c326435 100644 --- a/src/BootstrapAdminUi/tests/Functional/.application/config/routes/sylius_resource.yaml +++ b/src/BootstrapAdminUi/tests/Functional/.application/config/routes/sylius_resource.yaml @@ -2,6 +2,6 @@ sylius_crud_routes: resource: 'sylius.routing.loader.crud_routes_attributes' type: service -sylius_routes: - resource: 'sylius.routing.loader.routes_attributes' +sylius_resource_routes: + resource: 'sylius.symfony.routing.loader.resource' type: service diff --git a/src/TwigExtra/composer.json b/src/TwigExtra/composer.json index 4f0878396..c027642ce 100644 --- a/src/TwigExtra/composer.json +++ b/src/TwigExtra/composer.json @@ -15,20 +15,20 @@ ], "require": { "php": "^8.1", - "symfony/http-kernel": "^6.4 || ^7.0", - "symfony/twig-bundle": "^6.4 || ^7.0", + "symfony/http-kernel": "^6.4 || ^7.4 || ^8.0", + "symfony/twig-bundle": "^6.4 || ^7.4 || ^8.0", "symfony/ux-twig-component": "^2.17" }, "require-dev": { - "matthiasnoback/symfony-dependency-injection-test": "^5.1", - "phpunit/phpunit": "^9.6", - "symfony/browser-kit": "^6.4 || ^7.0", - "symfony/console": "^6.4 || ^7.0", - "symfony/css-selector": "^6.4 || ^7.0", - "symfony/dotenv": "^6.4 || ^7.0", - "symfony/framework-bundle": "^6.4 || ^7.0", - "symfony/runtime": "^6.4 || ^7.0", - "symfony/yaml": "^6.4 || ^7.0" + "matthiasnoback/symfony-dependency-injection-test": "^6.1.0", + "phpunit/phpunit": "^10.5", + "symfony/browser-kit": "^6.4 || ^7.4 || ^8.0", + "symfony/console": "^6.4 || ^7.4 || ^8.0", + "symfony/css-selector": "^6.4 || ^7.4 || ^8.0", + "symfony/dotenv": "^6.4 || ^7.4 || ^8.0", + "symfony/framework-bundle": "^6.4 || ^7.4 || ^8.0", + "symfony/runtime": "^6.4 || ^7.4 || ^8.0", + "symfony/yaml": "^6.4 || ^7.4 || ^8.0" }, "conflict": { "sylius/ui-bundle": "<2.0" diff --git a/src/TwigExtra/phpunit.xml.dist b/src/TwigExtra/phpunit.xml.dist index 3e90732a4..99f913916 100644 --- a/src/TwigExtra/phpunit.xml.dist +++ b/src/TwigExtra/phpunit.xml.dist @@ -5,7 +5,6 @@ xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd" colors="true" bootstrap="tests/Functional/.application/config/bootstrap.php" - convertDeprecationsToExceptions="true" > diff --git a/src/TwigExtra/tests/Functional/.application/config/routes/framework.yaml b/src/TwigExtra/tests/Functional/.application/config/routes/framework.yaml index 0fc74bbac..bc1feace1 100644 --- a/src/TwigExtra/tests/Functional/.application/config/routes/framework.yaml +++ b/src/TwigExtra/tests/Functional/.application/config/routes/framework.yaml @@ -1,4 +1,4 @@ when@dev: _errors: - resource: '@FrameworkBundle/Resources/config/routing/errors.xml' + resource: '@FrameworkBundle/Resources/config/routing/errors.php' prefix: /_error diff --git a/src/TwigHooks/composer.json b/src/TwigHooks/composer.json index f617c945a..d3cb26cc0 100644 --- a/src/TwigHooks/composer.json +++ b/src/TwigHooks/composer.json @@ -16,29 +16,29 @@ "require": { "php": "^8.1", "laminas/laminas-stdlib": "^3.18", - "symfony/config": "^6.4 || ^7.0", - "symfony/dependency-injection": "^6.4 || ^7.0", - "symfony/expression-language": "^6.4 || ^7.0", - "symfony/http-kernel": "^6.4 || ^7.0", - "symfony/stopwatch": "^6.4 || ^7.0", + "symfony/config": "^6.4 || ^7.4 || ^8.0", + "symfony/dependency-injection": "^6.4 || ^7.4 || ^8.0", + "symfony/expression-language": "^6.4 || ^7.4 || ^8.0", + "symfony/http-kernel": "^6.4 || ^7.4 || ^8.0", + "symfony/stopwatch": "^6.4 || ^7.4 || ^8.0", "symfony/ux-live-component": "^2.17", "symfony/ux-twig-component": "^2.17", - "symfony/twig-bundle": "^6.4 || ^7.0", + "symfony/twig-bundle": "^6.4 || ^7.4 || ^8.0", "twig/twig": "^2.15 || ^3.0", "webmozart/assert": "^1.9" }, "require-dev": { - "symfony/console": "^6.4 || ^7.0", - "symfony/dom-crawler": "^6.4 || ^7.0", - "symfony/dotenv": "^6.4 || ^7.0", - "symfony/framework-bundle": "^6.4 || ^7.0", - "symfony/runtime": "^6.4 || ^7.0", - "symfony/twig-bundle": "^6.4 || ^7.0", - "symfony/web-profiler-bundle": "^6.4 || ^7.0", - "symfony/yaml": "^6.4 || ^7.0", - "matthiasnoback/symfony-config-test": "^5.1", - "matthiasnoback/symfony-dependency-injection-test": "^5.1", - "phpunit/phpunit": "^9.6" + "symfony/console": "^6.4 || ^7.4 || ^8.0", + "symfony/dom-crawler": "^6.4 || ^7.4 || ^8.0", + "symfony/dotenv": "^6.4 || ^7.4 || ^8.0", + "symfony/framework-bundle": "^6.4 || ^7.4 || ^8.0", + "symfony/runtime": "^6.4 || ^7.4 || ^8.0", + "symfony/twig-bundle": "^6.4 || ^7.4 || ^8.0", + "symfony/web-profiler-bundle": "^6.4 || ^7.4 || ^8.0", + "symfony/yaml": "^6.4 || ^7.4 || ^8.0", + "matthiasnoback/symfony-config-test": "^6.1", + "matthiasnoback/symfony-dependency-injection-test": "^6.1.0", + "phpunit/phpunit": "^10.5" }, "suggest": { "symfony/ux-twig-component": "Symfony's package providing Twig components." diff --git a/src/TwigHooks/phpunit.xml.dist b/src/TwigHooks/phpunit.xml.dist index 56f25d259..85acf3881 100644 --- a/src/TwigHooks/phpunit.xml.dist +++ b/src/TwigHooks/phpunit.xml.dist @@ -5,7 +5,6 @@ xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd" colors="true" bootstrap="tests/Functional/.application/config/bootstrap.php" - convertDeprecationsToExceptions="true" > diff --git a/src/TwigHooks/tests/Functional/.application/config/routes/framework.yaml b/src/TwigHooks/tests/Functional/.application/config/routes/framework.yaml index 0fc74bbac..bc1feace1 100644 --- a/src/TwigHooks/tests/Functional/.application/config/routes/framework.yaml +++ b/src/TwigHooks/tests/Functional/.application/config/routes/framework.yaml @@ -1,4 +1,4 @@ when@dev: _errors: - resource: '@FrameworkBundle/Resources/config/routing/errors.xml' + resource: '@FrameworkBundle/Resources/config/routing/errors.php' prefix: /_error diff --git a/src/UiTranslations/composer.json b/src/UiTranslations/composer.json index d44e05a9e..e4a065e5d 100644 --- a/src/UiTranslations/composer.json +++ b/src/UiTranslations/composer.json @@ -4,18 +4,18 @@ "type": "library", "require": { "php": "^8.1", - "symfony/translation": "^6.4 || ^7.0", - "symfony/yaml": "^6.4 || ^7.0" + "symfony/translation": "^6.4 || ^7.4 || ^8.0", + "symfony/yaml": "^6.4 || ^7.4 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^9.6", - "symfony/browser-kit": "^6.4 || ^7.0", - "symfony/console": "^6.4 || ^7.0", - "symfony/css-selector": "^6.4 || ^7.0", - "symfony/dotenv": "^6.4 || ^7.0", - "symfony/framework-bundle": "^6.4 || ^7.0", - "symfony/http-kernel": "^6.4 || ^7.0", - "symfony/twig-bundle": "^6.4 || ^7.0", + "phpunit/phpunit": "^10.5", + "symfony/browser-kit": "^6.4 || ^7.4 || ^8.0", + "symfony/console": "^6.4 || ^7.4 || ^8.0", + "symfony/css-selector": "^6.4 || ^7.4 || ^8.0", + "symfony/dotenv": "^6.4 || ^7.4 || ^8.0", + "symfony/framework-bundle": "^6.4 || ^7.4 || ^8.0", + "symfony/http-kernel": "^6.4 || ^7.4 || ^8.0", + "symfony/twig-bundle": "^6.4 || ^7.4 || ^8.0", "twig/twig": "^2.15 || ^3.0" }, "autoload": { diff --git a/src/UiTranslations/phpunit.xml.dist b/src/UiTranslations/phpunit.xml.dist index 575581fe1..abb6d8437 100644 --- a/src/UiTranslations/phpunit.xml.dist +++ b/src/UiTranslations/phpunit.xml.dist @@ -5,7 +5,6 @@ xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd" colors="true" bootstrap="tests/Functional/.application/config/bootstrap.php" - convertDeprecationsToExceptions="true" > diff --git a/src/UiTranslations/tests/Functional/.application/config/routes/framework.yaml b/src/UiTranslations/tests/Functional/.application/config/routes/framework.yaml index 0fc74bbac..bc1feace1 100644 --- a/src/UiTranslations/tests/Functional/.application/config/routes/framework.yaml +++ b/src/UiTranslations/tests/Functional/.application/config/routes/framework.yaml @@ -1,4 +1,4 @@ when@dev: _errors: - resource: '@FrameworkBundle/Resources/config/routing/errors.xml' + resource: '@FrameworkBundle/Resources/config/routing/errors.php' prefix: /_error diff --git a/symfony.lock b/symfony.lock index 5ecd19b46..1ef480154 100644 --- a/symfony.lock +++ b/symfony.lock @@ -11,6 +11,15 @@ "ref": "64d8583af5ea57b7afa4aba4b159907f3a148b05" } }, + "doctrine/deprecations": { + "version": "1.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.0", + "ref": "87424683adc81d7dc305eefec1fced883084aab9" + } + }, "doctrine/doctrine-bundle": { "version": "2.12", "recipe": { @@ -142,6 +151,18 @@ ".env" ] }, + "symfony/form": { + "version": "7.4", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.2", + "ref": "7d86a6723f4a623f59e2bf966b6aad2fc461d36b" + }, + "files": [ + "config/packages/csrf.yaml" + ] + }, "symfony/framework-bundle": { "version": "5.4", "recipe": { @@ -170,6 +191,18 @@ "ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f" } }, + "symfony/property-info": { + "version": "7.4", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.3", + "ref": "dae70df71978ae9226ae915ffd5fad817f5ca1f7" + }, + "files": [ + "config/packages/property_info.yaml" + ] + }, "symfony/routing": { "version": "5.4", "recipe": { diff --git a/tests/Functional/BookTest.php b/tests/Functional/BookTest.php index 576365dc8..2edf8a13c 100644 --- a/tests/Functional/BookTest.php +++ b/tests/Functional/BookTest.php @@ -30,7 +30,7 @@ protected function setUp(): void ->create() ; - $this->client->loginUser($user->_real()); + $this->client->loginUser($user); } public function testShowingBook(): void diff --git a/tests/Functional/ConferenceTest.php b/tests/Functional/ConferenceTest.php index 1b02aaa04..d00bf1f69 100644 --- a/tests/Functional/ConferenceTest.php +++ b/tests/Functional/ConferenceTest.php @@ -28,7 +28,7 @@ protected function setUp(): void ->create() ; - $this->client->loginUser($user->_real()); + $this->client->loginUser($user); } public function testBrowsingConferences(): void diff --git a/tests/Functional/DashboardTest.php b/tests/Functional/DashboardTest.php index 374d21f92..a59873beb 100644 --- a/tests/Functional/DashboardTest.php +++ b/tests/Functional/DashboardTest.php @@ -26,7 +26,7 @@ protected function setUp(): void ->create() ; - $this->client->loginUser($user->_real()); + $this->client->loginUser($user); } public function testDashboard(): void diff --git a/tests/Functional/LegacyBookTest.php b/tests/Functional/LegacyBookTest.php index 21bb10ef4..148194c82 100644 --- a/tests/Functional/LegacyBookTest.php +++ b/tests/Functional/LegacyBookTest.php @@ -27,7 +27,7 @@ protected function setUp(): void ->create() ; - $this->client->loginUser($user->_real()); + $this->client->loginUser($user); } public function testBrowsingBooks(): void @@ -44,7 +44,7 @@ public function testBrowsingBooks(): void ->create() ; - $this->client->request('GET', '/admin/legacy/books/'); + $this->client->request('GET', '/admin/legacy/books'); self::assertResponseIsSuccessful(); } diff --git a/tests/Functional/SpeakerTest.php b/tests/Functional/SpeakerTest.php index a3dbe15e8..2f515124f 100644 --- a/tests/Functional/SpeakerTest.php +++ b/tests/Functional/SpeakerTest.php @@ -28,7 +28,7 @@ protected function setUp(): void ->create() ; - $this->client->loginUser($user->_real()); + $this->client->loginUser($user); } public function testBrowsingSpeakers(): void diff --git a/tests/Functional/TalkTest.php b/tests/Functional/TalkTest.php index e9d4403e2..9e764e8db 100644 --- a/tests/Functional/TalkTest.php +++ b/tests/Functional/TalkTest.php @@ -31,7 +31,7 @@ protected function setUp(): void ->create() ; - $this->client->loginUser($user->_real()); + $this->client->loginUser($user); } public function testBrowsingTalks(): void diff --git a/tests/Translations/FrenchTranslatedUiTest.php b/tests/Translations/FrenchTranslatedUiTest.php index 8a84810cd..7030a4d7b 100644 --- a/tests/Translations/FrenchTranslatedUiTest.php +++ b/tests/Translations/FrenchTranslatedUiTest.php @@ -32,7 +32,7 @@ protected function setUp(): void ->create() ; - $this->client->loginUser($user->_real()); + $this->client->loginUser($user); } public function testShowItem(): void From a88c06d57bdc7e5369bf109c3502f1025a19ae62 Mon Sep 17 00:00:00 2001 From: stlgaits Date: Tue, 20 Jan 2026 10:47:25 +0100 Subject: [PATCH 52/53] docs(install): update install instructions with Symfony 8 projects --- docs/getting-started.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 46ffb1239..fe1a7f3fc 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -14,7 +14,7 @@ You can set up the Sylius Stack on existing Symfony projects, but in the case yo composer create-project symfony/skeleton my_project # Or with Symfony CLI: -symfony new --docker --php 8.4 my_project +symfony new --docker --php 8.5 my_project ```` ### Install the package using Composer and Symfony Flex @@ -31,6 +31,21 @@ composer require -W \ sylius/ui-translations ``` +> The Sylius Stack now supports Symfony 8 ! As some packages remain in alpha, if you are starting a Symfony 8 project, +> run the following command instead to ensure all dependencies are resolved correctly: +> +> ```bash +> composer require -W \ +> doctrine/orm \ +> doctrine/doctrine-bundle \ +> pagerfanta/doctrine-orm-adapter \ +> symfony/asset-mapper \ +> sylius/bootstrap-admin-ui \ +> sylius/ui-translations \ +> sylius/resource-bundle "^1.14@alpha" \ +> sylius/grid-bundle "^1.15@alpha" +> ``` +
Flex recipes
From d12803c3601b611f75d99c09d162fabe644b565a Mon Sep 17 00:00:00 2001 From: stlgaits Date: Fri, 23 Jan 2026 14:21:20 +0100 Subject: [PATCH 53/53] docs(resource): add missing use statements in processors section --- docs/resource/processors.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/resource/processors.md b/docs/resource/processors.md index 0c84a82d4..7b77997cb 100644 --- a/docs/resource/processors.md +++ b/docs/resource/processors.md @@ -5,8 +5,8 @@ Processors process data: send an email, persist to storage, add to queue etc. * [Default processors](#default-processors) * [Custom processors](#custom-processors) - * [Example #1: Sending an email after persisting data](#example-1-sending-an-email-after-persisting-data) - * [Example #2: Use a custom delete processor](#example-2-use-a-custom-delete-processor) + * [Example #1: Sending an email after persisting data](#example-1-sending-an-email-after-persisting-data) + * [Example #2: Use a custom delete processor](#example-2-use-a-custom-delete-processor) * [Disable processing data](#disable-processing-data) @@ -33,10 +33,17 @@ As an example, send an email after customer registration {% code title="src/Sylius/State/Processor/CreateCustomerProcessor.php" lineNumbers="true" %} ```php +