Skip to content

Commit 41f63a9

Browse files
committed
Update file handling features: enhance File model, add ModelObserver, and implement FilesHandler trait
1 parent 23336e8 commit 41f63a9

File tree

6 files changed

+293
-17
lines changed

6 files changed

+293
-17
lines changed

composer.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
],
1717
"require": {
1818
"php": ">=8.0",
19-
"laravel/framework": ">=9.0",
20-
"pharaonic/laravel-uploader": "^4.0"
19+
"laravel/framework": ">=10.0",
20+
"pharaonic/laravel-uploader": "^4.0",
21+
"pharaonic/laravel-assistant": "^1.0"
2122
},
2223
"autoload": {
2324
"psr-4": {
@@ -33,4 +34,4 @@
3334
},
3435
"minimum-stability": "dev",
3536
"prefer-stable": true
36-
}
37+
}

src/Models/File.php

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,20 @@
1212
* @property string $model_type
1313
* @property string $model_id
1414
* @property-read string $url
15-
* @property-read Upload $file
15+
* @property-read Upload $upload
1616
* @property-read Upload $thumbnail
1717
* @property \Carbon\Carbon $created_at
1818
* @property \Carbon\Carbon $updated_at
1919
*/
2020
class File extends Model
2121
{
22+
/**
23+
* The Caller Model instance.
24+
*
25+
* @var Model
26+
*/
27+
public $caller;
28+
2229
/**
2330
* The attributes that are mass assignable.
2431
*
@@ -34,9 +41,9 @@ class File extends Model
3441
/**
3542
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
3643
*/
37-
public function file()
44+
public function upload()
3845
{
39-
return $this->belongsTo(Upload::class, 'upload_id');
46+
return $this->belongsTo(Upload::class);
4047
}
4148

4249
/**
@@ -48,22 +55,58 @@ public function model()
4855
}
4956

5057
/**
51-
* Get Url Directly
58+
* Get an attribute from the model.
59+
*
60+
* @param string $key
61+
* @return mixed
62+
*/
63+
public function getAttribute($key)
64+
{
65+
$value = parent::getAttribute($key);
66+
67+
if (!$value) {
68+
return $this->upload->{$key};
69+
}
70+
71+
return $value;
72+
}
73+
74+
/**
75+
* Handle dynamic method calls into the model.
5276
*
53-
* @return string
77+
* @param string $method
78+
* @param array $parameters
79+
* @return mixed
5480
*/
55-
public function getUrlAttribute()
81+
public function __call($method, $parameters)
5682
{
57-
return $this->file->url;
83+
if (in_array($method, [
84+
'size',
85+
'thumbnail',
86+
'visibility',
87+
'public',
88+
'private',
89+
'getUrlAttribute',
90+
'getTemporaryUrlAttribute',
91+
'url',
92+
'temporaryUrl'
93+
])) {
94+
return $this->upload->{$method}(...$parameters);
95+
}
96+
97+
return parent::__call($method, $parameters);
5898
}
5999

60100
/**
61-
* Get Thumbnail Object
101+
* Set the Caller Model instance.
62102
*
63-
* @return Upload|null
103+
* @param Model $model
104+
* @return void
64105
*/
65-
public function getThumbnailAttribute()
106+
public function setCaller(Model &$caller)
66107
{
67-
return $this->file->thumbnail ?? null;
108+
$this->caller = $caller;
109+
110+
return $this;
68111
}
69112
}

src/Observers/FileObserver.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,21 @@ class FileObserver
1414
*/
1515
public function deleting(File $file)
1616
{
17-
$file->file->delete();
18-
}
17+
$attribute = $file->caller->getAttributables()[$file->field];
18+
19+
if ($attribute->getOriginal() === $file) {
20+
$attribute->reset($attribute->getValue());
21+
} else {
22+
$attribute->reset(null);
23+
}
24+
25+
$file->caller->setRelation(
26+
'files',
27+
$file->caller
28+
->files
29+
->filter(fn($f) => $f->field != $file->field)
30+
);
31+
32+
$file->upload->delete();
33+
}
1934
}

src/Observers/ModelObserver.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace Pharaonic\Laravel\Files\Observers;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
7+
class ModelObserver
8+
{
9+
/**
10+
* Handle the model "saved" event.
11+
*
12+
* @param Model $model
13+
* @return void
14+
*/
15+
public function saved(Model $model)
16+
{
17+
foreach ($model->getDirtyFiles() as $attribute) {
18+
if ($attribute->isDirty() && $attribute->getOriginal()) {
19+
$attribute->getOriginal()->delete();
20+
}
21+
22+
$upload = upload(
23+
$attribute->get(),
24+
$model->getFileOptions($attribute->getName())
25+
);
26+
27+
$file = $model->files()->create([
28+
'field' => $attribute->getName(),
29+
'upload_id' => $upload->id
30+
])
31+
->setRelation('upload', $upload)
32+
->setCaller($model);
33+
34+
$attribute->reset($file);
35+
}
36+
}
37+
38+
/**
39+
* Handle the model "deleting" event.
40+
*
41+
* @param Model $model
42+
* @return void
43+
*/
44+
public function deleting(Model $model)
45+
{
46+
foreach ($model->getFilesAttributes() as $attribute) {
47+
$model->{$attribute}?->delete();
48+
}
49+
}
50+
}

