OOP Workshop PHP5蚀語でミックスむンを゚ミュレヌトする

ある晩、私の自転車フレヌムワヌクでORMの動䜜を実装するために、Rubyのmixinのように、たたはCの拡匵メ゜ッドずしおたたはPHPの将来のバヌゞョンではtrait / graftずしお動䜜するものが必芁でしたPHPで䞍玔物を実装する方法を楜しみに楜しみたした。 䞍玔物が䜕であるかわからない堎合は、問題ではありたせん。すべおを説明したす。



PHPでの䞍玔物の実装ず、それらを実装できる小さなラむブラリのプログラミングに぀いおの議論で私をフォロヌしおください。 この蚘事は、PHPの初心者および䞭玚の開発者を察象ずしおいたす䞻なこずは、OOPに粟通しおいるこずです。 その過皋で、クラスを䜿甚しおPHP 5.3を操䜜する際の耇雑さに぀いおも少し間違えたすが、しばらくしおから指摘し、修正するこずを提案したす。 たた、あなたの批刀に察する私の解決策を提䟛したす。 良い読曞をしおください。







䞍玔物ずは䜕ですか



混合物は、メ゜ッドずプロパティを他のクラスに単に公開するクラスです。 クラスに他のクラスを混圚させるこずは、倚重継承を゚ミュレヌトするための単なるオプションであり、PHPでは実装されおいないず想定できたす。 わかりやすくするために、構文がPHPに䌌た擬䌌コヌドを䜿甚した小さな䟋を瀺したす。

 <php
 mixin Timeable {
     private $ timeStarted;
    プラむベヌト$ timeStopped;
     public function start{$ timeStarted = time;  }
     public function stop{$ timeStopped = time;  }
     public function getElapsed{return $ timeStopped-$ timeStarted}
 }

 mixin Dumpable {
     public function dump{var_dump$ this;  }
 }

 Timeable、Dumpableを混合したMyClassクラス{
    パブリック関数hello{}
 }

 $ a = new MyClass;
 $ a-> start;
睡眠250;
 $ a-> stop;
 echo $ a-> getElapsed;
 $ a-> dump;
 >




アむデアは明確ですか 䞍玔物は、クラスがそれらすべおからすぐに継承されたかのように、単に機胜をクラスに远加したす。 ただし、それらは混合されおいるクラスのメンバヌを操䜜できたす。 これは、PHPで実装する機胜の䞀皮です。

自分でタスクを蚭定したしょう。





レゞストリを蚭蚈しおいたす。



考え盎しおみたしょう。 システムの異なるクラスに異なる䞍玔物を远加したい堎合がありたす。 ぀たり、どこに、どのクラスの䞍玔物が混入しおいるかに関する情報を保存する必芁がありたす。 プロゞェクトのこのような情報はグロヌバルであり、どこからでもアクセスできる必芁がありたす。 したがっお、そのようなストレヌゞを実装するために、静的クラスを遞択したしたPHPにはCのように静的クラスはありたせん。静的クラスずは、むンスタンス化する必芁のないクラスを意味したす。すべおの機胜は、クラス。 小さな䜜業ずしお、シングルトンを䜿甚する必芁がないように、レゞストリを再蚭蚈するこずをお勧めしたす興味がある堎合は、蚘事を最埌たで読んだ埌。



䞊蚘から、レゞストリはアグリゲヌタヌクラスの䞍玔物を登録できるはずです。 そしおもう少し高く、特定のクラスに䞍玔を登録する堎合、この䞍玔の機胜はすべおの子孫クラスに混合する必芁があるず蚀いたした。 登録時にすぐに祖先クラスのリストを取埗するこずはできたせんクラスのロヌドを避ける必芁があり、クラス階局の怜査にはこれが必芁です。 このこずから、埌で必芁なずきに察応関係のリストクラス=>䞍玔物のリストを䜜成したす。 さらに、アグリゲヌタヌクラスの新しいむンスタンスを䜜成するずきにリストが再構築されないように、このようなリストをキャッシュする必芁がありたす。



class Registry { private static $registeredMixins = array(); public static function register($className, $mixinClassName) { $mixinClassNames = func_get_args(); unset($mixinClassNames[0]); foreach ($mixinClassNames as $mixinClassName) { self::$registeredMixins[$className][] = $mixinClassName; } self::$classNameToMixinCache = array(); } }
      
      





登録機胜は非垞にシンプルであるこずが刀明したした。 アグリゲヌタヌクラスの名前ず䞍玔物のリストを圌女に䞎えたす。 䟿宜䞊、䞍玔物のリストはコンマで瀺すこずができたす。 Func_get_argsがこれを凊理したす必芁に応じお、䞍玔物のリストを配列ずしお指定するための適切なサポヌトを远加したす。 次に、このクラスの䞍玔物のリストに各䞍玔物を远加したす。 たた、関数の最埌の最埌の呌び出しはキャッシュをクリアしたす。これは、特定のクラスの䞍玔性を登録するず、そのすべおの子孫にもキャッシュが远加され、キャッシュの再構築が必芁になるためです。



キャッシング関数を曞きたしょう。 クラスずそれらに登録された䞍玔物のリストを調べ、同じ䞍玔物のリストで指定されたすべおの子孫クラスを远加する必芁がありたす。 結果はキャッシュです。

キャッシング関数には、このクラスの祖先のリストを受け取る関数が必芁です。

  private static $classNameToMixinCache = array(); private static function getAncestors($className) { $classes = array($className); while (($className = get_parent_class($className)) !== false) { $classes[] = $className; } return $classes; } private static function precacheMixinListForClass($className) { if (isset(self::$classNameToMixinCache[$className])) { return; } $ancestors = self::getAncestors($className); $result = array(); foreach ($ancestors as $ancestor) { if (isset(self::$registeredMixins[$ancestor])) { $result = array_merge($result, self::$registeredMixins[$ancestor]); } } self::$classNameToMixinCache[$className] = array_unique($result); }
      
      







この関数は、指定されたクラスの䞍玔物のリストのみをキャッシュに远加するこずに泚意しおください。 キャッシュの内容のほずんどが必芁になるこずはないため、キャッシュ党䜓をすぐには䜜成したせん。 キャッシュを構築する前に、すでにこれを行っおいるかどうかを確認したした。



ここで、特定のクラスの䞍玔物のリストを取埗する必芁がある堎合、この関数を䜿甚できたす。

  public static function getMixinsFor($className) { self::precacheMixinListForClass($className); return self::$classNameToMixinCache[$className]; }
      
      





次のステップに進みたす。 私たちがアグリゲヌタヌクラスメ゜ッドを呌び出し、そのアグリゲヌタヌクラスメ゜ッドではなく、いく぀かの䞍玔物で定矩されおいるず想像しおください。 䜕をする必芁がありたすか このクラスの䞍玔物のリストを取埗し、それらを調べお、必芁なメ゜ッドがそれらのいずれかで定矩されおいるかどうかを確認する必芁がありたす。

混合はクラスの本質であるため、次のようにしたす。



  private static $methodLookupCache = array(); public static function getMixinNameByMethodName($className, $methodName) { if (isset(self::$methodLookupCache[$className][$methodName])) { return self::$methodLookupCache[$className][$methodName]; } self::precacheMixinListForClass($className); foreach (self::$classNameToMixinCache[$className] as $mixin) { if (method_exists($mixin, $methodName)) { self::$methodLookupCache[$className][$methodName] = $mixin; return $mixin; } } throw new MemberNotFoundException("$className has no mixed method $methodName()!"); }
      
      







぀たり、キャッシュにこのクラスずメ゜ッド名の゚ントリが既にある堎合、単玔にそれらを返したす。 そうでない堎合は、キャッシュからこのクラスの䞍玔物のリストを取埗し、それらをバむパスしお、実装が必芁なメ゜ッドがあるかどうかを確認したす。 その堎合、キャッシュに远加し、䞍玔物の名前を返したす。 䜕も芋぀からない堎合、䟋倖をスロヌしたす。

プロパティに察しおたったく同じオプションが取埗されたす。 自分で曞くこずをお勧めしたす。

以䞊です。 レゞストリを実装したした。 䞍玔物クラスのプログラミングに進みたす。



混合物をプログラムしたす。



だから、混合物。 どんな䞍玔物 混合物は普通のクラスです。 圌は別のクラスのフィヌルドを操䜜する方法を知っおいたす。 そしお、この他のクラスのむンスタンスは、コンストラクタヌで枡すこずが論理的です。

 class Base { protected $_owningClassInstance; protected $_owningClassName; public function __construct($owningClassInstance) { $this->_owningClassInstance = $owningClassInstance; $this->_owningClassName = get_class($owningClassInstance); } }
      
      







私のプロゞェクトでは、Mixins名前空間に属しおいるため、より具䜓的に名前を付ける必芁はないため、ベヌスの䞍玔クラスをBaseず名付けたした。 ただし、奜きな名前を付けるこずができたす。



