yii2 添加变量,Yii::$service,并像组件component那样可以添加单例配置

技巧库 · fecommerce · 于 3年前 发布 · 2754 次阅读

原文链接:yii2 给Yii 添加一个变量,Yii::$service,并像组件component那样可以添加单例配置

在yii2中,组件是可以通过配置的方式添加到Yii::$app中的。

现在我们想添加一个Yii静态变量,$service,下面都称呼这个变量为服务,

可以通过Yii::$service访问,然后添加服务,以及服务的子服务, 譬如我添加了一个cms服务,以及cms服务的子服务article。

那么我想访问cms服务,可以通过Yii::$service->cms访问,

如果我想访问cms服务的子服务,那么,我可以通过Yii::$service->cms->article访问。

上面的使用访问和yii2的组件很类似,是单例模式。下面说具体实现方式:

1.创建一个Yii.php添加$service参数: @vendor/fancyecommerce/fecshop/yii/Yii.php

    <?php
    $dir = __DIR__ . '/../../../yiisoft/yii2';
    require($dir.'/BaseYii.php');
    /**
     * Yii is a helper class serving common framework functionalities.
     *
     * It extends from [[\yii\BaseYii]] which provides the actual implementation.
     * By writing your own Yii class, you can customize some functionalities of [[\yii\BaseYii]].
     *
     * @author Qiang Xue <qiang.xue@gmail.com>
     * @since 2.0
     */
    class Yii extends \yii\BaseYii
    {
      public static $service;
      
    }
    spl_autoload_register(['Yii', 'autoload'], true, true);
    Yii::$classMap = require($dir.'/classes.php');
    Yii::$container = new yii\di\Container();

2.在入口文件index.php中,将对yii的Yii.php去掉,换成:

    require(__DIR__ . '/../../vendor/fancyecommerce/fecshop/yii/Yii.php');

也就是加载上面,我创建的文件。

然后修改index.php底部代码:

    new fecshop\services\Application($config['services']);
    unset($config['services']);
    //var_dump($config);
    $application = new yii\web\Application($config);
    $application->run();

也就是添加了代码:

    new fecshop\services\Application($config['services']);
    unset($config['services']);

这个代码的作用是,给$service变量赋值,Yii::$service = fecshop\services\Application($config[‘services’]);

