yii2 fecshop 功能特性添加

fecshop,全程: Fancy E-Commerce Shop

今天进一步整理了fecshop的需求。

一:view部分

在yii2的view中,大致分两部分,一个是layout.php文件,先通过render画出这个文件的html,然后具体的view文件在通过render函数画出来,内容放到layout.php文件的$content中替代。

对于view部分的路径查找,一共有两个部分,一个是所在的namespace,也就是绝对根目录,另外一个就是相对文件路径,这两个拼出来view文件和layout.php文件的路径。

1.对于决定根目录,这个是由模板决定,优先级最高的是当前的   模板包/模板名称  路径,然后是中间层模板路径,这个是第三方插件在初始化的时候设置的,最后是fecshop的模板路径,fecshop的模板路径是优先级最低的,但是是文件最全的,也就是说,如果当前的模板想修改fecshop的某个view,可以在当前模板下面把这个文件复制过来,然后修改。

2.对于layout.php文件,首先由全体设定,也就是yii2整体的application中读取,如果module设置了,那么以module为准,如果controller设置了,那么以controller为准,也就是层层覆盖,优先级最高的是controller,然后是module,然后是全体设置,然后得出当前的layout文件的相对路径

3.现在我们有了多个模板的根路径,以及优先级,和view,layout的相对路径,那么我们先到当前模板下面找view文件和layout文件,如果不存在,那么我们去第三方模板或者插件的路径下面找,如果还找不到,我们就去fecshop的文件路径下面找,如果还找不到就报错,

4.到这里,我们的多模板系统的优先级找文件,和各个页面的layout文件的设置就完成了,这里可以让第三方和当前模板通过覆盖的方式,重写fecshop的任何一个view文件。

5.对于第三方的模板或者插件可能有多个,可以通过全局配置的方式添加,譬如模板,也可以通过某个controller文件下面动态设置,譬如插件只对某个页面进行了view的修改,

6.目前想的是通过Yii::$app->page->theme进行,  theme->currentTheme设置当前的模板,譬如:   [ ‘name’ => ‘terry/theme01’,’path’=>’@app/theme’]。

当前模板路径是为了给用户二次开发的。

theme->middleTheme = [

[ ‘name’ => ‘fec/theme01’,’path’=>’@vendor/fancyecommerce/fecshoptheme’],

[ ‘name’ => ‘mage/theme01’,’path’=>’@app/fancyecommerce/fecshoptheme’],

],

前面的优先级最高。如果您有多个模板,请不要一定都设置,这样叠加后可能会出问题,

对于插件,一般在某几个controller中做view的更改,我们建议您通过动态加载的方式,在controller中设置middleTheme的值,进行更高。

theme->baseTheme ,这个是固定值,由fecshop决定。这个是最基础最全面的theme。

7.重写yii\web\controller 的render方法,在生成view路径前,先通过优先级去找view文件,找到后返回,然后画出来页面。

总之,通过配置的方式,各个模板进行优先级的查找view文件,找到后就加载之。

二:第三方开发,插件和模板

1.配置,配置通过在index.php加载的方式加载插件或者模板的配置,通过顺序进行优先级的调整,顺序为:common的配置,fecshop的配置,第三方插件和模板的配置,本地local的配置,其中后面的会覆盖前面的,

2.扩展可以重写yii2框架的文件,可以重写fecshop的services,modules,module controller,block,view文件等等,也就是说可以重写所有的fecshop和 yii2框架的文件,对于重写,后面的优先级最高,如果对同一个service进行重写,在入口文件中,配置在最后面和前面的取他们的并集,如果某个key的value都存在,则会覆盖。

3.对于配置型数据和页面   cms 和statici block

要可以加入配置变量,可以配置布局文件layout,可以加入配置块等

4.review 通过配置的方式决定是全语言显示还是单语言显示,全语言显示,标注store

5.对于同一个服务,可能后期会重构另外一种,那么通过扩展的方式让用户自己选择加载,譬如购物车,订单,优惠券,积分,网站联盟,要改成分库分表的方式,则通过一个插件,通过重构service的方式,来重构,上面的modules则不必更改

6.为了尽量的避免冲突,view文件要尽可能的碎小。

7.多语言采用,翻译文件,数据库数据,配置数据来实现多语言。

8.controller,集成于fecshop的controller,然后fecshop controller集成yii\web\controller. 这样后期可以加入统计等一些功能,然后通过队列的方式,进行一些统计的事情等等,方便后期扩展。

9.对于ajax,后期是否考虑开启不同的入口,譬如在app/web下面新建一个文件夹ajax

app/web/ajax/index.php,这个文件中只有很少的加载文件,更快的速度执行,

10.对于货币,基于session或者cookie,没有url的改变

11.产品购物车通过配置的方式决定跳转还是不跳转

12.类似jd的购物车产品选择下单的方式

13.登录和游客下面的功能的开启和关闭

14,积分分为可用和不可用积分。

