Skip to content

Commit

Permalink
Remove renderers and rename the Slack facade to Kit (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremeamia authored Dec 3, 2020
1 parent cac66f1 commit 6e1f7ee
Show file tree
Hide file tree
Showing 24 changed files with 235 additions and 464 deletions.
223 changes: 120 additions & 103 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ From Slack's [Block Kit documentation](https://api.slack.com/block-kit):
> Customize the order and appearance of information and guide users through your app's capabilities by composing,
> updating, sequencing, and stacking _blocks_ — reusable components that work almost everywhere in Slack.
This library provides an OOP interface in PHP for composing messages using Slack Block Kit.
This library provides an OOP interface in PHP for composing messages/modals using Slack Block Kit. It also does the
reverse, meaning you can "hydrate" message/modal JSON into an object hierarchy.

## Block Kit Concepts

Expand All @@ -41,7 +42,7 @@ but it does not validate or guard against every single rule.

You may want to review the following concepts in the Slack documentation:

- [Surfaces](https://api.slack.com/surfaces) – There are 3 types: Message, Modal, and App Home
- [Surfaces](https://api.slack.com/surfaces) – There are 3 main types: Message, Modal, and App Home
- [Blocks](https://api.slack.com/reference/block-kit/blocks) – Includes _section_, _context_, _actions_, and more
- [Interactive Components](https://api.slack.com/reference/block-kit/interactive-components) – We call these "Inputs" in this library
- [Composition Objects](https://api.slack.com/reference/block-kit/composition-objects) – We call these "Partials" ion the library
Expand All @@ -58,127 +59,160 @@ composer require jeremeamia/slack-block-kit

Then include the Composer-generated autoloader in your project's initialization code.

_Note: This library is built for PHP 7.2+._
_Note: This library is built for PHP 7.3+._

## Basic Usage

This library supports an intuitive syntax for composing Slack messages. The `Slack` class acts as a façade to the
entire library, and let's you start new messages.
This library supports an intuitive and fluid syntax for composing Slack surfaces (e.g., messages, modals). The `Kit`
class acts as a façade to the library, and let's you start new messages/modals.

```php
<?php

use Jeremeamia\Slack\BlockKit\Slack;
use Jeremeamia\Slack\BlockKit\Kit;
use Jeremeamia\Slack\BlockKit\Surfaces\Message;

// ...

$msg = Slack::newMessage();
// You can start a message from the `Kit` class.
$msg = Kit::newMessage();
// OR via the surface class's "new" method.
$msg = Message::new();

// Then you can add blocks using the surface's available methods.
$msg->text('Don\'t you just love XKCD?');
$msg->divider();
$msg->newImage()
->title('Team Chat')
->url('https://imgs.xkcd.com/comics/team_chat.png')
->altText('Comic about the stubbornness of some people switching chat clients');

// Messages can be converted to JSON using PHP's regular `json_encode` function.
$json = json_encode($msg);
// To convert to JSON (to send to Slack API, webhook, or response_url), use PHP's `json_encode` function.
echo json_encode($msg);
// OR you can use the surfaces's `toJson` method, which also includes a convenience parameter for pretty printing.
echo $msg->toJson(true);
```

### Renderers

This library comes with 3 message/surface renderers out of the box.

All the renderers can be accessed from the `Slack` façade class, as well.
### Fluid Interface

All the examples below will show the message above being rendered.
When using the fluid interface, every method that sets a property or adds a sub-element returns the original element's
object, so you can chain additional method calls.

#### JSON
```php
$msg = Message::new()
->text('Don\'t you just love XKCD?');
->divider();
```

This renderer outputs JSON and is similar to just `json_encode`-ing the message. However, it will use the
pretty-print option.
Methods with a `new` prefix will return the new element's object, so be careful with how you are using the fluid
interface in those cases.

```php
echo Slack::newRenderer()->forJson()->render($msg);
// Correctly renders the whole message.
$msg = Message::new()
->text('Don\'t you just love XKCD?')
->divider();
$msg->newImage()
->title('Team Chat')
->url('https://imgs.xkcd.com/comics/team_chat.png')
->altText('Comic about the stubbornness of some people switching chat clients');
echo json_encode($msg);
// YAY!

// INCORRECT: Renders just the image, because only that element gets stored in the variable.
$msg = Message::new()
->text('Don\'t you just love XKCD?')
->divider()
->newImage()
->title('Team Chat')
->url('https://imgs.xkcd.com/comics/team_chat.png')
->altText('Comic about the stubbornness of some people switching chat clients');
echo json_encode($msg);
// WHOOPS!
```

##### Output
#### Tapping

```
{
"response_type": "ephemeral",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Don't you just love XKCD?"
}
},
{
"type": "divider"
},
{
"type": "image",
"title": {
"type": "plain_text",
"text": "Team Chat",
"emoji": true
},
"image_url": "https:\/\/imgs.xkcd.com\/comics\/team_chat.png",
"alt_text": "Comic about the stubbornness of some people switching chat clients"
}
]
}
Tapping is a way to keep the fluid interface going, but makes sure the whole message is preserved.

```php
// Correctly renders the whole message, by using tap()
$msg = Message::new()
->text('Don\'t you just love XKCD?')
->divider()
->tap(function (Message $msg) {
$msg->newImage()
->title('Team Chat')
->url('https://imgs.xkcd.com/comics/team_chat.png')
->altText('Comic about the stubbornness of some people switching chat clients');
});
echo json_encode($msg);
// YAY!
```

### Block Kit Builder
### Preview in Block Kit Builder

Slack provides an [interactive Block Kit Builder](https://api.slack.com/tools/block-kit-builder) for composing/testing
messages. This is a great way to play around with and learn the Block Kit format.
Slack provides an [interactive Block Kit Builder](https://app.slack.com/block-kit-builder) for composing/testing
messages and other surfaces. This is a great way to play around with and learn the Block Kit format.

The `KitBuilder` renderer allows you to render your message/surface as a Block Kit Builder link, so you can preview your
message in the browser as Slack would render it via their interactive tool.
The `Kit::preview` method allows you to render your message/surface as a Block Kit Builder URL, so you can link to a
preview or your message/surface in the browser via their interactive tool. This will help you see how it would be
rendered in a Slack client.

```php
echo Slack::newRenderer()->forKitBuilder()->render($msg);
$msg = Kit::newMessage()
->text('Don\'t you just love XKCD?')
->divider()
->tap(function (Message $msg) {
$msg->newImage()
->title('Team Chat')
->url('https://imgs.xkcd.com/comics/team_chat.png')
->altText('Comic about the stubbornness of some people switching chat clients');
});

echo Kit::preview($msg);
```

##### Output
#### Output

```
https://api.slack.com/tools/block-kit-builder?mode=message&blocks=%5B%7B%22type%22%3A%22section%22%2C%22text%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22Don%27t%20you%20just%20love%20XKCD%3F%22%7D%7D%2C%7B%22type%22%3A%22divider%22%7D%2C%7B%22type%22%3A%22image%22%2C%22title%22%3A%7B%22type%22%3A%22plain_text%22%2C%22text%22%3A%22Team%20Chat%22%2C%22emoji%22%3Atrue%7D%2C%22image_url%22%3A%22https%3A%2F%2Fimgs.xkcd.com%2Fcomics%2Fteam_chat.png%22%2C%22alt_text%22%3A%22Comic%20about%20the%20stubbornness%20of%20some%20people%20switching%20chat%20clients%22%7D%5D
https://app.slack.com/block-kit-builder#%7B"blocks":%5B%7B"type":"section"%2C"text":%7B"type":"mrkdwn"%2C"text":"Don%27t%20you%20just%20love%20XKCD%3F"%7D%7D%2C%7B"type":"divider"%7D%2C%7B"type":"image"%2C"title":%7B"type":"plain_text"%2C"text":"Team%20Chat"%7D%2C"image_url":"https:%5C%2F%5C%2Fimgs.xkcd.com%5C%2Fcomics%5C%2Fteam_chat.png"%2C"alt_text":"Comic%20about%20the%20stubbornness%20of%20some%20people%20switching%20chat%20clients"%7D%5D%7D
```

And here's the [actual Block Kit Builder link](https://api.slack.com/tools/block-kit-builder?mode=message&blocks=%5B%7B%22type%22%3A%22section%22%2C%22text%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22Don%27t%20you%20just%20love%20XKCD%3F%22%7D%7D%2C%7B%22type%22%3A%22divider%22%7D%2C%7B%22type%22%3A%22image%22%2C%22title%22%3A%7B%22type%22%3A%22plain_text%22%2C%22text%22%3A%22Team%20Chat%22%2C%22emoji%22%3Atrue%7D%2C%22image_url%22%3A%22https%3A%2F%2Fimgs.xkcd.com%2Fcomics%2Fteam_chat.png%22%2C%22alt_text%22%3A%22Comic%20about%20the%20stubbornness%20of%20some%20people%20switching%20chat%20clients%22%7D%5D).
And here's the [actual Block Kit Builder link](https://app.slack.com/block-kit-builder#%7B"blocks":%5B%7B"type":"section"%2C"text":%7B"type":"mrkdwn"%2C"text":"Don%27t%20you%20just%20love%20XKCD%3F"%7D%7D%2C%7B"type":"divider"%7D%2C%7B"type":"image"%2C"title":%7B"type":"plain_text"%2C"text":"Team%20Chat"%7D%2C"image_url":"https:%5C%2F%5C%2Fimgs.xkcd.com%5C%2Fcomics%5C%2Fteam_chat.png"%2C"alt_text":"Comic%20about%20the%20stubbornness%20of%20some%20people%20switching%20chat%20clients"%7D%5D%7D).

It will show up in the Block Kit Builder looking something like this:

![Screenshot of rendered message in Block Kit Builder](block-kit-screenshot.png)

#### CLI
### Surface Hydration

Sometimes previewing the content of a message in the Terminal/CLI is useful, but the JSON representation can be
difficult to read. The CLI renderer will render to a more CLI-friendly format.
Some Slack application integrations (such as with Modals) require receiving the JSON of an existing surface and then
modifying or replacing that surface with another. You can "hydrate" the JSON of a surface (or element) into its object
representation using its `fromArray` method (or `fromJson`).

```php
echo Slack::newRenderer()->forCli()->render($msg);
```
$messageJson = <<<JSON
{
"blocks": [
{
"type": "section",
"block_id": "block1",
"text": {
"type": "mrkdwn",
"text": "*foo bar*"
}
}
}
}
JSON;

##### Output
// Use fromArray to hydrate the message from parsed JSON data.
$decodedMessageJson = json_decode($messageJson, true);
$message = Message::fromArray($decodedMessageJson);

```
(•) Only visible to you
message:
blocks:
section:
text:
mrkdwn: "Don't you just love XKCD?"
----------------------------------------
image:
title:
plain_text: "Team Chat"
image_url: https://imgs.xkcd.com/comics/team_chat.png
alt_text: Comic about the stubbornness of some people switching chat clients
// OR... use fromJson to hydrate from a JSON string.
$message = Message::fromJson($messageJson);
```

## Supported Elements
Expand Down Expand Up @@ -222,50 +256,33 @@ The following are virtual/custom elements composed of one or more blocks:

## Class Structure

### Surfaces and Renderers

The `Slack` façade provides ways to create _surfaces_ and _renderers_. A renderer is used to render a surface (and its
blocks) into a displayable format.
The `Kit` façade provides ways to create _surfaces_. Surfaces contain one or more _blocks_. _Blocks_ are the primary
element of the Block Kit. Blocks contain other elements, including other blocks, _inputs_ (interactive elements), and
_partials_ (element parts that are not uniquely identifiable).

![UML diagram for surfaces and renderers](https://yuml.me/5d2be60a.png)
![UML diagram for slack-block-kit](https://yuml.me/d667870c.png)

<details>
<summary>See the YUML</summary>
<pre>
[Slack]-creates>[Renderer]
[Slack]-creates>[Surface]
[Kit]-creates>[Surface]
[Surface]^[Message]
[Surface]^[Modal]
[Surface]^[AppHome]
[Element]^[Surface]
[Element]^[Block]
[Renderer]^[Json]
[Renderer]^[KitBuilder]
[Renderer]^[Cli]
[Surface]<>->[Block]
</pre>
</details>

### Blocks and Other Elements

_Blocks_ are the primary element of the Block Kit. Blocks contain other elements that are grouped into _inputs_
(interactive elements) and _partials_ (repeatable element parts that are not uniquely identifiable).

![UML diagram for blocks](https://yuml.me/6bf6925a.png)

<details>
<summary>See the YUML</summary>
<pre>
[Element]^[Surface]
[Element]^[Block]
[Element]^[Input]
[Element]^[Partial]
[Surface]<>->[Block]
[Block]<>->[Block]
[Block]<>->[Input]
[Block]<>->[Partial]
[Input]-[note: examples:;Button;DatePicker{bg:cornsilk}]
[Partial]-[note: examples:;Text;Fields{bg:cornsilk}]
[Block]-[note: examples:;Section;Actions{bg:cornsilk}]
[Input]-[note:Examples: Button
DatePicker {bg:cornsilk}]
[Partial]-[note: Examples: Text
Fields {bg:cornsilk}]
[Block]-[note: Examples: Section
Actions {bg:cornsilk}]
</pre>
</details>

Expand Down
12 changes: 11 additions & 1 deletion src/Element.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,20 @@ public function toArray(): array
return $data;
}

public function toJson(bool $prettyPrint = false): string
{
$opts = JSON_THROW_ON_ERROR;
if ($prettyPrint) {
$opts |= JSON_PRETTY_PRINT;
}

return (string) json_encode($this, $opts);
}

/**
* @return array
*/
final public function jsonSerialize()
public function jsonSerialize()
{
return $this->toArray();
}
Expand Down
44 changes: 44 additions & 0 deletions src/Kit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Jeremeamia\Slack\BlockKit;

use Jeremeamia\Slack\BlockKit\Surfaces;

use function rawurlencode;

abstract class Kit
{
public static function newAppHome(): Surfaces\AppHome
{
return new Surfaces\AppHome();
}

public static function newMessage(): Surfaces\Message
{
return new Surfaces\Message();
}

public static function newModal(): Surfaces\Modal
{
return new Surfaces\Modal();
}

public static function preview(Surfaces\Surface $surface): string
{
if ($surface instanceof Surfaces\Message) {
// Block Kit Builder doesn't support message directives.
$surface->directives([]);
} elseif ($surface instanceof Surfaces\Attachment) {
// Block Kit Builder can only show an attachment within a message.
$surface = self::newMessage()->addAttachment($surface);
} elseif ($surface instanceof Surfaces\WorkflowStep) {
throw new Exception('The "workflow_step" surface is not compatible with Block Kit Builder');
}

$encoded = str_replace(['%22', '%3A'], ['"', ':'], rawurlencode($surface->toJson()));

return "https://app.slack.com/block-kit-builder#{$encoded}";
}
}
Loading

0 comments on commit 6e1f7ee

Please sign in to comment.