slack-php/slack-app-framework

提供在PHP中构建Slack应用的基础

1.0.0 2022-02-24 19:02 UTC

This package is auto-updated.

Last update: 2024-08-25 00:33:15 UTC


README

PHP Slack应用框架

由Jeremy Lindblom (@jeremeamia)创作

Slack PHP logo written in PHP's font

Coded in PHP 7.4 Packagist Version Build Status

简介

一个用于构建Slack应用的PHP框架。它从Slack的Bolt框架中汲取灵感。

如果你是Slack应用开发的初学者,你可以在Slack网站上了解它。这个库只有在您已经了解构建Slack应用的基础知识时才有用。

安装

  • 需要PHP 7.4+
  • 使用Composer安装:composer require slack-php/slack-app-framework

一般用法

快速警告

这个库已经被大量内部测试,但项目严重缺乏测试覆盖率和文档,因此请自行承担风险,如MIT许可证所述。

  • 欢迎贡献(尤其是文档和测试)。
  • 有关问题、反馈、建议等,请使用讨论
  • 有关问题或担忧,请使用问题

开发模式

创建应用时,您可以从Slack网站配置您的应用。该框架旨在接收来自您的应用所有交互点的请求,因此您应将所有URL(例如,在斜杠命令交互性与快捷键(不要忘记选择菜单部分)和事件订阅中)配置为指向已部署的应用代码的根URL。

在开发应用代码时,您可以使用App的路线方法声明一个或多个Listener,这些方法对应于不同类型的应用交互。可以将Listener声明为闭包,或作为SlackPhp\Framework\Listener类型的对象和类名。一个Listener接收一个Context对象,该对象包含Slack提供给应用的有效负载数据,并提供了所有可以采取的与Slack交互或通信的方法。

快速示例

这个小程序响应/cool斜杠命令。

