diff --git a/config/services.yaml b/config/services.yaml index 640b0608..c80fc44c 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -299,6 +299,26 @@ services: parent: App\Report\AbstractReport tags: [ 'barometre.report' ] + App\Report\AiUsageFrequencyReport: + parent: App\Report\AbstractReport + tags: [ 'barometre.report' ] + + App\Report\AiUsagePurposeReport: + parent: App\Report\AbstractReport + tags: [ 'barometre.report' ] + + App\Report\AiPerceptionReport: + parent: App\Report\AbstractReport + tags: [ 'barometre.report' ] + + App\Report\AiJobImpactReport: + parent: App\Report\AbstractReport + tags: [ 'barometre.report' ] + + App\Report\AiJobMarketImpactReport: + parent: App\Report\AbstractReport + tags: [ 'barometre.report' ] + NumberFormatter: class: \NumberFormatter arguments: ['fr', '1'] diff --git a/migrations/Version20260830082229.php b/migrations/Version20260830082229.php new file mode 100644 index 00000000..2e74da3e --- /dev/null +++ b/migrations/Version20260830082229.php @@ -0,0 +1,51 @@ +addSql('CREATE TABLE ai_usage_purpose (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(100) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE response_aiusagepurpose (response_id INT NOT NULL, aiusagepurpose_id INT NOT NULL, INDEX IDX_8CAA88E7FBF32840 (response_id), INDEX IDX_8CAA88E74827D971 (aiusagepurpose_id), PRIMARY KEY(response_id, aiusagepurpose_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE response_aiusagepurpose ADD CONSTRAINT FK_8CAA88E7FBF32840 FOREIGN KEY (response_id) REFERENCES response (id) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE response_aiusagepurpose ADD CONSTRAINT FK_8CAA88E74827D971 FOREIGN KEY (aiusagepurpose_id) REFERENCES ai_usage_purpose (id) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE response ADD aiUsageFrequency INT DEFAULT NULL, ADD aiPerception INT DEFAULT NULL, ADD aiJobImpact INT DEFAULT NULL, ADD aiJobMarketImpact INT DEFAULT NULL'); + + $sqlAiUsagePurpose = 'INSERT INTO ai_usage_purpose (name) VALUE (:name)'; + + $aiUsagePurposes = [ + 'Debug / compréhension de code', + 'Refactoring', + 'Génération de boilerplate', + 'Écriture de tests', + 'Documentation', + 'Rédaction de specs', + 'Revue de code', + 'Automatisation / CI', + ]; + + foreach ($aiUsagePurposes as $aiUsagePurpose) { + $this->addSql($sqlAiUsagePurpose, ['name' => $aiUsagePurpose]); + } + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE response_aiusagepurpose DROP FOREIGN KEY FK_8CAA88E7FBF32840'); + $this->addSql('ALTER TABLE response_aiusagepurpose DROP FOREIGN KEY FK_8CAA88E74827D971'); + $this->addSql('DROP TABLE ai_usage_purpose'); + $this->addSql('DROP TABLE response_aiusagepurpose'); + $this->addSql('ALTER TABLE response DROP aiUsageFrequency, DROP aiPerception, DROP aiJobImpact, DROP aiJobMarketImpact'); + } +} diff --git a/public/reports/2026/ai_job_market_impact.png b/public/reports/2026/ai_job_market_impact.png new file mode 100644 index 00000000..538c9f2d Binary files /dev/null and b/public/reports/2026/ai_job_market_impact.png differ diff --git a/public/reports/2026/ai_perception.png b/public/reports/2026/ai_perception.png new file mode 100644 index 00000000..fce91c9e Binary files /dev/null and b/public/reports/2026/ai_perception.png differ diff --git a/public/reports/2026/ai_usage_frequency.png b/public/reports/2026/ai_usage_frequency.png new file mode 100644 index 00000000..f02ce0e2 Binary files /dev/null and b/public/reports/2026/ai_usage_frequency.png differ diff --git a/public/reports/2026/evolution_salaire.png b/public/reports/2026/evolution_salaire.png new file mode 100644 index 00000000..f29adc5f Binary files /dev/null and b/public/reports/2026/evolution_salaire.png differ diff --git a/public/reports/2026/evolution_salaire_moyen_genre.png b/public/reports/2026/evolution_salaire_moyen_genre.png new file mode 100644 index 00000000..85a6551c Binary files /dev/null and b/public/reports/2026/evolution_salaire_moyen_genre.png differ diff --git a/public/reports/2026/repartition_teletravailleurs.png b/public/reports/2026/repartition_teletravailleurs.png new file mode 100644 index 00000000..10485a7c Binary files /dev/null and b/public/reports/2026/repartition_teletravailleurs.png differ diff --git a/src/Campaign/Format/Formats/Format2026.php b/src/Campaign/Format/Formats/Format2026.php new file mode 100644 index 00000000..92c62cb7 --- /dev/null +++ b/src/Campaign/Format/Formats/Format2026.php @@ -0,0 +1,56 @@ + ['key' => 'covid19_salary_impact', 'class' => SalaryImpactEnums::class], 'Covid19PartialUnemployment' => ['key' => 'covid19_partial_unemployment', 'class' => PartialUnemploymentEnums::class], 'Covid19RegularRemoteFeeling' => ['key' => 'covid19_regular_remote_feeling', 'class' => RegularRemoteFeelingEnums::class], + 'aiUsageFrequency' => ['key' => 'ai_usage_frequency', 'class' => AiUsageFrequencyEnums::class], + 'aiPerception' => ['key' => 'ai_perception', 'class' => AiPerceptionEnums::class], + 'aiJobImpact' => ['key' => 'ai_job_impact', 'class' => AiJobImpactEnums::class], + 'aiJobMarketImpact' => ['key' => 'ai_job_market_impact', 'class' => AiJobMarketImpactEnums::class], ]; foreach ($enumValues as $field => $enum) { @@ -194,14 +205,21 @@ public function createResponse(array $data, Campaign $campaign): Response 'oui' === mb_strtolower($data['formation_impact'] ?? '') ); - $response->setUseGenerativeAI( - 'oui' === mb_strtolower($data['use_generative_ai'] ?? '') - ); + if (isset($data['use_generative_ai'])) { + $response->setUseGenerativeAI( + 'oui' === mb_strtolower($data['use_generative_ai']) + ); + } $response->setIncludeAiInProject( 'oui' === mb_strtolower($data['include_ai_in_project'] ?? '') ); + $this->addAiUsagePurpose( + $response, + explode(', ', $data['ai_usage_purpose'] ?? '') + ); + return $response; } @@ -261,6 +279,19 @@ private function addContainerEnvironmentUsage(Response $response, array $contain } } + private function addAiUsagePurpose(Response $response, array $aiUsagePurposes) + { + foreach ($aiUsagePurposes as $name) { + $aiUsagePurpose = $this->aiUsagePurposeRepository->findOneBy(['name' => mb_trim($name)]); + + if (!$aiUsagePurpose instanceof AiUsagePurpose) { + continue; + } + + $response->addAiUsagePurpose($aiUsagePurpose); + } + } + private function addJobInterest(Response $response, array $jobInterests) { foreach ($jobInterests as $name) { diff --git a/src/Entity/AiUsagePurpose.php b/src/Entity/AiUsagePurpose.php new file mode 100644 index 00000000..d356717d --- /dev/null +++ b/src/Entity/AiUsagePurpose.php @@ -0,0 +1,43 @@ +id; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + public function getName(): string + { + return $this->name; + } + + public function __toString(): string + { + return $this->name; + } +} diff --git a/src/Entity/Response.php b/src/Entity/Response.php index 37eb8aaf..3b00be83 100644 --- a/src/Entity/Response.php +++ b/src/Entity/Response.php @@ -192,6 +192,22 @@ class Response #[ORM\Column(type: 'boolean', nullable: true)] private ?bool $includeAiInProject = null; + #[ORM\Column(type: 'integer', nullable: true)] + private ?int $aiUsageFrequency = null; + + /** @var Collection */ + #[ORM\ManyToMany(targetEntity: AiUsagePurpose::class)] + private Collection $aiUsagePurposes; + + #[ORM\Column(type: 'integer', nullable: true)] + private ?int $aiPerception = null; + + #[ORM\Column(type: 'integer', nullable: true)] + private ?int $aiJobImpact = null; + + #[ORM\Column(type: 'integer', nullable: true)] + private ?int $aiJobMarketImpact = null; + public function __construct() { $this->certifications = new ArrayCollection(); @@ -199,6 +215,7 @@ public function __construct() $this->containerEnvironmentsUsage = new ArrayCollection(); $this->hostingTypes = new ArrayCollection(); $this->jobInterests = new ArrayCollection(); + $this->aiUsagePurposes = new ArrayCollection(); } public function getId(): int @@ -931,4 +948,71 @@ public function setIncludeAiInProject(?bool $includeAiInProject): self return $this; } + + public function getAiUsageFrequency(): ?int + { + return $this->aiUsageFrequency; + } + + public function setAiUsageFrequency(?int $aiUsageFrequency): self + { + $this->aiUsageFrequency = $aiUsageFrequency; + + return $this; + } + + public function getAiUsagePurposes(): Collection + { + return $this->aiUsagePurposes; + } + + public function setAiUsagePurposes(Collection $aiUsagePurposes): self + { + $this->aiUsagePurposes = $aiUsagePurposes; + + return $this; + } + + public function addAiUsagePurpose(AiUsagePurpose $aiUsagePurpose): self + { + $this->aiUsagePurposes->add($aiUsagePurpose); + + return $this; + } + + public function getAiPerception(): ?int + { + return $this->aiPerception; + } + + public function setAiPerception(?int $aiPerception): self + { + $this->aiPerception = $aiPerception; + + return $this; + } + + public function getAiJobImpact(): ?int + { + return $this->aiJobImpact; + } + + public function setAiJobImpact(?int $aiJobImpact): self + { + $this->aiJobImpact = $aiJobImpact; + + return $this; + } + + public function getAiJobMarketImpact(): ?int + { + return $this->aiJobMarketImpact; + } + + public function setAiJobMarketImpact(?int $aiJobMarketImpact): self + { + $this->aiJobMarketImpact = $aiJobMarketImpact; + + return $this; + } } diff --git a/src/Enums/AiJobImpactEnums.php b/src/Enums/AiJobImpactEnums.php new file mode 100644 index 00000000..fa04591e --- /dev/null +++ b/src/Enums/AiJobImpactEnums.php @@ -0,0 +1,23 @@ + 'Autre', + self::AUCUN_IMPACT_SIGNIFICATIF => 'Aucun impact significatif', + self::IMPACT_POSITIF => 'Impact positif (gain d\'efficacité, montée en compétence…)', + self::TRANSFORMATION_DU_POSTE => 'Transformation du poste (évolution des missions)', + self::RISQUE_DE_REDUCTION_OU_SUPPRESSION => 'Risque de réduction ou suppression du poste', + self::INCERTITUDE => 'Incertitude / difficile à évaluer', + ]; +} diff --git a/src/Enums/AiJobMarketImpactEnums.php b/src/Enums/AiJobMarketImpactEnums.php new file mode 100644 index 00000000..1b0b5bb3 --- /dev/null +++ b/src/Enums/AiJobMarketImpactEnums.php @@ -0,0 +1,25 @@ + 'Autre', + self::FORTE_CREATION_D_EMPLOIS => 'Forte création d\'emplois', + self::CREATION_D_EMPLOIS_SPECIALISES => 'Création d\'emplois spécialisés', + self::TRANSFORMATION_DES_COMPETENCES_DEMANDEES => 'Transformation des compétences demandées', + self::RALENTISSEMENT_DES_EMBAUCHES => 'Ralentissement des embauches', + self::SUPPRESSION_NETTE_D_EMPLOIS => 'Suppression nette d\'emplois', + self::NE_SE_PRONONCE_PAS => 'Ne se prononce pas', + ]; +} diff --git a/src/Enums/AiPerceptionEnums.php b/src/Enums/AiPerceptionEnums.php new file mode 100644 index 00000000..8e19d266 --- /dev/null +++ b/src/Enums/AiPerceptionEnums.php @@ -0,0 +1,21 @@ + 'Autre', + self::GADGET => 'Gadget', + self::GAIN_DE_CONFORT => 'Gain de confort', + self::GAIN_DE_PRODUCTIVITE_SIGNIFICATIF => 'Gain de productivité significatif', + self::CHANGEMENT_PROFOND => 'Changement profond de manière de travailler', + ]; +} diff --git a/src/Enums/AiUsageFrequencyEnums.php b/src/Enums/AiUsageFrequencyEnums.php new file mode 100644 index 00000000..5c3fb683 --- /dev/null +++ b/src/Enums/AiUsageFrequencyEnums.php @@ -0,0 +1,29 @@ + 'Autre', + self::NE_UTILISE_PAS => 'Je n\'utilise pas d\'IA générative', + self::AUTOCOMPLETION => 'Surtout comme auto-complétion avancée dans l\'éditeur', + self::CHAT_PONCTUEL => 'De temps en temps en mode chat (questions, explications, debug…)', + self::PROMPT_MANUEL => 'J\'écris mes prompts "à la main" et l\'IA génère du code', + self::SPECS_PUIS_GENERATION => 'L\'IA m\'aide à écrire les specs, puis génère le code', + self::USER_STORIES_PUIS_GENERATION => 'J\'écris des specs / user stories, puis l\'IA génère le code', + self::WORKFLOWS_AGENTS => 'J\'utilise des workflows avec agents (itérations specs ↔ code ↔ tests)', + self::BOUCLES_AUTOMATISEES => 'Ça va jusqu\'à des boucles quasi automatisées (génération, tests, corrections, déploiement)', + ]; +} diff --git a/src/Enums/PHPVersionEnums.php b/src/Enums/PHPVersionEnums.php index 46b7da4b..e2e1644a 100644 --- a/src/Enums/PHPVersionEnums.php +++ b/src/Enums/PHPVersionEnums.php @@ -21,6 +21,8 @@ class PHPVersionEnums extends AbstractEnums public const PHP_81 = 12; public const PHP_82 = 13; public const PHP_83 = 14; + public const PHP_84 = 15; + public const PHP_85 = 16; protected array $choices = [ self::PHP_4 => 'PHP 4', @@ -38,6 +40,8 @@ class PHPVersionEnums extends AbstractEnums self::PHP_81 => 'PHP 8.1', self::PHP_82 => 'PHP 8.2', self::PHP_83 => 'PHP 8.3', + self::PHP_84 => 'PHP 8.4', + self::PHP_85 => 'PHP 8.5', ]; protected function getDefaultValue(): ?int diff --git a/src/Filter/AiJobImpactFilter.php b/src/Filter/AiJobImpactFilter.php new file mode 100644 index 00000000..187592b8 --- /dev/null +++ b/src/Filter/AiJobImpactFilter.php @@ -0,0 +1,69 @@ +add($this->getName(), Select2MultipleFilterType::class, [ + 'label' => 'filter.ai_job_impact', + 'choices' => array_flip($this->enums->getChoices()), + ]); + } + + /** + * {@inheritdoc} + */ + public function buildQuery(QueryBuilder $queryBuilder, array $values = []) + { + if (!\array_key_exists($this->getName(), $values) || 0 === \count($values[$this->getName()])) { + return; + } + + $queryBuilder + ->andWhere($queryBuilder->expr()->in('response.aiJobImpact', $values[$this->getName()])); + } + + /** + * {@inheritdoc} + */ + public function convertValuesToLabels($value) + { + return array_map(function ($value) { + return $this->enums->getLabelById($value); + }, $value); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'ai_job_impact'; + } + + /** + * Filter weight. + * + * @return int + */ + public function getWeight() + { + return 163; + } +} diff --git a/src/Filter/AiJobMarketImpactFilter.php b/src/Filter/AiJobMarketImpactFilter.php new file mode 100644 index 00000000..ec79e79f --- /dev/null +++ b/src/Filter/AiJobMarketImpactFilter.php @@ -0,0 +1,69 @@ +add($this->getName(), Select2MultipleFilterType::class, [ + 'label' => 'filter.ai_job_market_impact', + 'choices' => array_flip($this->enums->getChoices()), + ]); + } + + /** + * {@inheritdoc} + */ + public function buildQuery(QueryBuilder $queryBuilder, array $values = []) + { + if (!\array_key_exists($this->getName(), $values) || 0 === \count($values[$this->getName()])) { + return; + } + + $queryBuilder + ->andWhere($queryBuilder->expr()->in('response.aiJobMarketImpact', $values[$this->getName()])); + } + + /** + * {@inheritdoc} + */ + public function convertValuesToLabels($value) + { + return array_map(function ($value) { + return $this->enums->getLabelById($value); + }, $value); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'ai_job_market_impact'; + } + + /** + * Filter weight. + * + * @return int + */ + public function getWeight() + { + return 164; + } +} diff --git a/src/Filter/AiPerceptionFilter.php b/src/Filter/AiPerceptionFilter.php new file mode 100644 index 00000000..144d1164 --- /dev/null +++ b/src/Filter/AiPerceptionFilter.php @@ -0,0 +1,69 @@ +add($this->getName(), Select2MultipleFilterType::class, [ + 'label' => 'filter.ai_perception', + 'choices' => array_flip($this->enums->getChoices()), + ]); + } + + /** + * {@inheritdoc} + */ + public function buildQuery(QueryBuilder $queryBuilder, array $values = []) + { + if (!\array_key_exists($this->getName(), $values) || 0 === \count($values[$this->getName()])) { + return; + } + + $queryBuilder + ->andWhere($queryBuilder->expr()->in('response.aiPerception', $values[$this->getName()])); + } + + /** + * {@inheritdoc} + */ + public function convertValuesToLabels($value) + { + return array_map(function ($value) { + return $this->enums->getLabelById($value); + }, $value); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'ai_perception'; + } + + /** + * Filter weight. + * + * @return int + */ + public function getWeight() + { + return 162; + } +} diff --git a/src/Filter/AiUsageFrequencyFilter.php b/src/Filter/AiUsageFrequencyFilter.php new file mode 100644 index 00000000..77a6dca3 --- /dev/null +++ b/src/Filter/AiUsageFrequencyFilter.php @@ -0,0 +1,69 @@ +add($this->getName(), Select2MultipleFilterType::class, [ + 'label' => 'filter.ai_usage_frequency', + 'choices' => array_flip($this->enums->getChoices()), + ]); + } + + /** + * {@inheritdoc} + */ + public function buildQuery(QueryBuilder $queryBuilder, array $values = []) + { + if (!\array_key_exists($this->getName(), $values) || 0 === \count($values[$this->getName()])) { + return; + } + + $queryBuilder + ->andWhere($queryBuilder->expr()->in('response.aiUsageFrequency', $values[$this->getName()])); + } + + /** + * {@inheritdoc} + */ + public function convertValuesToLabels($value) + { + return array_map(function ($value) { + return $this->enums->getLabelById($value); + }, $value); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'ai_usage_frequency'; + } + + /** + * Filter weight. + * + * @return int + */ + public function getWeight() + { + return 161; + } +} diff --git a/src/Menu/MenuBuilder.php b/src/Menu/MenuBuilder.php index 0342a35f..627e4745 100644 --- a/src/Menu/MenuBuilder.php +++ b/src/Menu/MenuBuilder.php @@ -41,10 +41,10 @@ protected function getBaseMenu() $menu->setChildrenAttribute('class', 'nav navbar-nav'); $menu->addChild( - 'menu.result2025', + 'menu.result2026', [ 'route' => 'afup_barometre_campaign', - 'routeParameters' => ['campaignName' => 2025], + 'routeParameters' => ['campaignName' => 2026], 'routeAbsolute' => UrlGeneratorInterface::ABSOLUTE_URL, ] ); diff --git a/src/Report/AiJobImpactReport.php b/src/Report/AiJobImpactReport.php new file mode 100644 index 00000000..325ba4a3 --- /dev/null +++ b/src/Report/AiJobImpactReport.php @@ -0,0 +1,38 @@ +queryBuilder + ->select('response.aiJobImpact as aiJobImpact') + ->addSelect('COUNT(response.id) as nbResponse') + ->addGroupBy('response.aiJobImpact') + ->addOrderBy('response.aiJobImpact', 'asc'); + + $this->data = $this->queryBuilder->fetchAllAssociative(); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'ai_job_impact'; + } + + /** + * report weight. + */ + public function getWeight(): int + { + return 127; + } +} diff --git a/src/Report/AiJobMarketImpactReport.php b/src/Report/AiJobMarketImpactReport.php new file mode 100644 index 00000000..1bd1cb82 --- /dev/null +++ b/src/Report/AiJobMarketImpactReport.php @@ -0,0 +1,38 @@ +queryBuilder + ->select('response.aiJobMarketImpact as aiJobMarketImpact') + ->addSelect('COUNT(response.id) as nbResponse') + ->addGroupBy('response.aiJobMarketImpact') + ->addOrderBy('response.aiJobMarketImpact', 'asc'); + + $this->data = $this->queryBuilder->fetchAllAssociative(); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'ai_job_market_impact'; + } + + /** + * report weight. + */ + public function getWeight(): int + { + return 126; + } +} diff --git a/src/Report/AiPerceptionReport.php b/src/Report/AiPerceptionReport.php new file mode 100644 index 00000000..64e85203 --- /dev/null +++ b/src/Report/AiPerceptionReport.php @@ -0,0 +1,38 @@ +queryBuilder + ->select('response.aiPerception as aiPerception') + ->addSelect('COUNT(response.id) as nbResponse') + ->addGroupBy('response.aiPerception') + ->addOrderBy('response.aiPerception', 'asc'); + + $this->data = $this->queryBuilder->fetchAllAssociative(); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'ai_perception'; + } + + /** + * report weight. + */ + public function getWeight(): int + { + return 128; + } +} diff --git a/src/Report/AiUsageFrequencyReport.php b/src/Report/AiUsageFrequencyReport.php new file mode 100644 index 00000000..5f49588d --- /dev/null +++ b/src/Report/AiUsageFrequencyReport.php @@ -0,0 +1,38 @@ +queryBuilder + ->select('response.aiUsageFrequency as aiUsageFrequency') + ->addSelect('COUNT(response.id) as nbResponse') + ->addGroupBy('response.aiUsageFrequency') + ->addOrderBy('response.aiUsageFrequency', 'asc'); + + $this->data = $this->queryBuilder->fetchAllAssociative(); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'ai_usage_frequency'; + } + + /** + * report weight. + */ + public function getWeight(): int + { + return 130; + } +} diff --git a/src/Report/AiUsagePurposeReport.php b/src/Report/AiUsagePurposeReport.php new file mode 100644 index 00000000..b56f1487 --- /dev/null +++ b/src/Report/AiUsagePurposeReport.php @@ -0,0 +1,53 @@ +queryBuilder + ->select('count(distinct response.id) as nbResponse') + ->join( + 'response', + 'response_aiusagepurpose', + 'response_aiusagepurpose', + 'response.id = response_aiusagepurpose.response_id' + ) + ->join( + 'response_aiusagepurpose', + 'ai_usage_purpose', + 'ai_usage_purpose', + 'response_aiusagepurpose.aiusagepurpose_id = ai_usage_purpose.id' + ) + ->addSelect('ai_usage_purpose.name as aiUsagePurposeName') + ->addGroupBy('aiUsagePurposeName') + ->addOrderBy('nbResponse', 'desc'); + + $this->data = $this->queryBuilder->fetchAllAssociative(); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'ai_usage_purpose'; + } + + /** + * report weight. + */ + public function getWeight(): int + { + return 129; + } +} diff --git a/src/Repository/AiUsagePurposeRepository.php b/src/Repository/AiUsagePurposeRepository.php new file mode 100644 index 00000000..3c43ecc0 --- /dev/null +++ b/src/Repository/AiUsagePurposeRepository.php @@ -0,0 +1,17 @@ + + + + + +{% endblock %} + + +{% block content %} +
+
+

