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

Snapshot::loadAsync()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 2
nc 2
nop 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 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);
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 streamDecompress()
132
    {
133
        $stream      = $this->disk->readStream($this->fileName);
134
        $directory   = (new TemporaryDirectory(config('db-snapshots.temporary_directory_path')))->create();
135
        $loadPath    = $directory->path('temp-load.tmp');
136
        $gzPath      = $loadPath.'.gz';
137
        $sqlPath     = $loadPath.'.sql';
138
        $fileDest    = fopen($gzPath, 'w');
139
        $buffer_size = 4096;
140
141
        if (!file_exists($this->disk->path($this->fileName))) {
142
            while (feof($stream) !== true) {
143
                fwrite($fileDest, fread($stream, $buffer_size));
144
            }
145
        }
146
147
        $fileSource = gzopen($gzPath, 'rb');
148
        $fileDest = fopen($sqlPath, 'w');
149
150
        while (feof($fileSource) !== true) {
151
            fwrite($fileDest, gzread($fileSource, $buffer_size));
152
        }
153
154
        fclose($stream);
155
        fclose($fileDest);
156
157
        $this->disk = Storage::disk('local');
158
159
        return $sqlPath;
160
    }
161
162
    public function delete()
163
    {
164
        event(new DeletingSnapshot($this));
165
166
        $this->disk->delete($this->fileName);
167
168
        event(new DeletedSnapshot($this->fileName, $this->disk));
169
    }
170
171
    public function size(): int
172
    {
173
        return $this->disk->size($this->fileName);
174
    }
175
176
    public function createdAt(): Carbon
177
    {
178
        return Carbon::createFromTimestamp($this->disk->lastModified($this->fileName));
179
    }
180
181
    protected function dropAllCurrentTables()
182
    {
183
        DB::connection(DB::getDefaultConnection())
184
            ->getSchemaBuilder()
185
            ->dropAllTables();
186
187
        DB::reconnect();
188
    }
189
}
190