---
titre: Architecture technique
statut: Draft v1
date: 2026-05-08
---

# 01 — Architecture technique

## Stack

| Couche | Technologie | Version cible |
|---|---|---|
| Langage | PHP | 8.3 |
| Framework | Symfony | 7.x |
| ORM | Doctrine ORM | 3.x |
| Base de données | MySQL | 8.0 |
| CSS | Bulma | 1.x |
| JS | Stimulus + JS vanilla | — |
| Build assets | AssetMapper (natif Symfony 7) | — |
| Auth | Symfony Security + LexikJWT (API future) | — |
| Tests | PHPUnit + Panther (E2E) | — |
| Conteneurs | Docker / Docker Compose | — |
| CI | Bitbucket Pipelines | — |

## [ADR-001] Multi-tenancy par discriminant `organization_id`

**Décision** : utiliser le pattern *shared database / shared schema* avec une colonne `organization_id` sur toutes les tables tenant-scoped.

**Alternatives écartées** :
- *Database-per-tenant* : trop coûteux en exploitation, complique les migrations.
- *Schema-per-tenant* : MySQL gère mal le pattern, complexité opérationnelle élevée.

**Conséquences** :
- Un filtre Doctrine global (`OrganizationFilter`) injecte automatiquement le `WHERE organization_id = :current_org` sur toutes les entités taggées.
- Le SUPERADMIN peut désactiver le filtre pour intervenir cross-tenant.
- Les tests d'isolation tenant sont **obligatoires** sur chaque entité.

## [ADR-002] AssetMapper plutôt que Webpack Encore

**Décision** : utiliser AssetMapper natif Symfony 7 + import maps.

**Raisons** : pas de build Node.js, déploiement simplifié, suffisant pour Bulma + Stimulus. On migrera vers Vite si besoin d'un bundler.

## [ADR-003] Bus de messages pour les imports

**Décision** : les imports PDF/CSV passent par Symfony Messenger en async (transport Doctrine en MVP, Redis en v2).

**Raisons** : un import PDF peut prendre 10+ secondes, on ne bloque pas la requête HTTP. L'utilisateur suit la progression via polling ou SSE.

## Architecture applicative

```
┌─────────────────────────────────────────────────────────────┐
│                        Navigateur                            │
│              Bulma + Stimulus + Turbo (futur)                │
└────────────────────────────┬────────────────────────────────┘
                             │ HTTPS
┌────────────────────────────▼────────────────────────────────┐
│                    nginx (reverse proxy)                     │
└────────────────────────────┬────────────────────────────────┘
                             │ FastCGI
┌────────────────────────────▼────────────────────────────────┐
│                       PHP-FPM 8.3                            │
│  ┌────────────────────────────────────────────────────────┐ │
│  │                   Symfony 7                            │ │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │ │
│  │  │ Controllers  │  │  Services    │  │  Messenger   │  │ │
│  │  │  (web + api) │──│  (métier)    │──│  (workers)   │  │ │
│  │  └──────────────┘  └──────────────┘  └──────────────┘  │ │
│  │           │                │                  │         │ │
│  │           ▼                ▼                  ▼         │ │
│  │  ┌────────────────────────────────────────────────────┐ │ │
│  │  │              Doctrine ORM + Filters                │ │ │
│  │  │              (OrganizationFilter)                  │ │ │
│  │  └────────────────────────────────────────────────────┘ │ │
│  └─────────────────────────┬──────────────────────────────┘ │
└────────────────────────────┼────────────────────────────────┘
                             │
            ┌────────────────┼────────────────┐
            ▼                ▼                ▼
      ┌──────────┐    ┌──────────┐    ┌──────────┐
      │  MySQL   │    │  Volume  │    │ Worker   │
      │   8.0    │    │  /uploads│    │ Messenger│
      └──────────┘    └──────────┘    └──────────┘
```

## Arborescence Symfony cible

```
src/
├── Controller/
│   ├── Admin/             # Backoffice SUPERADMIN
│   ├── App/               # Application principale
│   └── Api/               # Future API REST
├── Entity/
│   ├── Organization.php
│   ├── User.php
│   ├── Membership.php     # User <-> Organization + role
│   ├── BankAccount.php
│   ├── Transaction.php
│   ├── Category.php
│   ├── ImportJob.php
│   └── Statement.php      # Relevé importé
├── Repository/
├── Service/
│   ├── Import/
│   │   ├── CsvImporter.php
│   │   ├── PdfImporter.php
│   │   └── BankParser/    # Un parser par banque
│   ├── Security/
│   │   └── PermissionVoter.php
│   └── Tenant/
│       └── OrganizationContext.php
├── Security/
│   ├── Voter/
│   └── OrganizationFilter.php
├── Message/               # DTOs Messenger
├── MessageHandler/        # Handlers async
├── Form/
└── Twig/
templates/
├── base.html.twig
├── _partials/
├── auth/
├── dashboard/
├── account/
├── transaction/
└── import/
```

## Sécurité

- Mots de passe hachés en `bcrypt` (cost 13) ou `argon2id`.
- CSRF activé sur tous les formulaires.
- Headers de sécurité (CSP, HSTS, X-Frame-Options) via `nelmio/security-bundle`.
- IBAN stockés **masqués** en base (4 derniers chiffres seulement) ; valeur complète chiffrée si besoin via `phpseclib`.
- Audit log sur les actions sensibles (suppressions, exports, changements de rôles).
- Rate-limiting sur le login (5 tentatives / 15 min) via `RateLimiter`.

## Déploiement Docker (résumé)

Voir [`guidelines/docker-conventions.md`](../guidelines/docker-conventions.md) pour le détail.

Services :
- `viizia-nginx` (port 8180)
- `viizia-php` (FPM, pas exposé)
- `viizia-mysql` (port 8181)
- `viizia-phpmyadmin` (port 8182)
- `viizia-mailhog` (ports 8183 SMTP, 8184 UI)
- `viizia-worker` (Messenger, pas exposé)

Réseau : `viizia_net` isolé.
Volumes : `viizia_db`, `viizia_uploads`.