Résultats de la campagne {{ campaignName }}

+ +

+ L'AFUP, Human Coders et le Journal du Net sont ravis de vous présenter les résultats de l'enquête effectuée au printemps auprès de 509 devs. Cette édition est marquée par des salaires qui progressent nettement plus vite que l'inflation, et surtout par une transformation profonde de la manière dont l'IA générative s'est installée dans le quotidien des développeuses et développeurs PHP : l'usage n'est plus une simple question de "oui ou non", il s'étend désormais du copilote d'autocomplétion jusqu'aux workflows agentiques quasi autonomes. On vous explique tout cela en détail. +

+ +

À propos de la campagne 2026

+

Comme chaque année, l'AFUP, en partenariat avec Human Coders et le Journal du Net, effectue une enquête auprès des développeurs et développeuses PHP. Cette enquête a eu lieu du 2 mars au 31 mai 2026.

+ +

Nous avons reçu 509 réponses cette année, contre 721 en 2025 (soit environ 29 % de réponses en moins). Nous encourageons chaleureusement la communauté à relayer largement le baromètre l'an prochain pour retrouver un échantillon plus large.

+ +

Après l'introduction de deux premières questions sur l'IA générative en 2025, nous avons cette année entièrement retravaillé ce volet de l'enquête pour refléter la manière dont les usages ont évolué : une question sur le niveau d'autonomie laissé à l'IA (de l'autocomplétion aux workflows agentiques), une question sur les cas d'usage concrets, une question sur la perception de ce changement, ainsi que deux questions distinctes sur l'impact perçu de l'IA — l'une sur son propre poste, l'autre sur le marché de l'emploi du secteur. Une page et plusieurs filtres dédiés ont été ajoutés sur le baromètre.

