Passed
Push — master ( ad6b2a...b41ca0 )
by Caen
07:45 queued 14s
created

MediaFile::make()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Hyde\Support\Filesystem;
6
7
use Hyde\Facades\Filesystem;
8
use Hyde\Hyde;
9
use Stringable;
10
use Hyde\Facades\Config;
11
use Illuminate\Support\Collection;
12
use Hyde\Framework\Exceptions\FileNotFoundException;
13
use Illuminate\Support\Str;
14
15
use function Hyde\unslash;
16
use function Hyde\path_join;
17
use function Hyde\trim_slashes;
18
use function array_merge;
19
20
/**
21
 * File abstraction for a project media file.
22
 *
23
 * All input paths are relative to the project's media source directory.
24
 */
25
class MediaFile extends ProjectFile implements Stringable
26
{
27
    /** @var array<string> The default extensions for media types */
28
    final public const EXTENSIONS = ['png', 'svg', 'jpg', 'jpeg', 'webp', 'gif', 'ico', 'css', 'js'];
29
30
    protected readonly int $length;
31
    protected readonly string $mimeType;
32
    protected readonly string $hash;
33
34
    /**
35
     * Create a new MediaFile instance.
36
     *
37
     * @param  string  $path  The file path relative to the project root or media source directory.
38
     *
39
     * @throws \Hyde\Framework\Exceptions\FileNotFoundException If the file does not exist in the media source directory.
40
     */
41
    public function __construct(string $path)
42
    {
43
        parent::__construct($this->getNormalizedPath($path));
44
    }
45
46
    /**
47
     * Cast the instance to a string which is the resolved web link to the media file.
48
     */
49
    public function __toString(): string
50
    {
51
        return $this->getLink();
52
    }
53
54
    /**
55
     * Create a media file instance for the given file.
56
     */
57
    public static function make(string $path): static
58
    {
59
        return parent::make($path);
0 ignored issues
show
Bug Best Practice introduced by
The expression return parent::make($path) returns the type Hyde\Support\Filesystem\ProjectFile which includes types incompatible with the type-hinted return Hyde\Support\Filesystem\MediaFile.
Loading history...
60
    }
61
62
    /**
63
     * Get or create a media file instance from the HydeKernel for the given file.
64
     *
65
     * @throws \Hyde\Framework\Exceptions\FileNotFoundException If the file does not exist in the `_media` source directory.
66
     */
67
    public static function get(string $path): MediaFile
68
    {
69
        return Hyde::assets()->get($path) ?? static::make($path);
0 ignored issues
show
Bug introduced by
The method assets() does not exist on Hyde\Hyde. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

69
        return Hyde::/** @scrutinizer ignore-call */ assets()->get($path) ?? static::make($path);
Loading history...
70
    }
71
72
    /**
73
     * Get a collection of all media files, parsed into `MediaFile` instances, keyed by the filenames relative to the `_media/` directory.
74
     *
75
     * @return \Illuminate\Support\Collection<string, \Hyde\Support\Filesystem\MediaFile>
76
     */
77
    public static function all(): Collection
78
    {
79
        return Hyde::assets();
80
    }
81
82
    /**
83
     * Get an array of media asset filenames relative to the `_media/` directory.
84
     *
85
     * @return array<int, string> {@example `['app.css', 'images/logo.svg']`}
86
     */
87
    public static function files(): array
88
    {
89
        return static::all()->keys()->all();
90
    }
91
92
    /**
93
     * Get the absolute path to the media source directory, or a file within it.
94
     */
95
    public static function sourcePath(string $path = ''): string
96
    {
97
        if (empty($path)) {
98
            return Hyde::path(Hyde::getMediaDirectory());
0 ignored issues
show
Bug introduced by
The method getMediaDirectory() does not exist on Hyde\Hyde. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

98
            return Hyde::path(Hyde::/** @scrutinizer ignore-call */ getMediaDirectory());
Loading history...
Bug introduced by
The method path() does not exist on Hyde\Hyde. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

98
            return Hyde::/** @scrutinizer ignore-call */ path(Hyde::getMediaDirectory());
Loading history...
99
        }
100
101
        return Hyde::path(path_join(Hyde::getMediaDirectory(), unslash($path)));
102
    }
103
104
    /**
105
     * Get the absolute path to the compiled site's media directory, or a file within it.
106
     */
107
    public static function outputPath(string $path = ''): string
108
    {
109
        if (empty($path)) {
110
            return Hyde::sitePath(Hyde::getMediaOutputDirectory());
0 ignored issues
show
Bug introduced by
The method getMediaOutputDirectory() does not exist on Hyde\Hyde. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

110
            return Hyde::sitePath(Hyde::/** @scrutinizer ignore-call */ getMediaOutputDirectory());
Loading history...
Bug introduced by
The method sitePath() does not exist on Hyde\Hyde. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

110
            return Hyde::/** @scrutinizer ignore-call */ sitePath(Hyde::getMediaOutputDirectory());
Loading history...
111
        }
112
113
        return Hyde::sitePath(path_join(Hyde::getMediaOutputDirectory(), unslash($path)));
114
    }
115
116
    /**
117
     * Get the path to the media file relative to the media directory.
118
     */
119
    public function getIdentifier(): string
120
    {
121
        return Str::after($this->getPath(), Hyde::getMediaDirectory().'/');
122
    }
123
124
    /**
125
     * Get a relative web link to the media file.
126
     */
127
    public function getLink(): string
128
    {
129
        $name = $this->getIdentifier();
130
131
        $name = Str::start($name, Hyde::getMediaOutputDirectory().'/');
132
133
        if (Hyde::hasSiteUrl()) {
0 ignored issues
show
Bug introduced by
The method hasSiteUrl() does not exist on Hyde\Hyde. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

133
        if (Hyde::/** @scrutinizer ignore-call */ hasSiteUrl()) {
Loading history...
134
            return Hyde::url($name).$this->getCacheBustKey();
0 ignored issues
show
Bug introduced by
The method url() does not exist on Hyde\Hyde. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

134
            return Hyde::/** @scrutinizer ignore-call */ url($name).$this->getCacheBustKey();
Loading history...
135
        }
136
137
        return Hyde::relativeLink($name).$this->getCacheBustKey();
0 ignored issues
show
Bug introduced by
The method relativeLink() does not exist on Hyde\Hyde. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

137
        return Hyde::/** @scrutinizer ignore-call */ relativeLink($name).$this->getCacheBustKey();
Loading history...
138
    }
139
140
    /**
141
     * Get the absolute path to the media file in the compiled site.
142
     */
143
    public function getOutputPath(): string
144
    {
145
        return static::outputPath($this->getIdentifier());
146
    }
147
148
    /**
149
     * Get the content length of the file in bytes.
150
     */
151
    public function getLength(): int
152
    {
153
        $this->ensureInstanceIsBooted('length');
154
155
        return $this->length;
156
    }
157
158
    /**
159
     * Get the MIME type of the file.
160
     */
161
    public function getMimeType(): string
162
    {
163
        $this->ensureInstanceIsBooted('mimeType');
164
165
        return $this->mimeType;
166
    }
167
168
    /**
169
     * Get the CRC32 hash of the file.
170
     */
171
    public function getHash(): string
172
    {
173
        $this->ensureInstanceIsBooted('hash');
174
175
        return $this->hash;
176
    }
177
178
    /**
179
     * Get the file information as an array.
180
     *
181
     * @return array{name: string, path: string, length: int, mimeType: string, hash: string}
182
     */
183
    public function toArray(): array
184
    {
185
        return array_merge(parent::toArray(), [
186
            'length' => $this->getLength(),
187
            'mimeType' => $this->getMimeType(),
188
            'hash' => $this->getHash(),
189
        ]);
190
    }
191
192
    protected function getCacheBustKey(): string
193
    {
194
        return Config::getBool('hyde.cache_busting', true)
195
            ? '?v='.$this->getHash()
196
            : '';
197
    }
198
199
    protected function getNormalizedPath(string $path): string
200
    {
201
        // Ensure we are working with a relative project path
202
        $path = Hyde::pathToRelative($path);
0 ignored issues
show
Bug introduced by
The method pathToRelative() does not exist on Hyde\Hyde. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

202
        /** @scrutinizer ignore-call */ 
203
        $path = Hyde::pathToRelative($path);
Loading history...
203
204
        // Normalize paths using output directory to have source directory prefix
205
        if (str_starts_with($path, Hyde::getMediaOutputDirectory()) && str_starts_with(Hyde::getMediaDirectory(), '_')) {
206
            $path = '_'.$path;
207
        }
208
209
        // Normalize the path to include the media directory
210
        $path = static::sourcePath(trim_slashes(Str::after($path, Hyde::getMediaDirectory())));
211
212
        // Since assets need to exist on disk in order to be copied to the built site files we validate that the file is real here.
213
        if (Filesystem::missing($path)) {
214
            throw new FileNotFoundException($path, appendAfterPath: ' when trying to resolve a media asset.');
215
        }
216
217
        return $path;
218
    }
219
220
    protected function findLength(): int
221
    {
222
        return Filesystem::size($this->getPath());
223
    }
224
225
    protected function findMimeType(): string
226
    {
227
        return Filesystem::findMimeType($this->getPath());
228
    }
229
230
    protected function findHash(): string
231
    {
232
        return Filesystem::hash($this->getPath(), 'crc32');
233
    }
234
235
    protected function ensureInstanceIsBooted(string $property): void
236
    {
237
        if (! isset($this->$property)) {
238
            $this->boot();
239
        }
240
    }
241
242
    protected function boot(): void
243
    {
244
        $this->length = $this->findLength();
0 ignored issues
show
Bug introduced by
The property length is declared read-only in Hyde\Support\Filesystem\MediaFile.
Loading history...
245
        $this->mimeType = $this->findMimeType();
0 ignored issues
show
Bug introduced by
The property mimeType is declared read-only in Hyde\Support\Filesystem\MediaFile.
Loading history...
246
        $this->hash = $this->findHash();
0 ignored issues
show
Bug introduced by
The property hash is declared read-only in Hyde\Support\Filesystem\MediaFile.
Loading history...
247
    }
248
}
249