PHP 初心者が WordPress の プラグイン 作成 ! part48 ( コードを整理 動きの確認 )

この記事では PHP 初心者 が WordPress プラグイン を 作成 します。 part48 では引き続き今までのコードを少し整理してみます。

前回に引き続きココナラのヘルパークラスを作成していきます。

PHP 初心者が WordPress の プラグイン 作成 ! part47 ( コードを整理 04 )

今回は作成した部分が本当に動作するのか確認したいと思います。

現在のヘルパークラス

現時点でのヘルパークラスは以下のような実装になっています。

class.coconara-helper.php

<?php
namespace com\ik_genety\plugin\coconara;

// autoloadを読み込む
require 'vendor/autoload.php';

use HeadlessChromium\BrowserFactory;
use HeadlessChromium\Page;

use HeadlessChromium\Exception\OperationTimedOut;
use HeadlessChromium\Exception\NavigationExpired;

class Coconara_Helper {
    // ヘッドレスブラウザ
    
    private $browser;
    private $page;

    // ココナラアカウント
    private const USER_NAME = 'hoge';
    private const USER_PASSWORD = 'huga';

        
    function __construct() {
        // whereis chromiumでパスを探した
        $browserFactory = new BrowserFactory('/usr/bin/chromium');

        // for Docker option
        $options = [
            'headless' => true,
            'noSandbox' => true ,
            'sendSyncDefaultTimeout' => 100000,
            'windowSize'      => [1920, 1000],
            'userAgent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
            'enableImages'    => false,
        ];

        // ヘッドレスブラウザスタート
        $this->browser = $browserFactory->createBrowser($options);

        // 到達できない場合エラー
        if(!reachable()) {
            throw new Exception('サイトに到達できませんでした。');
        }
    }
    // 到達確認
    private function reachable(): bool
    {
        // トップページを取得する
        $tmpPage = getFirstPage();

        // ページタイトル取得
        $pageTitle = $tmpPage->evaluate('document.title')->getReturnValue();
        
        //echo $pageTitle;

        // ブラウザからページを破棄
        $tmpPage.close();

        // タイトル確認
        return $pageTitle === 'ココナラ - みんなの得意を売り買い スキルマーケット';
    }

    // トップページを取得
    private function getFirstPage(): Page
    {
        // 新しいタブを作成しサイトにアクセス
        $page = $this->browser->createPage();
        $page->navigate('https://coconala.com/')->waitForNavigation();

        return $page;
    }
    

    // ログインを行う
    private function login() : bool
    {
        // トップページを取得する
        $this->page = getFirstPage();

        // ココナラのログイン画面に遷移する(30秒待つ)
        $this->page->evaluate('document.querySelector("li.c-mainNavBeforeLogin_item-pc a:first-child").click()')->waitForPageReload(Page::LOAD, 30000);

        // アカウント入力
        $page->evaluate(
        '(() => {
            document.querySelector("#UserLoginEmail").value = "'.self::USER_NAME.'";
            document.querySelector("#UserLoginPassword").value = "'.self::USER_PASSWPRD.'";
            document.querySelector("form#UserLoginForm button[type=\"submit\"]").removeAttribute("disabled");
        })()');

        // ログインを実施(エラーは全て無視)
        try{
            $evaluation = $page->evaluate('document.querySelector("form#UserLoginForm button[type=\"submit\"]").click()')->waitForPageReload(Page::LOAD, 30000);
        } catch(OperationTimedOut $e) {
        } catch(NavigationExpired $e) {
        }

        // ダッシュボードのリンク確認を行ってログイン状態か確認する
        $isLogin = $this->page->evaluate('document.querySelector(".c-mainNavProviderLeft_item") != null ? true : false;')->getReturnValue();
        return $isLogin;

    }

    function __destruct() {
        // 親クラスのデストラクト呼び出し
        super._destruct();

        // ヘッドレスブラウザを閉じる
        $browser->close();
    }
}

index.phpから実装を引っ越しさせただけの部分が大半です。

外部呼出し用のメソッド

ココナラヘルパーはコンストラクタとデストラクタ以外はprivate修飾してあるので外部から使用できるメソッドありません。
外部からの呼び出し用にメソッドを作成します。
外部に見せるのは「投稿」の機能です。

投稿するには以下の手順ですね。

  1. ログイン
  2. ブログに遷移
  3. 記事入力
  4. 投稿

現時点では「ログイン」の実装のみ完了しています。
今回は「投稿」の機能にログインの機能を盛り込むだけにしておきます。

実装は以下のようになります。

class.coconara-helper.php

    /**
     * 投稿する
     * @param string $title タイトル
     * @param string $contents コンテンツ
     * @param bool $debug デバッグ出力
     * @return bool 投稿成功・失敗
     */
    public function post(string $title, string $contents, bool $debug=false) : bool {
        if(!login) {
            if($debug) {
                // screenshot(10秒待機)
                $this->page->screenshot()->saveToFile('/var/www/html/post.png', 10000);
            }
            return false;
        }

        if($debug) {
            // screenshot(10秒待機)
            $this->page->screenshot()->saveToFile('/var/www/html/post.png', 10000);
        }
        return true;
    }

「投稿」の機能の呼び出し時にタイトルと本文を渡すように定義してあります。
最後の引数にデバッグのフラグを定義し、trueの場合のみデバッグ出力(今回はスクリーンショット取得)するようにしました。
コメントについて、PHPDocという書き方があるようでなのでそちらに従って書きました。
他のメソッドも適宜修正していこうと思います。

 

呼び出し側の実装です。

index.php

<?php
require_once 'class.coconara-helper.php';

// クラスをまとめてインポート(PHP7.0.0から)
use com\ik_genety\plugin\coconara\{Coconara_Helper};

$coconara = new Coconara_Helper();
if($coconara->post('タイトルテスト', 'コンテンツテスト',true)) {
    echo '投稿成功!';
} else {
    echo '投稿失敗!';
}

かなりシンプルになりましたね!

確認

では早速アクセスしてみましょう。

http://localhost:8099/index.php ・・・っと・・・。
即エラーでした。(笑

エラー内容

エラー内容な以下の通りです。

Uncaught Error: Call to undefined function com\ik_genety\plugin\coconara\reachable() in /var/www/html/class.coconara-helper.php:44

メソッドが未定義という風に書いているように見えます。
この部分ですかね。

class.coconara-helper.php

if(!reachable()) {

原因

おそらくですが、$this->の記述を忘れてしまったことが原因のような気がします。
ほかにも同様の問題があったようなのでそれらも合わせて修正しました。

また、PHPはアロー演算子でメソッド呼び出しを行うのですが、ほかの言語の癖でドット演算子を使っている部分もありそちらも修正しておきます。

さらに親は「super」ではなく「parent」。しかもJavaのように暗黙的な親(Object)はいないようです。つまり私が作ったヘルパークラスは「最上位クラス」ということになります。
なので「親もデストラクタ呼び出しておこう!」というのはできるわけありません。

・・・案の定ボロボロ出てきますね😅

if(!$this->reachable()) {
・・・
$tmpPage = $this->getFirstPage();
$tmpPage->close();
if(!$this->login()) {
・・・
//他多数

定数名も誤っていたので修正です。

//self::USER_PASSWPRD→self::USER_PASSWORD

ということで修正して再実行してみます。

再度確認

では再度確認を行います。

http://localhost:8099/index.php

 

・・・無事にログインできたようです!

まとめ

ココナラヘルパーに実装した部分の動作確認を行いました。
思った通り問題がたくさん出てきましたね😅

他の言語の癖が出てしまっている部分がおおですね。
VS Codeが編集している最中に教えてくれればいいのに・・・。
できるだけミスをしないように実装を続けます。

ココナラヘルパーは現在以下のような実装となっています。

<?php
namespace com\ik_genety\plugin\coconara;

// autoloadを読み込む
require 'vendor/autoload.php';

use HeadlessChromium\BrowserFactory;
use HeadlessChromium\Page;

use HeadlessChromium\Exception\OperationTimedOut;
use HeadlessChromium\Exception\NavigationExpired;

class Coconara_Helper {
    // ヘッドレスブラウザ
    
    private $browser;
    private $page;

    // ココナラアカウント
    private const USER_NAME = 'hoge';
    private const USER_PASSWORD = 'fuga';

    /**
     * コンストラクタ
     */
    function __construct() {
        
        // whereis chromiumでパスを探した
        $browserFactory = new BrowserFactory('/usr/bin/chromium');

        // for Docker option
        $options = [
            'headless' =< true,
            'noSandbox' =< true ,
            'sendSyncDefaultTimeout' =< 100000,
            'windowSize'      =< [1920, 1000],
            'userAgent' =< 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
            'enableImages'    =< false,
        ];

        // ヘッドレスブラウザスタート
        $this-<browser = $browserFactory-<createBrowser($options);

        // 到達できない場合エラー
        if(!$this-<reachable()) {
            throw new Exception('サイトに到達できませんでした。');
        }
    }

    // 到達確認
    private function reachable(): bool
    {
        $oya = get_parent_class($this);
        // トップページを取得する
        $tmpPage = $this-<getFirstPage();

        // ページタイトル取得
        $pageTitle = $tmpPage-<evaluate('document.title')-<getReturnValue();
        
        //echo $pageTitle;

        // ブラウザからページを破棄
        $tmpPage-<close();

        // タイトル確認
        return $pageTitle === 'ココナラ - みんなの得意を売り買い スキルマーケット';
    }

    // トップページを取得
    private function getFirstPage(): Page
    {
        // 新しいタブを作成しサイトにアクセス
        $tmpPage = $this-<browser-<createPage();
        $tmpPage-<navigate('https://coconala.com/')-<waitForNavigation();

        return $tmpPage;
    }
    

    // ログインを行う
    private function login() : bool
    {
        // トップページを取得する
        $this-<page = $this-<getFirstPage();

        // ココナラのログイン画面に遷移する(30秒待つ)
        $this-<page-<evaluate('document.querySelector("li.c-mainNavBeforeLogin_item-pc a:first-child").click()')-<waitForPageReload(Page::LOAD, 30000);

        // アカウント入力
        $this-<page-<evaluate(
        '(() =< {
            document.querySelector("#UserLoginEmail").value = "'.self::USER_NAME.'";
            document.querySelector("#UserLoginPassword").value = "'.self::USER_PASSWORD.'";
            document.querySelector("form#UserLoginForm button[type=\"submit\"]").removeAttribute("disabled");
        })()');

        // ログインを実施(エラーは全て無視)
        try{
            $evaluation = $this-<page-<evaluate('document.querySelector("form#UserLoginForm button[type=\"submit\"]").click()')-<waitForPageReload(Page::LOAD, 30000);
        } catch(OperationTimedOut $e) {
        } catch(NavigationExpired $e) {
        }

        // ダッシュボードのリンク確認を行ってログイン状態か確認する
        $isLogin = $this-<page-<evaluate('document.querySelector(".c-mainNavProviderLeft_item") != null ? true : false;')-<getReturnValue();
        return $isLogin;

    }

    /**
     * 投稿する
     * @param string $title タイトル
     * @param string $contents コンテンツ
     * @param bool $debug デバッグ出力
     * @return bool 投稿成功・失敗
     */
    public function post(string $title, string $contents, bool $debug=false) : bool {
        if(!$this-<login()) {
            if($debug) {
                // screenshot(10秒待機)
                $this-<page-<screenshot()-<saveToFile('/var/www/html/post.png', 10000);
            }
            return false;
        }

        if($debug) {
            // screenshot(10秒待機)
            $this-<page-<screenshot()-<saveToFile('/var/www/html/post.png', 10000);
        }
        return true;
    }

    /**
     * デストラクタ
     */
    function __destruct() {
        // ヘッドレスブラウザを閉じる
        $this-<browser-<close();
    }
}

 

今日はここまで!