Files
max-bot-api-client-php/src/ModelFactory.php
T
2026-04-25 18:41:05 +03:00

479 lines
18 KiB
PHP

<?php
declare(strict_types=1);
namespace BushlanovDev\MaxMessengerBot;
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
use BushlanovDev\MaxMessengerBot\Enums\InlineButtonType;
use BushlanovDev\MaxMessengerBot\Enums\MarkupType;
use BushlanovDev\MaxMessengerBot\Enums\ReplyButtonType;
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
use BushlanovDev\MaxMessengerBot\Models\Attachments\AbstractAttachment;
use BushlanovDev\MaxMessengerBot\Models\Attachments\AudioAttachment;
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\AbstractInlineButton;
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\CallbackButton;
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\ChatButton;
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\ClipboardButton;
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\LinkButton;
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\MessageButton;
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\OpenAppButton;
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\RequestContactButton;
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Inline\RequestGeoLocationButton;
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply\AbstractReplyButton;
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply\SendContactButton;
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply\SendGeoLocationButton;
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\Reply\SendMessageButton;
use BushlanovDev\MaxMessengerBot\Models\Attachments\ContactAttachment;
use BushlanovDev\MaxMessengerBot\Models\Attachments\FileAttachment;
use BushlanovDev\MaxMessengerBot\Models\Attachments\InlineKeyboardAttachment;
use BushlanovDev\MaxMessengerBot\Models\Attachments\LocationAttachment;
use BushlanovDev\MaxMessengerBot\Models\Attachments\PhotoAttachment;
use BushlanovDev\MaxMessengerBot\Models\Attachments\ShareAttachment;
use BushlanovDev\MaxMessengerBot\Models\Attachments\StickerAttachment;
use BushlanovDev\MaxMessengerBot\Models\Attachments\VideoAttachment;
use BushlanovDev\MaxMessengerBot\Models\BotInfo;
use BushlanovDev\MaxMessengerBot\Models\Chat;
use BushlanovDev\MaxMessengerBot\Models\ChatList;
use BushlanovDev\MaxMessengerBot\Models\ChatMember;
use BushlanovDev\MaxMessengerBot\Models\ChatMembersList;
use BushlanovDev\MaxMessengerBot\Models\Markup\AbstractMarkup;
use BushlanovDev\MaxMessengerBot\Models\Markup\EmphasizedMarkup;
use BushlanovDev\MaxMessengerBot\Models\Markup\HeadingMarkup;
use BushlanovDev\MaxMessengerBot\Models\Markup\HighlightedMarkup;
use BushlanovDev\MaxMessengerBot\Models\Markup\LinkMarkup;
use BushlanovDev\MaxMessengerBot\Models\Markup\MonospacedMarkup;
use BushlanovDev\MaxMessengerBot\Models\Markup\StrikethroughMarkup;
use BushlanovDev\MaxMessengerBot\Models\Markup\StrongMarkup;
use BushlanovDev\MaxMessengerBot\Models\Markup\UnderlineMarkup;
use BushlanovDev\MaxMessengerBot\Models\Markup\UserMentionMarkup;
use BushlanovDev\MaxMessengerBot\Models\Message;
use BushlanovDev\MaxMessengerBot\Models\MessageBody;
use BushlanovDev\MaxMessengerBot\Models\Result;
use BushlanovDev\MaxMessengerBot\Models\Subscription;
use BushlanovDev\MaxMessengerBot\Models\UpdateList;
use BushlanovDev\MaxMessengerBot\Models\Updates\AbstractUpdate;
use BushlanovDev\MaxMessengerBot\Models\Updates\BotAddedToChatUpdate;
use BushlanovDev\MaxMessengerBot\Models\Updates\BotRemovedFromChatUpdate;
use BushlanovDev\MaxMessengerBot\Models\Updates\BotStartedUpdate;
use BushlanovDev\MaxMessengerBot\Models\Updates\BotStoppedUpdate;
use BushlanovDev\MaxMessengerBot\Models\Updates\ChatTitleChangedUpdate;
use BushlanovDev\MaxMessengerBot\Models\Updates\DialogClearedUpdate;
use BushlanovDev\MaxMessengerBot\Models\Updates\DialogMutedUpdate;
use BushlanovDev\MaxMessengerBot\Models\Updates\DialogRemovedUpdate;
use BushlanovDev\MaxMessengerBot\Models\Updates\DialogUnmutedUpdate;
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageCallbackUpdate;
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageChatCreatedUpdate;
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageCreatedUpdate;
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageEditedUpdate;
use BushlanovDev\MaxMessengerBot\Models\Updates\MessageRemovedUpdate;
use BushlanovDev\MaxMessengerBot\Models\Updates\UserAddedToChatUpdate;
use BushlanovDev\MaxMessengerBot\Models\Updates\UserRemovedFromChatUpdate;
use BushlanovDev\MaxMessengerBot\Models\UploadEndpoint;
use BushlanovDev\MaxMessengerBot\Models\VideoAttachmentDetails;
use LogicException;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use ReflectionException;
/**
* Creates DTOs from raw associative arrays returned by the API client.
*/
readonly class ModelFactory
{
private LoggerInterface $logger;
/**
* @param LoggerInterface|null $logger PSR LoggerInterface.
*/
public function __construct(?LoggerInterface $logger = null)
{
$this->logger = $logger ?? new NullLogger();
}
/**
* Simple response to request.
*
* @param array<string, mixed> $data
*
* @return Result
* @throws ReflectionException
*/
public function createResult(array $data): Result
{
return Result::fromArray($data);
}
/**
* Information about the current bot.
*
* @param array<string, mixed> $data
*
* @return BotInfo
* @throws ReflectionException
*/
public function createBotInfo(array $data): BotInfo
{
return BotInfo::fromArray($data);
}
/**
* Information about webhook subscription.
*
* @param array<string, mixed> $data
*
* @return Subscription
* @throws ReflectionException
*/
public function createSubscription(array $data): Subscription
{
return Subscription::fromArray($data);
}
/**
* List of all active webhook subscriptions.
*
* @param array<string, mixed> $data
*
* @return Subscription[]
* @throws ReflectionException
*/
public function createSubscriptions(array $data): array
{
return isset($data['subscriptions']) && is_array($data['subscriptions'])
? array_map([$this, 'createSubscription'], $data['subscriptions'])
: [];
}
/**
* Creates a Message from the specific response structure of the sendMessage endpoint.
*
* @param array<string, mixed> $data The raw response from the client.
*
* @return Message
* @throws ReflectionException
*/
public function createMessageFromSendResponse(array $data): Message
{
$messageData = $data['message'];
$topLevelData = [
'chat_id' => $data['chat_id'] ?? null,
'recipient_id' => $data['recipient_id'] ?? null,
'message_id' => $data['message_id'] ?? null,
];
$messageData = array_merge($messageData, array_filter($topLevelData, fn($value) => $value !== null));
if (isset($messageData['message']) && is_array($messageData['message'])) {
$messageData['body'] = $messageData['message'];
unset($messageData['message']);
}
return $this->createMessage($messageData);
}
/**
* Message.
*
* @param array<string, mixed> $data
*
* @return Message
* @throws ReflectionException
*/
public function createMessage(array $data): Message
{
if (isset($data['body']) && is_array($data['body'])) {
$data['body'] = $this->createMessageBody($data['body']);
}
return Message::fromArray($data);
}
/**
* List of messages.
*
* @param array<string, mixed> $data
*
* @return Message[]
*/
public function createMessages(array $data): array
{
return isset($data['messages']) && is_array($data['messages'])
? array_map([$this, 'createMessage'], $data['messages'])
: [];
}
/**
* Creates a MessageBody object from raw API data, handling polymorphic attachments and markup.
*
* @param array<string, mixed> $data
*
* @return MessageBody
* @throws ReflectionException
*/
private function createMessageBody(array $data): MessageBody
{
if (isset($data['attachments']) && is_array($data['attachments'])) {
$data['attachments'] = array_map(
[$this, 'createAttachment'],
$data['attachments'],
);
}
if (isset($data['markup']) && is_array($data['markup'])) {
$data['markup'] = array_map(
[$this, 'createMarkupElement'],
$data['markup'],
);
}
return MessageBody::fromArray($data);
}
/**
* Creates a specific Attachment model based on the 'type' field.
*
* @param array<string, mixed> $data
*
* @return AbstractAttachment
* @throws ReflectionException
*/
public function createAttachment(array $data): AbstractAttachment
{
$attachmentType = AttachmentType::tryFrom($data['type'] ?? '');
if ($attachmentType === AttachmentType::InlineKeyboard
&& isset($data['payload']['buttons']) && is_array($data['payload']['buttons'])) {
$data['payload']['buttons'] = array_map(
fn($rowOfButtons) => array_map([$this, 'createInlineButton'], $rowOfButtons),
$data['payload']['buttons']
);
}
return match ($attachmentType) {
AttachmentType::Image => PhotoAttachment::fromArray($data),
AttachmentType::Video => VideoAttachment::fromArray($data),
AttachmentType::Audio => AudioAttachment::fromArray($data),
AttachmentType::File => FileAttachment::fromArray($data),
AttachmentType::Sticker => StickerAttachment::fromArray($data),
AttachmentType::Contact => ContactAttachment::fromArray($data),
AttachmentType::InlineKeyboard => InlineKeyboardAttachment::fromArray($data),
AttachmentType::Location => LocationAttachment::fromArray($data),
AttachmentType::Share => ShareAttachment::fromArray($data),
default => throw new LogicException('Unknown or unsupported attachment type: ' . ($data['type'] ?? 'none')),
};
}
/**
* Creates a specific ReplyButton model based on the 'type' field.
*
* @param array<string, mixed> $data
*
* @return AbstractReplyButton
* @throws ReflectionException
* @throws LogicException
*/
public function createReplyButton(array $data): AbstractReplyButton
{
return match (ReplyButtonType::tryFrom($data['type'] ?? '')) {
ReplyButtonType::Message => SendMessageButton::fromArray($data),
ReplyButtonType::UserContact => SendContactButton::fromArray($data),
ReplyButtonType::UserGeoLocation => SendGeoLocationButton::fromArray($data),
default => throw new LogicException(
'Unknown or unsupported reply button type: ' . ($data['type'] ?? 'none')
),
};
}
/**
* Creates a specific InlineButton model based on the 'type' field.
*
* @param array<string, mixed> $data
* @return AbstractInlineButton
* @throws ReflectionException
* @throws LogicException
*/
public function createInlineButton(array $data): AbstractInlineButton
{
return match (InlineButtonType::tryFrom($data['type'] ?? '')) {
InlineButtonType::Callback => CallbackButton::fromArray($data),
InlineButtonType::Link => LinkButton::fromArray($data),
InlineButtonType::RequestContact => RequestContactButton::fromArray($data),
InlineButtonType::RequestGeoLocation => RequestGeoLocationButton::fromArray($data),
InlineButtonType::Chat => ChatButton::fromArray($data),
InlineButtonType::OpenApp => OpenAppButton::fromArray($data),
InlineButtonType::Clipboard => ClipboardButton::fromArray($data),
InlineButtonType::Message => MessageButton::fromArray($data),
default => throw new LogicException(
'Unknown or unsupported inline button type: ' . ($data['type'] ?? 'none')
),
};
}
/**
* Endpoint you should upload to your binaries.
*
* @param array<string, mixed> $data
*
* @return UploadEndpoint
* @throws ReflectionException
*/
public function createUploadEndpoint(array $data): UploadEndpoint
{
return UploadEndpoint::fromArray($data);
}
/**
* Chat information.
*
* @param array<string, mixed> $data
*
* @return Chat
* @throws ReflectionException
*/
public function createChat(array $data): Chat
{
return Chat::fromArray($data);
}
/**
* Creates a list of updates from a raw API response.
*
* @param array<string, mixed> $data Raw response data.
*
* @return UpdateList
* @throws ReflectionException
* @throws LogicException
*/
public function createUpdateList(array $data): UpdateList
{
$updateObjects = [];
if (isset($data['updates']) && is_array($data['updates'])) {
foreach ($data['updates'] as $updateData) {
// Here we delegate the creation of a specific update to another factory method
try {
$updateObjects[] = $this->createUpdate($updateData);
} catch (LogicException $e) {
$this->logger->debug($e->getMessage(), ['payload' => $updateData, 'exception' => $e]);
}
}
}
return new UpdateList(
$updateObjects,
$data['marker'] ? (int)$data['marker'] : null,
);
}
/**
* Creates a specific Update model based on the 'update_type' field.
*
* @param array<string, mixed> $data Raw data for a single update.
*
* @return AbstractUpdate
* @throws ReflectionException
* @throws LogicException
*/
public function createUpdate(array $data): AbstractUpdate
{
return match (UpdateType::tryFrom($data['update_type'] ?? '')) {
UpdateType::MessageCreated => MessageCreatedUpdate::fromArray($data),
UpdateType::MessageCallback => MessageCallbackUpdate::fromArray($data),
UpdateType::MessageEdited => MessageEditedUpdate::fromArray($data),
UpdateType::MessageRemoved => MessageRemovedUpdate::fromArray($data),
UpdateType::BotAdded => BotAddedToChatUpdate::fromArray($data),
UpdateType::BotRemoved => BotRemovedFromChatUpdate::fromArray($data),
UpdateType::DialogMuted => DialogMutedUpdate::fromArray($data),
UpdateType::DialogUnmuted => DialogUnmutedUpdate::fromArray($data),
UpdateType::DialogCleared => DialogClearedUpdate::fromArray($data),
UpdateType::DialogRemoved => DialogRemovedUpdate::fromArray($data),
UpdateType::UserAdded => UserAddedToChatUpdate::fromArray($data),
UpdateType::UserRemoved => UserRemovedFromChatUpdate::fromArray($data),
UpdateType::BotStarted => BotStartedUpdate::fromArray($data),
UpdateType::BotStopped => BotStoppedUpdate::fromArray($data),
UpdateType::ChatTitleChanged => ChatTitleChangedUpdate::fromArray($data),
UpdateType::MessageChatCreated => MessageChatCreatedUpdate::fromArray($data),
default => throw new LogicException(
'Unknown or unsupported update type received: ' . ($data['update_type'] ?? 'none')
),
};
}
/**
* Information about chat list.
*
* @param array<string, mixed> $data
*
* @return ChatList
* @throws ReflectionException
*/
public function createChatList(array $data): ChatList
{
return ChatList::fromArray($data);
}
/**
* Creates a ChatMember object from raw API data.
*
* @param array<string, mixed> $data
*
* @return ChatMember
* @throws ReflectionException
*/
public function createChatMember(array $data): ChatMember
{
return ChatMember::fromArray($data);
}
/**
* Creates a ChatMembersList object from raw API data.
*
* @param array<string, mixed> $data
*
* @return ChatMembersList
* @throws ReflectionException
*/
public function createChatMembersList(array $data): ChatMembersList
{
return ChatMembersList::fromArray($data);
}
/**
* Creates a VideoAttachmentDetails object from raw API data.
*
* @param array<string, mixed> $data
*
* @return VideoAttachmentDetails
* @throws ReflectionException
*/
public function createVideoAttachmentDetails(array $data): VideoAttachmentDetails
{
return VideoAttachmentDetails::fromArray($data);
}
/**
* Creates a specific Markup model based on the 'type' field.
*
* @param array<string, mixed> $data
*
* @return AbstractMarkup
* @throws ReflectionException
*/
public function createMarkupElement(array $data): AbstractMarkup
{
return match (MarkupType::tryFrom($data['type'] ?? '')) {
MarkupType::Strong => StrongMarkup::fromArray($data),
MarkupType::Emphasized => EmphasizedMarkup::fromArray($data),
MarkupType::Monospaced => MonospacedMarkup::fromArray($data),
MarkupType::Strikethrough => StrikethroughMarkup::fromArray($data),
MarkupType::Underline => UnderlineMarkup::fromArray($data),
MarkupType::Heading => HeadingMarkup::fromArray($data),
MarkupType::Highlighted => HighlightedMarkup::fromArray($data),
MarkupType::Link => LinkMarkup::fromArray($data),
MarkupType::UserMention => UserMentionMarkup::fromArray($data),
default => throw new LogicException(
'Unknown or unsupported markup type: ' . ($data['type'] ?? 'none')
),
};
}
}