224 lines
8.5 KiB
Markdown
224 lines
8.5 KiB
Markdown
# Architecture
|
|
|
|
Slim Blog suit désormais une organisation verticale légère par domaine, avec quatre zones récurrentes :
|
|
|
|
- `Domain` : règles métier simples, politiques, objets de valeur utilitaires
|
|
- `Application` : orchestration des cas d'usage
|
|
- `Infrastructure` : PDO, filesystem local, services techniques concrets
|
|
- `Http` : contrôleurs et adaptation requête/réponse
|
|
|
|
Cette structure garde les mêmes fonctionnalités qu'au départ, mais réduit la dette de transition : routes, conteneur DI et tests pointent désormais directement vers les implémentations finales.
|
|
|
|
## Vue d'ensemble
|
|
|
|
| Domaine | Rôle principal | Dépendances métier |
|
|
|-------------|----------------|--------------------|
|
|
| `Auth/` | Connexion, sessions, réinitialisation de mot de passe | `User/` |
|
|
| `Category/` | Catégories éditoriales | — |
|
|
| `Media/` | Upload, stockage local, usage dans les articles | `Post/` pour le comptage d'usage |
|
|
| `Post/` | Articles, recherche, RSS | `Category/` en présentation |
|
|
| `User/` | Comptes, rôles, création/suppression | — |
|
|
| `Shared/` | Infrastructure transverse | — |
|
|
|
|
## Structure cible d'un domaine
|
|
|
|
```text
|
|
src/MonDomaine/
|
|
├── Application/
|
|
│ └── MonDomaineApplicationService.php
|
|
├── Domain/
|
|
│ └── RegleOuPolitique.php
|
|
├── Http/
|
|
│ └── MonDomaineController.php
|
|
├── Infrastructure/
|
|
│ └── PdoMonDomaineRepository.php
|
|
├── MonEntite.php
|
|
├── MonDomaineRepositoryInterface.php
|
|
└── MonDomaineServiceInterface.php
|
|
```
|
|
|
|
Principes appliqués dans le projet :
|
|
|
|
1. Les contrôleurs HTTP dépendent d'interfaces de service (`*ServiceInterface`).
|
|
2. Les services applicatifs dépendent d'interfaces de repository et, si nécessaire, de ports techniques.
|
|
3. Les implémentations concrètes vivent en `Infrastructure/` et sont câblées dans `config/container.php`.
|
|
4. `Shared/` reste réservé au transverse : bootstrap, pagination, session, mail, sanitation HTML, utilitaires communs.
|
|
|
|
## Dépendances inter-domaines
|
|
|
|
Les dépendances entre domaines métier restent limitées et unidirectionnelles :
|
|
|
|
- **`Auth/ → User/`** : `AuthApplicationService` et `PasswordResetApplicationService` lisent les comptes via `UserRepositoryInterface`.
|
|
- **`Post/ → Category/`** : `Post\Http\PostController` injecte `CategoryServiceInterface` pour alimenter les formulaires et filtres.
|
|
- **`Media/ → Post/`** : `MediaApplicationService` utilise `PostRepositoryInterface` pour vérifier l'usage d'un média avant suppression.
|
|
|
|
Aucun domaine ne dépend d'une implémentation concrète d'un autre domaine.
|
|
|
|
## Bindings DI principaux
|
|
|
|
| Interface | Implémentation |
|
|
|-----------|----------------|
|
|
| `AuthServiceInterface` | `Auth\Application\AuthApplicationService` |
|
|
| `PasswordResetServiceInterface` | `Auth\Application\PasswordResetApplicationService` |
|
|
| `LoginAttemptRepositoryInterface` | `Auth\Infrastructure\PdoLoginAttemptRepository` |
|
|
| `PasswordResetRepositoryInterface` | `Auth\Infrastructure\PdoPasswordResetRepository` |
|
|
| `CategoryServiceInterface` | `Category\Application\CategoryApplicationService` |
|
|
| `CategoryRepositoryInterface` | `Category\Infrastructure\PdoCategoryRepository` |
|
|
| `MediaServiceInterface` | `Media\Application\MediaApplicationService` |
|
|
| `MediaRepositoryInterface` | `Media\Infrastructure\PdoMediaRepository` |
|
|
| `MediaStorageInterface` | `Media\Infrastructure\LocalMediaStorage` |
|
|
| `PostServiceInterface` | `Post\Application\PostApplicationService` |
|
|
| `PostRepositoryInterface` | `Post\Infrastructure\PdoPostRepository` |
|
|
| `UserServiceInterface` | `User\Application\UserApplicationService` |
|
|
| `UserRepositoryInterface` | `User\Infrastructure\PdoUserRepository` |
|
|
|
|
## Schéma de base de données
|
|
|
|
```text
|
|
users
|
|
├── id INTEGER PK AUTOINCREMENT
|
|
├── username TEXT UNIQUE NOT NULL
|
|
├── email TEXT UNIQUE NOT NULL
|
|
├── password_hash TEXT NOT NULL
|
|
├── role TEXT NOT NULL DEFAULT 'user'
|
|
└── created_at DATETIME
|
|
|
|
categories
|
|
├── id INTEGER PK AUTOINCREMENT
|
|
├── name TEXT UNIQUE NOT NULL
|
|
└── slug TEXT UNIQUE NOT NULL
|
|
|
|
posts
|
|
├── id INTEGER PK AUTOINCREMENT
|
|
├── title TEXT NOT NULL
|
|
├── content TEXT NOT NULL
|
|
├── slug TEXT UNIQUE NOT NULL
|
|
├── author_id INTEGER → users(id) ON DELETE SET NULL
|
|
├── category_id INTEGER → categories(id) ON DELETE SET NULL
|
|
├── created_at DATETIME
|
|
└── updated_at DATETIME
|
|
|
|
media
|
|
├── id INTEGER PK AUTOINCREMENT
|
|
├── filename TEXT NOT NULL
|
|
├── url TEXT NOT NULL
|
|
├── hash TEXT NOT NULL
|
|
├── user_id INTEGER → users(id) ON DELETE SET NULL
|
|
└── created_at DATETIME
|
|
|
|
password_resets
|
|
├── id INTEGER PK AUTOINCREMENT
|
|
├── user_id INTEGER → users(id) ON DELETE CASCADE
|
|
├── token_hash TEXT UNIQUE NOT NULL
|
|
├── expires_at DATETIME NOT NULL
|
|
├── used_at DATETIME DEFAULT NULL
|
|
└── created_at DATETIME
|
|
|
|
login_attempts
|
|
├── ip TEXT PRIMARY KEY
|
|
├── attempts INTEGER NOT NULL DEFAULT 0
|
|
├── locked_until TEXT DEFAULT NULL
|
|
└── updated_at TEXT NOT NULL
|
|
```
|
|
|
|
### Index utiles
|
|
|
|
| Index | Colonne(s) | Usage |
|
|
|-------|------------|-------|
|
|
| `idx_posts_author_id` | `posts(author_id)` | listes admin / recherche auteur |
|
|
| `idx_media_user_id` | `media(user_id)` | galerie média par utilisateur |
|
|
| `idx_media_hash_user_id` | `media(hash, user_id)` | déduplication par utilisateur |
|
|
|
|
## Flux principaux
|
|
|
|
### Création / mise à jour d'un article
|
|
|
|
1. `Post\Http\PostController` lit la requête HTTP.
|
|
2. `PostApplicationService` valide, sanitise et génère un slug unique.
|
|
3. `PdoPostRepository` persiste en base.
|
|
4. Twig rend la réponse ou le contrôleur redirige avec un flash.
|
|
|
|
### Upload d'un média
|
|
|
|
1. `Media\Http\MediaController` reçoit le fichier et l'utilisateur courant.
|
|
2. `MediaApplicationService` valide le type et la taille, prépare l'upload.
|
|
3. `LocalMediaStorage` convertit l'image en WebP et écrit le fichier.
|
|
4. `PdoMediaRepository` persiste l'enregistrement.
|
|
5. La déduplication se fait par couple `(hash, user_id)`.
|
|
|
|
### Réinitialisation de mot de passe
|
|
|
|
1. `PasswordResetController` applique le rate limit par IP.
|
|
2. `PasswordResetApplicationService` invalide les anciens tokens actifs, crée un nouveau token hashé et envoie l'e-mail.
|
|
3. La consommation du token est atomique via `PdoPasswordResetRepository`.
|
|
|
|
## Arborescence actuelle
|
|
|
|
```text
|
|
src/
|
|
├── Auth/
|
|
│ ├── Application/
|
|
│ ├── Domain/
|
|
│ ├── Http/
|
|
│ ├── Infrastructure/
|
|
│ ├── Middleware/
|
|
│ ├── AuthServiceInterface.php
|
|
│ ├── LoginAttemptRepositoryInterface.php
|
|
│ ├── PasswordResetRepositoryInterface.php
|
|
│ └── PasswordResetServiceInterface.php
|
|
├── Category/
|
|
│ ├── Application/
|
|
│ ├── Domain/
|
|
│ ├── Http/
|
|
│ ├── Infrastructure/
|
|
│ ├── Category.php
|
|
│ ├── CategoryRepositoryInterface.php
|
|
│ └── CategoryServiceInterface.php
|
|
├── Media/
|
|
│ ├── Application/
|
|
│ ├── Domain/
|
|
│ ├── Exception/
|
|
│ ├── Http/
|
|
│ ├── Infrastructure/
|
|
│ ├── Media.php
|
|
│ ├── MediaRepositoryInterface.php
|
|
│ └── MediaServiceInterface.php
|
|
├── Post/
|
|
│ ├── Application/
|
|
│ ├── Domain/
|
|
│ ├── Http/
|
|
│ ├── Infrastructure/
|
|
│ ├── Post.php
|
|
│ ├── PostRepositoryInterface.php
|
|
│ └── PostServiceInterface.php
|
|
├── Shared/
|
|
│ ├── Database/
|
|
│ ├── Exception/
|
|
│ ├── Extension/
|
|
│ ├── Html/
|
|
│ ├── Http/
|
|
│ ├── Mail/
|
|
│ ├── Pagination/
|
|
│ ├── Util/
|
|
│ ├── Bootstrap.php
|
|
│ ├── Config.php
|
|
│ └── Routes.php
|
|
└── User/
|
|
├── Application/
|
|
├── Domain/
|
|
├── Exception/
|
|
├── Http/
|
|
├── Infrastructure/
|
|
├── User.php
|
|
├── UserRepositoryInterface.php
|
|
└── UserServiceInterface.php
|
|
```
|
|
|
|
## Règles de maintenance
|
|
|
|
- Ajouter une nouvelle fonctionnalité dans le domaine concerné avant de créer un nouveau dossier partagé.
|
|
- Préférer une nouvelle classe d'application ciblée à une extension d'un contrôleur trop gros.
|
|
- Garder les interfaces aux frontières utiles : repository, session, mail, storage.
|
|
- Éviter les wrappers de compatibilité temporaires une fois la migration terminée.
|
|
- Valider chaque lot avec PHPUnit et PHPStan avant de passer au suivant.
|