1212namespace Symfony \Bundle \FrameworkBundle \Command ;
1313
1414use Symfony \Component \Config \Definition \ConfigurationInterface ;
15+ use Symfony \Component \Config \Definition \Dumper \YamlReferenceDumper ;
1516use Symfony \Component \Config \Definition \Processor ;
1617use Symfony \Component \Console \Attribute \AsCommand ;
1718use Symfony \Component \Console \Completion \CompletionInput ;
2829use Symfony \Component \DependencyInjection \Extension \ConfigurationExtensionInterface ;
2930use Symfony \Component \DependencyInjection \Extension \ExtensionInterface ;
3031use Symfony \Component \Yaml \Yaml ;
32+ use SebastianBergmann \Diff \Differ ;
33+ use SebastianBergmann \Diff \Output \UnifiedDiffOutputBuilder ;
3134
3235/**
33- * A console command for dumping available configuration reference.
36+ * A console command for dumping available configuration reference and optionally a diff with current one .
3437 *
3538 * @author Grégoire Pineau <lyrixx@lyrixx.info>
3639 *
@@ -47,6 +50,7 @@ protected function configure(): void
4750 new InputArgument ('path ' , InputArgument::OPTIONAL , 'The configuration option path ' ),
4851 new InputOption ('resolve-env ' , null , InputOption::VALUE_NONE , 'Display resolved environment variable values instead of placeholders ' ),
4952 new InputOption ('format ' , null , InputOption::VALUE_REQUIRED , \sprintf ('The output format ("%s") ' , implode ('", " ' , $ this ->getAvailableFormatOptions ())), class_exists (Yaml::class) ? 'txt ' : 'json ' ),
53+ new InputOption ('diff-reference-config ' , null , InputOption::VALUE_NONE , 'Compare the current configuration with the reference one ' ),
5054 ])
5155 ->setHelp (<<<EOF
5256 The <info>%command.name%</info> command dumps the current configuration for an
@@ -65,6 +69,10 @@ protected function configure(): void
6569
6670 <info>php %command.full_name% framework serializer.enabled</info>
6771
72+ The <info>--diff-reference-config</info> option makes a diff between current and reference configuration (only available for the yaml format):
73+
74+ <info>php %command.full_name% web_profiler --diff-reference-config --format=yaml</info>
75+
6876 EOF
6977 )
7078 ;
@@ -99,6 +107,22 @@ protected function execute(InputInterface $input, OutputInterface $output): int
99107 return 1 ;
100108 }
101109
110+ $ diff = $ input ->getOption ('diff-reference-config ' );
111+
112+ if ($ diff ) {
113+ if (!class_exists (Differ::class)) {
114+ $ errorIo ->error ('Using the "diff-reference-config" option requires the package "sebastian/diff". Try running "composer require --dev sebastian/diff". ' );
115+
116+ return 1 ;
117+ }
118+
119+ if ('yaml ' !== $ format ) {
120+ $ errorIo ->error ('Using the "diff-reference-config" option requires the output format to be "yaml". ' );
121+
122+ return 1 ;
123+ }
124+ }
125+
102126 if (null === $ path = $ input ->getArgument ('path ' )) {
103127 if ('txt ' === $ input ->getOption ('format ' )) {
104128 $ io ->title (
@@ -110,7 +134,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int
110134 }
111135 }
112136
113- $ io ->writeln ($ this ->convertToFormat ([$ extensionAlias => $ config ], $ format ));
137+ $ currentConfig = $ this ->convertToFormat ($ config , $ format );
138+
139+ if ($ diff ) {
140+ $ io ->note (\sprintf ('With Difference with default configuration for extension "%s" ' , $ extensionAlias ));
141+
142+ $ currentConfig = $ this ->doDiff ($ extension , $ currentConfig );
143+ }
144+
145+ $ io ->writeln ($ currentConfig );
114146
115147 return 0 ;
116148 }
@@ -284,4 +316,48 @@ private function getDocUrl(ExtensionInterface $extension, ContainerBuilder $cont
284316 ->getNode (true )
285317 ->getAttribute ('docUrl ' );
286318 }
319+
320+ private function doDiff (ExtensionInterface $ extension , string $ currentConfig ): string
321+ {
322+ // Use directly the existing command and get its output somehow?
323+ /*
324+ $configDumpReferenceInputs = new ArrayInput([
325+ 'command' => 'config:dump-reference',
326+ 'name' => $extensionAlias,
327+ '--format' => 'yaml',
328+ ]);
329+ $configDumpReferenceInputs->setInteractive(false);
330+ $application = $this->getApplication();
331+ $defaultConfig = $application->doRun($configDumpReferenceInputs, $output);;
332+ */
333+
334+ // Use the underlying code of "config:dump-reference" to dump default config and sanitize it for better diff
335+ if ($ extension instanceof ConfigurationInterface) {
336+ $ configuration = $ extension ;
337+ } else {
338+ $ configuration = $ extension ->getConfiguration ([], $ this ->getContainerBuilder ($ this ->getApplication ()->getKernel ()));
339+ }
340+ $ dumper = new YamlReferenceDumper ();
341+ $ defaultConfig = $ dumper ->dump ($ configuration );
342+
343+ // Remove first line, lines beginning with spaces and # and empty lines
344+ $ lines = explode ("\n" , $ defaultConfig );
345+ array_shift ($ lines );
346+ $ lines = array_filter (
347+ $ lines ,
348+ fn ($ line ) => !preg_match ('/^\s*#/ ' , $ line ) && trim ($ line ) !== ''
349+ );
350+ // Remove first 4 spaces from every line
351+ $ lines = array_map (fn ($ line ) => preg_replace ('/^ / ' , '' , $ line ), $ lines );
352+ // Remove comments (from # until end of line)
353+ $ lines = array_map (fn ($ line ) => preg_replace ('/#.*$/ ' , '' , $ line ), $ lines );
354+ // Replace multiple consecutive spaces with a single space (except at line start)
355+ $ lines = array_map (fn ($ line ) => preg_replace ('/(?<=\S) {2,}/ ' , ' ' , $ line ), $ lines );
356+ $ defaultConfig = implode ("\n" , $ lines );
357+ // Remove trailing newline
358+ $ defaultConfig = rtrim ($ defaultConfig , "\n" );
359+ $ currentConfig = rtrim ($ currentConfig , "\n" );
360+
361+ return new Differ (new UnifiedDiffOutputBuilder )->diff ($ defaultConfig , $ currentConfig );
362+ }
287363}
0 commit comments