Completed
Push — 3.0 ( 154001...3d6954 )
by Raphaël
08:06 queued 06:39
created

ZohoDatabaseCopier   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 231
Duplicated Lines 11.26 %

Coupling/Cohesion

Components 1
Dependencies 17

Importance

Changes 0
Metric Value
wmc 36
lcom 1
cbo 17
dl 26
loc 231
rs 9.52
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 13 13 2
D fetchUserFromZoho() 13 65 18
F fetchFromZoho() 0 101 16

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 Wabel\Zoho\CRM\Copy;
4
5
use Doctrine\DBAL\Connection;
6
use Psr\Log\LoggerInterface;
7
use Psr\Log\NullLogger;
8
use Wabel\Zoho\CRM\AbstractZohoDao;
9
use Wabel\Zoho\CRM\Request\Response;
10
11
/**
12
 * This class is in charge of synchronizing one table of your database with Zoho records.
13
 */
14
class ZohoDatabaseCopier
15
{
16
    /**
17
     * @var Connection
18
     */
19
    private $connection;
20
21
    private $prefix;
22
23
    /**
24
     * @var ZohoChangeListener[]
25
     */
26
    private $listeners;
27
28
    /**
29
     * @var LoggerInterface
30
     */
31
    private $logger;
32
33
    /**
34
     * @var LocalChangesTracker
35
     */
36
    private $localChangesTracker;
37
    /**
38
     * @var ZohoUserService
39
     */
40
    private $zohoUserService;
41
42
    /**
43
     * ZohoDatabaseCopier constructor.
44
     *
45
     * @param Connection           $connection
46
     * @param string               $prefix     Prefix for the table name in DB
47
     * @param ZohoChangeListener[] $listeners  The list of listeners called when a record is inserted or updated.
48
     */
49 View Code Duplication
    public function __construct(Connection $connection, ZohoUserService $zohoUserService, $prefix = 'zoho_', array $listeners = [], LoggerInterface $logger = null)
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...
50
    {
51
        $this->connection = $connection;
52
        $this->prefix = $prefix;
53
        $this->listeners = $listeners;
54
        if ($logger === null) {
55
            $this->logger = new NullLogger();
56
        } else {
57
            $this->logger = $logger;
58
        }
59
        $this->localChangesTracker = new LocalChangesTracker($connection, $this->logger);
60
        $this->zohoUserService = $zohoUserService;
61
    }
62
63
    /**
64
     * @throws \Doctrine\DBAL\DBALException
65
     * @throws \Doctrine\DBAL\Schema\SchemaException
66
     * @throws \Wabel\Zoho\CRM\Exception\ZohoCRMResponseException
67
     */
68
    public function fetchUserFromZoho()
69
    {
70
        $users = $this->zohoUserService->getUsers();
71
        $tableName = 'users';
72
        $this->logger->notice("Copying FULL data users for '$tableName'");
73
        $this->logger->info('Fetched '.count($users).' records');
74
75
        $table = $this->connection->getSchemaManager()->createSchema()->getTable($tableName);
76
        
77
        $select = $this->connection->prepare('SELECT * FROM '.$tableName.' WHERE id = :id');
78
79
        $this->connection->beginTransaction();
80
        foreach ($users as $user) {
81
            $data = [];
82
            $types = [];
83
            foreach ($table->getColumns() as $column) {
84
                if ($column->getName() === 'id') {
85
                    continue;
86
                } else {
87
                    $fieldMethod = ZohoDatabaseHelper::getUserMethodNameFromField($column->getName());
88
                    if (method_exists($user, $fieldMethod)
89
                        && (!is_array($user->{$fieldMethod}()) && !is_object($user->{$fieldMethod}()))
90
                    ) {
91
                        $data[$column->getName()] = $user->{$fieldMethod}();
92 View Code Duplication
                    } elseif (method_exists($user, $fieldMethod)
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...
93
                        && is_array($user->{$fieldMethod}())
94
                        && array_key_exists('name', $user->{$fieldMethod}())
95
                        && array_key_exists('id', $user->{$fieldMethod}())
96
                    ) {
97
                        $data[$column->getName()] = $user->{$fieldMethod}()['name'];
98
                    }
99 View Code Duplication
                    elseif (method_exists($user, $fieldMethod)
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...
100
                        && is_object($user->{$fieldMethod}()) && method_exists($user->{$fieldMethod}(), 'getName')
101
                    ) {
102
                        $object = $user->{$fieldMethod}();
103
                        $data[$column->getName()] = $object->getName();
104
                    }
105
                    elseif($column->getName() === 'Currency') {
106
                        //Todo: Do a pull request about \ZCRMUser::geCurrency() to \ZCRMUser::getCurrency()
107
                        $data[$column->getName()] = $user->geCurrency();
108
                    }
109
                    else {
110
                        continue;
111
                    }
112
                }
113
            }
114
            $select->execute(['id' => $user->getId()]);
115
            $result = $select->fetch(\PDO::FETCH_ASSOC);
116
            if ($result === false && $data) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $data 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...
117
                $this->logger->debug("Inserting record with ID '" . $user->getId() . "'.");
118
119
                $data['id'] = $user->getId();
120
                $types['id'] = 'string';
121
122
                $this->connection->insert($tableName, $data, $types);
123
            } elseif($data) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $data 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...
124
                $this->logger->debug("Updating record with ID '" . $user->getId() . "'.");
125
                $identifier = ['id' => $user->getId()];
126
                $types['id'] = 'string';
127
                $this->connection->update($tableName, $data, $identifier, $types);
128
            }
129
130
        }
131
        $this->connection->commit();
132
    }
133
    
