服务定位器

并不需要手动创建共享服务(应用组件)的实例,我们可以从一个特别的全局对象获取它们,这个全局对象包含配置和所有组件的实例。

一个服务定位器是一个全局对象,它包含一系列组件或者定义,通过一个ID进行唯一标识,并且允许我们通过它的ID获取任何想要的实例。这个定位器在第一次调用的时候创建了the component on-the-fly的一个单例,并在随后的调用中返回先前的实例。

在本节中,我们将会创建一个购物手推车组件,并使用它写一个手推车控制器。

准备

按照官方指导http://www.yiiframework.com/doc-2.0/guide-start-installation.html中的描述,使用composer包管理器创建一个新的应用。

如何做…

执行如下步骤,创建一个购物手推车组件:

  1. 创建一个购物手推车组件。它会在一个用户会话中,存储选择的商品。
  1. <?php
  2. namespace app\components;
  3. use Yii;
  4. use yii\base\Component;
  5. class ShoppingCart extends Component
  6. {
  7. public $sessionKey = 'cart';
  8. private $_items = [];
  9. public function add($id, $amount)
  10. {
  11. $this->loadItems();
  12. if (array_key_exists($id, $this->_items)) {
  13. $this->_items[$id]['amount'] += $amount;
  14. } else {
  15. $this->_items[$id] = [
  16. 'id' => $id,
  17. 'amount' => $amount,
  18. ];
  19. }
  20. $this->saveItems();
  21. }
  22. public function remove($id)
  23. {
  24. $this->loadItems();
  25. $this->_items = array_diff_key($this->_items, [$id =>
  26. []]);
  27. $this->saveItems();
  28. }
  29. public function clear()
  30. {
  31. $this->_items = [];
  32. $this->saveItems();
  33. }
  34. public function getItems()
  35. {
  36. $this->loadItems();
  37. return $this->_items;
  38. }
  39. private function loadItems()
  40. {
  41. $this->_items =
  42. Yii::$app->session->get($this->sessionKey, []);
  43. }
  44. private function saveItems()
  45. {
  46. Yii::$app->session->set($this->sessionKey,
  47. $this->_items);
  48. }
  49. }
  1. 在文件config/web.php中以应用组件的方式,注册ShoppingCart到服务定位器中:
  1. 'components' => [
  2. //…
  3. 'cart => [
  4. 'class' => 'app\components\ShoppingCart',
  5. 'sessionKey' => 'primary-cart',
  6. ],
  7. ]
  1. 创建一个手推车控制器:
  1. <?php
  2. namespace app\controllers;
  3. use app\models\CartAddForm;
  4. use Yii;
  5. use yii\data\ArrayDataProvider;
  6. use yii\filters\VerbFilter;
  7. use yii\web\Controller;
  8. class CartController extends Controller
  9. {
  10. public function behaviors()
  11. {
  12. return [
  13. 'verbs' => [
  14. 'class' => VerbFilter::className(),
  15. 'actions' => [
  16. 'delete' => ['post'],
  17. ],
  18. ],
  19. ];
  20. }
  21. public function actionIndex()
  22. {
  23. $dataProvider = new ArrayDataProvider([
  24. 'allModels' => Yii::$app->cart->getItems(),
  25. ]);
  26. return $this->render('index', [
  27. 'dataProvider' => $dataProvider,
  28. ]);
  29. }
  30. public function actionAdd()
  31. {
  32. $form = new CartAddForm();
  33. if ($form->load(Yii::$app->request->post()) &&
  34. $form->validate()) {
  35. Yii::$app->cart->add($form->productId,
  36. $form->amount);
  37. return $this->redirect(['index']);
  38. }
  39. return $this->render('add', [
  40. 'model' => $form,
  41. ]);
  42. }
  43. public function actionDelete($id)
  44. {
  45. Yii::$app->cart->remove($id);
  46. return $this->redirect(['index']);
  47. }
  48. }
  1. 创建一个表单:
  1. <?php
  2. namespace app\models;
  3. use yii\base\Model;
  4. class CartAddForm extends Model
  5. {
  6. public $productId;
  7. public $amount;
  8. public function rules()
  9. {
  10. return [
  11. [['productId', 'amount'], 'required'],
  12. [['amount'], 'integer', 'min' => 1],
  13. ];
  14. }
  15. }
  1. 创建视图文件views/cart/index.php:
  1. <?php
  2. use yii\grid\ActionColumn;
  3. use yii\grid\GridView;
  4. use yii\grid\SerialColumn;
  5. use yii\helpers\Html;
  6. /* @var $this yii\web\View */
  7. /* @var $dataProvider yii\data\ArrayDataProvider */
  8. $this->title = 'Cart';
  9. $this->params['breadcrumbs'][] = $this->title;
  10. ?>
  11. <div class="site-contact">
  12. <h1><?= Html::encode($this->title) ?></h1>
  13. <p><?= Html::a('Add Item', ['add'], ['class' => 'btn btn-success']) ?></p>
  14. <?= GridView::widget([
  15. 'dataProvider' => $dataProvider,
  16. 'columns' => [
  17. ['class' => SerialColumn::className()],
  18. 'id:text:Product ID',
  19. 'amount:text:Amount',
  20. [
  21. 'class' => ActionColumn::className(),
  22. 'template' => '{delete}',
  23. ]
  24. ],
  25. ]) ?>
  26. </div>
  1. 创建视图文件views/cart/add.php:
  1. <?php
  2. use yii\helpers\Html;
  3. use yii\bootstrap\ActiveForm;
  4. /* @var $this yii\web\View */
  5. /* @var $form yii\bootstrap\ActiveForm */
  6. /* @var $model app\models\CartAddForm */
  7. $this->title = 'Add item';
  8. $this->params['breadcrumbs'][] = ['label' => 'Cart', 'url' => ['index']];
  9. $this->params['breadcrumbs'][] = $this->title;
  10. ?>
  11. <div class="site-contact">
  12. <h1><?= Html::encode($this->title) ?></h1>
  13. <?php $form = ActiveForm::begin(['id' => 'contact-form']);
  14. ?>
  15. <?= $form->field($model, 'productId') ?>
  16. <?= $form->field($model, 'amount') ?>
  17. <div class="form-group">
  18. <?= Html::submitButton('Add', ['class' => 'btn btn-primary']) ?>
  19. </div>
  20. <?php ActiveForm::end(); ?>
  21. </div>
  1. 添加链接项到主菜单中:
  1. ['label' => 'Home', 'url' => ['/site/index']],
  2. ['label' => 'Cart', 'url' => ['/cart/index']],
  3. ['label' => 'About', 'url' => ['/site/about']],
  4. // …
  1. 打开手推车页面,并尝试添加几行:

