یک Disk اختصاصی برای پارسپک بسازید
در Laravel بهتر است جزئیات S3 پشت Filesystem Disk بماند. این راهنما تنظیمات filesystems.php و .env را به هم وصل میکند، سپس با یک Artisan Command مجموعهٔ کاملی از عملیات فایل و URL موقت را اجرا میکند.

نمونه، فایل hello.txt را با Key برابر tutorial/hello.txt ذخیره میکند. فهرست، متادیتا، دانلود، لینکهای موقت و حذف هم از همان Disk عبور میکنند.
قبل از اجرا، کش تنظیمات Laravel را پاک کنید؛ ویرایش .env روی برنامهای که از config cache استفاده میکند، فوراً اثر نمیگذارد.
پیشنیاز و نصب SDK
اول کتابخانه همین پوشه را نصب کنید. بدون این قدم، نمونه اجرا نمیشود.
(cd examples/laravel && composer install)examples/laravel/
├── .env # پس از کپیکردن فایل نمونه ساخته میشود
├── artisan # نقطه ورود خط فرمان Laravel
├── composer.json # وابستگیهای Laravel و Flysystem S3
├── config/filesystems.php # تعریف disk با نام parspack
└── app/Console/Commands/ParspackS3.php # فرمان parspack:s3 با هفت عملیاتساخت فایل .env
پنج مقدار را از پنل فضای ابری پارسپک کپی کنید. معنی هر متغیر و شکل معمول Endpoint در صفحه شروع آمده است.
upload با خطای امضا میخوابد.cp examples/laravel/.env.example examples/laravel/.envAPP_NAME="ParsPack S3 Laravel Example"
APP_ENV=local
APP_KEY=base64:pTLmKaeoyeEbLwNzFqSr/RbC4kyvXUOHo7JyAJEWcnc=
APP_DEBUG=true
LOG_CHANNEL=stack
LOG_STACK=single
LOG_LEVEL=debug
SESSION_DRIVER=file
CACHE_STORE=file
QUEUE_CONNECTION=sync
FILESYSTEM_DISK=local
PARSPACK_S3_ENDPOINT=https://c123456.parspack.net
PARSPACK_S3_BUCKET=c123456
PARSPACK_S3_REGION=us-east-1
PARSPACK_S3_ACCESS_KEY=YOUR_ACCESS_KEY
PARSPACK_S3_SECRET_KEY=YOUR_SECRET_KEY| متغیر | چه چیزی در آن میگذارید | اگر اشتباه باشد |
|---|---|---|
PARSPACK_S3_ENDPOINT | آدرس HTTPS پنل، بدون / در انتها؛ شکل معمول https://c123456.parspack.net | Timeout یا خطای امضا |
PARSPACK_S3_BUCKET | نام باکت؛ اغلب با شناسه داخل Endpoint یکی است | NoSuchBucket |
PARSPACK_S3_REGION | فقط برای امضای Signature V4 است. اگر پنل Region نداد، us-east-1 را ثابت نگه دارید | SignatureDoesNotMatch |
PARSPACK_S3_ACCESS_KEY / SECRET_KEY | کلیدهای API از پنل؛ فقط روی سرور | AccessDenied یا خطای امضا |
فایل .env را Commit نکنید. Access Key و Secret Key فقط روی سرور میمانند؛ مرورگر فقط لینک کوتاهعمر میگیرد.
اتصال برنامه به فضای پارسپک
Client را یک بار با مقدارهای .env بسازید. همین شیء برای هر هفت فرمان کافی است.
s3 در Laravel یعنی Flysystem + AWS SDK و این دیسک فقط به آن میگوید به کجا وصل شود. endpoint همان End Point URL پنل است با https:// و بدون اسلش انتها. bucket نام باکت و key و secret همان Access Key و Secret Key پنل. اگر یکی از پنج مقدار .env خالی باشد، فرمان با خطای Missing setting میخوابد و اصلا درخواستی به پنل نمیرود.use_path_style_endpoint باید true باشد؛ وگرنه SDK آدرس را به شکل bucket.endpoint میسازد و پارسپک آن را نمیشناسد. region موقعیت سرور نیست؛ اگر پنل Region نداد، us-east-1 را همان بگذار. throw: true یعنی خطا به شکل استثنا بیاید، نه اینکه ساکت بماند. نگران بساختن دیسک در هر درخواست نباشید؛ Laravel یک بار میسازد و نگه میدارد، پس Storage::disk('parspack') را هر جا خواستید صدا بزنید. کلیدها فقط روی سرور میمانند؛ مرورگر حداکثر لینک کوتاهعمر میگیرد.// config/filesystems.php
'disks' => [
'parspack' => [
'driver' => 's3',
'key' => env('PARSPACK_S3_ACCESS_KEY'),
'secret' => env('PARSPACK_S3_SECRET_KEY'),
'region' => env('PARSPACK_S3_REGION', 'us-east-1'),
'bucket' => env('PARSPACK_S3_BUCKET'),
'endpoint' => env('PARSPACK_S3_ENDPOINT'),
'use_path_style_endpoint' => true,
'throw' => true,
],
],آپلود فایل با Storage::put
اولین فرمان سناریو: فایل محلی را با یک نام مشخص در باکت بگذارید.
./hello.txt است؛ داخل باکت با نام tutorial/hello.txt ذخیره میشود. اسلش پوشه واقعی نمیسازد، فقط بخشی از نام است. ETag اثر انگشت محتواست. اگر همین نام از قبل در باکت باشد، محتوای قبلی عوض میشود.Storage::put برخلاف SDK بعد از آپلود ETag برنمیگرداند؛ برای همین خروجی upload اینجا فیلد etag ندارد. اگر ETag لازم دارید، با head بخوانیدش؛ فرمان head همین نمونه دقیقاً همان کار را میکند.Storage::disk('parspack')->put(
'tutorial/hello.txt',
file_get_contents('./hello.txt')
);| پارامتر | مثال | کاربرد |
|---|---|---|
Bucket | از .env | محل ذخیره Object |
Key | tutorial/hello.txt | نام کامل Object در S3 |
Contents | file_get_contents | بایتهای فایل محلی |
ContentType | text/plain | نوع محتوای فایل |
(cd examples/laravel && php artisan parspack:s3 upload ../../hello.txt tutorial/hello.txt)اگر tutorial/hello.txt از قبل وجود داشته باشد، Upload جدید همان Key را جایگزین میکند.
فهرست فایلها و مشخصات یک فایل
بعد از آپلود، اول ببینید فایل در فهرست هست؛ بعد حجم و نوع را بدون دانلود بخوانید.
list tutorial/ خود فایل را نمیآورد؛ فقط نام، حجم و زمان تغییر فایلهایی را برمیگرداند که با این Prefix شروع میشوند. اگر آرایه خالی بود، یا آپلود انجام نشده یا Prefix را اشتباه نوشتهاید.head وقتی لازم است که فقط بخواهید بدانید فایل هست یا نه، یا حجم و نوعش چیست. اگر Key نباشد معمولاً 404 میگیرید. این فرمان را با دانلود عوض نکنید؛ بدنه فایل را منتقل نمیکند.$listing = Storage::disk('parspack')
->getAdapter()
->listContents('tutorial/', false);
foreach ($listing as $attributes) {
if ($attributes->isFile()) {
echo $attributes->path(), ' — ', $attributes->fileSize();
}
}(cd examples/laravel && php artisan parspack:s3 list tutorial/)$info = Storage::disk('parspack')
->getClient()
->headObject([
'Bucket' => config('filesystems.disks.parspack.bucket'),
'Key' => 'tutorial/hello.txt',
]);
echo $info['ContentLength'];
echo $info['ContentType'];(cd examples/laravel && php artisan parspack:s3 head tutorial/hello.txt)tutorial/ بگذارید، فقط فایلهایی که نامشان با این مقدار شروع میشود دیده میشوند.listContents همان موتور زیر Storage::files() است. files فقط نام میدهد؛ اینجا حجم و زمان تغییر را هم از همان یک درخواست میگیریم.دانلود روی سرور
فایل را از باکت بگیرید و روی دیسک همین برنامه بنویسید. این کار لینک برای کاربر نمیسازد.
$contents = Storage::disk('parspack')->get('tutorial/hello.txt');
file_put_contents('./downloads/hello.txt', $contents);(cd examples/laravel && php artisan parspack:s3 download tutorial/hello.txt ../../downloads/hello.txt)لینک موقت دانلود برای کاربر
این فرمان را با دانلود روی سرور قاطی نکنید. اینجا فایل روی دیسک شما نوشته نمیشود؛ فقط یک آدرس زماندار ساخته میشود. جریان کامل در صفحه شروع است.
<a href> بگذارید، یا با curl --output file.pdf "$URL" بگیرید. Header اضافه لازم نیست. 900 یعنی پانزده دقیقه؛ بعد از آن آدرس کار نمیکند.$downloadUrl = Storage::disk('parspack')->temporaryUrl(
'tutorial/hello.txt',
now()->addSeconds(900)
);(cd examples/laravel && php artisan parspack:s3 presign-get tutorial/hello.txt 900)curl --output invoice.pdf "$SIGNED_DOWNLOAD_URL"لینک را کوتاه نگه دارید؛ بین ۱ ثانیه تا ۷ روز (۶۰۴۸۰۰ ثانیه). فقط همان Key را میخواند. لینک را فقط بعد از ورود کاربر و چک مجوز بسازید.
لینک موقت آپلود مستقیم
کاربر فایل را از مرورگر به باکت میفرستد. فایل از سرور شما رد نمیشود و کلید API هم به کاربر نمیرسد.
Content-Type را دقیقاً همان مقدار زمان ساخت لینک بگذارید؛ وگرنه معمولاً خطای امضا میگیرید. از مرورگر فقط وقتی کار میکند که CORS باکت سایت شما را مجاز کرده باشد؛ وگرنه از curl یا سرور خودتان استفاده کنید.$contentType = 'text/plain';
$signed = Storage::disk('parspack')->temporaryUploadUrl(
'tutorial/upload.txt',
now()->addSeconds(900),
['ContentType' => $contentType]
);
$uploadUrl = $signed['url'];(cd examples/laravel && php artisan parspack:s3 presign-put tutorial/upload.txt text/plain 900)curl --request PUT \
--header 'Content-Type: text/plain' \
--upload-file ./hello.txt \
"$SIGNED_UPLOAD_URL"await fetch(signed.url, {
method: "PUT",
headers: { "Content-Type": signed.headers["Content-Type"] },
body: file,
});| Presigned GET | Presigned PUT | |
|---|---|---|
| Method | GET | PUT |
| ورودی امضا | Bucket، Key، انقضا | Bucket، Key، Content-Type، انقضا |
| جایگزین کدام فرمان سرور است | جایگزین download برای کاربر نهایی | جایگزین upload وقتی فایل نباید از سرور شما عبور کند |
اگر موقع ساخت لینک text/plain گذاشتهاید، هنگام ارسال هم باید همان را بفرستید. از مرورگر این کار فقط وقتی درست است که CORS باکت سایت شما را مجاز کرده باشد؛ وگرنه از curl یا سرور خودتان استفاده کنید.
حذف فایل با Storage::delete
نام را از باکت برمیدارد. لازم نیست اول دانلودش کنید. فایل روی سیستم شما دست نمیخورد.
Storage::disk('parspack')->delete('tutorial/hello.txt');(cd examples/laravel && php artisan parspack:s3 delete tutorial/hello.txt)خطای Filesystem را تا پاسخ S3 دنبال کنید
Laravel ممکن است خطای اصلی AWS را داخل exception دیگری قرار دهد. نمونه، exception قبلی را هم بررسی میکند تا کد سرویس و Status Code در stderr گم نشوند.
} catch (Throwable $error) {
$aws = $error instanceof AwsException ? $error : $error->getPrevious();
if ($aws instanceof AwsException) {
fwrite(STDERR, ($aws->getAwsErrorCode() ?: 'S3Error').': '.$aws->getMessage().PHP_EOL);
return 1;
}
fwrite(STDERR, $error->getMessage().PHP_EOL);
return 2;
}| خطا | علت معمول | بررسی |
|---|---|---|
Missing setting | یکی از پنج متغیر PARSPACK_S3_* در .env خالی است | مقدارها را از پنل بگیرید و دوباره اجرا کنید |
AccessDenied | کلید یا دسترسی Bucket کافی نیست | مقدارهای پنل و نوع دسترسی کاربر فضای ابری |
SignatureDoesNotMatch | امضای درخواست با تنظیمات یکی نیست | Slash انتهای Endpoint، Region، کلیدها و Content-Type در PUT |
NoSuchBucket | نام Bucket با مقدار پنل یکی نیست | PARSPACK_S3_BUCKET |
Timeout / connection | Endpoint در دسترس نیست یا HTTPS قطع است | آدرس پنل را بدون تغییر و بدون / انتها وارد کنید |
اجرای کامل همین سناریو
فرمان parspack:s3 همین هفت عملیات صفحه است در یک فایل: خواندن دیسک، چک کردن آرگومانها و هر دو لینک امضاشده. اگر خروجی هر فرمان شبیه نمونههای بالا بود، میتوانید Key و مسیر را با نام فایلهای برنامه خودتان عوض کنید.
(cd examples/laravel && composer install)
cp examples/laravel/.env.example examples/laravel/.env
# مقدارهای .env را کامل کنید
printf 'hello ParsPack\n' > hello.txt
(cd examples/laravel && php artisan parspack:s3 upload ../../hello.txt tutorial/hello.txt)
(cd examples/laravel && php artisan parspack:s3 list tutorial/)
(cd examples/laravel && php artisan parspack:s3 head tutorial/hello.txt)
(cd examples/laravel && php artisan parspack:s3 download tutorial/hello.txt ../../downloads/hello.txt)
(cd examples/laravel && php artisan parspack:s3 presign-get tutorial/hello.txt 900)
(cd examples/laravel && php artisan parspack:s3 presign-put tutorial/upload.txt text/plain 900)
(cd examples/laravel && php artisan parspack:s3 delete tutorial/hello.txt)config/filesystems.php تعریف میشود. در Controller، Job، Queue و Seeder همان Storage::disk('parspack') را صدا بزنید؛ Client را در هر کلاس جدا نسازید.invoices/42.pdf یا users/123/avatar.jpg و Key نهایی را کنار رکورد مربوط در Database نگه دارید.temporaryUrl و temporaryUploadUrl فقط بعد از ورود کاربر و چک مجوز همان فایل. لینک را کوتاه نگه دارید؛ پیشفرض نمونه ۱۵ دقیقه است.AccessDenied، NoSuchBucket و ...) به error->getPrevious() نگاه کنید.