""" cv_analyzer_v2.py =================== Analyse IA des données du formulaire CV (WhileResume V2). Contrairement à `analyze_cv.py` (qui analyse un PDF uploadé via OCR), ce script analyse directement le JSON du formulaire (colonne `cv_data` de la table `cvs_candidates`) et produit une analyse riche et structurée destinée au dashboard candidat. Usage : python cv_analyzer_v2.py Le script : 1. Récupère `cv_data` directement depuis MySQL via selectOneCv(). 2. Marque l'analyse comme "pending" via updateCvV2(). 3. Appelle OpenAI (gpt-5-nano, même pattern que analyze_cv.py). 4. Stocke le résultat JSON dans la colonne `form_ai_analysis` via updateCvV2() (helper dédié V2 dans model.py, whitelist isolée). 5. Marque l'analyse comme "done" et écrit le timestamp. En cas d'erreur : `form_ai_analysis_status` = 'failed' et sortie code 1. AVANTAGES par rapport à un passage de fichier JSON en argument : - Aucune valse de fichiers temporaires côté PHP. - Source de vérité unique (la colonne `cv_data` en BDD). - Le script voit toujours la DERNIÈRE version du formulaire au moment de son exécution, même si plusieurs analyses s'enchaînent. """ import os import sys import json import time import re import threading from dotenv import load_dotenv from openai import OpenAI # Helpers DB partagés avec analyze_cv.py import db_connection # noqa: F401 import model # noqa: F401 from model import selectOneCv, updateCvV2 # NOTE : on n'utilise PAS unvalideAnalyseCV/valideAnalyseCV ici, car ces # fonctions flippent le flag global `analyse` (0/1) partagé avec l'ancien # analyseur PDF (`analyze_cv.py`). Si on les appelait, le "Résumé IA" de # l'ancien flux disparaîtrait du dashboard pendant toute la durée de # l'analyse formulaire, ce qui est indésirable. On s'appuie uniquement # sur la nouvelle colonne `form_ai_analysis_status`. load_dotenv() # ───────────────────────────────────────────── # TIMER # ───────────────────────────────────────────── start_time = time.time() stop_timer = False def timer_display(): while not stop_timer: elapsed = time.time() - start_time print(f"\r⏱️ Temps écoulé : {elapsed:.1f}s", end="", flush=True) time.sleep(0.1) timer_thread = threading.Thread(target=timer_display, daemon=True) timer_thread.start() def fail(message: str, candidate_id: str = None, code: int = 1): global stop_timer stop_timer = True time.sleep(0.2) print() print(f"❌ {message}") if candidate_id: try: updateCvV2(candidate_id, "form_ai_analysis_status", "failed") except Exception as e: print(f"⚠️ Impossible de marquer 'failed' : {e}") sys.exit(code) # ───────────────────────────────────────────── # VALIDATION DES ARGUMENTS # ───────────────────────────────────────────── if len(sys.argv) < 2: fail("Usage: python cv_analyzer_v2.py ") candidate_id = sys.argv[1].strip() if not candidate_id: fail("L'argument candidate_id est vide.") if not candidate_id.isdigit(): fail("candidate_id doit être un nombre.") # ───────────────────────────────────────────── # CHARGEMENT DU CANDIDAT DEPUIS MySQL # ───────────────────────────────────────────── cv_selection = selectOneCv(candidate_id) if not cv_selection: fail(f"Aucun candidat trouvé pour l'id '{candidate_id}'.") print(f"✅ Candidat trouvé : {candidate_id}") # Récupération du JSON du formulaire directement depuis la colonne cv_data. # selectOneCv utilise un DictCursor (cf. analyze_cv.py qui accède par clé). try: cv_data_raw = cv_selection.get("cv_data") if isinstance(cv_selection, dict) else None except Exception as e: fail(f"Impossible de lire cv_data depuis la ligne candidat : {e}", candidate_id) if not cv_data_raw: fail( f"La colonne cv_data est vide pour le candidat {candidate_id}. " "Le formulaire n'a jamais été validé ou la colonne n'a pas été flushée " "avant le lancement du script.", candidate_id, ) try: cv_data = json.loads(cv_data_raw) except json.JSONDecodeError as e: fail(f"cv_data en BDD n'est pas un JSON valide : {e}", candidate_id) print(f"📄 cv_data chargé depuis MySQL ({len(cv_data_raw)} octets)") # Marquer l'analyse comme en cours (SANS toucher au flag global `analyse`) try: updateCvV2(candidate_id, "form_ai_analysis_status", "pending") except Exception as e: print(f"⚠️ Impossible de marquer 'pending' : {e}") # ───────────────────────────────────────────── # OPENAI CLIENT # ───────────────────────────────────────────── api_key = os.getenv("OPENAI_KEY") if not api_key: fail("Variable d'environnement OPENAI_KEY manquante.", candidate_id) client = OpenAI(api_key=api_key) # ───────────────────────────────────────────── # JSON CLEANER (repris de analyze_cv.py) # ───────────────────────────────────────────── def clean_and_parse_json(raw_result: str): cleaned = raw_result.strip() if "```" in cleaned: match = re.search(r"```(?:json)?\s*([\s\S]*?)\s*```", cleaned) if match: cleaned = match.group(1).strip() else: cleaned = cleaned.replace("```json", "").replace("```", "").strip() cleaned = re.sub(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]", " ", cleaned) def fix_newlines_in_strings(match): content = match.group(0) content = content.replace("\r\n", "\\n") content = content.replace("\n", "\\n") content = content.replace("\r", "\\n") content = content.replace("\t", " ") return content cleaned = re.sub(r'"(?:[^"\\]|\\.)*"', fix_newlines_in_strings, cleaned, flags=re.DOTALL) return json.loads(cleaned, strict=False) # ───────────────────────────────────────────── # PROMPT — spécialisé pour FORMULAIRE structuré # ───────────────────────────────────────────── cv_language = (cv_data.get("cv_language") or "fr").lower() SYSTEM_PROMPT = ( "Tu es un coach carrière et recruteur senior. Tu reçois les données structurées " "d'un formulaire CV (JSON). Tu ne dois PAS réinventer les informations : tu t'appuies " "STRICTEMENT sur les données fournies. Ton objectif : produire une analyse recruteur " "exploitable, ATS-friendly, et actionnable pour le candidat. " "Tu réponds UNIQUEMENT en JSON valide, sans markdown, sans texte autour." ) user_prompt = f"""Analyse les données de ce CV (formulaire WhileResume V2) et produit une analyse structurée. LANGUE PRINCIPALE DE SORTIE : {cv_language} (Tous les champs textuels doivent être écrits dans cette langue, sauf `ats_keywords` qui reste dans la langue du CV pour le matching.) DONNÉES DU FORMULAIRE (JSON) : ```json {json.dumps(cv_data, ensure_ascii=False, indent=2)} ``` INSTRUCTIONS : 1. Analyse l'expérience, les projets, les compétences et le parcours global. 2. Identifie les vrais points forts (pas de platitudes). 3. Détecte les axes d'amélioration concrets sur le CV lui-même (manques, incohérences, formulations faibles). 4. Estime un niveau de séniorité réel, pas celui revendiqué. 5. Propose des mots-clés ATS pertinents pour maximiser la visibilité en recherche recruteur. Retourne EXACTEMENT cette structure JSON (aucun champ en plus, aucun en moins) : {{ "language": "code langue ISO utilisé (fr, en, de, ...)", "executive_summary": "Résumé exécutif percutant de 60-100 mots, à la 3ème personne, destiné à un recruteur qui lit 10 secondes. Doit contenir : séniorité, domaine, 2-3 forces clés, aspiration.", "recruiter_pitch": "Pitch de vente candidat de 40-60 mots, ton professionnel, répond à la question : pourquoi recruter cette personne ?", "seniority_level": "junior | mid | senior | lead | principal", "seniority_justification": "1 phrase expliquant le niveau détecté à partir des expériences réelles.", "strengths": [ {{ "title": "Titre court (3-6 mots)", "detail": "1-2 phrases expliquant pourquoi c'est une force, preuves tirées du CV." }} ], "improvement_suggestions": [ {{ "area": "Zone concernée (ex: 'Section expériences')", "suggestion": "Conseil actionnable en 1-2 phrases." }} ], "ats_keywords": ["mot-clé1", "mot-clé2", "..."], "match_profile": {{ "ideal_roles": ["Intitulé de poste idéal 1", "Intitulé de poste idéal 2", "..."], "ideal_industries": ["Secteur 1", "Secteur 2"], "red_flags": ["Point de vigilance éventuel pour un recruteur, ou tableau vide"] }}, "tone_analysis": {{ "overall": "Adjectif décrivant le ton général (ex: 'technique et factuel', 'commercial et narratif')", "clarity_score": 0, "impact_score": 0, "ats_score": 0 }}, "recommended_next_steps": [ "Action concrète 1 pour améliorer l'employabilité", "Action concrète 2", "Action concrète 3" ] }} QUALITÉ : - Les scores (clarity_score, impact_score, ats_score) sont des entiers 0-100. - `strengths` : minimum 3, maximum 6 items. - `improvement_suggestions` : minimum 2, maximum 5 items. - `ats_keywords` : 8 à 20 mots-clés, courts, pertinents. - `recommended_next_steps` : 3 à 5 actions. - Interdit : phrases creuses, hallucinations, compliments gratuits. - Si une section du CV est vide : base-toi uniquement sur ce qui existe, ne comble pas. RÈGLES STRICTES DE FORMAT : - Aucun retour chariot à l'intérieur d'une valeur string JSON. - Aucun markdown, aucun ```json```. - Réponds UNIQUEMENT l'objet JSON.""" # ───────────────────────────────────────────── # APPEL OPENAI # ───────────────────────────────────────────── print("🤖 Appel OpenAI (gpt-5-nano)...") try: response = client.chat.completions.create( model="gpt-5-nano", response_format={"type": "json_object"}, messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_prompt}, ], max_completion_tokens=20000, ) except Exception as e: import traceback traceback.print_exc() fail(f"Erreur appel API OpenAI : {e}", candidate_id) print(f"\n🔍 Finish reason: {response.choices[0].finish_reason}") try: usage = response.usage print( f"🔍 Tokens — Reasoning: {usage.completion_tokens_details.reasoning_tokens}, " f"Completion: {usage.completion_tokens}, Total: {usage.total_tokens}" ) except Exception: pass result = response.choices[0].message.content if not result or not result.strip(): fail("Réponse OpenAI vide.", candidate_id) print("\n🧠 Résultat brut :") print(result) # ───────────────────────────────────────────── # PARSING & SAUVEGARDE # ───────────────────────────────────────────── try: json_data = clean_and_parse_json(result) except json.JSONDecodeError as e: print(f"\n⚠️ Erreur JSON : {e}") if hasattr(e, "pos") and e.pos: start = max(0, e.pos - 50) end = min(len(result), e.pos + 50) print(f"Contexte : ...{result[start:end]}...") fail("Impossible de parser la réponse OpenAI en JSON valide.", candidate_id) print("\n✅ JSON valide :") print(json.dumps(json_data, indent=2, ensure_ascii=False)) # Stockage : on garde TOUT l'objet dans une seule colonne JSON pour rester # souple et éviter 20 nouvelles colonnes. Le dashboard mobile parse côté client. try: payload = json.dumps(json_data, ensure_ascii=False) ok1 = updateCvV2(candidate_id, "form_ai_analysis", payload) ok2 = updateCvV2(candidate_id, "form_ai_analysis_status", "done") ok3 = updateCvV2( candidate_id, "form_ai_analysis_updated_at", time.strftime("%Y-%m-%d %H:%M:%S"), ) if not (ok1 and ok2 and ok3): fail("Échec de l'écriture en base.", candidate_id) except Exception as e: fail(f"Erreur lors de l'écriture en base : {e}", candidate_id) # NOTE : on ne touche PAS au flag global `analyse` via valideAnalyseCV() # afin de ne pas interférer avec l'ancien système d'analyse PDF. stop_timer = True time.sleep(0.2) print() total_time = time.time() - start_time print(f"\n✨ Analyse IA formulaire enregistrée pour le candidat {candidate_id}") print(f"⏱️ Temps total : {total_time:.2f}s")""" cv_analyzer_v2.py =================== Analyse IA des données du formulaire CV (WhileResume V2). Contrairement à `analyze_cv.py` (qui analyse un PDF uploadé via OCR), ce script analyse directement le JSON du formulaire (colonne `cv_data` de la table `cvs_candidates`) et produit une analyse riche et structurée destinée au dashboard candidat. Usage : python cv_analyzer_v2.py Le script : 1. Récupère `cv_data` directement depuis MySQL via selectOneCv(). 2. Marque l'analyse comme "pending" via updateCvV2(). 3. Appelle OpenAI (gpt-5-nano, même pattern que analyze_cv.py). 4. Stocke le résultat JSON dans la colonne `form_ai_analysis` via updateCvV2() (helper dédié V2 dans model.py, whitelist isolée). 5. Marque l'analyse comme "done" et écrit le timestamp. En cas d'erreur : `form_ai_analysis_status` = 'failed' et sortie code 1. AVANTAGES par rapport à un passage de fichier JSON en argument : - Aucune valse de fichiers temporaires côté PHP. - Source de vérité unique (la colonne `cv_data` en BDD). - Le script voit toujours la DERNIÈRE version du formulaire au moment de son exécution, même si plusieurs analyses s'enchaînent. """ import os import sys import json import time import re import threading from dotenv import load_dotenv from openai import OpenAI # Helpers DB partagés avec analyze_cv.py import db_connection # noqa: F401 import model # noqa: F401 from model import selectOneCv, updateCvV2 # NOTE : on n'utilise PAS unvalideAnalyseCV/valideAnalyseCV ici, car ces # fonctions flippent le flag global `analyse` (0/1) partagé avec l'ancien # analyseur PDF (`analyze_cv.py`). Si on les appelait, le "Résumé IA" de # l'ancien flux disparaîtrait du dashboard pendant toute la durée de # l'analyse formulaire, ce qui est indésirable. On s'appuie uniquement # sur la nouvelle colonne `form_ai_analysis_status`. load_dotenv() # ───────────────────────────────────────────── # TIMER # ───────────────────────────────────────────── start_time = time.time() stop_timer = False def timer_display(): while not stop_timer: elapsed = time.time() - start_time print(f"\r⏱️ Temps écoulé : {elapsed:.1f}s", end="", flush=True) time.sleep(0.1) timer_thread = threading.Thread(target=timer_display, daemon=True) timer_thread.start() def fail(message: str, candidate_id: str = None, code: int = 1): global stop_timer stop_timer = True time.sleep(0.2) print() print(f"❌ {message}") if candidate_id: try: updateCvV2(candidate_id, "form_ai_analysis_status", "failed") except Exception as e: print(f"⚠️ Impossible de marquer 'failed' : {e}") sys.exit(code) # ───────────────────────────────────────────── # VALIDATION DES ARGUMENTS # ───────────────────────────────────────────── if len(sys.argv) < 2: fail("Usage: python cv_analyzer_v2.py ") candidate_id = sys.argv[1].strip() if not candidate_id: fail("L'argument candidate_id est vide.") if not candidate_id.isdigit(): fail("candidate_id doit être un nombre.") # ───────────────────────────────────────────── # CHARGEMENT DU CANDIDAT DEPUIS MySQL # ───────────────────────────────────────────── cv_selection = selectOneCv(candidate_id) if not cv_selection: fail(f"Aucun candidat trouvé pour l'id '{candidate_id}'.") print(f"✅ Candidat trouvé : {candidate_id}") # Récupération du JSON du formulaire directement depuis la colonne cv_data. # selectOneCv utilise un DictCursor (cf. analyze_cv.py qui accède par clé). try: cv_data_raw = cv_selection.get("cv_data") if isinstance(cv_selection, dict) else None except Exception as e: fail(f"Impossible de lire cv_data depuis la ligne candidat : {e}", candidate_id) if not cv_data_raw: fail( f"La colonne cv_data est vide pour le candidat {candidate_id}. " "Le formulaire n'a jamais été validé ou la colonne n'a pas été flushée " "avant le lancement du script.", candidate_id, ) try: cv_data = json.loads(cv_data_raw) except json.JSONDecodeError as e: fail(f"cv_data en BDD n'est pas un JSON valide : {e}", candidate_id) print(f"📄 cv_data chargé depuis MySQL ({len(cv_data_raw)} octets)") # Marquer l'analyse comme en cours (SANS toucher au flag global `analyse`) try: updateCvV2(candidate_id, "form_ai_analysis_status", "pending") except Exception as e: print(f"⚠️ Impossible de marquer 'pending' : {e}") # ───────────────────────────────────────────── # OPENAI CLIENT # ───────────────────────────────────────────── api_key = os.getenv("OPENAI_KEY") if not api_key: fail("Variable d'environnement OPENAI_KEY manquante.", candidate_id) client = OpenAI(api_key=api_key) # ───────────────────────────────────────────── # JSON CLEANER (repris de analyze_cv.py) # ───────────────────────────────────────────── def clean_and_parse_json(raw_result: str): cleaned = raw_result.strip() if "```" in cleaned: match = re.search(r"```(?:json)?\s*([\s\S]*?)\s*```", cleaned) if match: cleaned = match.group(1).strip() else: cleaned = cleaned.replace("```json", "").replace("```", "").strip() cleaned = re.sub(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]", " ", cleaned) def fix_newlines_in_strings(match): content = match.group(0) content = content.replace("\r\n", "\\n") content = content.replace("\n", "\\n") content = content.replace("\r", "\\n") content = content.replace("\t", " ") return content cleaned = re.sub(r'"(?:[^"\\]|\\.)*"', fix_newlines_in_strings, cleaned, flags=re.DOTALL) return json.loads(cleaned, strict=False) # ───────────────────────────────────────────── # PROMPT — spécialisé pour FORMULAIRE structuré # ───────────────────────────────────────────── cv_language = (cv_data.get("cv_language") or "fr").lower() SYSTEM_PROMPT = ( "Tu es un coach carrière et recruteur senior. Tu reçois les données structurées " "d'un formulaire CV (JSON). Tu ne dois PAS réinventer les informations : tu t'appuies " "STRICTEMENT sur les données fournies. Ton objectif : produire une analyse recruteur " "exploitable, ATS-friendly, et actionnable pour le candidat. " "Tu réponds UNIQUEMENT en JSON valide, sans markdown, sans texte autour." ) user_prompt = f"""Analyse les données de ce CV (formulaire WhileResume V2) et produit une analyse structurée. LANGUE PRINCIPALE DE SORTIE : {cv_language} (Tous les champs textuels doivent être écrits dans cette langue, sauf `ats_keywords` qui reste dans la langue du CV pour le matching.) DONNÉES DU FORMULAIRE (JSON) : ```json {json.dumps(cv_data, ensure_ascii=False, indent=2)} ``` INSTRUCTIONS : 1. Analyse l'expérience, les projets, les compétences et le parcours global. 2. Identifie les vrais points forts (pas de platitudes). 3. Détecte les axes d'amélioration concrets sur le CV lui-même (manques, incohérences, formulations faibles). 4. Estime un niveau de séniorité réel, pas celui revendiqué. 5. Propose des mots-clés ATS pertinents pour maximiser la visibilité en recherche recruteur. Retourne EXACTEMENT cette structure JSON (aucun champ en plus, aucun en moins) : {{ "language": "code langue ISO utilisé (fr, en, de, ...)", "executive_summary": "Résumé exécutif percutant de 60-100 mots, à la 3ème personne, destiné à un recruteur qui lit 10 secondes. Doit contenir : séniorité, domaine, 2-3 forces clés, aspiration.", "recruiter_pitch": "Pitch de vente candidat de 40-60 mots, ton professionnel, répond à la question : pourquoi recruter cette personne ?", "seniority_level": "junior | mid | senior | lead | principal", "seniority_justification": "1 phrase expliquant le niveau détecté à partir des expériences réelles.", "strengths": [ {{ "title": "Titre court (3-6 mots)", "detail": "1-2 phrases expliquant pourquoi c'est une force, preuves tirées du CV." }} ], "improvement_suggestions": [ {{ "area": "Zone concernée (ex: 'Section expériences')", "suggestion": "Conseil actionnable en 1-2 phrases." }} ], "ats_keywords": ["mot-clé1", "mot-clé2", "..."], "match_profile": {{ "ideal_roles": ["Intitulé de poste idéal 1", "Intitulé de poste idéal 2", "..."], "ideal_industries": ["Secteur 1", "Secteur 2"], "red_flags": ["Point de vigilance éventuel pour un recruteur, ou tableau vide"] }}, "tone_analysis": {{ "overall": "Adjectif décrivant le ton général (ex: 'technique et factuel', 'commercial et narratif')", "clarity_score": 0, "impact_score": 0, "ats_score": 0 }}, "recommended_next_steps": [ "Action concrète 1 pour améliorer l'employabilité", "Action concrète 2", "Action concrète 3" ] }} QUALITÉ : - Les scores (clarity_score, impact_score, ats_score) sont des entiers 0-100. - `strengths` : minimum 3, maximum 6 items. - `improvement_suggestions` : minimum 2, maximum 5 items. - `ats_keywords` : 8 à 20 mots-clés, courts, pertinents. - `recommended_next_steps` : 3 à 5 actions. - Interdit : phrases creuses, hallucinations, compliments gratuits. - Si une section du CV est vide : base-toi uniquement sur ce qui existe, ne comble pas. RÈGLES STRICTES DE FORMAT : - Aucun retour chariot à l'intérieur d'une valeur string JSON. - Aucun markdown, aucun ```json```. - Réponds UNIQUEMENT l'objet JSON.""" # ───────────────────────────────────────────── # APPEL OPENAI # ───────────────────────────────────────────── print("🤖 Appel OpenAI (gpt-5-nano)...") try: response = client.chat.completions.create( model="gpt-5-nano", response_format={"type": "json_object"}, messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_prompt}, ], max_completion_tokens=20000, ) except Exception as e: import traceback traceback.print_exc() fail(f"Erreur appel API OpenAI : {e}", candidate_id) print(f"\n🔍 Finish reason: {response.choices[0].finish_reason}") try: usage = response.usage print( f"🔍 Tokens — Reasoning: {usage.completion_tokens_details.reasoning_tokens}, " f"Completion: {usage.completion_tokens}, Total: {usage.total_tokens}" ) except Exception: pass result = response.choices[0].message.content if not result or not result.strip(): fail("Réponse OpenAI vide.", candidate_id) print("\n🧠 Résultat brut :") print(result) # ───────────────────────────────────────────── # PARSING & SAUVEGARDE # ───────────────────────────────────────────── try: json_data = clean_and_parse_json(result) except json.JSONDecodeError as e: print(f"\n⚠️ Erreur JSON : {e}") if hasattr(e, "pos") and e.pos: start = max(0, e.pos - 50) end = min(len(result), e.pos + 50) print(f"Contexte : ...{result[start:end]}...") fail("Impossible de parser la réponse OpenAI en JSON valide.", candidate_id) print("\n✅ JSON valide :") print(json.dumps(json_data, indent=2, ensure_ascii=False)) # Stockage : on garde TOUT l'objet dans une seule colonne JSON pour rester # souple et éviter 20 nouvelles colonnes. Le dashboard mobile parse côté client. try: payload = json.dumps(json_data, ensure_ascii=False) ok1 = updateCvV2(candidate_id, "form_ai_analysis", payload) ok2 = updateCvV2(candidate_id, "form_ai_analysis_status", "done") ok3 = updateCvV2( candidate_id, "form_ai_analysis_updated_at", time.strftime("%Y-%m-%d %H:%M:%S"), ) if not (ok1 and ok2 and ok3): fail("Échec de l'écriture en base.", candidate_id) except Exception as e: fail(f"Erreur lors de l'écriture en base : {e}", candidate_id) # NOTE : on ne touche PAS au flag global `analyse` via valideAnalyseCV() # afin de ne pas interférer avec l'ancien système d'analyse PDF. stop_timer = True time.sleep(0.2) print() total_time = time.time() - start_time print(f"\n✨ Analyse IA formulaire enregistrée pour le candidat {candidate_id}") print(f"⏱️ Temps total : {total_time:.2f}s") Expected to find class "App\Controller\Api\Cvs\CvGeneratorController" in file "/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/src/Controller/Api/Cvs/CvGeneratorController.php" while importing services from resource "../src/", but it was not found! Check the namespace prefix used with the resource in /var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/src/../config/services.yaml (which is being imported from "/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/src/Kernel.php"). (500 Internal Server Error)

