ایزی ریت
ورود / ثبت‌نامورود

دریافت نرخ ارز در PHP و لاراول با cURL و Guzzle؛ آموزش کامل

دریافت نرخ ارز در PHP خالص با cURL و در لاراول با Http Client (Guzzle): کلید در .env، کش با Cache::remember، مدیریت خطا و زمان‌بندی به‌روزرسانی با Scheduler.

عرفان فضلی
· کارشناس توسعه و برنامه نویسی
انتشار ۲ آبان ۱۴۰۵، ۱۶:۰۰ · ۲ دقیقه مطالعه
دریافت نرخ ارز در PHP و لاراول
خلاصه در سه خط
  • در PHP خالص با cURL و هدر X-API-Key درخواست بفرستید و JSON را با json_decode بخوانید.
  • در لاراول از Http::withHeaders()->timeout()->get() و Cache::remember استفاده کنید تا هر بازدید درخواست جدید نسازد.
  • برای سایت‌های پربازدید، یک دستور زمان‌بندی‌شده نرخ‌ها را هر دقیقه بگیرد و صفحه‌ها فقط از کش بخوانند.

PHP هنوز زبان اصلی بیشتر سایت‌های فروشگاهی و سامانه‌های داخلی ایران است. در این آموزش دریافت نرخ ارز در PHP را دو بار پیاده می‌کنیم: یک‌بار با cURL خالص برای پروژه‌های بدون فریم‌ورک و یک‌بار به روش لاراول، با کش و زمان‌بندی خودکار.

★
پاسخ کوتاه
در PHP: curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-Key: $key"]) و درخواست به https://api.ezrate.me/v1/rates?symbols=usd,eur. در لاراول: Http::withHeaders(["X-API-Key" => config("services.ezrate.key")])->timeout(8)->get(...)->json("data") داخل Cache::remember(…, 60, …).

این آموزش بخشی از راهنمای کامل API قیمت ارز و طلا است. نسخهٔ انگلیسی همین موضوع با تمرکز بر Redis در Laravel Exchange Rate API آمده است.

روش اول: PHP خالص با cURL

php
<?php
function ezrate_get(string $path, array $query = []): array
{
    $key = getenv('EZRATE_KEY');
    $url = 'https://api.ezrate.me/v1' . $path . ($query ? '?' . http_build_query($query) : '');
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 8,
        CURLOPT_HTTPHEADER => ["X-API-Key: {$key}", 'Accept: application/json'],
    ]);
    $body = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($body === false || $code !== 200) {
        throw new RuntimeException("EZRate API error: HTTP {$code}");
    }
    return json_decode($body, true, 512, JSON_THROW_ON_ERROR);
}

$res = ezrate_get('/rates', ['symbols' => 'usd,eur,aed', 'lang' => 'fa']);
foreach ($res['data'] as $r) {
    printf("%s: %s تومان\n", $r['name'], number_format($r['sell']));
}

کش ساده با فایل

اگر این کد در هر بازدید صفحه اجرا شود، سهمیهٔ شما در چند ساعت تمام می‌شود. ساده‌ترین کش، یک فایل موقت است:

php
function ezrate_rates_cached(int $ttl = 60): array
{
    $file = sys_get_temp_dir() . '/ezrate_rates.json';
    if (is_file($file) && time() - filemtime($file) < $ttl) {
        return json_decode(file_get_contents($file), true);
    }
    try {
        $data = ezrate_get('/rates', ['symbols' => 'usd,eur,aed,g18,emami'])['data'];
        file_put_contents($file, json_encode($data), LOCK_EX);
        return $data;
    } catch (Throwable $e) {
        // API در دسترس نیست: آخرین فایل حتی اگر قدیمی باشد
        return is_file($file) ? json_decode(file_get_contents($file), true) : [];
    }
}

روش دوم: لاراول با Http Client

Http Client لاراول روی Guzzle ساخته شده و کار را تمیزتر می‌کند. ابتدا کلید را در .env و config/services.php تعریف کنید:

php
// .env
EZRATE_KEY=ezr_live_…

// config/services.php
'ezrate' => [
    'key' => env('EZRATE_KEY'),
    'url' => 'https://api.ezrate.me/v1',
],

سپس یک سرویس کوچک بسازید:

php
<?php
namespace App\Services;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;

class ExchangeRates
{
    public function all(): array
    {
        return Cache::remember('ezrate:rates', 60, function () {
            $res = Http::withHeaders(['X-API-Key' => config('services.ezrate.key')])
                ->timeout(8)->retry(2, 500, throw: false)
                ->get(config('services.ezrate.url') . '/rates', ['category' => 'fiat', 'lang' => 'fa']);

            if (! $res->successful()) {
                return Cache::get('ezrate:rates:last', []);
            }
            $rates = collect($res->json('data'))->keyBy('symbol')->all();
            Cache::forever('ezrate:rates:last', $rates);   // آخرین مقدار معتبر
            return $rates;
        });
    }

    public function sell(string $symbol): ?int
    {
        return $this->all()[$symbol]['sell'] ?? null;
    }
}

حالا در کنترلر یا Blade کافی است app(ExchangeRates::class)->sell('usd') را صدا بزنید.

خروجی سرویس نرخ ارز در لاراول
نمایش نرخ‌ها در یک صفحهٔ Blade

به‌روزرسانی زمان‌بندی‌شده: بهترین الگو برای سایت پربازدید

در الگوی بالا، اولین کاربری که بعد از انقضای کش می‌آید منتظر پاسخ API می‌ماند. در سایت‌های پربازدید بهتر است یک دستور زمان‌بندی‌شده هر دقیقه کش را تازه کند و صفحه‌ها همیشه فقط از کش بخوانند:

php
// routes/console.php (لاراول ۱۱ به بعد)
use Illuminate\Support\Facades\Schedule;

Schedule::call(function () {
    Cache::forget('ezrate:rates');
    app(\App\Services\ExchangeRates::class)->all();
})->everyMinute()->name('ezrate-refresh')->withoutOverlapping();
i
نکته
فاصلهٔ تازه‌سازی را با meta.freshness_sec پلن خود هماهنگ کنید. اگر پلن شما داده را هر ۵ دقیقه تازه می‌کند، درخواست هر دقیقه فقط سهمیه را هدر می‌دهد.

تبدیل و قیمت‌گذاری

برای تبدیل مبلغ لازم نیست خودتان ضرب و تقسیم کنید؛ مسیر convert با سمت خرید یا فروش دلخواه این کار را می‌کند:

php
$res = Http::withHeaders(['X-API-Key' => config('services.ezrate.key')])
    ->get(config('services.ezrate.url') . '/convert', [
        'from' => 'usd', 'to' => 'irt', 'amount' => 49.99, 'side' => 'sell',
    ])->json('data');
// $res['result'] = معادل تومانی ۴۹٫۹۹ دلار

کاربرد رایج آن، قیمت‌گذاری خودکار کالاهای وارداتی است که در آموزش قیمت‌گذاری خودکار ووکامرس بر اساس دلار با جزئیات آمده است.

خطاهای رایج در PHP

خطاعلت محتملراه حل
SSL certificate problemگواهی‌های ریشه روی سرور قدیمی استبستهٔ ca-certificates را به‌روز کنید؛ هرگز SSL_VERIFYPEER را خاموش نکنید
HTTP 401کلید در محیط production خوانده نمی‌شودبعد از تغییر .env دستور php artisan config:cache را دوباره اجرا کنید
HTTP 403محدودیت IP کلید با IP سرور یکی نیستIP خروجی سرور را در پنل ثبت کنید
HTTP 429کش کار نمی‌کند و هر بازدید درخواست می‌سازددرایور کش (redis یا file) را بررسی کنید
یورو۳۰۴٬۵۶۰تومان+۰٫۷۳٪

ذخیرهٔ تاریخچه در پایگاه داده

اگر می‌خواهید نرخ هر روز را برای گزارش‌های داخلی نگه دارید (مثلاً نرخ تسعیر پایان روز)، یک جدول ساده کافی است:

php
// database/migrations/xxxx_create_daily_rates_table.php
Schema::create('daily_rates', function (Blueprint $t) {
    $t->id();
    $t->date('day');
    $t->string('symbol', 16);
    $t->unsignedBigInteger('buy');
    $t->unsignedBigInteger('sell');
    $t->string('quote', 8)->default('IRT');
    $t->unique(['day', 'symbol']);
});

// routes/console.php — هر شب ساعت ۲۳:۵۵ به وقت تهران
Schedule::call(function () {
    foreach (app(\App\Services\ExchangeRates::class)->all() as $r) {
        DB::table('daily_rates')->updateOrInsert(
            ['day' => today('Asia/Tehran'), 'symbol' => $r['symbol']],
            ['buy' => $r['buy'], 'sell' => $r['sell'], 'quote' => $r['quote']]
        );
    }
})->dailyAt('23:55')->timezone('Asia/Tehran');

اگر از قبل به تاریخچه نیاز دارید، لازم نیست منتظر بمانید؛ مسیر history کندل‌های روزانه را برای بازهٔ دلخواه می‌دهد. آموزش کامل در دیتای تاریخی قیمت دلار و طلا.

