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

Snapshot::delete()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
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
            $dumpFilePath = $this->disk->path($this->fileName);
78
        }
79
80
        $this->streamFileIntoDB($dumpFilePath, $connectionName);
81
82
        event(new LoadedSnapshot($this));
83
    }
84
85
    public function streamFileIntoDB($path, string $connectionName = null)
86
    {
87
        if ($connectionName !== null) {
88
            DB::setDefaultConnection($connectionName);
89
        }
90
91
        $tmpLine = '';
92
        $lines = file($path);
93
        $errors = [];
94
95
        foreach ($lines as $line) {
96
97
            // Skip it if line is a comment
98
            if (substr($line, 0, 2) == '--' || trim($line) == '') {
99
                continue;
100
            }
101
102
            // Add this line to the current segment
103
            $tmpLine .= $line;
104
105
            // If the line ends with a semicolon, it is the end of the query - run it
106
            if (substr(trim($line), -1, 1) == ';') {
107
                try {
108
                    DB::connection($connectionName)->unprepared($tmpLine);
109
                } catch (\Exception $e) {
110
                    $errors[] = [
111
                        'query'   => $tmpLine,
112
                        'message' => $e->getMessage(),
113
                    ];
114
                }
115
116
                $tmpLine = '';
117
            }
118
        }
119
120
        if (empty($errors)) {
121
            return true;
122
        }
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