Ibexa DXP でのカスタムフィールドタイプの作成

# カスタム汎用フィールドタイプの作成
汎用フィールドタイプは、住所などの構造化データを保持するフィールドタイプを抽象的に実装したものです。これをベースにして、カスタムフィールドタイプを作成することができます。汎用フィールドタイプには、基本的なメソッドが実装されており、作成しなければならないクラスの数を減らし、タグ付けのプロセスを簡略化することができます。

:::note
**ヒント**
非常に特殊な実装や、データの保存方法を完全に制御する必要がある場合は、汎用フィールドタイプを使うべきではありません。
:::

## Value オブジェクトの定義
まず、 `src/FieldType/HelloWorld` ディレクトリに `Value.php` を作成します。フィールドタイプの Value クラスには、フィールドタイプの基本的なロジックのみが含まれており、それ以外は Type クラスで処理されます。フィールドタイプの Value の詳細については、 [Value の取り扱い](https://doc.ibexa.co/en/latest/content_management/field_types/type_and_value/#value-handling)を参照してください。

`HelloWorld` の Value クラスには、以下のものが含まれているはずです。

* `name` を取得するパブリックプロパティ
* `toString()` メソッドの実装

```php:src/FieldType/HelloWorld/Value.php
<?php

namespace App\FieldType\HelloWorld;

use Ibexa\Contracts\Core\FieldType\Value as ValueInterface;
use Symfony\Component\Validator\Constraints as Assert;

final class Value implements ValueInterface
{
    /**
     * @Assert\NotBlank()
     */
    private $name;

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(?string $name): void
    {
        $this->name = $name;
    }

    public function __toString()
    {
        return "Hello {$this->name}!";
    }
}
```

## フィールドと設定の定義
次に、汎用フィールドタイプを拡張したフィールドタイプの定義を `src/FieldType/HelloWorld/Type.php` クラスで実装します。このクラスは、フィールドタイプの設定と `Ibexa\Contracts\Core\FieldType\FieldType` 抽象クラスの実装を提供します。

```php:src/FieldType/HelloWorld/Type.php
<?php

namespace App\FieldType\HelloWorld;

use App\Form\Type\HelloWorldType;
use Ibexa\Contracts\Core\FieldType\Generic\Type as GenericType;

final class Type extends GenericType implements FieldValueFormMapperInterface
{
    public function getFieldTypeIdentifier(): string
    {
        return 'hello_world';
    }
}
```

:::note
**チップ**
フィールドタイプの Type クラスの詳細については、「[Type クラス](https://doc.ibexa.co/en/latest/content_management/field_types/type_and_value/#type-class)」を参照してください。
:::

次に、フィールドタイプをサービスとして登録し、 `ibexa.field_type` でタグ付けします。

```yaml
services:
    App\FieldType\HelloWorld\Type:
        public: true
        tags:
            - { name: ibexa.field_type, alias: hello_world }
```

## Value オブジェクトのためのフォームの定義
`src/Form/Type/HelloWorldType.php` のフォームを作成します。これにより、新しいフィールドタイプを編集することができます。

```php:src/Form/Type/HelloWorldType.php
<?php

declare(strict_types=1);

namespace App\Form\Type;

use App\FieldType\HelloWorld\Value;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

final class HelloWorldType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder->add('name', TextType::class);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Value::class
        ]);
    }
}
```

これで、FormMapper を使って Symfony のフォームにフィールドの定義をマッピングすることができるようになりました。 `FieldValueFormMapperInterface` が要求する `mapFieldValueForm()` メソッドと、必要な `use` 文を `src/FieldType/HelloWorld/Type.php` に追加してください。

```php:src/FieldType/HelloWorld/Type.php
<?php

namespace App\FieldType\HelloWorld;

use App\Form\Type\HelloWorldType;
use Ibexa\Contracts\Core\FieldType\Generic\Type as GenericType;
use Ibexa\Contracts\ContentForms\Data\Content\FieldData;
use Ibexa\Contracts\ContentForms\FieldType\FieldValueFormMapperInterface;
use Symfony\Component\Form\FormInterface;

final class Type extends GenericType implements FieldValueFormMapperInterface
{
    public function getFieldTypeIdentifier(): string
    {
        return 'hello_world';
    }

    public function mapFieldValueForm(FormInterface $fieldForm, FieldData $data): void
    {
        $definition = $data->fieldDefinition;

        $fieldForm->add('value', HelloWorldType::class, [
            'required' => $definition->isRequired,
            'label' => $definition->getName()
        ]);
    }
}
```

:::note
**チップ**
フォームマッパーの詳細については、「[フィールドタイプのフォームとテンプレート](https://doc.ibexa.co/en/latest/content_management/field_types/form_and_template/)」を参照してください。
:::

次に、 `ibexa.admin_ui.field_type.form.mapper.value` タグをサービス定義に追加してください。

```yaml
services:
    App\FieldType\HelloWorld\Type:
        public: true
        tags:
            - { name: ibexa.field_type, alias: hello_world }
            - { name: ibexa.admin_ui.field_type.form.mapper.value, fieldType: hello_world }
```

## フィールドのレンダリング
### テンプレートの作成
新しいフィールドタイプ用のテンプレートを作成します。これは、 `HelloWorld` フィールドのデフォルトレンダリングを定義します。`templates` ディレクトリに `field_type.html.twig` ファイルを作成します。

```twig:templates/field_type.html.twig
{% block hello_world_field %}
    Hello <b>{{ field.value.getName() }}!</b>
{% endblock %}
```

### テンプレートマッピング
`config/packages/ibexa.yaml` にテンプレートのマッピングを指定します。

```yaml:config/packages/ibexa.yaml
ibexa:
    system:
        default:
            field_templates:
                - { template: 'field_type.html.twig', priority: 0 }
```

## 最終結果
最後に、管理画面で新しいコンテンツタイプを追加することができるはずです。[コンテンツタイプ]タブに移動し、[コンテンツ]カテゴリで新しいコンテンツタイプを作成します。

![](https://doc.ibexa.co/en/latest/content_management/img/extending_field_type_create.png)

次に、 **Hello World** フィールドを定義します。

![](https://doc.ibexa.co/en/latest/content_management/img/extending_field_type_definition.png)

保存すると、サイドバーメニューの「**コンテンツ**」に **Hello World** コンテンツタイプが表示されるようになります。

![](https://doc.ibexa.co/en/latest/content_management/img/extending_field_type_hello_world.png)

汎用フィールドタイプの詳細については、次の「Point 2D フィールドタイプ型の作成」をご覧ください。

# Point 2D フィールドタイプ型の作成
https://doc.ibexa.co/en/latest/tutorials/generic_field_type/creating_a_point2d_field_type/

このチュートリアルでは、Ibexa DXP の[カスタムフィールドタイプの作成](https://doc.ibexa.co/en/latest/content_management/field_types/create_custom_generic_field_type/)と開発について説明します。Generic Field Type は、非常に強力な拡張ポイントです。すぐに使えるフィールドタイプのテンプレートを使って、複雑なソリューションを簡単に構築することができます。

フィールドタイプは以下を担当します：
* データの保存（ネイティブストレージエンジンメカニズムまたは特定の手段）
* 入力データの検証（バリデーション）
* データを検索可能にする（該当する場合）
* フィールドの表示

詳細については、[フィールドタイプのドキュメント](https://doc.ibexa.co/en/latest/content_management/field_types/field_types/)を参照してください。フィールドタイプの各コンポーネントがシステムのさまざまなレイヤーとどのように相互作用するか、およびそれらを実装する方法について説明します。

### 対象者
このチュートリアルは、Ibexa DXP に慣れて親しんでおり、PHP と Symfony での操作を容易にこなせる開発者を対象としています。

### チュートリアルの内容
このチュートリアルでは、カスタムフィールドタイプのテンプレートとして Generic フィールドタイプを使用する方法を説明します。このチュートリアルでは:
* 2つの座標を入力として、カスタム Point 2D フィールドタイプを作成する（例：(4,5)
* 新しいフィールドタイプをサービスとして登録し、そのテンプレートを定義する
* Point 2D に基本的なバリデーションを追加する
* フィールドタイプにデータ移行を追加することで、簡単に出力を変更できるようにする


### ステップ
このチュートリアルでは、次の手順を実行します。

1. Point 2D Value クラスの実装
2. Point 2D フィ​​ールドタイプの定義
3. フィールドタイプを編集するためのフォームの作成
4. テンプレートの導入
5. 新しい Point 2D フィールドの追加
6. Point 2D の設定の実装
7. 基本的なバリデーションの追加
8. フィールドタイプのバージョン間のデータ移行

## ステップ1 - Point 2D Value クラスの実装

### プロジェクトのインストール
チュートリアルを始めるには、Ibexa DXP をクリーンインストールする必要があります。お使いのシステムのガイドに従って、Ibexa DXP をインストールし、サーバーを設定し、Web サーバーを起動します。dev 環境を使用してインストールすることを忘れないでください。

クリーンインストールしたプロジェクトを開き、 `src/FieldType/Point2D` に新しい Point 2D Field Type 用のベースディレクトリを作成します。

### Value クラス
フィールドタイプの Value クラスは、設計上非常にシンプルです。これは、コンテンツアイテムの中でフィールドタイプのインスタンスを表現するために使用されます。各フィールドは、そのタイプの Value クラスのインスタンスを使用してデータを表示します。フィールドタイプの Value の詳細については、[Value handling](https://doc.ibexa.co/en/latest/content_management/field_types/type_and_value/#value-handling) を参照してください。

:::note
**ヒント**
慣習に従って、フィールドタイプ Value を表すクラスは Value と名付けられ、タイプ定義と同じネームスペースに配置されるべきです。
:::

Point 2D Value クラスには、以下のものが含まれます。
* 実際のデータを格納するために使用されるプライベートプロパティ
* Value インタフェースで必要とされる `__toString()` メソッドの実装

デフォルトでは、`FieldType\Value` のコンストラクターが使用されます。

Point 2D には、2つの要素（2Dの座標）が格納される予定です。
* `x`値
* `y`値

この時点では、それらがどこに格納されているかは重要ではありません。フィールドタイプが API として何を公開しているかに注目しましょう。

`src/FieldType/Point2D/Value.php` は以下のプロパティを持っているはずです。

```php
    /** @var float|null */
    private $x;
    /** @var float|null */
    private $y;
```

Value クラスは、 `Ibexa\Contracts\Core\FieldType\Value` インターフェイスも実装する必要があります。 `FieldType\Value` インターフェイスに合わせるために、 `__toString()` メソッドを実装する必要があります。また、 `x` と `y` のプロパティのゲッターとセッターを追加する必要があります。このクラスは、2D座標を表現します。

最終的にはこのようなコードになります。

```php:src/FieldType/Point2D/Value.php
<?php
declare(strict_types=1);

namespace App\FieldType\Point2D;

use Ibexa\Contracts\Core\FieldType\Value as ValueInterface;

final class Value implements ValueInterface
{
    /** @var float|null */
    private $x;
    /** @var float|null */
    private $y;

    public function __construct(?float $x = null, ?float $y = null)
    {
        $this->x = $x;
        $this->y = $y;
    }

    public function getX(): ?float
    {
        return $this->x;
    }

    public function setX(?float $x): void
    {
        $this->x = $x;
    }

    public function getY(): ?float
    {
        return $this->y;
    }

    public function setY(?float $y): void
    {
        $this->y = $y;
    }

    public function __toString()
    {
        return "({$this->x}, {$this->y})";
    }
}
```

## ステップ2 - Point 2D フィ​​ールドタイプの定義
### Type クラス
Type には、データの検証、様々なフォーマットからの変換、バリデータの記述など、フィールドタイプのロジックが含まれます。この例では、Point 2D フィールドタイプは `Ibexa\Contracts\Core\FieldType\Generic\Type` クラスを継承しています。フィールドタイプの Type クラスの詳細については、[Type クラス](https://doc.ibexa.co/en/latest/content_management/field_types/type_and_value/#type-class)を参照してください。

### フィールドタイプ識別子
まず、 `src/FieldType/Point2D/Type.php` を作成します。そこに `getFieldTypeIdentifier()` メソッドを追加してください。この新しいメソッドは、あなたのフィールドタイプ（ここでは `point2d`）を**一意に**識別する文字列を返します。
```php:src/FieldType/Point2D/Type.php
<?php
declare(strict_types=1);

namespace App\FieldType\Point2D;

use Ibexa\Contracts\Core\FieldType\Generic\Type as GenericType;

final class Type extends GenericType
{
    public function getFieldTypeIdentifier(): string
    {
        return 'point2d';
    }
}
```

### 新しいサービスの定義の追加
次に、 `config/services.yaml` に `ibexa.field_type` タグを追加してください。
```yaml:config/services.yaml
services:
    App\FieldType\Point2D\Type:
        tags:
            - { name: ibexa.field_type, alias: point2d }
```

## ステップ3 - フィールドタイプを編集するためのフォームの作成
### フォームの作成
新しいフィールドタイプを編集するには、`Point2DType.php` フォームを `src/Form/Type` ディレクトリに作成します。次に、 `AbstractType` を継承し、 `buildForm()` メソッドを実装した `Point2DType` クラスを追加します。このメソッドは、`x` と `y` の座標のフィールドを追加します。

```php:src/Form/Type/Point2DType.php
<?php
declare(strict_types=1);

namespace App\Form\Type;

use App\FieldType\Point2D\Value;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

final class Point2DType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder->add('x', NumberType::class);
        $builder->add('y', NumberType::class);
    }

}
```

### フォームマッパーのインターフェイスの追加
FormMapper は `add()` メソッドを使って Symfony フォームにフィールドの定義を追加します。`FieldValueFormMapperInterface` は管理インターフェイスでフィールドタイプの編集フォームを提供します。FormMapper の詳細については、[フィールドタイプのフォームとテンプレート](https://doc.ibexa.co/en/latest/content_management/field_types/form_and_template/)を参照してください。

まず、 `src/FieldType/Point2D/Type.php` のフィールドタイプ定義に FieldValueFormMapperInterface インターフェース（`Ibexa\Contracts\ContentForms\FieldType\FieldValueFormMapperInterface`）を実装します。

次に、 `mapFieldValueForm()` メソッドを実装し、`FormInterface::add` メソッドを以下の引数（ハイライト行）で呼び出します。
* フィールドの値がマッピングされるプロパティの名前: `value`
* フィールドのタイプ。フィールドのタイプ: `Point2DType::class`
* カスタムオプション: `required` と `label`

最終版の Type クラスは、以下のステートメントと関数を持つ必要があります。
```diff_php:src/FieldType/Point2D/Type.php
 <?php
 declare(strict_types=1);

 namespace App\FieldType\Point2D;

 use App\Form\Type\Point2DType;
+use Ibexa\Contracts\Core\FieldType\Generic\Type as GenericType;
 use Ibexa\Contracts\ContentForms\Data\Content\FieldData;
 use Ibexa\Contracts\ContentForms\FieldType\FieldValueFormMapperInterface;
+use Symfony\Component\Form\FormInterface;

 final class Type extends GenericType implements FieldValueFormMapperInterface
 {
     public function getFieldTypeIdentifier(): string
     {
         return 'point2d';
     }

+    public function mapFieldValueForm(FormInterface $fieldForm, FieldData $data)
+    {
+        $definition = $data->fieldDefinition;
+        $fieldForm->add('value', Point2DType::class, [
+            'required' => $definition->isRequired,
+            'label' => $definition->getName()
+        ]);
+    }
 }
```

最後に、`src/Form/Type/Point2DType.php` に `configureOptions` メソッドを追加し、 `data_class` のデフォルト値を `Value::class` に設定します。これで、あなたのフォームがこのオブジェクトで動作するようになります。
```diff_php:src/Form/Type/Point2DType.php
<?php
declare(strict_types=1);

namespace App\Form\Type;

use App\FieldType\Point2D\Value;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

final class Point2DType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder->add('x', NumberType::class);
        $builder->add('y', NumberType::class);
    }
+   public function configureOptions(OptionsResolver $resolver): void
+   {
+       $resolver->setDefaults([
+           'data_class' => Value::class
+       ]);
+   }
}
```

### 新しいタグの追加
次に、 `config/services.yaml` に `ibexa.admin_ui.field_type.form.mapper.value` のタグを追加します。
```diff_yaml:config/services.yaml
    App\FieldType\Point2D\Type:
        tags:
            - { name: ibexa.field_type, alias: point2d }
+           - { name: ibexa.admin_ui.field_type.form.mapper.value, fieldType: point2d }
```

## ステップ4 - テンプレートの導入
### Point 2D テンプレート
フィールドタイプのデータを表示するためには、そのためのテンプレートを作成し登録する必要があります。各フィールドタイプテンプレートは、目的のゴールを達成するために使用できる一連の変数を受け取ります。この場合、最も重要な変数はフィールドで、`Ibexa\Contracts\Core\Repository\Values\Content\Field` のインスタンスです。自身のメタデータ（`id`、`fieldDefIdentifier`など）に加え、`value` プロパティを通じてフィールド値を公開します。

何がどのように表示されるかを微調整するために、フィールドタイプテンプレートをオーバーライドできることを忘れないでください。詳細は、[フィールドタイプテンプレート](https://doc.ibexa.co/en/latest/content_management/field_types/form_and_template/#content-view-templates)のドキュメントを参照してください。

まず、 `templates` ディレクトリに `point2d_field.html.twig` というテンプレートを作成します。これは、Point 2D のデフォルト表示を定義するものです。Point 2D の基本テンプレートは、次のようになります。
```twig:templates/point2d_field.html.twig
{% block point2d_field %}
    ({{ field.value.getX() }}, {{ field.value.getY() }})
{% endblock %}
```

### テンプレートマッピング
次に、 `config/packages/ibexa.yaml` にテンプレートのマッピングを指定します。
```yaml:config/packages/ibexa.yaml
ibexa:
    system:
        default:
            field_templates:
                - { template: 'point2d_field.html.twig', priority: 0 }
```

## ステップ5 - 新しい Point 2D フィールドの追加
このステップのすべてのアクションは管理画面で実行されます。管理画面(`<yourdomain>/admin`)に移動し、デフォルトのユーザー名: `admin` およびパスワード: `Publish` でログインします。

### 新しいコンテンツタイプの追加
管理画面の左側のメニューで、[コンテンツ タイプ]ページに移動します。[コンテンツカテゴリ]で、新しいコンテンツタイプを作成します。

![](https://doc.ibexa.co/en/latest/tutorials/generic_field_type/img/create_new_content_type.png)

新しいコンテンツタイプには次の設定が必要です。

* **名前:** Point 2D
* **識別子:** point_2d
* **フィールド:** point2d.name

![](https://doc.ibexa.co/en/latest/tutorials/generic_field_type/img/point2d_field_definition.png)

次に、以下のフィールドで **point2d** を定義します。

|フィールドタイプ	|名前	|識別子	|必須	|翻訳可能|
|---	|---	|---	|---	|---	|
|point2d	|Point 2D	|point_2d	|はい	|いいえ|

![](https://doc.ibexa.co/en/latest/tutorials/generic_field_type/img/new_field_definition.png)

すべてを保存して、 [コンテンツ/コンテンツ構造] タブに戻ります。

### コンテンツの作成
**[コンテンツ構造]** で、 **[コンテンツの作成]** を選択します。 **Content** の下に、追加したばかりの Point 2D Content Type が表示されます。クリックして新しいコンテンツを作成します。

![](https://doc.ibexa.co/en/latest/tutorials/generic_field_type/img/menu_point2d.png)

ここで、ポイントの座標 (例: 3、5) を入力できます。指定された座標は、新しいポイントのタイトルとして使用されます。

![](https://doc.ibexa.co/en/latest/tutorials/generic_field_type/img/creating_new_point2d.png)

[公開] をクリックします。これで、コンテンツ ツリーに新しい(3,5)ポイントが表示されます。

:::note
**ヒント**
結果が表示されない場合やエラーが発生した場合は、キャッシュをクリアしてアプリケーションをリロードしてください。
:::

![](https://doc.ibexa.co/en/latest/tutorials/generic_field_type/img/new_point2d.png)

## ステップ6 - Point 2D 設定の実装
設定を実装することで、フィールドをページ上に表示するためのフォーマットを定義することができます。そのために、Point 2D の座標の表示方法を変更することができる `format` フィールドを作成することになります。

### フィールドタイプのフォーマットの定義
このステップでは、Point 2D 座標の `format` フィールドを作成します。そのためには、`SettingsSchema` 定義を定義する必要があります。また、座標をプレースホルダー値 `%x%` と `%y%` として指定します。

`src/FieldType/Point2D/Type.php` を開き、以下のコードブロックに従って `getSettingsSchema` メソッドを追加してください。


```php:src/FieldType/Point2D/Type.php
<?php
declare(strict_types=1);

namespace App\FieldType\Point2D;

use App\Form\Type\Point2DSettingsType;
use App\Form\Type\Point2DType;
use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinition;
use Ibexa\Contracts\Core\FieldType\Generic\Type as GenericType;
use Ibexa\Contracts\Core\FieldType\Value;
use Ibexa\Contracts\ContentForms\Data\Content\FieldData;
use Ibexa\AdminUi\Form\Data\FieldDefinitionData;
use Ibexa\Contracts\ContentForms\FieldType\FieldValueFormMapperInterface;
use Symfony\Component\Form\FormInterface;

final class Type extends GenericType
{
    public function getFieldTypeIdentifier(): string
    {
        return 'point2d';
    }

    public function getSettingsSchema(): array
    {
        return [
            'format' => [
                'type' => 'string',
                'default' => '(%x%, %y%)',
            ],
        ];
    }

    public function mapFieldValueForm(FormInterface $fieldForm, FieldData $data)
    {
        $definition = $data->fieldDefinition;
        $fieldForm->add('value', Point2DType::class, [
            'required' => $definition->isRequired,
            'label' => $definition->getName()
        ]);
    }
}
```

### format フィールドの追加
このパートでは、フィールドタイプの編集フォームを定義して実装します。

`src/Form/Type/Point2DSettingsType.php` で `Point2DSettingsType` クラスを定義し、`format` フィールドを追加します。

```php:src/Form/Type/Point2DSettingsType.php
<?php
declare(strict_types=1);

namespace App\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

final class Point2DSettingsType extends AbstractType

{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('format', TextType::class);
    }
}
```

### FieldDefinitionFormMapper インターフェイス
さて、ユーザーが検証される座標を追加できるようにします。 `src/FieldType/Point2D/Type.php` で、以下のようになります。

* `FieldDefinitionFormMapperInterface` インターフェイスを実装する。
* フィールドの設定を定義する `mapFieldDefinitionForm` メソッドを最後に追加する。

```php:src/FieldType/Point2D/Type.php
<?php
declare(strict_types=1);

namespace App\FieldType\Point2D;

use Ibexa\AdminUi\FieldType\FieldDefinitionFormMapperInterface;


// ...

final class Type extends GenericType implements FieldValueFormMapperInterface, FieldDefinitionFormMapperInterface


// ...

    public function mapFieldDefinitionForm(FormInterface $fieldDefinitionForm, FieldDefinitionData $data): void
    {
        $fieldDefinitionForm->add('fieldSettings', Point2DSettingsType::class, [
            'label' => false
        ]);
    }
```

#### 完成した Type.php のコード
```php:src/FieldType/Point2D/Type.php
<?php
declare(strict_types=1);

namespace App\FieldType\Point2D;

use App\Form\Type\Point2DSettingsType;
use App\Form\Type\Point2DType;
use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinition;
use Ibexa\Contracts\Core\FieldType\Generic\Type as GenericType;
use Ibexa\Contracts\Core\FieldType\Value;
use Ibexa\Contracts\ContentForms\Data\Content\FieldData;
use Ibexa\AdminUi\Form\Data\FieldDefinitionData;
use Symfony\Component\Form\FormInterface;
use Ibexa\Contracts\ContentForms\FieldType\FieldValueFormMapperInterface;
use Ibexa\AdminUi\FieldType\FieldDefinitionFormMapperInterface;

final class Type extends GenericType implements FieldValueFormMapperInterface, FieldDefinitionFormMapperInterface
{
    public function getFieldTypeIdentifier(): string
    {
        return 'point2d';
    }

    public function getSettingsSchema(): array
    {
        return [
            'format' => [
                'type' => 'string',
                'default' => '(%x%, %y%)',
            ],
        ];
    }

    public function mapFieldValueForm(FormInterface $fieldForm, FieldData $data)
    {
        $definition = $data->fieldDefinition;
        $fieldForm->add('value', Point2DType::class, [
            'required' => $definition->isRequired,
            'label' => $definition->getName()
        ]);
    }

    public function mapFieldDefinitionForm(FormInterface $fieldDefinitionForm, FieldDefinitionData $data): void
    {
        $fieldDefinitionForm->add('fieldSettings', Point2DSettingsType::class, [
            'label' => false
        ]);
    }
}
```

### 新しいタグの追加
次に、 `FieldDefinitionFormMapper` を `config/services.yaml` の `AppttpFieldType` の追加タグ定義として追加する。

```yaml:config/services.yaml
    App\FieldType\Point2D\Type:
        tags:
            - { name: ibexa.field_type, alias: point2d }
            - { name: ibexa.admin_ui.field_type.form.mapper.value, fieldType: point2d }
            - { name: ibexa.admin_ui.field_type.form.mapper.definition, fieldType: point2d }
```

### フィールドタイプの定義
新しい `format` フィールドを表示できるようにするために、そのためのテンプレートを追加する必要があります。`templates/point2d_field_type_definition.html.twig` を作成します。

```twig:templates/point2d_field_type_definition.html.twig
{% block point2d_field_definition_edit %}
    <div class="{% if group_class is not empty %}{{ group_class }}{% endif %}">
        {{- form_label(form.fieldSettings.format) -}}
        {{- form_errors(form.fieldSettings.format) -}}
        {{- form_widget(form.fieldSettings.format) -}}
    </div>
{% endblock %}
```

#### format フィールドの設定の追加
次に、 `config/packages/ezplatform.yaml` にテンプレートのマッピングを指定します。

```yaml:config/packages/ezplatform.yaml
ibexa:
    system:
        default:
            field_templates:
                - { template: 'point2d_field.html.twig', priority: 0 }
            fielddefinition_edit_templates:
                - { template: 'point2d_field_type_definition.html.twig', priority: 0 }
```

### テンプレートの再定義
最後に、Point 2D テンプレートを再定義し、新しい書式フィールドに対応させます。

`templates/point2d_field.html.twig` で、内容を以下のように置き換えます。

```twig:templates/point2d_field.html.twig
{% block point2d_field %}
    {{ fieldSettings.format|replace({
        '%x%': field.value.x,
        '%y%': field.value.y
    }) }}
{% endblock %}
```

### コンテンツタイプの編集
これで、管理画面の Admin から、Point 2D コンテンツタイプを編集して、作業結果を確認することができます。

:::note
**ヒント**
結果が表示されない場合やエラーが発生した場合は、キャッシュをクリアしてアプリケーションを再読み込みしてください。
:::

下の画面のように、**Format** フィールドに新しいフォーマット（`%x%`, `%y%`）を追加します。

![](https://doc.ibexa.co/en/latest/tutorials/generic_field_type/img/field_definition_format_field.png)

## ステップ7 - 基本的なバリデーションの追加
両方の座標が提供されていることを確認する基本的な検証を行うには、 `src/FieldType/Point2D/Value.php` にアサーションを追加してください。

```php:src/FieldType/Point2D/Value.php
use Symfony\Component\Validator\Constraints as Assert;

final class Value implements ValueInterface
{
    /**
     * @var float|null
     *
     * @Assert\NotBlank()
     */
    private $x;

    /**
     * @var float|null
     *
     * @Assert\NotBlank()
     */
    private $y;

// ...
```

その結果、ユーザーが1つの値だけを指定してPoint 2Dを公開しようとすると、エラーメッセージが表示されます。

![](https://doc.ibexa.co/en/latest/tutorials/generic_field_type/img/point2d_validation.png)

## ステップ8 - フィールドタイプのバージョン間のデータ移行
データ移行を追加することで、現在のニーズに合わせてフィールドタイプの出力を簡単に変更することができる。このプロセスは、フィールドタイプがソートや検索の目的で比較される必要がある場合に重要である。シリアライゼーションでは、オブジェクトを正規化することで配列に変更し、エンコードすることで選択されたフォーマットに変更することができる。逆に、デシリアライゼーションでは、異なるフォーマットをデコードして配列に変更し、それをオブジェクトにデノーマライズする。

シリアライザーコンポーネントの詳細な情報は、[Symfony のドキュメント](http://symfony.com/doc/5.4/components/serializer.html)を参照してください。

### 正規化
まず、 `src/Serializer/Point2D/ValueNormalizer.php` に正規化のサポートを追加する必要があります。

```php:src/Serializer/Point2D/ValueNormalizer.php
<?php
declare(strict_types=1);

namespace App\Serializer\Point2D;

use App\FieldType\Point2D\Value;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

final class ValueNormalizer implements NormalizerInterface
{
    public function normalize($object, string $format = null, array $context = [])
    {
        return [
            $object->getX(),
            $object->getY()
        ];
    }

    public function supportsNormalization($data, string $format = null)
    {
        return $data instanceof Value;
    }
}
```

### ノーマライザーの定義の追加
次に、 `config/services.yaml` に `serializer.normalizer` タグを付けて `ValueNormalizer` のサービス定義を追加します。

```yaml:config/services.yaml
services:
    App\Serializer\Point2D\ValueNormalizer:
        tags:
            - { name: serializer.normalizer }
```

### 後方互換性
古いバージョンのフィールドタイプを受け入れるには、 `src/Serializer/Point2D/ValueDenormalizer.php` で非正規化のサポートを追加する必要があります。

```php:src/Serializer/Point2D/ValueDenormalizer.php
<?php
declare(strict_types=1);

namespace App\Serializer\Point2D;

use App\FieldType\Point2D\Value;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;

final class ValueDenormalizer implements DenormalizerInterface
{
    public function denormalize($data, string $class, string $format = null, array $context = [])
    {
        if (isset($data['x']) && isset($data['y'])) {
            // Support for old format
            $data = [$data['x'], $data['y']];
        }
        return new $class($data);
    }

    public function supportsDenormalization($data, string $type, string $format = null)
    {
        return $type === Value::class;
    }
}
```

### Denormalizer の定義の追加
次に、 `ValueDenormalizer` のサービス定義を `config/services.yaml` に `serializer.denormalizer` タグを付けて追加します。

```yaml:config/services.yaml
services:
    App\Serializer\Point2D\ValueDenormalizer:
        tags:
            - { name: serializer.denormalizer }
```

### その場での書式変更
その場でフォーマットを変更するには、 `src/FieldType/Point2D/Value.php` のコンストラクタを置き換える必要がある。

```php:src/FieldType/Point2D/Value.php
    public function __construct(array $coords = [])
    {
        if (!empty($coords)) {
            $this->x = $coords[0];
            $this->y = $coords[1];
        }
    }
```

Point 2D フィールドタイプの内部表現形式を簡単に変更できるようになりました。