LDAP検索式の構築(式ツリーを拡張)
前のロジックは「全ての関係式は2項式」って前提で書いたけど、一般的な数式のBNFみたいに「ANDが連続するならterm,ORが連続するならexpression」って思えば各ノードに2個以上の条件式を追加することができる。
括弧を単体の構造で表すのは面倒(beginParenthesisさせたらendParenthesisを忘れたりする)だからComplex構造を使う。言わばCompositeのノード。
前のロジックではComplexFilterとComplexFilterNodeに分けてたけど、今回はComplexFilterのみ。ノード用クラスは使わない。そのかわりComplexのルートノードになるオブジェクトは 必ず(OR式 -* AND式)になり、ANDのみからなる条件式はツリーのネストが1段階多い。じゃないとa OR B and C みたいな式でCを追加できない。
それではコードを。
class Expression{ public function compose(Composer $composer){} } class ComplexFilter extends Expression{ var $modifier; var $operator; var $filters; public function __construct($operator = "OR"){ $this->modifier = ""; $this->operator = $operator; $this->filters = array(); } public function add($filter,$operator = "OR"){ $childCount = count($this->filters); $last = $this->lastChild(); if($last == null){ $this->filters[] = $filter; } else{ if($this->operator == $operator){ $this->filters[] = $filter; } else{ $this->addToLast($filter,$operator); } } return $this; } public function addAnd($filter){ return $this->add($filter,"AND"); } public function addOr($filter){ return $this->add($filter,"OR"); } public function setModifier($mod){ $this->modifier = $mod; } public function compose(Composer $composer){ $composer->composeComplex($this); } function lastChild(){ $childCount = count($this->filters); if($childCount == 0){ return null; } return $this->filters[$childCount -1]; } function addToLast($filter,$operator){ $childCount = count($this->filters); $last = $this->lastChild(); if($last instanceof ComplexFilter){ if($last->operator == $operator){ $last->add($filter,$operator); } else{ $this->filters[$childCount -1] = $this->createComplex($last,$filter,$operator); } } else{ $this->filters[$childCount -1] = $this->createComplex($last,$filter,$operator); } } function createComplex($filter1,$filter2,$operator){ $complex = new ComplexFilter($operator); $complex->add($filter1,$operator); $complex->add($filter2,$operator); return $complex; } } class TextFilter extends Expression{ var $text; public function __construct($text){ $this->text = $text; } public function compose(Composer $composer){ $composer->composeText($this); } } class ExpressionFactory{ public function text($text){ return new TextFilter($text); } public function complex(){ return new ComplexFilter(); } } class Composer{ var $text; public function composeText(TextFilter $filter){ $this->text = $filter->text; } public function composeComplex(ComplexFilter $filter){ $operator = $filter->operator; $this->text = ""; $text = ""; foreach($filter->filters as $node){ if($text != ""){ $text .= " " . $filter->operator . " "; } $node->compose($this); $text .= $this->text; } if(count($filter->filters)>1){ $this->text = "(".$text.")"; } else{ $this->text = $text; } } } class LdapComposer extends Composer{ var $text; public function composeText(TextFilter $filter){ $this->text = "(". $filter->text .")"; } public function composeComplex(ComplexFilter $filter){ $operator = $filter->operator == "AND" ? "&" : "|"; $this->text = ""; $text = ""; foreach($filter->filters as $node){ $node->compose($this); $text .= $this->text; } if(count($filter->filters)>1){ $this->text = "(".$operator .$text.")"; } else{ $this->text = $text; } } } $composer = new Composer(); $expr = new ExpressionFactory(); //1 . A=1 $filter = $expr->text("A=1"); $filter->compose($composer); echo "A=1 : "; echo $composer->text. "<br />"; //2 . A=1 $filter = $expr->complex() ->addAnd($expr->text("A=1")) ; $filter->compose($composer); echo "A=1 : "; echo $composer->text. "<br />"; //3 . A=1 and B=1 $filter = $expr->complex() ->addAnd($expr->text("A=1")) ->addAnd($expr->text("B=1")) ; $filter->compose($composer); echo "A=1 and B=1 "; echo $composer->text. "<br />"; //4 . A=1 and B=1 and C=2 $filter = $expr->complex() ->add($expr->text("A=1")) ->addAnd($expr->text("B=1")) ->addAnd($expr->text("C=2")) ; $filter->compose($composer); echo "A=1 and B=1 and C=2 :"; echo $composer->text. "<br />"; //5 . A=1 and B=1 or C=2 $filter = $expr->complex() ->addAnd($expr->text("A=1")) ->addAnd($expr->text("B=1")) ->addOr($expr->text("C=2")) ; $filter->compose($composer); echo $composer->text. "<br />"; //6 . A=1 or B=1 and C=2 $filter = $expr->complex() ->addAnd($expr->text("A=1")) ->addOr($expr->text("B=1")) ->addAnd($expr->text("C=2")) ; $filter->compose($composer); echo $composer->text. "<br />"; //7 . A=1 and B=1 or C=2 and D=3 $filter = $expr->complex() ->addAnd($expr->text("A=1")) ->addAnd($expr->text("B=1")) ->addOr($expr->text("C=2")) ->addAnd($expr->text("D=3")) ; $filter->compose($composer); echo $composer->text. "<br />"; //8 . (A=1 or B=1) $filter = $expr->complex() ->add( $expr->complex() ->add($expr->text("A=1")) ->addOr($expr->text("B=1")) ) ; $filter->compose($composer); echo $composer->text. "<br />"; echo "<br />"; //9 . (A=1 or B=1) and (C=2 or D=3) $filter = $expr->complex() ->add( $expr->complex() ->add($expr->text("A=1")) ->addOr($expr->text("B=1")) ->addOr($expr->text("B=2")) ) ->addAnd( $expr->complex() ->add($expr->text("C=2")) ->addOr($expr->text("D=3")) ) ; $filter->compose($composer); echo $composer->text. "<br />"; echo "<br />"; //10 . (A=1 or B=1) or (C=2 or D=3) $filter = $expr->complex() ->add( $expr->complex() ->add($expr->text("A=1")) ->addOr($expr->text("B=1")) ) ->addOr( $expr->complex() ->add($expr->text("C=2")) ->addOr($expr->text("D=3")) ) ; $filter->compose($composer); echo $composer->text. "<br />"; echo "<br />"; //10 . (A=1 or B=1) and (C=2 or D=3 and ( E=1 or F=2) ) $filter = $expr->complex() ->add( $expr->complex() ->add($expr->text("A=1")) ->addOr($expr->text("B=1")) ) ->addOr( $expr->complex() ->addAnd($expr->complex()->add($expr->text("E=1"))->addOr($expr->text("F=2"))) ->addAnd($expr->text("C=2")) ->addOr($expr->text("D=3")) ) ; $filter->compose($composer); echo $composer->text. "<br />"; echo "<br />";
Composerクラスは本当はabstractになるべきで、DatabaseExpressionComposerとLdapExpressionComposerなどに分かれるべき。なぜかといえば「せっかく継承するのに親クラスのコードを一切使わないなら継承はしてはならない」から。シグネーチャをあわせるならインターフェースか抽象クラスを使うべき。
ところでComposerクラスの存在は微妙(デザインパターン的な意味で)なんだが、これはVisitorと思っていいんだろうか?