目录

1. Introduction   1.1

2. CmlPHP简介   1.2

3. 初始化一个项目   1.3

4. 入口文件   1.4

5. 项目目录说明   1.5

6. 开发指引   1.6

1. 配置   1.6.1

1. normal.php   1.6.1.1

2. common.php   1.6.1.2

3. plugin.php   1.6.1.3

4. route.php   1.6.1.4

5. 其它配置项   1.6.1.5

6. 配置的获取   1.6.1.6

7. 动态修改配置   1.6.1.7

2. URL模式   1.6.2

3. 路由   1.6.3

4. 控制器   1.6.4

1. 前置方法   1.6.4.1

5. 请求、响应   1.6.5

1. Input   1.6.5.1

2. Request   1.6.5.2

3. Response   1.6.5.3

4. Cookie   1.6.5.4

5. Session   1.6.5.5

6. 模型-MySQL   1.6.6

1. 模型的调用方式   1.6.6.1

2. 数据查询   1.6.6.2

3. 分页查询   1.6.6.3

4. 数据添加   1.6.6.4

5. 获取上一次插入的id   1.6.6.5

6. 数据更新   1.6.6.6

7. 获取上一次更新影响的行数   1.6.6.7

8. 数据删除   1.6.6.8

9. 简单的单字段自增(多字段参考update)   1.6.6.9

10. 简单的单字段自减(多字段参考update)   1.6.6.10

11. 聚合操作   1.6.6.11

12. 事务   1.6.6.12

13. 调用存储过程   1.6.6.13

14. 原生/子查询/union   1.6.6.14

15. 快捷方法   1.6.6.15

1. 通过某字段值获取数据   1.6.6.15.1

2. 添加数据   1.6.6.15.2

3. 通过某字段值更新数据   1.6.6.15.3

4. 通过字段值删除数据   1.6.6.15.4

5. 获取总数   1.6.6.15.5

6. 获取列表   1.6.6.15.6

7. 注意事项   1.6.6.15.7

16. 同时查询多个库   1.6.6.16

7. 模型-MongoDB   1.6.7

1. Orm详细Api   1.6.7.1

8. 服务   1.6.8

9. 视图   1.6.9

1. 统一的API   1.6.9.1

2. Html模板引擎   1.6.9.2

3. Blade模板引擎   1.6.9.3

10. 锁-并发处理   1.6.10

11. 缓存   1.6.11

12. 语言包   1.6.12

13. 日志   1.6.13

14. 调试   1.6.14

15. Session自定义保存位置   1.6.15

16. 框架自带的扩展包   1.6.16

17. 插件   1.6.17

1. 使用说明   1.6.17.1

2. 系统挂载点   1.6.17.2

18. 常用常量   1.6.18

19. 命令行运行程序   1.6.19

20. 守护工作进程   1.6.20

21. 队列服务   1.6.21

22. 权限管理   1.6.22

23. 数据验证   1.6.23

24. 验证码   1.6.24

25. 文件上传   1.6.25

26. 数据库迁移   1.6.26

7. 部署   1.7

1. 伪静态配置   1.7.1

1. Apache   1.7.1.1

2. Nginx   1.7.1.2

2. 静态资源管理   1.7.2

8. 安全   1.8

1. 防注入   1.8.1

2. 用户数据输入   1.8.2

3. csrf   1.8.3

4. 加密解密   1.8.4

9. 常见问题   1.9

1. 模板中不同的url_model模式下传参给后端 ? &符混乱   1.9.1

10. 服务高可用   1.10

11. 升级日志   1.11

CmlPHP v2.x开发手册

本教程是针对CmlPHP开发框架的V2.x系列版本的开发使用教程。

本教程离线版下载:pdf、mobi、epub

CmlPHP主页:cmlphp.com

CmlPHP v2.x Api手册:api.cmlphp.com

CmlPHP简介

CmlPHP是基于php5.3+(v2.7开始要求php5.4+)版本开发的MVC/HMVC/MVSC/HMVSC框架,支持composer、分布式数据库、分布式缓存(异常自动容灾)，支持文件、memcache、redis、apc等缓存，支持多种url模式、URL路由[RESTful]，支持多项目集成、第三方扩展、支持插件。

CmlPHP在Model层做了缓存集成，开发者无需关注数据缓存的问题，按照相应的API调用即可获得最大性能。从而从根本上避免了新手未使用缓存，或缓存使用不当造成的性能不佳的问题。也杜绝了多人协同开发缓存同步及管理的问题

CmlPHP支持根目录、子目录，单入口、多入口部署、支持独立服务器、虚拟主机、VPS等多种环境，绝大部分开发环境可直接运行，无需配置伪静态规则(部分低版本server只要修改框架URL配置即可，框架会自动处理)，快速上手开发。线上环境对SEO有要求时再配置伪静态即可。

初始化一个项目

为了方便开发人员初始化框架所需的目录结构及文件，cmlphp提供了项目推荐目录骨架(demo项目)。按照readme下载即可。

项目目录骨架包含了一个web项目演示。如果您开发的是api接口项目，可以在下载完项目推荐目录骨架(demo项目)后。再下载cmlphp api项目示例。

入口文件

从v2.7开始在入口文件中可灵活配置各个目录组成，以下为一个标准的入口文件内容。

<?php

//应用入口

use Cml\Cml;

//定义项目根目录

define('CML_PROJECT_PATH', dirname(__DIR__));

//载入composer自动加载文件

$loader = require '../vendor/autoload.php';

//配置目录组成

Cml::setApplicationDir([

'secure_src' => CML_PROJECT_PATH . DIRECTORY_SEPARATOR . 'proj3e9840c31203003edlxa75b4',

'app_config_path_name' => 'Config',//自动载入配置用

'app_lang_path_name' => 'Lang',//自动载入语言用

'app_view_path_name' => 'View',//渲染模板用

'app_controller_path_name' => 'Controller',//路由根据请求url映射控制器用

'app_static_path_name' => 'Resource', //静态资源目录名

]);

//根据上面配置的目录，配置其它目录

Cml::setApplicationDir([

'apps_path' => Cml::getApplicationDir('secure_src').'/Application',//应用存放目录其下可能有多个应用

'global_config_path' => Cml::getApplicationDir('secure_src').'/'.Cml::getApplicationDir('app_config_path_name'),//全局配置文件目录

'global_lang_path' => Cml::getApplicationDir('secure_src').'/'.Cml::getApplicationDir('app_lang_path_name'),//全局语言文件目录

'global_store_path' => Cml::getApplicationDir('secure_src').'/Runtime',//运行时文件目录

'runtime_cache_path' => Cml::getApplicationDir('secure_src').'/Runtime' . DIRECTORY_SEPARATOR . 'Cache',//运行时模板缓存 、文件缓存目录

'runtime_logs_path' => Cml::getApplicationDir('secure_src').'/Runtime' . DIRECTORY_SEPARATOR . 'Logs',//运行时日志文件目录

]);

$loader->setPsr4('', Cml::getApplicationDir('apps_path'));

//注入服务并运行应用

//要注意的是这边只是做绑定并没有真正实例化

Cml::runApp(function() {

//必须绑定。系统错误及异常捕获机制 如果想使用第三方的服务只要简单封装一个服务。实现\Cml\Interfaces\ErrorOrException接口即可

Cml::getContainer()->singleton('cml_error_or_exception', \Cml\ErrorOrException::class);

//Cml::getContainer()->singleton('cml_error_or_exception', \Cml\Service\Whoops::class);//Whoops封装服务使用前请安装Whoops. composer require filp/whoops

//必须绑定。环境解析。自带的服务实现development/product/cli三种。可以根据需要实现更多的环境

Cml::getContainer()->singleton('cml_environment', \Cml\Service\Environment::class);

//必须绑定。系统日志驱动 内置\Cml\Logger\File::class｜\Cml\Logger\Redis::class两种.

//自定义服务实现\Cml\Interfaces\Logger接口即可或继承\Cml\Logger\Base再按需重载

Cml::getContainer()->singleton('cml_log', \Cml\Logger\File::class);

//必须绑定。路由

//框架自带的路由支持restful分格的路由、路由分组。 在未声明/未匹配到路由规则时会按url映射到文件的方式来执行相应的控制器方法。具体参考 http://doc.cmlphp.com/devintro/route/readme.html。

//如果想使用第三方的路由只要简单封装一个服务。实现\Cml\Interfaces\Route接口即可

Cml::getContainer()->singleton('cml_route', \Cml\Service\Route::class);

//Cml::getContainer()->singleton('cml_route', \Cml\Service\FastRoute::class); //composer require nikic/fast-route

//开发模式必须绑定。Debug调试信息

//如果想使用第三方的调试台只要简单封装一个服务。实现\Cml\Interfaces\Debug接口即可/当然直接修改模板也可以。配置项 'debug_page' => CML_CORE_PATH.'/Tpl/debug.tpl', // debug调试信息模板

Cml::getContainer()->singleton('cml_debug', \Cml\Debug::class);

//可选，队列服务 内置 \Cml\Queue\Redis::class.(内置的redis服务与缓存挂钩)参考 http://doc.cmlphp.com/devintro/quenue.html

//自定义服务实现\Cml\Interfaces\Queue接口即可或继承\Cml\Queue\Base再按需重载

Cml::getContainer()->singleton('cml_queue', \Cml\Queue\Redis::class);

//可选，锁服务 内置\Cml\Lock\File::class｜\Cml\Lock\Redis::class｜\Cml\Lock\Memcache::class三种.

//内置的redis锁跟/memcache锁 跟缓存服务挂钩。参考 http://doc.cmlphp.com/devintro/lock.html

//自定义服务实现\Cml\Interfaces\Lock接口即可或继承\Cml\Lock\Base再按需重载

Cml::getContainer()->singleton('cml_lock', \Cml\Lock\Redis::class);

//可选。绑定要用到视图引擎内置以下5种 以view_为前缀，用的时候不用带前缀如使用view_html视图服务：             \Cml\View::getEngine('html');

//\Cml\View::getEngine();不传类型的时候，使用的引擎可在配置文件中配置 'view_render_engine' => 'Html'默认为view_html

//自定义服务实现\Cml\Interfaces\View接口即可或继承\Cml\View\Base再按需重载

Cml::getContainer()->singleton('view_html', \Cml\View\Html::class);

Cml::getContainer()->singleton('view_json', \Cml\View\Json::class);

// Cml::getContainer()->singleton('view_blade', \Cml\Service\Blade::class); //blade模板引擎，使用前安装依赖。composer require linhecheng/cmlphp-ext-blade

//Cml::getContainer()->singleton('view_excel', \Cml\View\Excel::class);

//Cml::getContainer()->singleton('view_xml', \Cml\View\Xml::class);

//可选，db 允许多种驱动同时使用。因同种数据库可能同时连多个.这边不使用单例绑定.内置 \Cml\Db\MySql\Pdo::class｜\Cml\Db\MongoDB\MongoDB::class 两种数据库支持.

//自定义数据库驱动实现\Cml\Interfaces\Db接口即可或继承\Cml\Db\Base再按需重载

Cml::getContainer()->bind('db_mysql', \Cml\Db\MySql\Pdo::class);

Cml::getContainer()->bind('db_mongodb', \Cml\Db\MongoDB\MongoDB::class);

//可选，cache 允许多种驱动同时使用。如即使用memcache又使用redis.有使用数据库时至少要启用一种缓存,因同种缓存可能同时连多个.这边不使用单例绑定。

// 内置 \Cml\Cache\Redis::class｜\Cml\Cache\File::class | \Cml\Cache\Memcache::class ｜ \Cml\Cache\Apc::class 四种缓存支持.

//自定义缓存驱动实现\Cml\Interfaces\Cache接口即可或继承\Cml\Cache\Base再按需重载

Cml::getContainer()->bind('cache_redis', \Cml\Cache\Redis::class);

Cml::getContainer()->bind('cache_file', \Cml\Cache\File::class);

//Cml::getContainer()->bind('cache_memcache', \Cml\Cache\Memcache::class);

//Cml::getContainer()->bind('cache_apc', \Cml\Cache\Memcache::class);

});

项目目录说明

v2.7开始在入口文件中可灵活配置各个目录组成，以下为推荐的目录结构

下载框架项目推荐目录骨架,执行 composer udpate (建议),可看到如下结构：

projxxxxxxxxx 目录是项目所在根目录。

proj后加上一串加密串目的是为了防止当其路径存放在站点目录下时恶意用户通过工具猜测到项目目录，获取到模板文件及log文件，很多采用开源php框架的站点就常常被发现这个问题,当然最好是将 projxxxxx 目录部署到站点根目录的上一级，即站点根目录配置到 projxxx/public 目录.

开始开发前前先修改这个目录名、修改完将index.php中 CML_APP_PATH 相应修改即可

下面展开对projxxxxxxxxx目录做详细说明:

proxxx/Application目录

Application 目录为整个项目的应用存放目录,假定我们有一个项目，该项目下有 web 、 admin 、 api 三个应用，则目录下就有  web 、 admin 、 api 三个应用目录，这边提供默认的 web 应用目录。

web

proxxx/Application/web  目录为当前项目下的其中一个应用。一个项目下可能有多个应用。

- web/Config  为web应用配置存放目录,web应用中独立的配置存放在这个目录下.

- web/Controller  为应用控制器存放的路径我们在其下面看到了 DefaultController.php 文件以下为文件的内容

<?php namespace web\Controller;

use Cml\Controller;

use Cml\View;

class DefaultController extends Controller

{

public function index()

{

echo '欢迎使用cml框架,应用初始化成功';

}

}

关于命名空间及其它参考控制器章节

- web/Lang   可选  为web应用语言包存放目录具体请参考 语言包章节

- web/Model   可选  为web应用模型文件存放目录 具体请参考模型章节

- web/Resource   可选  为web应用静态文件存放目录,应用下所有的静态资源都存放在这个目录下如：js、css、sass等

- web/Plugin   可选  为web应用插件存放目录 具体请参考 插件章节

- web/Service   可选  为web应用服务存放目录

- web/View   可选  这web应用存放模板文件目录

proxxx/Config目录

Config 目录即为项目全局配置文件存放目录，框架会自动识别当前运行的环境是 开发/cli(计划任务下)/正式环境，读取相应目录下的配置文件。下面对 Config 目录下的文件做详细说明

自v2.7.2起。 cli/development/product 这三个环境的判断组件也是可拨插替换的。 也就是说假如你觉得 cli/devolopment/product 不够用，想分成按照域名加载配置文件。 如 http://dev.com 从dev.com 目录取配置。 http://pro.com 从 pro.com 取配置。自己实现一个服务即可。修改入口中下面的配置为自已实现的服务.

//必须绑定。环境解析。自带的服务实现development/product/cli三种。可以根据需要实现更多的环境

Cml::getContainer()->singleton('cml_environment', \Cml\Service\Environment::class);

cli

proxxx/Config/cli  为通过命令行运行的时候加载的配置文件存放目录

development

proxxx/Config/development  为开发环境加载的配置文件存放目录

product 为正式环境加载的配置文件存放目录

proxxx/Config/product  为正式环境加载的配置文件存放目录

cli/development/product  这三个目录下都会默认生成一个 normal.php 的配置文件，该文件为主配置文件即框架会自动加载，具体请查看 配置文件章节

common.php

common.php  为公用配置文件，也就是无论是cli还是product或者是development的环境都会自动加载的配置文件

plugin.php

plugin.php  为插件配置目录，也是cli/product/development共用 关于插件请参看 插件章节

route.php

route.php  为路由配置，也是cli/product/development共用 关于路由的使用请参看 路由章节

proxxx/Runtime目录

Runtime 目录为运行时目录，该目录下存放了系统运行时的一些临时文件，下面做详细说明

Cache

该目录该目录下存放了模板渲染后的缓存及系统缓存使用文件缓存时生成的缓存文件

Data

框架会对数据库的表字段结构缓存在此目录下，所以线上环节如果有修改了主键名，请清空该目录

Logs

系统在开发环境会把警告以下的错误直接显示在控制台，致命错误/异常直接显示在页面上方便开发人员排查。如果是线上环境会将警告及以上的信息记录在此目录下的emergency.log中(底层是调用\Cml\Log::emergency)。按日期分目录存放(如20160801的日志则存放在  Runtime/Logs/2016/08/01/emergency.log 中)，方便排查，同时使用 \Cml\Log::xxx`相关方法记录的日志会分类存放在该目录下。具体可参考日志

public目录 (可选)

该目录为站点对外目录,在服务器配置方便修改的情况下入口文件也在这个目录下，在框架推荐的分应用模式下,上线后通过系统命令会将 projxxx 下的所有应用下  Resource目录 映射到  public 下。详情查看 静态资源管理

开发指引

本章节主要从开发的各个模块讲解 CmlPHP 的使用。帮忙开发人员快速、高效的使用 CmlPHP 开发项目。

配置

在项目目录说明 下的 Config章节说过，在 cli 、 development 、 product 这三种环境中框架加载的配置文件是分开的，在这边详细说明加载规则

cli

cli  顾名思义为命令行运行环境

在cli环境下 框架默认载入的是  Config/cli/normal.php  这个通用配置文件

development

development  顾名思议为开发环境

当我们的开发网址为  localhost 、 127.0.0.1 、 192.xxx.xxx.xxx （即以192开头的地址）、 example.loc (即域名的后缀为.loc)、 example.dev (即域名的后缀为.dev。【chrome更新到63.0.xxxx之后.dev的域名被加入了 HSTS preload, 这就意味着在chrome上，我们只能通过https的方式访问.dev的站点，所以开发测试不建议使用.dev了】) 、 example.test (即域名的后缀为.test。v2.8.0新增)这几类域名时框架会自动识别当时环境为开发环境，默认加载  Config/development.php  这个通用配置文件

product

product  即除了 cli 和 development 之外的域名框架识别为正式环境,默认加载  Config/normal.php  这个通用配置文件

自定义配置加载实现

自v2.7.2起。 cli/development/product 这三个环境的判断组件也是可拨插替换的。 也就是说假如你觉得 cli/devolopment/product 不够用，想分成按照域名加载配置文件。 如 http://dev.com 从dev.com 目录取配置。 http://pro.com 从 pro.com 取配置。自己实现一个服务即可。修改入口中下面的配置为自已实现的服务.

//必须绑定。环境解析。自带的服务实现development/product/cli三种。可以根据需要实现更多的环境

Cml::getContainer()->singleton('cml_environment', \Cml\Service\Environment::class);

normal.php

normal.php有两个。一个在项目根目录下的 Config 中,另外一个在 Application/web[应用名]/Config 中

项目根目录下的 normal.php

'default_db' => [ //默认的数据库连接串

'driver' => 'MySql.MySql', //数据库驱动

'master' => [

'host' => 'localhost', //数据库主机

'username' => 'root', //数据库用户名

'password' =>'', //数据库密码

'dbname' => 'cmlphp', //数据库名

'charset' => 'utf8', //数据库编码

'tableprefix' => 'cml_', //数据表前缀

'pconnect' => false, //是否开启数据库长连接

'engine'=>''//数据库引擎

],

'slaves'=> [], //从库配置

//查询数据缓存时间，表数据有变动会自动更新缓存。设置为0表示表数据没变动时缓存不过期。

//这边设置为3600意思是即使表数据没变动也让缓存每3600s失效一次,这样可以让缓存空间更合理的利用.

//如果不想启用缓存直接配置为false

'cache_expire' => 3600,

],

// 缓存服务器的配置

'default_cache' => [

'on' => 0, //为1则启用，或者不启用

'driver' => 'Memcache',

'prefix' => 'd_c_57c17_',

'server' => [

[

'host' => '127.0.0.1',

'port' => 11211

],

//多台...

],

],

'lock_prefix' => 'd_l_57c17_', //加锁前缀

在 projxxx/Config/[cli/development/product]/normal.php 中包含了开发会用到的常用配置，包换数据库连接串、缓存配置、锁配置等。

而在projxxx/Application/web/Config章节中我们说过。web应用中独立的配置存放在目录 projxxx/Application/web/Config 下。所以我们如果有一些应用特殊的配置只要配置在 projxxx/Application/web/Config/normal.php 中即可。建议是缓存、数据库连接等特殊的配置都配置在上层normal.php中，只有一些应用特殊的配置才配置在应用下的normal.php中

下面列出框架所有惯例配置，如有需要修改复制相应的值到 normal.php 中即可

<?php

/* * *********************************************************

* [cmlphp] (C)2012 - 3000 http://cmlphp.com

* @Author  linhecheng<linhechengbush@live.com>

* @Date: 14-2-21 下午2:23

* @version  @see \Cml\Cml::VERSION

* cmlphp框架惯例配置文件

* *********************************************************** */

return [

//调试模式  默认关闭

'debug' => false,

'db_fields_cache' => true, //在debug模式实时获取字段列表，线上模式是否开启数据库字段缓存到文件。自v2.6.3起。开启本功能主要用于获取主键缓存

'time_zone' => 'PRC', //时区

//数据库配置

'default_db' => [

'driver' => 'Pdo', //数据库驱动

'master' => [

'host' => 'localhost', //数据库主机

'username' => 'root', //数据库用户名

'password' => '', //数据库密码

'dbname' => 'cmlphp', //数据库名

'charset' => 'utf8mb4', //数据库编码

'tableprefix' => 'sun_', //数据表前缀

'pconnect' => false, //是否开启数据库长连接

'engine' => ''//数据库引擎

],

'slaves'=>[],

//查询数据缓存时间，表数据有变动会自动更新缓存。设置为0表示表数据没变动时缓存不过期。

//这边设置为3600意思是即使表数据没变动也让缓存每3600s失效一次,这样可以让缓存空间更合理的利用.

//如果不想启用缓存直接配置为false

'cache_expire' => 3600,

//是否记录执行慢的sql语句。只针对mysql有效。这个不能长期开，只能要分析sql性能的时候比如只开个5会钟有一定的log后就应该关掉。默认为false。

//为整形的时候则为执行时间大于这个时间的sql都记录到log下 比如配置为1则为执行超过1s的sql都记录到 notice.log里

'log_slow_sql' => false,

],

/**

使用MongoDB

'db_mongo' => [

'driver' => 'MongoDB', //数据库驱动

'master' => [

'host' => 'localhost:27017',

'username' => '',

'password' => '',

'dbname' => 'test',

//'replicaSet' => '' //replicaSet名称

],

'slaves'=>[],

],

**/

// 缓存服务器的配置

'default_cache' => [

'on' => 0, //为1则启用，或者不启用

'driver' => 'Memcache',

'prefix' => 'cml_',

'server' => [

[

'host' => '127.0.0.1',

'port' => 11211,//必须是整形

//'weight' => 100 //权重memcached扩展有效

],

//多台...

],

],

/**

//文件缓存

'default_cache' => [

'on' => 0, //为1则启用，或者不启用

'driver' => 'File',

'prefix' => 'cml_'

],

//apc缓存

'default_cache' => [

'on' => 0, //为1则启用，或者不启用

'driver' => 'Apc',

'prefix' => 'cml_'

],

//Redis缓存

'default_cache' => [

'on' => 0, //为1则启用，或者不启用

'driver' => 'Redis',

'prefix' => 'cml_',

'server' => [

[

'host' => '127.0.0.1',

'port' => 6379,

//'pconnect' => false //默认使用长连接

//'db' => 6

//'password' => '123456' //没有密码的时候不要配置

],

//多台...

],

//'back' => [//当server中有机器挂掉且back有开启时。会自动使用back来替换挂掉的server方便处理异常情况

//    'host' => '127.0.0.1',

//    'port' => 6379

//]

],

*/

/*模板设置*/

'view_render_engine' => 'html',//默认的视图渲染引擎，html/excel/json/xml

'default_charset' => 'utf-8', // 默认输出编码

'http_cache_control' => 'private', // 网页缓存控制

'output_encode' => true, // 页面压缩输出

/*Html引擎配置。只适用于html模板引擎*/

'html_theme' =>'', //默认只有单主题

'html_template_suffix' => '.html',     // 默认模板文件后缀

'html_left_deper' => '{{', //模板左定界符

'html_right_deper' => '}}', //模板右定界符

/*系统模板定义*/

'html_exception' => CML_CORE_PATH.'/Tpl/cmlException.tpl', // 默认成功跳转对应的模板文件

'404_page' => CML_CORE_PATH.'/Tpl/404.tpl', // 404跳转页

'debug_page' => CML_CORE_PATH.'/Tpl/debug.tpl', // debug调试信息模板

/* URL设置 */

'url_model' => 1,       // URL访问模式,可选参数1、2、3,代表以下四种模式：

// 1 (PATHINFO 模式显示index.php); 2 (PATHINFO 不显示index.php); 3 (兼容模式)  默认为PATHINFO 模式，提供最好的用户体验和SEO支持

'url_pathinfo_depr' => '/', // PATHINFO模式下，各参数之间的分割符号

'url_html_suffix' => '.html',  // URL伪静态后缀设置

'url_default_action' => 'web/Default/index', //默认操作

'var_pathinfo' => 'r',  // PATHINFO 兼容模式获取变量例如 ?r=/module/action/id/1中的s ,后面的分隔符/取决于url_pathinfo_depr

//'static__path' => 'http://static.cml.com/', //模板替换的{{public}}静态地址(访问静态资源用)  默认为 入口文件所在目录

/*安全过滤*/

'auth_key'=>'a5et3e41d', //Encry加密key

'check_csrf' => 1, //检查csrf跨站攻击 0、不检查，1、只检查post数据提交方式，2、get/post都检查 默认只检查post

'form_token'=> 0, //表单令牌 0不开启，1开启

/*语言包设置*/

'lang' =>'zh-cn',  //读取zh-cn.php文件

/*cookie设置*/

'cookie_prefix'=> 'cml_', //cookie前缀

'cookie_expire' => 0,    // Coodie有效期

'cookie_domain' => '',      // Cookie有效域名

'cookie_path' => '/',     // Cookie路径

'userauthid' => 'CmlUserAuth',  //用户登录成功之后的cookie标识

/*Session设置*/

'session_prefix' => 'cml_', //session前缀

'session_user' => 0, //SESSION保存位置自定义 0不开启、1开启

'session_user_loc' => 'db', //自定义保存SESSION的位置时 定义保存的位置  db、cache两种

'session_user_loc_table' => 'session', //自定义保存SESSION的保存位置设置为db时的表名

'session_user_loc_tableprefix' => 'cml_', //自定义保存SESSION的保存位置设置为db时的表前缀

/**锁配置**/

'lock_prefix' => 'cml_',

'locker_use_cache' => 'default_cache', //上锁使用的缓存

/**日志配置**/

'log_warn_log' => false, //警告级别的日志默认不记录

'log_prefix' => 'cml_log', //会显示到日志内容中,同时当以redis为驱动的时候会做为队列的前缀

//Log类使用redis为驱动时使用的缓存配置key

'redis_log_use_cache' => 'default_cache',//只有在该缓存的驱动为redis的时候才有效,否则会报错

/**队列配置**/

//Redis队列使用的缓存

'redis_queue_use_cache' => 'default_cache',//只有在该缓存的驱动为redis的时候才有效,否则会报错

/*系统路由-统一用小写url*/

'cmlframework_system_route' => [

'cmlframeworkstaticparse' => '\\Cml\\Tools\\StaticResource::parseResourceFile'

],

'static_file_version' => 'v1', //开发模式会自动在静态文件后加时间缀，实时过期,线上模板版本号固定，如有需要在这里改版本号强制过期

/*php-console配置*/

'dump_use_php_console' => false, //开启本功能需要先安装php-console composer require php-console/php-console ~3.0

'php_console_password' => 'cmlphp_php_console_pw123456',

/**

* 是否开启全局紧急模式--慎用。主要用于在系统mysql负载过高(如遇到攻击)mysql压力过大。先将所有查询转移至缓存。消化压力高峰

*

* 开启时 mysql的查询缓存不会在数据变更时实时更新。

* 所以如果要开启请确定开启后不会对业务造成影响。如:扣积分前的查询积分数，这种对数据实时要求高的，在开启本模式时要做下判断并屏蔽。

*/

'emergency_mode_not_real_time_refresh_mysql_query_cache' => false, //配置成int型则为缓存刷新周期。如配置成 300 则为数据变更时每五分钟更新一次mysql查询缓存

'var_page' => 'page', //分页时传递当前页数的变量名

'route_app_hierarchy' => 1, //路由找控制器的时候遍历应用目录的层级，默认为1

];

common.php

common.php 为公用配置文件，也就是无论是cli还是product或者是development的环境都会自动加载的配置文件

plugin.php

plugin.php  为插件配置目录，也是cli/product/development共用 关于插件请参看 插件章节

route.php

route.php  为路由配置，也是cli/product/development共用 关于路由的使用请参看 路由章节

其它配置项

除了常用的配置我们通常还要申明一些跟业务相关的配置项，这是只要在相应的目录下( cli / development / product / 某个应用/Config )下创建相应的文件即可，比如我们的业务要添加一个api接口的配置，这里只要在 cli / development / product / 某个应用/Config 下创建  api.php 文件在文件中编写以下代码

<?php

return [

'url' => 'http://api.com',

'key' => 'fdfdf'

];

使用的时候使用以下代码调用即可

$config = Cml\Config::load('api'); //上层Config目录下cli/development/product下的api.php配置

$config = Cml\Config::load('api', false);//某个应用下的api.php配置

echo $config['url'];

配置的获取

normal.php以及common.php中配置的获取 1.在控制器中获取

\Cml\Config::get('key1.key2', 'default_value');

多维数组以 . 分隔,若不存在则取default_value

2.在模板中获取

```html

{{config key1.key2}}

```

这边只能获取common.php/normal.php/惯例配置，特殊配置请使用\Cml\Config::load

除了normal.php、common.php之外的配置的获取

使用\Cml\Config::load载入。参考 其它配置项

动态修改配置

\Cml\Config::set('key1.key2', 'value');

动态修改配置只针对normal.php及框架的惯例配置，使用 Cml\Config::load() 方法载入的自定义配置不能动态修改

URL模式

框架默认支持3种url模式以适应各种环境，在配置文件中 使用url_model来配置url模式

'url_model' => 1,

- 1为pathinfo模式(显示php文件）如 http://site.com/index.php/web/Default/index

- 2为pathinfo模式(不显示php文件）如 http://site.com/web/Default/index.html

- 3为兼容模式如 http://site.com/index.php?r=web/Default/index

模式3适应所有php环境

模式1需要pathinfo的支持或rewrite重写

模式2需要rewrite的支持 使用模式2的时候会自动在url后面加上后缀 .html（可配置） 'url_html_suffix' => '.html'

框架封装了 \Cml\Http\Response::url(); 及模板标签 {{url }} 方法来生成url，通过使用U函数生成的url会随着url模式的切换而自动改变，保证项目运行不受环境影响

伪表态配置参考 部署章节

路由

框架自带的路由组件，支持按url映射到文件的方式来执行相应的控制器，也支持定义路由路由配置。v2.7版本开始也支持第三方路由组件。除了旧版本自带的路由外也封装了FastRoute服务。有需要的话直接修改入口文件中相应的配置

框架是默认的路由组件是按url映射到文件的方式来执行相应的控制器，但是有一些情况下我们需要简短而优雅的网址便于用户记忆及SEO。这时候就需要使用url路由或者使用第三方的路由的服务。

在配置章节我们介绍过，在 Config 目录下有 route.php 配置文件，我们的所有路由都配置在这个文件里，下面介绍方法及规则

相应接口

<?php

//应用路由配置文件

use Cml\Route;

//只针对get请求有效

Route::get('pattern' , 'adminbase/System/Config/show');

//v2.7+建议这么写效率更高且支持应用多级子目录

Route::get('blog/bb/:aid\d', [

'root/adminbase',//应用名,支持多级子目录

'System/Config',//控制器,支持多级子目录

'show'//方法名

]);

//只针对post请求有效

Route::post('pattern' , 'adminbase/System/Config/update');

//v2.7+建议这么写效率更高且支持应用多级子目录

Route::post('blog/bb/:aid\d', [

'root/adminbase',//应用名,支持多级子目录

'System/Config',//控制器,支持多级子目录

'update'//方法名

]);

//只针对put请求有效

Route::put('pattern' , 'adminbase/System/Config/add');

//v2.7+建议这么写效率更高且支持应用多级子目录

Route::put('blog/bb/:aid\d', [

'root/adminbase',//应用名,支持多级子目录

'System/Config',//控制器,支持多级子目录

'add'//方法名

]);

//只针对delete请求有效

Route::delete('pattern' , 'adminbase/System/Config/delete');

//v2.7+建议这么写效率更高且支持应用多级子目录

Route::delete('blog/bb/:aid\d', [

'root/adminbase',//应用名,支持多级子目录

'System/Config',//控制器,支持多级子目录

'delete'//方法名

]);

//只针对patch请求有效

Route::patch('pattern', 'adminbase/System/Config/patch');

//v2.7+建议这么写效率更高且支持应用多级子目录

Route::patch('blog/bb/:aid\d', [

'root/adminbase',//应用名,支持多级子目录

'System/Config',//控制器,支持多级子目录

'patch'//方法名

]);

//只针对所有请求都有效

Route::any('pattern' , 'adminbase/System/Config/index');

//v2.7+建议这么写效率更高且支持应用多级子目录

Route::any('blog/bb/:aid\d', [

'root/adminbase',//应用名,支持多级子目录

'System/Config',//控制器,支持多级子目录

'index'//方法名

]);

/**

* RESTful 路由

*

* get 请求对应 adminbase/System/Config/getRead

* post 请求对应 adminbase/System/Config/postRead

* delete 请求对应 adminbase/System/Config/deleteRead

* put 请求对应 adminbase/System/Config/putRead

* patch 请求对应 adminbase/System/Config/patchRead

*/

Route::rest('pattern' , 'adminbase/System/Config/read');

//v2.7+建议这么写效率更高且支持应用多级子目录

Route::any('blog/bb/:aid\d', [

'root/adminbase',//应用名,支持多级子目录

'System/Config',//控制器,支持多级子目录

'index'//方法名

]);

自带的路由pattern规则定义(第三方的路由请参考相对应的文档)

如我们配置了一条路由

\Cml\Route::get('list/:id\d' => [

'web'

'Blog/Article',

'show'

]);

这条路由的意思为 当用户以GET请求访问 http://域名/list/1.html 这样的地址时，框架会执行的Article控制器的list方法。 \d为匹配数字 :id意思为它是一个动态参数 且在控制器中可使用 \Cml\Http\Input::getInt('id') 来接收

在模板中可以使用  {{url "list/{$id}"}}  生成相应的url

分组路由

use Cml\Route;

Route::group('v1', function() {

Route::get('user/add', 'api/User/add');

Route::get('user/edit', 'api/User/edit');

});

这边定义了  v1分组 ，即匹配  v1/user/add 以及 v1/user/edit 这两个url

多应用单独定义路由

默认情况我们的路由是定义在全局 projxx/Config/route.php 中。但是如果我们有多个应用。

比如有 web 、 admin 、 api 三个应用，然后我们想在 web 中定义自己的路由。这时候可以在 web/Config/route.php 中定义 web 独立的路由。然后在 projxxx/Config/route.php 中使用  Route::loadAppRoute('web');  将其载入即可。

控制器

在项目目录说明中我们看到了 DefaultController.php 文件及的内容

<?php

namespace web\Controller;

use Cml\Controller;

class DefaultController extends Controller

{

public function index()

{

echo '欢迎使用cml框架,应用初始化成功';

}

}

可以看到 控制器都有带 Controller 后缀 且必须继承  Cml\Controller  这个基类 在这边命名空间以 web\Controller 开始 因为这边 DefaultController 直接是存放在 web\Controller 目录下 所以命名空间为  web\Controller ，假如我们有一个商品模块商品模块下有商品跟订单两个控制器 这时目录结构则为 相应的Goods的文件内容为

<?php

namespace web\Controller\Goods;

use Cml\Controller;

class GoodsController extends Controller

{

public function index()

{

}

}

在这里我们的命名空间就要相应的申明为  web\Controller\Goods

前置方法

如果要在执行所有方法前都先做某操作。只要申明  init() 方法即可 如我们需要做权限控制，只要声明一个 CommonController 其它控制器都继承它

<?php

namespace web\Controller;

use Cml\Controller;

class CommonController extends Controller

{

public function init()

{

//权限处理

}

}

请求、响应

CmlPHP在 Cml\Http 中内置一些工具方便开发人员处理请求、响应相关的内容;

Input

\Cml\Http\Input  用来接收用户 GET/POST 请求数据

获取get string数据

\Cml\Http\Input::getString($name, $default = null)

获取post string数据

\Cml\Http\Input::postString($name, $default = null)

获取$_REQUEST string数据

\Cml\Http\Input::requestString($name, $default = null)

获取get int数据

\Cml\Http\Input::getInt($name, $default = null)

获取post int数据

\Cml\Http\Input::postInt($name, $default = null)

获取$_REQUEST int数据

\Cml\Http\Input::requestInt($name, $default = null)

获取get bool数据

\Cml\Http\Input::getBool($name, $default = null)

获取post bool数据

\Cml\Http\Input::postBool($name, $default = null)

获取post bool数据

\Cml\Http\Input::requestBool($name, $default = null)

Request

Cml\Http\Request  用来获取请求的一些信息如 用户ip 、 是否为移动端发起请求 、 是否为ajax请求 等;

获取用户IP地址

\Cml\Http\Request::ip()

获取用户标识

\Cml\Http\Request::userAgent()

获取主机名称

\Cml\Http\Request::host()

判断是否为手机浏览器

\Cml\Http\Request::isMobile()

判断是否为POST请求

\Cml\Http\Request::isPost()

判断是否为GET请求

\Cml\Http\Request::isGet()

判断是否为AJAX请求

\Cml\Http\Request::isAjax()

判断是否以cli方式运行

\Cml\Http\Request::isCli()

获取POST过来的二进制数据,与手机端交互

\Cml\Http\Request::getBinaryData()

发起curl请求

\Cml\Http\Request::curl()

Response

Cml\Http\Response  用来处理 页面跳转 、 响应404 、及 发送http状态码 等;

重定向

\Cml\Http\Response::redirect($url, $time = 0)

显示404页面

\Cml\Http\Response::show404Page($tpl = null)

发送http状态码相对应的信息

\Cml\Http\Response::sendHttpStatus($code)

URL组装 支持不同URL模式

\Cml\Http\Response::url($url = '', $echo = 1)

Cookie

\Cml\Http\Cookie  用来处理 Cookie

判断Cookie是否存在

\Cml\Http\Cookie::isExist($key);

获取某个Cookie值

\Cml\Http\Cookie::get($name);

设置某个Cookie值

\Cml\Http\Cookie::set($name, $value, $expire = '',$path = '', $domain = '');

删除某个Cookie值

\Cml\Http\Cookie::delete($name);

清空Cookie值

\Cml\Http\Cookie::clear($name);

注意:使用 \Cml\Http\Cookie 处理cookie的时候默认加密key使用的是配置中 auth_key 配置的key。所以如果有使用到 \Cml\Http\Cookie 请修改 auth_key 配置的key

Session

Cml\Http\Session  用来处理 Session

获取session值

\Cml\Http\Session::get($name);

设置session值

\Cml\Http\Session::set($key, $value='');

删除session值

\Cml\Http\Session::delete($name);

清空session

\Cml\Http\Session::clear($name);

模型[mysql]

本小节介绍常用的orm mysql操作。详细的api 点这里 模型全部存放在应用路径下的 Model 目录(可自定义只是建议命名为Model)中，命名规则为 模型名 + Model (如 User 模型的文件名为 UserModel.php ，名字中的Model后缀也是可选)，模型名首字母大写, Cml\Model 为所有模型的基类，所有Model都要继承它，比如我们有一个pre_user表，相对应的我们声明一个无表前缀前缀的 UserModel

<?php

namespace web\Model;

use Cml\Model;

class UserModel extends Model

{

protected $table = 'user';//这个是可选的不带表前缀不配置会自动取Model文件名。但建议还是配置上去

}

命名空间的声明跟控制器类似以 应用名\Model 打头，或有多个模块则加上相应的模块名如 web\Model\Goods

模型的调用方式

在模型外部中调用模型有两种方式。示例如下:

//声明一个GoodsModel

<?php

namespace web\Model;

use Cml\Model;

class GoodsModel extends Model

{

protected $table = 'goods';

/**

* 取出status=1分类=$cid的数据

*

* @param int $cid 分类id

*

* @return array

*/

public function test($cid)

{

return $this->where('status', 1)->getByColumn($cid, 'cid');

}

}

在逻辑层中调用方式如下:

默认下面已经在文件头声明了  use web\Model\GoodsModel;

//方式一:

$goodsModel = new GoodsModel();

$goodsModel->test();//调用上面声明的test方法

$goodsModel->db()->xxx();//框架提供的数据库相关操作参考[api手册](http://api.cmlphp.com/Cml/Db/Base.html)

$goodsModel->xxx(); //当GoodsModel中不存在xxx方法时会去执行$goodsModel->db()->xxx();

//即我们可以直接使用

$goodsModel->table($goodsModel->getTableName())->where('status', 1)->select();

替代

$goodsModel->db()->table($goodsModel->getTableName())->where('status', 1)->select();

还可以这么用:

$goodsModel->where('status', 1)->getByColumn($cid, 'cid');

//上面那句中getByColumn是快捷方法。相当于:

$goodsModel->db()->table($goodsModel->getTableName())->where('status', 1)->where('cid', $cid)->getOne();

//即调用model中不存在的方法自动去调用$goodsModel->db()->xxx时返回的依然是model的实例而非db()的实例。

//通过这个方式我们可以将db()中的方法和model中的快捷方法混用.

上例中  getByColumn 这样的快捷方法是为了简化一些我们日常开发中常用的操作在Model中进行封装的一些方法参考快捷方法

//方式二:

GoodsModel::getInstance()->test();//用户自定义的方法

GoodsModel::getInstance()->db()->xxx();  //框架提供的数据库相关操作参考[api手册](http://api.cmlphp.com/Cml/Db/Base.html)

GoodsModel::getInstance()->xxx();//当GoodsModel中不存在xxx方法时会去执行GoodsModel::getInstance()->db()->xxx();

//即我们可以直接使用

GoodsModel::getInstance()->table($goodsModel->getTableName())->where('status', 1)->select();

替代

GoodsModel::getInstance()->db()->table($goodsModel->getTableName())->where('status', 1)->select();

还可以这么用:

GoodsModel::getInstance()->where('status', 1)->getByColumn($cid, 'cid');

//即调用model中不存在的方法自动去调用GoodsModel::getInstance()->db()->xxx时返回的依然是model的实例而非db()的实例。

//通过这个方式我们可以将db()中的方法和model中的快捷方法混用

上例中  getByColumn 这样的快捷方法是为了简化一些我们日常开发中常用的操作在Model中进行封装的一些方法参考快捷方法

//v2.7.8以上支持Model直接以静态方式调用db中的方法

GoodsModel::test();

GoodsModel::xxx();//当GoodsModel中不存在xxx方法时会去执行\web\Model\GoodsModel::getInstance()->db()->xxx()

//即我们可以这么使用

GoodsModel::where('status', 1)->get('goods-cid-1');

//也可以将db()中的方法和model中的快捷方法混用

GoodsModel::where('status', 1)->getByColumn($cid, 'cid');

自动注入表名，表前缀

在以上的操作中我们都要手动执行 $this->db('xxx')->table('xxx', 'xxx_');  如果在一个model里有多个方法,那么每一个我方法们都要执行一次 $this->db()->table() 。 为了简化这个操作。可以直接使用快捷方法或者使用 $this->mapDbAndTable() 。  $goodsModel->mapDbAndTable() 就相当于 $goodsModel->db()->table($goodsModel->getTableName())

数据查询

简单数据查询:

$this->db()->get('user-id-1'); //select * from pre_user where id = 1;

$this->db()->get('user-name-abc'); //select * from pre_user where name='abc';

多个条件

$this->db()->get('user-id-1-name-abc'); //id和name为and关系

$this->db()->get('user-id-1-name-abc', false); //id和name为or关系

$this->db()->get('user-id-1-name-abc', true, true);//id和name为and关系。且在数据库主从读写分离时强制从主库读(适用于对实时要求高的时候主从架构强制读主库)

$this->db()->get('user-id-1-name-abc', true, false, 'u_');//指定表前缀为u_。假定配置中配置的表前缀不为u_。这边可单独指定前缀

特殊说明

在这边要特殊说明  $this->db()  这边的 db() 方法,在配置文件章节中我们看到在normal.php中有配置项 default_db 这个配置,它是默认连接的数据库集群，如果我们的业务中有需要连接多个数据库集群呢？ 这时只要在 normal.php 中添加相关的数据库配置项如

'default_db' => [

'driver' => 'MySql.Pdo', //数据库驱动

'master' => [

'host' => 'localhost', //数据库主机

'username' => 'root', //数据库用户名

'password' => '', //数据库密码

'dbname' => 'test', //数据库名

'charset' => 'utf8', //数据库编码

'tableprefix' => 'h_', //数据表前缀

'pconnect' => false, //是否开启数据库长连接

'engine' => ''//数据库引擎

],

'slaves' => [], //从库配置

//查询数据缓存时间，表数据有变动会自动更新缓存。设置为0表示表数据没变动时缓存不过期。

//这边设置为3600意思是即使表数据没变动也让缓存每30s失效一次,这样可以让缓存空间更合理的利用.

//如果不想启用缓存直接配置为false

'cache_expire' => 30,//查询数据缓存时间

],

'game_db' => [

'driver' => 'MySql.Pdo', //数据库驱动 或者MongoDB.MongoDB使用MongoDB驱动

'master'=> [

'host' => 'localhost', //数据库主机

'username' => 'root', //数据库用户名

'password' =>'', //数据库密码

'dbname' => 'test', //数据库名

'charset' => 'utf8', //数据库编码

'tableprefix' => 'g_', //数据表前缀

'pconnect' => false, //是否开启数据库长连接

'engine'=>''//数据库引擎

],

'slaves'=> [],

//查询数据缓存时间，表数据有变动会自动更新缓存。设置为0表示表数据没变动时缓存不过期。

//这边设置为3600意思是即使表数据没变动也让缓存每30s失效一次,这样可以让缓存空间更合理的利用.

//如果不想启用缓存直接配置为false

'cache_expire' => 30,

],

然后在写查询语句的时候直接  $this->db('game_db') 即可，不写则为调用 default_db 配置的数据库集群。当然了如果数据库配置是从其它渠道过来的比如从db来的，也可在调用 db() 方法时直接传入数组。

复合查询

获取多行

$this->db()->table('user')

->columns('id', 'name')

->where('id', 1)

->whereGt('id', 7)

->orderBy('id', 'desc')

->select(0, 10);

//生成的语句 SELECT id, name FROM pre_user WHERE id = 1 AND id > 7 ORDER BY id DESC LIMIT 0, 10;

->where(['id' =>1, 'name'=>'name'], true)//where支持传数组生成`id`=1 AND`name`='name'

->where(['id' =>1, 'name'=>'name'], false)//where支持传数组生成`id`=1 OR `name`='name'

$this->db()->table('user')

->columns(['id' => 'user_id', 'name'])

->lBrackets()

->whereLike('name', false, '哈哈', true)

->whereNotLike('name', true, 'abc', false)

->rBrackets()

->_or()

->lBrackets()

->whereGte('id', 7)

->whereIn('id', array(1, 3, 5, 50, 100, 500))

->whereLt('id', 20)

->whereBetween('id', 1, 1000)

->rBrackets()

->groupBy('level')

->having('id', '>', 10)

->limit(0, 1)

->select();

//v2.7.7起这条语句可以写为

$this->db()->table('user')

->columns(['id' => 'user_id', 'name'])

->_and(function () {

$this->db()->whereLike('name', false, '哈哈', true)

->whereNotLike('name', true, 'abc', false);

})

->_or(function () {

$this->db()->whereGte('id', 7)

->whereIn('id', array(1, 3, 5, 50, 100, 500))

->whereLt('id', 20)

->whereBetween('id', 1, 1000);

})

->groupBy('status')

->having('id', '>', 10)

->limit(0, 1)

->select();

//对比两种写法可发现(_and/_or方法支持传入闭包。闭包内执行的语句为查询分组)

//生成的语句 SELECT id AS user_id, name FROM pre_user WHERE (name LIKE '哈哈%' AND name NOT LIKE '%abc') OR ( id >= 7 AND  id IN (1, 3, 5, 50, 100, 500) AND id < 20 AND id BETWEEN 1 AND 1000 ) GROUP BY level HAVING id > 10 LIMIT 0, 1;

只获取一行

v2.7.2+可用

$this->db()->table('user')

->where('id', 1)

->getOne();

//相当于select(0, 1) && return $result[0];

只获取第一行的某个字段

v2.7.3+可用

$this->db()->table('user')

->where('id', 1)

->getOneValue('name');

//相当于getOne(0, 1) && return $result['name'];

获取某列值的列表

v2.7.3+可用

$this->db()->table('user')

->whereGt('id', 1)

->plunk('name');

//返回的数组为 ['name1', 'name2'...]

$this->db()->table('user')

->whereGt('id', 1)

->plunk('name', 'id');

//返回的数组为 ['id1' => 'name1', 'id2' => 'name2'...]

组块结果集

v2.7.3+可用

如果你需要处理成千上百条数据库记录，可以使用chunk方法，该方法一次获取结果集的一小块，然后传递每一小块数据到闭包函数进行处理。

$this->db()->table('user')

->whereGt('id', 10)

->chunk(100, function($users) {

foreach ($users as $user) {

// 处理结果集...

}

});

//以100条数据为单位分批处理。假设共有1w条数据。则上面的匿名函数会被调用100次

你可以通过从闭包函数中返回false来终止组块的运行：

$this->db()->table('user')

->whereGt('id', 10)

->chunk(100, function($users) {

foreach ($users as $user) {

// 处理结果集...

return false;

}

});

连接

$this->db('game_db')->table(array('user' => 'u'))

->join(array('group' => 'g'), 'u.gid = g.id')

->leftJoin(array('acl' => 'a'), 'a.uid = u.id')

->rightJoin(array('order' => '0'), 'o.uid = u.id')

->select();

// 生成的语句 SELECT * FROM user as u INNER JOIN group AS g ON u.gid = g.id LEFT JOIN acl AS a ON a.uid = u.id  RIGHT JOIN order AS o ON o.uid = u.id;

自动注入表名，表前缀

在以上的操作上我们都要手动执行 $this->db('xxx')->table('xxx', 'xxx_'); 如果在一个model里有多个方法,那么每一个我方法们都要执行一次 $this->db()->table() 。为了简化这个操作。可以直接使用快捷方法或者执行 $this->mapDbAndTable() 。

class ApiLogModel extends Model

{

protected $db = 'game_db';//连接标识//默认为default_db

protected $table = 'apilog';//表名,不带前缀

protected $tablePrefix = 'pre_';//假设配置中的前缀不为pre_，这边单独配置表前缀

}

在上面的代码中。我们声明了要连接的 db 为 game_db 。操作的表为 apilog 。表前缀为 pre_ ,执行 $this->mapDbAndTable() 相当于执行了 $this->db($this->db)->table($this->table, $this->tablePrefix); 。当一个 model 中有多个方法时 mapDbAndTable 可以有效减少代码量，同时将表名和特殊的表前缀声明为 model 的属性便于后期修改。

上面基本上覆盖了大部分查询的情况,其它api在IDE中 $this->db()-> 即会自动提示或者查看api手册

分页查询

通常情况。我们在分页显示数据的时候。会传个分页参数。比如在url后带个page?=2。这边的2表示是第二页。

对应的php代码通常是这样：

//controller 中接收当前页数

$page = Input::getInt('page', 1);

$page < 1 && $page = 1;

//model中根据传过来的$page查询

$this->db()->table('user')

->select(($page - 1) * Config::get('page_nums'), Config::get('page_nums'));//page_nums为配置中配置的每页显示多少条

每次都要接收 page 参数。并判断有效性，select方法每次都要计算从哪一条开始。

为了节省这个步骤从v2.7起db中添加了paginate方法。即上面的代码可简化为

$this->db()->table('user')->paginate(Config::get('page_nums'));

在 paginate 方法内部会自动获取 page 参数并判断是否合法，然后根据传入的条数自动计算并以合适的参数调用 select

数据添加

插入一条

$this->db()->set('user', [

'name' => '张三',

'ctime' => '2015-05-15'

]);

插入多条

//v2.7.9起可用

$this->db()->->setMulti('user', ['roleid', 'area', 'nickname'], [

['role1', 'area1', 'nick1'],

['role11', 'area11', 'nick11']

])

插入多条会自动开启事务，也就是要么全部成功要么全部失败，失败的时候返回false，成功的时候返回insertid的数组

获取上一次数据添加的id

调用 set 方法添加数据时会自动返回 lastInsertId 。如果还要单独获取可如下操作:

$this->db()->insertId();

数据更新

简单数据更新

$this->db()->update('user-id-1', [

'status' => 1,

'stime' => '2015-05-16'

]);

//特别指定表前缀为u_(假设配置中配置的不为u_)v2.6.5起可用

$this->db()->update('user-id-1', [

'status' => 1,

'stime' => '2015-05-16'

], true, 'u_');

多个条件

$this->db()->where('id', 1)

->where('status', 1)

->update('user', [

'status' => 0

]);

或者

$this->db()->table('user')

->where('id', 1)

->where('status', 1)

->update([

'status' => 0

]);

更新多个字段，某些字段自增自减/包含运算/mysql函数

$this->db()->update('user-id-1', [

'status' => 0,

'count' => ['inc' =>2], //自增2,

'count2' => ['dec' => 2], //自减2,

'total' => ['count1' => ['+' => 2]], // `total` = `count1`+2

'ip' => [

'func' => ['concat' => ["1", '`username`', '2']]

] // `ip` = concat(1, `username`, 2)   //使用函数需>= v2.5.9.字段名用``引起来

]);

获取上一次数据更新影响的行数

调用 update 方法添加数据时会自动返回 affectedRows 。如果还要单独获取可如下操作:

$this->db()->affectedRows();

数据删除

简单数据删除

$this->db()->delete('user-id-1');

////特别指定表前缀为u_(假设配置中配置的不为u_)v2.6.5起可用 id=1 and status = 1

$this->db()->delete('user-id-1-status-1', and, 'u_');

多个条件

$this->db()->where('id', 1)

->where('status', 1)

->delete('user');

或者

$this->db()->table('user')

->where('id', 1)

->where('status', 1)

->delete();

简单的单字段数据自增(多字段参考update)

$this->db()->increment('user-id-1', 1, 'nums');

生成的语句 UPDATE pre_user SET nums = nums+1 WHERE id = 1;

//特别指定表前缀为u_(假设配置中配置的不为u_)v2.6.5起可用

$this->db()->increment('user-id-1', 1, 'nums', 'u_');

生成的语句 UPDATE u_user SET nums = nums+1 WHERE id = 1;

简单的单字段数据自减(多字段参考update)

$this->db()->decrement('user-id-1', 1, 'nums');

生成的语句 UPDATE pre_user SET nums = nums-1 WHERE id = 1;

//特别指定表前缀为u_(假设配置中配置的不为u_)v2.6.5起可用

$this->db()->decrement('user-id-1', 1, 'nums', 'u_');

生成的语句 UPDATE u_user SET nums = nums-1 WHERE id = 1;

聚合操作

count

$this->db()->table('user')->count('id');

max

$this->db()->table('user')->max('id');

min

$this->db()->table('user')->min('id');

sum

$this->db()->table('user')->sum('id');

avg

$this->db()->table('user')->avg('id');

事务的使用

开启事务

$this->db()->startTransAction();

提交事务

$this->db()->commit();

回滚事务

$this->db()->rollBack();

v2.7.7起新增 transaction 方法

我们可以这么操作，不用单独调用 startTransAction / commit

$this->db()->transaction(function () {

$this->db()->update('user', [

...

]);

$this->db()->update('group', [

...

]);

});

框架会自动调用 startTransAction / commit

调用存储过程

/**

* 调用存储过程

*

* @param string $procedureName 要调用的存储过程名称

* @param array $bindParams 绑定的参数

* @param bool|true $isSelect 是否为返回数据集的语句

*

* @return array|int

*/

$this->db()->callProcedure($procedureName = '', $bindParams = array(), $isSelect = true);

子查询

大部分情况只要使用前两节中的方式就可以我们想要的数据。但是有一些特殊情况我们要用到原生的查询方式/子查询/联合查询，这边做一个介绍。

原生查询

$stmt = $this->db()->prepare('xxx');

$this->db()->execute($stmt);

$stmt->fetchAll();

子查询

v2.7.1+起支持

$subSql = $this->db()->table('group')->buildSql();

联合查询

$this->db()->table('user')->orderBy('id', 'desc')

->union('select xxx from xxx')->select();

//或 v2.7.1+版本

$subSql = $this->db()->table('group')->buildSql();//创建一个子查询

$this->db()->table('user')->orderBy('id', 'desc')

->union($subSql)->select();

快捷方法

以上介绍了数据库的 查询 、 新增 、 更新 、 删除 、 事务 等相关API，但是有一些比较常用的操作为了简化使用在Model中已经做了封装，下面正式介绍这些方法.也可以参考api手册

通过某字段值获取数据

$this->getByColumn(1);

//自动获取主键,相当于$this->mapDbAndTable()->where('主键', 1)->getOne();

$this->getByColumn(1, 'uid');

//手动传入字段名,相当于$this->mapDbAndTable()->where('uid', 1)->getOne();

$this->getByColumn(1, 'uid', 'users');//手动传入表名 v2.6.2起可用

$this->getByColumn(1, 'uid', 'users', 'u_');//手动传入表名,且特殊指定表前缀v2.6.5起可用

这里也可以直接传多个条件,第一个参数控制是AND还是OR

$this->getByColumn(true, ['id' =>1, 'name'=>'name']);// `id`=1 AND`name`='name'

$this->getByColumn(false, ['id' =>1, 'name'=>'name']);// `id`=1 OR `name`='name'

若未传入表名(v2.6.2以下版本)。这边会自动判断当前Model中有无 $table 属性 ，若有则自动提取 $table属性 为要操作的table，若没有则自动获取 model 名为表名如  UserModel  对应的表名为  pre_user ，建议在 XxxModel 中声明 $table 属性。若没传入字段名框架会自动获取表的主键名称,如线上环境有修改主键名请清空相应缓存 点击查看详情说明。若没传入前缀，框架会自动获取当前Model有无 $tablePrefix 属性，若有则自动提取，若没有则使用配置文件中数据库相关配置里配置的表前缀，建议在当前模型的表前缀与配置中数据库相关配置里配置的表前缀不一样的时候在当前model中声明 $tablePrefix 属性

增加数据

插入一条

$this->set([

'name' => '张三',

'age' => 30

]);

$this->set([

'name' => '张三',

'age' => 30

], 'users');

//手动传入表名 v2.6.2起可用

$this->set([

'name' => '张三',

'age' => 30

], 'users', 'u_');

//手动传入表名,且特殊指定表前缀v2.6.5起可用

插入多条

//v2.7.9起可用

$this->setMulti(['roleid', 'area', 'nickname'], [

['role1', 'area1', 'nick1'],

['role11', 'area11', 'nick11']

]);

$this->setMulti(['roleid', 'area', 'nickname'], [

['role1', 'area1', 'nick1'],

['role11', 'area11', 'nick11']

],'user');

插入多条会自动开启事务，也就是要么全部成功要么全部失败，失败的时候返回false，成功的时候返回insertid的数组

若未传入表名(v2.6.2以下版本)。这边会自动判断当前Model中有无 $table 属性 ，若有则自动提取 $table属性 为要操作的table，若没有则自动获取 model 名为表名如  UserModel  对应的表名为  pre_user ，建议在 XxxModel 中声明 $table 属性。若没传入字段名框架会自动获取表的主键名称,如线上环境有修改主键名请清空相应缓存 点击查看详情说明。若没传入前缀，框架会自动获取当前Model有无 $tablePrefix 属性，若有则自动提取，若没有则使用配置文件中数据库相关配置里配置的表前缀，建议在当前模型的表前缀与配置中数据库相关配置里配置的表前缀不一样的时候在当前model中声明 $tablePrefix 属性

通过字段值更新数据

$this->updateByColumn(1, [

'name' => '李四'

]);//自动获取主键

//相当于

$this->mapDbAndTable()->where('主键', 1)->update([

'name' => '李四'

]);

$this->updateByColumn(1, [

'name' => '李四'

], 'uid'); //手动传入字段名

//相当于

$this->mapDbAndTable()->where('uid', 1)->update([

'name' => '李四'

]);

$this->updateByColumn(1, [

'name' => '李四'

], 'uid', 'users'); //手动传入表名 v2.6.2起可用

//相当于

$this->db()->table('users')->where('uid', 1)->update([

'name' => '李四'

]);

$this->updateByColumn(1， [

'name' => '李四'

], 'uid', 'users', 'u_'); //手动传入表名,且特殊指定表前缀v2.6.5起可用

//相当于

$this->db()->table('users', 'u_')->where('uid', 1)->update([

'name' => '李四'

]);

若未传入表名(v2.6.2以下版本)。这边会自动判断当前Model中有无 $table 属性 ，若有则自动提取 $table属性 为要操作的table，若没有则自动获取 model 名为表名如  UserModel  对应的表名为  pre_user ，建议在 XxxModel 中声明 $table 属性。若没传入字段名框架会自动获取表的主键名称,如线上环境有修改表字段请清空相应缓存 点击查看详情说明。若没传入前缀，框架会自动获取当前Model有无 $tablePrefix 属性，若有则自动提取，若没有则使用配置文件中数据库相关配置里配置的表前缀，建议在当前模型的表前缀与配置中数据库相关配置里配置的表前缀不一样的时候在当前model中声明 $tablePrefix 属性

通过字段值删除一条数据

$this->delByColumn(1);

//自动获取主键,相当于$this->mapDbAndTable()->where('主键', 1)->delete();

$this->delByColumn(1, 'uid');

//手动传入字段名,相当于$this->mapDbAndTable()->where('uid', 1)->delete();

$this->delByColumn(1, 'uid', 'users');

//手动传入表名v2.6.2起可用, 相当于$this->db()->table('users')->where('uid', 1)->delete();

$this->delByColumn(1, 'uid', 'users', 'u_');

//手动传入表名,且特殊指定表前缀v2.6.5起可用

//相当于$this->db()->table('users', 'u_')->where('uid', 1)->delete();

若未传入表名(v2.6.2以下版本)。这边会自动判断当前Model中有无 $table 属性 ，若有则自动提取 $table属性 为要操作的table，若没有则自动获取 model 名为表名如  UserModel  对应的表名为  pre_user ，建议在 XxxModel 中声明 $table 属性。若没传入字段名框架会自动获取表的主键名称,如线上环境有修改主键名请清空相应缓存 点击查看详情说明。若没传入前缀，框架会自动获取当前Model有无 $tablePrefix 属性，若有则自动提取，若没有则使用配置文件中数据库相关配置里配置的表前缀，建议在当前模型的表前缀与配置中数据库相关配置里配置的表前缀不一样的时候在当前model中声明 $tablePrefix 属性

获取总条数

$this->getTotalNums()

//自动获取主键,相当于$this->mapDbAndTable()->count('主键');

$this->getTotalNums('uid');

//当前操作的表无主键手动传入字段名,相当于$this->mapDbAndTable()->count('uid');

$this->getTotalNums('uid', 'users');

//当前操作的表无主键手动传入字段名,手动传入表名 v2.6.2起可用

//相当于$this->db()->table('users')->count('uid');

$this->getTotalNums('uid', 'users', 'u_');

//手动传入表名,且特殊指定表前缀v2.6.5起可用

//相当于$this->db()->table('users', 'u_')->count('uid');

上面两条都是没带条件的获取总数,如果带条件的时候不想写select()想直接用getTotalNums(),也可以这么操作:

//在xxModel中

$this->db()->where('id', 1)->whereGt('status', 1);//会将where条件注入到orm

$this->getTotalNums();//调用Model中的getTotalNums() orm会使用上面注入的条件

//v2.7.3起,在model里还支持这么写。框架会自动处理请求的是db类的方法还是model的方法。如：

$this->where('id', 1)->whereGt('status', 1)//这边操作的是orm下db类的方法

->getTotalNums(); //这边操作的是model的方法

//要注意的是在Model中的getTotalNums()会自动调用$this->db()->table所以在设置where条件时不能再调用$this->db()->table()。否则会造成sql出错。

若未传入表名(v2.6.2以下版本)。这边会自动判断当前Model中有无 $table 属性 ，若有则自动提取 $table属性 为要操作的table，若没有则自动获取 model 名为表名如  UserModel  对应的表名为  pre_user ，建议在 XxxModel 中声明 $table 属性。若没传入字段名框架会自动获取表的主键名称,如线上环境有修改主键名请清空相应缓存 点击查看详情说明。若没传入前缀，框架会自动获取当前Model有无 $tablePrefix 属性，若有则自动提取，若没有则使用配置文件中数据库相关配置里配置的表前缀，建议在当前模型的表前缀与配置中数据库相关配置里配置的表前缀不一样的时候在当前model中声明 $tablePrefix 属性

获取列表

$this->getList(0, 10, 'DESC');

//默认根据主键排序,相当于$this->mapDbAndTable()->orderBy('主键', 'desc')->select(0, 10);

$this->getList(0, 10, array('id' => 'desc', 'sort' => 'asc'));

//相当于$this->mapDbAndTable()->orderBy('id', 'desc')->orderBy('sort', 'asc')->select(0, 10);

$this->getList(0, 10, array('id' => 'desc', 'sort' => 'asc')， 'users');

//手动传入表名 v2.6.2起可用

//相当于$this->db()->table('users')->orderBy('id', 'desc')->orderBy('sort', 'asc')->select(0, 10);

$this->getList(0, 10, array('id' => 'desc', 'sort' => 'asc')， 'users', 'u_');

//手动传入表名,且特殊指定表前缀v2.6.5起可用

//相当于$this->db()->table('users', 'u_')->orderBy('id', 'desc')->orderBy('sort', 'asc')->select(0, 10);

$this->getListByPaginate(10, array('id' => 'desc', 'sort' => 'asc'));

//自动获取分页参数每页显示10条

//相当于$this->mapDbAndTable()->orderBy('id', 'desc')->orderBy('sort', 'asc')->paginate(10);

上面都是没带条件的获取列表,如果带条件的时候不想写select()想直接用getList(),也可以这么操作:

//在xxModel中

$this->db()->where('id', 1)->whereGt('status', 1);//会将where条件注入到orm

$this->getList(0, 10, 'DESC');//调用Model中的getList() orm会使用上面注入的条件

//v2.7.3起,在model里还支持这么写。框架会自动处理请求的是db类的方法还是model的方法。如：

$this->where('id', 1)->whereGt('status', 1)//这边操作的是orm下db类的方法

->getList(0, 10, 'DESC'); //这边操作的是model的方法

//要注意的是在Model中的getList()会自动调用$this->db()->table()所以在设置where条件时不能再调用$this->db()->table()。否则会造成sql出错。

若未传入表名(v2.6.2以下版本)。这边会自动判断当前Model中有无 $table 属性 ，若有则自动提取 $table属性 为要操作的table，若没有则自动获取 model 名为表名如  UserModel  对应的表名为  pre_user ，建议在 XxxModel 中声明 $table 属性。若没传入字段名框架会自动获取表的主键名称,如线上环境有修改主键名请清空相应缓存 点击查看详情说明。若没传入前缀，框架会自动获取当前Model有无 $tablePrefix 属性，若有则自动提取，若没有则使用配置文件中数据库相关配置里配置的表前缀，建议在当前模型的表前缀与配置中数据库相关配置里配置的表前缀不一样的时候在当前model中声明 $tablePrefix 属性

注意事项

使用快捷方法的时候操作的db都为 default_db 中配置的db，如果是操作其它db如 game_db 需要在相应的模型中声明 $db 属性值为相应的 db配置名称 如 有一个 Model 为 GameModel ，相应的代码为

<?php

namespace web\Model;

use Cml\Model;

class GameModel extends Model

{

/**

* 当前model快捷方法操作的db对应配置文件中的Key

*

* @var string

*/

protected $db = 'game_db';

/**

* 当前model要操作的表名

*

* @var string

*/

protected $table = 'game';

/**

* 当前model要操作的表前缀 若与配置文件中数据库相关配置中配置的前缀一样则不要配置此属性

*

* @var string

*/

protected $table = 'u_';

}

通过注释不同的db配置，我们就可以灵活的操作多个不同来源的数据库

同时查询多个库

在数据查询中已经说过，这边再单独说明方便查阅。  $this->db()  这边的 db() 方法,在配置文件章节中我们看到在normal.php中有配置项 default_db 这个配置,它是默认连接的数据库集群，如果我们的业务中有需要连接多个数据库集群呢？ 这时只要在 normal.php 中添加相关的数据库配置项如

'default_db' => [

'driver' => 'MySql.Pdo', //数据库驱动

'master' => [

'host' => 'localhost', //数据库主机

'username' => 'root', //数据库用户名

'password' => '', //数据库密码

'dbname' => 'test', //数据库名

'charset' => 'utf8', //数据库编码

'tableprefix' => 'h_', //数据表前缀

'pconnect' => false, //是否开启数据库长连接

'engine' => ''//数据库引擎

],

'slaves' => [], //从库配置

//查询数据缓存时间，表数据有变动会自动更新缓存。设置为0表示表数据没变动时缓存不过期。

//这边设置为3600意思是即使表数据没变动也让缓存每30s失效一次,这样可以让缓存空间更合理的利用.

//如果不想启用缓存直接配置为false

'cache_expire' => 30,//查询数据缓存时间

],

'game_db' => [

'driver' => 'MySql.Pdo', //数据库驱动 或者MongoDB.MongoDB使用MongoDB驱动

'master'=> [

'host' => 'localhost', //数据库主机

'username' => 'root', //数据库用户名

'password' =>'', //数据库密码

'dbname' => 'test', //数据库名

'charset' => 'utf8', //数据库编码

'tableprefix' => 'g_', //数据表前缀

'pconnect' => false, //是否开启数据库长连接

'engine'=>''//数据库引擎

],

'slaves'=> [],

//查询数据缓存时间，表数据有变动会自动更新缓存。设置为0表示表数据没变动时缓存不过期。

//这边设置为3600意思是即使表数据没变动也让缓存每30s失效一次,这样可以让缓存空间更合理的利用.

//如果不想启用缓存直接配置为false

'cache_expire' => 30,

],

然后在写sql语句的时候直接  $this->db('game_db') 即可，不写则为调用 default_db 配置的数据库集群。当然了如果数据库配置是从其它渠道过来的比如从db来的，也可在调用 db() 方法时直接传入数组。

在声明Model文件的时候我们可以直接指定查询的 db 这样就不用每个操作都 ->db('xxx')

class ApiLogModel extends Model

{

protected $db = 'game_db';//连接标识//默认为default_db

protected $table = 'apilog';//表名,不带前缀

protected $tablePrefix = 'pre_';//假设配置中的前缀不为pre_，这边单独配置表前缀

}

我们在使用 ApiLogModel::getInstance()->db()  时候默认调用的就是 game_db

模型[MongoDB]

本小节介绍常用的orm MongoDB操作。详细的api  点这里 在 CmlPHP 中对 MongoDB 的操作基本和 MySql 一致，大部分操作可以参考模型mysql 这里主要列出操作MongoDB与Mysql不同的地方:

1. 不支持 groupBy 、 having 、 join 、 leftJoin 、 rightJoin 、 union  、 startTransAction 、 commit 、 savePoint 、 rollBack 、 callProcedure 等聚合及事务相关的方法

2. and和or有差别 如:

Model::getInstance()->db()

->table('users')

->lBrackets()

->whereLt('id', 3)

->_or()

->where('status', 1)

->rBrackets()

->where('status', 0)

->select()

执行的 mysqlsql 语句为 SELECT * FROM hadm_users WHERE (id < '3' OR status = '1' ) AND status = '0'  执行的 mongodb 语句为 {"id":{"$lt":3},"$or":[{"status":1},{"status":0}]}

总结一下:

- 使用 MongoDb 驱动时 lBrackets 、 rBrackets 。只对 $or 有效

- _or() 在mysql中只代表在 WHERE 中生成一个 OR 。而在 MongoDB 中则代表此后的操作都为 $or 中的条件。除非调用 _and() 将条件变为and下

3. getDbFields 因为mongodb中collection对字段是没有做强制一制的。这边默认获取第一条数据的所有字段返回

4. getPk  mongo直接返回 '_id'

5. MongoDB 不支持设置自增主键。可以通过 getMongoDbAutoIncKey 获取一个自增值。详细Api

Orm详细Api

请参考 CmlPHP Api手册

服务

在CmlPHP中Server层是可选的，增加Server层的目的主要是为了让代码更加的可复用。比如表单搜索的处理，某应用中对图片上传扩展的二次封装，调用接口api，api单独封装成扩展返回原始格式，而当前应用中多处需要将api的返回值做格式化处理，此时在Server中封装成单独的Server给多个Controller调用，做到代码可复用和更高的抽象化

服务使用说明

我们创建一个 UploadServer.php 用来处理文件上传的逻辑，在其内部对文件上传的扩展进行封装

<?php namespace web\Server;

use Cml\Config;

use Cml\Server;

use Cml\Vendor\UploadFile;

class UploadServer extends Server

{

/**

* 上传图片

*/

public static function uploadImage()

{

$uploadFile = new UploadFile(array(

'subDir' => true,

'subDirType' => 'date'

));

if ($uploadFile->upload(Config::get('upload_file_root').'images')) {

die('{"file" : "'.$uploadFile->getSuccessInfo()[0]['savename'].'", "id" : "'.$_POST['id'].'"}');

} else {

die('{"error" : 102, "message" : "'.$uploadFile->getErrorInfo().'", "id" : "'.$_POST['id'].'"}');

}

}

}

可以看到 类似 Controller   Server 的命名空间是以 应用名\Server 开始,且类名必须加上 Server 后缀，继承 \Cml\Server基类

当然了这边也不仅限于Server你还可以建类型Logic等等 命名空间记得把Server改成Logic即可

视图

本节主要介绍了 CmlPHP 中的视图。包含了 Html 、 Blade 、 Json 、 Excel 、 Xml 等视图引擎

统一的API

在CmlPHP中视图包括了 Html 、 Json 、 Xml 、 Excel 、调用方式也非常简单

\Cml\View::getEngine('Html') 获取Html渲染引擎

\Cml\View::getEngine('Json') 获取Json渲染引擎

\Cml\View::getEngine('Xml') 获取Xml渲染引擎

\Cml\View::getEngine('Excel') 获取Excel渲染引擎

赋值到视图

\Cml\View::getEngine('XXX')->assign('key', $val);普通赋值

\Cml\View::getEngine('XXX')->assignByRef('key', $val); 引用赋值 //除非要在模板中改动变量的值不然直接用assign即可(引用计数)

渲染输出

\Cml\View::getEngine('XXX')->display($tpl);//当前应用下的$tpl模板

\Cml\View::getEngine('XXX')->display($tpl, 'common'); //common应用下的$tpl模板

这边的 $tpl 只针对 Html 渲染引擎 在项目目录说明中说过 View  目录是模板存放目录， display() 中的$tpl就是相对于此目录的无后缀文件名比如有模板文件 Application/web/View/Goods/list.html 这个模板文件则调用为 \Cml\View::getEngine('Html')->display('Goods/list')  不用带后缀，默认为html后缀，如要修改请参考配置章节修改相应 'html_template_suffix' => '.html' 配置项

Html模板引擎

CmlPHP使用自主开发的模板引擎体积小巧、简单高效，下面是语法说明 分隔符可以在配置文件中修改

模板定界符

'TMPL_LEFT_DEPER' => '{', //模板左定界符

'TMPL_RIGHT_DEPER' => '}', //模板右定界符

默认分隔符为 {{}}

标签说明

直接输出变量

//入库转译只需一次，输出的时候转译要n次。所以这边输出不做htmlspecialchars转译。建议入库前转义。

{{$a}}

{{+$a}}  htmlspecialchars转译输出 v2.7.1+可用

输出数组

//入库转译只需一次，输出的时候转译要n次。所以这边输出不做htmlspecialchars转译。建议入库前转义。

- 一维数组:  {{$a['key']}} 或者 {{$a.key}]}   {{+$a['key']}} 或者 {{+$a.key}]}  htmlspecialchars转译输出 v2.7.1+可用

- 二维数组:  {{$a['key1']['key2']}} 或者  {{$a.key1.key2}}   {{+$a['key1']['key2']}} 或者  {{+$a.key1.key2}}  htmlspecialchars转译输出 v2.7.1+可用

- 多维数组:  {{echo $a[..]....}}  用echo输出  {{+echo $a[..]....}}  htmlspecialchars转译输出 v2.7.1+可用

判断语句

{{if $abc <1 }}

。。。。

{{elseif}} 【可选】

。。。。

{{else}}  【可选】

。。。。

{{/if}}

循环语句

loop

{{loop $abc $key $val}} 【$key可选】

。。。

{{/loop}}

或

foreach

{{foreach $abc $key $val}} 【$key可选】

。。。

{{/foreach}}

包含公共模板

{{template /Site/Index/index}}

// 包含项目目录`/Views/Site/Index/index.html`模板文件

执行简单php代码

{{eval ...}}

执行某些函数

{{echo trim('  abc  ')}}

格式化日期时间

{{datetime $time|Y-m-d H:i:s }}

设置插件挂载点

{{hook test}}

获取 get 、 post 、 request 请求参数

{{get name}}、{{post name}}、{{request name}}

获取配置文件配置项

{{config key1.key2}}

获取语言包配置项

{{lang key1.key2}}

根据 url_model 配置项，生成相应的url(如果是在子目录自动处理)

{{url 'Index/index'}}

生成的地址可能是:

/index.php/Index/index

/Index/index.html

/index.php?r=/Index/index

subdir/Index/index.html

subdir/index.php/Index/index

...

生成表单令牌

{{token}}

输出静态文件目录基地址

{{public}}

生成的地址如:

http://baidu.com/index.php  生成 /

http://baidu.com/public/index.php 生成/public/

http://baidu.com/subdir/public/ 生成/subdir/public/

修改配置`static__path` => 'http://img.attach.com/' 生成http://img.attach.com/

生成指静态文件目录的路径(自动处理项目部署在二级三级目录的问题)，如果是存放在cdn/单独服务器有独立域名 ，只要修改配置文件的 static__path 配置项为相应地址即可

静态文件管理

{{assert xxx}}

在模板中输出静态文件的地址使用 {{assert}} 命令,如有静态文件 web/Resuorce/js/1.js  则在模板中使用  {{assert web/js/1.js}} 。 当我们开启debug时，使用 assert 框架会启用一个简单的文件服务器。此时我们在模板中的标签 {{assert web/js/1.js}} 会替换成,类似这样的地址,修改文件内容时实时生效。当关闭debug时生成的地址则是相对于  {{public}} 标签指向的目录。当我们设置 static__path 为 站点根目录/public时   {{assert web/js/1.js}} 生成的地址则为 http://mysite/public/web/js/1.js 。此时 public 目录中并没有这个静态文件，静态文件是存在于应用目录下的  Resource 中，此时我们要使用静态资源管理工具去管理静态资源目录，点击查看静态资源管理。

模板中判断有无某个模块的权限

使用模板标签 {{acl xxx}}

{{acl user/add}}

我有添加用户的权限

{{/acl}}

或

{{acl user/add}}

我有添加用户的权限

{{else}}

我没有添加用户的权限

{{/acl}}

模板布局

模板布局文件都是声明在 应用名/View/layout 下，如声明一个模板布局文件  xxx/View/layout/master.html  内容为:

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no" />

<title>标题</title>

<link href="{{public}}css/Test/style.css" rel="stylesheet" type="text/css" media="screen" />

</head>

<body>

{{block content}}

aaa

{{/block}}

</body>

</html>

我们看到有 {{block xxx}} {{/block}} 标签 即声明一个区块 建一个模板文件 xxxx/View/Index/index.html ，内容为:

{{to content}}

内容内容内容

{{parent}}

{{/to}}

在控制器中 使用布局渲染模板

//使用当前应用下的master部局文件,当前应用下的Index/index.html模板

\Cml\View::getEngine('Html')->->displayWithLayout('Index/index', 'master');

//使用base应用下的master部局文件,当前应用下的Index/index.html模板

\Cml\View::getEngine('Html')->->displayWithLayout('Index/index', 'master',  'base');

//使用base应用下的master部局文件,common应用下的Index/index.html模板

\Cml\View::getEngine('Html')->->displayWithLayout('Index/index', 'master', 'base', 'common');

渲染后的页面为

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8" />

<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no" />

<title>标题</title>

<link href="{{public}}css/Test/style.css" rel="stylesheet" type="text/css" media="screen" />

</head>

<body>

内容内容内容

aaa

</body>

</html>

{{urldeper}} 标签

参考常见问题

模板注释

{{comment 我只是一个模板注释}}

代码注释，编译后会自动删除

自定义一个模板标签.

/**

* 添加一个模板标签

*

* @param string $pattern 正则

* @param string $replacement 替换成xx内容

* @param bool $haveDelimiter $pattern的内容是否要带上左右定界符

*

* @return bool

*/

public function addRule($pattern, $replacement, $haveDelimiter = true)

{

if ($pattern && $replacement) {

$this->pattern = $haveDelimiter ? '#'.$this->options['leftDelimiter'].$pattern.$this->options['rightDelimiter'].'#s' : "#{$pattern}#s";

$this->replacement = $replacement;

return true;

}

return false;

}

Blade模板引擎

balde模板引擎，提取自laravel，适用于cmlphp

安装

使用composer安装

composer require linhecheng/cmlphp-ext-blade

使用

一般性标签参考官方文档。

官方文档: http://laravel.com/docs/5.1/blade

cmlphp中定制的模板标签

设置插件挂载点

@hook(test)

格式化日期时间

@datetime($time|Y-m-d H:i:s)

获取 get 、 post 、 request 请求参数

@get(key)、@post(key)、@request(key)

获取配置文件配置项

@config(key1.key2)

获取语言包配置项

@lang(key1.key2)

根据 url_model 配置项，生成相应的url(如果是在子目录自动处理)

@url(Index/index)

生成的地址可能是

/index.php/Index/index

/Index/index.html

/index.php?r=/Index/index

subdir/Index/index.html

subdir/index.php/Index/index

...

生成表单令牌

@token

输出静态文件目录基地址

@public

生成的地址如:

http://baidu.com/index.php  生成 /

http://baidu.com/public/index.php 生成/public/

http://baidu.com/subdir/public/ 生成/subdir/public/

修改配置`static__path` => 'http://img.attach.com/' 生成http://img.attach.com/

生成指静态文件基础目录的路径即入口文件所在目录(自动处理项目部署在二级三级目录的问题)，如果是存放在cdn/单独服务器有独立域名 ，只要修改配置文件的 static__path 配置项为相应地址即可

静态文件管理

@assert(xxx)

在模板中输出静态文件的地址使用 @assert 命令,如有静态文件 web/Resuorce/js/1.js  则在模板中使用  @assert( web/js/1.js) 。 当我们开启debug时，使用 assert 框架会启用一个简单的文件服务器。此时我们在模板中的标签 {{assert web/js/1.js}} 会替换成,类似这样的地址,修改文件内容时实时生效。当关闭debug时生成的地址则是相对于  @public 标签指向的目录。当我们设置 static__path 为 站点根目录/public时   {{assert web/js/1.js}} 生成的地址则为 http://mysite/public/web/js/1.js 。此时 public 目录中并没有这个静态文件，静态文件是存在于应用目录下的  Resource 中，此时我们要使用静态资源管理工具去管理静态资源目录，点击查看静态资源管理。

模板中判断有无某个模块的权限

使用模板标签 @acl(xxx)

@acl(user/add)

我有添加用户的权限

@endacl

或

@acl(user/add)

我有添加用户的权限

@else

我没有添加用户的权限

@endacl

@urldeper 标签

参考常见问题

自定义一个模板标签.

\Cml\View::getEngine('blade')->addRule('pp', function($pp) {

return 'ddd'.$pp.'dddd';

});

锁-并发处理

在实际的开发中我们经常会碰到并发的问题，这时候根据业务场景可以使用不同的方式去处理并发的问题。比如使用锁机制、队列的使用等。在 CmlPHP 要使用队列可以参考队列服务。同时 CmlPHP 也提供了易于使用的锁功能。

- 在控制器中使用锁  $this->locker()或者\Cml\Lock::getLocker() 获取锁实例

- 在Server中使用锁  $this->locker()或者\Cml\Lock::getLocker() 获取锁实例

- 其它地方统一使用 \Cml\Lock::getLocker() 获取锁实例

锁实例有 lock() 和 unlock() 方法//lock如果未unlock会在脚本执行结束统一解锁

v2.7开始在入口中配置使用的锁  \Cml::getContainer()->singleton('cml_lock', \Cml\Lock\Redis::class);

内置\Cml\Lock\File::class｜\Cml\Lock\Redis::class｜\Cml\Lock\Memcache::class三种

内置的redis锁跟/memcache锁 跟缓存服务挂钩,可以通过配置文件中 'locker_use_cache' => 'default_cache' 配置, 默认使用 default_cache  也可以在获取锁实例的时候手动指定如:  \Cml\Lock::getLocker('default_cache') 指定使用default_cache中配置的缓存

缓存

CmlPHP在model层做了缓存集成(实时的,一量有数据变更会自动更新缓存)。一般情况下开发人员无需关注缓存问题。对于高并发的场景如:门户/资讯类的页面，对实时性要求不高，但是并发又很高的情况。如果还是实时更新缓存的话容易出现缓存雪奔的情况。这个时候开发人员可根据实际业务需要单独加缓存。使用方法如下:

//某控制器中

$articleType1 = Model::getInstance()->cache()->get('article:type:1');

if (!$articleType1) {

//从model中取

$articleType1 = 111;

//设置10分钟缓存

Model::getInstance()->cache()->set('article:type:1', $articleType1, 600);

}

//某Model中

$articleType1 = $this->cache()->get('article:type:1');

if (!$articleType1) {

//从model中取

$articleType1 = 111;

//设置10分钟缓存

$this->cache()->set('article:type:1', $articleType1, 600);

}

return $articleType1;

语言包

说明：

CmlPHP中支持多语言， web/Lang 目录为语言包存放目录，默认生成了zh-cn.php 中文语言包存放在此目录下，如果有多个语言包直接copy  zh-ch.php 再修改相应键值对，如 en-us.php 为英文语言包，如果要将en-us设为默认语言只要修改配置文件

'lang' =>'en-us',

即可，如果是动态切换语言将语言包require进来覆盖即可

\Cml\Lang::set(array());

如果是多个模块要共用语言包。也可以跟配置文件一样，自定义全局的语言包。存放路径为  projxxx/Lang/xx语言.php

使用:

语言包  '_NOT_FOUND_' => '%s does not exist', //有一个参数

- 在程序中 \Cml\Lang::get('_NOT_FOUND_', $file)

- 在模板中  {{lang _NOT_FOUND_ , $file}}

v2.7.1起语言包支持占位符：

语言包: 'my name is {name}' => '我的名字是 {name}',

调用: \Cml\Lang::get('my name is {name}', ['name' => '林')

日志

CmlPHP中提供了简化的psr-3日志接口。在对其进行改进的过程中也是比较纠结是否要完全按照psr-3,甚至直接使用 monolog 这样强大的第三方包。但是在我直接用 monolog 后看了下内存占用,已经接近空业务的时候框架自身的占用了。而CmlPHP是定位轻量的开发框架。所以还是决定自己开发了一个简化的psr-3日志接口。

框架在线上模式的时候,默认Log只记录致命错误及未被捕获异常可以通过修改配置文件中 log_warn_log=>true 来记录更详细的日志。默认使用 File驱动 日志按日期保存在 Runtime/Logs 目录下。可以修改为 Redis 驱动。

涉及到Log主要的配置

/**日志配置**/

'log_warn_log' => false, //警告级别的日志默认不记录

'log_driver' => 'File', //日志驱动,内置File/Redis两种

'log_prefix' => 'cml_log',//会显示到日志内容中,同时当以redis为驱动的时候会做为队列的前缀

开发中使用 Log接口

\Cml\Log::debug($log, array $context = array()); //添加debug类型的日志

\Cml\Log::info($log, array $context = array()); //添加info类型的日志

\Cml\Log::notice($log, array $context = array()); //添加notice类型的日志

\Cml\Log::warning($log, array $context = array()); //添加warning类型的日志

\Cml\Log::error($log, array $context = array()); //添加error类型的日志

\Cml\Log::critical($log, array $context = array()); //添加critical类型的日志

\Cml\Log::emergency($log, array $context = array()); //添加emergency类型的日志

不同类型的日志存储 File驱动存储的路径为  Runtime/Logs/年/月/日/类型.log

在Redis中为cmllog类型名称的List。 如: 驱动为File时。框架在线上模式出现致命错误(如数据库连接不上)的时候。log文件为projxxxx\Runtime\Logs\2015\12\23\emergency.log (当天日期为2015/12/23) 驱动为Redis时。框架在线上模式出现致命错误(如数据库连接不上)的时候。Redis的List名为 cml_log_emergency

调试

CmlPHP中封装了 \Cml\dump() 和 \Cml\dd() 两个格式化输出函数 在 v2.5.12中加入php-console的支持,所以加了\Cml\dumpUsePHPConsole()方法  \Cml\dd() 为 \Cml\dump();exit() dump并中止

\Cml\dump()函数开发模式下打印调试信息显示在浏览器的效果

\Cml\dump()函数线上模式打印调试信息默认不显示在浏览器而是显示在浏览器控制台（再也不用担心线上程序出bug调试不方便了，完美解决线上程序调试不方便的问题）

除了手工使用\Cml\dump、\Cml\dd打印的调试信息外, 在debug=true下,框架本身就提供了详尽的调试信息.

- 普通请求会看到页面下的cmlphp控制台里面显示了程序运行信息

- ajax请求(Html渲染引擎),打开浏览器调试工具,会在浏览器控制台看到程序运行信息

- ajax请求(Json渲染引擎),打开浏览器调试工具,会在浏览器请求响应中看到返回的sql信息

在debug=true开发环境下以上几种调试信息一般已经满足开发中的调试需求了。

除了以上的框架自带调试信息外,自v2.5.12起框架也加入了php-console的支持,使用前先做以下几个准备工作: 1.使用composer安装php-console  composer require php-console/php-console ~3.0  2.安装chrome插件 https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef 3.修改配置文件

/*php-console配置*/

'dump_use_php_console' => true, //开启本功能需要先安装php-console composer require php-console/php-console ~3.0

'php_console_password' => '密码',

开启 dump_use_php_console 后正式环境(debug=false)下使用\Cml\dump、\Cml\dd打印的信息会被php-console接管,debug=true的开发模式下,ajax请求框架自身的调试信息也会被php-console接管,见以下效果图

Session自定义保存位置

php中默认session保存在文件中，在一个应用中如果session过多，用户量又很大的时候很可能会遇到磁盘IO瓶颈，又或者是服务器多台的时候session分布式存储的问题。。。。在CmlPHP中你可以通过修改配置文件灵活的把session保存到db\cache中，

/*Session设置*/

'SESSION_PREFIX' => 'cml_', //session前缀

'SESSION_USER' => 0, //SESSION保存位置自定义 0不开启、1开启

'SESSION_USER_LOC' => 'db', //自定义保存SESSION的位置时 定义保存的地方  db、cache两种

当session保存的文件设置为cache的时候，所使用的缓存为 default_cache 中配置的缓存

框架自带的扩展包

CmlPHP支持Composer可以使用海量的第三方扩展包,同时CmlPHP也内置了常用的扩展工具。

特别说明,这边的扩展工具类是我多年开发下来经常会用到的,有一些是从网上找的资源(二次修改或没有),如果有出处/版权疑义的请联系我

- Cml\Vendor\Acl  简单的权限管理类

- Cml\Vendor\Email  邮件发送类

- Cml\Vendor\Excel  Excel导出

- Cml\Vendor\File  文件处理

- Cml\Vendor\Ftp  Ftp工具

- Cml\Vendor\Html  Html静态页面生成工具

- Cml\Vendor\Http  Http扩展类

- Cml\Vendor\Image  图片处理工具

- Cml\Vendor\IpLocation  ip地理位置查询类

- Cml\Vendor\Page  分类处理工具

- Cml\Vendor\PhpThread  php模拟多线程处理工具

- Cml\Vendor\Pingyin  汉字转拼音工具

- Cml\Vendor\Quenu  队列实现类

- Cml\Vendor\Socket  Socket客户端扩展

- Cml\Vendor\StringProcess  字符串处理扩展

- Cml\Vendor\Tree  无限级分类

- Cml\Vendor\UploadFile  文件上传处理

- Cml\Vendor\Validate  数据验证类

- Cml\Vendor\VerifyCode  验证码生成类

插件

本章节主要介绍了插件的使用以及系统内置的钩子

使用说明

CmlPHP中插件的实现通过钩子来实现，模板中使用 {{hook test}} 设定一个名称为test的插件钩子，在程序中可使用\Cml\Plugin::hook('test');设定一个名称为test的插件钩子，当程序执行到设置hook的位置时，如果有插件挂载到这个钩子，执行该插件，一个钩子可以绑定多个插件.

CmlPHP中的插件信息通过plugin.php这个插件配置文件来配置， 如：

<?php

//应用插件配置文件

//  \Cml\Plugin::mount('hookName'/*要挂载的钩子*/, array(

//        'ClassName' => 'run', //该hook执行的插件名 =>方法名

//        'ClassName2' => 'run' //一个hook可挂载多个插件

//  ));

//我们就在test这个钩子上挂载了两个插件

插件存放在应用目录/Plugins目录中 插件文件的命名规则为 插件名Plugin.php如：我们定义一个名为Test的插件 TestPlugin.php 插件内容：

<?php

/**

* 测试插件

*

*/

class TestPlugin {

public function run() {

echo '测试插件';

}

}

在相应的位置使用  {{hook xxx}}  或 Cml\Plugin::hook（xxx）  设置钩子，在配置中挂载上Test插件的run方法即可

系统挂载点

Plugin::mount('cml.before_set_time_zone');//设置时区前。用于设置多时区 v2.6.6+可用

Plugin::mount('cml.before_fatal_error', $error);//抛出致命错误前

Plugin::hook('cml.after_fatal_error', $error);  //v2.6.8+可用，抛出致命错误后。此时php内置gc已经工作，挂载插件最好使用闭包。返回jump时。跳出致命错误

Plugin::mount('cml.before_throw_exception', $e);//抛出异常前

Plugin::mount('cml.before_parse_url');//解析路由前

Plugin::mount('cml.after_parse_url');  //解析路由后

Plugin::mount('cml.before_run_controller');//执行控制器前

Plugin::mount('cml.before_show_404_page'); //显示404错误页前

Plugin::mount('cml.before_cml_stop');//程序运行结束前

Plugin::mount('cml.cache_server_down', ['host' => $host, 'port' => $port] );//缓存服务器down了 v2.6.8+可用

Plugin::hook('cml.before_run_api_test');//运行api自动测试前 v2.7.7+可用

Plugin::hook('cml.before_run_api_test_one_api', $api)；//运行某个api方法前 v2.7.7+可用

常用常量

CML_PROJECT_PATH  工程根目录绝对路径。

CML_APP_FULL_PATH  项目目录绝对路径。v2.7起改用 Cml::getApplicationDir('apps_path')

CML_RUNTIME_PATH  运行时目录绝对路径。v2.7起改用 Cml::getApplicationDir('global_store_path')

CML_RUNTIME_CACHE_PATH  系统缓存目录绝对路径。v2.7起改用 Cml::getApplicationDir('runtime_cache_path')

CML_RUNTIME_LOGS_PATH  系统日志目录绝对路径。v2.7起改用 Cml::getApplicationDir('runtime_logs_path')

CML_RUNTIME_DATA_PATH  数据库字段缓存绝对路径。上线后若有改主键名请删除该目录 v2.7起改用Cml::getApplicationDir('global_store_path') . DIRECTORY_SEPARATOR.'Data'

命令行运行程序

在开发->配置->cli章节说过,在cli环境下 框架默认载入的是  Config/cli/normal.php  这个通用配置文件

在v2.7.1以下的版本中要在命令行中运行应用可采用以下方式:

比如: 通过系统计划任务执行程序。可以采用以下命令:

php /data/www/cmlproject/index.php web/Log/intoDb

即有一个采用CmlPHP的项目路径为 /data/www/cmlproject/ 入口文件名为index.php 我们用php命令运行它 执行 web/LogController.php 控制器文件里的 intoDb 方法。

自v2.7.1起,CmlPHP提供了强大且易于扩展的的命令行组件,内置了提问、选择、进度条、box框等，方便开发命令行下应用。

内置以下几种命令:

示例:

- php index.php symlink  为直接运行symlink指令

- php index.php help symlink  |  php index.php symlink -h  |  php index.php symlink --help 为获取symlink的帮助信息

- php index.php api-test --app=api 执行api自动测试并将参数app的值设了api

也就是在v2.7.1+版本中要运行应用除了采用以上方式:

php /data/www/cmlproject/index.php web/Log/intoDb

还支持：

php /data/www/cmlproject/index.php run-action web/Log/intoDb 这样的方式运行

其它命令的使用方式请参考本手册相关章节或使用  php index.php help command_name 获取相应命令的帮助信息。

自定义命令

用户如果要自定义命令只要继承 \Cml\Console\Command 类实现相对应的方法。

然后在全局配置文件目录下新增 command.php 。返回一个类似下例的数组注册命令即可:

return [ 'test:hahaha' => 'Cml\Console\Commands\Migrate\SeedRun',];

守护工作进程

自v2.5.11引入守护进程功能 顾名思义，守护进程是以cli的方式启动的，所以配置相关参考cli章节。

守护进程主要支持以下几个命令

- 启动后台进程

- v2.7.1+版本使用  php index.php worker:start

- v2.7.0及以下版本使用  php index.php cml.cmd Daemon\\ProcessManage::run start

- 添加任务,运行频率为60s

- v2.7.1+版本使用  php index.php worker:add-task \\web\\Controller\\DefaultController::index 60

- v2.7.0及以下版本使用  php index.php cml.cmd Daemon\\ProcessManage::run addtask \\web\\Controller\\DefaultController::index 60

- 删除任务

- v2.7.1+版本使用 php index.php worker:rm-task \\web\\Controller\\DefaultController::index

- v2.7.0及以下版本使用  php index.php cml.cmd Daemon\\ProcessManage::run rmtask \\web\\Controller\\DefaultController::index

- 查看进程运行状态(包括任务信息)

- v2.7.1+版本使用  php index.php worker:status

- v2.7.0及以下版本使用  php index.php cml.cmd Daemon\\ProcessManage::run status

- 修改了任务文件的时候reload工作进程使代码生效

- v2.7.1+版本使用  php index.php worker:reload

- v2.7.0及以下版本使用  php index.php cml.cmd Daemon\\ProcessManage::run reload

- 终止后台进程

- v2.7.1+版本使用  php index.php worker:stop

- v2.7.0及以下版本使用  php index.php cml.cmd Daemon\\ProcessManage::run stop

队列服务

队列的用处就不用多说了，v2.5.11中封装了统一的队列接口。目前封装了Redis驱动。默认使用 default_cache 中配置的redis。

驱动配置

//v2.7+

\Cml::getContainer()->singleton('cml_queue', \Cml\Queue\Redis::class);

用法

\Cml\Quene::getQueue();获取驱动实例

\Cml\Quene::getQueue('default_cache');获取驱动实例并指定使用的缓存配置

可通过修改配置文件中 'queue_use_cache' => 'default_cache', 修改只有在该缓存的驱动为redis的时候才有效,否则会报错。

目前封装的api:

/**

* 从列表头入队

*

* @param string $name

* @param mixed $data

*

* @return mixed

*/

public function lPush($name, $data)

/**

* 从列表头出队

*

* @param string $name

*

* @return mixed

*/

public function lPop($name)

/**

* 从列表尾入队

*

* @param string $name

* @param mixed $data

*

* @return mixed

*/

public function rPush($name, $data)

/**

* 从列表尾出队

*

* @param string $name

*

* @return mixed

*/

public function rPop($name)

/**

* 弹入弹出

*

* @param string $from

* @param string $to

*

* @return mixed

*/

public function rPopLpush($from, $to)

权限管理

在开发中经常需要用来权限管理，cmlphp中提供了权限控制类, \Cml\Vendor\Acl 。这边对使用进行说明。

数据库建表

Acl 类依赖的数据表如下，请复制到mysql中执行。 语句中的 pr_ 为表前缀，请自行替换在实际的表前缀。

建库语句

CREATE TABLE `pr_admin_app` (

`id` smallint(6) unsigned NOT NULL AUTO_INCREMENT,

`name` varchar(255) NOT NULL DEFAULT '' COMMENT '应用名',

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

CREATE TABLE `pr_admin_access` (

`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '权限ID',

