ReferenceExecutor   B
last analyzed

Complexity

Total Complexity 50

Size/Duplication

Total Lines 231
Duplicated Lines 0 %

Test Coverage

Coverage 74.55%

Importance

Changes 3
Bugs 1 Features 0
Metric Value
eloc 114
dl 0
loc 231
ccs 82
cts 110
cp 0.7455
rs 8.4
c 3
b 1
f 0
wmc 50

7 Methods

Rating   Name   Duplication   Size   Complexity  
A dumpVar() 0 19 3
B dump() 0 26 9
B save() 0 38 9
C load() 0 50 13
A __construct() 0 4 1
C set() 0 41 12
A execute() 0 17 3

How to fix   Complexity   

Complex Class

Complex classes like ReferenceExecutor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ReferenceExecutor, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Kaliop\eZMigrationBundle\Core\Executor;
4
5
use Kaliop\eZMigrationBundle\API\EnumerableReferenceResolverInterface;
6
use Kaliop\eZMigrationBundle\API\Exception\InvalidStepDefinitionException;
7
use Kaliop\eZMigrationBundle\API\Exception\MigrationBundleException;
8
use Kaliop\eZMigrationBundle\API\ReferenceResolverBagInterface;
9
use Kaliop\eZMigrationBundle\API\Value\MigrationStep;
10
use Symfony\Component\Console\Output\OutputInterface;
11
use Symfony\Component\DependencyInjection\ContainerInterface;
12
use Symfony\Component\VarDumper\Cloner\VarCloner;
13
use Symfony\Component\VarDumper\Dumper\CliDumper;
14
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
15
use Symfony\Component\Yaml\Yaml;
16
17
/**
18
 * @property ReferenceResolverBagInterface $referenceResolver
19
 */
