PHPで絵文字を扱うライブラリ “Pemojine”を書いた

投稿日:

Pemojine オ~ル ザッ ピ~ポ~♪
というわけで書きました。

https://github.com/shimabox/pemojine

名前は Pemojine(ペモジン) です。

これは何なのか

まず、これは何なのかというとPHPで絵文字を扱うライブラリです。

後述しますが、グループごとに絵文字を出力したりとかランダムに絵文字を出力したりとか絵文字を含めた文章を作成したりとかが可能になります。

なぜ作ったのか

このサイト Full Emoji List, v11.0 をふと見つけて、「あれ?これスクレイピングして情報を取得したら何かに使えるんじゃね?」という謎の初期衝動から今に至った次第です。

アーキテクチャ


@see Full Emoji List, v11.0

(雑で申し訳ないのですが)上記画像の通り絵文字を1つのグループとして扱い、それを束ねるグループがさらに複数いるイメージです。

箇条書きで挙げると、

  • ひとつの絵文字をグループ(Group)として扱う
  • MediumGroupGroup を複数(Groups) 持つ
  • BigGroupMediumGroup を複数(MediumGroups) 持つ
  • そして、VendorBigGroup を複数(BigGroups) 持つ

という構造になっています。
基本的には Vendor から必要な絵文字(Group)を利用することになります。

このへんの情報は上記で記したとおり、ここ からスクレイピングして情報を取得しています。
※ 実際は上記に加え、Full Emoji Modifier Sequences, v11.0, emojione/emoji.json at master · emojione/emojione · GitHub この2つからも情報を取得しています

特徴

以下の特徴を持ちます。

  • 各ベンダー(Apple, Google, etc) ごとに絵文字を出力できます
  • 各グループごとに絵文字を出力可能です
    • BigGroups、MediumGroups、Groups
  • ランダムに絵文字を出力できます
  • 絵文字を含む文章が作成できます
  • グループは簡単にカスタマイズできます

インストール

Composer で入れるか、git clone します。

Composer

$ composer require shimabox/pemojine

clone

$ git clone https://github.com/shimabox/pemojine.git
$ cd pemojine
$ composer install

使い方

各ベンダー(Apple, Google, etc) ごとに絵文字を出力

一部例
※ Googleを例に挙げています

<?php
require_once 'vendor/autoload.php';

use SMB\Pemojine\Container as PemojineContainer;
use SMB\Pemojine\Structure\Vendor;

// コンテナを使用して Google のインスタンスを作成する
$pemojine = PemojineContainer::make(new Vendor\Google());

// ベンダー名の確認
echo $pemojine->getVendorName(); // => Google

// すべてのBigGroupを取得
$allBigGroups = $pemojine->getAllBigGroups();

// $allBigGroupsは、キーにbigGroupNameを、値にBigGroupを持つ配列です.
foreach ($allBigGroups as $bigGroupName => $bigGroup) {
    echo 'BigGroupName::' . $bigGroupName/* or $bigGroup->getName() */;
    foreach ($bigGroup as $mediumGroupName => $mediumGroup) {
        echo 'MediumGroupName::' . $mediumGroupName/* or $mediumGroup->getName() */;
        foreach ($mediumGroup as $groupName => $group) {

            // ショートネームの出力
            echo 'Group::' . $groupName/* or $group->getName(), $group->getShortName() */;

            // 絵文字を出力.
            echo $group->output();

            // HTML参照文字を出力. e.g) '&#x1F600;', '&#x1F1EF;&#x1F1F5;'
            echo htmlspecialchars($group->outputHtml(), ENT_QUOTES, 'UTF-8');

            // Unicodeを取得. e.g.) 'U+1F600', 'U+1F1EF U+1F1F5'
            echo $group->getUnicode();

            // Unicodeを取得 (サロゲートペアの半角スペースを除去した状態で). e.g.) 'U+1F600', 'U+1F1EFU+1F1F5'
            echo $group->getUnicodeWithRemovedBlank();

            // UTF8 stringを取得. e.g.) '\u{1F600}', '\u{1F1EF} \u{1F1F5}'
            echo $group->getUtf8String();

            // UTF8 stringを取得 (サロゲートペアの半角スペースを除去した状態で). e.g.) '\u{1F600}', '\u{1F1EF}\u{1F1F5}'
            echo $group->getUtf8StringWithRemovedBlank();

            // ショートネームのエイリアスをすべて取得
            $alias = implode(' ', $group->getAliasesOfName()/* or $group->getAliasesOfShortName() */);
            echo htmlspecialchars($alias, ENT_QUOTES, 'UTF-8');
        }
    }
}