服务定位器 - 图1

工作原理…

首先创建使用一个公共sessionKey选项创建我们自己的类:

  1. <?php
  2. namespace app\components;
  3. use yii\base\Component;
  4. class ShoppingCart extends Component
  5. {
  6. public $sessionKey = 'cart';
  7. // …
  8. }

第二,我们添加组件定义到配置文件的components部分:

  1. 'components' => [
  2. //…
  3. 'cart => [
  4. 'class' => 'app\components\ShoppingCart',
  5. 'sessionKey' => 'primary-cart',
  6. ],
  7. ]

然后我们就可以通过两种方式获取组件实例:

  1. $cart = Yii::$app->cart;
  2. $cart = Yii::$app->get('cart');

然后我们可以在我们自己的控制器、控件和其它地方使用这个对象。

当我们调用任何组件时,例如cart:

  1. Yii::$app->cart

我们在Yii::$app静态变量中调用Application类实例的这个虚拟属性。但是yii\base\Application类继承了yii\base\Module class,后者继承了带有__call魔术方法的yii\di\ServiceLocator类。这个魔术方法只是调用yii\di\ServiceLocator 类的get()方法:

  1. <?php
  2. namespace yii\di;
  3. class ServiceLocator extends Component
  4. {
  5. private $_components = [];
  6. private $_definitions = [];
  7. public function __get($name)
  8. {
  9. if ($this->has($name)) {
  10. return $this->get($name);
  11. } else {
  12. return parent::__get($name);
  13. }
  14. }
  15. // …
  16. }

因此另一种直接调用这个服务的方法是通过get方法:

  1. Yii::$app->get('cart');

当我们从服务定位器的get方法获取到一个组件,定位器在它的_definitions列表中查找需要的定义,并且如果成功它会创建一个新的对象by the definition on the fly,并将它注册到它自己的完整的实例列表_components中,然后返回这个对象。

如果我们获取一些组件,multiplying定位器总会一次次返回先前保存的实例:

  1. $cart1 = Yii::$app->cart;
  2. $cart2 = Yii::$app->cart;
  3. var_dump($cart1 === $cart2); // bool(true)

它能让我们使用共享的单cart实例Yii::$app->cart或者单数据库连接Yii::$app->db,而不是一次又一次从头创建。

参考