倉数owningClassInstanceを介しお、パブリックフィヌルドずメ゜ッドを盎接操䜜できたす。 しかし、隠され保護された状態では、リフレクションを介しお䜜業する必芁がありたす。 耇雑なこずは䜕もありたせん。 関数のすべおの定矩を瀺したす。

  protected $_owningPropertyReflectionCache; protected $_owningMethodReflectionCache; protected function getProtected($name) { if (! isset($this->_owningPropertyReflectionCache[$name])) { $property = new \ReflectionProperty($this->_owningClassName, $name); $property->setAccessible(true); $this->_owningPropertyReflectionCache[$name] = $property; } return $this->_owningPropertyReflectionCache[$name]->getValue($this->_owningClassInstance); } protected function setProtected($name, $value) { if (! isset($this->_owningPropertyReflectionCache[$name])) { $property = new \ReflectionProperty($this->_owningClassName, $name); $property->setAccessible(true); $this->_owningPropertyReflectionCache[$name] = $property; } $this->_owningPropertyReflectionCache[$name]->setValue($this->_owningClassInstance, $value); } protected function invokeProtected($name, $parameters) { $method = new \ReflectionMethod($this->_owningClassName, $name); $method->setAccessible(true); $parameters = func_get_args(); unset($parameters[0]); $method->invokeArgs($this->_owningClassInstance, $parameters); }
      
      







ここでは、リフレクションのためにシステムクラスのむンスタンスを䜜成しお垞に構成しないように、キャッシュを再び有効にしおいるこずに泚意しおください。 メモリ消費を削枛するために、必芁に応じおキャッシュを砎棄できたす。

レゞストリクラスで䜿甚したmethod_existsおよびproperty_exists関数は、パブリック名ずずもに指定された名前の隠され保護された関数を持っおいる䞍玔物をチェックするこずに既に気づいおいるかもしれたせん。 これにより、アグリゲヌタヌクラスは、非衚瀺たたは保護ずしお定矩されおいる堎合、同じ名前の関数を「詊行」できるようになりたす。 その結果、ただ゚ラヌが発生したすが、明瀺的にそれを行うこずを奜みたす。



  public function __call($name, array $arguments) { throw new MemberNotFoundException( "Method $name is not defined or is not accessible in mixin \"" . get_class() . "\""); } public function __get($name) { throw new MemberNotFoundException( "Property $name is not defined or is not accessible in mixin \"" . get_class() . "\""); } public function __set($name, $value) { throw new MemberNotFoundException( "Property $name is not defined or is not accessible in mixin \"" . get_class() . "\""); }
      
      







小さなタスクずしお、レゞストリクラスのこのような䞍適切な動䜜を修正しおみおください。 さらに、以前は別の名前で非衚瀺たたは保護されおいた名前でパブリックな䞍玔物メ゜ッドを呌び出すこずができなくなりたす。



あの 以䞊です。 混合物はすぐに食べられたす。 最埌のステップ-䞍玔物を混合するためのプラットフォヌムの実装-アグリゲヌタヌクラス。 これが私たちが今やるこずです。



アグリゲヌタヌクラスを䜜成したす。



アグリゲヌタヌクラスは䜕ができたすか 圌は、䞍玔物のクラスのむンスタンスを保存し、それらのメ゜ッドを呌び出す方法を知っおいたす。 さお、プロパティを参照しおください。 この動䜜は、PHPの「マゞック」メ゜ッドを䜿甚しお実装したす。



 class Aggregator { protected $_mixins; protected $_className; public function __construct($aggregatorClassInstance = false) { $this->_className = $aggregatorClassInstance ? get_class($aggregatorClassInstance) : get_class($this); $mixinNames = Registry::getMixinsFor($this->_className); foreach ($mixinNames as $mixinName) { $this->_mixins[$mixinName] = new $mixinName($aggregatorClassInstance ? $aggregatorClassInstance : $this); } } }
      
      







コンストラクタヌコヌドでは、クラスの䞍玔物のリストを取埗し、それらをルヌプで凊理しおむンスタンスを䜜成したす。

倉数$ aggregatorClassInstanceは、Aggregatorクラスからクラスを継承する必芁がないこずを保蚌するのに圹立ちたす。 Aggregatorクラスを別のクラスに含め、この他のクラスのむンスタンスに等しい$ aggregatorClassInstanceパラメヌタヌを䜿甚しおコンストラクタヌを呌び出すこずができたす。 したがっお、そうするこずで、この所有者クラスの䞍玔物のリストを取埗し、アグリゲヌタヌクラスの察応するむンスタンスを䞍玔物に枡したす。



䞊蚘の説明が耇雑すぎるず思われる堎合は、問題ではありたせん。 少し䜎く滑っお、䟋がありたす。 継承の䟋がコンポゞションの䟋ずどのように異なり、どのように機胜するかをご芧ください。



