<?php
/**
* MVC 主体功能介绍文档
*
* #路由
* 路由通过直接及简单的网址分析，映射到具体的控制器及其方法。为性能考虑，不提供路由类或路由方法的模式，也不提供正则匹配析构模式。它们都严重影响性能,尤其是存在大量规则时。 如果有特殊需要时，如:重定向。可以去使用 apache或nginx本身的重写规则，获得最佳性能。
* 
* http://localhost/admin/ 路由: [站点]/action/indexAction.php 下的 index 方法
* http://localhost/admin/user 路由: [站点]/action/indexAction.php 下的 user 方法
* http://localhost/admin/user/add 路由: [站点]/action/userAction.php 下的 add 方法
* http://localhost/admin/user/form/add 路由: [站点]/action/user/formAction.php 下的 add 方法
* 
* - 网址传参-纯值 (使用<strong style="color:blue">/v/</strong>间隔)， 类/方法/v/第1个参数/第2个数
* http://localhost/admin/test/demo/v/111/222 路由: [站点]/action/testAction.php 下的 demo 方法 并按顺序传两个参数
* 
* - 网址传参-键值对 (使用<strong style="color:blue">/k/</strong>间隔)， 类/方法/k/变量名1/变量名1的值/变量名2/变量名2的值
* http://localhost/admin/test/demo/k/a/111/b/222 路由: [站点]/action/testAction.php 下的 demo 方法 并传$a,$b两参数
* 
* - 网址传参-GET (使用<strong style="color:blue">?</strong>间隔)， 类/方法?a=111&b=111
* http://localhost/admin/test/demo?a=111&b=111 路由: [站点]/action/testAction.php 下的 demo 方法 并传$a,$b两参数
* 
* - 非网址传参 _post, php://input 参见 {@link \action} 基类说明
* 
* - 单页模式
*     - http://localhost/admin/test/demo5.html<br>
            路由: [站点]/action/testAction.php文件,但不需要提供 demo5 方法<br>
            将调用action基类的 _CallHtml 方法<br>
            如果test类想做其他控制，可以重载_CallHtml这个方法<br>
            _CallHtml将直接输出 [站点]/view/test/demo5.view.php模板视图
*     - http://localhost/admin/test/demo5.html/k/a/2/b/3 单页模式网址传参, 单页支持 /k/键值对及GET方式传参,不支持 /v/ 传参
            模板中使用 ```<?=$req['a']?>``` 获取对应键参数值
*     - http://localhost/admin/test/demo5.html?a=2&b=3 GET传参
            模板中使用 ```<?=$req['a']?>``` 获取对应键参数值
* 
* 路由控制
*      [工程]/config/redirect.php 中配置[非必须文件]
*      ```
*  return [
*      '\admin\action\index.index' => function(){
*          //mvc::$req 键值对参数
*          //mvc::$reqUriValues 值参数 
*          \mvc::$req['a']= time(); //修正参数
*          return '\admin\action\test\demo.demo4';//重定向
*          //return false;
*          //返回 false 则不更改路由,但可以修正参数或做流量统计,日志等
*      },
*  ];
*       ```
* 
* #配置文件
*     DEBUG 常量，调试开关，一般在[工程目录]/config/站点.php 中设置
*     [工程]/config/common.php  工程配置,主要配置mvc所在位置
*     [工程]/config/database.php 数据库配置
*     [工程]/config/providers.php 容器服务配置
*     [工程]/config/events.php 事件配置
*     [工程]/config/redirect.php 路由控制配置
*     [工程]/config/站点.php  站点配置 例: [工程]/config/admin.php 作用于 admin 站点
* 
* #工程目录结构
*     最佳:为每一个站点独立配置域名
*     例 admin.qg.com 指向 admin站点目录, www.qg.com 指向 home站点目录
*     最佳:files 上传的文件目录，也配置独立域名, 例 files.qg.com
*     如果只使用一个域名,将域名解析到工程目录上
*         在工程目录下添加 .htaccess,index.php 然后修改相关跳传规则
*     关于.htaccess， 重写规则在 httpd.conf 或 vhost.conf 中配置性能最佳
*     demo //样例[工程]目录
*         admin //样例[站点]后台目录
*             action //站点控制器，支持多级目录与命名空间, 文件后缀 .action.php
*             cache //站点缓存目录
*             en //多语言中的英文目录 [可选]
*                 .htaccess //apache重写规则
*                 index.php //语言版本入口文件
*             log //站点日志目录
*                 error //错误日志
*             static //静态文件目录
*                 css
*                 js
*                 images
*             view //视图目录,支持多级目录与命名空间, 文件后缀 .view.php
*             .htaccess //apache重写规则
*             index.php //站点入口文件
*         home //样例[站点]前台目录
*                 参见admin目录......
*         config //配置目录
*             common.php //工程通用配置
*             database.php //数据库配置
*             providers.php //容器配置
*             events.php 事件配置
*             redirect.php 路由控制配置
*             admin.php //admin站点配置
*             home.php //home站点配置
*         files //上传的文件目录
*         function //工程与站点通用函数目录
*             function.php //工程全局通用函数
*             admin.php //admin站点全局通用函数
*             home.php //home站点全局通用函数
*         locale //本地化与国际化目录
*             project //域目录
*                 language //语言目录
*         model //工程模型目录,各站点均可直接引用, 文件后缀 .model.php
*         tools //工具
* 
* #全局函数库
*     MVC自己本身提供了一些最基础需要的全局函数，如取多语言等
*     工程全局通用函数位置 [工程]/function/function.php
*     站点全局通用函数位置 [工程]/function/站点.php //例 demo/function/admin.php
*     注：全局函数，无论是MVC的还是工程及站点的都是使用的根命令空间，函数名不要重叠
* 
* #容器
*     容器定义类的依赖、构造参数、单例模式, 容器的绑定并不会创建实例，只有调用时才创建
*           如果对容器不太理解，可以去搜索 laravel 相关容器及服务提供者的介绍文档。
*     创建或获取一个对象 //即使容器没有绑定，一样要使用make获取一个新的对象,将来可能绑
*         make(类名); 例 $man = make(\model\test\man::class);
*         make(类名,参数); 例 $man = make(\model\test\man::class,['price'=>'35W']);
*         键值对传递参数不要求顺序
*         make中的参数值将覆盖容器里已经静态配置的参数值
*         虽然支持非键值对按顺序传参，但最好不要使用
*         非键值传参将影响这个类以后添加到容器绑定
*         注：//单例模式的类禁止再次传递新参数获取对象,只允许第一次调用时修正参数
* 
*      容器服务提供-业务逻辑,在 [工程]/config/providers.php 进行配置[非必须文件]
*         return [
*             \model\test\man::class => [  //要绑定的类
*                 'single'=>false, //是否为单例模式
*                 'params' => [ //创建时参数,需要遵循构造函数顺序
*                      [
*                          'class'=>\model\test\car::class, //依赖类,将自动注入
*                          'single'=>true,
*                          'params' => [
*                                [
*                                    'class'=>\model\test\ballJoint::class
*                                    ......//支持无限级依赖
*                                ]
*                                '宝马'
*                          ]
*                      ], 
*                      '30W'
*                 ]
*            ],
*         ];
*      容器服务提供-MVC,[工程]/config/common.php 有一些MVC内库的配置
*         'providers'=>[
*             \Overtrue\Pinyin\Pinyin::class => ['single'=>true, 'params'=>[]],
*             \lib\file::class => ['single'=>true, 'params'=>[]],
*             ......
*         ]
*
*      手动添加类到容器(生命周期为当前请求程序的周期，非持久化)
*           binding 函数
*           single 函数
*
*     ---样例 man 类---
*         namespace model\test;
*         class man
*         {
*             private $car;
*             private $price;
*             function __construct(\model\test\car $car, $price, $has=true){
*                $this->car = $car;
*                 $this->price = $price;
*                 $this->has = $has;
*             }
*             function info(){
*                 echo '<div>HAS: '. strval($this->has).'</div>'; //1
*                 echo '<div>PRICE: '.$this->price.'</div>'; //35W
*                 echo '<div>CAR NAME: '.$this->car->name().'</div>'; //宝马
*             }
*         }
*
*     ---样例 car 类(依赖类)---
*         namespace model\test;
*         class car
*         {
*             private $make;
*             function __construct($make='丰田'){
*                 $this->make = $make;
*             }
*             function name(){
*                 return $this->make;
*             }
*         }
*
* #事件机制
*     [工程]/config/events.php 配置事件[非必须文件]
*     注：事件里绑定的类的构造使用的是容器里配置
*     注：不要使用 类::class 来配置，因为它会直接反射，增加include的IO操作
*     注：异步事件在 调用时加 & 字符, 如  event('&log.error');
*     注：异步事件是异步开一个进程走路由的，参数使用的是网址传参
*         大字符串参数传递可能会超过网址最大限制
*     注：异步事件不支持链路;
*     注：异步事件会在 [站点]/cache/asynEvent 下前置产生文件，执行成功后删除;
*      return [
*        'sync'=>[ //同步事件
*            'log.error' => [
*             '\model\test\log.onError'=> null, //event item1
*             '\model\test\mail.send'=> null, //event item2 //支持多个不同处理
*            ], //event
*           'log.access' => [
*                 '\model\test\log.onAccess'=> null, //event item
*            ], //event
*            '\model\test\part.delete' => [ //用于当前类当前方法的配置,可以是后置配置
*                '\model\test\product.onPartDelete'=>[
*                '\model\test\log.onProductDelete'=> null, //支持链路处理
*                ], //event item
*            ], //event
*        ],
*        'asyn'=>[ //异步事件
*            'log.error' => 'index/sendmail', 
*            'log.access' => 'index/sendsms',
*        ],
*     ];
*     
*     被观察者调用事件
*         event('log.access'); //调用配置中同步事件名为 log.access的事件
*         event('log.*'); //调用配置中同步事件名为 log.开头的所有事件
*         event(); //调用配置中同步事件名为 当前类当前方法的事件
*         event('&log.access'); //加 & 表示异步调用配置中异步事件名为 log.access的事件
*         注：event()不管在没有传参数，都会去找当前类当前方法的事件
*             如果存在就会执行，不存在不会抛出异常，可以后置配置
*         注：event()如果有传参数，会判断参数中的事件是否存在,不存在将抛出一个异常
*         注：一个方法只能调用一次event函数,你可以将多个事件写在一个方法里
*         例  \model\test\part
*             function delete(){
*                 echo '<div>删除零件组类</div>';
*                 $this->logMessage = ['msg'=>'part.delete.log.msg'];
*                 event('log.*');
*             }
*     
*     观察者 例 \model\test\log.onError
*      function onError($subject) {
*          echo $this->get('logmsg');
*          echo '<br>写错误日志'.get_class($subject).PHP_EOL;
*      }
*     
*     参数传递 参考 answer 类
*     被观察者在事件中参数传递 $this->set('参数',值);
*     观察者获取参数, $this->get(参数名);
*         如果参数没有被定义，将抛出一个错误，如果不想抛出，可先用 exists 判断
*     注：参数是使用的公共静态变量池\mvc::$answerList，会一级级传递
*         后面的调用者如果使用 set 设置相同参数名会覆盖前面调用者的值
*     魔术变量
*         $object->___activeMethod 第一个被观察者调用类执行的是哪个方法,三下划线
* 
* #定时任务
*     MVC TOOLS下有一个 timer 目录，将其复制到[工程]目录下
*     需求：需要开启 php 的 popen 函数
*     一个定时器一个进程，定时器会另开一个异步进程启动任务(action)
*     每异步启动一次任务, 将结束当前定时器进程，重新开新的定时器进程等待
*     注: php timer.php stop 会将有锁标记的action任务也杀死，没有加锁的不会
*     配置文件
*         [工程]/timer/confg.php
*             return [
*                 //定时器名称 initSitePath 初始化站点目录,files,缓存目录,log目录等
*                 'initSiteDir' => [
*                     'interval'=>1, //每多少秒执行一次, 为 0 将不启动此定时器
*                     'site'=> 'admin', //哪个站点
*                     'action'=> 'index/initSiteDir', //路由,支持 /k/ 或 /v/ 传参
*                     'actionTimeLimit'=> 120, //路由执行一次任务的超时时间,0永不超时
*                              //注，这里不是作用于定时器,是作用于 action的
*                              //定时器自身没有超时概念，只有 runOnce,endTime
*                              //为0时,如果设置了锁，并超过了lockMaxTime，会被杀死
*                     'lock'=> true, //是否要加锁，防止任务还没有执行完成，又继续调用
*                     'lockMaxTime'=> 600, //(秒) 加锁的最大时间
*                              //超过这个时间，将解除旧锁，然后创建新锁
*                              //解除旧锁的同时会杀死旧的任务进程(如果它还存在)
*                              //0 为没有最大时间限制
*                     'runOnce'=> true, //true 只执行一次, false 将一直轮询执行
*                     'startTime'=> '', //开始于什么时间, 为0或空表示立即执行，
*                              //格式 2020-04-10 10:00:00
*                              //每秒判断一次，每隔30秒结束当前进程并启动一次新的进程
*                     'endTime'=> '', //结止于什么时间, 格式同上，为0或空永久
*                 ],
*                 //定时器名称
*                 'timer2' => [
*                     'interval'=>0,
*                     'site'=> 'admin',
*                     'action'=> 'index/timer/k/www/abc6',
*                     'actionTimeLimit'=> 120,
*                     'lock'=> false,
*                     'lockMaxTime'=> 600,
*                     'runOnce'=> false,
*                     'startTime'=> '',
*                     'endTime'=> '',
*                 ],
*             ];
*     控制台命令 [工程]/timer
*         php timer.php show 显示已开启的定时器,进程ID,及上次执行时间
*         php timer.php start 开启定时器,判断是否已有开启，有则不再重复开启
*         php timer.php restart 重启所有定时器
*         php timer.php restart timer1 重启timer1的定时器
*         php timer.php stop 关闭所有定时器 
*         php timer.php stop timer1 关闭timer1的定时器
*         注：关闭命令会将有锁标记的action任务也杀死，没有加锁的则不会
*     [工程]/timer/process 目录下，存有当前定时器进程相关动态内容
*
* #本地化
*     本地化内容位置 [工程]/locale/
*     本地化支持多域分开数据  [工程]/locale/域 默认一个 project域
*     系统默认使用PHP数组存储语言包，也可以无缝对接gettext使用内存语言包
*     注:php7后gettext包性能与使用数组包提升并不大
*     语言包位置 [工程]/locale/域/language/en.php
*     如果采用gettext,语言包位置 [工程]/locale/域/language/en.mo
*     包命名 zh_cn简体,zh_tw繁体,en英语,ru俄语,fr法语,es西班牙语,de德语,ja日语,ko韩语
*     为站点添加语言版本，在[站点]目录下创建一个 en 的目录，例 [工程]/admin/en
*     将 .htaccess,index.php 复制到上面目录中，并修改 $commonCfg['lang'] = 'en'
*     Nginx 增加一个 location /en { ...... } 的规则
*     MVC Tools可以辅助创建一个完整的[工程],里面有一个en模板
*     注：/en只是一个虚拟路径
*           相关目录操作都是使用的[站点]路径
*           唯一例外的是$cfg['path']['domain']领域网址路径(含有语言版本)
*           所以css里面要使用绝对路径，例  background: url("/static/images/logo.jpg")
*           相对路径只有默认语言版可以正常, 如果这个站点只有一个语言版本，可以不讲究
*     如果：某语言版本需要独立UI，可以为这个版本创建一个新的站点
* 
* #API帮助文档
*     注释规则使用 phpdoc 全量标准
*     官方文档 https://docs.phpdoc.org/latest/references/phpdoc/index.html
*     MVC生成文档 mvc版本目录/tools/phpDocumentor/phpDocumentor.bat
*     工程生成文档 [工程]/tools/phpDocumentor/站点.bat
*     分内部文档与供第三方文档 todo
* 
* #错误日志
*
* [非DEBUG] 错误日志将保存到 $cfg['dir']['log'] 配置的目录下的 error下， 日志文件按日分隔
*
* #错误代码
*      1=>'错误'
*      1001=>'数据类型有误'
*      1002=>'不存在'
*      1003=>'读失败'
*      1004=>'写失败'
*      
*      2000=>'执行数据库操作失败'
*      2001=>'提交的数据检验失败'
*      2002=>'出现重复的记录'
*      2003=>'超出范围'
*      2004=>'数据约束'
*
* #辅助调试
*
* 以下仅DEBUG模式下有效
*
* http://localhost/admin/xxx/xxx/phpinfo
* 以phpinfo结尾将打印phpinfo()信息及系统配置(数据库配置除外)
* 注：要在 /v/ 或 /k/ 之前
* 
* http://localhost/admin/test?runtime=1 传递runtime参数将在UI末尾显示执行时间,及内存使用情况, 并打印执行堆栈
* 
* #Appache 重写规则 .htaccess
*     RewriteEngine On
*     RewriteCond %{REQUEST_FILENAME} !-d
*     //如果文件存在，直接访问文件
*     RewriteCond %{REQUEST_FILENAME} !-f
*     RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f
*     //找不到实际路径的文件，统一交给index.php处理
*     RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L]
* 
* #Nginx 重写规则
*     location / {
*       if (!-e $request_filename) { //如果文件不存在
*         rewrite ^/(.*)$ index.php/$1 last;
*         break;
*       }
*     }
* 

## composer 第三方内库

- [解压缩 alchemy/zippy 0.4.9](https://packagist.org/packages/alchemy/zippy)
- [数据库助手 catfan/medoo v1.7.10,下载装](https://medoo.lvtao.net/doc.php)
- [缓存 desarrolla2/cache v3.0.0](https://packagist.org/packages/desarrolla2/cache)
- [HTTP操作 guzzlehttp/guzzle 6.5.2] [网址一](http://docs.guzzlephp.org/en/stable/) , [网址二](https://www.cnblogs.com/yehuisir/p/11114807.html)
- [图片处理 intervention/image 2.5.1](http://image.intervention.io)
- [CSV处理 league/csv 9.6.0](https://packagist.org/packages/league/csv)
- [加密解密 phpseclib/phpseclib 2.0.26](https://blog.csdn.net/u012560213/article/details/81114602)
- [文件目录处理 symfony/filesystem v4.4.7](https://symfony.com/doc/current/components/filesystem.html)
- [EXCEL操作 phpoffice/phpexcel  1.8.2](https://github.com/PHPOffice/PHPExcel/wiki/User%20Documentation)
- [汉字转拼音 overtrue/pinyin 4.0.5]
```php
$pinyin- = new \Overtrue\Pinyin\Pinyin();
$pinyin->abbr($name) 首字母
$pinyin->permalink($name,'-'); 全拼
```

## 全量配置
相关解释请参见 config/common.php, config/database.php, providers.php, config/站点.php, 站点/index.php
*
*		[
*			"mvcDir" => "E:/www/qgmvc/v1.0/mvc",
*			"short_open_tag" => false,
*			"lang" => "en",
*			"domain" => "/qgmvc/demo/admin/en",
*			"locale" => "project",
*			"project" => "demo",
*			"projectDir" => "E:/www/qgmvc/demo",
*			"site" => "admin",
*			"siteTitle" => "Demo Console",
*			"siteDir" => "E:/www/qgmvc/demo/admin",
*			"sitePath" => "/qgmvc/demo/admin",
*			"dir" => [
*				"mvc" => "E:/www/qgmvc/v1.0/mvc",
*				"project" => "E:/www/qgmvc/demo",
*				"locale" => "E:/www/qgmvc/demo/locale",
*				"files" => "E:/www/qgmvc/demo/files",
*				"site" => "E:/www/qgmvc/demo/admin",
*				"view" => "E:/www/qgmvc/demo/admin/view",
*				"static" => "E:/www/qgmvc/demo/admin/static",
*				"cache" => "E:/www/qgmvc/demo/admin/cache",
*				"log" => "E:/www/qgmvc/demo/admin/log"
*			],
*			"path" => [
*				"files" => "/qgmvc/demo/admin/../files",
*				"site" => "/qgmvc/demo/admin",
*				"static" => "/qgmvc/demo/admin/static",
*				"cache" => "/qgmvc/demo/admin/cache",
*				"log" => "/qgmvc/demo/admin/log"
*			],
*			"upload" => [
*				'type' => ['jpg','jpeg','png','gif','bmp','tif','psd','ai','cdr'
*					,'txt','csv','xml','json','sql'
*					,'zip','gz','rar'
*					,'doc','docx','xls','xlsx','pdf','dwg'
*				],
*				"size" => 2097152,
*				"bigSize" => 4294967296
*			],
*			"db" => [
*				"main_read" => [
*					"db_type" => "mysql",
*					"db_host" => "127.0.0.1",
*					"db_port" => "3306",
*					"db_name" => "money",
*					"db_user" => "root",
*					"db_pass" => "123456",
*					"db_charset" => "utf8"
*				],
*				"main_write" => [
*					"db_type" => "mysql",
*					"db_host" => "127.0.0.1",
*					"db_port" => "3306",
*					"db_name" => "money",
*					"db_user" => "root",
*					"db_pass" => "123456",
*					"db_charset" => "utf8"
*				]
*			],
*			"localeDefaultLanguage" => "zh_cn",
*			"class" => "\action\index",
*			"method" => "index",
*			"view" => "index/index"
*		]

*
## 性能优化
*    - 强烈建议:极致性能提升
*    - 建议:较佳的性能提升
*    - 一般:普通的性能提升
*    - 可忽略:极小的性能提升
*
*      - [强烈建议] 开启 PHP zend Opcache
*             - 命中率从高到低的include文件将会预编译化并驻留内存，减少IO与编译,默认存128M
*             - php.ini 中
*             - extension=php_opcache(windows)
*             - zend_extension=opcache(linux)
*             - [opcache]
*                 - opcache.enable=1
*                 - opcache.enable_cli=0
*                 - opcache.memory_consumption=128
*      - [建议] 国际化多语言包，修改成 gettext 模式
*      - [可忽略] 开启PHP短标签用于模板,php.ini中 short_open_tag = On
*              并设置config/common.php 中的 short_open_tag = true
*/

class _README{}