-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathFileAttachmentStore.php
More file actions
138 lines (111 loc) · 4.09 KB
/
Copy pathFileAttachmentStore.php
File metadata and controls
138 lines (111 loc) · 4.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
<?php
declare(strict_types=1);
namespace Micilini\PhpSockets\Storage\File;
use DateTimeImmutable;
use Micilini\PhpSockets\Chat\Attachment;
use Micilini\PhpSockets\Contracts\AttachmentStoreInterface;
use Micilini\PhpSockets\Exceptions\StorageException;
final readonly class FileAttachmentStore implements AttachmentStoreInterface
{
public function __construct(private string $basePath)
{
$this->ensureDirectory($this->basePath);
}
public function save(Attachment $attachment): Attachment
{
$metadataPath = $this->metadataPath($attachment->id);
$metadata = json_encode($attachment->publicPayload(), JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
if (file_put_contents($metadataPath, $metadata, LOCK_EX) === false) {
throw new StorageException('Failed to save attachment metadata.');
}
return $attachment;
}
/**
* @param array<string, mixed> $metadata
*/
public function saveContent(
string $messageId,
string $fileName,
string $mimeType,
string $content,
array $metadata = [],
): Attachment {
$attachment = Attachment::new(
messageId: $messageId,
fileName: $fileName,
mimeType: $mimeType,
sizeBytes: strlen($content),
path: '',
metadata: $metadata,
);
$extension = pathinfo($fileName, PATHINFO_EXTENSION);
$physicalName = $attachment->id . ($extension !== '' ? '.' . $extension : '');
$filePath = $this->basePath . DIRECTORY_SEPARATOR . $physicalName;
if (file_put_contents($filePath, $content, LOCK_EX) === false) {
throw new StorageException('Failed to save attachment content.');
}
$attachment = new Attachment(
id: $attachment->id,
messageId: $attachment->messageId,
fileName: $attachment->fileName,
mimeType: $attachment->mimeType,
sizeBytes: $attachment->sizeBytes,
path: $filePath,
createdAt: $attachment->createdAt,
metadata: $attachment->metadata,
);
return $this->save($attachment);
}
public function find(string $attachmentId): ?Attachment
{
$metadataPath = $this->metadataPath($attachmentId);
if (!is_file($metadataPath)) {
return null;
}
$json = file_get_contents($metadataPath);
if (!is_string($json)) {
return null;
}
$payload = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
if (!is_array($payload)) {
return null;
}
$metadata = $payload['metadata'] ?? [];
if (!is_array($metadata)) {
$metadata = [];
}
/** @var array<string, mixed> $metadata */
return new Attachment(
id: (string) $payload['id'],
messageId: (string) $payload['messageId'],
fileName: (string) $payload['fileName'],
mimeType: (string) $payload['mimeType'],
sizeBytes: (int) $payload['sizeBytes'],
path: (string) $payload['path'],
createdAt: new DateTimeImmutable((string) $payload['createdAt']),
metadata: $metadata,
);
}
private function metadataPath(string $attachmentId): string
{
return $this->basePath . DIRECTORY_SEPARATOR . $attachmentId . '.json';
}
private function ensureDirectory(string $path): void
{
if (is_dir($path)) {
if (!is_writable($path)) {
throw new StorageException("Attachment directory is not writable: {$path}");
}
return;
}
if (file_exists($path)) {
throw new StorageException("Attachment path exists but is not a directory: {$path}");
}
if (!@mkdir($path, 0775, true) && !is_dir($path)) {
throw new StorageException("Failed to create attachment directory: {$path}");
}
if (!is_writable($path)) {
throw new StorageException("Attachment directory is not writable: {$path}");
}
}
}