3.下面我们实现这个Application。

    <?php
    /**
     * FecShop file.
     *
     * [@link](/member/link) http://www.fecshop.com/
     * @copyright Copyright (c) 2016 FecShop Software LLC
     * @license http://www.fecshop.com/license/
     */
    namespace fecshop\services;
    use Yii;
    use yii\base\Component;
    use yii\base\InvalidConfigException;
    /**
     * @author Terry Zhao <2358269014@qq.com>
     * @since 1.0
     */
    class Application
    {
      public $childService;
      public $_childService;
      
      
      public function __construct($config = [])
        {
            Yii::$service     = $this;
            $this->childService = $config;
        }
      /**
       * 得到services 里面配置的子服务childService的实例
       */
      public function getChildService($childServiceName){
        if(!$this->_childService[$childServiceName]){
          $childService = $this->childService;
          if(isset($childService[$childServiceName])){
            $service = $childService[$childServiceName];
            $this->_childService[$childServiceName] = Yii::createObject($service);
          }else{
            throw new InvalidConfigException('Child Service ['.$childServiceName.'] is not find in '.get_called_class().', you must config it! ');
          }
        }
        return $this->_childService[$childServiceName];
      }
      
      /**
       * 
       */
      public function __get($attr){
        return $this->getChildService($attr);
        
      }
      
    }
  1. 在配置中添加配置:
    [
    'services' => [
        'cms' => [
            'class' => 'fecshop\services\Cms',
            
            # 子服务
            'childService' => [
                'article' => [
                    'class'             => 'fecshop\services\cms\Article',
                    'storage' => 'mysqldb', # mysqldb or mongodb.
                ],
            ],
        ],
    ]

5.上面的配置,给cms添加了一个子服务article。

cms 对应的class的代码如下:

    <?php
    /**
     * FecShop file.
     *
     * [@link](/member/link) http://www.fecshop.com/
     * @copyright Copyright (c) 2016 FecShop Software LLC
     * @license http://www.fecshop.com/license/
     */
    namespace fecshop\services;
    use Yii;
    use yii\base\InvalidValueException;
    use yii\base\InvalidConfigException;
    use fec\helpers\CSession;
    use fec\helpers\CUrl;
    /**
     * Breadcrumbs services
     * @author Terry Zhao <2358269014@qq.com>
     * @since 1.0
     */
    class Cms extends Service
    {
      /**
       * cms storage db, you can set value: mysqldb,mongodb.
       */
      public $storage = 'mysqldb';
      
    }

artile的代码如下:

    <?php
    /**
     * FecShop file.
     *
     * [@link](/member/link) http://www.fecshop.com/
     * @copyright Copyright (c) 2016 FecShop Software LLC
     * @license http://www.fecshop.com/license/
     */
    namespace fecshop\services\cms;
    use Yii;
    use yii\base\InvalidValueException;
    use yii\base\InvalidConfigException;
    use fec\helpers\CSession;
    use fec\helpers\CUrl;
    use fecshop\services\Service;
    use fecshop\services\cms\article\ArticleMysqldb;
    use fecshop\services\cms\article\ArticleMongodb;
    /**
     * Breadcrumbs services
     * @author Terry Zhao <2358269014@qq.com>
     * @since 1.0
     */
    class Article extends Service
    {
      public $storage = 'mongodb';
      protected $_article;
      
      
      public function init(){
        if($this->storage == 'mongodb'){
          $this->_article = new ArticleMongodb;
        }else if($this->storage == 'mysqldb'){
          $this->_article = new ArticleMysqldb;
        }
      }
      /**
       * Get Url by article's url key.
       */
      public function getUrlByPath($urlPath){
        //return Yii::$service->url->getHttpBaseUrl().'/'.$urlKey;
        return Yii::$service->url->getUrlByPath($urlPath);
      }
      /**
       * get artile's primary key.
       */
      public function getPrimaryKey(){
        return $this->_article->getPrimaryKey();
      }
      /**
       * get artile model by primary key.
       */
      public function getByPrimaryKey($primaryKey){
        return $this->_article->getByPrimaryKey($primaryKey);
      }
      
      
      
      /**
       * @property $filter|Array
       * get artile collection by $filter
       * example filter:
       * [
       *     'numPerPage'   => 20,    
       *     'pageNum'    => 1,
       *     'orderBy'  => ['_id' => SORT_DESC, 'sku' => SORT_ASC ],
       *     'where'      => [
       *       'price' => [
       *         '?gt' => 1,
       *         '?lt' => 10,
       *       ],
       *       'sku' => 'uk10001',
       *     ],
       *   'asArray' => true,
       * ]
       */
      public function coll($filter=''){
        return $this->_article->coll($filter);
      }
      
      /**
       * @property $one|Array , save one data .
       * @property $originUrlKey|String , article origin url key.
       * save $data to cms model,then,add url rewrite info to system service urlrewrite.                 
       */
      public function save($one,$originUrlKey){
        return $this->_article->save($one,$originUrlKey);
      }
      
      public function remove($ids){
        return $this->_article->remove($ids);
      }
      
    }

他们继承的service类代码如下:

    <?php
    /**
     * FecShop file.
     *
     * [@link](/member/link) http://www.fecshop.com/
     * @copyright Copyright (c) 2016 FecShop Software LLC
     * @license http://www.fecshop.com/license/
     */
    namespace fecshop\services;
    use Yii;
    use yii\base\Object;
    use yii\base\InvalidConfigException;
    /**
     * @author Terry Zhao <2358269014@qq.com>
     * @since 1.0
     */
    class Service extends  Object
    {
      public $childService;
      public $_childService;
      
      /**
       * 得到services 里面配置的子服务childService的实例
       */
      public function getChildService($childServiceName){
        if(!$this->_childService[$childServiceName]){
          $childService = $this->childService;
          if(isset($childService[$childServiceName])){
            $service = $childService[$childServiceName];
            $this->_childService[$childServiceName] = Yii::createObject($service);
          }else{
            throw new InvalidConfigException('Child Service ['.$childServiceName.'] is not find in '.get_called_class().', you must config it! ');
          }
        }
        return $this->_childService[$childServiceName];
      }
      
      /**
       * 
       */
      public function __get($attr){
        return $this->getChildService($attr);
        
      }
      
    }

然后,我就可以使用了,按照上面的方法

    Yii::$service->cms->$storage     # 获取存储方式,这个变量可以在service配置中注入。
    Yii::$service->cms->article->remove($ids)  # cms服务对应子服务artile的删除功能。

由于这种方式是单例模式,因此,可以用这种方式,在controller和model之间,做一个中间服务层,方便日后的扩展和使用,譬如我如果把mysql换成了mongodb,只需要把这个服务的public方法 重新实现以下就可以了,目前fecshop采用了这种实现方式。

本文由 fecommerce 创作,采用 知识共享署名 3.0 中国大陆许可协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。


微信

共收到 6 条回复
forecho#13年前 0 个赞

有两个疑问:

  1. new fecshop\services\Application($config['services']); 之后为什么要 unset($config['services']); ? 有点不明白。

  2. 这个 Yii::$service 之后会有代码提示吗?

fecommerce#23年前 0 个赞

@forecho [[#1楼](#comment1)](#comment1) 在config里面所有的配置,最后都是当做参数传递给 yii\web\Application($config);

$application = new yii\web\Application($config);
$application->run();

如果不执行:unset($config['services'])], yii\web\Application 就会set,但是它没有services属性,就会报错。

Yii::$app 就是 new yii\web\Application($config); , 代码如下:

 public function __construct($config = [])
    {
        Yii::$app = $this;

new fecshop\services\Application($config['services']); 的原理和上面类似。

public function __construct($config = [])
    {
        Yii::$service 		= $this;
        $this->childService = $config;
    }
fecommerce#33年前 0 个赞

@forecho #1楼
Yii::$service 就是 new fecshop\services\Application($config['services']); 的单例模式

Yii::$app 就是 new yii\web\Application($config); 的单例模式,

在console, Yii::$app 就是 new yii\console\Application($config); 的单例模式,

Yii::$service->cms 就可以访问配置的一个服务类了,原理就是用魔术方式_get实现的,也就是从配置中读取,然后用容器创建,保存在一个成员变量里面,下次访问的时候,从成员变量里面查看是否存在,如果存在则直接读取,不存在则创建,也就是单例模式

fecommerce#43年前 0 个赞

@forecho #1楼 我用这个,加一个底层服务层,上次逻辑代码不能直接访问model ,必须通过Yii::$service 访问服务层调取数据,这样,某个功能,譬如文章功能,原来是mysql 提供的支持, 我可以很方便的改变数据库,譬如改成mongodb,我只要实现服务里面的public方法即可,然后通过配置覆盖,就可以进行切换。 或者我直接在服务里面添加配置选项,让使用者选择mysql还是mongodb,或者redis等。扩展性,重构比较好。

forecho#53年前 0 个赞

第一个问题明白了。

Yii::$service 使用 IDE 代码有提示吗?

fecommerce#63年前 0 个赞

@forecho #5楼 不是很明白你说的提示是什么,详细描述一下。

添加回复 (需要登录)
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册