DatabaseReplace::sendNotification()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 0
1
<?php namespace Tequilarapido\Cli\Commands;
2
3
use Symfony\Component\Console\Input\InputInterface;
4
use Symfony\Component\Console\Input\InputOption;
5
use Symfony\Component\Console\Output\OutputInterface;
6
use Tequilarapido\Cli\Commands\Base\AbstractDatabaseCommand;
7
use Tequilarapido\Database\Column;
8
use Tequilarapido\Helpers\ShellHelper;
9
use Tequilarapido\PHPSerialized\SearchReplace;
10
11
class DatabaseReplace extends AbstractDatabaseCommand
12
{
13
    const OPTION_USE_TRANSACTIONS = 'use-transactions';
14
15
    protected $useTransactions = false;
16
17 View Code Duplication
    protected function configure()
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...
18
    {
19
        parent::configure();
20
21
        $description = '';
22
        $description .= 'Search and replace string in database (even in serialized objects). ' . PHP_EOL;
23
        $description .= 'Can be used to switch domain for a WordPress application, when moving to different environement. ' . PHP_EOL;
24
        $description .= '  ';
25
26
        $this
27
            ->setName('db:replace')
28
            ->setDescription($description)
29
            ->addOption(
30
                static::OPTION_USE_TRANSACTIONS,
31
                null,
32
                InputOption::VALUE_NONE,
33
                'If specified, SQL update operations will be grouped into transactions.'
34
            );
35
    }
36
37
    protected function execute(InputInterface $input, OutputInterface $output)
38
    {
39
        parent::execute($input, $output);
40
        $this->useTransactions = $input->getOption(static::OPTION_USE_TRANSACTIONS);
41
        $replacements = $this->config->getReplacements();
42
        $excludeTables = $this->config->getExcludeTables();
43
44
        // Operations config
45
        if (is_null($replacements)) {
46
            $this->output->warn('There is nothing to replace according to configuration.');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Output\OutputInterface as the method warn() does only exist in the following implementations of said interface: Tequilarapido\Cli\EnhancedOutput.

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...
47
            return;
48
        }
49
50
        // Setup connection
51
        $this->setup();
52
53
        // Process
54
        $textColumns = $this->analyseDatabase($excludeTables);
55
        $progress = $this->startProgress(count($textColumns));
56
        $queriesCount = $this->processDatabase($textColumns, $progress, $replacements);
57
        $progress->finish();
58
59
        if ($this->useTransactions) {
60
            $this->output->info("Queries were executed using transactions.\n");
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Output\OutputInterface as the method info() does only exist in the following implementations of said interface: Tequilarapido\Cli\EnhancedOutput.

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...
61
        }
62
        $this->output->info("Total executed queries : $queriesCount");
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Output\OutputInterface as the method info() does only exist in the following implementations of said interface: Tequilarapido\Cli\EnhancedOutput.

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...
63
64
        // Mail
65
        $this->sendNotification();
66
    }
67
68
    protected function startProgress($total)
69
    {
70
        $progress = $this->getHelperSet()->get('progress');
71
        $progress->start($this->output, $total);
72
        return $progress;
73
    }
74
75
    protected function sendNotification()
76
    {
77
        $mail = array(
78
            'subject' => 'Done.',
79
            'body' => ''
80
        );
81
82
        $this->notify($mail);
83
    }
84
85
    /**
86
     * @param $excludeTables
87
     * @return array
88
     */
89
    protected function analyseDatabase($excludeTables)
90
    {
91
        $this->output->info('Analysing database : looking for text columns ...');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Output\OutputInterface as the method info() does only exist in the following implementations of said interface: Tequilarapido\Cli\EnhancedOutput.

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...
92
        $column = new Column();
93
        $textColumns = $column->scanTextColumns($this->databaseName, $excludeTables);
94
        return $textColumns;
95
    }
96
97
    protected function setup()
98
    {
99
        parent::setup();
100
101
        // Need memory on big databases
102
        if ($this->useTransactions) {
103
            $this->iAmHungry();
104
        }
105
    }
106
107
    /**
108
     * @param $textColumns
109
     * @param $progress
110
     * @param $replacements
111
     * @return int
112
     */
113
    protected function processDatabase($textColumns, $progress, $replacements)
114
    {
115
        $queriesCount = 0;
116
        foreach ($textColumns as $tableName => $tableInfos) {
117
            $progress->advance();
118
            $this->output->writeln(" : Processing $tableName  ");
119
            $tableCount = $this->processTable($tableName, $tableInfos, $replacements);
120
            $queriesCount += $tableCount;
121
            $this->output->writeln("                                        -> $tableCount queries ");
122
        }
123
        return $queriesCount;
124
    }
125
126
    protected function processTable($tableName, $tableInfos, $replacements)
127
    {
128
        $queriesCount = 0;
129
130
        // Is there columns ?
131
        if (empty($tableInfos['columns'])) {
132
            return;
133
        }
134
135
        // Iterates in each column and looks for the old string
136
        foreach ($tableInfos['columns'] as $field_name) {
137
138
            $this->beginTransaction();
139
140
            foreach ($replacements as $replacement) {
141
142
                // Search
143
                $searchQuery = $this->searchQuery($tableName, $tableInfos, $field_name, $replacement);
144
                $search_results = $this->db->select($searchQuery);
145
                if (empty($search_results)) {
146
                    continue;
147
                }
148
149
                // Loop through result and  search/replace
150
                foreach ($search_results as $found_data) {
151
152
                    // Pk check
153
                    if (isset($found_data['_id'])) {
154
                        $id = $found_data['_id'];
155
                        unset($found_data['_id']);
156
                    }
157
                    $found_data = current($found_data);
158
159
                    // Try to replace string
160
                    try {
161
                        $sr = new SearchReplace;
162
                        $edited_data = $sr->run($replacement->from, $replacement->to, $found_data);
163
                    } catch
164
                    (\Exception $e) {
165
                        continue;
166
                    }
167
168
                    // Update entry / value
169
                    if (isset($id)) {
170
                        $this->db
171
                            ->table($tableName)
172
                            ->where($tableInfos['pk'], '=', $id)
173
                            ->update(array($field_name => $edited_data));
174
175
                    } else {
176
                        $this->db
177
                            ->table($tableName)
178
                            ->where($field_name, '=', $found_data)
179
                            ->update(array($field_name => $edited_data));
180
                    }
181
182
                    $queriesCount++;
183
184
                    // display progress
185
                    ShellHelper::progress($this->output);
186
                }
187
            }
188
189
            // Commit updates if using transactions
190
            $this->endTransaction();
191
        }
192
193
        // End shell progress
194
        ShellHelper::progressEnd($this->output);
195
        return $queriesCount;
196
    }
197
198 View Code Duplication
    protected function searchQuery($tableName, $tableInfos, $field_name, $replacement)
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...
199
    {
200
        // table with primary key ?
201
        if (empty($tableInfos['pk'])) {
202
            return sprintf('SELECT `%s` FROM %s WHERE `%s` LIKE "%%%s%%"', $field_name, $tableName, $field_name, $replacement->from);
203
        } else {
204
            return sprintf('SELECT `%s`, ' . $tableInfos['pk'] . ' AS _id FROM %s WHERE `%s` LIKE "%%%s%%"', $field_name, $tableName, $field_name, $replacement->from);
205
        }
206
    }
207
208
    private function beginTransaction()
209
    {
210
        if ($this->useTransactions) {
211
            $this->db->beginTransaction();
212
        }
213
    }
214
215
    private function endTransaction()
216
    {
217
        if ($this->useTransactions) {
218
            $this->db->commit();
219
        }
220
    }
221
222
}
223