outputHtml(), getAliasesOfName(), getAliasesOfShortName() は生の文字列を返すのでHTMLエンティティ化が必要です

その他例 (MediumGroup, Group) のソースはここです。

各グループごとに絵文字を出力

一部例
MediumGroup から animal-mammal を取得する例を挙げています

<?php
require_once 'vendor/autoload.php';

use SMB\Pemojine\Container as PemojineContainer;
use SMB\Pemojine\Structure\Vendor;
use SMB\Pemojine\Exception\GroupNotFound;

// コンテナを使用して Google のインスタンスを作成する
$pemojine = PemojineContainer::make(new Vendor\Google());

// 'animal-mammal' を選択
$selectedMediumGroup = $pemojine->selectMediumGroup('animal-mammal');
// ベンダー名の確認
echo $selectedMediumGroup->getVendorName(); // => 'Google'
// 選択されているMediumGroup名の確認
echo $selectedMediumGroup->getName(); // => 'animal-mammal'

// $selectedMediumGroupは、キーにgroupNameを、値にGroupを持つイテレータです.
foreach ($selectedMediumGroup/* or $selectedMediumGroup->getChildren() */ as $groupName => $group) {
    echo 'Group::' . $groupName;
    echo $group->output();
    echo htmlspecialchars($group->outputHtml(), ENT_QUOTES, 'UTF-8');
    echo $group->getUnicode();
    echo $group->getUnicodeWithRemovedBlank();
    echo $group->getUtf8String();
    echo $group->getUtf8StringWithRemovedBlank();
    $alias = implode(' ', $group->getAliasesOfName());
    echo htmlspecialchars($alias, ENT_QUOTES, 'UTF-8');
}

// 選択されているMediumGroupからランダムに配下のグループを取得する
$groupOfM = $selectedMediumGroup->getGroupAtRandom();
echo $groupOfM->getName()/* or $groupOfM->getShortName() */;

$alias = implode(' ', $groupOfM->getAliasesOfName()/* or $groupOfM->getAliasesOfShortName() */);
echo htmlspecialchars($alias, ENT_QUOTES, 'UTF-8');

echo $groupOfM->output();
echo htmlspecialchars($groupOfM->outputHtml(), ENT_QUOTES, 'UTF-8');
echo $groupOfM->getUnicode();
echo $groupOfM->getUnicodeWithRemovedBlank();
echo $groupOfM->getUtf8String();
echo $groupOfM->getUtf8StringWithRemovedBlank();

// 親のBigGroupの検索が可能です
$parent = $pemojine->selectMediumGroup('animal-mammal')->findParentGroup(); // => Parent BigGroup
assert($parent->getName() === 'Animals & Nature'); // => true

// 以下は同じインスタンスです
$a = $pemojine->selectBigGroup('Animals & Nature')->selectMediumGroup('animal-mammal');
$b = $pemojine->selectMediumGroup('animal-mammal');
assert($a === $b); // => true

// 存在確認
assert($pemojine->hasMediumGroup('animal-bird') === true);
assert($pemojine->hasMediumGroup('piyo') === false);

その他例 (BigGroupの選択, Groupの選択) のソースはここです。

ランダムに絵文字を出力

一部例
Group からランダムに絵文字を出力する例を挙げています

<?php
require_once 'vendor/autoload.php';

use SMB\Pemojine\Container as PemojineContainer;
use SMB\Pemojine\Structure\Vendor;

// コンテナを使用してインスタンスを作成する
// CommonはすべてのVendorで出力できる絵文字を知っているクラスです
$pemojine = PemojineContainer::make(new Vendor\Common());

// Group をランダムに取得
$selectedGroup = $pemojine->randomFromGroup();

echo $selectedGroup->getName()/* or $selectedGroup->getShortName() */;

$alias = implode(' ', $selectedGroup->getAliasesOfName()/* or $selectedGroup->getAliasesOfShortName() */);
echo htmlspecialchars($alias, ENT_QUOTES, 'UTF-8');

echo $selectedGroup->output();
echo htmlspecialchars($selectedGroup->outputHtml(), ENT_QUOTES, 'UTF-8');
echo $selectedGroup->getUnicode();
echo $selectedGroup->getUnicodeWithRemovedBlank();
echo $selectedGroup->getUtf8String();
echo $selectedGroup->getUtf8StringWithRemovedBlank();

その他例 (BigGroupからの出力, MediumGroupからの出力) のソースはここです。

絵文字を含む文章の作成

一部例

