Completed
Push — master ( f4a302...e32202 )
by Alex
03:22
created

StepsPass::processStepAction()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 46
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

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