Repository navigation
Upgrade plan — Symfony 6.4 → 7.1 → 7.2 → 7.3 → 7.4 + API Platform 3.4 → 4.x #8723
Description
Context
Chamilo 2.0 currently runs on Symfony 6.4 LTS (PHP 8.2) with api-platform/core 3.4.17. This issue tracks the upgrade plan to Symfony 7.4 LTS (stepping through 7.1 → 7.2 → 7.3 → 7.4) and API Platform 4.x, landing in the Chamilo 3.0 release. Preparation PRs are tagged with this milestone.
Verified requirements (July 2026)
| Target | PHP | Symfony | Notes |
|---|---|---|---|
| Symfony 7.1–7.4 | ≥ 8.2 | — | 7.4 is LTS (bug fixes until Nov 2028, security until Nov 2029). 7.1/7.2/7.3 are already EOL, so intermediate hops must be merged quickly. Current PHP 8.2 is enough. |
| API Platform 4.x | ≥ 8.2 | ≥ 7.4 on current releases (4.3/4.4) | AP 3.4.17 supports symfony ^6.4||^7.1, so it survives the whole ladder. Order is fixed: Symfony first, AP4 last. |
Out of scope: Doctrine ORM 3 / DBAL 4 (staying on ORM 2.20 / DBAL 3.10, supported by both targets), PHPUnit 10/11, Symfony 8 (requires PHP 8.4).
Composer blockers (verified with composer why-not)
| Package | Action |
|---|---|
symfony/proxy-manager-bridge 6.4 |
Remove — no 7.x release exists; 0 usages; enable_lazy_ghost_objects: true already set in doctrine.yaml |
chamilo/settings-bundle 1.3.0 |
Update our fork (chamilo/SyliusSettingsBundle) to symfony ^6.4||^7.0 and tag. Only large-scope blocker (77 consumer files under src/CoreBundle/Settings/) |
knplabs/knp-components 3.6 |
→ ^4 (usages: src/CoreBundle/EventSubscriber/PaginationSubscriber.php + 3 legacy admin pages) |
symfony/webpack-encore-bundle 1.17 |
→ ^2.2 |
nesbot/carbon 2.73 |
→ ^3 (single usage: src/CoreBundle/Traits/TimestampableAgoTrait.php) |
dama/doctrine-test-bundle 6.7.5 |
→ ^8.0 (v8.0 supports SF7 while keeping PHPUnit 9.6; newer 8.x require PHPUnit ≥ 11, composer will resolve the highest compatible) |
hautelook/alice-bundle 2.13.0 |
Do not upgrade — 2.13 already supports ^7.0; 2.17+ requires PHP 8.4 |
All other third-party bundles installed already support Symfony 7 (a2lix 3.2.4, gregwar/captcha-bundle 2.5, lexik-jwt 2.21, vich, stof, oneup-flysystem, knpu-oauth2, fos-jsrouting, nelmio-cors, reset-password, security-acl 3.4, glide-symfony 2.1, monolog-bundle, gedmo 3.22, doctrine-bundle 2.18).
Code inventory
The codebase is already in good shape for SF7 (0 ContainerAware, 0 getDoctrine(), 0 @ORM annotations, security already on SecurityBundle\Security, 35 commands on #[AsCommand]). Remaining work:
- 3 normalizers implementing
ContextAwareNormalizerInterface(removed in 7.0) and missinggetSupportedTypes():src/CoreBundle/Serializer/{Document,Glossary,StudentPublicationComment}AiAssistedNormalizer.php. - 69 soft-deprecated
$request->get()calls in 19 files undersrc/. src/CoreBundle/Controller/AccessUrlController.phpimportsApiPlatform\Api\IriConverterInterface(removed in AP4).config/packages/api_platform.yamlexception_to_statusmaps two legacyApiPlatform\Core\Exception\*classes (AP4 boot blocker).- 86
Putoperations across 60 files, none withstandard_put— in AP4, PUT becomes unconditional replace, which breaks the Vue SPA's partial payloads. - Central Vue error handler
assets/vue/services/api.js(toServiceError) plus ~33 views readhydra:description/hydra:titlewithout an RFC 7807detail/titlefallback (AP4 default error format). SYMFONY_DEPRECATIONS_HELPER=999999in.env.testcurrently masks all deprecations.
Phase plan
Track A (Symfony) Track B (API, parallel from day 1)
F0.1 deprecation visibility F0.5 RFC 7807 on AP 3.4 + api.js fallbacks
F0.2 composer companions F0.6a PUT→PATCH Forum (22 ops)
F0.3 SF7-ready code fixes F0.6b PUT→PATCH Attendance/Document/Link
F0.4 settings-bundle fork F0.6c PUT→PATCH remaining (~55 ops)
└──► H1 (7.1) → H2 (7.2) → H3 (7.3) → H4 (7.4) ──► F-AP4 (needs H4 + Track B)
Every PR is independently mergeable into master. H1 requires F0.1–F0.4. Track B only blocks the final AP4 phase.
Phase 0 — groundwork on 6.4
- PR-0.1: make deprecations visible —
.env.test→SYMFONY_DEPRECATIONS_HELPER='max[self]=0&max[direct]=999999&verbose=1'(or a committed baseline file), plus a monolog handler for thedeprecationchannel in dev. - PR-0.2: composer companions (all dual-compatible 6.4/7.x): drop
proxy-manager-bridge; bumpknp-components ^4,webpack-encore-bundle ^2.2,carbon ^3,dama ^8.0. AdaptPaginationSubscriber.php(+question_pool.php,gradebook_list.php,questions.php) andTimestampableAgoTrait.php(write an "ago" unit test before the Carbon 3 bump —diff*()behavior changed). - PR-0.3: forward-compatible code fixes — migrate the 3
ContextAwareNormalizerInterfacenormalizers (template:src/CoreBundle/Serializer/Normalizer/UserNormalizer.php); replace the 69$request->get()calls; repointIriConverterInterfaceand the twoexception_to_statuslegacy classes toApiPlatform\Metadata\*(both new namespaces already exist in 3.4). - PR-0.4: fork work on chamilo/SyliusSettingsBundle — constraints
symfony ^6.4|^7.0,php ^8.2; SF7 sweep (native return types on form types, DI extension/compiler passes, Twig extensions, event subscribers); run the bundle suite against 6.4 (--prefer-lowest) and 7.x (SYMFONY_REQUIRE=7.1.*); tagv1.4.0, then bumpchamilo/settings-bundle ^1.4here. Plan B if the fork proves unviable: absorb the bundle intosrc/CoreBundle/Settings/Bundle/under aChamilo\namespace (all 77 consumers already live insrc/CoreBundle/Settings/). - PR-0.5 (Track B): adopt RFC 7807 already on AP 3.4 —
defaults.extra_properties.rfc_7807_compliant_errors: true(the key AP4 expects); adddata.detail || data["hydra:description"]/data.title || data["hydra:title"]fallbacks intoServiceError()and sweep the 67hydra:descriptionusages (centralizing where possible). Keepserializer.hydra_prefix: true— the SPA depends onhydra:member/hydra:totalItems/hydra:view. - PR-0.6a/b/c (Track B): PUT→PATCH migration. Audit each of the 86
Putops: partial Vue payload → addPatch(merge-patch is already configured) and switch the caller; full-object payload → keepPutwithextraProperties: ['standard_put' => true]; no caller → PATCH by default. Order by volume: 0.6a Forum (CForumPost7,CForumThread6,CForum5,CForumCategory4), 0.6bCAttendance/CDocument/CLink, 0.6c the rest. Backend + Vue caller in the same PR. Goal: zeroPutwithoutstandard_putbefore the AP4 flip, with ApiTestCase regression tests (PATCH must not reset absent fields).
H1 — 6.4 → 7.1 (the big hop: absorbs all 7.0 removals)
composer.json: all symfony/* 6.4.* → 7.1.* + extra.symfony.require, then composer update "symfony/*" --with-all-dependencies and composer recipes:update (review recipe diffs one by one). Main risk: native parameter/return types added to Symfony 7.0 interfaces — incompatible signatures in our voters/subscribers/form types/Twig extensions are fatal, not deprecations; caught by PHPStan/Psalm + lint:container + prod cache warmup. Runtime checks: gradebook export (SonataExporterBundle bridge has no direct symfony constraint) and legacy public/main/ pages (static service locator Chamilo\CoreBundle\Framework\Container, 36 usages in api.lib.php) — if a get() fails, add a targeted public: true alias.
H2 / H3 — 7.1 → 7.2 → 7.3 (short hops)
Same mechanics. Known items: session sid_length/sid_bits_per_character options (7.2); UserInterface::eraseCredentials() deprecation affects src/CoreBundle/Entity/User.php (7.3); Console invokable-command deprecations vs our 35 commands (7.3).
H4 — 7.3 → 7.4 LTS (final destination)
7.4 deprecates everything 8.0 removes → the gate here is zero self+direct deprecations from src/ and public/main/. Afterwards harden .env.test to max[self]=0&max[direct]=0 permanently. Keep phpstan 1.x / psalm 5.x unless actually broken (tooling bumps as separate follow-up).
F-AP4 — API Platform 3.4 → 4.x (requires H4 + Track B)
api-platform/core: ^4.2(stay on the metapackage; theapi-platform/symfony+api-platform/doctrine-ormsplit is an optional follow-up).api_platform.yaml: dropkeep_legacy_inflector;rfc_7807_compliant_errorsalready underdefaults.extra_properties; keephydra_prefix: true.- Adapt the 8 custom filters in
src/CoreBundle/Filter/(CidFilter,SidFilter,ExtraFieldFilter,GlobalEventFilter,ParentNullFilter,PartialSearchOrFilter,SocialWallFilter,UserExtraFieldFilter) to the newAbstractFilterconstructor signature. - PHPStan sweep over the 77 state providers + 74 processors + 23 query extensions (namespaces persist; only signatures may change).
- GraphQL: unused in this project → no action.
Verification gate (every PR/hop)
composer validate
php bin/console cache:clear && php bin/console lint:container && php bin/console lint:yaml config/
php bin/console debug:container --deprecations
composer phpcs && composer phpstan && composer psalm
php bin/phpunit
vendor/bin/behat tests/behat/features/ # full suite on hops; forum/documents features on PUT→PATCH PRs
yarn build # PRs touching VueManual smoke: admin login/dashboard → save a setting in /admin/settings (settings-bundle fork) → course documents upload/rename/delete → forum create/edit post (PATCH) → question pool + gradebook list (Knp pagination) + gradebook export (Sonata) → any legacy public/main/ page in course context (Container::) → trigger a Vue form validation error (error format).
Hop gate: zero self+direct deprecations from our code (vendor-indirect ones are recorded), green QA gates, smoke OK.
Residual risks
- Settings-bundle fork harder than expected → timebox + plan B (absorb into monorepo).
- Carbon 3
diff*output / knp-components 4 pagination events → tests written before the bumps. - RFC 7807 breaking uncovered Vue views → dual fallback centralized in
toServiceError; flag is reversible on 3.4. - New AP4
AbstractFiltersignature side effects oncid/sidfilters → naturally covered by Behat (course context on every request).
Activity
Replace deprecated Request::get() with typed request accessors (chami…
Upgrade composer dependencies toward Symfony 7 compatibility (chamilo…
Declare serializer normalizer return types for Symfony 7 (chamilo#8723)
Require Symfony 7 compatible settings-bundle via fork (chamilo#8723)
Allow Symfony 7 (chamilo/chamilo-lms#8723)
Require Symfony 7 compatible settings-bundle via fork (chamilo#8723)
Adopt RFC 7807 error format with Hydra fallback (chamilo#8723)
AngelFQC commentedon Jul 14, 2026
Progress update — Phase 0 groundwork complete
Work in progress on branch
AngelFQC/chamilo-lms:sf7-ap4-migration(9 commits). All Phase 0 preparation (still on Symfony 6.4) is done:IriConverterInterface→ApiPlatform\Metadata, andexception_to_statusrepointed from the deadApiPlatform\Core\Exception\*classes toMetadata\Exception\InvalidArgumentException+ParameterValidator\Exception\ValidationExceptionInterface.Scim/UserDenormalizermigrated to plainNormalizerInterfacewithgetSupportedTypes()and native return types (removesContextAwareNormalizerInterface, gone in SF7).Request::get()calls replaced with typed accessors (query/request/attributes,->all()for array params). Verified PHPStan net-zero.symfony/proxy-manager-bridge;knplabs/knp-components ^4(API-compatible, no code change),nesbot/carbon ^3(+ a characterization test),symfony/webpack-encore-bundle ^2.2,dama/doctrine-test-bundle ^8(which requiresuse_savepoints: true, added in the test env).chamilo/settings-bundlemade Symfony 7 compatible (constraints only; the code was already clean). See Allow Symfony 7 SyliusSettingsBundle#7. Meanwhile consumed via a temporary VCS repo to the fork tagv1.4.0; to be swapped for the canonical Packagist tag once #7 is merged and tagged.rfc_7807_compliant_errors: true) with a single central axios interceptor that mapsdetail→hydra:description/title→hydra:title, so all existing Vue error handling keeps working unchanged.Key findings
selfdeprecation notices, so the eventualmax[self]=0gate is achievable without a baseline.Putoperations (all Forum, plus CDocument/CLink/CGlossary/CLp/CBlog/PersonalFile/... — ~50 ops) are custom action endpoints withdeserialize: false+ a processor that reads the raw request.standard_putnever runs for them, so they are immune to the AP4 PUT-becomes-replace change — no migration needed. The genuinely affected set is ~30 standardPutoperations, most of them trivial single-operation resources, plus 2 landmines (CAttendance/toggle_visibilityand/soft_delete: action PUTs withoutdeserialize: falsethat receive an empty body → would reset fields under AP4; fix is a one-linedeserialize: false). Since merge-patch is safe for partial and full payloads, the affected set migrates uniformly to PATCH.chamilo_testschema (missingrequire_unique,progresscolumns → SQL errors → cascading 500s) plus 2 test/entity method mismatches. None touch the migrated code.Remaining work
api-platform/core ^4.2, adapt the 8 customAbstractFiltersubclasses, dropkeep_legacy_inflector.Disable body deserialization on attendance visibility and soft-delete…
Expose Patch on plain CoreBundle resources and migrate partial-update…
Pin framework.uid versions to silence Symfony 7 deprecation (chamilo#…
Send page updates as PATCH from the CRUD service (chamilo#8723)
Update page via the dedicated pageService (PATCH) instead of the CRUD…
80 remaining items
Refactor fixture references to include entity classes for improved ty…
Prepare non-migration code for Doctrine DBAL 4 chamilo#8723
[-]Chamilo 3.0: Upgrade plan — Symfony 6.4 → 7.1 → 7.2 → 7.3 → 7.4 + API Platform 3.4 → 4.x[/-][+][2.1] Upgrade plan — Symfony 6.4 → 7.1 → 7.2 → 7.3 → 7.4 + API Platform 3.4 → 4.x[/+]Fix Doctrine DBAL 4 breaking changes in migrations chamilo#8723
Replace removed DBAL 4 Platform::getName() and Connection::connect() …
Convert legacy array-type columns to json with a V300 data migration c…
Upgrade Doctrine ORM to 3.x #8723
[-][2.1] Upgrade plan — Symfony 6.4 → 7.1 → 7.2 → 7.3 → 7.4 + API Platform 3.4 → 4.x[/-][+]Upgrade plan — Symfony 6.4 → 7.1 → 7.2 → 7.3 → 7.4 + API Platform 3.4 → 4.x[/+]AngelFQC commentedon Jul 27, 2026
Doctrine — preparación DBAL 4, mergeada a master
El trabajo de ORM 3 y la preparación para DBAL 4 ya está mergeado a master.
Decisión de alcance: por ahora nos quedamos en DBAL 3 (ORM 3.6 + DBAL 3.10). Todo el código quedó adaptado para DBAL 4 pero es forward-safe en DBAL 3.10 — no cambia el comportamiento en el estado actual. El único blocker restante para saltar a DBAL 4 son 2 bumps de dependencias (abajo), que quedan diferidos.
APIs removidas/renombradas en DBAL 4 (todas válidas ya en DBAL 3.10), en
src/y en migraciones:getSchemaManager()→createSchemaManager()Connection::PARAM_*_ARRAY→ArrayParameterType::*Result::fetch()→fetchAssociative()/fetchNumeric()(además arregla un bug latente ya presente en DBAL 3)executeUpdate()→executeStatement()Table::hasPrimaryKey()→null !== getPrimaryKey()Connection::connect()(pasa aprotecteden DBAL 4) → llamadas explícitas eliminadas (la conexión es lazy)AbstractPlatform::getName()(removido) → reescrito coninstanceof AbstractMySQLPlatformdonde el guard es legítimo (conversión utf8mb4) y eliminado donde el SQL era portableEl wrapper legacy
Database::(~7900 usos) ya usaba la API moderna de DBAL 3 → aislado, sin cambios.Tipo
array→json: el tipo DBALarray(PHPserialize) está deprecado en DBAL 3 y removido en DBAL 4. Se migraron las 9 columnas de Chamilo (User.roles,Group.roles,SysAnnouncement.roles,CQuiz.page_result_configuration,Gradebook{Link,Evaluation}.user_score_list,Asset.metadata,ResourceFile.metadata,ExtraFieldSavedSearch.value) ajson, con una migración de datos enMigrations/Schema/V300que reencodaserialize→ JSON por chunks (por PK) antes delALTERa JSON, para que MySQL/MariaDB acepten los valores. Reversible e idempotente. Verificado en una BD de desarrollo (MariaDB).ext_log_entries.dataes de Gedmo (vendor) y queda enarray— fuera de alcance.Blocker restante para DBAL 4 (diferido): subir
carbonphp/carbon-doctrine-typesa v3 ysimpod/doctrine-utcdatetimea 0.3.x (ambos bloquean DBAL ≥ 4). ORM 3.6 ya soporta DBAL 4; no requiere PHP 8.4. Queda para cuando se decida dar el salto.Drop redundant MySQL platform guard in utf8mb4 migration #8723
Internal: Add tolerant json DBAL type so upgrades read legacy seriali…
Internal: fix use of aliases that are not supported in ORM 3 so Chami…
Support Doctrine ORM 3 in ParameterTransformerListener (chamilo/chami…
Vendor: Consume settings-bundle from Packagist instead of the fork #8723