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

ZohoDatabasePusher::pushInsertedRows()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
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\Service\EntitiesGeneratorService;
10
use Wabel\Zoho\CRM\ZohoBeanInterface;
11
12
/**
13
 * Description of ZohoDatabasePusher.
14
 *
15
 * @author rbergina
16
 */
17
class ZohoDatabasePusher
18
{
19
    /**
20
     * @var Connection
21
     */
22
    private $connection;
23
24
    /**
25
     * @var LoggerInterface
26
     */
27
    private $logger;
28
29
    /**
30
     * @var string
31
     */
32
    private $prefix;
33
34
    /**
35
     * @param Connection      $connection
36
     * @param int             $apiLimitInsertUpdateDelete
37
     * @param string          $prefix
38
     * @param LoggerInterface $logger
39
     */
40
    public function __construct(Connection $connection, $apiLimitInsertUpdateDelete = 100, $prefix = 'zoho_', LoggerInterface $logger = null)
41
    {
42
        $this->connection = $connection;
43
        $this->prefix = $prefix;
44
        if ($logger === null) {
45
            $this->logger = new NullLogger();
46
        } else {
47
            $this->logger = $logger;
48
        }
49
        $this->apiLimitInsertUpdateDelete = $apiLimitInsertUpdateDelete;
50
        if($apiLimitInsertUpdateDelete === null) {
51
            $this->apiLimitInsertUpdateDelete = 100;
52
        }
53
    }
54
55
    /**
56
     * @var int
57
     */
58
    private $apiLimitInsertUpdateDelete;
59
60
    /**
61
     * @param  AbstractZohoDao $zohoDao
62
     * @param  bool            $update
63
     * @return int
64
     */
65
    private function countElementInTable(AbstractZohoDao $zohoDao, $update = false)
66
    {
67
        $localTable = $update ? 'local_update' : 'local_insert';
68
        $tableName = ZohoDatabaseHelper::getTableName($zohoDao, $this->prefix);
69
        return $this->connection->executeQuery(
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Doctrine\DBAL\Driver\ResultStatement as the method rowCount() does only exist in the following implementations of said interface: Doctrine\DBAL\Cache\ResultCacheStatement, Doctrine\DBAL\Driver\IBMDB2\DB2Statement, Doctrine\DBAL\Driver\Mysqli\MysqliStatement, Doctrine\DBAL\Driver\OCI8\OCI8Statement, Doctrine\DBAL\Driver\PDOSqlsrv\Statement, Doctrine\DBAL\Driver\PDOStatement, Doctrine\DBAL\Driver\SQL...re\SQLAnywhereStatement, Doctrine\DBAL\Driver\SQLSrv\SQLSrvStatement, Doctrine\DBAL\Portability\Statement, Doctrine\DBAL\Statement.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
70
            'select uid from '.$localTable.' where table_name like :tableName',
0 ignored issues
show
Security introduced by
'select uid from ' . $lo...e_name like :tableName' is used as a query on line 69. If $localTable can contain user-input, it is usually preferable to use a parameter placeholder like :paramName and pass the dynamic input as second argument array('param' => $localTable).

Instead of embedding dynamic parameters in SQL, Doctrine also allows you to pass them separately and insert a placeholder instead:

function findUser(Doctrine\DBAL\Connection $con, $email) {
    // Unsafe
    $con->executeQuery("SELECT * FROM users WHERE email = '".$email."'");

    // Safe
    $con->executeQuery(
        "SELECT * FROM users WHERE email = :email",
        array('email' => $email)
    );
}
Loading history...
71
            ['tableName' => $tableName]
72
        )->rowCount();
73
    }
74
75
    /**
76
     * Insert or Update rows.
77
     *
78
     * @param AbstractZohoDao $zohoDao
79
     */
80
    public function pushDataToZoho(AbstractZohoDao $zohoDao, $update = false)
81
    {
82
        $localTable = $update ? 'local_update' : 'local_insert';
83
        $tableName = ZohoDatabaseHelper::getTableName($zohoDao, $this->prefix);
84
        do{
85
            $rowsDeleted = [];
86
            //@see https://www.zoho.com/crm/help/api/v2/#ra-update-records
87
            //To optimize your API usage, get maximum 200 records with each request and insert, update or delete maximum 100 records with each request.
88
            $statementLimiter = $this->connection->createQueryBuilder();
89
            $statementLimiter->select('DISTINCT table_name,uid')
90
                ->from($localTable)->setMaxResults($this->apiLimitInsertUpdateDelete);
91
            $statement = $this->connection->createQueryBuilder();
92
            $statement->select('zcrm.*');
93
            if ($update) {
94
                $statement->addSelect('l.field_name as updated_fieldname');
95
            }
96
            $statement->from($localTable, 'l')
97
                ->join('l', '('.$statementLimiter->getSQL().')', 'll', 'll.table_name = l.table_name and  ll.uid = l.uid')
98
                ->join('l', $tableName, 'zcrm', 'zcrm.uid = l.uid')
99
                ->where('l.table_name=:table_name')
100
                ->setParameters(
101
                    [
102
                    'table_name' => $tableName,
103
                    ]
104
                );
105
            $results = $statement->execute();
106
            /* @var $zohoBeans ZohoBeanInterface[] */
107
            $zohoBeans = array();
108
            while ($row = $results->fetch()) {
109
                //                $beanClassName = $zohoDao->getBeanClassName();
110
                /* @var $zohoBean ZohoBeanInterface */
111
                if (isset($zohoBeans[$row['uid']])) {
112
                    $zohoBean = $zohoBeans[$row['uid']];
113
                } else {
114
                    //                    $zohoBean = new $beanClassName();
115
                    $zohoBean = $zohoDao->create();
116
                }
117
118
                if (!$update) {
119
                    $this->insertDataZohoBean($zohoDao, $zohoBean, $row);
120
                    $zohoBeans[$row['uid']] = $zohoBean;
121
                    $rowsDeleted[] = $row['uid'];
122
                }
123
                if ($update && isset($row['updated_fieldname'])) {
124
                    $columnName = $row['updated_fieldname'];
125
                    $zohoBean->getZohoId() ?: $zohoBean->setZohoId($row['id']);
126
                    $this->updateDataZohoBean($zohoDao, $zohoBean, $columnName, $row[$columnName]);
127
                    $zohoBeans[$row['uid']] = $zohoBean;
128
                    $rowsDeleted[] = $row['uid'];
129
                }
130
            }
131
            if($zohoBeans) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $zohoBeans of type Wabel\Zoho\CRM\ZohoBeanInterface[] 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...
132
                $this->sendDataToZohoCleanLocal($zohoDao, $zohoBeans, $rowsDeleted, $update);
133
            }
134
            $countToPush = $this->countElementInTable($zohoDao, $update);
135
        } while($countToPush > 0);
136
    }
