Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
1 | <?php |
||
32 | final class ContextServiceEnvironmentHandler implements EnvironmentHandler |
||
33 | { |
||
34 | /** @var KernelInterface */ |
||
35 | private $symfonyKernel; |
||
36 | |||
37 | /** @var EnvironmentHandler */ |
||
38 | private $decoratedEnvironmentHandler; |
||
39 | |||
40 | /** @var ContextInitializer[] */ |
||
41 | private $contextInitializers = []; |
||
42 | |||
43 | public function __construct(KernelInterface $symfonyKernel, EnvironmentHandler $decoratedEnvironmentHandler) |
||
48 | |||
49 | public function registerContextInitializer(ContextInitializer $contextInitializer): void |
||
53 | |||
54 | public function supportsSuite(Suite $suite): bool |
||
58 | |||
59 | public function buildEnvironment(Suite $suite): Environment |
||
81 | |||
82 | public function supportsEnvironmentAndSubject(Environment $environment, $testSubject = null): bool |
||
86 | |||
87 | /** |
||
88 | * @throws EnvironmentIsolationException |
||
89 | */ |
||
90 | public function isolateEnvironment(Environment $uninitializedEnvironment, $testSubject = null): Environment |
||
91 | { |
||
92 | $this->assertEnvironmentCanBeIsolated($uninitializedEnvironment, $testSubject); |
||
93 | |||
94 | $environment = new InitializedSymfonyExtensionEnvironment($uninitializedEnvironment->getSuite()); |
||
95 | |||
96 | foreach ($uninitializedEnvironment->getServices() as $serviceId) { |
||
|
|||
97 | /** @var Context $context */ |
||
98 | $context = $this->getContainer()->get($serviceId); |
||
99 | |||
100 | $this->initializeContext($context); |
||
101 | |||
102 | $environment->registerContext($context); |
||
103 | } |
||
104 | |||
105 | /** @var InitializedContextEnvironment $delegatedEnvironment */ |
||
106 | $delegatedEnvironment = $this->decoratedEnvironmentHandler->isolateEnvironment($uninitializedEnvironment->getDelegatedEnvironment()); |
||
107 | |||
108 | foreach ($delegatedEnvironment->getContexts() as $context) { |
||
109 | $environment->registerContext($context); |
||
110 | } |
||
111 | |||
112 | return $environment; |
||
113 | } |
||
114 | |||
115 | private function initializeContext(Context $context): void |
||
116 | { |
||
117 | foreach ($this->contextInitializers as $contextInitializer) { |
||
118 | $contextInitializer->initializeContext($context); |
||
119 | } |
||
120 | } |
||
121 | |||
122 | /** |
||
123 | * @return string[] |
||
124 | * |
||
125 | * @throws SuiteConfigurationException If "contexts" setting is not an array |
||
126 | */ |
||
127 | private function getSuiteContextsServices(Suite $suite): array |
||
128 | { |
||
129 | $contexts = $suite->getSetting('contexts'); |
||
130 | |||
131 | View Code Duplication | if (!is_array($contexts)) { |
|
132 | throw new SuiteConfigurationException(sprintf( |
||
133 | '"contexts" setting of the "%s" suite is expected to be an array, %s given.', |
||
134 | $suite->getName(), |
||
135 | gettype($contexts) |
||
136 | ), $suite->getName()); |
||
137 | } |
||
138 | |||
139 | return array_map([$this, 'normalizeContext'], $contexts); |
||
140 | } |
||
141 | |||
142 | private function cloneSuiteWithoutContexts(Suite $suite, array $contextsToRemove): Suite |
||
143 | { |
||
144 | $contexts = $suite->getSetting('contexts'); |
||
145 | |||
146 | View Code Duplication | if (!is_array($contexts)) { |
|
147 | throw new SuiteConfigurationException(sprintf( |
||
148 | '"contexts" setting of the "%s" suite is expected to be an array, %s given.', |
||
149 | $suite->getName(), |
||
150 | gettype($contexts) |
||
151 | ), $suite->getName()); |
||
152 | } |
||
153 | |||
154 | $contexts = array_filter($contexts, function ($context) use ($contextsToRemove): bool { |
||
155 | return !in_array($this->normalizeContext($context), $contextsToRemove, true); |
||
156 | }); |
||
157 | |||
158 | return new GenericSuite($suite->getName(), array_merge($suite->getSettings(), ['contexts' => $contexts])); |
||
159 | } |
||
160 | |||
161 | private function normalizeContext($context): string |
||
162 | { |
||
163 | if (is_array($context)) { |
||
164 | return current(array_keys($context)); |
||
165 | } |
||
166 | |||
167 | if (is_string($context)) { |
||
168 | return $context; |
||
169 | } |
||
170 | |||
171 | throw new \Exception(); |
||
172 | } |
||
173 | |||
174 | /** |
||
175 | * @psalm-assert UninitializedSymfonyExtensionEnvironment $uninitializedEnvironment |
||
176 | * |
||
177 | * @throws EnvironmentIsolationException |
||
178 | */ |
||
179 | private function assertEnvironmentCanBeIsolated(Environment $uninitializedEnvironment, $testSubject): void |
||
189 | |||
190 | private function getContainer(): ContainerInterface |
||
206 | } |
||
207 |
Let’s take a look at an example:
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.
Available Fixes
Change the type-hint for the parameter:
Add an additional type-check:
Add the method to the interface: