Completed
Pull Request — master (#751)
by
unknown
01:45
created

createZipContainingEveryFileInManifest()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
1
<?php
2
3
namespace Spatie\Backup\Tasks\Backup;
4
5
use Exception;
6
use Carbon\Carbon;
7
use Spatie\DbDumper\DbDumper;
8
use Illuminate\Support\Collection;
9
use Spatie\DbDumper\Databases\Sqlite;
10
use Spatie\Backup\Events\BackupHasFailed;
11
use Spatie\Backup\Events\BackupWasSuccessful;
12
use Spatie\Backup\Events\BackupZipWasCreated;
13
use Spatie\Backup\Exceptions\InvalidBackupJob;
14
use Spatie\TemporaryDirectory\TemporaryDirectory;
15
use Spatie\Backup\Events\BackupManifestWasCreated;
16
use Spatie\Backup\BackupDestination\BackupDestination;
17
18
class BackupJob
19
{
20
    /** @var \Spatie\Backup\Tasks\Backup\FileSelection */
21
    protected $fileSelection;
22
23
    /** @var \Illuminate\Support\Collection */
24
    protected $dbDumpers;
25
26
    /** @var \Illuminate\Support\Collection */
27
    protected $backupDestinations;
28
29
    /** @var string */
30
    protected $filename;
31
32
    /** @var \Spatie\TemporaryDirectory\TemporaryDirectory */
33
    protected $temporaryDirectory;
34
35
    /** @var bool */
36
    protected $sendNotifications = true;
37
38
    public function __construct()
39
    {
40
        $this->dontBackupFilesystem();
41
        $this->dontBackupDatabases();
42
        $this->setDefaultFilename();
43
44
        $this->backupDestinations = new Collection();
45
    }
46
47
    public function dontBackupFilesystem(): self
48
    {
49
        $this->fileSelection = FileSelection::create();
50
51
        return $this;
52
    }
53
54
    public function onlyDbName(array $allowedDbNames): self
55
    {
56
        $this->dbDumpers = $this->dbDumpers->filter(
57
            function (DbDumper $dbDumper, string $connectionName) use ($allowedDbNames) {
58
                return in_array($connectionName, $allowedDbNames);
59
            });
60
61
        return $this;
62
    }
63
64
    public function dontBackupDatabases(): self
65
    {
66
        $this->dbDumpers = new Collection();
67
68
        return $this;
69
    }
70
71
    public function disableNotifications(): self
72
    {
73
        $this->sendNotifications = false;
74
75
        return $this;
76
    }
77
78
    public function setDefaultFilename(): self
79
    {
80
        $this->filename = Carbon::now()->format('Y-m-d-H-i-s').'.zip';
81
82
        return $this;
83
    }
84
85
    public function setFileSelection(FileSelection $fileSelection): self
86
    {
87
        $this->fileSelection = $fileSelection;
88
89
        return $this;
90
    }
91
92
    public function setDbDumpers(Collection $dbDumpers): self
93
    {
94
        $this->dbDumpers = $dbDumpers;
95
96
        return $this;
97
    }
98
99
    public function setFilename(string $filename): self
100
    {
101
        $this->filename = $filename;
102
103
        return $this;
104
    }
105
106
    public function onlyBackupTo(string $diskName): self
107
    {
108
        $this->backupDestinations = $this->backupDestinations->filter(function (BackupDestination $backupDestination) use ($diskName) {
109
            return $backupDestination->diskName() === $diskName;
110
        });
111
112
        if (! count($this->backupDestinations)) {
113
            throw InvalidBackupJob::destinationDoesNotExist($diskName);
114
        }
115
116
        return $this;
117
    }
118
119
    public function setBackupDestinations(Collection $backupDestinations): self
120
    {
121
        $this->backupDestinations = $backupDestinations;
122
123
        return $this;
124
    }
125
126
    public function run()
127
    {
128
        $temporaryDirectoryPath = config('backup.backup.temporary_directory') ?? storage_path('app/backup-temp');
129
130
        $this->temporaryDirectory = (new TemporaryDirectory($temporaryDirectoryPath))
131
            ->name('temp')
132
            ->force()
133
            ->create()
134
            ->empty();
135
136
        try {
137
            if (! count($this->backupDestinations)) {
138
                throw InvalidBackupJob::noDestinationsSpecified();
139
            }
140
141
            $manifest = $this->createBackupManifest();
142
143
            if (! $manifest->count()) {
144
                throw InvalidBackupJob::noFilesToBeBackedUp();
145
            }
146
147
            $zipFile = $this->createZipContainingEveryFileInManifest($manifest);
148
149
            $this->copyToBackupDestinations($zipFile);
150
        } catch (Exception $exception) {
151
            consoleOutput()->error("Backup failed because {$exception->getMessage()}.".PHP_EOL.$exception->getTraceAsString());
0 ignored issues
show
Documentation Bug introduced by
The method error does not exist on object<Spatie\Backup\Helpers\ConsoleOutput>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
152
153
            $this->sendNotification(new BackupHasFailed($exception));
154
155
            $this->temporaryDirectory->delete();
156
157
            throw $exception;
158
        }
159
160
        $this->temporaryDirectory->delete();
161
    }
162
163
    protected function createBackupManifest(): Manifest
164
    {
165
        $databaseDumps = $this->dumpDatabases();
166
167
        consoleOutput()->info('Determining files to backup...');
0 ignored issues
show
Documentation Bug introduced by
The method info does not exist on object<Spatie\Backup\Helpers\ConsoleOutput>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
168
169
        $manifest = Manifest::create($this->temporaryDirectory->path('manifest.txt'))
170
            ->addFiles($databaseDumps)
171
            ->addFiles($this->filesToBeBackedUp());
0 ignored issues
show
Documentation introduced by
$this->filesToBeBackedUp() is of type object<Generator>, but the function expects a array|string.

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...
172
173
        $this->sendNotification(new BackupManifestWasCreated($manifest));
174
175
        return $manifest;
176
    }
177
178
    public function filesToBeBackedUp()
179
    {
180
        $this->fileSelection->excludeFilesFrom($this->directoriesUsedByBackupJob());
181
182
        return $this->fileSelection->selectedFiles();
183
    }
184
185
    protected function directoriesUsedByBackupJob(): array
186
    {
187
        return $this->backupDestinations
188
            ->filter(function (BackupDestination $backupDestination) {
189
                return $backupDestination->filesystemType() === 'local';
190
            })
191
            ->map(function (BackupDestination $backupDestination) {
192
                return $backupDestination->disk()->getDriver()->getAdapter()->applyPathPrefix('').$backupDestination->backupName();
193
            })
194
            ->each(function (string $backupDestinationDirectory) {
195
                $this->fileSelection->excludeFilesFrom($backupDestinationDirectory);
196
            })
197
            ->push($this->temporaryDirectory->path())
198
            ->toArray();
199
    }
200
201
    protected function createZipContainingEveryFileInManifest(Manifest $manifest)
202
    {
203
        consoleOutput()->info("Zipping {$manifest->count()} files...");
0 ignored issues
show
Documentation Bug introduced by
The method info does not exist on object<Spatie\Backup\Helpers\ConsoleOutput>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
204
205
        $pathToZip = $this->temporaryDirectory->path(config('backup.backup.destination.filename_prefix').$this->filename);
206
207
        $zip = Zip::createForManifest($manifest, $pathToZip);
208
209
        consoleOutput()->info("Created zip containing {$zip->count()} files. Size is {$zip->humanReadableSize()}");
0 ignored issues
show
Documentation Bug introduced by
The method info does not exist on object<Spatie\Backup\Helpers\ConsoleOutput>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
210
211
        $this->sendNotification(new BackupZipWasCreated($pathToZip));
212
213
        return $pathToZip;
214
    }
215
216
    /**
217
     * Dumps the databases to the given directory.
218
     * Returns an array with paths to the dump files.
219
     *
220
     * @return array
221
     */
222
    protected function dumpDatabases(): array
223
    {
224
        return $this->dbDumpers->map(function (DbDumper $dbDumper) {
225
            consoleOutput()->info("Dumping database {$dbDumper->getDbName()}...");
0 ignored issues
show
Documentation Bug introduced by
The method info does not exist on object<Spatie\Backup\Helpers\ConsoleOutput>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
226
227
            $dbType = mb_strtolower(basename(str_replace('\\', '/', get_class($dbDumper))));
228
229
            $dbName = $dbDumper instanceof Sqlite ? 'database' : $dbDumper->getDbName();
230
231
            $fileName = "{$dbType}-{$dbName}.sql";
232
233
            if (config('backup.backup.gzip_database_dump')) {
234
                $dbDumper->GzipCompression();
0 ignored issues
show
Bug introduced by
The method GzipCompression() does not seem to exist on object<Spatie\DbDumper\DbDumper>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
235
                $fileName .= $dbDumper->getCompressorExtension();
0 ignored issues
show
Bug introduced by
The method getCompressorExtension() does not seem to exist on object<Spatie\DbDumper\DbDumper>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
236
            }
237
238
            if (config('backup.backup.compress_database_dump')) {
239
                $this->setCompressor($dbDumper);
240
                $fileName .= $dbDumper->getCompressorExtension();
0 ignored issues
show
Bug introduced by
The method getCompressorExtension() does not seem to exist on object<Spatie\DbDumper\DbDumper>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
241
            }
242
243
            $temporaryFilePath = $this->temporaryDirectory->path('db-dumps'.DIRECTORY_SEPARATOR.$fileName);
244
245
            $dbDumper->dumpToFile($temporaryFilePath);
246
247
            return $temporaryFilePath;
248
        })->toArray();
249
    }
250
251
    /**
252
     * Set the compressor.
253
     *
254
     * @return void
255
     */
256
    protected function setCompressor($dbDumper)
257
    {
258
        $compressor = config('backup.backup.compress_database_dump.compressor');
259
        $level = config('backup.backup.compress_database_dump.level');
260
261
        $compressor = ucfirst($compressor).'Compression';
262
        $dbDumper->$compressor($level);
263
    }
264
265
    protected function copyToBackupDestinations(string $path)
266
    {
267
        $this->backupDestinations->each(function (BackupDestination $backupDestination) use ($path) {
268
            try {
269
                consoleOutput()->info("Copying zip to disk named {$backupDestination->diskName()}...");
0 ignored issues
show
Documentation Bug introduced by
The method info does not exist on object<Spatie\Backup\Helpers\ConsoleOutput>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
270
271
                $backupDestination->write($path);
272
273
                consoleOutput()->info("Successfully copied zip to disk named {$backupDestination->diskName()}.");
0 ignored issues
show
Documentation Bug introduced by
The method info does not exist on object<Spatie\Backup\Helpers\ConsoleOutput>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
274
275
                $this->sendNotification(new BackupWasSuccessful($backupDestination));
276
            } catch (Exception $exception) {
277
                consoleOutput()->error("Copying zip failed because: {$exception->getMessage()}.");
0 ignored issues
show
Documentation Bug introduced by
The method error does not exist on object<Spatie\Backup\Helpers\ConsoleOutput>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
278
279
                $this->sendNotification(new BackupHasFailed($exception, $backupDestination ?? null));
280
            }
281
        });
282
    }
283
284
    protected function sendNotification($notification)
285
    {
286
        if ($this->sendNotifications) {
287
            event($notification);
288
        }
289
    }
290
}
291