Completed
Push — 1.0 ( de2696...32352e )
by David
02:13
created

ZohoDatabaseCopier::copyData()   C

Complexity

Conditions 10
Paths 60

Size

Total Lines 76
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 7
Bugs 0 Features 0
Metric Value
c 7
b 0
f 0
dl 0
loc 76
rs 5.7198
cc 10
eloc 49
nc 60
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Wabel\Zoho\CRM\Copy;
4
5
use Doctrine\DBAL\Connection;
6
use Doctrine\DBAL\Schema\Schema;
7
use Doctrine\DBAL\Schema\SchemaDiff;
8
use Psr\Log\LoggerInterface;
9
use Psr\Log\NullLogger;
10
use Wabel\Zoho\CRM\AbstractZohoDao;
11
use function Stringy\create as s;
12
13
/**
14
 * This class is in charge of synchronizing one table of your database with Zoho records.
15
 */
16
class ZohoDatabaseCopier
17
{
18
    /**
19
     * @var Connection
20
     */
21
    private $connection;
22
23
    private $prefix;
24
25
    /**
26
     * @var ZohoChangeListener[]
27
     */
28
    private $listeners;
29
30
    /**
31
     * @var LoggerInterface
32
     */
33
    private $logger;
34
35
    /**
36
     * ZohoDatabaseCopier constructor.
37
     *
38
     * @param Connection $connection
39
     * @param string $prefix Prefix for the table name in DB
40
     * @param ZohoChangeListener[] $listeners The list of listeners called when a record is inserted or updated.
41
     */
42
    public function __construct(Connection $connection, $prefix = 'zoho_', array $listeners = [], LoggerInterface $logger = null)
43
    {
44
        $this->connection = $connection;
45
        $this->prefix = $prefix;
46
        $this->listeners = $listeners;
47
        if ($logger === null) {
48
            $this->logger = new NullLogger();
49
        } else {
50
            $this->logger = $logger;
51
        }
52
    }
53
54
    /**
55
     * @param LoggerInterface $logger
56
     */
57
    public function setLogger(LoggerInterface $logger)
58
    {
59
        $this->logger = $logger;
60
    }
61
62
63
    /**
64
     * @param AbstractZohoDao $dao
65
     * @param bool            $incrementalSync Whether we synchronize only the modified files or everything.
66
     */
67
    public function copy(AbstractZohoDao $dao, $incrementalSync = true)
68
    {
69
        $this->synchronizeDbModel($dao);
70
        $this->copyData($dao, $incrementalSync);
71
    }
72
73
    /**
74
     * Synchronizes the DB model with Zoho.
75
     *
76
     * @param AbstractZohoDao $dao
77
     *
78
     * @throws \Doctrine\DBAL\DBALException
79
     * @throws \Doctrine\DBAL\Schema\SchemaException
80
     */
81
    private function synchronizeDbModel(AbstractZohoDao $dao)
82
    {
83
        $tableName = $this->getTableName($dao);
84
        $this->logger->info("Synchronizing DB Model for ".$tableName);
85
86
        $schema = new Schema();
87
        $table = $schema->createTable($tableName);
88
89
        $flatFields = $this->getFlatFields($dao->getFields());
0 ignored issues
show
Bug introduced by
The method getFields() cannot be called from this context as it is declared protected in class Wabel\Zoho\CRM\AbstractZohoDao.

This check looks for access to methods that are not accessible from the current context.

If you need to make a method accessible to another context you can raise its visibility level in the defining class.

Loading history...
90
91
        $table->addColumn('id', 'string', ['length' => 100]);
92
        $table->setPrimaryKey(['id']);
93
94
        foreach ($flatFields as $field) {
95
            $columnName = $field['name'];
96
97
            $length = null;
98
            $index = false;
99
100
            // Note: full list of types available here: https://www.zoho.com/crm/help/customization/custom-fields.html
101
            switch ($field['type']) {
102
                case 'Lookup ID':
103
                case 'Lookup':
104
                    $type = 'string';
105
                    $length = 100;
106
                    $index = true;
107
                    break;
108
                case 'OwnerLookup':
109
                    $type = 'string';
110
                    $index = true;
111
                    $length = 25;
112
                    break;
113
                case 'Formula':
114
                    // Note: a Formula can return any type, but we have no way to know which type it returns...
115
                    $type = 'string';
116
                    $length = 100;
117
                    break;
118
                case 'DateTime':
119
                    $type = 'datetime';
120
                    break;
121
                case 'Date':
122
                    $type = 'date';
123
                    break;
124
                case 'DateTime':
125
                    $type = 'datetime';
126
                    break;
127
                case 'Boolean':
128
                    $type = 'boolean';
129
                    break;
130
                case 'TextArea':
131
                    $type = 'text';
132
                    break;
133
                case 'BigInt':
134
                    $type = 'bigint';
135
                    break;
136
                case 'Phone':
137
                case 'Auto Number':
138
                case 'Text':
139
                case 'URL':
140
                case 'Email':
141
                case 'Website':
142
                case 'Pick List':
143
                case 'Multiselect Pick List':
144
                    $type = 'string';
145
                    $length = $field['maxlength'];
146
                    break;
147
                case 'Double':
148
                case 'Percent':
149
                    $type = 'float';
150
                    break;
151
                case 'Integer':
152
                    $type = 'integer';
153
                    break;
154
                case 'Currency':
155
                case 'Decimal':
156
                    $type = 'decimal';
157
                    break;
158
                default:
159
                    throw new \RuntimeException('Unknown type "'.$field['type'].'"');
160
            }
161
162
            $options = [];
163
164
            if ($length) {
165
                $options['length'] = $length;
166
            }
167
168
            //$options['notnull'] = $field['req'];
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
169
            $options['notnull'] = false;
170
171
            $table->addColumn($columnName, $type, $options);
172
173
            if ($index) {
174
                $table->addIndex([$columnName]);
175
            }
176
        }
177
178
        $dbSchema = $this->connection->getSchemaManager()->createSchema();
179
        if ($this->connection->getSchemaManager()->tablesExist($tableName)) {
0 ignored issues
show
Documentation introduced by
$tableName is of type string, but the function expects a array.

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...
180
            $dbTable = $dbSchema->getTable($tableName);
181
182
            $comparator = new \Doctrine\DBAL\Schema\Comparator();
183
            $tableDiff = $comparator->diffTable($dbTable, $table);
184
185
            if ($tableDiff !== false) {
186
                $this->logger->notice("Changes detected in table structure for ".$tableName.". Applying patch.");
187
                $diff = new SchemaDiff();
188
                $diff->fromSchema = $dbSchema;
189
                $diff->changedTables[$tableName] = $tableDiff;
190
                $statements = $diff->toSaveSql($this->connection->getDatabasePlatform());
191
                foreach ($statements as $sql) {
192
                    $this->connection->exec($sql);
193
                }
194
            } else {
195
                $this->logger->info("No changes detected in table structure for ".$tableName);
196
            }
197
        } else {
198
            $this->logger->notice("Creating new table '$tableName'.");
199
            $diff = new SchemaDiff();
200
            $diff->fromSchema = $dbSchema;
201
            $diff->newTables[$tableName] = $table;
202
            $statements = $diff->toSaveSql($this->connection->getDatabasePlatform());
203
            foreach ($statements as $sql) {
204
                $this->connection->exec($sql);
205
            }
206
        }
207
    }
208
209
    /**
210
     * @param AbstractZohoDao $dao
211
     * @param bool            $incrementalSync Whether we synchronize only the modified files or everything.
212
     *
213
     * @throws \Doctrine\DBAL\DBALException
214
     * @throws \Doctrine\DBAL\Schema\SchemaException
215
     * @throws \Wabel\Zoho\CRM\Exception\ZohoCRMResponseException
216
     */
217
    private function copyData(AbstractZohoDao $dao, $incrementalSync = true)
218
    {
219
        $tableName = $this->getTableName($dao);
220
221
        if ($incrementalSync) {
222
            $this->logger->info("Copying incremental data for '$tableName'");
223
            // Let's get the last modification date:
224
            $lastActivityTime = $this->connection->fetchColumn('SELECT MAX(lastActivityTime) FROM '.$tableName);
225
            if ($lastActivityTime !== null) {
226
                $lastActivityTime = new \DateTime($lastActivityTime);
227
                $this->logger->info("Last activity time: ".$lastActivityTime->format('c'));
228
            }
229
230
            $records = $dao->getRecords(null, null, $lastActivityTime);
231
        } else {
232
            $this->logger->notice("Copying FULL data for '$tableName'");
233
            $records = $dao->getRecords();
234
        }
235
        $this->logger->info("Fetched ".count($records)." records");
236
237
        $table = $this->connection->getSchemaManager()->createSchema()->getTable($tableName);
238
239
        $flatFields = $this->getFlatFields($dao->getFields());
0 ignored issues
show
Bug introduced by
The method getFields() cannot be called from this context as it is declared protected in class Wabel\Zoho\CRM\AbstractZohoDao.

This check looks for access to methods that are not accessible from the current context.

If you need to make a method accessible to another context you can raise its visibility level in the defining class.

Loading history...
240
        $fieldsByName = [];
241
        foreach ($flatFields as $field) {
242
            $fieldsByName[$field['name']] = $field;
243
        }
244
245
        $select = $this->connection->prepare('SELECT * FROM '.$tableName.' WHERE id = :id');
246
247
        $this->connection->beginTransaction();
248
249
        foreach ($records as $record) {
250
            $data = [];
251
            $types = [];
252
            foreach ($table->getColumns() as $column) {
253
                if ($column->getName() === 'id') {
254
                    continue;
255
                } else {
256
                    $field = $fieldsByName[$column->getName()];
257
                    $getterName = $field['getter'];
258
                    $data[$column->getName()] = $record->$getterName();
259
                    $types[$column->getName()] = $column->getType()->getName();
260
                }
261
            }
262
263
            $select->execute(['id' => $record->getZohoId()]);
264
            $result = $select->fetch(\PDO::FETCH_ASSOC);
265
            if ($result === false) {
266
                $this->logger->info("Inserting record with ID '".$record->getZohoId()."'.");
267
268
                $data['id'] = $record->getZohoId();
269
                $types['id'] = 'string';
270
271
                $this->connection->insert($tableName, $data, $types);
272
273
                foreach ($this->listeners as $listener) {
274
                    $listener->onInsert($data, $dao);
275
                }
276
            } else {
277
                $this->logger->info("Updating record with ID '".$record->getZohoId()."'.");
278
                $identifier = ['id' => $record->getZohoId()];
279
                $types['id'] = 'string';
280
281
                $this->connection->update($tableName, $data, $identifier, $types);
282
283
                // Let's add the id for the update trigger
284
                $data['id'] = $record->getZohoId();
285
                foreach ($this->listeners as $listener) {
286
                    $listener->onUpdate($data, $result, $dao);
287
                }
288
            }
289
        }
290
291
        $this->connection->commit();
292
    }
293
294
    private function getFlatFields(array $fields)
295
    {
296
        $flatFields = [];
297
        foreach ($fields as $cat) {
298
            $flatFields = array_merge($flatFields, $cat);
299
        }
300
301
        return $flatFields;
302
    }
303
304
    /**
305
     * Computes the name of the table based on the DAO plural module name.
306
     *
307
     * @param AbstractZohoDao $dao
308
     *
309
     * @return string
310
     */
311
    private function getTableName(AbstractZohoDao $dao)
312
    {
313
        $tableName = $this->prefix.$dao->getPluralModuleName();
0 ignored issues
show
Bug introduced by
The method getPluralModuleName() cannot be called from this context as it is declared protected in class Wabel\Zoho\CRM\AbstractZohoDao.

This check looks for access to methods that are not accessible from the current context.

If you need to make a method accessible to another context you can raise its visibility level in the defining class.

Loading history...
314
        $tableName = s($tableName)->upperCamelize()->underscored();
315
316
        return (string) $tableName;
317
    }
318
}
319