Skip to content

Upgrade plan — Symfony 6.4 → 7.1 → 7.2 → 7.3 → 7.4 + API Platform 3.4 → 4.x #8723

Closed

Description

@AngelFQC

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 missing getSupportedTypes(): src/CoreBundle/Serializer/{Document,Glossary,StudentPublicationComment}AiAssistedNormalizer.php.
  • 69 soft-deprecated $request->get() calls in 19 files under src/.
  • src/CoreBundle/Controller/AccessUrlController.php imports ApiPlatform\Api\IriConverterInterface (removed in AP4).
  • config/packages/api_platform.yaml exception_to_status maps two legacy ApiPlatform\Core\Exception\* classes (AP4 boot blocker).
  • 86 Put operations across 60 files, none with standard_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 read hydra:description/hydra:title without an RFC 7807 detail/title fallback (AP4 default error format).
  • SYMFONY_DEPRECATIONS_HELPER=999999 in .env.test currently 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 the deprecation channel in dev.
  • PR-0.2: composer companions (all dual-compatible 6.4/7.x): drop proxy-manager-bridge; bump knp-components ^4, webpack-encore-bundle ^2.2, carbon ^3, dama ^8.0. Adapt PaginationSubscriber.php (+ question_pool.php, gradebook_list.php, questions.php) and TimestampableAgoTrait.php (write an "ago" unit test before the Carbon 3 bump — diff*() behavior changed).
  • PR-0.3: forward-compatible code fixes — migrate the 3 ContextAwareNormalizerInterface normalizers (template: src/CoreBundle/Serializer/Normalizer/UserNormalizer.php); replace the 69 $request->get() calls; repoint IriConverterInterface and the two exception_to_status legacy classes to ApiPlatform\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.*); tag v1.4.0, then bump chamilo/settings-bundle ^1.4 here. Plan B if the fork proves unviable: absorb the bundle into src/CoreBundle/Settings/Bundle/ under a Chamilo\ namespace (all 77 consumers already live in src/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); add data.detail || data["hydra:description"] / data.title || data["hydra:title"] fallbacks in toServiceError() and sweep the 67 hydra:description usages (centralizing where possible). Keep serializer.hydra_prefix: true — the SPA depends on hydra:member/hydra:totalItems/hydra:view.
  • PR-0.6a/b/c (Track B): PUT→PATCH migration. Audit each of the 86 Put ops: partial Vue payload → add Patch (merge-patch is already configured) and switch the caller; full-object payload → keep Put with extraProperties: ['standard_put' => true]; no caller → PATCH by default. Order by volume: 0.6a Forum (CForumPost 7, CForumThread 6, CForum 5, CForumCategory 4), 0.6b CAttendance/CDocument/CLink, 0.6c the rest. Backend + Vue caller in the same PR. Goal: zero Put without standard_put before 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)

  1. api-platform/core: ^4.2 (stay on the metapackage; the api-platform/symfony + api-platform/doctrine-orm split is an optional follow-up).
  2. api_platform.yaml: drop keep_legacy_inflector; rfc_7807_compliant_errors already under defaults.extra_properties; keep hydra_prefix: true.
  3. Adapt the 8 custom filters in src/CoreBundle/Filter/ (CidFilter, SidFilter, ExtraFieldFilter, GlobalEventFilter, ParentNullFilter, PartialSearchOrFilter, SocialWallFilter, UserExtraFieldFilter) to the new AbstractFilter constructor signature.
  4. PHPStan sweep over the 77 state providers + 74 processors + 23 query extensions (namespaces persist; only signatures may change).
  5. 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 Vue

Manual 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 AbstractFilter signature side effects on cid/sid filters → naturally covered by Behat (course context on every request).

