Completed
Pull Request — 2.0 (#7)
by Raphaël
02:54
created

ZohoDatabaseCopier::fetchFromZoho()   F

Complexity

Conditions 15
Paths 600

Size

Total Lines 105
Code Lines 66

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 1
Metric Value
c 3
b 1
f 1
dl 0
loc 105
rs 2.4166
cc 15
eloc 66
nc 600
nop 3

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 Psr\Log\LoggerInterface;
7
use Psr\Log\NullLogger;
8
use Wabel\Zoho\CRM\AbstractZohoDao;
9
10
/**
11
 * This class is in charge of synchronizing one table of your database with Zoho records.
12
 */
13
class ZohoDatabaseCopier
14
{
15
    /**
16
     * @var Connection
17
     */
18
    private $connection;
19
20
    private $prefix;
21
22
    /**
23
     * @var ZohoChangeListener[]
24
     */
25
    private $listeners;
26
27
    /**
28
     * @var LoggerInterface
29
     */
30
    private $logger;
31
32
    /**
33
     * @var LocalChangesTracker
34
     */
35
    private $localChangesTracker;
36
37
    /**
38
     * ZohoDatabaseCopier constructor.
39
     *
40
     * @param Connection           $connection
41
     * @param string               $prefix     Prefix for the table name in DB
42
     * @param ZohoChangeListener[] $listeners  The list of listeners called when a record is inserted or updated.
43
     */
44
    public function __construct(Connection $connection, $prefix = 'zoho_', array $listeners = [], LoggerInterface $logger = null)
45
    {
46
        $this->connection = $connection;
47
        $this->prefix = $prefix;
48
        $this->listeners = $listeners;
49
        if ($logger === null) {
50
            $this->logger = new NullLogger();
51
        } else {
52
            $this->logger = $logger;
53
        }
54
        $this->localChangesTracker = new LocalChangesTracker($connection, $this->logger);
55
    }
56
57
    /**
58
     * @param LoggerInterface $logger
59
     */
60
    public function setLogger(LoggerInterface $logger)
61
    {
62
        $this->logger = $logger;
63
    }
64
65
    /**
66
     * @param AbstractZohoDao $dao
67
     * @param bool            $incrementalSync Whether we synchronize only the modified files or everything.
68
     * @param bool            $twoWaysSync
69
     *
70
     * @throws \Doctrine\DBAL\DBALException
71
     * @throws \Doctrine\DBAL\Schema\SchemaException
72
     * @throws \Wabel\Zoho\CRM\Exception\ZohoCRMResponseException
73
     */
74
    public function fetchFromZoho(AbstractZohoDao $dao, $incrementalSync = true, $twoWaysSync = true)
75
    {
76
        $tableName = ZohoDatabaseHelper::getTableName($dao, $this->prefix);
77
78
        if ($incrementalSync) {
79
            $this->logger->info("Copying incremental data for '$tableName'");
80
            //We need to check $lastActivity column exist.
81
            /* @var $existedColumns \Doctrine\DBAL\Schema\Column[] */
82
            $existedColumns = $this->connection->getSchemaManager()->listTableColumns($tableName);
83
            $existLastActivityTime = false;
84
            foreach ($existedColumns as $existedColumn) {
85
                if($existedColumn->getName() == 'lastActivityTime'){
86
                    $existLastActivityTime = true;
87
                    break;
88
                }
89
            }
90
            // To avoid checking this column when it is not used in a table.
91
            if($existLastActivityTime){
92
                /////'SHOW COLUMNS FROM '.$tableName.' LIKE `lastActivityTime`');
93
                // Let's get the last modification date:
94
                $lastActivityTime = $this->connection->fetchColumn('SELECT MAX(lastActivityTime) FROM '.$tableName);
95
                if ($lastActivityTime !== null) {
96
                    $lastActivityTime = new \DateTime($lastActivityTime);
97
                    $this->logger->info('Last activity time: '.$lastActivityTime->format('c'));
98
                    // Let's add one second to the last activity time (otherwise, we are fetching again the last record in DB).
99
                    $lastActivityTime->add(new \DateInterval('PT1S'));
100
                }
101
102
                $records = $dao->getRecords(null, null, $lastActivityTime);
103
                $deletedRecordIds = $dao->getDeletedRecordIds($lastActivityTime);
104
            }
105
        } else {
106
            $this->logger->notice("Copying FULL data for '$tableName'");
107
            $records = $dao->getRecords();
108
            $deletedRecordIds = [];
109
        }
110
        $this->logger->info('Fetched '.count($records).' records');
0 ignored issues
show
Bug introduced by
The variable $records does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
111
112
        $table = $this->connection->getSchemaManager()->createSchema()->getTable($tableName);
113
114
        $flatFields = ZohoDatabaseHelper::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...
115
        $fieldsByName = [];
116
        foreach ($flatFields as $field) {
117
            $fieldsByName[$field['name']] = $field;
118
        }
119
120
        $select = $this->connection->prepare('SELECT * FROM '.$tableName.' WHERE id = :id');
121
122
        $this->connection->beginTransaction();
123
124
        foreach ($records as $record) {
125
            $data = [];
126
            $types = [];
127
            foreach ($table->getColumns() as $column) {
128
                if (in_array($column->getName(), ['id', 'uid'])) {
129
                    continue;
130
                } else {
131
                    $field = $fieldsByName[$column->getName()];
132
                    $getterName = $field['getter'];
133
                    $data[$column->getName()] = $record->$getterName();
134
                    $types[$column->getName()] = $column->getType()->getName();
135
                }
136
            }
137
138
            $select->execute(['id' => $record->getZohoId()]);
139
            $result = $select->fetch(\PDO::FETCH_ASSOC);
140
            if ($result === false) {
141
                $this->logger->debug("Inserting record with ID '".$record->getZohoId()."'.");
142
143
                $data['id'] = $record->getZohoId();
144
                $types['id'] = 'string';
145
146
                $this->connection->insert($tableName, $data, $types);
147
148
                foreach ($this->listeners as $listener) {
149
                    $listener->onInsert($data, $dao);
150
                }
151
            } else {
152
                $this->logger->debug("Updating record with ID '".$record->getZohoId()."'.");
153
                $identifier = ['id' => $record->getZohoId()];
154
                $types['id'] = 'string';
155
156
                $this->connection->update($tableName, $data, $identifier, $types);
157
158
                // Let's add the id for the update trigger
159
                $data['id'] = $record->getZohoId();
160
                foreach ($this->listeners as $listener) {
161
                    $listener->onUpdate($data, $result, $dao);
162
                }
163
            }
164
        }
165
        $sqlStatementUid = 'select uid from '.$this->connection->quoteIdentifier($tableName).' where id = :id';
166
        foreach ($deletedRecordIds as $id) {
0 ignored issues
show
Bug introduced by
The variable $deletedRecordIds does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
167
            $uid = $this->connection->fetchColumn($sqlStatementUid, ['id' => $id]);
168
            $this->connection->delete($tableName, ['id' => $id]);
169
            if ($twoWaysSync) {
170
                // TODO: we could detect if there are changes to be updated to the server and try to warn with a log message
171
                // Also, let's remove the newly created field (because of the trigger) to avoid looping back to Zoho
172
                $this->connection->delete('local_delete', ['table_name' => $tableName, 'id' => $id]);
173
                $this->connection->delete('local_update', ['table_name' => $tableName, 'uid' => $uid]);
174
            }
175
        }
176
177
        $this->connection->commit();
178
    }
179
}
180