1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Spatie\Backup\Commands; |
4
|
|
|
|
5
|
|
|
use Illuminate\Console\Command; |
6
|
|
|
use Illuminate\Support\Facades\Storage; |
7
|
|
|
use Symfony\Component\Console\Input\InputOption; |
8
|
|
|
use ZipArchive; |
9
|
|
|
|
10
|
|
|
class BackupCommand extends Command |
11
|
|
|
{ |
12
|
|
|
/** |
13
|
|
|
* The console command name. |
14
|
|
|
* |
15
|
|
|
* @var string |
16
|
|
|
*/ |
17
|
|
|
protected $name = 'backup:run'; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* The console command description. |
21
|
|
|
* |
22
|
|
|
* @var string |
23
|
|
|
*/ |
24
|
|
|
protected $description = 'Run the backup'; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* Files that will be remove at the end of the command. |
28
|
|
|
* |
29
|
|
|
* @var array |
30
|
|
|
*/ |
31
|
|
|
protected $temporaryFiles = []; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* Execute the console command. |
35
|
|
|
* |
36
|
|
|
* @return bool |
37
|
|
|
*/ |
38
|
|
|
public function fire() |
39
|
|
|
{ |
40
|
|
|
$this->guardAgainstInvalidOptions(); |
41
|
|
|
|
42
|
|
|
$this->info('Start backing up'); |
43
|
|
|
|
44
|
|
|
$files = $this->getAllFilesToBeBackedUp(); |
45
|
|
|
|
46
|
|
|
if (count($files) == 0) { |
47
|
|
|
$this->info('Nothing to backup'); |
48
|
|
|
|
49
|
|
|
return true; |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
$backupZipFile = $this->createZip($files); |
53
|
|
|
|
54
|
|
|
$this->temporaryFiles[] = $backupZipFile; |
55
|
|
|
|
56
|
|
|
if (filesize($backupZipFile) == 0) { |
57
|
|
|
$this->warn('The zipfile that will be backupped has a filesize of zero.'); |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
foreach ($this->getTargetFileSystems() as $fileSystem) { |
61
|
|
|
$this->copyFileToFileSystem($backupZipFile, $fileSystem); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
$this->removeTemporaryFiles(); |
65
|
|
|
|
66
|
|
|
$this->info('Backup successfully completed'); |
67
|
|
|
|
68
|
|
|
return true; |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* Return an array with path to files that should be backed up. |
73
|
|
|
* |
74
|
|
|
* @return array |
75
|
|
|
*/ |
76
|
|
|
protected function getAllFilesToBeBackedUp() |
77
|
|
|
{ |
78
|
|
|
$files = []; |
79
|
|
|
|
80
|
|
|
if ((!$this->option('only-files')) && config('laravel-backup.source.backup-db')) { |
81
|
|
|
$files[] = ['realFile' => $this->getDatabaseDump($files), 'fileInZip' => 'dump.sql']; |
|
|
|
|
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
if (!$this->option('only-db')) { |
85
|
|
|
$this->comment('Determining which files should be backed up...'); |
86
|
|
|
$fileBackupHandler = app()->make('Spatie\Backup\BackupHandlers\Files\FilesBackupHandler') |
87
|
|
|
->setIncludedFiles(config('laravel-backup.source.files.include')) |
88
|
|
|
->setExcludedFiles(config('laravel-backup.source.files.exclude')); |
89
|
|
|
foreach ($fileBackupHandler->getFilesToBeBackedUp() as $file) { |
90
|
|
|
$files[] = ['realFile' => $file, 'fileInZip' => 'files/'.$file]; |
91
|
|
|
} |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
return $files; |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* Create a zip for the given files. |
99
|
|
|
* |
100
|
|
|
* @param $files |
101
|
|
|
* |
102
|
|
|
* @return string |
103
|
|
|
*/ |
104
|
|
|
protected function createZip($files) |
105
|
|
|
{ |
106
|
|
|
$this->comment('Start zipping '.count($files).' files...'); |
107
|
|
|
|
108
|
|
|
$tempZipFile = tempnam(sys_get_temp_dir(), 'laravel-backup-zip'); |
109
|
|
|
|
110
|
|
|
$zip = new ZipArchive(); |
111
|
|
|
$zip->open($tempZipFile, ZipArchive::CREATE); |
112
|
|
|
|
113
|
|
|
foreach ($files as $file) { |
114
|
|
|
if (file_exists($file['realFile'])) { |
115
|
|
|
$zip->addFile($file['realFile'], $file['fileInZip']); |
116
|
|
|
} |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
$zip->close(); |
120
|
|
|
|
121
|
|
|
$this->comment('Zip created!'); |
122
|
|
|
|
123
|
|
|
return $tempZipFile; |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* Copy the given file on the given disk to the given destination. |
128
|
|
|
* |
129
|
|
|
* @param string $file |
130
|
|
|
* @param \Illuminate\Contracts\Filesystem\Filesystem $disk |
131
|
|
|
* @param string $destination |
132
|
|
|
* @param bool $addIgnoreFile |
133
|
|
|
*/ |
134
|
|
|
protected function copyFile($file, $disk, $destination, $addIgnoreFile = false) |
135
|
|
|
{ |
136
|
|
|
$destinationDirectory = dirname($destination); |
137
|
|
|
|
138
|
|
|
$disk->makeDirectory($destinationDirectory); |
139
|
|
|
|
140
|
|
|
if ($addIgnoreFile) { |
141
|
|
|
$this->writeIgnoreFile($disk, $destinationDirectory); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/* |
145
|
|
|
* The file could be quite large. Use a stream to copy it |
146
|
|
|
* to the target disk to avoid memory problems |
147
|
|
|
*/ |
148
|
|
|
$disk->getDriver()->writeStream($destination, fopen($file, 'r+')); |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* Get the filesystems to where the database should be dumped. |
153
|
|
|
* |
154
|
|
|
* @return array |
155
|
|
|
*/ |
156
|
|
|
protected function getTargetFileSystems() |
157
|
|
|
{ |
158
|
|
|
$fileSystems = config('laravel-backup.destination.filesystem'); |
159
|
|
|
|
160
|
|
|
if (is_array($fileSystems)) { |
161
|
|
|
return $fileSystems; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
return [$fileSystems]; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* Write an ignore-file on the given disk in the given directory. |
169
|
|
|
* |
170
|
|
|
* @param \Illuminate\Contracts\Filesystem\Filesystem $disk |
171
|
|
|
* @param string $dumpDirectory |
172
|
|
|
*/ |
173
|
|
|
protected function writeIgnoreFile($disk, $dumpDirectory) |
174
|
|
|
{ |
175
|
|
|
$gitIgnoreContents = '*'.PHP_EOL.'!.gitignore'; |
176
|
|
|
$disk->put($dumpDirectory.'/.gitignore', $gitIgnoreContents); |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* Determine the name of the zip that contains the backup. |
181
|
|
|
* |
182
|
|
|
* @return string |
183
|
|
|
*/ |
184
|
|
|
protected function getBackupDestinationFileName() |
185
|
|
|
{ |
186
|
|
|
$backupDirectory = config('laravel-backup.destination.path'); |
187
|
|
|
$backupFilename = $this->getPrefix().date('YmdHis').$this->getSuffix().'.zip'; |
188
|
|
|
|
189
|
|
|
$destination = $backupDirectory; |
190
|
|
|
|
191
|
|
|
if ($destination !='') { |
192
|
|
|
$destination .= '/'; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
$destination .= $backupFilename; |
196
|
|
|
|
197
|
|
|
return $destination; |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* Get the prefix to be used in the filename of the backup file. |
202
|
|
|
* |
203
|
|
|
* @return string |
204
|
|
|
*/ |
205
|
|
|
public function getPrefix() |
206
|
|
|
{ |
207
|
|
|
if ($this->option('prefix') != '') { |
208
|
|
|
return $this->option('prefix'); |
|
|
|
|
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
return config('laravel-backup.destination.prefix'); |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* Get the suffix to be used in the filename of the backup file. |
216
|
|
|
* |
217
|
|
|
* @return string |
218
|
|
|
*/ |
219
|
|
|
public function getSuffix() |
220
|
|
|
{ |
221
|
|
|
if ($this->option('suffix') != '') { |
222
|
|
|
return $this->option('suffix'); |
|
|
|
|
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
return config('laravel-backup.destination.suffix'); |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* Copy the given file to given filesystem. |
230
|
|
|
* |
231
|
|
|
* @param string $file |
232
|
|
|
* @param $fileSystem |
233
|
|
|
*/ |
234
|
|
|
public function copyFileToFileSystem($file, $fileSystem) |
235
|
|
|
{ |
236
|
|
|
$this->comment('Start uploading backup to '.$fileSystem.'-filesystem...'); |
237
|
|
|
|
238
|
|
|
$disk = Storage::disk($fileSystem); |
239
|
|
|
|
240
|
|
|
$backupFilename = $this->getBackupDestinationFileName(); |
241
|
|
|
|
242
|
|
|
$this->copyFile($file, $disk, $backupFilename, $fileSystem == 'local'); |
243
|
|
|
|
244
|
|
|
$this->comment('Backup stored on '.$fileSystem.'-filesystem in file "'.$backupFilename.'"'); |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* Get the console command options. |
249
|
|
|
* |
250
|
|
|
* @return array |
251
|
|
|
*/ |
252
|
|
|
protected function getOptions() |
253
|
|
|
{ |
254
|
|
|
return [ |
255
|
|
|
['only-db', null, InputOption::VALUE_NONE, 'Only backup the database.'], |
256
|
|
|
['only-files', null, InputOption::VALUE_NONE, 'Only backup the files.'], |
257
|
|
|
['prefix', null, InputOption::VALUE_REQUIRED, 'The name of the zip file will get prefixed with this string.'], |
258
|
|
|
['suffix', null, InputOption::VALUE_REQUIRED, 'The name of the zip file will get suffixed with this string.'], |
259
|
|
|
]; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
/** |
263
|
|
|
* Get a dump of the db. |
264
|
|
|
* |
265
|
|
|
* @return string |
266
|
|
|
* |
267
|
|
|
* @throws \Exception |
268
|
|
|
*/ |
269
|
|
|
protected function getDatabaseDump() |
270
|
|
|
{ |
271
|
|
|
$databaseBackupHandler = app()->make('Spatie\Backup\BackupHandlers\Database\DatabaseBackupHandler'); |
272
|
|
|
|
273
|
|
|
$filesToBeBackedUp = $databaseBackupHandler->getFilesToBeBackedUp(); |
274
|
|
|
|
275
|
|
|
if (count($filesToBeBackedUp) != 1) { |
276
|
|
|
throw new \Exception('could not backup db'); |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
$this->comment('Database dumped'); |
280
|
|
|
|
281
|
|
|
$dbDumpFile = $filesToBeBackedUp[0]; |
282
|
|
|
|
283
|
|
|
$this->temporaryFiles[] = $dbDumpFile; |
284
|
|
|
|
285
|
|
|
return $dbDumpFile; |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
/** |
289
|
|
|
* @throws \Exception |
290
|
|
|
*/ |
291
|
|
|
protected function guardAgainstInvalidOptions() |
292
|
|
|
{ |
293
|
|
|
if ($this->option('only-db') && $this->option('only-files')) { |
294
|
|
|
throw new \Exception('cannot use only-db and only-files together'); |
295
|
|
|
} |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
/** |
299
|
|
|
* Remove temporary files |
300
|
|
|
*/ |
301
|
|
|
protected function removeTemporaryFiles() |
302
|
|
|
{ |
303
|
|
|
foreach ($this->temporaryFiles as $temporaryFile) { |
304
|
|
|
if (file_exists($temporaryFile)) { |
305
|
|
|
unlink($temporaryFile); |
306
|
|
|
} |
307
|
|
|
} |
308
|
|
|
} |
309
|
|
|
} |
310
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.