Completed
Push — master ( 0da15d...9e5495 )
by Sebastian
06:32
created

Redis::isDumpCreatedYet()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 12
rs 9.4286
cc 3
eloc 8
nc 3
nop 2
1
<?php
2
namespace phpbu\App\Backup\Source;
3
4
use phpbu\App\Backup\Cli;
5
use phpbu\App\Backup\Source;
6
use phpbu\App\Backup\Target;
7
use phpbu\App\Cli\Executable;
8
use phpbu\App\Exception;
9
use phpbu\App\Result;
10
use phpbu\App\Util;
11
12
/**
13
 * Tar source class.
14
 *
15
 * @package    phpbu
16
 * @subpackage Backup
17
 * @author     Sebastian Feldmann <[email protected]>
18
 * @copyright  Sebastian Feldmann <[email protected]>
19
 * @license    https://opensource.org/licenses/MIT The MIT License (MIT)
20
 * @link       http://phpbu.de/
21
 * @since      Class available since Release 2.1.12
22
 */
23
class Redis extends Cli implements Source
24
{
25
    /**
26
     * Redis Executable
27
     *
28
     * @var \phpbu\App\Cli\Executable\RedisCli
29
     */
30
    private $executableLast;
0 ignored issues
show
Unused Code introduced by
The property $executableLast is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
31
32
    /**
33
     * Path to executable.
34
     *
35
     * @var string
36
     */
37
    private $pathToRedisCli;
38
39
    /**
40
     * Show stdErr
41
     *
42
     * @var boolean
43
     */
44
    private $showStdErr;
45
46
    /**
47
     * Time to wait for the dump to finish
48
     *
49
     * @var integer
50
     */
51
    private $timeout;
52
53
    /**
54
     * Host to backup
55
     *
56
     * @var string
57
     */
58
    private $host;
59
60
    /**
61
     * Port to connect to
62
     *
63
     * @var boolean
64
     */
65
    private $port;
66
67
    /**
68
     * Password for authentication
69
     *
70
     * @var string
71
     */
72
    private $password;
73
74
    /**
75
     * Path to the redis rdb directory, for Debian it's /var/lib/redis/{PORT}/dump.rdb
76
     *
77
     * @var string
78
     */
79
    private $pathToRedisData;
80
81
    /**
82
     * Setup.
83
     *
84
     * @see    \phpbu\App\Backup\Source
85
     * @param  array $conf
86
     * @throws \phpbu\App\Exception
87
     */
88
    public function setup(array $conf = array())
89
    {
90
        $this->pathToRedisCli  = Util\Arr::getValue($conf, 'pathToRedisCli');
91
        $this->showStdErr      = Util\Str::toBoolean(Util\Arr::getValue($conf, 'showStdErr', ''), false);
92
        $this->pathToRedisData = Util\Arr::getValue($conf, 'pathToRedisData');
93
        $this->timeout         = Util\Arr::getValue($conf, 'timeout', 45);
94
        $this->host            = Util\Arr::getValue($conf, 'host');
95
        $this->port            = Util\Arr::getValue($conf, 'port');
96
        $this->password        = Util\Arr::getValue($conf, 'password');
97
98
        if (empty($this->pathToRedisData)) {
99
            throw new Exception('pathToRedisData option is mandatory');
100
        }
101
    }
102
103
    /**
104
     * Execute the backup.
105
     *
106
     * @see    \phpbu\App\Backup\Source
107
     * @param  \phpbu\App\Backup\Target $target
108
     * @param  \phpbu\App\Result        $result
109
     * @return \phpbu\App\Backup\Source\Status
110
     * @throws \phpbu\App\Exception
111
     */
112
    public function backup(Target $target, Result $result)
113
    {
114
        // set uncompressed default MIME type
115
        $target->setMimeType('application/octet-stream');
116
117
        $redisSave = $this->getExecutable($target);
118
        $redisLast = clone($redisSave);
119
        $redisLast->lastBackupTime();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface phpbu\App\Cli\Executable as the method lastBackupTime() does only exist in the following implementations of said interface: phpbu\App\Cli\Executable\RedisCli.

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...
120
121
        $lastBackupTimestamp = $this->getLastBackupTime($redisLast);
0 ignored issues
show
Compatibility introduced by
$redisLast of type object<phpbu\App\Cli\Executable> is not a sub-type of object<phpbu\App\Cli\Executable\RedisCli>. It seems like you assume a concrete implementation of the interface phpbu\App\Cli\Executable to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
122
123
        $saveResult = $redisSave->run();
124
        $result->debug($saveResult->getCmd());
125
        if (!$saveResult->wasSuccessful()) {
126
            throw new Exception('redis-cli BGSAVE failed');
127
        }
128
        $this->isDumpCreatedYet($lastBackupTimestamp, $redisLast);
0 ignored issues
show
Compatibility introduced by
$redisLast of type object<phpbu\App\Cli\Executable> is not a sub-type of object<phpbu\App\Cli\Executable\RedisCli>. It seems like you assume a concrete implementation of the interface phpbu\App\Cli\Executable to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
129
130
        $pathToDump = $this->copyDumpToTargetDir($target);
131
132
        return Status::create()->uncompressed()->dataPath($pathToDump);
133
    }
134
135
    /**
136
     * Setup the Executable to run the 'tar' command.
137
     *
138
     * @param  \phpbu\App\Backup\Target
139
     * @return \phpbu\App\Cli\Executable
140
     */
141
    public function getExecutable(Target $target)
142
    {
143
        if (null == $this->executable) {
144
            $this->executable = new Executable\RedisCli($this->pathToRedisCli);
145
            $this->executable->backup()
146
                             ->useHost($this->host)
147
                             ->usePort($this->port)
0 ignored issues
show
Documentation introduced by
$this->port is of type boolean, but the function expects a integer.

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...
148
                             ->usePassword($this->password)
149
                             ->showStdErr($this->showStdErr);
150
        }
151
        return $this->executable;
152
    }
153
154
    /**
155
     * Return last successful save timestamp.
156
     *
157
     * @param  \phpbu\App\Cli\Executable\RedisCli $redis
158
     * @return int
159
     * @throws \phpbu\App\Exception
160
     */
161
    private function getLastBackupTime(Executable\RedisCli $redis)
162
    {
163
        $result  = $redis->run();
164
        $output  = $result->getOutputAsString();
165
        $matches = [];
166
        if (!preg_match('#\(integer\) ([0-9]+)#i', $output, $matches)) {
167
            throw new Exception('invalid redis-cli LASTSAVE output');
168
        }
169
        return $matches[1];
170
    }
171
172
    /**
173
     * Check the dump date and return true if BGSAVE is finished.
174
     *
175
     * @param  int $lastTimestamp
176
     * @param  \phpbu\App\Cli\Executable\RedisCli $redis
177
     * @return bool
178
     * @throws \phpbu\App\Exception
179
     */
180
    private function isDumpCreatedYet($lastTimestamp, $redis)
181
    {
182
        $i = 0;
183
        while ($this->getLastBackupTime($redis) <= $lastTimestamp) {
184
            if ($i > $this->timeout) {
185
                throw new Exception('redis-cli BGSAVE is taking to long, increase timeout');
186
            }
187
            $i++;
188
            sleep(1);
189
        }
190
        return true;
191
    }
192
193
    /**
194
     * Copy the redis RDB file to its backup location.
195
     *
196
     * @param  \phpbu\App\Backup\Target $target
197
     * @return string
198
     * @throws \phpbu\App\Exception
199
     */
200
    private function copyDumpToTargetDir(Target $target)
201
    {
202
        if (!file_exists($this->pathToRedisData)) {
203
            throw new Exception('Redis data not found at: \'' . $this->pathToRedisCli . '\'');
204
        }
205
        $targetFile = $target->getPathnamePlain();
206
        copy($this->pathToRedisData, $targetFile);
207
        return $targetFile;
208
    }
209
}
210