137
138
    /**
139
     * @param AbstractZohoDao     $zohoDao
140
     * @param ZohoBeanInterface[] $zohoBeans
141
     * @param string[]            $rowsDeleted
142
     * @param bool                $update
143
     */
144
    private function sendDataToZohoCleanLocal(AbstractZohoDao $zohoDao, array $zohoBeans,$rowsDeleted, $update = false)
145
    {
146
        $tableName = ZohoDatabaseHelper::getTableName($zohoDao, $this->prefix);
147
        $zohoDao->save($zohoBeans);
148
        if (!$update) {
149
            foreach ($zohoBeans as $uid => $zohoBean) {
150
                $countResult = (int) $this->connection->fetchColumn('select count(id) from '.$tableName.' where id = :id', ['id'=>$zohoBean->getZohoId()]);
151
                //If the sent data were duplicates Zoho can merged so we need to check if the Zoho ID already exist.
152
                if ($countResult === 0) {
153
                    // ID not exist we can update the new row with the Zoho ID
154
                    $this->connection->beginTransaction();
155
                    $this->connection->update($tableName, ['id' => $zohoBean->getZohoId()], ['uid' => $uid]);
156
                    $this->connection->delete('local_insert', ['table_name'=>$tableName, 'uid' => $uid ]);
157
                    $this->connection->commit();
158
                } else {
159
                    //ID already exist we need to delete the duplicate row.
160
                    $this->connection->beginTransaction();
161
                    $this->connection->delete($tableName, ['uid' => $uid ]);
162
                    $this->connection->delete('local_insert', ['table_name'=>$tableName, 'uid' => $uid ]);
163
                    $this->connection->commit();
164
                }
165
            }
166
        } else {
167
            $this->connection->executeUpdate(
168
                'delete from local_update where uid in ( :rowsDeleted)',
169
                [
170
                    'rowsDeleted' => $rowsDeleted,
171
                ],
172
                [
173
                    'rowsDeleted' => Connection::PARAM_INT_ARRAY,
174
                ]
175
            );
176
        }
177
    }
178
179
    /**
180
     * Insert data to bean in order to insert zoho records.
181
     *
182
     * @param AbstractZohoDao   $dao
183
     * @param ZohoBeanInterface $zohoBean
184
     * @param array             $row
185
     */
186 View Code Duplication
    private function insertDataZohoBean(AbstractZohoDao $dao, ZohoBeanInterface $zohoBean, array $row)
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...
187
    {
188
        foreach ($row as $columnName => $columnValue) {
189
            $fieldMethod = $dao->getFieldFromFieldName($columnName);
190
            if(!in_array($columnName, EntitiesGeneratorService::$defaultDateFields) && $fieldMethod
191
                && (!in_array($columnName, ['id', 'uid'])) && !is_null($columnValue)
192
            ) {
193
                $type = $fieldMethod->getType();
194
                $value = $this->formatValueToBeans($type, $columnValue);
195
                $setterMethod = $fieldMethod->getSetter();
196
                $zohoBean->{$setterMethod}($value);
197
            }
198
        }
199
    }
