読者です 読者をやめる 読者になる 読者になる

設定ファイルのconcatと設定ファイルの継承と

github.com

設定ファイルのconcatについてのメモ。例えばconcatと言っても以下の3つくらいの段階がある。

  • 単にファイル単位でconcatするだけ
  • JSONの構造を意識しながらトップレベルの要素同士をconcat
  • 自由な位置に所定の要素をconcat

例えば、以下のようなyamlがあるとする。

person.yaml

definitions:
  person:
    properties:
      name:
        type: string
      age:
        type: integer

skill.yaml

definitions:
  skill:
    properties:
      name:
        type: string
      description:
        type: string

単にファイル単位でconcatするだけ

単にファイル単位でconcatするだけの場合にはcatでつなげる。

$ cat person.yaml skill.yaml > bundle0.yaml

もちろんただ単に繋げられただけ。

definitions:
  person:
    properties:
      name:
        type: string
      age:
        type: integer
definitions:
  skill:
    properties:
      name:
        type: string
      description:
        type: string

JSONの構造を意識しながらトップレベルの要素同士をconcat

JSONの構造を意識しながらトップレベルの要素同士をconcatしたければ、dictknifeが使える。

$ dictknife concat person.yaml skill.yaml > bundle1.yaml

definitions部分にまとめられる。

definitions:
  person:
    properties:
      name:
        type: string
      age:
        type: integer
  skill:
    properties:
      name:
        type: string
      description:
        type: string

自由な位置に所定の要素を参照(not concat)

自由な位置に所定の要素を埋め込むことはdictknifeではできない。jsonknifeのderefを使えば出来なくはない。ただし、これの意味あいはjson referenceのderef。

main.yaml

definitions:
  myperson:
    $ref: "./person.yaml#/definitions/person"
  myskill:
    $ref: "./skill.yaml#/definitions/skill"
$ jsonknife deref --src main.yaml > bundle2.yaml

personではなくmypsersonの位置にperson.yamlのpersonの値を置ける。

definitions:
  myperson:
    properties:
      name:
        type: string
      age:
        type: integer
  myskill:
    properties:
      name:
        type: string
      description:
        type: string

jsonschemaやswaggerなどのschemaファイルだとこれだけでも良いのだけれど。設定ファイルだとまた違った性質の機能が欲しくなる。

自由な位置に所定の要素をconcat

自由な位置に所定の要素をconcatができれば継承は結構手軽に作れる。例えばこのstack overflowの質問。

PHPにはJSONを拡張した継承付きで読み込むための機能が存在するらしい(らしい)。これと似たようなことをしようとすると、任意の位置に値を挿入しつつrefを使ったように単なる参照だけでは済まない形になる。任意の位置にconcatされて欲しい。

zenmaiでこれをできるようにした(zenmaiはdictknifeに依存している)。

上の質問のconfigをyamlに変換してちょっとだけ形を変えてみたものが以下のようなもの(_extends のかわりに $inherit になっている)。

production:
  phpSettings:
    display_startup_errors: false
    display_errors: false
  includePaths:
    library: APPLICATION_PATH/../library
  bootstrap:
    path: APPLICATION_PATH/Bootstrap.php
    class: Bootstrap
  appnamespace: Application
  resources:
    frontController:
      controllerDirectory: APPLICATION_PATH/controllers
      moduleDirectory: APPLICATION_PATH/modules
      params:
        displayExceptions: false
    modules: []
    db:
      adapter: pdo_sqlite
      params:
        dbname: APPLICATION_PATH/../data/db/application.db
    layout:
      layoutPath: APPLICATION_PATH/layouts/scripts/
staging:
  $inherit: "#/production"
testing:
  $inherit: "#/staging"
  phpSettings:
    display_startup_errors: true
    display_errors: true
development:
  $inherit: "#/staging"
  resources:
    frontController:
      params:
        displayExceptions: true

例えば、develpmentから見ると staging -> production という継承が行われて欲しい。zenmaiで変換した結果をとりだす(ついでに --select オプションを追加した。変換結果の一部分だけが欲しいので)。

$ zenmai config.yaml --select '#/development' > dev.yaml

dev.yaml

phpSettings:
  display_startup_errors: false
  display_errors: false
includePaths:
  library: APPLICATION_PATH/../library
bootstrap:
  path: APPLICATION_PATH/Bootstrap.php
  class: Bootstrap
appnamespace: Application
resources:
  frontController:
    controllerDirectory: APPLICATION_PATH/controllers
    moduleDirectory: APPLICATION_PATH/modules
    params:
      displayExceptions: true
  modules: []
  db:
    adapter: pdo_sqlite
    params:
      dbname: APPLICATION_PATH/../data/db/application.db
  layout:
    layoutPath: APPLICATION_PATH/layouts/scripts/

そんなわけでちょこっと手を加えれば結構柔軟なconfig loaderが作れる気がする。