Closed80

Drupal Backend Specialist

tnakaitnakai

1.2 Demonstrate knowlwdge of OO PHP programing concepts

ここも飛ばそう

tnakaitnakai

1.3 Demonstrate knowledge of managing dependencies using Composer

tnakaitnakai

1.4 Demonstrate the use of Git for version control¶

一旦飛ばそう

tnakaitnakai

Test がよく分かってないまま PHPUnit のマニュアル読んだら意味不明だったので、
動画を見る。
https://www.youtube.com/watch?v=g1_XxWqZpZY&ab_channel=KiaIshii

PHPUnit ブラウザテストチュートリアル

  • BrowserTestBase は Webベースの動作と相互作用をテストする方法を提供する
    • 例えばアクセスチェック
  • Drupal のブラウザテスト
    • 仮想 Web ブラウザを使用して Drupal をインストール
    • テスト用に0からのためユーザー等も一切存在しない
    • コアモジュール以外も有効になっていない

実行するための設定が必要になる

tnakaitnakai

実行するための設定

以下動画の内容が続く

  • Drupal では PHPUnit というテストフレームワークをサポート
  • テストスイートが5種類用意されている
    ソフトウェアテストの目的や対象ごとに複数のテストケースをまとめたもの
    • Unit: DB, 構成データを使わない。モックで対応可能。クラスや関数単位で行う。
    • Kernel: DB や 構成データを使うが Webブラウザは不要。
    • Functional: Webブラウザを使用したテスト。
    • FunctionalJavaScript: WebブラウザとJS を使用したテスト。
    • Build: ビルドプロセスとその結果に対してテストを行う。
tnakaitnakai

導入方法

  • composer require drupal/core-dev --dev --update-with-all-dependencies

ローカルで composer 使えない

tnakaitnakai
  • インストール
    • composer require drupal/core-dev --dev --update-with-all-dependencies
  • version 確認
    • ./vendor/bin/phpunit --version
  • PHPUnit設定ファイルを用意
    • cp app/core/phpunit.xml.dist phpunit.xml
      • app/core 下にコピーすると楽

../../vendor/bin/phpunit -c phpunit.xml --testsuite unit 等で実行

tnakaitnakai

書いてみよう

  • ルール
    • {モジュール名}/tests/src/{testsuit}/xxxTest.php
  • 名前空間
    • Drupal\Tests{モジュール名}{testsuit}\xxxTest
    • 例) Drupal\Tests\marucha\Unit\Controller\MaruchaControllerTest
  • テストクラスは使用するテストスイートが用意する基底クラスを拡張する
  • テストメソッドは test から始まる public メソッド
  • docblock でテストに関する情報を記載
tnakaitnakai
カテゴリ 基底クラス
Unit \Drupal\Tests\UnitTestCase
Kernel \Drupal\KernelTests\KernelTestBase
Functional \Drupal\Tests\BrowserTestBase
FunctionalJavaScript \Drupal\FunctionalJavaScriptsTests\WebDriverTestBase
Build \Drupal\BuildTests\Framework\BuildTestBase

ディレクトリは module/tests/src/{testunit}

tnakaitnakai

テストはローカルでは成功するが、drupal.org の testbot で実行すると失敗する

  • testbot 依存関係を認識していない
    • info.yml へ test_dependencies プロパティを設定する
      • 最大24hかかる
    • 開発バージョンを使用している
    • __DIR__ UnitTestCase::$root のルートが間違っている
      • UnitTestCase::__construct でオーバーライドできる
tnakaitnakai

寄り道したけどなんとなく理解できてきた。

tnakaitnakai

PHPUnit ブラウザテストチュートリアルに戻る

  • setUp() でテスト対象クラスのインスタンス化ができる
    • テストの前処理を行う
    • オプション
  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    // Create an article content type that we will use for testing.
    $type = $this->container->get('entity_type.manager')->getStorage('node_type')
      ->create([
        'type' => 'article',
        'name' => 'Article',
      ]);
    $type->save();
    $this->container->get('router.builder')->rebuild();
  }
tnakaitnakai

PHPUnit ファイル構造、名前空間、および必要なメタデータ

動画で網羅されていた

tnakaitnakai

