eZ Publish 5.x フィールドタイプ チュートリアル

https://doc.ez.no/display/EZP/eZ+Publish+5+Field+Type+Tutorial
## バンドルのスケルトン生成
```
php ezpublish/console generate:bundle \
--namespace=EzSystems/TweetFieldTypeBundle \
--bundle-name=EzSystemsTweetFieldTypeBundle \
--dir=src \
--format=yml
```
```
Do you want to generate the whole directory structure [no]? no<enter>
```

## バンドルの構造
```
eZ
└── Publish
   └── FieldType
       └── Tweet
           ├── Type.php
           └── Value.php
```
### Type クラス
Type にはフィールドタイプのロジックが含まれる：データのバリデーション、さまざまなフォーマットからの変換、バリデーターの説明...
Type クラスは `eZ\Publish\SPI\FieldType\FieldType` を実装する必要がある。また、`eZ\Publish\Core\FieldType\FieldType` 抽象クラスを拡張することもできる。

### Value クラス
Value はコンテンツアイテム内のタイプのインスタンスを表すために使用される。各フィールドは Type の Value クラスのインスタンスを使用してデータを出力する。
Value クラスは `eZ\Publish\SPI\FieldType\Value` を実装する必要がある。また、`eZ\Publish\Core\FieldType\Value` 抽象クラスを拡張することもできる。


## Tweet\Value クラスの実装

FieldType の Value クラスは設計上は非常にシンプルである。ステートレスで、できるだけ軽量であることを意図している。従って、Type クラスの責任として、このクラスはできるだけロジックを含まないようにしなくてはならない。

Value には少なくとも以下が含まれる：

* パブリックプロパティー（実際のデータを格納するために使用）
* `__toString()` メソッドの実装（継承する Value インタフェースで必要）

デフォルトでは、`FieldType\Value` のコンストラクタが使用され、プロパティ/値のペアのハッシュを渡すことができる。この例では、必要に応じて上書きすることができる。

Tweet FieldType は3つのものを保存する：

* ツイートの URL
* ツイートのオーサーの URL
* 本文（HTML 文字列として）

ここでは格納される場所は考慮しない。FieldType を API として公開することのみ。以下のプロパティで終わる：

```php:eZ/FieldType/Tweet/Value.php
/**
* Tweet URL on twitter.com (http://twitter.com/UserName/status/id).
* @var string
*/
public $url;
 
  
/**
* Author's tweet URL (http://twitter.com/UserName)
* @var string
*/
public $authorUrl;
 
  
/**
* The tweet's embed HTML
* @var string
*/
public $contents;
```

`FieldType\Value` インターフェイスに残るのは `__toString（）` メソッドのみ。ツイートの URL を返そう。

```php:eZ/FieldType/Tweet/Value.php
public function __toString()
{
   return $this->url;
}
```

## Tweet\Type クラスの実装

### 識別メソッド: `getFieldTypeIdentifier()`
この FieldType を一意に識別する文字列 （eZ Publish 4 の DataTypeString） を返す必要がある。

```php:eZ/FieldType/Tweet/Type
public function getFieldTypeIdentifier()
{
   return 'eztweet';
}
```

### Value 処理メソッド: `createValueFromInput()` と `checkValueStructure()`

両メソッドは `acceptValue()` の抽象 FieldType 実装で使われる。この FieldType インターフェースメソッドはさまざまな入力値をチェックし、type 自身の Value クラス (`eZ\FieldType\Tweet\Value`) に変換する。このメソッドは以下のいずれかの必要がある。

* 入力値から作成できた Value オブジェクトを返す
* この値を変更しないで返す。 API はこれを検出し、入力値が受け入れられなかったことを通知する。

この type に許容される値はツイートの URL のみ。

```php
protected function createValueFromInput( $inputValue )
{
   if ( is_string( $inputValue ) )
   {
       $inputValue = new Value( array( 'url' => $inputValue ) );
   }
 
   return $inputValue;
}
```

このメソッドを使用して、API を使用して属性の値を設定する便利な方法を提供する。単純なものから複雑なビジネスオブジェクトまで何でもよい。

次に `checkValueStructure（）` を実装する。これは Type に供給される Value が受け入れられることを保証するために抽象 FieldType によって呼び出される。ここでは `Tweet\Value::$url` が文字列であることを確認する。