<?php
require_once 'vendor/autoload.php';

use SMB\Pemojine\Helper\Sentence;
use SMB\Pemojine\Outputter\Outputter;

// Apple用絵文字テーブルインスタンスを生成
$emojiTable = new \SMB\Pemojine\Structure\Vendor\Apple\EmojiTable();
// Outputterインスタンスを生成
$outputter = new Outputter($emojiTable);

// 上記は以下でも同様です
/*
    $pemojine = SMB\Pemojine\Container::make(new SMB\Pemojine\Structure\Vendor\Apple());
    $outputter = $pemojine->getOutputter();
*/

// 文章作成用インスタンスを生成
$sentence = new Sentence($outputter);

$output_1 = $sentence->create('Hello U+1F1EFU+1F1F5 \u{1F601}:grin:|beaming face with smiling eyes|!!');
assert('Hello 🇯🇵 😁😁😁!!' === $output_1);
echo $output_1;

// 静的でも使用できます
$output_2 = Sentence::createStatically($outputter, 'Hello U+1F601:grin:|beaming face with smiling eyes|!!');
assert('Hello 😁😁😁!!' === $output_2);
echo $output_2;

// サポートされていない絵文字の場合は変換されずそのまま表示されます
$gmail = SMB\Pemojine\Container::make(new SMB\Pemojine\Structure\Vendor\GMail());
$gmailOutputter = $gmail->getOutputter();
$output_3 = Sentence::createStatically($gmailOutputter, '|⊛ hippopotamus|');
assert('|⊛ hippopotamus|' === $output_3); // Vendor\GMail で ⊛ hippopotamus は対応していないのでそのまま表示される
echo $output_3;

絵文字文章作成ルール

  • Sentence::create() を用いる
  • 対応しているのは
    • Unicod e.g) U+1F601
    • UTF8String e.g) \u{1F601}
    • ショートネーム
    • ショートネームのエイリアス
  • ショートネームの場合 |(パイプ)で囲む
    • e.g) |beaming face with smiling eyes|
    • ショートネームは、$group->getName(), $group->getShortName()で確認可能
    • $group は上記例で確認してください
  • ショートネームのエイリアスの場合
    • エイリアスは、$group->getAliasesOfName()で確認可能
    • $group は上記例で確認してください
    • エイリアス名が :(コロン)で囲まれていたらそのまま利用可能
    • e.g) :smiley: => 😃
    • エイリアス名が :(コロン)で囲まれていなければ |(パイプ)で囲む
    • e.g) |:D| => 😃
  • サロゲート文字の場合半角スペースを入れない
    • e.g.) flag: Japan
    • Good => U+1F1EFU+1F1F5 => 🇯🇵
    • Bad => U+1F1EF U+1F1F5
  • 入れ子にもある程度対応しています
    • 複雑な入れ子だとうまくいかないケースがあります。。

その他例のソースはここです。

カスタマイズ

特徴でも挙げましたが、オリジナルのベンダー(グループ) は簡単に作成できます。

絵文字グループを追加する方法

以下3つのPHPファイルを用意します。

  • SMB\Pemojine\Structure\Interfaces\Configurable インターフェイスを実装したもの.
  • SMB\Pemojine\Structure\Interfaces\Gettable インターフェイスを実装したもの.
  • SMB\Pemojine\Structure\Interfaces\EmojiTable\Gettable インターフェイスを実装したもの.
SMB\Pemojine\Structure\Interfaces\Configurable インターフェイスを実装したPHPファイルを用意

Custom\Structure\Vendor\Original.php とします。

<?php

namespace Custom\Structure\Vendor;

use SMB\Pemojine\Structure\Interfaces\Configurable; // 実装するインターフェイス

/**
 * Custom
 */
class Original implements Configurable
{
    /**
     *
     * @return string
     */
    public function getVendorName()
    {
        return 'Original'; // オリジナルのベンダー名を定義
    }

    /**
     *
     * @return array
     */
    public function getClassNameListOfStructure()
    {
        // 利用する構造体のクラス名を列挙する
        return [
            Original\Animals::class
        ];
    }

    /**
     *
     * @return string
     */
    public function getClassNameOfEmojiTable()
    {
        // オリジナルが知っている絵文字テーブルを定義したクラス名を返す
        return Original\EmojiTable::class;
    }
}
SMB\Pemojine\Structure\Interfaces\Gettable インターフェイスを実装したPHPファイルを用意

Custom\Structure\Vendor\Original\Animals.php とします。

<?php

namespace Custom\Structure\Vendor\Original;

