Completed
Pull Request — master (#93)
by Peter
01:15
created

Snapshot   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 176
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
wmc 23
lcom 1
cbo 8
dl 0
loc 176
rs 10
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 2
A load() 0 20 3
A loadStream() 0 21 3
B streamFileIntoDB() 0 40 8
A decompress() 0 33 3
A delete() 0 8 1
A size() 0 4 1
A createdAt() 0 4 1
A dropAllCurrentTables() 0 8 1
1
<?php
2
3
namespace Spatie\DbSnapshots;
4
5
use Carbon\Carbon;
6
use Illuminate\Filesystem\FilesystemAdapter as Disk;
7
use Illuminate\Support\Facades\DB;
8
use Spatie\DbSnapshots\Events\DeletedSnapshot;
9
use Spatie\DbSnapshots\Events\DeletingSnapshot;
10
use Spatie\DbSnapshots\Events\LoadedSnapshot;
11
use Spatie\DbSnapshots\Events\LoadingSnapshot;
12
13
class Snapshot
14
{
15
    /** @var \Illuminate\Filesystem\FilesystemAdapter */
16
    public $disk;
17
18
    /** @var string */
19
    public $fileName;
20
21
    /** @var string */
22
    public $name;
23
24
    /** @var string */
25
    public $compressionExtension = null;
26
27
    public function __construct(Disk $disk, string $fileName)
28
    {
29
        $this->disk = $disk;
30
31
        $this->fileName = $fileName;
32
33
        $pathinfo = pathinfo($fileName);
34
35
        if ($pathinfo['extension'] === 'gz') {
36
            $this->compressionExtension = $pathinfo['extension'];
37
            $fileName = $pathinfo['filename'];
38
        }
39
40
        $this->name = pathinfo($fileName, PATHINFO_FILENAME);
41
    }
42
43
    public function load(string $connectionName = null)
44
    {
45
        event(new LoadingSnapshot($this));
46
47
        if ($connectionName !== null) {
48
            DB::setDefaultConnection($connectionName);
49
        }
50
51
        $this->dropAllCurrentTables();
52
53
        $dbDumpContents = $this->disk->get($this->fileName);
54
55
        if ($this->compressionExtension === 'gz') {
56
            $dbDumpContents = gzdecode($dbDumpContents);
57
        }
58
59
        DB::connection($connectionName)->unprepared($dbDumpContents);
0 ignored issues
show
Security Bug introduced by
It seems like $dbDumpContents defined by $this->disk->get($this->fileName) on line 53 can also be of type false; however, Illuminate\Database\Conn...Interface::unprepared() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
60
61
        event(new LoadedSnapshot($this));
62
    }
63
64
    public function loadStream(string $connectionName = null)
65
    {
66
        event(new LoadingSnapshot($this));
67
68
        if ($connectionName !== null) {
69
            DB::setDefaultConnection($connectionName);
70
        }
71
72
        $this->dropAllCurrentTables();
73
74
        if ($this->compressionExtension === 'gz') {
75
            $dumpFilePath = $this->decompress();
76
        } else {
77
78
            $dumpFilePath = $this->disk->path($this->fileName);
79
        }
80
81
        $this->streamFileIntoDB($dumpFilePath, $connectionName);
82
83
        event(new LoadedSnapshot($this));
84
    }
85
86
    public function streamFileIntoDB($path, string $connectionName = null)
87
    {
88
        if ($connectionName !== null) {
89
            DB::setDefaultConnection($connectionName);
90
        }
91
92
        $tmpLine = '';
93
        $lines   = file($path);
94
        $errors  = [];
95
96
        foreach ($lines as $line) {
97
98
            // Skip it if line is a comment
99
            if (substr($line, 0, 2) == '--' || trim($line) == '') {
100
                continue;
101
            }
102
103
            // Add this line to the current segment
104
            $tmpLine .= $line;
105
106
            // If the line ends with a semicolon, it is the end of the query - run it
107
            if (substr(trim($line), -1, 1) == ';') {
108
                try {
109
                    DB::connection($connectionName)->unprepared($tmpLine);
110
                } catch (\Exception $e) {
111
                    $errors[] = [
112
                        'query'   => $tmpLine,
113
                        'message' => $e->getMessage(),
114
                    ];
115
                }
116
117
                $tmpLine = '';
118
            }
119
        }
120
121
        if (empty($errors)) {
122
            return true;
123
        }
124
        return $errors;
125
    }
126
127
    public function decompress()
128
    {
129
        $stream = $this->disk->readStream($this->fileName);
130
131
        $directory = (new TemporaryDirectory(config('db-snapshots.temporary_directory_path')))->create();
132
133
        $loadPath = $directory->path('temp-load.tmp');
134
135
        $gzPath = $loadPath.'.gz';
136
        $sqlPath = $loadPath.'.sql';
137
138
        $fileDest = fopen($gzPath, 'w');
139
140
        $buffer_size = 4096; // read 4kb at a time
141
142
        while(feof($stream)!==true) {
143
            fwrite($fileDest, fread($stream, $buffer_size));
144
        }
145
146
        $fileSource = gzopen($gzPath, "rb");
147
        $fileDest = fopen($sqlPath, 'w');
148
149
        while(feof($fileSource)!==true) {
150
            fwrite($fileDest, gzread($fileSource, $buffer_size));
151
        }
152
153
        fclose($stream);
154
        fclose($fileDest);
155
156
        $this->disk = Storage::disk('local');
157
158
        return $sqlPath;
159
    }
160
161
    public function delete()
162
    {
163
        event(new DeletingSnapshot($this));
164
165
        $this->disk->delete($this->fileName);
166
167
        event(new DeletedSnapshot($this->fileName, $this->disk));
168
    }
169
170
    public function size(): int
171
    {
172
        return $this->disk->size($this->fileName);
173
    }
174
175
    public function createdAt(): Carbon
176
    {
177
        return Carbon::createFromTimestamp($this->disk->lastModified($this->fileName));
178
    }
179
180
    protected function dropAllCurrentTables()
181
    {
182
        DB::connection(DB::getDefaultConnection())
183
            ->getSchemaBuilder()
184
            ->dropAllTables();
185
186
        DB::reconnect();
187
    }
188
}
189