تست خودکار بدون مصرف سهمیه

در تست‌های لاراول هرگز به API واقعی درخواست نفرستید. Http::fake پاسخ را شبیه‌سازی می‌کند:

php
use Illuminate\Support\Facades\Http;

it('shows the dollar price', function () {
    Http::fake(['api.ezrate.me/*' => Http::response([
        'data' => [['symbol' => 'usd', 'name' => 'دلار', 'buy' => 100000, 'sell' => 100200, 'quote' => 'IRT']],
        'meta' => ['as_of' => now()->toIso8601String(), 'freshness_sec' => 60],
    ])]);
    Cache::flush();
    expect(app(\App\Services\ExchangeRates::class)->sell('usd'))->toBe(100200);
});

it('keeps the last rate when the API is down', function () {
    Cache::forever('ezrate:rates:last', ['usd' => ['sell' => 99000]]);
    Http::fake(['api.ezrate.me/*' => Http::response([], 503)]);
    Cache::forget('ezrate:rates');
    expect(app(\App\Services\ExchangeRates::class)->sell('usd'))->toBe(99000);
});

واکنش به تغییر قیمت با Event

گاهی می‌خواهید وقتی دلار بیش از یک درصد تغییر کرد، کاری انجام شود؛ مثلاً به مدیر فروش ایمیل برود یا قیمت محصولات بازنگری شود. بعد از هر تازه‌سازی، نرخ جدید را با قبلی مقایسه کنید و یک Event لاراول بفرستید. این‌طور منطق تجاری از منطق دریافت داده جدا می‌ماند و راحت‌تر تست می‌شود.

php
$old = Cache::get('ezrate:usd:prev');
$new = $rates['usd']['sell'];
if ($old && abs($new - $old) / $old >= 0.01) {
    event(new \App\Events\DollarMoved($old, $new));
}
Cache::forever('ezrate:usd:prev', $new);
جدول daily_rates در پایگاه داده پس از چند روز ذخیره
آرشیو روزانهٔ نرخ‌ها در پایگاه داده

نرخ‌های لحظه‌ای مرتبط با این مطلب: قیمت دلار، قیمت طلای ۱۸ عیار، قیمت سکه امامی.

برای دریافت نرخ ارز در PHP، cURL بهتر است یا file_get_contents؟
cURL. کنترل timeout، هدرها و کد وضعیت را راحت‌تر می‌دهد و روی بیشتر هاست‌ها فعال است.
Guzzle را جدا نصب کنم؟
در لاراول نه؛ Http Client آن روی Guzzle ساخته شده است. در پروژه‌های دیگر با composer require guzzlehttp/guzzle نصب کنید.
در هاست اشتراکی که cron ندارد چه کنم؟
از همان کش فایل استفاده کنید؛ اولین بازدید بعد از انقضا عدد را تازه می‌کند. بیشتر هاست‌های سی‌پنل و دایرکت‌ادمین هم امکان Cron Job دارند.
قیمت را به ریال بگیرم یا تومان؟
هر کدام که سیستم شما استفاده می‌کند؛ با پارامتر quote=IRR ریال و پیش‌فرض تومان است. فقط در همهٔ جاهای پروژه یکی را نگه دارید.
عرفان فضلی
من در حوزه توسعه نرم‌افزار و برنامه‌نویسی فعالیت می‌کنم و به طراحی و پیاده‌سازی راهکارهای کاربردی و بهینه علاقه‌مندم. تمرکز من بر توسعه قابلیت‌های جدید، بهبود عملکرد سیستم‌ها و حل چالش‌های فنی با استفاده از فناوری‌های روز است. در مسیر کاری خود تلاش می‌کنم با یادگیری مستمر و به‌کارگیری روش‌های نوین برنامه‌نویسی، محصولات نرم‌افزاری کارآمد، پایدار و قابل‌اعتماد توسعه دهم.
همهٔ مطالب نویسنده ←

نظرات (۰)

نظرها پس از تأیید نمایش داده می‌شوند؛ برای ارسال، ورود به حساب لازم است.

مطالب مرتبط

API و برنامه‌نویسی
اسکرپ قیمت ارز یا API رسمی؟ مقایسهٔ فنی، حقوقی و هزینهٔ واقعی
API و برنامه‌نویسی
قیمت طلا در React و Next.js؛ پیاده‌سازی تابلو قیمت لحظه‌ای طلا و سکه
API و برنامه‌نویسی
نمایش قیمت ارز در وردپرس؛ آموزش با افزونه، شورت‌کد و API