```php
protected function checkValueStructure( BaseValue $value )
{
   if ( !is_string( $value->url ) )
   {
       throw new eZ\Publish\Core\Base\Exceptions\InvalidArgumentType(
           '$value->url',
           'string',
           $value->url
       );
   }
}
```
```php
public function getEmptyValue()
{
   return new Value;
}
```
```php
array(
   'StringLengthValidator' => array(
       'minStringLength' => 0,
       'maxStringLength' => 100
   )
);
```
```php
array(
   ‘TweetAuthorValidator’ => array(
       ‘AuthorList’ => array( ‘johndoe’, ‘janedoe’ )
   )
);
```
```php
public function validateValidatorConfiguration( $validatorConfiguration )
{
   $validationErrors = array();
 
   foreach ( $validatorConfiguration as $validatorIdentifier => $constraints )
   {
       // Report unknown validators
       if ( !$validatorIdentifier != 'TweetAuthorValidator' )
       {
           $validationErrors[] = new ValidationError( "Validator '$validatorIdentifier' is unknown" );
           continue;
       }
 
       // Validate arguments from TweetAuthorValidator
       if ( !isset( $constraints['AuthorList'] ) || !is_array( $constraints['AuthorList'] ) )
       {
           $validationErrors[] = new ValidationError( "Missing or invalid AuthorList argument" );
           continue;
       }
 
       foreach ( $constraints['AuthorList'] as $authorName )
       {
           if ( !preg_match( '/^[a-z0-9_]{1,15}$/i', $authorName ) )
           {
               $validationErrors[] = new ValidationError( "Invalid twitter username" );
           }
       }
   }
 
 
   return $validationErrors;
}
```
```php
public function validate( FieldDefinition $fieldDefinition, SPIValue $fieldValue )
{
    $errors = array();
 
    if ( $this->isEmptyValue( $fieldValue ) )
    {
        return $errors;
    }
 
    // Tweet Url validation
    if ( !preg_match( '#^https?://twitter.com/([^/]+)/status/[0-9]+$#', $fieldValue->url, $m ) )
        $errors[] = new ValidationError( "Invalid twitter status url %url%", null, array( $fieldValue->url ) );
 
    $validatorConfiguration = $fieldDefinition->getValidatorConfiguration();
    if ( isset( $validatorConfiguration['TweetAuthorValidator'] ) )
    {
        if ( !in_array( $m[1], $validatorConfiguration['TweetAuthorValidator']['AuthorList'] ) )
        {
            $errors[] = new ValidationError(
                "Twitter user %user% is not in the approved author list",
                null,
                array( $m[1] )
            );
        }
    }
 
    return $errors;
}
```
```php
public function getName( SPIValue $value )
{
   return preg_replace(
       '#^https?://twitter\.com/([^/]+)/status/([0-9]+)$#',
       '$1-$2',
       (string)$value->url );
}
 
 
protected function getSortInfo( CoreValue $value )
{
   return $this->getName( $value );
}
```
```php
public function fromHash( $hash )
{
   if ( $hash === null )
   {
       return $this->getEmptyValue();
   }
   return new Value( $hash );
}
 
public function toHash( SPIValue $value )
{
   if ( $this->isEmptyValue( $value ) )
   {
       return null;
   }
   return array(
       'url' => $value->url
   );
}
```
```php:Tweet\Type
/**
 * @param \EzSystems\TweetFieldTypeBundle\eZ\Publish\FieldType\Tweet\Value $value
 * @return \eZ\Publish\SPI\Persistence\Content\FieldValue
 */
public function toPersistenceValue( SPIValue $value )
{
    if ( $value === null )
    {
        return new PersistenceValue(
            array(
                "data" => null,
                "externalData" => null,
                "sortKey" => null,
            )
        );
    }
    return new PersistenceValue(
        array(
            "data" => $this->toHash( $value ),
            "sortKey" => $this->getSortInfo( $value ),
        )
    );
}
/**
 * @param \eZ\Publish\SPI\Persistence\Content\FieldValue $fieldValue
 * @return \EzSystems\TweetFieldTypeBundle\eZ\Publish\FieldType\Tweet\Value
 */
public function fromPersistenceValue( PersistenceValue $fieldValue )
{
    if ( $fieldValue->data === null )
    {
        return $this->getEmptyValue();
    }
    return new Value( $fieldValue->data );
}
```
```php:eZ/Publish/FieldType/Tweet/Type.php:
use EzSystems\TweetFieldTypeBundle\Twitter\TwitterClientInterface;
 
class Type extends FieldType
{
    /** @var TwitterClientInterface */
    protected $twitterClient;
 
    public function __construct( TwitterClientInterface $twitterClient )
    {
        $this->twitterClient = $twitterClient;
    }
}
```
```php:eZ/Publish/FieldType/Tweet/Type.php
 public function toPersistenceValue( SPIValue $value )
{
    // if ( $value === null )
    // {...}
 
 
    if ( $value->contents === null )
    {
        $value->contents = $this->twitterClient->getEmbed( $value->url );
    }
    return new PersistenceValue(
    // array(...)
}
```