200
201
    /**
202
     * Insert data to bean in order to update zoho records.
203
     *
204
     * @param ZohoBeanInterface $zohoBean
205
     * @param array             $fieldsMatching
0 ignored issues
show
Bug introduced by
There is no parameter named $fieldsMatching. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
206
     * @param type              $columnName
207
     * @param type              $valueDb
208
     */
209 View Code Duplication
    private function updateDataZohoBean(AbstractZohoDao $dao, ZohoBeanInterface $zohoBean, $columnName, $valueDb)
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...
210
    {
211
        $fieldMethod = $dao->getFieldFromFieldName($columnName);
212
        if (!in_array($columnName, EntitiesGeneratorService::$defaultDateFields) && $fieldMethod
213
            && !in_array($columnName, ['id', 'uid'])
214
        ) {
215
            $type = $fieldMethod->getType();
216
            $value = is_null($valueDb) ? $valueDb : $this->formatValueToBeans($type, $valueDb);
217
            $setterMethod = $fieldMethod->getSetter();
218
            $zohoBean->{$setterMethod}($value);
219
        }
220
    }
221
222
    /**
223
     * Change the value to the good format.
224
     *
225
     * @param string $type
226
     * @param mixed  $value
227
     *
228
     * @return mixed
229
     */
230
    private function formatValueToBeans($type, $value)
231
    {
232
        switch ($type) {
233
        case 'date':
234
            $value = \DateTime::createFromFormat('Y-m-d', $value)?:null;
235
            break;
236
        case 'datetime':
237
            $value = \DateTime::createFromFormat('Y-m-d H:i:s', $value)?:null;
238
            break;
239
        case 'boolean' :
240
            $value = (bool) $value;
241
            break;
242
        case 'percent' :
243
            $value = (int) $value;
244
            break;
245
        case 'double' :
246
            $value = number_format($value, 2);
247
            break;
248
        case 'multiselectlookup':
249
        case 'multiuserlookup':
250
        case 'multiselectpicklist':
251
            $value = explode(';', $value);
252
            break;
253
        }
254
255
        return $value;
256
    }
257
258
    /**
259
     * Run deleted rows to Zoho : local_delete.
260
     *
261
     * @param AbstractZohoDao $zohoDao
262
     */
263
    public function pushDeletedRows(AbstractZohoDao $zohoDao)
264
    {
265
        $localTable = 'local_delete';
266
        $tableName = ZohoDatabaseHelper::getTableName($zohoDao, $this->prefix);
267
        $statement = $this->connection->createQueryBuilder();
268
        $statement->select('l.id')
269
            ->from($localTable, 'l')
270
            ->where('l.table_name=:table_name')
271
            ->setParameters(
272
                [
273
                'table_name' => $tableName,
274
                ]
275
            );
276
        $results = $statement->execute();
277
        while ($row = $results->fetch()) {
278
            $zohoDao->delete($row['id']);
279
            $this->connection->delete($localTable, ['table_name' => $tableName, 'id' => $row['id']]);
280
        }
281
    }
282
283
    /**
284
     * Run inserted rows to Zoho : local_insert.
285
     *
286
     * @param AbstractZohoDao $zohoDao
287
     */
288
    public function pushInsertedRows(AbstractZohoDao $zohoDao)
289
    {
290
        return $this->pushDataToZoho($zohoDao);
291
    }
292
293
    /**
294
     * Run updated rows to Zoho : local_update.
295
     *
296
     * @param AbstractZohoDao $zohoDao
297
     */
298
    public function pushUpdatedRows(AbstractZohoDao $zohoDao)
299
    {
300
        $this->pushDataToZoho($zohoDao, true);
301
    }
302
303
    /**
304
     * Push data from db to Zoho.
305
     *
306
     * @param AbstractZohoDao $zohoDao
307
     */
308
    public function pushToZoho(AbstractZohoDao $zohoDao)
309
    {
310
        $this->logger->info(' > Insert new rows using {class_name}', ['class_name' => get_class($zohoDao)]);
311
        $this->pushInsertedRows($zohoDao);
312
        $this->logger->info(' > Update rows using {class_name}', ['class_name' => get_class($zohoDao)]);
313
        $this->pushUpdatedRows($zohoDao);
314
        $this->logger->info(' > Delete rows using  {class_name}', ['class_name' => get_class($zohoDao)]);
315
        $this->pushDeletedRows($zohoDao);
316
    }
317
}
318