+ +

Profil des personnes ayant répondu

+

Les personnes ayant répondu :

+
    +
  • sont en majorité des hommes, pour 91%, les femmes étant représentées à hauteur de 8% et les personnes non binaires à 1% ;
  • +
  • ont pour âge médian 37 ans ;
  • +
  • sont en CDI : cela reste de loin, le statut le plus représenté avec 93% des personnes ayant un contrat de travail de ce type ;
  • +
  • travaillent dans une zone géographique à forte densité (l'Île-de-France et la région Auvergne-Rhône-Alpes représentent à elles seules environ 46% des réponses, un niveau stable par rapport à 2025) ;
  • +
  • travaillent en majorité dans une entreprise de moins de 500 salariés (pour 83%, stable par rapport à 2025) ;
  • +
  • utilisent à 52% Linux en tant que système d'exploitation principal ;
  • +
  • font de la veille technique plusieurs fois par semaine ou plus (pour 69%) ;
  • +
  • 23% sont issues d'une reconversion ;
  • +
  • 97% font du télétravail au moins occasionnellement, et 34% le font à temps plein ;
  • +
  • 14% ont suivi une formation au cours des deux dernières années, et pour 15% de ces derniers, cela a eu un impact sur leur rémunération ;
  • +
  • utilisent à 91% PHP 8 ou une version plus récente (PHP 8.4 et 8.5 comprises).
  • +
+ + +

Analyse

+ +

Des salaires qui progressent nettement au-dessus de l'inflation

+ +

+ Sur le périmètre habituel (profils dev / tech lead), le salaire brut annuel médian s'établit à 46 656 € en 2026, contre 45 000 € en 2025, soit une progression de +3,7 %. Le salaire moyen suit la même tendance (53 407 € contre 51 896 €, soit +2,9 %). +

+ + + Évolution des salaires + + +

+ Cette hausse dépasse largement l'inflation constatée par l'INSEE sur 2025 (+0,9 % en moyenne annuelle, après +2,0 % en 2024) — voir le bilan annuel de l'INSEE. C'est la première fois depuis 2021 que le salaire médian de nos répondant·es progresse aussi nettement plus vite que les prix à la consommation. +

+ +

Un écart hommes/femmes qui se creuse cette année

+ +

+ Le salaire moyen des hommes dev/tech lead en CDI est de 54 570 €, en hausse de 3,9 % par rapport à 2025 (52 500 €), tandis que le salaire moyen des femmes sur ce même périmètre est de 41 892 €, en léger recul de 2,2 % (42 827 € en 2025). +

+ +

+ L'écart relatif se creuse donc cette année : le salaire moyen des femmes représente environ 77 % de celui des hommes, contre 82 % en 2025. Nous restons prudents sur ce chiffre : l'échantillon de femmes reste faible (environ 30 réponses), ce qui rend la mesure sensible aux variations d'une année sur l'autre. Nous continuerons de suivre cet indicateur dans la durée plutôt que de tirer des conclusions définitives sur une seule campagne. +

+ + + Évolution du salaire moyen par genre + + + +

Une pratique du télétravail toujours stable

+ +

Le télétravail reste bien installé, avec 97 % des répondant·es qui le pratiquent au moins occasionnellement, et 34 % à temps plein. Ces chiffres sont quasiment identiques à ceux de 2025, confirmant le plateau observé depuis 2022.

+ + + Évolution de la répartition des télétravailleurs + + + +

2026, l'année du changement de paradigme IA

+ +

+ En 2025, nous posions deux questions simples : utilisez-vous l'IA générative, et l'intégrez-vous dans vos projets ? Un an plus tard, ces questions binaires ne suffisaient plus à décrire la réalité du terrain. Nous avons donc entièrement repensé ce volet de l'enquête pour comprendre non plus si les devs PHP utilisent l'IA, mais comment. +

+ +

De l'autocomplétion aux workflows agentiques

+ +

+ Seulement 5 % des répondant·es déclarent ne pas utiliser d'IA générative du tout. 61 % restent sur un usage d'assistance ponctuelle (autocomplétion, chat, prompts manuels), 15 % sur un usage orienté specs, et 17 % sont déjà dans une logique agentique : workflows avec itérations specs ↔ code ↔ tests, voire des boucles quasi automatisées de génération, tests, correction et déploiement. Autrement dit, près d'une personne sur cinq a déjà dépassé le stade de l'assistant conversationnel. Sans surprise, les usages restent surtout concentrés sur le debug, le refactoring et l'écriture de tests. Autre signe de cette bascule : 43 % des répondant·es déclarent que leurs projets intègrent désormais des fonctionnalités liées à l'IA générative, contre 28 % en 2025. +

+ + + Comment utilisez-vous l'IA générative dans votre quotidien de dev + + +

Un « changement profond », pas un gadget

+ +

Interrogé·es sur leur perception de l'IA dans le développement aujourd'hui, 45 % des répondant·es y voient un changement profond de leur manière de travailler, et 21 % un gain de productivité significatif — soit 65 % qui décrivent un impact majeur. À l'inverse, seulement 7 % considèrent encore l'IA générative comme un simple gadget.

+ + + Selon vous, l'IA dans le dev aujourd'hui, c'est plutôt… + + +

Transformation du poste, mais marché plus inquiet

+ +

+ Sur son propre poste, les répondant·es sont plutôt sereins : 40 % anticipent une transformation du poste (évolution des missions), 16 % un impact positif, et seulement 13 % redoutent une réduction ou suppression de poste. Le regard change quand la question porte sur le marché de l'emploi du secteur : près d'une personne sur deux (49 %) y redoute un ralentissement des embauches ou une suppression nette d'emplois, contre seulement 4 % qui anticipent des créations d'emploi. Ce contraste — confiance sur sa propre trajectoire, inquiétude sur la dynamique collective — est l'un des enseignements marquants de cette édition, que nous suivrons dans les prochaines campagnes. +

+ + + Selon vous, quel sera l'impact de l'IA sur le marché de l'emploi dans votre secteur ? + + + +

À propos du baromètre

+ +

Le baromètre des salaires AFUP est un projet open-source porté par des bénévoles. Nous sommes toujours à la recherche de personnes pour aider sur ce projet, que ça soit côté technique, pour le lancement de l'enquête ou l'analyse des résultats. Si vous souhaitez participer vous pouvez envoyer un message à barometre@afup.org.

+ + +

A propos de l'AFUP

+ +

L'association française des utilisateurs de PHP (AFUP), Human Coders et le Journal du Net (JDN) vous présentent pour la 14ème année consécutive le baromètre des salaires AFUP – 2026.

+ +

Comme chaque année, le baromètre AFUP répond aux questions que se posent les professionnel·le·s de notre secteur :

+
    +
  • Combien gagne un·e développeur·euse débutant·e ?
  • +
  • Comment les salaires évoluent-ils avec l'expérience ?
  • +
  • Quelles sont les technos qui rapportent le plus ?
  • +
  • Dans quelles villes touche-t-on les meilleurs salaires ?
  • +
+ +

Nous espérons que vous aurez une vision claire du marché après consultation de ce baromètre des salaires en PHP.

+ +

AFUP

+

L'AFUP, Association Française des Utilisateurs de PHP, est une association loi 1901, qui a pour objectif principal de promouvoir le langage PHP auprès des professionnel·le·s et de participer à son développement. Elle organise de nombreux événements tout au long de l'année, notamment le Forum PHP et les AFUP Day, elle diffuse et partage les connaissances auprès des utilisateurs et utilisatrices de PHP, et participe à la valorisation des développeurs et développeuses PHP sur le marché du travail.

+ +

Human Coders

+ +

Human Coders est un centre de formation pour développeur·euse·s. Fondé par 2 développeurs web il y a 13 ans, il forme sur plus de 150 langages / frameworks / méthodologies dont bien sûr PHP, Symfony et Laravel, mais aussi sur d'autres domaines comme le frontend, l'IA ou encore devops.

+ +

Convaincu de l'importance de faire de la veille pour rester à jour, Human Coders, c'est aussi un podcast pour développeur·euse·s, un site d'actualité et des événements mensuels dans toute la France.

+ +

JDN

+ +

Avec plus de 3,1 millions de visiteurs uniques par mois, le JDN est la source d'information des dirigeants et cadres qui osent. Digital, management, nouvelles technologies : le JDN propose à la fois une veille exhaustive de l'actualité et des dossiers prospectifs pour mieux comprendre l'économie de demain. Le JDN propose 4 newsletters thématiques envoyées à plus de 500 000 abonnés.

+
+
+

Résultats détaillés

+ +
+ {% block menu_reports knp_menu_render('reports_menu', {}, 'reports') %} +
+
+
+{% endblock %} diff --git a/templates/Report/ai_job_impact.html.twig b/templates/Report/ai_job_impact.html.twig new file mode 100644 index 00000000..f60d1a0b --- /dev/null +++ b/templates/Report/ai_job_impact.html.twig @@ -0,0 +1,23 @@ + + + + + + + + + {% for row in results %} + + + + + {% endfor %} + +
{{ "report.ai_job_impact.label" | trans }}{{ "report.view.response_number" | trans }}
+ {{ row.aiJobImpact|enum_label('App\\Enums\\AiJobImpactEnums') }} + {{ row.nbResponse|number_format(null, '', ' ') }}
diff --git a/templates/Report/ai_job_market_impact.html.twig b/templates/Report/ai_job_market_impact.html.twig new file mode 100644 index 00000000..a1e3df25 --- /dev/null +++ b/templates/Report/ai_job_market_impact.html.twig @@ -0,0 +1,23 @@ + + + + + + + + + {% for row in results %} + + + + + {% endfor %} + +
{{ "report.ai_job_market_impact.label" | trans }}{{ "report.view.response_number" | trans }}
+ {{ row.aiJobMarketImpact|enum_label('App\\Enums\\AiJobMarketImpactEnums') }} + {{ row.nbResponse|number_format(null, '', ' ') }}
diff --git a/templates/Report/ai_perception.html.twig b/templates/Report/ai_perception.html.twig new file mode 100644 index 00000000..1cfb528e --- /dev/null +++ b/templates/Report/ai_perception.html.twig @@ -0,0 +1,23 @@ + + + + + + + + + {% for row in results %} + + + + + {% endfor %} + +
{{ "report.ai_perception.label" | trans }}{{ "report.view.response_number" | trans }}
+ {{ row.aiPerception|enum_label('App\\Enums\\AiPerceptionEnums') }} + {{ row.nbResponse|number_format(null, '', ' ') }}
diff --git a/templates/Report/ai_usage_frequency.html.twig b/templates/Report/ai_usage_frequency.html.twig new file mode 100644 index 00000000..397f9e6a --- /dev/null +++ b/templates/Report/ai_usage_frequency.html.twig @@ -0,0 +1,23 @@ + + + + + + + + + {% for row in results %} + + + + + {% endfor %} + +
{{ "report.ai_usage_frequency.label" | trans }}{{ "report.view.response_number" | trans }}
+ {{ row.aiUsageFrequency|enum_label('App\\Enums\\AiUsageFrequencyEnums') }} + {{ row.nbResponse|number_format(null, '', ' ') }}
diff --git a/templates/Report/ai_usage_purpose.html.twig b/templates/Report/ai_usage_purpose.html.twig new file mode 100644 index 00000000..c38f756d --- /dev/null +++ b/templates/Report/ai_usage_purpose.html.twig @@ -0,0 +1,21 @@ + + + + + + + + + {% for row in results %} + + + + + {% endfor %} + +
{{ "report.ai_usage_purpose.label" | trans }}{{ "report.view.response_number" | trans }}
{{ row.aiUsagePurposeName }}{{ row.nbResponse }}
diff --git a/tests/Campaign/ResponseFactoryTest.php b/tests/Campaign/ResponseFactoryTest.php index 46d0cdc3..55597d02 100644 --- a/tests/Campaign/ResponseFactoryTest.php +++ b/tests/Campaign/ResponseFactoryTest.php @@ -7,6 +7,10 @@ use App\Campaign\ResponseFactory; use App\Entity\Campaign; use App\Entity\Response; +use App\Enums\AiJobImpactEnums; +use App\Enums\AiJobMarketImpactEnums; +use App\Enums\AiPerceptionEnums; +use App\Enums\AiUsageFrequencyEnums; use App\Enums\CmsUsageInProjectEnums; use App\Enums\CompanySizeEnums; use App\Enums\CompanyTypeEnums; @@ -38,6 +42,7 @@ use App\Enums\StatusEnums; use App\Enums\TechnologicalWatchEnums; use App\Enums\WorkMethodEnums; +use App\Repository\AiUsagePurposeRepository; use App\Repository\CertificationRepository; use App\Repository\ContainerEnvironmentUsageRepository; use App\Repository\HostingTypeRepository; @@ -82,6 +87,10 @@ public function testCreateResponse(): void new SalaryImpactEnums(), new PartialUnemploymentEnums(), new RegularRemoteFeelingEnums(), + new AiUsageFrequencyEnums(), + new AiPerceptionEnums(), + new AiJobImpactEnums(), + new AiJobMarketImpactEnums(), ]); $certificationRepository = $this->createMock(CertificationRepository::class); @@ -89,6 +98,7 @@ public function testCreateResponse(): void $hostingTypeRepository = $this->createMock(HostingTypeRepository::class); $containerEnvironmentUsageRepository = $this->createMock(ContainerEnvironmentUsageRepository::class); $jobInterestRepository = $this->createMock(JobInterestRepository::class); + $aiUsagePurposeRepository = $this->createMock(AiUsagePurposeRepository::class); $propertyAccessor = PropertyAccess::createPropertyAccessor(); $responseFactory = new ResponseFactory( @@ -99,6 +109,7 @@ public function testCreateResponse(): void $hostingTypeRepository, $containerEnvironmentUsageRepository, $jobInterestRepository, + $aiUsagePurposeRepository, $propertyAccessor ); diff --git a/translations/messages.fr.xliff b/translations/messages.fr.xliff index 0832af60..f7a21465 100644 --- a/translations/messages.fr.xliff +++ b/translations/messages.fr.xliff @@ -42,6 +42,10 @@ menu.result2025 Résultats 2025 + + menu.result2026 + Résultats 2026 + menu.results Résultats détaillés @@ -142,6 +146,22 @@ filter.use_generative_ai Utilisation de l'ia générative + + filter.ai_usage_frequency + Fréquence d'utilisation de l'IA générative + + + filter.ai_perception + Perception de l'IA dans le dev + + + filter.ai_job_impact + Impact de l'IA sur le poste + + + filter.ai_job_market_impact + Impact de l'IA sur le marché de l'emploi + filter.submit Filtrer @@ -154,6 +174,26 @@ report.include_ai_in_project.label Vos projets intègrent-ils des fonctionnalités liées à l'IA générative? + + report.ai_usage_frequency.label + Comment utilises-tu l'IA générative dans ton quotidien de dev ? + + + report.ai_usage_purpose.label + Pour quels usages principaux utilises-tu l'IA ? + + + report.ai_perception.label + Selon toi, l'IA dans le dev aujourd'hui, c'est plutôt… + + + report.ai_job_impact.label + Comment estimez-vous que l'IA impactera votre poste dans les 2 à 5 prochaines années ? + + + report.ai_job_market_impact.label + Selon vous, quel sera l'impact de l'IA sur le marché de l'emploi dans votre secteur ? + report.company_department.label Répartition géographique