Upload
ec-cube
View
143
Download
0
Embed Size (px)
Citation preview
EC-CUBE 3.1 開発方針説明
機能カスタマイズ編機能カスタマイズのためのアーキテクチャ
概要
#1985 で、 v3.1 に向けた実験的な実装を行っています。詳細は主にShoppingController のソースコメントに記載しています。
• forward(Sub Request) を使用して、 Controller の処理を抽象化。継承を使用せず、処理をオーバーライドできるようにした。
• Order 関連の FormType の抽象化• 単価集計を CalculateService にまとめて、 Strategy パターンを適用• 支払を PaymentService にまとめて、 Adapter パターンを適用
概要
その他、以下アーキテクチャの変更をしています。
• Symfony3.2• v3.1 では Symfony3.4 LTS を採用予定
• Silex2.0• Pimple3.0• Doctrine2.5• SensioFrameworkExtraBundle• Inheritance Mapping
カスタマイズ方法の改善
• アノテーションの採用• forward(Sub Request) の使用• Inheritance Mapping の採用• 単価集計や支払いなどの処理にデザインパターンを適用• プラグインを使用しないカスタマイズ
アノテーションの採用
• 新たに、 Doctrine アノテーション、 SensioFrameworkExtraBundle アノテーションが使用できるようになりました。•Entity の定義や、 コントローラのルーティング設定をアノテーションで記述できるようになり、より簡易に拡張が可能になりました。
※ 現在、既存のエンティティや、ルーティングは従来の Yaml や PHP での定義となっていますが、将来的にはすべてアノテーションに置き換えられる予定です。
サポートされているアノテーション
•Doctrine アノテーション•SensioFrameworkExtraBundle アノテーション
サポートされているアノテーション
Doctrine アノテーションhttp://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html
@Column, @Entity, @GeneratedValue, @Index, @Id, @InheritanceType, @JoinColumn, @JoinColumns, @JoinTable, @ManyToOne, @ManyToMany, @MappedSuperclass, @OneToOne, @OneToMany, @OrderBy, @SequenceGenerator, @Table, @UniqueConstraint...
サポートされているアノテーション
SensioFrameworkExtraBundle アノテーションhttp://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html
• @Route 及び @Method• @Template• @Security
forward(Sub Request) の使用
従来、本体に手を入れずに、コントローラの処理を拡張する場合は、主に以下のような方法がありました。
• 継承して別のインスタンスへ置き変える• イベントハンドラで頑張る
これらの方法は、プラグインとコントローラの結合度が強くなり、開発効率を下げる要因となっていました。
今回、カテゴリなどのブロックの処理に使用している Sub Request を流用し、コントローラ内の処理を簡便に、他のコントローラへ移譲できるようになりました。
forward(Sub Request) の使用
ApplicationTrait::forward($path, $requestParameters) というメソッドが追 加されており、 $path で指定したコントローラへ処理を移譲することが
できます。このメソッドは、 Response を返します。
コントローラ内で、この Response を return すると、レスポンスが出力されます。return しなければ、内部の処理のみ実行されます。
コントローラのメソッドは、ルーティングを介して、緩く結合しているイメージです。
forward(Sub Request) の使用
例として、 ShoppingController::index() メソッドは以下のような実装になっています。
src/Eccube/Controller/ShoppingController.php
/** * 購入画面表示
* * @Route("/", name="shopping") * @Template("Shopping/index.twig") */ public function index(Application $app, Request $request) { // カートチェック
$response = $app->forward($app->path("shopping/checkToCart")); if ($response->isRedirection() || $response->getContent()) { return $response; } // 受注情報を初期化
$response = $app->forward($app->path("shopping/initializeOrder")); if ($response->isRedirection() || $response->getContent()) { return $response; } // 単価集計し , フォームを生成する
$app->forwardChain($app->path("shopping/calculateOrder")) ->forwardChain($app->path("shopping/createForm"));
src/Eccube/Controller/ShoppingController.php
// 受注のマイナスチェック
$response = $app->forward( $app->path("shopping/checkToMinusPrice")); if ($response->isRedirection() || $response->getContent()) { return $response; } // 複数配送の場合、エラーメッセージを一度だけ表示
$app->forward($app->path("shopping/handleMultipleErrors"));
$Order = $app['request_scope']->get('Order'); $form = $app['request_scope']->get(OrderType::class);
return [ 'form' => $form->createView(), 'Order' => $Order ]; }
forward(Sub Request) の使用
例えば、カートチェックの振舞いを変更したい場合は、shopping/checkToCart のルーティングをオーバーライドしたメソッドを作
成します。この処理は、 app/Acme/Controller 以下や、プラグインなどで拡張できます。
app/Acme/Controller/ExampleController.php
/** * @Route("/shopping") */class ExampleController{ /** * カート画面のチェック
* * @Route("/checkToCart", name="shopping/checkToCart") */ public function checkToCart(Application $app, Request $request) { $cartService = $app['eccube.service.cart'];
// カートチェック
if (!$cartService->isLocked()) { log_info(' カートが存在しません '); // カートが存在しない、カートがロックされていない時はエラー
return $app->redirect($app->url('cart')); }
app/Acme/Controller/ExampleController.php
// 独自の処理を記述
log_info(' カートの内容をチェックしました ');
// 各コントローラ間の値の受け渡しには $app['request_scope'] を使用可能
$Order = $app['request_scope']->get('Order'); if ($Order) { $Order->setNote(' 独自カスタマイズ処理を通過しました '); $app['orm.em']->flush($Order); }
return new Response(); }}
forward(Sub Request) の使用
forwardChain を使用することで、複数の forward を連続してつなげることも可能です。forward を活用することにより、各ルーティングの処理をコンパクトにまとめることができます。依存するクラスも少ないため、簡単にテストを記述することが可能です。
ExampleTest
public function testCheckToCart() { $Controller = new \Eccube\Controller\ShoppingController();
$this->assertInstanceOf('\Eccube\Controller\ShoppingController', $Controller); $Request = Request::create($this->app->path('shopping/checkToCart'), 'GET'); $Response = $Controller->checkToCart($this->app, $Request);
$this->assertInstanceOf('\Symfony\Component\HttpFoundation\RedirectResponse', $Response); $this->assertTrue($Response->isRedirect($this->app->url('cart')), $this->app->url('cart').' へリダイレクト '); }
ExampleTest
public function testCheckToCartIn() { $Controller = new \Eccube\Controller\ShoppingController();
// カートに商品を投入
$cartService = $this->app['eccube.service.cart']; $cartService->addProduct(1); $cartService->lock();
$this->assertInstanceOf('\Eccube\Controller\ShoppingController', $Controller); $Request = Request::create($this->app->path('shopping/checkToCart'), 'GET'); $Response = $Controller->checkToCart($this->app, $Request);
$this->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $Response); $this->assertEmpty($Response->getContent(), ' 空のレスポンスを返却 '); }
Inheritance Mapping の採用
Inheritance Mappinghttp://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html
データベースのテーブルに新たなカラムを追加したい場合に Inheritance Mapping を使用できるようになりました。
例えば、 商品 (Product) に `ExampleField` という項目を追加したい場合 は、以下のようなクラスを作成し、 schema-tool で UPDATE するだけで
す!※http://ec-cube.github.io/collaboration_migration#db-1
Acme/Entity/ExamplePayment.php
/** * Product の拡張
* @Entity * @Table(name="example_product") */class ExamplePayment extends \Eccube\Entity\Product{ /** * @Column(name="example_field", type="string") */ public $ExampleField;}
単価集計や、支払いなどの処理にデザインパターンを適用
一部のビジネスロジックにデザインパターンを適用し、柔軟かつ効率的にカスタマイズできるようになりました。
以下、 ShoppingController の一部です。 プラグイン側では、 CalculateStrategy や PaymentMethod クラスを実装
することで、独自の決済手段を実装可能です。
現在は、商品購入処理のみとなっていますが、商品管理など他の機能にも適用していく予定です。
src/Eccube/Controller/ShoppingController.php
// 購入処理
// 集計は , この 1 行で実行可能
// プラグインで CalculateStrategy をセットしたりしてアルゴリズムの変更が可能
// 集計はステートレスな実装とし、再計算時に状態を考慮しなくても良いようにする
$app['eccube.service.calculate']($Order, $Order->getCustomer())->calculate();
// 支払処理
$paymentService = $app['eccube.service.payment']($Order->getPayment()->getServiceClass()); // PaymentMethod クラスは、 Cash( 銀行振込 ) 、 CreditCard( クレジットカード ) などを取得する
$paymentMethod = $app['payment.method.request']($Order->getPayment()->getMethodClass(), $form, $request);
// PaymentMethod 内の処理で、必要に応じて別のコントローラへ forward( 移譲 ) 可能
$dispatcher = $paymentService->dispatch($paymentMethod); // 決済処理中 . if ($dispatcher instanceof Response && ($dispatcher->isRedirection() || $dispatcher->getContent())) { // $paymentMethod->apply() が Response を返した場合は画面遷移
return $dispatcher; } $PaymentResult = $paymentService->doCheckout($paymentMethod); // 決済実行
if (!$PaymentResult->isSuccess()) { $em->getConnection()->rollback(); return $app->redirect($app->url('shopping_error')); }
プラグインを使用しないカスタマイズ
新たに `app/Acme` 以下に、カスタマイズ用のプログラムを置けるようになりました。
• プラグインにするまでもないような、ちょっとしたカスタマイズ• 既存のプラグインの振舞いを変更したい場合• プラグインでは対応しにくい大規模カスタマイズ
などに利用できます。
※`Acme` という namespace は、任意のものに変更可能です。
参考実装
• プラグインの参考実装• プラグインを使用しないカスタマイズの参考実装
プラグインの参考実装
https://github.com/nanasess/ec-cube/tree/CalculateStrategy/app/Plugin/ExamplePlugin
• Plugin\ExamplePlugin\Controller\ExampleController - ShoppingController をオーバーライドし、独自の決済ボタンを実装しています。
• Plugin\ExamplePlugin\Payment\Method\ExamplePaymentCreditCard - 独自の決済処理を実装しています。
• Plugin\ExamplePlugin\Entity\ExamplePayment - dtb_payment に独自のカラムを追加しています。
プラグインを使用しないカスタマイズの参考実装
https://github.com/nanasess/ec-cube/tree/CalculateStrategy/app/Acme
• Acme\Controller\TestController - 独自コントローラの作成例です。• Acme\Controller\AController - 上記 TestController の拡張例です。• Acme\Controller\RoutingTestController - 管理画面 , user_data の拡張
例です。• Acme\Entity\ExtendedProduct - エンティティの拡張例です。 public
プロパティを使用しています。
thanks.