Symfony Exception

Expected to find class "App\Controller\Api\Cvs\CvGeneratorController" in file "/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/src/Controller/Api/Cvs/CvGeneratorController.php" while importing services from resource "../src/", but it was not found! Check the namespace prefix used with the resource in /var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/src/../config/services.yaml (which is being imported from "/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/src/Kernel.php").

Exceptions 2

Symfony\Component\Config\Exception\ LoaderLoadException

  1.                 // prevent embedded imports from nesting multiple exceptions
  2.                 if ($e instanceof LoaderLoadException) {
  3.                     throw $e;
  4.                 }
  5.                 throw new LoaderLoadException($resource$sourceResource0$e$type);
  6.             }
  7.         }
  8.         return null;
  9.     }
  1.             if ($isSubpath) {
  2.                 return isset($ret[1]) ? $ret : ($ret[0] ?? null);
  3.             }
  4.         }
  5.         return $this->doImport($resource$type$ignoreErrors$sourceResource);
  6.     }
  7.     /**
  8.      * @internal
  9.      */
  1.         } elseif (!\is_bool($ignoreErrors)) {
  2.             throw new \TypeError(sprintf('Invalid argument $ignoreErrors provided to "%s::import()": boolean or "not_found" expected, "%s" given.', static::class, get_debug_type($ignoreErrors)));
  3.         }
  4.         try {
  5.             return parent::import(...$args);
  6.         } catch (LoaderLoadException $e) {
  7.             if (!$ignoreNotFound || !($prev $e->getPrevious()) instanceof FileLocatorFileNotFoundException) {
  8.                 throw $e;
  9.             }
  1.     }
  2.     final public function import(string $resource, ?string $type null$ignoreErrors false)
  3.     {
  4.         $this->loader->setCurrentDir(\dirname($this->path));
  5.         $this->loader->import($resource$type$ignoreErrors$this->file);
  6.     }
  7.     final public function parameters(): ParametersConfigurator
  8.     {
  9.         return new ParametersConfigurator($this->container);
  1.         $container->import('../config/{packages}/*.yaml');
  2.         $container->import('../config/{packages}/'.$this->environment.'/*.yaml');
  3.         if (is_file(\dirname(__DIR__).'/config/services.yaml')) {
  4.             $container->import('../config/services.yaml');
  5.             $container->import('../config/{services}_'.$this->environment.'.yaml');
  6.         } elseif (is_file($path \dirname(__DIR__).'/config/services.php')) {
  7.             (require $path)($container->withPath($path), $this);
  8.         }
  1.             AbstractConfigurator::$valuePreProcessor = function ($value) {
  2.                 return $this === $value ? new Reference('kernel') : $value;
  3.             };
  4.             try {
  5.                 $configureContainer->getClosure($this)(new ContainerConfigurator($container$kernelLoader$instanceof$file$file$this->getEnvironment()), $loader$container);
  6.             } finally {
  7.                 $instanceof = [];
  8.                 $kernelLoader->registerAliasesForSinglyImplementedInterfaces();
  9.                 AbstractConfigurator::$valuePreProcessor $valuePreProcessor;
  10.             }
  1.     /**
  2.      * {@inheritdoc}
  3.      */
  4.     public function load($resource, ?string $type null)
  5.     {
  6.         return $resource($this->container$this->env);
  7.     }
  8.     /**
  9.      * {@inheritdoc}
  10.      */
  1.     {
  2.         if (false === $loader $this->resolver->resolve($resource$type)) {
  3.             throw new LoaderLoadException($resourcenull0null$type);
  4.         }
  5.         return $loader->load($resource$type);
  6.     }
  7.     /**
  8.      * {@inheritdoc}
  9.      */
  1.     /**
  2.      * {@inheritdoc}
  3.      */
  4.     public function registerContainerConfiguration(LoaderInterface $loader)
  5.     {
  6.         $loader->load(function (ContainerBuilder $container) use ($loader) {
  7.             $container->loadFromExtension('framework', [
  8.                 'router' => [
  9.                     'resource' => 'kernel::loadRoutes',
  10.                     'type' => 'service',
  11.                 ],
  1.         $container $this->getContainerBuilder();
  2.         $container->addObjectResource($this);
  3.         $this->prepareContainer($container);
  4.         if (null !== $cont $this->registerContainerConfiguration($this->getContainerLoader($container))) {
  5.             trigger_deprecation('symfony/http-kernel''5.3''Returning a ContainerBuilder from "%s::registerContainerConfiguration()" is deprecated.'get_debug_type($this));
  6.             $container->merge($cont);
  7.         }
  8.         $container->addCompilerPass(new AddAnnotatedClassesToCachePass($this));
  1.             });
  2.         }
  3.         try {
  4.             $container null;
  5.             $container $this->buildContainer();
  6.             $container->compile();
  7.         } finally {
  8.             if ($collectDeprecations) {
  9.                 restore_error_handler();
  1.             $_ENV['SHELL_VERBOSITY'] = 3;
  2.             $_SERVER['SHELL_VERBOSITY'] = 3;
  3.         }
  4.         $this->initializeBundles();
  5.         $this->initializeContainer();
  6.         $container $this->container;
  7.         if ($container->hasParameter('kernel.trusted_hosts') && $trustedHosts $container->getParameter('kernel.trusted_hosts')) {
  8.             Request::setTrustedHosts($trustedHosts);
  1.      * {@inheritdoc}
  2.      */
  3.     public function handle(Request $requestint $type HttpKernelInterface::MAIN_REQUESTbool $catch true)
  4.     {
  5.         if (!$this->booted) {
  6.             $container $this->container ?? $this->preBoot();
  7.             if ($container->has('http_cache')) {
  8.                 return $container->get('http_cache')->handle($request$type$catch);
  9.             }
  10.         }
  1.     Request::setTrustedHosts([$trustedHosts]);
  2. }
  3. $kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
  4. $request Request::createFromGlobals();
  5. $response $kernel->handle($request);
  6. $response->send();
  7. $kernel->terminate($request$response);

Symfony\Component\DependencyInjection\Exception\ InvalidArgumentException

Expected to find class "App\Controller\Api\Cvs\CvGeneratorController" in file "/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/src/Controller/Api/Cvs/CvGeneratorController.php" while importing services from resource "../src/", but it was not found! Check the namespace prefix used with the resource.

  1.                 $classes[$class] = $e->getMessage();
  2.                 continue;
  3.             }
  4.             // check to make sure the expected class exists
  5.             if (!$r) {
  6.                 throw new InvalidArgumentException(sprintf('Expected to find class "%s" in file "%s" while importing services from resource "%s", but it was not found! Check the namespace prefix used with the resource.'$class$path$pattern));
  7.             }
  8.             if ($r->isInstantiable() || $r->isInterface()) {
  9.                 $classes[$class] = null;
  10.             }
  1.             throw new InvalidArgumentException(sprintf('Namespace is not a valid PSR-4 prefix: "%s".'$namespace));
  2.         }
  3.         $autoconfigureAttributes = new RegisterAutoconfigureAttributesPass();
  4.         $autoconfigureAttributes $autoconfigureAttributes->accept($prototype) ? $autoconfigureAttributes null;
  5.         $classes $this->findClasses($namespace$resource, (array) $exclude$autoconfigureAttributes);
  6.         // prepare for deep cloning
  7.         $serializedPrototype serialize($prototype);
  8.         foreach ($classes as $class => $errorMessage) {
  9.             if (null === $errorMessage && $autoconfigureAttributes && $this->env) {
  1.             if (!\is_string($service['resource'])) {
  2.                 throw new InvalidArgumentException(sprintf('A "resource" attribute must be of type string for service "%s" in "%s". Check your YAML syntax.'$id$file));
  3.             }
  4.             $exclude $service['exclude'] ?? null;
  5.             $namespace $service['namespace'] ?? $id;
  6.             $this->registerClasses($definition$namespace$service['resource'], $exclude);
  7.         } else {
  8.             $this->setDefinition($id$definition);
  9.         }
  10.     }
  1.         }
  2.         $this->isLoadingInstanceof false;
  3.         $defaults $this->parseDefaults($content$file);
  4.         foreach ($content['services'] as $id => $service) {
  5.             $this->parseDefinition($id$service$file$defaultsfalse$trackBindings);
  6.         }
  7.     }
  8.     /**
  9.      * @throws InvalidArgumentException
  1.         // services
  2.         $this->anonymousServicesCount 0;
  3.         $this->anonymousServicesSuffix '~'.ContainerBuilder::hash($path);
  4.         $this->setCurrentDir(\dirname($path));
  5.         try {
  6.             $this->parseDefinitions($content$path);
  7.         } finally {
  8.             $this->instanceof = [];
  9.             $this->registerAliasesForSinglyImplementedInterfaces();
  10.         }
  11.     }
  1.         // empty file
  2.         if (null === $content) {
  3.             return null;
  4.         }
  5.         $this->loadContent($content$path);
  6.         // per-env configuration
  7.         if ($this->env && isset($content['when@'.$this->env])) {
  8.             if (!\is_array($content['when@'.$this->env])) {
  9.                 throw new InvalidArgumentException(sprintf('The "when@%s" key should contain an array in "%s". Check your YAML syntax.'$this->env$path));
  1.                 }
  2.             }
  3.             self::$loading[$resource] = true;
  4.             try {
  5.                 $ret $loader->load($resource$type);
  6.             } finally {
  7.                 unset(self::$loading[$resource]);
  8.             }
  9.             return $ret;
  1.             if ($isSubpath) {
  2.                 return isset($ret[1]) ? $ret : ($ret[0] ?? null);
  3.             }
  4.         }
  5.         return $this->doImport($resource$type$ignoreErrors$sourceResource);
  6.     }
  7.     /**
  8.      * @internal
  9.      */
  1.         } elseif (!\is_bool($ignoreErrors)) {
  2.             throw new \TypeError(sprintf('Invalid argument $ignoreErrors provided to "%s::import()": boolean or "not_found" expected, "%s" given.', static::class, get_debug_type($ignoreErrors)));
  3.         }
  4.         try {
  5.             return parent::import(...$args);
  6.         } catch (LoaderLoadException $e) {
  7.             if (!$ignoreNotFound || !($prev $e->getPrevious()) instanceof FileLocatorFileNotFoundException) {
  8.                 throw $e;
  9.             }
  1.     }
  2.     final public function import(string $resource, ?string $type null$ignoreErrors false)
  3.     {
  4.         $this->loader->setCurrentDir(\dirname($this->path));
  5.         $this->loader->import($resource$type$ignoreErrors$this->file);
  6.     }
  7.     final public function parameters(): ParametersConfigurator
  8.     {
  9.         return new ParametersConfigurator($this->container);
  1.         $container->import('../config/{packages}/*.yaml');
  2.         $container->import('../config/{packages}/'.$this->environment.'/*.yaml');
  3.         if (is_file(\dirname(__DIR__).'/config/services.yaml')) {
  4.             $container->import('../config/services.yaml');
  5.             $container->import('../config/{services}_'.$this->environment.'.yaml');
  6.         } elseif (is_file($path \dirname(__DIR__).'/config/services.php')) {
  7.             (require $path)($container->withPath($path), $this);
  8.         }
  1.             AbstractConfigurator::$valuePreProcessor = function ($value) {
  2.                 return $this === $value ? new Reference('kernel') : $value;
  3.             };
  4.             try {
  5.                 $configureContainer->getClosure($this)(new ContainerConfigurator($container$kernelLoader$instanceof$file$file$this->getEnvironment()), $loader$container);
  6.             } finally {
  7.                 $instanceof = [];
  8.                 $kernelLoader->registerAliasesForSinglyImplementedInterfaces();
  9.                 AbstractConfigurator::$valuePreProcessor $valuePreProcessor;
  10.             }
  1.     /**
  2.      * {@inheritdoc}
  3.      */
  4.     public function load($resource, ?string $type null)
  5.     {
  6.         return $resource($this->container$this->env);
  7.     }
  8.     /**
  9.      * {@inheritdoc}
  10.      */
  1.     {
  2.         if (false === $loader $this->resolver->resolve($resource$type)) {
  3.             throw new LoaderLoadException($resourcenull0null$type);
  4.         }
  5.         return $loader->load($resource$type);
  6.     }
  7.     /**
  8.      * {@inheritdoc}
  9.      */
  1.     /**
  2.      * {@inheritdoc}
  3.      */
  4.     public function registerContainerConfiguration(LoaderInterface $loader)
  5.     {
  6.         $loader->load(function (ContainerBuilder $container) use ($loader) {
  7.             $container->loadFromExtension('framework', [
  8.                 'router' => [
  9.                     'resource' => 'kernel::loadRoutes',
  10.                     'type' => 'service',
  11.                 ],
  1.         $container $this->getContainerBuilder();
  2.         $container->addObjectResource($this);
  3.         $this->prepareContainer($container);
  4.         if (null !== $cont $this->registerContainerConfiguration($this->getContainerLoader($container))) {
  5.             trigger_deprecation('symfony/http-kernel''5.3''Returning a ContainerBuilder from "%s::registerContainerConfiguration()" is deprecated.'get_debug_type($this));
  6.             $container->merge($cont);
  7.         }
  8.         $container->addCompilerPass(new AddAnnotatedClassesToCachePass($this));
  1.             });
  2.         }
  3.         try {
  4.             $container null;
  5.             $container $this->buildContainer();
  6.             $container->compile();
  7.         } finally {
  8.             if ($collectDeprecations) {
  9.                 restore_error_handler();
  1.             $_ENV['SHELL_VERBOSITY'] = 3;
  2.             $_SERVER['SHELL_VERBOSITY'] = 3;
  3.         }
  4.         $this->initializeBundles();
  5.         $this->initializeContainer();
  6.         $container $this->container;
  7.         if ($container->hasParameter('kernel.trusted_hosts') && $trustedHosts $container->getParameter('kernel.trusted_hosts')) {
  8.             Request::setTrustedHosts($trustedHosts);
  1.      * {@inheritdoc}
  2.      */
  3.     public function handle(Request $requestint $type HttpKernelInterface::MAIN_REQUESTbool $catch true)
  4.     {
  5.         if (!$this->booted) {
  6.             $container $this->container ?? $this->preBoot();
  7.             if ($container->has('http_cache')) {
  8.                 return $container->get('http_cache')->handle($request$type$catch);
  9.             }
  10.         }
  1.     Request::setTrustedHosts([$trustedHosts]);
  2. }
  3. $kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
  4. $request Request::createFromGlobals();
  5. $response $kernel->handle($request);
  6. $response->send();
  7. $kernel->terminate($request$response);

Stack Traces 2

[2/2] LoaderLoadException
Symfony\Component\Config\Exception\LoaderLoadException:
Expected to find class "App\Controller\Api\Cvs\CvGeneratorController" in file "/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/src/Controller/Api/Cvs/CvGeneratorController.php" while importing services from resource "../src/", but it was not found! Check the namespace prefix used with the resource in /var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/src/../config/services.yaml (which is being imported from "/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/src/Kernel.php").

  at /var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/config/Loader/FileLoader.php:174
  at Symfony\Component\Config\Loader\FileLoader->doImport()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/config/Loader/FileLoader.php:98)
  at Symfony\Component\Config\Loader\FileLoader->import()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/dependency-injection/Loader/FileLoader.php:66)
  at Symfony\Component\DependencyInjection\Loader\FileLoader->import()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php:64)
  at Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator->import()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/src/Kernel.php:32)
  at App\Kernel->configureContainer()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/framework-bundle/Kernel/MicroKernelTrait.php:188)
  at App\Kernel->Symfony\Bundle\FrameworkBundle\Kernel\{closure}()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/dependency-injection/Loader/ClosureLoader.php:39)
  at Symfony\Component\DependencyInjection\Loader\ClosureLoader->load()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/config/Loader/DelegatingLoader.php:40)
  at Symfony\Component\Config\Loader\DelegatingLoader->load()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/framework-bundle/Kernel/MicroKernelTrait.php:142)
  at App\Kernel->registerContainerConfiguration()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/http-kernel/Kernel.php:649)
  at Symfony\Component\HttpKernel\Kernel->buildContainer()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/http-kernel/Kernel.php:545)
  at Symfony\Component\HttpKernel\Kernel->initializeContainer()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/http-kernel/Kernel.php:789)
  at Symfony\Component\HttpKernel\Kernel->preBoot()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/http-kernel/Kernel.php:190)
  at Symfony\Component\HttpKernel\Kernel->handle()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/public/index.php:51)                