`userid` int(11) DEFAULT '0' COMMENT '所属用户权限ID',

`groupid` int(11) DEFAULT '0' COMMENT '所属群组权限ID',

`menuid` int(11) NOT NULL DEFAULT '0' COMMENT '权限模块ID',

PRIMARY KEY (`id`),

KEY `idx_userid` (`userid`) USING BTREE,

KEY `idx_groupid` (`groupid`) USING BTREE,

KEY `idx_menuid` (`menuid`) USING BTREE

) ENGINE=MyISAM AUTO_INCREMENT=190 DEFAULT CHARSET=utf8 COMMENT='用户或者用户组权限记录';

CREATE TABLE `pr_admin_groups` (

`id` smallint(3) unsigned NOT NULL AUTO_INCREMENT,

`name` varchar(150) DEFAULT NULL,

`status` tinyint(1) unsigned DEFAULT '1' COMMENT '1正常，0删除',

`remark` text NOT NULL,

PRIMARY KEY (`id`)

) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;

CREATE TABLE `pr_admin_menus` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`pid` int(11) NOT NULL DEFAULT '0' COMMENT '父模块ID编号 0则为顶级模块',

`title` char(64) NOT NULL COMMENT '标题',

`url` char(64) NOT NULL COMMENT 'url路径',