src/Traits/FilesHandler.php

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?php
2+
3+
namespace Pharaonic\Laravel\Files\Traits;
4+
5+
use Pharaonic\Laravel\Files\Models\File;
6+
7+
trait FilesHandler
8+
{
9+
/**
10+
* Files data & options list.
11+
*
12+
* @var array
13+
*/
14+
protected $filesData = [];
15+
16+
/**
17+
* Has thumbnail relationship
18+
*
19+
* @var boolean
20+
*/
21+
protected $hasThumbnailRelationship = false;
22+
23+
/**
24+
* Check if files relationship has been loaded.
25+
*
26+
* @var boolean
27+
*/
28+
protected $filesRelationLoaded = false;
29+
30+
/**
31+
* Check if file attribute
32+
*
33+
* @param string $key
34+
* @return bool
35+
*/
36+
public function isFileAttribute(string $key)
37+
{
38+
return in_array($key, $this->getFilesAttributes());
39+
}
40+
41+
/**
42+
* Getting files attributes
43+
*
44+
* @return array
45+
*/
46+
public function getFilesAttributes(): array
47+
{
48+
return array_keys($this->filesData);
49+
}
50+
51+
/**
52+
* Getting file options.
53+
*
54+
* @param string $key
55+
* @return array
56+
*/
57+
public function getFileOptions(string $key)
58+
{
59+
return $this->filesData[$key] ?? [];
60+
}
61+
62+
/**
63+
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
64+
*/
65+
public function files()
66+
{
67+
return $this
68+
->morphMany(File::class, 'model')
69+
->with('upload')
70+
->when(
71+
$this->hasThumbnailRelationship,
72+
fn($q) => $q->with('upload.thumbnail')
73+
);
74+
}
75+
76+
/**
77+
* Assign the files to it's attributes.
78+
*
79+
* @return void
80+
*/
81+
public function loadFiles()
82+
{
83+
if (!$this->filesRelationLoaded) {
84+
$this->files->each(function ($file) {
85+
$this->{$file->field} = $file->setCaller($this);
86+
});
87+
88+
$this->filesRelationLoaded = true;
89+
}
90+
}
91+
92+
/**
93+
* Getting a specific file.
94+
*
95+
* @param string $key
96+
* @return \Pharaonic\Laravel\Files\Models\File|null
97+
*/
98+
public function getFile(string $key)
99+
{
100+
$this->loadFiles();
101+
102+
if ($this->isFileAttribute($key)) {
103+
return $this->attributables[$key]?->getValue()
104+
?? $this->files->where('field', $key)->first()
105+
?? null;
106+
}
107+
108+
return null;
109+
}
110+
111+
/**
112+
* Get Dirty Files
113+
*
114+
* @return array
115+
*/
116+
public function getDirtyFiles()
117+
{
118+
return array_filter(
119+
$this->getDirtyAttributables(),
120+
fn($attributable) => $this->isFileAttribute($attributable->getName())
121+
);
122+
}
123+
}

src/Traits/HasFiles.php

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,51 @@
22

33
namespace Pharaonic\Laravel\Files\Traits;
44

5+
use Pharaonic\Laravel\Assistant\Traits\Eloquent\Attributable;
6+
use Pharaonic\Laravel\Files\Observers\ModelObserver;
7+
58
trait HasFiles
69
{
7-
//
10+
use Attributable, FilesHandler;
11+
12+
/**
13+
* Initialize Has Files
14+
*
15+
* @return void
16+
*/
17+
public function initializeHasFiles()
18+
{
19+
if (property_exists($this, 'files')) {
20+
foreach ($this->files as $key => $value) {
21+
$attribute = is_numeric($key) ? $value : $key;
22+
$options = is_numeric($key) ? [] : $value;
23+
24+
$this->fillable[] = $attribute;
25+
$this->filesData[$attribute] = $options;
26+
27+
$this->addAttributable(
28+
$attribute,
29+
null,
30+
fn() => $this->getFile($attribute)
31+
);
32+
33+
// Check Thumbnail Relationship Existence
34+
if (isset($options['thumbnail'])) {
35+
$this->hasThumbnailRelationship = true;
36+
}
37+
}
38+
39+
unset($this->files);
40+
}
41+
}
42+
43+
/**
44+
* Boot the HasFiles Trait
45+
*
46+
* @return void
47+
*/
48+
public static function bootHasFiles()
49+
{
50+
static::observe(ModelObserver::class);
51+
}
852
}

0 commit comments

Comments
 (0)