Skip to content

Instantly share code, notes, and snippets.

@blood72
Created August 1, 2020 03:22
Show Gist options
  • Save blood72/a0f2a2fdf8ade5a4798444eda3870ff7 to your computer and use it in GitHub Desktop.
Save blood72/a0f2a2fdf8ade5a4798444eda3870ff7 to your computer and use it in GitHub Desktop.
Attachment model (personal use)
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateAttachmentsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('attachments', function (Blueprint $table) {
$table->id();
$table->nullableMorphs('attachable');
$table->string('disk', 20);
$table->string('collection')->default('default');
$table->integer('size')->unsigned()->nullable();
$table->string('mime')->nullable();
$table->string('path')->nullable();
$table->boolean('visibility')->default(true);
$table->boolean('protection')->default(false);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('attachments');
}
}
<?php
namespace App\Models\File;
use App\Models\Model;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Support\Facades\Storage;
class Attachment extends Model
{
/** @var \Illuminate\Contracts\Filesystem\Filesystem|\Illuminate\Filesystem\FilesystemAdapter */
protected Filesystem $adapter;
/**
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function attachable()
{
return $this->morphTo();
}
/**
* Check that it is in safe-mode.
*
* @return bool
*/
public function isProtected(): bool
{
return $this->protection;
}
/**
* Check a directory has no files.
*
* @param string $path
* @return bool
*/
protected function isEmptyDirectory(string $path): bool
{
return empty($this->adapter->allFiles($path));
}
/**
* Get a file directory path.
*
* @param string|null $path
* @return string
*/
public function getDirectoryPath(?string $path = null): string
{
return pathinfo($path ?: $this->path, PATHINFO_DIRNAME) . DIRECTORY_SEPARATOR;
}
/**
* Set a filesystem instance driver disk.
*
* @param string|null $disk
* @return void
*/
protected function setAdapterDisk(?string $disk): void
{
$this->adapter = Storage::disk($disk);
}
/**
* Delete associated all files.
*
* @return void
*/
protected function deleteAssociateFiles()
{
if ($this->isProtected()) {
return;
}
$this->adapter->delete($this->path);
if ($directory = $this->getDirectoryPath() and $this->isEmptyDirectory($directory)) {
$this->adapter->deleteDirectory($directory);
}
}
/**
* Attachment constructor.
*
* @param array $attributes
* @return void
*/
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->setAdapterDisk($this->disk);
}
/**
* The "booting" method of the model.
*
* @return void
*/
public static function boot()
{
parent::boot();
static::deleting(function (self $attachment) {
$attachment->deleteAssociateFiles();
});
}
}
<?php
namespace App\Traits;
use App\Models\File\Attachment;
trait HasAttachments
{
/**
* @see Attachment
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function attachments()
{
return $this->morphMany(Attachment::class, 'attachable');
}
/**
* The "booting" method of the model.
*
* @return void
*/
protected static function bootHasAttachments()
{
static::deleting(function ($model) {
if (method_exists($model, 'isForceDeleting') && ! $model->isForceDeleting()) {
return;
}
foreach ($model->attachments as $attachment) {
/** @var Attachment|\Illuminate\Database\Eloquent\Model $attachment */
if ($attachment->isProtected()) {
return;
}
$attachment->delete();
}
});
}
/**
* Define a polymorphic one-to-many relationship.
*
* @param string $related
* @param string $name
* @param string|null $type
* @param string|null $id
* @param string|null $localKey
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
abstract public function morphMany($related, $name, $type = null, $id = null, $localKey = null);
/**
* Register a deleting model event with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
abstract public static function deleting($callback);
}
<?php
namespace App\Console\Commands;
use App\Models\File\Attachment;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
class RegisterAttachments extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'attachment:register {--disk=} {--prefix=} {--collection=default} {--protection}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Register attachments';
/** @var \Illuminate\Contracts\Filesystem\Filesystem|\Illuminate\Filesystem\FilesystemAdapter */
protected $adapter;
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$this->setAdapterDisk();
$this->info('Calculating attachments..');
$paths = $this->getRegisterNeededFiles();
if (empty($amount = count($paths))) {
$this->info('No need to register.');
return -1;
}
$rows = [];
$bar = $this->output->createProgressBar($amount);
foreach ($paths as $path) {
$rows[] = $this->setAttachmentRow($path);
$bar->advance();
}
$bar->finish();
$this->info("\nRegistering attachments..");
Attachment::insert($rows);
$this->info("\nDone.");
return 0;
}
/**
* @param string $path
* @return array
*/
protected function setAttachmentRow($path)
{
static $now;
$now ??= now();
return [
'disk' => $this->getDiskName(),
'collection' => $this->option('collection'),
'size' => $this->adapter->size($path),
'mime' => $this->adapter->mimeType($path),
'path' => $path,
'visibility' => $this->adapter->getVisibility($path) !== 'private' ? true : false,
'protection' => (bool) $this->option('protection'),
'created_at' => $now,
'updated_at' => $now,
];
}
/**
* @return array
*/
protected function getRegisterNeededFiles()
{
return array_diff(
$this->adapter->allFiles($this->option('prefix')),
$this->getRegisteredAttachments(),
);
}
/**
* @return array
*/
protected function getRegisteredAttachments()
{
return Attachment::where('disk', $this->getDiskName())->pluck('path')->toArray();
}
/**
* @return string
*/
protected function getDiskName()
{
return $this->option('disk') ?: config('filesystems.default');
}
/**
* Set a filesystem instance driver disk.
*
* @return void
*/
protected function setAdapterDisk(): void
{
$this->adapter = Storage::disk($this->getDiskName());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment