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

- در PHP خالص با cURL و هدر X-API-Key درخواست بفرستید و JSON را با json_decode بخوانید.
- در لاراول از Http::withHeaders()->timeout()->get() و Cache::remember استفاده کنید تا هر بازدید درخواست جدید نسازد.
- برای سایتهای پربازدید، یک دستور زمانبندیشده نرخها را هر دقیقه بگیرد و صفحهها فقط از کش بخوانند.
PHP هنوز زبان اصلی بیشتر سایتهای فروشگاهی و سامانههای داخلی ایران است. در این آموزش دریافت نرخ ارز در PHP را دو بار پیاده میکنیم: یکبار با cURL خالص برای پروژههای بدون فریمورک و یکبار به روش لاراول، با کش و زمانبندی خودکار.
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
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']));
}کش ساده با فایل
اگر این کد در هر بازدید صفحه اجرا شود، سهمیهٔ شما در چند ساعت تمام میشود. سادهترین کش، یک فایل موقت است:
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 تعریف کنید:
// .env
EZRATE_KEY=ezr_live_…
// config/services.php
'ezrate' => [
'key' => env('EZRATE_KEY'),
'url' => 'https://api.ezrate.me/v1',
],سپس یک سرویس کوچک بسازید:
<?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') را صدا بزنید.

بهروزرسانی زمانبندیشده: بهترین الگو برای سایت پربازدید
در الگوی بالا، اولین کاربری که بعد از انقضای کش میآید منتظر پاسخ API میماند. در سایتهای پربازدید بهتر است یک دستور زمانبندیشده هر دقیقه کش را تازه کند و صفحهها همیشه فقط از کش بخوانند:
// 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();meta.freshness_sec پلن خود هماهنگ کنید. اگر پلن شما داده را هر ۵ دقیقه تازه میکند، درخواست هر دقیقه فقط سهمیه را هدر میدهد.تبدیل و قیمتگذاری
برای تبدیل مبلغ لازم نیست خودتان ضرب و تقسیم کنید؛ مسیر convert با سمت خرید یا فروش دلخواه این کار را میکند:
$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) را بررسی کنید |
ذخیرهٔ تاریخچه در پایگاه داده
اگر میخواهید نرخ هر روز را برای گزارشهای داخلی نگه دارید (مثلاً نرخ تسعیر پایان روز)، یک جدول ساده کافی است:
// 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 پاسخ را شبیهسازی میکند:
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 لاراول بفرستید. اینطور منطق تجاری از منطق دریافت داده جدا میماند و راحتتر تست میشود.
$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);
نرخهای لحظهای مرتبط با این مطلب: قیمت دلار، قیمت طلای ۱۸ عیار، قیمت سکه امامی.

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