15.思考以后分库分表的变迁,如何解决。这些的解决方式,是通过扩展的方式重构service的方式,而不是修改fecshop的代码的方式,这样让客户根据自己的需要,选定不同的插件,根据公司的方式状况进行不同的重构方式。

16.网站联盟affiliate,积分points,团购,线下分销dropship,批发wholesale等等后续的功能,可以通过插件的方式,也可以在fecshop升级的方式加载。来让fecshop的功能越来越强大。

yii2 在域名后面加一个路径作为首页

对于多语言网站,我们可以用 en.fecshop.com,fr.fecshop.com,es.fecshop.com, 这种子域名的方式表现做多语言
也可以用 www.fecshop.com , www.fecshop.com/fr , www.fecshop.com/es , 这种方式做多语言
yii2对第一种支持还是不错,对于第二种支持不好,我们搞一种自己的方式来支持这种。

1.在app/web/下面新建文件夹

app/web/fr

app/web/es

在上面的文件夹下面新建index.php文件,assets文件夹设置可写。

/app/web/fr/index.php

1.nginx设置:

在nginx的server配置中加入:

location /fr/ {
    index index.php;
    if (!-e $request_filename){
      rewrite . /fr/index.php last;
    }
    }

 
2.入口/app/web/fr/index.php文件开始加入:

$secure = 0;
$http = $secure ? 'https' : 'http';
$homeUrl = $http.'://'.$_SERVER['HTTP_HOST'].rtrim(dirname($_SERVER['SCRIPT_NAME']), '\\/');

 

$application = new yii\web\Application($config);
的上面加入:

$config['homeUrl'] = $homeUrl;

加完之后的样子:

<?php
//$secure = isset($_SERVER['HTTPS']) && (strcasecmp($_SERVER['HTTPS'], 'on') === 0 || $_SERVER['HTTPS'] == 1) || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0;
$secure = 0;
$http = $secure ? 'https' : 'http';
$homeUrl = $http.'://'.$_SERVER['HTTP_HOST'].rtrim(dirname($_SERVER['SCRIPT_NAME']), '\\/');

defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');
require(__DIR__ . '/../../../vendor/autoload.php');
require(__DIR__ . '/../../../vendor/yiisoft/yii2/Yii.php');
require(__DIR__ . '/../../../common/config/bootstrap.php');
require(__DIR__ . '/../../config/bootstrap.php');

$config = yii\helpers\ArrayHelper::merge(
    require(__DIR__ . '/../../../common/config/main.php'),
    require(__DIR__ . '/../../../common/config/main-local.php'),
    require(__DIR__ . '/../../config/main.php'),
  require(__DIR__ . '/../../config/main-local.php')
    

);
$config['homeUrl'] = $homeUrl;
$application = new yii\web\Application($config);
$application->run();

3.添加store组件:

'store' => [
    'class' => 'fecshop\services\Store',
    'stores' => [
      'fecshop.appadmin.fancyecommerce.com/fr' => [
        
        'language' 		=> 'fr',
        'themePackage'	=> 'default',
        'theme'	=> 'default',
        'currency' => 'USD',
      ],
      'fecshop.appadmin.fancyecommerce.com/es' => [
        'language' 		=> 'es',
        'themePackage'	=> 'default',
        'theme'	=> 'default',
        'currency' => 'USD',
      ],
       'fecshop.appadmin.fancyecommerce.com' => [
        'language'  => 'en',
        'themePackage' => 'default',
        'theme' => 'default',
        'currency' => 'USD',
      ],
    ],
    'languages' => [
      //'en','fr','it','de','es','nl','pt','ru',
    ],
  ],

组件:

<?php
/**
 * FecShop file.
 *
 * @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 yii\base\BootstrapInterface;
/**
 * @author Terry Zhao <2358269014@qq.com>
 * @since 1.0
 */
class Store extends Service implements BootstrapInterface
{
  /**
   * init by config file.
   * all stores config . include : domain,language,theme,themePackage
   */
  public $stores; 
  /**
   * init by config file.
   * all store Language.
   */	
  public $languages;
  
  /**
   * current store language
   */
  public $currentLanguage = 'en';
  
  /**
   * current store theme package
   */
  public $currentThemePackage = 'default';
  /**
   * current store theme
   */
  public $currentTheme = 'default';
  /**
   * current store name , this property will  init value with domain.
   */
  public $currentStore;
  
  
  /**
   *	Bootstrap:init website,  class property $currentLanguage ,$currentTheme and $currentStore.
   *  if you not config this ,default class property will be set.
   *  if current domain is not config , InvalidValueException will be throw. 
   *	class property $currentStore will be set value $domain.
   */
  public function bootstrap($app){
    $host = explode('://' ,$app->getHomeUrl());
    $stores = $this->stores;
    $init_compelte = 0;
    if(is_array($stores) && !empty($stores)){
      foreach($stores as $domain => $lang){
        if($host[1] == $domain){
          Yii::$app->store->currentStore = $domain;
          if(isset($lang['language']) && !empty($lang['language'])){
            Yii::$app->store->currentLanguage = $lang['language'];
          }
          if(isset($lang['theme']) && !empty($lang['theme'])){
            Yii::$app->store->currentTheme = $lang['theme'];
          }
          if(isset($lang['themePackage']) && !empty($lang['themePackage'])){
            Yii::$app->store->currentThemePackage = $lang['themePackage'];
          }
          /**
           * init store currency.
           */
          if(isset($lang['currency']) && !empty($lang['currency'])){
            $currency = $lang['currency'];
          }else{
            $currency = '';
          }
          
          Yii::$app->page->currency->initCurrency($currency);
          /**
           * current domian is config is store config.
           */
          $init_compelte = 1;
        }
      }
    }
    if(!$init_compelte){
      throw new InvalidValueException('this domain is not config in store component');
    }
    
    }
  
