Passed
Pull Request — master (#2028)
by Gabriel
203:46 queued 138:41
created

RenderMailTemplatesCommand::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare( strict_types = 1 );
4
5
namespace WMDE\Fundraising\Frontend\Cli;
6
7
use Symfony\Component\Console\Command\Command;
8
use Symfony\Component\Console\Input\InputDefinition;
9
use Symfony\Component\Console\Input\InputInterface;
10
use Symfony\Component\Console\Input\InputOption;
11
use Symfony\Component\Console\Output\OutputInterface;
12
use Twig\Environment;
13
use Twig\Error\Error;
14
use WMDE\Fundraising\Frontend\App\MailTemplates;
15
use WMDE\Fundraising\Frontend\Factories\FunFunFactory;
16
use WMDE\Fundraising\Frontend\Infrastructure\Mail\MailFormatter;
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 GPL-2.0-or-later
27
 */
28
class RenderMailTemplatesCommand extends Command {
29
30
	private const NAME = 'app:dump-mail-templates';
31
32
	private FunFunFactory $ffFactory;
33
34
	public function __construct( FunFunFactory $ffFactory ) {
35
		parent::__construct( self::NAME );
36
		$this->ffFactory = $ffFactory;
37
	}
38
39
	protected function configure(): void {
40
		$this->setName( self::NAME )
41
			->setDescription( 'Dump rendered Mail_* Twig templates' )
42
			->setDefinition(
43
				new InputDefinition( [
44
					new InputOption(
45
						'output-path',
46
						'o',
47
						InputOption::VALUE_REQUIRED,
48
						'Output path for rendered text'
49
					),
50
				] )
51
			);
52
	}
53
54
	protected function execute( InputInterface $input, OutputInterface $output ): int {
55
		$mailTemplates = new MailTemplates( $this->ffFactory );
56
		$testData = $mailTemplates->get();
57
58
		$hasErrors = $this->validateTemplateFixtures(
59
			$testData,
60
			iterator_to_array( $this->ffFactory->newMailTemplateFilenameTraversable() ),
61
			$output
62
		);
63
64
		$outputPath = $input->getOption( 'output-path' ) ?? '';
65
		if ( $outputPath && substr( $outputPath, -1 ) !== '/' ) {
66
			$outputPath .= '/';
67
		}
68
69
		$twig = $this->ffFactory->getMailerTwig();
70
		$twig->enableStrictVariables();
71
		$hasErrors = $this->renderTemplates( $testData, $twig, $outputPath, $output ) || $hasErrors;
72
73
		return $hasErrors ? 1 : 0;
74
	}
75
76
	/**
77
	 * Check that there are templates for all fixtures and (even more important) vice-versa
78
	 *
79
	 * @param array $testData Template names and fixture information to render these templates
80
	 * @param array $mailTemplatePaths
81
	 * @param OutputInterface $output Command output
82
	 * @return bool
83
	 */
84
	private function validateTemplateFixtures( array $testData, array $mailTemplatePaths, OutputInterface $output ): bool {
85
		$hasErrors = false;
86
		$testTemplateNames = array_keys( $testData );
87
88
		$untestedTemplates = array_diff( $mailTemplatePaths, $testTemplateNames );
89
90
		if ( !empty( $untestedTemplates ) ) {
91
			$hasErrors = true;
92
			$output->writeln(
93
				'<error>There are untested templates: ' . implode( ', ', $untestedTemplates ) . '</error>'
94
			);
95
		}
96
97
		$strayTemplates = array_diff( $testTemplateNames, $mailTemplatePaths );
98
99
		if ( !empty( $strayTemplates ) ) {
100
			$hasErrors = true;
101
			$output->writeln(
102
				'<error>There are tests for non-existing templates: ' . implode( ', ', $strayTemplates ) . '</error>'
103
			);
104
		}
105
		return $hasErrors;
106
	}
107
108
	/**
109
	 * Render all templates and write them to disk to allow a comparison with an alternative data set
110
	 *
111
	 * @param array $testData Template names and fixture information to render these templates
112
	 * @param Environment $twig The templating engine to render the templates
113
	 * @param string $outputPath Path where rendered templates will be written to
114
	 * @param OutputInterface $output Command output
115
	 * @return bool
116
	 */
117
	private function renderTemplates( array $testData, Environment $twig, string $outputPath, OutputInterface $output ): bool {
118
		$hasErrors = false;
119
		foreach ( $testData as $templateFileName => $templateSettings ) {
120
121
			if ( empty( $templateSettings['variants'] ) ) {
122
				$templateSettings['variants'] = [ '' => [] ];
123
			}
124
125
			foreach ( $templateSettings['variants'] as $variantName => $additionalContext ) {
126
				$outputName =
127
					$outputPath .
128
					basename( $templateFileName, '.txt.twig' ) .
129
					( $variantName ? ".$variantName" : '' ) .
130
					'.txt';
131
132
				$output->write( "$outputName" );
133
				if ( file_exists( $outputName ) ) {
134
					$output->writeln( "$outputName already exists, skipping ..." );
135
					continue;
136
				}
137
138
				try {
139
					file_put_contents(
140
						$outputName,
141
						MailFormatter::format(
142
							$twig->render(
143
								$templateFileName,
144
								array_merge_recursive(
145
									$templateSettings['context'],
146
									$additionalContext
147
								)
148
							)
149
						)
150
					);
151
				} catch ( Error $e ) {
152
					$hasErrors = true;
153
					$output->writeln( '' );
154
					$output->writeln( '<error>' . $e->getMessage() . '</error>' );
155
					$output->writeln( var_export( $e->getSourceContext(), true ) );
156
				}
157
				$output->writeln( '' );
158
			}
159
		}
160
		return $hasErrors;
161
	}
162
}
163