# tueen/telegram - Full Documentation Context
# The Royal Telegram Bot SDK for Modern PHP (PHP 8.4+)
# Official Repository: https://github.com/tueen/telegram/
# Documentation Base URL: https://tueen.github.io/telegram/
#
# Note for AI Models: If any detail, method signature, or edge-case behavior is not
# fully covered in this documentation, consult the official GitHub repository:
# https://github.com/tueen/telegram/
# ==============================================================================
# File: /guide/getting-started
# Section: Getting Started > Getting Started
# ==============================================================================
# Getting Started
## Introduction
> *The Royal Telegram Bot SDK for Modern PHP*
`tueen/telegram` is a modern Telegram Bot API client for PHP. It provides strict typing, full IDE autocompletion, and forward compatibility with the Telegram Bot API specification.
---
## Requirements
- **PHP 8.4+**
- **Composer 2.x**
- Extensions: `json`, `curl` or stream wrappers
---
## Installation
Install the package via Composer:
```bash
composer require tueen/telegram
```
---
## Client Initialization
### Direct Initialization
If you just need standard defaults:
```php
use Tueen\Telegram\Telegram;
$bot = new Telegram('YOUR_BOT_TOKEN');
```
### Fluent ConfigBuilder
For advanced options (proxies, timeouts, error modes, test environment):
```php
use Tueen\Telegram\Telegram;
$config = Telegram::create('YOUR_BOT_TOKEN')
->withTimeout(30.0)
->withProxy('http://127.0.0.1:10809')
->withRetryCount(3)
->withErrorObjectMode()
->build();
$bot = new Telegram($config);
```
::: tip Building a Standalone Bot with a Web Dashboard?
If you're building a standalone bot from scratch, `tueen/telegram` also includes **`App`** โ a zero-config application orchestrator featuring a 3-file project template, automated session storage, CLI tools, and a real-time **Web Setup & Management Dashboard**. See the [Zero-Config App & Web Dashboard Guide](./app).
:::
---
## Supported Bot API Version
You can inspect the supported Telegram Bot API version programmatically at runtime:
```php
use Tueen\Telegram\Telegram;
echo Telegram::BOT_API_VERSION;
```
---
## First API Call: `getMe`
```php
$me = $bot->getMe();
if ($me->ok()) {
echo "Bot ID: {$me->id}\n";
echo "Bot Username: @{$me->username}\n";
}
```
---
## Sending a Message
You can call any Telegram Bot API method directly using named arguments:
```php
use Tueen\Telegram\Enums\ParseMode;
$message = $bot->sendMessage(
chatId: 123456789,
text: 'Hello from tueen/telegram!',
parseMode: ParseMode::HTML
);
echo "Message sent with ID: {$message->messageId}\n";
```
::: tip Always Use Named Parameters
In `tueen/telegram`, **always invoke API methods using PHP named parameters** (e.g. `chatId: ...`, `text: ...`, `parseMode: ...`). Named parameters make your code self-documenting, protect against argument reordering in future Bot API updates, and allow you to omit contextual parameters (like `chatId`) seamlessly.
:::
---
## Handling Incoming Updates
Choose between **Webhook Mode** (recommended for production) and **Polling Mode** (ideal for CLI/local testing):
```php
use Tueen\Telegram\Telegram;
use Tueen\Telegram\Types\Update;
// Run the bot using the configured running mode (Webhook or Polling)
$bot->run(function (Update $update, Telegram $bot) {
$message = $update->findMessage();
if ($message !== null) {
$bot->sendMessage(
chatId: $message->chat->id,
text: "Received: {$message->findAnyText()}"
);
}
});
```
---
## Next Steps
Now that your bot is running, explore the core essentials:
- ๐ฌ **[Calling Methods & Types](./methods-and-types):** Learn how to invoke Bot API methods with named arguments and contextual auto-injection.
- โจ๏ธ **[Interactive Keyboards](./keyboards):** Build Inline and Reply keyboards with fluent builders.
- ๐ **[Text Formatting & Escaping](./formatting):** Format messages safely using HTML and MarkdownV2 without entity parsing errors.
- ๐งญ **[Update Routing & Attributes](./routing):** Organize commands, regex patterns, and callback queries with attributes and controllers.
- ๐ **[Running Modes](./running-modes):** Dive deeper into Webhook, Polling, and AutoMode configurations.
# ==============================================================================
# File: /guide/telegram-client
# Section: Getting Started > The Telegram Client
# ==============================================================================
# The Telegram Client
The `Tueen\Telegram\Telegram` class (conventionally instantiated as `$bot`) is the primary entry point and client facade of the `tueen/telegram` library. It provides a unified, strictly typed interface for sending Bot API requests, listening for updates, managing multi-step flows, and resolving contextual parameters.
---
## ๐ 1. Instantiation
You can initialize the client either directly with your bot token or via a pre-built `Config` object:
### Basic Instantiation
```php
use Tueen\Telegram\Telegram;
$bot = new Telegram('YOUR_BOT_TOKEN');
```
### Advanced Instantiation with `ConfigBuilder`
For production environments requiring custom timeouts, proxy connections, retry policies, or test servers:
```php
use Tueen\Telegram\Telegram;
$config = Telegram::create('YOUR_BOT_TOKEN')
->withTimeout(30.0)
->withRetryCount(3)
->build();
$bot = new Telegram($config);
```
> For a full list of networking and runtime settings, see the [Configuration Reference](./configuration).
---
## โก 2. The Three Primary Roles of `$bot`
In your application, the `$bot` instance serves three essential purposes:
### 1. Direct Bot API Client
You can invoke all Telegram Bot API methods dynamically with full IDE autocompletion and named arguments:
```php
$bot->sendMessage(
chatId: 123456789,
text: 'Hello from tueen/telegram!'
);
```
> Learn more in [Calling Methods & Types](./methods-and-types).
### 2. Update Router & Dispatcher
The client listens for incoming updates and dispatches them to closures, classes, or attribute controllers:
```php
// Register a command route:
$bot->onCommand('start', function (Update $update, Telegram $bot) {
$bot->sendMessage(text: 'Welcome! How can I assist you today?');
});
// Run the bot via Webhook, Polling, or AutoMode:
$bot->run();
```
> Learn more in [Update Routing & Attributes](./routing) and [Running Modes](./running-modes).
### 3. Contextual Entity Resolver (PHP 8.4 Property Hooks)
During update processing, `$bot` automatically tracks the active update and provides instant shortcuts to chat, user, and message identifiers via modern PHP 8.4 Property Hooks:
```php
$chatId = $bot->chatId; // e.g. 123456789
$userId = $bot->userId; // e.g. 987654321
$message = $bot->message; // Current ?Message instance
```
> **Tip:** Legacy method calls such as `$bot->chatId()` continue to work with a deprecation notice for full backward compatibility. Learn more in [Update & Message Helpers](./update-and-message-helpers).
---
## ๐ก 3. Client Best Practices
Here are the recommended patterns to keep in mind when working with the `$bot` instance:
### 1. Always Use PHP Named Arguments
Telegram Bot API methods accept many optional parameters. Named arguments protect your code against parameter order changes and enable **Contextual Auto-Injection** (where repetitive parameters like `chatId` are automatically resolved from the active update):
```php
// โ
RECOMMENDED: Order-independent, self-documenting, auto-injects chatId:
$bot->sendMessage(
text: 'Order confirmed successfully.',
parseMode: ParseMode::HTML
);
// โ AVOID: Fragile positional arguments with trailing nulls:
$bot->sendMessage(123456, 'Order confirmed successfully.', null, null, null, null, 'HTML');
```
### 2. Reusing a Single Client Instance
`Telegram` is lightweight and designed to be created once. In modern frameworks (such as Laravel, Symfony, or Slim), register `$bot` as a singleton in your dependency injection container:
```php
// Example: Registering as a singleton in a PSR-11 container:
$container->singleton(Telegram::class, function () {
return new Telegram(getenv('TELEGRAM_BOT_TOKEN'));
});
```
### 3. Organizing Complex Bots with Controllers
For simple bots, defining routes with closures directly in your script is quick and convenient. As your application grows, organize your commands and actions into dedicated Controller classes:
```php
// Register a dedicated controller class decorated with routing attributes:
$bot->registerController(OrderController::class);
```
> See [Attribute-Driven Controllers](./routing#3-attribute-driven-controllers) for examples of using `#[OnCommand]` and `#[OnCallbackQuery]`.
---
## ๐๏ธ 4. Architecture & Decoupled Subsystems
Under the hood, `Telegram` is not a monolithic class holding all bot logic. Instead, it uses a **modular composition architecture** that delegates work to single-responsibility components:
```mermaid
flowchart LR
subgraph InboundTrack ["Inbound Pipeline (Receiving Updates)"]
direction TB
TelegramIn[("Telegram Bot API")] -->|Webhook or Polling| Runner["RunningMode"]
Runner --> Dispatcher["UpdateDispatcher"]
Dispatcher --> UpdatePipes["Global Middlewares"]
UpdatePipes --> PriorityCheck{"Priority Route?"}
PriorityCheck -->|Yes| ConsultFlow{"Flow Allows?"}
ConsultFlow -->|Allowed| PriorityHandler["Priority Handler"]
PriorityCheck -->|No| FlowCheck{"Active Flow?"}
ConsultFlow -->|Vetoed| FlowCheck
FlowCheck -->|Yes| FlowStep["Execute Flow Step"]
FlowStep --> FlowDecision{"Step Handled?"}
FlowDecision -->|Yes| FlowDone["Save Flow State"]
FlowDecision -->|Pass Through| RouterEngine["Router & Route Groups"]
FlowCheck -->|No| RouterEngine
RouterEngine -->|Matched Route| RoutePipes["Route Middlewares"]
RoutePipes --> AutoWire["Auto-Wiring & Type Casting"]
AutoWire --> RouteHandlers["Route Handlers"]
RouterEngine -->|Unmatched| Fallback["Fallback Handler"]
end
subgraph OutboundTrack ["Outbound Pipeline (Sending Requests)"]
direction TB
Context["ContextResolver (Auto-Inject)"]
Context --> HttpPipes["HTTP Middlewares (Retry, RateLimit)"]
HttpPipes --> Client["TelegramClient (PSR-18)"]
Client --> Pool["Persistent cURL Connection Pool"]
Pool --> TelegramOut[("Telegram Bot API")]
end
%% Handlers send API requests back through Outbound Pipeline
PriorityHandler -->|Reply or Send| Context
FlowStep -->|Reply or Send| Context
RouteHandlers -->|Reply or Send| Context
Fallback -.->|Optional Reply| Context
DirectCall["Queue Worker or Scoped Worker"] -.-> Context
```
### When to Access Subsystems Directly:
Most of the time, you interact exclusively with the `$bot` facade. However, you can access underlying subsystems when building low-level extensions, worker queues, persistent daemons, or custom integrations:
```php
use Tueen\Telegram\Telegram;
use Tueen\Telegram\Client\CurlHttpClient;
$bot = new Telegram('YOUR_BOT_TOKEN');
// 1. Pure HTTP client (useful for worker queue jobs that only call API methods without processing updates):
$client = $bot->client;
$me = $client->getMe();
// 2. Underlying router instance (inspect or manipulate route collections directly):
$router = $bot->router;
// 3. Flow state manager (for custom state stores or manual session manipulation):
$flowManager = $bot->flowManager;
// 4. Context parameter resolver (manual default binding or contextual entity inspection):
$context = $bot->context;
$activeChatId = $context->chatId;
// 5. Scoped Client for Persistent Worker Engines (FrankenPHP, RoadRunner, Swoole, Laravel Octane):
// Clones the client into a thread-safe, isolated instance with dedicated context per update:
$scopedBot = $bot->scoped($incomingUpdate);
// 6. PSR-11 Dependency Injection Container:
// Connect an external DI container (e.g. Laravel Container, PHP-DI, Symfony DI) for controller auto-wiring:
$bot->container = $myContainer;
// 7. Persistent HTTP Connection Pool Management:
// Check cached TCP/TLS keep-alive handles or flush them during worker maintenance:
$poolCount = CurlHttpClient::getPoolCount();
CurlHttpClient::closeAllHandles();
```
---
## ๐งญ 5. Complete Method & Property Reference
Below is the comprehensive catalog of all constants, properties, and methods provided on the `Telegram` class, grouped by architectural responsibility. Every item features syntax-highlighted signatures, parameter types, default values, and status badges.
### ๐๏ธ Class Constants & Public Properties
---
### A. Initialization & Testing Factory
---
### B. Execution & Running Modes
---
### C. Update Pipeline & Middleware
---
### D. Exception & Error Handling
---
### E. Update Routing & Controllers
---
### F. Multi-Step Conversational Flows
---
### G. Contextual Accessors & Property Hooks
---
### H. Bot API Method Calls & File Operations
---
### I. Underlying Subsystems & Dependency Injection
# ==============================================================================
# File: /guide/configuration
# Section: Getting Started > Configuration
# ==============================================================================
# Configuration
`tueen/telegram` features an immutable configuration architecture paired with an expressive, fluent `ConfigBuilder`. Every parameter โ from networking timeouts and persistent connection pooling to progress event hooks, running modes, and error handling strategies โ is strictly typed and discoverable via IDE autocompletion.
---
## ๐ ๏ธ 1. Creating Configuration
You can configure the client using `Telegram::create('TOKEN')`, `Config::builder('TOKEN')`, or by instantiating `Config` directly:
### Fluent Builder via `Telegram::create()`
The recommended and most expressive approach:
```php
use Tueen\Telegram\Telegram;
use Tueen\Telegram\Enums\ErrorHandlingMode;
use Tueen\Telegram\Running\WebhookMode;
$config = Telegram::create('123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11')
->withTimeout(30.0)
->withConnectTimeout(10.0)
->withProxy('http://127.0.0.1:10809')
->withRetryCount(3)
->withErrorObjectMode()
->withRunningMode(new WebhookMode(secretToken: 'my_super_secret_token'))
->build();
$bot = new Telegram($config);
```
### Direct Instantiation of `Config`
For configuration files or dependency injection factories:
```php
use Tueen\Telegram\Config;
$config = new Config(
botToken: 'YOUR_BOT_TOKEN',
timeout: 45.0,
connectTimeout: 5.0,
retryCount: 5,
testEnvironment: false
);
$bot = new Telegram($config);
```
---
## ๐งญ 2. Detailed Configuration Reference
Below is the exhaustive catalog of all configuration options available on `ConfigBuilder` and `Config`.
### ๐ Server & Credentials
---
### โก HTTP Client, Networking & Transport
---
### ๐ Progress Tracking Callbacks
#### Progress Callback Signature
Both progress callbacks receive three informative parameters:
```php
$config = Telegram::create('YOUR_TOKEN')
->withUploadProgress(function (int $bytesUploaded, int $totalBytes, float $percentage): void {
printf("Uploading: %5.1f%% (%d / %d bytes)\r", $percentage, $bytesUploaded, $totalBytes);
})
->withDownloadProgress(function (int $bytesDownloaded, int $totalBytes, float $percentage): void {
printf("Downloading: %5.1f%% (%d / %d bytes)\r", $percentage, $bytesDownloaded, $totalBytes);
})
->build();
```
---
### ๐ก๏ธ Error Handling Strategy
---
### ๐ Running Modes & Resiliency
---
### ๐ Conversation Flow & Orchestration
---
### ๐ Logging & Observability
---
### ๐ Dependency Injection & Container
---
### โก Client Instantiation Shortcuts
---
## ๐งฌ 3. Immutability & Modern PHP 8.5 Patterns
The `Config` class is completely immutable. Every `with...()` method creates and returns a clean new instance using PHP 8.5's `clone with` and `#[\NoDiscard]` attributes:
```php
$baseConfig = Telegram::create('YOUR_TOKEN')
->withTimeout(30.0)
->build();
// Derive a specialized configuration without modifying $baseConfig:
$fastConfig = $baseConfig
->withTimeout(5.0)
->withRetryCount(1);
// $baseConfig->timeout remains 30.0
// $fastConfig->timeout is 5.0
```
---
## ๐ 4. Standard URI & URL Resolution Properties
`Config` provides convenient properties powered by PHP 8.4 property hooks to resolve target URLs and standards-compliant `Uri\Rfc3986\Uri` objects:
```php
$config = Telegram::create('123456:ABC')->build();
echo $config->baseApiUrl;
// "https://api.telegram.org/bot123456:ABC"
$uri = $config->apiUri;
echo $uri->getHost(); // "api.telegram.org"
echo $uri->getPath(); // "/bot123456:ABC"
```
---
## ๐ก 5. Real-World Configuration Recipes
### Recipe 1: Production Webhook with Persistent cURL & Monolog
```php
use Tueen\Telegram\Telegram;
use Tueen\Telegram\Running\WebhookMode;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$logger = new Logger('telegram');
$logger->pushHandler(new StreamHandler(storage_path('logs/telegram.log')));
$config = Telegram::create($_ENV['TELEGRAM_BOT_TOKEN'])
// High-performance persistent cURL handles
->withCurlClient(persistent: true)
->withTimeout(15.0)
->withConnectTimeout(3.0)
->withRetryCount(3)
->withLogger($logger)
// Production Webhook mode with secret token verification
->withRunningMode(new WebhookMode(secretToken: $_ENV['TELEGRAM_WEBHOOK_SECRET']))
->build();
$bot = new Telegram($config);
```
---
### Recipe 2: High-Volume CLI Long-Polling Worker
```php
use Tueen\Telegram\Telegram;
use Tueen\Telegram\Running\PollingMode;
$polling = new PollingMode(
timeout: 50, // Long-polling timeout in seconds
limit: 100, // Max updates per batch
errorBackoffSeconds: 5 // Auto-backoff on network interruptions
);
// Enable multi-process concurrency on CLI:
$polling->forkProcess(true);
$config = Telegram::create(getenv('BOT_TOKEN'))
->withTimeout(60.0) // Must exceed polling timeout
->withRunningMode($polling)
->build();
$bot = new Telegram($config);
$bot->run(App\Handlers\UpdateHandler::class);
```
---
### Recipe 3: Tor / SOCKS5 Proxy for Restricted Environments
```php
use Tueen\Telegram\Telegram;
$config = Telegram::create('YOUR_TOKEN')
->withProxy('socks5h://127.0.0.1:9050')
->withTimeout(60.0)
->withConnectTimeout(15.0)
->withRetryCount(5)
->build();
$bot = new Telegram($config);
```
---
### Recipe 4: Self-Hosted Local Bot API Server (2GB Uploads)
```php
use Tueen\Telegram\Telegram;
$config = Telegram::create('YOUR_TOKEN')
// Point to your local Telegram Bot API Docker container:
->withApiServer('http://telegram-bot-api:8081')
->withTimeout(300.0) // 5 minutes for massive files
->build();
$bot = new Telegram($config);
```
---
### Recipe 5: Laravel & Symfony PSR-11 Container Integration
```php
use Tueen\Telegram\Telegram;
// In Laravel service provider or Symfony dependency injection:
$config = Telegram::create(config('services.telegram.token'))
->withContainer(app()) // Injects Laravel's IoC container
->build();
$bot = new Telegram($config);
// Handlers and Flow classes now automatically resolve with full dependency injection!
$bot->run(App\Handlers\OnboardingHandler::class);
```
---
### Recipe 6: Adaptive AutoMode Execution (Unified CLI & HTTP Webhook)
```php
use Tueen\Telegram\Telegram;
use Tueen\Telegram\Running\PollingMode;
use Tueen\Telegram\Running\WebhookMode;
$bot = Telegram::create($_ENV['BOT_TOKEN'])
->withPollingMode(new PollingMode(timeout: 45))
->withWebhookMode(new WebhookMode(secretToken: $_ENV['WEBHOOK_SECRET']))
->withAutoMode(autoDeleteWebhook: true)
->client();
// In CLI (php bot.php): executes PollingMode
// In Web Server (POST /webhook): executes WebhookMode
$bot->run(App\Handlers\BotHandler::class);
```
# ==============================================================================
# File: /guide/methods-and-types
# Section: Core Essentials > Calling Methods & Types
# ==============================================================================
# Calling Methods & Types
`tueen/telegram` provides native, strictly typed support for Telegram Bot API methods and types with full IDE autocompletion, enums, computed property hooks, and forward-compatible fallbacks.
---
## ๐ 1. Method Invocations
You can invoke API methods either dynamically or via dedicated Method classes:
### Dynamic Method Calls (Recommended)
Call any Telegram method directly using camelCase and named arguments:
```php
use Tueen\Telegram\Enums\ParseMode;
$message = $bot->sendMessage(
chatId: 12345678,
text: "Hello from Tueen!",
parseMode: ParseMode::HTML // Direct Enum instance
);
```
::: tip Recommended: Use PHP Named Arguments
In `tueen/telegram`, **always invoke API methods using PHP named arguments** (`paramName: $value`).
```php
// โ
RECOMMENDED: Explicit, readable, and order-independent
$bot->sendMessage(
text: "Welcome to our bot!",
parseMode: ParseMode::HTML
);
// โ AVOID: Fragile positional arguments
$bot->sendMessage(123456, "Hello", null, null, null, null, 'HTML');
```
#### Why Named Arguments Are Recommended:
1. **Contextual Auto-Injection:** Because parameters like `chatId`, `businessConnectionId`, and query IDs are automatically inferred from the active update, named arguments allow you to supply only the parameters you need (e.g. `text: "..."`) without worrying about parameter position.
2. **No Trailing Nulls:** Telegram Bot API methods accept dozens of optional parameters. Named arguments eliminate the need for long lists of `null` placeholders.
3. **Forward-Compatibility:** When Telegram adds new parameters to Bot API methods, named arguments prevent positional shift bugs.
4. **Self-Documenting:** Code is clear and easy to read during code review.
:::
Full IDE autocompletion and parameter docblocks are provided via the `@mixin \Tueen\Telegram\Contracts\TelegramMethods` contract.
### Method Objects (For Custom Pipelines & Architecture)
Every Telegram Bot API method has a dedicated class in `Tueen\Telegram\Methods`:
```php
use Tueen\Telegram\Methods\SendMessage;
$method = new SendMessage(
chatId: 12345678,
text: "Hello from Tueen!"
);
$message = $bot->send($method);
```
### Forward-Compatible Unknown / New Parameters
If Telegram introduces a new parameter before the library updates, you can pass it immediately as a named parameter or via variadic `$extra`:
```php
// If Telegram adds 'new_feature_flag' tomorrow:
$message = $bot->sendMessage(
chatId: 12345678,
text: "Testing new feature",
newFeatureFlag: true // Seamlessly passed to the API
);
```
### Passing Complex & Nested Types (Objects or Arrays)
Methods that accept complex structures (such as `reply_markup`, `link_preview_options`, or `reply_parameters`) accept both **strongly-typed Type objects** and **associative arrays**:
#### A. Using Strongly-Typed Type Objects
```php
use Tueen\Telegram\Types\InlineKeyboardMarkup;
use Tueen\Telegram\Types\InlineKeyboardButton;
use Tueen\Telegram\Types\LinkPreviewOptions;
$bot->sendMessage(
chatId: 123456,
text: "Check out Tueen:",
replyMarkup: new InlineKeyboardMarkup(
inlineKeyboard: [
[
new InlineKeyboardButton(text: '๐ Website', url: 'https://tueen.dev'),
new InlineKeyboardButton(text: 'โญ GitHub', url: 'https://github.com/tueen/telegram'),
]
]
),
linkPreviewOptions: new LinkPreviewOptions(isDisabled: true)
);
```
#### B. Using Structured Arrays
You can also pass arrays; the library automatically normalizes, converts nested objects/enums, and serializes them:
```php
$bot->sendMessage(
chatId: 123456,
text: "Choose an option:",
replyMarkup: [
'inline_keyboard' => [
[
['text' => 'Option 1', 'callback_data' => 'opt_1'],
['text' => 'Option 2', 'callback_data' => 'opt_2'],
]
]
]
);
```
---
## ๐๏ธ 2. The Universal Base `Type` Class
All Telegram Bot API response models (over 400 types such as `Message`, `User`, `Chat`, `Update`) extend the base class `Tueen\Telegram\Types\Type`.
### Core Features of `Type`:
1. **Universal `ok(): bool` and `isOk(): bool`:** Every response object guarantees a boolean success check. Real response types always return `true`, while `Tueen\Telegram\Types\Error` objects return `false`.
2. **Dual Property Access:** Access properties using camelCase (`$message->messageId`) or snake_case (`$message->message_id`).
3. **`ArrayAccess` Support:** Access properties using bracket syntax (`$message['chat']['id']`).
4. **`JsonSerializable` & Stringable:** Passing any type to `json_encode($type)` or `(string)$type` produces clean, unescaped JSON.
5. **Array Transformation:** Call `toArray()` or `toJson()` to recursively convert the type and all nested objects into primitive arrays or JSON strings.
6. **Bulletproof Forward-Compatibility:** If Telegram introduces new fields in the future, they are preserved inside `$extra` and accessible dynamically. If Telegram returns an entirely new, unmapped object type, Tueen falls back gracefully to the base `Type` class without crashing.
---
## โ๏ธ 3. The Base `Method` Class
Every Telegram Bot API method is represented by a class extending `Tueen\Telegram\Methods\Method`.
### Core Responsibilities of `Method`:
1. **Endpoint Resolution:** Defines the API method name (e.g. `'sendMessage'`).
2. **Payload Serialization:** Prepares and filters parameters, converting nested `Type` objects, Enums, and JSON-encoded fields.
3. **Multipart & File Upload Detection:** Inspects parameters for `InputFile` instances. If any file is present, `isMultipart()` returns `true` and the HTTP client uploads via `multipart/form-data`.
4. **Typed Return Resolution:** Defines `returnType()` so the HTTP client automatically instantiates and deserializes the response into the appropriate concrete `Type` (e.g. `Message::class`, `BooleanResult::class`).
---
## ๐งฉ 4. Concerns & Computed Helpers
Rather than bloating generated classes, helper methods and computed properties are organized into reusable traits inside `Tueen\Telegram\Types\Concerns\`:
> For a complete guide and code examples of these helpers, see [Update & Message Helpers](./update-and-message-helpers).
---
## ๐ฆ 5. Custom Result Wrapper Types
When Telegram Bot API methods return primitives (such as boolean values, integer counts, or plain strings), Tueen wraps them in dedicated classes inside `Tueen\Telegram\Types\Custom\` so you can always check `$result->ok()`:
- **`BooleanResult`** (e.g. from `setWebhook`, `deleteMessage`, `pinChatMessage`): Wraps a `bool $value`.
- **`IntegerResult`** (e.g. from `getChatMemberCount`): Wraps an `int $value`.
- **`StringResult`** (e.g. from `exportChatInviteLink`, `createInvoiceLink`): Wraps a `string $value`.
- **`ArrayResult`** (e.g. from `getUpdates`, `forwardMessages`): An iterable collection wrapping arrays of typed objects.
- **`InputFile`**: Universal multipart file wrapper for uploads (`fromPath`, `fromResource`, `fromString`, `fromStream`).
---
## ๐งญ 6. Methods & Types API Reference
Below is the comprehensive catalog of core methods and properties across the Base `Type`, Base `Method`, Contextual Auto-Injection, and Custom Wrapper types.
### ๐๏ธ Base `Type` Class (`Tueen\Telegram\Types\Type`)
---
### โ๏ธ Base `Method` Class (`Tueen\Telegram\Methods\Method`)
---
### ๐ฏ Contextual Parameter Auto-Injection
---
### ๐ฆ Custom Wrapper Types (`Tueen\Telegram\Types\Custom\`)
# ==============================================================================
# File: /guide/keyboards
# Section: Core Essentials > Fluent Keyboard Builders
# ==============================================================================
# Fluent Keyboard Builders
`tueen/telegram` provides modern, fluent builders for creating both **Inline Keyboards** (`InlineKeyboardMarkup`) and **Reply Keyboards** (`ReplyKeyboardMarkup` / `ReplyKeyboardRemove`).
---
## ๐ 1. Inline Keyboards (`InlineKeyboard`)
Inline keyboards appear directly attached to a specific message. Buttons trigger actions such as callback queries, opening URLs, launching Telegram Web Apps, or initiating payments.
### Basic Usage
Use `InlineKeyboard::make()` followed by rows and buttons:
```php
use Tueen\Telegram\Keyboards\InlineKeyboard;
$keyboard = InlineKeyboard::make()
->row()
->callback('๐ Like', 'action:like')
->callback('๐ Dislike', 'action:dislike')
->row()
->url('๐ Visit Website', 'https://tueen.org')
->build();
$bot->sendMessage(
chatId: $chatId,
text: 'Please cast your vote:',
replyMarkup: $keyboard
);
```
### Supported Button Types
`InlineKeyboard` supports all Telegram Bot API inline button types:
```php
$keyboard = InlineKeyboard::make()
// Callback data button
->callback('Confirm', 'order:confirm')
// External URL button
->url('Documentation', 'https://tueen.org/docs')
// Telegram Mini App (Web App)
->webApp('Open App', 'https://webapp.tueen.org')
// Seamless Telegram Login URL
->loginUrl('Login with Telegram', 'https://example.com/auth/telegram')
// Copy text to clipboard on tap
->copyText('Copy Promo Code', 'TUEEN-VIP-2026')
// Telegram Payments Pay button
->pay('๐ณ Pay Now')
// Prompt user to select a chat and paste inline query
->switchInlineQuery('Share with friends', 'check out this bot!')
// Insert inline query into current chat
->switchInlineQueryCurrentChat('Search here', 'query')
// Prompt user to select a specific type of chat (channel, group, etc.)
->switchInlineQueryChosenChat('Send to Channel', $chosenChatConfig)
->build();
```
### Auto-Chunking Buttons (`chunk`)
Instead of manually breaking rows with `->row()`, you can add all buttons and let `chunk($size)` automatically arrange them into neat rows:
```php
$builder = InlineKeyboard::make();
for ($i = 1; $i <= 9; $i++) {
$builder->callback((string) $i, "num:{$i}");
}
// Automatically divides into 3 rows of 3 buttons:
$grid = $builder->chunk(3)->build();
```
---
## โจ๏ธ 2. Reply Keyboards (`ReplyKeyboard`)
Reply keyboards replace the user's regular keyboard with custom option buttons.
### Basic Usage
```php
use Tueen\Telegram\Keyboards\ReplyKeyboard;
$keyboard = ReplyKeyboard::make()
->resize() // Automatically fit buttons neatly
->placeholder('Select an option...')
->row()
->text('๐ฆ My Orders')
->text('โ๏ธ Settings')
->row()
->requestContact('๐ฑ Share Phone Number')
->requestLocation('๐ Share Location')
->build();
$bot->sendMessage(
chatId: $chatId,
text: 'Main Menu:',
replyMarkup: $keyboard
);
```
### Supported Features & Button Types
```php
$keyboard = ReplyKeyboard::make()
// Keyboard behaviors:
->resize(true) // Scale keyboard vertically to button count
->oneTime(true) // Hide keyboard after first button press
->persistent(true) // Keep keyboard visible even when soft keyboard is closed
->selective(true) // Show only to specific target users (e.g. in groups)
->placeholder('Type message...')// Placeholder text in input bar
// Button types:
->text('Plain Text')
->requestContact('Send Contact')
->requestLocation('Send Location')
->requestPoll('Create a Quiz', type: 'quiz')
->requestUsers('Select Friends', requestId: 1, userIsBot: false, maxQuantity: 5)
->requestChat('Select Channel', requestId: 2, chatIsChannel: true)
->webApp('Open Mini App', 'https://webapp.tueen.org')
->build();
```
### Dismissing Keyboards (`ReplyKeyboard::remove`)
To hide an active reply keyboard from the user's interface:
```php
$bot->sendMessage(
chatId: $chatId,
text: 'Keyboard dismissed.',
replyMarkup: ReplyKeyboard::remove()
);
```
---
## ๐งญ 3. Keyboard Builders API Reference
Below is the complete reference of methods available on `InlineKeyboard` and `ReplyKeyboard`.
### ๐ `InlineKeyboard` Builder (`Tueen\Telegram\Keyboards\InlineKeyboard`)
---
### โจ๏ธ `ReplyKeyboard` Builder (`Tueen\Telegram\Keyboards\ReplyKeyboard`)
# ==============================================================================
# File: /guide/formatting
# Section: Core Essentials > Text Formatting & Escaping
# ==============================================================================
# Text Formatting & Escaping
Telegram supports rich text formatting via **HTML**, **MarkdownV2**, and legacy **Markdown**. However, unescaped characters (such as `_`, `*`, `[`, `<`, `>`, `&`) frequently cause Telegram API errors like `400 Bad Request: can't parse entities`.
`tueen/telegram` provides two complementary utilities strictly adhering to the [Telegram Bot API Formatting Options specification](https://core.telegram.org/bots/api#formatting-options):
1. **`Text` Builder**: A safe, fluent, nesting-capable builder for constructing styled messages.
2. **`Escape` Helper**: Low-level escaping functions for user-generated strings.
---
## ๐จ 1. Fluent `Text` Builder
The `Text` builder lets you chain formatting methods while automatically escaping any dynamic or user-supplied variables.
### Direct API Integration
You can pass `Text` instances directly into Telegram methods like `$bot->sendMessage()` or `$bot->sendPhoto(caption: ...)`. Tueen **automatically casts the text to a string** and **auto-sets `parseMode`** to the builder's mode:
```php
use Tueen\Telegram\Formatting\Text;
// No need to manually cast (string) or specify parseMode:
$bot->sendMessage(
chatId: $chatId,
text: Text::html()
->bold('Invoice #100')->line()
->plain('Total: $50.00')
);
```
### HTML Formatting (`Text::html`)
```php
$message = Text::html()
->bold('Order Confirmation')->line()
->line()
->plain('User: ')->userMention($user->name, $user->id)->line()
->plain('Item: ')->bold($item->name)->line()
->plain('Price: ')->code("$" . number_format($item->price, 2))->line()
->line()
->blockquote('Thank you for shopping with us!')
->line()
->link('Track Package', 'https://track.example.com/12345');
```
### MarkdownV2 Formatting (`Text::markdownV2`)
You can generate MarkdownV2 without manually escaping the 18 reserved Telegram characters:
```php
$message = Text::markdownV2()
->bold('System Alert!')->line()
->spoiler('Secret token inside')->line()
->expandableBlockquote("Log line 1: OK\nLog line 2: WARNING\nLog line 3: SUCCESS")
->line()
->pre("Bold and Italic
$text = Text::html()->bold(fn(Text $t) => $t->italic('Bold and Italic'));
// Bold spoiler in MarkdownV2: *||Secret||*
$text = Text::markdownV2()->bold(fn(Text $t) => $t->spoiler('Secret'));
// Hyperlink with bold label
$link = Text::html()->link(fn(Text $t) => $t->bold('Click Here'), 'https://example.com');
```
---
## ๐ก๏ธ 3. Low-Level Escaping with `Escape`
When constructing strings manually or templating with Blade/Twig, use `Tueen\Telegram\Formatting\Escape`:
```php
use Tueen\Telegram\Formatting\Escape;
// 1. Escaping for HTML
$safeHtml = Escape::html(" & Bob");
// Returns: <script>alert('xss');</script> & Bob
// 2. Escaping for MarkdownV2 (escapes 18 reserved Telegram characters)
$safeMarkdown = Escape::markdownV2("Price is [10.00$] *special*");
// Returns: Price is \[10\.00\$\] \*special\*
// 3. Escaping URL query components
$safeUrl = Escape::urlParam("https://example.com?query=hello world");
```
---
## ๐งญ 4. Formatting API Catalog
Below is the complete reference of methods available on the `Text` builder and `Escape` helper.
### ๐จ `Text` Builder (`Tueen\Telegram\Formatting\Text`)
---
### ๐ก๏ธ `Escape` Helper (`Tueen\Telegram\Formatting\Escape`)
# ==============================================================================
# File: /guide/file-upload-download
# Section: Core Essentials > File Upload & Download
# ==============================================================================
# File Upload & Download
`tueen/telegram` makes file transfers seamless using the `InputFile` wrapper, streaming HTTP transports, and real-time upload/download progress callbacks.
---
## ๐ค 1. File Uploads with `InputFile`
`InputFile` can wrap local files, streams, raw strings, or PSR-7 streams:
### From Local File Path
```php
use Tueen\Telegram\Types\Custom\InputFile;
$photo = InputFile::fromPath('/path/to/cat.jpg');
$bot->sendPhoto(
chatId: 123456,
photo: $photo,
caption: 'My cute cat!'
);
```
### From Stream Resource
```php
$fp = fopen('https://example.com/audio.mp3', 'rb');
$audio = InputFile::fromResource($fp, 'audio.mp3');
$bot->sendAudio(
chatId: 123456,
audio: $audio
);
```
### From Raw In-Memory String
```php
$csv = InputFile::fromString("id,name\n1,Alice\n2,Bob", 'report.csv', 'text/csv');
$bot->sendDocument(
chatId: 123456,
document: $csv
);
```
---
## ๐ผ๏ธ 2. Sending Media Groups (Albums)
To send multiple photos or videos as an album in a single message:
```php
$bot->sendMediaGroup(
chatId: 123456,
media: [
[
'type' => 'photo',
'media' => 'https://example.com/photo1.jpg',
'caption' => 'Holiday album'
],
[
'type' => 'photo',
'media' => 'https://example.com/photo2.jpg',
]
]
);
```
---
## โก 3. Real-Time Upload Progress
You can track upload progress per-request:
```php
use Tueen\Telegram\Methods\SendDocument;
use Tueen\Telegram\Types\Custom\InputFile;
$bot->send(
new SendDocument(
chatId: 123456,
document: InputFile::fromPath('/path/to/large_video.mp4')
),
uploadProgress: function (int $bytesUploaded, int $totalBytes, float $percentage) {
printf("Upload: %d / %d bytes (%.2f%%)\r", $bytesUploaded, $totalBytes, $percentage);
}
);
```
---
## ๐ฅ 4. Streaming File Downloads with Progress
Telegram allows downloading files up to 20MB (or up to 2GB when using a self-hosted Bot API server).
`$bot->downloadFile` downloads directly to a local file or stream without loading the entire payload into RAM:
```php
$bot->downloadFile(
file: 'photos/file_0.jpg', // or File object / file_id
destination: '/local/path/saved.jpg',
progress: function (int $bytesDownloaded, int $totalBytes, float $percentage) {
printf("Downloading: %.2f%%\r", $percentage);
}
);
```
---
## ๐งญ 5. File Transfer API Catalog
Below is the complete reference of file transfer methods and factory functions.
### ๐ฆ `InputFile` Wrapper (`Tueen\Telegram\Types\Custom\InputFile`)
---
### ๐ฅ Client Download APIs (`Telegram`)
# ==============================================================================
# File: /guide/running-modes
# Section: Updates & Routing > Running Modes
# ==============================================================================
# Running Modes
`tueen/telegram` features an elegant execution strategy separating how updates are received from how they are processed.
You can switch between **Webhook Mode** and **Long-Polling Mode** seamlessly. In all modes, incoming updates are resolved, stored in the `$bot->update` property, and dispatched to your handler(s) with full type-safety.
```mermaid
flowchart TD
subgraph PollingComparison ["๐ Long-Polling Mode (CLI / Development / Daemons)"]
direction TB
CLI["๐ป PHP CLI Daemon
php bot.php"] -->|1. getUpdates timeout 30s| TG1[("โ๏ธ Telegram Bot API")]
TG1 -.->|2. Holds HTTP connection until update occurs| CLI
CLI -->|3. Dispatches update to handlers & updates offset| CLI
end
subgraph WebhookComparison ["โก Webhook Mode (Production / Web Servers)"]
direction TB
TG2[("โ๏ธ Telegram Bot API")] -->|1. HTTPS POST /webhook
Header: X-Telegram-Bot-Api-Secret-Token| WebServer["๐ Web Server (Nginx / PHP-FPM / Caddy)"]
WebServer -->|2. Validates secret & payload| TueenWeb["๐ Tueen WebhookMode"]
TueenWeb -->|3. Dispatches Update & returns 200 OK| WebServer
end
subgraph AutoSelection ["๐ช AutoMode (Adaptive Execution)"]
direction LR
Detect{"Detect Environment"}
Detect -->|CLI SAPI detected| PollingComparison
Detect -->|Web SAPI detected| WebhookComparison
end
```
---
## ๐ Unified Execution with `$bot->run()`
The `$bot->run()` method is the central entry point for executing your bot. It inspects the configured `RunningMode` and dispatches incoming updates to your handlers.
### 1. Handler Signature
Handlers receive two parameters:
1. `Update $update` โ The incoming update instance.
2. `Telegram $bot` โ The `Telegram` client instance.
```php
use Tueen\Telegram\Telegram;
use Tueen\Telegram\Types\Update;
$bot = new Telegram('YOUR_BOT_TOKEN');
$bot->run(function (Update $update, Telegram $bot) {
if ($update->message !== null) {
$bot->sendMessage(
chatId: $update->findChat()->id,
text: "Hello! You said: {$update->message->findAnyText()}"
);
}
});
```
### 2. Invokable Handler Classes
Instead of inline closures, you can organize your logic into dedicated classes that implement `__invoke(Update $update, Telegram $bot)`:
```php
namespace App\Handlers;
use Tueen\Telegram\Telegram;
use Tueen\Telegram\Types\Update;
class CommandHandler
{
public function __invoke(Update $update, Telegram $bot): void
{
$message = $update->findMessage();
if ($message?->isCommand && $message->command === 'start') {
$bot->sendMessage(
chatId: $update->findChat()->id,
text: 'Welcome to Tueen!'
);
}
}
}
```
Pass the class name string directly to `run()`:
```php
$bot->run(CommandHandler::class);
```
If a PSR-11 container (or callable resolver) is configured via `$bot->setContainer($container)` or `ConfigBuilder::withContainer($container)`, Tueen will resolve handler dependencies through your container automatically.
### 3. Multiple Handlers & Middleware Chains
You can pass multiple handlers as variadic arguments or as an array. Handlers run in sequential order:
```php
$bot->run(
AuthMiddleware::class,
CommandHandler::class,
function (Update $update, Telegram $bot) {
// Fallback logger
}
);
// Or using an array:
$bot->run([AuthMiddleware::class, CommandHandler::class]);
```
You can also pre-register handlers using the fluent `$bot->handle(...)` method:
```php
$bot
->handle(AuthMiddleware::class)
->handle(CommandHandler::class)
->run();
```
> [!TIP]
> If any handler explicitly returns `false`, execution of subsequent handlers in the chain will stop immediately for that update.
---
## ๐ฆ The `$bot->update` Property
The `Telegram` client maintains an internal `$update` property that defaults to `null`:
```php
private(set) ?Update $update = null;
```
- When `run()` executes under **Webhook Mode**, the incoming update is resolved from the HTTP request and assigned to `$bot->update`.
- When `run()` executes under **Long-Polling Mode**, each received update is assigned to `$bot->update` before being passed to handlers.
```php
// In a webhook controller:
$bot->run();
// Access the resolved update directly:
$chatId = $bot->update?->findChat()?->id;
```
---
## 1. Webhook Mode (`WebhookMode`)
In production environments (Nginx, Apache, Caddy, FrankenPHP, Laravel, Symfony), bots receive updates as incoming HTTP POST requests sent by Telegram.
### Basic Setup
```php
use Tueen\Telegram\Telegram;
use Tueen\Telegram\Running\WebhookMode;
use Tueen\Telegram\Types\Update;
$bot = new Telegram('YOUR_BOT_TOKEN');
// Configure Webhook mode with optional secret token
$bot->setRunningMode(new WebhookMode(secretToken: 'your-secret-token'));
// Run and dispatch
$bot->run(function (Update $update, Telegram $bot) {
if ($update->message) {
$bot->sendMessage(
chatId: $update->message->chat->id,
text: 'Message received via Webhook!'
);
}
});
```
### Secret Token Verification
Telegram allows specifying a `secret_token` when setting your webhook (`setWebhook`). On every request, Telegram sends this token in the `X-Telegram-Bot-Api-Secret-Token` header.
`WebhookMode` automatically verifies this header:
```php
$mode = new WebhookMode(secretToken: 'my_super_secure_token');
$bot->setRunningMode($mode);
// If the header is missing or doesn't match, a TelegramException is thrown immediately,
// protecting your server from forged requests.
$bot->run(MyHandler::class);
```
### Fast Response (`safeResponse`)
Telegram expects your webhook endpoint to answer with an `HTTP 200 OK` within a few seconds. If your logic performs heavy tasks, call `safeResponse()` to immediately send HTTP 200 and detach the connection via `fastcgi_finish_request()`:
```php
$mode = new WebhookMode();
$mode->safeResponse(); // Sends HTTP 200, Content-Type: application/json, closes connection
// Continue heavy processing in the background:
// e.g. generate AI images, process database transactions, etc.
$bot->run(HeavyProcessingHandler::class);
```
### Integration with Frameworks (Laravel, Symfony, PSR-7)
```php
// In a Laravel Controller:
public function webhook(Request $request, Telegram $bot)
{
$mode = new WebhookMode(
secretToken: config('services.telegram.secret'),
rawInput: $request->getContent(),
headers: $request->headers->all()
);
$bot->setRunningMode($mode);
$bot->run(function (Update $update, Telegram $bot) {
// Handle update
});
return response()->json(['ok' => true]);
}
```
---
## 2. Long-Polling Mode (`PollingMode`)
During local development or for CLI background workers, Long-Polling continuously queries Telegram for new updates in an infinite loop.
### Continuous Listening
```php
use Tueen\Telegram\Telegram;
use Tueen\Telegram\Running\PollingMode;
use Tueen\Telegram\Types\Update;
$bot = new Telegram('YOUR_BOT_TOKEN');
// Set Polling mode
$polling = new PollingMode(
timeout: 30, // Long-polling timeout in seconds
limit: 100, // Updates per batch
allowedUpdates: ['message', 'callback_query']
);
$bot->setRunningMode($polling);
// Starts infinite worker loop in CLI:
echo "Bot started polling...\n";
$bot->run(function (Update $update, Telegram $bot) {
echo "Received update #{$update->updateId}\n";
if ($update->message?->text === '/ping') {
$bot->sendMessage(
chatId: $update->message->chat->id,
text: 'Pong!'
);
}
});
```
### Multi-Process Concurrency (`forkProcess`)
In high-throughput environments, you can process each incoming update in a separate forked process:
```php
$polling = new PollingMode(timeout: 30);
// Enable process forking (requires pcntl extension on CLI):
$polling->forkProcess(true);
$bot->setRunningMode($polling);
$bot->run(MyUpdateHandler::class);
```
When enabled, `PollingMode` forks a dedicated child process for each update (and automatically reaps completed children to avoid zombie processes). On environments where `pcntl_fork` is unavailable (e.g. Windows), it gracefully executes synchronously without crashing.
### Custom Process Dispatcher
You can customize how updates are dispatched to background processes (e.g. using worker pools, message queues, ReactPHP, Amp, or Swoole):
```php
$polling->setProcessDispatcher(function (Update $update, Telegram $bot, callable $next) {
// E.g., dispatch to an async queue or background pool
$next();
});
```
### Features of `PollingMode`:
1. **Automatic Offset Advancement:** Tracks `update_id + 1` automatically so updates are never processed twice.
2. **Per-Update Isolation & Resilience:** If a single update in a batch throws an uncaught exception, `PollingMode` logs the error, safely advances the offset, and continues processing remaining updates in the batch without crashing the long-polling daemon (configurable via `stopOnError(bool)`).
3. **Resilience & Backoff:** Automatically handles transient network interruptions with a configurable cooldown (`errorBackoffSeconds`).
4. **Graceful Shutdown:** You can call `$polling->stop()` from a signal handler (e.g. `SIGINT` / `SIGTERM`) to cleanly exit the polling loop.
### Manual Generator Iteration
If you prefer standard PHP `foreach` iteration:
```php
foreach ($bot->poll(timeout: 30) as $update) {
// Process $update
}
```
---
## Native Telegram Bot API Method: `getUpdates`
The official Telegram Bot API method to fetch updates is `getUpdates` (plural). You can call it directly at any time:
```php
// Direct Telegram Bot API call:
$updatesResult = $bot->getUpdates(offset: 0, limit: 10);
if ($updatesResult->ok()) {
foreach ($updatesResult->items as $update) {
echo "Update ID: {$update->updateId}\n";
}
}
```
---
## Setting Running Mode via Config
You can also define the default running mode directly within the configuration:
```php
$config = Telegram::create('YOUR_BOT_TOKEN')
->withRunningMode(new WebhookMode(secretToken: 'my_secret'))
->build();
$bot = new Telegram($config);
$bot->run(MyHandler::class);
```
---
## โก AutoMode: Adaptive Execution (CLI Polling & HTTP Webhook)
`AutoMode` is an intelligent, multi-vector execution strategy that eliminates the need to maintain separate entry points for CLI development and production webhooks. It dynamically detects whether the bot is running inside a command-line terminal (`PollingMode`) or responding to an HTTP web request (`WebhookMode`).
### Key Features of `AutoMode`:
1. **Multi-Vector Environment Detection:**
- Detects incoming HTTP requests (`$_SERVER['REQUEST_METHOD'] === 'POST'`, Telegram secret headers) even within CLI-based web workers such as **RoadRunner**, **Swoole**, or **FrankenPHP**.
- Detects standard terminal environments (`PHP_SAPI === 'cli'`) when no HTTP request is present.
2. **Telegram Conflict Prevention (`autoDeleteWebhook`):**
- Automatically deletes any active webhook before starting `PollingMode` to prevent Telegram's notorious `409 Conflict: can't use getUpdates method while webhook is active`.
3. **Observability & Hooks:**
- Emits structured logs to your configured PSR-3 logger detailing which mode was selected and why.
- Provides an `onModeResolved(fn($mode, $type, $bot) => ...)` hook for custom lifecycle logic.
4. **Convenient Fluent API:**
- Use `Telegram::create()->withAutoMode(...)` or `$bot->autoRun()`.
### Example 1: Fluent Builder Configuration
```php
use Tueen\Telegram\Telegram;
use Tueen\Telegram\Running\PollingMode;
use Tueen\Telegram\Running\WebhookMode;
$bot = Telegram::create($_ENV['TELEGRAM_BOT_TOKEN'])
->withPollingMode(new PollingMode(timeout: 45))
->withWebhookMode(new WebhookMode(secretToken: $_ENV['TELEGRAM_WEBHOOK_SECRET']))
->withAutoMode(autoDeleteWebhook: true) // Prevents 409 Conflict in local CLI
->client();
$bot->onCommand('start', function (Update $update, Telegram $bot) {
$bot->sendMessage(text: 'Hello from AutoMode!');
});
// Single unified entry point:
// โข In terminal (php bot.php): Runs continuous Long-Polling
// โข Via Web Server (Nginx / Caddy / FrankenPHP): Handles incoming Webhook
$bot->run();
```
### Example 2: Quick `$bot->autoRun()` Execution
```php
use Tueen\Telegram\Telegram;
$bot = new Telegram($_ENV['TELEGRAM_BOT_TOKEN']);
$bot->onCommand('ping', fn(Update $u, Telegram $b) => $b->sendMessage(text: 'pong!'));
// Automatically configures AutoMode and executes:
$bot->autoRun();
```
---
## ๐ PHP 8.5 Pipe Operator (`|>`) Pipelines
PHP 8.5 introduces the native **Pipe Operator (`|>`)**, enabling functional, left-to-right expression composition. In traditional PHP, processing an incoming Telegram update often results in either deeply nested function calls:
```php
// Traditional nested approach (hard to read from inside out):
respond(dispatch(authenticate(filter(parseUpdate(file_get_contents('php://input'))))));
```
or excessive intermediate temporary variables:
```php
// Traditional intermediate variables approach:
$rawPayload = file_get_contents('php://input');
$update = $bot->parseUpdate($rawPayload);
$filtered = $guard->check($update);
$response = $router->dispatch($filtered);
```
With PHP 8.5 and `tueen/telegram`'s callable-friendly design, data flows naturally as a readable, linear pipeline.
---
### 1. Fundamental Syntax & First-Class Callables
The pipe operator passes the result of the left-hand expression as the first argument to the right-hand callable. You can combine it with first-class callables (`$bot->parseUpdate(...)`) and parenthesized closures `(fn($x) => ...)`:
```php
use Tueen\Telegram\Telegram;
use Tueen\Telegram\Types\Update;
$bot = new Telegram('YOUR_BOT_TOKEN');
// Linear left-to-right transformation:
$message = file_get_contents('php://input')
|> $bot->parseUpdate(...)
|> (fn(Update $update) => $update->findMessage())
|> (fn($msg) => $msg?->findAnyText());
```
---
### 2. Production Webhook Ingestion Pipeline
In a webhook controller, you can pipe raw input directly from the HTTP stream into security validation, update parsing, flow handling, and routing without intermediate state:
```php
namespace App\Controllers;
use Tueen\Telegram\Telegram;
use Tueen\Telegram\Types\Update;
use Tueen\Telegram\Exceptions\TelegramException;
class TelegramWebhookController
{
public function __invoke(Telegram $bot): void
{
file_get_contents('php://input')
// Step 1: Parse JSON into a typed Update instance (#[\NoDiscard] safe)
|> $bot->parseUpdate(...)
// Step 2: Set current update on client for contextual auto-fill
|> (function (Update $update) use ($bot): Update {
$bot->setUpdate($update);
return $update;
})
// Step 3: Prioritize active conversation flows
|> (function (Update $update) use ($bot): ?Update {
if ($bot->flowManager->handle($update, $bot)) {
return null; // Flow consumed the update, halt pipeline
}
return $update;
})
// Step 4: Dispatch to attribute router if not consumed by a flow
|> (function (?Update $update) use ($bot): void {
if ($update !== null) {
$bot->router->dispatch($update);
}
});
}
}
```
---
### 3. Multi-Stage Filter & Guard Pipeline
Pipelines are particularly powerful for composing modular security guards, spam filters, and bot filters:
```php
use Tueen\Telegram\Types\Update;
// Dedicated pure or callable guard functions:
$rejectBannedUsers = function (Update $update): ?Update {
$userId = $update->findUser()?->id;
if ($userId !== null && in_array($userId, [/* banned user IDs */], true)) {
return null; // Halt processing for banned users
}
return $update;
};
$rejectOldUpdates = function (?Update $update): ?Update {
if ($update === null) return null;
$date = $update->findMessage()?->date;
// Drop updates older than 2 minutes:
if ($date !== null && (time() - $date) > 120) {
return null;
}
return $update;
};
$logAudit = function (?Update $update): ?Update {
if ($update !== null) {
error_log("Processing Update #{$update->updateId} for Chat {$update->findChat()?->id}");
}
return $update;
};
// Execute complete guard pipeline:
$payload
|> $bot->parseUpdate(...)
|> $rejectBannedUsers
|> $rejectOldUpdates
|> $logAudit
|> (fn(?Update $update) => $update ? $bot->handleUpdate($update) : null);
```
---
### 4. Direct Action & Response Pipelines
You can transform an incoming update straight into an outbound API action using PHP 8.5 pipes:
```php
use Tueen\Telegram\Types\Update;
use Tueen\Telegram\Enums\ParseMode;
file_get_contents('php://input')
|> $bot->parseUpdate(...)
|> (fn(Update $update) => match (true) {
$update->message?->text === '/ping' => $bot->sendMessage(
chatId: $update->findChat()->id,
text: '๐ Pong!',
parseMode: ParseMode::HTML
),
$update->message?->text === '/help' => $bot->sendMessage(
chatId: $update->findChat()->id,
text: '๐ก Send me any text to echo.'
),
default => null,
});
```
---
### 5. Architectural Benefits in Tueen
1. **Zero Intermediate State:** Eliminates redundant local variables (`$raw`, `$json`, `$obj`) that pollute scope and waste memory.
2. **Left-to-Right Readability:** Reading code mirrors the natural direction of data flow: from network stream, through domain filters, to Telegram API response.
3. **Callable & Hook Synergy:** Seamlessly integrates with Tueen's first-class callable methods (`$bot->parseUpdate(...)`, `$bot->sendMessage(...)`) and property hooks.
4. **Graceful Early Exits:** Steps can return `null` or specialized result types to short-circuit subsequent stages without deeply nested `if/else` checks.
---
## ๐งญ 6. Running Modes API Catalog
Below is the complete reference of running mode runners and client execution methods.
### ๐ `WebhookMode` Runner (`Tueen\Telegram\Running\WebhookMode`)
---
### ๐ `PollingMode` Runner (`Tueen\Telegram\Running\PollingMode`)
---
### ๐ `AutoMode` Runner (`Tueen\Telegram\Running\AutoMode`)
# ==============================================================================
# File: /guide/routing
# Section: Updates & Routing > Update Routing & Attributes
# ==============================================================================
# Update Routing & Attributes
Instead of writing monolithic `switch` or `if/else` ladders inside your update handlers, `tueen/telegram` features an expressive, type-safe **Update Router**.
The router supports both **Fluent Route Definitions** and **Attribute-Driven Controllers** (via PHP 8 Attributes).
```mermaid
flowchart TD
Update["๐ฅ Incoming Telegram Update"] --> Router["๐งญ Tueen Update Router"]
subgraph MatchPipeline ["Matching Pipeline (Sequential Evaluation)"]
direction TB
M1{"1. Is Bot Command?
/start, /help, etc."}
M2{"2. Is Callback Query?
inline button data"}
M3{"3. Is Regex / Text Match?
custom patterns"}
M4{"4. Matches Specific Type?
photos, documents, chat joins"}
M1 -->|No| M2
M2 -->|No| M3
M3 -->|No| M4
end
Router --> M1
subgraph Handlers ["Target Dispatch"]
direction TB
H1["โก Command Route / #[OnCommand]"]
H2["๐ Callback Route / #[OnCallbackQuery]
(Extracts parameters like {id})"]
H3["๐ฌ Message Route / #[OnMessage]"]
H4["๐ Type Route / #[OnUpdate]"]
Fallback["๐ก๏ธ Fallback Handler / Next Middleware"]
end
M1 -->|Yes| H1
M2 -->|Yes| H2
M3 -->|Yes| H3
M4 -->|Yes| H4
M4 -->|No Match| Fallback
```
---
## โก 1. Fluent Routing
You can register routes directly on your `Telegram` client instance.
```php
use Tueen\Telegram\Telegram;
use Tueen\Telegram\Types\Update;
$bot = new Telegram('YOUR_BOT_TOKEN');
// 1. Handle bot commands (/start, /help, etc.)
// Bot username (@my_bot) is stripped automatically:
$bot->onCommand('start', function (Update $update, Telegram $bot) {
$bot->sendMessage(
chatId: $update->findChat()->id,
text: 'Welcome! How can I assist you today?'
);
});
// 2. Handle callback queries with parameterized patterns
$bot->onCallbackQuery('item:{id}:details', function (Update $update, Telegram $bot, string $id) {
$bot->answerCallbackQuery(callbackQueryId: $update->callbackQuery->id);
$bot->sendMessage(
chatId: $update->findChat()->id,
text: "Viewing details for item #{$id}"
);
});
// 3. Handle messages matching a regex pattern
$bot->onMessage('/^contact support$/i', function (Update $update, Telegram $bot) {
$bot->sendMessage(
chatId: $update->findChat()->id,
text: 'A support agent will reach out shortly.'
);
});
// 4. Handle inline queries
$bot->onInlineQuery(function (Update $update, Telegram $bot) {
$query = $update->inlineQuery->query;
// Answer inline query...
});
// 5. Catch-all fallback route
$bot->onFallback(function (Update $update, Telegram $bot) {
$bot->sendMessage(
chatId: $update->findChat()?->id,
text: 'Sorry, I did not understand that command.'
);
});
// Execute the bot (automatically triggers router dispatch)
$bot->run();
```
---
## ๐ฏ 2. Parameter Extraction in Routes
The router provides first-class support for parameterized patterns using `{paramName}`:
```php
$bot->onCallbackQuery('cart:add:{productId}:{quantity}', function (
Update $update,
Telegram $bot,
string $productId,
string $quantity
) {
// Parameters are automatically injected into the handler arguments!
});
```
You can also use full regular expressions:
```php
$bot->onCallbackQuery('/^order_(?Papprove|reject)_(?P\d+)$/', function (
Update $update,
Telegram $bot,
string $action,
string $id
) {
// Named regex capture groups are passed as parameters
});
```
---
## ๐๏ธ 3. Attribute-Driven Controllers
For medium to large applications, you can organize your routes into dedicated Controller classes decorated with PHP 8 Attributes:
### Example Controller
```php
namespace App\Controllers;
use Tueen\Telegram\Telegram;
use Tueen\Telegram\Types\Update;
use Tueen\Telegram\Routing\Attributes\OnCommand;
use Tueen\Telegram\Routing\Attributes\OnCallbackQuery;
use Tueen\Telegram\Routing\Attributes\OnMessage;
use Tueen\Telegram\Routing\Attributes\OnInlineQuery;
class ShopController
{
#[OnCommand('shop')]
public function openShop(Update $update, Telegram $bot): void
{
$bot->sendMessage(
chatId: $update->findChat()->id,
text: 'Welcome to our shop!'
);
}
#[OnCallbackQuery('product:{id}')]
public function showProduct(Update $update, Telegram $bot, string $id): void
{
$bot->answerCallbackQuery($update->callbackQuery->id);
$bot->sendMessage(
chatId: $update->findChat()->id,
text: "Loading product #{$id}..."
);
}
#[OnMessage('/^\$([0-9\.]+)$/')]
public function handleTip(Update $update, Telegram $bot, string $amount): void
{
$bot->sendMessage(
chatId: $update->findChat()->id,
text: "Received tip: \${$amount}"
);
}
#[OnInlineQuery]
public function search(Update $update, Telegram $bot): void
{
// Handle inline query
}
}
```
### Registering Controllers
Register controllers with `$bot->registerController(...)`:
```php
$bot->registerController(ShopController::class);
$bot->registerController(UserController::class);
$bot->run();
```
If a PSR-11 container is configured (`$bot->setContainer($container)`), controller instances and their constructor dependencies will be resolved automatically.
---
## ๐งญ 4. Routing API Catalog
Below is the complete reference of routing attributes and router configuration methods.
### ๐ท๏ธ Routing Attributes (`Tueen\Telegram\Routing\Attributes\`)
---
## ๐จ 4. Priority Routes (Overriding Active Flows)
By default, an active conversational `Flow` session takes precedence over standard routes in `Router` to keep the user immersed in multi-step dialogues.
However, certain commands (such as `/help`, `/support`, `/cancel`, `/emergency`, or admin overrides) need to execute **even when a user is in the middle of a Flow**.
### Registering Priority Routes
Pass `priority: true` to fluent route methods or routing attributes:
```php
// 1. Fluent Priority Route:
$bot->onCommand('help', function (Update $update, Telegram $bot) {
$bot->sendMessage(chatId: $bot->chatId, text: 'Need assistance? Contact @support.');
}, priority: true);
// 2. Attribute-Driven Priority Route:
class SupportController
{
#[OnCommand('emergency', priority: true)]
public function emergency(Update $update, Telegram $bot): void
{
$bot->sendMessage(chatId: $bot->chatId, text: 'Emergency operator notified!');
}
}
```
### Flow Customization & Interception
When a priority route matches, Tueen offers the active Flow complete control through two lifecycle hooks:
1. **`allowsPriorityRoute(Route $route, Update $update): bool`** โ Return `false` to intercept and veto the priority route, keeping execution entirely inside the Flow.
2. **`onPriorityRoute(Route $route, Update $update): void`** โ Executed when the priority route is permitted to run, allowing the Flow to pause itself, clean up UI screens, or record that a global command occurred.
---
### ๐ฃ๏ธ `Router` Methods (`Tueen\Telegram\Routing\Router`)
# ==============================================================================
# File: /guide/update-and-message-helpers
# Section: Updates & Routing > Update & Message Helpers
# ==============================================================================
# Update & Message Helpers
`tueen/telegram` provides helper methods and computed properties on `Update`, `Message`, `Chat`, and `User` objects to simplify event processing and eliminate boilerplate null checks.
---
## โก 1. Update Helpers (`Update`)
### Checking Update Types (`isType`)
The `Update` object exposes a `$type` property and a variadic `isType` method that accepts multiple enums or string names:
```php
use Tueen\Telegram\Enums\UpdateType;
// Check single type:
if ($update->type === UpdateType::MESSAGE) {
// Normal user message
}
// Check against multiple types at once:
if ($update->isType(UpdateType::MESSAGE, UpdateType::EDITED_MESSAGE)) {
// Message or edited message
}
// Check by string values:
if ($update->isType('callback_query', 'message')) {
// Matches callback_query or message
}
```
### Smart In-Memory Model Finders
Unlike direct properties (such as `$update->message` which is only set on standard messages), smart finders search across all branches of the incoming update payload (including edited messages, channel posts, callback queries, and business messages):
```php
// 1. Find the primary Message:
$message = $update->findMessage();
// 2. Find the acting User:
$user = $update->findUser();
echo "User: {$user?->fullName} (@{$user?->username})\n";
// 3. Find the target Chat:
$chat = $update->findChat();
echo "Chat: {$chat?->fullName} (ID: {$chat?->id})\n";
```
### ID & File Shortcut Finders
Quickly extract IDs directly from the update without chaining null-safe calls:
```php
$userId = $update->findUserId(); // Returns ?int
$chatId = $update->findChatId(); // Returns ?int
$messageId = $update->findMessageId(); // Returns ?int
$fileId = $update->findFileId(); // Returns ?string (or $update->fileId)
$bizConnId = $update->findBusinessConnectionId(); // Returns ?string
$threadId = $update->findMessageThreadId(); // Returns ?int
```
### Modern PHP 8.4 Property Hooks
Using PHP 8.4 property hooks, you can access these values directly as properties without invoking methods:
```php
$chatId = $update->chatId;
$userId = $update->userId;
$messageId = $update->messageId;
$bizId = $update->businessConnectionId;
$threadId = $update->messageThreadId;
$cbQueryId = $update->callbackQueryId;
$inlineId = $update->inlineMessageId;
```
---
## ๐ 2. Client-Level Contextual Shortcuts (`Telegram` & `Context`)
The `Telegram` client facade and `Context` instance expose modern PHP 8.4 Property Hooks that proxy the active update without empty parentheses:
```php
// Active Contextual ID Properties
$chatId = $bot->chatId;
$userId = $bot->userId;
$msgId = $bot->messageId;
$bizId = $bot->businessConnectionId;
// Model objects
$user = $bot->user; // ?User
$chat = $bot->chat; // ?Chat
$message = $bot->message; // ?Message
```
---
## ๐ฌ 3. Message Helpers (`Message`)
### Checking Message Types (`isType` & `isMessage`)
Check message contents using property hooks or the variadic `isType` / `isMessage` helpers:
```php
use Tueen\Telegram\Enums\MessageType;
// Direct property:
if ($message->type === MessageType::TEXT) {
// Plain text
}
// Variadic check for multiple message types:
if ($message->isMessage(MessageType::TEXT, MessageType::PHOTO, MessageType::DOCUMENT)) {
// Message has text, photo, or document
}
// Check by string names:
if ($message->isType('photo', 'video')) {
// Media message
}
```
### Checking Replies (`isRepliedToMessage`)
Determine whether a message is a reply to another message, or specifically a reply to a particular message ID or object:
```php
// Check if message is a reply to any message:
if ($message->isRepliedToMessage()) {
echo "This is a reply to message #{$message->replyToMessage?->messageId}\n";
}
// Check if message is a reply to a specific message ID:
if ($message->isRepliedToMessage(12345)) {
echo "User replied specifically to message #12345\n";
}
// Check if message is a reply to a specific Message object:
if ($message->isRepliedToMessage($promptMessage)) {
echo "User replied to the prompt message\n";
}
```
### Extracting Any Text, Caption, or Rich Text (`findAnyText`)
Text content in Telegram can reside in several places:
- Standard messages use `text`
- Media messages (photos, videos, documents) use `caption`
- Formatted rich messages use `rich_message` with structured blocks
Use `$message->findAnyText()` (or `$message->findText()`) to retrieve whatever text content is present:
```php
// Returns text, caption, or recursively extracted text from rich message blocks (or null)
$text = $message->findAnyText();
```
### Media & File ID Extraction (`findFileId` & `findLargestPhoto`)
Telegram media messages attach files across different properties (`photo`, `video`, `document`, `audio`, `voice`, `animation`, `sticker`, `paidMedia`, etc.).
Use `$message->findFileId()` (or `$message->fileId`) to immediately obtain the active `file_id`:
```php
// Automatically inspects photo, video, document, audio, voice, animation, sticker, etc.
$fileId = $message->findFileId(); // or $message->fileId
// For photos with multiple resolutions, findFileId() automatically resolves the highest resolution:
if ($message->isType(MessageType::PHOTO)) {
$largeFileId = $message->findFileId();
// You can also retrieve the largest PhotoSize object directly:
$largestPhoto = $message->findLargestPhoto(); // or $message->largestPhoto
echo "Dimensions: {$largestPhoto->width}x{$largestPhoto->height}\n";
}
```
### Bot Command Parsing
Extract commands and their arguments with built-in property hooks:
```php
if ($message->isCommand()) {
// Extract clean command name (strips slash and @botusername) via property hook
// E.g. '/ban@my_bot 123 spam' -> 'ban'
$command = $message->command;
// Extract command arguments as array of strings via property hook
// E.g. ['123', 'spam']
$args = $message->args;
switch ($command) {
case 'start':
$bot->sendMessage(chatId: $message->chat->id, text: 'Welcome!');
break;
case 'ban':
$targetUserId = (int)($args[0] ?? 0);
$reason = $args[1] ?? 'None';
// execute ban...
break;
}
}
```
---
## ๐ค 4. Chat & User Helpers
### Automatic `fullName`
`Chat` and `User` models provide an automatic `$fullName` property:
```php
// On Chat: Returns title for groups/channels, or "first_name last_name" for private chats
echo $chat->fullName;
// On User: Returns "first_name last_name" (or just first_name)
echo $user->fullName;
```
---
## ๐งญ 5. Complete Helpers API Catalog
Below is the complete reference of all computed property hooks and helper methods provided by the `HasUpdateHelpers`, `HasMessageHelpers`, `HasChatHelpers`, and `HasUserHelpers` traits.
### โก `Update` Helpers (`HasUpdateHelpers`)
---
### ๐ฌ `Message` Helpers (`HasMessageHelpers`)
---
### ๐ค `Chat` & `User` Helpers (`HasChatHelpers` & `HasUserHelpers`)
# ==============================================================================
# File: /guide/flow/
# Section: Conversational Flows > Flow & Interactive Screen Ecosystem
# ==============================================================================
# Flow & Interactive Screen Ecosystem
Multi-step dialogues and interactive bot screens (such as user onboarding wizards, settings panels, catalogs, e-commerce checkouts, surveys, and support ticket flows) are notoriously difficult to maintain when managed through scattered database flags, nested `if/else` statements, or naive polling handlers.
`tueen/telegram` provides a unified, two-tier architecture for conversational and interactive bot applications:
1. **[`Flow`](./conversational-flows.md):** A linear, step-by-step state machine designed for text questionnaires, registration wizards, and multi-prompt dialogues.
2. **[`InteractiveFlow`](./interactive-screens.md):** A screen-oriented, component-based engine featuring in-place message editing (zero-flicker UI updates), comprehensive lifecycle hooks, hierarchical navigation stacks (`push`/`pop`), and declarative action routing (`#[Action]`).
---
## ๐๏ธ Two-Tier Architecture Comparison
| Feature | Standard `Flow` | `InteractiveFlow` |
| :--- | :--- | :--- |
| **Primary Paradigm** | Linear Step-by-Step | Screen & Menu Component |
| **Message Updates** | Sends new messages on each step | Edits existing message in-place (`editMessageText`) |
| **Flicker-Free UI** | No (new messages each turn) | Yes (zero-flicker in-place transitions) |
| **Button Handling** | Manual step matching | Declarative `#[Action('pattern')]` attributes |
| **Navigation Bar** | Custom reply buttons | Built-in language-neutral buttons (`๐` Back, `๐ ` Home) |
| **Navigation Model** | Linear History Stack | Hierarchical Stack (`push` child flow / `pop` with results) |
| **Lifecycle Hooks** | `onExit` | `onCreate`, `onStart`, `onResume`, `onPause`, `beforeRender`, `afterRender`, `onUnhandled` |
| **Global Interruption** | Checked via `exitCommands` | Intercepted via `canInterrupt($update)` |
---
## ๐งญ Flow Documentation Sitemap
Explore each section of the Flow ecosystem in detail:
- **[Conversational Flows (Linear Forms)](./conversational-flows.md):** Multi-step questionnaires, step navigation (`to`, `stay`, `back`), validation loops, and session data.
- **[Interactive Screens & Zero-Flicker](./interactive-screens.md):** In-place message rendering, `Screen` builder, zero-flicker transitions across flows, and error recovery.
- **[Lifecycle Hooks Reference](./lifecycle-hooks.md):** Deep-dive into `onCreate`, `onStart`, `onResume`, `onPause`, `beforeRender`, `afterRender`, and `canInterrupt`.
- **[Keyboards & Action Binding](./keyboards-and-actions.md):** Declarative `#[Action]` attributes, parameterized patterns (`item_{id}`), `#[Input]`, and keyboard switching.
- **[Navigation Stack & Root Flow](./navigation-and-stack.md):** Hierarchical flow stack (`push`/`pop`), `๐` Back and `๐ ` Home buttons, and configuring `rootFlow` with `/start`.
- **[State Storage & Drivers](./state-storage.md):** Zero-config session persistence using Memory, File, Redis, and PSR-16 cache drivers.
---
## โก Quickstart: Interactive Menu
```php
use Tueen\Telegram\Flow\InteractiveFlow;
use Tueen\Telegram\Flow\Screen;
use Tueen\Telegram\Flow\Attributes\Action;
use Tueen\Telegram\Keyboards\InlineKeyboard;
class DashboardFlow extends InteractiveFlow
{
public function render(): Screen
{
$status = $this->get('status', 'Active');
return Screen::make("๐ User Dashboard\n\nCurrent Status: {$status}")
->inline(fn (InlineKeyboard $k) => $k
->action('Toggle Status', 'toggle')
->action('Open Settings', 'settings')
)
->withNavigation(back: true, home: true);
}
#[Action('toggle')]
public function toggleStatus(): void
{
$next = $this->get('status', 'Active') === 'Active' ? 'Paused' : 'Active';
$this->set('status', $next);
$this->refresh();
}
#[Action('settings')]
public function openSettings(): void
{
$this->push(SettingsFlow::class);
}
}
```
# ==============================================================================
# File: /guide/flow/conversational-flows
# Section: Conversational Flows > Conversational Flows (Linear Forms)
# ==============================================================================
# Conversational Flows (Linear Forms)
The base `Tueen\Telegram\Flow\Flow` class provides a linear state machine for multi-step textual dialogues, surveys, registration forms, and multi-prompt wizards.
```mermaid
flowchart TD
Trigger(["User triggers /register or $bot->startFlow()"]) --> StepStart
subgraph StepStart ["Step 1: start()"]
PromptName["๐ค Bot: 'Welcome! What is your full name?'"]
end
StepStart --> UserEntersName[/"๐ค User sends Name"/] --> StepEmail
subgraph StepEmail ["Step 2: askEmail()"]
CheckName{"Name length >= 2?"}
CheckName -->|No| RepromptName["โ ๏ธ $this->stay()
'Name too short, please re-enter'"]
RepromptName -.-> UserEntersName
CheckName -->|Yes| PromptEmail["๐ค Bot: 'Great! What is your email address?'"]
end
StepEmail --> UserEntersEmail[/"๐ค User sends Email"/] --> StepConfirm
subgraph StepConfirm ["Step 3: confirm()"]
CheckEmail{"Valid Email Regex?"}
CheckEmail -->|No| RepromptEmail["โ ๏ธ $this->stay()
'Invalid email format, try again'"]
RepromptEmail -.-> UserEntersEmail
CheckEmail -->|Yes| PromptConfirm["๐ค Bot: 'Confirm details? (yes/no)'"]
Decision{"User Response"}
PromptConfirm --> Decision
Decision -->|User sends /cancel| CancelFlow["๐ซ $this->cancel()
Triggers onExit('cancelled')"]
Decision -->|User sends 'yes'| FinishFlow["โ
$this->finish()
Persists record & finishes"]
end
```
---
## ๐๏ธ Anatomy of a Conversational Flow
Every step in a `Flow` is a public method that receives the incoming Telegram `Update`:
```php
namespace App\Flows;
use Tueen\Telegram\Flow\Flow;
use Tueen\Telegram\Types\Update;
class RegistrationFlow extends Flow
{
public function start(Update $update): void
{
$this->bot->sendMessage(chatId: $this->chatId, text: 'Welcome! What is your full name?');
$this->to('askEmail');
}
public function askEmail(Update $update): void
{
$name = trim($update->findAnyText() ?? '');
if (strlen($name) < 2) {
$this->stay('Name must be at least 2 characters. Please try again:');
return;
}
$this->set('name', $name);
$this->bot->sendMessage(chatId: $this->chatId, text: "Nice to meet you, {$name}! What is your email?");
$this->to('confirm');
}
public function confirm(Update $update): void
{
$email = trim($update->findAnyText() ?? '');
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$this->stay('Invalid email format. Please enter a valid email:');
return;
}
$this->set('email', $email);
$name = $this->get('name');
$this->bot->sendMessage(
chatId: $this->chatId,
text: "โ
Registration complete for {$name} ({$email})!"
);
$this->finish();
}
}
```
---
## ๐งญ Step Navigation Methods
### `$this->to(string $stepMethod, array $data = []): static`
Moves the user to the specified step method on their next incoming update.
- Records the previous step in `$state->history` for back-navigation.
- Merges optional key-value `$data` into session storage.
- Automatically saves state and refreshes session TTL.
### `$this->stay(?string $replyMessage = null, mixed $keyboard = null): static`
Retains the user on the current step without appending to history.
- Sends an optional validation error prompt.
- Optionally displays a keyboard.
### `$this->back(?string $replyMessage = null): static`
Navigates to the previous step in history, or completes the flow if history is empty.
### `$this->finish(): void`
Completes the flow, removes session state from storage, and executes `onExit($update, 'finished')`.
### `$this->cancel(?string $replyMessage = 'Operation cancelled.'): void`
Cancels the flow, purges stored state, sends the optional cancellation message, and triggers `onExit($update, 'cancelled')`.
---
## ๐ฏ Filtering Allowed Updates (`allowedUpdates` & `#[AllowedUpdates]`)
By default, an active Flow intercepts all incoming updates for that user/chat. However, you can restrict which update types enter a Flow using either property, attribute, or global configuration:
### Using the `#[AllowedUpdates]` Attribute
```php
use Tueen\Telegram\Flow\Flow;
use Tueen\Telegram\Flow\Attributes\AllowedUpdates;
use Tueen\Telegram\Enums\UpdateType;
#[AllowedUpdates('message', 'callback_query')]
// Or with enums: #[AllowedUpdates(UpdateType::MESSAGE, UpdateType::CALLBACK_QUERY)]
class SurveyFlow extends Flow
{
public function start(Update $update): void
{
// ...
}
}
```
### Using the `$allowedUpdates` Property or Method
```php
class CustomFlow extends Flow
{
protected array $allowedUpdates = ['message'];
// Or dynamic check:
public function allowsUpdate(Update $update): bool
{
return $update->isMessage();
}
}
```
### Global Default via Configuration
```php
$config = Telegram::create('TOKEN')
->withFlowAllowedUpdates(['message', 'callback_query'])
->build();
```
> [!TIP]
> When an update type is not allowed for an active flow (for example, a `chat_join_request` or `inline_query`), the flow **leaves the user's active session intact** and allows the update to pass through to your regular bot routes and attribute controllers!
---
## ๐น๏ธ External Flow Inspection & Control (`$bot->flow()`)
You can inspect and control active conversation flows from anywhere outside the flow โ such as inside command handlers, route controllers, update middlewares, or background jobs.
### The Fluent `FlowSession` Interface
Call `$bot->flow()` (which automatically resolves `chatId` and `userId` from the current update), or pass explicit IDs:
```php
$bot->onCommand('status', function (Update $update, Telegram $bot) {
$session = $bot->flow();
if ($session->isActive) {
$flowClass = $session->class; // e.g. App\Flows\OrderFlow
$step = $session->step; // e.g. 'askQuantity'
$data = $session->data; // ['item_id' => 42]
$bot->sendMessage(
chatId: $update->chat->id,
text: "You are currently in {$flowClass} on step: {$step}"
);
}
});
```
### Controlling Flows from Outside
You can programmatically navigate, modify, or terminate flows:
```php
$session = $bot->flow();
// Navigate back to previous step:
$session->back('Returning to previous question...');
// Jump directly to another step:
$session->to('confirm', ['reviewed' => true]);
// Transition seamlessly to a different flow:
$session->jumpTo(HelpFlow::class);
// Reset flow back to start step and clear data:
$session->reset();
// Cancel or finish the active flow:
$session->cancel('Flow was cancelled.');
$session->finish();
// Directly mutate state data:
$session->set('discount_code', 'SUMMER2026');
$code = $session->get('discount_code');
```
### Direct Facade Shortcuts on `$bot` & `$app`
For quick checks, convenient one-line helpers are available directly on `$bot` and `$app`:
```php
if ($bot->hasActiveFlow()) {
$activeClass = $bot->getActiveFlowClass();
$bot->flowBack(); // Navigate back
$bot->cancelFlow(); // Cancel active flow
$bot->finishFlow(); // Finish active flow
}
```
# ==============================================================================
# File: /guide/flow/interactive-screens
# Section: Conversational Flows > Interactive Screens & Zero-Flicker Transitions
# ==============================================================================
# Interactive Screens & Zero-Flicker Transitions
While standard `Flow` classes handle linear textual forms, **`InteractiveFlow`** is built for rich, menu-driven bots, dashboards, e-commerce product catalogs, and interactive control panels.
---
## โก Zero-Flicker In-Place Editing
Unlike other frameworks that repeatedly delete and resend messagesโcausing visible UI flickering and consuming redundant Telegram API callsโ`InteractiveFlow` automatically tracks the active message ID (`$this->messageId`) and updates messages in-place using `editMessageText` and `editMessageReplyMarkup`.
```mermaid
sequenceDiagram
autonumber
actor User as ๐ค Telegram User
participant App as ๐ฑ Telegram App
participant TG as โ๏ธ Telegram Cloud API
participant Bot as ๐ Tueen (InteractiveFlow)
User->>App: Taps Inline Button [๐ณ Checkout]
App->>TG: Send Callback Query (data: "checkout", msg_id: 420)
TG->>Bot: Deliver Update via Webhook or Polling
Note over Bot: โก Zero-Flicker Transition
Reuses active message ID: 420
Bot->>TG: answerCallbackQuery(callback_query_id)
Bot->>TG: editMessageText(chat_id, message_id: 420, newScreen)
TG-->>App: Push Updated Content & Keyboards
App-->>User: ๐ช Instant UI Refresh (No flicker, same message)
```
### Automatic Fallback Recovery
If in-place editing is not possible (e.g. the message was deleted by the user, the 48-hour Telegram edit window elapsed, or the screen switched from an inline keyboard to a native reply keyboard), `InteractiveFlow` gracefully falls back to sending a new message with `sendMessage`, automatically updating `$this->messageId`.
---
## ๐ฅ๏ธ The `Screen` Builder
The `Screen` object defines the content, formatting, keyboard markup, and navigation options for the active step:
```php
use Tueen\Telegram\Enums\ParseMode;
use Tueen\Telegram\Flow\Screen;
use Tueen\Telegram\Keyboards\InlineKeyboard;
public function render(): Screen
{
return Screen::make("๐ฆ *Product Catalog*\n\nChoose an item below:")
->parseMode(ParseMode::MARKDOWN_V2)
->inline(fn (InlineKeyboard $k) => $k
->row()
->action('Item 1 ($10)', 'item_1')
->action('Item 2 ($20)', 'item_2')
->row()
->url('Official Store', 'https://example.com')
)
->withNavigation(back: true, home: true);
}
```
### Screen Builder Methods
---
## ๐ Refreshing Screens In-Place
To re-render the screen when state changes (e.g., toggling a switch or updating a counter), call `$this->refresh()`:
```php
#[Action('toggle_notify')]
public function toggleNotify(): void
{
$current = $this->get('notify', true);
$this->set('notify', !$current);
// Immediately re-renders the screen in-place with updated state
$this->refresh();
}
```
---
## ๐ Native Telegram Alerts & Toasts
Instead of updating the screen text, you can display native Telegram popups in response to button clicks:
```php
#[Action('like')]
public function handleLike(): void
{
$this->likes++;
// Shows a subtle toast notification at the bottom of the user's screen:
$this->toast('โค๏ธ Added to favorites!');
// Or show a modal dialog box with an OK button:
// $this->alert('โ ๏ธ Warning: Action requires confirmation.');
$this->refresh();
}
```
---
## ๐ Sticky Screen / Pin to Bottom (`resendAtBottom`)
When an interactive flow accepts free-form text input from users, incoming messages push the interactive menu upward in the chat history.
Use `$this->resendAtBottom()` to delete the old message and send a fresh screen at the very bottom of the chat:
```php
#[Input]
public function handleSearchQuery(Update $update): void
{
$query = trim($update->findAnyText() ?? '');
$this->set('searchQuery', $query);
// Deletes the older menu message and posts a new one at the bottom:
$this->resendAtBottom(deleteOld: true);
}
```
---
## ๐ผ๏ธ Media Screens (Photos, Videos, Animations)
`Screen` supports rich media with automatic caption editing:
```php
public function render(): Screen
{
return Screen::make()
->photo('https://example.com/banner.jpg', caption: 'โจ Special Offer: 50% Off!')
->inline(fn (InlineKeyboard $k) => $k->action('Claim Coupon', 'claim'))
->withNavigation(back: true, home: true);
}
```
---
## โ ๏ธ Confirmation Dialogs
For destructive or critical operations, invoke `$this->confirm(...)` to display an in-place confirmation prompt:
```php
#[Action('delete_item')]
public function confirmDeletion(): void
{
$this->confirm(
prompt: 'Are you sure you want to delete this item?',
onConfirmedAction: 'performDeletion',
confirmLabel: 'โ
Yes, Delete',
cancelLabel: 'โ Cancel'
);
}
#[Action('performDeletion')]
public function performDeletion(): void
{
$this->toast('๐๏ธ Item deleted successfully.');
$this->pop();
}
```
---
## โ๏ธ Multi-Select Checklist Grid (`withChecklist`)
Interactive flows often need multi-select toggles (e.g. notifications settings, category filters, or item selection). `Screen::withChecklist()` builds interactive toggle button grids with automatic checkmark icons:
```php
public function render(): Screen
{
$selected = $this->get('selected_interests', ['news', 'tech']);
$options = [
'news' => 'World News',
'tech' => 'Technology',
'crypto' => 'Cryptocurrency',
'sports' => 'Sports & Fitness',
];
return Screen::make("Choose your favorite topics:")
->withChecklist(
options: $options,
selectedKeys: $selected,
actionPattern: 'toggle_interest_{key}',
columns: 2,
checkedIcon: 'โ
',
uncheckedIcon: 'โฌ '
)
->inline(fn (InlineKeyboard $k) => $k->row()->action('Continue โก๏ธ', 'proceed'))
->withNavigation(back: true, home: true);
}
```
### Toggling Checklist Items
Use `$this->toggleChecklist()` to add or remove an item key from the state array:
```php
#[Action('toggle_interest_{key}')]
public function toggleInterest(string $key): void
{
$this->toggleChecklist('selected_interests', $key);
$this->refresh();
}
```
---
## ๐ช Step Wizard & Progress Stepper (`withStepper`)
For multi-step onboarding, registration, or checkout forms, `Screen::withStepper()` injects a visual progress bar directly into the screen header:
```php
public function render(): Screen
{
return Screen::make("Please enter your shipping address:")
->withStepper(currentStep: 2, totalSteps: 4, style: 'blocks')
->inline(...)
->withNavigation(back: true, home: false);
}
```
This prefixes your screen text with:
```text
[Step 2 of 4] ๐ฉ๐ฉโฌโฌ
Please enter your shipping address:
```
### Supported Stepper Styles
| Style | Pattern | Visual Output (Step 2 of 4) |
| :--- | :--- | :--- |
| `'blocks'` | `๐ฉ` / `โฌ` | `[Step 2 of 4] ๐ฉ๐ฉโฌโฌ` |
| `'dots'` | `โ` / `โ` | `[Step 2 of 4] โโโโ` |
| `'circles'` | `๐ข` / `โช` | `[Step 2 of 4] ๐ข๐ขโชโช` |
| `'numbers'` | `(X/Y)` | `[Step 2 of 4]` |
---
## ๐บ Single-Screen Support & `replaceFlow()`
In interactive Telegram bots, users frequently type commands like `/start`, `/tasks`, or `/settings` while already viewing an active screen. In traditional bots, spinning up a new flow leaves redundant menu messages behind in the chat history, cluttering the conversation and breaking the single-screen paradigm.
Tueen provides first-class architectural support for **Single-Screen flows** and a dedicated `$bot->replaceFlow()` API to cleanly terminate previous screens.
### The `#[SingleScreen]` Attribute
Decorate an `InteractiveFlow` with `#[SingleScreen]` to automatically delete the previous active screen message whenever the flow starts:
```php
use Tueen\Telegram\Flow\Attributes\SingleScreen;
use Tueen\Telegram\Flow\InteractiveFlow;
use Tueen\Telegram\Flow\Screen;
#[SingleScreen]
class DashboardFlow extends InteractiveFlow
{
public function render(): Screen
{
return Screen::make("๐ *Main Dashboard*")
->inline(...);
}
}
```
Whenever `$bot->startFlow(DashboardFlow::class)` is invoked, Tueen inspects the current session. If a previous screen message was active, it is safely deleted with `deleteMessage(chatId, messageId)` before rendering the new screen. Any deletion failures (such as the message already being removed by the user or the 48-hour window expiring) are silently suppressed.
You can also control this behavior via the protected property or the attribute argument:
```php
// Via attribute argument
#[SingleScreen(clearPrevious: false)]
class ModalFlow extends InteractiveFlow { ... }
// Or via protected property
class CustomDashboardFlow extends InteractiveFlow
{
protected bool $clearPreviousScreen = true;
}
```
### Dedicated `$bot->replaceFlow()` API
In command handlers (e.g. `#[OnCommand('start')]`), use `$bot->replaceFlow()` to terminate the existing flow session, delete its active screen, and start a fresh flow in a single atomic call:
```php
use Tueen\Telegram\Routing\Attributes\OnCommand;
use Tueen\Telegram\Telegram;
#[OnCommand('start')]
public function handleStart(Telegram $bot): void
{
// Safely deletes the old screen and launches DashboardFlow
$bot->replaceFlow(DashboardFlow::class);
}
```
If you wish to retain the previous screen message in chat history while resetting the session state, pass `deletePreviousScreen: false`:
```php
$bot->replaceFlow(DashboardFlow::class, deletePreviousScreen: false);
```
### Overriding at Runtime via `startFlow()`
You can also explicitly override screen clearing at call-time by passing `clearPreviousScreen`:
```php
// Force-clear previous screen even if the flow is not decorated with #[SingleScreen]
$bot->startFlow(SettingsFlow::class, clearPreviousScreen: true);
// Preserve previous screen even if the flow has #[SingleScreen]
$bot->startFlow(DashboardFlow::class, clearPreviousScreen: false);
```
# ==============================================================================
# File: /guide/flow/lifecycle-hooks
# Section: Conversational Flows > Lifecycle Hooks
# ==============================================================================
# Lifecycle Hooks
`InteractiveFlow` provides a predictable, component-based lifecycle. You can hook into each phase to load data, persist state, intercept exits, or update screens.
```mermaid
stateDiagram-v2
direction TB
[*] --> Initializing
Initializing --> RenderingScreen: onCreate() โ onStart()
state RenderingScreen {
[*] --> PrepareScreen: beforeRender()
PrepareScreen --> CommitTelegram: editMessageText / sendMessage
CommitTelegram --> ScreenMounted: afterRender()
}
RenderingScreen --> AwaitingUserInput: Screen presented to user
AwaitingUserInput --> SuspendedInStack: push(ChildFlow) [triggers onPause]
SuspendedInStack --> RenderingScreen: pop(result) [triggers onResume]
AwaitingUserInput --> RenderingScreen: Valid User Action [to / stay]
AwaitingUserInput --> RenderingScreen: Unmatched Update [triggers onUnhandled]
AwaitingUserInput --> Terminated: finish() / cancel() [triggers onExit]
Terminated --> [*]
```
---
## ๐ช Lifecycle Reference
### `onCreate(): void`
Executed once when the flow instance is instantiated.
- **Use Case:** Load user profile, fetch records from a database, or initialize complex defaults.
### `onStart(): void`
Executed when entering the flow's entry point (`start`).
- **Use Case:** Reset step indicators or prepare initial parameters.
### `onResume(mixed $result = null): void`
Executed when the user returns to this flow after a child flow was popped off the stack via `$this->pop($result)`.
- **Use Case:** Process selection or return data from a sub-menu (e.g., chosen payment method or selected address) and re-render.
```php
public function onResume(mixed $result = null): void
{
if ($result !== null) {
$this->set('chosenAddress', $result);
}
}
```
### `onPause(): void`
Executed immediately before this flow is suspended and a child flow is pushed onto the stack via `$this->push(...)`.
- **Use Case:** Persist transient form inputs or sync drafts.
### `beforeRender(Screen $screen): void`
Executed right before the `Screen` is rendered or edited in Telegram.
- **Use Case:** Dynamically inject badges, breadcrumbs, or user notification banners into the screen text or markup.
### `afterRender(mixed $sentMessage): void`
Executed immediately after a message is successfully sent or edited.
- **Use Case:** Audit logging or triggering secondary asynchronous tasks.
### `onUnhandled(Update $update): void`
Invoked when an incoming update does not match any registered `#[Action]`, `#[Input]`, or step method.
- **Default Behavior:** Calls `$this->refresh()` to re-render the screen in-place and keep the bot UI intact.
### `canInterrupt(Update $update): bool`
Invoked when `/start` or a cancellation command is received while the flow is active.
- **Return Value:**
- `true` (default): Permits the user to exit or redirect to `rootFlow`.
- `false`: Blocks the exit and keeps the user inside the flow.
- **Use Case:** Lock users into critical flows such as checkout, payment processing, or security verification.
# ==============================================================================
# File: /guide/flow/keyboards-and-actions
# Section: Conversational Flows > Keyboards & Action Binding
# ==============================================================================
# Keyboards & Action Binding
`InteractiveFlow` decouples callback routing from tedious manual string checking using modern PHP 8 attributes: `#[Action]` and `#[Input]`.
---
## ๐ฏ The `#[Action]` Attribute
Methods annotated with `#[Action('pattern')]` are automatically executed when the user clicks an inline keyboard button matching the specified pattern.
### 1. Literal Action Patterns
```php
use Tueen\Telegram\Flow\InteractiveFlow;
use Tueen\Telegram\Flow\Screen;
use Tueen\Telegram\Flow\Attributes\Action;
use Tueen\Telegram\Keyboards\InlineKeyboard;
class SettingsFlow extends InteractiveFlow
{
public function render(): Screen
{
return Screen::make('Settings Menu')
->inline(fn (InlineKeyboard $k) => $k
->action('Toggle Dark Mode', 'toggle_dark')
->action('Language', 'language')
);
}
#[Action('toggle_dark')]
public function toggleDarkMode(): void
{
$current = $this->get('dark', false);
$this->set('dark', !$current);
$this->refresh();
}
}
```
### 2. Parameterized Action Patterns
You can capture dynamic URL-style parameters in callback data using `{param}` placeholders:
```php
public function render(): Screen
{
return Screen::make('Select an Item:')
->inline(fn (InlineKeyboard $k) => $k
->action('Item 10', 'item_10')
->action('Item 25', 'item_25')
);
}
#[Action('item_{id}')]
public function selectItem(int $id): void
{
$this->set('selected_id', $id);
$this->push(ItemDetailsFlow::class, ['item_id' => $id]);
}
```
The parameter is automatically converted to the type hinted in the method signature (`int`, `string`, etc.).
---
## โ๏ธ Free-Text Inputs with `#[Input]`
If a screen needs to accept free-form text from the user while keeping the interactive screen active:
```php
use Tueen\Telegram\Flow\Attributes\Input;
use Tueen\Telegram\Types\Update;
#[Input]
public function handleTextInput(Update $update): void
{
$query = trim($update->findAnyText() ?? '');
$this->set('search_query', $query);
$this->refresh();
}
```
---
## ๐๏ธ Switching Keyboard Types Seamlessly
`Screen` lets you switch seamlessly between inline keyboards, native reply keyboards, and removing keyboards:
```php
// Switch to a native contact sharing keyboard:
Screen::make('Please share your phone number:')
->reply(fn (ReplyKeyboard $k) => $k
->requestContact('๐ฑ Share Contact')
->resize()
->oneTime()
);
// Remove any existing reply keyboards:
Screen::make('Processing your request...')
->removeKeyboard();
```
> [!NOTE]
> Telegram's Bot API does not allow converting an inline message into a native reply keyboard via `editMessage`. When transitioning from an inline keyboard to a `ReplyKeyboard`, `InteractiveFlow` automatically sends a new message with the reply keyboard and tracks the new `$this->messageId`.
---
## ๐ Automatic Pagination Bar
When rendering paginated items or catalogues, add an automatic pagination control row directly using `withPagination()`:
```php
Screen::make("Products (Page {$page} of {$totalPages}):")
->inline(...)
->withPagination(
currentPage: $page,
totalPages: $totalPages,
actionPattern: 'page_{page}', // Generates [โ๏ธ] [2/5] [โถ๏ธ]
prevLabel: 'โ๏ธ',
nextLabel: 'โถ๏ธ'
)
->withNavigation(back: true, home: true);
```
Then bind the page action with a parameterized `#[Action]`:
```php
#[Action('page_{page}')]
public function changePage(int $page): void
{
$this->set('currentPage', $page);
$this->refresh();
}
```
---
## โฑ๏ธ Action Rate-Limiting & Debouncing (`#[Debounce]`)
Users in Telegram often double-tap buttons impulsively, which can cause duplicate orders, race conditions, or duplicate payment requests.
Annotate any `#[Action]` method with `#[Debounce]` to lock rapid button clicks:
```php
use Tueen\Telegram\Flow\Attributes\Action;
use Tueen\Telegram\Flow\Attributes\Debounce;
#[Action('checkout')]
#[Debounce(seconds: 3.0, notice: 'โณ Please wait, processing your payment...', showAlert: false)]
public function handleCheckout(): void
{
// Executes at most once every 3 seconds per user
$this->processPayment();
}
```
- **`seconds`**: Debounce window in seconds (supports fractional seconds like `1.5`).
- **`notice`**: The toast notification or alert message shown to the user when throttled.
- **`showAlert`**: If `true`, pops up a modal alert dialog; if `false` (default), shows a non-intrusive toast popup.
---
## ๐ก๏ธ Channel Membership Guard (`#[RequireMember]`)
Gate actions or screens behind mandatory Telegram channel or supergroup subscriptions. The `#[RequireMember]` attribute verifies that the user is currently a member, administrator, or creator:
```php
use Tueen\Telegram\Flow\Attributes\Action;
use Tueen\Telegram\Flow\Attributes\RequireMember;
#[Action('claim_bonus')]
#[RequireMember(
channel: '@tueen_channel',
fallbackMessage: '๐ You must join our official channel to claim this daily reward!',
joinUrl: 'https://t.me/tueen_channel'
)]
public function claimDailyBonus(): void
{
// Executed ONLY if the user is a member/admin/creator of @tueen_channel
$this->grantBonus(100);
$this->toast('๐ Bonus claimed!');
$this->refresh();
}
```
### Guard Workflow:
1. `InteractiveFlow` queries Telegram's `getChatMember(chat_id, user_id)`.
2. If member status is `creator`, `administrator`, or `member`, execution proceeds to the action method.
3. If not joined (or `left`, `kicked`, `restricted`), execution is halted. If `joinUrl` is specified, an inline screen with a direct "๐ข Join Channel" button and a "๐ Check Again" refresh button is automatically rendered for the user.
# ==============================================================================
# File: /guide/flow/navigation-and-stack
# Section: Conversational Flows > Navigation Stack & Root Flow
# ==============================================================================
# Navigation Stack & Root Flow
`InteractiveFlow` features a hierarchical navigation stack that models menus and multi-layered screens just like modern UI frameworks.
```mermaid
flowchart TD
subgraph StackRepresentation ["๐ฅ Navigation Stack Hierarchy ($state->flowStack)"]
direction TB
TopScreen["๐ NotificationsFlow
[Active Screen]
Rendered on message_id: 420"]
MidScreen["โ๏ธ SettingsFlow
[Suspended Frame 1]
Saved state & step memory"]
RootScreen["๐ MainMenuFlow
[Root Frame 0]
Application Entry Point"]
TopScreen -->|"$this->pop('result')"| MidScreen
MidScreen -->|"$this->pop()"| RootScreen
TopScreen -.->|"Click ๐ Home (Stack Reset)"| RootScreen
end
subgraph Transitions ["Navigation Operations"]
direction LR
PushOp["push(Child::class, data)
โข Suspends current flow
โข Pushes state to stack
โข Reuses same message_id"]
PopOp["pop(result)
โข Drops top frame
โข Resumes parent flow
โข Calls onResume(result)"]
HomeOp["home() / ๐
โข Purges entire stack
โข Restores rootFlow
โข Clean state slate"]
end
```
---
## ๐ฅ Stack Navigation: `push()` and `pop()`
### `$this->push(string $childFlowClass, array $data = []): void`
Suspends the active flow, pushes its state onto `$state->flowStack`, and opens the child flow on the **exact same message ID** for zero-flicker transitions.
```php
#[Action('open_profile')]
public function openProfile(): void
{
$this->push(UserProfileFlow::class, ['user_id' => 123]);
}
```
### `$this->pop(mixed $result = null): void`
Pops the active flow off the stack and restores the parent flow on the same message ID, executing the parent's `onResume($result)` hook with any returned data.
```php
#[Action('save_selection')]
public function saveSelection(): void
{
$this->pop('English (US)');
}
```
---
## ๐งญ Navigation Buttons: `๐` Back and `๐ ` Home
`Screen` includes a navigation bar with language-neutral emoji defaults:
- **Back Button:** `๐` (triggers `$this->pop()`)
- **Home Button:** `๐ ` (resets flow stack and jumps to `rootFlow`)
```php
// Enable both Back and Home:
Screen::make('Select an option:')
->withNavigation(back: true, home: true);
// Enable only Back:
Screen::make('Select an option:')
->withNavigation(back: true, home: false);
// Customizing labels:
Screen::make('Select an option:')
->withNavigation(
back: true,
home: true,
backLabel: '๐ Return',
homeLabel: '๐ Main Menu'
);
```
> [!TIP]
> Navigation buttons work seamlessly on both **Inline Keyboards** (via callback query) and **Reply Keyboards** (via emoji text message `๐` or `๐ `).
---
## ๐ Configuring `rootFlow` & Handling `/start`
You can configure a global root flow on the client or FlowManager:
```php
$bot->setRootFlow(MainMenuFlow::class);
// Or through Config builder:
$config = Config::builder()
->token('...')
->rootFlow(MainMenuFlow::class)
->build();
```
### Global `/start` Behavior
When a user issues `/start` while in an active flow:
1. `FlowManager` checks `$activeFlow->canInterrupt($update)`.
2. If `true` (default), the entire flow stack is cleared, and the user is redirected to `rootFlow`.
3. If `false` (e.g. during checkout or an active form), the `/start` command is blocked, and the user remains safely in the active step.
---
## ๐ Breadcrumbs Trail (`withBreadcrumbs`)
When nesting multi-level menus (e.g. `Catalog` ยป `Electronics` ยป `Headphones`), orient the user with an automatic breadcrumbs trail:
```php
use Tueen\Telegram\Flow\Attributes\Breadcrumb;
#[Breadcrumb('Headphones')]
class HeadphoneDetailsFlow extends InteractiveFlow
{
public function render(): Screen
{
return Screen::make("Sony WH-1000XM5 Specs...")
->withBreadcrumbs(separator: ' ยป ')
->inline(...)
->withNavigation(back: true, home: true);
}
}
```
This prefixes your screen text with:
```text
Catalog ยป Electronics ยป Headphones
Sony WH-1000XM5 Specs...
```
You can also customize the breadcrumb label dynamically via PHP 8.4 property hook:
```php
public string $breadcrumbTitle {
get => $this->get('product_name', 'Item');
}
```
---
## ๐ก๏ธ Resilience & Missing Class Fallback
In long-running or distributed systems, code deployments may remove, rename, or refactor a Flow class while active users still have that Flow recorded in their persistent storage (e.g. `storage/flow/*.json` or Redis/database).
`tueen/telegram` features an intelligent, multi-layered resilience algorithm to ensure your bot never crashes or leaves users stranded:
```mermaid
flowchart TD
Update(["Incoming User Update"]) --> ValidateClass{"Flow Class Exists
in Codebase?"}
subgraph HappyPath ["Normal Execution"]
ValidateClass -->|Yes| ValidateStep{"Step Method
Exists?"}
ValidateStep -->|Yes| ExecStep["โถ๏ธ Execute Flow Step"]
end
subgraph ClassRecovery ["Class Missing (Deleted / Renamed in Deploy)"]
ValidateClass -->|No| HasParent{"Parent Flow
in Stack?"}
HasParent -->|Yes| UnwindParent["โช Pop & Resume Nearest Valid Parent"]
HasParent -->|No| HasRoot1{"rootFlow
Configured?"}
end
subgraph StepRecovery ["Step Missing (Refactored Method)"]
ValidateStep -->|No| TriggerHook["๐ Trigger onMissingStep()"]
TriggerHook --> HasHistory{"History Stack
Available?"}
HasHistory -->|Yes| StepBack["๐ Auto Fallback: $this->back()"]
HasHistory -->|No| HasStart{"start()
Available?"}
HasStart -->|Yes| StepStart["๐ Reset to: $this->start()"]
HasStart -->|No| HasRoot2{"rootFlow
Configured?"}
end
subgraph TerminalFallback ["Root Fallback & Cleanup"]
HasRoot1 -->|Yes| JumpRoot["๐ Reset to rootFlow"]
HasRoot2 -->|Yes| JumpRoot
HasRoot1 -->|No| PurgeState["๐งน Purge Orphaned State
Pass to Bot Router"]
HasRoot2 -->|No| PurgeState
end
```
### 1. Unwinding Navigation Stack to Parent Flows
If the user was inside a child flow (pushed via `$this->push(...)`) whose class was removed, `FlowManager` automatically inspects `$state->flowStack` and pops back to the nearest parent Flow that still exists, resuming it smoothly with its original state and `messageId`.
### 2. Fallback to `rootFlow` (Default Flow)
If the stack is empty (or parent classes were also removed), `FlowManager` checks if a `rootFlow` (or `defaultFlow`) is configured in `Config`:
```php
$config = Telegram::create('TOKEN')
->withRootFlow(MainMenuFlow::class) // or withDefaultFlow(MainMenuFlow::class)
->build();
```
When configured, the user's corrupted state is cleared and they are seamlessly redirected to the entry step of the root flow.
### 3. Missing Step Recovery (`onMissingStep`)
If a Flow class exists, but a specific step method was removed or renamed in code, `Flow::onMissingStep($step, $update)` is automatically invoked:
- It checks `$state->history` and navigates back one step.
- If history is empty, it attempts to return to the `start($update)` method.
- If a custom `$this->fallbackFlow` is set on the flow, it transitions there.
- Or it redirects to `rootFlow`.
You can also customize missing step handling directly on any Flow:
```php
class CustomFlow extends Flow
{
protected ?string $fallbackFlow = HomeFlow::class;
public function onMissingStep(string $step, Update $update): void
{
$this->bot->sendMessage(
chatId: $this->chatId,
text: "This section was recently updated. Taking you back..."
);
$this->back();
}
}
```
### 4. Graceful Router Fallthrough
If no parent flows, root flows, or step recovery paths are available, `FlowManager` purges the orphaned session and returns `false`. This ensures the user's message is not discarded and is immediately handled by your regular command handlers, router attributes, or fallback routes.
# ==============================================================================
# File: /guide/flow/state-storage
# Section: Conversational Flows > State Storage Drivers
# ==============================================================================
# State Storage Drivers
Flow sessions and interactive screen states are persisted between updates using pluggable state storage drivers.
```mermaid
flowchart TD
subgraph RequestCycle ["Stateful Execution in Stateless PHP"]
direction TB
Update["๐ฅ Incoming Telegram Update
(chat_id: 100, user_id: 200)"] --> Resolve["๐ Session Key Resolution
100:200"]
Resolve --> LoadState["๐ 1. Load Session
$store->get(key)"]
LoadState --> Hydrate["๐ง Hydrate Flow Instance
Restores current step, data & message_id"]
Hydrate --> Exec["โ๏ธ 2. Execute Step Method
User mutation / step transition"]
Exec --> SaveState["๐พ 3. Persist Updated State
$store->set(key, state, ttl)"]
end
subgraph Drivers ["Pluggable Storage Drivers (StateStoreInterface)"]
direction TB
File["๐ FileStateStore
Zero-config for Webhooks / FPM
Atomic file locking (LOCK_EX)"]
Redis["โก RedisStateStore
Distributed & Clustered
High-concurrency microservices"]
Psr16["๐ Psr16StateStore
Framework Adapters
Laravel Cache, Symfony, PSR-16"]
Memory["๐ง MemoryStateStore
CLI & Testing
Ephemeral in-process memory"]
end
LoadState <-.-> Drivers
SaveState -.-> Drivers
```
---
## ๐พ Available Storage Drivers
### 1. `FileStateStore` (Default for Webhooks)
Zero-configuration file-based storage for webhook environments (PHP-FPM, Apache). State is safely stored in temporary session files with atomic file locking and automatic expiration.
```php
use Tueen\Telegram\Flow\Storage\FileStateStore;
$bot->setFlowStore(new FileStateStore(directory: sys_get_temp_dir() . '/tueen_flows'));
```
### 2. `MemoryStateStore` (Default for CLI Polling)
In-memory array store. Ultra-fast and optimal for long-running daemon processes or unit test suites.
```php
use Tueen\Telegram\Flow\Storage\MemoryStateStore;
$bot->setFlowStore(new MemoryStateStore());
```
### 3. `RedisStateStore` (High-Concurrency & Distributed)
Native Redis driver supporting Redis clusters, automatic TTL expiration, and cross-server session sharing.
```php
use Tueen\Telegram\Flow\Storage\RedisStateStore;
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
$bot->setFlowStore(new RedisStateStore(redis: $redis, prefix: 'tueen:flow:'));
```
### 4. `Psr16StateStore` (Framework Adapters)
Integrates with any PSR-16 compliant cache (Laravel Cache, Symfony Cache, etc.).
```php
use Tueen\Telegram\Flow\Storage\Psr16StateStore;
$bot->setFlowStore(new Psr16StateStore($psr16CachePool));
```
# ==============================================================================
# File: /guide/app
# Section: Application & Production > Zero-Config App & Web Dashboard
# ==============================================================================
# Zero-Config App & Web Dashboard
`Tueen\Telegram\App` is the high-level application bootstrapper and orchestrator for `tueen/telegram`. It eliminates boilerplate configuration, automatically sets up file-based conversation flow storage, auto-discovers attribute controllers, and provides a built-in Web Setup Wizard and CLI tooling.
---
## ๐ 1. The Single-File Entry Point
With `App`, you can launch a complete, production-ready Telegram bot from a single file:
```php
run();
```
::: tip Flexible Entry Point Naming
While `index.php` is standard, you can freely name your entry point file whatever you want (such as `bot.php`, `update.php`, or `webhook.php`). `App::create(__DIR__)` dynamically inspects the executing script and automatically resolves the correct public webhook URL.
:::
---
## โก 2. Adaptive Execution Environments
When `$app->run()` is invoked, it intelligently detects the execution context:
---
## ๐ 3. Web Setup Wizard & Management Console
Opening `index.php` in a web browser activates the **Royal Web Dashboard**:
### 1. First-Time Setup Wizard (When Token is Empty)
If no bot token is configured in `config.php`:
- Displays a setup form to enter your **Bot Token** (from `@BotFather`).
- Automatically detects your public webhook URL (`https://yourdomain.com/index.php`).
- Includes a button to generate a secure 32-character random `secret_token`.
- Tests connection with `getMe()` and automatically sets the webhook via `setWebhook()`.
- Automatically writes credentials into `config.php`.
### 2. Live Management Console (When Configured)
Once configured, visiting the URL in a browser displays a real-time status console:
- **Bot Profile:** Name, `@username`, Bot ID, group permissions, and Bot API ping latency.
- **Webhook Telemetry:** Active webhook URL, pending update queue count, SSL certificate type, max connections, and any recent delivery errors from Telegram servers.
- **One-Click Actions:**
- ๐ฏ **Set / Update Webhook:** Sets the webhook to the current URL with optional `drop_pending_updates`.
- ๐๏ธ **Delete Webhook:** Removes the webhook from Telegram servers.
- ๐ **Refresh:** Reloads live status from Telegram Bot API.
- **Storage & Flow Metrics:** Displays active flow session files and storage path.
### Securing the Dashboard
To protect the web dashboard with a password, specify `setup.password` in your `config.php`, or set `SETUP_PASSWORD` in your `.env` file:
```php
return [
'token' => '...',
'setup' => [
'enabled' => true,
'password' => 'my-secure-dashboard-password',
],
];
```
Or in `.env`:
```env
SETUP_PASSWORD=my-secure-dashboard-password
```
Once logged in, an active session is securely maintained. You can click **๐ Logout** in the header at any time to lock the dashboard again.
---
## ๐พ 4. Automatic Flow State Storage (`storage/flow`)
`App` automatically configures persistent conversation flow storage:
- By default, it creates `$basePath . '/storage/flow'` and attaches `FileStateStore`.
- Protects the directory against direct web browser access with an Apache `.htaccess` (`Deny from all`) and an `index.php` fallback (`403 Forbidden`).
- Multi-step conversational flows (`$bot->startFlow(...)`) automatically persist state without any external database or Redis setup.
To customize the flow storage path, configure `flow_storage` in `config.php`:
```php
return [
'token' => '...',
'flow_storage' => '/var/data/telegram_flows',
];
```
---
## ๐ 5. Automatic Controller Discovery
If your bot project has a `controllers/` directory in its project root, `App` automatically scans all PHP files inside it. Any class decorated with routing attributes (`#[OnCommand]`, `#[OnCallbackQuery]`, `#[OnMessage]`, `#[OnInlineQuery]`, `#[OnUpdate]`) is registered automatically with zero boilerplate!
```php
// controllers/StartController.php
namespace App\Controllers;
use Tueen\Telegram\Routing\Attributes\OnCommand;
use Tueen\Telegram\Types\Update;
use Tueen\Telegram\Telegram;
class StartController
{
#[OnCommand('start')]
public function start(Update $update, Telegram $bot): void
{
$bot->sendMessage(text: "Hello from auto-discovered controller!");
}
}
```
---
## ๐ฉบ 6. System Diagnostics (`doctor`)
To troubleshoot webhook issues, SSL certificate mismatches, or file permission errors on shared hosting:
- Run `php index.php doctor` in your terminal to see a colored diagnostic status table.
- Or open your bot in a browser to inspect the **System Diagnostics (Doctor)** card in the Web Dashboard.
It verifies:
- PHP version compatibility (PHP 8.4+).
- Required extensions (`curl`, `json`, `mbstring`, `openssl`).
- Flow storage permissions (`storage/flow` writable).
- Telegram Bot API server reachability and latency.
- Webhook URL HTTPS compliance.
---
## ๐งญ 7. App & CLI API Reference
Below is the complete reference of methods and properties available on `Tueen\Telegram\App`, along with terminal CLI commands.
### ๐๏ธ `App` Class (`Tueen\Telegram\App`)
---
### ๐ป CLI Administrative Commands
# ==============================================================================
# File: /guide/pipeline-middleware
# Section: Application & Production > Pipeline & Middlewares
# ==============================================================================
# Pipeline & Middlewares
Every outbound API request in `tueen/telegram` passes through an extensible onion middleware pipeline before reaching the underlying HTTP transport. This allows you to transparently handle retries, rate limits, request logging, tracing, and metric collection.
```mermaid
flowchart LR
subgraph ClientExecution ["Client Invocation"]
Req["๐ป Developer Calls
$bot->sendMessage(...)"]
end
subgraph PipelineLayers ["๐ง
Extensible Middleware Onion Pipeline"]
direction LR
L1["๐ 1. LoggingMiddleware
Records start time, method & payload"]
L2["โฑ๏ธ 2. RateLimitMiddleware
Token bucket check (30 req/s, 1s/chat)"]
L3["๐ 3. RetryMiddleware
Catches 429 & network drops, auto-retries"]
L1 -->|Pass next| L2
L2 -->|Pass next| L3
end
subgraph TransportLayer ["๐ HTTP Transport & Cloud"]
Transport["Client Transport
(Guzzle / cURL / PSR-18)"]
TelegramCloud[("โ๏ธ Telegram Bot API Server")]
Transport -->|HTTPS POST| TelegramCloud
end
Req --> L1
L3 -->|Final dispatch| Transport
TelegramCloud -.->|Response / Status 200| L3
L3 -.->|Bubble response| L2
L2 -.->|Bubble response| L1
L1 -.->|Final Typed Result| Ret["๐ฆ Typed Response: Message"]
```
---
## โก 1. Native Built-In Middlewares
`tueen/telegram` includes three battle-tested middlewares out of the box:
### 1. `RetryMiddleware`
Automatically retries requests upon network dropouts or Telegram `429 Too Many Requests` penalties with exponential backoff and strict adherence to Telegram's `retry_after` header.
### 2. `RateLimitMiddleware`
Enforces Telegram's official rate limits locally using an in-memory token-bucket algorithm, preventing rate limit penalties before they occur:
- **Global Pacing:** Default 30 requests per second across all chats.
- **Per-Chat Pacing:** Enforces minimum interval between consecutive messages to the same chat (default: 1.0 second per chat).
- **Automatic 429 Sleep Backoff:** If Telegram still responds with 429, sleeps for `retry_after` seconds and replays automatically.
```php
use Tueen\Telegram\Telegram;
use Tueen\Telegram\Pipeline\RateLimitMiddleware;
$bot = new Telegram('YOUR_BOT_TOKEN');
// Attach rate limiter with default Telegram limits (30 req/sec, 1 sec/chat):
$bot->pipe(new RateLimitMiddleware());
// Or customize limits for high-volume broadcast bots:
$bot->pipe(new RateLimitMiddleware(
maxRequestsPerSecond: 25.0,
chatIntervalSeconds: 1.2,
autoRetryOn429: true
));
```
### 3. `LoggingMiddleware`
Integrates with any PSR-3 logger (Monolog, Laravel Log, etc.) to log endpoint calls, parameters, and execution durations:
```php
use Tueen\Telegram\Pipeline\LoggingMiddleware;
$bot->pipe(new LoggingMiddleware($logger));
```
---
## ๐ ๏ธ 2. Authoring Custom Middleware
Create a class implementing `Tueen\Telegram\Pipeline\MiddlewareInterface`:
```php
namespace App\Middleware;
use Tueen\Telegram\Client\Request;
use Tueen\Telegram\Client\Response;
use Tueen\Telegram\Config;
use Tueen\Telegram\Pipeline\MiddlewareInterface;
class ExecutionTimerMiddleware implements MiddlewareInterface
{
public function handle(Request $request, Config $config, callable $next): Response
{
// 1. Pre-request hook
$startTime = hrtime(true);
// 2. Delegate to next middleware in onion pipeline
$response = $next($request, $config);
// 3. Post-response hook
$durationMs = (hrtime(true) - $startTime) / 1e6;
error_log("API call [{$request->method}] completed in {$durationMs}ms");
return $response;
}
}
```
Attach your custom middleware using `$bot->pipe(...)`:
```php
$bot->pipe(new ExecutionTimerMiddleware());
```
---
## ๐งญ 3. Pipeline API Catalog
Below is the complete reference of built-in pipeline middlewares and registration methods.
### ๐งฑ Pipeline Middlewares (`Tueen\Telegram\Pipeline\`)
# ==============================================================================
# File: /guide/error-handling-and-hooks
# Section: Application & Production > Error Handling & Lifecycle Hooks
# ==============================================================================
# Error Handling & Lifecycle Hooks
`tueen/telegram` provides a comprehensive, type-safe error handling architecture designed for PHP 8.4+ and PHP 8.5:
1. **Strongly-Typed Exceptions (Default):** Throws granular, domain-specific exceptions (`ChatNotFoundException`, `BotBlockedException`, `RateLimitException`, `CantParseEntitiesException`, etc.).
2. **Error-Object Mode:** Returns a typed `Tueen\Telegram\Types\Error` value object instead of throwing, with `TelegramErrorCode` enum reasons and helper checks.
3. **Automated Error Catalog:** Built from comprehensive Telegram Bot API error specifications with forward-compatibility fallback for unknown errors.
---
## 1. Universal `ok()` / `isOk()` Check
Every response object produced by the library inherits from [`Type`](./methods-and-types) and provides `ok(): bool` and `isOk(): bool`:
- **Successful Types (`Message`, `User`, `BooleanResult`, etc.):** Always return `true`.
- **`Error` Objects:** Always return `false`.
```php
$res = $bot->sendMessage(chatId: 12345, text: 'Hello!');
if ($res->ok()) {
echo "Success! Message ID: {$res->messageId}\n";
} else {
// $res is an Error object
echo "Failed [{$res->errorCode}]: {$res->description}\n";
}
```
---
## 2. Strongly-Typed Exceptions Hierarchy
In standard exception mode, failed API requests throw specific exceptions matching the exact error returned by Telegram:
```
TelegramException
โโโ ApiException
โโโ BadRequestException (400)
โ โโโ ChatNotFoundException
โ โโโ UserNotFoundException
โ โโโ MessageNotModifiedException
โ โโโ MessageNotFoundException
โ โโโ MessageCantBeDeletedException
โ โโโ MessageTooLongException
โ โโโ CantParseEntitiesException
โ โโโ CommandsListEmptyException
โ โโโ ...
โโโ UnauthorizedException (401)
โโโ ForbiddenException (403)
โ โโโ BotBlockedException
โ โโโ BotKickedException
โ โโโ UserDeactivatedException
โ โโโ NotEnoughRightsException
โโโ NotFoundException (404)
โโโ ConflictException (409)
โ โโโ WebhookActiveConflictException
โ โโโ TerminatedByOtherGetUpdatesException
โโโ FileTooLargeException (413)
โโโ RateLimitException (429)
โโโ NetworkException (cURL/connection failures)
```
### Catching Specific Exceptions
Catch the exact error condition directly without parsing strings:
```php
use Tueen\Telegram\Exceptions\BotBlockedException;
use Tueen\Telegram\Exceptions\ChatNotFoundException;
use Tueen\Telegram\Exceptions\CantParseEntitiesException;
use Tueen\Telegram\Exceptions\RateLimitException;
use Tueen\Telegram\Exceptions\BadRequestException;
use Tueen\Telegram\Exceptions\ApiException;
try {
$bot->sendMessage(chatId: $userId, text: $markdownText, parseMode: 'MarkdownV2');
} catch (BotBlockedException $e) {
// User blocked the bot - mark user inactive in DB
$user->update(['is_active' => false]);
} catch (ChatNotFoundException $e) {
// Chat or channel no longer exists
$logger->warning("Chat {$userId} not found.");
} catch (CantParseEntitiesException $e) {
// Markdown syntax error - fallback to plain text
$bot->sendMessage(chatId: $userId, text: strip_tags($markdownText));
} catch (RateLimitException $e) {
// Flood wait: inspect retryAfter duration
sleep($e->retryAfter);
} catch (BadRequestException $e) {
// Catch-all for any other 400 Bad Request
} catch (ApiException $e) {
// Catch-all for any other Telegram Bot API error
}
```
---
## 3. Typed Error Reasons (`TelegramErrorCode` Enum)
All API errors are mapped to the `TelegramErrorCode` Backed Enum:
```php
use Tueen\Telegram\Enums\TelegramErrorCode;
// Match cleanly using PHP 8.4 match expression
match ($error->reason) {
TelegramErrorCode::BotBlocked => $user->markBlocked(),
TelegramErrorCode::ChatNotFound => $user->delete(),
TelegramErrorCode::FloodWait => sleep($error->retryAfter),
TelegramErrorCode::CantParseEntities => $bot->sendMessage(chatId: $chatId, text: $plain),
default => $logger->error($error->description),
};
```
---
## 4. Error-Object Mode with Smart Helpers
To prevent API errors from throwing exceptions:
```php
use Tueen\Telegram\Telegram;
use Tueen\Telegram\Enums\TelegramErrorCode;
$bot = new Telegram(
Telegram::create('YOUR_BOT_TOKEN')
->withErrorObjectMode()
->build()
);
$res = $bot->sendMessage(chatId: 99999, text: 'Hi');
if (!$res->ok()) {
// 1. Inspect typed reason
if ($res->is(TelegramErrorCode::BotBlocked)) {
echo "User has blocked the bot.\n";
}
// 2. Convenience helpers
if ($res->isChatNotFound()) { ... }
if ($res->isBotBlocked()) { ... }
if ($res->isRateLimit()) {
$seconds = $res->retryAfter;
}
// 3. Convert to exception on demand
$exception = $res->toException(); // returns ChatNotFoundException, etc.
}
```
---
## 5. Catch All Errors (Including Network Errors)
By default, `withErrorObjectMode()` only converts Telegram `ApiException` responses into `Error` objects.
If you also want connection drops, cURL errors, and network timeouts converted into `Error` objects:
```php
$bot = new Telegram(
Telegram::create('YOUR_BOT_TOKEN')
->withCatchAllErrors()
->build()
);
$res = $bot->getMe();
if (!$res->ok()) {
// Network/cURL errors receive negative error codes (e.g. -28)
echo "Error: {$res->description} (Code: {$res->errorCode})\n";
}
```
---
## 6. Declarative `#[ApiErrors]` & Method Metadata
Every method generated by `tueen/telegram` declares its documented errors using the `#[ApiErrors]` attribute:
```php
use Tueen\Telegram\Methods\SendMessage;
$method = new SendMessage(chatId: 12345, text: 'Hello');
// Inspect expected errors programmatically
$expectedErrors = $method->expectedErrors;
// [TelegramErrorCode::ChatNotFound, TelegramErrorCode::BotBlocked, TelegramErrorCode::MessageTooLong, ...]
```
In IDEs (PhpStorm, VSCode), all methods in `TelegramMethods` and individual method classes contain `@throws` annotations for IDE autocompletion and inspection.
---
## 7. Lifecycle Event Hooks
You can register callbacks on the `Telegram` facade to observe or intercept requests and responses:
```php
$bot = new Telegram('YOUR_BOT_TOKEN');
// 1. Before sending HTTP request
$bot->onBeforeRequest(function (Request $request, Config $config) {
// Log outbound request or attach telemetry spans
});
// 2. Immediately after raw HTTP response is received
$bot->onAfterRequest(function (Response $response, Request $request) {
// Track HTTP status code and latency
});
// 3. When an error or exception occurs
$bot->onError(function (Throwable $error, Request $request) {
// Send alert to Sentry, Bugsnag, or log
});
// 4. When the final result (Type or Error) is created
$bot->onResponse(function (Type $result, Request $request) {
// Inspect the resulting Type or Error instance
});
```
---
## 8. Incoming Update Error Handling (`catch` & `onUpdateError`)
When handling incoming updates (in controllers, routes, conversation flows, or update handlers), uncaught exceptions can disrupt update dispatching if not handled. `tueen/telegram` provides first-class, bulletproof update exception handling:
### Universal Catch
Catches any `\Throwable` thrown during the processing of an incoming update:
```php
$bot->catch(function (Throwable $e, Update $update, Telegram $bot) {
// 1. Log or report to monitoring service
error_log("[Update {$update->updateId} Failed]: " . $e->getMessage());
// 2. Safely reply to the user using contextual chatId
$bot->reply('โ ๏ธ An unexpected error occurred while processing your request.');
});
```
> [!TIP]
> You can also use `$bot->onUpdateError(...)` or `$app->catch(...)` as an identical fluent alias.
### Typed Catch (Specific Exception Filtering)
You can register granular error handlers for specific exception classes. Any unmatched exceptions will cascade to subsequent handlers or default logging:
```php
// Handles database query failures
$bot->catch(DatabaseException::class, function (DatabaseException $e, Update $update, Telegram $bot) {
$bot->reply('Database temporarily unavailable. Please try again shortly.');
});
// Handles payment or order domain exceptions
$bot->catch(OrderExpiredException::class, function (OrderExpiredException $e, Update $update, Telegram $bot) {
$bot->reply('Your order has expired. Please restart the checkout.');
});
// Fallback universal catch
$bot->catch(function (Throwable $e, Update $update, Telegram $bot) {
$bot->reply('Something went wrong.');
});
```
### Quick Reply Helper (`$bot->reply`)
Inside any update handler or error handler, `$bot->reply($text, ...)` automatically resolves the active `chat_id` from the contextual update:
```php
// Automatically sends to current chat without manually extracting chatId
$bot->reply('Hello from Tueen!');
// Accepts formatted Text, keyboards, and extra API parameters
$bot->reply(
text: Text::bold('Notice: ') . 'Service updated.',
replyMarkup: InlineKeyboard::make()->url('Support', 'https://example.com/support')
);
```
---
## ๐งญ 9. Error Handling & Hooks API Catalog
Below is the complete reference of lifecycle hooks, exception catchers, and typed error inspection methods.
### ๐ช Lifecycle Request Hooks (`Telegram`)
---
### ๐ก๏ธ Incoming Update Exception Catchers (`Telegram`)
# ==============================================================================
# File: /guide/testing
# Section: Application & Production > Testing & Fakes
# ==============================================================================
# Testing & Fakes
Testing Telegram bots often poses challenges: calling real Telegram API servers during automated tests is slow, rate-limited, and may inadvertently message real users.
`tueen/telegram` ships with built-in testing fakes inspired by modern testing paradigms. With `Telegram::fake()`, you can intercept all outbound API calls, stub custom responses, and assert that expected API methods were called with precise parameters.
---
## ๐งช 1. Quick Example
```php
use PHPUnit\Framework\TestCase;
use Tueen\Telegram\Telegram;
use Tueen\Telegram\Types\Update;
class WelcomeBotTest extends TestCase
{
public function test_it_sends_welcome_message_on_start(): void
{
// 1. Swap real client with in-memory recording fake
$bot = Telegram::fake();
// 2. Simulate an incoming update
$update = new Update([
'update_id' => 1,
'message' => [
'message_id' => 10,
'chat' => ['id' => 12345, 'type' => 'private'],
'text' => '/start',
],
]);
// 3. Run handler or controller
$bot->onCommand('start', function (Update $update, Telegram $bot) {
$bot->sendMessage(
chatId: $update->findChat()->id,
text: 'Welcome!'
);
});
$bot->router->dispatch($update, $bot);
// 4. Assert that sendMessage was called with expected parameters
$bot->assertSent('sendMessage', function (array $params) {
return $params['chat_id'] === 12345 && $params['text'] === 'Welcome!';
});
$bot->assertSentCount('sendMessage', 1);
}
}
```
---
## ๐ ๏ธ 2. Response Stubbing
By default, `Telegram::fake()` provides a generic success response (`['ok' => true, 'result' => []]`) for any method called.
You can customize response stubs to test how your application handles specific Telegram responses or errors:
### Stubbing Successful Responses
```php
$bot = Telegram::fake();
// Stub a specific method response:
$bot->fakeResponse('getMe', [
'ok' => true,
'result' => [
'id' => 999999,
'is_bot' => true,
'first_name' => 'TestBot',
'username' => 'test_bot',
],
]);
$user = $bot->getMe();
$this->assertSame('test_bot', $user->username);
```
### Stubbing Telegram API Errors
Test how your system responds when Telegram returns an error (e.g. user blocked the bot):
```php
$bot = Telegram::fake();
$bot->fakeError('sendMessage', 'Forbidden: bot was blocked by the user', 403);
// In ErrorHandlingMode::EXCEPTION:
$this->expectException(\Tueen\Telegram\Exceptions\TelegramException::class);
$bot->sendMessage(chatId: 123, text: 'Hello');
```
---
## ๐ 3. Inspecting Sent Requests
You can inspect the recorded requests directly:
```php
$requests = $bot->sentRequests(); // list of recorded Request objects
$lastRequest = $bot->lastSentRequest();
echo $lastRequest->method; // e.g. "sendMessage"
print_r($lastRequest->params);
```
---
## ๐งญ 4. Testing & Fakes API Catalog
Below is the complete reference of assertion methods, stubbing helpers, and inspection APIs available on `Tueen\Telegram\Testing\TelegramFake`.
### ๐ก๏ธ Assertion Methods (`TelegramFake`)
---
### ๐ญ Stubbing & Inspection Methods (`TelegramFake`)
# ==============================================================================
# File: /guide/enums
# Section: Application & Production > Enums Reference
# ==============================================================================
# Enums Reference
`tueen/telegram` provides Backed Enums for Telegram Bot API string constants, ensuring type safety and IDE autocompletion.
---
## 1. Core Telegram Enums
### `ParseMode`
Specifies how Telegram parses entities in messages or captions:
```php
use Tueen\Telegram\Enums\ParseMode;
ParseMode::HTML; // 'HTML'
ParseMode::MARKDOWN_V2; // 'MarkdownV2'
ParseMode::MARKDOWN; // 'Markdown' (Legacy)
```
```php
$bot->sendMessage(
chatId: 12345,
text: "Hello World!",
parseMode: ParseMode::HTML
);
```
### `ChatAction`
Tells the user that something is happening on the bot's side (e.g. typing, uploading media):
```php
use Tueen\Telegram\Enums\ChatAction;
ChatAction::TYPING; // 'typing'
ChatAction::UPLOAD_PHOTO; // 'upload_photo'
ChatAction::RECORD_VIDEO; // 'record_video'
ChatAction::UPLOAD_VIDEO; // 'upload_video'
ChatAction::RECORD_VOICE; // 'record_voice'
ChatAction::UPLOAD_VOICE; // 'upload_voice'
ChatAction::UPLOAD_DOCUMENT; // 'upload_document'
ChatAction::CHOOSE_STICKER; // 'choose_sticker'
ChatAction::FIND_LOCATION; // 'find_location'
ChatAction::RECORD_VIDEO_NOTE; // 'record_video_note'
ChatAction::UPLOAD_VIDEO_NOTE; // 'upload_video_note'
```
```php
$bot->sendChatAction(chatId: 12345, action: ChatAction::TYPING);
```
### `ChatType`
Identifies the type of conversation:
```php
use Tueen\Telegram\Enums\ChatType;
ChatType::SENDER; // 'sender'
ChatType::PRIVATE; // 'private'
ChatType::GROUP; // 'group'
ChatType::SUPERGROUP; // 'supergroup'
ChatType::CHANNEL; // 'channel'
```
```php
if ($message->chat->type === ChatType::PRIVATE) {
// 1-on-1 private chat
}
```
### `ChatMemberStatus`
Identifies a user's role and privileges in a chat or channel:
```php
use Tueen\Telegram\Enums\ChatMemberStatus;
ChatMemberStatus::CREATOR; // 'creator'
ChatMemberStatus::ADMINISTRATOR; // 'administrator'
ChatMemberStatus::MEMBER; // 'member'
ChatMemberStatus::RESTRICTED; // 'restricted'
ChatMemberStatus::LEFT; // 'left'
ChatMemberStatus::KICKED; // 'kicked'
```
### `DiceEmoji`
Animation types for `sendDice`:
```php
use Tueen\Telegram\Enums\DiceEmoji;
DiceEmoji::DICE; // '๐ฒ' (Values 1-6)
DiceEmoji::DARTS; // '๐ฏ' (Values 1-6)
DiceEmoji::BASKETBALL; // '๐' (Values 1-5)
DiceEmoji::FOOTBALL; // 'โฝ' (Values 1-5)
DiceEmoji::SLOT_MACHINE; // '๐ฐ' (Values 1-64)
DiceEmoji::BOWLING; // '๐ณ' (Values 1-6)
```
```php
$res = $bot->sendDice(chatId: 12345, emoji: DiceEmoji::SLOT_MACHINE);
echo "Result value: {$res->dice->value}\n";
```
### `ButtonStyle`
Button colors for inline keyboards:
```php
use Tueen\Telegram\Enums\ButtonStyle;
ButtonStyle::PRIMARY; // 'primary' (blue)
ButtonStyle::SUCCESS; // 'success' (green)
ButtonStyle::DANGER; // 'danger' (red)
```
### `BotCommandScopeType`
Defines who can see specific bot commands:
```php
use Tueen\Telegram\Enums\BotCommandScopeType;
BotCommandScopeType::DEFAULT; // 'default'
BotCommandScopeType::ALL_PRIVATE_CHATS; // 'all_private_chats'
BotCommandScopeType::ALL_GROUP_CHATS; // 'all_group_chats'
BotCommandScopeType::ALL_CHAT_ADMINISTRATORS; // 'all_chat_administrators'
BotCommandScopeType::CHAT; // 'chat'
BotCommandScopeType::CHAT_ADMINISTRATORS; // 'chat_administrators'
BotCommandScopeType::CHAT_MEMBER; // 'chat_member'
```
### `PollType`
```php
use Tueen\Telegram\Enums\PollType;
PollType::REGULAR; // 'regular'
PollType::QUIZ; // 'quiz'
```
---
## 2. Update & Message Enums
### `UpdateType`
Every update received from Telegram is classified into an `UpdateType` enum:
| Enum Case | Value | Trigger |
| :--- | :--- | :--- |
| `UpdateType::MESSAGE` | `'message'` | Incoming user message |
| `UpdateType::EDITED_MESSAGE` | `'edited_message'` | Edited message |
| `UpdateType::CHANNEL_POST` | `'channel_post'` | New channel post |
| `UpdateType::EDITED_CHANNEL_POST` | `'edited_channel_post'` | Edited channel post |
| `UpdateType::BUSINESS_CONNECTION` | `'business_connection'` | Telegram Business connected/disconnected |
| `UpdateType::BUSINESS_MESSAGE` | `'business_message'` | Message in Telegram Business chat |
| `UpdateType::EDITED_BUSINESS_MESSAGE` | `'edited_business_message'` | Edited Business message |
| `UpdateType::DELETED_BUSINESS_MESSAGES` | `'deleted_business_messages'` | Deleted Business messages |
| `UpdateType::MESSAGE_REACTION` | `'message_reaction'` | User reacted to a message |
| `UpdateType::MESSAGE_REACTION_COUNT` | `'message_reaction_count'` | Anonymous reaction tally changed |
| `UpdateType::INLINE_QUERY` | `'inline_query'` | Inline query typed (`@bot query`) |
| `UpdateType::CHOSEN_INLINE_RESULT` | `'chosen_inline_result'` | Inline result clicked |
| `UpdateType::CALLBACK_QUERY` | `'callback_query'` | Inline keyboard button tapped |
| `UpdateType::SHIPPING_QUERY` | `'shipping_query'` | Shipping query for invoice |
| `UpdateType::PRE_CHECKOUT_QUERY` | `'pre_checkout_query'` | Final order verification before payment |
| `UpdateType::PURCHASED_PAID_MEDIA` | `'purchased_paid_media'` | Telegram Stars paid media purchased |
| `UpdateType::POLL` | `'poll'` | Poll state change |
| `UpdateType::POLL_ANSWER` | `'poll_answer'` | User voted in a non-anonymous poll |
| `UpdateType::MY_CHAT_MEMBER` | `'my_chat_member'` | Bot's status in a chat updated |
| `UpdateType::CHAT_MEMBER` | `'chat_member'` | A member's status updated in a chat |
| `UpdateType::CHAT_JOIN_REQUEST` | `'chat_join_request'` | User requested to join a private group/channel |
| `UpdateType::CHAT_BOOST` | `'chat_boost'` | Premium user boosted the chat |
| `UpdateType::REMOVED_CHAT_BOOST` | `'removed_chat_boost'` | Boost expired or removed |
---
### `MessageType`
Classifies the content of a `Message` object:
| Category | Enum Cases |
| :--- | :--- |
| **Text & Rich Text** | `TEXT`, `RICH_MESSAGE` |
| **Media Attachments** | `PHOTO`, `VIDEO`, `AUDIO`, `VOICE`, `DOCUMENT`, `STICKER`, `ANIMATION`, `VIDEO_NOTE`, `PAID_MEDIA` |
| **Interactive & Games** | `DICE`, `GAME`, `POLL`, `LOCATION`, `VENUE`, `CONTACT`, `STORY` |
| **System & Chat Events** | `NEW_CHAT_MEMBERS`, `LEFT_CHAT_MEMBER`, `NEW_CHAT_TITLE`, `NEW_CHAT_PHOTO`, `DELETE_CHAT_PHOTO`, `GROUP_CHAT_CREATED`, `SUPERGROUP_CHAT_CREATED`, `CHANNEL_CHAT_CREATED`, `PINNED_MESSAGE` |
| **Payments & Stars** | `INVOICE`, `SUCCESSFUL_PAYMENT`, `REFUNDED_PAYMENT` |
| **Forum & Topics** | `FORUM_TOPIC_CREATED`, `FORUM_TOPIC_EDITED`, `FORUM_TOPIC_CLOSED`, `FORUM_TOPIC_REOPENED`, `GENERAL_FORUM_TOPIC_HIDDEN`, `GENERAL_FORUM_TOPIC_UNHIDDEN` |
| **Giveaways & Boosts** | `GIVEAWAY`, `GIVEAWAY_CREATED`, `GIVEAWAY_WINNERS`, `GIVEAWAY_COMPLETED`, `BOOST_ADDED` |
| **Web Apps & Passport** | `CONNECTED_WEBSITE`, `WRITE_ACCESS_ALLOWED`, `PASSPORT_DATA`, `USERS_SHARED`, `CHAT_SHARED` |
---
## 3. Complete Enums Reference
For reference, here is the complete index of Enums available in `Tueen\Telegram\Enums`:
- `BackgroundFillType`
- `BackgroundTypeType`
- `BotCommandScopeType`
- `ButtonStyle`
- `ChatAction`
- `ChatBoostSourceSource`
- `ChatJoinRequestResult`
- `ChatMemberStatus`
- `ChatType`
- `Currency`
- `DiceEmoji`
- `ErrorHandlingMode`
- `ForumIconColor`
- `InlineQueryResultType`
- `InputMediaType`
- `InputPaidMediaType`
- `InputProfilePhotoType`
- `InputRichBlockButtonsAlign`
- `InputRichBlockType`
- `InputStoryContentType`
- `MaskPositionPoint`
- `MenuButtonType`
- `MessageEntityType`
- `MessageOriginType`
- `MessageType`
- `OwnedGiftType`
- `PaidMediaType`
- `ParseMode`
- `PassportSource`
- `PassportType`
- `PollType`
- `ReactionTypeType`
- `RevenueWithdrawalStateType`
- `RichBlockButtonsAlign`
- `RichBlockListItemType`
- `RichBlockTableCellAlign`
- `RichBlockTableCellValign`
- `RichBlockType`
- `RichTextType`
- `StickerFormat`
- `StickerType`
- `StoryActivePeriod`
- `StoryAreaTypeType`
- `SubscriptionState`
- `SuggestedPostInfoState`
- `SuggestedPostRefundedReason`
- `TelegramErrorCode`
- `TransactionPartnerType`
- `UniqueGiftInfoOrigin`
- `UniqueGiftModelRarity`
- `UpdateType`
- `VideoQualityCodec`