Failed Conditions
Push — master ( 656579...2742cd )
by Marco
11:55
created

SQLAzureFederationsSynchronizer   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 257
Duplicated Lines 10.51 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 32
dl 27
loc 257
ccs 0
cts 148
cp 0
rs 9.6
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A getCreateFederationStatement() 7 7 1
B getFederationTypeDefaultValue() 0 19 5
A updateSchema() 0 3 1
A dropSchema() 0 3 1
A dropAllSchema() 0 3 1
A createSchema() 0 3 1
A __construct() 0 5 2
B getDropAllSchema() 10 26 4
B getCreateSchema() 0 24 3
A getDropSchema() 0 4 1
A getUpdateSchema() 0 4 1
A partitionSchema() 0 5 1
B work() 10 27 4
B extractSchemaFederation() 0 22 6

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
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL\Sharding\SQLAzure;
21
22
use Doctrine\DBAL\Schema\Schema;
23
use Doctrine\DBAL\Connection;
24
use Doctrine\DBAL\Types\Type;
25
26
use Doctrine\DBAL\Schema\Synchronizer\AbstractSchemaSynchronizer;
27
use Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer;
28
use Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer;
29
30
/**
31
 * SQL Azure Schema Synchronizer.
32
 *
33
 * Will iterate over all shards when performing schema operations. This is done
34
 * by partitioning the passed schema into subschemas for the federation and the
35
 * global database and then applying the operations step by step using the
36
 * {@see \Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer}.
37
 *
38
 * @author Benjamin Eberlei <[email protected]>
39
 */
