@artilingo/artiframe-cli 1.4.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core-stubs/bin/SystemMethod.php +600 -521
- package/core-stubs/bin/ViewMethod.php +591 -393
- package/core-stubs/docs/de.html +4347 -4390
- package/core-stubs/docs/en.html +4347 -4389
- package/core-stubs/docs/es.html +4347 -4390
- package/core-stubs/docs/fr.html +4347 -4389
- package/core-stubs/docs/tr.html +138 -109
- package/package.json +1 -1
- package/src/App.php +8 -0
- package/src/Commands/AddCommand.php +1 -1
- package/src/Commands/ListCommand.php +2 -1
- package/src/Commands/MakeApiCommand.php +1 -1
- package/src/Commands/MakeClassCommand.php +1 -1
- package/src/Commands/MakeViewCommand.php +1 -1
- package/src/Commands/NewProjectCommand.php +37 -25
- package/src/Commands/RemoveCommand.php +21 -10
- package/src/Commands/ServeCommand.php +29 -9
- package/src/Commands/TableCommand.php +2 -1
- package/src/Commands/UpgradeCommand.php +209 -0
- package/src/Commands/VersionCommand.php +2 -1
- package/src/Lang/de.php +7 -1
- package/src/Lang/en.php +6 -0
- package/src/Lang/es.php +7 -1
- package/src/Lang/fr.php +6 -0
- package/src/Lang/tr.php +7 -1
- package/src/Services/Safeguard.php +25 -0
- package/stubs/service/image.stub +334 -334
- package/stubs/service/iyzico.stub +637 -637
- package/stubs/service/jwt.stub +312 -312
- package/stubs/service/phpmailer.stub +143 -143
- package/stubs/service/pusher.stub +232 -232
- package/stubs/service/qrcode.stub +169 -169
- package/stubs/service/redis.stub +361 -361
- package/stubs/service/s3.stub +377 -377
- package/stubs/service/sentry.stub +76 -106
- package/stubs/service/stripe.stub +526 -526
- package/stubs/service/twilio.stub +361 -361
- package/core-stubs/app/R2Manager.php +0 -130
package/stubs/service/s3.stub
CHANGED
|
@@ -1,377 +1,377 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
namespace Service;
|
|
4
|
-
|
|
5
|
-
use Aws\S3\S3Client;
|
|
6
|
-
use Aws\S3\Exception\S3Exception;
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* S3Storage — Wrapper for AWS S3 SDK.
|
|
10
|
-
*
|
|
11
|
-
* Provides simple methods for uploading, downloading, deleting, listing,
|
|
12
|
-
* and managing files on Amazon S3, MinIO, DigitalOcean Spaces, or any
|
|
13
|
-
* S3-compatible object storage.
|
|
14
|
-
*
|
|
15
|
-
* Required ENV variables:
|
|
16
|
-
* AWS_ACCESS_KEY_ID — IAM access key
|
|
17
|
-
* AWS_SECRET_ACCESS_KEY — IAM secret key
|
|
18
|
-
* AWS_DEFAULT_REGION — e.g. "eu-central-1"
|
|
19
|
-
* AWS_BUCKET — Default bucket name
|
|
20
|
-
*
|
|
21
|
-
* Optional ENV variables:
|
|
22
|
-
* AWS_ENDPOINT — Custom endpoint URL (MinIO, Spaces, etc.)
|
|
23
|
-
*
|
|
24
|
-
* @package Service
|
|
25
|
-
*/
|
|
26
|
-
class S3Storage
|
|
27
|
-
{
|
|
28
|
-
/** @var S3Client AWS S3 client instance */
|
|
29
|
-
private S3Client $client;
|
|
30
|
-
|
|
31
|
-
/** @var string Default bucket name */
|
|
32
|
-
private string $bucket;
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Create the S3 client from environment variables.
|
|
36
|
-
*
|
|
37
|
-
* When AWS_ENDPOINT is set the client connects to a custom
|
|
38
|
-
* S3-compatible service (MinIO, DigitalOcean Spaces, Cloudflare R2, etc.).
|
|
39
|
-
*/
|
|
40
|
-
public function __construct()
|
|
41
|
-
{
|
|
42
|
-
$region = $_ENV['AWS_DEFAULT_REGION'] ?? 'us-east-1';
|
|
43
|
-
$endpoint = $_ENV['AWS_ENDPOINT'] ?? null;
|
|
44
|
-
|
|
45
|
-
$config = [
|
|
46
|
-
'version' => 'latest',
|
|
47
|
-
'region' => $region,
|
|
48
|
-
'credentials' => [
|
|
49
|
-
'key' => $_ENV['AWS_ACCESS_KEY_ID'],
|
|
50
|
-
'secret' => $_ENV['AWS_SECRET_ACCESS_KEY'],
|
|
51
|
-
],
|
|
52
|
-
];
|
|
53
|
-
|
|
54
|
-
// Custom endpoint for S3-compatible services
|
|
55
|
-
if ($endpoint) {
|
|
56
|
-
$config['endpoint'] = $endpoint;
|
|
57
|
-
$config['use_path_style_endpoint'] = true;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
$this->client = new S3Client($config);
|
|
61
|
-
$this->bucket = $_ENV['AWS_BUCKET'];
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ---------------------------------------------------------------
|
|
65
|
-
// Upload
|
|
66
|
-
// ---------------------------------------------------------------
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Upload a local file to S3.
|
|
70
|
-
*
|
|
71
|
-
* @param string $localPath Absolute path to the local file.
|
|
72
|
-
* @param string $remotePath Object key (path) in the bucket.
|
|
73
|
-
* @param string $acl Canned ACL: private, public-read, etc.
|
|
74
|
-
* @return array{status: bool, url: string|null, error: string|null}
|
|
75
|
-
*/
|
|
76
|
-
public function upload(string $localPath, string $remotePath, string $acl = 'private'): array
|
|
77
|
-
{
|
|
78
|
-
try {
|
|
79
|
-
$result = $this->client->putObject([
|
|
80
|
-
'Bucket' => $this->bucket,
|
|
81
|
-
'Key' => $remotePath,
|
|
82
|
-
'SourceFile' => $localPath,
|
|
83
|
-
'ACL' => $acl,
|
|
84
|
-
'ContentType' => mime_content_type($localPath) ?: 'application/octet-stream',
|
|
85
|
-
]);
|
|
86
|
-
|
|
87
|
-
return [
|
|
88
|
-
'status' => true,
|
|
89
|
-
'url' => $result['ObjectURL'] ?? $this->url($remotePath),
|
|
90
|
-
'error' => null,
|
|
91
|
-
];
|
|
92
|
-
} catch (S3Exception $e) {
|
|
93
|
-
error_log('[S3Storage::upload] ' . $e->getMessage());
|
|
94
|
-
return ['status' => false, 'url' => null, 'error' => $e->getMessage()];
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// ---------------------------------------------------------------
|
|
99
|
-
// Upload from String
|
|
100
|
-
// ---------------------------------------------------------------
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Upload content from a string directly to S3.
|
|
104
|
-
*
|
|
105
|
-
* Useful for storing generated data (JSON, CSV, HTML) without
|
|
106
|
-
* writing a temporary file to disk first.
|
|
107
|
-
*
|
|
108
|
-
* @param string $content The raw string content to store.
|
|
109
|
-
* @param string $remotePath Object key (path) in the bucket.
|
|
110
|
-
* @param string $contentType MIME type, e.g. "application/json".
|
|
111
|
-
* @param string $acl Canned ACL: private, public-read, etc.
|
|
112
|
-
* @return array{status: bool, url: string|null, error: string|null}
|
|
113
|
-
*/
|
|
114
|
-
public function uploadFromString(string $content, string $remotePath, string $contentType, string $acl = 'private'): array
|
|
115
|
-
{
|
|
116
|
-
try {
|
|
117
|
-
$result = $this->client->putObject([
|
|
118
|
-
'Bucket' => $this->bucket,
|
|
119
|
-
'Key' => $remotePath,
|
|
120
|
-
'Body' => $content,
|
|
121
|
-
'ACL' => $acl,
|
|
122
|
-
'ContentType' => $contentType,
|
|
123
|
-
]);
|
|
124
|
-
|
|
125
|
-
return [
|
|
126
|
-
'status' => true,
|
|
127
|
-
'url' => $result['ObjectURL'] ?? $this->url($remotePath),
|
|
128
|
-
'error' => null,
|
|
129
|
-
];
|
|
130
|
-
} catch (S3Exception $e) {
|
|
131
|
-
error_log('[S3Storage::uploadFromString] ' . $e->getMessage());
|
|
132
|
-
return ['status' => false, 'url' => null, 'error' => $e->getMessage()];
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// ---------------------------------------------------------------
|
|
137
|
-
// Download
|
|
138
|
-
// ---------------------------------------------------------------
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Download a file from S3 to a local path.
|
|
142
|
-
*
|
|
143
|
-
* @param string $remotePath Object key in the bucket.
|
|
144
|
-
* @param string $localPath Absolute local destination path.
|
|
145
|
-
* @return bool True on success.
|
|
146
|
-
*/
|
|
147
|
-
public function download(string $remotePath, string $localPath): bool
|
|
148
|
-
{
|
|
149
|
-
try {
|
|
150
|
-
$this->client->getObject([
|
|
151
|
-
'Bucket' => $this->bucket,
|
|
152
|
-
'Key' => $remotePath,
|
|
153
|
-
'SaveAs' => $localPath,
|
|
154
|
-
]);
|
|
155
|
-
|
|
156
|
-
return true;
|
|
157
|
-
} catch (S3Exception $e) {
|
|
158
|
-
error_log('[S3Storage::download] ' . $e->getMessage());
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// ---------------------------------------------------------------
|
|
164
|
-
// Delete
|
|
165
|
-
// ---------------------------------------------------------------
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Delete a single object from S3.
|
|
169
|
-
*
|
|
170
|
-
* @param string $remotePath Object key to delete.
|
|
171
|
-
* @return bool True on success.
|
|
172
|
-
*/
|
|
173
|
-
public function delete(string $remotePath): bool
|
|
174
|
-
{
|
|
175
|
-
try {
|
|
176
|
-
$this->client->deleteObject([
|
|
177
|
-
'Bucket' => $this->bucket,
|
|
178
|
-
'Key' => $remotePath,
|
|
179
|
-
]);
|
|
180
|
-
|
|
181
|
-
return true;
|
|
182
|
-
} catch (S3Exception $e) {
|
|
183
|
-
error_log('[S3Storage::delete] ' . $e->getMessage());
|
|
184
|
-
return false;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Delete multiple objects from S3 in a single request.
|
|
190
|
-
*
|
|
191
|
-
* @param array<string> $remotePaths List of object keys to delete.
|
|
192
|
-
* @return bool True when all objects are deleted successfully.
|
|
193
|
-
*/
|
|
194
|
-
public function deleteMany(array $remotePaths): bool
|
|
195
|
-
{
|
|
196
|
-
try {
|
|
197
|
-
$objects = array_map(fn(string $key) => ['Key' => $key], $remotePaths);
|
|
198
|
-
|
|
199
|
-
$this->client->deleteObjects([
|
|
200
|
-
'Bucket' => $this->bucket,
|
|
201
|
-
'Delete' => [
|
|
202
|
-
'Objects' => $objects,
|
|
203
|
-
'Quiet' => true,
|
|
204
|
-
],
|
|
205
|
-
]);
|
|
206
|
-
|
|
207
|
-
return true;
|
|
208
|
-
} catch (S3Exception $e) {
|
|
209
|
-
error_log('[S3Storage::deleteMany] ' . $e->getMessage());
|
|
210
|
-
return false;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// ---------------------------------------------------------------
|
|
215
|
-
// URL helpers
|
|
216
|
-
// ---------------------------------------------------------------
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Get the public URL for an object.
|
|
220
|
-
*
|
|
221
|
-
* @param string $remotePath Object key in the bucket.
|
|
222
|
-
* @return string The public URL.
|
|
223
|
-
*/
|
|
224
|
-
public function url(string $remotePath): string
|
|
225
|
-
{
|
|
226
|
-
return $this->client->getObjectUrl($this->bucket, $remotePath);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Generate a pre-signed (temporary) URL for private objects.
|
|
231
|
-
*
|
|
232
|
-
* @param string $remotePath Object key in the bucket.
|
|
233
|
-
* @param int $minutes Link validity in minutes (default 60).
|
|
234
|
-
* @return string Pre-signed URL.
|
|
235
|
-
*/
|
|
236
|
-
public function signedUrl(string $remotePath, int $minutes = 60): string
|
|
237
|
-
{
|
|
238
|
-
$command = $this->client->getCommand('GetObject', [
|
|
239
|
-
'Bucket' => $this->bucket,
|
|
240
|
-
'Key' => $remotePath,
|
|
241
|
-
]);
|
|
242
|
-
|
|
243
|
-
$request = $this->client->createPresignedRequest($command, "+{$minutes} minutes");
|
|
244
|
-
|
|
245
|
-
return (string) $request->getUri();
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// ---------------------------------------------------------------
|
|
249
|
-
// Existence check
|
|
250
|
-
// ---------------------------------------------------------------
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Check whether an object exists in the bucket.
|
|
254
|
-
*
|
|
255
|
-
* @param string $remotePath Object key to check.
|
|
256
|
-
* @return bool True if the object exists.
|
|
257
|
-
*/
|
|
258
|
-
public function exists(string $remotePath): bool
|
|
259
|
-
{
|
|
260
|
-
try {
|
|
261
|
-
return $this->client->doesObjectExistV2($this->bucket, $remotePath);
|
|
262
|
-
} catch (S3Exception $e) {
|
|
263
|
-
error_log('[S3Storage::exists] ' . $e->getMessage());
|
|
264
|
-
return false;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// ---------------------------------------------------------------
|
|
269
|
-
// List objects
|
|
270
|
-
// ---------------------------------------------------------------
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* List objects under a given prefix (folder).
|
|
274
|
-
*
|
|
275
|
-
* Returns an array of associative arrays with key, size, and
|
|
276
|
-
* last_modified for each object.
|
|
277
|
-
*
|
|
278
|
-
* @param string $prefix Key prefix to filter by (e.g. "uploads/").
|
|
279
|
-
* @return array<int, array{key: string, size: int, last_modified: string}>
|
|
280
|
-
*/
|
|
281
|
-
public function list(string $prefix = ''): array
|
|
282
|
-
{
|
|
283
|
-
try {
|
|
284
|
-
$results = [];
|
|
285
|
-
$params = [
|
|
286
|
-
'Bucket' => $this->bucket,
|
|
287
|
-
'Prefix' => $prefix,
|
|
288
|
-
];
|
|
289
|
-
|
|
290
|
-
$paginator = $this->client->getPaginator('ListObjectsV2', $params);
|
|
291
|
-
|
|
292
|
-
foreach ($paginator as $page) {
|
|
293
|
-
$contents = $page['Contents'] ?? [];
|
|
294
|
-
foreach ($contents as $object) {
|
|
295
|
-
$results[] = [
|
|
296
|
-
'key' => $object['Key'],
|
|
297
|
-
'size' => $object['Size'],
|
|
298
|
-
'last_modified' => $object['LastModified']->format('Y-m-d H:i:s'),
|
|
299
|
-
];
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
return $results;
|
|
304
|
-
} catch (S3Exception $e) {
|
|
305
|
-
error_log('[S3Storage::list] ' . $e->getMessage());
|
|
306
|
-
return [];
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// ---------------------------------------------------------------
|
|
311
|
-
// Copy & Move
|
|
312
|
-
// ---------------------------------------------------------------
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Copy an object to a new key within the same bucket.
|
|
316
|
-
*
|
|
317
|
-
* @param string $from Source object key.
|
|
318
|
-
* @param string $to Destination object key.
|
|
319
|
-
* @return bool True on success.
|
|
320
|
-
*/
|
|
321
|
-
public function copy(string $from, string $to): bool
|
|
322
|
-
{
|
|
323
|
-
try {
|
|
324
|
-
$this->client->copyObject([
|
|
325
|
-
'Bucket' => $this->bucket,
|
|
326
|
-
'CopySource' => "{$this->bucket}/{$from}",
|
|
327
|
-
'Key' => $to,
|
|
328
|
-
]);
|
|
329
|
-
|
|
330
|
-
return true;
|
|
331
|
-
} catch (S3Exception $e) {
|
|
332
|
-
error_log('[S3Storage::copy] ' . $e->getMessage());
|
|
333
|
-
return false;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Move an object to a new key (copy + delete).
|
|
339
|
-
*
|
|
340
|
-
* @param string $from Source object key.
|
|
341
|
-
* @param string $to Destination object key.
|
|
342
|
-
* @return bool True when copy and delete both succeed.
|
|
343
|
-
*/
|
|
344
|
-
public function move(string $from, string $to): bool
|
|
345
|
-
{
|
|
346
|
-
if ($this->copy($from, $to)) {
|
|
347
|
-
return $this->delete($from);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return false;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// ---------------------------------------------------------------
|
|
354
|
-
// File size
|
|
355
|
-
// ---------------------------------------------------------------
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Get the size of an object in bytes.
|
|
359
|
-
*
|
|
360
|
-
* @param string $remotePath Object key in the bucket.
|
|
361
|
-
* @return int File size in bytes, or -1 on failure.
|
|
362
|
-
*/
|
|
363
|
-
public function size(string $remotePath): int
|
|
364
|
-
{
|
|
365
|
-
try {
|
|
366
|
-
$result = $this->client->headObject([
|
|
367
|
-
'Bucket' => $this->bucket,
|
|
368
|
-
'Key' => $remotePath,
|
|
369
|
-
]);
|
|
370
|
-
|
|
371
|
-
return (int) ($result['ContentLength'] ?? 0);
|
|
372
|
-
} catch (S3Exception $e) {
|
|
373
|
-
error_log('[S3Storage::size] ' . $e->getMessage());
|
|
374
|
-
return -1;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Src\Service;
|
|
4
|
+
|
|
5
|
+
use Aws\S3\S3Client;
|
|
6
|
+
use Aws\S3\Exception\S3Exception;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* S3Storage — Wrapper for AWS S3 SDK.
|
|
10
|
+
*
|
|
11
|
+
* Provides simple methods for uploading, downloading, deleting, listing,
|
|
12
|
+
* and managing files on Amazon S3, MinIO, DigitalOcean Spaces, or any
|
|
13
|
+
* S3-compatible object storage.
|
|
14
|
+
*
|
|
15
|
+
* Required ENV variables:
|
|
16
|
+
* AWS_ACCESS_KEY_ID — IAM access key
|
|
17
|
+
* AWS_SECRET_ACCESS_KEY — IAM secret key
|
|
18
|
+
* AWS_DEFAULT_REGION — e.g. "eu-central-1"
|
|
19
|
+
* AWS_BUCKET — Default bucket name
|
|
20
|
+
*
|
|
21
|
+
* Optional ENV variables:
|
|
22
|
+
* AWS_ENDPOINT — Custom endpoint URL (MinIO, Spaces, etc.)
|
|
23
|
+
*
|
|
24
|
+
* @package Service
|
|
25
|
+
*/
|
|
26
|
+
class S3Storage
|
|
27
|
+
{
|
|
28
|
+
/** @var S3Client AWS S3 client instance */
|
|
29
|
+
private S3Client $client;
|
|
30
|
+
|
|
31
|
+
/** @var string Default bucket name */
|
|
32
|
+
private string $bucket;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create the S3 client from environment variables.
|
|
36
|
+
*
|
|
37
|
+
* When AWS_ENDPOINT is set the client connects to a custom
|
|
38
|
+
* S3-compatible service (MinIO, DigitalOcean Spaces, Cloudflare R2, etc.).
|
|
39
|
+
*/
|
|
40
|
+
public function __construct()
|
|
41
|
+
{
|
|
42
|
+
$region = $_ENV['AWS_DEFAULT_REGION'] ?? 'us-east-1';
|
|
43
|
+
$endpoint = $_ENV['AWS_ENDPOINT'] ?? null;
|
|
44
|
+
|
|
45
|
+
$config = [
|
|
46
|
+
'version' => 'latest',
|
|
47
|
+
'region' => $region,
|
|
48
|
+
'credentials' => [
|
|
49
|
+
'key' => $_ENV['AWS_ACCESS_KEY_ID'],
|
|
50
|
+
'secret' => $_ENV['AWS_SECRET_ACCESS_KEY'],
|
|
51
|
+
],
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
// Custom endpoint for S3-compatible services
|
|
55
|
+
if ($endpoint) {
|
|
56
|
+
$config['endpoint'] = $endpoint;
|
|
57
|
+
$config['use_path_style_endpoint'] = true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
$this->client = new S3Client($config);
|
|
61
|
+
$this->bucket = $_ENV['AWS_BUCKET'];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------
|
|
65
|
+
// Upload
|
|
66
|
+
// ---------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Upload a local file to S3.
|
|
70
|
+
*
|
|
71
|
+
* @param string $localPath Absolute path to the local file.
|
|
72
|
+
* @param string $remotePath Object key (path) in the bucket.
|
|
73
|
+
* @param string $acl Canned ACL: private, public-read, etc.
|
|
74
|
+
* @return array{status: bool, url: string|null, error: string|null}
|
|
75
|
+
*/
|
|
76
|
+
public function upload(string $localPath, string $remotePath, string $acl = 'private'): array
|
|
77
|
+
{
|
|
78
|
+
try {
|
|
79
|
+
$result = $this->client->putObject([
|
|
80
|
+
'Bucket' => $this->bucket,
|
|
81
|
+
'Key' => $remotePath,
|
|
82
|
+
'SourceFile' => $localPath,
|
|
83
|
+
'ACL' => $acl,
|
|
84
|
+
'ContentType' => mime_content_type($localPath) ?: 'application/octet-stream',
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
return [
|
|
88
|
+
'status' => true,
|
|
89
|
+
'url' => $result['ObjectURL'] ?? $this->url($remotePath),
|
|
90
|
+
'error' => null,
|
|
91
|
+
];
|
|
92
|
+
} catch (S3Exception $e) {
|
|
93
|
+
error_log('[S3Storage::upload] ' . $e->getMessage());
|
|
94
|
+
return ['status' => false, 'url' => null, 'error' => $e->getMessage()];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ---------------------------------------------------------------
|
|
99
|
+
// Upload from String
|
|
100
|
+
// ---------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Upload content from a string directly to S3.
|
|
104
|
+
*
|
|
105
|
+
* Useful for storing generated data (JSON, CSV, HTML) without
|
|
106
|
+
* writing a temporary file to disk first.
|
|
107
|
+
*
|
|
108
|
+
* @param string $content The raw string content to store.
|
|
109
|
+
* @param string $remotePath Object key (path) in the bucket.
|
|
110
|
+
* @param string $contentType MIME type, e.g. "application/json".
|
|
111
|
+
* @param string $acl Canned ACL: private, public-read, etc.
|
|
112
|
+
* @return array{status: bool, url: string|null, error: string|null}
|
|
113
|
+
*/
|
|
114
|
+
public function uploadFromString(string $content, string $remotePath, string $contentType, string $acl = 'private'): array
|
|
115
|
+
{
|
|
116
|
+
try {
|
|
117
|
+
$result = $this->client->putObject([
|
|
118
|
+
'Bucket' => $this->bucket,
|
|
119
|
+
'Key' => $remotePath,
|
|
120
|
+
'Body' => $content,
|
|
121
|
+
'ACL' => $acl,
|
|
122
|
+
'ContentType' => $contentType,
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
return [
|
|
126
|
+
'status' => true,
|
|
127
|
+
'url' => $result['ObjectURL'] ?? $this->url($remotePath),
|
|
128
|
+
'error' => null,
|
|
129
|
+
];
|
|
130
|
+
} catch (S3Exception $e) {
|
|
131
|
+
error_log('[S3Storage::uploadFromString] ' . $e->getMessage());
|
|
132
|
+
return ['status' => false, 'url' => null, 'error' => $e->getMessage()];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ---------------------------------------------------------------
|
|
137
|
+
// Download
|
|
138
|
+
// ---------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Download a file from S3 to a local path.
|
|
142
|
+
*
|
|
143
|
+
* @param string $remotePath Object key in the bucket.
|
|
144
|
+
* @param string $localPath Absolute local destination path.
|
|
145
|
+
* @return bool True on success.
|
|
146
|
+
*/
|
|
147
|
+
public function download(string $remotePath, string $localPath): bool
|
|
148
|
+
{
|
|
149
|
+
try {
|
|
150
|
+
$this->client->getObject([
|
|
151
|
+
'Bucket' => $this->bucket,
|
|
152
|
+
'Key' => $remotePath,
|
|
153
|
+
'SaveAs' => $localPath,
|
|
154
|
+
]);
|
|
155
|
+
|
|
156
|
+
return true;
|
|
157
|
+
} catch (S3Exception $e) {
|
|
158
|
+
error_log('[S3Storage::download] ' . $e->getMessage());
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ---------------------------------------------------------------
|
|
164
|
+
// Delete
|
|
165
|
+
// ---------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Delete a single object from S3.
|
|
169
|
+
*
|
|
170
|
+
* @param string $remotePath Object key to delete.
|
|
171
|
+
* @return bool True on success.
|
|
172
|
+
*/
|
|
173
|
+
public function delete(string $remotePath): bool
|
|
174
|
+
{
|
|
175
|
+
try {
|
|
176
|
+
$this->client->deleteObject([
|
|
177
|
+
'Bucket' => $this->bucket,
|
|
178
|
+
'Key' => $remotePath,
|
|
179
|
+
]);
|
|
180
|
+
|
|
181
|
+
return true;
|
|
182
|
+
} catch (S3Exception $e) {
|
|
183
|
+
error_log('[S3Storage::delete] ' . $e->getMessage());
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Delete multiple objects from S3 in a single request.
|
|
190
|
+
*
|
|
191
|
+
* @param array<string> $remotePaths List of object keys to delete.
|
|
192
|
+
* @return bool True when all objects are deleted successfully.
|
|
193
|
+
*/
|
|
194
|
+
public function deleteMany(array $remotePaths): bool
|
|
195
|
+
{
|
|
196
|
+
try {
|
|
197
|
+
$objects = array_map(fn(string $key) => ['Key' => $key], $remotePaths);
|
|
198
|
+
|
|
199
|
+
$this->client->deleteObjects([
|
|
200
|
+
'Bucket' => $this->bucket,
|
|
201
|
+
'Delete' => [
|
|
202
|
+
'Objects' => $objects,
|
|
203
|
+
'Quiet' => true,
|
|
204
|
+
],
|
|
205
|
+
]);
|
|
206
|
+
|
|
207
|
+
return true;
|
|
208
|
+
} catch (S3Exception $e) {
|
|
209
|
+
error_log('[S3Storage::deleteMany] ' . $e->getMessage());
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ---------------------------------------------------------------
|
|
215
|
+
// URL helpers
|
|
216
|
+
// ---------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get the public URL for an object.
|
|
220
|
+
*
|
|
221
|
+
* @param string $remotePath Object key in the bucket.
|
|
222
|
+
* @return string The public URL.
|
|
223
|
+
*/
|
|
224
|
+
public function url(string $remotePath): string
|
|
225
|
+
{
|
|
226
|
+
return $this->client->getObjectUrl($this->bucket, $remotePath);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Generate a pre-signed (temporary) URL for private objects.
|
|
231
|
+
*
|
|
232
|
+
* @param string $remotePath Object key in the bucket.
|
|
233
|
+
* @param int $minutes Link validity in minutes (default 60).
|
|
234
|
+
* @return string Pre-signed URL.
|
|
235
|
+
*/
|
|
236
|
+
public function signedUrl(string $remotePath, int $minutes = 60): string
|
|
237
|
+
{
|
|
238
|
+
$command = $this->client->getCommand('GetObject', [
|
|
239
|
+
'Bucket' => $this->bucket,
|
|
240
|
+
'Key' => $remotePath,
|
|
241
|
+
]);
|
|
242
|
+
|
|
243
|
+
$request = $this->client->createPresignedRequest($command, "+{$minutes} minutes");
|
|
244
|
+
|
|
245
|
+
return (string) $request->getUri();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ---------------------------------------------------------------
|
|
249
|
+
// Existence check
|
|
250
|
+
// ---------------------------------------------------------------
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Check whether an object exists in the bucket.
|
|
254
|
+
*
|
|
255
|
+
* @param string $remotePath Object key to check.
|
|
256
|
+
* @return bool True if the object exists.
|
|
257
|
+
*/
|
|
258
|
+
public function exists(string $remotePath): bool
|
|
259
|
+
{
|
|
260
|
+
try {
|
|
261
|
+
return $this->client->doesObjectExistV2($this->bucket, $remotePath);
|
|
262
|
+
} catch (S3Exception $e) {
|
|
263
|
+
error_log('[S3Storage::exists] ' . $e->getMessage());
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ---------------------------------------------------------------
|
|
269
|
+
// List objects
|
|
270
|
+
// ---------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* List objects under a given prefix (folder).
|
|
274
|
+
*
|
|
275
|
+
* Returns an array of associative arrays with key, size, and
|
|
276
|
+
* last_modified for each object.
|
|
277
|
+
*
|
|
278
|
+
* @param string $prefix Key prefix to filter by (e.g. "uploads/").
|
|
279
|
+
* @return array<int, array{key: string, size: int, last_modified: string}>
|
|
280
|
+
*/
|
|
281
|
+
public function list(string $prefix = ''): array
|
|
282
|
+
{
|
|
283
|
+
try {
|
|
284
|
+
$results = [];
|
|
285
|
+
$params = [
|
|
286
|
+
'Bucket' => $this->bucket,
|
|
287
|
+
'Prefix' => $prefix,
|
|
288
|
+
];
|
|
289
|
+
|
|
290
|
+
$paginator = $this->client->getPaginator('ListObjectsV2', $params);
|
|
291
|
+
|
|
292
|
+
foreach ($paginator as $page) {
|
|
293
|
+
$contents = $page['Contents'] ?? [];
|
|
294
|
+
foreach ($contents as $object) {
|
|
295
|
+
$results[] = [
|
|
296
|
+
'key' => $object['Key'],
|
|
297
|
+
'size' => $object['Size'],
|
|
298
|
+
'last_modified' => $object['LastModified']->format('Y-m-d H:i:s'),
|
|
299
|
+
];
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return $results;
|
|
304
|
+
} catch (S3Exception $e) {
|
|
305
|
+
error_log('[S3Storage::list] ' . $e->getMessage());
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ---------------------------------------------------------------
|
|
311
|
+
// Copy & Move
|
|
312
|
+
// ---------------------------------------------------------------
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Copy an object to a new key within the same bucket.
|
|
316
|
+
*
|
|
317
|
+
* @param string $from Source object key.
|
|
318
|
+
* @param string $to Destination object key.
|
|
319
|
+
* @return bool True on success.
|
|
320
|
+
*/
|
|
321
|
+
public function copy(string $from, string $to): bool
|
|
322
|
+
{
|
|
323
|
+
try {
|
|
324
|
+
$this->client->copyObject([
|
|
325
|
+
'Bucket' => $this->bucket,
|
|
326
|
+
'CopySource' => "{$this->bucket}/{$from}",
|
|
327
|
+
'Key' => $to,
|
|
328
|
+
]);
|
|
329
|
+
|
|
330
|
+
return true;
|
|
331
|
+
} catch (S3Exception $e) {
|
|
332
|
+
error_log('[S3Storage::copy] ' . $e->getMessage());
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Move an object to a new key (copy + delete).
|
|
339
|
+
*
|
|
340
|
+
* @param string $from Source object key.
|
|
341
|
+
* @param string $to Destination object key.
|
|
342
|
+
* @return bool True when copy and delete both succeed.
|
|
343
|
+
*/
|
|
344
|
+
public function move(string $from, string $to): bool
|
|
345
|
+
{
|
|
346
|
+
if ($this->copy($from, $to)) {
|
|
347
|
+
return $this->delete($from);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ---------------------------------------------------------------
|
|
354
|
+
// File size
|
|
355
|
+
// ---------------------------------------------------------------
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Get the size of an object in bytes.
|
|
359
|
+
*
|
|
360
|
+
* @param string $remotePath Object key in the bucket.
|
|
361
|
+
* @return int File size in bytes, or -1 on failure.
|
|
362
|
+
*/
|
|
363
|
+
public function size(string $remotePath): int
|
|
364
|
+
{
|
|
365
|
+
try {
|
|
366
|
+
$result = $this->client->headObject([
|
|
367
|
+
'Bucket' => $this->bucket,
|
|
368
|
+
'Key' => $remotePath,
|
|
369
|
+
]);
|
|
370
|
+
|
|
371
|
+
return (int) ($result['ContentLength'] ?? 0);
|
|
372
|
+
} catch (S3Exception $e) {
|
|
373
|
+
error_log('[S3Storage::size] ' . $e->getMessage());
|
|
374
|
+
return -1;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|