假设

  • 您已要求Composer自动加载器启用框架文件的自动加载。
  • 您已在环境中设置了SLACK_SIGNING_KEY(例如,putenv("SLACK_SIGNING_KEY=foo");
<?php

use SlackPhp\Framework\App;
use SlackPhp\Framework\Context;

App::new()
    ->command('cool', function (Context $ctx) {
        $ctx->ack(':thumbsup: That is so cool!');
    })
    ->run();

示例应用程序

"Hello World"应用通过利用每种类型的应用交互,包括:斜杠命令、块操作、块建议(即菜单选项)、快捷键(全局和消息级别)、模态、事件和应用程序主页,向您问好。

"Hello World"应用代码

假设

  • 您已要求Composer自动加载器启用框架文件的自动加载。
  • 您已在环境中设置了SLACK_SIGNING_KEY(例如,putenv("SLACK_SIGNING_KEY=foo");
  • 您已在环境中设置了SLACK_BOT_TOKEN(例如,putenv("SLACK_BOT_TOKEN=bar");
<?php

declare(strict_types=1);

use SlackPhp\BlockKit\Surfaces\{Message, Modal};
use SlackPhp\Framework\{App, Context, Route};

// Helper for creating a modal with the "hello-form" for choosing a greeting.
$createModal = function (): Modal {
    return Modal::new()
        ->title('Choose a Greeting')
        ->submit('Submit')
        ->callbackId('hello-form')
        ->notifyOnClose(true)
        ->tap(function (Modal $modal) {
            $modal->newInput('greeting-block')
                ->label('Which Greeting?')
                ->newSelectMenu('greeting')
                ->forExternalOptions()
                ->placeholder('Choose a greeting...');
        });
};

App::new()
    // Handles the `/hello` slash command.
    ->command('hello', function (Context $ctx) {
        $ctx->ack(Message::new()->tap(function (Message $msg) {
            $msg->newSection()
                ->mrkdwnText(':wave: Hello world!')
                ->newButtonAccessory('open-form')
                ->text('Choose a Greeting');
        }));
    })
    // Handles the "open-form" button click.
    ->blockAction('open-form', function (Context $ctx) use ($createModal) {
        $ctx->modals()->open($createModal());
    })
    // Handles when the "greeting" select menu needs its options.
    ->blockSuggestion('greeting', function (Context $ctx) {
        $ctx->options(['Hello', 'Howdy', 'Good Morning', 'Hey']);
    })
    // Handles when the "hello-form" modal is submitted.
    ->viewSubmission('hello-form', function (Context $ctx) {
        $state = $ctx->payload()->getState();
        $greeting = $state->get('greeting-block.greeting.selected_option.value');
        $ctx->view()->update(":wave: {$greeting} world!");
    })
    // Handles when the "hello-form" modal is closed without submitting.
    ->viewClosed('hello-form', function (Context $ctx) {
        $ctx->logger()->notice('User closed hello-form modal early.');
    })
    // Handles when the "hello-global" global shortcut is triggered from the lightning menu.
    ->globalShortcut('hello-global', function (Context $ctx) use ($createModal) {
        $ctx->modals()->open($createModal());
    })
    // Handles when the "hello-message" message shortcut is triggered from a message context menu.
    ->messageShortcut('hello-message', function (Context $ctx) {
        $user = $ctx->fmt()->user($ctx->payload()->get('message.user'));
        $ctx->say(":wave: Hello {$user}!", null, $ctx->payload()->get('message.ts'));
    })
    // Handles when the Hello World app "home" is accessed.
    ->event('app_home_opened', function (Context $ctx) {
        $user = $ctx->fmt()->user($ctx->payload()->get('event.user'));
        $ctx->home(":wave: Hello {$user}!");
    })
    // Handles when any public message contains the word "hello".
    ->event('message', Route::filter(
        ['event.channel_type' => 'channel', 'event.text' => 'regex:/^.*hello.*$/i'],
        function (Context $ctx) {
            $user = $ctx->fmt()->user($ctx->payload()->get('event.user'));
            $ctx->say(":wave: Hello {$user}!");
        })
    )
    // Run that app to process the incoming Slack request.
    ->run();

面向对象版本

您还可以将您的App和Listeners作为一组类创建。如果您有多个监听器或监听器很复杂,我建议您采取这种方法。以下是"Hello World"应用以这种方式开发的示例。

"Hello World"应用代码

App.php

<?php

declare(strict_types=1);

namespace MyApp;

use SlackPhp\Framework\{BaseApp, Route, Router};
use MyApp\Listeners;

class MyCoolApp extends BaseApp
{
    protected function prepareRouter(Router $router): void
    {
        $router->command('hello', Listeners\HelloCommand::class)
            ->blockAction('open-form', Listeners\OpenFormButtonClick::class)
            ->blockSuggestion('greeting', Listeners\GreetingOptions::class)
            ->viewSubmission('hello-form', Listeners\FormSubmission::class)
            ->viewClosed('hello-form', Listeners\FormClosed::class)
            ->globalShortcut('hello-global', Listeners\HelloGlobalShortcut::class)
            ->messageShortcut('hello-message', Listeners\HelloMessageShortcut::class)
            ->event('app_home_opened', Listeners\AppHome::class)
            ->event('message', Route::filter(
                ['event.channel_type' => 'channel', 'event.text' => 'regex:/^.*hello.*$/i'],
                Listeners\HelloMessage::class
            ));
    }
}

index.php

假设

  • 您已要求Composer自动加载器启用框架文件的自动加载。
  • 您已配置composer.json,以便自动加载您的MyApp命名空间代码。
  • 您已在环境中设置了SLACK_SIGNING_KEY(例如,putenv("SLACK_SIGNING_KEY=foo");
  • 您已在环境中设置了SLACK_BOT_TOKEN(例如,putenv("SLACK_BOT_TOKEN=bar");
<?php

use MyApp\MyCoolApp;

$app = new MyCoolApp();
$app->run();

使用Context对象处理请求

Context对象是您的应用与Slack之间交互的主要点。以下是您可以使用Context执行的所有操作

// To respond (ack) to incoming Slack request:
$ctx->ack(Message|array|string|null)  // Responds to request with 200 (and optional message)
$ctx->options(OptionList|array|null)  // Responds to request with an options list
$ctx->view(): View
  ->clear()                           // Responds to modal submission by clearing modal stack
  ->close()                           // Responds to modal submission by clearing current modal
  ->errors(array)                     // Responds to modal submission by providing form errors
  ->push(Modal|array|string)          // Responds to modal submission by pushing new modal to stack
  ->update(Modal|array|string)        // Responds to modal submission by updating current modal

// To call Slack APIs (to send messages, open/update modals, etc.) after the ack:
$ctx->respond(Message|array|string)   // Responds to message. Uses payload.response_url
$ctx->say(Message|array|string)       // Responds in channel. Uses API and payload.channel.id
$ctx->modals(): Modals
  ->open(Modal|array|string)          // Opens a modal. Uses API and payload.trigger_id
  ->push(Modal|array|string)          // Pushes a new modal. Uses API and payload.trigger_id
  ->update(Modal|array|string)        // Updates a modal. Uses API and payload.view.id
$ctx->home(AppHome|array|string)      // Modifies App Home for user. Uses API and payload.user.id
$ctx->api(string $api, array $params) // Use Slack API client for arbitrary API operations

// Access payload or other contextual data:
$ctx->payload(): Payload              // Returns the payload of the incoming request from Slack
$ctx->getAppId(): ?string             // Gets the app ID, if it's known
$ctx->get(string): mixed              // Gets a value from the context
$ctx->set(string, mixed)              // Sets a value in the context
$ctx->isAcknowledged(): bool          // Returns true if ack has been sent
$ctx->isDeferred(): bool              // Returns true if additional processing will happen after the ack

// Access additional helpers:
$ctx->blocks(): Blocks                // Returns a helper for creating Block Kit surfaces
$ctx->fmt(): Formatter                // Returns the "mrkdwn" formatting helper for Block Kit text
$ctx->logger(): LoggerInterface       // Returns an instance of the configured PSR-3 logger
$ctx->container(): ContainerInterface // Returns an instance of the configured PSR-11 container

高级设计

UML diagram of the framework

UML 源文件
[AppServer]<>-runs>[App]
[AppServer]creates->[Context]
[App]<>->[AppConfig]
[App]<>->[Router]
[Router]-^[Listener]
[Router]<>1-*>[Listener]
[Listener]handles->[Context]
[Context]<>->[Payload]
[Context]<>->[AppConfig]
[Context]<>->[_Clients_;RespondClient;ApiClient]
[Context]<>->[_Helpers_;BlockKit;Modals;View]
[Context]<>->[_Metadata_]
[AppConfig]<>->[Logger]
[AppConfig]<>->[Container]
[AppConfig]<>->[_Credentials_]

套接字模式

套接字模式由单独的包提供支持。请参阅slack-php/slack-php-socket-mode

未实现

以下特性已知缺失

  • 处理安装到不同工作区的 OAuth 流程。
    • 尽管在 SlackPhp\Framework\Auth 命名空间中有些类,但如果你现在需要自定义。

使用的标准

  • PSR-1, PSR-12: 编码风格
  • PSR-3: 日志接口
  • PSR-4: 自动加载
  • PSR-7, PSR-15, PSR-17: HTTP
  • PSR-11: 容器接口