diff --git a/README.md b/README.md index f9c5d4b..da62046 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ > TaoLer是一个简单迅捷的轻论坛系统,适用于个人或组织区域型信息交流发布平台。 * 官网:https://www.aieok.com - * 版本:TaoLer 1.9.1 - * 日期:2022.4.16 + * 版本:TaoLer 1.9.2 + * 日期:2022.4.19 webman版新架构已适配90% diff --git a/app/admin/controller/Addons.php b/app/admin/controller/Addons.php index 8f193e1..d0a1959 100644 --- a/app/admin/controller/Addons.php +++ b/app/admin/controller/Addons.php @@ -10,8 +10,6 @@ use think\facade\Config; use app\admin\model\Addons as AddonsModel; use taoler\com\Files; use taoler\com\Api; -use app\common\lib\SetConf; -use think\App; use app\common\lib\Zip; class Addons extends AdminController diff --git a/app/admin/controller/Seo.php b/app/admin/controller/Seo.php index c9ecc73..84e9dd1 100644 --- a/app/admin/controller/Seo.php +++ b/app/admin/controller/Seo.php @@ -2,7 +2,7 @@ /* * @Author: TaoLer * @Date: 2022-04-13 09:54:31 - * @LastEditTime: 2022-04-17 16:55:13 + * @LastEditTime: 2022-04-19 16:42:47 * @LastEditors: TaoLer * @Description: 搜索引擎SEO优化设置 * @FilePath: \TaoLer\app\admin\controller\Seo.php @@ -15,7 +15,7 @@ use app\common\controller\AdminController; use think\facade\View; use think\facade\Request; use think\facade\Db; -use app\common\lib\SetArr; +use taoser\SetArr; class Seo extends AdminController { @@ -34,9 +34,16 @@ class Seo extends AdminController public function push() { + $data = Request::only(['start_id','end_id']); + if(empty(config('taoler.baidu.push_api'))) return json(['code'=>-1,'msg'=>'请先配置接口push_api']); $urls = []; - $artAllId = Db::name('article')->where(['delete_time'=>0,'status'=>1])->column('id'); + if(empty($data['start_id']) || empty($data['end_id'])) { + $artAllId = Db::name('article')->where(['delete_time'=>0,'status'=>1])->column('id'); + } else { + $artAllId = Db::name('article')->where(['delete_time'=>0,'status'=>1])->where('id','between',[$data['start_id'],$data['end_id']])->column('id'); + } + foreach($artAllId as $aid) { $urls[] = $this->getRouteUrl($aid); } @@ -62,9 +69,13 @@ class Seo extends AdminController return json(['code'=>0,'msg'=>'成功推送'.$data->success.'条,今天剩余'.$data->remain]); } + /** + * 百度接口配置 + * + * @return void + */ public function config() { - // $baidu = []; $data = Request::only(['client_id','client_secret','push_api']); foreach($data as $k => $v) { @@ -76,7 +87,7 @@ class Seo extends AdminController } } } - $res = (new SetArr('taoler'))::edit([ + $res = SetArr::name('taoler')->edit([ 'baidu' => $baidu, ]); if($res == true){ @@ -150,6 +161,7 @@ class Seo extends AdminController $str .= << $url + $time daily 0.5 @@ -188,7 +200,7 @@ class Seo extends AdminController } while($last_id < (int) $maxId); // 写配置,标记最后写入ID - $res = (new SetArr('taoler'))::edit([ + $res = SetArr::name('taoler')->edit([ 'sitemap' => [ 'map_num' => $data['map_num'], 'write_id' => $last_id, @@ -255,13 +267,36 @@ class Seo extends AdminController { $indexUrl = $this->getIndexUrl(); $artUrl = (string) url('detail_id', ['id' => $aid]); - if(empty(config('app.domain_bind'))) { - // 未绑定域名 - $url = $indexUrl . str_replace('admin','index',$artUrl); - } else { - // 单独绑定域名 - $url = $indexUrl . $artUrl; + + // 判断是否开启绑定 + //$domain_bind = array_key_exists('domain_bind',config('app')); + + // 判断index应用是否绑定域名 + $bind_index = array_search('index',config('app.domain_bind')); + // 判断admin应用是否绑定域名 + $bind_admin = array_search('admin',config('app.domain_bind')); + + // 判断index应用是否域名映射 + $map_index = array_search('index',config('app.app_map')); + // 判断admin应用是否域名映射 + $map_admin = array_search('admin',config('app.app_map')); + + $index = $map_index ? $map_index : 'index'; // index应用名 + $admin = $map_admin ? $map_admin : 'admin'; // admin应用名 + + if($bind_index) { + // index绑定域名 + $url = $indexUrl . str_replace($admin.'/','',$artUrl); + } else { // index未绑定域名 + // admin绑定域名 + if($bind_admin) { + $url = $indexUrl .'/' . $index . $artUrl; + } else { + $url = $indexUrl . str_replace($admin,$index,$artUrl); + } + } + return $url; } diff --git a/app/admin/controller/Set.php b/app/admin/controller/Set.php index b8cfe8c..6347208 100644 --- a/app/admin/controller/Set.php +++ b/app/admin/controller/Set.php @@ -1,4 +1,13 @@ + * @Date: 2021-12-06 16:04:50 + * @LastEditTime: 2022-04-19 14:06:54 + * @LastEditors: TaoLer + * @Description: 搜索引擎SEO优化设置 + * @FilePath: \TaoLer\app\admin\controller\Set.php + * Copyright (c) 2020~2022 http://www.aieok.com All rights reserved. + */ namespace app\admin\controller; use app\common\controller\AdminController; @@ -10,10 +19,9 @@ use think\facade\Config; use app\admin\model\System; use app\admin\model\MailServer; use taoler\com\Files; -use app\common\lib\SetConf; -use app\common\lib\SetArr; use think\facade\Session; use think\facade\Cookie; +use taoser\SetArr; class Set extends AdminController { @@ -35,7 +43,6 @@ class Set extends AdminController 'index' => isset($data['index']) ? $data['index'] : '', 'admin' => isset($data['admin']) ? $data['admin'] : '', ]; - } else { $domain_bind = [ 'index' => '', @@ -73,12 +80,12 @@ class Set extends AdminController $data = array_flip($data); if(empty(config('app.domain_bind'))){ // 写入token - $res = (new SetArr('app'))::add([ + $res = SetArr::name('app')->add([ 'domain_bind'=> $data, ]); }else{ // 编辑 - $res = (new SetArr('app'))::edit([ + $res = SetArr::name('app')->edit([ 'domain_bind'=> $data, ]); } @@ -86,7 +93,7 @@ class Set extends AdminController Cookie::delete('adminAuth'); Session::clear(); } else { - $res = (new SetArr('app'))::delete([ + $res = SetArr::name('app')->delete([ 'domain_bind'=> config('app.domain_bind'), ]); } @@ -178,15 +185,41 @@ class Set extends AdminController } } - $setConf = new SetConf; $value = [ 'config'=>$conf ]; - $upRes = $setConf->setConfig('taoler',$value); - return $upRes; + + $result = SetArr::name('taoler')->edit($value); + if($result){ + $res = ['code'=>0,'msg'=>'配置成功']; + } else { + $res = ['code'=>-1,'msg'=>'配置出错!']; + } + return json($res); } } + public function setUrl() + { + // + $data = Request::only(['article_as','cate_as']); + $arr = [ + 'url_rewrite'=>$data, + ]; + if(!array_key_exists('url_rewrite',config('taoler'))){ + $result = SetArr::name('taoler')->add($arr); + } else { + $result = SetArr::name('taoler')->edit($arr); + } + if($result){ + $res = ['code'=>0,'msg'=>'配置成功']; + } else { + $res = ['code'=>-1,'msg'=>'配置出错!']; + } + return json($res); + + } + //上传logo public function upload() { diff --git a/app/admin/controller/Upgrade.php b/app/admin/controller/Upgrade.php index f688b0f..d4db59d 100644 --- a/app/admin/controller/Upgrade.php +++ b/app/admin/controller/Upgrade.php @@ -23,11 +23,9 @@ use taoler\com\Str; use taoler\com\Files; use think\facade\Config; use think\facade\Log; -use app\common\lib\ZipFile; -use app\common\lib\SetConf; -use app\common\lib\SetArr; use app\common\lib\SqlFile; use app\common\lib\Zip; +use taoser\SetArr; class Upgrade extends AdminController { @@ -218,16 +216,15 @@ class Upgrade extends AdminController //更新版本 //Db::name('system')->update(['sys_version_num'=>$version_num,'id'=>1]); - $setConf = new SetConf; + $value = [ 'version' => $version_num ]; - - $upRes = $setConf->setConfig('taoler',$value); - $result = $upRes->getData(); - if($result['code'] == -1){ - return json(['code'=>-1,'msg'=>'代码写入成功,但'.$result['msg']]); + $res = SetArr::name('taoler')->edit($value); + if($res == false){ + return json(['code'=>-1,'msg'=>'代码更新成功,但版本写入失败']); } + return json(['code'=>0,'msg'=>'升级成功']); } @@ -363,16 +360,15 @@ class Upgrade extends AdminController //更新版本 //Db::name('system')->update(['sys_version_num'=>$version_num,'id'=>1]); - $setConf = new SetConf; + $value = [ 'version' => $version_num ]; + $res = SetArr::name('taoler')->edit($value); + if($res == false){ + return json(['code'=>-1,'msg'=>'代码更新成功,但版本写入失败']); + } - $upRes = $setConf->setConfig('taoler',$value); - $result = $upRes->getData(); - if($result['code'] == -1){ - return json(['code'=>-1,'msg'=>$result['msg']]); - } return json(['code'=>0,'msg'=>'升级成功']); } diff --git a/app/admin/route/route.php b/app/admin/route/route.php index 461885f..fea4895 100644 --- a/app/admin/route/route.php +++ b/app/admin/route/route.php @@ -1,16 +1,18 @@ -// +---------------------------------------------------------------------- +/* + * @Author: TaoLer + * @Date: 2021-12-06 16:04:50 + * @LastEditTime: 2022-04-19 14:31:41 + * @LastEditors: TaoLer + * @Description: 搜索引擎SEO优化设置 + * @FilePath: \TaoLer\app\admin\route\route.php + * Copyright (c) 2020~2022 http://www.aieok.com All rights reserved. + */ + use think\facade\Route; -//Route::get('jie/:id','index/article/detail'); + +//详情页URL别称 +$detail_as = config('taoler.url_rewrite.article_as'); Route::get('captcha/[:config]','\\think\\captcha\\CaptchaController@index'); -//Route::get('jie/:id', '\app\index\controller\Article@detail')->name('detail_id'); -Route::get('jie/:id', 'index/Article/detail')->name('detail_id'); +Route::get($detail_as.'/:id', '\app\index\controller\Article::detail')->name('detail_id'); diff --git a/app/admin/view/addons/add.html b/app/admin/view/addons/add.html index 16b4fef..353ae5a 100644 --- a/app/admin/view/addons/add.html +++ b/app/admin/view/addons/add.html @@ -70,7 +70,6 @@ ,method: 'get' ,exts: 'zip|rar|7z' ,done: function(res){ - //console.log(res) $(this.item).prev("div").children("input").val(res.src) if(res.code == 0){ diff --git a/app/admin/view/addons/index.html b/app/admin/view/addons/index.html index 94b9e38..dc8a41c 100644 --- a/app/admin/view/addons/index.html +++ b/app/admin/view/addons/index.html @@ -94,7 +94,7 @@ //提交 Ajax 成功后,静态更新表格中的数据 $.ajax({ type:"post", - url:"{:url('admin/Addons/add')}", + url:"{:url('Addons/add')}", data:field, daType:"json", success:function (data){ diff --git a/app/admin/view/admin/index.html b/app/admin/view/admin/index.html index 744f69d..7f51d44 100644 --- a/app/admin/view/admin/index.html +++ b/app/admin/view/admin/index.html @@ -50,17 +50,17 @@ {{# if(d.id == '1'){ }} {{# } else { }} - {if condition="checkRuleButton('admin/admin/check')"}{else}{/if} + {if condition="checkRuleButton('admin/check')"}{else}{/if} {{# } }} @@ -72,7 +72,7 @@ form.on('select(LAY-admin-group-type)', function(data){ $.ajax({ type:"post", - url:"{:url('admin/AuthAccess/index')}", + url:"{:url('AuthAccess/index')}", data:{"uid":data.value}, daType:"json", success:function (data){ diff --git a/app/admin/view/auth_group/role.html b/app/admin/view/auth_group/role.html index 09b17c5..30eddd6 100644 --- a/app/admin/view/auth_group/role.html +++ b/app/admin/view/auth_group/role.html @@ -27,14 +27,14 @@
@@ -74,7 +74,7 @@ $.ajax({ type:"post", - url:"{:url('admin/AuthGroup/list')}", + url:"{:url('AuthGroup/list')}", data:{"id":data.value}, daType:"json", success:function (data){ @@ -111,7 +111,7 @@ //执行角色审核 $.ajax({ type:'post', - url:"{:url('admin/AuthGroup/check')}", + url:"{:url('AuthGroup/check')}", data:{id:data.id,status:data.value,}, dataType:'json', success:function(res){ @@ -192,7 +192,7 @@ //提交 Ajax 成功后,静态更新表格中的数据 $.ajax({ type:"post", - url:"{:url('admin/AuthGroup/roleAdd')}", + url:"{:url('AuthGroup/roleAdd')}", data:{"rules":rules,"title":field.title,"descr":field.descr}, daType:"json", success:function (data){ diff --git a/app/admin/view/auth_rule/edit.html b/app/admin/view/auth_rule/edit.html index 3dd2a63..0451cb4 100644 --- a/app/admin/view/auth_rule/edit.html +++ b/app/admin/view/auth_rule/edit.html @@ -1,4 +1,3 @@ - {extend name="public:base" /} {block name="body"} @@ -113,9 +112,6 @@ } }); - - - }) diff --git a/app/admin/view/auth_rule/index.html b/app/admin/view/auth_rule/index.html index fe48c77..5e6d5d8 100644 --- a/app/admin/view/auth_rule/index.html +++ b/app/admin/view/auth_rule/index.html @@ -19,7 +19,7 @@ {{# if(d.id == '1' | d.id == '2' | d.id == '9' | d.id == '10' ){ }}删除 {{# } else { }} - {if condition="checkRuleButton('admin/authrule/delete')"}删除 + {if condition="checkRuleButton('authrule/delete')"}删除 {else /}删除{/if} {{# } }} diff --git a/app/admin/view/databackup/index.html b/app/admin/view/databackup/index.html index 2d7c00b..eaf332f 100644 --- a/app/admin/view/databackup/index.html +++ b/app/admin/view/databackup/index.html @@ -51,7 +51,7 @@ { //var index = layer.load(); $.ajax({ - url: "{:url('admin/Databackup/backup')}", + url: "{:url('Databackup/backup')}", type: 'POST', //POST async: true, //或false,是否异步 dataType: 'json', @@ -85,7 +85,7 @@ //总帐信息 table.render({ elem: '#LAY-app-content-ledger' - ,url: "{:url('admin/Database/index')}" //进列表接口 + ,url: "{:url('Database/index')}" //进列表接口 ,toolbar: '#toolbarDemo' //开启头部工具栏,并为其绑定左侧模板 ,defaultToolbar: ['filter', 'exports', 'print'] ,cols: [[ diff --git a/app/admin/view/database/index.html b/app/admin/view/database/index.html index edb33f8..9a92974 100644 --- a/app/admin/view/database/index.html +++ b/app/admin/view/database/index.html @@ -50,7 +50,7 @@ { var index = layer.load(); $.ajax({ - url: "{:url('admin/Database/backup')}", + url: "{:url('Database/backup')}", type: 'POST', //POST async: true, //或false,是否异步 dataType: 'json', @@ -85,7 +85,7 @@ //信息 table.render({ elem: '#LAY-app-content-database' - ,url: "{:url('admin/Database/index')}" //进列表接口 + ,url: "{:url('Database/index')}" //进列表接口 ,toolbar: '#toolbarDemo' //开启头部工具栏,并为其绑定左侧模板 ,cols: [[ {type: 'checkbox', fixed: 'left'} diff --git a/app/admin/view/forum/list.html b/app/admin/view/forum/list.html index 57ed997..1d485fc 100644 --- a/app/admin/view/forum/list.html +++ b/app/admin/view/forum/list.html @@ -78,12 +78,12 @@ {{# } }} @@ -89,7 +89,7 @@ overflow: visible; $.ajax({ type:"post", - url:"{:url('admin/Forum/tagsform')}", + url:"{:url('Forum/tagsform')}", data:{"sort":sort,"catename":tags,"ename":ename,"detpl":detpl,"icon":icon,"desc":desc}, daType:"json", success:function (data){ @@ -110,7 +110,7 @@ overflow: visible; //详情页模板选择控制 form.on('select(detpl)', function(data){ var detpl = data.value; - $.post("{:url('admin/Forum/tplSet')}",{"id":data.elem.id,"detpl":detpl}); + $.post("{:url('Forum/tplSet')}",{"id":data.elem.id,"detpl":detpl}); //执行重载 table.reload('LAY-app-content-tags', { where: detpl @@ -124,7 +124,7 @@ overflow: visible; data.value = data.checked ? 1 : 0; $.ajax({ type:'post', - url:"{:url('admin/Forum/tagshot')}", + url:"{:url('Forum/tagshot')}", data:{"id":data.id,"is_hot":data.value,}, dataType:'json', success:function(data){ diff --git a/app/admin/view/login/login.html b/app/admin/view/login/login.html index 5cde107..36599fb 100644 --- a/app/admin/view/login/login.html +++ b/app/admin/view/login/login.html @@ -33,7 +33,7 @@
@@ -44,7 +44,7 @@ - 注册帐号 + 注册帐号 {:hook('socialhook')}
diff --git a/app/admin/view/login/reg.html b/app/admin/view/login/reg.html index 56e5e78..d1d3ccd 100644 --- a/app/admin/view/login/reg.html +++ b/app/admin/view/login/reg.html @@ -108,7 +108,7 @@ ,icon: 1 ,time: 1000 }, function(){ - location.hash = '/admin/user/login'; //跳转到登入页 + location.hash = "{:url('user/login')}"; //跳转到登入页 }); } }); diff --git a/app/admin/view/notice/index.html b/app/admin/view/notice/index.html index aa5daaf..5c2990a 100644 --- a/app/admin/view/notice/index.html +++ b/app/admin/view/notice/index.html @@ -35,7 +35,7 @@
- {if condition="checkRuleButton('admin/notice/add')"} + {if condition="checkRuleButton('notice/add')"} {else /}{/if}
@@ -50,10 +50,10 @@
diff --git a/app/admin/view/public/side_menu.html b/app/admin/view/public/side_menu.html index 67d70c4..0a9616c 100644 --- a/app/admin/view/public/side_menu.html +++ b/app/admin/view/public/side_menu.html @@ -1,3 +1,4 @@ +
@@ -28,13 +29,13 @@ 网站用户1
- 后台管理员 + 后台管理员
- 角色管理 + 角色管理
- 权限管理 + 权限管理
diff --git a/app/admin/view/seo/index.html b/app/admin/view/seo/index.html index 780088c..5c1195f 100644 --- a/app/admin/view/seo/index.html +++ b/app/admin/view/seo/index.html @@ -1,32 +1,72 @@ {extend name="public/base" /} {block name="body"} -
+
-
-
    -
  • 百度推送
  • -
  • 站点地图
  • +
      +
    • * 接口配置
    • +
    • 百度推送
    • +
    • 站点地图
    • robots
    • -
    • * 接口配置
    • -
    +
-
-
-
- -
- {if condition="checkRuleButton('Seo/push')"} - - {else /} - - {/if} +
+
+
+ +
+ +
+
百度分词API Key
+
+
+ +
+ +
+
百度分词Secret Key
+
+
+ +
+ +
+
接口调用地址:http://data.zz.baidu.com/urls?site=https://www.youurl.com&token=yuotoken
+
+
+
+ +
+
+
+
+
+
+
+ +
+ +
+
-
+
+ +
+
帖子ID范围,如果不填,默认全站提交
+
+
+ +
+ {if condition="checkRuleButton('Seo/push')"} + + {else /} + + {/if} +
-
@@ -76,36 +116,7 @@
-
-
-
- -
- -
-
百度分词API Key
-
-
- -
- -
-
百度分词Secret Key
-
-
- -
- -
-
百度准入密钥
-
-
-
- -
-
-
-
+
@@ -131,8 +142,8 @@ // baidu push form.on('submit(search_push)', function(data){ - //var field = data.field; - $.post("{:url('seo/push')}",function(res){ + var field = data.field; + $.post("{:url('seo/push')}",field,function(res){ if(res.code == 0){ layer.msg(res.msg,{icon:6,tiye:2000 }); diff --git a/app/admin/view/set/system/server.html b/app/admin/view/set/system/server.html index b0a8c2c..92e6df6 100644 --- a/app/admin/view/set/system/server.html +++ b/app/admin/view/set/system/server.html @@ -38,7 +38,7 @@
- {if condition="checkRuleButton('admin/Sign/add')"} + {if condition="checkRuleButton('Sign/add')"} {else /}{/if}
@@ -47,10 +47,10 @@
@@ -78,7 +78,7 @@
- {if condition="checkRuleButton('admin/Vip/add')"} + {if condition="checkRuleButton('Vip/add')"} {else /}{/if}
@@ -88,10 +88,10 @@
@@ -160,7 +160,7 @@ var field = data.field; $.ajax({ type:"post", - url:"{:url('admin/Vip/add')}", + url:"{:url('Vip/add')}", data:field, daType:"json", success:function (data){ diff --git a/app/admin/view/set/system/website.html b/app/admin/view/set/system/website.html index 6733599..f17cf45 100644 --- a/app/admin/view/set/system/website.html +++ b/app/admin/view/set/system/website.html @@ -13,6 +13,7 @@
  • 邮箱服务
  • 服务配置
  • 域名绑定
  • +
  • URL美化
  • @@ -44,7 +45,7 @@
    - {if condition="checkRuleButton('admin/set/upload')"} + {if condition="checkRuleButton('set/upload')"} {else /}{/if} @@ -121,7 +122,7 @@
    - {if condition="checkRuleButton('admin/Set/website')"} + {if condition="checkRuleButton('Set/website')"} {else /}{/if}
    @@ -164,7 +165,7 @@
    - {if condition="checkRuleButton('admin/Set/email')"} + {if condition="checkRuleButton('Set/email')"} {else /}{/if}
    @@ -289,17 +290,38 @@
    - {if condition="checkRuleButton('admin/Set/email')"} + {if condition="checkRuleButton('Set/email')"} {else /}{/if}
    -
    - +
    +
    +
    + +
    + +
    +
    如:www.aieok.com/article/1.html
    +
    +
    + +
    + +
    +
    如:www.aieok.com/cate/ask.html
    +
    +
    +
    + +
    +
    +
    +
    @@ -327,7 +349,7 @@ //LOGO选完文件后不自动上传 upload.render({ elem: '#logo-img' - ,url: "{:url('admin/set/upload')}" + ,url: "{:url('set/upload')}" ,data: {type:'image'} ,auto: false ,exts: 'jpg|png|gif|bmp|jpeg' @@ -348,7 +370,7 @@ //网站配置 form.on('submit(set_system_config)', function(data){ var field = data.field; - $.post("{:url('admin/set/config')}",field,function(res){ + $.post("{:url('set/config')}",field,function(res){ if(res.code == 0){ layer.msg(res.msg,{icon:6,tiye:2000 }); @@ -361,7 +383,7 @@ //邮箱发送测试码 form.on('submit(test_system_email)', function(data){ var field = data.field; - $.post("{:url('admin/set/sendMailCode')}",field,function(res){ + $.post("{:url('set/sendMailCode')}",field,function(res){ if(res.code == 0){ layer.msg(res.msg,{icon:6,tiye:2000 }); @@ -375,7 +397,7 @@ //邮箱激活 form.on('submit(active_system_email)', function(data){ var field = data.field; - $.post("{:url('admin/set/activeMailServer')}",field,function(res){ + $.post("{:url('set/activeMailServer')}",field,function(res){ if(res.code == 0){ layer.msg(res.msg,{icon:6,tiye:2000 }); @@ -389,7 +411,7 @@ //网站配置 form.on('submit(set_system_domain)', function(data){ var field = data.field; - $.post("{:url('admin/set/setDomain')}",field,function(res){ + $.post("{:url('set/setDomain')}",field,function(res){ if(res.code == 0){ layer.msg(res.msg,{icon:6,tiye:2000 }, function(){ @@ -402,6 +424,33 @@ return false; }); + // URL美化 + form.on('submit(set_url_rewrite)', function(data){ + var field = data.field; + $.post("{:url('set/setUrl')}",field,function(res){ + if(res.code == 0){ + layer.msg(res.msg,{icon:6,tiye:2000 + }, function(){ + //parent.location.href = '/'; + }); + } else { + layer.open({title:"设置失败",content:res.msg,icon:5,anim:6}); + } + }); + return false; + }); + + // 获取描述的内容 + $("input[name='article_as']").bind('input propertychange', function(){ + var content = $(this).val() + $('#artdesc').html('如:www.aieok.com/'+ content +'/1.html'); + }) + $("input[name='cate_as']").bind('input propertychange', function(){ + var content = $(this).val() + $('#catedesc').html('如:www.aieok.com/'+ content +'/ask.html'); + }) + + form.on('switch(domain_check)', function(data){ var data = data.elem; status = data.checked ? 'on' : 'off'; @@ -409,7 +458,7 @@ $('#set_domain').removeClass('layui-hide'); } else { $('#set_domain').addClass('layui-hide'); - $.post("{:url('admin/set/setDomain')}",{"domain_check":status},function(res){ + $.post("{:url('set/setDomain')}",{"domain_check":status},function(res){ if(res.code == 0){ layer.msg(res.msg,{icon:6,tiye:2000 }, function(){ diff --git a/app/admin/view/set/user/info.html b/app/admin/view/set/user/info.html index e18256d..d0f0f5c 100644 --- a/app/admin/view/set/user/info.html +++ b/app/admin/view/set/user/info.html @@ -72,7 +72,7 @@
    - {if condition="checkRuleButton('admin/Admin/infoSet')"} + {if condition="checkRuleButton('Admin/infoSet')"} {else /} diff --git a/app/admin/view/set/user/repass.html b/app/admin/view/set/user/repass.html index 24d0373..dd56985 100644 --- a/app/admin/view/set/user/repass.html +++ b/app/admin/view/set/user/repass.html @@ -1,3 +1,12 @@ + {extend name="public/base" /} {block name="body"} @@ -30,7 +39,7 @@
    - {if condition="checkRuleButton('admin/Admin/repassSet')"} + {if condition="checkRuleButton('Admin/repassSet')"} {else /} diff --git a/app/admin/view/slider/index.html b/app/admin/view/slider/index.html index eeb8163..0d979c2 100644 --- a/app/admin/view/slider/index.html +++ b/app/admin/view/slider/index.html @@ -41,7 +41,7 @@ {$vo.slid_status ?'显示':'禁止'} - {if condition="checkRuleButton('admin/Slider/delete')"} + {if condition="checkRuleButton('Slider/delete')"} {else /}{/if} @@ -95,7 +95,7 @@ //提交 Ajax 成功后,静态更新表格中的数据 $.ajax({ type:"post", - url:"{:url('admin/slider/add')}", + url:"{:url('slider/add')}", data:{"slid_type":field.slid_type,"slid_name":field.slid_name,"slid_color":field.slid_color,"slid_start":field.slid_start,"slid_over":field.slid_over,"slid_href":field.slid_href,"slid_img":field.slid_img}, daType:"json", success:function (data){ @@ -145,7 +145,7 @@ //提交 Ajax 成功后,静态更新表格中的数据 $.ajax({ type:"post", - url:"{:url('admin/slider/edit')}", + url:"{:url('slider/edit')}", data:{"id":id,"slid_type":field.slid_type,"slid_name":field.slid_name,"slid_color":field.slid_color,"slid_start":field.slid_start,"slid_over":field.slid_over,"slid_href":field.slid_href,"slid_img":field.slid_img}, daType:"json", success:function (data){ @@ -180,7 +180,7 @@ //提交 Ajax 成功后,静态更新表格中的数据 $.ajax({ type:"post", - url:"{:url('admin/slider/delete')}", + url:"{:url('slider/delete')}", data:{"id":id}, daType:"json", success:function (data){ diff --git a/app/admin/view/upgrade/index.html b/app/admin/view/upgrade/index.html index 2ee48c2..cc735cd 100644 --- a/app/admin/view/upgrade/index.html +++ b/app/admin/view/upgrade/index.html @@ -17,7 +17,7 @@
    无Key不能升级
    - +
    {else /}
    @@ -27,7 +27,7 @@
    {$ver_num.key}
    - {if condition="checkRuleButton('admin/Upgrade/keyedit')"} + {if condition="checkRuleButton('Upgrade/keyedit')"} 修改? {else /}修改?{/if}
    @@ -36,7 +36,7 @@
    当前版本:Taoler v_{:config('taoler.version')}
    - {if condition="checkRuleButton('admin/Upgrade/check')"} + {if condition="checkRuleButton('Upgrade/check')"} {else /}{/if}
    @@ -44,7 +44,7 @@
    - {if condition="checkRuleButton('admin/Upgrade/uploadzip')"} + {if condition="checkRuleButton('Upgrade/uploadzip')"} {else /}{/if} @@ -91,7 +91,7 @@ shade: [0.2, '#000'], //time: 2000, }); - $.get("{:url('admin/upgrade/check')}",function (data){ + $.get("{:url('upgrade/check')}",function (data){ if (data.code == 0) { //已件最新版本 layer.close(loading); @@ -130,7 +130,7 @@ //更新 function uploads(){ var load = layer.load(); //loading - $.get("{:url('admin/upgrade/upload')}",function (data){ + $.get("{:url('upgrade/upload')}",function (data){ if (data.code == 0) { layer.close(load); layer.msg(data.msg,{ @@ -151,7 +151,7 @@ //手动更新,选完文件后不自动上传 upload.render({ elem: '#select-file' - ,url: "{:url('admin/upgrade/uploadzip')}" + ,url: "{:url('upgrade/uploadzip')}" ,data: {type:'zip'} ,accept: 'file' ,acceptMime: 'application/zip' @@ -198,7 +198,7 @@ $.ajax({ type:"post", - url:"{:url('admin/upgrade/key')}", + url:"{:url('upgrade/key')}", data:{"key":key}, daType:"json", success:function (data){ @@ -245,7 +245,7 @@ //提交 Ajax 成功后,静态更新表格中的数据 $.ajax({ type:"post", - url:"{:url('admin/upgrade/keyedit')}", + url:"{:url('upgrade/keyedit')}", data:{"key":field.key,"upcheck_url":field.upcheck_url,"upgrade_url":field.upgrade_url}, daType:"json", success:function (data){ diff --git a/app/admin/view/upgrade/keyedit.html b/app/admin/view/upgrade/keyedit.html index fd13620..7ef94ff 100644 --- a/app/admin/view/upgrade/keyedit.html +++ b/app/admin/view/upgrade/keyedit.html @@ -1,3 +1,12 @@ + {extend name="public/base" /} {block name="body"} @@ -24,7 +33,7 @@
    - +
    未通知api接口变更,请不要私自更改api,否则无法升级
    diff --git a/app/admin/view/user/list.html b/app/admin/view/user/list.html index 9da8191..77f8c23 100644 --- a/app/admin/view/user/list.html +++ b/app/admin/view/user/list.html @@ -74,21 +74,21 @@ {{# if(d.id == '1'){ }} {{# } else { }} - {if condition="checkRuleButton('admin/user/check')"}{else}{/if} + {if condition="checkRuleButton('user/check')"}{else}{/if} {{# } }} @@ -126,7 +126,7 @@ //监听搜索 form.on('submit(LAY-user-front-search)', function(data){ var field = data.field; - $.post("{:url('admin/User/list')}",field); + $.post("{:url('User/list')}",field); //执行重载 table.reload('LAY-user-manage', { where: field @@ -183,7 +183,7 @@ //提交 Ajax 成功后,静态更新表格中的数据 $.ajax({ type:"post", - url:"{:url('admin/User/userform')}", + url:"{:url('User/userform')}", data:{"name":field.username,"phone":field.phone,"email":field.email,"user_img":field.avatar,"sex":field.sex}, daType:"json", success:function (data){ @@ -219,7 +219,7 @@ //执行用户审核 $.ajax({ type:'post', - url:"{:url('admin/User/check')}", + url:"{:url('User/check')}", data:{"id":data.id,"status":status}, dataType:'json', success:function(res){ @@ -249,7 +249,7 @@ var auth = data.checked ? 1 : 0; $.ajax({ type:'post', - url:"{:url('admin/User/auth')}", + url:"{:url('User/auth')}", data:{"id":data.id,"auth":auth}, dataType:'json', success:function(data){ diff --git a/app/index/route/route.php b/app/index/route/route.php index 740e09f..8cd98c9 100644 --- a/app/index/route/route.php +++ b/app/index/route/route.php @@ -1,22 +1,27 @@ -// +---------------------------------------------------------------------- +/* + * @Author: TaoLer + * @Date: 2021-12-06 16:04:50 + * @LastEditTime: 2022-04-19 13:19:28 + * @LastEditors: TaoLer + * @Description: 搜索引擎SEO优化设置 + * @FilePath: \TaoLer\app\index\route\route.php + * Copyright (c) 2020~2022 http://www.aieok.com All rights reserved. + */ use think\facade\Route; +//详情页URL别称 +$detail_as = config('taoler.url_rewrite.article_as'); +//分类别称 +$cate_as = config('taoler.url_rewrite.cate_as'); + Route::get('captcha/[:config]','\\think\\captcha\\CaptchaController@index'); Route::rule('/', 'index'); // 首页访问路由 -Route::group(function () { - Route::get('jie/:id', 'article/detail'); - Route::get('column/$','article/cate'); - Route::get('column//$', 'article/cate')->name('cate_type'); - Route::rule('column///', 'article/cate')->name('cate_page'); +Route::group(function () use($detail_as,$cate_as){ + Route::get($detail_as .'/:id', 'article/detail'); + Route::get($cate_as.'/$','article/cate'); + Route::get($cate_as.'//$', 'article/cate')->name('cate_type'); + Route::rule($cate_as.'///', 'article/cate')->name('cate_page'); Route::rule('add','Article/add'); Route::rule('tags','Article/tags')->allowCrossDomain(); Route::rule('edit/[:id]','Article/edit'); diff --git a/app/install/data/taoler.sql b/app/install/data/taoler.sql index 1b3ead1..67b0cdb 100644 --- a/app/install/data/taoler.sql +++ b/app/install/data/taoler.sql @@ -265,7 +265,7 @@ INSERT INTO `tao_auth_rule` VALUES ('96', 'Database/delete', '备份删除', '', INSERT INTO `tao_auth_rule` VALUES ('97', 'addons', '插件', '', '1', '1', '0', '0', 'layui-icon-flag', '1', '2', '', '1635757328', '1635757632', '0'); INSERT INTO `tao_auth_rule` VALUES ('98', 'Addons/index', '插件市场', '', '1', '1', '97', '1', '', '1', '0', '', '1635757426', '0', '0'); INSERT INTO `tao_auth_rule` VALUES ('99', 'Addons/addonsList', '插件列表', '', '1', '1', '98', '2', '', '-1', '0', '', '1638775199', '0', '0'); -INSERT INTO `tao_auth_rule` VALUES ('111','Seo/index','SEO','1','1','0','0','layui-icon-component','1','7','','1649829142','0','0'); +INSERT INTO `tao_auth_rule` VALUES ('111','Seo/index','SEO', '', '1','1','0','0','layui-icon-component','1','7','','1649829142','0','0'); -- ---------------------------- -- Table structure for tao_cate diff --git a/composer.json b/composer.json index 24fa4e3..dcf29c7 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,8 @@ "wamkj/thinkphp6.0-databackup": "^1.0", "taoser/think-addons": "^1.0", "liliuwei/thinkphp-social": "^1.3", - "taoser/think-setarr": "^0.0.3" + "taoser/think-setarr": "^0.0.3", + "topthink/think-migration": "^3.0" }, "require-dev": { "symfony/var-dumper": "^4.2", diff --git a/composer.lock b/composer.lock index 8704a2a..b75fac1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "52d2121e202720cc23a0528605fafe09", + "content-hash": "9b81a97861a9ca21e9a9308a2587c14f", "packages": [ { "name": "firebase/php-jwt", @@ -210,16 +210,16 @@ }, { "name": "league/mime-type-detection", - "version": "1.10.0", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "3e4a35d756eedc67096f30240a68a3149120dae7" + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/3e4a35d756eedc67096f30240a68a3149120dae7", - "reference": "3e4a35d756eedc67096f30240a68a3149120dae7", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd", "shasum": "" }, "require": { @@ -250,7 +250,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.10.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.11.0" }, "funding": [ { @@ -262,7 +262,7 @@ "type": "tidelift" } ], - "time": "2022-04-11T12:49:04+00:00" + "time": "2022-04-17T13:12:02+00:00" }, { "name": "liliuwei/thinkphp-social", @@ -1072,6 +1072,60 @@ }, "time": "2021-01-14T12:12:14+00:00" }, + { + "name": "topthink/think-migration", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-migration.git", + "reference": "5717d9e5f3ea745f6dbfd1e30b4402aaadff9a79" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-migration/zipball/5717d9e5f3ea745f6dbfd1e30b4402aaadff9a79", + "reference": "5717d9e5f3ea745f6dbfd1e30b4402aaadff9a79", + "shasum": "" + }, + "require": { + "topthink/framework": "^6.0.0", + "topthink/think-helper": "^3.0.3" + }, + "require-dev": { + "fzaninotto/faker": "^1.8" + }, + "suggest": { + "fzaninotto/faker": "Required to use the factory builder (^1.8)." + }, + "type": "library", + "extra": { + "think": { + "services": [ + "think\\migration\\Service" + ] + } + }, + "autoload": { + "psr-4": { + "Phinx\\": "phinx/src/Phinx", + "think\\migration\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "support": { + "issues": "https://github.com/top-think/think-migration/issues", + "source": "https://github.com/top-think/think-migration/tree/v3.0.3" + }, + "time": "2020-12-07T05:54:22+00:00" + }, { "name": "topthink/think-multi-app", "version": "v1.0.14", diff --git a/config/app.php b/config/app.php index 3b08d2f..093bd48 100644 --- a/config/app.php +++ b/config/app.php @@ -2,7 +2,7 @@ /* * @Author: TaoLer * @Date: 2021-12-06 16:04:50 - * @LastEditTime: 2022-04-17 16:49:19 + * @LastEditTime: 2022-04-19 16:49:29 * @LastEditors: TaoLer * @Description: 搜索引擎SEO优化设置 * @FilePath: \TaoLer\config\app.php @@ -30,7 +30,7 @@ return [ 'app_map' => [ //'bbs' => 'index', //'*' => 'home', - //'admin' => 'admin', + //'admin1' => 'admin', ], // 域名绑定(自动多应用模式有效) 'domain_bind' => [ diff --git a/config/taoler.php b/config/taoler.php index 3166552..0ae5fdb 100644 --- a/config/taoler.php +++ b/config/taoler.php @@ -2,9 +2,9 @@ /* * @Author: TaoLer * @Date: 2021-12-06 16:04:50 - * @LastEditTime: 2022-04-17 16:54:16 + * @LastEditTime: 2022-04-19 16:48:27 * @LastEditors: TaoLer - * @Description: 搜索引擎SEO优化设置 + * @Description: 网站公共配置 * @FilePath: \TaoLer\config\taoler.php * Copyright (c) 2020~2022 http://www.aieok.com All rights reserved. */ @@ -16,11 +16,13 @@ return [ // 应用名,此项不可更改 'appname' => 'TaoLer', // 版本配置 - 'version' => '1.9.1', + 'version' => '1.9.2', // 加盐 'salt' => 'taoler', // 数据库备份目录 'databasebackdir' => app()->getRootPath() .'data/', + 'article_as' => 'aa', + 'cate_as' => 'column', // 配置 'config' =>[ 'email_notice' => 0, @@ -36,20 +38,30 @@ return [ // 百度标签分词 'baidu' => [ 'grant_type' => '', - 'client_id' => '', - 'client_secret' => '', + 'client_id' => 'aa', + 'client_secret' => 'aa', 'access_token' => '', - 'push_api' => '', + 'push_api' => 'http://', ], // sitemap 'sitemap' => [ + // 每次生成数量 'map_num' => '1000', 'map_time' => 'daily', 'map_level' => '0.5', - 'write_id' => 0, + // 已生成id位标记 + 'write_id' => 12, ], + // URL美化 + 'url_rewrite' => [ + // 详情url + 'article_as' => 'article', + // 分类url + 'cate_as' => 'column', + ], + ]; \ No newline at end of file diff --git a/public/.gitignore b/public/.gitignore index 3528d35..1606024 100644 --- a/public/.gitignore +++ b/public/.gitignore @@ -1,2 +1,2 @@ -!.gitignore -install.lock \ No newline at end of file +install.lock +/home \ No newline at end of file diff --git a/public/robots.txt b/public/robots.txt index 793accc..5c3517f 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -5,6 +5,4 @@ Disallow: /index/user/ Disallow: /index/api/ Disallow: /index/login.html Disallow: /index/reg.html -Disallow: /*?* -sitemap:http://www.test.com/2022-04-16_1.xml -sitemap:http://www.test.com/2022-04-16_2.xml \ No newline at end of file +Disallow: /*?* \ No newline at end of file diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php index c6185a3..8081fa3 100644 --- a/vendor/composer/autoload_psr4.php +++ b/vendor/composer/autoload_psr4.php @@ -9,6 +9,7 @@ return array( 'wamkj\\thinkphp\\' => array($vendorDir . '/wamkj/thinkphp6.0-databackup/src'), 'think\\view\\driver\\' => array($vendorDir . '/topthink/think-view/src'), 'think\\trace\\' => array($vendorDir . '/topthink/think-trace/src'), + 'think\\migration\\' => array($vendorDir . '/topthink/think-migration/src'), 'think\\composer\\' => array($vendorDir . '/topthink/think-installer/src'), 'think\\captcha\\' => array($vendorDir . '/topthink/think-captcha/src'), 'think\\app\\' => array($vendorDir . '/topthink/think-multi-app/src'), @@ -27,6 +28,7 @@ return array( 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'), 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), + 'Phinx\\' => array($vendorDir . '/topthink/think-migration/phinx/src/Phinx'), 'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'), 'League\\MimeTypeDetection\\' => array($vendorDir . '/league/mime-type-detection/src'), 'League\\Flysystem\\Cached\\' => array($vendorDir . '/league/flysystem-cached-adapter/src'), diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 9728d0b..22e6960 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -27,6 +27,7 @@ class ComposerStaticInit1b32198725235c8d6500c87262ef30c2 array ( 'think\\view\\driver\\' => 18, 'think\\trace\\' => 12, + 'think\\migration\\' => 16, 'think\\composer\\' => 15, 'think\\captcha\\' => 14, 'think\\app\\' => 10, @@ -60,6 +61,7 @@ class ComposerStaticInit1b32198725235c8d6500c87262ef30c2 'Psr\\Http\\Message\\' => 17, 'Psr\\Container\\' => 14, 'Psr\\Cache\\' => 10, + 'Phinx\\' => 6, 'PHPMailer\\PHPMailer\\' => 20, ), 'L' => @@ -87,6 +89,10 @@ class ComposerStaticInit1b32198725235c8d6500c87262ef30c2 array ( 0 => __DIR__ . '/..' . '/topthink/think-trace/src', ), + 'think\\migration\\' => + array ( + 0 => __DIR__ . '/..' . '/topthink/think-migration/src', + ), 'think\\composer\\' => array ( 0 => __DIR__ . '/..' . '/topthink/think-installer/src', @@ -163,6 +169,10 @@ class ComposerStaticInit1b32198725235c8d6500c87262ef30c2 array ( 0 => __DIR__ . '/..' . '/psr/cache/src', ), + 'Phinx\\' => + array ( + 0 => __DIR__ . '/..' . '/topthink/think-migration/phinx/src/Phinx', + ), 'PHPMailer\\PHPMailer\\' => array ( 0 => __DIR__ . '/..' . '/phpmailer/phpmailer/src', diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 39235b3..891a010 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -227,17 +227,17 @@ }, { "name": "league/mime-type-detection", - "version": "1.10.0", - "version_normalized": "1.10.0.0", + "version": "1.11.0", + "version_normalized": "1.11.0.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "3e4a35d756eedc67096f30240a68a3149120dae7" + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/3e4a35d756eedc67096f30240a68a3149120dae7", - "reference": "3e4a35d756eedc67096f30240a68a3149120dae7", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd", "shasum": "" }, "require": { @@ -249,7 +249,7 @@ "phpstan/phpstan": "^0.12.68", "phpunit/phpunit": "^8.5.8 || ^9.3" }, - "time": "2022-04-11T12:49:04+00:00", + "time": "2022-04-17T13:12:02+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -270,7 +270,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.10.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.11.0" }, "funding": [ { @@ -1542,6 +1542,63 @@ }, "install-path": "../topthink/think-installer" }, + { + "name": "topthink/think-migration", + "version": "v3.0.3", + "version_normalized": "3.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-migration.git", + "reference": "5717d9e5f3ea745f6dbfd1e30b4402aaadff9a79" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-migration/zipball/5717d9e5f3ea745f6dbfd1e30b4402aaadff9a79", + "reference": "5717d9e5f3ea745f6dbfd1e30b4402aaadff9a79", + "shasum": "" + }, + "require": { + "topthink/framework": "^6.0.0", + "topthink/think-helper": "^3.0.3" + }, + "require-dev": { + "fzaninotto/faker": "^1.8" + }, + "suggest": { + "fzaninotto/faker": "Required to use the factory builder (^1.8)." + }, + "time": "2020-12-07T05:54:22+00:00", + "type": "library", + "extra": { + "think": { + "services": [ + "think\\migration\\Service" + ] + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Phinx\\": "phinx/src/Phinx", + "think\\migration\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "support": { + "issues": "https://github.com/top-think/think-migration/issues", + "source": "https://github.com/top-think/think-migration/tree/v3.0.3" + }, + "install-path": "../topthink/think-migration" + }, { "name": "topthink/think-multi-app", "version": "v1.0.14", diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index cc89ad7..611991a 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -5,7 +5,7 @@ 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => 'f8f39741994332a33848a2cf4f58ade195aea126', + 'reference' => '1250b449335a867caa1976e6117d3b3448b5d7bf', 'name' => 'taoser/taoler', 'dev' => true, ), @@ -38,12 +38,12 @@ 'dev_requirement' => false, ), 'league/mime-type-detection' => array( - 'pretty_version' => '1.10.0', - 'version' => '1.10.0.0', + 'pretty_version' => '1.11.0', + 'version' => '1.11.0.0', 'type' => 'library', 'install_path' => __DIR__ . '/../league/mime-type-detection', 'aliases' => array(), - 'reference' => '3e4a35d756eedc67096f30240a68a3149120dae7', + 'reference' => 'ff6248ea87a9f116e78edd6002e39e5128a0d4dd', 'dev_requirement' => false, ), 'liliuwei/thinkphp-social' => array( @@ -160,7 +160,7 @@ 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => 'f8f39741994332a33848a2cf4f58ade195aea126', + 'reference' => '1250b449335a867caa1976e6117d3b3448b5d7bf', 'dev_requirement' => false, ), 'taoser/think-addons' => array( @@ -226,6 +226,15 @@ 'reference' => '38ba647706e35d6704b5d370c06f8a160b635f88', 'dev_requirement' => false, ), + 'topthink/think-migration' => array( + 'pretty_version' => 'v3.0.3', + 'version' => '3.0.3.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../topthink/think-migration', + 'aliases' => array(), + 'reference' => '5717d9e5f3ea745f6dbfd1e30b4402aaadff9a79', + 'dev_requirement' => false, + ), 'topthink/think-multi-app' => array( 'pretty_version' => 'v1.0.14', 'version' => '1.0.14.0', diff --git a/vendor/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php b/vendor/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php index aff2922..f092388 100644 --- a/vendor/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php +++ b/vendor/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php @@ -730,6 +730,7 @@ class GeneratedExtensionToMimeTypeMap implements ExtensionToMimeTypeMap 'pgm' => 'image/x-portable-graymap', 'pgn' => 'application/x-chess-pgn', 'pgp' => 'application/pgp', + 'phar' => 'application/octet-stream', 'php' => 'application/x-httpd-php', 'php3' => 'application/x-httpd-php', 'php4' => 'application/x-httpd-php', diff --git a/vendor/services.php b/vendor/services.php index 9c468a2..825b809 100644 --- a/vendor/services.php +++ b/vendor/services.php @@ -1,9 +1,10 @@ 'taoser\\addons\\Service', 1 => 'think\\captcha\\CaptchaService', - 2 => 'think\\app\\Service', - 3 => 'think\\trace\\Service', + 2 => 'think\\migration\\Service', + 3 => 'think\\app\\Service', + 4 => 'think\\trace\\Service', ); \ No newline at end of file diff --git a/vendor/topthink/think-migration/.gitignore b/vendor/topthink/think-migration/.gitignore new file mode 100644 index 0000000..30babff --- /dev/null +++ b/vendor/topthink/think-migration/.gitignore @@ -0,0 +1,3 @@ +/vendor/ +.idea +composer.lock diff --git a/vendor/topthink/think-migration/LICENSE b/vendor/topthink/think-migration/LICENSE new file mode 100644 index 0000000..2c76a5a --- /dev/null +++ b/vendor/topthink/think-migration/LICENSE @@ -0,0 +1,32 @@ + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 +版权所有Copyright © 2006-2016 by ThinkPHP (http://thinkphp.cn) +All rights reserved。 +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +Apache Licence是著名的非盈利开源组织Apache采用的协议。 +该协议和BSD类似,鼓励代码共享和尊重原作者的著作权, +允许代码修改,再作为开源或商业软件发布。需要满足 +的条件: +1. 需要给代码的用户一份Apache Licence ; +2. 如果你修改了代码,需要在被修改的文件中说明; +3. 在延伸的代码中(修改和有源代码衍生的代码中)需要 +带有原来代码中的协议,商标,专利声明和其他原来作者规 +定需要包含的说明; +4. 如果再发布的产品中包含一个Notice文件,则在Notice文 +件中需要带有本协议内容。你可以在Notice中增加自己的 +许可,但不可以表现为对Apache Licence构成更改。 +具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0 + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/vendor/topthink/think-migration/README.md b/vendor/topthink/think-migration/README.md new file mode 100644 index 0000000..39e6584 --- /dev/null +++ b/vendor/topthink/think-migration/README.md @@ -0,0 +1,6 @@ +# thinkphp6 数据库迁移工具 + +## 安装 +~~~ +composer require topthink/think-migration +~~~ diff --git a/vendor/topthink/think-migration/composer.json b/vendor/topthink/think-migration/composer.json new file mode 100644 index 0000000..34b1339 --- /dev/null +++ b/vendor/topthink/think-migration/composer.json @@ -0,0 +1,34 @@ +{ + "name": "topthink/think-migration", + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "license": "Apache-2.0", + "autoload": { + "psr-4": { + "Phinx\\": "phinx/src/Phinx", + "think\\migration\\": "src" + } + }, + "extra": { + "think": { + "services": [ + "think\\migration\\Service" + ] + } + }, + "require": { + "topthink/framework": "^6.0.0", + "topthink/think-helper": "^3.0.3" + }, + "minimum-stability": "dev", + "suggest": { + "fzaninotto/faker": "Required to use the factory builder (^1.8)." + }, + "require-dev": { + "fzaninotto/faker": "^1.8" + } +} diff --git a/vendor/topthink/think-migration/phinx/CHANGELOG.md b/vendor/topthink/think-migration/phinx/CHANGELOG.md new file mode 100644 index 0000000..df1c2e4 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/CHANGELOG.md @@ -0,0 +1,370 @@ +# Version History + +**0.6.5** (Thursday, 27 October 2016) + +* Documentation updates +* Pull requests + * [#831](https://github.com/robmorgan/phinx/pull/831) Typos + * [#929](https://github.com/robmorgan/phinx/pull/929) Support glob brace for seed paths + * [#949](https://github.com/robmorgan/phinx/pull/949) Fix for Config::getMigrationBaseClassName + * [#958](https://github.com/robmorgan/phinx/pull/958) Allow console input to be used within adapters + +**0.6.4** (Wednesday, 27th July 2016) + +* Documentation updates +* Pull requests + * [#909](https://github.com/robmorgan/phinx/pull/909) Declare test class properties + * [#910](https://github.com/robmorgan/phinx/pull/910), [#916](https://github.com/robmorgan/phinx/pull/916) Remove unused variables + * [#912](https://github.com/robmorgan/phinx/pull/912) ConfigInterface usage consistency + * [#914](https://github.com/robmorgan/phinx/pull/914) Set return values and @return documentation + * [#918](https://github.com/robmorgan/phinx/pull/918) Docblock correction for Phinx\Migration\Manager::executeSeed() + * [#921](https://github.com/robmorgan/phinx/pull/921) Add Phinx\Wrapper\TextWrapper::getSeed() +* Bug fixes + * [#908](https://github.com/robmorgan/phinx/pull/908) Fix setting options for Column, ForeignKey and Index + * [#922](https://github.com/robmorgan/phinx/pull/922) SQLite adapter drops table on changeColumn if there's a foreign key + +**0.6.3** (Monday, 18th July 2016) + +* New features + * [#707](https://github.com/robmorgan/phinx/pull/707/files) Add arguments for timestamps columns names +* Documentation cleanup +* Bug fixes + * [#884](https://github.com/robmorgan/phinx/pull/884) Only rollback 1 migration when only 2 migrations exist + * Input and Output are now correctly supplied to migration template creation classes + +**0.6.2** (Thursday, 23rd June 2016) + +* Fix breakpoint support for Postgres +* HHVM now passes all tests + +**0.6.1** (Tuesday, 21st June 2016) + +* Fix rollback when only 1 migration + +**0.6.0** (Tuesday, 21st June 2016) + +* Backward incompatibility - see [UPGRADE_0.6](UPGRADE_0.6.md) document +* Introduce Input and Output access to migrations and template creation +* New breakpoint command +* Moved version history to this CHANGELOG.md document +* More tests + +**0.5.5** (Friday, 17th May 2016) + +* Fix support for running multiple seeders +* Bug fix for migration template source - defaults and command line +* Bug fixes + +**0.5.4** (Monday, 25th April 2016) + +* Added support for running multiple seeders +* Use `GLOB_BRACE` when finding migrations only if its available +* Added support for MySQL `VARBINARY` column type +* Minor bug fixes + +**0.5.3** (Monday, 7th March 2016) + +* Critical fix: allow `migration_name` to be `null`. Introduced in 0.5.2 +* Status command now shows migration start and end times +* Bug fix for rolling back by date +* Documentation improvements + +**0.5.2** (Tuesday, 1st March 2016) + +* Status command now includes missing migration names +* Added support for Postgres table comments +* Added `insert()` for the TablePrefixAdapter +* Fixed the migration verbosity flag +* Added MySQL 5.7 JSON support +* Added support for MySQL `FULLTEXT` indexes +* Postgres now supports `BIGSERIAL` for primary keys +* Added support for MySQL index limits +* Initial support for multiple migration paths (using glob) +* Documentation improvements +* Unit test enhancements + +**0.5.1** (Wednesday, 30th December 2015) + +* **PHP 5.3 is no longer supported!** +* Add support for Symfony 3.0 components +* Ensure that the `status` command returns the correct exit code +* Allow `$version` to be passed into templates +* Support for MySQL `YEAR` column type +* Multiple documentation updates and corrections + +**0.5.0** (Monday, 30th November 2015) + +* Support for seeding data after database creation +* The migration and seed directories are now nested under `db` by default +* Moved `Phinx\Migration\Util` to `Phinx\Util\Util` +* All `insert()` methods now have a slightly different method signature +* Fixed key/insert operations for MySQL +* Introduced `AdapterInterface::hasIndexByName()` +* Improved `dropForeignKey()` handling for SQLite +* Added support for the MySQL `binary` datatype. BLOBs now use the proper type. +* The status command shows a count of pending migrations in JSON output +* We are now testing against PHP 7 + +**0.4.6** (Friday, 11th September 2015) + +* You can now set custom migration templates in the config files +* Support for MySQL unsigned booleans +* Support for Postgres `smallint` column types +* Support for `AFTER` when using `changeColumn()` with MySQL +* Support for `precision` and `scale` when using the Postgres `decimal` type +* Fixed a bug where duplicate migration names could be used +* The schema table is now created with a primary key +* Fixed issues when using the MySQL `STRICT_TRANS_TABLE` mode +* Improved the docs in the default migration template +* Made Box PHAR ignore the bundled `phinx.yml` configuration file +* Updated Box installer URL +* Internal code improvements +* Documentation improvements + +**0.4.5** (Tuesday, 1st September 2015) + +* The rollback command now supports a date argument +* Fixed DBLIB DSN strings for Microsoft SQL Server +* Postgres support for `jsonb` columns added +* The `addTimestamps()` helper method no longer updates the `created_at` column +* Fix for Postgres named foreign keys +* Unit test improvements (including strict warnings) +* Documentation improvements + +**0.4.4** (Sunday, 14th June 2015) + +* The `change` method is now the default +* Added a generic adapter insert method. Warning: The implementation will change! +* Updated Symfony depdencies to ~2.7 +* Support for MySQL `BLOB` column types +* SQLite migration fixes +* Documentation improvements + +**0.4.3** (Monday, 23rd Feburary 2015) + +* Postgres bugfix for modifying column DEFAULTs +* MySQL bugfix for setting column INTEGER lengths +* SQLite bugfix for creating multiple indexes with similar names + +**0.4.2.1** (Saturday, 7th Feburary 2015) + +* Proper release, updated docs + +**0.4.2** (Friday, 6th Feburary 2015) + +* Postgres support for `json` columns added +* MySQL support for `enum` and `set` columns added +* Allow setting `identity` option on columns +* Template configuration and generation made more extensible +* Created a base class for `ProxyAdapter` and `TablePrefixAdapter` +* Switched to PSR-4 + +**0.4.1** (Tuesday, 23rd December 2014) + +* MySQL support for reserved words in hasColumn and getColumns methods +* Better MySQL Adapter test coverage and performance fixes +* Updated dependent Symfony components to 2.6.x + +**0.4.0** (Sunday, 14th December 2014) + +* Adding initial support for running Phinx via a web interface +* Support for table prefixes and suffixes +* Bugfix for foreign key options +* MySQL keeps column default when renaming columns +* MySQL support for tiny/medium and longtext columns added +* Changed SQL Server binary columns to varbinary +* MySQL supports table comments +* Postgres supports column comments +* Empty strings are now supported for default column values +* Booleans are now supported for default column values +* Fixed SQL Server default constraint error when changing column types +* Migration timestamps are now created in UTC +* Locked Symfony Components to 2.5.0 +* Support for custom migration base classes +* Cleaned up source code formatting +* Migrations have access to the output stream +* Support for custom PDO connections when a PHP config +* Added support for Postgres UUID type +* Fixed issue with Postgres dropping foreign keys + +**0.3.8** (Sunday, 5th October 2014) + +* Added new CHAR & Geospatial column types +* Added MySQL unix socket support +* Added precision & scale support for SQL Server +* Several bug fixes for SQLite +* Improved error messages +* Overall code optimizations +* Optimizations to MySQL hasTable method + +**0.3.7** (Tuesday, 12th August 2014) + +* Smarter configuration file support +* Support for Postgres Schemas +* Fixed charset support for Microsoft SQL Server +* Fix for Unique indexes in all adapters +* Improvements for MySQL foreign key migration syntax +* Allow MySQL column types with extra info +* Fixed SQLite autoincrement behaviour +* PHPDoc improvements +* Documentation improvements +* Unit test improvements +* Removing primary_key as a type + +**0.3.6** (Sunday, 29th June 2014) + +* Add custom adapter support +* Fix PHP 5.3 compatibility for SQL Server + +**0.3.5** (Saturday, 21st June 2014) + +* Added Microsoft SQL Server support +* Removed Primary Key column type +* Cleaned up and optimized many methods +* Updated Symfony dependencies to v2.5.0 +* PHPDoc improvements + +**0.3.4** (Sunday, 27th April 2014) + +* Added support MySQL unsigned integer, biginteger, float and decimal types +* Added JSON output support for the status command +* Fix a bug where Postgres couldnt rollback foreign keys +* Moved Phinx type references to interface constants +* Fixed a bug with SQLite in-memory databases + +**0.3.3** (Saturday, 22nd March 2014) + +* Added support for JSON configuration +* Named index support for all adapters (thanks @archer308) +* Updated Composer dependencies +* Fix for SQLite Integer Type +* Fix for MySQL port option + +**0.3.2** (Monday, 24th February 2014) + +* Adding better Postgres type support + +**0.3.1** (Sunday, 23rd February 2014) + +* Adding MySQL charset support to the YAML config +* Removing trailing spaces + +**0.3.0** (Sunday, 2nd February 2014) + +* PSR-2 support +* Method to add timestamps easily to tables +* Support for column comments in the Postgres adapter +* Fixes for MySQL driver options +* Fixes for MySQL biginteger type + +**0.2.9** (Saturday, 16th November 2013) + +* Added SQLite Support +* Improving the unit tests, especially on Windows + +**0.2.8** (Sunday, 25th August 2013) + +* Added PostgresSQL Support + +**0.2.7** (Saturday, 24th August 2013) + +* Critical fix for a token parsing bug +* Removed legacy build system +* Improving docs + +**0.2.6** (Saturday, 24th August 2013) + +* Added support for environment vars in config files +* Added support for environment vars to set the Phinx Env +* Improving docs +* Fixed a bug with column names in indexes +* Changes for developers in regards to the unit tests + +**0.2.5** (Sunday, 26th May 2013) + +* Added support for Box Phar Archive Packaging +* Added support for MYSQL_ATTR driver options +* Fixed a bug where foreign keys cannot be removed +* Added support for MySQL table collation +* Updated Composer dependencies +* Removed verbosity options, now relies on Symfony instead +* Improved unit tests + +**0.2.4** (Saturday, 20th April 2013) + +* The Rollback command supports the verbosity parameter +* The Rollback command has more detailed output +* Table::dropForeignKey now returns the table instance + +**0.2.3** (Saturday, 6th April 2013) + +* Fixed a reporting bug when Phinx couldn't connect to a database +* Added support for the MySQL 'ON UPDATE' function +* Phinx timestamp is now mapped to MySQL timestamp instead of datetime +* Fixed a docs typo for the minimum PHP version +* Added UTF8 support for migrations +* Changed regex to handle migration names differently +* Added support for custom MySQL table engines such as MyISAM +* Added the change method to the migration template + +**0.2.2** (Sunday, 3rd March 2013) + +* Added a new verbosity parameter to see more output when migrating +* Support for PHP config files + +**0.2.1** (Sunday, 3rd March 2013) + +* Broken Release. Do not use! +* Unit tests no longer rely on the default phinx.yml file +* Running migrate for the first time does not give php warnings +* `default_migration_table` is now actually supported +* Updated docblocks to 2013. + +**0.2.0** (Sunday, 13th January 2013) + +* First Birthday Release +* Added Reversible Migrations +* Removed options parameter from AdapterInterface::hasColumn() + +**0.1.7** (Tuesday, 8th January 2013) + +* Improved documentation on the YAML configuration file +* Removed options parameter from AdapterInterface::dropIndex() + +**0.1.6** (Sunday, 9th December 2012) + +* Added foreign key support +* Removed PEAR support +* Support for auto_increment on custom id columns +* Bugfix for column default value 0 +* Documentation improvements + +**0.1.5** (Sunday, 4th November 2012) + +* Added a test command +* Added transactions for adapters that support it +* Changing the Table API to use pending column methods +* Fixed a bug when defining multiple indexes on a table + +**0.1.4** (Sunday, 21st October 2012) + +* Documentation Improvements + +**0.1.3** (Saturday, 20th October 2012) + +* Fixed broken composer support + +**0.1.2** (Saturday, 20th October 2012) + +* Added composer support +* Now forces migrations to be in CamelCase format +* Now specifies the database name when migrating +* Creates the internal log table using its API instead of raw SQL + +**0.1.1** (Wednesday, 13th June 2012) + +* First point release. Ready for limited production use. + +**0.1.0** (Friday, 13th January 2012) + +* Initial public release. diff --git a/vendor/topthink/think-migration/phinx/CONTRIBUTING.md b/vendor/topthink/think-migration/phinx/CONTRIBUTING.md new file mode 100644 index 0000000..73b54ac --- /dev/null +++ b/vendor/topthink/think-migration/phinx/CONTRIBUTING.md @@ -0,0 +1,66 @@ +# How to contribute to Phinx + +Phinx relies heavily on external contributions in order to make it the best database migration +tool possible. Without the support of our 115+ contributors we wouldn't be where we are today! +We encourage anyone to submit documentation enhancements and code. + +Issues, feature requests and bugs should be submitted using the Github issue tool: +https://github.com/robmorgan/phinx/issues. + +This document briefly outlines the requirements to contribute code to Phinx. + +## Considerations + +Before you submit your pull request take a moment to answer the following questions. + +Answering '**YES**' to all questions will increase the likelihood of your PR being accepted! + +* Have I implemented my feature for as many database adapters as possible? +* Does my new feature improve Phinx's performance or keep it consistent? +* Does my feature fit within the database migration space? +* Is the code entirely my own and free from any commercial licensing? +* Am I happy to release my code under the MIT license? +* Is my code formatted using the [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) coding standard? + +**Note:** We accept bug fixes much faster into our development branch than features. + +## Getting Started + +Great, so you want to contribute. Let's get started: + +1. Start by forking Phinx on GitHub: https://github.com/robmorgan/phinx + +1. Clone your repository to a local directory on your development box. + +1. If you do not have Composer set up already, install it: + + ``` + curl -sS https://getcomposer.org/installer | php + ``` + +1. Change to your Phinx clone directory and pull the necessary dependencies: + + ``` + php composer.phar install + ``` + +1. Copy the `phpunit.xml.dist` template to `phpunit.xml` and change the configuration to suit your environment. If you are not using any particular adapter you can disable it in the `phpunit.xml` file. + +1. Run the unit tests locally to ensure they pass: + + ``` + php vendor/bin/phpunit --config phpunit.xml + ``` + +1. Write the code and unit tests for your bug fix or feature. + +1. Add any relevant documentation. + +1. Run the unit tests again and ensure they pass. + +1. Open a pull request on the Github project page. Ensure the code is being merged into the latest development branch (e.g: `0.5.x-dev`) and not `master`. + +## Documentation + +The Phinx documentation is stored in the **docs** directory using the [RestructedText](http://docutils.sourceforge.net/rst.html) format. All documentation merged to `master` is automatically published to the Phinx documentation site available +at: http://docs.phinx.org. Keep this in mind when submitting your PR, or ask someone to merge the development branch back down to master. diff --git a/vendor/topthink/think-migration/phinx/LICENSE b/vendor/topthink/think-migration/phinx/LICENSE new file mode 100644 index 0000000..26ade90 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/LICENSE @@ -0,0 +1,9 @@ +(The MIT license) + +Copyright (c) 2014 Rob Morgan + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendor/topthink/think-migration/phinx/README.md b/vendor/topthink/think-migration/phinx/README.md new file mode 100644 index 0000000..ed1badc --- /dev/null +++ b/vendor/topthink/think-migration/phinx/README.md @@ -0,0 +1,128 @@ +# [Phinx](https://phinx.org): Simple PHP Database Migrations + +[![Build Status](https://travis-ci.org/robmorgan/phinx.png?branch=master)](https://travis-ci.org/robmorgan/phinx) +[![Build status](https://ci.appveyor.com/api/projects/status/9vag4892hfq6effr)](https://ci.appveyor.com/project/robmorgan/phinx) +[![Code Coverage](https://scrutinizer-ci.com/g/robmorgan/phinx/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/robmorgan/phinx/) +[![Latest Stable Version](https://poser.pugx.org/robmorgan/phinx/version.png)](https://packagist.org/packages/robmorgan/phinx) +[![Total Downloads](https://poser.pugx.org/robmorgan/phinx/d/total.png)](https://packagist.org/packages/robmorgan/phinx) + +Phinx makes it ridiculously easy to manage the database migrations for your PHP app. In less than 5 minutes you can install Phinx and create your first database migration. Phinx is just about migrations without all the bloat of a database ORM system or framework. + +**Check out http://docs.phinx.org for the comprehensive documentation.** + +![phinxterm](https://cloud.githubusercontent.com/assets/178939/3887559/e6b5e524-21f2-11e4-8256-0ba6040725fc.gif) + +### Features + +* Write database migrations using database agnostic PHP code. +* Migrate up and down. +* Migrate on deployment. +* Seed data after database creation. +* Get going in less than 5 minutes. +* Stop worrying about the state of your database. +* Take advantage of SCM features such as branching. +* Integrate with any app. + +### Supported Adapters + +Phinx natively supports the following database adapters: + +* MySQL +* PostgreSQL +* SQLite +* Microsoft SQL Server + +## Install & Run + +### Composer + +The fastest way to install Phinx is to add it to your project using Composer (http://getcomposer.org/). + +1. Install Composer: + + ``` + curl -sS https://getcomposer.org/installer | php + ``` + +1. Require Phinx as a dependency using Composer: + + ``` + php composer.phar require robmorgan/phinx + ``` + +1. Install Phinx: + + ``` + php composer.phar install + ``` + +1. Execute Phinx: + + ``` + php vendor/bin/phinx + ``` + +### As a Phar + +You can also use the Box application to build Phinx as a Phar archive (https://box-project.github.io/box2/). + +1. Clone Phinx from GitHub + + ``` + git clone git://github.com/robmorgan/phinx.git + cd phinx + ``` + +1. Install Composer + + ``` + curl -s https://getcomposer.org/installer | php + ``` + +1. Install the Phinx dependencies + + ``` + php composer.phar install + ``` + +1. Install Box: + + ``` + curl -LSs https://box-project.github.io/box2/installer.php | php + ``` + +1. Create a Phar archive + + ``` + php box.phar build + ``` + +## Documentation + +Check out http://docs.phinx.org for the comprehensive documentation. + +## Contributing + +Please read the [CONTRIBUTING](CONTRIBUTING.md) document. + +## News & Updates + +Follow Rob (@\_rjm\_) on Twitter to stay up to date (http://twitter.com/_rjm_) + +## Misc + +### Version History + +Please read the [CHANGELOG](CHANGELOG.md) document. + +### License + +(The MIT license) + +Copyright (c) 2016 Rob Morgan + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterFactory.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterFactory.php new file mode 100644 index 0000000..90674f9 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterFactory.php @@ -0,0 +1,181 @@ + + */ +class AdapterFactory +{ + /** + * @var AdapterFactory + */ + protected static $instance; + + /** + * Get the factory singleton instance. + * + * @return AdapterFactory + */ + public static function instance() + { + if (!static::$instance) { + static::$instance = new static(); + } + return static::$instance; + } + + /** + * Class map of database adapters, indexed by PDO::ATTR_DRIVER_NAME. + * + * @var array + */ + protected $adapters = array( + 'mysql' => 'Phinx\Db\Adapter\MysqlAdapter', + 'pgsql' => 'Phinx\Db\Adapter\PostgresAdapter', + 'sqlite' => 'Phinx\Db\Adapter\SQLiteAdapter', + 'sqlsrv' => 'Phinx\Db\Adapter\SqlServerAdapter', + ); + + /** + * Class map of adapters wrappers, indexed by name. + * + * @var array + */ + protected $wrappers = array( + 'prefix' => 'Phinx\Db\Adapter\TablePrefixAdapter', + 'proxy' => 'Phinx\Db\Adapter\ProxyAdapter', + ); + + /** + * Add or replace an adapter with a fully qualified class name. + * + * @throws \RuntimeException + * @param string $name + * @param string $class + * @return $this + */ + public function registerAdapter($name, $class) + { + if (!is_subclass_of($class, 'Phinx\Db\Adapter\AdapterInterface')) { + throw new \RuntimeException(sprintf( + 'Adapter class "%s" must implement Phinx\\Db\\Adapter\\AdapterInterface', + $class + )); + } + $this->adapters[$name] = $class; + return $this; + } + + /** + * Get an adapter class by name. + * + * @throws \RuntimeException + * @param string $name + * @return string + */ + protected function getClass($name) + { + if (empty($this->adapters[$name])) { + throw new \RuntimeException(sprintf( + 'Adapter "%s" has not been registered', + $name + )); + } + return $this->adapters[$name]; + } + + /** + * Get an adapter instance by name. + * + * @param string $name + * @param array $options + * @return AdapterInterface + */ + public function getAdapter($name, array $options) + { + $class = $this->getClass($name); + return new $class($options); + } + + /** + * Add or replace a wrapper with a fully qualified class name. + * + * @throws \RuntimeException + * @param string $name + * @param string $class + * @return $this + */ + public function registerWrapper($name, $class) + { + if (!is_subclass_of($class, 'Phinx\Db\Adapter\WrapperInterface')) { + throw new \RuntimeException(sprintf( + 'Wrapper class "%s" must be implement Phinx\\Db\\Adapter\\WrapperInterface', + $class + )); + } + $this->wrappers[$name] = $class; + return $this; + } + + /** + * Get a wrapper class by name. + * + * @throws \RuntimeException + * @param string $name + * @return string + */ + protected function getWrapperClass($name) + { + if (empty($this->wrappers[$name])) { + throw new \RuntimeException(sprintf( + 'Wrapper "%s" has not been registered', + $name + )); + } + return $this->wrappers[$name]; + } + + /** + * Get a wrapper instance by name. + * + * @param string $name + * @param AdapterInterface $adapter + * @return AdapterInterface + */ + public function getWrapper($name, AdapterInterface $adapter) + { + $class = $this->getWrapperClass($name); + return new $class($adapter); + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterInterface.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterInterface.php new file mode 100644 index 0000000..f2e9fe1 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterInterface.php @@ -0,0 +1,510 @@ + + */ +interface AdapterInterface +{ + const PHINX_TYPE_STRING = 'string'; + const PHINX_TYPE_CHAR = 'char'; + const PHINX_TYPE_TEXT = 'text'; + const PHINX_TYPE_INTEGER = 'integer'; + const PHINX_TYPE_BIG_INTEGER = 'biginteger'; + const PHINX_TYPE_FLOAT = 'float'; + const PHINX_TYPE_DECIMAL = 'decimal'; + const PHINX_TYPE_DATETIME = 'datetime'; + const PHINX_TYPE_TIMESTAMP = 'timestamp'; + const PHINX_TYPE_TIME = 'time'; + const PHINX_TYPE_DATE = 'date'; + const PHINX_TYPE_BINARY = 'binary'; + const PHINX_TYPE_VARBINARY = 'varbinary'; + const PHINX_TYPE_BLOB = 'blob'; + const PHINX_TYPE_BOOLEAN = 'boolean'; + const PHINX_TYPE_JSON = 'json'; + const PHINX_TYPE_JSONB = 'jsonb'; + const PHINX_TYPE_UUID = 'uuid'; + const PHINX_TYPE_FILESTREAM = 'filestream'; + + // Geospatial database types + const PHINX_TYPE_GEOMETRY = 'geometry'; + const PHINX_TYPE_POINT = 'point'; + const PHINX_TYPE_LINESTRING = 'linestring'; + const PHINX_TYPE_POLYGON = 'polygon'; + + // only for mysql so far + const PHINX_TYPE_ENUM = 'enum'; + const PHINX_TYPE_SET = 'set'; + + /** + * Get all migrated version numbers. + * + * @return array + */ + public function getVersions(); + + /** + * Get all migration log entries, indexed by version number. + * + * @return array + */ + public function getVersionLog(); + + /** + * Set adapter configuration options. + * + * @param array $options + * @return AdapterInterface + */ + public function setOptions(array $options); + + /** + * Get all adapter options. + * + * @return array + */ + public function getOptions(); + + /** + * Check if an option has been set. + * + * @param string $name + * @return boolean + */ + public function hasOption($name); + + /** + * Get a single adapter option, or null if the option does not exist. + * + * @param string $name + * @return mixed + */ + public function getOption($name); + + /** + * Sets the console input. + * + * @param InputInterface $input Input + * @return AdapterInterface + */ + public function setInput(InputInterface $input); + + /** + * Gets the console input. + * + * @return InputInterface + */ + public function getInput(); + + /** + * Sets the console output. + * + * @param OutputInterface $output Output + * @return AdapterInterface + */ + public function setOutput(OutputInterface $output); + + /** + * Gets the console output. + * + * @return OutputInterface + */ + public function getOutput(); + + /** + * Records a migration being run. + * + * @param MigrationInterface $migration Migration + * @param string $direction Direction + * @param int $startTime Start Time + * @param int $endTime End Time + * @return AdapterInterface + */ + public function migrated(MigrationInterface $migration, $direction, $startTime, $endTime); + + /** + * Toggle a migration breakpoint. + * + * @param MigrationInterface $migration + * + * @return AdapterInterface + */ + public function toggleBreakpoint(MigrationInterface $migration); + + /** + * Reset all migration breakpoints. + * + * @return int The number of breakpoints reset + */ + public function resetAllBreakpoints(); + + /** + * Does the schema table exist? + * + * @deprecated use hasTable instead. + * @return boolean + */ + public function hasSchemaTable(); + + /** + * Creates the schema table. + * + * @return void + */ + public function createSchemaTable(); + + /** + * Returns the adapter type. + * + * @return string + */ + public function getAdapterType(); + + /** + * Initializes the database connection. + * + * @throws \RuntimeException When the requested database driver is not installed. + * @return void + */ + public function connect(); + + /** + * Closes the database connection. + * + * @return void + */ + public function disconnect(); + + /** + * Does the adapter support transactions? + * + * @return boolean + */ + public function hasTransactions(); + + /** + * Begin a transaction. + * + * @return void + */ + public function beginTransaction(); + + /** + * Commit a transaction. + * + * @return void + */ + public function commitTransaction(); + + /** + * Rollback a transaction. + * + * @return void + */ + public function rollbackTransaction(); + + /** + * Executes a SQL statement and returns the number of affected rows. + * + * @param string $sql SQL + * @return int + */ + public function execute($sql); + + /** + * Executes a SQL statement and returns the result as an array. + * + * @param string $sql SQL + * @return array + */ + public function query($sql); + + /** + * Executes a query and returns only one row as an array. + * + * @param string $sql SQL + * @return array + */ + public function fetchRow($sql); + + /** + * Executes a query and returns an array of rows. + * + * @param string $sql SQL + * @return array + */ + public function fetchAll($sql); + + /** + * Inserts data into a table. + * + * @param Table $table where to insert data + * @param array $row + * @return void + */ + public function insert(Table $table, $row); + + /** + * Quotes a table name for use in a query. + * + * @param string $tableName Table Name + * @return string + */ + public function quoteTableName($tableName); + + /** + * Quotes a column name for use in a query. + * + * @param string $columnName Table Name + * @return string + */ + public function quoteColumnName($columnName); + + /** + * Checks to see if a table exists. + * + * @param string $tableName Table Name + * @return boolean + */ + public function hasTable($tableName); + + /** + * Creates the specified database table. + * + * @param Table $table Table + * @return void + */ + public function createTable(Table $table); + + /** + * Renames the specified database table. + * + * @param string $tableName Table Name + * @param string $newName New Name + * @return void + */ + public function renameTable($tableName, $newName); + + /** + * Drops the specified database table. + * + * @param string $tableName Table Name + * @return void + */ + public function dropTable($tableName); + + /** + * Returns table columns + * + * @param string $tableName Table Name + * @return Column[] + */ + public function getColumns($tableName); + + /** + * Checks to see if a column exists. + * + * @param string $tableName Table Name + * @param string $columnName Column Name + * @return boolean + */ + public function hasColumn($tableName, $columnName); + + /** + * Adds the specified column to a database table. + * + * @param Table $table Table + * @param Column $column Column + * @return void + */ + public function addColumn(Table $table, Column $column); + + /** + * Renames the specified column. + * + * @param string $tableName Table Name + * @param string $columnName Column Name + * @param string $newColumnName New Column Name + * @return void + */ + public function renameColumn($tableName, $columnName, $newColumnName); + + /** + * Change a table column type. + * + * @param string $tableName Table Name + * @param string $columnName Column Name + * @param Column $newColumn New Column + * @return Table + */ + public function changeColumn($tableName, $columnName, Column $newColumn); + + /** + * Drops the specified column. + * + * @param string $tableName Table Name + * @param string $columnName Column Name + * @return void + */ + public function dropColumn($tableName, $columnName); + + /** + * Checks to see if an index exists. + * + * @param string $tableName Table Name + * @param mixed $columns Column(s) + * @return boolean + */ + public function hasIndex($tableName, $columns); + + /** + * Checks to see if an index specified by name exists. + * + * @param string $tableName Table Name + * @param string $indexName + * @return boolean + */ + public function hasIndexByName($tableName, $indexName); + + /** + * Adds the specified index to a database table. + * + * @param Table $table Table + * @param Index $index Index + * @return void + */ + public function addIndex(Table $table, Index $index); + + /** + * Drops the specified index from a database table. + * + * @param string $tableName + * @param mixed $columns Column(s) + * @return void + */ + public function dropIndex($tableName, $columns); + + /** + * Drops the index specified by name from a database table. + * + * @param string $tableName + * @param string $indexName + * @return void + */ + public function dropIndexByName($tableName, $indexName); + + /** + * Checks to see if a foreign key exists. + * + * @param string $tableName + * @param string[] $columns Column(s) + * @param string $constraint Constraint name + * @return boolean + */ + public function hasForeignKey($tableName, $columns, $constraint = null); + + /** + * Adds the specified foreign key to a database table. + * + * @param Table $table + * @param ForeignKey $foreignKey + * @return void + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey); + + /** + * Drops the specified foreign key from a database table. + * + * @param string $tableName + * @param string[] $columns Column(s) + * @param string $constraint Constraint name + * @return void + */ + public function dropForeignKey($tableName, $columns, $constraint = null); + + /** + * Returns an array of the supported Phinx column types. + * + * @return array + */ + public function getColumnTypes(); + + /** + * Checks that the given column is of a supported type. + * + * @param Column $column + * @return boolean + */ + public function isValidColumnType(Column $column); + + /** + * Converts the Phinx logical type to the adapter's SQL type. + * + * @param string $type + * @param integer $limit + * @return string + */ + public function getSqlType($type, $limit = null); + + /** + * Creates a new database. + * + * @param string $name Database Name + * @param array $options Options + * @return void + */ + public function createDatabase($name, $options = array()); + + /** + * Checks to see if a database exists. + * + * @param string $name Database Name + * @return boolean + */ + public function hasDatabase($name); + + /** + * Drops the specified database. + * + * @param string $name Database Name + * @return void + */ + public function dropDatabase($name); +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php new file mode 100644 index 0000000..ae5ec39 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php @@ -0,0 +1,507 @@ + + */ +abstract class AdapterWrapper implements AdapterInterface, WrapperInterface +{ + /** + * @var AdapterInterface + */ + protected $adapter; + + /** + * {@inheritdoc} + */ + public function __construct(AdapterInterface $adapter) + { + $this->setAdapter($adapter); + } + + /** + * {@inheritdoc} + */ + public function setAdapter(AdapterInterface $adapter) + { + $this->adapter = $adapter; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * {@inheritdoc} + */ + public function setOptions(array $options) + { + $this->adapter->setOptions($options); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOptions() + { + return $this->adapter->getOptions(); + } + + /** + * {@inheritdoc} + */ + public function hasOption($name) + { + return $this->adapter->hasOption($name); + } + + /** + * {@inheritdoc} + */ + public function getOption($name) + { + return $this->adapter->getOption($name); + } + + /** + * {@inheritdoc} + */ + public function setInput(InputInterface $input) + { + $this->adapter->setInput($input); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getInput() + { + return $this->adapter->getInput(); + } + + /** + * {@inheritdoc} + */ + public function setOutput(OutputInterface $output) + { + $this->adapter->setOutput($output); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOutput() + { + return $this->adapter->getOutput(); + } + + /** + * {@inheritdoc} + */ + public function connect() + { + return $this->getAdapter()->connect(); + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + return $this->getAdapter()->disconnect(); + } + + /** + * {@inheritdoc} + */ + public function execute($sql) + { + return $this->getAdapter()->execute($sql); + } + + /** + * {@inheritdoc} + */ + public function query($sql) + { + return $this->getAdapter()->query($sql); + } + + /** + * {@inheritdoc} + */ + public function insert(Table $table, $row) + { + return $this->getAdapter()->insert($table, $row); + } + + /** + * {@inheritdoc} + */ + public function fetchRow($sql) + { + return $this->getAdapter()->fetchRow($sql); + } + + /** + * {@inheritdoc} + */ + public function fetchAll($sql) + { + return $this->getAdapter()->fetchAll($sql); + } + + /** + * {@inheritdoc} + */ + public function getVersions() + { + return $this->getAdapter()->getVersions(); + } + + /** + * {@inheritdoc} + */ + public function getVersionLog() + { + return $this->getAdapter()->getVersionLog(); + } + + /** + * {@inheritdoc} + */ + public function migrated(MigrationInterface $migration, $direction, $startTime, $endTime) + { + $this->getAdapter()->migrated($migration, $direction, $startTime, $endTime); + return $this; + } + + /** + * @inheritDoc + */ + public function toggleBreakpoint(MigrationInterface $migration) + { + $this->getAdapter()->toggleBreakpoint($migration); + return $this; + } + + /** + * @inheritDoc + */ + public function resetAllBreakpoints() + { + return $this->getAdapter()->resetAllBreakpoints(); + } + + /** + * {@inheritdoc} + */ + public function hasSchemaTable() + { + return $this->getAdapter()->hasSchemaTable(); + } + + /** + * {@inheritdoc} + */ + public function createSchemaTable() + { + return $this->getAdapter()->createSchemaTable(); + } + + /** + * {@inheritdoc} + */ + public function getColumnTypes() + { + return $this->getAdapter()->getColumnTypes(); + } + + /** + * {@inheritdoc} + */ + public function isValidColumnType(Column $column) + { + return $this->getAdapter()->isValidColumnType($column); + } + + /** + * {@inheritdoc} + */ + public function hasTransactions() + { + return $this->getAdapter()->hasTransactions(); + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + return $this->getAdapter()->beginTransaction(); + } + + /** + * {@inheritdoc} + */ + public function commitTransaction() + { + return $this->getAdapter()->commitTransaction(); + } + + /** + * {@inheritdoc} + */ + public function rollbackTransaction() + { + return $this->getAdapter()->rollbackTransaction(); + } + + /** + * {@inheritdoc} + */ + public function quoteTableName($tableName) + { + return $this->getAdapter()->quoteTableName($tableName); + } + + /** + * {@inheritdoc} + */ + public function quoteColumnName($columnName) + { + return $this->getAdapter()->quoteColumnName($columnName); + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + return $this->getAdapter()->hasTable($tableName); + } + + /** + * {@inheritdoc} + */ + public function createTable(Table $table) + { + return $this->getAdapter()->createTable($table); + } + + /** + * {@inheritdoc} + */ + public function renameTable($tableName, $newTableName) + { + return $this->getAdapter()->renameTable($tableName, $newTableName); + } + + /** + * {@inheritdoc} + */ + public function dropTable($tableName) + { + return $this->getAdapter()->dropTable($tableName); + } + + /** + * {@inheritdoc} + */ + public function getColumns($tableName) + { + return $this->getAdapter()->getColumns($tableName); + } + + /** + * {@inheritdoc} + */ + public function hasColumn($tableName, $columnName) + { + return $this->getAdapter()->hasColumn($tableName, $columnName); + } + + /** + * {@inheritdoc} + */ + public function addColumn(Table $table, Column $column) + { + return $this->getAdapter()->addColumn($table, $column); + } + + /** + * {@inheritdoc} + */ + public function renameColumn($tableName, $columnName, $newColumnName) + { + return $this->getAdapter()->renameColumn($tableName, $columnName, $newColumnName); + } + + /** + * {@inheritdoc} + */ + public function changeColumn($tableName, $columnName, Column $newColumn) + { + return $this->getAdapter()->changeColumn($tableName, $columnName, $newColumn); + } + + /** + * {@inheritdoc} + */ + public function dropColumn($tableName, $columnName) + { + return $this->getAdapter()->dropColumn($tableName, $columnName); + } + + /** + * {@inheritdoc} + */ + public function hasIndex($tableName, $columns) + { + return $this->getAdapter()->hasIndex($tableName, $columns); + } + + /** + * {@inheritdoc} + */ + public function hasIndexByName($tableName, $indexName) + { + return $this->getAdapter()->hasIndexByName($tableName, $indexName); + } + + /** + * {@inheritdoc} + */ + public function addIndex(Table $table, Index $index) + { + return $this->getAdapter()->addIndex($table, $index); + } + + /** + * {@inheritdoc} + */ + public function dropIndex($tableName, $columns, $options = array()) + { + return $this->getAdapter()->dropIndex($tableName, $columns, $options); + } + + /** + * {@inheritdoc} + */ + public function dropIndexByName($tableName, $indexName) + { + return $this->getAdapter()->dropIndexByName($tableName, $indexName); + } + + /** + * {@inheritdoc} + */ + public function hasForeignKey($tableName, $columns, $constraint = null) + { + return $this->getAdapter()->hasForeignKey($tableName, $columns, $constraint); + } + + /** + * {@inheritdoc} + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey) + { + return $this->getAdapter()->addForeignKey($table, $foreignKey); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($tableName, $columns, $constraint = null) + { + return $this->getAdapter()->dropForeignKey($tableName, $columns, $constraint); + } + + /** + * {@inheritdoc} + */ + public function getSqlType($type, $limit = null) + { + return $this->getAdapter()->getSqlType($type, $limit); + } + + /** + * {@inheritdoc} + */ + public function createDatabase($name, $options = array()) + { + return $this->getAdapter()->createDatabase($name, $options); + } + + /** + * {@inheritdoc} + */ + public function hasDatabase($name) + { + return $this->getAdapter()->hasDatabase($name); + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($name) + { + return $this->getAdapter()->dropDatabase($name); + } + + /** + * @inheritDoc + */ + public function castToBool($value) + { + return $this->getAdapter()->castToBool($value); + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php new file mode 100644 index 0000000..f882fb5 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php @@ -0,0 +1,1145 @@ + + */ +class MysqlAdapter extends PdoAdapter implements AdapterInterface +{ + + protected $signedColumnTypes = array('integer' => true, 'biginteger' => true, 'float' => true, 'decimal' => true, 'boolean' => true); + + const TEXT_TINY = 255; + const TEXT_SMALL = 255; /* deprecated, alias of TEXT_TINY */ + const TEXT_REGULAR = 65535; + const TEXT_MEDIUM = 16777215; + const TEXT_LONG = 4294967295; + + // According to https://dev.mysql.com/doc/refman/5.0/en/blob.html BLOB sizes are the same as TEXT + const BLOB_TINY = 255; + const BLOB_SMALL = 255; /* deprecated, alias of BLOB_TINY */ + const BLOB_REGULAR = 65535; + const BLOB_MEDIUM = 16777215; + const BLOB_LONG = 4294967295; + + const INT_TINY = 255; + const INT_SMALL = 65535; + const INT_MEDIUM = 16777215; + const INT_REGULAR = 4294967295; + const INT_BIG = 18446744073709551615; + + const TYPE_YEAR = 'year'; + + /** + * {@inheritdoc} + */ + public function connect() + { + if (null === $this->connection) { + if (!class_exists('PDO') || !in_array('mysql', \PDO::getAvailableDrivers(), true)) { + // @codeCoverageIgnoreStart + throw new \RuntimeException('You need to enable the PDO_Mysql extension for Phinx to run properly.'); + // @codeCoverageIgnoreEnd + } + + $db = null; + $options = $this->getOptions(); + + $dsn = 'mysql:'; + + if (!empty($options['unix_socket'])) { + // use socket connection + $dsn .= 'unix_socket=' . $options['unix_socket']; + } else { + // use network connection + $dsn .= 'host=' . $options['host']; + if (!empty($options['port'])) { + $dsn .= ';port=' . $options['port']; + } + } + + $dsn .= ';dbname=' . $options['name']; + + // charset support + if (!empty($options['charset'])) { + $dsn .= ';charset=' . $options['charset']; + } + + $driverOptions = array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION); + + // support arbitrary \PDO::MYSQL_ATTR_* driver options and pass them to PDO + // http://php.net/manual/en/ref.pdo-mysql.php#pdo-mysql.constants + foreach ($options as $key => $option) { + if (strpos($key, 'mysql_attr_') === 0) { + $driverOptions[constant('\PDO::' . strtoupper($key))] = $option; + } + } + + try { + $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions); + } catch (\PDOException $exception) { + throw new \InvalidArgumentException(sprintf( + 'There was a problem connecting to the database: %s', + $exception->getMessage() + )); + } + + $this->setConnection($db); + } + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + $this->connection = null; + } + + /** + * {@inheritdoc} + */ + public function hasTransactions() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + $this->execute('START TRANSACTION'); + } + + /** + * {@inheritdoc} + */ + public function commitTransaction() + { + $this->execute('COMMIT'); + } + + /** + * {@inheritdoc} + */ + public function rollbackTransaction() + { + $this->execute('ROLLBACK'); + } + + /** + * {@inheritdoc} + */ + public function quoteTableName($tableName) + { + return str_replace('.', '`.`', $this->quoteColumnName($tableName)); + } + + /** + * {@inheritdoc} + */ + public function quoteColumnName($columnName) + { + return '`' . str_replace('`', '``', $columnName) . '`'; + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + $options = $this->getOptions(); + + $exists = $this->fetchRow(sprintf( + "SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'", + $options['name'], $tableName + )); + + return !empty($exists); + } + + /** + * {@inheritdoc} + */ + public function createTable(Table $table) + { + $this->startCommandTimer(); + + // This method is based on the MySQL docs here: http://dev.mysql.com/doc/refman/5.1/en/create-index.html + $defaultOptions = array( + 'engine' => 'InnoDB', + 'collation' => 'utf8_general_ci' + ); + $options = array_merge($defaultOptions, $table->getOptions()); + + // Add the default primary key + $columns = $table->getPendingColumns(); + + if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) { + $options['id'] = 'id'; + } + + if (isset($options['id']) && is_string($options['id'])) { + // Handle id => "field_name" to support AUTO_INCREMENT + $column = new Column(); + $column->setName($options['id']) + ->setType('integer') + ->setSigned(isset($options['signed']) ? $options['signed'] : true) + ->setIdentity(true); + + array_unshift($columns, $column); + $options['primary_key'] = $options['id']; + } + + // TODO - process table options like collation etc + + // process table engine (default to InnoDB) + $optionsStr = 'ENGINE = InnoDB'; + if (isset($options['engine'])) { + $optionsStr = sprintf('ENGINE = %s', $options['engine']); + } + + // process table collation + if (isset($options['collation'])) { + $charset = explode('_', $options['collation']); + $optionsStr .= sprintf(' CHARACTER SET %s', $charset[0]); + $optionsStr .= sprintf(' COLLATE %s', $options['collation']); + } + + // set the table comment + if (isset($options['comment'])) { + $optionsStr .= sprintf(" COMMENT=%s ", $this->getConnection()->quote($options['comment'])); + } + + $sql = 'CREATE TABLE '; + $sql .= $this->quoteTableName($table->getName()) . ' ('; + foreach ($columns as $column) { + $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', '; + } + + // set the primary key(s) + if (isset($options['primary_key'])) { + $sql = rtrim($sql); + $sql .= ' PRIMARY KEY ('; + if (is_string($options['primary_key'])) { // handle primary_key => 'id' + $sql .= $this->quoteColumnName($options['primary_key']); + } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id') + // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the + // anonymous function, but for now just hard-code the adapter quotes + $sql .= implode( + ',', + array_map( + function ($v) { + return '`' . $v . '`'; + }, + $options['primary_key'] + ) + ); + } + $sql .= ')'; + } else { + $sql = substr(rtrim($sql), 0, -1); // no primary keys + } + + // set the indexes + $indexes = $table->getIndexes(); + if (!empty($indexes)) { + foreach ($indexes as $index) { + $sql .= ', ' . $this->getIndexSqlDefinition($index); + } + } + + // set the foreign keys + $foreignKeys = $table->getForeignKeys(); + if (!empty($foreignKeys)) { + foreach ($foreignKeys as $foreignKey) { + $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey); + } + } + + $sql .= ') ' . $optionsStr; + $sql = rtrim($sql) . ';'; + + // execute the sql + $this->writeCommand('createTable', array($table->getName())); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function renameTable($tableName, $newTableName) + { + $this->startCommandTimer(); + $this->writeCommand('renameTable', array($tableName, $newTableName)); + $this->execute(sprintf('RENAME TABLE %s TO %s', $this->quoteTableName($tableName), $this->quoteTableName($newTableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropTable($tableName) + { + $this->startCommandTimer(); + $this->writeCommand('dropTable', array($tableName)); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getColumns($tableName) + { + $columns = array(); + $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName))); + foreach ($rows as $columnInfo) { + + $phinxType = $this->getPhinxType($columnInfo['Type']); + + $column = new Column(); + $column->setName($columnInfo['Field']) + ->setNull($columnInfo['Null'] !== 'NO') + ->setDefault($columnInfo['Default']) + ->setType($phinxType['name']) + ->setLimit($phinxType['limit']); + + if ($columnInfo['Extra'] === 'auto_increment') { + $column->setIdentity(true); + } + + $columns[] = $column; + } + + return $columns; + } + + /** + * {@inheritdoc} + */ + public function hasColumn($tableName, $columnName) + { + $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName))); + foreach ($rows as $column) { + if (strcasecmp($column['Field'], $columnName) === 0) { + return true; + } + } + + return false; + } + + /** + * Get the defintion for a `DEFAULT` statement. + * + * @param mixed $default + * @return string + */ + protected function getDefaultValueDefinition($default) + { + if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) { + $default = $this->getConnection()->quote($default); + } elseif (is_bool($default)) { + $default = $this->castToBool($default); + } + return isset($default) ? ' DEFAULT ' . $default : ''; + } + + /** + * {@inheritdoc} + */ + public function addColumn(Table $table, Column $column) + { + $this->startCommandTimer(); + $sql = sprintf( + 'ALTER TABLE %s ADD %s %s', + $this->quoteTableName($table->getName()), + $this->quoteColumnName($column->getName()), + $this->getColumnSqlDefinition($column) + ); + + if ($column->getAfter()) { + $sql .= ' AFTER ' . $this->quoteColumnName($column->getAfter()); + } + + $this->writeCommand('addColumn', array($table->getName(), $column->getName(), $column->getType())); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function renameColumn($tableName, $columnName, $newColumnName) + { + $this->startCommandTimer(); + $rows = $this->fetchAll(sprintf('DESCRIBE %s', $this->quoteTableName($tableName))); + foreach ($rows as $row) { + if (strcasecmp($row['Field'], $columnName) === 0) { + $null = ($row['Null'] == 'NO') ? 'NOT NULL' : 'NULL'; + $extra = ' ' . strtoupper($row['Extra']); + if (!is_null($row['Default'])) { + $extra .= $this->getDefaultValueDefinition($row['Default']); + } + $definition = $row['Type'] . ' ' . $null . $extra; + + $this->writeCommand('renameColumn', array($tableName, $columnName, $newColumnName)); + $this->execute( + sprintf( + 'ALTER TABLE %s CHANGE COLUMN %s %s %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName), + $this->quoteColumnName($newColumnName), + $definition + ) + ); + $this->endCommandTimer(); + return; + } + } + + throw new \InvalidArgumentException(sprintf( + 'The specified column doesn\'t exist: ' + . $columnName + )); + } + + /** + * {@inheritdoc} + */ + public function changeColumn($tableName, $columnName, Column $newColumn) + { + $this->startCommandTimer(); + $this->writeCommand('changeColumn', array($tableName, $columnName, $newColumn->getType())); + $after = $newColumn->getAfter() ? ' AFTER ' . $this->quoteColumnName($newColumn->getAfter()) : ''; + $this->execute( + sprintf( + 'ALTER TABLE %s CHANGE %s %s %s%s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName), + $this->quoteColumnName($newColumn->getName()), + $this->getColumnSqlDefinition($newColumn), + $after + ) + ); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropColumn($tableName, $columnName) + { + $this->startCommandTimer(); + $this->writeCommand('dropColumn', array($tableName, $columnName)); + $this->execute( + sprintf( + 'ALTER TABLE %s DROP COLUMN %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName) + ) + ); + $this->endCommandTimer(); + } + + /** + * Get an array of indexes from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + protected function getIndexes($tableName) + { + $indexes = array(); + $rows = $this->fetchAll(sprintf('SHOW INDEXES FROM %s', $this->quoteTableName($tableName))); + foreach ($rows as $row) { + if (!isset($indexes[$row['Key_name']])) { + $indexes[$row['Key_name']] = array('columns' => array()); + } + $indexes[$row['Key_name']]['columns'][] = strtolower($row['Column_name']); + } + return $indexes; + } + + /** + * {@inheritdoc} + */ + public function hasIndex($tableName, $columns) + { + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $columns = array_map('strtolower', $columns); + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $index) { + if ($columns == $index['columns']) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function hasIndexByName($tableName, $indexName) + { + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $name => $index) { + if ($name === $indexName) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function addIndex(Table $table, Index $index) + { + $this->startCommandTimer(); + $this->writeCommand('addIndex', array($table->getName(), $index->getColumns())); + $this->execute( + sprintf( + 'ALTER TABLE %s ADD %s', + $this->quoteTableName($table->getName()), + $this->getIndexSqlDefinition($index) + ) + ); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropIndex($tableName, $columns) + { + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $this->writeCommand('dropIndex', array($tableName, $columns)); + $indexes = $this->getIndexes($tableName); + $columns = array_map('strtolower', $columns); + + foreach ($indexes as $indexName => $index) { + if ($columns == $index['columns']) { + $this->execute( + sprintf( + 'ALTER TABLE %s DROP INDEX %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($indexName) + ) + ); + $this->endCommandTimer(); + return; + } + } + } + + /** + * {@inheritdoc} + */ + public function dropIndexByName($tableName, $indexName) + { + $this->startCommandTimer(); + $this->writeCommand('dropIndexByName', array($tableName, $indexName)); + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $name => $index) { + //$a = array_diff($columns, $index['columns']); + if ($name === $indexName) { + $this->execute( + sprintf( + 'ALTER TABLE %s DROP INDEX %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($indexName) + ) + ); + $this->endCommandTimer(); + return; + } + } + } + + /** + * {@inheritdoc} + */ + public function hasForeignKey($tableName, $columns, $constraint = null) + { + if (is_string($columns)) { + $columns = array($columns); // str to array + } + $foreignKeys = $this->getForeignKeys($tableName); + if ($constraint) { + if (isset($foreignKeys[$constraint])) { + return !empty($foreignKeys[$constraint]); + } + return false; + } else { + foreach ($foreignKeys as $key) { + $a = array_diff($columns, $key['columns']); + if ($columns == $key['columns']) { + return true; + } + } + return false; + } + } + + /** + * Get an array of foreign keys from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + protected function getForeignKeys($tableName) + { + $foreignKeys = array(); + $rows = $this->fetchAll(sprintf( + "SELECT + CONSTRAINT_NAME, + TABLE_NAME, + COLUMN_NAME, + REFERENCED_TABLE_NAME, + REFERENCED_COLUMN_NAME + FROM information_schema.KEY_COLUMN_USAGE + WHERE REFERENCED_TABLE_SCHEMA = DATABASE() + AND REFERENCED_TABLE_NAME IS NOT NULL + AND TABLE_NAME = '%s' + ORDER BY POSITION_IN_UNIQUE_CONSTRAINT", + $tableName + )); + foreach ($rows as $row) { + $foreignKeys[$row['CONSTRAINT_NAME']]['table'] = $row['TABLE_NAME']; + $foreignKeys[$row['CONSTRAINT_NAME']]['columns'][] = $row['COLUMN_NAME']; + $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_table'] = $row['REFERENCED_TABLE_NAME']; + $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_columns'][] = $row['REFERENCED_COLUMN_NAME']; + } + return $foreignKeys; + } + + /** + * {@inheritdoc} + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey) + { + $this->startCommandTimer(); + $this->writeCommand('addForeignKey', array($table->getName(), $foreignKey->getColumns())); + $this->execute( + sprintf( + 'ALTER TABLE %s ADD %s', + $this->quoteTableName($table->getName()), + $this->getForeignKeySqlDefinition($foreignKey) + ) + ); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($tableName, $columns, $constraint = null) + { + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $this->writeCommand('dropForeignKey', array($tableName, $columns)); + + if ($constraint) { + $this->execute( + sprintf( + 'ALTER TABLE %s DROP FOREIGN KEY %s', + $this->quoteTableName($tableName), + $constraint + ) + ); + $this->endCommandTimer(); + return; + } else { + foreach ($columns as $column) { + $rows = $this->fetchAll(sprintf( + "SELECT + CONSTRAINT_NAME + FROM information_schema.KEY_COLUMN_USAGE + WHERE REFERENCED_TABLE_SCHEMA = DATABASE() + AND REFERENCED_TABLE_NAME IS NOT NULL + AND TABLE_NAME = '%s' + AND COLUMN_NAME = '%s' + ORDER BY POSITION_IN_UNIQUE_CONSTRAINT", + $tableName, + $column + )); + foreach ($rows as $row) { + $this->dropForeignKey($tableName, $columns, $row['CONSTRAINT_NAME']); + } + } + } + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getSqlType($type, $limit = null) + { + switch ($type) { + case static::PHINX_TYPE_STRING: + return array('name' => 'varchar', 'limit' => $limit ? $limit : 255); + break; + case static::PHINX_TYPE_CHAR: + return array('name' => 'char', 'limit' => $limit ? $limit : 255); + break; + case static::PHINX_TYPE_TEXT: + if ($limit) { + $sizes = array( + // Order matters! Size must always be tested from longest to shortest! + 'longtext' => static::TEXT_LONG, + 'mediumtext' => static::TEXT_MEDIUM, + 'text' => static::TEXT_REGULAR, + 'tinytext' => static::TEXT_SMALL, + ); + foreach ($sizes as $name => $length) { + if ($limit >= $length) { + return array('name' => $name); + } + } + } + return array('name' => 'text'); + break; + case static::PHINX_TYPE_BINARY: + return array('name' => 'binary', 'limit' => $limit ? $limit : 255); + break; + case static::PHINX_TYPE_VARBINARY: + return array('name' => 'varbinary', 'limit' => $limit ? $limit : 255); + break; + case static::PHINX_TYPE_BLOB: + if ($limit) { + $sizes = array( + // Order matters! Size must always be tested from longest to shortest! + 'longblob' => static::BLOB_LONG, + 'mediumblob' => static::BLOB_MEDIUM, + 'blob' => static::BLOB_REGULAR, + 'tinyblob' => static::BLOB_SMALL, + ); + foreach ($sizes as $name => $length) { + if ($limit >= $length) { + return array('name' => $name); + } + } + } + return array('name' => 'blob'); + break; + case static::PHINX_TYPE_INTEGER: + if ($limit && $limit >= static::INT_TINY) { + $sizes = array( + // Order matters! Size must always be tested from longest to shortest! + 'bigint' => static::INT_BIG, + 'int' => static::INT_REGULAR, + 'mediumint' => static::INT_MEDIUM, + 'smallint' => static::INT_SMALL, + 'tinyint' => static::INT_TINY, + ); + $limits = array( + 'int' => 11, + 'bigint' => 20, + ); + foreach ($sizes as $name => $length) { + if ($limit >= $length) { + $def = array('name' => $name); + if (isset($limits[$name])) { + $def['limit'] = $limits[$name]; + } + return $def; + } + } + } elseif (!$limit) { + $limit = 11; + } + return array('name' => 'int', 'limit' => $limit); + break; + case static::PHINX_TYPE_BIG_INTEGER: + return array('name' => 'bigint', 'limit' => 20); + break; + case static::PHINX_TYPE_FLOAT: + return array('name' => 'float'); + break; + case static::PHINX_TYPE_DECIMAL: + return array('name' => 'decimal'); + break; + case static::PHINX_TYPE_DATETIME: + return array('name' => 'datetime'); + break; + case static::PHINX_TYPE_TIMESTAMP: + return array('name' => 'timestamp'); + break; + case static::PHINX_TYPE_TIME: + return array('name' => 'time'); + break; + case static::PHINX_TYPE_DATE: + return array('name' => 'date'); + break; + case static::PHINX_TYPE_BOOLEAN: + return array('name' => 'tinyint', 'limit' => 1); + break; + case static::PHINX_TYPE_UUID: + return array('name' => 'char', 'limit' => 36); + // Geospatial database types + case static::PHINX_TYPE_GEOMETRY: + case static::PHINX_TYPE_POINT: + case static::PHINX_TYPE_LINESTRING: + case static::PHINX_TYPE_POLYGON: + return array('name' => $type); + case static::PHINX_TYPE_ENUM: + return array('name' => 'enum'); + break; + case static::PHINX_TYPE_SET: + return array('name' => 'set'); + break; + case static::TYPE_YEAR: + if (!$limit || in_array($limit, array(2, 4))) + $limit = 4; + return array('name' => 'year', 'limit' => $limit); + break; + case static::PHINX_TYPE_JSON: + return array('name' => 'json'); + break; + default: + throw new \RuntimeException('The type: "' . $type . '" is not supported.'); + } + } + + /** + * Returns Phinx type by SQL type + * + * @param string $sqlTypeDef + * @throws \RuntimeException + * @internal param string $sqlType SQL type + * @returns string Phinx type + */ + public function getPhinxType($sqlTypeDef) + { + if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*(.+)*$/', $sqlTypeDef, $matches)) { + throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported'); + } else { + $limit = null; + $precision = null; + $type = $matches[1]; + if (count($matches) > 2) { + $limit = $matches[3] ? (int) $matches[3] : null; + } + if (count($matches) > 4) { + $precision = (int) $matches[5]; + } + if ($type === 'tinyint' && $limit === 1) { + $type = static::PHINX_TYPE_BOOLEAN; + $limit = null; + } + switch ($type) { + case 'varchar': + $type = static::PHINX_TYPE_STRING; + if ($limit === 255) { + $limit = null; + } + break; + case 'char': + $type = static::PHINX_TYPE_CHAR; + if ($limit === 255) { + $limit = null; + } + if ($limit === 36) { + $type = static::PHINX_TYPE_UUID; + } + break; + case 'tinyint': + $type = static::PHINX_TYPE_INTEGER; + $limit = static::INT_TINY; + break; + case 'smallint': + $type = static::PHINX_TYPE_INTEGER; + $limit = static::INT_SMALL; + break; + case 'mediumint': + $type = static::PHINX_TYPE_INTEGER; + $limit = static::INT_MEDIUM; + break; + case 'int': + $type = static::PHINX_TYPE_INTEGER; + if ($limit === 11) { + $limit = null; + } + break; + case 'bigint': + if ($limit === 20) { + $limit = null; + } + $type = static::PHINX_TYPE_BIG_INTEGER; + break; + case 'blob': + $type = static::PHINX_TYPE_BINARY; + break; + case 'tinyblob': + $type = static::PHINX_TYPE_BINARY; + $limit = static::BLOB_TINY; + break; + case 'mediumblob': + $type = static::PHINX_TYPE_BINARY; + $limit = static::BLOB_MEDIUM; + break; + case 'longblob': + $type = static::PHINX_TYPE_BINARY; + $limit = static::BLOB_LONG; + break; + case 'tinytext': + $type = static::PHINX_TYPE_TEXT; + $limit = static::TEXT_TINY; + break; + case 'mediumtext': + $type = static::PHINX_TYPE_TEXT; + $limit = static::TEXT_MEDIUM; + break; + case 'longtext': + $type = static::PHINX_TYPE_TEXT; + $limit = static::TEXT_LONG; + break; + } + + $this->getSqlType($type, $limit); + + return array( + 'name' => $type, + 'limit' => $limit, + 'precision' => $precision + ); + } + } + + /** + * {@inheritdoc} + */ + public function createDatabase($name, $options = array()) + { + $this->startCommandTimer(); + $this->writeCommand('createDatabase', array($name)); + $charset = isset($options['charset']) ? $options['charset'] : 'utf8'; + + if (isset($options['collation'])) { + $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s` COLLATE `%s`', $name, $charset, $options['collation'])); + } else { + $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s`', $name, $charset)); + } + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function hasDatabase($name) + { + $rows = $this->fetchAll( + sprintf( + 'SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = \'%s\'', + $name + ) + ); + + foreach ($rows as $row) { + if (!empty($row)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($name) + { + $this->startCommandTimer(); + $this->writeCommand('dropDatabase', array($name)); + $this->execute(sprintf('DROP DATABASE IF EXISTS `%s`', $name)); + $this->endCommandTimer(); + } + + /** + * Gets the MySQL Column Definition for a Column object. + * + * @param Column $column Column + * @return string + */ + protected function getColumnSqlDefinition(Column $column) + { + $sqlType = $this->getSqlType($column->getType(), $column->getLimit()); + + $def = ''; + $def .= strtoupper($sqlType['name']); + if ($column->getPrecision() && $column->getScale()) { + $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')'; + } elseif (isset($sqlType['limit'])) { + $def .= '(' . $sqlType['limit'] . ')'; + } + if (($values = $column->getValues()) && is_array($values)) { + $def .= "('" . implode("', '", $values) . "')"; + } + $def .= (!$column->isSigned() && isset($this->signedColumnTypes[$column->getType()])) ? ' unsigned' : '' ; + $def .= ($column->isNull() == false) ? ' NOT NULL' : ' NULL'; + $def .= ($column->isIdentity()) ? ' AUTO_INCREMENT' : ''; + $def .= $this->getDefaultValueDefinition($column->getDefault()); + + if ($column->getComment()) { + $def .= ' COMMENT ' . $this->getConnection()->quote($column->getComment()); + } + + if ($column->getUpdate()) { + $def .= ' ON UPDATE ' . $column->getUpdate(); + } + + return $def; + } + + /** + * Gets the MySQL Index Definition for an Index object. + * + * @param Index $index Index + * @return string + */ + protected function getIndexSqlDefinition(Index $index) + { + $def = ''; + $limit = ''; + if ($index->getLimit()) { + $limit = '(' . $index->getLimit() . ')'; + } + + if ($index->getType() == Index::UNIQUE) { + $def .= ' UNIQUE'; + } + + if ($index->getType() == Index::FULLTEXT) { + $def .= ' FULLTEXT'; + } + + $def .= ' KEY'; + + if (is_string($index->getName())) { + $def .= ' `' . $index->getName() . '`'; + } + + $def .= ' (`' . implode('`,`', $index->getColumns()) . '`' . $limit . ')'; + + return $def; + } + + /** + * Gets the MySQL Foreign Key Definition for an ForeignKey object. + * + * @param ForeignKey $foreignKey + * @return string + */ + protected function getForeignKeySqlDefinition(ForeignKey $foreignKey) + { + $def = ''; + if ($foreignKey->getConstraint()) { + $def .= ' CONSTRAINT ' . $this->quoteColumnName($foreignKey->getConstraint()); + } + $columnNames = array(); + foreach ($foreignKey->getColumns() as $column) { + $columnNames[] = $this->quoteColumnName($column); + } + $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')'; + $refColumnNames = array(); + foreach ($foreignKey->getReferencedColumns() as $column) { + $refColumnNames[] = $this->quoteColumnName($column); + } + $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')'; + if ($foreignKey->getOnDelete()) { + $def .= ' ON DELETE ' . $foreignKey->getOnDelete(); + } + if ($foreignKey->getOnUpdate()) { + $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate(); + } + return $def; + } + + /** + * Describes a database table. This is a MySQL adapter specific method. + * + * @param string $tableName Table name + * @return array + */ + public function describeTable($tableName) + { + $options = $this->getOptions(); + + // mysql specific + $sql = sprintf( + "SELECT * + FROM information_schema.tables + WHERE table_schema = '%s' + AND table_name = '%s'", + $options['name'], + $tableName + ); + + return $this->fetchRow($sql); + } + + /** + * Returns MySQL column types (inherited and MySQL specified). + * @return array + */ + public function getColumnTypes() + { + return array_merge(parent::getColumnTypes(), array('enum', 'set', 'year', 'json')); + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/PdoAdapter.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/PdoAdapter.php new file mode 100644 index 0000000..9924c3a --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/PdoAdapter.php @@ -0,0 +1,587 @@ + + */ +abstract class PdoAdapter implements AdapterInterface +{ + /** + * @var array + */ + protected $options = array(); + + /** + * @var InputInterface + */ + protected $input; + + /** + * @var OutputInterface + */ + protected $output; + + /** + * @var string + */ + protected $schemaTableName = 'migrations'; + + /** + * @var \PDO + */ + protected $connection; + + /** + * @var float + */ + protected $commandStartTime; + + /** + * Class Constructor. + * + * @param array $options Options + * @param InputInterface $input Input Interface + * @param OutputInterface $output Output Interface + */ + public function __construct(array $options, InputInterface $input = null, OutputInterface $output = null) + { + $this->setOptions($options); + if (null !== $input) { + $this->setInput($input); + } + if (null !== $output) { + $this->setOutput($output); + } + } + + /** + * {@inheritdoc} + */ + public function setOptions(array $options) + { + $this->options = $options; + + if (isset($options['default_migration_table'])) { + $this->setSchemaTableName($options['default_migration_table']); + } + + if (isset($options['connection'])) { + $this->setConnection($options['connection']); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOptions() + { + return $this->options; + } + + /** + * {@inheritdoc} + */ + public function hasOption($name) + { + return isset($this->options[$name]); + } + + /** + * {@inheritdoc} + */ + public function getOption($name) + { + if (!$this->hasOption($name)) { + return; + } + return $this->options[$name]; + } + + /** + * {@inheritdoc} + */ + public function setInput(InputInterface $input) + { + $this->input = $input; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getInput() + { + return $this->input; + } + + /** + * {@inheritdoc} + */ + public function setOutput(OutputInterface $output) + { + $this->output = $output; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOutput() + { + if (null === $this->output) { + $output = new OutputInterface('nothing'); + $this->setOutput($output); + } + return $this->output; + } + + /** + * Sets the schema table name. + * + * @param string $schemaTableName Schema Table Name + * @return PdoAdapter + */ + public function setSchemaTableName($schemaTableName) + { + $this->schemaTableName = $schemaTableName; + return $this; + } + + /** + * Gets the schema table name. + * + * @return string + */ + public function getSchemaTableName() + { + return $this->schemaTableName; + } + + /** + * Sets the database connection. + * + * @param \PDO $connection Connection + * @return AdapterInterface + */ + public function setConnection(\PDO $connection) + { + $this->connection = $connection; + + // Create the schema table if it doesn't already exist + if (!$this->hasSchemaTable()) { + $this->createSchemaTable(); + } else { + $table = new Table($this->getSchemaTableName(), array(), $this); + if (!$table->hasColumn('migration_name')) { + $table + ->addColumn('migration_name', 'string', + array('limit' => 100, 'after' => 'version', 'default' => null, 'null' => true) + ) + ->save(); + } + if (!$table->hasColumn('breakpoint')) { + $table + ->addColumn('breakpoint', 'boolean', array('default' => false)) + ->save(); + } + } + + return $this; + } + + /** + * Gets the database connection + * + * @return \PDO + */ + public function getConnection() + { + if (null === $this->connection) { + $this->connect(); + } + return $this->connection; + } + + /** + * Sets the command start time + * + * @param int $time + * @return AdapterInterface + */ + public function setCommandStartTime($time) + { + $this->commandStartTime = $time; + return $this; + } + + /** + * Gets the command start time + * + * @return int + */ + public function getCommandStartTime() + { + return $this->commandStartTime; + } + + /** + * Start timing a command. + * + * @return void + */ + public function startCommandTimer() + { + $this->setCommandStartTime(microtime(true)); + } + + /** + * Stop timing the current command and write the elapsed time to the + * output. + * + * @return void + */ + public function endCommandTimer() + { + $end = microtime(true); + if (OutputInterface::VERBOSITY_VERBOSE <= $this->getOutput()->getVerbosity()) { + $this->getOutput()->writeln(' -> ' . sprintf('%.4fs', $end - $this->getCommandStartTime())); + } + } + + /** + * Write a Phinx command to the output. + * + * @param string $command Command Name + * @param array $args Command Args + * @return void + */ + public function writeCommand($command, $args = array()) + { + if (OutputInterface::VERBOSITY_VERBOSE <= $this->getOutput()->getVerbosity()) { + if (count($args)) { + $outArr = array(); + foreach ($args as $arg) { + if (is_array($arg)) { + $arg = array_map(function ($value) { + return '\'' . $value . '\''; + }, $arg); + $outArr[] = '[' . implode(', ', $arg) . ']'; + continue; + } + + $outArr[] = '\'' . $arg . '\''; + } + $this->getOutput()->writeln(' -- ' . $command . '(' . implode(', ', $outArr) . ')'); + return; + } + $this->getOutput()->writeln(' -- ' . $command); + } + } + + /** + * {@inheritdoc} + */ + public function connect() + { + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + } + + /** + * {@inheritdoc} + */ + public function execute($sql) + { + return $this->getConnection()->exec($sql); + } + + /** + * {@inheritdoc} + */ + public function query($sql) + { + return $this->getConnection()->query($sql); + } + + /** + * {@inheritdoc} + */ + public function fetchRow($sql) + { + $result = $this->query($sql); + return $result->fetch(); + } + + /** + * {@inheritdoc} + */ + public function fetchAll($sql) + { + $rows = array(); + $result = $this->query($sql); + while ($row = $result->fetch()) { + $rows[] = $row; + } + return $rows; + } + + /** + * {@inheritdoc} + */ + public function insert(Table $table, $row) + { + $this->startCommandTimer(); + $this->writeCommand('insert', array($table->getName())); + + $sql = sprintf( + "INSERT INTO %s ", + $this->quoteTableName($table->getName()) + ); + + $columns = array_keys($row); + $sql .= "(". implode(', ', array_map(array($this, 'quoteColumnName'), $columns)) . ")"; + $sql .= " VALUES (" . implode(', ', array_fill(0, count($columns), '?')) . ")"; + + $stmt = $this->getConnection()->prepare($sql); + $stmt->execute(array_values($row)); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getVersions() + { + $rows = $this->getVersionLog(); + + return array_keys($rows); + } + + /** + * {@inheritdoc} + */ + public function getVersionLog() + { + $result = array(); + $rows = $this->fetchAll(sprintf('SELECT * FROM %s ORDER BY version ASC', $this->getSchemaTableName())); + foreach ($rows as $version) { + $result[$version['version']] = $version; + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function migrated(MigrationInterface $migration, $direction, $startTime, $endTime) + { + if (strcasecmp($direction, MigrationInterface::UP) === 0) { + // up + $sql = sprintf( + "INSERT INTO %s (%s, %s, %s, %s, %s) VALUES ('%s', '%s', '%s', '%s', %s);", + $this->getSchemaTableName(), + $this->quoteColumnName('version'), + $this->quoteColumnName('migration_name'), + $this->quoteColumnName('start_time'), + $this->quoteColumnName('end_time'), + $this->quoteColumnName('breakpoint'), + $migration->getVersion(), + substr($migration->getName(), 0, 100), + $startTime, + $endTime, + $this->castToBool(false) + ); + + $this->query($sql); + } else { + // down + $sql = sprintf( + "DELETE FROM %s WHERE %s = '%s'", + $this->getSchemaTableName(), + $this->quoteColumnName('version'), + $migration->getVersion() + ); + + $this->query($sql); + } + + return $this; + } + + /** + * @inheritDoc + */ + public function toggleBreakpoint(MigrationInterface $migration) + { + $this->query( + sprintf( + 'UPDATE %1$s SET %2$s = CASE %2$s WHEN %3$s THEN %4$s ELSE %3$s END WHERE %5$s = \'%6$s\';', + $this->getSchemaTableName(), + $this->quoteColumnName('breakpoint'), + $this->castToBool(true), + $this->castToBool(false), + $this->quoteColumnName('version'), + $migration->getVersion() + ) + ); + + return $this; + } + + /** + * @inheritDoc + */ + public function resetAllBreakpoints() + { + return $this->execute( + sprintf( + 'UPDATE %1$s SET %2$s = %3$s WHERE %2$s <> %3$s;', + $this->getSchemaTableName(), + $this->quoteColumnName('breakpoint'), + $this->castToBool(false) + ) + ); + } + + /** + * {@inheritdoc} + */ + public function hasSchemaTable() + { + return $this->hasTable($this->getSchemaTableName()); + } + + /** + * {@inheritdoc} + */ + public function createSchemaTable() + { + try { + $options = array( + 'id' => false, + 'primary_key' => 'version' + ); + + $table = new Table($this->getSchemaTableName(), $options, $this); + + if ($this->getConnection()->getAttribute(\PDO::ATTR_DRIVER_NAME) === 'mysql' + && version_compare($this->getConnection()->getAttribute(\PDO::ATTR_SERVER_VERSION), '5.6.0', '>=')) { + $table->addColumn('version', 'biginteger', array('limit' => 14)) + ->addColumn('migration_name', 'string', array('limit' => 100, 'default' => null, 'null' => true)) + ->addColumn('start_time', 'timestamp', array('default' => 'CURRENT_TIMESTAMP')) + ->addColumn('end_time', 'timestamp', array('default' => 'CURRENT_TIMESTAMP')) + ->addColumn('breakpoint', 'boolean', array('default' => false)) + ->save(); + } else { + $table->addColumn('version', 'biginteger') + ->addColumn('migration_name', 'string', array('limit' => 100, 'default' => null, 'null' => true)) + ->addColumn('start_time', 'timestamp') + ->addColumn('end_time', 'timestamp') + ->addColumn('breakpoint', 'boolean', array('default' => false)) + ->save(); + } + } catch (\Exception $exception) { + throw new \InvalidArgumentException('There was a problem creating the schema table: ' . $exception->getMessage()); + } + } + + /** + * {@inheritdoc} + */ + public function getAdapterType() + { + return $this->getOption('adapter'); + } + + /** + * {@inheritdoc} + */ + public function getColumnTypes() + { + return array( + 'string', + 'char', + 'text', + 'integer', + 'biginteger', + 'float', + 'decimal', + 'datetime', + 'timestamp', + 'time', + 'date', + 'blob', + 'binary', + 'varbinary', + 'boolean', + 'uuid', + // Geospatial data types + 'geometry', + 'point', + 'linestring', + 'polygon', + ); + } + + /** + * {@inheritdoc} + */ + public function isValidColumnType(Column $column) { + return in_array($column->getType(), $this->getColumnTypes()); + } + + /** + * Cast a value to a boolean appropriate for the adapter. + * + * @param mixed $value The value to be cast + * + * @return mixed + */ + public function castToBool($value) + { + return (bool) $value ? 1 : 0; + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php new file mode 100644 index 0000000..3c506bd --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php @@ -0,0 +1,1182 @@ +connection) { + if (!class_exists('PDO') || !in_array('pgsql', \PDO::getAvailableDrivers(), true)) { + // @codeCoverageIgnoreStart + throw new \RuntimeException('You need to enable the PDO_Pgsql extension for Phinx to run properly.'); + // @codeCoverageIgnoreEnd + } + + $db = null; + $options = $this->getOptions(); + + // if port is specified use it, otherwise use the PostgreSQL default + if (isset($options['port'])) { + $dsn = 'pgsql:host=' . $options['host'] . ';port=' . $options['port'] . ';dbname=' . $options['name']; + } else { + $dsn = 'pgsql:host=' . $options['host'] . ';dbname=' . $options['name']; + } + + try { + $db = new \PDO($dsn, $options['user'], $options['pass'], array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION)); + } catch (\PDOException $exception) { + throw new \InvalidArgumentException(sprintf( + 'There was a problem connecting to the database: %s', + $exception->getMessage() + )); + } + + $this->setConnection($db); + } + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + $this->connection = null; + } + + /** + * {@inheritdoc} + */ + public function hasTransactions() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + $this->execute('BEGIN'); + } + + /** + * {@inheritdoc} + */ + public function commitTransaction() + { + $this->execute('COMMIT'); + } + + /** + * {@inheritdoc} + */ + public function rollbackTransaction() + { + $this->execute('ROLLBACK'); + } + + /** + * Quotes a schema name for use in a query. + * + * @param string $schemaName Schema Name + * @return string + */ + public function quoteSchemaName($schemaName) + { + return $this->quoteColumnName($schemaName); + } + + /** + * {@inheritdoc} + */ + public function quoteTableName($tableName) + { + return $this->quoteSchemaName($this->getSchemaName()) . '.' . $this->quoteColumnName($tableName); + } + + /** + * {@inheritdoc} + */ + public function quoteColumnName($columnName) + { + return '"'. $columnName . '"'; + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + $result = $this->getConnection()->query( + sprintf( + 'SELECT * + FROM information_schema.tables + WHERE table_schema = %s + AND lower(table_name) = lower(%s)', + $this->getConnection()->quote($this->getSchemaName()), + $this->getConnection()->quote($tableName) + ) + ); + + return $result->rowCount() === 1; + } + + /** + * {@inheritdoc} + */ + public function createTable(Table $table) + { + $this->startCommandTimer(); + $options = $table->getOptions(); + + // Add the default primary key + $columns = $table->getPendingColumns(); + if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) { + $column = new Column(); + $column->setName('id') + ->setType('integer') + ->setIdentity(true); + + array_unshift($columns, $column); + $options['primary_key'] = 'id'; + + } elseif (isset($options['id']) && is_string($options['id'])) { + // Handle id => "field_name" to support AUTO_INCREMENT + $column = new Column(); + $column->setName($options['id']) + ->setType('integer') + ->setIdentity(true); + + array_unshift($columns, $column); + $options['primary_key'] = $options['id']; + } + + // TODO - process table options like collation etc + $sql = 'CREATE TABLE '; + $sql .= $this->quoteTableName($table->getName()) . ' ('; + + $this->columnsWithComments = array(); + foreach ($columns as $column) { + $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', '; + + // set column comments, if needed + if ($column->getComment()) { + $this->columnsWithComments[] = $column; + } + } + + // set the primary key(s) + if (isset($options['primary_key'])) { + $sql = rtrim($sql); + $sql .= sprintf(' CONSTRAINT %s_pkey PRIMARY KEY (', $table->getName()); + if (is_string($options['primary_key'])) { // handle primary_key => 'id' + $sql .= $this->quoteColumnName($options['primary_key']); + } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id') + // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the anonymous function, + // but for now just hard-code the adapter quotes + $sql .= implode( + ',', + array_map( + function ($v) { + return '"' . $v . '"'; + }, + $options['primary_key'] + ) + ); + } + $sql .= ')'; + } else { + $sql = substr(rtrim($sql), 0, -1); // no primary keys + } + + // set the foreign keys + $foreignKeys = $table->getForeignKeys(); + if (!empty($foreignKeys)) { + foreach ($foreignKeys as $foreignKey) { + $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey, $table->getName()); + } + } + + $sql .= ');'; + + // process column comments + if (!empty($this->columnsWithComments)) { + foreach ($this->columnsWithComments as $column) { + $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName()); + } + } + + // set the indexes + $indexes = $table->getIndexes(); + if (!empty($indexes)) { + foreach ($indexes as $index) { + $sql .= $this->getIndexSqlDefinition($index, $table->getName()); + } + } + + // execute the sql + $this->writeCommand('createTable', array($table->getName())); + $this->execute($sql); + + // process table comments + if (isset($options['comment'])) { + $sql = sprintf( + 'COMMENT ON TABLE %s IS %s', + $this->quoteTableName($table->getName()), + $this->getConnection()->quote($options['comment']) + ); + $this->execute($sql); + } + + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function renameTable($tableName, $newTableName) + { + $this->startCommandTimer(); + $this->writeCommand('renameTable', array($tableName, $newTableName)); + $sql = sprintf( + 'ALTER TABLE %s RENAME TO %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($newTableName) + ); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropTable($tableName) + { + $this->startCommandTimer(); + $this->writeCommand('dropTable', array($tableName)); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getColumns($tableName) + { + $columns = array(); + $sql = sprintf( + "SELECT column_name, data_type, is_identity, is_nullable, + column_default, character_maximum_length, numeric_precision, numeric_scale + FROM information_schema.columns + WHERE table_name ='%s'", + $tableName + ); + $columnsInfo = $this->fetchAll($sql); + + foreach ($columnsInfo as $columnInfo) { + $column = new Column(); + $column->setName($columnInfo['column_name']) + ->setType($this->getPhinxType($columnInfo['data_type'])) + ->setNull($columnInfo['is_nullable'] === 'YES') + ->setDefault($columnInfo['column_default']) + ->setIdentity($columnInfo['is_identity'] === 'YES') + ->setPrecision($columnInfo['numeric_precision']) + ->setScale($columnInfo['numeric_scale']); + + if (preg_match('/\bwith time zone$/', $columnInfo['data_type'])) { + $column->setTimezone(true); + } + + if (isset($columnInfo['character_maximum_length'])) { + $column->setLimit($columnInfo['character_maximum_length']); + } + $columns[] = $column; + } + return $columns; + } + + /** + * {@inheritdoc} + */ + public function hasColumn($tableName, $columnName, $options = array()) + { + $sql = sprintf("SELECT count(*) + FROM information_schema.columns + WHERE table_schema = '%s' AND table_name = '%s' AND column_name = '%s'", + $this->getSchemaName(), + $tableName, + $columnName + ); + + $result = $this->fetchRow($sql); + return $result['count'] > 0; + } + + /** + * {@inheritdoc} + */ + public function addColumn(Table $table, Column $column) + { + $this->startCommandTimer(); + $this->writeCommand('addColumn', array($table->getName(), $column->getName(), $column->getType())); + $sql = sprintf( + 'ALTER TABLE %s ADD %s %s', + $this->quoteTableName($table->getName()), + $this->quoteColumnName($column->getName()), + $this->getColumnSqlDefinition($column) + ); + + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function renameColumn($tableName, $columnName, $newColumnName) + { + $this->startCommandTimer(); + $sql = sprintf( + "SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END AS column_exists + FROM information_schema.columns + WHERE table_name ='%s' AND column_name = '%s'", + $tableName, + $columnName + ); + $result = $this->fetchRow($sql); + if (!(bool) $result['column_exists']) { + throw new \InvalidArgumentException("The specified column does not exist: $columnName"); + } + $this->writeCommand('renameColumn', array($tableName, $columnName, $newColumnName)); + $this->execute( + sprintf( + 'ALTER TABLE %s RENAME COLUMN %s TO %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName), + $newColumnName + ) + ); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function changeColumn($tableName, $columnName, Column $newColumn) + { + // TODO - is it possible to merge these 3 queries into less? + $this->startCommandTimer(); + $this->writeCommand('changeColumn', array($tableName, $columnName, $newColumn->getType())); + // change data type + $sql = sprintf( + 'ALTER TABLE %s ALTER COLUMN %s TYPE %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName), + $this->getColumnSqlDefinition($newColumn) + ); + //NULL and DEFAULT cannot be set while changing column type + $sql = preg_replace('/ NOT NULL/', '', $sql); + $sql = preg_replace('/ NULL/', '', $sql); + //If it is set, DEFAULT is the last definition + $sql = preg_replace('/DEFAULT .*/', '', $sql); + $this->execute($sql); + // process null + $sql = sprintf( + 'ALTER TABLE %s ALTER COLUMN %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName) + ); + if ($newColumn->isNull()) { + $sql .= ' DROP NOT NULL'; + } else { + $sql .= ' SET NOT NULL'; + } + $this->execute($sql); + if (!is_null($newColumn->getDefault())) { + //change default + $this->execute( + sprintf( + 'ALTER TABLE %s ALTER COLUMN %s SET %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName), + $this->getDefaultValueDefinition($newColumn->getDefault()) + ) + ); + } + else { + //drop default + $this->execute( + sprintf( + 'ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName) + ) + ); + } + // rename column + if ($columnName !== $newColumn->getName()) { + $this->execute( + sprintf( + 'ALTER TABLE %s RENAME COLUMN %s TO %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName), + $this->quoteColumnName($newColumn->getName()) + ) + ); + } + + // change column comment if needed + if ($newColumn->getComment()) { + $sql = $this->getColumnCommentSqlDefinition($newColumn, $tableName); + $this->execute($sql); + } + + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropColumn($tableName, $columnName) + { + $this->startCommandTimer(); + $this->writeCommand('dropColumn', array($tableName, $columnName)); + $this->execute( + sprintf( + 'ALTER TABLE %s DROP COLUMN %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName) + ) + ); + $this->endCommandTimer(); + } + + /** + * Get an array of indexes from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + protected function getIndexes($tableName) + { + $indexes = array(); + $sql = "SELECT + i.relname AS index_name, + a.attname AS column_name + FROM + pg_class t, + pg_class i, + pg_index ix, + pg_attribute a + WHERE + t.oid = ix.indrelid + AND i.oid = ix.indexrelid + AND a.attrelid = t.oid + AND a.attnum = ANY(ix.indkey) + AND t.relkind = 'r' + AND t.relname = '$tableName' + ORDER BY + t.relname, + i.relname;"; + $rows = $this->fetchAll($sql); + foreach ($rows as $row) { + if (!isset($indexes[$row['index_name']])) { + $indexes[$row['index_name']] = array('columns' => array()); + } + $indexes[$row['index_name']]['columns'][] = strtolower($row['column_name']); + } + return $indexes; + } + + /** + * {@inheritdoc} + */ + public function hasIndex($tableName, $columns) + { + if (is_string($columns)) { + $columns = array($columns); + } + $columns = array_map('strtolower', $columns); + $indexes = $this->getIndexes($tableName); + foreach ($indexes as $index) { + if (array_diff($index['columns'], $columns) === array_diff($columns, $index['columns'])) { + return true; + } + } + return false; + } + + /** + * {@inheritdoc} + */ + public function hasIndexByName($tableName, $indexName) + { + $indexes = $this->getIndexes($tableName); + foreach ($indexes as $name => $index) { + if ($name === $indexName) { + return true; + } + } + return false; + } + + /** + * {@inheritdoc} + */ + public function addIndex(Table $table, Index $index) + { + $this->startCommandTimer(); + $this->writeCommand('addIndex', array($table->getName(), $index->getColumns())); + $sql = $this->getIndexSqlDefinition($index, $table->getName()); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropIndex($tableName, $columns) + { + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $this->writeCommand('dropIndex', array($tableName, $columns)); + $indexes = $this->getIndexes($tableName); + $columns = array_map('strtolower', $columns); + + foreach ($indexes as $indexName => $index) { + $a = array_diff($columns, $index['columns']); + if (empty($a)) { + $this->execute( + sprintf( + 'DROP INDEX IF EXISTS %s', + $this->quoteColumnName($indexName) + ) + ); + $this->endCommandTimer(); + return; + } + } + } + + /** + * {@inheritdoc} + */ + public function dropIndexByName($tableName, $indexName) + { + $this->startCommandTimer(); + $this->writeCommand('dropIndexByName', array($tableName, $indexName)); + $sql = sprintf( + 'DROP INDEX IF EXISTS %s', + $indexName + ); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function hasForeignKey($tableName, $columns, $constraint = null) + { + if (is_string($columns)) { + $columns = array($columns); // str to array + } + $foreignKeys = $this->getForeignKeys($tableName); + if ($constraint) { + if (isset($foreignKeys[$constraint])) { + return !empty($foreignKeys[$constraint]); + } + return false; + } else { + foreach ($foreignKeys as $key) { + $a = array_diff($columns, $key['columns']); + if (empty($a)) { + return true; + } + } + return false; + } + } + + /** + * Get an array of foreign keys from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + protected function getForeignKeys($tableName) + { + $foreignKeys = array(); + $rows = $this->fetchAll(sprintf( + "SELECT + tc.constraint_name, + tc.table_name, kcu.column_name, + ccu.table_name AS referenced_table_name, + ccu.column_name AS referenced_column_name + FROM + information_schema.table_constraints AS tc + JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name + JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name + WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s' + ORDER BY kcu.position_in_unique_constraint", + $tableName + )); + foreach ($rows as $row) { + $foreignKeys[$row['constraint_name']]['table'] = $row['table_name']; + $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name']; + $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name']; + $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name']; + } + return $foreignKeys; + } + + /** + * {@inheritdoc} + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey) + { + $this->startCommandTimer(); + $this->writeCommand('addForeignKey', array($table->getName(), $foreignKey->getColumns())); + $sql = sprintf( + 'ALTER TABLE %s ADD %s', + $this->quoteTableName($table->getName()), + $this->getForeignKeySqlDefinition($foreignKey, $table->getName()) + ); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($tableName, $columns, $constraint = null) + { + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + $this->writeCommand('dropForeignKey', array($tableName, $columns)); + + if ($constraint) { + $this->execute( + sprintf( + 'ALTER TABLE %s DROP CONSTRAINT %s', + $this->quoteTableName($tableName), + $constraint + ) + ); + } else { + foreach ($columns as $column) { + $rows = $this->fetchAll(sprintf( + "SELECT CONSTRAINT_NAME + FROM information_schema.KEY_COLUMN_USAGE + WHERE TABLE_SCHEMA = CURRENT_SCHEMA() + AND TABLE_NAME IS NOT NULL + AND TABLE_NAME = '%s' + AND COLUMN_NAME = '%s' + ORDER BY POSITION_IN_UNIQUE_CONSTRAINT", + $tableName, + $column + )); + + foreach ($rows as $row) { + $this->dropForeignKey($tableName, $columns, $row['constraint_name']); + } + } + } + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getSqlType($type, $limit = null) + { + switch ($type) { + case static::PHINX_TYPE_INTEGER: + if ($limit && $limit == static::INT_SMALL) { + return array( + 'name' => 'smallint', + 'limit' => static::INT_SMALL + ); + } + return array('name' => $type); + case static::PHINX_TYPE_TEXT: + case static::PHINX_TYPE_TIME: + case static::PHINX_TYPE_DATE: + case static::PHINX_TYPE_BOOLEAN: + case static::PHINX_TYPE_JSON: + case static::PHINX_TYPE_JSONB: + case static::PHINX_TYPE_UUID: + return array('name' => $type); + case static::PHINX_TYPE_DECIMAL: + return array('name' => $type, 'precision' => 18, 'scale' => 0); + case static::PHINX_TYPE_STRING: + return array('name' => 'character varying', 'limit' => 255); + case static::PHINX_TYPE_CHAR: + return array('name' => 'character', 'limit' => 255); + case static::PHINX_TYPE_BIG_INTEGER: + return array('name' => 'bigint'); + case static::PHINX_TYPE_FLOAT: + return array('name' => 'real'); + case static::PHINX_TYPE_DATETIME: + case static::PHINX_TYPE_TIMESTAMP: + return array('name' => 'timestamp'); + case static::PHINX_TYPE_BLOB: + case static::PHINX_TYPE_BINARY: + return array('name' => 'bytea'); + // Geospatial database types + // Spatial storage in Postgres is done via the PostGIS extension, + // which enables the use of the "geography" type in combination + // with SRID 4326. + case static::PHINX_TYPE_GEOMETRY: + return array('name' => 'geography', 'geometry', 4326); + break; + case static::PHINX_TYPE_POINT: + return array('name' => 'geography', 'point', 4326); + break; + case static::PHINX_TYPE_LINESTRING: + return array('name' => 'geography', 'linestring', 4326); + break; + case static::PHINX_TYPE_POLYGON: + return array('name' => 'geography', 'polygon', 4326); + break; + default: + if ($this->isArrayType($type)) { + return array('name' => $type); + } + // Return array type + throw new \RuntimeException('The type: "' . $type . '" is not supported'); + } + } + + /** + * Returns Phinx type by SQL type + * + * @param string $sqlType SQL type + * @returns string Phinx type + */ + public function getPhinxType($sqlType) + { + switch ($sqlType) { + case 'character varying': + case 'varchar': + return static::PHINX_TYPE_STRING; + case 'character': + case 'char': + return static::PHINX_TYPE_CHAR; + case 'text': + return static::PHINX_TYPE_TEXT; + case 'json': + return static::PHINX_TYPE_JSON; + case 'jsonb': + return static::PHINX_TYPE_JSONB; + case 'smallint': + return array( + 'name' => 'smallint', + 'limit' => static::INT_SMALL + ); + case 'int': + case 'int4': + case 'integer': + return static::PHINX_TYPE_INTEGER; + case 'decimal': + case 'numeric': + return static::PHINX_TYPE_DECIMAL; + case 'bigint': + case 'int8': + return static::PHINX_TYPE_BIG_INTEGER; + case 'real': + case 'float4': + return static::PHINX_TYPE_FLOAT; + case 'bytea': + return static::PHINX_TYPE_BINARY; + break; + case 'time': + case 'timetz': + case 'time with time zone': + case 'time without time zone': + return static::PHINX_TYPE_TIME; + case 'date': + return static::PHINX_TYPE_DATE; + case 'timestamp': + case 'timestamptz': + case 'timestamp with time zone': + case 'timestamp without time zone': + return static::PHINX_TYPE_DATETIME; + case 'bool': + case 'boolean': + return static::PHINX_TYPE_BOOLEAN; + case 'uuid': + return static::PHINX_TYPE_UUID; + default: + throw new \RuntimeException('The PostgreSQL type: "' . $sqlType . '" is not supported'); + } + } + + /** + * {@inheritdoc} + */ + public function createDatabase($name, $options = array()) + { + $this->startCommandTimer(); + $this->writeCommand('createDatabase', array($name)); + $charset = isset($options['charset']) ? $options['charset'] : 'utf8'; + $this->execute(sprintf("CREATE DATABASE %s WITH ENCODING = '%s'", $name, $charset)); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function hasDatabase($databaseName) + { + $sql = sprintf("SELECT count(*) FROM pg_database WHERE datname = '%s'", $databaseName); + $result = $this->fetchRow($sql); + return $result['count'] > 0; + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($name) + { + $this->startCommandTimer(); + $this->writeCommand('dropDatabase', array($name)); + $this->disconnect(); + $this->execute(sprintf('DROP DATABASE IF EXISTS %s', $name)); + $this->connect(); + $this->endCommandTimer(); + } + + /** + * Get the defintion for a `DEFAULT` statement. + * + * @param mixed $default + * @return string + */ + protected function getDefaultValueDefinition($default) + { + if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) { + $default = $this->getConnection()->quote($default); + } elseif (is_bool($default)) { + $default = $this->castToBool($default); + } + return isset($default) ? 'DEFAULT ' . $default : ''; + } + + /** + * Gets the PostgreSQL Column Definition for a Column object. + * + * @param Column $column Column + * @return string + */ + protected function getColumnSqlDefinition(Column $column) + { + $buffer = array(); + if ($column->isIdentity()) { + $buffer[] = $column->getType() == 'biginteger' ? 'BIGSERIAL' : 'SERIAL'; + } else { + $sqlType = $this->getSqlType($column->getType(), $column->getLimit()); + $buffer[] = strtoupper($sqlType['name']); + // integers cant have limits in postgres + if (static::PHINX_TYPE_DECIMAL === $sqlType['name'] && ($column->getPrecision() || $column->getScale())) { + $buffer[] = sprintf( + '(%s, %s)', + $column->getPrecision() ? $column->getPrecision() : $sqlType['precision'], + $column->getScale() ? $column->getScale() : $sqlType['scale'] + ); + } elseif (!in_array($sqlType['name'], array('integer', 'smallint'))) { + if ($column->getLimit() || isset($sqlType['limit'])) { + $buffer[] = sprintf('(%s)', $column->getLimit() ? $column->getLimit() : $sqlType['limit']); + } + } + + $timeTypes = array( + 'time', + 'timestamp', + ); + if (in_array($sqlType['name'], $timeTypes) && $column->isTimezone()) { + $buffer[] = strtoupper('with time zone'); + } + } + + $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL'; + + if (!is_null($column->getDefault())) { + $buffer[] = $this->getDefaultValueDefinition($column->getDefault()); + } + + return implode(' ', $buffer); + } + + /** + * Gets the PostgreSQL Column Comment Defininition for a column object. + * + * @param Column $column Column + * @param string $tableName Table name + * @return string + */ + protected function getColumnCommentSqlDefinition(Column $column, $tableName) + { + // passing 'null' is to remove column comment + $comment = (strcasecmp($column->getComment(), 'NULL') !== 0) + ? $this->getConnection()->quote($column->getComment()) + : 'NULL'; + + return sprintf( + 'COMMENT ON COLUMN %s.%s IS %s;', + $tableName, + $column->getName(), + $comment + ); + } + + /** + * Gets the PostgreSQL Index Definition for an Index object. + * + * @param Index $index Index + * @param string $tableName Table name + * @return string + */ + protected function getIndexSqlDefinition(Index $index, $tableName) + { + if (is_string($index->getName())) { + $indexName = $index->getName(); + } else { + $columnNames = $index->getColumns(); + if (is_string($columnNames)) { + $columnNames = array($columnNames); + } + $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames)); + } + $def = sprintf( + "CREATE %s INDEX %s ON %s (%s);", + ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''), + $indexName, + $this->quoteTableName($tableName), + implode(',', $index->getColumns()) + ); + return $def; + } + + /** + * Gets the MySQL Foreign Key Definition for an ForeignKey object. + * + * @param ForeignKey $foreignKey + * @param string $tableName Table name + * @return string + */ + protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, $tableName) + { + $constraintName = $foreignKey->getConstraint() ?: $tableName . '_' . implode('_', $foreignKey->getColumns()); + $def = ' CONSTRAINT "' . $constraintName . '" FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")'; + $def .= " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")'; + if ($foreignKey->getOnDelete()) { + $def .= " ON DELETE {$foreignKey->getOnDelete()}"; + } + if ($foreignKey->getOnUpdate()) { + $def .= " ON UPDATE {$foreignKey->getOnUpdate()}"; + } + return $def; + } + + /** + * {@inheritdoc} + */ + public function createSchemaTable() + { + // Create the public/custom schema if it doesn't already exist + if (false === $this->hasSchema($this->getSchemaName())) { + $this->createSchema($this->getSchemaName()); + } + + $this->fetchAll(sprintf('SET search_path TO %s', $this->getSchemaName())); + + return parent::createSchemaTable(); + } + + /** + * Creates the specified schema. + * + * @param string $schemaName Schema Name + * @return void + */ + public function createSchema($schemaName = 'public') + { + $this->startCommandTimer(); + $this->writeCommand('addSchema', array($schemaName)); + $sql = sprintf('CREATE SCHEMA %s;', $this->quoteSchemaName($schemaName)); // from postgres 9.3 we can use "CREATE SCHEMA IF NOT EXISTS schema_name" + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * Checks to see if a schema exists. + * + * @param string $schemaName Schema Name + * @return boolean + */ + public function hasSchema($schemaName) + { + $sql = sprintf( + "SELECT count(*) + FROM pg_namespace + WHERE nspname = '%s'", + $schemaName + ); + $result = $this->fetchRow($sql); + return $result['count'] > 0; + } + + /** + * Drops the specified schema table. + * + * @param string $schemaName Schema name + * @return void + */ + public function dropSchema($schemaName) + { + $this->startCommandTimer(); + $this->writeCommand('dropSchema', array($schemaName)); + $sql = sprintf("DROP SCHEMA IF EXISTS %s CASCADE;", $this->quoteSchemaName($schemaName)); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * Drops all schemas. + * + * @return void + */ + public function dropAllSchemas() + { + $this->startCommandTimer(); + $this->writeCommand('dropAllSchemas'); + foreach ($this->getAllSchemas() as $schema) { + $this->dropSchema($schema); + } + $this->endCommandTimer(); + } + + /** + * Returns schemas. + * + * @return array + */ + public function getAllSchemas() + { + $sql = "SELECT schema_name + FROM information_schema.schemata + WHERE schema_name <> 'information_schema' AND schema_name !~ '^pg_'"; + $items = $this->fetchAll($sql); + $schemaNames = array(); + foreach ($items as $item) { + $schemaNames[] = $item['schema_name']; + } + return $schemaNames; + } + + /** + * {@inheritdoc} + */ + public function getColumnTypes() + { + return array_merge(parent::getColumnTypes(), array('json', 'jsonb')); + } + + /** + * {@inheritdoc} + */ + public function isValidColumnType(Column $column) + { + // If not a standard column type, maybe it is array type? + return (parent::isValidColumnType($column) || $this->isArrayType($column->getType())); + } + + /** + * Check if the given column is an array of a valid type. + * + * @param string $columnType + * @return bool + */ + protected function isArrayType($columnType) + { + if (!preg_match('/^([a-z]+)(?:\[\]){1,}$/', $columnType, $matches)) { + return false; + } + + $baseType = $matches[1]; + return in_array($baseType, $this->getColumnTypes()); + } + + /** + * Gets the schema name. + * + * @return string + */ + private function getSchemaName() + { + $options = $this->getOptions(); + return empty($options['schema']) ? 'public' : $options['schema']; + } + + /** + * Cast a value to a boolean appropriate for the adapter. + * + * @param mixed $value The value to be cast + * + * @return mixed + */ + public function castToBool($value) + { + return (bool) $value ? 'TRUE' : 'FALSE'; + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php new file mode 100644 index 0000000..1236031 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php @@ -0,0 +1,325 @@ + + */ +class ProxyAdapter extends AdapterWrapper +{ + /** + * @var array + */ + protected $commands; + + /** + * {@inheritdoc} + */ + public function getAdapterType() + { + return 'ProxyAdapter'; + } + /** + * {@inheritdoc} + */ + public function createTable(Table $table) + { + $this->recordCommand('createTable', array($table->getName())); + } + + /** + * {@inheritdoc} + */ + public function renameTable($tableName, $newTableName) + { + $this->recordCommand('renameTable', array($tableName, $newTableName)); + } + + /** + * {@inheritdoc} + */ + public function dropTable($tableName) + { + $this->recordCommand('dropTable', array($tableName)); + } + + /** + * {@inheritdoc} + */ + public function addColumn(Table $table, Column $column) + { + $this->recordCommand('addColumn', array($table, $column)); + } + + /** + * {@inheritdoc} + */ + public function renameColumn($tableName, $columnName, $newColumnName) + { + $this->recordCommand('renameColumn', array($tableName, $columnName, $newColumnName)); + } + + /** + * {@inheritdoc} + */ + public function changeColumn($tableName, $columnName, Column $newColumn) + { + $this->recordCommand('changeColumn', array($tableName, $columnName, $newColumn)); + } + + /** + * {@inheritdoc} + */ + public function dropColumn($tableName, $columnName) + { + $this->recordCommand('dropColumn', array($tableName, $columnName)); + } + + /** + * {@inheritdoc} + */ + public function addIndex(Table $table, Index $index) + { + $this->recordCommand('addIndex', array($table, $index)); + } + + /** + * {@inheritdoc} + */ + public function dropIndex($tableName, $columns, $options = array()) + { + $this->recordCommand('dropIndex', array($tableName, $columns, $options)); + } + + /** + * {@inheritdoc} + */ + public function dropIndexByName($tableName, $indexName) + { + $this->recordCommand('dropIndexByName', array($tableName, $indexName)); + } + + /** + * {@inheritdoc} + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey) + { + $this->recordCommand('addForeignKey', array($table, $foreignKey)); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($tableName, $columns, $constraint = null) + { + $this->recordCommand('dropForeignKey', array($columns, $constraint)); + } + + /** + * {@inheritdoc} + */ + public function createDatabase($name, $options = array()) + { + $this->recordCommand('createDatabase', array($name, $options)); + } + + /** + * Record a command for execution later. + * + * @param string $name Command Name + * @param array $arguments Command Arguments + * @return void + */ + public function recordCommand($name, $arguments) + { + $this->commands[] = array( + 'name' => $name, + 'arguments' => $arguments + ); + } + + /** + * Sets an array of recorded commands. + * + * @param array $commands Commands + * @return ProxyAdapter + */ + public function setCommands($commands) + { + $this->commands = $commands; + return $this; + } + + /** + * Gets an array of the recorded commands. + * + * @return array + */ + public function getCommands() + { + return $this->commands; + } + + /** + * Gets an array of the recorded commands in reverse. + * + * @throws IrreversibleMigrationException if a command cannot be reversed. + * @return array + */ + public function getInvertedCommands() + { + if (null === $this->getCommands()) { + return array(); + } + + $invCommands = array(); + $supportedCommands = array( + 'createTable', 'renameTable', 'addColumn', + 'renameColumn', 'addIndex', 'addForeignKey' + ); + foreach (array_reverse($this->getCommands()) as $command) { + if (!in_array($command['name'], $supportedCommands)) { + throw new IrreversibleMigrationException(sprintf( + 'Cannot reverse a "%s" command', + $command['name'] + )); + } + $invertMethod = 'invert' . ucfirst($command['name']); + $invertedCommand = $this->$invertMethod($command['arguments']); + $invCommands[] = array( + 'name' => $invertedCommand['name'], + 'arguments' => $invertedCommand['arguments'] + ); + } + + return $invCommands; + } + + /** + * Execute the recorded commands. + * + * @return void + */ + public function executeCommands() + { + $commands = $this->getCommands(); + foreach ($commands as $command) { + call_user_func_array(array($this->getAdapter(), $command['name']), $command['arguments']); + } + } + + /** + * Execute the recorded commands in reverse. + * + * @return void + */ + public function executeInvertedCommands() + { + $commands = $this->getInvertedCommands(); + foreach ($commands as $command) { + call_user_func_array(array($this->getAdapter(), $command['name']), $command['arguments']); + } + } + + /** + * Returns the reverse of a createTable command. + * + * @param array $args Method Arguments + * @return array + */ + public function invertCreateTable($args) + { + return array('name' => 'dropTable', 'arguments' => array($args[0])); + } + + /** + * Returns the reverse of a renameTable command. + * + * @param array $args Method Arguments + * @return array + */ + public function invertRenameTable($args) + { + return array('name' => 'renameTable', 'arguments' => array($args[1], $args[0])); + } + + /** + * Returns the reverse of a addColumn command. + * + * @param array $args Method Arguments + * @return array + */ + public function invertAddColumn($args) + { + return array('name' => 'dropColumn', 'arguments' => array($args[0]->getName(), $args[1]->getName())); + } + + /** + * Returns the reverse of a renameColumn command. + * + * @param array $args Method Arguments + * @return array + */ + public function invertRenameColumn($args) + { + return array('name' => 'renameColumn', 'arguments' => array($args[0], $args[2], $args[1])); + } + + /** + * Returns the reverse of a addIndex command. + * + * @param array $args Method Arguments + * @return array + */ + public function invertAddIndex($args) + { + return array('name' => 'dropIndex', 'arguments' => array($args[0]->getName(), $args[1]->getColumns())); + } + + /** + * Returns the reverse of a addForeignKey command. + * + * @param array $args Method Arguments + * @return array + */ + public function invertAddForeignKey($args) + { + return array('name' => 'dropForeignKey', 'arguments' => array($args[0]->getName(), $args[1]->getColumns())); + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php new file mode 100644 index 0000000..4ff7b74 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php @@ -0,0 +1,1136 @@ + + * @author Richard McIntyre + */ +class SQLiteAdapter extends PdoAdapter implements AdapterInterface +{ + protected $definitionsWithLimits = array( + 'CHARACTER', + 'VARCHAR', + 'VARYING CHARACTER', + 'NCHAR', + 'NATIVE CHARACTER', + 'NVARCHAR' + ); + + /** + * {@inheritdoc} + */ + public function connect() + { + if (null === $this->connection) { + if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers(), true)) { + // @codeCoverageIgnoreStart + throw new \RuntimeException('You need to enable the PDO_SQLITE extension for Phinx to run properly.'); + // @codeCoverageIgnoreEnd + } + + $db = null; + $options = $this->getOptions(); + + // if port is specified use it, otherwise use the MySQL default + if (isset($options['memory'])) { + $dsn = 'sqlite::memory:'; + } else { + $dsn = 'sqlite:' . $options['name']; + if (file_exists($options['name'] . '.sqlite3')) { + $dsn = 'sqlite:' . $options['name'] . '.sqlite3'; + } + } + + try { + $db = new \PDO($dsn); + } catch (\PDOException $exception) { + throw new \InvalidArgumentException(sprintf( + 'There was a problem connecting to the database: %s', + $exception->getMessage() + )); + } + + $this->setConnection($db); + } + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + $this->connection = null; + } + + /** + * {@inheritdoc} + */ + public function hasTransactions() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + $this->execute('BEGIN TRANSACTION'); + } + + /** + * {@inheritdoc} + */ + public function commitTransaction() + { + $this->execute('COMMIT'); + } + + /** + * {@inheritdoc} + */ + public function rollbackTransaction() + { + $this->execute('ROLLBACK'); + } + + /** + * {@inheritdoc} + */ + public function quoteTableName($tableName) + { + return str_replace('.', '`.`', $this->quoteColumnName($tableName)); + } + + /** + * {@inheritdoc} + */ + public function quoteColumnName($columnName) + { + return '`' . str_replace('`', '``', $columnName) . '`'; + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + $tables = array(); + $rows = $this->fetchAll(sprintf('SELECT name FROM sqlite_master WHERE type=\'table\' AND name=\'%s\'', $tableName)); + foreach ($rows as $row) { + $tables[] = strtolower($row[0]); + } + + return in_array(strtolower($tableName), $tables); + } + + /** + * {@inheritdoc} + */ + public function createTable(Table $table) + { + $this->startCommandTimer(); + + // Add the default primary key + $columns = $table->getPendingColumns(); + $options = $table->getOptions(); + if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) { + $column = new Column(); + $column->setName('id') + ->setType('integer') + ->setIdentity(true); + + array_unshift($columns, $column); + + } elseif (isset($options['id']) && is_string($options['id'])) { + // Handle id => "field_name" to support AUTO_INCREMENT + $column = new Column(); + $column->setName($options['id']) + ->setType('integer') + ->setIdentity(true); + + array_unshift($columns, $column); + } + + $sql = 'CREATE TABLE '; + $sql .= $this->quoteTableName($table->getName()) . ' ('; + foreach ($columns as $column) { + $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', '; + } + + // set the primary key(s) + if (isset($options['primary_key'])) { + $sql = rtrim($sql); + $sql .= ' PRIMARY KEY ('; + if (is_string($options['primary_key'])) { // handle primary_key => 'id' + $sql .= $this->quoteColumnName($options['primary_key']); + } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id') + // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the anonymous function, + // but for now just hard-code the adapter quotes + $sql .= implode( + ',', + array_map( + function ($v) { + return '`' . $v . '`'; + }, + $options['primary_key'] + ) + ); + } + $sql .= ')'; + } else { + $sql = substr(rtrim($sql), 0, -1); // no primary keys + } + + // set the foreign keys + $foreignKeys = $table->getForeignKeys(); + if (!empty($foreignKeys)) { + foreach ($foreignKeys as $foreignKey) { + $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey); + } + } + + $sql = rtrim($sql) . ');'; + // execute the sql + $this->writeCommand('createTable', array($table->getName())); + $this->execute($sql); + $this->endCommandTimer(); + + foreach ($table->getIndexes() as $index) { + $this->addIndex($table, $index); + } + } + + /** + * {@inheritdoc} + */ + public function renameTable($tableName, $newTableName) + { + $this->startCommandTimer(); + $this->writeCommand('renameTable', array($tableName, $newTableName)); + $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), $this->quoteTableName($newTableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropTable($tableName) + { + $this->startCommandTimer(); + $this->writeCommand('dropTable', array($tableName)); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getColumns($tableName) + { + $columns = array(); + $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName))); + + foreach ($rows as $columnInfo) { + $column = new Column(); + $type = strtolower($columnInfo['type']); + $column->setName($columnInfo['name']) + ->setNull($columnInfo['notnull'] !== '1') + ->setDefault($columnInfo['dflt_value']); + + $phinxType = $this->getPhinxType($type); + $column->setType($phinxType['name']) + ->setLimit($phinxType['limit']); + + if ($columnInfo['pk'] == 1) { + $column->setIdentity(true); + } + + $columns[] = $column; + } + + return $columns; + } + + /** + * {@inheritdoc} + */ + public function hasColumn($tableName, $columnName) + { + $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName))); + foreach ($rows as $column) { + if (strcasecmp($column['name'], $columnName) === 0) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function addColumn(Table $table, Column $column) + { + $this->startCommandTimer(); + + $sql = sprintf( + 'ALTER TABLE %s ADD COLUMN %s %s', + $this->quoteTableName($table->getName()), + $this->quoteColumnName($column->getName()), + $this->getColumnSqlDefinition($column) + ); + + $this->writeCommand('addColumn', array($table->getName(), $column->getName(), $column->getType())); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function renameColumn($tableName, $columnName, $newColumnName) + { + $tmpTableName = 'tmp_' . $tableName; + + $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\''); + + $sql = ''; + foreach ($rows as $table) { + if ($table['tbl_name'] === $tableName) { + $sql = $table['sql']; + } + } + + $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName))); + $selectColumns = array(); + $writeColumns = array(); + foreach ($columns as $column) { + $selectName = $column['name']; + $writeName = ($selectName == $columnName) ? $newColumnName : $selectName; + $selectColumns[] = $this->quoteColumnName($selectName); + $writeColumns[] = $this->quoteColumnName($writeName); + } + + if (!in_array($this->quoteColumnName($columnName), $selectColumns)) { + throw new \InvalidArgumentException(sprintf( + 'The specified column doesn\'t exist: ' . $columnName + )); + } + + $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName)); + + $sql = str_replace( + $this->quoteColumnName($columnName), + $this->quoteColumnName($newColumnName), + $sql + ); + $this->execute($sql); + + $sql = sprintf( + 'INSERT INTO %s(%s) SELECT %s FROM %s', + $tableName, + implode(', ', $writeColumns), + implode(', ', $selectColumns), + $tmpTableName + ); + + $this->execute($sql); + + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function changeColumn($tableName, $columnName, Column $newColumn) + { + + // TODO: DRY this up.... + + $this->startCommandTimer(); + $this->writeCommand('changeColumn', array($tableName, $columnName, $newColumn->getType())); + + $tmpTableName = 'tmp_' . $tableName; + + $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\''); + + $sql = ''; + foreach ($rows as $table) { + if ($table['tbl_name'] === $tableName) { + $sql = $table['sql']; + } + } + + $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName))); + $selectColumns = array(); + $writeColumns = array(); + foreach ($columns as $column) { + $selectName = $column['name']; + $writeName = ($selectName === $columnName) ? $newColumn->getName() : $selectName; + $selectColumns[] = $this->quoteColumnName($selectName); + $writeColumns[] = $this->quoteColumnName($writeName); + } + + if (!in_array($this->quoteColumnName($columnName), $selectColumns)) { + throw new \InvalidArgumentException(sprintf( + 'The specified column doesn\'t exist: ' . $columnName + )); + } + + $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName)); + + $sql = preg_replace( + sprintf("/%s[^,]+([,)])/", $this->quoteColumnName($columnName)), + sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)), + $sql, + 1 + ); + + $this->execute($sql); + + $sql = sprintf( + 'INSERT INTO %s(%s) SELECT %s FROM %s', + $tableName, + implode(', ', $writeColumns), + implode(', ', $selectColumns), + $tmpTableName + ); + + $this->execute($sql); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropColumn($tableName, $columnName) + { + // TODO: DRY this up.... + + $this->startCommandTimer(); + $this->writeCommand('dropColumn', array($tableName, $columnName)); + + $tmpTableName = 'tmp_' . $tableName; + + $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\''); + + $sql = ''; + foreach ($rows as $table) { + if ($table['tbl_name'] === $tableName) { + $sql = $table['sql']; + } + } + + $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName))); + $columns = array(); + $columnType = null; + foreach ($rows as $row) { + if ($row['name'] !== $columnName) { + $columns[] = $row['name']; + } else { + $found = true; + $columnType = $row['type']; + } + } + + if (!isset($found)) { + throw new \InvalidArgumentException(sprintf( + 'The specified column doesn\'t exist: ' . $columnName + )); + } + + $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName)); + + $sql = preg_replace( + sprintf("/%s\s%s[^,)]*(,\s|\))/", preg_quote($this->quoteColumnName($columnName)), preg_quote($columnType)), + "", + $sql + ); + + if (substr($sql, -2) === ', ') { + $sql = substr($sql, 0, -2) . ')'; + } + + $this->execute($sql); + + $sql = sprintf( + 'INSERT INTO %s(%s) SELECT %s FROM %s', + $tableName, + implode(', ', $columns), + implode(', ', $columns), + $tmpTableName + ); + + $this->execute($sql); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName))); + $this->endCommandTimer(); + } + + /** + * Get an array of indexes from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + protected function getIndexes($tableName) + { + $indexes = array(); + $rows = $this->fetchAll(sprintf('pragma index_list(%s)', $tableName)); + + foreach ($rows as $row) { + $indexData = $this->fetchAll(sprintf('pragma index_info(%s)', $row['name'])); + if (!isset($indexes[$tableName])) { + $indexes[$tableName] = array('index' => $row['name'], 'columns' => array()); + } + foreach ($indexData as $indexItem) { + $indexes[$tableName]['columns'][] = strtolower($indexItem['name']); + } + } + return $indexes; + } + + /** + * {@inheritdoc} + */ + public function hasIndex($tableName, $columns) + { + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $columns = array_map('strtolower', $columns); + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $index) { + $a = array_diff($columns, $index['columns']); + if (empty($a)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function hasIndexByName($tableName, $indexName) + { + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $index) { + if ($indexName === $index['index']) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function addIndex(Table $table, Index $index) + { + $this->startCommandTimer(); + $this->writeCommand('addIndex', array($table->getName(), $index->getColumns())); + $indexColumnArray = array(); + foreach ($index->getColumns() as $column) { + $indexColumnArray []= sprintf('`%s` ASC', $column); + } + $indexColumns = implode(',', $indexColumnArray); + $this->execute( + sprintf( + 'CREATE %s ON %s (%s)', + $this->getIndexSqlDefinition($table, $index), + $this->quoteTableName($table->getName()), + $indexColumns + ) + ); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropIndex($tableName, $columns) + { + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $this->writeCommand('dropIndex', array($tableName, $columns)); + $indexes = $this->getIndexes($tableName); + $columns = array_map('strtolower', $columns); + + foreach ($indexes as $index) { + $a = array_diff($columns, $index['columns']); + if (empty($a)) { + $this->execute( + sprintf( + 'DROP INDEX %s', + $this->quoteColumnName($index['index']) + ) + ); + $this->endCommandTimer(); + return; + } + } + } + + /** + * {@inheritdoc} + */ + public function dropIndexByName($tableName, $indexName) + { + $this->startCommandTimer(); + + $this->writeCommand('dropIndexByName', array($tableName, $indexName)); + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $index) { + if ($indexName === $index['index']) { + $this->execute( + sprintf( + 'DROP INDEX %s', + $this->quoteColumnName($indexName) + ) + ); + $this->endCommandTimer(); + return; + } + } + } + + /** + * {@inheritdoc} + */ + public function hasForeignKey($tableName, $columns, $constraint = null) + { + if (is_string($columns)) { + $columns = array($columns); // str to array + } + $foreignKeys = $this->getForeignKeys($tableName); + + $a = array_diff($columns, $foreignKeys); + if (empty($a)) { + return true; + } + return false; + } + + /** + * Get an array of foreign keys from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + protected function getForeignKeys($tableName) + { + $foreignKeys = array(); + $rows = $this->fetchAll( + "SELECT sql, tbl_name + FROM ( + SELECT sql sql, type type, tbl_name tbl_name, name name + FROM sqlite_master + UNION ALL + SELECT sql, type, tbl_name, name + FROM sqlite_temp_master + ) + WHERE type != 'meta' + AND sql NOTNULL + AND name NOT LIKE 'sqlite_%' + ORDER BY substr(type, 2, 1), name" + ); + + foreach ($rows as $row) { + if ($row['tbl_name'] === $tableName) { + + if (strpos($row['sql'], 'REFERENCES') !== false) { + preg_match_all("/\(`([^`]*)`\) REFERENCES/", $row['sql'], $matches); + foreach ($matches[1] as $match) { + $foreignKeys[] = $match; + } + } + } + } + return $foreignKeys; + } + + /** + * {@inheritdoc} + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey) + { + // TODO: DRY this up.... + $this->startCommandTimer(); + $this->writeCommand('addForeignKey', array($table->getName(), $foreignKey->getColumns())); + $this->execute('pragma foreign_keys = ON'); + + $tmpTableName = 'tmp_' . $table->getName(); + $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\''); + + $sql = ''; + foreach ($rows as $row) { + if ($row['tbl_name'] === $table->getName()) { + $sql = $row['sql']; + } + } + + $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($table->getName()))); + $columns = array(); + foreach ($rows as $column) { + $columns[] = $this->quoteColumnName($column['name']); + } + + $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($table->getName()), $tmpTableName)); + + $sql = substr($sql, 0, -1) . ',' . $this->getForeignKeySqlDefinition($foreignKey) . ')'; + $this->execute($sql); + + $sql = sprintf( + 'INSERT INTO %s(%s) SELECT %s FROM %s', + $this->quoteTableName($table->getName()), + implode(', ', $columns), + implode(', ', $columns), + $this->quoteTableName($tmpTableName) + ); + + $this->execute($sql); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($tableName, $columns, $constraint = null) + { + // TODO: DRY this up.... + + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $this->writeCommand('dropForeignKey', array($tableName, $columns)); + + $tmpTableName = 'tmp_' . $tableName; + + $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\''); + + $sql = ''; + foreach ($rows as $table) { + if ($table['tbl_name'] === $tableName) { + $sql = $table['sql']; + } + } + + $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName))); + $replaceColumns = array(); + foreach ($rows as $row) { + if (!in_array($row['name'], $columns)) { + $replaceColumns[] = $row['name']; + } else { + $found = true; + } + } + + if (!isset($found)) { + throw new \InvalidArgumentException(sprintf( + 'The specified column doesn\'t exist: ' + )); + } + + $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), $tmpTableName)); + + foreach ($columns as $columnName) { + $search = sprintf( + "/,[^,]*\(%s(?:,`?(.*)`?)?\) REFERENCES[^,]*\([^\)]*\)[^,)]*/", + $this->quoteColumnName($columnName) + ); + $sql = preg_replace($search, '', $sql, 1); + } + + $this->execute($sql); + + $sql = sprintf( + 'INSERT INTO %s(%s) SELECT %s FROM %s', + $tableName, + implode(', ', $columns), + implode(', ', $columns), + $tmpTableName + ); + + $this->execute($sql); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName))); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function insert(Table $table, $row) + { + $this->startCommandTimer(); + $this->writeCommand('insert', array($table->getName())); + + $sql = sprintf( + "INSERT INTO %s ", + $this->quoteTableName($table->getName()) + ); + + $columns = array_keys($row); + $sql .= "(". implode(', ', array_map(array($this, 'quoteColumnName'), $columns)) . ")"; + $sql .= " VALUES "; + + $sql .= "(" . implode(', ', array_map(function ($value) { + if (is_numeric($value)) { + return $value; + } + return "'{$value}'"; + }, $row)) . ")"; + + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getSqlType($type, $limit = null) + { + switch ($type) { + case static::PHINX_TYPE_STRING: + return array('name' => 'varchar', 'limit' => 255); + break; + case static::PHINX_TYPE_CHAR: + return array('name' => 'char', 'limit' => 255); + break; + case static::PHINX_TYPE_TEXT: + return array('name' => 'text'); + break; + case static::PHINX_TYPE_INTEGER: + return array('name' => 'integer'); + break; + case static::PHINX_TYPE_BIG_INTEGER: + return array('name' => 'bigint'); + break; + case static::PHINX_TYPE_FLOAT: + return array('name' => 'float'); + break; + case static::PHINX_TYPE_DECIMAL: + return array('name' => 'decimal'); + break; + case static::PHINX_TYPE_DATETIME: + return array('name' => 'datetime'); + break; + case static::PHINX_TYPE_TIMESTAMP: + return array('name' => 'datetime'); + break; + case static::PHINX_TYPE_TIME: + return array('name' => 'time'); + break; + case static::PHINX_TYPE_DATE: + return array('name' => 'date'); + break; + case static::PHINX_TYPE_BLOB: + case static::PHINX_TYPE_BINARY: + return array('name' => 'blob'); + break; + case static::PHINX_TYPE_BOOLEAN: + return array('name' => 'boolean'); + break; + case static::PHINX_TYPE_UUID: + return array('name' => 'char', 'limit' => 36); + case static::PHINX_TYPE_ENUM: + return array('name' => 'enum'); + // Geospatial database types + // No specific data types exist in SQLite, instead all geospatial + // functionality is handled in the client. See also: SpatiaLite. + case static::PHINX_TYPE_GEOMETRY: + case static::PHINX_TYPE_POLYGON: + return array('name' => 'text'); + return; + case static::PHINX_TYPE_LINESTRING: + return array('name' => 'varchar', 'limit' => 255); + break; + case static::PHINX_TYPE_POINT: + return array('name' => 'float'); + default: + throw new \RuntimeException('The type: "' . $type . '" is not supported.'); + } + } + + /** + * Returns Phinx type by SQL type + * + * @param string $sqlTypeDef SQL type + * @returns string Phinx type + */ + public function getPhinxType($sqlTypeDef) + { + if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*$/', $sqlTypeDef, $matches)) { + throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported'); + } else { + $limit = null; + $precision = null; + $type = $matches[1]; + if (count($matches) > 2) { + $limit = $matches[3] ? $matches[3] : null; + } + if (count($matches) > 4) { + $precision = $matches[5]; + } + switch ($matches[1]) { + case 'varchar': + $type = static::PHINX_TYPE_STRING; + if ($limit === 255) { + $limit = null; + } + break; + case 'char': + $type = static::PHINX_TYPE_CHAR; + if ($limit === 255) { + $limit = null; + } + if ($limit === 36) { + $type = static::PHINX_TYPE_UUID; + } + break; + case 'int': + $type = static::PHINX_TYPE_INTEGER; + if ($limit === 11) { + $limit = null; + } + break; + case 'bigint': + if ($limit === 11) { + $limit = null; + } + $type = static::PHINX_TYPE_BIG_INTEGER; + break; + case 'blob': + $type = static::PHINX_TYPE_BINARY; + break; + } + if ($type === 'tinyint') { + if ($matches[3] === 1) { + $type = static::PHINX_TYPE_BOOLEAN; + $limit = null; + } + } + + $this->getSqlType($type); + + return array( + 'name' => $type, + 'limit' => $limit, + 'precision' => $precision + ); + } + } + + /** + * {@inheritdoc} + */ + public function createDatabase($name, $options = array()) + { + $this->startCommandTimer(); + $this->writeCommand('createDatabase', array($name)); + touch($name . '.sqlite3'); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function hasDatabase($name) + { + return is_file($name . '.sqlite3'); + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($name) + { + $this->startCommandTimer(); + $this->writeCommand('dropDatabase', array($name)); + if (file_exists($name . '.sqlite3')) { + unlink($name . '.sqlite3'); + } + $this->endCommandTimer(); + } + + /** + * Get the definition for a `DEFAULT` statement. + * + * @param mixed $default + * @return string + */ + protected function getDefaultValueDefinition($default) + { + if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) { + $default = $this->getConnection()->quote($default); + } elseif (is_bool($default)) { + $default = $this->castToBool($default); + } + return isset($default) ? ' DEFAULT ' . $default : ''; + } + + /** + * Gets the SQLite Column Definition for a Column object. + * + * @param Column $column Column + * @return string + */ + protected function getColumnSqlDefinition(Column $column) + { + $sqlType = $this->getSqlType($column->getType()); + $def = ''; + $def .= strtoupper($sqlType['name']); + if ($column->getPrecision() && $column->getScale()) { + $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')'; + } + $limitable = in_array(strtoupper($sqlType['name']), $this->definitionsWithLimits); + if (($column->getLimit() || isset($sqlType['limit'])) && $limitable) { + $def .= '(' . ($column->getLimit() ? $column->getLimit() : $sqlType['limit']) . ')'; + } + if (($values = $column->getValues()) && is_array($values)) { + $def .= " CHECK({$column->getName()} IN ('" . implode("', '", $values) . "'))"; + } + + $default = $column->getDefault(); + + $def .= ($column->isNull() || is_null($default)) ? ' NULL' : ' NOT NULL'; + $def .= $this->getDefaultValueDefinition($default); + $def .= ($column->isIdentity()) ? ' PRIMARY KEY AUTOINCREMENT' : ''; + + if ($column->getUpdate()) { + $def .= ' ON UPDATE ' . $column->getUpdate(); + } + + $def .= $this->getCommentDefinition($column); + + return $def; + } + + /** + * Gets the comment Definition for a Column object. + * + * @param Column $column Column + * @return string + */ + protected function getCommentDefinition(Column $column) + { + if ($column->getComment()) { + return ' /* ' . $column->getComment() . ' */ '; + } + return ''; + } + + /** + * Gets the SQLite Index Definition for an Index object. + * + * @param Index $index Index + * @return string + */ + protected function getIndexSqlDefinition(Table $table, Index $index) + { + if ($index->getType() === Index::UNIQUE) { + $def = 'UNIQUE INDEX'; + } else { + $def = 'INDEX'; + } + if (is_string($index->getName())) { + $indexName = $index->getName(); + } else { + $indexName = $table->getName() . '_'; + foreach ($index->getColumns() as $column) { + $indexName .= $column . '_'; + } + $indexName .= 'index'; + } + $def .= ' `' . $indexName . '`'; + return $def; + } + + /** + * {@inheritdoc} + */ + public function getColumnTypes() + { + return array_merge(parent::getColumnTypes(), array('enum')); + } + + /** + * Gets the SQLite Foreign Key Definition for an ForeignKey object. + * + * @param ForeignKey $foreignKey + * @return string + */ + protected function getForeignKeySqlDefinition(ForeignKey $foreignKey) + { + $def = ''; + if ($foreignKey->getConstraint()) { + $def .= ' CONSTRAINT ' . $this->quoteColumnName($foreignKey->getConstraint()); + } else { + $columnNames = array(); + foreach ($foreignKey->getColumns() as $column) { + $columnNames[] = $this->quoteColumnName($column); + } + $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')'; + $refColumnNames = array(); + foreach ($foreignKey->getReferencedColumns() as $column) { + $refColumnNames[] = $this->quoteColumnName($column); + } + $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')'; + if ($foreignKey->getOnDelete()) { + $def .= ' ON DELETE ' . $foreignKey->getOnDelete(); + } + if ($foreignKey->getOnUpdate()) { + $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate(); + } + } + return $def; + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php new file mode 100644 index 0000000..99f8a82 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php @@ -0,0 +1,1165 @@ + + */ +class SqlServerAdapter extends PdoAdapter implements AdapterInterface +{ + protected $schema = 'dbo'; + + protected $signedColumnTypes = array('integer' => true, 'biginteger' => true, 'float' => true, 'decimal' => true); + + /** + * {@inheritdoc} + */ + public function connect() + { + if (null === $this->connection) { + if (!class_exists('PDO') || !in_array('sqlsrv', \PDO::getAvailableDrivers(), true)) { + // try our connection via freetds (Mac/Linux) + return $this->connectDblib(); + } + + $db = null; + $options = $this->getOptions(); + + // if port is specified use it, otherwise use the SqlServer default + if (empty($options['port'])) { + $dsn = 'sqlsrv:server=' . $options['host'] . ';database=' . $options['name']; + } else { + $dsn = 'sqlsrv:server=' . $options['host'] . ',' . $options['port'] . ';database=' . $options['name']; + } + $dsn .= ';MultipleActiveResultSets=false'; + + $driverOptions = array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION); + + // charset support + if (isset($options['charset'])) { + $driverOptions[\PDO::SQLSRV_ATTR_ENCODING] = $options['charset']; + } + + // support arbitrary \PDO::SQLSRV_ATTR_* driver options and pass them to PDO + // http://php.net/manual/en/ref.pdo-sqlsrv.php#pdo-sqlsrv.constants + foreach ($options as $key => $option) { + if (strpos($key, 'sqlsrv_attr_') === 0) { + $driverOptions[constant('\PDO::' . strtoupper($key))] = $option; + } + } + + try { + $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions); + } catch (\PDOException $exception) { + throw new \InvalidArgumentException(sprintf( + 'There was a problem connecting to the database: %s', + $exception->getMessage() + )); + } + + $this->setConnection($db); + } + } + + /** + * Connect to MSSQL using dblib/freetds. + * + * The "sqlsrv" driver is not available on Unix machines. + * + * @throws \InvalidArgumentException + */ + protected function connectDblib() + { + if (!class_exists('PDO') || !in_array('dblib', \PDO::getAvailableDrivers(), true)) { + // @codeCoverageIgnoreStart + throw new \RuntimeException('You need to enable the PDO_Dblib extension for Phinx to run properly.'); + // @codeCoverageIgnoreEnd + } + + $options = $this->getOptions(); + + // if port is specified use it, otherwise use the SqlServer default + if (empty($options['port'])) { + $dsn = 'dblib:host=' . $options['host'] . ';dbname=' . $options['name']; + } else { + $dsn = 'dblib:host=' . $options['host'] . ':' . $options['port'] . ';dbname=' . $options['name']; + } + + $driverOptions = array(\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION); + + try { + $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions); + } catch (\PDOException $exception) { + throw new \InvalidArgumentException(sprintf( + 'There was a problem connecting to the database: %s', + $exception->getMessage() + )); + } + + $this->setConnection($db); + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + $this->connection = null; + } + + /** + * {@inheritdoc} + */ + public function hasTransactions() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + $this->execute('BEGIN TRANSACTION'); + } + + /** + * {@inheritdoc} + */ + public function commitTransaction() + { + $this->execute('COMMIT TRANSACTION'); + } + + /** + * {@inheritdoc} + */ + public function rollbackTransaction() + { + $this->execute('ROLLBACK TRANSACTION'); + } + + /** + * {@inheritdoc} + */ + public function quoteTableName($tableName) + { + return str_replace('.', '].[', $this->quoteColumnName($tableName)); + } + + /** + * {@inheritdoc} + */ + public function quoteColumnName($columnName) + { + return '[' . str_replace(']', '\]', $columnName) . ']'; + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + $result = $this->fetchRow(sprintf('SELECT count(*) as [count] FROM information_schema.tables WHERE table_name = \'%s\';', $tableName)); + return $result['count'] > 0; + } + + /** + * {@inheritdoc} + */ + public function createTable(Table $table) + { + $this->startCommandTimer(); + + $options = $table->getOptions(); + + // Add the default primary key + $columns = $table->getPendingColumns(); + if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) { + $column = new Column(); + $column->setName('id') + ->setType('integer') + ->setIdentity(true); + + array_unshift($columns, $column); + $options['primary_key'] = 'id'; + + } elseif (isset($options['id']) && is_string($options['id'])) { + // Handle id => "field_name" to support AUTO_INCREMENT + $column = new Column(); + $column->setName($options['id']) + ->setType('integer') + ->setIdentity(true); + + array_unshift($columns, $column); + $options['primary_key'] = $options['id']; + } + + $sql = 'CREATE TABLE '; + $sql .= $this->quoteTableName($table->getName()) . ' ('; + $sqlBuffer = array(); + $columnsWithComments = array(); + foreach ($columns as $column) { + $sqlBuffer[] = $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column); + + // set column comments, if needed + if ($column->getComment()) { + $columnsWithComments[] = $column; + } + } + + // set the primary key(s) + if (isset($options['primary_key'])) { + $pkSql = sprintf('CONSTRAINT PK_%s PRIMARY KEY (', $table->getName()); + if (is_string($options['primary_key'])) { // handle primary_key => 'id' + $pkSql .= $this->quoteColumnName($options['primary_key']); + } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id') + // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the anonymous function, + // but for now just hard-code the adapter quotes + $pkSql .= implode( + ',', + array_map( + function ($v) { + return '[' . $v . ']'; + }, + $options['primary_key'] + ) + ); + } + $pkSql .= ')'; + $sqlBuffer[] = $pkSql; + } + + // set the foreign keys + $foreignKeys = $table->getForeignKeys(); + if (!empty($foreignKeys)) { + foreach ($foreignKeys as $foreignKey) { + $sqlBuffer[] = $this->getForeignKeySqlDefinition($foreignKey, $table->getName()); + } + } + + $sql .= implode(', ', $sqlBuffer); + $sql .= ');'; + + // process column comments + if (!empty($columnsWithComments)) { + foreach ($columnsWithComments as $column) { + $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName()); + } + } + + // set the indexes + $indexes = $table->getIndexes(); + if (!empty($indexes)) { + foreach ($indexes as $index) { + $sql .= $this->getIndexSqlDefinition($index, $table->getName()); + } + } + + // execute the sql + $this->writeCommand('createTable', array($table->getName())); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * Gets the SqlServer Column Comment Defininition for a column object. + * + * @param Column $column Column + * @param string $tableName Table name + * + * @return string + */ + protected function getColumnCommentSqlDefinition(Column $column, $tableName) + { + // passing 'null' is to remove column comment + $currentComment = $this->getColumnComment($tableName, $column->getName()); + + $comment = (strcasecmp($column->getComment(), 'NULL') !== 0) ? $this->getConnection()->quote($column->getComment()) : '\'\''; + $command = $currentComment === false ? 'sp_addextendedproperty' : 'sp_updateextendedproperty'; + return sprintf( + "EXECUTE %s N'MS_Description', N%s, N'SCHEMA', N'%s', N'TABLE', N'%s', N'COLUMN', N'%s';", + $command, + $comment, + $this->schema, + $tableName, + $column->getName() + ); + } + + /** + * {@inheritdoc} + */ + public function renameTable($tableName, $newTableName) + { + $this->startCommandTimer(); + $this->writeCommand('renameTable', array($tableName, $newTableName)); + $this->execute(sprintf('EXEC sp_rename \'%s\', \'%s\'', $tableName, $newTableName)); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropTable($tableName) + { + $this->startCommandTimer(); + $this->writeCommand('dropTable', array($tableName)); + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName))); + $this->endCommandTimer(); + } + + public function getColumnComment($tableName, $columnName) + { + $sql = sprintf("SELECT cast(extended_properties.[value] as nvarchar(4000)) comment + FROM sys.schemas + INNER JOIN sys.tables + ON schemas.schema_id = tables.schema_id + INNER JOIN sys.columns + ON tables.object_id = columns.object_id + INNER JOIN sys.extended_properties + ON tables.object_id = extended_properties.major_id + AND columns.column_id = extended_properties.minor_id + AND extended_properties.name = 'MS_Description' + WHERE schemas.[name] = '%s' AND tables.[name] = '%s' AND columns.[name] = '%s'", $this->schema, $tableName, $columnName); + $row = $this->fetchRow($sql); + + if ($row) { + return $row['comment']; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getColumns($tableName) + { + $columns = array(); + $sql = sprintf( + "SELECT DISTINCT TABLE_SCHEMA AS [schema], TABLE_NAME as [table_name], COLUMN_NAME AS [name], DATA_TYPE AS [type], + IS_NULLABLE AS [null], COLUMN_DEFAULT AS [default], + CHARACTER_MAXIMUM_LENGTH AS [char_length], + NUMERIC_PRECISION AS [precision], + NUMERIC_SCALE AS [scale], ORDINAL_POSITION AS [ordinal_position], + COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as [identity] + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = '%s' + ORDER BY ordinal_position", + $tableName + ); + $rows = $this->fetchAll($sql); + foreach ($rows as $columnInfo) { + $column = new Column(); + $column->setName($columnInfo['name']) + ->setType($this->getPhinxType($columnInfo['type'])) + ->setNull($columnInfo['null'] !== 'NO') + ->setDefault($this->parseDefault($columnInfo['default'])) + ->setIdentity($columnInfo['identity'] === '1') + ->setComment($this->getColumnComment($columnInfo['table_name'], $columnInfo['name'])); + + if (!empty($columnInfo['char_length'])) { + $column->setLimit($columnInfo['char_length']); + } + + $columns[$columnInfo['name']] = $column; + } + + return $columns; + } + + protected function parseDefault($default) + { + $default = preg_replace(array("/\('(.*)'\)/", "/\(\((.*)\)\)/", "/\((.*)\)/"), '$1', $default); + + if (strtoupper($default) === 'NULL') { + $default = null; + } elseif (is_numeric($default)) { + $default = (int) $default; + } + + return $default; + } + + /** + * {@inheritdoc} + */ + public function hasColumn($tableName, $columnName, $options = array()) + { + $sql = sprintf( + "SELECT count(*) as [count] + FROM information_schema.columns + WHERE table_name = '%s' AND column_name = '%s'", + $tableName, + $columnName + ); + $result = $this->fetchRow($sql); + + return $result['count'] > 0; + } + + /** + * {@inheritdoc} + */ + public function addColumn(Table $table, Column $column) + { + $this->startCommandTimer(); + $sql = sprintf( + 'ALTER TABLE %s ADD %s %s', + $this->quoteTableName($table->getName()), + $this->quoteColumnName($column->getName()), + $this->getColumnSqlDefinition($column) + ); + + $this->writeCommand('addColumn', array($table->getName(), $column->getName(), $column->getType())); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function renameColumn($tableName, $columnName, $newColumnName) + { + $this->startCommandTimer(); + + if (!$this->hasColumn($tableName, $columnName)) { + throw new \InvalidArgumentException("The specified column does not exist: $columnName"); + } + $this->writeCommand('renameColumn', array($tableName, $columnName, $newColumnName)); + $this->renameDefault($tableName, $columnName, $newColumnName); + $this->execute( + sprintf( + "EXECUTE sp_rename N'%s.%s', N'%s', 'COLUMN' ", + $tableName, + $columnName, + $newColumnName + ) + ); + $this->endCommandTimer(); + } + + protected function renameDefault($tableName, $columnName, $newColumnName) + { + $oldConstraintName = "DF_{$tableName}_{$columnName}"; + $newConstraintName = "DF_{$tableName}_{$newColumnName}"; + $sql = <<execute(sprintf( + $sql, + $oldConstraintName, + $newConstraintName + )); + } + + public function changeDefault($tableName, Column $newColumn) + { + $constraintName = "DF_{$tableName}_{$newColumn->getName()}"; + $default = $newColumn->getDefault(); + + if ($default === null) { + $default = 'DEFAULT NULL'; + } else { + $default = $this->getDefaultValueDefinition($default); + } + + if (empty($default)) { + return; + } + + $this->execute(sprintf( + 'ALTER TABLE %s ADD CONSTRAINT %s %s FOR %s', + $this->quoteTableName($tableName), + $constraintName, + $default, + $this->quoteColumnName($newColumn->getName()) + )); + } + + /** + * {@inheritdoc} + */ + public function changeColumn($tableName, $columnName, Column $newColumn) + { + $this->startCommandTimer(); + $this->writeCommand('changeColumn', array($tableName, $columnName, $newColumn->getType())); + $columns = $this->getColumns($tableName); + $changeDefault = $newColumn->getDefault() !== $columns[$columnName]->getDefault() || $newColumn->getType() !== $columns[$columnName]->getType(); + if ($columnName !== $newColumn->getName()) { + $this->renameColumn($tableName, $columnName, $newColumn->getName()); + } + + if ($changeDefault) { + $this->dropDefaultConstraint($tableName, $newColumn->getName()); + } + + $this->execute( + sprintf( + 'ALTER TABLE %s ALTER COLUMN %s %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($newColumn->getName()), + $this->getColumnSqlDefinition($newColumn, false) + ) + ); + // change column comment if needed + if ($newColumn->getComment()) { + $sql = $this->getColumnCommentSqlDefinition($newColumn, $tableName); + $this->execute($sql); + } + + if ($changeDefault) { + $this->changeDefault($tableName, $newColumn); + } + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropColumn($tableName, $columnName) + { + $this->startCommandTimer(); + $this->writeCommand('dropColumn', array($tableName, $columnName)); + $this->dropDefaultConstraint($tableName, $columnName); + + $this->execute( + sprintf( + 'ALTER TABLE %s DROP COLUMN %s', + $this->quoteTableName($tableName), + $this->quoteColumnName($columnName) + ) + ); + $this->endCommandTimer(); + } + + protected function dropDefaultConstraint($tableName, $columnName) + { + $defaultConstraint = $this->getDefaultConstraint($tableName, $columnName); + + if (!$defaultConstraint) { + return; + } + + $this->dropForeignKey($tableName, $columnName, $defaultConstraint); + } + + protected function getDefaultConstraint($tableName, $columnName) + { + $sql = "SELECT + default_constraints.name +FROM + sys.all_columns + + INNER JOIN + sys.tables + ON all_columns.object_id = tables.object_id + + INNER JOIN + sys.schemas + ON tables.schema_id = schemas.schema_id + + INNER JOIN + sys.default_constraints + ON all_columns.default_object_id = default_constraints.object_id + +WHERE + schemas.name = 'dbo' + AND tables.name = '{$tableName}' + AND all_columns.name = '{$columnName}'"; + + $rows = $this->fetchAll($sql); + return empty($rows) ? false : $rows[0]['name']; + } + + protected function getIndexColums($tableId, $indexId) + { + $sql = "SELECT AC.[name] AS [column_name] +FROM sys.[index_columns] IC + INNER JOIN sys.[all_columns] AC ON IC.[column_id] = AC.[column_id] +WHERE AC.[object_id] = {$tableId} AND IC.[index_id] = {$indexId} AND IC.[object_id] = {$tableId} +ORDER BY IC.[key_ordinal];"; + + $rows = $this->fetchAll($sql); + $columns = array(); + foreach($rows as $row) { + $columns[] = strtolower($row['column_name']); + } + return $columns; + } + + /** + * Get an array of indexes from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + public function getIndexes($tableName) + { + $indexes = array(); + $sql = "SELECT I.[name] AS [index_name], I.[index_id] as [index_id], T.[object_id] as [table_id] +FROM sys.[tables] AS T + INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id] +WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> 'HEAP' AND T.[name] = '{$tableName}' +ORDER BY T.[name], I.[index_id];"; + + $rows = $this->fetchAll($sql); + foreach ($rows as $row) { + $columns = $this->getIndexColums($row['table_id'], $row['index_id']); + $indexes[$row['index_name']] = array('columns' => $columns); + } + + return $indexes; + } + + /** + * {@inheritdoc} + */ + public function hasIndex($tableName, $columns) + { + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $columns = array_map('strtolower', $columns); + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $index) { + $a = array_diff($columns, $index['columns']); + + if (empty($a)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function hasIndexByName($tableName, $indexName) + { + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $name => $index) { + if ($name === $indexName) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function addIndex(Table $table, Index $index) + { + $this->startCommandTimer(); + $this->writeCommand('addIndex', array($table->getName(), $index->getColumns())); + $sql = $this->getIndexSqlDefinition($index, $table->getName()); + $this->execute($sql); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropIndex($tableName, $columns) + { + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $this->writeCommand('dropIndex', array($tableName, $columns)); + $indexes = $this->getIndexes($tableName); + $columns = array_map('strtolower', $columns); + + foreach ($indexes as $indexName => $index) { + $a = array_diff($columns, $index['columns']); + if (empty($a)) { + $this->execute( + sprintf( + 'DROP INDEX %s ON %s', + $this->quoteColumnName($indexName), + $this->quoteTableName($tableName) + ) + ); + $this->endCommandTimer(); + return; + } + } + } + + /** + * {@inheritdoc} + */ + public function dropIndexByName($tableName, $indexName) + { + $this->startCommandTimer(); + + $this->writeCommand('dropIndexByName', array($tableName, $indexName)); + $indexes = $this->getIndexes($tableName); + + foreach ($indexes as $name => $index) { + if ($name === $indexName) { + $this->execute( + sprintf( + 'DROP INDEX %s ON %s', + $this->quoteColumnName($indexName), + $this->quoteTableName($tableName) + ) + ); + $this->endCommandTimer(); + return; + } + } + } + + /** + * {@inheritdoc} + */ + public function hasForeignKey($tableName, $columns, $constraint = null) + { + if (is_string($columns)) { + $columns = array($columns); // str to array + } + $foreignKeys = $this->getForeignKeys($tableName); + if ($constraint) { + if (isset($foreignKeys[$constraint])) { + return !empty($foreignKeys[$constraint]); + } + return false; + } else { + foreach ($foreignKeys as $key) { + $a = array_diff($columns, $key['columns']); + if (empty($a)) { + return true; + } + } + return false; + } + } + + /** + * Get an array of foreign keys from a particular table. + * + * @param string $tableName Table Name + * @return array + */ + protected function getForeignKeys($tableName) + { + $foreignKeys = array(); + $rows = $this->fetchAll(sprintf( + "SELECT + tc.constraint_name, + tc.table_name, kcu.column_name, + ccu.table_name AS referenced_table_name, + ccu.column_name AS referenced_column_name + FROM + information_schema.table_constraints AS tc + JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name + JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name + WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s' + ORDER BY kcu.ordinal_position", + $tableName + )); + foreach ($rows as $row) { + $foreignKeys[$row['constraint_name']]['table'] = $row['table_name']; + $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name']; + $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name']; + $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name']; + } + + return $foreignKeys; + } + + /** + * {@inheritdoc} + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey) + { + $this->startCommandTimer(); + $this->writeCommand('addForeignKey', array($table->getName(), $foreignKey->getColumns())); + $this->execute( + sprintf( + 'ALTER TABLE %s ADD %s', + $this->quoteTableName($table->getName()), + $this->getForeignKeySqlDefinition($foreignKey, $table->getName()) + ) + ); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($tableName, $columns, $constraint = null) + { + $this->startCommandTimer(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + + $this->writeCommand('dropForeignKey', array($tableName, $columns)); + + if ($constraint) { + $this->execute( + sprintf( + 'ALTER TABLE %s DROP CONSTRAINT %s', + $this->quoteTableName($tableName), + $constraint + ) + ); + $this->endCommandTimer(); + return; + } else { + foreach ($columns as $column) { + $rows = $this->fetchAll(sprintf( + "SELECT + tc.constraint_name, + tc.table_name, kcu.column_name, + ccu.table_name AS referenced_table_name, + ccu.column_name AS referenced_column_name + FROM + information_schema.table_constraints AS tc + JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name + JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name + WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s' and ccu.column_name='%s' + ORDER BY kcu.ordinal_position", + $tableName, + $column + )); + foreach ($rows as $row) { + $this->dropForeignKey($tableName, $columns, $row['constraint_name']); + } + } + } + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function getSqlType($type, $limit = null) + { + switch ($type) { + case static::PHINX_TYPE_STRING: + return array('name' => 'nvarchar', 'limit' => 255); + break; + case static::PHINX_TYPE_CHAR: + return array('name' => 'nchar', 'limit' => 255); + break; + case static::PHINX_TYPE_TEXT: + return array('name' => 'ntext'); + break; + case static::PHINX_TYPE_INTEGER: + return array('name' => 'int'); + break; + case static::PHINX_TYPE_BIG_INTEGER: + return array('name' => 'bigint'); + break; + case static::PHINX_TYPE_FLOAT: + return array('name' => 'float'); + break; + case static::PHINX_TYPE_DECIMAL: + return array('name' => 'decimal'); + break; + case static::PHINX_TYPE_DATETIME: + case static::PHINX_TYPE_TIMESTAMP: + return array('name' => 'datetime'); + break; + case static::PHINX_TYPE_TIME: + return array('name' => 'time'); + break; + case static::PHINX_TYPE_DATE: + return array('name' => 'date'); + break; + case static::PHINX_TYPE_BLOB: + case static::PHINX_TYPE_BINARY: + return array('name' => 'varbinary'); + break; + case static::PHINX_TYPE_BOOLEAN: + return array('name' => 'bit'); + break; + case static::PHINX_TYPE_UUID: + return array('name' => 'uniqueidentifier'); + case static::PHINX_TYPE_FILESTREAM: + return array('name' => 'varbinary', 'limit' => 'max'); + // Geospatial database types + case static::PHINX_TYPE_GEOMETRY: + case static::PHINX_TYPE_POINT: + case static::PHINX_TYPE_LINESTRING: + case static::PHINX_TYPE_POLYGON: + // SQL Server stores all spatial data using a single data type. + // Specific types (point, polygon, etc) are set at insert time. + return array('name' => 'geography'); + break; + default: + throw new \RuntimeException('The type: "' . $type . '" is not supported.'); + } + } + + /** + * Returns Phinx type by SQL type + * + * @param $sqlTypeDef + * @throws \RuntimeException + * @internal param string $sqlType SQL type + * @returns string Phinx type + */ + public function getPhinxType($sqlType) + { + switch ($sqlType) { + case 'nvarchar': + case 'varchar': + return static::PHINX_TYPE_STRING; + case 'char': + case 'nchar': + return static::PHINX_TYPE_CHAR; + case 'text': + case 'ntext': + return static::PHINX_TYPE_TEXT; + case 'int': + case 'integer': + return static::PHINX_TYPE_INTEGER; + case 'decimal': + case 'numeric': + case 'money': + return static::PHINX_TYPE_DECIMAL; + case 'bigint': + return static::PHINX_TYPE_BIG_INTEGER; + case 'real': + case 'float': + return static::PHINX_TYPE_FLOAT; + case 'binary': + case 'image': + case 'varbinary': + return static::PHINX_TYPE_BINARY; + break; + case 'time': + return static::PHINX_TYPE_TIME; + case 'date': + return static::PHINX_TYPE_DATE; + case 'datetime': + case 'timestamp': + return static::PHINX_TYPE_DATETIME; + case 'bit': + return static::PHINX_TYPE_BOOLEAN; + case 'uniqueidentifier': + return static::PHINX_TYPE_UUID; + case 'filestream': + return static::PHINX_TYPE_FILESTREAM; + default: + throw new \RuntimeException('The SqlServer type: "' . $sqlType . '" is not supported'); + } + } + + /** + * {@inheritdoc} + */ + public function createDatabase($name, $options = array()) + { + $this->startCommandTimer(); + $this->writeCommand('createDatabase', array($name)); + + if (isset($options['collation'])) { + $this->execute(sprintf('CREATE DATABASE [%s] COLLATE [%s]', $name, $options['collation'])); + } else { + $this->execute(sprintf('CREATE DATABASE [%s]', $name)); + } + $this->execute(sprintf('USE [%s]', $name)); + $this->endCommandTimer(); + } + + /** + * {@inheritdoc} + */ + public function hasDatabase($name) + { + $result = $this->fetchRow( + sprintf( + 'SELECT count(*) as [count] FROM master.dbo.sysdatabases WHERE [name] = \'%s\'', + $name + ) + ); + + return $result['count'] > 0; + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($name) + { + $this->startCommandTimer(); + $this->writeCommand('dropDatabase', array($name)); + $sql = <<execute($sql); + $this->endCommandTimer(); + } + + /** + * Get the defintion for a `DEFAULT` statement. + * + * @param mixed $default + * @return string + */ + protected function getDefaultValueDefinition($default) + { + if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) { + $default = $this->getConnection()->quote($default); + } elseif (is_bool($default)) { + $default = $this->castToBool($default); + } + return isset($default) ? ' DEFAULT ' . $default : ''; + } + + /** + * Gets the SqlServer Column Definition for a Column object. + * + * @param Column $column Column + * @return string + */ + protected function getColumnSqlDefinition(Column $column, $create = true) + { + $buffer = array(); + + $sqlType = $this->getSqlType($column->getType()); + $buffer[] = strtoupper($sqlType['name']); + // integers cant have limits in SQlServer + $noLimits = array( + 'bigint', + 'int', + 'tinyint' + ); + if (!in_array($sqlType['name'], $noLimits) && ($column->getLimit() || isset($sqlType['limit']))) { + $buffer[] = sprintf('(%s)', $column->getLimit() ? $column->getLimit() : $sqlType['limit']); + } + if ($column->getPrecision() && $column->getScale()) { + $buffer[] = '(' . $column->getPrecision() . ',' . $column->getScale() . ')'; + } + + $properties = $column->getProperties(); + $buffer[] = $column->getType() === 'filestream' ? 'FILESTREAM' : ''; + $buffer[] = isset($properties['rowguidcol']) ? 'ROWGUIDCOL' : ''; + + $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL'; + + if ($create === true) { + if ($column->getDefault() === null && $column->isNull()) { + $buffer[] = ' DEFAULT NULL'; + } else { + $buffer[] = $this->getDefaultValueDefinition($column->getDefault()); + } + } + + if ($column->isIdentity()) { + $buffer[] = 'IDENTITY(1, 1)'; + } + + return implode(' ', $buffer); + } + + /** + * Gets the SqlServer Index Definition for an Index object. + * + * @param Index $index Index + * @return string + */ + protected function getIndexSqlDefinition(Index $index, $tableName) + { + if (is_string($index->getName())) { + $indexName = $index->getName(); + } else { + $columnNames = $index->getColumns(); + if (is_string($columnNames)) { + $columnNames = array($columnNames); + } + $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames)); + } + $def = sprintf( + "CREATE %s INDEX %s ON %s (%s);", + ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''), + $indexName, + $this->quoteTableName($tableName), + '[' . implode('],[', $index->getColumns()) . ']' + ); + + return $def; + } + + /** + * Gets the SqlServer Foreign Key Definition for an ForeignKey object. + * + * @param ForeignKey $foreignKey + * @return string + */ + protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, $tableName) + { + $def = ' CONSTRAINT "'; + $def .= $tableName . '_' . implode('_', $foreignKey->getColumns()); + $def .= '" FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")'; + $def .= " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")'; + if ($foreignKey->getOnDelete()) { + $def .= " ON DELETE {$foreignKey->getOnDelete()}"; + } + if ($foreignKey->getOnUpdate()) { + $def .= " ON UPDATE {$foreignKey->getOnUpdate()}"; + } + + return $def; + } + + /** + * {@inheritdoc} + */ + public function getColumnTypes() + { + return array_merge(parent::getColumnTypes(), array('filestream')); + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php new file mode 100644 index 0000000..66a850c --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php @@ -0,0 +1,272 @@ + + */ +class TablePrefixAdapter extends AdapterWrapper +{ + /** + * {@inheritdoc} + */ + public function getAdapterType() + { + return 'TablePrefixAdapter'; + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::hasTable($adapterTableName); + } + + /** + * {@inheritdoc} + */ + public function createTable(Table $table) + { + $adapterTable = clone $table; + $adapterTableName = $this->getAdapterTableName($table->getName()); + $adapterTable->setName($adapterTableName); + + foreach ($adapterTable->getForeignKeys() as $fk) { + $adapterReferenceTable = $fk->getReferencedTable(); + $adapterReferenceTableName = $this->getAdapterTableName($adapterReferenceTable->getName()); + $adapterReferenceTable->setName($adapterReferenceTableName); + } + + return parent::createTable($adapterTable); + } + + /** + * {@inheritdoc} + */ + public function renameTable($tableName, $newTableName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + $adapterNewTableName = $this->getAdapterTableName($newTableName); + return parent::renameTable($adapterTableName, $adapterNewTableName); + } + + /** + * {@inheritdoc} + */ + public function dropTable($tableName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::dropTable($adapterTableName); + } + + /** + * {@inheritdoc} + */ + public function getColumns($tableName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::getColumns($adapterTableName); + } + + /** + * {@inheritdoc} + */ + public function hasColumn($tableName, $columnName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::hasColumn($adapterTableName, $columnName); + } + + /** + * {@inheritdoc} + */ + public function addColumn(Table $table, Column $column) + { + $adapterTable = clone $table; + $adapterTableName = $this->getAdapterTableName($table->getName()); + $adapterTable->setName($adapterTableName); + return parent::addColumn($adapterTable, $column); + } + + /** + * {@inheritdoc} + */ + public function renameColumn($tableName, $columnName, $newColumnName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::renameColumn($adapterTableName, $columnName, $newColumnName); + } + + /** + * {@inheritdoc} + */ + public function changeColumn($tableName, $columnName, Column $newColumn) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::changeColumn($adapterTableName, $columnName, $newColumn); + } + + /** + * {@inheritdoc} + */ + public function dropColumn($tableName, $columnName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::dropColumn($adapterTableName, $columnName); + } + + /** + * {@inheritdoc} + */ + public function hasIndex($tableName, $columns) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::hasIndex($adapterTableName, $columns); + } + + /** + * {@inheritdoc} + */ + public function hasIndexByName($tableName, $indexName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::hasIndexByName($adapterTableName, $indexName); + } + + /** + * {@inheritdoc} + */ + public function addIndex(Table $table, Index $index) + { + $adapterTable = clone $table; + $adapterTableName = $this->getAdapterTableName($table->getName()); + $adapterTable->setName($adapterTableName); + return parent::addIndex($adapterTable, $index); + } + + /** + * {@inheritdoc} + */ + public function dropIndex($tableName, $columns, $options = array()) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::dropIndex($adapterTableName, $columns, $options); + } + + /** + * {@inheritdoc} + */ + public function dropIndexByName($tableName, $indexName) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::dropIndexByName($adapterTableName, $indexName); + } + + /** + * {@inheritdoc} + */ + public function hasForeignKey($tableName, $columns, $constraint = null) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::hasForeignKey($adapterTableName, $columns, $constraint); + } + + /** + * {@inheritdoc} + */ + public function addForeignKey(Table $table, ForeignKey $foreignKey) + { + $adapterTable = clone $table; + $adapterTableName = $this->getAdapterTableName($table->getName()); + $adapterTable->setName($adapterTableName); + return parent::addForeignKey($adapterTable, $foreignKey); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($tableName, $columns, $constraint = null) + { + $adapterTableName = $this->getAdapterTableName($tableName); + return parent::dropForeignKey($adapterTableName, $columns, $constraint); + } + + /** + * {@inheritdoc} + */ + public function insert(Table $table, $row) + { + $adapterTable = clone $table; + $adapterTableName = $this->getAdapterTableName($table->getName()); + $adapterTable->setName($adapterTableName); + return parent::insert($adapterTable, $row); + } + + /** + * Gets the table prefix. + * + * @return string + */ + public function getPrefix() + { + return (string) $this->getOption('table_prefix'); + } + + /** + * Gets the table suffix. + * + * @return string + */ + public function getSuffix() + { + return (string) $this->getOption('table_suffix'); + } + + /** + * Applies the prefix and suffix to the table name. + * + * @param string $tableName + * @return string + */ + public function getAdapterTableName($tableName) + { + return $this->getPrefix() . $tableName . $this->getSuffix(); + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/WrapperInterface.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/WrapperInterface.php new file mode 100644 index 0000000..b8181d4 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Adapter/WrapperInterface.php @@ -0,0 +1,60 @@ + + */ +interface WrapperInterface +{ + /** + * Class constructor, must always wrap another adapter. + * + * @param AdapterInterface $adapter + */ + public function __construct(AdapterInterface $adapter); + + /** + * Sets the database adapter to proxy commands to. + * + * @param AdapterInterface $adapter + * @return AdapterInterface + */ + public function setAdapter(AdapterInterface $adapter); + + /** + * Gets the database adapter. + * + * @throws \RuntimeException if the adapter has not been set + * @return AdapterInterface + */ + public function getAdapter(); +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table.php new file mode 100644 index 0000000..b0e7202 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table.php @@ -0,0 +1,674 @@ +setName($name); + $this->setOptions($options); + + if (null !== $adapter) { + $this->setAdapter($adapter); + } + } + + /** + * Sets the table name. + * + * @param string $name Table Name + * @return Table + */ + public function setName($name) + { + $this->name = $name; + return $this; + } + + /** + * Gets the table name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the table options. + * + * @param array $options + * @return Table + */ + public function setOptions($options) + { + $this->options = $options; + return $this; + } + + /** + * Gets the table options. + * + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * Sets the database adapter. + * + * @param AdapterInterface $adapter Database Adapter + * @return Table + */ + public function setAdapter(AdapterInterface $adapter) + { + $this->adapter = $adapter; + return $this; + } + + /** + * Gets the database adapter. + * + * @return AdapterInterface + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * Does the table exist? + * + * @return boolean + */ + public function exists() + { + return $this->getAdapter()->hasTable($this->getName()); + } + + /** + * Drops the database table. + * + * @return void + */ + public function drop() + { + $this->getAdapter()->dropTable($this->getName()); + } + + /** + * Renames the database table. + * + * @param string $newTableName New Table Name + * @return Table + */ + public function rename($newTableName) + { + $this->getAdapter()->renameTable($this->getName(), $newTableName); + $this->setName($newTableName); + return $this; + } + + /** + * Sets an array of columns waiting to be committed. + * Use setPendingColumns + * + * @deprecated + * @param array $columns Columns + * @return Table + */ + public function setColumns($columns) + { + $this->setPendingColumns($columns); + } + + /** + * Gets an array of the table columns. + * + * @return Column[] + */ + public function getColumns() + { + return $this->getAdapter()->getColumns($this->getName()); + } + + /** + * Sets an array of columns waiting to be committed. + * + * @param array $columns Columns + * @return Table + */ + public function setPendingColumns($columns) + { + $this->columns = $columns; + return $this; + } + + /** + * Gets an array of columns waiting to be committed. + * + * @return Column[] + */ + public function getPendingColumns() + { + return $this->columns; + } + + /** + * Sets an array of columns waiting to be indexed. + * + * @param array $indexes Indexes + * @return Table + */ + public function setIndexes($indexes) + { + $this->indexes = $indexes; + return $this; + } + + /** + * Gets an array of indexes waiting to be committed. + * + * @return array + */ + public function getIndexes() + { + return $this->indexes; + } + + /** + * Sets an array of foreign keys waiting to be commited. + * + * @param ForeignKey[] $foreignKeys foreign keys + * @return Table + */ + public function setForeignKeys($foreignKeys) + { + $this->foreignKeys = $foreignKeys; + return $this; + } + + /** + * Gets an array of foreign keys waiting to be commited. + * + * @return array|ForeignKey[] + */ + public function getForeignKeys() + { + return $this->foreignKeys; + } + + /** + * Sets an array of data to be inserted. + * + * @param array $data Data + * @return Table + */ + public function setData($data) + { + $this->data = $data; + return $this; + } + + /** + * Gets the data waiting to be inserted. + * + * @return array + */ + public function getData() + { + return $this->data; + } + + /** + * Resets all of the pending table changes. + * + * @return void + */ + public function reset() + { + $this->setPendingColumns(array()); + $this->setIndexes(array()); + $this->setForeignKeys(array()); + $this->setData(array()); + } + + /** + * Add a table column. + * + * Type can be: string, text, integer, float, decimal, datetime, timestamp, + * time, date, binary, boolean. + * + * Valid options can be: limit, default, null, precision or scale. + * + * @param string|Column $columnName Column Name + * @param string $type Column Type + * @param array $options Column Options + * @throws \RuntimeException + * @throws \InvalidArgumentException + * @return Table + */ + public function addColumn($columnName, $type = null, $options = array()) + { + // we need an adapter set to add a column + if (null === $this->getAdapter()) { + throw new \RuntimeException('An adapter must be specified to add a column.'); + } + + // create a new column object if only strings were supplied + if (!$columnName instanceof Column) { + $column = new Column(); + $column->setName($columnName); + $column->setType($type); + $column->setOptions($options); // map options to column methods + } else { + $column = $columnName; + } + + // Delegate to Adapters to check column type + if (!$this->getAdapter()->isValidColumnType($column)) { + throw new \InvalidArgumentException(sprintf( + 'An invalid column type "%s" was specified for column "%s".', + $column->getType(), + $column->getName() + )); + } + + $this->columns[] = $column; + return $this; + } + + /** + * Remove a table column. + * + * @param string $columnName Column Name + * @return Table + */ + public function removeColumn($columnName) + { + $this->getAdapter()->dropColumn($this->getName(), $columnName); + return $this; + } + + /** + * Rename a table column. + * + * @param string $oldName Old Column Name + * @param string $newName New Column Name + * @return Table + */ + public function renameColumn($oldName, $newName) + { + $this->getAdapter()->renameColumn($this->getName(), $oldName, $newName); + return $this; + } + + /** + * Change a table column type. + * + * @param string $columnName Column Name + * @param string|Column $newColumnType New Column Type + * @param array $options Options + * @return Table + */ + public function changeColumn($columnName, $newColumnType, $options = array()) + { + // create a column object if one wasn't supplied + if (!$newColumnType instanceof Column) { + $newColumn = new Column(); + $newColumn->setType($newColumnType); + $newColumn->setOptions($options); + } else { + $newColumn = $newColumnType; + } + + // if the name was omitted use the existing column name + if (null === $newColumn->getName() || strlen($newColumn->getName()) === 0) { + $newColumn->setName($columnName); + } + + $this->getAdapter()->changeColumn($this->getName(), $columnName, $newColumn); + return $this; + } + + /** + * Checks to see if a column exists. + * + * @param string $columnName Column Name + * @param array $options Options + * @return boolean + */ + public function hasColumn($columnName, $options = array()) + { + return $this->getAdapter()->hasColumn($this->getName(), $columnName, $options); + } + + /** + * Add an index to a database table. + * + * In $options you can specific unique = true/false or name (index name). + * + * @param string|array|Index $columns Table Column(s) + * @param array $options Index Options + * @return Table + */ + public function addIndex($columns, $options = array()) + { + // create a new index object if strings or an array of strings were supplied + if (!$columns instanceof Index) { + $index = new Index(); + if (is_string($columns)) { + $columns = array($columns); // str to array + } + $index->setColumns($columns); + $index->setOptions($options); + } else { + $index = $columns; + } + + $this->indexes[] = $index; + return $this; + } + + /** + * Removes the given index from a table. + * + * @param array $columns Columns + * @param array $options Options + * @return Table + */ + public function removeIndex($columns, $options = array()) + { + $this->getAdapter()->dropIndex($this->getName(), $columns, $options); + return $this; + } + + /** + * Removes the given index identified by its name from a table. + * + * @param string $name Index name + * @return Table + */ + public function removeIndexByName($name) + { + $this->getAdapter()->dropIndexByName($this->getName(), $name); + return $this; + } + + /** + * Checks to see if an index exists. + * + * @param string|array $columns Columns + * @param array $options Options + * @return boolean + */ + public function hasIndex($columns, $options = array()) + { + return $this->getAdapter()->hasIndex($this->getName(), $columns, $options); + } + + /** + * Add a foreign key to a database table. + * + * In $options you can specify on_delete|on_delete = cascade|no_action .., + * on_update, constraint = constraint name. + * + * @param string|array $columns Columns + * @param string|Table $referencedTable Referenced Table + * @param string|array $referencedColumns Referenced Columns + * @param array $options Options + * @return Table + */ + public function addForeignKey($columns, $referencedTable, $referencedColumns = array('id'), $options = array()) + { + if (is_string($referencedColumns)) { + $referencedColumns = array($referencedColumns); // str to array + } + $fk = new ForeignKey(); + if ($referencedTable instanceof Table) { + $fk->setReferencedTable($referencedTable); + } else { + $fk->setReferencedTable(new Table($referencedTable, array(), $this->adapter)); + } + $fk->setColumns($columns) + ->setReferencedColumns($referencedColumns) + ->setOptions($options); + $this->foreignKeys[] = $fk; + + return $this; + } + + /** + * Removes the given foreign key from the table. + * + * @param string|array $columns Column(s) + * @param null|string $constraint Constraint names + * @return Table + */ + public function dropForeignKey($columns, $constraint = null) + { + if (is_string($columns)) { + $columns = array($columns); + } + if ($constraint) { + $this->getAdapter()->dropForeignKey($this->getName(), array(), $constraint); + } else { + $this->getAdapter()->dropForeignKey($this->getName(), $columns); + } + + return $this; + } + + /** + * Checks to see if a foreign key exists. + * + * @param string|array $columns Column(s) + * @param null|string $constraint Constraint names + * @return boolean + */ + public function hasForeignKey($columns, $constraint = null) + { + return $this->getAdapter()->hasForeignKey($this->getName(), $columns, $constraint); + } + + /** + * Add timestamp columns created_at and updated_at to the table. + * + * @param string $createdAtColumnName + * @param string $updatedAtColumnName + * + * @return Table + */ + public function addTimestamps($createdAtColumnName = 'created_at', $updatedAtColumnName = 'updated_at') + { + $createdAtColumnName = is_null($createdAtColumnName) ? 'created_at' : $createdAtColumnName; + $updatedAtColumnName = is_null($updatedAtColumnName) ? 'updated_at' : $updatedAtColumnName; + $this->addColumn($createdAtColumnName, 'timestamp', array( + 'default' => 'CURRENT_TIMESTAMP', + 'update' => '' + )) + ->addColumn($updatedAtColumnName, 'timestamp', array( + 'null' => true, + 'default' => null + )); + + return $this; + } + + /** + * Insert data into the table. + * + * @param $data array of data in the form: + * array( + * array("col1" => "value1", "col2" => "anotherValue1"), + * array("col2" => "value2", "col2" => "anotherValue2"), + * ) + * or array("col1" => "value1", "col2" => "anotherValue1") + * + * @return Table + */ + public function insert($data) + { + // handle array of array situations + if (isset($data[0]) && is_array($data[0])) { + foreach ($data as $row) { + $this->data[] = $row; + } + return $this; + } + $this->data[] = $data; + return $this; + } + + /** + * Creates a table from the object instance. + * + * @return void + */ + public function create() + { + $this->getAdapter()->createTable($this); + $this->saveData(); + $this->reset(); // reset pending changes + } + + /** + * Updates a table from the object instance. + * + * @throws \RuntimeException + * @return void + */ + public function update() + { + if (!$this->exists()) { + throw new \RuntimeException('Cannot update a table that doesn\'t exist!'); + } + + // update table + foreach ($this->getPendingColumns() as $column) { + $this->getAdapter()->addColumn($this, $column); + } + + foreach ($this->getIndexes() as $index) { + $this->getAdapter()->addIndex($this, $index); + } + + foreach ($this->getForeignKeys() as $foreignKey) { + $this->getAdapter()->addForeignKey($this, $foreignKey); + } + + $this->saveData(); + $this->reset(); // reset pending changes + } + + /** + * Commit the pending data waiting for insertion. + * + * @return void + */ + public function saveData() + { + foreach ($this->getData() as $row) { + $this->getAdapter()->insert($this, $row); + } + } + + /** + * Commits the table changes. + * + * If the table doesn't exist it is created otherwise it is updated. + * + * @return void + */ + public function save() + { + if ($this->exists()) { + $this->update(); // update the table + } else { + $this->create(); // create the table + } + + $this->reset(); // reset pending changes + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/Column.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/Column.php new file mode 100644 index 0000000..874341e --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/Column.php @@ -0,0 +1,550 @@ +name = $name; + return $this; + } + + /** + * Gets the column name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the column type. + * + * @param string $type + * @return $this + */ + public function setType($type) + { + $this->type = $type; + return $this; + } + + /** + * Gets the column type. + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Sets the column limit. + * + * @param integer $limit + * @return $this + */ + public function setLimit($limit) + { + $this->limit = $limit; + return $this; + } + + /** + * Gets the column limit. + * + * @return integer + */ + public function getLimit() + { + return $this->limit; + } + + /** + * Sets whether the column allows nulls. + * + * @param boolean $null + * @return $this + */ + public function setNull($null) + { + $this->null = (bool) $null; + return $this; + } + + /** + * Gets whether the column allows nulls. + * + * @return boolean + */ + public function getNull() + { + return $this->null; + } + + /** + * Does the column allow nulls? + * + * @return boolean + */ + public function isNull() + { + return $this->getNull(); + } + + /** + * Sets the default column value. + * + * @param mixed $default + * @return $this + */ + public function setDefault($default) + { + $this->default = $default; + return $this; + } + + /** + * Gets the default column value. + * + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * Sets whether or not the column is an identity column. + * + * @param boolean $identity + * @return $this + */ + public function setIdentity($identity) + { + $this->identity = $identity; + return $this; + } + + /** + * Gets whether or not the column is an identity column. + * + * @return boolean + */ + public function getIdentity() + { + return $this->identity; + } + + /** + * Is the column an identity column? + * + * @return boolean + */ + public function isIdentity() + { + return $this->getIdentity(); + } + + /** + * Sets the name of the column to add this column after. + * + * @param string $after After + * @return $this + */ + public function setAfter($after) + { + $this->after = $after; + return $this; + } + + /** + * Returns the name of the column to add this column after. + * + * @return string + */ + public function getAfter() + { + return $this->after; + } + + /** + * Sets the 'ON UPDATE' mysql column function. + * + * @param string $update On Update function + * @return $this + */ + public function setUpdate($update) + { + $this->update = $update; + return $this; + } + + /** + * Returns the value of the ON UPDATE column function. + * + * @return string + */ + public function getUpdate() + { + return $this->update; + } + + /** + * Sets the column precision for decimal. + * + * @param integer $precision + * @return $this + */ + public function setPrecision($precision) + { + $this->precision = $precision; + return $this; + } + + /** + * Gets the column precision for decimal. + * + * @return integer + */ + public function getPrecision() + { + return $this->precision; + } + + /** + * Sets the column scale for decimal. + * + * @param integer $scale + * @return $this + */ + public function setScale($scale) + { + $this->scale = $scale; + return $this; + } + + /** + * Gets the column scale for decimal. + * + * @return integer + */ + public function getScale() + { + return $this->scale; + } + + /** + * Sets the column comment. + * + * @param string $comment + * @return $this + */ + public function setComment($comment) + { + $this->comment = $comment; + return $this; + } + + /** + * Gets the column comment. + * + * @return string + */ + public function getComment() + { + return $this->comment; + } + + /** + * Sets whether field should be signed. + * + * @param bool $signed + * @return $this + */ + public function setSigned($signed) + { + $this->signed = (bool) $signed; + return $this; + } + + /** + * Gets whether field should be signed. + * + * @return string + */ + public function getSigned() + { + return $this->signed; + } + + /** + * Should the column be signed? + * + * @return boolean + */ + public function isSigned() + { + return $this->getSigned(); + } + + /** + * Sets whether the field should have a timezone identifier. + * Used for date/time columns only! + * + * @param bool $timezone + * @return $this + */ + public function setTimezone($timezone) + { + $this->timezone = (bool) $timezone; + return $this; + } + + /** + * Gets whether field has a timezone identifier. + * + * @return boolean + */ + public function getTimezone() + { + return $this->timezone; + } + + /** + * Should the column have a timezone? + * + * @return boolean + */ + public function isTimezone() + { + return $this->getTimezone(); + } + + /** + * Sets field properties. + * + * @param array $properties + * + * @return $this + */ + public function setProperties($properties) + { + $this->properties = $properties; + return $this; + } + + /** + * Gets field properties + * + * @return array + */ + public function getProperties() + { + return $this->properties; + } + + /** + * Sets field values. + * + * @param mixed (array|string) $values + * + * @return $this + */ + public function setValues($values) + { + if (!is_array($values)) { + $values = preg_split('/,\s*/', $values); + } + $this->values = $values; + return $this; + } + + /** + * Gets field values + * + * @return string + */ + public function getValues() + { + return $this->values; + } + + /** + * Gets all allowed options. Each option must have a corresponding `setFoo` method. + * + * @return array + */ + protected function getValidOptions() + { + return array( + 'limit', + 'default', + 'null', + 'identity', + 'precision', + 'scale', + 'after', + 'update', + 'comment', + 'signed', + 'timezone', + 'properties', + 'values', + ); + } + + /** + * Gets all aliased options. Each alias must reference a valid option. + * + * @return array + */ + protected function getAliasedOptions() + { + return array( + 'length' => 'limit', + ); + } + + /** + * Utility method that maps an array of column options to this objects methods. + * + * @param array $options Options + * @return $this + */ + public function setOptions($options) + { + $validOptions = $this->getValidOptions(); + $aliasOptions = $this->getAliasedOptions(); + + foreach ($options as $option => $value) { + if (isset($aliasOptions[$option])) { + // proxy alias -> option + $option = $aliasOptions[$option]; + } + + if (!in_array($option, $validOptions, true)) { + throw new \RuntimeException(sprintf('"%s" is not a valid column option.', $option)); + } + + $method = 'set' . ucfirst($option); + $this->$method($value); + } + return $this; + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/ForeignKey.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/ForeignKey.php new file mode 100644 index 0000000..3033503 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/ForeignKey.php @@ -0,0 +1,252 @@ + + */ +namespace Phinx\Db\Table; + +use Phinx\Db\Table; + +class ForeignKey +{ + const CASCADE = 'CASCADE'; + const RESTRICT = 'RESTRICT'; + const SET_NULL = 'SET NULL'; + const NO_ACTION = 'NO ACTION'; + + /** + * @var array + */ + protected $columns = array(); + + /** + * @var Table + */ + protected $referencedTable; + + /** + * @var array + */ + protected $referencedColumns = array(); + + /** + * @var string + */ + protected $onDelete; + + /** + * @var string + */ + protected $onUpdate; + + /** + * @var string|boolean + */ + protected $constraint; + + /** + * Sets the foreign key columns. + * + * @param array|string $columns + * @return ForeignKey + */ + public function setColumns($columns) + { + if (is_string($columns)) { + $columns = array($columns); + } + $this->columns = $columns; + return $this; + } + + /** + * Gets the foreign key columns. + * + * @return array + */ + public function getColumns() + { + return $this->columns; + } + + /** + * Sets the foreign key referenced table. + * + * @param Table $table + * @return ForeignKey + */ + public function setReferencedTable(Table $table) + { + $this->referencedTable = $table; + return $this; + } + + /** + * Gets the foreign key referenced table. + * + * @return Table + */ + public function getReferencedTable() + { + return $this->referencedTable; + } + + /** + * Sets the foreign key referenced columns. + * + * @param array $referencedColumns + * @return ForeignKey + */ + public function setReferencedColumns(array $referencedColumns) + { + $this->referencedColumns = $referencedColumns; + return $this; + } + + /** + * Gets the foreign key referenced columns. + * + * @return array + */ + public function getReferencedColumns() + { + return $this->referencedColumns; + } + + /** + * Sets ON DELETE action for the foreign key. + * + * @param string $onDelete + * @return ForeignKey + */ + public function setOnDelete($onDelete) + { + $this->onDelete = $this->normalizeAction($onDelete); + return $this; + } + + /** + * Gets ON DELETE action for the foreign key. + * + * @return string + */ + public function getOnDelete() + { + return $this->onDelete; + } + + /** + * Gets ON UPDATE action for the foreign key. + * + * @return string + */ + public function getOnUpdate() + { + return $this->onUpdate; + } + + /** + * Sets ON UPDATE action for the foreign key. + * + * @param string $onUpdate + * @return ForeignKey + */ + public function setOnUpdate($onUpdate) + { + $this->onUpdate = $this->normalizeAction($onUpdate); + return $this; + } + + /** + * Sets constraint for the foreign key. + * + * @param string $constraint + * @return ForeignKey + */ + public function setConstraint($constraint) + { + $this->constraint = $constraint; + return $this; + } + + /** + * Gets constraint name for the foreign key. + * + * @return string + */ + public function getConstraint() + { + return $this->constraint; + } + + /** + * Utility method that maps an array of index options to this objects methods. + * + * @param array $options Options + * @throws \RuntimeException + * @throws \InvalidArgumentException + * @return ForeignKey + */ + public function setOptions($options) + { + // Valid Options + $validOptions = array('delete', 'update', 'constraint'); + foreach ($options as $option => $value) { + if (!in_array($option, $validOptions, true)) { + throw new \RuntimeException(sprintf('"%s" is not a valid foreign key option.', $option)); + } + + // handle $options['delete'] as $options['update'] + if ('delete' === $option) { + $this->setOnDelete($value); + } elseif ('update' === $option) { + $this->setOnUpdate($value); + } else { + $method = 'set' . ucfirst($option); + $this->$method($value); + } + } + + return $this; + } + + /** + * From passed value checks if it's correct and fixes if needed + * + * @param string $action + * @throws \InvalidArgumentException + * @return string + */ + protected function normalizeAction($action) + { + $constantName = 'static::' . str_replace(' ', '_', strtoupper(trim($action))); + if (!defined($constantName)) { + throw new \InvalidArgumentException('Unknown action passed: ' . $action); + } + return constant($constantName); + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/Index.php b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/Index.php new file mode 100644 index 0000000..82404cb --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Db/Table/Index.php @@ -0,0 +1,185 @@ +columns = $columns; + return $this; + } + + /** + * Gets the index columns. + * + * @return array + */ + public function getColumns() + { + return $this->columns; + } + + /** + * Sets the index type. + * + * @param string $type + * @return Index + */ + public function setType($type) + { + $this->type = $type; + return $this; + } + + /** + * Gets the index type. + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Sets the index name. + * + * @param string $name + * @return Index + */ + public function setName($name) + { + $this->name = $name; + return $this; + } + + /** + * Gets the index name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the index limit. + * + * @param integer $limit + * @return Index + */ + public function setLimit($limit) + { + $this->limit = $limit; + return $this; + } + + /** + * Gets the index limit. + * + * @return integer + */ + public function getLimit() + { + return $this->limit; + } + + /** + * Utility method that maps an array of index options to this objects methods. + * + * @param array $options Options + * @throws \RuntimeException + * @return Index + */ + public function setOptions($options) + { + // Valid Options + $validOptions = array('type', 'unique', 'name', 'limit'); + foreach ($options as $option => $value) { + if (!in_array($option, $validOptions, true)) { + throw new \RuntimeException(sprintf('"%s" is not a valid index option.', $option)); + } + + // handle $options['unique'] + if (strcasecmp($option, self::UNIQUE) === 0) { + if ((bool) $value) { + $this->setType(self::UNIQUE); + } + continue; + } + + $method = 'set' . ucfirst($option); + $this->$method($value); + } + return $this; + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Migration/AbstractMigration.php b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/AbstractMigration.php new file mode 100644 index 0000000..1598cfb --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/AbstractMigration.php @@ -0,0 +1,273 @@ + + */ +abstract class AbstractMigration implements MigrationInterface +{ + /** + * @var float + */ + protected $version; + + /** + * @var AdapterInterface + */ + protected $adapter; + + /** + * @var OutputInterface + */ + protected $output; + + /** + * @var InputInterface + */ + protected $input; + + /** + * Class Constructor. + * + * @param int $version Migration Version + * @param InputInterface|null $input + * @param OutputInterface|null $output + */ + final public function __construct($version, InputInterface $input = null, OutputInterface $output = null) + { + $this->version = $version; + if (!is_null($input)){ + $this->setInput($input); + } + if (!is_null($output)){ + $this->setOutput($output); + } + + $this->init(); + } + + /** + * Initialize method. + * + * @return void + */ + protected function init() + { + } + + /** + * {@inheritdoc} + */ + public function up() + { + } + + /** + * {@inheritdoc} + */ + public function down() + { + } + + /** + * {@inheritdoc} + */ + public function setAdapter(AdapterInterface $adapter) + { + $this->adapter = $adapter; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * {@inheritdoc} + */ + public function setInput(InputInterface $input) + { + $this->input = $input; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getInput() + { + return $this->input; + } + + /** + * {@inheritdoc} + */ + public function setOutput(OutputInterface $output) + { + $this->output = $output; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOutput() + { + return $this->output; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return get_class($this); + } + + /** + * {@inheritdoc} + */ + public function setVersion($version) + { + $this->version = $version; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getVersion() + { + return $this->version; + } + + /** + * {@inheritdoc} + */ + public function execute($sql) + { + return $this->getAdapter()->execute($sql); + } + + /** + * {@inheritdoc} + */ + public function query($sql) + { + return $this->getAdapter()->query($sql); + } + + /** + * {@inheritdoc} + */ + public function fetchRow($sql) + { + return $this->getAdapter()->fetchRow($sql); + } + + /** + * {@inheritdoc} + */ + public function fetchAll($sql) + { + return $this->getAdapter()->fetchAll($sql); + } + + /** + * {@inheritdoc} + */ + public function insert($table, $data) + { + // convert to table object + if (is_string($table)) { + $table = new Table($table, array(), $this->getAdapter()); + } + return $table->insert($data)->save(); + } + + /** + * {@inheritdoc} + */ + public function createDatabase($name, $options) + { + $this->getAdapter()->createDatabase($name, $options); + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($name) + { + $this->getAdapter()->dropDatabase($name); + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + return $this->getAdapter()->hasTable($tableName); + } + + /** + * {@inheritdoc} + */ + public function table($tableName, $options = array()) + { + return new Table($tableName, $options, $this->getAdapter()); + } + + /** + * A short-hand method to drop the given database table. + * + * @param string $tableName Table Name + * @return void + */ + public function dropTable($tableName) + { + $this->table($tableName)->drop(); + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Migration/AbstractTemplateCreation.php b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/AbstractTemplateCreation.php new file mode 100644 index 0000000..589a5f7 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/AbstractTemplateCreation.php @@ -0,0 +1,97 @@ +setInput($input); + } + if (!is_null($output)) { + $this->setOutput($output); + } + } + + /** + * {@inheritdoc} + */ + public function getInput() + { + return $this->input; + } + + /** + * {@inheritdoc} + */ + public function setInput(InputInterface $input) + { + $this->input = $input; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOutput() + { + return $this->output; + } + + /** + * {@inheritdoc} + */ + public function setOutput(OutputInterface $output) + { + $this->output = $output; + + return $this; + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Migration/CreationInterface.php b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/CreationInterface.php new file mode 100644 index 0000000..b60c889 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/CreationInterface.php @@ -0,0 +1,94 @@ + + */ +interface CreationInterface +{ + /** + * CreationInterface constructor. + * + * @param InputInterface|null $input + * @param OutputInterface|null $output + */ + public function __construct(InputInterface $input = null, OutputInterface $output = null); + + /** + * @param InputInterface $input + * + * @return CreationInterface + */ + public function setInput(InputInterface $input); + + /** + * @param OutputInterface $output + * + * @return CreationInterface + */ + public function setOutput(OutputInterface $output); + + /** + * @return InputInterface + */ + public function getInput(); + + /** + * @return OutputInterface + */ + public function getOutput(); + + /** + * Get the migration template. + * + * This will be the content that Phinx will amend to generate the migration file. + * + * @return string The content of the template for Phinx to amend. + */ + public function getMigrationTemplate(); + + /** + * Post Migration Creation. + * + * Once the migration file has been created, this method will be called, allowing any additional + * processing, specific to the template to be performed. + * + * @param string $migrationFilename The name of the newly created migration. + * @param string $className The class name. + * @param string $baseClassName The name of the base class. + * @return void + */ + public function postMigrationCreation($migrationFilename, $className, $baseClassName); +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Migration/IrreversibleMigrationException.php b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/IrreversibleMigrationException.php new file mode 100644 index 0000000..9c2a8d2 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/IrreversibleMigrationException.php @@ -0,0 +1,39 @@ + + */ +class IrreversibleMigrationException extends \Exception +{ +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Migration/Migration.template.php.dist b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/Migration.template.php.dist new file mode 100644 index 0000000..80ac0c1 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Migration/Migration.template.php.dist @@ -0,0 +1,32 @@ + + */ +interface MigrationInterface +{ + /** + * @var string + */ + const CHANGE = 'change'; + + /** + * @var string + */ + const UP = 'up'; + + /** + * @var string + */ + const DOWN = 'down'; + + /** + * Migrate Up + * + * @return void + */ + public function up(); + + /** + * Migrate Down + * + * @return void + */ + public function down(); + + /** + * Sets the database adapter. + * + * @param AdapterInterface $adapter Database Adapter + * @return MigrationInterface + */ + public function setAdapter(AdapterInterface $adapter); + + /** + * Gets the database adapter. + * + * @return AdapterInterface + */ + public function getAdapter(); + + /** + * Sets the input object to be used in migration object + * + * @param InputInterface $input + * @return MigrationInterface + */ + public function setInput(InputInterface $input); + + /** + * Gets the input object to be used in migration object + * + * @return InputInterface + */ + public function getInput(); + + /** + * Sets the output object to be used in migration object + * + * @param OutputInterface $output + * @return MigrationInterface + */ + public function setOutput(OutputInterface $output); + + /** + * Gets the output object to be used in migration object + * + * @return OutputInterface + */ + public function getOutput(); + + /** + * Gets the name. + * + * @return string + */ + public function getName(); + + /** + * Sets the migration version number. + * + * @param float $version Version + * @return MigrationInterface + */ + public function setVersion($version); + + /** + * Gets the migration version number. + * + * @return float + */ + public function getVersion(); + + /** + * Executes a SQL statement and returns the number of affected rows. + * + * @param string $sql SQL + * @return int + */ + public function execute($sql); + + /** + * Executes a SQL statement and returns the result as an array. + * + * @param string $sql SQL + * @return array + */ + public function query($sql); + + /** + * Executes a query and returns only one row as an array. + * + * @param string $sql SQL + * @return array + */ + public function fetchRow($sql); + + /** + * Executes a query and returns an array of rows. + * + * @param string $sql SQL + * @return array + */ + public function fetchAll($sql); + + /** + * Insert data into a table. + * + * @param string $tableName + * @param array $data + * @return void + */ + public function insert($tableName, $data); + + /** + * Create a new database. + * + * @param string $name Database Name + * @param array $options Options + * @return void + */ + public function createDatabase($name, $options); + + /** + * Drop a database. + * + * @param string $name Database Name + * @return void + */ + public function dropDatabase($name); + + /** + * Checks to see if a table exists. + * + * @param string $tableName Table Name + * @return boolean + */ + public function hasTable($tableName); + + /** + * Returns an instance of the \Table class. + * + * You can use this class to create and manipulate tables. + * + * @param string $tableName Table Name + * @param array $options Options + * @return Table + */ + public function table($tableName, $options); +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Seed/AbstractSeed.php b/vendor/topthink/think-migration/phinx/src/Phinx/Seed/AbstractSeed.php new file mode 100644 index 0000000..4d631f5 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Seed/AbstractSeed.php @@ -0,0 +1,215 @@ + + */ +abstract class AbstractSeed implements SeedInterface +{ + /** + * @var AdapterInterface + */ + protected $adapter; + + /** + * @var InputInterface + */ + protected $input; + + /** + * @var OutputInterface + */ + protected $output; + + /** + * Class Constructor. + * + * @param InputInterface $input + * @param OutputInterface $output + */ + final public function __construct(InputInterface $input = null, OutputInterface $output = null) + { + if (!is_null($input)){ + $this->setInput($input); + } + if (!is_null($output)){ + $this->setOutput($output); + } + + $this->init(); + } + + /** + * Initialize method. + * + * @return void + */ + protected function init() + { + } + + /** + * {@inheritdoc} + */ + public function run() + { + } + + /** + * {@inheritdoc} + */ + public function setAdapter(AdapterInterface $adapter) + { + $this->adapter = $adapter; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * {@inheritdoc} + */ + public function setInput(InputInterface $input) + { + $this->input = $input; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getInput() + { + return $this->input; + } + + /** + * {@inheritdoc} + */ + public function setOutput(OutputInterface $output) + { + $this->output = $output; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOutput() + { + return $this->output; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return get_class($this); + } + + /** + * {@inheritdoc} + */ + public function execute($sql) + { + return $this->getAdapter()->execute($sql); + } + + /** + * {@inheritdoc} + */ + public function query($sql) + { + return $this->getAdapter()->query($sql); + } + + /** + * {@inheritdoc} + */ + public function fetchRow($sql) + { + return $this->getAdapter()->fetchRow($sql); + } + + /** + * {@inheritdoc} + */ + public function fetchAll($sql) + { + return $this->getAdapter()->fetchAll($sql); + } + + /** + * {@inheritdoc} + */ + public function insert($table, $data) + { + // convert to table object + if (is_string($table)) { + $table = new Table($table, array(), $this->getAdapter()); + } + return $table->insert($data)->save(); + } + + /** + * {@inheritdoc} + */ + public function hasTable($tableName) + { + return $this->getAdapter()->hasTable($tableName); + } + + /** + * {@inheritdoc} + */ + public function table($tableName, $options = array()) + { + return new Table($tableName, $options, $this->getAdapter()); + } +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Seed/Seed.template.php.dist b/vendor/topthink/think-migration/phinx/src/Phinx/Seed/Seed.template.php.dist new file mode 100644 index 0000000..698ae26 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Seed/Seed.template.php.dist @@ -0,0 +1,19 @@ + + */ +interface SeedInterface +{ + /** + * @var string + */ + const RUN = 'run'; + + /** + * Run the seeder. + * + * @return void + */ + public function run(); + + /** + * Sets the database adapter. + * + * @param AdapterInterface $adapter Database Adapter + * @return MigrationInterface + */ + public function setAdapter(AdapterInterface $adapter); + + /** + * Gets the database adapter. + * + * @return AdapterInterface + */ + public function getAdapter(); + + /** + * Sets the input object to be used in migration object + * + * @param InputInterface $input + * @return MigrationInterface + */ + public function setInput(InputInterface $input); + + /** + * Gets the input object to be used in migration object + * + * @return InputInterface + */ + public function getInput(); + + /** + * Sets the output object to be used in migration object + * + * @param OutputInterface $output + * @return MigrationInterface + */ + public function setOutput(OutputInterface $output); + + /** + * Gets the output object to be used in migration object + * + * @return OutputInterface + */ + public function getOutput(); + + /** + * Gets the name. + * + * @return string + */ + public function getName(); + + /** + * Executes a SQL statement and returns the number of affected rows. + * + * @param string $sql SQL + * @return int + */ + public function execute($sql); + + /** + * Executes a SQL statement and returns the result as an array. + * + * @param string $sql SQL + * @return array + */ + public function query($sql); + + /** + * Executes a query and returns only one row as an array. + * + * @param string $sql SQL + * @return array + */ + public function fetchRow($sql); + + /** + * Executes a query and returns an array of rows. + * + * @param string $sql SQL + * @return array + */ + public function fetchAll($sql); + + /** + * Insert data into a table. + * + * @param string $tableName + * @param array $data + * @return void + */ + public function insert($tableName, $data); + + /** + * Checks to see if a table exists. + * + * @param string $tableName Table Name + * @return boolean + */ + public function hasTable($tableName); + + /** + * Returns an instance of the \Table class. + * + * You can use this class to create and manipulate tables. + * + * @param string $tableName Table Name + * @param array $options Options + * @return Table + */ + public function table($tableName, $options); +} diff --git a/vendor/topthink/think-migration/phinx/src/Phinx/Util/Util.php b/vendor/topthink/think-migration/phinx/src/Phinx/Util/Util.php new file mode 100644 index 0000000..9b8edf2 --- /dev/null +++ b/vendor/topthink/think-migration/phinx/src/Phinx/Util/Util.php @@ -0,0 +1,190 @@ +format(static::DATE_FORMAT); + } + + /** + * Gets an array of all the existing migration class names. + * + * @return string + */ + public static function getExistingMigrationClassNames($path) + { + $classNames = array(); + + if (!is_dir($path)) { + return $classNames; + } + + // filter the files to only get the ones that match our naming scheme + $phpFiles = glob($path . DIRECTORY_SEPARATOR . '*.php'); + + foreach ($phpFiles as $filePath) { + if (preg_match('/([0-9]+)_([_a-z0-9]*).php/', basename($filePath))) { + $classNames[] = static::mapFileNameToClassName(basename($filePath)); + } + } + + return $classNames; + } + + /** + * Get the version from the beginning of a file name. + * + * @param string $fileName File Name + * @return string + */ + public static function getVersionFromFileName($fileName) + { + $matches = array(); + preg_match('/^[0-9]+/', basename($fileName), $matches); + return $matches[0]; + } + + /** + * Turn migration names like 'CreateUserTable' into file names like + * '12345678901234_create_user_table.php' or 'LimitResourceNamesTo30Chars' into + * '12345678901234_limit_resource_names_to_30_chars.php'. + * + * @param string $className Class Name + * @return string + */ + public static function mapClassNameToFileName($className) + { + $arr = preg_split('/(?=[A-Z])/', $className); + unset($arr[0]); // remove the first element ('') + $fileName = static::getCurrentTimestamp() . '_' . strtolower(implode('_', $arr)) . '.php'; + return $fileName; + } + + /** + * Turn file names like '12345678901234_create_user_table.php' into class + * names like 'CreateUserTable'. + * + * @param string $fileName File Name + * @return string + */ + public static function mapFileNameToClassName($fileName) + { + $matches = array(); + if (preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName, $matches)) { + $fileName = $matches[1]; + } + + return str_replace(' ', '', ucwords(str_replace('_', ' ', $fileName))); + } + + /** + * Check if a migration class name is unique regardless of the + * timestamp. + * + * This method takes a class name and a path to a migrations directory. + * + * Migration class names must be in CamelCase format. + * e.g: CreateUserTable or AddIndexToPostsTable. + * + * Single words are not allowed on their own. + * + * @param string $className Class Name + * @param string $path Path + * @return boolean + */ + public static function isUniqueMigrationClassName($className, $path) + { + $existingClassNames = static::getExistingMigrationClassNames($path); + return !(in_array($className, $existingClassNames)); + } + + /** + * Check if a migration/seed class name is valid. + * + * Migration & Seed class names must be in CamelCase format. + * e.g: CreateUserTable, AddIndexToPostsTable or UserSeeder. + * + * Single words are not allowed on their own. + * + * @param string $className Class Name + * @return boolean + */ + public static function isValidPhinxClassName($className) + { + return (bool) preg_match('/^([A-Z][a-z0-9]+)+$/', $className); + } + + /** + * Check if a migration file name is valid. + * + * @param string $fileName File Name + * @return boolean + */ + public static function isValidMigrationFileName($fileName) + { + $matches = array(); + return preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName, $matches); + } + + /** + * Check if a seed file name is valid. + * + * @param string $fileName File Name + * @return boolean + */ + public static function isValidSeedFileName($fileName) + { + $matches = array(); + return preg_match(static::SEED_FILE_NAME_PATTERN, $fileName, $matches); + } +} diff --git a/vendor/topthink/think-migration/src/Command.php b/vendor/topthink/think-migration/src/Command.php new file mode 100644 index 0000000..b4db21f --- /dev/null +++ b/vendor/topthink/think-migration/src/Command.php @@ -0,0 +1,89 @@ + +// +---------------------------------------------------------------------- +namespace think\migration; + +use InvalidArgumentException; +use Phinx\Db\Adapter\AdapterFactory; + +abstract class Command extends \think\console\Command +{ + + public function getAdapter() + { + if (isset($this->adapter)) { + return $this->adapter; + } + + $options = $this->getDbConfig(); + + $adapter = AdapterFactory::instance()->getAdapter($options['adapter'], $options); + + if ($adapter->hasOption('table_prefix') || $adapter->hasOption('table_suffix')) { + $adapter = AdapterFactory::instance()->getWrapper('prefix', $adapter); + } + + $this->adapter = $adapter; + + return $adapter; + } + + /** + * 获取数据库配置 + * @return array + */ + protected function getDbConfig(): array + { + $default = $this->app->config->get('database.default'); + + $config = $this->app->config->get("database.connections.{$default}"); + + if (0 == $config['deploy']) { + $dbConfig = [ + 'adapter' => $config['type'], + 'host' => $config['hostname'], + 'name' => $config['database'], + 'user' => $config['username'], + 'pass' => $config['password'], + 'port' => $config['hostport'], + 'charset' => $config['charset'], + 'table_prefix' => $config['prefix'], + ]; + } else { + $dbConfig = [ + 'adapter' => explode(',', $config['type'])[0], + 'host' => explode(',', $config['hostname'])[0], + 'name' => explode(',', $config['database'])[0], + 'user' => explode(',', $config['username'])[0], + 'pass' => explode(',', $config['password'])[0], + 'port' => explode(',', $config['hostport'])[0], + 'charset' => explode(',', $config['charset'])[0], + 'table_prefix' => explode(',', $config['prefix'])[0], + ]; + } + + $table = $this->app->config->get('database.migration_table', 'migrations'); + + $dbConfig['default_migration_table'] = $dbConfig['table_prefix'] . $table; + + return $dbConfig; + } + + protected function verifyMigrationDirectory(string $path) + { + if (!is_dir($path)) { + throw new InvalidArgumentException(sprintf('Migration directory "%s" does not exist', $path)); + } + + if (!is_writable($path)) { + throw new InvalidArgumentException(sprintf('Migration directory "%s" is not writable', $path)); + } + } +} diff --git a/vendor/topthink/think-migration/src/Creator.php b/vendor/topthink/think-migration/src/Creator.php new file mode 100644 index 0000000..f4bc804 --- /dev/null +++ b/vendor/topthink/think-migration/src/Creator.php @@ -0,0 +1,77 @@ +app = $app; + } + + public function create(string $className) + { + $path = $this->ensureDirectory(); + + if (!Util::isValidPhinxClassName($className)) { + throw new InvalidArgumentException(sprintf('The migration class name "%s" is invalid. Please use CamelCase format.', $className)); + } + + if (!Util::isUniqueMigrationClassName($className, $path)) { + throw new InvalidArgumentException(sprintf('The migration class name "%s" already exists', $className)); + } + + // Compute the file path + $fileName = Util::mapClassNameToFileName($className); + $filePath = $path . DIRECTORY_SEPARATOR . $fileName; + + if (is_file($filePath)) { + throw new InvalidArgumentException(sprintf('The file "%s" already exists', $filePath)); + } + + // Verify that the template creation class (or the aliased class) exists and that it implements the required interface. + $aliasedClassName = null; + + // Load the alternative template if it is defined. + $contents = file_get_contents($this->getTemplate()); + + // inject the class names appropriate to this migration + $contents = strtr($contents, [ + 'MigratorClass' => $className, + ]); + + if (false === file_put_contents($filePath, $contents)) { + throw new RuntimeException(sprintf('The file "%s" could not be written to', $path)); + } + + return $filePath; + } + + protected function ensureDirectory() + { + $path = $this->app->getRootPath() . 'database' . DIRECTORY_SEPARATOR . 'migrations'; + + if (!is_dir($path) && !mkdir($path, 0755, true)) { + throw new InvalidArgumentException(sprintf('directory "%s" does not exist', $path)); + } + + if (!is_writable($path)) { + throw new InvalidArgumentException(sprintf('directory "%s" is not writable', $path)); + } + + return $path; + } + + protected function getTemplate() + { + return __DIR__ . '/command/stubs/migrate.stub'; + } +} diff --git a/vendor/topthink/think-migration/src/Factory.php b/vendor/topthink/think-migration/src/Factory.php new file mode 100644 index 0000000..976a999 --- /dev/null +++ b/vendor/topthink/think-migration/src/Factory.php @@ -0,0 +1,313 @@ +faker = $faker; + } + + /** + * Define a class with a given short-name. + * + * @param string $class + * @param string $name + * @param callable $attributes + * @return $this + */ + public function defineAs(string $class, string $name, callable $attributes) + { + return $this->define($class, $attributes, $name); + } + + /** + * Define a class with a given set of attributes. + * + * @param string $class + * @param callable $attributes + * @param string $name + * @return $this + */ + public function define(string $class, callable $attributes, string $name = 'default') + { + $this->definitions[$class][$name] = $attributes; + + return $this; + } + + /** + * Define a state with a given set of attributes. + * + * @param string $class + * @param string $state + * @param callable|array $attributes + * @return $this + */ + public function state(string $class, string $state, $attributes) + { + $this->states[$class][$state] = $attributes; + + return $this; + } + + /** + * Define a callback to run after making a model. + * + * @param string $class + * @param callable $callback + * @param string $name + * @return $this + */ + public function afterMaking(string $class, callable $callback, string $name = 'default') + { + $this->afterMaking[$class][$name][] = $callback; + + return $this; + } + + /** + * Define a callback to run after making a model with given state. + * + * @param string $class + * @param string $state + * @param callable $callback + * @return $this + */ + public function afterMakingState(string $class, string $state, callable $callback) + { + return $this->afterMaking($class, $callback, $state); + } + + /** + * Define a callback to run after creating a model. + * + * @param string $class + * @param callable $callback + * @param string $name + * @return $this + */ + public function afterCreating(string $class, callable $callback, string $name = 'default') + { + $this->afterCreating[$class][$name][] = $callback; + + return $this; + } + + /** + * Define a callback to run after creating a model with given state. + * + * @param string $class + * @param string $state + * @param callable $callback + * @return $this + */ + public function afterCreatingState(string $class, string $state, callable $callback) + { + return $this->afterCreating($class, $callback, $state); + } + + /** + * Create an instance of the given model and persist it to the database. + * + * @param string $class + * @param array $attributes + * @return mixed + */ + public function create(string $class, array $attributes = []) + { + return $this->of($class)->create($attributes); + } + + /** + * Create an instance of the given model and type and persist it to the database. + * + * @param string $class + * @param string $name + * @param array $attributes + * @return mixed + */ + public function createAs(string $class, string $name, array $attributes = []) + { + return $this->of($class, $name)->create($attributes); + } + + /** + * Create an instance of the given model. + * + * @param string $class + * @param array $attributes + * @return mixed + */ + public function make(string $class, array $attributes = []) + { + return $this->of($class)->make($attributes); + } + + /** + * Create an instance of the given model and type. + * + * @param string $class + * @param string $name + * @param array $attributes + * @return mixed + */ + public function makeAs(string $class, string $name, array $attributes = []) + { + return $this->of($class, $name)->make($attributes); + } + + /** + * Get the raw attribute array for a given named model. + * + * @param string $class + * @param string $name + * @param array $attributes + * @return array + */ + public function rawOf(string $class, string $name, array $attributes = []) + { + return $this->raw($class, $attributes, $name); + } + + /** + * Get the raw attribute array for a given model. + * + * @param string $class + * @param array $attributes + * @param string $name + * @return array + */ + public function raw(string $class, array $attributes = [], string $name = 'default') + { + return array_merge( + call_user_func($this->definitions[$class][$name], $this->faker), $attributes + ); + } + + /** + * Create a builder for the given model. + * + * @param string $class + * @param string $name + * @return FactoryBuilder + */ + public function of(string $class, string $name = 'default') + { + return new FactoryBuilder( + $class, $name, $this->definitions, $this->states, + $this->afterMaking, $this->afterCreating, $this->faker + ); + } + + /** + * Load factories from path. + * + * @param string $path + * @return $this + */ + public function load(string $path) + { + $factory = $this; + + if (is_dir($path)) { + foreach (glob($path . '*.php') as $file) { + require $file; + } + } + + return $factory; + } + + /** + * Determine if the given offset exists. + * + * @param string $offset + * @return bool + */ + public function offsetExists($offset) + { + return isset($this->definitions[$offset]); + } + + /** + * Get the value of the given offset. + * + * @param string $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->make($offset); + } + + /** + * Set the given offset to the given value. + * + * @param string $offset + * @param callable $value + * @return void + */ + public function offsetSet($offset, $value) + { + $this->define($offset, $value); + } + + /** + * Unset the value at the given offset. + * + * @param string $offset + * @return void + */ + public function offsetUnset($offset) + { + unset($this->definitions[$offset]); + } + +} diff --git a/vendor/topthink/think-migration/src/FactoryBuilder.php b/vendor/topthink/think-migration/src/FactoryBuilder.php new file mode 100644 index 0000000..d192ba1 --- /dev/null +++ b/vendor/topthink/think-migration/src/FactoryBuilder.php @@ -0,0 +1,434 @@ +name = $name; + $this->class = $class; + $this->faker = $faker; + $this->states = $states; + $this->definitions = $definitions; + $this->afterMaking = $afterMaking; + $this->afterCreating = $afterCreating; + } + + /** + * Set the amount of models you wish to create / make. + * + * @param int $amount + * @return $this + */ + public function times($amount) + { + $this->amount = $amount; + + return $this; + } + + /** + * Set the state to be applied to the model. + * + * @param string $state + * @return $this + */ + public function state($state) + { + return $this->states([$state]); + } + + /** + * Set the states to be applied to the model. + * + * @param array|mixed $states + * @return $this + */ + public function states($states) + { + $this->activeStates = is_array($states) ? $states : func_get_args(); + + return $this; + } + + /** + * Set the database connection on which the model instance should be persisted. + * + * @param string $name + * @return $this + */ + public function connection($name) + { + $this->connection = $name; + + return $this; + } + + /** + * Create a model and persist it in the database if requested. + * + * @param array $attributes + * @return \Closure + */ + public function lazy(array $attributes = []) + { + return function () use ($attributes) { + return $this->create($attributes); + }; + } + + /** + * Create a collection of models and persist them to the database. + * + * @param array $attributes + * @return mixed + */ + public function create(array $attributes = []) + { + $results = $this->make($attributes); + + if ($results instanceof Model) { + $this->store(new Collection([$results])); + + $this->callAfterCreating(new Collection([$results])); + } else { + $this->store($results); + + $this->callAfterCreating($results); + } + + return $results; + } + + /** + * Set the connection name on the results and store them. + * + * @param Collection $results + * @return void + */ + protected function store($results) + { + $results->each(function (Model $model) { + $model->save(); + }); + } + + /** + * Create a collection of models. + * + * @param array $attributes + * @return mixed + */ + public function make(array $attributes = []) + { + if ($this->amount === null) { + return tap($this->makeInstance($attributes), function ($instance) { + $this->callAfterMaking(new Collection([$instance])); + }); + } + + if ($this->amount < 1) { + return (new $this->class)->toCollection(); + } + + $instances = (new $this->class)->toCollection(array_map(function () use ($attributes) { + return $this->makeInstance($attributes); + }, range(1, $this->amount))); + + $this->callAfterMaking($instances); + + return $instances; + } + + /** + * Create an array of raw attribute arrays. + * + * @param array $attributes + * @return mixed + */ + public function raw(array $attributes = []) + { + if ($this->amount === null) { + return $this->getRawAttributes($attributes); + } + + if ($this->amount < 1) { + return []; + } + + return array_map(function () use ($attributes) { + return $this->getRawAttributes($attributes); + }, range(1, $this->amount)); + } + + /** + * Get a raw attributes array for the model. + * + * @param array $attributes + * @return mixed + * + * @throws \InvalidArgumentException + */ + protected function getRawAttributes(array $attributes = []) + { + if (!isset($this->definitions[$this->class][$this->name])) { + throw new InvalidArgumentException("Unable to locate factory with name [{$this->name}] [{$this->class}]."); + } + + $definition = call_user_func( + $this->definitions[$this->class][$this->name], + $this->faker, $attributes + ); + + return $this->expandAttributes( + array_merge($this->applyStates($definition, $attributes), $attributes) + ); + } + + /** + * Make an instance of the model with the given attributes. + * + * @param array $attributes + * @return Model + */ + protected function makeInstance(array $attributes = []) + { + return new $this->class( + $this->getRawAttributes($attributes) + ); + } + + /** + * Apply the active states to the model definition array. + * + * @param array $definition + * @param array $attributes + * @return array + */ + protected function applyStates(array $definition, array $attributes = []) + { + foreach ($this->activeStates as $state) { + if (!isset($this->states[$this->class][$state])) { + if ($this->stateHasAfterCallback($state)) { + continue; + } + + throw new InvalidArgumentException("Unable to locate [{$state}] state for [{$this->class}]."); + } + + $definition = array_merge( + $definition, + $this->stateAttributes($state, $attributes) + ); + } + + return $definition; + } + + /** + * Get the state attributes. + * + * @param string $state + * @param array $attributes + * @return array + */ + protected function stateAttributes($state, array $attributes) + { + $stateAttributes = $this->states[$this->class][$state]; + + if (!is_callable($stateAttributes)) { + return $stateAttributes; + } + + return call_user_func( + $stateAttributes, + $this->faker, $attributes + ); + } + + /** + * Expand all attributes to their underlying values. + * + * @param array $attributes + * @return array + */ + protected function expandAttributes(array $attributes) + { + foreach ($attributes as &$attribute) { + if (is_callable($attribute) && !is_string($attribute) && !is_array($attribute)) { + $attribute = $attribute($attributes); + } + + if ($attribute instanceof static) { + $attribute = $attribute->create()->getKey(); + } + + if ($attribute instanceof Model) { + $attribute = $attribute->getKey(); + } + } + + return $attributes; + } + + /** + * Run after making callbacks on a collection of models. + * + * @param Collection $models + * @return void + */ + public function callAfterMaking($models) + { + $this->callAfter($this->afterMaking, $models); + } + + /** + * Run after creating callbacks on a collection of models. + * + * @param Collection $models + * @return void + */ + public function callAfterCreating($models) + { + $this->callAfter($this->afterCreating, $models); + } + + /** + * Call after callbacks for each model and state. + * + * @param array $afterCallbacks + * @param Collection $models + * @return void + */ + protected function callAfter(array $afterCallbacks, $models) + { + $states = array_merge([$this->name], $this->activeStates); + + $models->each(function ($model) use ($states, $afterCallbacks) { + foreach ($states as $state) { + $this->callAfterCallbacks($afterCallbacks, $model, $state); + } + }); + } + + /** + * Call after callbacks for each model and state. + * + * @param array $afterCallbacks + * @param Model $model + * @param string $state + * @return void + */ + protected function callAfterCallbacks(array $afterCallbacks, $model, $state) + { + if (!isset($afterCallbacks[$this->class][$state])) { + return; + } + + foreach ($afterCallbacks[$this->class][$state] as $callback) { + $callback($model, $this->faker); + } + } + + /** + * Determine if the given state has an "after" callback. + * + * @param string $state + * @return bool + */ + protected function stateHasAfterCallback($state) + { + return isset($this->afterMaking[$this->class][$state]) || + isset($this->afterCreating[$this->class][$state]); + } +} diff --git a/vendor/topthink/think-migration/src/Migrator.php b/vendor/topthink/think-migration/src/Migrator.php new file mode 100644 index 0000000..1965953 --- /dev/null +++ b/vendor/topthink/think-migration/src/Migrator.php @@ -0,0 +1,27 @@ + +// +---------------------------------------------------------------------- +namespace think\migration; + +use Phinx\Migration\AbstractMigration; +use think\migration\db\Table; + +class Migrator extends AbstractMigration +{ + /** + * @param string $tableName + * @param array $options + * @return Table + */ + public function table($tableName, $options = []) + { + return new Table($tableName, $options, $this->getAdapter()); + } +} diff --git a/vendor/topthink/think-migration/src/Seeder.php b/vendor/topthink/think-migration/src/Seeder.php new file mode 100644 index 0000000..a1c46db --- /dev/null +++ b/vendor/topthink/think-migration/src/Seeder.php @@ -0,0 +1,18 @@ + +// +---------------------------------------------------------------------- +namespace think\migration; + +use Phinx\Seed\AbstractSeed; + +class Seeder extends AbstractSeed +{ + +} diff --git a/vendor/topthink/think-migration/src/Service.php b/vendor/topthink/think-migration/src/Service.php new file mode 100644 index 0000000..614e9e0 --- /dev/null +++ b/vendor/topthink/think-migration/src/Service.php @@ -0,0 +1,51 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration; + +use Faker\Factory as FakerFactory; +use Faker\Generator as FakerGenerator; +use think\migration\command\factory\Create as FactoryCreate; +use think\migration\command\migrate\Breakpoint as MigrateBreakpoint; +use think\migration\command\migrate\Create as MigrateCreate; +use think\migration\command\migrate\Rollback as MigrateRollback; +use think\migration\command\migrate\Run as MigrateRun; +use think\migration\command\migrate\Status as MigrateStatus; +use think\migration\command\seed\Create as SeedCreate; +use think\migration\command\seed\Run as SeedRun; + +class Service extends \think\Service +{ + + public function boot() + { + $this->app->bind(FakerGenerator::class, function () { + return FakerFactory::create($this->app->config->get('app.faker_locale', 'zh_CN')); + }); + + $this->app->bind(Factory::class, function () { + return (new Factory($this->app->make(FakerGenerator::class)))->load($this->app->getRootPath() . 'database/factories/'); + }); + + $this->app->bind('migration.creator', Creator::class); + + $this->commands([ + MigrateCreate::class, + MigrateRun::class, + MigrateRollback::class, + MigrateBreakpoint::class, + MigrateStatus::class, + SeedCreate::class, + SeedRun::class, + FactoryCreate::class, + ]); + } +} diff --git a/vendor/topthink/think-migration/src/command/Migrate.php b/vendor/topthink/think-migration/src/command/Migrate.php new file mode 100644 index 0000000..1d97973 --- /dev/null +++ b/vendor/topthink/think-migration/src/command/Migrate.php @@ -0,0 +1,146 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\command; + +use Phinx\Db\Adapter\AdapterFactory; +use Phinx\Db\Adapter\ProxyAdapter; +use Phinx\Migration\AbstractMigration; +use Phinx\Migration\MigrationInterface; +use Phinx\Util\Util; +use think\migration\Command; +use think\migration\Migrator; + +abstract class Migrate extends Command +{ + /** + * @var array + */ + protected $migrations; + + protected function getPath() + { + return $this->app->getRootPath() . 'database' . DIRECTORY_SEPARATOR . 'migrations'; + } + + protected function executeMigration(MigrationInterface $migration, $direction = MigrationInterface::UP) + { + $this->output->writeln(''); + $this->output->writeln(' ==' . ' ' . $migration->getVersion() . ' ' . $migration->getName() . ':' . ' ' . (MigrationInterface::UP === $direction ? 'migrating' : 'reverting') . ''); + + // Execute the migration and log the time elapsed. + $start = microtime(true); + + $startTime = time(); + $direction = (MigrationInterface::UP === $direction) ? MigrationInterface::UP : MigrationInterface::DOWN; + $migration->setAdapter($this->getAdapter()); + + // begin the transaction if the adapter supports it + if ($this->getAdapter()->hasTransactions()) { + $this->getAdapter()->beginTransaction(); + } + + // Run the migration + if (method_exists($migration, MigrationInterface::CHANGE)) { + if (MigrationInterface::DOWN === $direction) { + // Create an instance of the ProxyAdapter so we can record all + // of the migration commands for reverse playback + /** @var ProxyAdapter $proxyAdapter */ + $proxyAdapter = AdapterFactory::instance()->getWrapper('proxy', $this->getAdapter()); + $migration->setAdapter($proxyAdapter); + /** @noinspection PhpUndefinedMethodInspection */ + $migration->change(); + $proxyAdapter->executeInvertedCommands(); + $migration->setAdapter($this->getAdapter()); + } else { + /** @noinspection PhpUndefinedMethodInspection */ + $migration->change(); + } + } else { + $migration->{$direction}(); + } + + // commit the transaction if the adapter supports it + if ($this->getAdapter()->hasTransactions()) { + $this->getAdapter()->commitTransaction(); + } + + // Record it in the database + $this->getAdapter() + ->migrated($migration, $direction, date('Y-m-d H:i:s', $startTime), date('Y-m-d H:i:s', time())); + + $end = microtime(true); + + $this->output->writeln(' ==' . ' ' . $migration->getVersion() . ' ' . $migration->getName() . ':' . ' ' . (MigrationInterface::UP === $direction ? 'migrated' : 'reverted') . ' ' . sprintf('%.4fs', $end - $start) . ''); + } + + protected function getVersionLog() + { + return $this->getAdapter()->getVersionLog(); + } + + protected function getVersions() + { + return $this->getAdapter()->getVersions(); + } + + protected function getMigrations() + { + if (null === $this->migrations) { + $phpFiles = glob($this->getPath() . DIRECTORY_SEPARATOR . '*.php', defined('GLOB_BRACE') ? GLOB_BRACE : 0); + + // filter the files to only get the ones that match our naming scheme + $fileNames = []; + /** @var Migrator[] $versions */ + $versions = []; + + foreach ($phpFiles as $filePath) { + if (Util::isValidMigrationFileName(basename($filePath))) { + $version = Util::getVersionFromFileName(basename($filePath)); + + if (isset($versions[$version])) { + throw new \InvalidArgumentException(sprintf('Duplicate migration - "%s" has the same version as "%s"', $filePath, $versions[$version]->getVersion())); + } + + // convert the filename to a class name + $class = Util::mapFileNameToClassName(basename($filePath)); + + if (isset($fileNames[$class])) { + throw new \InvalidArgumentException(sprintf('Migration "%s" has the same name as "%s"', basename($filePath), $fileNames[$class])); + } + + $fileNames[$class] = basename($filePath); + + // load the migration file + /** @noinspection PhpIncludeInspection */ + require_once $filePath; + if (!class_exists($class)) { + throw new \InvalidArgumentException(sprintf('Could not find class "%s" in file "%s"', $class, $filePath)); + } + + // instantiate it + $migration = new $class($version, $this->input, $this->output); + + if (!($migration instanceof AbstractMigration)) { + throw new \InvalidArgumentException(sprintf('The class "%s" in file "%s" must extend \Phinx\Migration\AbstractMigration', $class, $filePath)); + } + + $versions[$version] = $migration; + } + } + + ksort($versions); + $this->migrations = $versions; + } + + return $this->migrations; + } +} diff --git a/vendor/topthink/think-migration/src/command/Seed.php b/vendor/topthink/think-migration/src/command/Seed.php new file mode 100644 index 0000000..e7a2c06 --- /dev/null +++ b/vendor/topthink/think-migration/src/command/Seed.php @@ -0,0 +1,73 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\command; + +use InvalidArgumentException; +use Phinx\Seed\AbstractSeed; +use Phinx\Util\Util; +use think\migration\Command; +use think\migration\Seeder; + +abstract class Seed extends Command +{ + + /** + * @var array + */ + protected $seeds; + + protected function getPath() + { + return $this->app->getRootPath() . 'database' . DIRECTORY_SEPARATOR . 'seeds'; + } + + public function getSeeds() + { + if (null === $this->seeds) { + $phpFiles = glob($this->getPath() . DIRECTORY_SEPARATOR . '*.php', defined('GLOB_BRACE') ? GLOB_BRACE : 0); + + // filter the files to only get the ones that match our naming scheme + $fileNames = []; + /** @var Seeder[] $seeds */ + $seeds = []; + + foreach ($phpFiles as $filePath) { + if (Util::isValidSeedFileName(basename($filePath))) { + // convert the filename to a class name + $class = pathinfo($filePath, PATHINFO_FILENAME); + $fileNames[$class] = basename($filePath); + + // load the seed file + /** @noinspection PhpIncludeInspection */ + require_once $filePath; + if (!class_exists($class)) { + throw new InvalidArgumentException(sprintf('Could not find class "%s" in file "%s"', $class, $filePath)); + } + + // instantiate it + $seed = new $class($this->input, $this->output); + + if (!($seed instanceof AbstractSeed)) { + throw new InvalidArgumentException(sprintf('The class "%s" in file "%s" must extend \Phinx\Seed\AbstractSeed', $class, $filePath)); + } + + $seeds[$class] = $seed; + } + } + + ksort($seeds); + $this->seeds = $seeds; + } + + return $this->seeds; + } +} diff --git a/vendor/topthink/think-migration/src/command/factory/Create.php b/vendor/topthink/think-migration/src/command/factory/Create.php new file mode 100644 index 0000000..3db6497 --- /dev/null +++ b/vendor/topthink/think-migration/src/command/factory/Create.php @@ -0,0 +1,82 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\command\factory; + +use InvalidArgumentException; +use Phinx\Util\Util; +use RuntimeException; +use think\console\Command; +use think\console\input\Argument as InputArgument; + +class Create extends Command +{ + protected function configure() + { + $this->setName('factory:create') + ->setDescription('Create a new model factory') + ->addArgument('name', InputArgument::REQUIRED, 'What is the name of the model?'); + } + + protected function handle() + { + $path = $this->getPath(); + + if (!file_exists($path)) { + mkdir($path, 0755, true); + } + + if (!is_dir($path)) { + throw new InvalidArgumentException(sprintf('Factory directory "%s" does not exist', $path)); + } + + if (!is_writable($path)) { + throw new InvalidArgumentException(sprintf('Factory directory "%s" is not writable', $path)); + } + + $path = realpath($path); + $className = $this->input->getArgument('name'); + + if (!Util::isValidPhinxClassName($className)) { + throw new InvalidArgumentException(sprintf('The migration class name "%s" is invalid. Please use CamelCase format.', $className)); + } + + $filePath = $path . DIRECTORY_SEPARATOR . $className . '.php'; + + if (is_file($filePath)) { + throw new InvalidArgumentException(sprintf('The file "%s" already exists', $filePath)); + } + + // Load the alternative template if it is defined. + $contents = file_get_contents($this->getTemplate()); + + // inject the class names appropriate to this migration + $contents = strtr($contents, [ + '"ModelClass"' => "\\app\\model\\" . $className . '::class', + ]); + + if (false === file_put_contents($filePath, $contents)) { + throw new RuntimeException(sprintf('The file "%s" could not be written to', $path)); + } + + $this->output->writeln('created .' . str_replace(getcwd(), '', $filePath)); + } + + protected function getTemplate() + { + return __DIR__ . '/../stubs/factory.stub'; + } + + protected function getPath() + { + return $this->app->getRootPath() . 'database' . DIRECTORY_SEPARATOR . 'factories'; + } +} diff --git a/vendor/topthink/think-migration/src/command/migrate/Breakpoint.php b/vendor/topthink/think-migration/src/command/migrate/Breakpoint.php new file mode 100644 index 0000000..cfb2d32 --- /dev/null +++ b/vendor/topthink/think-migration/src/command/migrate/Breakpoint.php @@ -0,0 +1,92 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\command\migrate; + +use think\console\Input; +use think\console\input\Option as InputOption; +use think\console\Output; +use think\migration\command\Migrate; + +class Breakpoint extends Migrate +{ + protected function configure() + { + $this->setName('migrate:breakpoint') + ->setDescription('Manage breakpoints') + ->addOption('--target', '-t', InputOption::VALUE_REQUIRED, 'The version number to set or clear a breakpoint against') + ->addOption('--remove-all', '-r', InputOption::VALUE_NONE, 'Remove all breakpoints') + ->setHelp(<<breakpoint command allows you to set or clear a breakpoint against a specific target to inhibit rollbacks beyond a certain target. +If no target is supplied then the most recent migration will be used. +You cannot specify un-migrated targets + +php think migrate:breakpoint +php think migrate:breakpoint -t 20110103081132 +php think migrate:breakpoint -r +EOT + ); + } + + protected function execute(Input $input, Output $output) + { + $version = $input->getOption('target'); + $removeAll = $input->getOption('remove-all'); + + if ($version && $removeAll) { + throw new \InvalidArgumentException('Cannot toggle a breakpoint and remove all breakpoints at the same time.'); + } + + // Remove all breakpoints + if ($removeAll) { + $this->removeBreakpoints(); + } else { + // Toggle the breakpoint. + $this->toggleBreakpoint($version); + } + } + + protected function toggleBreakpoint($version) + { + $migrations = $this->getMigrations(); + $versions = $this->getVersionLog(); + + if (empty($versions) || empty($migrations)) { + return; + } + + if (null === $version) { + $lastVersion = end($versions); + $version = $lastVersion['version']; + } + + if (0 != $version && !isset($migrations[$version])) { + $this->output->writeln(sprintf('warning %s is not a valid version', $version)); + return; + } + + $this->getAdapter()->toggleBreakpoint($migrations[$version]); + + $versions = $this->getVersionLog(); + + $this->output->writeln(' Breakpoint ' . ($versions[$version]['breakpoint'] ? 'set' : 'cleared') . ' for ' . $version . '' . ' ' . $migrations[$version]->getName() . ''); + } + + /** + * Remove all breakpoints + * + * @return void + */ + protected function removeBreakpoints() + { + $this->output->writeln(sprintf(' %d breakpoints cleared.', $this->getAdapter()->resetAllBreakpoints())); + } +} diff --git a/vendor/topthink/think-migration/src/command/migrate/Create.php b/vendor/topthink/think-migration/src/command/migrate/Create.php new file mode 100644 index 0000000..8f96de7 --- /dev/null +++ b/vendor/topthink/think-migration/src/command/migrate/Create.php @@ -0,0 +1,55 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\command\migrate; + +use InvalidArgumentException; +use RuntimeException; +use think\console\Command; +use think\console\Input; +use think\console\input\Argument as InputArgument; +use think\console\Output; +use think\migration\Creator; + +class Create extends Command +{ + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('migrate:create') + ->setDescription('Create a new migration') + ->addArgument('name', InputArgument::REQUIRED, 'What is the name of the migration?') + ->setHelp(sprintf('%sCreates a new database migration%s', PHP_EOL, PHP_EOL)); + } + + /** + * Create the new migration. + * + * @param Input $input + * @param Output $output + * @return void + * @throws InvalidArgumentException + * @throws RuntimeException + */ + protected function execute(Input $input, Output $output) + { + /** @var Creator $creator */ + $creator = $this->app->get('migration.creator'); + + $className = $input->getArgument('name'); + + $path = $creator->create($className); + + $output->writeln('created .' . str_replace(getcwd(), '', realpath($path))); + } + +} diff --git a/vendor/topthink/think-migration/src/command/migrate/Rollback.php b/vendor/topthink/think-migration/src/command/migrate/Rollback.php new file mode 100644 index 0000000..e668ac7 --- /dev/null +++ b/vendor/topthink/think-migration/src/command/migrate/Rollback.php @@ -0,0 +1,146 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\command\migrate; + +use Phinx\Migration\MigrationInterface; +use think\console\input\Option as InputOption; +use think\console\Input; +use think\console\Output; +use think\migration\command\Migrate; + +class Rollback extends Migrate +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('migrate:rollback') + ->setDescription('Rollback the last or to a specific migration') + ->addOption('--target', '-t', InputOption::VALUE_REQUIRED, 'The version number to rollback to') + ->addOption('--date', '-d', InputOption::VALUE_REQUIRED, 'The date to rollback to') + ->addOption('--force', '-f', InputOption::VALUE_NONE, 'Force rollback to ignore breakpoints') + ->setHelp(<<migrate:rollback command reverts the last migration, or optionally up to a specific version + +php think migrate:rollback +php think migrate:rollback -t 20111018185412 +php think migrate:rollback -d 20111018 +php think migrate:rollback -v + +EOT + ); + } + + /** + * Rollback the migration. + * + * @param Input $input + * @param Output $output + * @return void + */ + protected function execute(Input $input, Output $output) + { + $version = $input->getOption('target'); + $date = $input->getOption('date'); + $force = !!$input->getOption('force'); + + // rollback the specified environment + $start = microtime(true); + if (null !== $date) { + $this->rollbackToDateTime(new \DateTime($date), $force); + } else { + $this->rollback($version, $force); + } + $end = microtime(true); + + $output->writeln(''); + $output->writeln('All Done. Took ' . sprintf('%.4fs', $end - $start) . ''); + } + + protected function rollback($version = null, $force = false) + { + $migrations = $this->getMigrations(); + $versionLog = $this->getVersionLog(); + $versions = array_keys($versionLog); + + ksort($migrations); + sort($versions); + + // Check we have at least 1 migration to revert + if (empty($versions) || $version == end($versions)) { + $this->output->writeln('No migrations to rollback'); + return; + } + + // If no target version was supplied, revert the last migration + if (null === $version) { + // Get the migration before the last run migration + $prev = count($versions) - 2; + $version = $prev < 0 ? 0 : $versions[$prev]; + } else { + // Get the first migration number + $first = $versions[0]; + + // If the target version is before the first migration, revert all migrations + if ($version < $first) { + $version = 0; + } + } + + // Check the target version exists + if (0 !== $version && !isset($migrations[$version])) { + $this->output->writeln("Target version ($version) not found"); + return; + } + + // Revert the migration(s) + krsort($migrations); + foreach ($migrations as $migration) { + if ($migration->getVersion() <= $version) { + break; + } + + if (in_array($migration->getVersion(), $versions)) { + if (isset($versionLog[$migration->getVersion()]) && 0 != $versionLog[$migration->getVersion()]['breakpoint'] && !$force) { + $this->output->writeln('Breakpoint reached. Further rollbacks inhibited.'); + break; + } + $this->executeMigration($migration, MigrationInterface::DOWN); + } + } + } + + protected function rollbackToDateTime(\DateTime $dateTime, $force = false) + { + $versions = $this->getVersions(); + $dateString = $dateTime->format('YmdHis'); + sort($versions); + + $earlierVersion = null; + $availableMigrations = array_filter($versions, function ($version) use ($dateString, &$earlierVersion) { + if ($version <= $dateString) { + $earlierVersion = $version; + } + return $version >= $dateString; + }); + + if (count($availableMigrations) > 0) { + if (is_null($earlierVersion)) { + $this->output->writeln('Rolling back all migrations'); + $migration = 0; + } else { + $this->output->writeln('Rolling back to version ' . $earlierVersion); + $migration = $earlierVersion; + } + $this->rollback($migration, $force); + } + } +} diff --git a/vendor/topthink/think-migration/src/command/migrate/Run.php b/vendor/topthink/think-migration/src/command/migrate/Run.php new file mode 100644 index 0000000..4714f02 --- /dev/null +++ b/vendor/topthink/think-migration/src/command/migrate/Run.php @@ -0,0 +1,140 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\command\migrate; + +use Phinx\Migration\MigrationInterface; +use think\console\Input; +use think\console\input\Option as InputOption; +use think\console\Output; +use think\migration\command\Migrate; + +class Run extends Migrate +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('migrate:run') + ->setDescription('Migrate the database') + ->addOption('--target', '-t', InputOption::VALUE_REQUIRED, 'The version number to migrate to') + ->addOption('--date', '-d', InputOption::VALUE_REQUIRED, 'The date to migrate to') + ->setHelp(<<migrate:run command runs all available migrations, optionally up to a specific version + +php think migrate:run +php think migrate:run -t 20110103081132 +php think migrate:run -d 20110103 +php think migrate:run -v + +EOT + ); + } + + /** + * Migrate the database. + * + * @param Input $input + * @param Output $output + */ + protected function execute(Input $input, Output $output) + { + $version = $input->getOption('target'); + $date = $input->getOption('date'); + + // run the migrations + $start = microtime(true); + if (null !== $date) { + $this->migrateToDateTime(new \DateTime($date)); + } else { + $this->migrate($version); + } + $end = microtime(true); + + $output->writeln(''); + $output->writeln('All Done. Took ' . sprintf('%.4fs', $end - $start) . ''); + } + + public function migrateToDateTime(\DateTime $dateTime) + { + $versions = array_keys($this->getMigrations()); + $dateString = $dateTime->format('YmdHis'); + + $outstandingMigrations = array_filter($versions, function ($version) use ($dateString) { + return $version <= $dateString; + }); + + if (count($outstandingMigrations) > 0) { + $migration = max($outstandingMigrations); + $this->output->writeln('Migrating to version ' . $migration); + $this->migrate($migration); + } + } + + protected function migrate($version = null) + { + $migrations = $this->getMigrations(); + $versions = $this->getVersions(); + $current = $this->getCurrentVersion(); + + if (empty($versions) && empty($migrations)) { + return; + } + + if (null === $version) { + $version = max(array_merge($versions, array_keys($migrations))); + } else { + if (0 != $version && !isset($migrations[$version])) { + $this->output->writeln(sprintf('warning %s is not a valid version', $version)); + return; + } + } + + // are we migrating up or down? + $direction = $version > $current ? MigrationInterface::UP : MigrationInterface::DOWN; + + if ($direction === MigrationInterface::DOWN) { + // run downs first + krsort($migrations); + foreach ($migrations as $migration) { + if ($migration->getVersion() <= $version) { + break; + } + + if (in_array($migration->getVersion(), $versions)) { + $this->executeMigration($migration, MigrationInterface::DOWN); + } + } + } + + ksort($migrations); + foreach ($migrations as $migration) { + if ($migration->getVersion() > $version) { + break; + } + + if (!in_array($migration->getVersion(), $versions)) { + $this->executeMigration($migration, MigrationInterface::UP); + } + } + } + + protected function getCurrentVersion() + { + $versions = $this->getVersions(); + $version = 0; + + if (!empty($versions)) { + $version = end($versions); + } + + return $version; + } +} diff --git a/vendor/topthink/think-migration/src/command/migrate/Status.php b/vendor/topthink/think-migration/src/command/migrate/Status.php new file mode 100644 index 0000000..1b5f8d4 --- /dev/null +++ b/vendor/topthink/think-migration/src/command/migrate/Status.php @@ -0,0 +1,124 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\command\migrate; + +use think\console\input\Option as InputOption; +use think\console\Input; +use think\console\Output; +use think\migration\command\Migrate; + +class Status extends Migrate +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('migrate:status') + ->setDescription('Show migration status') + ->addOption('--format', '-f', InputOption::VALUE_REQUIRED, 'The output format: text or json. Defaults to text.') + ->setHelp(<<migrate:status command prints a list of all migrations, along with their current status + +php think migrate:status +php think migrate:status -f json +EOT + ); + } + + /** + * Show the migration status. + * + * @param Input $input + * @param Output $output + * @return integer 0 if all migrations are up, or an error code + */ + protected function execute(Input $input, Output $output) + { + $format = $input->getOption('format'); + + if (null !== $format) { + $output->writeln('using format ' . $format); + } + + // print the status + return $this->printStatus($format); + } + + protected function printStatus($format = null) + { + $output = $this->output; + $migrations = []; + if (count($this->getMigrations())) { + // TODO - rewrite using Symfony Table Helper as we already have this library + // included and it will fix formatting issues (e.g drawing the lines) + $output->writeln(''); + $output->writeln(' Status Migration ID Started Finished Migration Name '); + $output->writeln('----------------------------------------------------------------------------------'); + + $versions = $this->getVersionLog(); + $maxNameLength = $versions ? max(array_map(function ($version) { + return strlen($version['migration_name']); + }, $versions)) : 0; + + foreach ($this->getMigrations() as $migration) { + $version = array_key_exists($migration->getVersion(), $versions) ? $versions[$migration->getVersion()] : false; + if ($version) { + $status = ' up '; + } else { + $status = ' down '; + } + $maxNameLength = max($maxNameLength, strlen($migration->getName())); + + $output->writeln(sprintf('%s %14.0f %19s %19s %s', $status, $migration->getVersion(), $version['start_time'], $version['end_time'], $migration->getName())); + + if ($version && $version['breakpoint']) { + $output->writeln(' BREAKPOINT SET'); + } + + $migrations[] = [ + 'migration_status' => trim(strip_tags($status)), + 'migration_id' => sprintf('%14.0f', $migration->getVersion()), + 'migration_name' => $migration->getName() + ]; + unset($versions[$migration->getVersion()]); + } + + if (count($versions)) { + foreach ($versions as $missing => $version) { + $output->writeln(sprintf(' up %14.0f %19s %19s %s ** MISSING **', $missing, $version['start_time'], $version['end_time'], str_pad($version['migration_name'], $maxNameLength, ' '))); + + if ($version && $version['breakpoint']) { + $output->writeln(' BREAKPOINT SET'); + } + } + } + } else { + // there are no migrations + $output->writeln(''); + $output->writeln('There are no available migrations. Try creating one using the create command.'); + } + + // write an empty line + $output->writeln(''); + if ($format !== null) { + switch ($format) { + case 'json': + $output->writeln(json_encode([ + 'pending_count' => count($this->getMigrations()), + 'migrations' => $migrations + ])); + break; + default: + $output->writeln('Unsupported format: ' . $format . ''); + } + } + } +} diff --git a/vendor/topthink/think-migration/src/command/seed/Create.php b/vendor/topthink/think-migration/src/command/seed/Create.php new file mode 100644 index 0000000..6873980 --- /dev/null +++ b/vendor/topthink/think-migration/src/command/seed/Create.php @@ -0,0 +1,83 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\command\seed; + +use Phinx\Util\Util; +use think\console\Input; +use think\console\input\Argument as InputArgument; +use think\console\Output; +use think\migration\command\Seed; + +class Create extends Seed +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('seed:create') + ->setDescription('Create a new database seeder') + ->addArgument('name', InputArgument::REQUIRED, 'What is the name of the seeder?') + ->setHelp(sprintf('%sCreates a new database seeder%s', PHP_EOL, PHP_EOL)); + } + + /** + * Create the new seeder. + * + * @param Input $input + * @param Output $output + * @return void + * @throws \InvalidArgumentException + * @throws \RuntimeException + */ + protected function execute(Input $input, Output $output) + { + $path = $this->getPath(); + + if (!file_exists($path)) { + mkdir($path, 0755, true); + } + + $this->verifyMigrationDirectory($path); + + $path = realpath($path); + + $className = $input->getArgument('name'); + + if (!Util::isValidPhinxClassName($className)) { + throw new \InvalidArgumentException(sprintf('The seed class name "%s" is invalid. Please use CamelCase format', $className)); + } + + // Compute the file path + $filePath = $path . DIRECTORY_SEPARATOR . $className . '.php'; + + if (is_file($filePath)) { + throw new \InvalidArgumentException(sprintf('The file "%s" already exists', basename($filePath))); + } + + // inject the class names appropriate to this seeder + $contents = file_get_contents($this->getTemplate()); + $classes = [ + 'SeederClass' => $className, + ]; + $contents = strtr($contents, $classes); + + if (false === file_put_contents($filePath, $contents)) { + throw new \RuntimeException(sprintf('The file "%s" could not be written to', $path)); + } + + $output->writeln('created .' . str_replace(getcwd(), '', $filePath)); + } + + protected function getTemplate() + { + return __DIR__ . '/../stubs/seed.stub'; + } +} diff --git a/vendor/topthink/think-migration/src/command/seed/Run.php b/vendor/topthink/think-migration/src/command/seed/Run.php new file mode 100644 index 0000000..a0ac8f9 --- /dev/null +++ b/vendor/topthink/think-migration/src/command/seed/Run.php @@ -0,0 +1,107 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\command\seed; + +use Phinx\Seed\SeedInterface; +use think\console\Input; +use think\console\input\Option as InputOption; +use think\console\Output; +use think\migration\command\Seed; + +class Run extends Seed +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('seed:run') + ->setDescription('Run database seeders') + ->addOption('--seed', '-s', InputOption::VALUE_REQUIRED, 'What is the name of the seeder?') + ->setHelp(<<seed:run command runs all available or individual seeders + +php think seed:run +php think seed:run -s UserSeeder +php think seed:run -v + +EOT + ); + } + + /** + * Run database seeders. + * + * @param Input $input + * @param Output $output + * @return void + */ + protected function execute(Input $input, Output $output) + { + $seed = $input->getOption('seed'); + + // run the seed(ers) + $start = microtime(true); + $this->seed($seed); + $end = microtime(true); + + $output->writeln(''); + $output->writeln('All Done. Took ' . sprintf('%.4fs', $end - $start) . ''); + } + + public function seed($seed = null) + { + $seeds = $this->getSeeds(); + + if (null === $seed) { + // run all seeders + foreach ($seeds as $seeder) { + if (array_key_exists($seeder->getName(), $seeds)) { + $this->executeSeed($seeder); + } + } + } else { + // run only one seeder + if (array_key_exists($seed, $seeds)) { + $this->executeSeed($seeds[$seed]); + } else { + throw new \InvalidArgumentException(sprintf('The seed class "%s" does not exist', $seed)); + } + } + } + + protected function executeSeed(SeedInterface $seed) + { + $this->output->writeln(''); + $this->output->writeln(' ==' . ' ' . $seed->getName() . ':' . ' seeding'); + + // Execute the seeder and log the time elapsed. + $start = microtime(true); + $seed->setAdapter($this->getAdapter()); + + // begin the transaction if the adapter supports it + if ($this->getAdapter()->hasTransactions()) { + $this->getAdapter()->beginTransaction(); + } + + // Run the seeder + if (method_exists($seed, SeedInterface::RUN)) { + $seed->run(); + } + + // commit the transaction if the adapter supports it + if ($this->getAdapter()->hasTransactions()) { + $this->getAdapter()->commitTransaction(); + } + $end = microtime(true); + + $this->output->writeln(' ==' . ' ' . $seed->getName() . ':' . ' seeded' . ' ' . sprintf('%.4fs', $end - $start) . ''); + } +} diff --git a/vendor/topthink/think-migration/src/command/stubs/factory.stub b/vendor/topthink/think-migration/src/command/stubs/factory.stub new file mode 100644 index 0000000..eef91dc --- /dev/null +++ b/vendor/topthink/think-migration/src/command/stubs/factory.stub @@ -0,0 +1,11 @@ +define("ModelClass", function (Faker $faker) { + return [ + // + ]; +}); diff --git a/vendor/topthink/think-migration/src/command/stubs/migrate.stub b/vendor/topthink/think-migration/src/command/stubs/migrate.stub new file mode 100644 index 0000000..f11d29a --- /dev/null +++ b/vendor/topthink/think-migration/src/command/stubs/migrate.stub @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\db; + +use Phinx\Db\Adapter\AdapterInterface; +use Phinx\Db\Adapter\MysqlAdapter; + +class Column extends \Phinx\Db\Table\Column +{ + protected $unique = false; + + public function setNullable() + { + return $this->setNull(true); + } + + public function setUnsigned() + { + return $this->setSigned(false); + } + + public function setUnique() + { + $this->unique = true; + return $this; + } + + public function getUnique() + { + return $this->unique; + } + + public function isUnique() + { + return $this->getUnique(); + } + + public static function make($name, $type, $options = []) + { + $column = new self(); + $column->setName($name); + $column->setType($type); + $column->setOptions($options); + return $column; + } + + public static function bigInteger($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_BIG_INTEGER); + } + + public static function binary($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_BLOB); + } + + public static function boolean($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_BOOLEAN); + } + + public static function char($name, $length = 255) + { + return self::make($name, AdapterInterface::PHINX_TYPE_CHAR, compact('length')); + } + + public static function date($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_DATE); + } + + public static function dateTime($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_DATETIME); + } + + public static function decimal($name, $precision = 8, $scale = 2) + { + return self::make($name, AdapterInterface::PHINX_TYPE_DECIMAL, compact('precision', 'scale')); + } + + public static function enum($name, array $values) + { + return self::make($name, AdapterInterface::PHINX_TYPE_ENUM, compact('values')); + } + + public static function float($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_FLOAT); + } + + public static function integer($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_INTEGER); + } + + public static function json($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_JSON); + } + + public static function jsonb($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_JSONB); + } + + public static function longText($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_TEXT, ['length' => MysqlAdapter::TEXT_LONG]); + } + + public static function mediumInteger($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_INTEGER, ['length' => MysqlAdapter::INT_MEDIUM]); + } + + public static function mediumText($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_TEXT, ['length' => MysqlAdapter::TEXT_MEDIUM]); + } + + public static function smallInteger($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_INTEGER, ['length' => MysqlAdapter::INT_SMALL]); + } + + public static function string($name, $length = 255) + { + return self::make($name, AdapterInterface::PHINX_TYPE_STRING, compact('length')); + } + + public static function text($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_TEXT); + } + + public static function time($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_TIME); + } + + public static function tinyInteger($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_INTEGER, ['length' => MysqlAdapter::INT_TINY]); + } + + public static function unsignedInteger($name) + { + return self::integer($name)->setUnSigned(); + } + + public static function timestamp($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_TIMESTAMP); + } + + public static function uuid($name) + { + return self::make($name, AdapterInterface::PHINX_TYPE_UUID); + } + +} diff --git a/vendor/topthink/think-migration/src/db/Table.php b/vendor/topthink/think-migration/src/db/Table.php new file mode 100644 index 0000000..cf4daa9 --- /dev/null +++ b/vendor/topthink/think-migration/src/db/Table.php @@ -0,0 +1,135 @@ + +// +---------------------------------------------------------------------- + +namespace think\migration\db; + +use Phinx\Db\Table\Index; + +class Table extends \Phinx\Db\Table +{ + /** + * 设置id + * @param $id + * @return $this + */ + public function setId($id) + { + $this->options['id'] = $id; + return $this; + } + + /** + * 设置主键 + * @param $key + * @return $this + */ + public function setPrimaryKey($key) + { + $this->options['primary_key'] = $key; + return $this; + } + + /** + * 设置引擎 + * @param $engine + * @return $this + */ + public function setEngine($engine) + { + $this->options['engine'] = $engine; + return $this; + } + + /** + * 设置表注释 + * @param $comment + * @return $this + */ + public function setComment($comment) + { + $this->options['comment'] = $comment; + return $this; + } + + /** + * 设置排序比对方法 + * @param $collation + * @return $this + */ + public function setCollation($collation) + { + $this->options['collation'] = $collation; + return $this; + } + + public function addSoftDelete() + { + $this->addColumn(Column::timestamp('delete_time')->setNullable()); + return $this; + } + + public function addMorphs($name, $indexName = null) + { + $this->addColumn(Column::unsignedInteger("{$name}_id")); + $this->addColumn(Column::string("{$name}_type")); + $this->addIndex(["{$name}_id", "{$name}_type"], ['name' => $indexName]); + return $this; + } + + public function addNullableMorphs($name, $indexName = null) + { + $this->addColumn(Column::unsignedInteger("{$name}_id")->setNullable()); + $this->addColumn(Column::string("{$name}_type")->setNullable()); + $this->addIndex(["{$name}_id", "{$name}_type"], ['name' => $indexName]); + return $this; + } + + /** + * @param string $createdAtColumnName + * @param string $updatedAtColumnName + * @return \Phinx\Db\Table|Table + */ + public function addTimestamps($createdAtColumnName = 'create_time', $updatedAtColumnName = 'update_time') + { + return parent::addTimestamps($createdAtColumnName, $updatedAtColumnName); + } + + /** + * @param \Phinx\Db\Table\Column|string $columnName + * @param null $type + * @param array $options + * @return \Phinx\Db\Table|Table + */ + public function addColumn($columnName, $type = null, $options = []) + { + if ($columnName instanceof Column && $columnName->getUnique()) { + $index = new Index(); + $index->setColumns([$columnName->getName()]); + $index->setType(Index::UNIQUE); + $this->addIndex($index); + } + return parent::addColumn($columnName, $type, $options); + } + + /** + * @param string $columnName + * @param null $newColumnType + * @param array $options + * @return \Phinx\Db\Table|Table + */ + public function changeColumn($columnName, $newColumnType = null, $options = []) + { + if ($columnName instanceof \Phinx\Db\Table\Column) { + return parent::changeColumn($columnName->getName(), $columnName, $options); + } + return parent::changeColumn($columnName, $newColumnType, $options); + } +} diff --git a/vendor/topthink/think-migration/src/helper.php b/vendor/topthink/think-migration/src/helper.php new file mode 100644 index 0000000..6dc47ab --- /dev/null +++ b/vendor/topthink/think-migration/src/helper.php @@ -0,0 +1,40 @@ +of($arguments[0], $arguments[1])->times($arguments[2] ?? null); + } elseif (isset($arguments[1])) { + return $factory->of($arguments[0])->times($arguments[1]); + } + + return $factory->of($arguments[0]); + } +} + +if (!function_exists('database_path')) { + /** + * 获取数据迁移脚本地址 + * @param string $path + * @return string + */ + function database_path($path = '') + { + return app()->getRootPath() . 'database' . DIRECTORY_SEPARATOR . $path; + } +}