🐕

コードリーディング rialto-php/rialto等

2024/12/09に公開

Intervention/image

https://github.com/Intervention/image
GDライブラリとImagickのラッパーライブラリ(より簡単に扱えるようにしている)

  • 処理構造:GDとImagickで具体的な違いを意識せずに利用できることから(インタフェースが共通化されている)Strategyパターンの構造になっている
  • GDライブラリ:PHPに標準で組み込まれている画像処理ライブラリ。小規模な画像操作に適しており、軽量でシンプル
  • Imagick:ImageMagickのPHP拡張で、より高度な画像処理機能を提供します。GDよりも多機能で高品質な処理が可能。
  • GDはシンプルで高速ですが、機能がやや限定されており、高度な画像処理や品質に関してはImagick(ImageMagick)ほどの柔軟性はない
  • 静的ヘルパーメソッド:
    • ユースケース:共通で使い回す処理や、コードの重複を減らすため
    • そのクラスに関連するが、特定のインスタンスに依存しない処理
    • 使うべきでないケース:
      • 静的メソッドはインスタンスを使わないため、オブジェクト指向の原則(状態と振る舞いのカプセル化)を損なう可能性がある。データやオブジェクトの状態を操作する必要がある場合は、インスタンスメソッドを使うべき
    • 過度に静的メソッドを使用すると、コードが手続き型になりがち

名前付け・PHP言語仕様・設計

  • resolver:リゾルバは、ドメイン名とIPアドレスを対応づける名前解決を行うプログラム
  • クラス名::class
  • 無名クラス
  • call_user_func:最初の引数で指定したコールバック関数をコール
    • call_user_func('increment', $a)
    • 関数名の指定と引数をセットすると実行される
  • Interfaceを使ってクラスの振る舞いを縛る理由
    • 一貫性を保つ
      • クラスに対して必須のメソッドやプロパティの設計図を提供
      • 異なるクラスが同じ契約(インターフェース)で実装することでインタフェースが同じになる
        • 利用側は全て同じ扱い方ができることが保証される
    • 依存性の注入を容易にする
      • 具体的なクラスに依存せず、抽象化された契約に依存する設計が可能になる
      • テスト時にはモックを注入することでテスト可能なコードが書ける

spatie/crawler

https://github.com/spatie/crawler

  • 再帰的にリンクをたどってクロールしていきます(キュー構造:Observerパターン)
    • startCrawlingQueueでキューがなくなるまで非同期リクエスト実行
    • リクエスト成功時(CrawlRequestFulfilled)、LinkUrlParser->addFromHtmlでリンクURLを抽出→リンクURLをキューにセットで再帰的にリンクが辿れるようになっている
  • <class that extends \Spatie\Crawler\CrawlObservers\CrawlObserver>
    • CrawlObserver を継承した独自のクラスを指定する必要があるという意味
    • CrawlObserverでページの取得が成功・失敗・完了時の処理を実行できます
  • Guzzleを利用
    • Crawler::createでGuzzleのoption指定等
  • Observer:特定のイベントや状態の変化を監視し、それに応じて処理を実行する役割を持つもの
  • setBrowsershot()は何している?
    • getBodyAfterExecutingJavaScriptで$html = $browsershot->setUrl((string) $url)->bodyHtml();している
    • javascript実行後のHTMLを取得できる
    • 再度リクエストするので2回リクエストするのか
  • new Poolは何してる?
  • _invoke():スクリプトがオブジェクトを関数としてコールしようとした際にコールされる
  • Handlerの命名
    • 特定のイベントやリクエストを処理する場合
    • ルートごとに特定の処理を実行する場合、Handlerと命名することがある
    • 1つの処理(ファイルのアップロード、エラーログの出力など)を担当するクラスやメソッド
    • リクエストの前処理や後処理を行うクラス
    • 特定の条件やルールを基に、動的に処理を分岐する場合

owner888/phpspider

https://github.com/owner888/phpspider

spatie/crawlerと同じく再帰的にリンクをたどってクロールしていく

    /**
     * Signal hander.
     *
     * @param int $signal
     */
    public function signal_handler($signal)
    {
        switch ($signal)
        {
            // Stop.
            case SIGINT:
                log::warn('Program stopping...');
                self::$terminate = true;
                break;
            // Show status.
            case SIGUSR2:
                echo "show status\n";
                break;
        }
    }

    /**
     * Install signal handler.
     *
     * @return void
     */
    public function install_signal()
    {
        if (function_exists('pcntl_signal')) 
        {
            // stop
            // static调用方式
            //pcntl_signal(SIGINT, array(__CLASS__, 'signal_handler'), false);
            pcntl_signal(SIGINT, array(&$this, 'signal_handler'), false);
            // status
            pcntl_signal(SIGUSR2, array(&$this, 'signal_handler'), false);
            // ignore
            pcntl_signal(SIGPIPE, SIG_IGN, false);
        }
    }

dompdf/dompdf

https://github.com/dompdf/dompdf

  • pdfとは:テキストや画像を含む文書を、アプリケーションやOSなどの環境に依存しない方法で表示することができるファイル形式
    • PostScriptページ記述言語:電子出版やDTPビジネスにおけるページ記述言語
    • オブジェクトという構成要素の集まりを参照して作りあげられている
    • オブジェクトの位置をファイルそのものに表として書き込んでいるおかげで、どのページに行っても、ファイル内の位置によらずロードができるようになる
    • https://qiita.com/zawawahoge/items/4312649f8d56f8983ffb
  • loadHtml
  • setPaper
    • サイズを指定(A4等)
    • 縦長(portrait)・横長(landscape)
  • render
    • 外部スタイルシートやCSSの適用
    • 生成されたPDFデータをHTTPレスポンスとしてクライアントに送信する機能を提供

zoonru/puphpeteer

https://github.com/zoonru/puphpeteer

  • phpからnode(puppeteer)を操作する
  • __call():アクセス不能メソッドをオブジェクトのコンテキストで実行したときに起動
  • __set():アクセス不能(protected または private)または存在しないプロパティへデータを書き込む際に実行
  • __get():アクセス不能(protected または private)または存在しないプロパティからデータを読み込む際に使用

rialto-php/rialto

https://github.com/rialto-php/rialto
phpとnodeのソケット通信をする処理

ソケット通信の大まかな流れ

  • socket_select(ソケットを非同期で監視、書き込み可能チェック)
  • socket_write(ソケットに書き込み、相手へデータ送信)
  • ループして待ちながらsocket_read(レスポンスの受け取り)

詳細

  • Socket\Raw\Factory
  • $puppeteer->launch()
    • CommunicatesWithProcessSupervisor.php
    • $instruction = Instruction::withCall($name, ...$value);
      • proxyAction呼ばれる→src/Instruction.php __callStatic→Instruction/call()メソッド呼ばれる→Instructionインスタンスにlaunchがセットされる
      • executeInstructionメソッドでInstructionインスタンスをjson化
      • $this->client->selectWrite(1);
        • socket_select:ソケットを非同期で監視、複数のソケットリソースを渡し、どのソケットが「準備ができたか」(書き込み可能になったか)を確認する
        • 書き込み可能でなければ例外を返す
        • 書き込み可能とは:ソケットにデータを送信(書き込み)しようとしたときに、送信処理がブロックされることなく実行できる状態
        • 通常、ソケットにデータを書き込もうとする際に、相手側がデータを受信できない状態(例: バッファが満杯)だと、処理が一時停止(ブロック)される可能性がある
        • https://github.com/clue/socket-raw/blob/91e9f619f6769f931454a9882c21ffd7623d06cb/src/Socket.php#L343
      • $this->client->write($serializedInstruction);
        • socket_write:ソケットに書き込み、相手側に送信
          • バッファの内容を必ずしもすべて 書き込むとは限らず、途中までしか書き込まれなくても正常処理となるので注意
          • ネットワークや相手の状態によってデータの到達に遅延が生じる可能性がある
          • 通常の TCP ソケット通信では、socket_write を使うのが一般的
          • 別なフラグが必要な場合(例えば緊急データ送信)には、socket_send を使用
      • readNextProcessValue(bool $valueShouldBeLogged = true)
        • $this->client->selectRead($readTimeout);
          • socket_selectしている、次書き込めるかチェック
        • $packet = $this->client->read(static::SOCKET_PACKET_SIZE);
          • レスポンスを受信する。レスポンスのデータが複数回に分割される可能性があるため、ループを使ってすべてのデータを受け取る
      • logProcessStandardStreams():ログ出力
  • use Nesk\Rialto\{Instruction, ProcessSupervisor}
    • こういう2つまとめて名前空間指定する方法もあるのか
  • call_user_func('increment', $a);
    • ユースケース
      • https://github.com/rialto-php/rialto/blob/dev/src/Instruction.php
        • __callStatic() は、 アクセス不能メソッドをstatic メソッドとして呼び出した場合に起動
          • 呼び出されたメソッド名を文字列整形してcall_user_funcで自身のメソッドを呼び出す
      • 引数を変えて何度も同じメソッドを呼び出す場合
    • call_user_func() のパラメータは 参照渡しではない
  • bindTo:オブジェクトの置き換えができる
    • プロパティの値が異なるオブジェクト(インスタンス)を置き換える
    • プラグイン:メソッドを追加する
      • bindToで関数追加:$this->functions[$method_name] = $callback->bindTo($this, self::class);
      • __call($name, $args)で存在しないメソッドに対してbindToで追加された関数を実行
      • $callback = $this->functions[$name]; call_user_func_array($callback, $args);

引数を変えて何度も同じメソッドを呼び出す場合

$data = [1, 2, 3];
$callback = function($num) {
    return $num * 2;
};

$result = array_map(function($item) use ($callback) {
    return call_user_func($callback, $item);
}, $data);

print_r($result);

phpseclib/phpseclib

https://github.com/phpseclib/phpseclib
https://phpseclib.com/docs/why
SSH2, SFTP等のプロトコル、RSA・DSAといった暗号をPHPで操作するためのライブラリ

  • WeakReference:弱い参照(「参照している変数が存在していても、ガベージコレクタはそのメモリ領域を開放できる」ことを実現するもの)
    • 循環参照が起きている領域を開放するためには、「未開放のオブジェクトから参照元を全部辿って、その領域が未使用であることを調べる」というコストがかかる処理を行う必要があるため、即時実行するのではなく、一定の条件が成り立った時にまとめて回収を行うようになっている
    • unset()しただけでは循環参照領域は開放されずGCを強制実行(gc_collect_cycles())することで解放
    • PHPのGCサイクルは、デフォルトでオブジェクトが10,000個作られた時
    • 循環参照はメモリ枯渇のリスクをはらんでいるので、不用意に使うのは良くない
    • https://qiita.com/miracle-FJSW/items/f35c3e90a5d14eb6eba3
  • connect
    • https://www.alaxala.com/jp/techinfo/archive/manual/AX3650S/HTML/11_14_S/CFGUIDE/0215.HTM
    • fsockopen:指定したリソースへのソケット接続を開始
    • 識別情報「SSH-2.0-phpseclib_3.0」をサーバへ送信
      • SSH通信を確立する前に、クライアントとサーバーが互いにプロトコルバージョンや識別情報を交換する
    • サーバから識別子を受信
      • stream_select:ソケットが読み込み可になるまで待ち
      • stream_get_line:1行分のデータを読み込み
    • クライアントが鍵交換メッセージを最初に送信しない場合サーバーからの鍵交換メッセージを受信して処理を進める
    • key_exchange():鍵交換
      • クライアントがKEXINITメッセージを送信(またはサーバーから受信)。
        • SSHプロトコルで使用される鍵交換(Key Exchange, KEX)プロセスの最初のメッセージ
        • クライアントとサーバーが通信を開始する際に、利用可能な暗号化方式や鍵交換アルゴリズムを交換するために使用
      • 鍵交換アルゴリズムの決定。
      • 鍵交換データ(パブリックキーなど)を計算。
      • 鍵交換メッセージをサーバーに送信。
      • サーバーから鍵交換データを受信。
      • セッション鍵の計算。
      • 暗号化とハッシュアルゴリズムの決定。
      • 暗号化通信の準備が完了

pubnub/php

https://github.com/pubnub/php

  • PubNub:リアルタイムなメッセージングやMQTTなどの裏側の仕組みを肩代わりしてくれるBaaSサービス
    • サーバーを介さずにデバイス間でリアルタイムにデータをやり取りできる
    • 用途:チャット、リアルタイム通知、オンラインゲーム、位置情報共有
  • MQTT(Message Queueing Telemetry Transport):IoT(Internet of Things)機器などのデバイス間でメッセージを送受信するための通信プロトコル
  • チャンネル:リアルタイムメッセージ通信を行うための仮想的な「ストリーム」のこと
  • ユーザーやデバイスは、同じチャンネルを介して双方向にメッセージを送受信できる
  • 特定のチャンネルに送られたメッセージは、そのチャンネルを購読しているクライアントだけに届く

googleapis/google-api-php-client/

https://github.com/googleapis/google-api-php-client/

composer.json

{
    "require": {
        "google/apiclient": "^2.15.0"
    },
    "scripts": {
        "pre-autoload-dump": "Google\\Task\\Composer::cleanup"
    },
    "extra": {
        "google/apiclient-services": [
            "Drive",
            "YouTube"
        ]
    }
}

composer.jsonのextraの値を取得している箇所:
https://github.com/googleapis/google-api-php-client/blob/8142a3e523299b999aa4833235a07f0f5fe24c82/src/Task/Composer.php#L35-L38

ドライブ直下にフォルダ作成したい場合はparentsは"root"をセット:

$config = array(
    'name' => $folderName,
     'mimeType' => 'application/vnd.google-apps.folder',
    'parents' => ["root"]
);

$folderMetadata = new \Google\Service\Drive\DriveFile($config);
$folderObject = $this->service->files->create($folderMetadata, array('fields' => 'id,name,parents'));

nextPageTokenが存在する場合まだデータがあるためループさせる(nullならオワリ):

$response = $this->service->files->listFiles(array(
    'q' => $query,
    'spaces' => 'drive',
    'pageToken' => $pageToken,
    'fields' => 'nextPageToken, files(id, name)',
 ));

Discussion