Chats & Contacts Management
The Chat and Contact entities represent the primary actors in the Telegram bot ecosystem, while ChatXp, ChatLike, and ChatRating power social engagement and gamification.
💬 The Chat Entity
Telegram represents users, basic groups, supergroups, and channels through a single entity: the Chat.
Chat ID Sign Rule
- Private Users: Have positive IDs (e.g.
123456789). - Groups, Supergroups & Channels: Have negative IDs (e.g.
-1001987654321).
Tueen Database maps Chat.id as a signed 64-bit bigint, fully accommodating both positive user IDs and negative supergroup/channel IDs.
🛠️ The findOrCreate Pattern
The most frequent operation in any Telegram bot is registering a chat on the first message or update. ChatRepository::findOrCreate() handles this atomically:
use Tueen\Database\Enum\ChatType;
$chat = $db->chats()->findOrCreate(
chatId: $update['message']['chat']['id'],
username: $update['message']['chat']['username'] ?? null,
name: $update['message']['chat']['first_name'] ?? 'User',
type: ChatType::Private,
languageCode: $update['message']['from']['language_code'] ?? 'en',
);Automatic Normalization & Touch
- Username Cleaning: Leading
@characters are automatically stripped via PHP 8.4 backed property hooks (set => ...). - Touch Active Time: Calling
findOrCreate()on an existing chat automatically updatesactiveAtandupdatedAt. - Name / Language Synchronization: If the user changed their Telegram name or language,
findOrCreate()updates the database record automatically.
🪝 PHP 8.4 Property Hooks on Chat
The Chat model includes several convenient virtual property hooks:
// Check chat types directly
if ($chat->isPrivate) {
// 1-on-1 private conversation
}
if ($chat->isGroupOrSupergroup) {
// Community group
}
if ($chat->isChannel) {
// Broadcast channel
}
// Display Name helper
echo "Hello, " . $chat->displayName; // Returns name, or @username, or chat ID
// Message delivery status check
if ($chat->canReceiveMessages) {
// True only if accessible = true AND banned = false
}📇 User Contacts & Phone Verification (ContactRepository)
The Contact entity stores verified phone numbers and identity details for Telegram users.
Contact records can originate from multiple sources:
- Telegram Native Contact Sharing: When a user taps a keyboard button requesting contact (
request_contact: true) or forwards a native contact card (message.contact). - Telegram Mini Apps (TMA): Phone numbers requested and verified through WebApp dialogs.
- Manual Input & OTP Verification: Numbers entered via conversational bot steps or SMS flows.
Using ContactRepository
// 1. Save or update contact details
$contact = $db->contacts()->saveContact(
user: $chat,
phoneNumber: '+1 (987) 654-3210', // Automatically stripped to pure digits: 19876543210
firstName: 'Alice',
lastName: 'Smith',
);
echo "Registered: {$contact->fullName} ({$contact->phoneNumber})\n";
// 2. Lookup user by verified phone number
$existing = $db->contacts()->findByPhoneNumber('19876543210');
if ($existing !== null) {
echo "Found user with chat ID: {$existing->user->id}\n";
}
// 3. Lookup contact for a specific chat
$userContact = $db->contacts()->findByChat($chat);TIP
Automatic Phone Normalization:Contact::$phoneNumber utilizes a native PHP 8.4 backed property hook (set => (string) preg_replace('/\D+/', '', $value)) that automatically strips non-digit characters (+, spaces, dashes, parentheses). This ensures clean, uniform phone numbers for database lookups and SMS gateways.
🎮 Gamification & Experience Points (ChatXpRepository)
Telegram bots frequently reward users with experience points (XP) for sending messages, inviting friends, completing tasks, or participating in quizzes.
Tueen Database provides an atomic XP engine with an immutable transaction audit log:
// 1. Award XP points for an activity
$tx = $db->chatXp()->addXp(
chat: $chat,
amount: 100,
source: 'daily_quest',
sourceId: 42,
);
echo "Awarded: {$tx->value} XP\n";
echo "XP Before: {$tx->xpBefore} | XP After: {$tx->xpAfter}\n";
// 2. Query current XP
$currentXp = $db->chatXp()->getXp($chat);
// 3. Deduct XP (e.g. penalty or spent points)
$deductTx = $db->chatXp()->deductXp($chat, 25, 'store_redemption');
// 4. Fetch the Community Leaderboard
$topUsers = $db->chatXp()->getLeaderboard(limit: 10);
foreach ($topUsers as $rank => $entry) {
echo "#" . ($rank + 1) . " {$entry->chat->displayName}: {$entry->xp} XP\n";
}⭐ Social Feedback (ChatLike & ChatRating)
Build social and community features (user reputations, admin karma, peer ratings) between chats:
use Tueen\Database\Entity\ChatLike;
use Tueen\Database\Entity\ChatRating;
// 1. User A likes User B's profile
$like = new ChatLike(
fromChat: $senderChat,
toChat: $targetChat,
);
$db->getEntityManager()->persist($like);
// 2. User A rates User B (e.g. 95 out of 100)
$rating = new ChatRating(
fromChat: $senderChat,
toChat: $targetChat,
rating: 95,
);
$db->getEntityManager()->persist($rating);
$db->getEntityManager()->flush();🏷️ Chat Metadata (ChatMeta)
Attach arbitrary JSON or typed metadata to any chat (settings, onboarding progress, preferences):
use Tueen\Database\Entity\ChatMeta;
$meta = new ChatMeta(
chat: $chat,
metaKey: 'notification_settings',
value: ['sound' => true, 'frequency' => 'daily'],
group: 'preferences',
);
$db->getEntityManager()->persist($meta);
$db->getEntityManager()->flush();🧭 Next Steps
- Manage bot finances with Wallets & Treasury Ledger.
- Implement E-Commerce Orders & Billing.
- Connect Mini Apps with Mini App Sessions & Auth.