- PHP 97.1%
- Twig 2%
- JavaScript 0.4%
- Shell 0.3%
- Dockerfile 0.2%
|
|
||
|---|---|---|
| .claude | ||
| .forgejo/workflows | ||
| .idea | ||
| assets | ||
| bin | ||
| config | ||
| docker/db | ||
| frankenphp | ||
| migrations | ||
| public | ||
| scripts | ||
| src | ||
| templates | ||
| tests | ||
| translations | ||
| .dockerignore | ||
| .editorconfig | ||
| .env | ||
| .env.test | ||
| .gitignore | ||
| .mcp.json | ||
| Caddyfile | ||
| CLAUDE.md | ||
| compose.override.yaml | ||
| compose.yaml | ||
| composer.json | ||
| composer.lock | ||
| dc | ||
| Dockerfile | ||
| exec-bin-console | ||
| exec-test-coverage | ||
| importmap.php | ||
| mago.toml | ||
| package.xml | ||
| phpstan.dist.neon | ||
| phpunit.dist.xml | ||
| README.md | ||
| report.xml | ||
| symfony.lock | ||
PhpLocalReviewer
PhpLocalReviewer est un outil d'analyse sémantique de code PHP piloté par l'IA (LLM via Ollama). Il ne se contente pas de vérifier la syntaxe : il analyse le sens et l'architecture du code (principes SOLID, clarté des noms, pertinence des tests).
Toutes les analyses s'exécutent en local — le code source ne quitte jamais la machine.
Prérequis
- Docker & Docker Compose : l'application (FrankenPHP) et la base (MySQL) sont conteneurisées.
- Ollama : pour faire tourner les modèles d'IA en local. Télécharger Ollama
- Git : pour cloner le projet.
- PHP 8.5+ (optionnel) : utile uniquement pour lancer
magooucomposerhors Docker.
Le script ./dc
Docker Compose ne lit que .env par défaut — il ignore .env.local, qui est pourtant la convention Symfony pour les surcharges locales.
Le script ./dc à la racine passe automatiquement les deux fichiers :
./dc up -d # = docker compose --env-file .env [--env-file .env.local] up -d
./dc exec php ... # idem pour exec, run, down, etc.
Toujours utiliser
./dcà la place dedocker composeafin que.env.localsoit pris en compte à l'interpolation du compose.
Installation rapide
-
Cloner le projet et démarrer Docker.
-
Télécharger un modèle Ollama :
ollama pull ministral-3:14b -
Créer un
.env.localà la racine :OLLAMA_BASE_URL=http://host.docker.internal:11434 EXTERNAL_PROJECTS_PATH=/chemin/vers/votre/projetSur Linux, Ollama doit écouter sur toutes les interfaces ou utiliser l'IP de
docker0. -
Démarrer les services :
./dc up -dLe schéma de base est initialisé automatiquement au démarrage.
-
Vérifier l'accès : https://localhost
Analyser des projets externes
Définissez EXTERNAL_PROJECTS_PATH dans .env.local, puis relancez ./dc up -d. Toutes les analyses ciblent exclusivement ce dossier (monté dans le conteneur à /app/external-projects).
Revues de code (CLI)
Chaque commande analyse un fichier ou un répertoire selon un principe SOLID ou une dimension qualité.
| Commande | Principe | Description |
|---|---|---|
app:review:syntaxe |
Syntaxe & Nommage | Clarté des noms, conventions, cohérence linguistique |
app:review:srp |
SRP | Responsabilité unique |
app:review:ocp |
OCP | Ouvert/fermé |
app:review:liskov |
LSP | Substitution de Liskov |
app:review:isp |
ISP | Ségrégation des interfaces |
app:review:dip |
DIP | Inversion des dépendances |
app:review:tu |
Tests Unitaires | Qualité et couverture des tests |
app:review:all |
Tous | Lance toutes les revues ci-dessus en chaîne |
app:review:branch |
Tous (filtré) | Revue limitée aux .php modifiés sur la branche courante vs develop |
Lancer une analyse :
# Tout external-projects (aucun argument)
./dc exec php bin/console app:review:srp
# Sous-dossier ou fichier (chemin relatif à external-projects)
./dc exec php bin/console app:review:srp src
./dc exec php bin/console app:review:srp src/Service/Foo.php
Options communes des commandes mono-type :
| Option | Description |
|---|---|
--format=console|json |
Format de sortie (défaut console) |
--fail-on=N |
Retourne un code non-zéro si total d'erreurs >= N (0 = désactivé) |
--no-cache |
Ignore le cache SHA-1 en lecture (le nouveau rapport est tout de même persisté) |
Options de app:review:branch :
| Option | Description |
|---|---|
--types=srp,ocp,… |
Sous-ensemble des types à exécuter (défaut : tous) |
--with-tests |
Inclure aussi les fichiers de tests (défaut : exclus) |
Toutes les analyses sont persistées en base. Si le fichier n'a pas changé depuis la dernière analyse (même hash SHA-1 + même empreinte prompts/modèle), le rapport en cache est retourné sans appel LLM.
Attention TU : si vous ne modifiez que le fichier de test, le SHA-1 du code source reste identique et l'analyse n'est pas relancée.
Stratégie multi-appels (sub-prompts)
Toutes les revues (Syntaxe, TU et SOLID) effectuent 3 appels LLM séquentiels, chacun ciblé sur un angle précis. Les critères retournés sont fusionnés dans un unique rapport.
| Type | Sub-prompt 1 | Sub-prompt 2 | Sub-prompt 3 |
|---|---|---|---|
| Syntaxe | Clarté du nommage | Conventions du langage | Cohérence de la documentation |
| TU | Présence & mapping | Branches & cas limites | Isolation & qualité |
| SRP | Densité des méthodes publiques | Cohésion interne | Couplage des responsabilités |
| OCP | Fermeture aux modifications | Ouverture aux extensions | Patterns de conception |
| LISKOV | Compatibilité des signatures | Invariants | Polymorphisme |
| ISP | Interfaces larges | Implémentations vides | Injection excessive |
| DIP | Abstractions au constructeur | Instanciation dans les méthodes | Séparation des couches |
Les fichiers de prompt se trouvent dans config/prompts/<type>/01_*.md, 02_*.md, 03_*.md. Ils sont découverts automatiquement par convention — aucune variable d'environnement par prompt n'est nécessaire.
Contexte de revue enrichi
Avant chaque appel LLM, le système construit un ReviewContext enrichi par fichier. L'enrichissement est piloté par AnalysisType::enrichments() (qui renvoie un ContextEnrichmentFlags), ce qui évite de charger les données inutiles selon le type :
| Donnée | Source | Activée par |
|---|---|---|
| Interfaces implémentées (source) | PHP-Parser + grep | Toutes les revues |
| Classe parente (source) | PHP-Parser + grep | Toutes les revues |
| Callers (fichiers qui utilisent la classe) | grep -rl |
SOLID uniquement |
Diff git (git diff HEAD) |
git diff |
SOLID uniquement |
Fichier de test associé (*Test.php) |
Convention de nommage | TU uniquement |
Pour la revue TU, si aucun fichier de test n'est trouvé, un rapport d'erreur est retourné immédiatement sans appel LLM.
Filtres de fichiers par type
app:review:branch (et le runner batch) applique un filtre par AnalysisType avant l'appel LLM :
| Type | Filtre | Effet |
|---|---|---|
SYNTAX |
AllPhpFilesFilter |
Tous les .php lisibles |
SRP/OCP/LISKOV/ISP/DIP |
ConcreteClassFileFilter |
Classes concrètes uniquement (pas les interfaces, traits, enums) |
TU |
NonTestFileFilter |
Fichiers de code source (exclut les *Test.php) |
Initialisation de branche (CLI)
La commande app:init:branch crée interactivement une branche git typée depuis develop, avec génération optionnelle d'un document de spec.
./dc exec php bin/console app:init:branch [--spec] [--no-llm] [--base=develop]
| Option | Description |
|---|---|
--spec |
Génère un document de spécification pour la branche |
--no-llm |
Avec --spec : utilise un template statique au lieu du LLM |
--base=<branche> |
Branche de base (défaut : develop) |
Types disponibles : feature, fix, hotfix, chore
Le wizard demande le type, la référence de ticket (optionnelle) et une description courte qui devient le slug. Avec --spec, deux questions supplémentaires (contexte et critères d'acceptance) permettent de générer un document de spec.
Le fichier produit est écrit dans external-projects/docs/specs/<YYYY-MM-DD>-<branch-name>.md.
Analyse de projet (init:context)
La commande app:init:context analyse le projet présent dans external-projects/ et génère brain/storm.md à sa racine — un document de synthèse architectural produit par un pipeline LLM multi-étapes.
./dc exec php bin/console app:init:context
Le fichier généré (brain/storm.md) est non versionné : c'est un artefact de travail à relire avant tout commit.
Serveur MCP
PhpLocalReviewer expose un serveur MCP (Model Context Protocol) utilisable par Claude Code ou tout client compatible. Il est configuré dans .mcp.json à la racine du projet.
Les conteneurs doivent être démarrés (
./dc up -d) avant utilisation.
Démarrage automatique (via .mcp.json) :
{
"mcpServers": {
"php-local-reviewer": {
"type": "stdio",
"command": "docker",
"args": ["compose", "exec", "-T", "php", "bin/console", "mcp:server:start"]
}
}
}
Outils MCP exposés (un par AnalysisType, plus utilitaires) :
| Outil | Description |
|---|---|
syntax-review |
Analyse syntaxe & nommage |
srp-review |
Analyse SRP |
ocp-review |
Analyse OCP |
liskov-review |
Analyse LSP |
isp-review |
Analyse ISP |
dip-review |
Analyse DIP |
tu-review |
Qualité des tests |
get-analysis-report |
Récupère le dernier rapport stocké pour un fichier |
bash-exec |
Exécute une commande de la liste autorisée (ALLOWED_BASH_COMMANDS) |
ollama-chat |
Chat direct avec Ollama (modèle + prompt + system) |
Les handlers de revue sont enregistrés automatiquement à la compilation par ReviewHandlerCompilerPass (une instance de McpReviewHandler par AnalysisType). Le nom de l'outil est dérivé de la valeur de l'enum : strtolower($type->value) . '-review'.
Qualité du code
Le projet utilise Mago pour le lint, le format, l'analyse statique et le guard architectural.
# Vérification complète
./dc exec php scripts/mago-check.sh
# Sortie détaillée
./dc exec php scripts/mago-check.sh true
Les règles de dépendance entre couches (Domain → Application → Infrastructure) sont définies dans mago.toml.
Tests
# Tous les tests
./dc exec php scripts/run-tests.sh
# Un fichier précis
./dc exec php vendor/bin/phpunit tests/Unit/Infrastructure/Adapter/Serialization/AnalysisReportDeserializerTest.php
# Tests d'intégration uniquement
./dc exec php vendor/bin/phpunit tests/Integration/
Les tests d'intégration (tests/Integration/) exercent les commandes de bout en bout, sans appel réseau. La partie LLM est remplacée par un stub et la base de données par SQLite en mémoire.
Problème : Unable to create the "cache" directory
Symptôme lors de l'exécution des tests hors de Docker :
RuntimeException: Unable to create the "cache" directory (…/var/cache/test).
Cause : var/ a été créé par Docker (root) et n'est pas accessible en écriture par l'utilisateur local.
Solution — corriger les permissions via Docker :
./dc run --rm php chown -R $(id -u):$(id -g) .
Interface d'administration
Accédez à https://localhost/admin pour consulter les rapports d'analyse, les KPI et les métriques.
Architecture
Le projet suit une architecture hexagonale stricte, vérifiée par mago guard en CI.
| Couche | Dépendances autorisées |
|---|---|
Domain |
PHP natif + Psl uniquement |
Application |
Domain + Symfony + PSR + PhpParser |
Infrastructure |
Domain + Application + tout le reste |
Composants principaux
src/Domain/
Entity/AnalysisReport— agrégat principal (critères, statut,fileHash, type)Entity/LLMConfig/LLMConfigInterface,LLMParameters,LLMMetric— configuration LLMEnum/AnalysisType—SYNTAX,TU,SRP,OCP,LISKOV,ISP,DIP; exposeenrichments()qui pilote l'enrichissement de contexteEnum/AnalysisStatus—PENDING,SUCCESS,FAILURE, …Enum/CacheMode—READ(défaut) ouBYPASS(--no-cache)Enum/BranchType—feature,fix,hotfix,choreEnum/BranchSpecGeneratorKind—LLMouTEMPLATEConstants/CharConstants,Constants/ProjectConstants,Constants/BranchConstantes,Constants/ReviewLabels,Constants/TextLines— constantes partagéesValueObject/ReviewContext— contexte enrichi (filename, sourceCode, runName, callbacks lazy,cacheMode,cacheFingerprint,fileHashcalculé)ValueObject/ContextEnrichmentFlags— quels enrichissements charger pour un typeValueObject/BranchName— nom de branche typé (type + ticket + slug)ValueObject/Criterion,ValueObject/Problem— value objects pour les findingsReview/ReviewStrategyInterface+AbstractMultiCallReviewStrategy,MultiCallReviewStrategy,TuReviewStrategy,ReviewStrategyContextProvider/— ports persistance/FS (AnalysisReportProvider,ProjectFileSystemProvider/Lister/Writer,CodeReviewerProvider,CacheFingerprintProviderInterface)Stats/DashboardStats,Stats/DashboardStatsProviderInterface,Stats/RunFilter,Stats/RunFilterKind,Stats/RunNameListerInterface
src/Application/
UseCase/Review/CodeReviewOrchestratorContext— cache SHA-1+fingerprint, délègue à la stratégie résolue, persisteUseCase/Review/FileReviewRunner— itère les fichiers, construit le contexte, dispatche vers unFileReviewCollectorInterfaceUseCase/Review/ReviewBatchRunner— orchestre plusieursAnalysisTypesur un même set de fichiers en appliquant les filtres par typeUseCase/Review/RunAllSummary— agrégation des compteurs (success/error/skipped) pourapp:review:all/branchUseCase/Branch/CreateBranchOrchestratorContext— création interactive d'une branche typée + spec optionnelleUseCase/InitContext/InitContextOrchestratorContext— pipeline LLM multi-étapes →brain/storm.mdPort/— interfaces ports (BashCommandRunnerInterface,BranchChangedFilesProviderInterface,FileDiffProviderInterface,FileFilterProviderInterface,FileReviewCollectorInterface,GitBranchManagerInterface,InheritanceResolverInterface,CallerLocatorInterface,TestFileResolverInterface,OllamaChatAdapterInterface,ProjectStructureAnalyzerInterface,ReviewContextBuilderInterface,RunNameProviderInterface,ShellTokenizerInterface, etc.)DTO/CreateBranchInput,DTO/BranchSpecInputService/BranchSpecGeneratorSelector— sélectionneLLMouTEMPLATEselonBranchSpecGeneratorKindService/Prompt/TemplateRenderer— rendu de templates{{placeholder}}Service/InitContext/InitPromptGraphValidator— valide les dépendances du DAG de prompts init-context
src/Infrastructure/
Adapter/LLM/AiCodeReviewer— implémenteCodeReviewerProvider; orchestreOllamaInvoker,ReportAssembler,ReviewerErrorHandlerAdapter/LLM/OllamaInvoker(+OllamaMessageBagFactory,LLMOptionsBuilder,HttpClientPlatformExceptionClassifier) — couche d'appel OllamaAdapter/LLM/ReviewPromptBuilder— assemble le prompt depuis unReviewContextAdapter/LLM/OllamaChatAdapter— chat Ollama simple (implémenteOllamaChatAdapterInterface)Adapter/Serialization/AnalysisReportDeserializer,MetricReportDeserializer,ManualAnalysisReportMapper,AliasResolverAdapter/Persistence/Doctrine/DoctrineAnalysisReportRepository— implémentation deAnalysisReportProviderAdapter/Persistence/Doctrine/DoctrineDashboardStatsProvider+ helpersStats/(ByTypeBreakdownBuilder,DailyTrendBuilder,RunFilterClauseBuilder,StatusBreakdownClassifier)Adapter/Persistence/Doctrine/DoctrineRunNameLister— liste lesrunNameexistants pour les filtres dashboardAdapter/Persistence/Doctrine/Type/CriteriaType,Type/CriterionHydrator— type Doctrine custom pour les critèresAdapter/FileSystem/SymfonyProjectFileSystem— implémenteProjectFileSystemProvider/Lister/WriterAdapter/Bash/ShellBashCommandRunner,ShellTokenizerBridge/Symfony/Command/AbstractReviewCommand— base des commandes mono-type (options--format,--fail-on,--no-cache)Bridge/Symfony/Command/ReviewCommand— implémentation générique enregistrée 7 fois (une parAnalysisType)Bridge/Symfony/Command/ReviewAllTypeCommand(app:review:all) — enchaîne tous les typesBridge/Symfony/Command/ReviewBranchCommand(app:review:branch) — revue limitée aux fichiers modifiés vsdevelopBridge/Symfony/Command/InitBranchCommand(app:init:branch)Bridge/Symfony/Command/InitContextCommand(app:init:context)Bridge/Symfony/Command/StartMcpServerCommand(mcp:server:start) — démarre le serveur MCPBridge/Symfony/Command/{Console,Json,Composite,}FileReviewCollector— collecteurs de résultatsBridge/Symfony/Command/CommandFailure— helper de sortie d'erreur uniformeBridge/Symfony/Mcp/McpReviewHandler— handler générique enregistré par compiler pass (un service parAnalysisType)Bridge/Symfony/Mcp/McpReportHandler,McpBashCommandHandler,McpOllamaChatHandler,McpFileReviewCollectorBridge/Symfony/DependencyInjection/ReviewHandlerCompilerPass— crée et câble une instance deMcpReviewHandlerparAnalysisTypeBridge/Symfony/SummaryTableRenderer— tableau récap pourapp:review:all/branchBridge/Symfony/Controller/Admin/— EasyAdmin CRUD (AnalysisReportCrudController,LLMMetricCrudController,DashboardController)Bridge/Symfony/Controller/HomeController,DashboardControllerBridge/Symfony/Twig/Components/KpiCard,RecentAnalysesTableBridge/Symfony/Form/RunFilterType,Bridge/Symfony/Chart/DashboardChartFactoryService/Prompt/PromptLoader(+PromptFileLister,LoadedPrompt) — chargeconfig/prompts/<principe>/*.mdService/Prompt/InitPromptLoader— charge les prompts init-context avec ordre de dépendancesService/Review/ReviewStrategyFactory—createMultiCall(principe, type)/createTu(principe)Service/Review/ReviewContextBuilder— orchestre l'enrichissement (lazy : interfaces, parent, callers, diff, test)Service/Review/ReviewStrategyFingerprintProvider— empreinte du couple (modèle + prompts) injectée dansReviewContext::$cacheFingerprint; toute évolution invalide le cacheService/Review/TimestampRunNameProvider— génère lerunName(horodatage + suffixe aléatoire) partagé par tous les fichiers d'un runService/FileSystem/AnalysisFileResolver+FileReviewPipeline— résolution et lecture des.phpService/FileSystem/{AllPhpFiles,ConcreteClass,NonTest}FileFilter+AnalysisTypeFileFilterContext— filtres parAnalysisTypeService/Git/GitFileDiffProvider,GitBranchManager,GitBranchChangedFilesProviderService/PhpAnalysis/PhpInheritanceResolver,PhpSymbolFileLocator,ProjectStructureAnalyzer(+Builder/,Parser/)Service/Project/GrepCallerLocator,UnitTestFileResolver,ProjectRootResolverService/BranchSpec/LlmBranchSpecGenerator,TemplateBranchSpecGenerator,ProjectDirBranchSpecWriter(écrit dansexternal-projects/docs/specs/)Service/InitContext/ClassItemFormatterService/Stats/RunFilterResolverService/Json/ExtractorJson,ReparatorJson— nettoyage de la sortie LLM
Ajouter un nouveau type de revue
- Ajouter un case à
Domain/Enum/AnalysisType(et son cas dansenrichments()) - Créer les sub-prompts
config/prompts/<type>/01_*.md,02_*.md,03_*.md - Enregistrer
app.review_strategy.<name>dansservices.yaml(viaReviewStrategyFactory) et l'ajouter à la map deReviewStrategyContext - Enregistrer
app.review_command.<name>dansservices.yaml(laReviewCommandgénérique) - Ajouter une entrée au
AnalysisTypeFileFilterContext(filtre par type) - Rien à faire pour MCP :
ReviewHandlerCompilerPasscrée automatiquement unMcpReviewHandlerpour chaque case d'AnalysisType
Variables d'environnement
| Variable | Description | Valeur par défaut |
|---|---|---|
DATABASE_URL |
DSN MySQL | mysql://app:!ChangeMe!@database:3306/app |
OLLAMA_BASE_URL |
URL de l'API Ollama | http://localhost:11434 |
OLLAMA_MODEL |
Modèle Ollama partagé par toutes les stratégies de revue | ministral-3:14b |
EXTERNAL_PROJECTS_PATH |
Chemin hôte monté dans /app/external-projects |
(vide) |
ALLOWED_BASH_COMMANDS |
Commandes bash autorisées par le serveur MCP | ls,cat,grep,find |
LLM_SEED |
Seed fixe optionnelle (sortie déterministe) | (vide) |
TOKEN_PRICE_PER_1M |
Prix par million de tokens (affichage dashboard) | 1 |
Les sub-prompts ne sont pas configurés via env — ils sont découverts par convention dans config/prompts/<type>/*.md.
Stack technique
- PHP 8.5+ avec property hooks
- FrankenPHP — serveur d'application
- Symfony 8 avec Symfony AI Bundle
- EasyAdmin 5 — interface d'administration
- Doctrine ORM avec mappings XML (
config/orm/domain/) - Mago — lint, format, analyse statique et guard architectural
- Ollama — inférence LLM locale