`isshow` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否显示',

`sort` smallint(3) unsigned NOT NULL DEFAULT '0' COMMENT '排序倒序',

`app` smallint(6) unsigned NOT NULL DEFAULT '0' COMMENT '菜单所属app，对应app表中的主键',

`params` varchar(64) NOT NULL DEFAULT '' COMMENT 'url参数',

PRIMARY KEY (`id`),

KEY `idex_pid` (`pid`) USING BTREE,

KEY `idex_order` (`sort`) USING BTREE,

KEY `idx_action` (`url`)

) ENGINE=MyISAM AUTO_INCREMENT=55 DEFAULT CHARSET=utf8 COMMENT='权限模块信息表';

CREATE TABLE `pr_admin_users` (

`id` int(11) unsigned NOT NULL AUTO_INCREMENT,

`groupid` varchar(255) NOT NULL DEFAULT '0',

`username` varchar(40) NOT NULL DEFAULT '',

`nickname` varchar(50) DEFAULT NULL COMMENT '昵称',

`password` char(32) NOT NULL DEFAULT '',

`lastlogin` int(10) unsigned NOT NULL DEFAULT '0',

`ctime` int(10) unsigned NOT NULL DEFAULT '0',

`stime` int(10) unsigned NOT NULL DEFAULT '0',

`status` tinyint(1) unsigned DEFAULT '1' COMMENT '1正常，0删除',

`remark` text NOT NULL,

`from_type` tinyint(3) unsigned DEFAULT '1' COMMENT '用户类型。1为系统用户。',

PRIMARY KEY (`id`),

UNIQUE KEY `username` (`username`)

) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;