  /**
   * if a object or array  attribute is a store attribute, you can get current 
   * language value by this function.
   */
  public function getLangVal($attr,$attrName){
    return $attr[$this->currentLanguage."_".$attrName];
  }
  
  
  public function getAllLanguage(){
    
    
  }
  
  
}

上面通过相应的域名设置不同的语言,货币,模板等。

4.yii的yii\helpers\Url已经对我们不适合,我们需要自己写一个url的生成:

<?php
namespace fec\helpers;
use Yii; 
class CUrl
{
  public static $_baseHttpUrl;
  public static $_baseHttpsUrl;
  # 1.获取首页地址。
  public static function getHomeUrl(){
    return Yii::$app->getHomeUrl();
    //return Yii::$app->getBaseUrl(true);
  }
  # 2. 获取首页地址。同上
  public static function getBaseUrl($isHttps=false){
    if($isHttps){
      if(!self::$_baseHttpsUrl){
        self::$_baseHttpsUrl = str_replace('http','https',self::getHomeUrl());
      }
      return self::$_baseHttpsUrl;
    }else{
      if(!self::$_baseHttpUrl){
        self::$_baseHttpUrl = str_replace('https','http',self::getHomeUrl());
      }
      return self::$_baseHttpUrl;
    }
  }
  
  # 3.立即跳转  和 yii2的跳转还是不同
  public static function redirect($url,$isHttps=false){
    if($url){
      if(substr($url,0,4) != "http"){
        $url = self::getUrl($url,[],$isHttps);	
      }
      header("Location: $url");
      exit;
    }
  }
  
  # 4.通过模板name,得到对应文件路径。
  # 默认是 domain.com/skin/theme/下面的绝对URL
  public static function getSkinUrl($dir = '',$relative_path=false){
    $currentTheme = CConfig::getCurrentTheme();
    $url = '';
    if(!$relative_path){
      $url = self::getHomeUrl(). DIRECTORY_SEPARATOR;
    }
    return  $url.'skin'.DIRECTORY_SEPARATOR
        .$currentTheme.DIRECTORY_SEPARATOR
        .$dir;
  }
  
  #5. 通过url path 和参数  得到当前网站下的完整url路径。
  public static function getUrl($url_path,$params=array(),$isHttps=false){
    $url_path = trim($url_path,DIRECTORY_SEPARATOR);
    $url =  self::getBaseUrl($isHttps). DIRECTORY_SEPARATOR .$url_path;
    $str = "";
    if(!empty($params) && is_array($params)){
      $str .= "?";
      foreach($params as $k=>$v){
        $str .= $k."=".$v."&";
      }
      $str = substr($str,0,strlen($str)-1);
    }
    return $url.$str;
  } 
  
  # 6.得到当前的完整url
  public static function getCurrentUrl(){
    //$s =  self::getHomeUrl();
    //return $s.$_SERVER["REQUEST_URI"];
    return \yii\helpers\Url::current();
  }
  # 7.得到当前的完整url  no param
  public static function getCurrentUrlNoParam(){
    $url = self::getCurrentUrl();
    if(strstr($url,"#")){
      $url = substr($url,0,strpos($url,"#"));
    }
    
    if(strstr($url,"?")){
      $url = substr($url,0,strpos($url,"?"));
    }
    return $url;
    
  }
  
  # 8、得到url key   ,譬如  http://www.x.com/ss/dd/aa?aaaa=ddddd   返回 /ss/dd/aa
  public static function getUrlKey(){
    
    return Yii::$app->request->getPathInfo();
  }
  # 9.得到url    ,譬如  http://www.x.com/ss/dd/aa?aaaa=ddddd   返回 /ss/dd/aa?aaaa=ddddd   
  public static function getUrlKeyWithParam(){
    return Yii::$app->getRequest()->url;
  }
  
}

 

得到url:

CUrl::getUrl('/x/x/x',['p'=>'2]);

#将生成

http://fecshop.appadmin.fancyecommerce.com/fr/x/x/x?p=2

CUrl::getUrl('/x/x/x',['p'=>'2],rue);

#将生成

https://fecshop.appadmin.fancyecommerce.com/fr/x/x/x?p=2

OK,到这里就完成整个过程了。