134
    /**
135
     * @param AbstractZohoDao $dao
136
     * @param bool            $incrementalSync Whether we synchronize only the modified files or everything.
137
     * @param bool            $twoWaysSync
138
     *
139
     * @throws \Doctrine\DBAL\DBALException
140
     * @throws \Doctrine\DBAL\Schema\SchemaException
141
     * @throws \Wabel\Zoho\CRM\Exception\ZohoCRMResponseException
142
     */
143
    public function fetchFromZoho(AbstractZohoDao $dao, $incrementalSync = true, $twoWaysSync = true)
144
    {
145
        $tableName = ZohoDatabaseHelper::getTableName($dao, $this->prefix);
146
147
        if ($incrementalSync) {
148
            $this->logger->info("Copying incremental data for '$tableName'");
149
            // Let's get the last modification date:
150
            $tableDetail = $this->connection->getSchemaManager()->listTableDetails($tableName);
151
            $lastActivityTime = null;
152
            if($tableDetail->hasColumn('modifiedTime')) {
153
                $lastActivityTime = $this->connection->fetchColumn('SELECT MAX(modifiedTime) FROM '.$tableName);
154
            }
155
            if(!$lastActivityTime && $tableDetail->hasColumn('createdTime')) {
156
                $lastActivityTime = $this->connection->fetchColumn('SELECT MAX(createdTime) FROM '.$tableName);
157
            }
158
159
            if ($lastActivityTime !== null) {
160
                $lastActivityTime = new \DateTime($lastActivityTime);
161
                $lastActivityTime->setTimezone(new \DateTimeZone($dao->getZohoClient()->getTimezone()));
162
                $this->logger->info('Last modified time: '.$lastActivityTime->format(\DateTime::ATOM));
163
                // Let's add one second to the last activity time (otherwise, we are fetching again the last record in DB).
164
                //                $lastActivityTime->add(new \DateInterval('PT1S'));
165
                //                $lastActivityTime->sub(new \DateInterval('PT1H'));
166
                $lastActivityTime->sub(new \DateInterval('PT1H'));
167
            }
168
169
            $records = $dao->getRecords(null, null, null, $lastActivityTime);
170
            $deletedRecords = $dao->getDeletedRecordIds($lastActivityTime);
171
        } else {
172
            $this->logger->notice("Copying FULL data for '$tableName'");
173
            $records = $dao->getRecords();
174
            $deletedRecords = [];
175
        }
176
        $this->logger->info('Fetched '.count($records).' records');
177
178
        $table = $this->connection->getSchemaManager()->createSchema()->getTable($tableName);
179
180
        $select = $this->connection->prepare('SELECT * FROM '.$tableName.' WHERE id = :id');
181
182
        $this->connection->beginTransaction();
183
184
        foreach ($records as $record) {
185
            $data = [];
186
            $types = [];
187
            foreach ($table->getColumns() as $column) {
188
                if (in_array($column->getName(), ['id', 'uid'])) {
189
                    continue;
190
                } else {
191
                    $field = $dao->getFieldFromFieldName($column->getName());
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $field is correct as $dao->getFieldFromFieldName($column->getName()) (which targets Wabel\Zoho\CRM\AbstractZ...getFieldFromFieldName()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
192
                    if(!$field) {
193
                        continue;
194
                    }
195
                    $getterName = $field->getGetter();
196
                    $dataValue = $record->$getterName();
197
                    $data[$column->getName()] = is_array($dataValue) ? implode(';', $dataValue) : $dataValue;
198
                    $types[$column->getName()] = $column->getType()->getName();
199
                }
200
            }
201
202
            $select->execute(['id' => $record->getZohoId()]);
203
            $result = $select->fetch(\PDO::FETCH_ASSOC);
204
            if ($result === false) {
205
                $this->logger->debug("Inserting record with ID '".$record->getZohoId()."'.");
206
207
                $data['id'] = $record->getZohoId();
208
                $types['id'] = 'string';
209
210
                $this->connection->insert($tableName, $data, $types);
211
212
                foreach ($this->listeners as $listener) {
213
                    $listener->onInsert($data, $dao);
214
                }
215
            } else {
216
                $this->logger->debug("Updating record with ID '".$record->getZohoId()."'.");
217
                $identifier = ['id' => $record->getZohoId()];
218
                $types['id'] = 'string';
219
220
                $this->connection->update($tableName, $data, $identifier, $types);
221
222
                // Let's add the id for the update trigger
223
                $data['id'] = $record->getZohoId();
224
                foreach ($this->listeners as $listener) {
225
                    $listener->onUpdate($data, $result, $dao);
226
                }
227
            }
228
        }
229
        $sqlStatementUid = 'select uid from '.$this->connection->quoteIdentifier($tableName).' where id = :id';
230
        foreach ($deletedRecords as $deletedRecord) {
231
            $uid = $this->connection->fetchColumn($sqlStatementUid, ['id' => $deletedRecord->getEntityId()]);
232
            $this->connection->delete($tableName, ['id' => $deletedRecord->getEntityId()]);
233
            if ($twoWaysSync) {
234
                // TODO: we could detect if there are changes to be updated to the server and try to warn with a log message
235
                // Also, let's remove the newly created field (because of the trigger) to avoid looping back to Zoho
236
                $this->connection->delete('local_delete', ['table_name' => $tableName, 'id' => $deletedRecord->getEntityId()]);
237
                $this->connection->delete('local_update', ['table_name' => $tableName, 'uid' => $uid]);
238
            }
239
        }
240
        $this->logger->info('Deleted '.count($deletedRecords).' records');
241
242
        $this->connection->commit();
243
    }
244
}
245