数据表说明

- pr_admin_group 为用户组表

用来存储用户组相关信息。

- pr_admin_users 为用户信息表

用来存储用户相关信息。一个用户可能属于多个用户组，比如某个用户同时属于id为 1、3的用户组。则用户表中的 groupid 字段存储的数据则为  1|3 。多个 组id 以 | 分隔

- pr_admin_menu 为菜单表

用来存储菜单相关信息。数据格式类似  url 字段为访问的操作的地址

- pr_admin_access 为权限关系表

用来存储权限相关信息。

- pr_admin_app 为应用表

用来存储应用相关信息,app表和权限管理无关只是用来更细粒度的管理我们的菜单。

修改配置

在配置文件中加入如下配置:  'administratorid'=> 1, //超管理员id

这边的1是指 pr_admin_users 表中的 id 。这个配置的作用是标识 id 为1的用户为超级管理员。 超级管理员默认拥有所有权限

在php中使用

- 保存登录信息

<?php

use Cml\Vendor\Acl;

//判断用户名密码....登录是否成功

//登录成功后保存用户登录信息

Acl::setEncryptKey('ffddsff');//配置加密key 这个最好在共公控制器的init方法/__construct方法里设置

Acl::setLoginStatus($user['id']); //设置某用户为登录判断

- 判断用户是否已登录以及是否有权限

