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

Snapshot::size()   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 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 Illuminate\Support\Facades\File;
9
use Illuminate\Support\Facades\Storage;
10
use Spatie\DbSnapshots\Events\DeletedSnapshot;
11
use Spatie\DbSnapshots\Events\DeletingSnapshot;
12
use Spatie\DbSnapshots\Events\LoadedSnapshot;
13
use Spatie\DbSnapshots\Events\LoadingSnapshot;
14
use \Exception;
15
use Spatie\TemporaryDirectory\TemporaryDirectory;
16
17
class Snapshot
18
{
19
    /** @var \Illuminate\Filesystem\FilesystemAdapter */
20
    public $disk;
21
22
    /** @var string */
23
    public $fileName;
24
25
    /** @var string */
26
    public $name;
27
28
    /** @var string */
29
    public $compressionExtension = null;
30
31
    /** @var bool */
32
    private $useStream = false;
33
34
    public function __construct(Disk $disk, string $fileName)
35
    {
36
        $this->disk = $disk;
37
38
        $this->fileName = $fileName;
39
40
        $pathinfo = pathinfo($fileName);
41
42
        if ($pathinfo['extension'] === 'gz') {
43
            $this->compressionExtension = $pathinfo['extension'];
44
            $fileName = $pathinfo['filename'];
45
        }
46
47
        $this->name = pathinfo($fileName, PATHINFO_FILENAME);
48
    }
49
50
    public function useStream()
51
    {
52
        $this->useStream = true;
53
        return $this;
54
    }
55
56
    public function load(string $connectionName = null)
57
    {
58
        event(new LoadingSnapshot($this));
59
60
        if ($connectionName !== null) {
61
            DB::setDefaultConnection($connectionName);
62
        }
63
64
        $this->dropAllCurrentTables();
65
66
        $this->useStream ? $this->loadStream($connectionName) : $this->loadAsync($connectionName);
67
68
        event(new LoadedSnapshot($this));
69
    }
70
71
    public function loadAsync(string $connectionName = null)
72
    {
73
        $dbDumpContents = $this->disk->get($this->fileName);
74
75
        if ($this->compressionExtension === 'gz') {
76
            $dbDumpContents = gzdecode($dbDumpContents);
77
        }
78
79
        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 73 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...
80
    }
81
82
    public function loadStream(string $connectionName = null)
83
    {
84
        $dumpFilePath = $this->compressionExtension === 'gz' ? $this->streamDecompress() : $this->disk->path($this->fileName);
0 ignored issues
show
Bug introduced by
The method streamDecompress() does not exist on Spatie\DbSnapshots\Snapshot. Did you maybe mean decompress()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

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