StepsPass::processStepAction()   C
last analyzed

Complexity

Conditions 12
Paths 35

Size

Total Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 50
rs 6.9666
c 0
b 0
f 0
cc 12
nc 35
nop 4

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the PierstovalCharacterManagerBundle package.
7
 *
8
 * (c) Alexandre Rock Ancelet <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Pierstoval\Bundle\CharacterManagerBundle\DependencyInjection\Compiler;
15
16
use Doctrine\Common\Inflector\Inflector;
17
use Doctrine\ORM\EntityManagerInterface;
18
use Pierstoval\Bundle\CharacterManagerBundle\Action\AbstractStepAction;
19
use Pierstoval\Bundle\CharacterManagerBundle\Action\StepActionInterface;
20
use Pierstoval\Bundle\CharacterManagerBundle\Registry\ActionsRegistry;
21
use Pierstoval\Bundle\CharacterManagerBundle\Resolver\StepResolverInterface;
22
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
23
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
24
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
25
use Symfony\Component\DependencyInjection\ContainerBuilder;
26
use Symfony\Component\DependencyInjection\Definition;
27
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
28
use Symfony\Component\DependencyInjection\Reference;
29
use Symfony\Component\Routing\RouterInterface;
30
use Symfony\Contracts\Translation\TranslatorInterface;
31
use Twig\Environment;
32
33
/**
34
 * Check that every action class extends the right action interface.
35
 * And for each step-tagged service, if the class extends the provided abstract class,
36
 *  calls some useful methods.
37
 */
