yii2 fecshop 多模板的介绍

多模板系统参看的magento的模板系统,多份模板按照优先级叠加,譬如模板路径按照优先级存放到一个数组:

 

[
'@appfront/theme/base/default',
'@thrid/theme/act/theme01',
'@fecshop/app/appfront/theme/base/default',
]

当我去找一个view文件的时候,,譬如是 cms/home/index.php

会根据优先级,先查找

@appfront/theme/base/default/cms/home/index.php 文件是否存在,如果不存在,则去第二优先级的模板路径下面找

@thrid/theme/act/theme01/cms/home/index.php,如果文件不存在,则去第三优先级的模板路径下面查找

@fecshop/app/appfront/theme/base/default/cms/home/index.php ,直到找到文件为止,如果到最后还找不到文件,则会throw exception。

@fecshop/app/appfront/theme/base/default 这个是最后一个模板,这个模板是最全的底层模板,这个模板文件是不允许改动的,如果要对模板进行二次开发,可以在上层模板通过文件覆盖的方式进行改动。

当后续升级,@fecshop/app/appfront/theme/base/default里面的模板文件可能会有所改动,上层模板可以通过比对差异性,将升级的部分代码添加上去来解决。

通过多模板系统,用户可以通过在高级别的模板路径下面添加模板文件,而不需要修改 fecshop的模板文件,这样就做好了安全隔离,各自不会出现文件冲突,用户可以一直二次开发模板,fecshop根据功能的添加持续升级,相互干扰性降低到最小。

模板系统是由 Yii::@app->page->theme管理,在初始化的时候,当前用户local路径,第三方路径,fecshop会通过配置的方式添加到theme组件中,

fecshop\app\appfront\modules\AppfrontController 在init对theme组件进行了参数设置,重写了render方法,按照上面的模板优先级加载文件,只要您的controller集成了AppfrontController,就可以通过render方法,使用多模板系统。

除了在controller的render使用多模板外,还有独立块也是可以使用,使用独立块的方法,具体:如何使用yii2 fecshop的小部件widget

在上面的view文件,可以通过绝对路径的方式加载view,也可以使用多模板的方式加载view。

如何使用yii2 fecshop的小部件widget

首先需要说明的是,fecshop的小部件和yii2的不同,fecshop的小部件是由

Yii::$app->page->widget->render($configKey) 函数得到的内容

小部件的介绍:相当于个独立功能块,有2部分组成,1. 数据提供者block,2. view部分,view部分是html内容,一些动态数据需要从block中获取,通过数据提供部分和数据显示部分组成一个独立块。

小部件的功能:在电商网站,或者其他网站,我们会有一些块,在很多页面显示,譬如newsletter,rss,product view history等,可能在产品页面侧栏,分类页面搜索页侧栏面,账户中心侧栏等页面显示这个快,因此我引入了小部件的功能,通过配置直接添加,比较方面。

小部件实现的原理:分为3块,1.view文件和多模板路径,通过优先级依次匹配,直到文件存在,返回viewFile 。2. 通过配置里面的类以及方法,得到动态数据,数据格式为数组,3.通过view组件的renderFile函数,把view的路径和动态参数param,画出来最终的html。

小部件的使用:

1.添加配置:

在page组件中找到子组件widget

