A Powerful and Easy-to-Use PHP Library for Building Telegram Bot
- Introduction
- Features
- Requirements
- Installation
- Quick Start
- Core Concepts
- Basic Usage
- Handling Messages
- Sending Messages
- Keyboards
- Advanced Features
- Configuration
- Examples
- Testing
- Troubleshooting
- Contributing
- License
TGram is a modern, user-friendly PHP framework for building Telegram bots. Whether you're creating a simple bot that responds to messages or building a complex interactive application, TGram provides all the tools you need with an intuitive API.
With TGram, you can:
- Easily create Telegram bots in PHP
- Handle messages and commands with simple syntax
- Build interactive keyboards and menus
- Manage user interactions effortlessly
- Deploy using polling or webhook methods
✨ Key Features:
- Simple API: Clean, intuitive syntax for developers of all levels
- Command Handling: Built-in support for bot commands (
/start,/help, etc.) - Command Parameters: Extract dynamic values from commands (
/user/{id},/post/{slug}) - Message Listeners: Listen for exact messages or regex patterns
- Regex Support: Match messages using custom regular expressions
- Keyboard Support: Create inline and reply keyboards with buttons
- Message Types ( Media ): Handle photos, documents, videos and more
- Chat Management: Manage chat information and user interactions
- Multiple Modes: Support for both Polling and Webhook processing
- Error Handling: Robust exception handling and error reporting
- Modern PHP: Built with PHP 8.0+ features (attributes, typed properties, etc.)
- Well-Structured: Organized codebase with clear separation of concerns
- Fully Tested: Comprehensive test suite with 100+ tests
Before you start, make sure you have:
- PHP 8.2 or higher
- Composer (dependency manager for PHP)
- Active Telegram Bot Token (create one via BotFather)
The easiest way to install TGram is through Composer:
composer require arefshojaei/tgramThis will download TGram and all its dependencies automatically.
If you prefer to clone the repository directly:
git clone https://github.com/ArefShojaei/TGram.git
cd TGram
composer installHere's the simplest way to get your first bot running:
<?php
require __DIR__ . "/vendor/autoload.php";
use TGram\{Telegram, Context};
// Create a new bot instance with your token
$app = new Telegram("YOUR_BOT_TOKEN_HERE");
// Handle /start command
$app->start(function (Context $ctx) {
$ctx->sendMessage("Hello! 👋 I'm your bot!");
});
// Run the bot
$app->run();That's it! Your bot will now:
- Listen for incoming messages
- Respond to
/startcommand with a greeting - Keep running and polling for new messages
- Open Telegram and search for @BotFather
- Start the conversation and type
/newbot - Follow the prompts to create your bot
- Copy your token and replace
"YOUR_BOT_TOKEN_HERE"in the code above
The Telegram class is your main application. It's the entry point for all bot functionality:
$bot = new Telegram("TOKEN");The Context object is passed to your handlers and contains:
- Update Information: Details about the incoming message
- User Information: Who sent the message
- Methods: Functions to send responses
$app->start(function (Context $context) {
// Access the user
$user = $context->update->user;
echo $user->first_name; // User's first name
// Send a response
$context->sendMessage("Hello!");
});TGram supports two ways to receive updates:
Polling (Default - Long Polling):
- Your bot continuously asks Telegram for new messages
- Simple to set up, no server required
- Good for development and small deployments
Webhook (Server-based):
- Telegram sends updates directly to your server
- More efficient for production
- Requires a public URL and SSL certificate
use TGram\Enums\ProcessMode;
// Start with polling (default)
$app->run(ProcessMode::POLLING);
// Or use webhook
$app->run(ProcessMode::WEBHOOK);The /start command is special - it's sent when a user first interacts with your bot:
$app->start(function (Context $ctx) {
$user = $ctx->update->user;
$message = "Welcome, {$user->first_name}! 👋";
$ctx->sendMessage($message);
});Handle custom commands with the command() method:
$app->command("/help", function (Context $ctx) {
$message = "
📖 **Available Commands:**
/start - Start the bot
/help - Show this help message
/about - Learn about me
";
$ctx->sendMessage($message);
});
$app->command("/about", function (Context $ctx) {
$ctx->sendMessage("I'm a TGram bot! 🤖");
});TGram supports dynamic command parameters using placeholders:
$app->command("/user/{id}", function (Context $ctx) {
$id = $ctx->params("id");
$ctx->sendMessage("User ID: " . $id);
});Use the hears() method to respond to exact messages:
$app->hears("Hello", function (Context $ctx) {
$ctx->sendMessage("Hello to you too! 👋");
});
$app->hears("How are you?", function (Context $ctx) {
$ctx->sendMessage("I'm doing great, thanks for asking! 😊");
});hears() also supports regular expression patterns:
$app->hears('/^hello\s+(.*)$/', function (Context $ctx) {
$name = $ctx->matches[1];
$ctx->sendMessage("Hello $name!");
});Use the on() method to respond to exact media messages:
use TGram\Enums\MediaType;
$app->on(MediaType::PHOTO, function (Context $ctx) {
$ctx->sendMessage("Media message is: Photo");
});
$app->on(MediaType::LOCATION, function (Context $ctx) {
$ctx->sendMessage("Media message is: Location");
});Use the callback() method to respond to exact callback-query messages:
$app->callback(function (Context $ctx) {
$ctx->sendMessage("Callback-query data received.");
});Use the use() method to run a Middleware before messages:
$app->use(function (Context $ctx) {
$isAdmin = $ctx->update->message === "Dashboard";
if (!$isAdmin) {
$ctx->sendMessage("403 | Forbideen");
}
$ctx->sendMessage("Welcome Admin!");
});When a message arrives, you can access detailed information:
$app->start(function (Context $ctx) {
$message = $ctx->update->message;
$user = $ctx->update->user;
// Message details
echo $message->message_id; // Unique message ID
echo $message->text; // Message text
echo $message->chat->id; // Chat ID
// User details
echo $user->id; // User's numeric ID
echo $user->first_name; // First name
echo $user->username; // Username (if set)
echo $user->is_bot; // Is this user a bot?
});$ctx->sendMessage("Hello, world!");Use HTML or Markdown formatting:
// HTML formatting
$message = "This is <b>bold</b> and <i>italic</i> text";
$ctx->sendMessage($message);
// Multiple lines
$message = "
<b>Title</b>
Hello <i>world</i>
Line 3
";
$ctx->sendMessage($message);Add additional parameters to customize your message:
$ctx->sendMessage(
"Hello!",
parse_mode: "HTML", // or "Markdown"
disable_web_page_preview: true, // Don't show link previews
);// Send a photo
$ctx->sendPhoto(
"https://example.com/photo.jpg",
caption: "Beautiful photo! 📸",
);
// Send a document
$ctx->sendDocument(
"https://example.com/file.pdf",
caption: "Important document",
);
// Send voice
$ctx->sendVoice("https://example.com/audio.ogg");Update previously sent messages:
$ctx->editMessageText(
text: "Updated message",
message_id: $messageId,
chat_id: $chatId,
);Reply keyboards appear as a menu at the bottom of the chat:
use TGram\Utils\Keyboard\{Keyboard, Button};
$keyboard = Keyboard::reply()
->row(Button::text("Option 1"), Button::text("Option 2"))
->row(Button::text("Option 3"))
->toArray();
$ctx->sendMessage("Choose an option:", reply_markup: $keyboard);Inline keyboards appear directly above or below messages:
$keyboard = Keyboard::inline()
->row(
Button::url("GitHub", "https://github.com/ArefShojaei/TGram"),
Button::url("Docs", "https://example.com/docs"),
)
->row(Button::callback("Delete", "action_delete"))
->toArray();
$ctx->sendMessage("Check these links:", reply_markup: $keyboard);Text Button:
Button::text("Click me");URL Button:
Button::url("Visit GitHub", "https://github.com");Callback Button:
Button::callback("Vote 👍", "vote_yes");Web App Button:
Button::webApp("Open App", "https://example.com/app");Simple Row:
$keyboard = Keyboard::reply()
->row(Button::text("Yes"), Button::text("No"))
->toArray();Multiple Rows:
$keyboard = Keyboard::reply()
->row(Button::text("Option 1"))
->row(Button::text("Option 2"))
->row(Button::text("Option 3"))
->row(Button::text("Option 4"))
->toArray();Hide Keyboard:
$ctx->sendMessage("Keyboard hidden", reply_markup: ["remove_keyboard" => true]);Configure your bot behavior:
$bot = new Telegram("TOKEN");
$bot->configure([
"transport" => [
"driver" => ProcessMode::POLLING, // polling | webhook
],
"polling" => [
"interval" => 1,
"offset" => 0,
"limit" => 100,
"timeout" => 30,
],
"webhook" => [
"secret_token" => "secret",
],
"http" => [
"timeout" => 30,
"connect_timeout" => 5,
],
"fallback_messages" => [
"text" => "Unknown message",
"media" => "Unsupported media",
"command" => "Unknown command",
],
"logging" => [
"enabled" => true,
"path" => "/logs/tgram.log",
],
]);
$bot->run();When users click inline buttons, handle the callback:
$app->callback(function (Context $ctx) {
$data = $ctx->update->callback_query->data;
if ($data === "vote_yes") {
$ctx->answerCallbackQuery("Thanks for voting! ✅");
// Update message or send new one
}
});Get and manage chat information:
$app->start(function (Context $ctx) {
$chat = $ctx->update->message->chat;
echo $chat->id; // Chat ID
echo $chat->title; // Chat title (for groups)
echo $chat->type; // "private", "group", "supergroup", etc.
// Get chat info from API
$chatInfo = $ctx->getChatInfo();
});Access detailed user data:
$user = $ctx->update->user;
echo $user->id; // Unique user ID
echo $user->is_bot; // Is a bot?
echo $user->first_name; // First name
echo $user->last_name; // Last name (optional)
echo $user->username; // Username (optional)
echo $user->language_code; // User's languageCreate custom filters for advanced message handling:
$app->command("/admin", function (Context $ctx) {
// Check if user is admin
if ($ctx->update->user->id !== ADMIN_ID) {
$ctx->sendMessage("❌ You don't have permission");
return;
}
$ctx->sendMessage("✅ Admin panel");
});Store your token securely using environment variables:
// .env file
TELEGRAM_BOT_TOKEN=your_token_here
// In your code
$token = getenv("TELEGRAM_BOT_TOKEN");
$app = new Telegram($token);To use webhooks instead of polling:
$app = new Telegram("TOKEN");
$webhook_url = "https://your-domain.com/webhook.php";
$webhook_secret_token = "TOKEN"; # Optional
$app->setWebhook($webhook_url, $webhook_secret_token);
$app->run(ProcessMode::WEBHOOK);A bot that repeats everything the user sends:
<?php
require __DIR__ . "/vendor/autoload.php";
use TGram\{Telegram, Context};
$app = new Telegram("TOKEN");
$app->start(function (Context $ctx) {
$ctx->sendMessage("Send me anything and I'll repeat it!");
});
$app->run();A bot with navigation buttons:
<?php
require __DIR__ . "/vendor/autoload.php";
use TGram\{Telegram, Context};
use TGram\Utils\Keyboard\{Keyboard, Button};
$app = new Telegram("TOKEN");
$app->start(function (Context $ctx) {
$keyboard = Keyboard::reply()
->row(Button::text("📚 Learn"))
->row(Button::text("🎮 Games"))
->row(Button::text("⚙️ Settings"))
->toArray();
$ctx->sendMessage("Welcome! Choose an option:", reply_markup: $keyboard);
});
$app->hears("📚 Learn", function (Context $ctx) {
$ctx->sendMessage("Learning resources coming soon!");
});
$app->hears("🎮 Games", function (Context $ctx) {
$keyboard = Keyboard::inline()
->row(
Button::callback("🎲 Dice", "game_dice"),
Button::callback("🃏 Card", "game_card"),
)
->toArray();
$ctx->sendMessage("Pick a game:", reply_markup: $keyboard);
});
$app->hears("⚙️ Settings", function (Context $ctx) {
$ctx->sendMessage("Settings panel will be here");
});
$app->run();TGram includes a comprehensive test suite with 105+ tests covering all major features and components.
The test suite is organized into two main categories:
Unit Tests - Test individual components in isolation:
- Core classes (Bot, Telegram, Context)
- Exceptions and error handling
- Enums and data types
- Abilities and traits
- Utilities and helpers
Feature Tests - Test complete workflows:
- Bot initialization flow
- Command handling
- Message listening
- Keyboard interactions
- Exception handling
composer installThis automatically installs PHPUnit and other testing dependencies defined in composer.json.
Execute the complete test suite:
vendor/bin/phpunitThis runs all 105+ tests and generates a summary report.
Run only Unit Tests:
vendor/bin/phpunit tests/Unit/Run only Feature Tests:
vendor/bin/phpunit tests/Feature/Test Telegram class:
vendor/bin/phpunit tests/Unit/Core/TelegramTest.phpTest Exception handling:
vendor/bin/phpunit tests/Unit/Exceptions/InvalidTokenExceptionTest.phpTest Keyboard utilities:
vendor/bin/phpunit tests/Unit/Utils/KeyboardTest.phpTest listener abilities:
vendor/bin/phpunit tests/Unit/Abilities/CanProvideListenerTest.phpRun tests matching a pattern:
vendor/bin/phpunit --filter testConstructorRun tests for a specific method:
vendor/bin/phpunit --filter CanProvideListenerGenerate HTML code coverage report:
vendor/bin/phpunit --coverage-html coverage/The report will be saved in the coverage/ directory. Open coverage/index.html in a browser to view detailed coverage information.
Generate text coverage summary:
vendor/bin/phpunit --coverage-textGenerate clover format for CI/CD integration:
vendor/bin/phpunit --coverage-clover coverage.xmlRun tests with verbose output:
vendor/bin/phpunit -vRun tests with very verbose output:
vendor/bin/phpunit -vvRun tests and generate test documentation (testdox):
vendor/bin/phpunit --testdoxSave testdox output to file:
vendor/bin/phpunit --testdox > testdox.txtRun tests in parallel (if supported):
vendor/bin/phpunit -pAll test settings are configured in phpunit.xml:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheDirectory=".phpunit.cache"
executionOrder="depends,defects"
beStrictAboutCoverageMetadata="true"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
colors="true"
testdox="true"
failOnWarning="true">
<testsuites>
<testsuite name="default">
<directory>tests</directory>
</testsuite>
</testsuites>
<source restrictDeprecations="true" restrictNotices="true" restrictWarnings="true">
<include>
<directory>src</directory>
</include>
</source>
</phpunit># Quick test run
vendor/bin/phpunit
# Full test suite with coverage
vendor/bin/phpunit --coverage-html coverage/
# Generate test documentation
vendor/bin/phpunit --testdox > tests.txt
# Test a specific component
vendor/bin/phpunit tests/Unit/Core/
# Stop after first failure
vendor/bin/phpunit --stop-on-failure
# Stop after N failures
vendor/bin/phpunit --stop-on-defect=5
# Run tests in reverse order
vendor/bin/phpunit --reverse-order
# Show only skipped and incomplete tests
vendor/bin/phpunit --list-tests
# Display interactive menu
vendor/bin/phpunit --interactiveAll tests are located in the tests/ directory:
tests/
├── Helpers/
│ └── TestHelper.php # Test utility functions
├── Fixtures/
│ ├── FakeUpdateData.php # Mock Telegram data
│ └── FakeTelegramResponse.php # Mock API responses
├── Unit/
│ ├── Core/
│ │ ├── BotTest.php
│ │ ├── TelegramTest.php
│ │ └── ContextTest.php
│ ├── Exceptions/
│ │ ├── InvalidTokenExceptionTest.php
│ │ └── ValidationExceptionTest.php
│ ├── Enums/
│ │ ├── ProcessModeTest.php
│ │ └── HttpMethodTest.php
│ ├── Abilities/
│ │ └── CanProvideListenerTest.php
│ └── Utils/
│ ├── KeyboardTest.php
│ ├── ButtonTest.php
│ └── ConsoleTest.php
└── Feature/
├── BotInitializationTest.php
├── CommandHandlingTest.php
├── MessageListeningTest.php
├── KeyboardInteractionTest.php
└── ExceptionHandlingTest.php
- Overall: 85%+ coverage
- Core Classes: 90%+ coverage
- Exceptions: 100% coverage
- Enums: 100% coverage
- Utilities: 85%+ coverage
To add new tests:
- Create test file in appropriate directory under
tests/ - Extend
PHPUnit\Framework\TestCase - Name test methods with
testprefix - Use descriptive names:
testConstructorWithValidToken() - Follow Arrange-Act-Assert pattern
Example test:
<?php
namespace Tests\Unit\Core;
use PHPUnit\Framework\TestCase;
use TGram\Telegram;
class MyNewTest extends TestCase
{
public function testSomethingWorks(): void
{
// Arrange
$bot = new Telegram("token");
// Act
$result = $bot->doSomething();
// Assert
$this->assertTrue($result);
}
}Problem: Bot doesn't respond to messages
Solutions:
- Verify your bot token is correct
- Check that your bot is actually running
- Make sure you've started a conversation with
/start - Check your bot's privacy settings with @BotFather
Problem: "Connection refused" or timeout errors
Solutions:
- Check your internet connection
- Verify Telegram API is accessible from your server
- Increase timeout in configuration
- Check firewall rules if on a server
Problem: sendMessage() doesn't work
Solutions:
- Verify chat ID is correct
- Ensure the context object is valid
- Check error messages in logs
- Test with a simple text message first
Problem: Bot crashes or uses too much memory
Solutions:
- Reduce
polling_intervalto check less frequently - Avoid storing large data in memory
- Clear temporary files regularly
- Monitor memory usage with
memory_get_usage()
Problem: Tests are failing
Solutions:
- Ensure PHPUnit is installed:
composer install - Check PHP version is 8.0+:
php -v - Verify all dependencies are installed:
composer update - Run tests with verbose output:
vendor/bin/phpunit -v - Check test configuration in
phpunit.xml
We welcome contributions! To contribute:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Write tests for your changes
- Ensure all tests pass:
vendor/bin/phpunit - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- ✅ Run the full test suite
- ✅ Ensure code coverage is maintained
- ✅ Follow PSR-4 autoloading standards
- ✅ Use English for code and comments
- ✅ Update documentation if needed
This project is licensed under the MIT License - see the LICENSE file for details.
For more information:
- 📖 Documentation
- 🐛 Report Issues
- 💬 Discussions
- 📧 Email: arefshojaei82@gmail.com
- Built with ❤️ by Aref Shojaei
- Powered by the Telegram Bot API
- Uses Guzzle HTTP Client
Happy Bot Building! 🚀