<?php

use Cml\Vendor\Acl;

$user = Acl::getLoginInfo();

if (!$user) {//未登录

//提示用户登录

}

if (!Acl::checkAcl($this)) {//无权限

//提示用户没没权限

}

- 获取当前登录用户有权限的菜单

<?php

use Cml\Vendor\Acl;

$menus = Acl::getMenus();//包含用户自有的权限、以及其所在用户组所拥有的权限

- 登出

<?php

use Cml\Vendor\Acl;

Acl::logout();//包含用户自有的权限、以及其所在用户组所拥有的权限

- 判断当前用户是否是超级管理员

<?php

use Cml\Vendor\Acl;

$isSuperUser = Acl::isSuperUser();

- 自定义表名

如果想要自定义表名可以如下操作:

Acl::setTableName([

'access' => 'admin_access',

'groups' => 'admin_groups',

'menus' => 'admin_menus',

'users' => 'admin_users',

]);

app

特殊说明

通常情况我们会建一个共用的控制器。在共用控制器的 init 方法或 __construct 方法里，验证用户是否登录以及是否拥有当前模块的访问权限。这时是所有对外的方法都会被验证。但是在实际项目中我们往往会想要跳过一些方法的权限校验，比如:

我们有个用户管理。用户管理有

- add  方法用来渲染输出用户添加表单。

- edit  方法用来渲染编辑用户资料的表单。

- save  方法用来接收  add 以及 edit 这两个页面提交过来的数据。

按照正常流程我们需要添加三个菜单分别是  user/add  、  user/edit  、 user/save 三条记录到 pr_admin_menu 表。然后给用户授权的时候如果只给添加权限，也必须添加 add和save 的记录到 pr_admin_access 表中或者是只添加 add 记录然后在 save 方法中判断有无 add 的权限。这样是非常不方便的。所以在cmlphp中提供了两个偷懒的方法。

- 对方法注释  @noacl  则不检查该方法的权限

比如有一个方法 resume .想要跳过权限检查则注释如下即可

/**

* 恢复微博

* @noacl

*

*/

public function resume()

- 对方法注释  @acljump web/User/add  则将当前方法的权限检查跳转为检查  web/User/add 方法的权限

比如上面说的  user/save 权限只要判断有无 user/add 或 user/edit 的权限就好了。

/**

* 保存用户信息

* @acljump user/add|user/edit

*

*/

public function save()

模板中判断有无某个模块的权限

参考视图相关章节

示例

可参考示例后台项目

数据验证

数据验证做为开发中不可缺少的一环。在cmlphp中也提供了简单易用的数据验证工具。

用法

v2.6.8以下版本

<?php

use Cml\Vendor\Validate;

if (Validate::isUrl($description)) {

}

if (Validate::isIp($ip)) {

}

//更多方法参考 http://api.cmlphp.com/Cml/Vendor/Validate.html

v2.6.8+

在v2.6.8版本中对Validate做了增强,除了以上的直接调用方法外，还可以有以下用法:

<?php

use Cml\Vendor\Validate;

//初始化实例 传入包含要验证数据的数组 这边是$_POST

$v = new Validate($_POST);

//type、ptime、status、action 必传

$v->rule('require', ['type', 'ptime', 'status', 'action']);

//type、ptime、status 必须为整形

$v->rule('int', ['type', 'ptime', 'status']);

//ptime只能在0-23范围内

$v->rule('in', ['ptime'], range(0, 23));

//设置字段->实际显示字段的映射关系

$v->label(array(

'type' => '类型',

'ptime' => '执行时间/间隔时间',

'status' => '状态',

'action' => '操作',

));

//批量校验

if(!$v->validate()) {

$error = $v->getErrors();//获取错误信息

}

上面大概演示了如何使用cmlphp自带的 Validate 组件进行批量数据验证。下面列出所有的规则以及如何自定义规则。

内置验证规则