return [
  'page' => [
    'class' => 'fecshop\services\Page',
    //'terry' => 'xxxx',
    
    # 子服务
    'childService' => [
                'widget' => [
        'class' 		=> 'fecshop\services\page\Widget',
        # 定义默认方法,也就是widgetConfig 里面各个部件里面的method如果没有填写
        # 则使用该配置。
        # 'defaultObMethod' => 'getLastData',
        
        'widgetConfig' => [
          'menu' =>[
            # 必填
            'class' => 'fec\block\TestMenu',
            # view 的绝对路径配置方式
            'view'  => '@fec/views/testmenu/index.php',
            # 下面为选填
            'method'=> 'getLastData',
            'terry1'=> 'My1',
            'terry2'=> 'My2',
          ],
          'love' => [
            'class' => 'fecshop\app\appfront\modules\Cms\block\widget\Test',
            # 根据多模板的优先级,依次去模板找查找该文件,直到找到这个文件。
            'view'  => 'cms/home/test.php',
            'terry' => 'II',
          ]
        ]
        
      ],

上面定义了2个小部件,一个是menu,一个是love,

class是数据提供的类,method是返回数据的方法,view是html部分,其他的参数为class初始化传入类变量,

对于class 是和namesapces一致的

对于view 有2中方法,一种是加入@,譬如@fec,@vendor等,通过autoload查找绝对路径,另外一种就是通过多模板路径,通过多模板的优先级,依次到路径里面查找该view文件,最终返回view文件,因此,优先级低的view文件如果想更改,可以在高优先级的模板中新建这个文件即可(路径要对应好)。

下面以love举例:

新建class文件:

<?php
namespace fecshop\app\appfront\modules\Cms\block\widget;
use Yii;
use fecshop\app\appfront\modules\AppfrontController;
class Test 
{
  public $terry;
  # 网站信息管理
    public function getLastData()
    {
    return [
      'i'   	=> $this->terry,
      'love' 	=> 'loves',
      'you' 	=> 'terry',
    ];
  }
}



view文件内容:appfront/theme/terry/theme01/cms/home/test.php

love test
<?= "$i $love $you"  ?>

通过上面,我们在page-theme组件中加入了配置,新建了小部件的class和对应的方法和变量,view文件,下面我们就可以调用了

在controller中调用:

echo Yii::$app->page->widget->render('love');

可以看到页面输出:

love test II loves terry

我们在controller,view,layout等加入这个小部件,然后对应的html就会显示出来,还是蛮好用的。我们可以配置好各个小部件,有需要就直接添加,甚至给一个小部件做几种样式,在不同的页面调用。

 

 

 

 

yii2 fecshop 服务组件的详细介绍

yii2 fecshop 服务组件的详细介绍

在  vendor/fancyecommerce/fecshop/services路径下就是所有的fecshop组件,组件分为组件和子组件,组件本质上是yii2的组件,子组件的原理是组件的属性指向子组件对象,譬如Yii::$app->page是组件,Yii::$app->page->theme是子组件,组件的配置是在 vendor/fancyecommerce/fecshop/config/services里面,下面是一个配置组件和子组件的例子:

<?php
/**
 * FecShop file.
 *
 * @link http://www.fecshop.com/
 * @copyright Copyright (c) 2016 FecShop Software LLC
 * @license http://www.fecshop.com/license/
 */
return [
  'page' => [
    'class' => 'fecshop\services\Page',
    //'terry' => 'xxxx',
    
    # 子服务
    'childService' => [
      'breadcrumbs' => [
        'class' 		=> 'fecshop\services\page\Breadcrumbs',
        'homeName' 		=> 'Home',  # if homeName => '', Home will not show in breadcrums.
        'ifAddHomeUrl'	=> true,  	# default true, if set false, home will not add url (a).
        //'intervalSymbol'=> ' >> '	# default value:' > '
      ],
      
      'cms' => [
        'class' 		=> 'fecshop\services\page\Cms',
      ],
      'theme' => [
        'class' 		=> 'fecshop\services\page\Theme',
      ],
      'widget' => [
        'class' 		=> 'fecshop\services\page\Widget',
        # 定义默认方法,也就是widgetConfig 里面各个部件里面的method如果没有填写
        # 则使用该配置。
        # 'defaultObMethod' => 'getLastData',
        
        'widgetConfig' => [
          'menu' =>[
            # 必填
            'class' => 'fec\block\TestMenu',
            # view 的绝对路径配置方式
            'view'  => '@fec/views/testmenu/index.php',
            # 下面为选填
            'method'=> 'getLastData',
            'terry1'=> 'My1',
            'terry2'=> 'My2',
          ],
          'love' => [
            'class' => 'fecshop\app\appfront\modules\Cms\block\widget\Test',
            # 根据多模板的优先级,依次去模板找查找该文件,直到找到这个文件。
            'view'  => 'cms/home/test.php',
            'terry' => 'II',
          ]
        ]
        
      ],
      'currency' => [
        'class' => 'fecshop\services\page\Currency',
        'currencys' => [
          'USD' => [
            'rate' 		=> 1,
            'symbol' 	=> '$',
          ],
          'RMB' => [
            'rate' 		=> 6.3,
            'symbol' 	=> '¥',
          ],
        ],
        //'defaultCurrency' => 'USD',
      ],
      
      'footer' => [
        'class' 		=> 'fecshop\services\page\Footer',
       
      ],
      
      
      'newsletter' => [
        'class' 		=> 'fecshop\services\page\Newsletter',
      ],
      
      'staticblock' => [
        'class' 		=> 'fecshop\services\page\StaticBlock',
      ],
      
      'menu' => [
        'class' => 'fecshop\services\page\Menu',
        'displayHome' => [
          'enable' => true,
          'display'=> 'Home',
        ],
        /**
         *	custom menu  in the front menu section.
         */
        'frontCustomMenu' => [
          [
            'name' 		=> 'my custom menu',
            'urlPath'	=> '/my-custom-menu.html',
            'childMenu' => [
              [
                'name' 		=> 'my custom menu 2',
                'urlPath'	=> '/my-custom-menu-2.html',
              ],
              [
                'name' 		=> 'my custom menu 2',
                'urlPath'	=> '/my-custom-menu-2.html',
                'childMenu' => [
                  [
                    'name' 		=> 'my custom menu 2',
                    'urlPath'	=> '/my-custom-menu-2.html',
                  ],
                  [
                    'name' 		=> 'my custom menu 2',
                    'urlPath'	=> '/my-custom-menu-2.html',
                  ],
                ],	
              ],
            ],	
          ],
          [
            'name' 		=> 'my custom menu 2',
            'urlPath'	=> '/my-custom-menu-2.html',
          ],
        ],
        /**
         *	custom menu  behind the menu section.
         */
        'behindCustomMenu' => [
          [
            'name' 		=> 'my behind custom menu',
            'urlPath'	=> '/my-behind-custom-menu.html',
          ],
          [
            'name' 		=> 'my behindcustom menu 2',
            'urlPath'	=> '/my-behind-custom-menu-2.html',
          ],
        ],
      ],
      
    ],
  ],
];

上面是page组件的配置,childService里面是page组件的子组件配置,配置方法和组件类似。

在fecshop的底层,组件作为整个开源商城服务的支撑,组件服务为上层的modules提供数据,为各个应用提供数据,有点类似SOA架构的服务,

下面是目前的服务的列表:

Affiliate.php: 网站联盟组件服务

Blog.php:博客服务组件

Cart.php:购物车服务组件

Category.php:分类服务组件

Coupon.php:优惠券服务组件

Customer.php:用户账号服务组件

——–Customer子组件 – 地址:Address

——–Customer子组件 – 网盟:Affiliate

——–Customer子组件 – 优惠券:Coupon

——–Customer子组件 – 线下分销:DropShip

——–Customer子组件 – 收藏:Favorite

——–Customer子组件 – 面包屑导航:Breadcrumbs

——–Customer子组件 – 消息:Message

——–Customer子组件 – 订单:Order

——–Customer子组件 – 积分:Point

——–Customer子组件 – 评论:Review

——–Customer子组件 – 批发:Wholesale

Email.php:邮件服务组件

Order.php:订单服务组件

Page.php:页面等服务组件

——–Page子组件 – 面包屑导航:Breadcrumbs

——–Page子组件 – cms Page:cms

——–Page子组件 – 货币:Currency

——–Page子组件 – 页面底部:Footer

——–Page子组件 – 菜单:menu

——–Page子组件 – 邮件订阅:newsletter

——–Page子组件 – 静态块:Static Block

——–Page子组件 – 模板配置功能:theme

——–Page子组件 – 小部件:Widget

Payment.php:支付服务组件

point.php:积分服务组件

Product.php:产品服务组件

——–Product子组件 – 产品访问历史记录:ViewLog

————————–ViewLog子组件 – Db.php:Mysql 存储

————————–ViewLog子组件 – Mongodb.php:Mongodb 存储

————————–ViewLog子组件 – Session.php:Session 存储

——–Product子组件 – 热卖产品:BestSell

——–Product子组件 -买了的还买了:BuyAlsoBuy

——–Product子组件 – 产品分类:Category

——–Product子组件 – 产品集合:Coll

——–Product子组件 – 产品图片:Image

——–Product子组件 – 产品详细:Info

——–Product子组件 – 产品价格:Price

——–Product子组件 – 相关产品:Relate

——–Product子组件 – 产品评论:Review

——–Product子组件 -看了的还看了:ViewAlsoView

Request.php:Request服务组件

Search.php:搜索功能服务组件

Shipping:货运物流服务组件

Sitemap.php:Sitemap服务组件

Store.php:Store服务组件

Url.php:Url服务组件

Wholesale.php:批发功能服务组件

 

 

 

 

 

 

 

yii2 fecshop 的扩展开发

  1. 如何做一个基于composer的插件,来扩展fecshop。
  2. 如何通过yii2插件或者二次开发修改 重写yii2的功能
  3. 如何通过插件或者二次开发修改 重写fecshop service
    1. service列表
  4. 如何通过插件或者二次开发修改 重写fecshop module
    1. fecshop module 列表
  5. 如何通过插件或者二次开发修改fecshop module controller
    1. controller
  6. 如何做fecshop的模板?

上面是一些列表,找时间做深度解答。

yii2 fecshop 功能列表

fecshop的功能列表:

  1. 多入口机制,每一个入口添加一个域名,独立分开,譬
    1. demo.appadmin.fecshop.com  解析到 appadmin 应用.
    2. demo.appfront.fecshop.com    解析到 appfront   应用.
    3. demo.apphtml5.fecshop.com  解析到 apphtml5  应用.
    4. demo.appserver.fecshop.com 解析到 appserver 应用.
  2. 单入口单index.php 多域名,可以把多个域名解析到同一个入口,譬如为了实现多语言,我们把  en.appfront.fecshop , fr.appfront.fecshop , de.appfront.fecshop 都解析到appfront应用入口,然后通过不同的域名,加载不同的配置语言,和初始默认货币等,进而实现多语言。
  3. 单入口 多文件夹index.php 单域名,譬如将  www.fecshop.com解析到  appfront/web/index.php,然后新建
    1. appfront/web/fr/index.php
    2. appfront/web/es/index.php
    3. appfront/web/de/index.php

然后访问  www/fecshop.com   ,www/fecshop.com/fr ,www/fecshop.com/es ,www/fecshop.com/de,进行语言的切换。

4. 多语言  根据不同的store,加载不同的语言,也可以进行语言的切换

5.多货币,根据不同的store,加载不同的货币,也可以进行货币的切换

6.产品属性可配置化。分类选择一个属性组,用来做分类侧栏属性过滤 ,产品选择一个属性组,用来做属性的添加。最终在这个分类做属性过滤后,就会出现对应的属性产品。

7.组件化服务可配置,可重写

8.整体系统配置化,可以在不动fecshop源代码的情况下,修改任意功能。

9.多模板系统,通过各个模板的优先级顺序,加载view文件,如果在高级别的模板中文件不存在则依次到优先级低的模板中查找,直到找到view文件,  通过这种方式,高级别的模板如果想要重写低级别模板的某个文件,只要把这个文件复制到当前模板,路径对应好即可。

 

yii2 fecshop 框架特性

框架特性:

按照程序的执行顺序,大致分为4个层次:

1.app应用入口:首先通过nginx解析到appfront,appadmin,apphtml5等app层入口index文件,然后由index.php加载各个配置传递给application,这一层做的事情是配置的收集,组合,最终生成一个大的config数据传递给yii2 application,每一个app应用的配置文件是不同的,进而后面的各个层的执行都会不一样。

2.yii2初始化层,在第一步骤的config数组传递给yii2 application,就开始了yii2的初始化层,一直到执行conroller之前,也就是在做调度之前,都是yii2的初始化层,yii2系统执行的任务,设立涉及到很多对象的初始化,有yii2系统的,也有第三方添加的bootstrap部分,其中组件,模块都是可以添加bootstrap的,这一步骤发生在执行controller之前的初始化,也就是无论执行那个controller,都必须执行的部分。(注意,这里的bootstrap指的是yii2的初始化,而不是twitter 出的css框架 bootstrap)

3.Module层:在yii2初始化完成后,执行的就是模块层,这一层,里面有controller,block,view三层结构,controller仅仅负责调度,block负责解析前台传递的数据,调用相应的组件服务,产生相应的结果,对于content部分,是由controller接收,传递到view,也有直接使用block和view生成的html部分代码,

对于controller层和view层,都不会陌生,block层是我新加入的一个层,也就是把controller中对逻辑的处理,全部放到block层来完成,block像是肌肉层,负责连接,连接前台传递的数据,调用相应的组件服务,返回数据给controller,当然block也是某个小部件的数据提供者(独立功能块)。

view接收数据,view分为3部分,layout部分和view content部分,这个是yii2本身有的,另外还有通过render函数生成的独立块,独立块由block和view文件组成,将生成的html可以通过配置的方式加到很多页面,譬如产品访问记录块,在产品页面的侧栏,分类页面的侧栏可能都要显示,那么可以做成块的方式调用即可。

这一层是加入缓存最多的一层,甚至是整页缓存(full page cache),动态数据用ajax动态异步加载。

总之,module是一个大的调度层和数据显示层,本身不涉及到复杂的数据处理,负责的数据处理都是在组件层完成,在module层是不允许访问model层的,大致的流程为:解析前端请求,到后端的组件服务层提取数据,组织数据,结果传递给view,然后画出来整个页面。

4.组件服务层:在model层之上的,就是组件服务层,这一层面提供各种服务,服务的粒子大致为描述性粒度,譬如产品加入购物车,查看购物车的产品,生 成订单,删除购物车产品等,这些功能的操作都是通过组件的一个函数直接返回,有点类似于微服务结构里面的各个服务,不过fecshop的组件服务层是有状态的,另外,该层和上面的各个层是放到一个主机上面的,因为是直接通过组件服务进行调用方法,所以不可以分割到不同的主机。通俗来讲,组件服务层提供的是 一个描述性的操作粒度,为了快速响应,根据需求,这一层可能被加入缓存功能,总之,这一层面的操作,会涉及到多个model的 操作,最终成为一个描述性粒度的功能。

在举个例子:譬如产品加入购物车,假设我们要扣除库存,那么对应着操作:

当前用户是否登录,如果登录则…

验证传递的产品参数是否存在有效(前端传递数据的有效性),然后….

将产品数据加入到cart表中…

扣除库存…

等等,上面这一系列的操作,会涉及到很多表的查询和更新插入等操作,但是我们描述的粒度为  产品加入购物车,在数据库端要执行的所有操作,都是在一个描述粒度里面,后期可能加入很多其他方面的扩展,譬如判断是否有优惠券,是否使用积分等等,都会添加到这个粒度里面,但是对访问服务组件层的module来说,是透明的,无论服务层如何更改添加新功能,module还是按照之前的方式调用(当然有可能多传递一些参数,甚至返回的数据格式有变化)。

5.Model层:数据库操作层,分为mysql和mongodb,访问数据库层,所做的事情有:数据的查找,更新和插入时的数据验证,保存,数据的删除,以及其他一部分基于数据操作的函数等,这个层面提供的功能粒度比较小,是基于表操作的粒度,module不会添加事务,事务的控制在服务层添加。由组件服务层来组织。

OK,fecshop的框架大致如此。

 

 

 

yii2 fecshop的初衷

以下为做fecshop的初衷和fecshop的简介

1.关于对magento的情感

自从2010年进入外贸电商,就开始玩magento,magento一直以来,以seo友好,容易收录,功能强大,可配置性强,灵活度大,支持度高等各个方面完胜其他开源框架,基于xml的配置和基于EAV数据表模型,让magento的插件和模板异常的丰富,magento的插件,基本上可以在不修改源文件的前提,修改任意magento代码,当然,这些都是有代价的,magento的初始化必须加载完所有的xml文件才能进行解析controller,初始化过于笨重,自身的框架过于雍容,在速度,并发方面一直处于劣势,尤其是EAV模型的数据库模型,让magento在产品数据增多的情况下,一旦超过10000个产品后,性能就会带来一定的下降,当产品超过3,4万,性能明显下降,对mysql的内存消耗非常大,当插件增多的情况下,php端对xml解析对服务器的资源吃的也比较厉害。另外在产品很多,store很多的情况下,indexer索引也是硬伤,关于magneto的一些问题,我在知乎上面回答了一个问题:https://www.zhihu.com/question/19813425#zh-question-answer-wrap

总之,magento的灵活度高,解决了自身版本升级,第三方插件升级,和用户二次开发之间冲突的问题,通过配置的方式来协调使用那一块文件代码,magento除了自身功能完善,还有大量的第三方插件和模板,让magento这个闭环更加的完善,但是magento的并发性能较差,日ip超过2万的情况下,没有varnish,2台独立主机都很难平稳的抗住,另外,重构magento的某个底层也是比较困难,这些都是magento的不足。

2.对yii2的情感。

从2014年接触yii2,用yii2做了3个项目,一个电商网站,一个erp系统,一个业务数据分析系统,对yii2框架的认知加深后,越来越喜欢这个php框架,在架构方面非常的优秀,全程基于配置,用户可以在不修改yii2文件代码的前提下修改和扩展yii2框架的功能,单例模式的组件,等基于控制反转的思想设计而成,还有model的role验证,命令行模式的批处理console,类似于一个小系统的模块,多入口应用,可以新建一堆类似frontend,backend这样的入口应用,安全性高,可以通过bootstrap配置加入初始化代码,数据库升级migrate等,yii2是一个非常适合做产品的框架。

3.fecshop的想法

作为一个电商系统产品,我认为有两个大问题比较关键:

3.1 产品自身功能升级,和第三方插件扩展升级,和用户自身二次开发的矛盾的解决, 总体来说就是:各自在各自的文件系统中开发,通过配置的方式覆盖,yii2的配置是基于数据,而不是magento那类笨笨的xml,资源消耗小

3.2 对底层功能的重构和选择,对于一个底层的功能,譬如购物车,我前期用的是基于session,后期我发现不行,我需要吧购物车数据放到mysql中,后来我又想放到redis中(假设),如果我是标注的mvc模式,重构将非常的困难,因为对购物车的操作散步在各个地方,不知道那个地方会漏掉,因此,我们需要更改我们的模式,我想到的是用组件的形式提供服务,各个模块不允许调用数据库,只能通过对服务的调用来完成功能,如果我想重构某个服务,我只需要把这个服务组件里面所有的public方法实现即可,数据的流向为: model –>service component –>module->block->view

这样,我甚至可以对同一个功能做多个服务组件,譬如购物车,我做了session cart,mysql cart让用户选择。

糅合yii2的新型思维,mongodb的多维数组,mysql的事务性,mongodb的强大配置和灵活度,我想基于mysql和mongodb数据库,用yii2框架,参膜magento的灵活,实现一套电商开源系统,我给予的名字为fancy ecommerce shop,简称fecshop。

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,到这里就完成整个过程了。