Download the PHP package calisero/calisero-symfony without Composer
On this page you can find all versions of the php package calisero/calisero-symfony. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download calisero/calisero-symfony
More information about calisero/calisero-symfony
Files in calisero/calisero-symfony
Package calisero-symfony
Short Description Symfony bundle for sending SMS through the Calisero API
License MIT
Homepage https://calisero.ro
Informations about the package calisero-symfony
Calisero SMS Bundle for Symfony
Official Symfony bundle for the Calisero transactional SMS API.
It wraps the Calisero PHP SDK and brings it into Symfony the Symfony way: semantic configuration, autowired services, a Notifier transport, a delivery webhook dispatching events, console commands and validation constraints. Supports Symfony 6.4, 7.x and 8.x on PHP 8.2 to 8.5.
Features
- ๐ Autowired client:
SmsClientInterfacesends SMS and verification codes, reads messages and the account - ๐ Notifier transport:
calisero://forTexterInterfaceand thesmschannel of notifications, nosymfony/http-clientneeded - ๐ URL shortening with click statistics for every link of a message
- ๐ Two-factor authentication with the Verifications API: Calisero generates, sends and checks the codes
- ๐ฌ Delivery webhook with token security, dispatched as typed Symfony events
- ๐ Daily sending limit & credit monitoring through events and the account API
- โ
Validation constraints
#[PhoneE164]and#[SenderId], with Romanian translations - ๐งช Console commands to send a test SMS, read a message, the account, send and check codes
- ๐งฐ The SDK's services, autowired: messages, verifications, opt-outs, accounts
- ๐งฏ Typed error handling, with the trace ID of every failed request
- โ๏ธ Environment variables everywhere: every setting can be
%env(...)%, read only at runtime - ๐ Type safe: PHPStan level 9, 100% test coverage, tested on every supported PHP and Symfony version
Requirements
| Bundle | Symfony | PHP | Calisero PHP SDK | Calisero API |
|---|---|---|---|---|
^1.0 |
6.4, 7.x, 8.x | 8.2 โ 8.5 (Symfony 8 requires PHP 8.4+) | ^2.3 |
1.0.14 |
Composer picks the Symfony release your PHP version allows: on PHP 8.2 and 8.3 you get Symfony 6.4 or 7.x, on PHP 8.4
and 8.5 any of the three. The SDK needs ext-curl and ext-json.
The Notifier transport needs symfony/notifier, the constraints symfony/validator; both are optional.
Installation
With Symfony Flex the bundle is registered for you. Otherwise add it to config/bundles.php:
Configure the bundle
Create config/packages/calisero.yaml:
Declare the variables in .env, committed, without their values:
and set their values in .env.local, in your secrets vault (bin/console secrets:set CALISERO_API_KEY) or in your
platform's environment:
That is all you need to send. The Configuration Reference lists every option; the webhook, the Notifier and the constraints are set up in their own sections below.
Getting Your API Key
- Log in to your Calisero account at https://calisero.ro
- Open the API Keys section of the dashboard and click Add Key
- Name it after the application and the environment (one key per environment), set IP filters if you need them
- Copy the key: it is shown only once
Security Note: never commit an API key. Keep it in
.env.local, in Symfony's secrets vault or in your platform's environment. A sandbox key sends no real message and costs nothing: use one in development.
Usage
Quick Start: Send an SMS
Autowire SmsClientInterface:
Sending Options
Every optional parameter accepts snake_case or camelCase (shorten_urls or shortenUrls):
schedule_at: the API reads it asY-m-d H:i:sin Romania time (Europe/Bucharest) and refuses ISO 8601 strings such as2026-10-05T10:00:00Z. Pass a\DateTimeInterfaceand the bundle converts it, daylight saving time included; a string is sent as is.-
shorten_urls: Calisero replaces thehttp://andhttps://links of the text with short ones before sending. The short links, and how many times each was opened, come back on the message: - Unknown parameters are refused: a typo such as
sheduleAtthrows an\InvalidArgumentExceptionnaming the accepted parameters, instead of sending the message without it. So does a value of the wrong type ('validity' => 'two days'). Strings of digits and booleans as forms give them ('48','false','on') are read as such. - What the answer reports:
$response->getResponseMeta()gives the request's trace ID and, while the account has a daily sending limit, how many messages it can still send today:
Verification Codes (2FA)
Calisero generates the code (6 characters, case-insensitive), sends it by SMS and checks it:
A wrong, expired or exhausted code is refused with a ValidationException (422), whose message says which. Either
brand or template is required. A code's SMS counts towards the daily sending limit. See
examples/verification_controller.php for a whole flow.
Messages and the Account
Through the Notifier
With symfony/notifier, Calisero becomes an SMS transport (TexterInterface, and the sms channel of
notifications):
| DSN | Means |
|---|---|
calisero://default |
The configured API key (calisero.api_key) and base URI |
calisero://API_KEY@default |
Its own API key |
?from=MyBrand |
The sender of the messages that do not set one (optional) |
calisero://[email protected] |
Another host: https://staging.example.com/api/v1 (a port and a path may follow) |
The transport applies the bundle's configuration like SmsClientInterface does: timeouts, User-Agent and the
webhook's callback_url. It sends through the SDK, so it needs no symfony/http-client.
The sender is the SmsMessage's from (third constructor argument), else the DSN's, else Calisero's default.
Notifications go out the same way, through the sms channel:
A notification implementing SmsNotificationInterface shapes its own SmsMessage, Calisero options included: see
examples/notification_example.php.
When the API refuses a message, the transport throws a CaliseroTransportException, a TransportExceptionInterface
of the Notifier: getApiException() (also its previous exception) gives the SDK's, to tell a
DailyLimitExceededException from a ValidationException, and getDebug() the HTTP status, the trace ID and the
error body.
Sending Asynchronously
Route the SMS of the Notifier through Messenger to send them from a worker:
The bundle never retries a request: the API takes no idempotency key, so a send retried after a timeout could deliver
the message twice. The API refuses (422) the same message to the same recipient sent again within a few seconds.
Retry from your own code when a failure means nothing was sent: a DailyLimitExceededException (after
getResetsAt()), a RateLimitedException (after getRetryAfter() seconds), an UnauthorizedException...
A worker has no request to build absolute URLs from: set framework.router.default_uri (see
Webhook Handling).
The SDK's Services
SmsClientInterface covers what an application needs most. The Calisero PHP SDK's services are autowired too, built
on the bundle configuration (base URI, timeouts, User-Agent), for everything else:
| Autowire | Service | Gives |
|---|---|---|
Calisero\Sms\Services\MessageService |
calisero.messages |
create(), get(), list(), delete() |
Calisero\Sms\Services\VerificationService |
calisero.verifications |
create(), get(), list($page, $status), validate() |
Calisero\Sms\Services\OptOutService |
calisero.opt_outs |
create(), get(), list(), update(), delete() |
Calisero\Sms\Services\AccountService |
calisero.accounts |
get($accountId) |
Calisero\SymfonySms\Client\SdkClient |
calisero.sdk_client |
The four, and the SDK's HttpClient (getLastResponse()) |
Calisero\SymfonySms\Client\ClientFactory |
calisero.client_factory |
make($apiKey): the same on another API key |
One API key per tenant? ClientFactory::make($apiKey) builds the services on it, and new SmsClient($sdkClient, null, $accountId) the client: see examples/multi_tenant_client.php. The
SDK's README documents every service and DTO.
Validation Constraints
| Constraint | Options | Error codes |
|---|---|---|
PhoneE164 |
message |
PhoneE164::INVALID_FORMAT_ERROR |
SenderId |
lengthMessage, charactersMessage |
SenderId::INVALID_LENGTH_ERROR, SenderId::INVALID_CHARACTERS_ERROR |
Like Symfony's own constraints, both let null and '' through (add NotBlank to require a value) and report a value
that is not a string as a type error. Their messages are translated into Romanian (validators domain); override them
in your translations/validators.ro.xlf.
Webhook Handling
When a message has a callback_url, Calisero posts its delivery status there each time it changes. The bundle answers
these callbacks and dispatches them as events.
1. Enable the webhook and set a token:
2. Import the bundle's routes:
The route, POST /calisero/webhook named calisero_webhook, is loaded only while the webhook is enabled.
3. Tell the router your public URL, for the messages sent outside a request (console commands, Messenger workers), where it has none to build the callback URL from:
While the webhook is enabled, every message that sets no callback_url, sent by SmsClientInterface or the
Notifier, carries the webhook's absolute URL, e.g. https://shop.example.com/calisero/webhook?token=โฆ. If the route
is not loaded, the message is refused with a \LogicException, rather than sent with a callback URL nothing answers.
Securing the Webhook
With a token set, the callback URL carries it as ?token=, and the endpoint answers 401 to a callback without it,
compared in constant time. If you set a callback_url yourself, add the ?token= to it. Without a token the endpoint
is open to anyone who knows its URL (POST only): prefer a token, or other controls (IP allow-list, WAF).
To rotate the token, update CALISERO_WEBHOOK_TOKEN and deploy: messages sent from then on carry the new one, and the
callbacks of the messages sent before still carry the old one, refused from then on.
Listening for the Events
| Status | Event | Meaning |
|---|---|---|
sent |
MessageSentEvent |
Accepted and handed to the network |
delivered |
MessageDeliveredEvent |
The handset or the network confirmed the delivery |
undelivered |
MessageFailedEvent |
The delivery failed for good (failed, posted by hand, too) |
The three extend DeliveryStatusEvent: getPayload(), getMessage(), getMessageId(), getStatus(). The webhook
also dispatches the monitoring events of Credit Monitoring and
Daily Sending Limit.
The endpoint answers 200 {"ok": true}, 401 for a wrong token and 400 for a body that is not a JSON object.
Calisero retries a callback only when the connection fails or your endpoint does not answer within 2 seconds (at most
5 attempts); a non-2xx answer is not retried. Keep the listeners fast: hand slow work to Messenger.
Payload example:
dailyLimit and dailyRemaining are null while the account has no daily sending limit. A failed delivery has
"status": "undelivered" and a deliveredAt of null.
Console Commands
A refusal prints the API's message and the trace ID, tells the daily sending limit from the request rate limit, and
lists the fields at fault in a 422; the command then exits with 1.
Configuration Reference
bin/console config:dump-reference calisero prints it, with the description of every option. Every value may come
from an environment variable, read only when the application runs; cast the ones that are not strings:
%env(bool:CALISERO_WEBHOOK_ENABLED)%, %env(float:CALISERO_TIMEOUT)%, %env(int:CALISERO_DAILY_LIMIT_LOW)%, and
declare every variable in .env with its default (CALISERO_WEBHOOK_ENABLED=false, CALISERO_ACCOUNT_ID=): an
empty account ID or token counts as none. An empty API key is checked by the first request, which then throws a
\RuntimeException naming calisero.api_key: an application without one still boots, and fails only where it calls
the API.
| Suggested variable | Option | Description |
|---|---|---|
CALISERO_API_KEY |
api_key |
Your API key, from the dashboard |
CALISERO_ACCOUNT_ID |
account_id |
Your account ID, for getAccount(), getBalance() and calisero:account |
CALISERO_WEBHOOK_ENABLED |
webhook.enabled |
Load the webhook route and send its URL as the callback_url |
CALISERO_WEBHOOK_TOKEN |
webhook.token |
Shared secret required from every callback |
CALISERO_DSN |
framework.notifier.texter_transports |
The Notifier transport, e.g. calisero://default?from=MyBrand |
User-Agent
Every request names the bundle and its version, PHP, Symfony and the platform, as the other Calisero libraries do, so Calisero can tell the bundle's requests apart and see which versions sent them:
The header cannot be configured: a request whose User-Agent starts with Calisero-SMS-Symfony/ always comes from
this bundle.
Sender ID (Alphanumeric) Requirements
Custom alphanumeric sender IDs (the from parameter) must be approved by Calisero before they can be used in
production. With an unapproved sender the API may refuse the message (422), or the gateway substitute a default one.
- Length: 3โ11 characters; letters, digits, spaces, hyphens (
-) and dots (.), as theSenderIdconstraint checks. - No fully numeric sender IDs unless explicitly provisioned; avoid trademarks you do not own.
- To request one: Calisero dashboard โ Sender IDs, with a short business justification.
- In multi-tenant applications, map tenants to approved senders: never take a sender from user input.
Without an approved sender, leave from out and Calisero assigns its default one.
Credit Monitoring
Set thresholds, and every delivery callback compares the account's remaining balance with them:
At or below the critical threshold only CreditCriticalEvent is dispatched. Leave a threshold null to disable it.
The webhook must be enabled.
Daily Sending Limit
Every Calisero account has its own daily sending limit. Each real message counts once, whatever its number of parts,
verification codes included; test messages (a sandbox account or API key) never count. The day ends at midnight,
Romania time (Europe/Bucharest).
Where to read it
When it is reached, the API answers 429 and the SDK throws DailyLimitExceededException: nothing was sent and
nothing billed. It extends RateLimitedException, so catch it first to tell it from the request rate limit (240
requests a minute):
Before it is reached, set a threshold and listen for DailyLimitLowEvent, dispatched by the delivery webhook
(which must be enabled) whenever the messages left today are at or below it:
To raise the limit, contact Calisero.
Error Handling
The SDK throws a typed exception for every error status, carrying the API's own message:
| Exception | Status |
|---|---|
UnauthorizedException |
401 |
ForbiddenException |
403 |
NotFoundException |
404 |
ValidationException |
422 |
DailyLimitExceededException (extends RateLimitedException) |
429, code: daily_limit_exceeded |
RateLimitedException |
429 |
ServerException |
500, 502, 503, 504 |
ApiException (the parent of all of the above) |
any other error |
TransportException |
no answer (connection failed, timeout) |
All in Calisero\Sms\Exceptions. Every ApiException has getTraceId(): quote it to Calisero support, or look the
request up in the dashboard under Developers โ Debug. The bundle's own \InvalidArgumentException (a missing, unknown
or wrongly typed parameter), \RuntimeException (a missing API key or account ID) and \LogicException (the webhook
enabled without its route) report a mistake before any request is made. Through the Notifier, API errors arrive
wrapped in a CaliseroTransportException.
Coming from calisero/laravel-sms
The client's methods and parameters are the same as the Laravel package's:
| Laravel package | This bundle |
|---|---|
Calisero::sendSms([...]) (facade) |
SmsClientInterface::sendSms([...]) (autowired) |
config/calisero.php, CALISERO_* variables |
config/packages/calisero.yaml, %env(CALISERO_*)% |
Notification channel calisero, SmsMessage |
Notifier transport calisero://, SmsMessage + CaliseroOptions |
MessageSent, MessageDelivered, MessageFailed |
MessageSentEvent, MessageDeliveredEvent, MessageFailedEvent |
$event->messageData, $event->message() |
$event->getPayload(), $event->getMessage() |
CreditLow, CreditCritical, DailyLimitLow |
CreditLowEvent, CreditCriticalEvent, DailyLimitLowEvent |
Rule::phoneE164(), Rule::senderId() |
#[PhoneE164], #[SenderId] |
php artisan calisero:โฆ |
bin/console calisero:โฆ (same names and options) |
| Route registered when enabled | Routes imported once (type: calisero), loaded when enabled |
Unlike the Laravel package, the bundle refuses unknown sendSms() parameters instead of ignoring them.
Testing Your Application
Mock SmsClientInterface in unit tests. In functional tests, replace the bundle's transport, the service
calisero.http_transport, with a stub implementing Calisero\Sms\Contracts\HttpClientInterface: everything else
(the client, the SDK's services, the Notifier transport) then runs for real, without a request leaving the test.
The bundle's own tests work the same way: see TESTING.md.
Examples
Copy-paste friendly examples for a Symfony application live in examples/:
| Scenario | File |
|---|---|
| Configuration: bundle, routes, Notifier | examples/config/ |
| Send from a service, with error handling | examples/send_sms_service.php |
| Shortened URLs, scheduling, the daily limit left | examples/send_sms_with_shortened_urls.php |
| Send through the Notifier | examples/notifier_texter.php |
| A notification sent by SMS | examples/notification_example.php |
| Two-factor authentication controller | examples/verification_controller.php |
| Delivery webhook listeners | examples/webhook_listeners.php |
| Credit monitoring listeners | examples/credit_monitoring_listeners.php |
Daily sending limit (account, refusal, DailyLimitLowEvent) |
examples/daily_limit.php |
| Validation constraints | examples/validation_constraints.php |
| The SDK's services: opt-outs, verifications | examples/sdk_services.php |
| One API key per tenant | examples/multi_tenant_client.php |
See the Examples README.
Development
The development environment runs in Docker: nothing but Docker is needed on your machine.
automation/README.md the environment, including a demo application to try the bundle against the real API.
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security
If you discover any security-related issues, please email [email protected] instead of using the issue tracker.
License
The MIT License (MIT). Please see the License File for more information.
Support
- ๐ Documentation: https://docs.calisero.ro
- ๐ Calisero PHP SDK: calisero/calisero-php
- ๐ Issues: GitHub Issues
- ๐ง Email: [email protected]
All versions of calisero-symfony with dependencies
calisero/calisero-php Version ^2.3
symfony/console Version ^6.4 || ^7.0 || ^8.0
symfony/framework-bundle Version ^6.4 || ^7.0 || ^8.0