'require' => "不能为空",

'gt' => "必须大于 %s",

'lt' => "必须小于 %s",

'lengthGt' => "长度必须大于 %d",

'lengthLt' => "长度必须小于 %d",

'in' => "无效的值",

'notIn' => "无效的值",

'length' => "字符串长度必须在 %d, %d之间",

'empty' => "必须为空",

'equals' => "必须和 '%s' 一致",

'different' => "必须和 '%s' 不一致",

'arr' => "必须是数组",

'email' => "无效邮箱地址",

'ip' => "无效IP地址",

'number' => "只能是数字",

'int' => "只能是整数",

'bool' => "只能是布尔值",

'card' => "必须是身份证",

'mobile' => "手机号码不正确",

'phone' => "固话格式不正确",

'url' => "无效的URL",

'zip' => "邮政编码不对",

'qq' => "qq号格式不正确",

'english' => "只能包括英文字母(A-za-z)",

'chinese' => "只能为中文"

自定义验证规则

除了以上内置的校验规则外，还可以自定义验证规则，方法如下:

<?php

use Cml\Vendor\Validate;

//添加

Validate::addRule('test', function($value, $params) {

//$value的值就是$_POST['test']的值

//$params是参数数组，就是下面$v->rule...中的[1, 2]

return false;

}, '就是不让你通过检验');

$v = new Validate($_POST);

//使用

$v->rule('test', 1, 2);

...

提示信息

内置的验证规则，提示信息跟随系统的语言设置。当框架的语言包为哪个语言时验证规则也显示哪个语言。语言包存放路径为框架的 Vendor\Validate\Lang 目录下。也可以在实例化的时候自定义语言包所在路径

<?php

use Cml\Vendor\Validate;

$v = new Validate($_POST, 'lang_dir');

如果不想用内置的语言包也可以使用  message 自定义提示信息，使用方法如下:

<?php

use Cml\Vendor\Validate;

$v = new Validate($_POST);

$v->rule('require', ['type', 'ptime', 'status', 'action'])

->message('{field} 必须填写!');

//其中{field}为占位符。有设置label时会替换为label,没设置label时会替换为相应的字段名

验证码

在表单中我们经常会用到验证码，cmlphp中提供了验证码组件, \Cml\Vendor\VerifyCode 。这边对使用进行说明。

生成显示验证码

<?php

use Cml\Vendor\VerifyCode;

//生成数字计算题验证码

VerifyCode::calocVerify();

//生成中文验证码

VerifyCode::CnVerify();

//生成数字验证码

VerifyCode::numVerify();

校验验证码是否正确

<?php

use Cml\Vendor\VerifyCode;

if (!VerifyCode::checkCode($code)) {

//提示用户输入错误

}

文件上传

cmlphp中提供了文件上传组件, \Cml\Vendor\UploadFile 。这边对使用进行说明。

<?php

use Cml\Vendor\UploadFile;

$fileupload = new UploadFile();

$res = $fileupload->upload(CML_PROJECT_PATH . '/public/upload');//设置保存路径

if ($res) {

$resp = $fileupload->getSuccessInfo();

$filename = $resp[0]['savepath'].$resp[0]['savename'];

} else {

$error = $fileupload->getErrorInfo();

}

//后续业务处理

数据库迁移

自v2.7.1起CmlPHP提供了数据库迁移组件

安装依赖

使用composer安装

composer require linhecheng/cmlphp-ext-phinx

使用

使用命令行控制台工具运行 migrate / seed 相关命令运行

- migrate:create  创建一个新的迁移

- migrate:run  运行迁移

- migrate:rollback  回滚迁移

- migrate:status  查看迁移状态

- migrate:breakpoint  管理迁移的断点

- seed:create  创建一个种子数据生成器

- seed:run  运行种子数据生成器

更多帮助信息在相应的命令后面带上  --help 或 -h 即可 默认使用的数据库配置项为 default_db  可修改配置  'migration_use_db' => 'migrate_db'

默认使用的迁移记录表名为 phinxlog  可修改配置  'migration_use_table' => 'pre_phinxlog',

迁移的编写

可参考phinx官方文档: http://docs.phinx.org/en/latest/migrations.html

部署

本节介绍项目部署的相关知识点

伪静态配置

本节介绍部署过程中伪静态的配置

Apache

<IfModule mod_rewrite.c>

RewriteEngine On

RewriteBase /

RewriteCond %{REQUEST_FILENAME} !-d

RewriteCond %{REQUEST_FILENAME} !-f

RewriteRule ^(.*)$ index.php?r=/$1 [QSA,PT,L]

</IfModule>

RewriteRule ^(.*)$ index.php?r=/$1 [QSA,PT,L]

规则中的?r=  r 是可配置的分隔符通过 var_pathinfo=>'r' 配置

Nginx

server {

listen       80;

server_name  app.dev;

root   D:/newfm/;

index index.php index.html;

location / {

try_files $uri $uri/ /index.php?r=$uri&$args;

}

location ~ .php$ {

try_files $uri =404;

include fastcgi.conf;

fastcgi_pass 127.0.0.1:9000;

}

}

try_files $uri $uri/ /index.php?r=$uri&$args;

规则中的?r=  r 是可配置的分隔符通过 var_pathinfo=>'r' 配置

静态资源管理

在模板标签的说明中说过,当我们开启debug时，使用 assert 框架会启用一个简单的文件服务器。当关闭debug时生成的地址则是相对于 {{public}} 标签指向的目录。当我们设置 static__path 为 站点根目录/public时   {{assert web/js/1.js}} 生成的地址则为 http://mysite/public/web/js/1.js 。此时 public 目录中并没有这个静态文件，静态文件是存在于应用目录下的  Resource 中，此时我们要使用静态资源管理工具去管理静态资源目录。在cml中有两种方式去使用管理工具。

1. 使用shell/cmd 执行以下命令生成

- v2.7.1+  php index.php make:symlink

- v2.7.0及以下版本  php index.php cml.cmd StaticResource::createSymbolicLink

框架会自动将项目下的所有应用下的 Resource 软链接到 public 下。

2.直接在php程序里调用 \Cml\Tools\StaticResource::createSymbolicLink(); 创建，注意控制权限即可。

使用命令管理静态文件需要使用 exec 方法,需要将 php.ini 中 disable_functions 的 exec 移除。由于开发环境和线上环境的软链接路径多是不一样的，所以上线后记得重新生成(假设开发环境有生成过)。 如果静态资源是存放在云存储上只要上传文件,同时修改配置文件的 static__path 配置项为相应地址

安全

本章介绍如何使用 CmlPHP 提供的工具去编写更安全的代码。

防注入

CmlPHP在db层防注入,对数据库的操作框架都已经做了防注入处理

用户数据输入

使用 \Cml\Http\Input类 进行处理,点击查看详细

csrf

框架默认开启对非本站域名post数据的检查，只要是非本站域post名过来的数据都返回403，可在配置中修改过滤选项

'CHECK_CSRF' => 1,

//检查csrf跨站攻击 0、不检查，1、只检查post数据提交方式，2、get/post都检查 默认只检查post

当然这只是最基本过滤，最好是使用表单令牌或者验证码来防止csrf,表单令牌的使用很简单 配置文件中设置

'FORM_TOKEN'=> 1, //表单令牌 0不开启，1开启 默认不开启

然后在模板的中设置 {{token}} 标签，框架会自动在其位置生成名称为 CML_TOKEN 的Input  <input type="hidden" name="CML_TOKEN" value="xxxxx" />  如：

<form method="post" action="">

{{token}}

</form>

在控制器中使用 \Cml\Secure::checkToken(); 检查令牌，通过返回true，不通过返回false

上面介绍了常见的安全过滤，更多的过滤方法参看\Cml\Secure类

加密解密

框架中提供了 \Cml\Encry 加密解密类

- \Cml\Encry::encrypt($data, $key = null)  加密接口

- \Cml\Encry::decrypt($data, $key = null)  解密接口

这边的key可以单独配置,如果没配置框架默认读取配置文件中 auth_key 配置的key。 注意:使用 \Cml\Http\Cookie 处理cookie的时候默认加密key就是使用 auth_key 配置的key。所以如果有使用到 \Cml\Encry 或者 \Cml\Http\Cookie 都请修改 auth_key 配置的key

常见问题

模板中不同的url_model模式下传参给后端 ? &符混乱的问题

我们在模板中使用标签 {{url 'Log/save'}}  生成一个地址 在 url_model=2 时 生成的地址为 /Log/save.html,假定我们要用js传参id=1给后端,这时模板写法为

var good = {id:1,name:'商品1'}

$.get('{{url 'Log/save'}}' + '?id='+good.id,         function() {

});

此时生成的静态代码为

var good = {id:1,name:'商品1'}

$.get('/Log/save.html' + '?id='+good.id, function() {

})

后端可正常接收参数 但是若某开发人员本地的环境只支持 url_model=3 则生成的静态代码为

var good = {id:1,name:'商品1'}

$.get('/index.php?r=/Log/save' + '?id='+good.id, function() {

})

因为已经有?r=这边的?号所以后面的?id=无效，此时后端无法正常接收参数，为了兼容CmlPHP中加了 {{urldeper}} 这个模板标签 此时模板写法只要改为

var good = {id:1,name:'商品1'}

$.get('{{url 'Log/save'}}' + '{{urldeper}}id='+good.id, function() {

})

则在 url_model=2 的模式下生成的地址为

var good = {id:1,name:'商品1'}

$.get('/Log/save.html' + '?id='+good.id, function() {

})

则在 url_model=3 的模式下生成的地址为

var good = {id:1,name:'商品1'}

$.get('/index.php?r=/Log/save' + '&id='+good.id, function() {

})

后端都可正常接收到参数

服务高可用

作为一个web服务，保证我们的系统24小时不间断对外服务是很重要的。在CmlPHP中也尽可能的让这个过程变得简单、易操作。

web服务(即php-fpm/nginx服务)

在实际项目中通常php跟nginx是部署在同一台上的。nginx对外跟用户交互。通过fast_cgi来跟php-fpm进程组通信。所以这边把这两者统一称做web服务。在项目允许的情况下尽可能根据需要部署多台nginx/php-fpm服务通过lvs/动态dns等方式进行轮询，至少电信一台网通一台让用户的请求尽快的得到响应。同时两台以上的机器在碰到网络异常等等的情况也不至于直接对外不可用。

数据库服务

通常在项目初期数据量较小、请求不多的时候是只采用单台db服务并定期备份数据。但是随着业务的增长，单台压力上来。我们可能会加入多台从库(mysql)/副本集、分片(mongo)。 在cmlphp中配置mysql读写分离的方式如下:

'default_db' => array(

'driver' => 'MySql.Pdo', //数据库驱动

'master'=>array(

'host' => '192.168.3.10', //数据库主机

'username' => 'user', //数据库用户名

'password' =>'', //数据库密码

'dbname' => 'test', //数据库名

'charset' => 'utf8mb4', //数据库编码

'tableprefix' => 'pre_', //数据表前缀

),

'slaves'=>array(

array(

'host' => '192.168.3.11', //数据库主机

'username' => 'user', //数据库用户名

'password' =>'', //数据库密码

'dbname' => 'test', //数据库名

'charset' => 'utf8mb4', //数据库编码

'tableprefix' => 'pre_', //数据表前缀

),

array(

'host' => '192.168.3.12', //数据库主机

'username' => 'user', //数据库用户名

'password' =>'', //数据库密码

'dbname' => 'test', //数据库名

'charset' => 'utf8mb4', //数据库编码

'tableprefix' => 'pre_', //数据表前缀

)

),

'cache_expire' => 3600,//查询数据缓存时间

),

我们这边看到这边配置了一主两从。框架会自动写到主库读从库。

缓存服务

在实际开发中，我们通常会将数据持久化到数据库中。通过使用缓存的方式来加快数据的读取，提高系统的并发能力。在一般场景中我们使用单台缓存服务就足够了（使用内网通信）。但是系统负载高的时候就需要多台cache时。cmlphp中缓存服务支持多台cache。

// 缓存服务器的配置

'default_cache' => array(

'on' => 1, //为1则启用，或者不启用

'driver' => 'Redis',

//'driver' => 'Memcache',

'prefix' => 'd_',

'server' => array(

array(

'host' => '127.0.0.1',

'port' => '6701'

),

array(

'host' => '127.0.0.1',

'port' => '6702'

),

),

),

这边配置了两台cache.多台直接配上去即可。这边有一台down掉。会自动容灾到其它机器。使用memcache驱动时使用驱动自带的集群功能。使用redis时当其中一台当掉自动容灾到相应的机器。 也可以配置一台back，当redis有当掉的时候则优先使用back中的机器替代配置如下。back配置项只支持redis

// 缓存服务器的配置

'default_cache' => array(

'on' => 1, //为1则启用，或者不启用

'driver' => 'Redis',

//'driver' => 'Memcache',

'prefix' => 'd_',

'server' => array(

array(

'host' => '127.0.0.1',

'port' => '6701'

),

array(

'host' => '127.0.0.1',

'port' => '6702'

),

),

'back' => array(

'host' => '127.0.0.1',

'port' => '6379'

)

),

升级日志

主要的变更直接看github的提交历史就好啦。这边主要对不兼容的地方做说明。

目前从v2.5.0 => v2.6.1都是可以直接升级的。不用修改任何代码

- v2.6.2起 废除了 getByPk 方法请统一用  getByColumn

- v2.6.2起 废除了 updateByPk 方法请统一用  updateByColumn

- v2.6.2起 废除了 delByPk 方法请统一用  delByColumn

从v2.7开始。重要组件服务化。各个组件使用容器来管理、注入依赖 array()写法改为[] php5.4+

- 主要不兼容的地方有移除了CML_APP_FULL_PATH、CML_RUNTIME_PATH、CML_RUNTIME_CACHE_PATH、CML_RUNTIME_LOGS_PATH、CML_APP_MODULES_PATH、CML_IS_MULTI_MODULES这几相常量并用相应的方法替代。 参考 常用常量

- Route::$urlParams。改为私有。不允许外部方法。可使用以下方法替换。具体参考api手册

Cml::getContainer()->make('cml_route')->getFullPathNotContainSubDir();

Cml::getContainer()->make('cml_route')->getSubDirName();

Cml::getContainer()->make('cml_route')->getAppName();

Cml::getContainer()->make('cml_route')->getControllerName();

Cml::getContainer()->make('cml_route')->getActionName();

v2.7.1起将命令行组件化。为了避免歧义，原 cmlframework_system_command 配置项改名为 cmlframework_system_route

Table of Contents

Introduction

CmlPHP简介

初始化一个项目

入口文件

项目目录说明

开发指引

配置

normal.php

common.php

plugin.php

route.php

其它配置项

配置的获取

动态修改配置

URL模式

路由

控制器

前置方法

请求、响应

Input

Request

Response

Cookie

Session

模型-MySQL

模型的调用方式

数据查询

分页查询

数据添加

获取上一次插入的id

数据更新

获取上一次更新影响的行数

数据删除

简单的单字段自增(多字段参考update)

简单的单字段自减(多字段参考update)

聚合操作

事务

调用存储过程

原生/子查询/union

快捷方法

同时查询多个库

模型-MongoDB

Orm详细Api

服务

视图

统一的API

Html模板引擎

Blade模板引擎

锁-并发处理

缓存

语言包

日志

调试

Session自定义保存位置

框架自带的扩展包

插件

使用说明

系统挂载点

常用常量

命令行运行程序

守护工作进程

队列服务

权限管理

数据验证

验证码

文件上传

数据库迁移

部署

伪静态配置

Apache

Nginx

静态资源管理

安全

防注入

用户数据输入

csrf

加密解密

常见问题

模板中不同的url_model模式下传参给后端 ? &符混乱

服务高可用

升级日志