Completed
Pull Request — master (#188)
by Vincent
03:16
created

EntityContext   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 313
Duplicated Lines 2.56 %

Coupling/Cohesion

Components 2
Dependencies 9

Importance

Changes 12
Bugs 4 Features 3
Metric Value
wmc 37
c 12
b 4
f 3
lcom 2
cbo 9
dl 8
loc 313
rs 8.6

15 Methods

Rating   Name   Duplication   Size   Complexity  
B theFollowing() 8 32 3
A thereIs() 0 21 2
B thereIsLikeFollowing() 0 27 2
B entitiesShouldHaveBeen() 0 41 3
B existLikeFollowing() 0 31 5
B beforeScenario() 0 21 6
A afterScenario() 0 6 1
A resetSchema() 0 9 2
A compareArray() 0 11 3
A getPoolingShardManager() 0 11 4
A getMetadata() 0 4 1
A getEntityManagers() 0 4 1
A getConnections() 0 4 1
A getDefaultOptions() 0 6 1
A getEntityIdentifiers() 0 14 2

How to fix   Duplicated Code   

Duplicated Code

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
2
3
namespace Knp\FriendlyContexts\Context;
4
5
use Behat\Gherkin\Node\TableNode;
6
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
7
use Doctrine\DBAL\Connection;
8
use Doctrine\DBAL\Sharding\PoolingShardConnection;
9
use Doctrine\DBAL\Sharding\PoolingShardManager;
10
use Doctrine\ORM\EntityManager;
11
use Doctrine\ORM\Tools\SchemaTool;
12
use Symfony\Component\PropertyAccess\PropertyAccess;
13
14
class EntityContext extends Context
15
{
16
    /**
17
     * @Given /^the following ([\w ]+):?$/
18
     */
19
    public function theFollowing($name, TableNode $table)
20
    {
21
        $entityName = $this->resolveEntity($name)->getName();
22
23
        $rows = $table->getRows();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
24
        $headers = array_shift($rows);
25
26
        foreach ($rows as $row) {
27
            $values     = array_combine($headers, $row);
28
            $entity     = new $entityName;
29
            $reflection = new \ReflectionClass($entity);
30
31 View Code Duplication
            do {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

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

Loading history...
32
                $this
33
                    ->getRecordBag()
34
                    ->getCollection($reflection->getName())
35
                    ->attach($entity, $values)
36
                ;
37
                $reflection = $reflection->getParentClass();
38
            } while (false !== $reflection);
39
40
            $this
41
                ->getEntityHydrator()
42
                ->hydrate($this->getEntityManager(), $entity, $values)
43
                ->completeRequired($this->getEntityManager(), $entity)
44
            ;
45
46
            $this->getEntityManager()->persist($entity);
47
        }
48
49
        $this->getEntityManager()->flush();
50
    }
51
52
    /**
53
     * @Given /^there (?:is|are) (\d+) ((?!\w* like)\w*)$/
54
     */
55
    public function thereIs($nbr, $name)
56
    {
57
        $entityName = $this->resolveEntity($name)->getName();
58
59
        for ($i = 0; $i < $nbr; $i++) {
60
            $entity = new $entityName;
61
            $this
62
                ->getRecordBag()
63
                ->getCollection($entityName)
64
                ->attach($entity)
65
            ;
66
            $this
67
                ->getEntityHydrator()
68
                ->completeRequired($this->getEntityManager(), $entity)
69
            ;
70
71
            $this->getEntityManager()->persist($entity);
72
        }
73
74
        $this->getEntityManager()->flush();
75
    }
76
77
    /**
78
     * @Given /^there (?:is|are) (\d+) (.*) like:?$/
79
     */
80
    public function thereIsLikeFollowing($nbr, $name, TableNode $table)
81
    {
82
        $entityName = $this->resolveEntity($name)->getName();
83
84
        $rows = $table->getRows();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
85
        $headers = array_shift($rows);
86
87
        for ($i = 0; $i < $nbr; $i++) {
88
            $row = $rows[$i % count($rows)];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
89
            $values = array_combine($headers, $row);
90
            $entity = new $entityName;
91
            $this
92
                ->getRecordBag()
93
                ->getCollection($entityName)
94
                ->attach($entity, $values)
95
            ;
96
            $this
97
                ->getEntityHydrator()
98
                ->hydrate($this->getEntityManager(), $entity, $values)
99
                ->completeRequired($this->getEntityManager(), $entity)
100
            ;
101
102
            $this->getEntityManager()->persist($entity);
103
        }
104
105
        $this->getEntityManager()->flush();
106
    }
107
108
    /**
109
     * @Given /^(\w+) (.+) should have been (created|deleted)$/
110
     */
111
    public function entitiesShouldHaveBeen($expected, $entity, $state)
112
    {
113
        $expected = (int) $expected;
114
115
        $entityName = $this->resolveEntity($entity)->getName();
116
        $collection = $this
117
            ->getRecordBag()
118
            ->getCollection($entityName)
119
        ;
120
121
        $records = array_map(function ($e) { return $e->getEntity(); }, $collection->all());
122
        $entities = $this
123
            ->getEntityManager()
124
            ->getRepository($entityName)
125
            ->createQueryBuilder('o')
126
            ->resetDQLParts()
127
            ->select('o')
128
            ->from($entityName, ' o')
129
            ->getQuery()
130
            ->getResult()
131
        ;
132
133
        if ($state === 'created') {
134
            $diff = $this->compareArray($entities, $records);
135
            foreach ($diff as $e) {
136
                $collection->attach($e);
137
            }
138
        } else {
139
            $diff = $this->compareArray($records, $entities);
140
        }
141
        $real = count($diff);
142
143
        $this
144
            ->getAsserter()
145
            ->assertEquals(
146
                $real,
147
                $expected,
148
                sprintf('%s %s should have been %s, %s actually', $expected, $entity, $state, $real)
149
            )
150
        ;
151
    }
152
153
    /**
154
     * @Then /^should be (\d+) (.*) like:?$/
155
     */
156
    public function existLikeFollowing($nbr, $name, TableNode $table)
157
    {
158
        $entityName = $this->resolveEntity($name)->getName();
159
160
        $rows = $table->getRows();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
161
        $headers = array_shift($rows);
162
163
        $accessor = PropertyAccess::createPropertyAccessor();
164
165
        for ($i = 0; $i < $nbr; $i++) {
166
            $row = $rows[$i % count($rows)];
167
168
            $values = array_combine($headers, $row);
169
            $object = $this->getEntityManager()
170
                ->getRepository($entityName)
171
                ->findOneBy(
172
                    $this->getEntityIdentifiers($entityName, $headers, $row)
173
                );
174
175
            if (is_null($object)) {
176
                throw new \Exception(sprintf("There is not any object for the following identifiers: %s", json_encode($this->getEntityIdentifiers($entityName, $headers, $row))));
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal There is not any object ...llowing identifiers: %s does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
177
            }
178
            $this->getEntityManager()->refresh($object);
179
180
            foreach ($values as $key => $value) {
181
                if ($value != $accessor->getValue($object, $key) ) {
182
                    throw new \Exception("The expected object does not have property $key with value $value");
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $key instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $value instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
183
                }
184
            }
185
        }
186
    }
187
188
    /**
189
     * @BeforeScenario
190
     */
191
    public function beforeScenario($event)
192
    {
193
        $this->storeTags($event);
194
195
        if ($this->hasTags([ 'reset-schema', '~not-reset-schema' ])) {
196
            foreach ($this->getEntityManagers() as $name => $entityManager) {
197
                $connection = $entityManager->getConnection();
198
                if ($connection instanceof PoolingShardConnection && $this->hasTag('reset-shard-schema')) {
199
                    $poolingShardManager = $this->getPoolingShardManager($connection);
200
                    foreach ($poolingShardManager->getShards() as $shardId) {
201
                        // Switch to shard database
202
                        $connection->connect($shardId['id']);
203
                        $this->resetSchema($entityManager);
204
                    }
205
                    // Back to global
206
                    $connection->connect(0);
207
                }
208
                $this->resetSchema($entityManager);
209
            }
210
        }
211
    }
212
213
    /**
214
     * @AfterScenario
215
     */
216
    public function afterScenario($event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
217
    {
218
        $this->getRecordBag()->clear();
219
        $this->getUniqueCache()->clear();
220
        $this->getEntityManager()->clear();
221
    }
222
223
    /**
224
     * @param EntityManager $entityManager
225
     */
226
    protected function resetSchema(EntityManager $entityManager)
227
    {
228
        $metadata = $this->getMetadata($entityManager);
229
        if (!empty($metadata)) {
230
            $tool = new SchemaTool($entityManager);
231
            $tool->dropSchema($metadata);
232
            $tool->createSchema($metadata);
233
        }
234
    }
235
236
    protected function compareArray(array $a1, array $a2)
237
    {
238
        $diff = [];
239
        foreach ($a1 as $e) {
240
            if (!in_array($e, $a2)) {
241
                $diff[] = $e;
242
            }
243
        }
244
245
        return $diff;
246
    }
247
248
    /**
249
     * Find PoolingShardManager related to connection.
250
     *
251
     * Cannot directly find PoolingShardManager by EntityManager's name cause
252
     * PoolingShardManager is created from Connection's name.
253
     *
254
     * @param PoolingShardConnection $poolingShardConnection
255
     *
256
     * @return PoolingShardManager
257
     *
258
     * @throws \LogicException Unable to find PoolingShardManager related to this PoolingShardConnection.
259
     */
260
    protected function getPoolingShardManager(PoolingShardConnection $poolingShardConnection)
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $poolingShardConnection exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
261
    {
262
        foreach ($this->getConnections() as $name => $connection) {
263
            if ($connection === $poolingShardConnection
264
                && $this->getKernel()->getContainer()->has(sprintf('doctrine.dbal.%s_shard_manager', $name))
265
            ) {
266
                return $this->get(sprintf('doctrine.dbal.%s_shard_manager', $name));
267
            }
268
        }
269
        throw new \LogicException('Unable to find PoolingShardManager related to this PoolingShardConnection.');
270
    }
271
272
    /**
273
     * @param EntityManager $entityManager
274
     *
275
     * @return ClassMetadata[]
276
     */
277
    protected function getMetadata(EntityManager $entityManager)
278
    {
279
        return $entityManager->getMetadataFactory()->getAllMetadata();
280
    }
281
282
    /**
283
     * @return EntityManager[]
284
     */
285
    protected function getEntityManagers()
286
    {
287
        return $this->get('doctrine')->getManagers();
288
    }
289
290
    /**
291
     * @return Connection[]
292
     */
293
    protected function getConnections()
294
    {
295
        return $this->get('doctrine')->getConnections();
296
    }
297
298
    protected function getDefaultOptions()
299
    {
300
        return [
301
            'Entities' => [''],
302
        ];
303
    }
304
305
    /**
306
     * @param string $entityName
307
     * @param array $headers Headers of the Behat TableNode
308
     * @param array $row current row if tge Behat TableNode
309
     * @return array ['id_column_A' => 'value', 'id_column_B' => 'value']
310
     * @throws \Exception
311
     */
312
    protected function getEntityIdentifiers($entityName, $headers, $row)
313
    {
314
        $metadata = $this->getEntityManager()->getClassMetadata($entityName);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
315
        $identifiers = $metadata->getIdentifierFieldNames();
316
317
        $identifiersWithValues = [];
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $identifiersWithValues exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
318
319
        foreach ($identifiers as $identifier) {
320
            $headersPosition = array_search($identifier, $headers);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 20 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
321
            $identifiersWithValues[$identifier] = $row[$headersPosition];
322
        }
323
324
        return $identifiersWithValues;
325
    }
326
}
327
328