20
class ReferenceExecutor extends AbstractExecutor
21
{
22
    use IgnorableStepExecutorTrait;
0 ignored issues
show
Bug introduced by
The trait Kaliop\eZMigrationBundle...orableStepExecutorTrait requires the property $dsl which is not provided by Kaliop\eZMigrationBundle...cutor\ReferenceExecutor.
Loading history...
23
    use ReferenceSetterTrait;
24
25
    protected $supportedStepTypes = array('reference');
26
    protected $supportedActions = array('set', 'load', 'save', 'dump');
27
28 149
    protected $container;
29
30 149
    public function __construct(ContainerInterface $container, ReferenceResolverBagInterface $referenceResolver)
31 149
    {
32 149
        $this->container = $container;
33
        $this->referenceResolver = $referenceResolver;
34
    }
35
36
    /**
37
     * @param MigrationStep $step
38
     * @return mixed
39 30
     * @throws \Exception
40
     */
41 30
    public function execute(MigrationStep $step)
42
    {
43 30
        parent::execute($step);
44
45
        if (!isset($step->dsl['mode'])) {
46
            throw new InvalidStepDefinitionException("Invalid step definition: missing 'mode'");
47 30
        }
48
49 30
        $action = $step->dsl['mode'];
50
51
        if (!in_array($action, $this->supportedActions)) {
52
            throw new InvalidStepDefinitionException("Invalid step definition: value '$action' is not allowed for 'mode'");
53 30
        }
54
55 30
        $this->skipStepIfNeeded($step);
56
57
        return $this->$action($step->dsl, $step->context);
58 27
    }
59
60 27
    protected function set($dsl, $context)
61
    {
62
        if (!isset($dsl['identifier'])) {
63 27
            throw new InvalidStepDefinitionException("Invalid step definition: miss 'identifier' for setting reference");
64
        }
65
        if (!isset($dsl['value'])) {
66
            throw new InvalidStepDefinitionException("Invalid step definition: miss 'value' for setting reference");
67 27
        }
68
69 26
        if (!isset($dsl['resolve_references']) || $dsl['resolve_references']) {
70
            // this makes sense since we started supporting embedded refs...
71 1
            $value = $this->resolveReferencesRecursively($dsl['value']);
72
        } else {
73
            $value = $dsl['value'];
74 27
        }
75 27
76
        /// @todo make this happen recursively too in case the ref value is an array. Overload resolveReferencesRecursively
77
        if (is_string($value)) {
78
            if (0 === strpos($value, '%env(') && ')%' === substr($value, -2) && '%env()%' !== $value) {
79
                /// @todo find out how to use Sf components to resolve this value instead of doing it by ourselves...
80
                $env = substr($value, 5, -2);
81
                // we use getenv because $_ENV gets cleaned up (by whom?)
82
                $val = getenv($env);
83
                if ($val === false) {
84
                    throw new MigrationBundleException("Env var $env seems not to be defined");
85
                }
86
                $value = $val;
87 27
            } else {
88
                /// @todo add support for eZ dynamic parameters too
89 1
90
                if (preg_match('/.*%.+%.*$/', $value)) {
91
                    // we use the same parameter resolving rule as symfony, even though this means abusing the ContainerInterface
92
                    $value = $this->container->getParameterBag()->resolveString($value);
0 ignored issues
show
Bug introduced by
The method getParameterBag() does not exist on Symfony\Component\Depend...tion\ContainerInterface. Did you maybe mean getParameter()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

92
                    $value = $this->container->/** @scrutinizer ignore-call */ getParameterBag()->resolveString($value);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
93
                }
94 27
            }
95 27
        }
96
97 27
        $overwrite = isset($dsl['overwrite']) ? $dsl['overwrite'] : false;
98
        $this->addReference($dsl['identifier'], $value, $overwrite);
99
100 1
        return $value;
101
    }
102 1
103
    protected function load($dsl, $context)
104
    {
105 1
        if (!isset($dsl['file'])) {
106 1
            throw new InvalidStepDefinitionException("Invalid step definition: miss 'file' for loading references");
107
        }
108 1
        $fileName = $this->resolveReference($dsl['file']);
109
        $fileName = str_replace('{ENV}', $this->container->get('kernel')->getEnvironment(), $fileName);
110 1
111 1
        $overwrite = isset($dsl['overwrite']) ? $dsl['overwrite'] : false;
112
113
        if (!is_file($fileName) && is_file(dirname($context['path']) . '/references/' . $fileName)) {
114 1
            $fileName = dirname($context['path']) . '/references/' . $fileName;
115
        }
116
117 1
        if (!is_file($fileName)) {
118
            throw new InvalidStepDefinitionException("Invalid step definition: invalid file '$fileName' for loading references");
119 1
        }
120 1
        $data = file_get_contents($fileName);
121 1
122
        $ext = pathinfo($fileName, PATHINFO_EXTENSION);
123
        switch ($ext) {
124 1
            case 'json':
125
                $data = json_decode($data, true);
126 1
                break;
127 1
            case 'yml':
128
            case 'yaml':
129
                $data = Yaml::parse($data);
130
                break;
131
            default:
132 1
                throw new InvalidStepDefinitionException("Invalid step definition: unsupported file extension '$ext' for loading references from");
133
        }
134
135
        if (!is_array($data)) {
136
            // the error is not in the step definition here, but rather in the data file
137 1
            throw new MigrationBundleException("Invalid step definition: file does not contain an array of key/value pairs");
138 1
        }
139
140
        foreach ($data as $refName => $value) {
141
            /// @todo what about '%env()%' syntax ?
142 1
143
            if (preg_match('/%.+%$/', $value)) {
144
                $value = $this->container->getParameter(trim($value, '%'));
145
            }
146
147 1
            if (!$this->addReference($refName, $value, $overwrite)) {
148
                throw new MigrationBundleException("Failed adding to Reference Resolver the reference: $refName");
149
            }
150
        }
151
152
        return $data;
153 1
    }
154
155 1
    /**
156
     * @todo find a smart way to allow saving the references file next to the current migration
157
     */
158 1
    protected function save($dsl, $context)
159 1
    {
160
        if (!isset($dsl['file'])) {
161 1
            throw new InvalidStepDefinitionException("Invalid step definition: miss 'file' for saving references");
162
        }
163 1
        $fileName = $this->resolveReference($dsl['file']);
164
        $fileName = str_replace('{ENV}', $this->container->get('kernel')->getEnvironment(), $fileName);
165
166
        $overwrite = isset($dsl['overwrite']) ? $dsl['overwrite'] : false;
167
168 1
        if (is_file($fileName) && !$overwrite) {
169
            // the error is not in the step definition, really, but in external circumstances
170
            throw new MigrationBundleException("Invalid step definition: file '$fileName' for saving references already exists");
171
        }
172 1
173
        if (! $this->referenceResolver instanceof EnumerableReferenceResolverInterface) {
174 1
            throw new MigrationBundleException("Can not save references as resolver is not enumerable");
175 1
        }
176 1
177
        $data = $this->referenceResolver->listReferences();
178
179 1
        $ext = pathinfo($fileName, PATHINFO_EXTENSION);
180
        switch ($ext) {
181
            case 'json':
182 1
                $data = json_encode($data, JSON_PRETTY_PRINT);
183 1
                break;
184
            case 'yml':
185
            case 'yaml':
186
            /// @todo use Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE option if it is supported
187
                $data = Yaml::dump($data);
188 1
                break;
189
            default:
190 1
                throw new InvalidStepDefinitionException("Invalid step definition: unsupported file extension '$ext' for saving references to");
191
        }
192
193 18
        file_put_contents($fileName, $data);
194
195 18
        return $data;
196
    }
197
198 18
    protected function dump($dsl, $context)
199
    {
200
        if (!isset($dsl['identifier'])) {
201 18
            throw new InvalidStepDefinitionException("Invalid step definition: miss 'identifier' for dumping reference");
202 1
        }
203
        if (!$this->referenceResolver->isReference($dsl['identifier'])) {
204
            throw new InvalidStepDefinitionException("Invalid step definition: identifier '{$dsl['identifier']}' is not a reference");
205 17
        }
206 1
        if (isset($context['output']) && $context['output'] instanceof OutputInterface && $context['output']->isQuiet()) {
207
            return $this->resolveReference($dsl['identifier']);
208 17
        }
209
210 17
        if (isset($dsl['label'])) {
211
            $label = $dsl['label'];
212 16
        } else {
213 16
            $label = $this->dumpVar($dsl['identifier']);
214
        }
215
        $value = $this->dumpVar($this->resolveReference($dsl['identifier']));
216
217
        if (isset($context['output']) && $context['output'] instanceof OutputInterface) {
218 16
            $context['output']->write($label . $value, false, OutputInterface::OUTPUT_RAW|OutputInterface::VERBOSITY_NORMAL);
219
        } else {
220
            echo $label . $value;
221
        }
222
223
        return $value;
224
    }
225
226
    /**
227 17
     * Similar to VarDumper::dump(), but returns the output
228
     * @param mixed $var
229 17
     * @return string
230 17
     * @throws \ErrorException
231 17
     */
232
    protected function dumpVar($var)
233 17
    {
234 17
        $cloner = new VarCloner();
235
        $dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? new CliDumper() : new HtmlDumper();
236
        $output = '';
237 17
238
        $dumper->dump(
239
            $cloner->cloneVar($var),
240 17
            function ($line, $depth) use (&$output) {
241
                // A negative depth means "end of dump"
242 17
                if ($depth >= 0) {
243
                    // Adds a two spaces indentation to the line
244
                    /// @todo should we use NBSP for html dumping?
245 17
                    $output .= str_repeat('  ', $depth) . $line . "\n";
246
                }
247
            }
248
        );
249
250
        return $output;
251
    }
252
}
253