データモデルを使用して作業を行わない、かなり大きなプロジェクトを想像することは困難です。 さらに言います:私の経験では、すべてのコードの約4分の3がレコードの作成、ロード、変更、保存、または削除です。 ユーザー登録、数十件の最近の記事の出力、管理パネルでの作業など、すべてこれは基本的なモデル操作での小さな作業です。 そして、したがって、そのようなコードは迅速に作成および読み取りを行う必要があり、プログラマの頭に技術的な詳細を詰まらせてはなりません。
序文の代わりに
私の意見では、コードを読みやすくするために、(もちろん、コーディング標準とアルゴリズムの理解度に加えて)自然言語にできるだけ近づける必要があります。 この製品をダウンロードし、名前を設定し、価格を設定し、保存します。 また、コードの繰り返しであっても、コードの一部であっても変数の名前であっても(1つのオブジェクトを操作する場合)、回避する必要があります。 私の場合、流体インターフェースは変数名の絶え間ない退屈なコピーから私を救った。
流体インターフェース
ハエをカツレツから分離し、別のクラスで「流体インターフェース」を取り出すことは、モデルだけでなく使用する必要がある場合に論理的です。
abstract class Core_Fluent extends ArrayObject {}
書き始める前に、使用する最終コードをどのように見たいかを決めました。 これが判明しました:
$instance->load($entity_id) ->setName('Foo') ->setDescription('Bar') ->setBasePrice(250) ->save();
同時に、データを「name」、「description」、「base_price」という形式のキーで保存することを望んでいました(これにより、データベースとのやり取りを実装するのがはるかに簡単になり、これはエンコード標準で必要でした)
各モデルに同じタイプのメソッドを記述しないために、「マジックメソッド」( マジックメソッド)、特に__call()メソッドを使用する必要があります。 __get()および__set()メソッドを使用することもできますが、私はArrayIteratorを適用しました 。
したがって、呼び出されたものと次に何をすべきかを正確に決定する__callメソッド:
... // CamelCase __ // StackOverflow, const PREG_CAMEL_CASE = '/(?<=[AZ])(?=[AZ][az])|(?<=[^AZ])(?=[AZ])|(?<=[A-Za-z])(?=[^A-Za-z])/'; // protected $_data = array(); public function __call($method_name, array $arguments = array()) { // , , // (setBasePrice set BasePrice) if(!preg_match('/^(get|set|isset|unset)([A-Za-z0-9]+)$/', $method_name, $data)) { // , throw new Core_Exception('Method '.get_called_class().'::'.$method_name.' was not found'); } // (BasePrice => base_price) $property $property = strtolower(preg_replace(self::PREG_CAMEL_CASE, '_$0', $data[2])); // , switch($data[1]) { case 'get': { // $object->getBasePrice(): return $this->get($property); } break; case 'set': { // $object->setBasePrice(): return $this->set($property, $arguments[0]); } break; case 'unset': { // $object->getBasePrice(): return $this->_unset($property); } break; case 'isset': { // $object->getBasePrice(): , return $this->_isset($property); } break; default: { } } // , , , " " return $this; } ...
取得 、 設定 、 _isset、および_unsetメソッド
これらのメソッドの実装は難しくありません。その効果は名前から明らかです。
... public function get($code) { if($this->_isset($code)) { return $this->_data[$code]; } // , NULL return NULL; } public function set($code, $value) { $this->_data[$code] = $value; return $this; } public function _unset($code) { unset($this->_data[$code]); return $this; } public function _isset($code) { return isset($this->_data[$code]); } ...
ArrayIterator
上記のアプローチに加えて、通常の連想配列(だけでなく、別の話でもあります)のように、オブジェクトを操作する機能を追加することにしました。これにはArrayIteratorがあります。 もちろん、前のセクションで説明したメソッドに名前を付けて複製する必要がないようにする方が正しいでしょうが、まず、これらのメソッドを直接使用するコードがあり、多くのものがあったため、ここではすでに後方互換性について考える必要がありました第二に、私の意見では、一つのことはArrayIteratorの実装であり、もう一つは流動的なインターフェースの実装です。
... public function offsetExists($offset) { return $this->_isset($offset); } public function offsetUnset($offset) { return $this->_unset($offset); } public function offsetGet($offset) { return $this->get($offset); } public function offsetSet($offset, $value) { return $this->set($offset, $value); } public function getIterator() { return new Core_Fluent_Iterator($this->_data); } ...
そして、それに応じて、 Core_Fluent_Iteratorクラス:
class Core_Fluent_Iterator extends ArrayIterator {}
それだけです 現在、Core_Fluentを継承するクラスでは、次の操作が可能です。
class Some_Class extends Core_Fluent {} $instance = new Some_Class(); $instance->set('name', 'Foo')->setDescription('Bar')->setBasePrice(32.95); echo $instance->getDescription(), PHP_EOL; // Bar echo $instance['base_price'], PHP_EOL; // 32.95 echo $instance->get('name'), PHP_EOL; // Foo // name => Foo // description => Bar // base_price => 32.95 foreach($instance as $key => $value) { echo $key, ' => ', $value, PHP_EOL; } var_dump($instance->issetBasePrice()); // true var_dump($instance->issetFinalPrice()); // false var_dump($instance->unsetBasePrice()->issetBasePrice()); // false
モデル
ここで、モデル自体、上記のメカニズムのアプリケーションの特別なケース。
abstract class Core_Model_Abstract extends Core_Fluent {}
最初に、CRUDの基盤を追加する必要があります(作成、ロード、変更、削除)。 ロジック(データベース、ファイル、その他の処理)は階層が低くなります。ここでは、最も基本的なことだけを行う必要があります。
... // , protected $_changed_properties = array(); // . save() // , // ( ) public function create() { return $this; } // public function load($id) { $this->_changed_properties = array(); return $this; } // public function loadFromArray(array $array = array()) { $this->_data = $array; return $this; } // public function save() { $this->_changed_properties = array(); return $this; } // public function remove() { return $this->unload(); } // public function unload() { $this->_changed_properties = array(); $this->_data = array(); return $this; } // public function toArray() { return $this->_data; } ...
最後に、変更されたプロパティの配列を追加してset()をオーバーライドします
... public function set($code, $value) { $this->_changed_properties[] = $code; return parent::set($code, $value); } ...
これで、このクラスからさまざまなアダプターをデータベース、ファイル、またはAPIに継承できます。これらのアダプターからは、最終的なデータモデルが既に継承されています。
ネタバレの下にある3つのファイルすべての完全なコード。
3つのファイルすべての完全なコード
コア/ Fluent.php
コア/ Fluent / Iterator.php
コア/モデル/ Abstract.php
<?php abstract class Core_Fluent extends ArrayObject { const PREG_CAMEL_CASE = '/(?<=[AZ])(?=[AZ][az])|(?<=[^AZ])(?=[AZ])|(?<=[A-Za-z])(?=[^A-Za-z])/'; protected $_data = array(); public function __call($method_name, array $arguments = array()) { if(!preg_match('/^(get|set|isset|unset)([A-Za-z0-9]+)$/', $method_name, $data)) { throw new Core_Exception('Method '.get_called_class().'::'.$method_name.' was not found'); } $property = strtolower(preg_replace(self::PREG_CAMEL_CASE, '_$0', $data[2])); switch($data[1]) { case 'get': { return $this->get($property); } break; case 'set': { return $this->set($property, $arguments[0]); } break; case 'unset': { return $this->_unset($property); } break; case 'isset': { return $this->_isset($property); } break; default: { } } return $this; } public function get($code) { if($this->_isset($code)) { return $this->_data[$code]; } return NULL; } public function set($code, $value) { $this->_data[$code] = $value; return $this; } public function _unset($code) { unset($this->_data[$code]); return $this; } public function _isset($code) { return isset($this->_data[$code]); } /** * Implementation of ArrayIterator */ public function offsetExists($offset) { return $this->_isset($offset); } public function offsetUnset($offset) { return $this->_unset($offset); } public function offsetGet($offset) { return $this->get($offset); } public function offsetSet($offset, $value) { return $this->set($offset, $value); } public function getIterator() { return new Core_Fluent_Iterator($this->_data); } } ?>
コア/ Fluent / Iterator.php
<?php class Core_Fluent_Iterator extends ArrayIterator {} ?>
コア/モデル/ Abstract.php
<?php abstract class Core_Model_Abstract extends Core_Fluent { protected $_changed_properties = array(); public function set($code, $value) { $this->_changed_properties[] = $code; return parent::set($code, $value); } public function create() { return $this; } public function load($id) { $this->_changed_properties = array(); return $this; } public function loadFromArray(array $array = array()) { $this->_data = $array; return $this; } public function save() { $this->_changed_properties = array(); return $this; } public function remove() { return $this->unload(); } public function unload() { $this->_changed_properties = array(); $this->_data = array(); return $this; } public function toArray() { return $this->_data; } } ?>
結論の代わりに
それは非常に大量に判明しましたが、主にコードが原因でした。 このトピックが興味深い場合は、同じメカニズムでのコレクション(フィルタリングアクションと一括(バッチ)アクションでロードする機能を備えたレコードの一種)の実装を説明できます。 コレクションとこれらのモデルはどちらも、私が開発しているフレームワークから取得したものです。したがって、それらを複雑なものと考える方が正しいのですが、私はすでに膨大な記事を読み過ぎませんでした。
もちろん、私はあなたの意見や理由付けられた批判を聞いてうれしいです。