Changes between Initial Version and Version 1 of EC-CUBE標準規約/開発効率向上のためのTips


Ignore:
Timestamp:
2013/05/16 20:15:48 (11 years ago)
Author:
nanasess
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • EC-CUBE標準規約/開発効率向上のためのTips

    v1 v1  
     1= 開発効率向上のための Tips = 
     2 
     3 == 概要 == 
     4 
     5この Tips は, EC-CUBE標準規約には含まれないが, 開発や保守の効率を向上するための秘訣を記載します. 
     6なお, この Tips に沿っていない既存のソースコードが多々ありますが, すべてを修正するのは現実的ではありません. 
     7新規にカスタマイズを加える箇所のみ適用すると良いでしょう. 
     8 
     9 === 要件 === 
     10 
     11主に, PHP5.3.2 以降の環境で稼働するものを対象とします. 
     12 
     13 == PHP5 の構文を使用する == 
     14 
     15EC-CUBE 2.12.0 以降, PHP4 はサポートしていないため, PHP5 の構文を使用します. 
     16これらを適切に使用することで, 開発・保守効率の向上が望めます. 
     17 
     18 === Property Visibility (アクセス権) === 
     19 
     20クラスのメンバーには, public, protected, private のアクセス権を付与します. 
     21これにより, 外部クラスからの意図せぬメンバ変数アクセスを防ぐことができ, 堅牢かつ保守しやすいコードになります. 
     22 
     23{{{ 
     24#!php 
     25  <?php 
     26 
     27  /** 
     28   * NG クラスのメンバ変数で振舞いを変更できてしまう 
     29   */ 
     30  class MyBadClass { 
     31 
     32      var $order_id; 
     33 
     34      function doSendMail() { 
     35          $objPurchase = new SC_Purchase_Ex(); 
     36          $objPurchase->sendOrderMail($this->order_id); 
     37      } 
     38  } 
     39 
     40  /** 
     41   * OK 引数やアクセサメソッドを経由することにより, 意図せぬ振舞い変更を防ぐ 
     42   */ 
     43  class MyGoodClass { 
     44 
     45      private $order_id; 
     46 
     47      public function setOrderId($orderId) { 
     48          $this->order_id = $order_id; 
     49      } 
     50 
     51      public function getOrderId() { 
     52          return $this->order_id; 
     53      } 
     54 
     55      public function doSendMail() { 
     56          $objPurchase = new SC_Purchase_Ex(); 
     57          $objPurchase->sendOrderMail($this->getOrderId()); 
     58      } 
     59  } 
     60}}} 
     61 
     62private または protected 修飾子により, 外部からのアクセスを防止することは重要ですが, EC-CUBEは継承によるカスタマイズを多用するため, 特に理由の無い限りは protected 修飾子を利用します. 
     63 
     64{{{ 
     65#!php 
     66  <?php 
     67  /** 
     68   * NG 内部用メソッドを外部からアクセスされてしまう 
     69   */ 
     70  class MyBadClass { 
     71 
     72      function lfLocalFunction($order_id) { 
     73          $objPurchase = new SC_Purchase_Ex(); 
     74          $objPurchase->sendOrderMail($order_id); 
     75      } 
     76  } 
     77 
     78  /** 
     79   * OK 内部用クラスは, 継承によるカスタマイズの余地を残すため, protected を使用する 
     80   */ 
     81  class MyGoodClass { 
     82 
     83      protected function localFunction($order_id) { 
     84          $objPurchase = new SC_Purchase_Ex(); 
     85          $objPurchase->sendOrderMail($order_id); 
     86      } 
     87  } 
     88}}} 
     89 
     90 === Exception (例外) === 
     91 
     92適切な Exception を使用することにより, エラーハンドリングを簡潔に記述できます. 
     93 
     94{{{ 
     95#!php 
     96 
     97  <?php 
     98 
     99  /** 
     100   * NG Exception を使用しない場合は, 返り値が煩雑になります. 
     101   * このクラスの場合は, mixed なので, ソースを見ないと何が返ってくるか解りません! 
     102   */ 
     103  class MyBadClass { 
     104 
     105      /** 
     106       * @return mixed 処理が成功した場合は計算後の値を返す; 
     107       * 失敗した場合は false を返す. 
     108       */ 
     109      public function customFunction($number) { 
     110          // 失敗した場合は false を返す 
     111          if ($number > 10) { 
     112              return false; 
     113          } 
     114          return $number + 10; 
     115      } 
     116  } 
     117 
     118  /** 
     119   * OK Exception を使用すると, 返り値が簡潔になり, エラーハンドリングもしやすくなります. 
     120   * Exception クラスを拡張し, 独自の Exception を定義しています. 
     121   * customFunction() は, 必ず数値が返り, エラーが発生した場合は MyException をスローします. 
     122   */ 
     123  class MyGoodClass { 
     124 
     125      /** 
     126       * @return integer 計算後の値を返す. 
     127       * @throw MyException 処理が失敗した場合 
     128       */ 
     129      public function customFunction($number) { 
     130          if ($number > 10) { 
     131              throw new MyException('error message!'); 
     132          } 
     133          return $number + 10; 
     134      } 
     135  } 
     136 
     137  class MyException extends Exception { 
     138 
     139      public function __construct($message, $code, $previous) { 
     140          parent::__construct($message, $code, $previous); 
     141      } 
     142  } 
     143}}} 
     144 
     145 === Enum (列挙型) === 
     146 
     147PHP の標準構文ではありませんが, interface を使用することにより Enum(列挙型)を擬似的に表現できます. 
     148定数をたくさん定義するより, 直感的に記述できます. 
     149 
     150{{{ 
     151#!php 
     152 
     153  <?php 
     154  /** 
     155   * NG 定数を羅列すると, 煩雑になる 
     156   */ 
     157  define('OPENID_SCOPE_OPENID', 'openid'); 
     158  define('OPENID_SCOPE_PROFILE', 'profile'); 
     159  define('OPENID_SCOPE_EMAIL', 'email'); 
     160  define('OPENID_SCOPE_ADDRESS', 'address'); 
     161  define('OPENID_SCOPE_PHONE', 'phone'); 
     162 
     163  // 他の定数と混同しやすい 
     164  echo sprintf('%s %s', 
     165               OPENID_SCOPE_OPENID, 
     166               OPENID_SCOPE_PROFILE); 
     167 
     168 
     169  /** 
     170   * OK Enum(列挙型)を表現することにより, 直感的にアクセス可能. 
     171   * 継承もできるため, 拡張しやすい. 
     172   */ 
     173 
     174  /** 
     175   * OpenID Connect の Scope を表す列挙型 
     176   */ 
     177  interface OIDConnect_Scope { 
     178      const OPENID = 'openid'; 
     179      const PROFILE = 'profile'; 
     180      const EMAIL = 'email'; 
     181      const ADDRESS = 'address'; 
     182      const PHONE = 'phone'; 
     183  } 
     184 
     185  // OpenID Connect 関連の定義ということが直感的に判断できる 
     186  echo sprintf('%s %s', 
     187               OIDConnect_Scope::OPENID, 
     188               OIDConnect_Scope::PROFILE); 
     189}}} 
     190 
     191 === Design Pattern (デザインパターン) === 
     192 
     193PHP5 の構文を活用することにより, 各種デザインパターンを利用しやすくなりました. 
     194特に Singleton, Chain of Responsibility, Factory, Strategy などをよく利用します. 
     195 
     196http://www.ibm.com/developerworks/jp/opensource/library/os-php-designptrns/ 
     197 
     198 
     199 == エンティティクラスを作成する == 
     200 
     201標準の EC-CUBE では, データベースから連想配列で値を取得します. 
     202 
     203この連想配列を使用して, さまざまなビジネスロジックを制御したり, 画面表示したりするわけですが, 複雑なデータ構造になると, 
     204とても煩雑なコードとなってしまいます. 
     205 
     206エンティティクラスを作成することにより, 簡潔にコーディングでき, 開発効率向上が望めます. 
     207 
     208{{{ 
     209#!php 
     210 
     211  <?php 
     212 
     213  /** 
     214   * エンティティクラスの例 
     215   */ 
     216  class SC_Entity_OrderDetail { 
     217 
     218      private $order_detail_id; 
     219      private $order_id; 
     220      private $product_id; 
     221      private $product_class_id; 
     222      private $product_name; 
     223      private $product_code; 
     224      private $classcategory_name1; 
     225      private $classcategory_name2; 
     226      private $price; 
     227      private $quantity; 
     228      private $point_rate; 
     229 
     230      /** 
     231       * コンストラクタ. 
     232       *  
     233       * 引数の連想配列で, 本オブジェクトのプロパティを設定可能. 
     234       *  
     235       * @param array $arrOrderDetail 
     236       */ 
     237      public function __construct($arrOrderDetail = array()) { 
     238          if (is_array($arrOrderDetail)) { 
     239              $this->setPropertiesFromArray($arrOrderDetail); 
     240          } 
     241      } 
     242 
     243      /** 
     244       * 税込単価を返します. 
     245       *  
     246       * @return integer 税込単価 
     247       */ 
     248      public function getTotalInTax() { 
     249          return SC_Helper_DB_Ex::sfTax($this->getPrice()) * 
     250          $this->getQuantity(); 
     251      } 
     252 
     253      /** 
     254       * 該当商品のカテゴリIDを配列で返します. 
     255       *  
     256       * @return array カテゴリ一覧の配列 
     257       */ 
     258      public function getCategoryIds() { 
     259          // エンティティクラスの内部での DBアクセスは極力避けるべきですが, 
     260          // 内部でDBアクセスした方が効率が良い場合は, その旨のコメントを入れて使用します. 
     261          // この場合は, product_id に紐づいた category_id を取得します. 
     262          // 以下のロジックを外出しするより簡潔なコードになります. 
     263          $objQuery = SC_Query_Ex::getSingletonInstance(); 
     264          $col = 'category_id'; 
     265          $from = 'dtb_product_categories'; 
     266          $where = 'product_id = ?'; 
     267          $arrResults = $objQuery->select($col, $from, $where, 
     268          array($this->getProductId())); 
     269          $arrCategoryIds = array(); 
     270          foreach ($arrResults as $val) { 
     271              $arrCategoryIds[] = $val; 
     272          } 
     273          return $arrCategoryIds; 
     274      } 
     275 
     276      /* 
     277       * 読み取り専用にしたいプロパティは, getter のみ作成します. 
     278       * 更新可能なプロパティは setter を作成します. 
     279       */ 
     280      public function getOrderId() { 
     281          return $this->order_id; 
     282      } 
     283      public function getProductId() { 
     284          return $this->product_id; 
     285      } 
     286      public function getProductClassId() { 
     287          return $this->product_class_id; 
     288      } 
     289      public function getProductCode() { 
     290          return $this->product_code; 
     291      } 
     292      public function getClassCategoryName1() { 
     293          return $this->classcategory_name1; 
     294      } 
     295      public function getClassCategoryName2() { 
     296          return $this->classcategory_name2; 
     297      } 
     298      public function getPrice() { 
     299          return $this->price; 
     300      } 
     301      public function setQuantity($quantity) { 
     302          $this->quantity = $quantity; 
     303      } 
     304      public function getQuantity() { 
     305          return $this->quantity; 
     306      } 
     307      protected function getPointRate() { 
     308          return $this->point_rate; 
     309      } 
     310 
     311      /** 
     312       * 引数の連想配列を元にプロパティを設定します. 
     313       * DBから取り出した連想配列を, プロパティへ設定する際に使用します. 
     314       *  
     315       * @param array プロパティの情報を格納した連想配列 
     316       * @param ReflectionClass $parentClass 親のクラス. 本メソッドの内部的に使用します. 
     317       */ 
     318      public function setPropertiesFromArray($arrProps, ReflectionClass $parentClass = null) { 
     319          $objReflect = null; 
     320          if (is_object($parentClass)) { 
     321              $objReflect = $parentClass; 
     322          } else { 
     323              $objReflect = new ReflectionClass($this); 
     324          } 
     325 
     326          $arrProperties = $objReflect->getProperties(); 
     327          foreach ($arrProperties as $objProperty) { 
     328              $objProperty->setAccessible(true); 
     329              $name = $objProperty->getName(); 
     330              $objProperty->setValue($this, $arrProps[$name]); 
     331          } 
     332 
     333          // 親クラスがある場合は再帰的にプロパティを取得 
     334          $parentClass = $objReflect->getParentClass(); 
     335          if (is_object($parentClass)) { 
     336              self::setPropertiesFromArray($arrProps, $parentClass); 
     337          } 
     338      } 
     339 
     340      /** 
     341       * プロパティの値を連想配列で返します. 
     342       * DBを更新する場合などで, 連想配列の値を取得したい場合に使用します. 
     343       *  
     344       * @return array 連想配列のプロパティの値 
     345       */ 
     346      public function toArray() { 
     347          $arrResults = array(); 
     348          $objReflect = new ReflectionClass($this); 
     349          $arrProperties = $objReflect->getProperties(); 
     350 
     351          foreach ($arrProperties as $objProperty) { 
     352              $objProperty->setAccessible(true); 
     353              $name = $objProperty->getName(); 
     354              $arrResults[$name] = $objProperty->getValue($this); 
     355          } 
     356          return $arrResults; 
     357      } 
     358  } 
     359}}} 
     360 
     361PHP で, 以下のようにデータベースから取得できます 
     362 
     363{{{ 
     364#!php 
     365 
     366  <?php 
     367  $objPurchase = new SC_Helper_Purchase_Ex(); 
     368  $arrOrderDetail = $objPurchase->getOrderDetail($order_id); 
     369  $this->arrObjOrderDetail = array(); 
     370  foreach ($arrOrderDetail as $orderDetail) { 
     371      $this->arrObjOrderDetail[] = new SC_Entity_OrderDetail($orderDetail); 
     372  } 
     373}}} 
     374 
     375Smarty で記述した場合に, 連想配列より簡潔にコーディングできます 
     376{{{ 
     377#!html 
     378 
     379  <!--{foreach from=$arrObjOrderDetail item=objOrderDetail}--> 
     380      <!-- 各受注明細の税込小計を取得します --> 
     381      <p><!--{$objOrderDetail->getTotalInTax()}-->円</p> 
     382  <!--{/foreach}--> 
     383}}} 
     384 
     385データベースを更新する場合も簡潔にコーディングできます 
     386 
     387{{{ 
     388#!php 
     389 
     390  <?php 
     391  // 数量を2に変更します 
     392  $objOrderDetail->setQuantity(2); 
     393  $objQuery = SC_Query_Ex::getSingletonInstance(); 
     394  $objQuery->update('dtb_order_detail', $objOrderDetail->toArray(), 
     395                    'order_detail_id = ?', 
     396                    $objOrderDetail->getOrderDetailId()); 
     397}}} 
     398 
     399 == ユニットテストを活用する == 
     400 
     401TODO