40
class SQLAzureFederationsSynchronizer extends AbstractSchemaSynchronizer
41
{
42
    const FEDERATION_TABLE_FEDERATED   = 'azure.federated';
43
    const FEDERATION_DISTRIBUTION_NAME = 'azure.federatedOnDistributionName';
44
45
    /**
46
     * @var \Doctrine\DBAL\Sharding\SQLAzure\SQLAzureShardManager
47
     */
48
    private $shardManager;
49
50
    /**
51
     * @var \Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer
52
     */
53
    private $synchronizer;
54
55
    /**
56
     * @param \Doctrine\DBAL\Connection                                  $conn
57
     * @param \Doctrine\DBAL\Sharding\SQLAzure\SQLAzureShardManager      $shardManager
58
     * @param \Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer|null $sync
59
     */
60
    public function __construct(Connection $conn, SQLAzureShardManager $shardManager, SchemaSynchronizer $sync = null)
61
    {
62
        parent::__construct($conn);
63
        $this->shardManager = $shardManager;
64
        $this->synchronizer = $sync ?: new SingleDatabaseSynchronizer($conn);
65
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70
    public function getCreateSchema(Schema $createSchema)
71
    {
72
        $sql = [];
73
74
        list($global, $federation) = $this->partitionSchema($createSchema);
75
76
        $globalSql = $this->synchronizer->getCreateSchema($global);
77
        if ($globalSql) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $globalSql of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
78
            $sql[] = "-- Create Root Federation\n" .
79
                     "USE FEDERATION ROOT WITH RESET;";
80
            $sql = array_merge($sql, $globalSql);
81
        }
82
83
        $federationSql = $this->synchronizer->getCreateSchema($federation);
84
85
        if ($federationSql) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $federationSql of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
86
            $defaultValue = $this->getFederationTypeDefaultValue();
87
88
            $sql[] = $this->getCreateFederationStatement();
89
            $sql[] = "USE FEDERATION " . $this->shardManager->getFederationName() . " (" . $this->shardManager->getDistributionKey() . " = " . $defaultValue . ") WITH RESET, FILTERING = OFF;";
90
            $sql = array_merge($sql, $federationSql);
91
        }
92
93
        return $sql;
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99
    public function getUpdateSchema(Schema $toSchema, $noDrops = false)
100
    {
101
        return $this->work($toSchema, function ($synchronizer, $schema) use ($noDrops) {
102
            return $synchronizer->getUpdateSchema($schema, $noDrops);
103
        });
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109
    public function getDropSchema(Schema $dropSchema)
110
    {
111
        return $this->work($dropSchema, function ($synchronizer, $schema) {
112
            return $synchronizer->getDropSchema($schema);
113
        });
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119
    public function createSchema(Schema $createSchema)
120
    {
121
        $this->processSql($this->getCreateSchema($createSchema));
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127
    public function updateSchema(Schema $toSchema, $noDrops = false)
128
    {
129
        $this->processSql($this->getUpdateSchema($toSchema, $noDrops));
130
    }
131
132
    /**
133
     * {@inheritdoc}
134
     */
135
    public function dropSchema(Schema $dropSchema)
136
    {
137
        $this->processSqlSafely($this->getDropSchema($dropSchema));
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143
    public function getDropAllSchema()
144
    {
145
        $this->shardManager->selectGlobal();
146
        $globalSql = $this->synchronizer->getDropAllSchema();
147
148
        if ($globalSql) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $globalSql of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
149
            $sql[] = "-- Work on Root Federation\nUSE FEDERATION ROOT WITH RESET;";
0 ignored issues
show
Comprehensibility Best Practice introduced by
$sql was never initialized. Although not strictly required by PHP, it is generally a good practice to add $sql = array(); before regardless.
Loading history...
150
            $sql = array_merge($sql, $globalSql);
151
        }
152
153
        $shards = $this->shardManager->getShards();
154 View Code Duplication
        foreach ($shards as $shard) {
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...
155
            $this->shardManager->selectShard($shard['rangeLow']);
156
157
            $federationSql = $this->synchronizer->getDropAllSchema();
158
            if ($federationSql) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $federationSql of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
159
                $sql[] = "-- Work on Federation ID " . $shard['id'] . "\n" .
160
                         "USE FEDERATION " . $this->shardManager->getFederationName() . " (" . $this->shardManager->getDistributionKey() . " = " . $shard['rangeLow'].") WITH RESET, FILTERING = OFF;";
161
                $sql = array_merge($sql, $federationSql);
162
            }
163
        }
164
165
        $sql[] = "USE FEDERATION ROOT WITH RESET;";
166
        $sql[] = "DROP FEDERATION " . $this->shardManager->getFederationName();
167
168
        return $sql;
169
    }
170
171
    /**
172
     * {@inheritdoc}
173
     */
174
    public function dropAllSchema()
175
    {
176
        $this->processSqlSafely($this->getDropAllSchema());
177
    }
178
179
    /**
180
     * @param \Doctrine\DBAL\Schema\Schema $schema
181
     *
182
     * @return array
183
     */
184
    private function partitionSchema(Schema $schema)
185
    {
186
        return [
187
            $this->extractSchemaFederation($schema, false),
188
            $this->extractSchemaFederation($schema, true),
189
        ];
190
    }
191
192
    /**
193
     * @param \Doctrine\DBAL\Schema\Schema $schema
194
     * @param boolean                      $isFederation
195
     *
196
     * @return \Doctrine\DBAL\Schema\Schema
197
     *
198
     * @throws \RuntimeException
199
     */
200
    private function extractSchemaFederation(Schema $schema, $isFederation)
201
    {
202
        $partitionedSchema = clone $schema;
203
204
        foreach ($partitionedSchema->getTables() as $table) {
205
            if ($isFederation) {
206
                $table->addOption(self::FEDERATION_DISTRIBUTION_NAME, $this->shardManager->getDistributionKey());
207
            }
208
209
            if ($table->hasOption(self::FEDERATION_TABLE_FEDERATED) !== $isFederation) {
210
                $partitionedSchema->dropTable($table->getName());
211
            } else {
212
                foreach ($table->getForeignKeys() as $fk) {
213
                    $foreignTable = $schema->getTable($fk->getForeignTableName());
214
                    if ($foreignTable->hasOption(self::FEDERATION_TABLE_FEDERATED) !== $isFederation) {
215
                        throw new \RuntimeException("Cannot have foreign key between global/federation.");
216
                    }
217
                }
218
            }
219
        }
220
221
        return $partitionedSchema;
222
    }
223
224
    /**
225
     * Work on the Global/Federation based on currently existing shards and
226
     * perform the given operation on the underlying schema synchronizer given
227
     * the different partitioned schema instances.
228
     *
229
     * @param \Doctrine\DBAL\Schema\Schema $schema
230
     * @param \Closure                     $operation
231
     *
232
     * @return array
233
     */
234
    private function work(Schema $schema, \Closure $operation)
235
    {
236
        list($global, $federation) = $this->partitionSchema($schema);
237
        $sql = [];
238
239
        $this->shardManager->selectGlobal();
240
        $globalSql = $operation($this->synchronizer, $global);
241
242
        if ($globalSql) {
243
            $sql[] = "-- Work on Root Federation\nUSE FEDERATION ROOT WITH RESET;";
244
            $sql   = array_merge($sql, $globalSql);
245
        }
246
247
        $shards = $this->shardManager->getShards();
248
249 View Code Duplication
        foreach ($shards as $shard) {
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...
250
            $this->shardManager->selectShard($shard['rangeLow']);
251
252
            $federationSql = $operation($this->synchronizer, $federation);
253
            if ($federationSql) {
254
                $sql[] = "-- Work on Federation ID " . $shard['id'] . "\n" .
255
                         "USE FEDERATION " . $this->shardManager->getFederationName() . " (" . $this->shardManager->getDistributionKey() . " = " . $shard['rangeLow'].") WITH RESET, FILTERING = OFF;";
256
                $sql   = array_merge($sql, $federationSql);
257
            }
258
        }
259
260
        return $sql;
261
    }
262
263
    /**
264
     * @return string
265
     */
266
    private function getFederationTypeDefaultValue()
267
    {
268
        $federationType = Type::getType($this->shardManager->getDistributionType());
269
270
        switch ($federationType->getName()) {
271
            case Type::GUID:
272
                $defaultValue = '00000000-0000-0000-0000-000000000000';
273
                break;
274
            case Type::INTEGER:
275
            case Type::SMALLINT:
276
            case Type::BIGINT:
277
                $defaultValue = '0';
278
                break;
279
            default:
280
                $defaultValue = '';
281
                break;
282
        }
283
284
        return $defaultValue;
285
    }
286
287
    /**
288
     * @return string
289
     */
290 View Code Duplication
    private function getCreateFederationStatement()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
291
    {
292
        $federationType = Type::getType($this->shardManager->getDistributionType());
293
        $federationTypeSql = $federationType->getSQLDeclaration([], $this->conn->getDatabasePlatform());
294
295
        return "--Create Federation\n" .
296
               "CREATE FEDERATION " . $this->shardManager->getFederationName() . " (" . $this->shardManager->getDistributionKey() . " " . $federationTypeSql ."  RANGE)";
297
    }
298
}
299