use SMB\Pemojine\Structure\Interfaces\Gettable; // 実装するインターフェイス

/**
 * Custom
 */
class Animals implements Gettable
{
    /**
     *
     * @return array
     */
    public function getBigGroup()
    {
        return $this->bigGroup;
    }

    /**
     *
     * @return array
     */
    public function getMediumGroups()
    {
        return $this->mediumGroups;
    }

    /**
     *
     * @return array
     */
    public function getGroups()
    {
        return $this->groups;
    }

    /**
     * BigGroupの定義
     *
     * @var array
     */
    private $bigGroup = [
        'Animals' => [
            'parent' => null,
            'children' => [ // このBigGroupに紐づくMediumGroup名を列挙
                'UMA',
                'dinosaur'
            ]
        ]
    ];

    /**
     * MediumGroupの定義
     *
     * @var array
     */
    private $mediumGroups = [
        'UMA' => [ // MediumGroup名
            'parent' => 'Animals', // 親のBigGroup名
            'children' => [ // このMediumGroupに紐づくGroup名を列挙
                'unicorn face',
                'dragon face',
                'dragon',
            ]
        ],
        'dinosaur' => [
            'parent' => 'Animals',
            'children' => [
                'sauropod',
                'T-Rex',
            ]
        ],
    ];

    /**
     * Groupの定義
     *
     * @var array
     */
    private $groups = [
        'unicorn face' => [ // Group名(ショートネームにもなる)
            'parent' => 'UMA', // 親のMediumGroup名
            'children' => null,
            'aliases' => [ // ショートネームのエイリアス名を列挙
                ':unicorn:',
                ':unicorn_face:',
                ':u:', // 新しく追加するエイリアス名があれば追加
            ]
        ],
        'dragon face' => [
            'parent' => 'UMA',
            'children' => null,
            'aliases' => [
                ':dragon_face:',
            ]
        ],
        'dragon' => [
            'parent' => 'UMA',
            'children' => null,
            'aliases' => [
                ':dragon:',
            ]
        ],
        'sauropod' => [
            'parent' => 'dinosaur',
            'children' => null,
            'aliases' => [
                ':sauropod:',
            ]
        ],
        'T-Rex' => [
            'parent' => 'dinosaur',
            'children' => null,
            'aliases' => [
                ':t_rex:',
            ]
        ],
    ];
}
SMB\Pemojine\Structure\Interfaces\EmojiTable\Gettable インターフェイスを実装したPHPファイルを用意

Custom\Structure\Vendor\Original\EmojiTable.php とします。

<?php

namespace Custom\Structure\Vendor\Original;

use SMB\Pemojine\Structure\Interfaces\EmojiTable\Gettable; // 実装するインターフェイス
use SMB\Pemojine\Structure\Traits\EmojiTableTrait; // Trait

/**
 * Custom
 */
class EmojiTable implements Gettable
{
    use EmojiTableTrait; // Traitの読み込み

    /**
     * グループ名(ショートネーム)とUnicodeの紐づけを定義
     *
     * @var array
     */
    private static $table = [
        'unicorn face' => 'U+1F984',
        'dragon face'  => 'U+1F432',
        'dragon'       => 'U+1F409',
        'sauropod'     => 'U+1F995',
        'T-Rex'        => 'U+1F996',
    ];

    /**
     * ショートネームエイリアスとグループ名(ショートネーム)のマッピングを定義
     *
     * @var array
     */
    public static $mappingOfShortName = [
        ':unicorn:'      => 'unicorn face',
        ':unicorn_face:' => 'unicorn face',
        ':u:'            => 'unicorn face', // オリジナル
        ':dragon_face:'  => 'dragon face',
        ':dragon:'       => 'dragon',
        ':sauropod:'     => 'sauropod',
        ':t_rex:'        => 'T-Rex',
    ];
}

カスタマイズ用に用意したクラスの利用

<?php
require_once 'vendor/autoload.php');

use SMB\Pemojine\Container as PemojineContainer;

// カスタマイズ用に用意したクラス
use Custom\Structure\Vendor;

// コンテナを使用して上記で用意した Original のインスタンスを作成する
$pemojine = PemojineContainer::make(new Vendor\Original());

/*
 |------------------------------------------------------------------------------
 | 以下は上記で挙げている他の例と同様に利用可能です
 |------------------------------------------------------------------------------
 */

echo $pemojine->getVendorName(); // => Original