[1/2] InvalidArgumentException
Symfony\Component\DependencyInjection\Exception\InvalidArgumentException:
Expected to find class "App\Controller\Api\Cvs\CvGeneratorController" in file "/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/src/Controller/Api/Cvs/CvGeneratorController.php" while importing services from resource "../src/", but it was not found! Check the namespace prefix used with the resource.

  at /var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/dependency-injection/Loader/FileLoader.php:224
  at Symfony\Component\DependencyInjection\Loader\FileLoader->findClasses()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/dependency-injection/Loader/FileLoader.php:105)
  at Symfony\Component\DependencyInjection\Loader\FileLoader->registerClasses()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/dependency-injection/Loader/YamlFileLoader.php:700)
  at Symfony\Component\DependencyInjection\Loader\YamlFileLoader->parseDefinition()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/dependency-injection/Loader/YamlFileLoader.php:256)
  at Symfony\Component\DependencyInjection\Loader\YamlFileLoader->parseDefinitions()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/dependency-injection/Loader/YamlFileLoader.php:176)
  at Symfony\Component\DependencyInjection\Loader\YamlFileLoader->loadContent()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/dependency-injection/Loader/YamlFileLoader.php:132)
  at Symfony\Component\DependencyInjection\Loader\YamlFileLoader->load()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/config/Loader/FileLoader.php:159)
  at Symfony\Component\Config\Loader\FileLoader->doImport()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/config/Loader/FileLoader.php:98)
  at Symfony\Component\Config\Loader\FileLoader->import()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/dependency-injection/Loader/FileLoader.php:66)
  at Symfony\Component\DependencyInjection\Loader\FileLoader->import()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php:64)
  at Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator->import()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/src/Kernel.php:32)
  at App\Kernel->configureContainer()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/framework-bundle/Kernel/MicroKernelTrait.php:188)
  at App\Kernel->Symfony\Bundle\FrameworkBundle\Kernel\{closure}()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/dependency-injection/Loader/ClosureLoader.php:39)
  at Symfony\Component\DependencyInjection\Loader\ClosureLoader->load()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/config/Loader/DelegatingLoader.php:40)
  at Symfony\Component\Config\Loader\DelegatingLoader->load()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/framework-bundle/Kernel/MicroKernelTrait.php:142)
  at App\Kernel->registerContainerConfiguration()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/http-kernel/Kernel.php:649)
  at Symfony\Component\HttpKernel\Kernel->buildContainer()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/http-kernel/Kernel.php:545)
  at Symfony\Component\HttpKernel\Kernel->initializeContainer()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/http-kernel/Kernel.php:789)
  at Symfony\Component\HttpKernel\Kernel->preBoot()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/vendor/symfony/http-kernel/Kernel.php:190)
  at Symfony\Component\HttpKernel\Kernel->handle()
     (/var/www/vhosts/mirtillostudio.fr/b1.mirtillostudio.fr/version-1/public/index.php:51)