38
class StepsPass implements CompilerPassInterface
39
{
40
    public const PARAMETERS_MANAGERS = 'pierstoval_character_manager.managers';
41
42
    /**
43
     * {@inheritdoc}
44
     */
45
    public function process(ContainerBuilder $container): void
46
    {
47
        $this->validateManagers($container);
48
        $this->processConfiguredServices($container);
49
    }
50
51
    private function validateManagers(ContainerBuilder $container): void
52
    {
53
        $managers = $container->getParameter(static::PARAMETERS_MANAGERS);
54
55
        foreach ($managers as $name => $config) {
56
            $config['name'] = $name;
57
            $managers[$name] = $this->validateManagerSteps($config, $container);
58
        }
59
60
        $container->setParameter(static::PARAMETERS_MANAGERS, $managers);
61
    }
62
63
    /**
64
     * Validate steps defined in configuration, and makes sure all "action" classes are instances of StepActionInterface.
65
     */
66
    private function validateManagerSteps(array $managerConfiguration, ContainerBuilder $container): array
67
    {
68
        /** @var array[] $steps */
69
        $steps = $managerConfiguration['steps'];
70
71
        $stepNumber = 1;
72
73
        // Validate steps that can't be validated in Configuration.
74
        // First loop mandatory because we re-validate each step dependency in another loop after normalization.
75
        foreach ($steps as $id => $step) {
76
            $name = $step['name'] = $id;
77
78
            if (!$step['label']) {
79
                $step['label'] = $this->generateStepLabel($step['name']);
80
            }
81
82
            $step['manager_name'] = $managerConfiguration['name'];
83
84
            $step['number'] = $stepNumber++;
85
86
            $steps[$name] = $step;
87
        }
88
89
        foreach ($steps as $name => $step) {
90
            // Validate steps to disable on change, to be sure each step is defined.
91
            foreach ($step['onchange_clear'] as $stepToDisable) {
92
                if (!\array_key_exists($stepToDisable, $steps)) {
93
                    throw new InvalidConfigurationException(\sprintf(
94
                        'Step to disable must be a valid step name, "%s" given.'."\n".
95
                        'Available steps: %s',
96
                        $stepToDisable,
97
                        \implode(', ', \array_keys($steps))
98
                    ));
99
                }
100
            }
101
102
            // Validate steps dependencies, to be sure each step is defined.
103
            foreach ($step['dependencies'] as $stepDependency) {
104
                if (!\array_key_exists($stepDependency, $steps)) {
105
                    throw new InvalidConfigurationException(\sprintf(
106
                        'Step dependency must be a valid step name, "%s" given.'."\n".
107
                        'Available steps: %s',
108
                        $stepDependency,
109
                        \implode(', ', \array_keys($steps))
110
                    ));
111
                }
112
            }
113
114
            // Validate step actions.
115
            $action = $step['action'];
116
117
            // Check if action defined as a service or as a simple class.
118
            $class = $container->has($action) ? $container->getDefinition($action)->getClass() : $action;
119
120
            if (!\class_exists($class) || !\is_a($class, StepActionInterface::class, true)) {
121
                throw new InvalidArgumentException(\sprintf(
122
                    'Step action must be a valid class implementing %s. "%s" given.',
123
                    StepActionInterface::class,
124
                    \class_exists($class) ? $class : \gettype($class)
125
                ));
126
            }
127
        }
128
129
        // And update all steps.
130
        $managerConfiguration['steps'] = $steps;
131
132
        return $managerConfiguration;
133
    }
134
135
    /**
136
     * Automatically convert the actions into services.
137
     * If they're defined as classes, this has the advantage to autowire them, etc.
138
     */
139
    private function processConfiguredServices(ContainerBuilder $container): void
140
    {
141
        if (!$container->hasDefinition(ActionsRegistry::class)) {
142
            throw new InvalidConfigurationException('Step actions registry not set in your configuration. Maybe the extension was not processed properly?');
143
        }
144
145
        $registryDefinition = $container->getDefinition(ActionsRegistry::class);
146
147
        foreach ($container->getParameter(static::PARAMETERS_MANAGERS) as $managerName => $config) {
148
            $this->processStepAction($managerName, $config, $registryDefinition, $container);
149
        }
150
    }
151
152
    private function processStepAction(string $managerName, array $config, Definition $registryDefinition, ContainerBuilder $container): void
153
    {
154
        /** @var array[] $finalSteps */
155
        $finalSteps = $config['steps'];
156
157
        foreach ($finalSteps as $step) {
158
            $action = $step['action'];
159
            if ($container->has($action)) {
160
                /** @var $definition */
161
                $definition = $container->getDefinition($action);
162
            } else {
163
                // If action is not yet a service, it means it's a class name.
164
                // In this case, we create a new service.
165
                $definition = new Definition($action);
166
                $definition
167
                    ->setPrivate(true)
168
                    ->setAutowired(true)
169
                ;
170
                $container->setDefinition($action, $definition);
171
            }
172
173
            $definition
174
                ->addMethodCall('configure', [
175
                    $managerName,
176
                    $step['name'],
177
                    $config['character_class'],
178
                    new Reference(StepResolverInterface::class),
179
                ])
180
            ;
181
182
            // If class extends the abstract one, we inject some cool services.
183
            if (\is_a($definition->getClass(), AbstractStepAction::class, true)) {
184
                if ($container->hasDefinition(EntityManagerInterface::class) || $container->hasAlias(EntityManagerInterface::class)) {
185
                    $definition->addMethodCall('setObjectManager', [new Reference(EntityManagerInterface::class)]);
186
                }
187
                if ($container->hasDefinition(Environment::class) || $container->hasAlias(Environment::class)) {
188
                    $definition->addMethodCall('setTwig', [new Reference(Environment::class)]);
189
                }
190
                if ($container->hasDefinition(RouterInterface::class) || $container->hasAlias(RouterInterface::class)) {
191
                    $definition->addMethodCall('setRouter', [new Reference(RouterInterface::class)]);
192
                }
193
                if ($container->hasDefinition(TranslatorInterface::class) || $container->hasAlias(TranslatorInterface::class)) {
194
                    $definition->addMethodCall('setTranslator', [new Reference(TranslatorInterface::class)]);
195
                }
196
            }
197
198
            // Finally add the step action to the registry
199
            $registryDefinition->addMethodCall('addStepAction', [$managerName, $step['name'], new ServiceClosureArgument(new Reference($action))]);
200
        }
201
    }
202
203
    private function generateStepLabel(string $name): string
204
    {
205
        return Inflector::ucwords(\trim(\str_replace(['.', '_', '-'], ' ', $name)));
206
    }
207
}
208