以降読んだけど、実際使ってないとよくわからない印象。読むだけ読んだ。

tnakaitnakai

2.1 Demonstrate an ability to register paths to define how URL requests are handled in Drupal using Routing system and Menu API¶

Factory

オブジェクトの生成を担う

  • 2つの利点
    • Automoble クラスを変更、置換がある場合ファクトリ内のコードを変更するだけで良い
    • オブジェクトの作成が複雑の場合、新しいインスタンスを作成するたびに繰り返すのではなく、すべての作業をファクトリで実行できる
interface Vehicle {
    public function drive();
}

// Car クラス
class Car implements Vehicle {
    public function drive() {
        echo "Driving a car...\n";
    }
}

// Truck クラス
class Truck implements Vehicle {
    public function drive() {
        echo "Driving a truck...\n";
    }
}

// VehicleFactory クラス
class VehicleFactory {
    public static function createVehicle(string $type): Vehicle {
        switch ($type) {
            case 'car':
                return new Car();
            case 'truck':
                return new Truck();
            default:
                throw new Exception("Invalid vehicle type specified.");
        }
    }
}

$car = VehicleFactory::createVehicle('car');
$car->drive(); // Driving a car...

$truck = VehicleFactory::createVehicle('truck');
$truck->drive(); // Driving a truck...

Singleton

  • 特定のクラスの1つのインスタンスのみにアクセスできるようにする
  • インスタンスが1つであることを保証する
  • 依存性注入を使用する方が多い(?)
class Singleton {

    private static $singleton;

    // private にする. new できない.
    private function __construct() {
        echo ;
    }

    public static function getInstance() {
        if (!isset(self::$singleton)) {
            self::$singleton = new Singleton();    
        }
        return self::$singleton;
    }
}

Strategy

  • 柔軟性と拡張性の高さ: ストラテジーパターンを使用することで、アルゴリズムを定義し、それらをカプセル化し、お互いに置換可能にすることができます。これにより、クライアントコードは、アルゴリズムを変更する必要がある場合に、そのコードを変更することなく、異なるアルゴリズムを使用できるようになる。

  • 再利用性の高さ: ストラテジーパターンでは、複数のクラスが同じインターフェースを実装するため、コードの再利用性が高まりまる。

  • テスト容易性の向上: ストラテジーパターンでは、アルゴリズムを単一のクラスにカプセル化するため、個々のアルゴリズムのテストが容易になる。

  • アルゴリズムの切り替えの容易さ: ストラテジーパターンでは、クライアントコードは、実行時に異なるアルゴリズムを切り替えることができる。これにより、アルゴリズムの変更によるシステムへの影響を最小限に抑えることができる。

  • アルゴリズムの比較の容易さ: ストラテジーパターンでは、複数のアルゴリズムが同じインターフェースを実装するため、アルゴリズム間の比較が容易。

interface PaymentMethod {
  public function pay($amount);
}

class CreditCardPayment implements PaymentMethod {
  public function pay($amount) {
    // クレジットカードで支払いを行う
  }
}

class BankTransferPayment implements PaymentMethod {
  public function pay($amount) {
    // 銀行振込で支払いを行う
  }
}

class PaypalPayment implements PaymentMethod {
  public function pay($amount) {
    // Paypalで支払いを行う
  }
}

class Payment {
  private $paymentMethod;

  public function setPaymentMethod(PaymentMethod $paymentMethod) {
    $this->paymentMethod = $paymentMethod;
  }

  public function pay($amount) {
    $this->paymentMethod->pay($amount);
  }
}

// 使用例
$payment = new Payment();

// クレジットカードで支払い
$creditCardPayment = new CreditCardPayment();
$payment->setPaymentMethod($creditCardPayment);
$payment->pay(1000);

// 銀行振込で支払い
$bankTransferPayment = new BankTransferPayment();
$payment->setPaymentMethod($bankTransferPayment);
$payment->pay(2000);

// Paypalで支払い
$paypalPayment = new PaypalPayment();
$payment->setPaymentMethod($paypalPayment);
$payment->pay(3000);

Front Controller

index.php のように単一の入り口を持つ

tnakaitnakai

Dependency Injection

  • 可読性の工場
  • テストの用意下
  • 再利用性の向上