$allBigGroups = $pemojine->getAllBigGroups();
foreach ($allBigGroups as $bigGroupName => $bigGroup) {
    echo 'BigGroupName::' . $bigGroupName/* or $bigGroup->getName() */;
    foreach ($bigGroup as $mediumGroupName => $mediumGroup) {
        echo 'MediumGroupName::' . $mediumGroupName/* or $mediumGroup->getName() */;
        foreach ($mediumGroup as $groupName => $group) {
            echo $group->output();
        }
    }
}

/*
 |----------------------------------------------------------------------
 | Outputter.
 |----------------------------------------------------------------------
 */
$outputter = $pemojine->getOutputter();

// e.g) unicorn face
echo $outputter->output(':u:'/* Original */); // => 🦄
echo htmlspecialchars($outputter->outputHtml(':unicorn:'), ENT_QUOTES, 'UTF-8'); // => '&#x1f984;'
echo $outputter->outputByUnicode('U+1F984'); // => 🦄
echo $outputter->getUnicode(':unicorn_face:'); // => 'U+1F984'
echo $outputter->getUtf8String(':unicorn_face:'); // => '\u{1F984}'

その他例のソースはこのへんを参考にしてみてください。

データの再作成(スクレイピング)

冒頭に述べたとおり、ここ, ここ, ここ の情報に依存しています。
これら情報を都度、自分で調べて再構築するのは大変めんどくさいのでスクレイピング用スクリプトを用意し、取得した情報をそのままPHPファイルとして出力、それを利用しています。
※ スクレイピングした情報はキャッシュ(1時間のTTL)するのでマナーには気をつけているつもりですが、著作権的にNGだったらどなたか教えてください

  • PHPファイルの生成には PhiloNL/Laravel-Blade を利用しています
  • スクレイピングには electrolinux/phpquery を利用しています
    WARNING: abandonware and buggy: use at your own risk となっていますがこの用途であればいまのところ問題ないのでそのまま利用しています。他におすすめがあったら教えてください。

なお、開発用として考えているのでdevでしか動きません。
composer requireとかで取り込んだ後に動かしたい場合は、pemojine以下でcomposer installする必要があります。
えと、つまりrequire-devの依存解決ができていればOKです。
(うまく説明できてるかしら。このへんどうやって説明するのがベターなんだろ。。)

コマンド

$ php pemojine/scraping/src/generator.php

これを叩くと、pemojine/scraping/output/Config, pemojine/scraping/output/Structure 以下にファイルが作成されます。
それを、pemojine/src/Config, pemojine/src/Structure 以下に上書きします。

データ再作成後のSync (shell)

再作成後に各ファイルを手動でコピペってもいいですが、shellを用意しているのでそれを叩いたほうが楽です。

  • Mac (Linux) の場合
    $ sh pemojine/scraping/sync.sh
    

  • Windows の場合

    $ cd pemojine/scraping/
    $ win_sync.bat
    

pemojine/scraping/output/Config, pemojine/scraping/output/Structure 以下のファイルを、pemojine/src/Config, pemojine/src/Structure 以下に上書きします。

これらを行った後に後述するPHPUnitを流してエラーが出れば、以前とは情報が変わったということがわかる手はずです。

テスト

$ composer test # or vendor/bin/phpunit
  • カバレッジ
$ composer coverage # or vendor/bin/phpunit --coverage-text --coverage-html ./report/

Todo

  • デモサイト作る
  • 絵文字で文章つくるところ (SMB\Pemojine\Helper\Sentence) の正規表現がかなり乱暴な気がする
    • 入れ子とかに対応しようとしてたらこうなった。。
    • かといって複雑な入れ子に対応しきれていない。。
  • 絵文字のマッピング(配列) の持ち方が富豪的なのでもう少しどうにかならないか考える
  • Vendor\EmojiOne に関して emojione/lib/php at master · emojione/emojione と組み合わせるのが正解な気がする
  • 実機でのテストが必要(な気がする)
    • Vendor\WindowsとかVendor\Samsungよくわかっていないし、Vendor\DoCoMoとかってガラケー?のことを指しているのかよくわかっていない
    • WindowsPhoneとかなの?

おわりに

多分、夏前くらいからちょくちょく作りはじめてて、途中で「あれ、なんで作ってるんだっけ??」となることが多々ありましたが一度手を出したのだから作りきるのが漢だろっていう謎のドリブンで作りました。

正直使い所がわからないのですが、好きな絵文字グループを定義して使う っていう用途に向いている気がします。
(デモサイトを作ったらイメージが湧きやすいかなぁと思われるので近い内に作りたいと思います)

ではでは。

作成者: shimabox

Web系のプログラマをやっています。 なるべく楽しく生きていきたい。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください