Completed
Pull Request — master (#525)
by Harish
01:41
created

BackupJob::copyToBackupDestinations()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 10
nc 1
nop 1
dl 0
loc 18
rs 9.4285
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(): BackupJob
48
    {
49
        $this->fileSelection = FileSelection::create();
50
51
        return $this;
52
    }
53
54
    public function dontBackupDatabases(): BackupJob
55
    {
56
        $this->dbDumpers = new Collection();
57
58
        return $this;
59
    }
60
61
    public function disableNotifications(): BackupJob
62
    {
63
        $this->sendNotifications = false;
64
65
        return $this;
66
    }
67
68
    public function setDefaultFilename(): BackupJob
69
    {
70
        $this->filename = Carbon::now()->format('Y-m-d-H-i-s').'.zip';
71
72
        return $this;
73
    }
74
75
    public function setFileSelection(FileSelection $fileSelection): BackupJob
76
    {
77
        $this->fileSelection = $fileSelection;
78
79
        return $this;
80
    }
81
82
    public function setDbDumpers(Collection $dbDumpers): BackupJob
83
    {
84
        $this->dbDumpers = $dbDumpers;
85
86
        return $this;
87
    }
88
89
    public function setFilename(string $filename): BackupJob
90
    {
91
        $this->filename = $filename;
92
93
        return $this;
94
    }
95
96
    public function onlyBackupTo(string $diskName): BackupJob
97
    {
98
        $this->backupDestinations = $this->backupDestinations->filter(function (BackupDestination $backupDestination) use ($diskName) {
99
            return $backupDestination->diskName() === $diskName;
100
        });
101
102
        if (! count($this->backupDestinations)) {
103
            throw InvalidBackupJob::destinationDoesNotExist($diskName);
104
        }
105
106
        return $this;
107
    }
108
109
    public function setBackupDestinations(Collection $backupDestinations): BackupJob
110
    {
111
        $this->backupDestinations = $backupDestinations;
112
113
        return $this;
114
    }
115
116
    public function temporaryDirectoryLocation(): string
117
    {
118
        $disk = config('backup.backup.destination.disks');
119
120
        return config("filesystems.disks.$disk[0].root") ?? storage_path('app/backups');
121
    }
122
123
    public function run()
124
    {
125
        $this->temporaryDirectory = (new TemporaryDirectory($this->temporaryDirectoryLocation()))
126
            ->name('temp')
127
            ->force()
128
            ->create()
129
            ->empty();
130
131
        try {
132
            if (! count($this->backupDestinations)) {
133
                throw InvalidBackupJob::noDestinationsSpecified();
134
            }
135
136
            $manifest = $this->createBackupManifest();
137
138
            if (! $manifest->count()) {
139
                throw InvalidBackupJob::noFilesToBeBackedUp();
140
            }
141
142
            $zipFile = $this->createZipContainingEveryFileInManifest($manifest);
143
144
            $this->copyToBackupDestinations($zipFile);
145
        } catch (Exception $exception) {
146
            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...
147
148
            $this->sendNotification(new BackupHasFailed($exception));
149
        }
150
151
        $this->temporaryDirectory->delete();
152
    }
153
154
    protected function createBackupManifest(): Manifest
155
    {
156
        $databaseDumps = $this->dumpDatabases();
157
158
        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...
159
160
        $manifest = Manifest::create($this->temporaryDirectory->path('manifest.txt'))
161
            ->addFiles($databaseDumps)
162
            ->addFiles($this->filesToBeBackedUp());
0 ignored issues
show
Documentation introduced by
$this->filesToBeBackedUp() is of type object<Generator>, but the function expects a array.

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...
163
164
        $this->sendNotification(new BackupManifestWasCreated($manifest));
165
166
        return $manifest;
167
    }
168
169
    public function filesToBeBackedUp()
170
    {
171
        $this->fileSelection->excludeFilesFrom($this->directoriesUsedByBackupJob());
172
173
        return $this->fileSelection->selectedFiles();
174
    }
175
176
    protected function directoriesUsedByBackupJob(): array
177
    {
178
        return $this->backupDestinations
179
            ->filter(function (BackupDestination $backupDestination) {
180
                return $backupDestination->filesystemType() === 'local';
181
            })
182
            ->map(function (BackupDestination $backupDestination) {
183
                return $backupDestination->disk()->getDriver()->getAdapter()->applyPathPrefix('').$backupDestination->backupName();
184
            })
185
            ->each(function (string $backupDestinationDirectory) {
186
                $this->fileSelection->excludeFilesFrom($backupDestinationDirectory);
187
            })
188
            ->push($this->temporaryDirectory->path())
189
            ->toArray();
190
    }
191
192
    protected function createZipContainingEveryFileInManifest(Manifest $manifest)
193
    {
194
        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...
195
196
        $pathToZip = $this->temporaryDirectory->path(config('backup.backup.name').'-'.$this->filename);
197
198
        $zip = Zip::createForManifest($manifest, $pathToZip);
199
200
        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...
201
202
        $this->sendNotification(new BackupZipWasCreated($pathToZip));
203
204
        return $pathToZip;
205
    }
206
207
    /**
208
     * Dumps the databases to the given directory.
209
     * Returns an array with paths to the dump files.
210
     *
211
     * @return array
212
     */
213
    protected function dumpDatabases(): array
214
    {
215
        return $this->dbDumpers->map(function (DbDumper $dbDumper) {
216
            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...
217
218
            $dbType = mb_strtolower(basename(str_replace('\\', '/', get_class($dbDumper))));
219
220
            $dbName = $dbDumper instanceof Sqlite ? 'database' : $dbDumper->getDbName();
221
222
            $fileName = "{$dbType}-{$dbName}.sql";
223
224
            $temporaryFilePath = $this->temporaryDirectory->path('db-dumps'.DIRECTORY_SEPARATOR.$fileName);
225
226
            $dbDumper->dumpToFile($temporaryFilePath);
227
228
            if (config('backup.backup.gzip_database_dump')) {
229
                consoleOutput()->info("Gzipping {$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...
230
231
                $compressedDumpPath = Gzip::compress($temporaryFilePath);
232
233
                return $compressedDumpPath;
234
            }
235
236
            return $temporaryFilePath;
237
        })->toArray();
238
    }
239
240
    protected function copyToBackupDestinations(string $path)
241
    {
242
        $this->backupDestinations->each(function (BackupDestination $backupDestination) use ($path) {
243
            try {
244
                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...
245
246
                $backupDestination->write($path);
247
248
                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...
249
250
                $this->sendNotification(new BackupWasSuccessful($backupDestination));
251
            } catch (Exception $exception) {
252
                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...
253
254
                $this->sendNotification(new BackupHasFailed($exception, $backupDestination ?? null));
255
            }
256
        });
257
    }
258
259
    protected function sendNotification($notification)
260
    {
261
        if ($this->sendNotifications) {
262
            event($notification);
263
        }
264
    }
265
}
266