class Database {
    protected $adapter;

    public function __construct(MySqlAdapter $adapter) {
        $this->adapter = $adapter;
    }
}
class MysqlAdapter {}

S.O.L.I.D.

  • S: 単一責任の原則 (Single Responsibility Principle)
  • O: オープン・クローズドの原則 (Open/Closed Principle)
    • 拡張用にオープンにする必要はあるが、変更用にはクローズ
    • 新しいクラスが必要な場合に、既存のコードを変更するのではなく、既存のコードで使用される新しいコードを作成するように設計する
  • L: リスコフの置換原則 (Liskov Substitution Principle)
    • 子クラスは親クラスの型定義を壊してはならない
  • I: インターフェース分離の原則 (Interface Segregation Principle)
  • D: 依存性逆転の原則 (Dependency Inversion Principle)
tnakaitnakai

info.yml に必須

  • name
  • type
  • core_version_requirement
tnakaitnakai

モジュールの命名

  • 文字で始まる
  • 小文字、数字、アンダースコア
  • スペースはだめ
  • 50文字以内
tnakaitnakai

コントローラーとルーティング
やるのみ。
https://www.drupal.org/docs/develop/creating-modules/create-a-custom-page-using-a-controller

example.my_page:
  path: '/mypage/page'
  defaults:
    _controller: '\Drupal\example\Controller\ExampleController::myPage'
    _title: 'My first page in D8'
  requirements:
    _permission: 'access content'
<?php
namespace Drupal\example\Controller;

use Drupal\Core\Controller\ControllerBase;

/**
 * Provides route responses for the Example module.
 */
class ExampleController extends ControllerBase {

  /**
   * Returns a simple page.
   *
   * @return array
   *   A simple renderable array.
   */
  public function myPage() {
    return [
      '#markup' => 'Hello, world',
    ];
  }

}
tnakaitnakai

カスタムブロック作るのみ
https://www.drupal.org/docs/creating-modules/creating-custom-blocks

  • blockForm() でフォーム作成
  • blockSubmit() で保存
  public function blockSubmit($form, FormStateInterface $form_state) {
    $this->configuration['hello_block_name'] = $form_state->getValue('hello_block_name');
  }
// フィールドセットラッパーがあったら配列で
$this->configuration['hello_block_name'] = $form_state->getValue(['myfieldset', 'hello_block_name']);

構成として保存された値を使用

    $config = $this->getConfiguration();
    if (!empty($config['hello_block_name'])) {
      $name = $config['hello_block_name'];
    }
tnakaitnakai

プラグインAPI

プラグインの3つの要素

  • プラグインタイプ
    • プラグインタイプはこのタイプのプラグインがどのように発見され、インスタンス化されるかを定義する中央制御クラス
    • キャッシュバックエンド, イメージアクション, ブロックなどのすべてのプラグインの中心的な目的を記述
  • プラグインディスカバリー
    • 利用可能なコードベースの中から、特定のプラグインタイプのユースケースで使用するのに適したプラグインを見つけるプロセス
  • プラグインファクトリー
    • インスタンス化するところ

さらに色々役立つコンポーネントもある。

  • プラグインデリバティブ
    • 1つのプラグインが多数のプラグインの代わりに機能
    • 管理者の負担を減らすために部分的に構成されたファーストクラスのプラグインとして提供
    • 複数のプラグインを1つのプラグインの変わりに表示することで、UIをサポートしてヘルプテキストもレンダリングできる
  • ディスカバリーデコレーター
    • 既存のディスカバリーメソッドをラップする方法
    • CoreではcacheDecoratorを提供して、プラグインタイプのディスカバリープロセスをキャッシュ
  • プラグインマッパー
    • 文字列を特定のプラグインインスタンスにマップ
    • 開発者がプラグインインスタンスを手動でインスタンス化と構成する必要がない
tnakaitnakai

独自のプラグインタイプを作成

  • プラグインマネージャクラスを用意
    • クラス内で検出方法を定義
    • services.yml に登録
  • アノテーションクラス
    • プラグインで利用したいメタデータを定義
  • インターフェースを用意
    • プラグインで実装して欲しいメソッドを定義

プラグインマネージャ

  • 新しいプラグインタイプの定義
  • プラグインの検出方法の定義
  • 検出とインタンス化

サービスとして定義されている

tnakaitnakai

プラグインを選ぶ理由

なぜプラグイン

  • インターフェイスの検出、メタデータの処理、プラグインクラスのファクトリを提供

プラグインかタグ付きサービスか

  • ユーザーが動作を選択、構成する必要があるときはプラグイン
  • ユーザーの操作が不要な場合はタグ付きサービス
tnakaitnakai

アノテーションベースのプラグイン

コアによって提供されるプラグインタイプの例

  • ブロック
  • フィールドフォーマッタ、フィールドウィジェット
  • ビュー
  • コンディション

なぜアノテーション

  • 複雑なデータの構造化が可能になった
  • プラグイン検出に以前まではメモリがめっちゃ使われていた
    • 各クラスに getInfo() メソッドがあり、各クラスをロードする必要があった。
    • リクエストが終了するまでメモリが開放されない
    • アノテーションを解析する実装はPHPファイルとして含めずトークン化するためメモリを抑えられる

/**
 * Checks if a user name is unique on the site.
 *
 * @Constraint(
 *   id = "UserNameUnique",
 *   label = @Translation("User name unique", context = "Validation"),
 * )
 */
class UserNameUnique extends Constraint {
}

アノテーションの構文

  • プラグインIDから始める必要がある
  • 二重引用符
  • 1重引用符は例外をスロー
  • 定数
  • 使用可能な型
    • 文字列: 二重引用符を文字列として扱うとき "The ""On"" value"
    • 数値: 引用符を使用しない
    • 真偽値 引用符を使用しない
    • リスト
base = {
  "node",
  "foo",
}
  • マップ
edit = {
  "editor" = "direct",
}

独自のプラグインタイプでアノテーションを使用する

  • AnnotatedClassDiscovery を使用する DefaultPluginManager を拡張するだけ
  • DefaultPluginManager コンストラクタの第一引数は 名前空間
tnakaitnakai

独自のプラグインマネージャの作成

特定のタイプのプラグインを検出してインスタンス化する方法を定義する中央制御クラス。

プラグインマネージャーの定義

  • 検出方法を定義する
  • ファクトリを定義する

DefaultPluginManager を基本クラスとして使用する

ディスカバリーデコレーター

  • ディスカバリクラスをラップ
tnakaitnakai

Drupal プラグインディスカバリー

Drupal が特定のプラグインタイプを見つけるプロセス

4つの異なるコアディスカバリータイプがある

  • StaticDiscovery
    • プラグインを直接登録できる
    • \Drupal::service()のように呼び出せる
  • フックディスカバリー
  • AnnotatedClassDiscovery
    • アノテーションを使用して検出できる
  • Yaml ディスカバリー
    • プラグインを yml ファイルで定義
tnakaitnakai

プラグインの定義

  • UIをコンポーネント化する役割を持つ
  • それ以外にも様々な状況でコンポーネントが使用されるため、メタ情報または定義を使用して容易に関連付けができるようになっている
tnakaitnakai

プラグインコンテキスト

  • 例えばプラグインがブロックとして表示される場合、そのブロックが表示されるページの URL、言語、ユーザーのロールなどがコンテキストといえる
  • 各コンテキストに対して名前、説明、タイプ、オプション等を定義するために使用
  • 別のオブジェクトを注入する手段
tnakaitnakai

プラグインデリバティブ

公式分からない。

https://gotohayato.com/content/140/
を確認

1 つのプラグインクラスから複数のプラグインを生成するための仕組み

  • 通常1:1 であるが、1:nを実現する仕組みのこと

あるタイプのエンティティが増えるとそれに伴ってプラグインが動的に生成される

  • view 等が例
tnakaitnakai

作成する

  • 必須

    • ID
    • ラベル
    • デフォルトフォーマット
  • FieldItemInterface を実装

    • それを実装しているFieldItemBaseを拡張
class BazItem extends FieldItemBase {}
  • FieldItemInterface::schema をオーバーライド

    • フィールドの値を格納
  • propertyDefinitions

    • バリデーション、プロパティを設定
  • isEmpty()

    • 空かどうか

フィールドの設定

  • defaultFieldSettings

    • デフォルトの設定
  • スキーマを作成

    • defaultFieldSettings で設定したスキーマ
      [MODULE ROOT]/config/schema/[MODULE_NAME].schema.yml
field.field_settings.[FIELD ID]:
  type: mapping
  label: 'FIELDNAME settings'
  mapping:
    size:
      type: string
      label: 'Size'
  • fieldSettingsForm
    • ユーザーが設定を変更できるフォームを提供
tnakaitnakai

カスタムウィジェット

フォーム内のフィールドをレンダリングするために使用される
formElement()が必須

  • defaultSettings()

    • デフォルトの設定
  • スキーマを作成
    /[MODULE_NAME]/config/schema/[MODULE_NAME].schema.yml

field.widget.settings.[WIDGET ID]:
  type: mapping
  label: 'WIDGET NAME widget settings'
  mapping:
    size:
      type: integer
      label: 'Size'
  • settingsForm()
    • ユーザーが設定できるフォーム作成
tnakaitnakai

フォーマッター

エンドユーザー用にフォーマットする

  • defaultSettings()

    • デフォルト設定
  • スキーマ作成

[MODULE ROOT]/config/schema/[MODULE_NAME].schema.yml

field.formatter.settings.[FORMATTER ID]:
  type: mapping
  label: 'FORMATTER NAME text length'
  mapping:
    text_length:
      type: string
      label: 'Text Length'
  • settingsForm()
    • ユーザーが設定を変更できるようにする
tnakaitnakai

フォーマッタでDI

  • ContainerFactoryPluginInterface() を実装
  • create() を実装 or オーバーライド
  • コンストラクタを実装 or オーバーライド
tnakaitnakai
tnakaitnakai

フォームの種類

  • FormBase
    • 普通のフォーム
  • ConfigFormBase
    • 構成を保存するフォーム
  • ConfirmFormBase
    • ユーザーへの確認画面

実装する

tnakaitnakai

https://www.drupal.org/docs/drupal-apis/form-api/conditional-form-fields

states 確認
否定も使える

    $form['colour_select'] = [
      '#type' => 'radios',
      '#title' => $this->t('Pick a colour'),
      '#options' => [
        'black' => $this->t('Black'),
        'other' => $this->t('Other'),
      ],
      '#attributes' => [
        'id' => 'field_colour_select',
      ],
    ];
    $form['custom_color'] = [
      '#type' => 'textfield',
      '#size' => '60',
      '#placeholder' => 'Enter favourite colour',
      '#attributes' => [
        'id' => 'custom-colour',
      ],
      '#states' => [
        'visible' => [
          ':input[id="field_colour_select"]' => ['!value' => 'other'],
        ],
      ],
    ];

tnakaitnakai
tnakaitnakai

Drupal で扱われるデータ

  • Content: サイト訪問者に表示するテキスト、画像など
  • Configuration: サイトの動作、表示方法を定義する情報
  • State: cron が最後に実行された時刻等サイトの現在の状態に関する一時的な性質
  • Session: ログインしているかどうか, Cookie等個々のサイト訪問者のサイトとのやり取り

Content ち Configuration が エンティティ

tnakaitnakai

エンティティとは

  • コンテンツ, 構成の永続的なストレージに使用されるオブジェクト
  • データを扱う方法の1つ, データやコンテンツのモデリングを行う
  • グルーピングはプラグインシステムによって定義される
tnakaitnakai

エンティティの用語

  • エンティティ: 構造化された単一のアイテム(インスタンス)
    • 1つの記事、1つのユーザ
  • エンティティタイプ: グルーピングしたもの。エンティティタイプはEntityTypeManager によって定義されるプラグインタイプのプラグイン
    • コンテンツ、タクソノミー
  • バンドル: サブタイプ。任意。
    • 記事コンテンツタイプ、タグ
tnakaitnakai

構成エンティティとコンテンツエンティティ

  • エンティティタイプを定義するアノテーションを見て判別できる
    • ContentEntityType または ConfigEntityType
tnakaitnakai

とりあえず一周した。理解は半分ぐらい。

tnakaitnakai

とりあえず落ちた。ぼちぼち復習やっていく。

このスクラップは2023/04/28にクローズされました