php 调试利器: debug_backtrace

 

debug_backtrace是一个bug工具,如果加入到某个类函数里面,那么可以看到调用这个函数的类方法,依次上推,直至index.php
下面是我打印的一个yii2组件的日志:

public function actionBootstrap($app){
    
    $d = debug_backtrace();
    foreach($d as $e){
      $function = $e['function'];
      $class = $e['class'];
      $file = $e['file'];
      $line = $e['line'];
      echo $file.'('.$line.'),'.
      $class.'::'.$function.'()<br/>';
    }
    echo '<br/><br/>';

输出的日志为:

(),fecshop\services\Store::actionBootstrap()
/www/web/develop/fecshop/vendor/fancyecommerce/fecshop/services/Service.php(58),::call_user_func_array()
/www/web/develop/fecshop/vendor/fancyecommerce/fecshop/components/Store.php(22),fecshop\services\Service::__call()
/www/web/develop/fecshop/vendor/fancyecommerce/fecshop/components/Store.php(22),fecshop\services\Store::bootstrap()
/www/web/develop/fecshop/vendor/yiisoft/yii2/base/Application.php(316),fecshop\components\Store::bootstrap()
/www/web/develop/fecshop/vendor/yiisoft/yii2/web/Application.php(66),yii\base\Application::bootstrap()
/www/web/develop/fecshop/vendor/yiisoft/yii2/base/Application.php(267),yii\web\Application::bootstrap()
/www/web/develop/fecshop/vendor/yiisoft/yii2/base/Object.php(107),yii\base\Application::init()
/www/web/develop/fecshop/vendor/yiisoft/yii2/base/Application.php(206),yii\base\Object::__construct()
/www/web/develop/fecshop/appfront/web/index.php(44),yii\base\Application::__construct()

这样就可以记录出来这个函数被调用的位置。

yii2 strace 追踪 某个执行的url

在日常的使用yii2,我们有时候想跟踪一下执行加载的各个文件,也就是说,某个url执行后,具体文件的加载,下面所以下具体的方法:

我们使用strace来跟踪url执行,下面是详细步骤:

1.需要更改一下yii2 的库包文件

vendor\yiisoft\yii2\web\Request.php文件 713行左右:

$scriptUrl = $this->getScriptUrl();  
        $baseUrl = $this->getBaseUrl();  
        if (strpos($pathInfo, $scriptUrl) === 0) {  
            $pathInfo = substr($pathInfo, strlen($scriptUrl));  
        } elseif ($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0) {  
            $pathInfo = substr($pathInfo, strlen($baseUrl));  
        } elseif (isset($_SERVER['PHP_SELF']) && strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) {  
            //$pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl));  
        } else {  
            throw new InvalidConfigException('Unable to determine the path info of the current request.');  
        }

需要注释掉这个,才能通过strace 追踪,不然会丢失 REQUEST_URI ,这段代码会把  $pathInfo 弄成空值。

譬如我要追踪的 url 为: http://10.10.10.252:610/wishorder/product/syncwish

HTTP_HOST=10.10.10.252:610 REQUEST_URI=/wishorder/product/syncwish    strace -s 600 -t -f -o  strace.txt  /usr/local/php/bin/php  /www/web/develop/erp2/backend/web/index.php   >> 22.thml    
-s 代表字符的最大长度,默认是32  
-o 追踪日志到这个文件

然后就可以追踪了,

譬如wish抓取订单api的一段log:(当然,上面的命令,你可以用grep过滤一下,譬如我想要看我执行的某个url,系统加载的文件)

3389  17:27:37 stat("/www/web/develop/erp2/common/lib/Wish/WishRequest.php", {st_mode=S_IFREG|0664, st_size=2967, ...}) = 0  
3389  17:27:37 lstat("/www/web/develop/erp2/common/lib/Wish/WishRequest.php", {st_mode=S_IFREG|0664, st_size=2967, ...}) = 0  
3389  17:27:37 lstat("/www/web/develop/erp2/common/lib/Wish", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0  
3389  17:27:37 lstat("/www/web/develop/erp2/common/lib", {st_mode=S_IFDIR|0777, st_size=4096, ...}) = 0  
3389  17:27:37 lstat("/www/web/develop/erp2/common", {st_mode=S_IFDIR|0777, st_size=4096, ...}) = 0  
3389  17:27:37 open("/www/web/develop/erp2/common/lib/Wish/WishRequest.php", O_RDONLY) = 6  
3389  17:27:37 fstat(6, {st_mode=S_IFREG|0664, st_size=2967, ...}) = 0  
3389  17:27:37 fstat(6, {st_mode=S_IFREG|0664, st_size=2967, ...}) = 0  
3389  17:27:37 fstat(6, {st_mode=S_IFREG|0664, st_size=2967, ...}) = 0  
3389  17:27:37 mmap(NULL, 2967, PROT_READ, MAP_SHARED, 6, 0) = 0x7fea6f2fb000  
3389  17:27:37 brk(0x2619000)           = 0x2619000  
3389  17:27:37 munmap(0x7fea6f2fb000, 2967) = 0  
3389  17:27:37 close(6)                 = 0  
3389  17:27:37 rt_sigaction(SIGALRM, NULL, {SIG_DFL, [], 0}, 8) = 0  
3389  17:27:37 rt_sigaction(SIGALRM, {0x3dd300ee40, [], SA_RESTORER, 0x37f96326a0}, NULL, 8) = 0  
3389  17:27:37 alarm(10)                = 0  
3389  17:27:37 rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0  
3389  17:27:37 socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP) = 6  
3389  17:27:37 close(6)                 = 0  
3389  17:27:37 socket(PF_NETLINK, SOCK_RAW, 0) = 6  
3389  17:27:37 bind(6, {sa_family=AF_NETLINK, pid=0, groups=00000000}, 12) = 0  
3389  17:27:37 getsockname(6, {sa_family=AF_NETLINK, pid=3389, groups=00000000}, [12]) = 0  
3389  17:27:37 sendto(6, "\24\0\0\0\26\0\1\3\t\364gV\0\0\0\0\0\0\0\0", 20, 0, {sa_family=AF_NETLINK, pid=0, groups=00000000}, 12) = 20  
3389  17:27:37 recvmsg(6, {msg_name(12)={sa_family=AF_NETLINK, pid=0, groups=00000000}, msg_iov(1)=[{"0\0\0\0\24\0\2\0\t\364gV=\r\0\0\2\10\200\376\1\0\0\0\10\0\1\0\177\0\0\1\10\0\2\0\177\0\0\1\7\0\3\0lo\0\0<\0\0\0\24\0\2\0\t\364gV=\r\0\0\2\30\200\0\2\0\0\0\10\0\1\0\n\n\n\374\10\0\2\0\n\n\n\374\10\0\4\0\n\n\n\377\t\0\3\0eth0\0\0\0\0\0\0\0\0h)\215n\352\177\0\0\0`\256n\352\177\0\0\2315\323n\352\177\0\0X\7a\3717\0\0\0Hu\322n\352\177\0\0\0\0\0\0\5\0\0\0\37\3\0\0\1\0\0\0\360\20\235\226\377\177\0\0005\0\0\0\0\0\0\0\0\355.o\352\177\0\0\340\21\235\226\377\177\0\0\10\22\235\226\377\177\0\0\250\351.o\352\177\0\0h)\215n\352\177\0\0O\333\235|\0\0\0\0\252\236\340\3707\0\0\0\0\0\0\0\0\0\0\0h)\215n\352\177\0\0\5\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\250\351.o\352\177\0\0\r\0\0\0\0\0\0\0\354X\322n\352\177\0\0\0\0\0\0\0\0\0\0\260\22\235\226\377\177\0\0\0\0\0\0\0\0\0\0\0\355.o\352\177\0\0\220\21\235\226\377\177\0\0\320\22\235\226\377\177\0\0\250\21\235\226\377\177\0\0@\340\322n\1\0\0\0\340\213\322n\352\177\0\0\2315\323n\352\177\0\0\250\351.o\352\177\0\0\0\0\0\0\0\0\0\0008)\215n\352\177\0\0\250\351.o\352\177\0\0Q\20\323n\352\177\0\0000\371\322n\352\177\0\0\310\213\322n\352\177\0\0\0\0\0\0\5\0\0\0\\\1\0\0\1\0\0\0000\371\322n\352\177\0\0@\340\322n\352\177\0\0\0\355.o\352\177\0\0000\207`\3717\0\0\0\0`\256n\352\177\0\0/www/web/develop/erp2/common/lib/Wish/WishRequest.php\0\0\0\0\0\0\0\0\0\0\0\0@\322n\352\177\0\0@\340\340\3707\0\0\0\5\0\0\0\377\177\0\0\0\0\0\0\0\0\0\0\1\0\0\0\352\177\0\0"..., 4096}], msg_controllen=0, msg_flags=0}, 0) = 108  
3389  17:27:37 recvmsg(6, {msg_name(12)={sa_family=AF_NETLINK, pid=0, groups=00000000}, msg_iov(1)=[{"@\0\0\0\24\0\2\0\t\364gV=\r\0\0\n\200\200\376\1\0\0\0\24\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\24\0\6\0\377\377\377\377\377\377\377\377\n\10\0\0\n\10\0\0@\0\0\0\24\0\2\0\t\364gV=\r\0\0\n@\200\375\2\0\0\0\24\0\1\0\376\200\0\0\0\0\0\0\326=~\377\376L\361B\24\0\6\0\377\377\377\377\377\377\377\377\353\10\0\0\353\10\0\0\2315\323n\352\177\0\0X\7a\3717\0\0\0Hu\322n\352\177\0\0\0\0\0\0\5\0\0\0\37\3\0\0\1\0\0\0\360\20\235\226\377\177\0\0005\0\0\0\0\0\0\0\0\355.o\352\177\0\0\340\21\235\226\377\177\0\0\10\22\235\226\377\177\0\0\250\351.o\352\177\0\0h)\215n\352\177\0\0O\333\235|\0\0\0\0\252\236\340\3707\0\0\0\0\0\0\0\0\0\0\0h)\215n\352\177\0\0\5\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\250\351.o\352\177\0\0\r\0\0\0\0\0\0\0\354X\322n\352\177\0\0\0\0\0\0\0\0\0\0\260\22\235\226\377\177\0\0\0\0\0\0\0\0\0\0\0\355.o\352\177\0\0\220\21\235\226\377\177\0\0\320\22\235\226\377\177\0\0\250\21\235\226\377\177\0\0@\340\322n\1\0\0\0\340\213\322n\352\177\0\0\2315\323n\352\177\0\0\250\351.o\352\177\0\0\0\0\0\0\0\0\0\0008)\215n\352\177\0\0\250\351.o\352\177\0\0Q\20\323n\352\177\0\0000\371\322n\352\177\0\0\310\213\322n\352\177\0\0\0\0\0\0\5\0\0\0\\\1\0\0\1\0\0\0000\371\322n\352\177\0\0@\340\322n\352\177\0\0\0\355.o\352\177\0\0000\207`\3717\0\0\0\0`\256n\352\177\0\0/www/web/develop/erp2/common/lib/Wish/WishRequest.php\0\0\0\0\0\0\0\0\0\0\0\0@\322n\352\177\0\0@\340\340\3707\0\0\0\5\0\0\0\377\177\0\0\0\0\0\0\0\0\0\0\1\0\0\0\352\177\0\0"..., 4096}], msg_controllen=0, msg_flags=0}, 0) = 128  
3389  17:27:37 recvmsg(6, {msg_name(12)={sa_family=AF_NETLINK, pid=0, groups=00000000}, msg_iov(1)=[{"\24\0\0\0\3\0\2\0\t\364gV=\r\0\0\0\0\0\0\1\0\0\0\24\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\24\0\6\0\377\377\377\377\377\377\377\377\n\10\0\0\n\10\0\0@\0\0\0\24\0\2\0\t\364gV=\r\0\0\n@\200\375\2\0\0\0\24\0\1\0\376\200\0\0\0\0\0\0\326=~\377\376L\361B\24\0\6\0\377\377\377\377\377\377\377\377\353\10\0\0\353\10\0\0\2315\323n\352\177\0\0X\7a\3717\0\0\0Hu\322n\352\177\0\0\0\0\0\0\5\0\0\0\37\3\0\0\1\0\0\0\360\20\235\226\377\177\0\0005\0\0\0\0\0\0\0\0\355.o\352\177\0\0\340\21\235\226\377\177\0\0\10\22\235\226\377\177\0\0\250\351.o\352\177\0\0h)\215n\352\177\0\0O\333\235|\0\0\0\0\252\236\340\3707\0\0\0\0\0\0\0\0\0\0\0h)\215n\352\177\0\0\5\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\250\351.o\352\177\0\0\r\0\0\0\0\0\0\0\354X\322n\352\177\0\0\0\0\0\0\0\0\0\0\260\22\235\226\377\177\0\0\0\0\0\0\0\0\0\0\0\355.o\352\177\0\0\220\21\235\226\377\177\0\0\320\22\235\226\377\177\0\0\250\21\235\226\377\177\0\0@\340\322n\1\0\0\0\340\213\322n\352\177\0\0\2315\323n\352\177\0\0\250\351.o\352\177\0\0\0\0\0\0\0\0\0\0008)\215n\352\177\0\0\250\351.o\352\177\0\0Q\20\323n\352\177\0\0000\371\322n\352\177\0\0\310\213\322n\352\177\0\0\0\0\0\0\5\0\0\0\\\1\0\0\1\0\0\0000\371\322n\352\177\0\0@\340\322n\352\177\0\0\0\355.o\352\177\0\0000\207`\3717\0\0\0\0`\256n\352\177\0\0/www/web/develop/erp2/common/lib/Wish/WishRequest.php\0\0\0\0\0\0\0\0\0\0\0\0@\322n\352\177\0\0@\340\340\3707\0\0\0\5\0\0\0\377\177\0\0\0\0\0\0\0\0\0\0\1\0\0\0\352\177\0\0"..., 4096}], msg_controllen=0, msg_flags=0}, 0) = 20  
3389  17:27:37 close(6)                 = 0  
3389  17:27:37 open("/etc/hosts", O_RDONLY|O_CLOEXEC) = 6  
3389  17:27:37 fstat(6, {st_mode=S_IFREG|0644, st_size=178, ...}) = 0  
3389  17:27:37 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fea6f2fb000  
3389  17:27:37 read(6, "127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4\n10.10.10.252 grande\n::1         localhost localhost.localdomain localhost6 localhost6.localdomain6\n", 4096) = 178  
3389  17:27:37 read(6, "", 4096)        = 0  
3389  17:27:37 close(6)                 = 0  
3389  17:27:37 munmap(0x7fea6f2fb000, 4096) = 0  
3389  17:27:37 open("/usr/local/lib/libnss_dns.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)  
3389  17:27:37 open("/usr/local/mysql/lib/libnss_dns.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)  
3389  17:27:37 open("/etc/ld.so.cache", O_RDONLY) = 6  
3389  17:27:37 fstat(6, {st_mode=S_IFREG|0644, st_size=50231, ...}) = 0  
3389  17:27:37 mmap(NULL, 50231, PROT_READ, MAP_PRIVATE, 6, 0) = 0x7fea663ec000  
3389  17:27:37 close(6)                 = 0  
3389  17:27:37 open("/lib64/libnss_dns.so.2", O_RDONLY) = 6  
3389  17:27:37 read(6, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\20\0\0\0\0\0\0@\0\0\0\0\0\0\0\340b\0\0\0\0\0\0\0\0\0\0@\0008\0\t\0@\0!\0 \0\6\0\0\0\5\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0\370\1\0\0\0\0\0\0\370\1\0\0\0\0\0\0\10\0\0\0\0\0\0\0\3\0\0\0\4\0\0\0\260D\0\0\0\0\0\0\260D\0\0\0\0\0\0\260D\0\0\0\0\0\0\34\0\0\0\0\0\0\0\34\0\0\0\0\0\0\0\20\0\0\0\0\0\0\0\1\0\0\0\5\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\254I\0\0\0\0\0\0\254I\0\0\0\0\0\0\0\0 \0\0\0\0\0\1\0\0\0\6\0\0\0\300M\0\0\0\0\0\0\300M \0\0\0\0\0\300M \0\0\0\0\0(\3\0\0\0\0\0\0008\3\0\0\0\0\0\0\0\0 \0\0\0\0\0\2\0\0\0\6\0\0\0\360M\0\0\0\0\0\0\360M \0\0\0\0\0\360M \0\0\0\0\0\320\1\0\0\0\0\0\0\320\1\0\0\0\0\0\0\10\0\0\0\0\0\0\0\4\0\0\0\4\0\0\0008\2\0\0\0\0\0\0008\2\0\0\0\0\0\0008\2\0\0\0\0\0\0D\0\0\0\0\0\0\0D\0\0\0\0\0\0\0\4\0\0\0\0\0\0\0P\345td\4\0\0\0\314D\0\0\0\0\0\0\314D\0\0\0\0\0\0\314D\0\0\0\0\0\0l\0\0\0\0\0\0\0l\0\0\0\0\0\0\0\4\0\0\0\0\0\0\0Q\345td\6\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\0\0\0\0\0\0\0R\345td\4\0\0\0\300M\0\0\0\0\0\0\300M \0\0\0\0\0\300M \0\0\0\0\0@\2\0\0\0\0\0\0@\2\0\0\0\0\0\0\1\0\0\0\0\0\0\0\4\0\0\0\24\0\0\0\3\0\0\0GNU\0v\214\234\260?\257fS\327\"\234\340-\264pw"..., 832) = 832  
3389  17:27:37 fstat(6, {st_mode=S_IFREG|0755, st_size=27424, ...}) = 0  
3389  17:27:37 mmap(NULL, 2117880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 6, 0) = 0x7fea65bca000  
3389  17:27:37 mprotect(0x7fea65bcf000, 2093056, PROT_NONE) = 0  
3389  17:27:37 mmap(0x7fea65dce000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 6, 0x4000) = 0x7fea65dce000  
3389  17:27:37 close(6)                 = 0  
3389  17:27:37 mprotect(0x7fea65dce000, 4096, PROT_READ) = 0  
3389  17:27:37 munmap(0x7fea663ec000, 50231) = 0  
3389  17:27:37 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 6  
3389  17:27:37 connect(6, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("8.8.8.8")}, 16) = 0  
3389  17:27:37 poll([{fd=6, events=POLLOUT}], 1, 0) = 1 ([{fd=6, revents=POLLOUT}])  
3389  17:27:37 sendto(6, "\370\23\1\0\0\1\0\0\0\0\0\0\10merchant\4wish\3com\0\0\1\0\1", 35, MSG_NOSIGNAL, NULL, 0) = 35  
3389  17:27:37 poll([{fd=6, events=POLLIN|POLLOUT}], 1, 5000) = 1 ([{fd=6, revents=POLLOUT}])  
3389  17:27:37 sendto(6, "\373\275\1\0\0\1\0\0\0\0\0\0\10merchant\4wish\3com\0\0\34\0\1", 35, MSG_NOSIGNAL, NULL, 0) = 35  
3389  17:27:37 poll([{fd=6, events=POLLIN}], 1, 4998) = 1 ([{fd=6, revents=POLLIN}])  
3389  17:27:37 ioctl(6, FIONREAD, [186]) = 0  
3389  17:27:37 recvfrom(6, "\373\275\201\200\0\1\0\1\0\1\0\0\10merchant\4wish\3com\0\0\34\0\1\300\f\0\5\0\1\0\0\1+\0:\37merchantfrontendproxy-842303458\tus-west-1\3elb\tamazonaws\300\32\300O\0\6\0\1\0\0\0;\0E\6ns-699\tawsdns-23\3net\0\21awsdns-hostmaster\6amazon\300\32\0\0\0\1\0\0\34 \0\0\3\204\0\22u\0\0\0\0<", 2048, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("8.8.8.8")}, [16]) = 186  
3389  17:27:37 poll([{fd=6, events=POLLIN}], 1, 4749

 

 

 

 

 

 

 

 

 

 

 

 

Yii2 一个隐藏的小坑,致使我的组件的bootstrap方法执行了多次。

昨天,我通过日志打印,发现我的store 组件的bootstrap在初始化的时候被莫名的执行了两次,日志如下:

store
(),fecshop\services\Store::actionBootstrap()
/www/web/develop/fecshop/vendor/fancyecommerce/fecshop/services/Service.php(58),::call_user_func_array()
/www/web/develop/fecshop/vendor/fancyecommerce/fecshop/components/Store.php(22),fecshop\services\Service::__call()
/www/web/develop/fecshop/vendor/fancyecommerce/fecshop/components/Store.php(22),fecshop\services\Store::bootstrap()
/www/web/develop/fecshop/vendor/yiisoft/yii2/base/Application.php(316),fecshop\components\Store::bootstrap()
/www/web/develop/fecshop/vendor/yiisoft/yii2/web/Application.php(66),yii\base\Application::bootstrap()
/www/web/develop/fecshop/vendor/yiisoft/yii2/base/Application.php(267),yii\web\Application::bootstrap()
/www/web/develop/fecshop/vendor/yiisoft/yii2/base/Object.php(107),yii\base\Application::init()
/www/web/develop/fecshop/vendor/yiisoft/yii2/base/Application.php(206),yii\base\Object::__construct()
/www/web/develop/fecshop/appfront/web/index.php(44),yii\base\Application::__construct()


store
(),fecshop\services\Store::actionBootstrap()
/www/web/develop/fecshop/vendor/fancyecommerce/fecshop/services/Service.php(58),::call_user_func_array()
/www/web/develop/fecshop/vendor/fancyecommerce/fecshop/components/Store.php(22),fecshop\services\Service::__call()
/www/web/develop/fecshop/vendor/fancyecommerce/fecshop/components/Store.php(22),fecshop\services\Store::bootstrap()
/www/web/develop/fecshop/vendor/yiisoft/yii2/base/Application.php(316),fecshop\components\Store::bootstrap()
/www/web/develop/fecshop/vendor/yiisoft/yii2/web/Application.php(66),yii\base\Application::bootstrap()
/www/web/develop/fecshop/vendor/yiisoft/yii2/base/Application.php(267),yii\web\Application::bootstrap()
/www/web/develop/fecshop/vendor/yiisoft/yii2/base/Object.php(107),yii\base\Application::init()
/www/web/develop/fecshop/vendor/yiisoft/yii2/base/Application.php(206),yii\base\Object::__construct()
/www/web/develop/fecshop/appfront/web/index.php(44),yii\base\Application::__construct()

最终找到了原因,步骤如下:

对于Yii2的方法:

yii\helpers\ArrayHelper::merge();

我们知道,对于数组中key为数字的部分,譬如:

yii\helpers\ArrayHelper::merge(['store','view'],['log','store']);
合并后的结果为 ['store','log','view','store'] ,而不是 ['store','log','view']

对于yii2的bootstrap,我写了一个store组件,然后,没有注意到,在两个地方加入了这个配置:

'bootstrap' => ['store'],

bootstrap的执行代码在:

yii\base\Application的bootstrap()方法中,大约298行出的代码:

foreach ($this->bootstrap as $class) {
            $component = null;
            if (is_string($class)) {
                if ($this->has($class)) {
                    $component = $this->get($class);
                } elseif ($this->hasModule($class)) {
                    $component = $this->getModule($class);
                } elseif (strpos($class, '\\') === false) {
                    throw new InvalidConfigException("Unknown bootstrapping component ID: $class");
                }
            }
            if (!isset($component)) {
                $component = Yii::createObject($class);
            }

            if ($component instanceof BootstrapInterface) {
                Yii::trace('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
                $component->bootstrap($this);
            } else {
                Yii::trace('Bootstrap with ' . get_class($component), __METHOD__);
            }
        }

我加了下打印,

foreach ($this->bootstrap as $class) {
     echo $class.'<br/>';
           $component = null;
           if (is_string($class)) {
               if ($this->has($class)) {
                   $component = $this->get($class);
               } elseif ($this->hasModule($class)) {
                   $component = $this->getModule($class);
               } elseif (strpos($class, '\\') === false) {
                   throw new InvalidConfigException("Unknown bootstrapping component ID: $class");
               }
           }
           if (!isset($component)) {
               $component = Yii::createObject($class);
           }

           if ($component instanceof BootstrapInterface) {
               Yii::trace('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
               $component->bootstrap($this);
           } else {
               Yii::trace('Bootstrap with ' . get_class($component), __METHOD__);
           }
       }

然后在调用组件的地方:

然后在Store组件的bootstrap方法中加入

public function bootstrap($app){
    $d = debug_backtrace();
    foreach($d as $e){
      $function = $e['function'];
      $class = $e['class'];
      $file = $e['file'];
      $line = $e['line'];
      echo $file.'('.$line.'),'.
      $class.'::'.$function.'()<br/>';
    }
    echo '<br/><br/>';

通过debug_backtrace(),进行打印输出:

结果如下:

debug
gii
store
(),fecshop\services\Store::actionBootstrap()
/www/web/develop/fecshop/vendor/fancyecommerce/fecshop/services/Service.php(58),::call_user_func_array()
/www/web/develop/fecshop/vendor/fancyecommerce/fecshop/components/Store.php(22),fecshop\services\Service::__call()
/www/web/develop/fecshop/vendor/fancyecommerce/fecshop/components/Store.php(22),fecshop\services\Store::bootstrap()
/www/web/develop/fecshop/vendor/yiisoft/yii2/base/Application.php(316),fecshop\components\Store::bootstrap()
/www/web/develop/fecshop/vendor/yiisoft/yii2/web/Application.php(66),yii\base\Application::bootstrap()
/www/web/develop/fecshop/vendor/yiisoft/yii2/base/Application.php(267),yii\web\Application::bootstrap()
/www/web/develop/fecshop/vendor/yiisoft/yii2/base/Object.php(107),yii\base\Application::init()
/www/web/develop/fecshop/vendor/yiisoft/yii2/base/Application.php(206),yii\base\Object::__construct()
/www/web/develop/fecshop/appfront/web/index.php(44),yii\base\Application::__construct()


store
(),fecshop\services\Store::actionBootstrap()
/www/web/develop/fecshop/vendor/fancyecommerce/fecshop/services/Service.php(58),::call_user_func_array()
/www/web/develop/fecshop/vendor/fancyecommerce/fecshop/components/Store.php(22),fecshop\services\Service::__call()
/www/web/develop/fecshop/vendor/fancyecommerce/fecshop/components/Store.php(22),fecshop\services\Store::bootstrap()
/www/web/develop/fecshop/vendor/yiisoft/yii2/base/Application.php(316),fecshop\components\Store::bootstrap()
/www/web/develop/fecshop/vendor/yiisoft/yii2/web/Application.php(66),yii\base\Application::bootstrap()
/www/web/develop/fecshop/vendor/yiisoft/yii2/base/Application.php(267),yii\web\Application::bootstrap()
/www/web/develop/fecshop/vendor/yiisoft/yii2/base/Object.php(107),yii\base\Application::init()
/www/web/develop/fecshop/vendor/yiisoft/yii2/base/Application.php(206),yii\base\Object::__construct()
/www/web/develop/fecshop/appfront/web/index.php(44),yii\base\Application::__construct()

发现我的store 组件确实被执行了2次,

原因就是,\appfront\config\fecshop_local.php加入了配置:对store组件的配置

return [
  'modules'=>$modules,
  'bootstrap' => ['store'],
    'services' => $services,
];

在另外一个地方,我也加入了配置:

'modules'=>$modules,
  /* only config in front web */
  'bootstrap' => ['store'],
  'params'	=> [
    /* appfront base theme dir   */
    'appfrontBaseTheme' 	=> '@fecshop/app/appfront/theme/base/front',
    'appfrontBaseLayoutName'=> 'main.php',
  ],

造成store组件的bootstrap被执行了两次,

不知道为什么yii2,不在这里执行一次数组的array_unique方法,

不然配置乱了,在很多地方配置了bootstrap方法,但是又没有注意到,尤其是bootstrap()方法在每次初始化的时候都要执行,造成额外开销,这个小坑,还是得通过打印$config的方式查看。

 

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.在入口文件,将对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 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);
    
  }
  
}

4. 在配置中添加配置:

[

'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 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 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 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采用了这种实现方式。

yii2 多模板路径优先级加载view方式下- js和css 的解决

fecshop 使用了多模板view文件优先级加载,在view和layout文件比较容易解决

比较难弄的是js和css部分,用yii2提供的yii\web\AssetBundle; 做了很多尝试都没有解决,最后自己写了一个来最终实现多模板下   js和css,也根据模板路径优先级下载。

首先说一下多模板路径,下面有多个模板路径,按照优先级依次如下:

@appfront/theme/terry/theme01/

@fecshop/theme/base/front/

@fecshop/theme/base/base/

如果我要加载的view文件为   cms/index/index,  首先查看  @appfront/theme/terry/theme01/cms/index/index.php文件是否存在,如果不存在,则查看@fecshop/theme/base/front/cms/index/index.php,如果还不存在,则看文件@fecshop/theme/base/base/cms/index/index.php 是否存在,如果不存在则报错返回。

上面是多模板的思路,为了是解决view文件,在fecshop系统模板升级和用户二次开发模板的矛盾冲突。

现在我想要css和js也这样,通过优先级加载css和js,

下面是我的实现:定义组件配置  :

      
      'asset' => [
        'class' =>  'fecshop\services\page\Asset',
        # js config
        'jsOptions'	=> [
          # js config 1
          [
            'options' => [
              'position' =>  'POS_END',
            //	'condition'=> 'lt IE 9',
            ],
            'js'	=>[
              'js/jquery-3.0.0.min.js',
              'js/js.js',
            ],
          ],
          # js config 2
          [
            'options' => [
              'condition'=> 'lt IE 9',
            ],
            'js'	=>[
              'js/ie9js.js'
            ],
          ],
        ],
        # css config
        'cssOptions'	=> [
          # css config 1.
          [
            'css'	=>[
              'css/style.css',
              'css/ie.css',
            ],
          ],
          
          # css config 2.
          [
            'options' => [
              'condition'=> 'lt IE 9',
            ],
            'css'	=>[
              'css/ltie9.css',
            ],
            
          ],
          
        ],
        
        //'cssOptions' => [
        //	'condition'
        //],
        //'jsOptions' => [
        //	'position' =>  'POS_END',
        //]
        
      ],

2. 组件部分:

<?php
/**
 * FecShop file.
 *
 * @link http://www.fecshop.com/
 * @copyright Copyright (c) 2016 FecShop Software LLC
 * @license http://www.fecshop.com/license/
 */
namespace fecshop\services\page;
use Yii;

use yii\base\Component;
use yii\helpers\ArrayHelper;
use yii\helpers\Url;

/**
 * Breadcrumbs services
 * @author Terry Zhao <2358269014@qq.com>
 * @since 1.0
 extends AssetBundle
 */
class Asset extends Component
{
  public $cssOptions;
  public $jsOptions; 
  /**
   * 在模板路径下的相对文件夹。
   * 譬如模板路径为@fecshop/app/theme/base/front
   * 那么js,css路径默认为@fecshop/app/theme/base/front/assets
   */
  public $defaultDir = 'assets';
    /**
   * 文件路径默认放到模板路径下面的assets里面
   */
  public function register($view){
    $assetArr = [];
    $themeDir = Yii::$app->page->theme->getThemeDirArr();
    if( is_array($themeDir) && !empty($themeDir)){
      if( is_array($this->jsOptions) && !empty($this->jsOptions)){
        foreach($this->jsOptions as $jsOption){
          if( isset($jsOption['js']) && is_array($jsOption['js']) && !empty($jsOption['js'])){
      
            foreach($jsOption['js'] as $jsPath){
              foreach($themeDir as $dir){
                $dir = $dir.'/'.$this->defaultDir.'/';
                $jsAbsoluteDir = $dir.$jsPath;
                if(file_exists($jsAbsoluteDir)){
                    $assetArr[$dir]['jsOptions'][] = [
                    'js' 		=>  $jsPath,
                    'options' 	=>  $this->initOptions($jsOption['options']),
                  ];
                  break;
                }
              }
            }
          }
        }	
      }
      
      if( is_array($this->cssOptions) && !empty($this->cssOptions)){
        foreach($this->cssOptions as $cssOption){
          if( isset($cssOption['css']) && is_array($cssOption['css']) && !empty($cssOption['css'])){
            foreach($cssOption['css'] as $cssPath){		
              foreach($themeDir as $dir){
                $dir = $dir.'/'.$this->defaultDir.'/';
                $cssAbsoluteDir = $dir.$cssPath;
                if(file_exists($cssAbsoluteDir)){
                  $assetArr[$dir]['cssOptions'][] = [
                    'css' 		=>  $cssPath,
                    'options' 	=>  $this->initOptions($cssOption['options']),
                  ];
                  break;
                }
              }
            }
          }
        }	
      }
    }
    if(!empty($assetArr)){
      foreach($assetArr as $fileDir=>$as){
        $cssConfig = $as['cssOptions'];
        $jsConfig = $as['jsOptions'];
        $publishDir = $view->assetManager->publish($fileDir);
        if(!empty($jsConfig) && is_array($jsConfig)){
          foreach($jsConfig as $c){
            $view->registerJsFile($publishDir[1].'/'.$c['js'],$c['options']);
          }
        }
        if(!empty($cssConfig) && is_array($cssConfig)){
          foreach($cssConfig as $c){
            $view->registerCssFile($publishDir[1].'/'.$c['css'],$c['options']);
          }
        }
      }
    }
  }
  
  
  public function initOptions($options){
    if(isset($options['position'])){
      if($options['position'] == 'POS_HEAD'){
        $options['position'] =  \yii\web\View::POS_HEAD;
      }else if($options['position'] == 'POS_END'){
        $options['position'] =  \yii\web\View::POS_END;
      }
    }
    return $options;
  }
}












上面的 Yii::$app->page->theme->getThemeDirArr() 表示得到多模板路径数组。

在layout文件中使用

\Yii::$app->page->asset->register($this);

 

经过测试,可以在多个模板路径的assets下面查找js,找到后返回,找不到,继续到下一个模板路径中找js或者css文件。最后发布。

 

 

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

 

 

 

 

 

 

yii2 页面功能块配置实现原理(前端+后端提供数据类),以及Views深度嵌套

在用yii2做大型网站的时候,尤其是做产品的时候,譬如做电商系统,在很多页面的侧栏,或者内容部分的底部等部分,我们希望通过配置的方式,很容易的把某个块加入,譬如在分类页面限制客户的浏览产品记录和newsletter,在产品页面,我们同样想显示这些,我们可能做出来很多这样的显示独立块,让客户在后台通过配置的方式就可以加入这些块,我们需要考虑的如下

  1. 这些块由两部分组成,数据提供者block部分,view html部分,通过block提供的动态数据,画出整个页面。
  2. 当前功能块可能包含其他的功能块,也就是多个功能块嵌套。
  3. 实现上述功能,我们就可以做出来很多独立功能块,在各个页面很容易的配置。下面是实现的方法

我的yii2 fec扩展已经实现这个功能,github地址:https://github.com/fancyecommerce/yii2-fec

下面讲述的是实现原理。

  1. 这个是实现的原理文件:
<?php
namespace fec\helpers;
use Yii;
use yii\base\View;
use yii\base\InvalidConfigException;
class CView
{	
  # 功能块:
  # 本功能的作用是通过一个类文件,和一个view 文件,生成一个html块,而且在这个html中还可以嵌套其他的块
  # 这样设计的好处:譬如在前端,我们在很多url中都会有一些公用的侧栏块,我们很希望我们的块可以通过配置的方式很容易的加入到侧栏
  # 譬如电商网站侧栏的:客户的浏览记录,我们在多个页面都想加入这个功能,我们就可以很方便的做加入。
  
  # 默认的方法,功能块的提供数据的默认方法。可以在配置中配置method方法自定义。
  const DATA_METHOD = 'getLastData';
  
  /*  你可以在view 文件中通过下面的方式使用
    <?php

      use fec\helpers\CView;
      $config = [
        # 必填
        'class' => 'fec\block\TestMenu',
        'view'  => '@fec/views/testmenu/index.php',
        # 下面为选填
        'method'=> 'getLastData',
        'terry1'=> 'My1',
        'terry2'=> 'My2',
      ];
      echo CView::getChildHtml($config)
      ?>
  */
    public static function getChildHtml($config)
    {
    if(!isset($config['class']) || empty($config['class'])
    || !isset($config['view']) || empty($config['view'])
    ){
      throw new InvalidConfigException('view and class must exist in array config!');
    }
    $method = self::DATA_METHOD;
    if(isset($config['method']) && !empty($config['method'])){
      $method = $config['method'];
      unset($config['method']);
    }
    $view = $config['view'];
    unset($config['view']);
    $ob = Yii::createObject($config);
    $params = $ob->$method();
    return Yii::$app->view->render($view, $params);
    
    }
  # 通过配置
  /*
  1.add config param to modules params or application params.
  params.php
  [
    'params' => [
      'block' =>[
        'menu' =>[
          # 必填
          'class' => 'fec\block\TestMenu',
          'view'  => '@fec/views/testmenu/index.php',
          # 下面为选填
          'method'=> 'getLastData',
          'terry1'=> 'My1',
          'terry2'=> 'My2',
        ],
      ]
    ]
  ]
  2.
  use fec\helpers\CView;
  CView::getConfigChildHtml('menu');
  */
  public static function getConfigChildHtml($configKey){
    $config = [];
    # get config from module param 
    if($module = Yii::$app->controller->module){
      $module_config = CModule::param("block");
      if(isset($module_config[$configKey])){
        $config = $module_config[$configKey];
      }
    }
    # if module config param is empty or not exist,
    # get config from application
    if(empty($config)){
      $app_config = CConfig::param("block");
      if(isset($app_config[$configKey])){
        $config = $app_config[$configKey];
      }
    }
    
    if(!isset($config['class']) || empty($config['class'])
    || !isset($config['view']) || empty($config['view'])
    ){
      throw new InvalidConfigException('view and class must exist in array config!');
    }else{
      return self::getChildHtml($config);
    }
    
  }
}

2.新建功能块的block文件:

<?php
namespace fec\block;
use Yii;

class TestMenu
{
  public $terry1;
  public $terry2;
  
    public function getLastData()
    {
        $arr = [
      'terry1' =>$this->terry1,
      'terry2' =>$this->terry2,
    ];
        return $arr;
    }
}

3.新建view显示部分:

<div>My Name is
<?php

  echo $terry1."_".$terry2;
?>

</div>

4.在其他的view文件中调用:

<?php

use fec\helpers\CView;
$config = [
  'class' => 'fec\block\TestMenu',
  'view'  => '@fec/views/testmenu/index.php',
  # 下面为选填
  'method'=> 'getLastData',
  'terry1'=> 'My111',
  'terry2'=> 'My222',
];
echo CView::getChildHtml($config);
?>

 

也就是通过上面的配置数组指定block的提供者, view文件的实现文件,也就是上面的步骤 2 和3,method为选填,如果不填写,默认为getLastData函数,terry1,terry2为初始化提供变量,然后我们就可以看到输出了

My Name is My111_My222

5.我们通过配置的方式调用CView::getConfigChildHtml()函数

5.1 加入module配置:(在report 配置中)

<?php
return [
  'report' => [
    'class' => 'appadmin\code\Report\Module',
    
    'components'=>[
      'mycomponent' => [
        'class' => 'appadmin\component\MyComponent',
        'terry' => 'xxxx',
      ],
    ],
    
    'params' => [
      'water' => 'good',
      'block' =>[
        'menu' =>[
          # 必填
          'class' => 'fec\block\TestMenu',
          'view'  => '@fec/views/testmenu/index.php',
          # 下面为选填
          'method'=> 'getLastData',
          'terry1'=> 'My1',
          'terry2'=> 'My2',
        ],
      ]
    ],
  ],
];

当然,您如果不使用模块,可以在params.php中直接加入block的配置部分:

'params' => [
      'water' => 'good',
      'block' =>[
        'menu' =>[
          # 必填
          'class' => 'fec\block\TestMenu',
          'view'  => '@fec/views/testmenu/index.php',
          # 下面为选填
          'method'=> 'getLastData',
          'terry1'=> 'My1',
          'terry2'=> 'My2',
        ],
      ]
    ],

如果application 和module同时配置,那么在当前module下面,module下面的配置有限,

使用:

<?php

use fec\helpers\CView;

echo CView::getConfigChildHtml('menu')
?>
My Name is My1_My2

总结:这个功能是可以深度嵌套,譬如在view1.php调用了child -> menu,

在menu的view文件中又调用了child–>product ,…..,深入嵌套,这是魅力所在。