From ed0b847bdca08b33284882ee74b6e0a38eadbad2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Nozi=C3=A8re?= Date: Sat, 8 Aug 2026 11:43:02 +0200 Subject: [PATCH 1/5] docs: install Thelia 3 from tagged releases instead of a development branch --- .../thelia/skills/fresh-install-test/SKILL.md | 21 +++++++------ plugins/thelia/skills/thelia2/SKILL.md | 4 +-- .../skills/thelia3-module-migration/SKILL.md | 6 ++-- .../thelia/skills/thelia3-tooling/SKILL.md | 2 +- plugins/thelia/skills/thelia3/SKILL.md | 30 +++++++++++++++++-- .../skills/thelia3/references/glossary.md | 2 +- .../skills/thelia3/references/modules.md | 2 +- 7 files changed, 48 insertions(+), 19 deletions(-) diff --git a/plugins/thelia/skills/fresh-install-test/SKILL.md b/plugins/thelia/skills/fresh-install-test/SKILL.md index 0ebe7a9..4bd1a25 100644 --- a/plugins/thelia/skills/fresh-install-test/SKILL.md +++ b/plugins/thelia/skills/fresh-install-test/SKILL.md @@ -4,8 +4,8 @@ description: > Validates a fresh Thelia 3 install from an empty directory and empty database. Use after a version bump, a merge, or any change to bin/install, bin/test-prepare, bootstrap.php, or DatabaseSetup. Covers two scenarios: the thelia/thelia dev repo - (with core/ as a path repository) and the thelia-project skeleton - (simulating a new developer install via composer create-project). + (with core/ as a path repository) and the thelia-project skeleton installed the way + a new developer installs it, with composer create-project from the tagged releases. --- # Skill: Fresh Install Test @@ -22,9 +22,11 @@ Reusable validation protocol for a clean Thelia 3 installation. Run it after any ## Prerequisites - DDEV installed and running -- SSH access to the GitHub repos under `thelia/*` +- SSH access to the GitHub repos under `thelia/*` for the dev-repo scenario - The target workspace directory must be empty (the protocol deletes and recreates it) +Thelia 3 ships as tagged releases; there is no development branch to install from. Test 1 clones the development repository, whose default branch is `main`. Test 2 installs the published packages. While `3.0.0-beta1` is the newest tag, the skeleton needs `--stability=beta` (or an explicit `thelia/thelia-project:^3.0.0-beta1`), and a project's own `composer.json` needs `"minimum-stability": "beta"` with `"prefer-stable": true`. + --- ## Test 1: thelia/thelia (dev repo with core/ as a path repository) @@ -35,7 +37,7 @@ Reusable validation protocol for a clean Thelia 3 installation. Run it after any WORKSPACE= PROJECT=thelia-3 -BRANCH=twig # replace with the branch you want to test +BRANCH=main # replace with the branch or tag you want to test # 1. Full cleanup ddev stop --unlist $PROJECT 2>/dev/null @@ -116,20 +118,20 @@ ddev exec php bin/console debug:container --deprecations | head -3 ## Test 2: thelia/thelia-project (simulates a new developer install) ```bash -# Set WORKSPACE to the directory that will contain the cloned project. +# Set WORKSPACE to the directory that will contain the project. WORKSPACE= PROJECT=thelia-project-test -BRANCH=twig # 1. Full cleanup ddev stop --unlist $PROJECT 2>/dev/null ddev delete -Oy $PROJECT 2>/dev/null rm -rf "$WORKSPACE/$PROJECT" -# 2. Clone +# 2. Create the project from the tagged release cd "$WORKSPACE" -git clone -b $BRANCH git@github.com:thelia/thelia-project.git $PROJECT +composer create-project --stability=beta thelia/thelia-project $PROJECT +# Equivalent, pinned: composer create-project thelia/thelia-project:^3.0.0-beta1 $PROJECT cd $PROJECT # 3. Configure DDEV (MariaDB version can vary by host) @@ -137,7 +139,7 @@ ddev config --project-name=$PROJECT --project-type=symfony --docroot=public \ --php-version=8.3 --webserver-type=nginx-fpm --database=mariadb:11.8 ddev start -# 4. Install PHP dependencies +# 4. Install PHP dependencies inside the container ddev exec composer install # 5. Install Thelia with demo data and admin account @@ -168,6 +170,7 @@ ddev exec php -r 'require "vendor/autoload.php"; echo Symfony\Component\HttpKern - `bootstrap.php` must NOT load `vendor/autoload.php` (doing so disables the Symfony Runtime via its `require_once` guard). - `public/index.php` must load `bootstrap.php` first, then `vendor/autoload_runtime.php`. - `bin/console` passes through `vendor/thelia/core/Thelia`, not the standard Symfony pattern. +- Constraints in the generated `composer.json`: `^3.0.0-beta1` for `thelia/core` and the skeleton, `^1.0.0-beta1` for the templates, the module's current major for `thelia/*-module`, plus `"minimum-stability": "beta"` and `"prefer-stable": true`. --- diff --git a/plugins/thelia/skills/thelia2/SKILL.md b/plugins/thelia/skills/thelia2/SKILL.md index 73694cf..0ff465e 100644 --- a/plugins/thelia/skills/thelia2/SKILL.md +++ b/plugins/thelia/skills/thelia2/SKILL.md @@ -1,11 +1,11 @@ --- name: thelia2 -description: "Thelia 2.6 e-commerce framework (branch main, Symfony 6.4, API Platform 3.4, PHP 8.2+, Propel ORM, Smarty front + back + email + pdf). Covers module creation: BaseModule lifecycle 8 methods (install/update/preActivation/postActivation/registerHooks/preDeactivation/postDeactivation/destroy), config.xml/module.xml/schema.xml (XSD module-2_2.xsd, thelia-1.0.xsd), Propel-AP Bridge (PropelResourceInterface, PropelResourceTrait, ResourceAddonInterface, ResourceAddonTrait, AbstractTranslatableResource, I18nCollection, Relation/Column/CompositeIdentifiers attributes), 7 custom Propel filters (SearchFilter/OrderFilter/BooleanFilter/RangeFilter/DateFilter/NotInFilter/AbstractFilter), JWT Lexik 2.x without refresh, Smarty back+front hooks (BaseHook + getSubscribedHooks(), HookRenderEvent .add() vs HookRenderBlockEvent fragments), loops (BaseLoop + PropelSearchLoopInterface vs ArraySearchLoopInterface mutex, BaseI18nLoop, SearchLoopInterface, ArgumentCollection 10 factories), forms (BaseForm + init() non-constructor + getName() auto-FQCN snake_case + ParserContext, success_url/error_url hidden fields), 175 events TheliaEvents (ORDER_BEFORE_PAYMENT, AFTER_CARTADDITEM, FORM_BEFORE_BUILD/AFTER_BUILD, MODULE_PAY, MODULE_DELIVERY_GET_POSTAGE), 20 Smarty plugins / ~85 tags ({loop}, {ifloop}, {elseloop}, {pageloop}, {hook}, {hookblock}, {form}, {form_field}, {form_hidden_fields}, {intl}, {url}, {token_url}, {theme}, {theme_url}, {flash}, {check_auth}, {format_money}, {format_date}, {encore_entry_script_tags}), payment/delivery modules (AbstractPaymentModule pay()/isValidPayment(), AbstractDeliveryModule getPostage()), TheliaSmarty local module + SmartyParser, RegisterHookListenersPass, RegisterLoopPass. Use when working on Thelia 2.6 projects, branch main repo thelia/thelia, creating modules in local/modules, building Smarty front+back+email+pdf templates, exposing API resources, writing hooks/loops/forms, integrating payment/delivery modules. Triggers on: thelia 2, thelia 2.6, branch main, BaseModule, config.xml, module.xml, schema.xml, postActivation, registerHooks, TheliaEvents, BaseHook, getSubscribedHooks, HookRenderEvent, HookRenderBlockEvent, BaseLoop, PropelSearchLoopInterface, ArraySearchLoopInterface, BaseI18nLoop, BaseForm, ParserContext, SmartyParser, TheliaSmarty, Smarty, {loop}, {hook}, {hookblock}, {form}, {intl}, {url}, {pageloop}, {ifloop}, {elseloop}, {form_field}, {form_hidden_fields}, {check_auth}, {format_money}, PropelResourceInterface, PropelResourceTrait, ResourceAddonInterface, ResourceAddonTrait, AbstractTranslatableResource, I18nCollection, ApiFilter SearchFilter OrderFilter BooleanFilter RangeFilter DateFilter NotInFilter, Relation Column CompositeIdentifiers, local/modules, AbstractPaymentModule, AbstractDeliveryModule, ORDER_BEFORE_PAYMENT, AFTER_CARTADDITEM, CART_ADDITEM, Api/Resource/, normalizationContext per operation, GROUP_ADMIN_READ_SINGLE, BankCoordinatesForm, IBAN normalization, setPostage save, module.configuration save Controller, routing.xml admin module, token_url CSRF, final readonly tests Reflection, BaseAdminController checkAuth, validateForm, generateSuccessRedirect, generateErrorRedirect. Do NOT trigger for Thelia 3 projects (look for Twig .html.twig templates, FlexyBundle, LiveComponent, TwigComponent, AP 4.3 standalone, branch twig, IntegrationTestCase, FixtureFactory, resources(), attr(), CartFacade)." +description: "Thelia 2.6 e-commerce framework (maintenance branch 2.6, Symfony 6.4, API Platform 3.4, PHP 8.2+, Propel ORM, Smarty front + back + email + pdf). Covers module creation: BaseModule lifecycle 8 methods (install/update/preActivation/postActivation/registerHooks/preDeactivation/postDeactivation/destroy), config.xml/module.xml/schema.xml (XSD module-2_2.xsd, thelia-1.0.xsd), Propel-AP Bridge (PropelResourceInterface, PropelResourceTrait, ResourceAddonInterface, ResourceAddonTrait, AbstractTranslatableResource, I18nCollection, Relation/Column/CompositeIdentifiers attributes), 7 custom Propel filters (SearchFilter/OrderFilter/BooleanFilter/RangeFilter/DateFilter/NotInFilter/AbstractFilter), JWT Lexik 2.x without refresh, Smarty back+front hooks (BaseHook + getSubscribedHooks(), HookRenderEvent .add() vs HookRenderBlockEvent fragments), loops (BaseLoop + PropelSearchLoopInterface vs ArraySearchLoopInterface mutex, BaseI18nLoop, SearchLoopInterface, ArgumentCollection 10 factories), forms (BaseForm + init() non-constructor + getName() auto-FQCN snake_case + ParserContext, success_url/error_url hidden fields), 175 events TheliaEvents (ORDER_BEFORE_PAYMENT, AFTER_CARTADDITEM, FORM_BEFORE_BUILD/AFTER_BUILD, MODULE_PAY, MODULE_DELIVERY_GET_POSTAGE), 20 Smarty plugins / ~85 tags ({loop}, {ifloop}, {elseloop}, {pageloop}, {hook}, {hookblock}, {form}, {form_field}, {form_hidden_fields}, {intl}, {url}, {token_url}, {theme}, {theme_url}, {flash}, {check_auth}, {format_money}, {format_date}, {encore_entry_script_tags}), payment/delivery modules (AbstractPaymentModule pay()/isValidPayment(), AbstractDeliveryModule getPostage()), TheliaSmarty local module + SmartyParser, RegisterHookListenersPass, RegisterLoopPass. Use when working on Thelia 2.6 projects, maintenance branch 2.6 of thelia/thelia, creating modules in local/modules, building Smarty front+back+email+pdf templates, exposing API resources, writing hooks/loops/forms, integrating payment/delivery modules. Triggers on: thelia 2, thelia 2.6, branch 2.6, thelia2 module branch, BaseModule, config.xml, module.xml, schema.xml, postActivation, registerHooks, TheliaEvents, BaseHook, getSubscribedHooks, HookRenderEvent, HookRenderBlockEvent, BaseLoop, PropelSearchLoopInterface, ArraySearchLoopInterface, BaseI18nLoop, BaseForm, ParserContext, SmartyParser, TheliaSmarty, Smarty, {loop}, {hook}, {hookblock}, {form}, {intl}, {url}, {pageloop}, {ifloop}, {elseloop}, {form_field}, {form_hidden_fields}, {check_auth}, {format_money}, PropelResourceInterface, PropelResourceTrait, ResourceAddonInterface, ResourceAddonTrait, AbstractTranslatableResource, I18nCollection, ApiFilter SearchFilter OrderFilter BooleanFilter RangeFilter DateFilter NotInFilter, Relation Column CompositeIdentifiers, local/modules, AbstractPaymentModule, AbstractDeliveryModule, ORDER_BEFORE_PAYMENT, AFTER_CARTADDITEM, CART_ADDITEM, Api/Resource/, normalizationContext per operation, GROUP_ADMIN_READ_SINGLE, BankCoordinatesForm, IBAN normalization, setPostage save, module.configuration save Controller, routing.xml admin module, token_url CSRF, final readonly tests Reflection, BaseAdminController checkAuth, validateForm, generateSuccessRedirect, generateErrorRedirect. Do NOT trigger for Thelia 3 projects (look for Twig .html.twig templates, FlexyBundle, LiveComponent, TwigComponent, AP 4.3 standalone, IntegrationTestCase, FixtureFactory, resources(), attr(), CartFacade)." --- # Thelia 2.6: Module Development Guide -> Stack: Symfony 6.4, API Platform 3.4 (bundle), PHP 8.2+, Propel ORM, branch `main`. Front + back + email + pdf in Smarty exclusively (no Twig). API JSON-LD only. JWT Lexik 2.x without refresh. No Doctrine, no Messenger, no Turbo/Mercure, no LiveComponent. +> Stack: Symfony 6.4, API Platform 3.4 (bundle), PHP 8.2+, Propel ORM. Maintenance happens on the `2.6` branch of `thelia/thelia`; Thelia 2 module branches are named `thelia2`. Front + back + email + pdf in Smarty exclusively (no Twig). API JSON-LD only. JWT Lexik 2.x without refresh. No Doctrine, no Messenger, no Turbo/Mercure, no LiveComponent. ## 1. Decision router "I want X" diff --git a/plugins/thelia/skills/thelia3-module-migration/SKILL.md b/plugins/thelia/skills/thelia3-module-migration/SKILL.md index 59a4112..a7b1ac9 100644 --- a/plugins/thelia/skills/thelia3-module-migration/SKILL.md +++ b/plugins/thelia/skills/thelia3-module-migration/SKILL.md @@ -1,11 +1,11 @@ --- name: thelia3-module-migration -description: "Migrating a Thelia 2 module to Thelia 3 (twig branch, Symfony 7.4 LTS, API Platform 4.3, PHP 8.3): namespace and directory structure changes, config.xml cleanup, auto-discovery of hooks/loops/forms (no config.xml declarations needed in T3), #[Route] replacing @Route and routing.xml, #[AutowireIterator]/#[AutowireLocator] replacing deprecated #[TaggedIterator]/#[TaggedLocator], native return types on Symfony interface overrides, Symfony 6.4-to-7.4 breaking changes, API Platform 3.x-to-4.3 breaking changes (namespace removals, openapiContext removal, standalone setup), Propel native typing strictness (tinyint as int not bool, decimal as string not float), migrating from thelia/open-api-module to native API Platform resources, back-office Smarty hook templates, front-office Smarty .tpl to Twig .html.twig with resources() replacing {loop}, LiveComponents replacing manual JS. Triggers on: migrate module, port module T2 T3, config.xml to configureServices, routing.xml to Route attribute, TaggedIterator AutowireIterator, open-api-module drop, BaseApiModel PropelResourceInterface, ApiPlatform\\Api removed, openapiContext deprecated, Thelia\\Install\\Database legacy namespace, getAnnotationRoutePrefix deprecated, BaseLoop deprecated." +description: "Migrating a Thelia 2 module to Thelia 3 (Symfony 7.4 LTS, API Platform 4.3, PHP 8.3 or later): namespace and directory structure changes, config.xml cleanup, auto-discovery of hooks/loops/forms (no config.xml declarations needed in T3), #[Route] replacing @Route and routing.xml, #[AutowireIterator]/#[AutowireLocator] replacing deprecated #[TaggedIterator]/#[TaggedLocator], native return types on Symfony interface overrides, Symfony 6.4-to-7.4 breaking changes, API Platform 3.x-to-4.3 breaking changes (namespace removals, openapiContext removal, standalone setup), Propel native typing strictness (tinyint as int not bool, decimal as string not float), migrating from thelia/open-api-module to native API Platform resources, back-office Smarty hook templates, front-office Smarty .tpl to Twig .html.twig with resources() replacing {loop}, LiveComponents replacing manual JS. Triggers on: migrate module, port module T2 T3, config.xml to configureServices, routing.xml to Route attribute, TaggedIterator AutowireIterator, open-api-module drop, BaseApiModel PropelResourceInterface, ApiPlatform\\Api removed, openapiContext deprecated, Thelia\\Install\\Database legacy namespace, getAnnotationRoutePrefix deprecated, BaseLoop deprecated." --- # Thelia 2 to Thelia 3: Module Migration Guide -> Covers porting a T2 module to T3 (twig branch, Symfony 7.4 LTS, API Platform 4.3, PHP 8.3) and modernizing an older T3 module that predates auto-registration. +> Covers porting a T2 module to T3 (Symfony 7.4 LTS, API Platform 4.3, PHP 8.3 or later) and modernizing an older T3 module that predates auto-registration. ## 1. Breaking changes at a glance @@ -462,7 +462,7 @@ composer why thelia/open-api-module If `composer why` still lists consuming modules, migrate those first. -**Payment event migration.** Before Thelia 3 twig commit `2e0e5da9d`, `PaymentModuleService` dispatched two events that lived inside the OpenApi module. Those events were replaced with native Thelia equivalents: +**Payment event migration.** In early Thelia 3 development, `PaymentModuleService` dispatched two events that lived inside the OpenApi module. Those events were replaced with native Thelia equivalents: - `Thelia\Api\Resource\PaymentModuleOption{,Group,Choice}` (mirroring `DeliveryModuleOption`). - `Thelia\Api\Bridge\Propel\Event\PaymentModuleOptionEvent` with the same constructor contract. - `PaymentModuleService` now uses `TheliaEvents::MODULE_PAYMENT_GET_OPTIONS` (same string `thelia.module.payment.options`, so no BC on the event name contract). diff --git a/plugins/thelia/skills/thelia3-tooling/SKILL.md b/plugins/thelia/skills/thelia3-tooling/SKILL.md index 4604ec6..9e74362 100644 --- a/plugins/thelia/skills/thelia3-tooling/SKILL.md +++ b/plugins/thelia/skills/thelia3-tooling/SKILL.md @@ -1,6 +1,6 @@ --- name: thelia3-tooling -description: Operational gotchas when developing and testing Thelia 3 (the twig branch): the Thelia console versus bin/console, a stale PHPStan result cache on Propel classes, PHPUnit 11 failing on deprecated XML, JWT keys for the API test suite, and rebuilding a theme's compiled assets after composer update. Use when a Thelia command, the test suite, PHPStan, JWT auth, or a theme's assets behave in a way the code does not explain. +description: "Operational gotchas when developing and testing Thelia 3: the Thelia console versus bin/console, a stale PHPStan result cache on Propel classes, PHPUnit 11 failing on deprecated XML, JWT keys for the API test suite, and rebuilding a theme's compiled assets after composer update. Use when a Thelia command, the test suite, PHPStan, JWT auth, or a theme's assets behave in a way the code does not explain." --- # Thelia 3 tooling diff --git a/plugins/thelia/skills/thelia3/SKILL.md b/plugins/thelia/skills/thelia3/SKILL.md index 73865fd..90b1c4a 100644 --- a/plugins/thelia/skills/thelia3/SKILL.md +++ b/plugins/thelia/skills/thelia3/SKILL.md @@ -1,11 +1,37 @@ --- name: thelia3 -description: "Thelia 3 e-commerce framework (twig branch, Symfony 7.4 LTS, API Platform 4.3, PHP 8.3, Propel ORM, Flexy/Twig front, Twig back-office via the default-twig theme). Covers modern module development: configureServices() autoconfigure, API resources (PropelResourceInterface, ResourceAddonInterface), Flexy front (LiveComponents, TwigComponents, resources() / attr() Twig, facades CartFacade/CustomerFacade/OrderFacade/CheckoutFacade), auto-discovered hooks and loops (NO config.xml required for these declarations), front theme hooks (theme_hook() Twig function + ThemeHookInterface), Thelia events, typed modules (AbstractPaymentModule/AbstractDeliveryModule), tests (IntegrationTestCase, ApiTestCase, FixtureFactory). Use when working on Thelia 3 projects, creating modules in local/modules or vendor, building front-office Twig/Flexy with LiveComponents, exposing API resources, extending native resources, migrating from Thelia 2. Triggers on: thelia 3, twig branch, TheliaKernel, BaseModule, configureServices, BaseHook, theme_hook, ThemeHookInterface, theme hooks, BaseLoop, BaseForm, PropelResourceInterface, ResourceAddonInterface, AbstractTranslatableResource, DataAccessService, resources(), attr(), AsLiveComponent, AsTwigComponent, CartFacade, CustomerFacade, OrderFacade, CheckoutFacade, Flexy, FlexyBundle, AbstractPaymentModule, AbstractDeliveryModule, FixtureFactory, IntegrationTestCase, ApiTestCase, local/modules, vendor/thelia/modules, ApiFilter SearchFilter OrderFilter BooleanFilter RangeFilter NotInFilter DateFilter. Do NOT trigger for Thelia 2 projects (Smarty .html front templates, {loop}, {hook} Smarty syntax)." +description: "Thelia 3 e-commerce framework (Symfony 7.4 LTS, API Platform 4.3, PHP 8.3 or later, Propel ORM, Flexy/Twig front, Twig back-office via the default-twig theme). Covers modern module development: configureServices() autoconfigure, API resources (PropelResourceInterface, ResourceAddonInterface), Flexy front (LiveComponents, TwigComponents, resources() / attr() Twig, facades CartFacade/CustomerFacade/OrderFacade/CheckoutFacade), auto-discovered hooks and loops (NO config.xml required for these declarations), front theme hooks (theme_hook() Twig function + ThemeHookInterface), Thelia events, typed modules (AbstractPaymentModule/AbstractDeliveryModule), tests (IntegrationTestCase, ApiTestCase, FixtureFactory). Use when working on Thelia 3 projects, creating modules in local/modules or vendor, building front-office Twig/Flexy with LiveComponents, exposing API resources, extending native resources, migrating from Thelia 2. Triggers on: thelia 3, TheliaKernel, BaseModule, configureServices, BaseHook, theme_hook, ThemeHookInterface, theme hooks, BaseLoop, BaseForm, PropelResourceInterface, ResourceAddonInterface, AbstractTranslatableResource, DataAccessService, resources(), attr(), AsLiveComponent, AsTwigComponent, CartFacade, CustomerFacade, OrderFacade, CheckoutFacade, Flexy, FlexyBundle, AbstractPaymentModule, AbstractDeliveryModule, FixtureFactory, IntegrationTestCase, ApiTestCase, local/modules, vendor/thelia/modules, ApiFilter SearchFilter OrderFilter BooleanFilter RangeFilter NotInFilter DateFilter. Do NOT trigger for Thelia 2 projects (Smarty .html front templates, {loop}, {hook} Smarty syntax)." --- # Thelia 3 - Module Development Guide -> Stack: Symfony 7.4 LTS, API Platform 4.3, PHP 8.3, Propel ORM, `twig` branch. Front in Twig (Flexy, Webpack Encore, Tailwind), back-office in Twig via the default-twig theme. No Doctrine, no Messenger, no Turbo/Mercure. +> Stack: Symfony 7.4 LTS, API Platform 4.3, PHP 8.3 or later, Propel ORM. Front in Twig (Flexy, Webpack Encore, Tailwind), back-office in Twig via the default-twig theme. Email and PDF templates are Twig too. No Doctrine, no Messenger, no Turbo/Mercure. + +## 0. Install and version constraints + +Thelia 3 is installed from tagged releases, not from a development branch: + +```bash +composer create-project thelia/thelia-project my-shop +``` + +While `3.0.0-beta1` is the only tag, add `--stability=beta`, or target the version explicitly: + +```bash +composer create-project thelia/thelia-project:^3.0.0-beta1 my-shop +``` + +Constraints for an existing project: + +| Package | Constraint | +|---|---| +| `thelia/core`, `thelia/thelia-project` | `^3.0.0-beta1` | +| Templates (`thelia/flexy`, back-office, email, PDF) | `^1.0.0-beta1` | +| `thelia/*-module` | the module's current major | + +The project `composer.json` also needs `"minimum-stability": "beta"` and `"prefer-stable": true` until a stable release is tagged. + +`THELIA_VERSION` is `3.0.0-beta1`. ## 1. Decision router - "I want to..." diff --git a/plugins/thelia/skills/thelia3/references/glossary.md b/plugins/thelia/skills/thelia3/references/glossary.md index 650f383..c29fc54 100644 --- a/plugins/thelia/skills/thelia3/references/glossary.md +++ b/plugins/thelia/skills/thelia3/references/glossary.md @@ -1,6 +1,6 @@ # Thelia 3 Vocabulary - Complete Glossary -> Exhaustive reference of classes, interfaces, services, attributes, Twig functions, and constants used in Thelia 3 module development (`twig` branch, SF 7.4 LTS, AP 4.3, PHP 8.3). +> Exhaustive reference of classes, interfaces, services, attributes, Twig functions, and constants used in Thelia 3 module development (SF 7.4 LTS, AP 4.3, PHP 8.3 or later). ## Module lifecycle diff --git a/plugins/thelia/skills/thelia3/references/modules.md b/plugins/thelia/skills/thelia3/references/modules.md index 57a97a4..e14c50a 100644 --- a/plugins/thelia/skills/thelia3/references/modules.md +++ b/plugins/thelia/skills/thelia3/references/modules.md @@ -1,6 +1,6 @@ # Thelia 3 Modules - Skeleton, lifecycle, persistence -> Stack: SF 7.4 LTS, AP 4.3, PHP 8.3, Propel ORM, `twig` branch. Modules are loaded from the DB (`ModuleQuery::getActivated()`). +> Stack: SF 7.4 LTS, AP 4.3, PHP 8.3 or later, Propel ORM. Modules are loaded from the DB (`ModuleQuery::getActivated()`). ## 1. Module discovery From 707267d1526df7fad76208b6b408a4c3c5cad28d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Nozi=C3=A8re?= Date: Sat, 8 Aug 2026 11:43:41 +0200 Subject: [PATCH 2/5] docs: align version, stability and routing facts on 3.0.0-beta1 --- plugins/thelia/skills/thelia3-module-migration/SKILL.md | 8 ++++---- plugins/thelia/skills/thelia3/SKILL.md | 2 +- plugins/thelia/skills/thelia3/references/glossary.md | 2 +- plugins/thelia/skills/thelia3/references/modules.md | 6 ++++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/plugins/thelia/skills/thelia3-module-migration/SKILL.md b/plugins/thelia/skills/thelia3-module-migration/SKILL.md index a7b1ac9..c28519a 100644 --- a/plugins/thelia/skills/thelia3-module-migration/SKILL.md +++ b/plugins/thelia/skills/thelia3-module-migration/SKILL.md @@ -9,7 +9,7 @@ description: "Migrating a Thelia 2 module to Thelia 3 (Symfony 7.4 LTS, API Plat ## 1. Breaking changes at a glance -| Aspect | Thelia 2 | Thelia 3 (twig) | Impact | +| Aspect | Thelia 2 | Thelia 3 | Impact | |---|---|---|---| | PHP | 8.0 - 8.2 | **8.3+** | strict_types, modern types | | Symfony | 6.0 - 6.3 | **7.4 LTS** | PHP 8 attributes, MapRequestPayload, Voters | @@ -20,7 +20,7 @@ description: "Migrating a Thelia 2 module to Thelia 3 (Symfony 7.4 LTS, API Plat | Front HTML injection | `{hook}` Smarty | **Theme hooks** (`ThemeHookInterface` + `theme_hook()`) | Pure code, the theme declares the points | | Back-office | Smarty + XML hooks | **Smarty + auto-tag hooks** | XML hook declarations become optional | | DI | `` in config.xml | `configureServices()` PHP | Modern, autoconfigure | -| Routes | routing.xml | `#[Route]` PHP 8 | Auto-scanned from `Controller/` | +| Routes | routing.xml | `#[Route]` PHP 8 (routing.xml deprecated) | Auto-scanned from `Controller/` | | Business logic | Event Actions | **Facades** + Services | `CartFacade`, `CustomerFacade`, etc. | | Database namespace | `Thelia\Install\Database` | `Thelia\Core\Install\Database` | Update use statement | | `configureServices` exclude | `THELIA_MODULE_DIR` constant | Relative path (`__DIR__.'/I18n/*'`) | Simpler | @@ -34,7 +34,7 @@ description: "Migrating a Thelia 2 module to Thelia 3 (Symfony 7.4 LTS, API Plat 2. **`module.xml`**: update to XSD `module-2_2.xsd`, namespace `http://thelia.net/schema/dic/module`. 3. **`MyModule.php`**: add `configureServices()` static method. Without it, zero classes are scanned. 4. **`config.xml`**: strip ``, ``, ``, ``, `` (see section 3). They are now auto-discovered. -5. **`routing.xml`**: delete, replace with `#[Route]` PHP 8 attributes on controllers. +5. **`routing.xml`**: deprecated in T3. Delete it and declare every route with `#[Route]` PHP 8 attributes on controllers. 6. **`schema.xml`**: adapt table namespaces and `external-schema` declarations for core FKs. 7. **Hooks**: convert `` XML to `extends BaseHook` + `static getSubscribedHooks()`. Back-office templates stay Smarty. 8. **Loops**: `BaseLoop` is `@deprecated`. Keep as-is for back-office Smarty. For front-office, migrate to API Resources. @@ -481,7 +481,7 @@ Payment modules that type-hint the old OpenApi event class (PayPal, Payzen, Cawl | Stale Twig cache after template override | `cache:clear` | | `module_template_dirs.php` stale | `cache:clear` after activation | | `var/propel/test/` cache pointing at wrong database | `bin/test-prepare` auto-purges it | -| `THELIA_VERSION = '2.6.0'` | Constant not bumped for T3, do not rely on it | +| `THELIA_VERSION` still read as `'2.6.0'` | The T3 constant is `'3.0.0-beta1'`; a stale value means the old core is still autoloaded | | `getPropelRelatedTableMap()` returns null on concrete resource | Always return `new XxxTableMap()` | | LiveProp with Propel object | Use DTOs or scalar values only | | `resources()` called from CLI | Unusable, throws `RuntimeException` (no main request). Add a guard or avoid. | diff --git a/plugins/thelia/skills/thelia3/SKILL.md b/plugins/thelia/skills/thelia3/SKILL.md index 90b1c4a..62417f8 100644 --- a/plugins/thelia/skills/thelia3/SKILL.md +++ b/plugins/thelia/skills/thelia3/SKILL.md @@ -31,7 +31,7 @@ Constraints for an existing project: The project `composer.json` also needs `"minimum-stability": "beta"` and `"prefer-stable": true` until a stable release is tagged. -`THELIA_VERSION` is `3.0.0-beta1`. +`THELIA_VERSION` is `3.0.0-beta1`. The announced PHP matrix is 8.3; 8.4 support is being validated in CI. ## 1. Decision router - "I want to..." diff --git a/plugins/thelia/skills/thelia3/references/glossary.md b/plugins/thelia/skills/thelia3/references/glossary.md index c29fc54..51e3dca 100644 --- a/plugins/thelia/skills/thelia3/references/glossary.md +++ b/plugins/thelia/skills/thelia3/references/glossary.md @@ -216,7 +216,7 @@ | `THELIA_LIB` | `core/lib/` | | `THELIA_WEB_DIR` | Public directory (`web/` or `public/`) | | `THELIA_TEMPLATE_DIR` | `templates/` | -| `THELIA_VERSION` | `'2.6.0'` - not bumped for T3, do not rely on it | +| `THELIA_VERSION` | `'3.0.0-beta1'` | | `Translator::getInstance()` | Singleton - prefer injected `TranslatorInterface` | | `Thelia\Core\Translation\Translator` | Alias of injected `TranslatorInterface`; default domain **`core`**; missing key -> raw string (`strtr`). Distinct from Symfony `translator` service (Twig `\|trans`, domain `messages`) | | `URL::getInstance()` | Singleton - same | diff --git a/plugins/thelia/skills/thelia3/references/modules.md b/plugins/thelia/skills/thelia3/references/modules.md index e14c50a..5969176 100644 --- a/plugins/thelia/skills/thelia3/references/modules.md +++ b/plugins/thelia/skills/thelia3/references/modules.md @@ -61,7 +61,9 @@ Target XSD: `module-2_2.xsd`, namespace `http://thelia.net/schema/dic/module`. T ``` -Required fields: ``, ``, `<version>`, `<type>` (`classic|delivery|payment|marketplace|price|accounting|seo|administration|statistic`), `<stability>` (`alpha|beta|rc|prod|other`). +Required fields: `<fullnamespace>`, `<descriptive locale><title>`, `<version>`, `<type>` (`classic|delivery|payment|marketplace|price|accounting|seo|administration|statistic`), `<stability>` (`alpha|beta|rc|prod|other` - `production` is not a valid value and fails XSD validation). + +`<thelia>` holds the minimum core version a module needs: `3.0.0` for a Thelia 3 module. Optional: `<required><module version="x.y">Code</module>` (dependencies), `<thelia>` (min version), `<mandatory>` (1 = cannot be uninstalled), `<hidden>`. @@ -126,7 +128,7 @@ CLI: `ModuleManagement::installModule()` validates, inserts into DB, dispatches Canonical convention: PHP 8 `#[Route]` on controllers. `ModuleAttributeLoader` (`ModuleAttributeLoader.php:35`) scans `Controller/` of each active module with `AttributeDirectoryLoader` + `AttributeRouteControllerLoader`. -`routing.xml`: EMPTY/absent in all modern modules. +`routing.xml` is deprecated for modules. `#[Route]` attributes are the only documented way to declare module routes. Module prefix: override `static getRoutePrefix(): string` (NOT the deprecated `getAnnotationRoutePrefix()`). From 1f31e50c942f8ca36f6815a467e68ddcde9c41e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Nozi=C3=A8re?= <anoziere@openstudio.fr> Date: Sat, 8 Aug 2026 11:45:03 +0200 Subject: [PATCH 3/5] docs: email, PDF and back-office templates are Twig in Thelia 3 --- .../skills/thelia3-module-migration/SKILL.md | 12 +++++----- .../skills/thelia3/references/front-office.md | 6 ++--- .../skills/thelia3/references/glossary.md | 4 ++-- .../thelia/skills/thelia3/references/hooks.md | 22 +++++++++---------- .../thelia3/references/payment-delivery.md | 2 +- 5 files changed, 24 insertions(+), 22 deletions(-) diff --git a/plugins/thelia/skills/thelia3-module-migration/SKILL.md b/plugins/thelia/skills/thelia3-module-migration/SKILL.md index c28519a..4998f37 100644 --- a/plugins/thelia/skills/thelia3-module-migration/SKILL.md +++ b/plugins/thelia/skills/thelia3-module-migration/SKILL.md @@ -1,6 +1,6 @@ --- name: thelia3-module-migration -description: "Migrating a Thelia 2 module to Thelia 3 (Symfony 7.4 LTS, API Platform 4.3, PHP 8.3 or later): namespace and directory structure changes, config.xml cleanup, auto-discovery of hooks/loops/forms (no config.xml declarations needed in T3), #[Route] replacing @Route and routing.xml, #[AutowireIterator]/#[AutowireLocator] replacing deprecated #[TaggedIterator]/#[TaggedLocator], native return types on Symfony interface overrides, Symfony 6.4-to-7.4 breaking changes, API Platform 3.x-to-4.3 breaking changes (namespace removals, openapiContext removal, standalone setup), Propel native typing strictness (tinyint as int not bool, decimal as string not float), migrating from thelia/open-api-module to native API Platform resources, back-office Smarty hook templates, front-office Smarty .tpl to Twig .html.twig with resources() replacing {loop}, LiveComponents replacing manual JS. Triggers on: migrate module, port module T2 T3, config.xml to configureServices, routing.xml to Route attribute, TaggedIterator AutowireIterator, open-api-module drop, BaseApiModel PropelResourceInterface, ApiPlatform\\Api removed, openapiContext deprecated, Thelia\\Install\\Database legacy namespace, getAnnotationRoutePrefix deprecated, BaseLoop deprecated." +description: "Migrating a Thelia 2 module to Thelia 3 (Symfony 7.4 LTS, API Platform 4.3, PHP 8.3 or later): namespace and directory structure changes, config.xml cleanup, auto-discovery of hooks/loops/forms (no config.xml declarations needed in T3), #[Route] replacing @Route and routing.xml, #[AutowireIterator]/#[AutowireLocator] replacing deprecated #[TaggedIterator]/#[TaggedLocator], native return types on Symfony interface overrides, Symfony 6.4-to-7.4 breaking changes, API Platform 3.x-to-4.3 breaking changes (namespace removals, openapiContext removal, standalone setup), Propel native typing strictness (tinyint as int not bool, decimal as string not float), migrating from thelia/open-api-module to native API Platform resources, back-office Smarty hook templates to Twig, front-office Smarty .tpl to Twig .html.twig with resources() replacing {loop}, LiveComponents replacing manual JS. Triggers on: migrate module, port module T2 T3, config.xml to configureServices, routing.xml to Route attribute, TaggedIterator AutowireIterator, open-api-module drop, BaseApiModel PropelResourceInterface, ApiPlatform\\Api removed, openapiContext deprecated, Thelia\\Install\\Database legacy namespace, getAnnotationRoutePrefix deprecated, BaseLoop deprecated." --- # Thelia 2 to Thelia 3: Module Migration Guide @@ -18,7 +18,8 @@ description: "Migrating a Thelia 2 module to Thelia 3 (Symfony 7.4 LTS, API Plat | Front data | `{loop}` Smarty | `resources('/api/front/...')` | Internal API Platform call | | Front interactivity | Hooks + manual JS | **LiveComponents + Stimulus** | Reactive components | | Front HTML injection | `{hook}` Smarty | **Theme hooks** (`ThemeHookInterface` + `theme_hook()`) | Pure code, the theme declares the points | -| Back-office | Smarty + XML hooks | **Smarty + auto-tag hooks** | XML hook declarations become optional | +| Back-office | Smarty + XML hooks | **Twig (default-twig) + auto-tag hooks** | Template rewrite; XML hook declarations become optional | +| Email / PDF templates | Smarty `.html` | **Twig `.html.twig`** (PDF rendered by dompdf) | Template rewrite | | DI | `<services>` in config.xml | `configureServices()` PHP | Modern, autoconfigure | | Routes | routing.xml | `#[Route]` PHP 8 (routing.xml deprecated) | Auto-scanned from `Controller/` | | Business logic | Event Actions | **Facades** + Services | `CartFacade`, `CustomerFacade`, etc. | @@ -36,8 +37,8 @@ description: "Migrating a Thelia 2 module to Thelia 3 (Symfony 7.4 LTS, API Plat 4. **`config.xml`**: strip `<services>`, `<hooks>`, `<loops>`, `<forms>`, `<commands>` (see section 3). They are now auto-discovered. 5. **`routing.xml`**: deprecated in T3. Delete it and declare every route with `#[Route]` PHP 8 attributes on controllers. 6. **`schema.xml`**: adapt table namespaces and `external-schema` declarations for core FKs. -7. **Hooks**: convert `<hooks>` XML to `extends BaseHook` + `static getSubscribedHooks()`. Back-office templates stay Smarty. -8. **Loops**: `BaseLoop` is `@deprecated`. Keep as-is for back-office Smarty. For front-office, migrate to API Resources. +7. **Hooks**: convert `<hooks>` XML to `extends BaseHook` + `static getSubscribedHooks()`. Rewrite the back-office templates to Twig under `templates/backOffice/default-twig/`. +8. **Loops**: `BaseLoop` is `@deprecated`. Keep it only where an existing `{loop}` call still depends on it. For front-office, migrate to API Resources. 9. **Front templates**: rewrite `.tpl` Smarty to `.html.twig` Flexy. `{loop}` becomes `resources()`. Manual JS becomes LiveComponents + Stimulus. 10. **API**: expose models via `PropelResourceInterface` (see section 5). To extend a native resource, use `ResourceAddonInterface`. 11. **Forms**: keep `BaseForm` for HTML web forms. For simple REST APIs, use DTO + `#[MapRequestPayload]`. @@ -506,7 +507,8 @@ Payment modules that type-hint the old OpenApi event class (PayPal, Payzen, Cawl - [ ] `#[AutowireIterator]` / `#[AutowireLocator]` instead of `#[TaggedIterator]` / `#[TaggedLocator]` - [ ] `Thelia\Core\Install\Database` instead of `Thelia\Install\Database` - [ ] `BaseHook` + `getSubscribedHooks()` for back-office hooks (no XML declaration) -- [ ] Back-office hook templates are Smarty (`.html` in `templates/backOffice/default/`) +- [ ] Back-office hook templates are Twig (`.html.twig` in `templates/backOffice/default-twig/`) +- [ ] Email and PDF templates are Twig (`.html.twig` / `.txt.twig`) - [ ] Front-office templates are Twig (`.html.twig` in `templates/frontOffice/flexy/`) - [ ] `{loop}` replaced by `resources('/api/front/...')` in Twig templates - [ ] No `BaseApiModel`, no `extends BaseAdminOpenApiController` diff --git a/plugins/thelia/skills/thelia3/references/front-office.md b/plugins/thelia/skills/thelia3/references/front-office.md index 3fdc3cf..7c77d6e 100644 --- a/plugins/thelia/skills/thelia3/references/front-office.md +++ b/plugins/thelia/skills/thelia3/references/front-office.md @@ -225,11 +225,11 @@ When to use a Facade vs direct Propel: ## 8. Mailing and PDF -Emails: **Smarty legacy** in `templates/email/default/` (`.html` + `.txt`). `MailerFactory` uses `ParserResolver` - if `.html` (without `.twig`), `SmartyParser` takes over. Module override: `{module}/templates/email/default/`. +Emails: Twig templates in `templates/email/default/` (`.html.twig` + `.txt.twig`), resolved by `TwigParser`. Module override: `{module}/templates/email/default/`. The `message` table row points at the files through `html_template_file_name` / `text_template_file_name`. -No Twig migration planned for emails. A module that wants Twig email: create its `.html.twig` files and ensure TwigParser resolves them. +PDF: Twig templates in `templates/pdf/default/` (admin invoices and delivery slips), rendered with dompdf. Generation is event-driven: dispatch `TheliaEvents::GENERATE_PDF` with a `PdfEvent($html)` and `Action\Pdf` produces the document. A module can listen to that event at a higher priority to swap the renderer. -PDF: `templates/pdf/default/` = back-office (admin invoices) in Smarty. No PDF engine on the Flexy front side - but the core has one, event-driven: dispatching `TheliaEvents::GENERATE_PDF` with a `PdfEvent($html)` triggers `Action\Pdf` which renders via `spipu/html2pdf` (already in core, no dompdf/wkhtmltopdf needed). A module can listen to this event at higher priority to swap the renderer. The HTML source still needs to be produced (currently Smarty BO). +Both template sets are Twig in Thelia 3. A `.html` (non-Twig) email or PDF template is a Thelia 2 leftover. ## 9. Assets - Webpack Encore + Tailwind diff --git a/plugins/thelia/skills/thelia3/references/glossary.md b/plugins/thelia/skills/thelia3/references/glossary.md index 51e3dca..cd7455b 100644 --- a/plugins/thelia/skills/thelia3/references/glossary.md +++ b/plugins/thelia/skills/thelia3/references/glossary.md @@ -143,11 +143,11 @@ | Symbol | Description | |---|---| -| `BaseHook` | Back-office hook base (Smarty) | +| `BaseHook` | Back-office hook base (Twig templates) | | `BaseHookInterface` | Auto-tagged `hook.event_listener` | | `getSubscribedHooks(): array` | Auto hook declaration (static) | | `HookRenderEvent` | Hook event - `add(html)`, `addTemplate()`, `addJS()`, `addCSS()` | -| `BaseLoop` | Canonical for `{loop type=...}` Smarty BO. Flexy/Twig front prefers `resources()`. | +| `BaseLoop` | Legacy `{loop type=...}` data source. Prefer `resources()`. | | `LoopInterface` | Auto-tagged `thelia.loop` | | `PropelSearchLoopInterface` | Loop with Propel query | | `LoopResult` / `LoopResultRow` | Loop results | diff --git a/plugins/thelia/skills/thelia3/references/hooks.md b/plugins/thelia/skills/thelia3/references/hooks.md index bd61161..1010748 100644 --- a/plugins/thelia/skills/thelia3/references/hooks.md +++ b/plugins/thelia/skills/thelia3/references/hooks.md @@ -1,6 +1,6 @@ # Events and back-office extensions - Thelia 3 -> Symfony listeners + back-office hooks (Smarty) + front theme hooks (Twig) + legacy loops. Everything is auto-tagged from `BaseHookInterface` / `ThemeHookInterface` / `LoopInterface` / `EventSubscriberInterface` - NO XML declaration required. +> Symfony listeners + back-office hooks (Twig) + front theme hooks (Twig) + legacy loops. Everything is auto-tagged from `BaseHookInterface` / `ThemeHookInterface` / `LoopInterface` / `EventSubscriberInterface` - NO XML declaration required. ## 1. `TheliaEvents` @@ -122,7 +122,7 @@ class ProductHook extends BaseHook public function onProductTop(HookRenderEvent $event): void { - $event->add($this->render('product-top.html')); // Smarty + $event->add($this->render('product-top.html.twig')); } public function onNavbar(HookRenderEvent $event): void @@ -175,8 +175,8 @@ final class CustomerEditHook extends BaseHook Option 1 is safer - no risk of breaking the parent init chain. Note: `final readonly class` is not possible on a Hook (same `#[Required]` setter constraint as controllers). -Back-office hook templates = **Smarty only**: -- `render:template.html` -> Smarty render +Back-office hook templates are Twig, in `{module}/templates/backOffice/default-twig/`: +- `render:template.html.twig` -> Twig render - `js:assets/js/script.js` -> inject JS - `css:assets/css/style.css` -> inject CSS @@ -200,10 +200,10 @@ public function getHooks(): array | Value | Context | Equivalent | |---|---|---| -| `'front'` | Front-office (Twig/Smarty front) | - | -| `'back'` / `'bo'` / `'backoffice'` | Back-office (Smarty BO) | **all three are equivalent** | -| `'email'` | Email templates | rare | -| `'pdf'` | PDF generation (invoices, slips) | rare | +| `'front'` | Front-office (Twig, Flexy) | - | +| `'back'` / `'bo'` / `'backoffice'` | Back-office (Twig, default-twig) | **all three are equivalent** | +| `'email'` | Email templates (Twig) | rare | +| `'pdf'` | PDF generation (Twig, dompdf) | rare | For a BO hook, prefer `'back'` (most used in core). `'admin'` is **NOT** recognized - do not invent it. @@ -237,8 +237,8 @@ public static function getSubscribedHooks(): array BO hooks are strings registered dynamically in the DB via `getHooks()` from modules + those placed in templates. To enumerate the extension points actually available in a given project: ```bash -# Hooks placed in BO templates (Smarty) -grep -rn '{hook name="' templates/backOffice/default/ | sed -E 's/.*hook name="([^"]+)".*/\1/' | sort -u +# Hooks placed in BO templates (Twig) +grep -rn "hook('" templates/backOffice/default-twig/ | sed -E "s/.*hook\('([^']+)'.*/\1/" | sort -u # Legacy hooks placed in front templates (Twig) grep -rn 'hook(' templates/frontOffice/flexy/ | grep -E "hook\('([^']+)'" | sed -E "s/.*hook\('([^']+)'.*/\1/" | sort -u @@ -330,7 +330,7 @@ class ProductLoop extends BaseLoop implements PropelSearchLoopInterface ``` **NOTE**: `BaseLoop` is `@deprecated` in Thelia 3, the future path is API Resources (`PropelResourceInterface`). Create a new loop only if: -- Back-office compatibility (Smarty `{loop}`) is required +- Back-office compatibility with an existing `{loop}` call is required - No equivalent API Resource can be exposed For all new front development: **API Resources + `resources()`** or **LiveComponent + `DataAccessService`**. diff --git a/plugins/thelia/skills/thelia3/references/payment-delivery.md b/plugins/thelia/skills/thelia3/references/payment-delivery.md index 55a0033..54b68b1 100644 --- a/plugins/thelia/skills/thelia3/references/payment-delivery.md +++ b/plugins/thelia/skills/thelia3/references/payment-delivery.md @@ -312,7 +312,7 @@ final class ConfigController extends BaseAdminController } ``` -Smarty template (back-office): `templates/backOffice/default/mypayment/config.html`. +Back-office template (Twig): `templates/backOffice/default-twig/mypayment/config.html.twig`. ## 4. Typed module traps From 283610e96b66467ee1eb864c90251eab45588f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Nozi=C3=A8re?= <anoziere@openstudio.fr> Date: Sat, 8 Aug 2026 11:46:20 +0200 Subject: [PATCH 4/5] docs: add field findings on hook injection, request access and Propel models --- plugins/thelia/skills/propel-thelia/SKILL.md | 5 ++- plugins/thelia/skills/thelia3/SKILL.md | 2 ++ .../thelia3/references/code-patterns.md | 7 +++- .../skills/thelia3/references/front-office.md | 2 ++ .../thelia/skills/thelia3/references/hooks.md | 32 ++++++------------- .../skills/thelia3/references/modules.md | 2 +- .../thelia3/references/payment-delivery.md | 2 +- 7 files changed, 25 insertions(+), 27 deletions(-) diff --git a/plugins/thelia/skills/propel-thelia/SKILL.md b/plugins/thelia/skills/propel-thelia/SKILL.md index fa143ab..bd8d320 100644 --- a/plugins/thelia/skills/propel-thelia/SKILL.md +++ b/plugins/thelia/skills/propel-thelia/SKILL.md @@ -563,6 +563,8 @@ public function setPassword($password) { ... } public function setPassword(?string $password = null): static { ... } ``` +Never widen a non-nullable Base signature: if the Base getter returns `string`, an override that returns `?string` is not a compatible signature and fails at load. Keep the Base contract and handle the empty case inside the method. + Common cases to align: - `getValue(): ?string` on Config, MetaData - `setPosition(?int $v = null): static` on Product, Content @@ -633,7 +635,8 @@ Some methods that overrode Propel getters/setters with incompatible signatures w - **Singletons** `Translator::$instance` and `URL::$instance` must stay `?self = null` (fatal error in tests otherwise). - **`#[Ignore]` on `static` methods** crashes the Symfony Serializer. Never do this. - **Propel subprocess:** `PropelInitService` crashes when Propel is launched in a cold subprocess. Always boot `App\Kernel` in-process. -- **`Base/` classes are regenerated.** Never edit them manually. +- **`Base/` classes are regenerated.** Never edit them manually. Module models are generated under `var/propel/{APP_ENV}/model/`: a `Class not found` on `MyModuleQuery` usually means that cache is stale or was never built, not that the class is missing. Regenerate (`module:generate:model`, or re-run the post-activation) before looking for a namespace bug. +- **Never widen a getter to nullable in a stub.** Overriding a non-nullable Base getter with a `?type` return is an incompatible signature and fails at load. - **`Collection` is no longer an iterator:** use `getIterator()`; the `current()`/`next()` methods are `@deprecated`. - **Strict setter typing:** setters now have native PHP types. Passing a `bool` to a `?int` setter (TINYINT) or a `float` to a `?string` setter (DECIMAL) raises a `TypeError`. Always cast explicitly. - **ENUM/SET are untyped:** ENUM/SET getters and properties have no native type (the getter returns a string, but Propel maps ENUM to int internally). Do not attempt to type them. diff --git a/plugins/thelia/skills/thelia3/SKILL.md b/plugins/thelia/skills/thelia3/SKILL.md index 62417f8..2dc22ea 100644 --- a/plugins/thelia/skills/thelia3/SKILL.md +++ b/plugins/thelia/skills/thelia3/SKILL.md @@ -219,6 +219,8 @@ final class MyModule extends BaseModule | `OpenApi @OA\Schema` annotations | T2 residual | API Platform 4.3 attributes (`#[ApiResource]`) | | `BaseApiModel` | T2 residual | `PropelResourceInterface` + `PropelResourceTrait` | | `setRequest()` setter in services | service locator | constructor injection | +| `#[Required]` setter for a dependency of a `BaseHook` | left unwired by autowiring, `render()` throws, hook isolation swallows it, blank output | constructor injection forwarding the parent arguments | +| `$request->get('x')` | ambiguous: searches attributes, then query, then body | read `$request->attributes` / `query` / `request` explicitly | | `Thelia\Install\Database` (without `\Core`) | legacy namespace | `Thelia\Core\Install\Database` | | `new Database($con->getWrappedConnection())` | constructor accepts `ConnectionInterface` | `new Database($con)` | | `ContainerAwareInterface` (services, outside commands) | deprecated | explicit injection | diff --git a/plugins/thelia/skills/thelia3/references/code-patterns.md b/plugins/thelia/skills/thelia3/references/code-patterns.md index 084bd63..112af4e 100644 --- a/plugins/thelia/skills/thelia3/references/code-patterns.md +++ b/plugins/thelia/skills/thelia3/references/code-patterns.md @@ -562,7 +562,11 @@ Diagnosing "Tried to validate an invalid token": if `_token` values in the DOM a ### SSR fragments via `render(controller(...))` -When a template embeds a sub-controller (`render(controller('App\\Controller\\X::method', {param: value}))`), parameters arrive in `$request->attributes`, **not** in the query string. The controller must read via `$request->get('param')` (which looks in attributes + query + request), never `$request->query->get('param')` (query string only). The same endpoint exposed as a GET AJAX call receives its params in query -> `$request->get()` covers both cases. +When a template embeds a sub-controller (`render(controller('App\\Controller\\X::method', {param: value}))`), parameters arrive in `$request->attributes`, **not** in the query string. Declare the parameter as a typed controller argument so Symfony resolves it from the attributes, or read `$request->attributes` explicitly. Do not fall back to `$request->get()`: it silently searches attributes, then query, then body, so the source of a value is unknown and a query parameter can shadow a route attribute. If the same endpoint is also called as a GET AJAX request, read both bags explicitly: + +```php +$id = $request->attributes->getInt('id') ?: $request->query->getInt('id'); +``` ## 11. Code traps @@ -580,3 +584,4 @@ When a template embeds a sub-controller (`render(controller('App\\Controller\\X: | Untyped exception (`\Exception`) | specific exceptions | | Redundant PHPDoc | remove - code is doc | | Mutable services | always `final readonly class` | +| `$request->get('x')` | ambiguous source - read `$request->attributes`, `$request->query` or `$request->request` explicitly | diff --git a/plugins/thelia/skills/thelia3/references/front-office.md b/plugins/thelia/skills/thelia3/references/front-office.md index 7c77d6e..bec79ac 100644 --- a/plugins/thelia/skills/thelia3/references/front-office.md +++ b/plugins/thelia/skills/thelia3/references/front-office.md @@ -246,6 +246,8 @@ ddev exec bash -c "cd templates/frontOffice/flexy && npm install && npm run buil # Variants: npm run watch (encore dev --watch), npm run dev (encore dev) ``` +`composer update` on a theme deletes its compiled `dist/`, and the next render fails with "Could not find the entrypoints file from Webpack". Rebuild after every theme update. + Public path: `/templates-assets/frontOffice/{theme}/dist`, symlinked by `EncoreExtension` at kernel boot (guard `!is_dir($dest)`). In production, `THELIA_WEB_DIR/templates-assets/` must be writable. Tailwind `tailwind.config.js`: custom CSS tokens (`var(--theme)`, `var(--theme-dark)`) -> theming without rebuild. Content scanned: `components/**/*.twig`, `src/UiComponents/**/*.twig`, `form/**/*.twig`, `*.twig`. diff --git a/plugins/thelia/skills/thelia3/references/hooks.md b/plugins/thelia/skills/thelia3/references/hooks.md index 1010748..d8f6ee2 100644 --- a/plugins/thelia/skills/thelia3/references/hooks.md +++ b/plugins/thelia/skills/thelia3/references/hooks.md @@ -136,18 +136,17 @@ class ProductHook extends BaseHook ### Dependency injection in `BaseHook` -`BaseHook` has a constructor signature `(?EventDispatcherInterface $dispatcher = null, ?ParserResolver $parserResolver = null)` + `#[Required]` setter for `container`. To inject your own deps, **two options**: +`BaseHook` has a constructor signature `(?EventDispatcherInterface $dispatcher = null, ?ParserResolver $parserResolver = null)`. Inject your own dependencies **through the constructor**, forwarding the parent arguments: ```php -// Option 1: #[Required] setters (compatible with parent init) final class CustomerEditHook extends BaseHook { - private CustomerLoyaltyRepository $loyaltyRepo; - - #[Required] - public function setLoyaltyRepository(CustomerLoyaltyRepository $repo): void - { - $this->loyaltyRepo = $repo; + public function __construct( + private readonly CustomerLoyaltyRepository $loyaltyRepo, + ?EventDispatcherInterface $dispatcher = null, + ?ParserResolver $parserResolver = null, + ) { + parent::__construct($dispatcher, $parserResolver); } public static function getSubscribedHooks(): array { /* ... */ } @@ -155,25 +154,12 @@ final class CustomerEditHook extends BaseHook public function onCustomerEdit(HookRenderEvent $event): void { $loyalty = $this->loyaltyRepo->findByCustomerId($event->getArgument('id')); - $event->add($this->render('customer-edit-loyalty.html', ['loyalty' => $loyalty])); - } -} - -// Option 2: child constructor calling parent -final class CustomerEditHook extends BaseHook -{ - public function __construct( - private readonly CustomerLoyaltyRepository $loyaltyRepo, - ?EventDispatcherInterface $dispatcher = null, - ?ParserResolver $parserResolver = null, - ) { - parent::__construct($dispatcher, $parserResolver); + $event->add($this->render('customer-edit-loyalty.html.twig', ['loyalty' => $loyalty])); } - // ... } ``` -Option 1 is safer - no risk of breaking the parent init chain. Note: `final readonly class` is not possible on a Hook (same `#[Required]` setter constraint as controllers). +**Never use a `#[Required]` setter or property for a hook dependency.** With autowiring it is silently left unwired, the property stays null, `render()` throws, and hook dispatch isolates the listener and swallows the exception - the hook renders nothing at all, with no error anywhere. Note: `final readonly class` is not possible on a Hook (`BaseHook` carries a `#[Required]` setter for the container, same constraint as controllers). Back-office hook templates are Twig, in `{module}/templates/backOffice/default-twig/`: - `render:template.html.twig` -> Twig render diff --git a/plugins/thelia/skills/thelia3/references/modules.md b/plugins/thelia/skills/thelia3/references/modules.md index 5969176..c0c9477 100644 --- a/plugins/thelia/skills/thelia3/references/modules.md +++ b/plugins/thelia/skills/thelia3/references/modules.md @@ -113,7 +113,7 @@ public function postActivation(?ConnectionInterface $con = null): void **Strict rule**: ALL code in `postActivation()` (insertSql, Propel seed, default hook creation, fixtures) MUST be inside the `if (!is_initialized)` block. Outside the guard = re-executes on every reactivation, causing N+1 on all customers/products, timeout risk in production, duplicate data, and unexpected side effects. -**Propel seed on first activation**: `MyModule\Model\*` Propel classes are only generated after `module:post-activate-all` (`bin/install`). If the seed instantiates `MyModuleQuery::create()` directly in `postActivation()`, the first pass results in `Class not found`. Three solutions: +**Propel seed on first activation**: `MyModule\Model\*` Propel classes are generated under `var/propel/{APP_ENV}/model/`, and only after `module:post-activate-all` (`bin/install`). A `Class not found` on a module model later in the project life is the same cache being stale, not a namespace error. If the seed instantiates `MyModuleQuery::create()` directly in `postActivation()`, the first pass results in `Class not found`. Three solutions: 1. Seed via raw SQL in `Config/TheliaMain.sql` (simplest, guaranteed idempotent via `INSERT ... ON DUPLICATE KEY UPDATE`) 2. Seed in a dedicated command `app:my-module:seed` launched after `module:post-activate-all` 3. Lazy pattern: detect `class_exists(MyModuleQuery::class)` before seeding, otherwise defer diff --git a/plugins/thelia/skills/thelia3/references/payment-delivery.md b/plugins/thelia/skills/thelia3/references/payment-delivery.md index 54b68b1..c82083b 100644 --- a/plugins/thelia/skills/thelia3/references/payment-delivery.md +++ b/plugins/thelia/skills/thelia3/references/payment-delivery.md @@ -187,7 +187,7 @@ public function callback(Request $request, EventDispatcherInterface $dispatcher) if (!$this->verifySignature($request)) { return new Response('', 403); } - $order = OrderQuery::create()->findPk((int) $request->get('order_id')); + $order = OrderQuery::create()->findPk($request->request->getInt('order_id')); if ($order === null) { return new Response('', 404); } From 05ee033f56586fe7208db0059bb41f92cfac2995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Nozi=C3=A8re?= <anoziere@openstudio.fr> Date: Sat, 8 Aug 2026 11:46:28 +0200 Subject: [PATCH 5/5] chore: bump thelia plugin to 0.3.0 --- plugins/thelia/.claude-plugin/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/thelia/.claude-plugin/plugin.json b/plugins/thelia/.claude-plugin/plugin.json index 004883a..5adf521 100644 --- a/plugins/thelia/.claude-plugin/plugin.json +++ b/plugins/thelia/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "thelia", "description": "Develop Thelia 3 with Claude Code: skills and agents for modules, Propel ORM, the API Platform bridge, the Flexy front-office, the default-twig back-office, and Thelia 2 to 3 migration.", - "version": "0.2.0", + "version": "0.3.0", "author": { "name": "Thelia" },