flc/alidayu is a thin, framework-agnostic PHP SDK that wraps the legacy Alibaba Dayu (阿里大于, later rebranded Aliyun SMS) Taobao-gateway API. It ships no Laravel service provider, facade, or config file — that's not a defect, it just means the "integration" work is yours.
Install
composer require flc/alidayu
Requires PHP 5.5+/7.x era code; it runs fine on PHP 8 in practice, but verify against your PHP version since the last tagged release is from April 2019.
Where to look first
src/Client.php — the executor; everything flows through Client::execute(RequestInterface $request).src/App.php — credential container (app_key, app_secret, and a sandbox flag).src/Requests/ — one class per API method (AlibabaAliqinFcSmsNumSend, AlibabaAliqinFcSmsNumQuery, AlibabaAliqinFcVoiceNumSinglecall, AlibabaAliqinFcTtsNumSinglecall, AlibabaAliqinFcFlowCharge/Grade/Query, etc.). Each request object exposes setX() setters that map 1:1 to the API's parameters — the source is the documentation.tests/ — the fastest way to see a full request/response round trip.First use case: a verification-code SMS
use Flc\Alidayu\App;
use Flc\Alidayu\Client;
use Flc\Alidayu\Requests\AlibabaAliqinFcSmsNumSend;
$client = new Client(new App([
'app_key' => config('services.alidayu.key'),
'app_secret' => config('services.alidayu.secret'),
// 'sandbox' => true, // logs the request instead of sending
]));
$request = new AlibabaAliqinFcSmsNumSend;
$request->setRecNum('13800138000')
->setSmsFreeSignName('你的签名')
->setSmsTemplateCode('SMS_12345678')
->setSmsParam(['code' => '4821', 'product' => 'MyApp']);
$response = $client->execute($request);
The return value is a decoded response object, not a boolean. Success looks like $response->alibaba_aliqin_fc_sms_num_send_response->result->success === true; failure arrives as $response->error_response->code / ->msg / ->sub_code. Check both branches — the SDK does not throw on business errors.
Bind a configured client in the container. Put credentials in config/services.php and register a singleton in a small service provider so you're not re-reading config on every send:
$this->app->singleton(Client::class, fn () => new Client(new App([
'app_key' => config('services.alidayu.key'),
'app_secret' => config('services.alidayu.secret'),
])));
Then inject Client into a SmsSender wrapper class. Don't let Flc\Alidayu\* types leak into controllers — you want one place to swap in alibabacloud/dysmsapi when the day comes (it will).
Wrap send in your own value object. Return a small SendResult (ok, code, message, requestId) so the rest of your app isn't parsing alibaba_aliqin_fc_sms_num_send_response deeply-nested stdClass. This pays off enormously.
OTP flow. Generate the code with random_int(), store it in the cache with a TTL, keyed by purpose + phone, and send inside a queued job:
Cache::put("otp:login:{$phone}", $code, now()->addMinutes(5));
SendSmsJob::dispatch($phone, 'SMS_12345678', ['code' => $code]);
The job should retry ($tries = 3, $backoff = [10, 30]) because gateway timeouts are common. Do not retry on isv.BUSINESS_LIMIT_CONTROL or invalid-template errors — inspect sub_code and $this->fail() when the error is permanent.
Verification. Store the code, not just a hash, if you need to show it in support tooling; otherwise Hash::make() is fine. Always delete the cache entry on success and count failed attempts (this belongs in your app, not the SDK).
Notifications. Laravel's Notifiable/Notification abstraction fits nicely — implement a custom channel class that receives a toSms($notifiable) payload and calls your SmsSender. You get queueing, via() routing, and testability for free.
Batch / bulk. setRecNum() accepts comma-separated numbers (there's a per-request cap — historically 200 with the same template and params for all recipients). For per-recipient params, fan out to individual jobs instead.
Testing. The sandbox flag on App makes the client log rather than call out — perfect for local and CI. For unit tests, bind a fake Client subclass (or mock execute()) into the container and assert the request parameters via the getters on the request object.
Multi-tenant. Since credentials live in App, you can construct a second Client at runtime from a tenant's stored keys. Keep the container singleton as the "default" and resolve a fresh client when a tenant override exists.
dysmsapi.aliyuncs.com, signed with HMAC-SHA1, SDK alibabacloud/dysmsapi-20170525). This package still targets the old gw.api.taobao.com/router/rest gateway with MD5 signing. Those endpoints have been on life support for years. Budget a migration path; wrap the SDK so swapping it out is a one-file change. With 0 dependents and a 2019 last release, assume no upstream fixes are coming.extra.laravel block, so nothing is registered automatically. Don't go hunting for a publishable config — write your own.error_response. Every call site needs an explicit success check. Common sub_codes worth handling differently: isv.MOBILE_NUMBER_ILLEGAL, isv.BUSINESS_LIMIT_CONTROL (per-number and per-day quotas — back off, don't retry), isv.SMS_TEMPLATE_ILLEGAL, isv.SMS_SIGNATURE_ILLEGAL, isv.AMOUNT_NOT_ENOUGH (account balance).setSmsFreeSignName() value must match an approved signature exactly, and setSmsTemplateCode() must be the exact SMS_xxx code. Mismatches are the single most common failure and produce confusing errors.setSmsParam() takes an array and the SDK JSON-encodes it. If you pass a pre-encoded string you can end up double-encoding. Also, JSON-encode non-ASCII signatures carefully — the legacy gateway can be finicky about UTF-8, so keep signatures ASCII-safe where possible.+-less prefixes. Validate before calling — the gateway's error is unhelpful.execute(). In a web request that means the user waits on the gateway. Always queue. Also set a timeout — a hung gateway will pin a PHP-FPM worker.sub_code and the Alibaba-side request_id are what support will ask for.RateLimiter::attempt("sms:{$phone}", 1, 60) before dispatching so you don't burn quota or trigger BUSINESS_LIMIT_CONTROL.Flc\Alidayu\Request\AbstractRequest and implement getApiMethodName() / getApiParas() following the patterns in src/Requests/. It's a small, readable codebase — patching locally via a Composer path repository is a viable stopgap.composer update can surprise you. Use an exact or ~ constraint and commit composer.lock.How can I help you explore Laravel packages today?