Activity

  1. added this to the 3.0 milestone on Jul 13, 2026
  2. linked a pull request that will close this issueSf7 ap4 migration #8725on Jul 13, 2026
  3. AngelFQC commented on Jul 14, 2026

    @AngelFQC
    MemberAuthor

    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:

    • PR-0.1 — deprecation reporting enabled in the test env.
    • PR-0.3a — API Platform namespaces made AP4-ready: IriConverterInterface → ApiPlatform\Metadata, and exception_to_status repointed from the dead ApiPlatform\Core\Exception\* classes to Metadata\Exception\InvalidArgumentException + ParameterValidator\Exception\ValidationExceptionInterface.
    • PR-0.3b — the 3 AI-assisted normalizers + Scim/UserDenormalizer migrated to plain NormalizerInterface with getSupportedTypes() and native return types (removes ContextAwareNormalizerInterface, gone in SF7).
    • PR-0.3c — 71 deprecated Request::get() calls replaced with typed accessors (query/request/attributes, ->all() for array params). Verified PHPStan net-zero.
    • PR-0.2 — composer bumps: dropped 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 requires use_savepoints: true, added in the test env).
    • PR-0.4 — chamilo/settings-bundle made 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 tag v1.4.0; to be swapped for the canonical Packagist tag once #7 is merged and tagged.
    • PR-0.5 — RFC 7807 error format adopted (rfc_7807_compliant_errors: true) with a single central axios interceptor that maps detail→hydra:description / title→hydra:title, so all existing Vue error handling keeps working unchanged.

    Key findings

    1. Zero self-deprecations. A full test-suite run (428 tests) reports 0 self deprecation notices, so the eventual max[self]=0 gate is achievable without a baseline.
    2. PUT→PATCH scope is far smaller than "86 operations". Most Put operations (all Forum, plus CDocument/CLink/CGlossary/CLp/CBlog/PersonalFile/... — ~50 ops) are custom action endpoints with deserialize: false + a processor that reads the raw request. standard_put never runs for them, so they are immune to the AP4 PUT-becomes-replace change — no migration needed. The genuinely affected set is ~30 standard Put operations, most of them trivial single-operation resources, plus 2 landmines (CAttendance /toggle_visibility and /soft_delete: action PUTs without deserialize: false that receive an empty body → would reset fields under AP4; fix is a one-line deserialize: false). Since merge-patch is safe for partial and full payloads, the affected set migrates uniformly to PATCH.
    3. Test-suite failures are environmental, not from the migration. The ~106 failures (25 errors + 81 failures / 428) are a stale chamilo_test schema (missing require_unique, progress columns → SQL errors → cascading 500s) plus 2 test/entity method mismatches. None touch the migrated code.

    Remaining work

    • Track B — PUT→PATCH: implement the ~30 affected operations (landmines first, then plain entities, then DTO-input resources case-by-case).
    • Symfony hops H1 (6.4→7.1) → H2 → H3 → H4 (7.4 LTS).
    • API Platform 4 phase (after H4 + Track B): api-platform/core ^4.2, adapt the 8 custom AbstractFilter subclasses, drop keep_legacy_inflector.
  4. 80 remaining items

  5. changed the title [-]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[/+] on Jul 27, 2026
  6. changed the title [-][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[/+] on Jul 27, 2026
  7. AngelFQC commented on Jul 27, 2026

    @AngelFQC
    MemberAuthor

    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 a protected en DBAL 4) → llamadas explícitas eliminadas (la conexión es lazy)
    • AbstractPlatform::getName() (removido) → reescrito con instanceof AbstractMySQLPlatform donde el guard es legítimo (conversión utf8mb4) y eliminado donde el SQL era portable

    El wrapper legacy Database:: (~7900 usos) ya usaba la API moderna de DBAL 3 → aislado, sin cambios.

    Tipo array → json: el tipo DBAL array (PHP serialize) 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) a json, con una migración de datos en Migrations/Schema/V300 que reencoda serialize → JSON por chunks (por PK) antes del ALTER a JSON, para que MySQL/MariaDB acepten los valores. Reversible e idempotente. Verificado en una BD de desarrollo (MariaDB). ext_log_entries.data es de Gedmo (vendor) y queda en array — fuera de alcance.

    Blocker restante para DBAL 4 (diferido): subir carbonphp/carbon-doctrine-types a v3 y simpod/doctrine-utcdatetime a 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions