Generate::count()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 10
c 0
b 0
f 0
ccs 9
cts 9
cp 1
rs 9.9332
cc 4
nc 3
nop 1
crap 4
1
<?php
2
3
namespace Maketok\DataMigration\Action\Type;
4
5
use Faker\Generator;
6
use Maketok\DataMigration\Action\ActionInterface;
7
use Maketok\DataMigration\Action\ConfigInterface;
8
use Maketok\DataMigration\Action\Exception\WrongContextException;
9
use Maketok\DataMigration\ArrayUtilsTrait;
10
use Maketok\DataMigration\Expression\LanguageInterface;
11
use Maketok\DataMigration\MapInterface;
12
use Maketok\DataMigration\Storage\Db\ResourceHelperInterface;
13
use Maketok\DataMigration\Unit\GenerateUnitInterface;
14
use Maketok\DataMigration\Unit\ImportFileUnitInterface;
15
use Maketok\DataMigration\Unit\UnitBagInterface;
16
use Maketok\DataMigration\Unit\UnitInterface;
17
use Maketok\DataMigration\Workflow\ResultInterface;
18
19
/**
20
 * Generate data and insert into tmp files
21
 */
22
class Generate extends AbstractAction implements ActionInterface
0 ignored issues
show
Complexity introduced by
This class has a complexity of 53 which exceeds the configured maximum of 50.

The class complexity is the sum of the complexity of all methods. A very high value is usually an indication that your class does not follow the single reponsibility principle and does more than one job.

Some resources for further reading:

You can also find more detailed suggestions for refactoring in the “Code” section of your repository.

Loading history...
Complexity introduced by
The class Generate has a coupling between objects value of 20. Consider to reduce the number of dependencies under 13.
Loading history...
23
{
24
    use ArrayUtilsTrait;
25
26
    /**
27
     * @var UnitBagInterface|GenerateUnitInterface[]|ImportFileUnitInterface[]
28
     */
29
    protected $bag;
30
    /**
31
     * @var int
32
     */
33
    private $count;
34
    /**
35
     * @var Generator
36
     */
37
    private $generator;
38
    /**
39
     * @var array
40
     */
41
    private $buffer = [];
42
    /**
43
     * @var array
44
     */
45
    private $writeBuffer = [];
46
    /**
47
     * @var LanguageInterface
48
     */
49
    private $language;
50
    /**
51
     * @var ResultInterface
52
     */
53
    protected $result;
54
    /**
55
     * @var ResourceHelperInterface
56
     */
57
    private $helperResource;
58
    /**
59
     * @var MapInterface
60
     */
61
    private $map;
62
    /**
63
     * @var array
64
     */
65
    private $processedCounterContainer = [];
66
    /**
67
     * @var array
68
     */
69
    private $contributionBuffer = [];
70
    /**
71
     * Units that have been processed this iteration
72
     * @var array
73
     */
74
    private $processedUnits = [];
75
76
    /**
77
     * @param UnitBagInterface $bag
78
     * @param ConfigInterface $config
79
     * @param LanguageInterface $language
80
     * @param Generator $generator
81
     * @param int $count
82
     * @param MapInterface $map
83
     * @param ResourceHelperInterface $helperResource
84
     */
85 8
    public function __construct(
86
        UnitBagInterface $bag,
87
        ConfigInterface $config,
88
        LanguageInterface $language,
89
        Generator $generator,
90
        $count,
91
        MapInterface $map,
92
        ResourceHelperInterface $helperResource
93
    ) {
94 8
        parent::__construct($bag, $config);
95 8
        $this->count = $count;
96 8
        $this->generator = $generator;
97 8
        $this->language = $language;
98 8
        $this->helperResource = $helperResource;
99 8
        $this->map = $map;
100 8
    }
101
102
    /**
103
     * {@inheritdoc}
104
     * @throws WrongContextException
105
     * @throws \LogicException
106
     */
107 6
    public function process(ResultInterface $result)
108
    {
109 6
        $this->result = $result;
110
        try {
111 6
            $this->start();
112 5
            while ($this->count > 0) {
113 5
                foreach ($this->bag as $unit) {
114 5
                    if (in_array($unit->getCode(), $this->processedUnits)) {
115 2
                        continue;
116
                    }
117 5
                    list($max, $center) = $unit->getGenerationSeed();
118 5
                    $rnd = $this->getRandom($max, $center);
119 5
                    $i = 0;
120 5
                    while ($rnd > 0) {
121 5
                        $siblings = $unit->getSiblings();
122 5
                        array_unshift($siblings, $unit);
123
                        /** @var GenerateUnitInterface|ImportFileUnitInterface $innerUnit */
124 5
                        foreach ($siblings as $innerUnit) {
125 5
                            $this->processAdditions($innerUnit, $i);
126 5
                            if (!$this->shouldWrite($innerUnit)) {
127
                                $this->processedUnits[] = $innerUnit->getCode();
128
                                continue 1;
129
                            }
130 5
                            $row = $this->getMappedRow($innerUnit);
131
                            // we care about parent ;)
132
                            /** @var ImportFileUnitInterface|GenerateUnitInterface $parent */
133 5
                            if ($parent = $innerUnit->getParent()) {
134 3
                                $this->updateParents($parent);
135 3
                            }
136
                            // freeze map after 1st addition
137 5
                            $this->map->freeze();
138 5
                            $this->buffer[$innerUnit->getCode()] = $row;
139 5
                            $this->processedUnits[] = $innerUnit->getCode();
140 5
                            $this->writeBuffered($innerUnit->getCode(), $row);
141 5
                            $this->count($innerUnit);
142 5
                        }
143 5
                        $rnd--;
144 5
                        $i++;
145 5
                    }
146 5
                }
147 5
                $this->contributionBuffer = [];
148 5
                $this->processedCounterContainer = [];
149 5
                $this->writeRows();
150 5
                $this->map->unFreeze();
151 5
                $this->count--;
152 5
                $this->processedUnits = [];
153 5
                $this->buffer = [];
154 5
            }
155 6
        } catch (\Exception $e) {
156 1
            $this->close();
157 1
            throw $e;
158
        }
159 5
        $this->close();
160 5
    }
161
162
    /**
163
     * @param GenerateUnitInterface|ImportFileUnitInterface $unit
164
     * @param int $idx
165
     */
166 5
    protected function processAdditions(GenerateUnitInterface $unit, $idx = 0)
167
    {
168 5
        if (isset($this->contributionBuffer[$idx])) {
169 4
            $cBuffer = $this->contributionBuffer[$idx];
170 4
        } else {
171 5
            $this->contributionBuffer[$idx] = [];
172 5
            $cBuffer = [];
173
        }
174 5
        if (!in_array($unit->getCode(), $cBuffer)) {
175 5
            foreach ($unit->getGenerationContributions() as $contribution) {
176
                $this->language->evaluate($contribution, [
177
                    'generator' => $this->generator,
178
                    'map' => $this->map,
179
                    'resource' => $this->helperResource,
180
                    'hashmaps' => $unit->getHashmaps(),
181
                ]);
182 5
            }
183 5
            $this->contributionBuffer[$idx][] = $unit->getCode();
184 5
        }
185
186
        /** @var GenerateUnitInterface|ImportFileUnitInterface $sibling */
187 5
        foreach ($unit->getSiblings() as $sibling) {
188 2
            if (!in_array($sibling->getCode(), $cBuffer)) {
189 2
                foreach ($sibling->getGenerationContributions() as $contribution) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Maketok\DataMigration\Unit\UnitInterface as the method getGenerationContributions() does only exist in the following implementations of said interface: Maketok\DataMigration\Unit\Type\GeneratorUnit, Maketok\DataMigration\Unit\Type\Unit.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

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

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
190
                    $this->language->evaluate($contribution, [
191
                        'generator' => $this->generator,
192
                        'map' => $this->map,
193
                        'resource' => $this->helperResource,
194
                        'hashmaps' => $sibling->getHashmaps(),
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Maketok\DataMigration\Unit\UnitInterface as the method getHashmaps() does only exist in the following implementations of said interface: Maketok\DataMigration\Unit\Type\ExportDbUnit, Maketok\DataMigration\Unit\Type\ExportFileUnit, Maketok\DataMigration\Unit\Type\GeneratorUnit, Maketok\DataMigration\Unit\Type\ImportDbUnit, Maketok\DataMigration\Unit\Type\ImportFileUnit, Maketok\DataMigration\Unit\Type\Unit.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

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

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
195
                    ]);
196 2
                }
197 2
                $this->contributionBuffer[$idx][] = $sibling->getCode();
198 2
            }
199 5
        }
200 5
    }
201
202
    /**
203
     * @param ImportFileUnitInterface $unit
204
     * @return bool
205
     */
206 5
    protected function shouldWrite(ImportFileUnitInterface $unit)
207
    {
208 5
        foreach ($unit->getWriteConditions() as $condition) {
209
            $shouldAdd = $this->language->evaluate($condition, [
210
                'generator' => $this->generator,
211
                'map' => $this->map,
212
                'resource' => $this->helperResource,
213
                'hashmaps' => $unit->getHashmaps(),
214
            ]);
215
            if (!$shouldAdd) {
216
               return false;
217
            }
218 5
        }
219 5
        return true;
220
    }
221
222
    /**
223
     * @param UnitInterface $unit
224
     */
225 5
    protected function count(UnitInterface $unit)
226
    {
227 5
        if (($this->bag->getUnitLevel($unit->getCode()) == 1) && !in_array($unit->getCode(), $this->processedCounterContainer)) {
228 5
            $this->result->incrementActionProcessed($this->getCode());
229 5
            $this->processedCounterContainer[] = $unit->getCode();
230 5
            foreach ($unit->getSiblings() as $sibling) {
231 2
                $this->processedCounterContainer[] = $sibling->getCode();
232 5
            }
233 5
        }
234 5
    }
235
236
    /**
237
     * @param GenerateUnitInterface|ImportFileUnitInterface $unit
238
     * @return array
239
     */
240 5
    protected function getMappedRow(GenerateUnitInterface $unit)
241
    {
242
        return array_map(function ($el) use ($unit) {
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $el. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
243 5
            return $this->language->evaluate($el, [
244 5
                'generator' => $this->generator,
245 5
                'resource' => $this->helperResource,
246 5
                'map' => $this->map,
247 5
                'units' => $this->buffer,
248 5
                'hashmaps' => $unit->getHashmaps(),
249 5
            ]);
250 5
        }, $unit->getGeneratorMapping());
251
    }
252
253
    /**
254
     * prepare map
255
     * @deprecated do not use
256
     */
257
    protected function prepareMap()
258
    {
259
        if (!empty($this->buffer)) {
260
            $assembledBuffer = $this->assembleResolve($this->buffer);
261
            if ($this->map->isFresh($assembledBuffer)) {
262
                $this->map->feed($assembledBuffer);
263
            }
264
        }
265
    }
266
267
    /**
268
     * @param GenerateUnitInterface|ImportFileUnitInterface $parent
269
     */
270 3
    protected function updateParents(GenerateUnitInterface $parent)
271
    {
272
        $parentRow = array_map(function ($el) use ($parent) {
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $el. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
273 3
            return $this->language->evaluate($el, [
274 3
                'generator' => $this->generator,
275 3
                'resource' => $this->helperResource,
276 3
                'map' => $this->map,
277 3
                'units' => $this->buffer,
278 3
                'hashmaps' => $parent->getHashmaps(),
279 3
            ]);
280 3
        }, $parent->getGeneratorMapping());
281 3
        if ($this->shouldWrite($parent)) {
0 ignored issues
show
Documentation introduced by
$parent is of type object<Maketok\DataMigra...\GenerateUnitInterface>, but the function expects a object<Maketok\DataMigra...mportFileUnitInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
282 3
            $this->buffer[$parent->getCode()] = $parentRow;
283 3
            $this->writeBuffered($parent->getCode(), $parentRow, true);
284 3
        }
285
        // account for parent siblings :)
286
        /** @var GenerateUnitInterface|ImportFileUnitInterface $sibling */
287 3
        foreach ($parent->getSiblings() as $sibling) {
288
            $siblingRow = array_map(function ($el) use ($sibling) {
289
                return $this->language->evaluate($el, [
290
                    'generator' => $this->generator,
291
                    'resource' => $this->helperResource,
292
                    'map' => $this->map,
293
                    'units' => $this->buffer,
294
                    'hashmaps' => $sibling->getHashmaps(),
295
                ]);
296
            }, $sibling->getGeneratorMapping());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Maketok\DataMigration\Unit\UnitInterface as the method getGeneratorMapping() does only exist in the following implementations of said interface: Maketok\DataMigration\Unit\Type\GeneratorUnit, Maketok\DataMigration\Unit\Type\Unit.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

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

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
297
            if ($this->shouldWrite($sibling)) {
0 ignored issues
show
Compatibility introduced by
$sibling of type object<Maketok\DataMigration\Unit\UnitInterface> is not a sub-type of object<Maketok\DataMigra...mportFileUnitInterface>. It seems like you assume a child interface of the interface Maketok\DataMigration\Unit\UnitInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
298
                $this->buffer[$sibling->getCode()] = $siblingRow;
299
                $this->writeBuffered($sibling->getCode(), $siblingRow, true);
300
            }
301 3
        }
302 3
    }
303
304
    /**
305
     * @param string $unitCode
306
     * @param array $row
307
     * @param bool $replace
308
     */
309 5
    protected function writeBuffered($unitCode, $row, $replace = false)
310
    {
311 5
        if (isset($this->writeBuffer[$unitCode]) && is_array($this->writeBuffer[$unitCode])) {
312 3
            if ($replace) {
313
                // pop last element into void
314 3
                array_pop($this->writeBuffer[$unitCode]);
315 3
            }
316 3
            $this->writeBuffer[$unitCode][] = $row;
317 3
        } else {
318 5
            $this->writeBuffer[$unitCode] = [$row];
319
        }
320 5
    }
321
322
    /**
323
     * write buffered rows
324
     */
325 5
    protected function writeRows()
326
    {
327 5
        foreach ($this->bag as $unit) {
328 5
            if (isset($this->buffer[$unit->getCode()])) {
329 5
                $buffered = $this->writeBuffer[$unit->getCode()];
330 5
                foreach ($buffered as $row) {
331 5
                    $unit->getFilesystem()->writeRow($row);
332 5
                }
333 5
            }
334 5
        }
335 5
        $this->writeBuffer = [];
336 5
    }
337
338
    /**
339
     * @param int $max
340
     * @param int $center
341
     * @param int $min
342
     * @return int
343
     * @throws \LogicException
344
     */
345 6
    public function getRandom($max, $center, $min = 1)
346
    {
347 6
        $min = (int) $min;
348 6
        $max = (int) $max;
349 6
        $center = (int) $center;
350 6
        if ($min == $max) {
351 5
            return $min;
352
        }
353 4
        if ($min > $max || $min > $center || $center > $max) {
354
            throw new \LogicException("Wrong values given.");
355
        }
356
        // get 1/8
357 4
        $period = (int) ceil($max/8);
358 4
        $pMin = max($min, $center - $period);
359 4
        $pMax = min($center+$period, $max);
360 4
        $s1 = mt_rand(0, 1);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $s1. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
361 4
        if ($s1 == 0) {
362 4
            return mt_rand($pMin, $pMax);
363
        } else {
364 4
            $s2 = mt_rand(0, 1);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $s2. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
365 4
            if (0 == $s2) {
366 3
                return mt_rand($min, $pMin);
367
            } else {
368 4
                return mt_rand($pMax, $max);
369
            }
370
        }
371
    }
372
373
    /**
374
     * open handlers
375
     * @throws WrongContextException
376
     */
377 6
    private function start()
378
    {
379 6
        $this->result->setActionStartTime($this->getCode(), new \DateTime());
380 6
        foreach ($this->bag as $unit) {
381 6
            if ($unit->getGeneratorMapping() === null) {
382 1
                throw new WrongContextException(
383 1
                    sprintf(
384 1
                        "Can not use generation with unit %s. No generation mapping found.",
385 1
                        $unit->getCode()
386 1
                    )
387 1
                );
388
            }
389 5
            $unit->setTmpFileName($this->getTmpFileName($unit));
390 5
            $unit->getFilesystem()->open($unit->getTmpFileName(), 'w');
391 5
        }
392 5
    }
393
394
    /**
395
     * close all handlers
396
     */
397 6
    private function close()
398
    {
399 6
        foreach ($this->bag as $unit) {
400 6
            $unit->getFilesystem()->close();
401 6
        }
402 6
        $this->result->setActionEndTime($this->getCode(), new \DateTime());
403 6
    }
404
405
    /**
406
     * {@inheritdoc}
407
     */
408 7
    public function getCode()
409
    {
410 7
        return 'generate';
411
    }
412
}
413