├── .gitignore ├── src ├── Button.php ├── helpers.php ├── InlineKeyboard │ ├── InlineKeyboardMarkup.php │ └── InlineKeyboardButton.php ├── ReplyKeyboardRemove.php ├── ForceReply.php ├── ReplyKeyboard │ ├── KeyboardButtonPollType.php │ ├── ReplyKeyboardMarkup.php │ └── KeyboardButton.php ├── FluentEntity.php └── KeyboardMarkup.php ├── docs └── images │ ├── inlinekeyboard-stack.png │ ├── inlinekeyboard-multiple-rows.png │ └── inlinekeyboards-multiline-buttons.png ├── phpunit.xml ├── tests ├── Unit │ ├── ForceReplyTest.php │ ├── ReplyKeyboardRemoveTest.php │ ├── KeyboardButtonPollTypeTest.php │ ├── KeyboardButtonTest.php │ ├── InlineKeyboardButtonTest.php │ ├── InlineKeyboardMarkupTest.php │ └── ReplyKeyboardMarkupTest.php └── Pest.php ├── .github └── workflows │ └── tests.yaml ├── composer.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | .phpunit.result.cache 4 | .phpunit.cache -------------------------------------------------------------------------------- /src/Button.php: -------------------------------------------------------------------------------- 1 | true, 13 | ]; 14 | 15 | protected array $defaults = [ 16 | 'selective' => true, 17 | ]; 18 | 19 | } -------------------------------------------------------------------------------- /src/ForceReply.php: -------------------------------------------------------------------------------- 1 | true, 14 | ]; 15 | 16 | protected array $defaults = [ 17 | 'selective' => true, 18 | ]; 19 | 20 | } -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ./tests 10 | 11 | 12 | 13 | 14 | ./src 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/ReplyKeyboard/KeyboardButtonPollType.php: -------------------------------------------------------------------------------- 1 | 'quiz' 24 | ]); 25 | } 26 | 27 | public static function regular(): static 28 | { 29 | return new static([ 30 | 'type' => 'regular' 31 | ]); 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /tests/Unit/ForceReplyTest.php: -------------------------------------------------------------------------------- 1 | toMatchEntity([ 9 | 'force_reply' => true, 10 | ]); 11 | }); 12 | 13 | it('can set known fields', function () { 14 | $keyboard = ForceReply::make() 15 | ->selective(); 16 | 17 | expect($keyboard)->toMatchEntity([ 18 | 'force_reply' => true, 19 | 'selective' => true, 20 | ]); 21 | }); 22 | 23 | it('can set unknown fields', function () { 24 | $keyboard = ForceReply::make() 25 | ->unknownFields('unknown'); 26 | 27 | expect($keyboard)->toMatchEntity([ 28 | 'force_reply' => true, 29 | 'unknown_fields' => 'unknown' 30 | ]); 31 | }); -------------------------------------------------------------------------------- /src/ReplyKeyboard/ReplyKeyboardMarkup.php: -------------------------------------------------------------------------------- 1 | true, 21 | 'one_time_keyboard' => true, 22 | 'selective' => true, 23 | ]; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | tests: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Validate composer.json and composer.lock 18 | run: composer validate --strict 19 | 20 | - name: Cache Composer packages 21 | id: composer-cache 22 | uses: actions/cache@v2 23 | with: 24 | path: vendor 25 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} 26 | restore-keys: | 27 | ${{ runner.os }}-php- 28 | 29 | - name: Install dependencies 30 | run: composer install --prefer-dist --no-progress 31 | 32 | - name: Run test suite 33 | run: composer run-script test 34 | -------------------------------------------------------------------------------- /tests/Unit/ReplyKeyboardRemoveTest.php: -------------------------------------------------------------------------------- 1 | toMatchEntity([ 9 | 'remove_keyboard' => true 10 | ]); 11 | }); 12 | 13 | it('can set known fields', function () { 14 | $keyboard = ReplyKeyboardRemove::make() 15 | ->selective(); 16 | 17 | expect($keyboard)->toMatchEntity([ 18 | 'remove_keyboard' => true, 19 | 'selective' => true, 20 | ]); 21 | }); 22 | 23 | it('can set unknown fields', function () { 24 | $keyboard = ReplyKeyboardRemove::make() 25 | ->unknownFields('unknown'); 26 | 27 | expect($keyboard)->toMatchEntity([ 28 | 'remove_keyboard' => true, 29 | 'unknown_fields' => 'unknown' 30 | ]); 31 | }); -------------------------------------------------------------------------------- /tests/Unit/KeyboardButtonPollTypeTest.php: -------------------------------------------------------------------------------- 1 | toMatchEntity([]); 9 | }); 10 | 11 | it('creates quiz type', function () { 12 | $button = KeyboardButtonPollType::quiz(); 13 | 14 | expect($button)->toMatchEntity([ 15 | 'type' => 'quiz' 16 | ]); 17 | }); 18 | 19 | it('creates regular type', function () { 20 | $button = KeyboardButtonPollType::regular(); 21 | 22 | expect($button)->toMatchEntity([ 23 | 'type' => 'regular' 24 | ]); 25 | }); 26 | 27 | it('creates unknown type', function () { 28 | $button = new KeyboardButtonPollType([ 29 | 'type' => 'unknown', 30 | ]); 31 | 32 | expect($button)->toMatchEntity([ 33 | 'type' => 'unknown', 34 | ]); 35 | }); 36 | -------------------------------------------------------------------------------- /src/FluentEntity.php: -------------------------------------------------------------------------------- 1 | data = $data + $this->data; 15 | } 16 | 17 | public static function make(): static 18 | { 19 | return new static; 20 | } 21 | 22 | public function __call($name, $arguments): self 23 | { 24 | $key = snake_case($name); 25 | 26 | $this->data[$key] = $arguments[0] ?? $this->getDefault($key); 27 | 28 | return $this; 29 | } 30 | 31 | public function jsonSerialize(): object 32 | { 33 | return (object) $this->data; 34 | } 35 | 36 | private function getDefault($key): mixed 37 | { 38 | return $this->defaults[$key] ?? null; 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "php-telegram-bot/fluent-keyboard", 3 | "description": "Fluent Keyboard builder for ReplyKeyboardMarkup and InlineKeyboardMarkup.", 4 | "keywords": ["telegram", "bot", "api", "fluent", "keyboard", "builder"], 5 | "type": "library", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Tii", 10 | "email": "mail@tii.one" 11 | } 12 | ], 13 | "minimum-stability": "stable", 14 | "autoload": { 15 | "files": [ 16 | "./src/helpers.php" 17 | ], 18 | "psr-4": { 19 | "PhpTelegramBot\\FluentKeyboard\\": "./src" 20 | } 21 | }, 22 | "require": { 23 | "php": "^8.0" 24 | }, 25 | "require-dev": { 26 | "pestphp/pest": "^1.21", 27 | "ext-json": "*" 28 | }, 29 | "scripts": { 30 | "test": "vendor/bin/pest" 31 | }, 32 | "config": { 33 | "allow-plugins": { 34 | "pestphp/pest-plugin": true 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Unit/KeyboardButtonTest.php: -------------------------------------------------------------------------------- 1 | toMatchEntity([]); 9 | }); 10 | 11 | it('can set known fields', function () { 12 | $button = KeyboardButton::make() 13 | ->text('Text') 14 | ->requestContact() 15 | ->requestLocation() 16 | ->requestPoll(); 17 | 18 | expect($button)->toMatchEntity([ 19 | 'text' => 'Text', 20 | 'request_contact' => true, 21 | 'request_location' => true, 22 | 'request_poll' => [] 23 | ]); 24 | }); 25 | 26 | it('can set text via make', function () { 27 | $button = KeyboardButton::make('Text'); 28 | 29 | expect($button)->toMatchEntity([ 30 | 'text' => 'Text', 31 | ]); 32 | }); 33 | 34 | it('can set unknown fields', function () { 35 | $button = KeyboardButton::make() 36 | ->unknownField('Test'); 37 | 38 | expect($button)->toMatchEntity([ 39 | 'unknown_field' => 'Test', 40 | ]); 41 | }); -------------------------------------------------------------------------------- /src/ReplyKeyboard/KeyboardButton.php: -------------------------------------------------------------------------------- 1 | true, 18 | 'request_location' => true, 19 | 'request_poll' => [], 20 | ]; 21 | 22 | public static function make(string $text = null): static 23 | { 24 | $data = []; 25 | 26 | if ($text !== null) { 27 | $data['text'] = $text; 28 | } 29 | 30 | return new static($data); 31 | } 32 | 33 | public function webApp(array|string $web_app): self 34 | { 35 | if (is_string($web_app)) { 36 | $web_app = [ 37 | 'url' => $web_app, 38 | ]; 39 | } 40 | 41 | $this->data['web_app'] = $web_app; 42 | 43 | return $this; 44 | } 45 | } -------------------------------------------------------------------------------- /src/InlineKeyboard/InlineKeyboardButton.php: -------------------------------------------------------------------------------- 1 | true, 21 | ]; 22 | 23 | public static function make(string $text = null): static 24 | { 25 | $data = []; 26 | 27 | if ($text !== null) { 28 | $data['text'] = $text; 29 | } 30 | 31 | return new static($data); 32 | } 33 | 34 | public function webApp(array|string $web_app): self 35 | { 36 | if (is_string($web_app)) { 37 | $web_app = [ 38 | 'url' => $web_app, 39 | ]; 40 | } 41 | 42 | $this->data['web_app'] = $web_app; 43 | 44 | return $this; 45 | } 46 | 47 | public function loginUrl(array|string $login_url): self 48 | { 49 | if (is_string($login_url)) { 50 | $login_url = [ 51 | 'url' => $login_url 52 | ]; 53 | } 54 | 55 | $this->data['login_url'] = $login_url; 56 | 57 | return $this; 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /tests/Pest.php: -------------------------------------------------------------------------------- 1 | in('Feature'); 15 | 16 | /* 17 | |-------------------------------------------------------------------------- 18 | | Expectations 19 | |-------------------------------------------------------------------------- 20 | | 21 | | When you're writing tests, you often need to check that values meet certain conditions. The 22 | | "expect()" function gives you access to a set of "expectations" methods that you can use 23 | | to assert different things. Of course, you may extend the Expectation API at any time. 24 | | 25 | */ 26 | 27 | expect()->extend('toMatchEntity', function ($array) { 28 | return expect(json_encode($this->value))->json()->toMatchArray($array); 29 | }); 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Functions 34 | |-------------------------------------------------------------------------- 35 | | 36 | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your 37 | | project that you don't want to repeat in every file. Here you can also expose helpers as 38 | | global functions to help you to reduce the number of lines of code in your test files. 39 | | 40 | */ 41 | 42 | //function something() 43 | //{ 44 | // // .. 45 | //} 46 | -------------------------------------------------------------------------------- /tests/Unit/InlineKeyboardButtonTest.php: -------------------------------------------------------------------------------- 1 | toMatchEntity([ 9 | 10 | ]); 11 | }); 12 | 13 | it('can set known fields', function () { 14 | $button = InlineKeyboardButton::make() 15 | ->text('Text') 16 | ->url('http://example.com') 17 | ->loginUrl([]) 18 | ->callbackData('Callback Data') 19 | ->switchInlineQuery('Switch Inline Query') 20 | ->switchInlineQueryCurrentChat('Switch Inline Query Current Chat') 21 | ->callbackGame('Callback Game') 22 | ->pay(); 23 | 24 | expect($button)->toMatchEntity([ 25 | 'text' => 'Text', 26 | 'url' => 'http://example.com', 27 | 'login_url' => [], 28 | 'callback_data' => 'Callback Data', 29 | 'switch_inline_query' => 'Switch Inline Query', 30 | 'switch_inline_query_current_chat' => 'Switch Inline Query Current Chat', 31 | 'callback_game' => 'Callback Game', 32 | 'pay' => true, 33 | ]); 34 | }); 35 | 36 | it('can set text via make', function () { 37 | $button = InlineKeyboardButton::make('Test'); 38 | 39 | expect($button)->toMatchEntity([ 40 | 'text' => 'Test', 41 | ]); 42 | }); 43 | 44 | it('can set unknown fields', function () { 45 | $button = InlineKeyboardButton::make() 46 | ->unknownField('unknown'); 47 | 48 | expect($button)->toMatchEntity([ 49 | 'unknown_field' => 'unknown', 50 | ]); 51 | }); -------------------------------------------------------------------------------- /src/KeyboardMarkup.php: -------------------------------------------------------------------------------- 1 | data[static::$keyboardFieldName] = [[]]; 23 | } 24 | 25 | /** 26 | * Adds a new row to the keyboard. 27 | * 28 | * @param Button[] $buttons 29 | * @return $this 30 | */ 31 | public function row(array $buttons = []): self 32 | { 33 | $keyboard = &$this->data[static::$keyboardFieldName]; 34 | 35 | // Last row is not empty, add new row 36 | if (! empty($keyboard[$this->currentRowIndex])) { 37 | $keyboard[] = []; 38 | $this->currentRowIndex++; 39 | } 40 | 41 | // Buttons have been passed, add them 42 | if (! empty($buttons)) { 43 | $keyboard[$this->currentRowIndex] = $buttons; 44 | $this->currentRowIndex++; 45 | } 46 | 47 | return $this; 48 | } 49 | 50 | /** 51 | * Adds buttons to the keyboard, one per row. 52 | * 53 | * @param Button[] $buttons 54 | * @return $this 55 | */ 56 | public function stack(array $buttons): self 57 | { 58 | foreach ($buttons as $button) { 59 | $this->row([$button]); 60 | } 61 | 62 | return $this; 63 | } 64 | 65 | /** 66 | * Adds a button to the last row. 67 | * 68 | * @param Button $button 69 | * @return $this 70 | */ 71 | public function button(Button $button): self 72 | { 73 | $keyboard = &$this->data[static::$keyboardFieldName]; 74 | 75 | $keyboard[$this->currentRowIndex][] = $button; 76 | 77 | return $this; 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /tests/Unit/InlineKeyboardMarkupTest.php: -------------------------------------------------------------------------------- 1 | toMatchEntity([ 10 | 'inline_keyboard' => [[]] 11 | ]); 12 | }); 13 | 14 | it('can set unknown fields', function () { 15 | $keyboard = InlineKeyboardMarkup::make() 16 | ->unknownField('unknown'); 17 | 18 | expect($keyboard)->toMatchEntity([ 19 | 'unknown_field' => 'unknown', 20 | ]); 21 | }); 22 | 23 | it('can have multiple buttons', function () { 24 | $keyboard = InlineKeyboardMarkup::make() 25 | ->button(InlineKeyboardButton::make()->text('Button A')) 26 | ->button(InlineKeyboardButton::make()->text('Button B')) 27 | ->row() 28 | ->button(InlineKeyboardButton::make()->text('Button C')) 29 | ->button(InlineKeyboardButton::make()->text('Button D')); 30 | 31 | expect($keyboard)->toMatchEntity([ 32 | 'inline_keyboard' => [ 33 | [ 34 | ['text' => 'Button A'], 35 | ['text' => 'Button B'], 36 | ], [ 37 | ['text' => 'Button C'], 38 | ['text' => 'Button D'], 39 | ] 40 | ] 41 | ]); 42 | }); 43 | 44 | it('can have multiple rows', function () { 45 | $keyboard = InlineKeyboardMarkup::make() 46 | ->row([ 47 | InlineKeyboardButton::make()->text('Button A'), 48 | InlineKeyboardButton::make()->text('Button B'), 49 | ])->row([ 50 | InlineKeyboardButton::make()->text('Button C'), 51 | InlineKeyboardButton::make()->text('Button D'), 52 | ]); 53 | 54 | expect($keyboard)->toMatchEntity([ 55 | 'inline_keyboard' => [ 56 | [ 57 | ['text' => 'Button A'], 58 | ['text' => 'Button B'], 59 | ], [ 60 | ['text' => 'Button C'], 61 | ['text' => 'Button D'], 62 | ] 63 | ] 64 | ]); 65 | }); 66 | 67 | it('can have multiple stacks', function () { 68 | $keyboard = InlineKeyboardMarkup::make() 69 | ->stack([ 70 | InlineKeyboardButton::make()->text('Button A'), 71 | InlineKeyboardButton::make()->text('Button B'), 72 | ]) 73 | ->stack([ 74 | InlineKeyboardButton::make()->text('Button C'), 75 | InlineKeyboardButton::make()->text('Button D'), 76 | ]); 77 | 78 | expect($keyboard)->toMatchEntity([ 79 | 'inline_keyboard' => [ 80 | [ 81 | ['text' => 'Button A'], 82 | ], 83 | [ 84 | ['text' => 'Button B'], 85 | ], 86 | [ 87 | ['text' => 'Button C'], 88 | ], 89 | [ 90 | ['text' => 'Button D'], 91 | ], 92 | ] 93 | ]); 94 | }); 95 | 96 | 97 | it('can mix rows, stacks and buttons', function () { 98 | $keyboard = InlineKeyboardMarkup::make() 99 | ->button(InlineKeyboardButton::make()->text('Button A')) 100 | ->button(InlineKeyboardButton::make()->text('Button B')) 101 | ->row([ 102 | InlineKeyboardButton::make()->text('Button C'), 103 | InlineKeyboardButton::make()->text('Button D') 104 | ]) 105 | ->button(InlineKeyboardButton::make()->text('Button E')) 106 | ->button(InlineKeyboardButton::make()->text('Button F')) 107 | ->stack([ 108 | InlineKeyboardButton::make()->text('Button G'), 109 | InlineKeyboardButton::make()->text('Button H') 110 | ]) 111 | ->button(InlineKeyboardButton::make()->text('Button I')) 112 | ->button(InlineKeyboardButton::make()->text('Button J')); 113 | 114 | expect($keyboard)->toMatchEntity([ 115 | 'inline_keyboard' => [ 116 | [ 117 | ['text' => 'Button A'], 118 | ['text' => 'Button B'], 119 | ], 120 | [ 121 | ['text' => 'Button C'], 122 | ['text' => 'Button D'], 123 | ], 124 | [ 125 | ['text' => 'Button E'], 126 | ['text' => 'Button F'], 127 | ], 128 | [ 129 | ['text' => 'Button G'], 130 | ], [ 131 | ['text' => 'Button H'], 132 | ], 133 | [ 134 | ['text' => 'Button I'], 135 | ['text' => 'Button J'], 136 | ], 137 | ] 138 | ]); 139 | }); -------------------------------------------------------------------------------- /tests/Unit/ReplyKeyboardMarkupTest.php: -------------------------------------------------------------------------------- 1 | toMatchEntity([ 10 | 'keyboard' => [[]], 11 | ]); 12 | }); 13 | 14 | it('can set known fields', function () { 15 | $keyboard = ReplyKeyboardMarkup::make() 16 | ->inputFieldPlaceholder('Placeholder') 17 | ->oneTimeKeyboard() 18 | ->resizeKeyboard() 19 | ->selective(); 20 | 21 | expect($keyboard)->toMatchEntity([ 22 | 'keyboard' => [[]], 23 | 'input_field_placeholder' => 'Placeholder', 24 | 'one_time_keyboard' => true, 25 | 'resize_keyboard' => true, 26 | 'selective' => true, 27 | ]); 28 | }); 29 | 30 | it('can set unknown fields', function () { 31 | $keyboard = ReplyKeyboardMarkup::make() 32 | ->unknownField('Test'); 33 | 34 | expect($keyboard)->toMatchEntity([ 35 | 'keyboard' => [[]], 36 | 'unknown_field' => 'Test' 37 | ]); 38 | }); 39 | 40 | it('can have multiple buttons', function () { 41 | $keyboard = ReplyKeyboardMarkup::make() 42 | ->button(KeyboardButton::make()->text('Button A')) 43 | ->button(KeyboardButton::make()->text('Button B')) 44 | ->row() 45 | ->button(KeyboardButton::make()->text('Button C')) 46 | ->button(KeyboardButton::make()->text('Button D')); 47 | 48 | expect($keyboard)->toMatchEntity([ 49 | 'keyboard' => [ 50 | [ 51 | ['text' => 'Button A'], 52 | ['text' => 'Button B'], 53 | ], [ 54 | ['text' => 'Button C'], 55 | ['text' => 'Button D'], 56 | ] 57 | ] 58 | ]); 59 | }); 60 | 61 | it('can have multiple rows', function () { 62 | $keyboard = ReplyKeyboardMarkup::make() 63 | ->row([ 64 | KeyboardButton::make()->text('Button A'), 65 | KeyboardButton::make()->text('Button B') 66 | ])->row([ 67 | KeyboardButton::make()->text('Button C'), 68 | KeyboardButton::make()->text('Button D') 69 | ]); 70 | 71 | expect($keyboard)->toMatchEntity([ 72 | 'keyboard' => [ 73 | [ 74 | ['text' => 'Button A'], 75 | ['text' => 'Button B'], 76 | ], [ 77 | ['text' => 'Button C'], 78 | ['text' => 'Button D'], 79 | ], 80 | ] 81 | ]); 82 | }); 83 | 84 | it('can have multiple stacks', function () { 85 | $keyboard = ReplyKeyboardMarkup::make() 86 | ->stack([ 87 | KeyboardButton::make()->text('Button A'), 88 | KeyboardButton::make()->text('Button B'), 89 | ]) 90 | ->stack([ 91 | KeyboardButton::make()->text('Button C'), 92 | KeyboardButton::make()->text('Button D'), 93 | ]); 94 | 95 | expect($keyboard)->toMatchEntity([ 96 | 'keyboard' => [ 97 | [ 98 | ['text' => 'Button A'], 99 | ], 100 | [ 101 | ['text' => 'Button B'], 102 | ], 103 | [ 104 | ['text' => 'Button C'], 105 | ], 106 | [ 107 | ['text' => 'Button D'], 108 | ], 109 | ] 110 | ]); 111 | }); 112 | 113 | it('can mix rows, stacks and buttons', function () { 114 | $keyboard = ReplyKeyboardMarkup::make() 115 | ->button(KeyboardButton::make()->text('Button A')) 116 | ->button(KeyboardButton::make()->text('Button B')) 117 | ->row([ 118 | KeyboardButton::make()->text('Button C'), 119 | KeyboardButton::make()->text('Button D') 120 | ]) 121 | ->button(KeyboardButton::make()->text('Button E')) 122 | ->button(KeyboardButton::make()->text('Button F')) 123 | ->stack([ 124 | KeyboardButton::make()->text('Button G'), 125 | KeyboardButton::make()->text('Button H') 126 | ]) 127 | ->button(KeyboardButton::make()->text('Button I')) 128 | ->button(KeyboardButton::make()->text('Button J')); 129 | 130 | expect($keyboard)->toMatchEntity([ 131 | 'keyboard' => [ 132 | [ 133 | ['text' => 'Button A'], 134 | ['text' => 'Button B'], 135 | ], 136 | [ 137 | ['text' => 'Button C'], 138 | ['text' => 'Button D'], 139 | ], 140 | [ 141 | ['text' => 'Button E'], 142 | ['text' => 'Button F'], 143 | ], 144 | [ 145 | ['text' => 'Button G'], 146 | ], [ 147 | ['text' => 'Button H'], 148 | ], 149 | [ 150 | ['text' => 'Button I'], 151 | ['text' => 'Button J'], 152 | ], 153 | ] 154 | ]); 155 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | [![Version][version]](https://packagist.org/packages/php-telegram-bot/fluent-keyboard) 4 | ![PHP Version][php-version] 5 | [![Bot API][bot-api-shield]](https://core.telegram.org/bots/api#january-31-2022) 6 | [![Tests][tests-shield]](https://github.com/php-telegram-bot/fluent-keyboard/actions/workflows/tests.yaml) 7 | 8 | 9 | 10 |
11 | Table of Contents 12 |
    13 |
  1. Installation
  2. 14 |
  3. 15 | Usage 16 |
      17 |
    1. Defining a Keyboard
    2. 18 |
    3. Defining Buttons
    4. 19 |
    5. 20 | Bind Buttons to a Keyboard 21 |
        22 |
      1. By Row
      2. 23 |
      3. By Button
      4. 24 |
      5. As Stack
      6. 25 |
      26 |
    6. 27 |
    7. ForceReply and ReplyKeyboardRemove
    8. 28 |
    9. KeyboardButtonPollType
    10. 29 |
    30 |
  4. 31 |
32 |
33 | 34 | ## Installation 35 | 36 | Install the package using composer: 37 | 38 | ```shell 39 | composer require php-telegram-bot/fluent-keyboard 40 | ``` 41 | 42 |

(back to top)

43 | 44 | ## Usage 45 | 46 | If you need to create a keyboard you can use the classes provided by this package as a drop-in replacement. 47 | 48 | This is best explained with an example: 49 | 50 | ```php 51 | Request::sendMessage([ 52 | 'chat_id' => 12345, 53 | 'text' => 'Keyboard Example', 54 | 'reply_markup' => ReplyKeyboardMarkup::make() 55 | ->oneTimeKeyboard() 56 | ->button(KeyboardButton::make('Cancel')) 57 | ->button(KeyboardButton::make('OK')), 58 | ]); 59 | ``` 60 | 61 | A ReplyKeyboardMarkup is created by calling the static `make()` method on `ReplyKeyboardMarkup`. After that every field, 62 | like `one_time_keyboard`, can be chained by calling it in camelCase. Buttons can be added by calling 63 | the `button()` method. We have a detailed look on that later. 64 | 65 | The classes and fields are named after the corresponding types and fields of 66 | the [Telegram Bot API](https://core.telegram.org/bots/api). 67 | 68 |

(back to top)

69 | 70 | ### Defining a Keyboard 71 | 72 | You can create a keyboard by calling the static `make()` method on its class. 73 | 74 | After that you can chain methods to set additional fields that are available in the Bot API. This is done by calling the 75 | field name in camelCase. So instead of `input_field_placeholder`, you need to call `inputFieldPlaceholder()`. 76 | 77 | ```php 78 | ReplyKeyboardMarkup::make() 79 | ->inputFieldPlaceholder('Placeholder'); 80 | ``` 81 | 82 |

(back to top)

83 | 84 | ### Defining Buttons 85 | 86 | The Buttons are created in the same way: 87 | 88 | ```php 89 | KeyboardButton::make() 90 | ->text('Send my Contact') 91 | ->requestContact(); 92 | ``` 93 | 94 | As a shortcut, you can pass the mandatory `text` field as an argument to the static method `make()` like this: 95 | 96 | ```php 97 | KeyboardButton::make('Send my Location') 98 | ->requestLocation(); 99 | ``` 100 | 101 | This is done the same way for `InlineKeyboardButton`: 102 | 103 | ```php 104 | InlineKeyboardButton::make('Login') 105 | ->loginUrl(['url' => 'https://example.com']); 106 | ``` 107 | 108 | To find out which fields are available have a look at the [Bot API documentation](https://core.telegram.org/bots/api). 109 | 110 |

(back to top)

111 | 112 | ### Bind Buttons to a Keyboard 113 | 114 | The keyboard does not work without any buttons, so you need to pass the buttons to the keyboard. There are a few ways to 115 | do this. 116 | 117 | #### By Row 118 | 119 | ```php 120 | ReplyKeyboardMarkup::make() 121 | ->row([ 122 | KeyboardButton::make('Cancel'), 123 | KeyboardButton::make('OK') 124 | ]); 125 | ``` 126 | 127 | If you need more than one row, call `row()` multiple times: 128 | 129 | ```php 130 | InlineKeyboardMarkup::make() 131 | ->row([ 132 | InlineKeyboardButton::make('1')->callbackData('page-1'), 133 | InlineKeyboardButton::make('2')->callbackData('page-2'), 134 | InlineKeyboardButton::make('3')->callbackData('page-3') 135 | ]) 136 | ->row([ 137 | InlineKeyboardButton::make('prev')->callbackData('page-prev'), 138 | InlineKeyboardButton::make('next')->callbackData('page-next') 139 | ]); 140 | ``` 141 | 142 | ![InlineKeyboard with multiple rows](./docs/images/inlinekeyboard-multiple-rows.png) 143 | 144 | #### By Button 145 | 146 | ```php 147 | ReplyKeyboardMarkup::make() 148 | ->button(KeyboardButton::make('First Button')) 149 | ->button(KeyboardButton::make('Second Button')); 150 | ``` 151 | 152 | If you need more than one row, just call the row method without arguments, and continue calling `button()`: 153 | 154 | ```php 155 | InlineKeyboardMarkup::make() 156 | ->button(InlineKeyboardButton::make('A')->callbackData('answer-a')) 157 | ->button(InlineKeyboardButton::make('B')->callbackData('answer-b')) 158 | ->row() 159 | ->button(InlineKeyboardButton::make('C')->callbackData('answer-c')) 160 | ->button(InlineKeyboardButton::make('D')->callbackData('answer-d')); 161 | ``` 162 | ![InlineKeyboard with multiline buttons](./docs/images/inlinekeyboards-multiline-buttons.png) 163 | 164 | It's up to you if you define your buttons inline like in these examples or if you'd like to generate a whole row beforehand and 165 | pass the variable to the `row()` method. 166 | 167 | #### As Stack 168 | 169 | If you want to add a bunch of buttons that have each a row for themselves you can use the `stack()` method. 170 | 171 | ```php 172 | InlineKeyboardMarkup::make() 173 | ->stack([ 174 | InlineKeyboardButton::make('Login')->loginUrl('https://example.com/login'), 175 | InlineKeyboardButton::make('Visit Homepage')->url('https://example.com') 176 | ]); 177 | ``` 178 | 179 | ![InlineKeyboard with stack](./docs/images/inlinekeyboard-stack.png) 180 | 181 | **You can mix and match the `row()`, `stack()` and `button()` methods as it fits your needs.** 182 | 183 |

(back to top)

184 | 185 | ### ForceReply and ReplyKeyboardRemove 186 | 187 | ForceReply and ReplyKeyboardRemove can be used the same way as a normal keyboard, but they do not receive any buttons: 188 | 189 | ```php 190 | $this->replyToUser('Thank you', [ 191 | 'reply_markup' => ReplyKeyboardRemove::make()->selective(), 192 | ]); 193 | ``` 194 | 195 | ```php 196 | $data['reply_markup'] = ForceReply::make()->inputFieldPlaceholder('Please type something...'); 197 | ``` 198 | 199 |

(back to top)

200 | 201 | ### KeyboardButtonPollType 202 | 203 | The `request_poll` field is a little special. You can specify which poll type the user can create by passing 204 | a `KeyboardButtonPollType` object. 205 | 206 | ```php 207 | KeyboardButton::make()->requestPoll(KeyboardButtonPollType::regular()) 208 | ``` 209 | 210 | The `KeyboardButtonPollType` class has static methods for each possible type. But if there are new types in the future 211 | you don't have to wait until we release an update. You can either pass the array structure directly to 212 | the `requestPoll()` method or you pass the array structure to the constructor of `KeyboardButtonPollType`. 213 | 214 | ```php 215 | $pollButton = new KeyboardButtonPollType([ 216 | 'type' => 'quiz' 217 | ]); 218 | ``` 219 | 220 | [tests-shield]: https://img.shields.io/github/workflow/status/php-telegram-bot/fluent-keyboard/Tests?label=Tests&style=for-the-badge 221 | 222 | [bot-api-shield]: https://img.shields.io/badge/Bot%20API-5.7%20(Jan%202022)-%232a9ed6?style=for-the-badge 223 | 224 | [php-version]: https://img.shields.io/packagist/php-v/php-telegram-bot/fluent-keyboard?style=for-the-badge 225 | 226 | [version]: https://img.shields.io/packagist/v/php-telegram-bot/fluent-keyboard?color=orange&label=Version&style=for-the-badge 227 | --------------------------------------------------------------------------------