helicon/object-mapperを作った

helicon/object-mapperを作った

December 12, 2019,
tags: php


このエントリーをはてなブックマークに追加

この記事は「PHP Advent Calendar 2019」の12日目の記事です。

今年の10月にちょっと時間が空いてたので、helicon/object-mapperを作りました。

helicon/object-mapperとはなんなのか?

連想配列からオブジェクトへ変換するためのライブラリです。
単純に、連想配列からオブジェクトに変換したいのであればzend-hydratorを使えば実現できます。

しかし、クラスのプロパティが期待する型(@varで書いている型情報)に変換することは出来ません。
そこを補うために作ったものがhelicon/object-mapperになります。

使い方

README.mdを見てもらえれば詳しい使い方が載っています。

kojirockさんが記事を書いてくれていますので、そちらを確認いただければと思います。
object-mapperで配列からオブジェクトへの簡単マッピング

実装について

public function __invoke(array $data, string $className);

こんな感じのAPIデザインを考えていました。
シンプルに、マッピングさせたい配列データとクラスを渡したら、そのクラスにマッピングしてデータを返してくれるというシンプルな発想です。

object-mapperの実装はすごく薄くてObjectMapper.phpしか実装がありません。

やってることは以下の通り

  1. 型情報の取得
  2. 配列の型変換
  3. Zend\Hydrator\ReflectionHydratorで変換

型情報の取得

helicon/object-type-parserという別パッケージで用意しています。 パースするだけのライブラリは再利用出来そうだったのでパッケージを別にしました。

zend-codeを使ってDocCommentの @var アノテーションの情報を読んで型を決定します。
DocCommentパースしているので、実際使う場合にパフォーマンスの問題が発生するかと思います。
CacheParserも用意しています。

これをすれば少しはマシになるかと。

php7.4の対応について

現状は対応できていません。 ReflectionPropertygetType,hasTypeのメソッドが追加されているので、それらを良しなに使って実装するのが良さそうだなぁーと考えています。

配列の型変換

helicon/type-converterという別パッケージを用意しています。

API的には以下のようになっています。

    public function __invoke(array $row, array $schemas): array;

配列で、変換したいデータ,型情報をの2つを渡すと型変換をしたデータを返してくれます。 どのような型をサポートしているかはsrc/TypeCasterのディレクトリを確認すればわかります。

現在サポートしている型は

  • ScalarType
  • Class
  • DateTime,DateTimeImmutable
  • Carbon

になります。 なんか入れてほしいものがあればPRください。

独自で追加することもできる

独自で変換クラスを追加することもできます。

TypeCasterInterfaceをimplementsして、実装してからResolverに登録するだけで大丈夫です。

Zend\Hydrator\ReflectionHydratorで変換

ReflectionHydratorのhydrateをしているだけなので、特に説明はいらないかと。

json-schemaに合わせた変換もできる

helicon/json-schema-mapperも作っていて、json-schemaが期待する型に変換させることも出来ます。

{
  "$id": "https://example.com/person.schema.json",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Person",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "user name."
    },
    "age": {
      "type": "integer",
      "description": "Age in years which must be equal to or greater than zero."
    },
    "weight": {
      "type": "number"
    }
  }
}
<?php

use Helicon\JsonSchemaMapper\JsonSchemaMapper;
use Helicon\JsonSchemaMapper\NumberTypeCaster;
use Helicon\TypeConverter\Converter;
use Helicon\TypeConverter\Resolver;
use Helicon\TypeConverter\TypeCaster\ScalarTypeCaster;
use PHPUnit\Framework\TestCase;

$resolver = new Resolver();
$resolver->addConverter(new NumberTypeCaster());
$resolver->addConverter(new ScalarTypeCaster());
$converter = new Converter($resolver);
$mapper = new JsonSchemaMapper($converter);
$data = [
    'name' => 'polidog',
    'age' => '33',
    'weight' => '88.4',
];
$actual = ($mapper)($data, __DIR__.'/schema.json');
$this->assertSame('polidog', $actual['name']);
$this->assertSame(33, $actual['age']);
$this->assertSame(88.4, $actual['weight']);

今後について

いくつか派生したライブラリを作りたいなと思っています。

  • クラスのプロパティを利用したバリデーション
  • クラスのプロパティからjson schemaの生成

json schemaの生成はptyhard/JsonSchemaBundleでも同じような事を実装していますが、annotationを定義しないで作りたいとかいろいろあるので、作っていきたいです。

最後に

PHPでライブラリを作って公開するのって楽しいです。
振り返ってみると、もう7年ほど前から様々なライブラリを作ってきました。

最近だとpolidog/payjp-decoratorというpayjpのPHPライブラリを拡張するライブラリなんかも作っています。

誰にも使ってもらえないライブラリばかりですが、それでも学びは多いし、再発明でもなんでもいいから自分で作って見ることは大切だと思います。

comments powered by Disqus