「魔法の方法」を実珟したす。



  public function __call($name, array $arguments) { return call_user_func_array(array($this->_mixins[Registry::getMixinNameByMethodName($this->_className, $name)], $name), $arguments); } public function __get($name) { return $this->_mixins[Registry::getMixinNameByPropertyName($this->_className, $name)]->$name; } public function __set($name, $value) { $this->_mixins[Registry::getMixinNameByPropertyName($this->_className, $name)]->$name = $value; } public function __isset($name) { return isset($this->_mixins[Registry::getMixinNameByPropertyName($this->_className, $name)]->$name); }
      
      







各マゞックメ゜ッドは、情報を取埗するためにレゞストリにアクセスしたす。 すべおがシンプルです。



䜿甚した䟋倖クラスは次のようになりたす。



 class MemberNotFoundException extends \Exception {}
      
      







いく぀かの䟋を芋おみたしょう。





最初に、継承を䜿甚した埓来のスキヌムの堎合



 class MixinAggregatorSample extends Mixins\Aggregator { } class MixinHello extends Mixins\Base { protected $inaccessible; public $text = "I am a text!\r\n"; public function hello() { echo ("Hello from mixin!\r\n"); } } Mixins\Registry::register("MixinAggregatorSample", "MixinHello"); $a = new MixinAggregatorSample(); $a->hello(); //Accesing mixed methid echo ($a->text); //Accessing mixed property $a->text = "I am also a text!\r\n"; //Setting mixed property //$a->inaccessible = 'Error here'; //Throws exception //$a->inaccessible2 = 'Error here'; //Throws yet another exception (Homework: explain, why) echo ($a->text); var_dump(isset($a->text));
      
      







包含回路を芋おみたしょう。



 class MixinAggregatorSample { protected $_aggregator; public function __construct() { $this->_aggregator = new Mixins\Aggregator($this); } public function __call($name, $arguments) { return $this->_aggregator->__call($name, $arguments); } } class MixinHello extends Mixins\Base { public function hello() { echo ("Hellp from mixin!"); } } Mixins\Registry::register("MixinAggregatorSample", "MixinHello"); $a = new MixinAggregatorSample(); $a->hello();
      
      







違いがわかりたすか 含める堎合、機胜を倱うこずなく、アグリゲヌタヌクラスを他のクラスから自由に継承できたす。 もちろん、通垞の䜿甚では、__ callだけでなく、すべおのマゞックメ゜ッドを実装する必芁がありたす。



性胜





結果のラむブラリの速床を枬定したした。 枬定は非垞に近䌌しおおり、オヌプンIDE、Winamp、および想定されるすべおの機胜を備えたホヌムコンピュヌタヌで実行されたす。



ネむティブ時間0.57831501960754
時間による名前1.5227220058441
混合時間7.5425450801849
時間の反映12.221807956696








このようなアプロヌチを倧芏暡なプロゞェクトで䜿甚できるように、䞊蚘の数倀は十分に受け入れられるず思いたす。 原則ずしお、䞍玔なメ゜ッドはスクリプトで䜕千回も呌び出されず、メ゜ッドを呌び出すのに0.7マむクロ秒に察しお10マむクロ秒はネむティブメ゜ッドには非垞に受け入れられるオプションです。 特に、倧量のテキストやデヌタベヌスぞのク゚リの実行など、htmlspecialcharsに費やされる時間が非垞に長いず考えるず特にそうです。



ほずんどすべおの堎所で、ハッシュされたPHP配列に基づいたキャッシュを䜿甚しおいるため、䞍玔物やアグリゲヌタヌクラスの数が増えおも、パフォヌマンスはそれほど䜎䞋したせん。 しかし、誰かが必芁なテストを行う堎合、私は非垞に幞せになりたす。



゚ピロヌグ



この蚘事に関するあなたの批刀を聞いおうれしいです。 私はこの資料をすべおの読者が理解できるようにしたかどうかに特に興味がありたす。



もちろん、この蚘事は完党、正確、たたぱラヌのないこずを目的ずするものではありたせん。 あなたが私を修正しおくれたら本圓に感謝しおいたす。 ここで結果のラむブラリのコヌドを広げたした。 もちろん、プロゞェクトはトレヌニングプロゞェクトであり、実際のプロゞェクトで䜿甚する前に、慎重に考えおすべおをテストする䟡倀がありたす。 同じクラス内の同じ名前のパブリックメ゜ッドを持぀いく぀かの䞍玔物など、いく぀かの問題のある問題が存圚したす。



PHPの䞍玔物のトピックに興味がある堎合は、グヌグルもお勧めしたす。



All Articles