Completed
Push — master ( 6d48a0...fb36d6 )
by Jeroen De
14s
created

RenderMailTemplatesCommand::execute()   B

Complexity

Conditions 3
Paths 2

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 27
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 16
nc 2
nop 2
1
<?php
2
3
declare( strict_types = 1 );
4
5
namespace WMDE\Fundraising\Frontend\Cli;
6
7
use FileFetcher\SimpleFileFetcher;
8
use Symfony\Component\Console\Command\Command;
9
use Symfony\Component\Console\Input\InputDefinition;
10
use Symfony\Component\Console\Input\InputInterface;
11
use Symfony\Component\Console\Input\InputOption;
12
use Symfony\Component\Console\Output\OutputInterface;
13
use Twig_Environment;
14
use Twig_Error;
15
use WMDE\Fundraising\Frontend\Factories\FunFunFactory;
16
use WMDE\Fundraising\Frontend\Infrastructure\ConfigReader;
17
18
/**
19
 * A command to check and dump mail templates
20
 *
21
 * The most useful way to apply this is probably to...
22
 * - run this once before starting the work on a feature that touches emails, dumping the rendered templates
23
 * - run it again after the changes, dumping to another folder
24
 * - then diffing the resulting folders
25
 *
26
 * @license GNU GPL v2+
27
 * @author Gabriel Birke < [email protected] >
28
 */
29
class RenderMailTemplatesCommand extends Command {
30
31
	private const NAME = 'dump-mail-tpl';
32
33
	protected function configure() {
34
		$this->setName( self::NAME )
35
			->setDescription( 'Dump rendered Mail_* Twig templates' )
36
			->setDefinition(
37
				new InputDefinition( [
38
					new InputOption(
39
						'output-path',
40
						'o',
41
						InputOption::VALUE_REQUIRED,
42
						'Output path for rendered text'
43
					),
44
				] )
45
			);
46
	}
47
48
	protected function execute( InputInterface $input, OutputInterface $output ) {
49
		$config = $this->getDefaultConfig();
50
		$config['twig']['strict-variables'] = true;
51
52
		$ffFactory = new FunFunFactory( $config );
53
54
		$app = require __DIR__ . '/../app/bootstrap.php';
55
56
		$app->flush();
57
58
		$ffFactory->setTwigEnvironment( $app['twig'] );
59
60
		$testData = require __DIR__ . '/../tests/Data/mail_templates.php';
61
62
		$this->validateTemplateFixtures(
63
			$testData,
64
			iterator_to_array( $ffFactory->newMailTemplateFilenameTraversable() ),
65
			$output
66
		);
67
68
		$outputPath = $input->getOption( 'output-path' ) ?? '';
69
		if ( $outputPath && substr( $outputPath, -1 ) !== '/' ) {
70
			$outputPath .= '/';
71
		}
72
73
		$this->renderTemplates( $testData, $ffFactory->getTwig(), $outputPath, $output );
74
	}
75
76
	private function getDefaultConfig(): array {
77
		$prodConfigPath = __DIR__ . '/../app/config/config.prod.json';
78
79
		$configReader = new ConfigReader(
80
			new SimpleFileFetcher(),
81
			__DIR__ . '/../app/config/config.dist.json',
82
			is_readable( $prodConfigPath ) ? $prodConfigPath : null
83
		);
84
85
		return $configReader->getConfig();
86
	}
87
88
	/**
89
	 * Check that there are templates for all fixtures and (even more important) vice-versa
90
	 *
91
	 * @param array $testData Template names and fixture information to render these templates
92
	 * @param array $mailTemplatePaths
93
	 * @param OutputInterface $output Command output
94
	 */
95
	private function validateTemplateFixtures( array $testData, array $mailTemplatePaths, OutputInterface $output ): void {
96
		$testTemplateNames = array_keys( $testData );
97
98
		$untestedTemplates = array_diff( $mailTemplatePaths, $testTemplateNames );
99
100
		if ( !empty( $untestedTemplates ) ) {
101
			$output->writeln(
102
				'<error>There are untested templates: ' . implode( ', ', $untestedTemplates ) . '</error>'
103
			);
104
		}
105
106
		$strayTemplates = array_diff( $testTemplateNames, $mailTemplatePaths );
107
108
		if ( !empty( $strayTemplates ) ) {
109
			$output->writeln(
110
				'<error>There are tests for non-existing templates: ' . implode( ', ', $strayTemplates ) . '</error>'
111
			);
112
		}
113
	}
114
115
	/**
116
	 * Render all templates and write them to disk to allow a comparison with an alternative data set
117
	 *
118
	 * @param array $testData Template names and fixture information to render these templates
119
	 * @param Twig_Environment $twig The templating engine to render the templates
120
	 * @param string $outputPath Path where rendered templates will be written to
121
	 * @param OutputInterface $output Command output
122
	 */
123
	private function renderTemplates( array $testData, Twig_Environment $twig, string $outputPath, OutputInterface $output ): void {
124
		foreach( $testData as $templateFileName => $templateSettings ) {
125
126
			if ( empty( $templateSettings['variants'] ) ) {
127
				$templateSettings['variants'] = [ '' => [] ];
128
			}
129
130
			foreach( $templateSettings['variants'] as $variantName => $additionalContext ) {
131
				$outputName =
132
					$outputPath .
133
					basename( $templateFileName, '.txt.twig' ) .
134
					( $variantName ? ".$variantName" : '' ) .
135
					'.txt';
136
137
				$output->write( "$outputName" );
138
				if ( file_exists( $outputName ) ) {
139
					$output->writeln( "$outputName already exists, skipping ..." );
140
					continue;
141
				}
142
143
				try {
144
					file_put_contents(
145
						$outputName,
146
						$twig->render(
147
							$templateFileName,
148
							array_merge_recursive(
149
								$templateSettings['context'],
150
								$additionalContext
151
							)
152
						)
153
					);
154
				} catch( Twig_Error $e ) {
155
					$output->writeln( '' );
156
					$output->writeln( '<error>' . $e->getMessage() . '</error>' );
157
					$output->writeln( var_export( $e->getSourceContext(), true ) );
158
				}
159
				$output->writeln( '' );
160
			}
161
		}
162
	}
163
}
164