json_encode 所有字符串数据的编码必须是 UTF-8,不然返回false
ini_set('session.cookie_lifetime',0);
把安装目录下的composer.phar移动到项目根目录下:
1、找到composer位置的命令:which composer
2、把安装目录下的composer.phar移动到项目根目录下
3、在项目下正常运行安装命令即可
javascript的sessionStorage,在微信关闭时会自动失效,默认设置。这里的关闭不是指彻底关闭微信,而是只要离开页面就会失效!
设置:
sessionStorage.setItem('inviteuid', inviteuid);
获取:
sessionStorage.getItem('inviteuid')
判断是否设置了:
sessionStorage.getItem("inviteuid") != null
if ($a) { } else switch($a) { }
if ($a) { } else do { } while (!$a);
//例如判断数组是否为空后做操作 if (count($array)) { for ($i = 0; $i < count($array); $i++) { } } else { //数组为空的逻辑 } //可以写成 if (count($array) == 0) { //数组为空的逻辑 } else for ($i = 0; $i < count($array); $i++) { }
$a = true; if ($a) { echo “true”; } else label: { echo “false”; } //转化后写法 $a = true; if ($a) { echo "true"; } else { label: ; //单独的一条语句 } echo "false"; //所以运行结果是:truefalse //label不是一个常量可以是任意字符串
unserialize(): Error at offset 0 of 13465 bytes
类似于上面这种报错大多数是因为缓存引起的,建议先彻底清掉缓存试一下,再找其他原因!
var_dump(1...9);//输出10.9
原因:
首先明确一点,var_dump()把1...9识别为了浮点数。
1...9会被依次识别为:1(浮点数1),然后是.(字符串连接符号),然后是.9(浮点数0.9)
function sum(...$numbers) { $acc = 0; foreach ($numbers as $n) { $acc += $n; } return $acc; } echo sum(1, 2, 3, 4);//输出10
function add($a, $b) { return $a + $b; } echo add(...[1, 2]);//输出3 $a = [1, 2]; echo add(...$a);//输出3
PHP 从5.6+开始在用户自定义函数中支持可变数量的参数列表
if (print("1\n") && print("2\n") && print("3\n") && print("4\n")) { ; }//输出:4 111
实际上等同于:
if (print ("1\n" && print ("2\n" && print ("3\n" && print "4\n")))) { ; }
解析:
print并不是一个函数,所以并不要求一定要有小括号(所以即使你写了小括号,括号也会在语法分析阶段被忽略),即:第一个代码块在PHP看来就是第二个代码块的样子。
所以就是:
最先输出4, 然后输出 "3\n" && print的结果1 , 然后输出 "2\n" && 1的结果1, 最后是 "1\n" && 1的结果1。
多数情况直接删除与入口文件同级的user.ini
在Linux系统时,路径中的斜杠必须用(/)而不能用反斜杠(\)否则将识别不到文件。
首先说一下self的限制,使用 self:: 或者 __CLASS__ 对当前类的静态引用,取决于定义当前方法所在的类。
<?php class A { public static function who() { echo __CLASS__; } public static function test() { self::who(); } } class B extends A { public static function who() { echo __CLASS__; } } B::test();//A
以上案例本意是想输出:B,却因为self限制而失败。
后期静态绑定本想通过引入一个新的关键字表示运行时最初调用的类来绕过限制。简单地说,这个关键字能够让你在上述例子中调用 test() 时引用的类是 B 而不是 A。最终决定不引入新的关键字,而是使用已经预留的 static 关键字。
<?php class A { public static function who() { echo __CLASS__; } public static function test() { static::who(); // 后期静态绑定从这里开始 } } class B extends A { public static function who() { echo __CLASS__; } } B::test();//B
在非静态环境下,所调用的类即为该对象实例所属的类。由于 $this-> 会在同一作用范围内尝试调用私有方法,而 static:: 则可能给出不同结果。另一个区别是 static:: 只能用于静态属性。
foo(); static::foo(); } } class B extends A { /* foo() will be copied to B, hence its scope will still be A and * the call be successful */ } class C extends A { private function foo() { /* original method is replaced; the scope of the new one is C */ } } $b = new B(); $b->test(); $c = new C(); $c->test(); //fails
输出结果:
success! success! success! Fatal error: Call to private method C::foo() from context 'A' in /tmp/test.php on line 9
后期静态绑定的解析会一直到取得一个完全解析了的静态调用为止。另一方面,如果静态调用使用 parent:: 或者 self:: 将转发调用信息。
<?php class A { public static function foo() { static::who(); } public static function who() { echo __CLASS__."\n"; } } class B extends A { public static function test() { A::foo(); parent::foo(); self::foo(); } public static function who() { echo __CLASS__."\n"; } } class C extends B { public static function who() { echo __CLASS__."\n"; } } C::test(); //结果: A C C
//快捷查询 Db::table('think_user') ->where('name|title','like','thinkphp%') ->where('create_time&update_time','>',0) ->find(); //sql: SELECT * FROM `think_user` WHERE ( `name` LIKE 'thinkphp%' OR `title` LIKE 'thinkphp%' ) AND ( `create_time` > 0 AND `update_time` > 0 ) LIMIT 1
->whereOr([//第一维代表多个条件 [//第二维之间用or链接 //第三维之间用and连接 ['create_time', '<=', time()], ['status', '=', '1'] ],[ ['id', '>=', 10], ['update_time', '>=', time()], ] ]) //拼凑出来的结果: //where (create_time <= time() and status = 1) //or (update_time >= time() and id >= 10)
适用于多对多,并且有中间表的,例如,不同用户关联不同的兴趣标签,关联数据单独存在一个表中:
建表:
article表(字段:id,……)
label表(字段:id,name,……)
article_label表(字段:id,article_id,label_id,createtime,……)
article模型中:
//方法名随意,调用时使用此方法名 public function Labels(){ //参数1:关联模型 //参数2:中间表表名,不带表前缀 //参数3:中间表 关联 关联模型的外键 //参数4:中间表 关联 当前模型的外键 return $this->belongsToMany('LabelModel', 'article_label','label_id','article_id'); }
控制器中使用:
//查询 $data=ArticleModel::with('Labels')->select(); //使用,因为查询结果为对象,所以调用方式如下: //打印第一条文章数据下第一个标签的名字 dump($data[0]->Labels[0]->name); //接口调用:直接用json_encode()处理后自动变成三维数组 echo json_encode($data); //删除 $data = ArticleModel::find(1);//查询文章信息 $data->Labels()->detach();//删除article_label表中当前文章的信息 //添加 $label_ids=[1,2,3,4,5,6,7,8,9];//需要绑定的label表主键id //参数1是需要绑定的多个label_id,参数2是其他需要补充的字段 $data->Labels()->attach($label_ids,['createtime'=> time()]);//保存
一般用于一条记录对应多条记录,例如某个文章的评论:
建表:
article表(字段:id,……)
comment表(字段:id,article_id,content,……)
article模型中:
//方法名字随意,调用时方法名作为参数使用 public function comments(){ //参数1:关联模型名 //参数2:关联模型外键 //参数3:当前模型表主键 return $this->hasMany('CommentModel','article_id','id'); }
控制器中使用:
$data=ArticleModel::with('comments')->select(); //使用,因为查询结果为对象,所以调用方式如下: //打印第一条文章数据下第一条评论数据的内容 dump($data[0]->comments[0]->content); //接口调用:直接用json_encode()处理后自动变成三维数组 echo json_encode($data);
一般用于多级分类存在同一个表中:
建表:
classify表(字段:id,name,level,pid,……)
classify模型中:
//方法名字随意,调用时方法名作为参数使用 public function secondClassify(){ //参数1:当前模型名 //参数2:上级id(原关联模型外键) //参数3:当前模型表主键 return $this->hasMany('ClassifyModel','pid','id'); }
控制器中使用:
//查询一级分类,自动补全二级 $data=ClassifyModel::with('secondClassify')->where('level',1)->select(); //使用,因为查询结果为对象,所以调用方式如下: //打印第一条一级分类数据下第一个二级分类的名称 dump($data[0]->secondClassify[0]->name); //接口调用:直接用json_encode()处理后自动变成三维数组 echo json_encode($data);
一般用于主记录查询子记录(子表有关联记录在主表),例如一个用户信息只对应一个用户:
建表:
user表(字段:id,message_id,……)
message表(字段:id,user_id,……)
user模型中:
//方法名字随意,调用时方法名作为参数使用 public function user(){ //参数1:关联模型名 //参数2:关联模型外键 //参数3:当前模型主键 return $this->hasOne('UserModel','message_id','id'); }
控制器中使用:
$data=UserModel::with('user')->select(); //使用,因为查询结果为对象,所以调用方式如下: //打印当前用户信息对应的用户生日 dump($data[0]->user->birthday); //接口调用:直接用json_encode()处理后自动变成三维数组 echo json_encode($data);
一般用于主记录查询子记录(子表有一条记录属于主表),例如一个用户只有一条用户信息:
建表:
user表(字段:id,message_id,……)
message表(字段:id,user_id,……)
classify模型中:
//方法名字随意,调用时方法名作为参数使用 public function messages(){ //参数1:关联模型名 //参数2:关联模型外键 //参数3:当前模型主键 return $this->belongsTo('MessageModel','message_id','id'); }
控制器中使用:
$data=UserModel::with('messages')->select(); //使用,因为查询结果为对象,所以调用方式如下: //打印第一条用户数据下子信息的金额 dump($data[0]->messages->money); //接口调用:直接用json_encode()处理后自动变成三维数组 echo json_encode($data);
//取单列:获取所有标签名,输出结构:[id=>name] LabelModel::column('name','id'); //取多列:获取所有标签名和创建时间,输出结构:[id=>[id,name,createtime]] LabelModel::where('status',1)->column('name,createtime','id');
cache()可以用于select、find、value和column方法,以及其衍生方法,使用cache方法后,在缓存有效期之内不会再次进行数据库查询操作,而是直接获取缓存中的数据,关于数据缓存的类型和设置可以参考缓存部分。
1. 简单的存储
//查询news表中id=10的新闻存储于cache中 //写true默认读取配置的中缓存时间 db('news')->cache(true)->find(10); //你也可以自定义时间,60秒表示: db('news')->cache(true,60)->find(10);
2. 指定缓存标识
//等同于使用缓存时的键,默认读取配置的中缓存时间: db('news')->cache('key')->find(15); //全局读取这条数据: $data = \think\Cahce::get('key');
3.支持设置缓存标签:
//缓存键:key,缓存时间:60秒,标签为:tagName db('news')->cache('key',60,'tagName')->find(15); //全局读取带标签的缓存: Db::name('news')->cache('news_list',60,'shx')->select(); $data = \think\Cache::tag('shx')->get('news_list');
封装性:
也称为信息隐藏,就是将一个类的使用和实现分开,只保留部分接口和方法与外部联系,或者说只公开了一些供开发人员使用的方法。于是开发人员只 需要关注这个类如何使用,而不用去关心其具体的实现过程,这样就能实现MVC分工合作,也能有效避免程序间相互依赖,实现代码模块间松藕合。
继承性:
就是子类自动继承其父级类中的属性和方法,并可以添加新的属性和方法或者对部分属性和方法进行重写。继承增加了代码的可重用性。PHP只支持单继承,也就是说一个子类只能有一个父类。
多态性:
子类继承了来自父级类中的属性和方法,并对其中部分方法进行重写。于是多个子类中虽然都具有同一个方法,但是这些子类实例化的对象调用这些相同的方法后却可以获得完全不同的结果,这种技术就是多态性。多态性增强了软件的灵活性。
易维护
采用面向对象思想设计的结构,可读性高,由于继承的存在,即使改变需求,那么维护也只是在局部模块,所以维护起来是非常方便和较低成本的。
质量高
在设计时,可重用现有的,在以前的项目的领域中已被测试过的类使系统满足业务需求并具有较高的质量。
效率高
在软件开发时,根据设计的需要对现实世界的事物进行抽象,产生类。使用这样的方法解决问题,接近于日常生活和自然的思考方式,势必提高软件开发的效率和质量。
易扩展
由于继承、封装、多态的特性,自然设计出高内聚、低耦合的系统结构,使得系统更灵活、更容易扩展,而且成本较低。
正则表达式是一种文本模式,包括普通字符(例如a到z之间的字母)和特殊符号(称为“元字符”)。
正则表达式使用单个字符串来描述、匹配一系列匹配某个语法规则的字符串。
正则表达式是繁琐的,但它是强大的,学会之后的应用会让你提高效率。
*:匹配的字符可以不出现,也可以出现一次或多次(大于等于0)
+:匹配的字符至少出现一次(大于等于1)
?:匹配的字符最多只可以出现一次(0或1)
{n}:代表匹配n次
{n,}:代表至少匹配n次,最多无限
{n,m}:代表至少匹配n次,最多匹配m次
^:匹配输入字符串的开始位置
$:匹配输入字符串的结束位置
\b:匹配一个单词边界,即单词与空格之间的位置(光标所在的位置)
\B:非单词边界匹配
跟在定界符//的后面:
g:匹配出所有符合要求的结果,例:/\d+/g 匹配字符串中所有数字组合
s:使正则表达式中的(.)可以匹配到换行符
i:忽略大小写配匹配,例:/[a-z]/gi 匹配所有字母忽略大小写
m:进行多行匹配,必须配合定位符^和$使用,例:
str="1233asfasfasf\nasdas13232\n54346waqadsafaf\n9999asdasda" str.match(/^\d+/gm)//匹配每行以数字开头的内容 结果: ["1233", "54346", "9999"]
字符 | 描述 |
(pattern) | 匹配 pattern 并获取这一匹配。这是一个获取匹配。 |
(?:pattern) | 匹配 pattern 但不获取匹配结果,这是一个非获取匹配,不进行存储供以后使用,但是消耗字符匹配。 |
(?=pattern) | 正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,不需要获取供以后使用。 |
(?!pattern) | 正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,不需要获取供以后使用。 |
(?<=pattern) | 反向(look behind)肯定预查,与正向肯定预查类似,只是方向相反。 |
(?<!pattern) | 反向否定预查,与正向否定预查类似,只是方向相反。 |
x|y | 匹配 x 或 y。 |
[xyz] | 字符集合。匹配所包含的任意一个字符。 |
[^xyz] | 负值字符集合。匹配未包含的任意字符。 |
[a-z] | 字符范围。匹配指定范围内的任意字符。 |
[^a-z] | 负值字符范围。匹配任何不在指定范围内的任意字符。 |
\d | 匹配一个数字字符。等价于 [0-9]。 |
\D | 匹配一个非数字字符。等价于 [^0-9]。 |
\s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。 |
\S | 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 |
\f | 匹配一个换页符。等价于 \x0c 和 \cL。 |
\n | 匹配一个换行符。等价于 \x0a 和 \cJ。 |
\r | 匹配一个回车符。等价于 \x0d 和 \cM。 |
\t | 匹配一个制表符。等价于 \x09 和 \cI。 |
\v | 匹配一个垂直制表符。等价于 \x0b 和 \cK。 |
\w | 匹配字母、数字、下划线。等价于'[A-Za-z0-9_]'。 |
\W | 匹配非字母、数字、下划线。等价于 '[^A-Za-z0-9_]'。 |
\cx | 匹配由 x 指明的控制字符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 'c' 字符。 |
在使用正则表达式的时候,我们经常会使用()把某个部分括起来,称为一个子模式。
获取匹配:是指系统会在幕后将所有的子模式匹配结果保存起来,供我们查找或者替换。
非获取匹配:是指系统不会保存子模式的匹配结果,子模式的匹配更多的只是作为一种限制条件使用,如正向预查(?=)、反向预查(?<=)、负正向预查(?!)、负反向预查(?<!)
一般放在整个正则表达式的后面。
正向预查的意思是,子模式仅仅作为允许出现的条件限制,并不作为匹配结果输出。
//内容: Windows 95 and Windows 98 are the successor. Then Windows 2000 and Windows Xp appeared. Windows Vista is the Latest version of the family. //正则表达式 /Windows(?= [\d]+\b)/g //匹配结果: Windows Windows Windows //以上三个Windows分别指的是Windows 95、Windows 98、Windows 2000中的Windows。
一般放在整个正则表达式的后面。
负正向预查意思是,子模式仅仅作为不允许出现的条件限制,并不作为匹配结果输出。
//内容: Windows 95 and Windows 98 are the successor. Then Windows 2000 and Windows Xp appeared. Windows Vista is the Latest version of the family. //正则表达式 /Windows(?! [\d]+\b)/g //匹配结果: Windows Windows //以上两个Windows分别指的是Windows Xp、Windows Vista中的Windows。
一般放在整个正则表达式的前面。
反向预查与正向预查很相似,子模式仅仅作为允许出现的条件限制,不作为结果输出。
不同的是,正向预查匹配子模式前面的结果作为匹配结果,而反向预查匹配子模式后面的结果作为匹配结果。
//内容 CNY:100.2 USD:222.1 USD:301.3 HKD:122.1 CNY:114.4 //正则表达式 /(?<=CNY:)\d+\.\d/g //匹配结果: 100.2 114.4
一般放在整个正则表达式的前面。
负反向预查与反向预查很相似,子模式仅仅作为不允许出现的条件限制,不作为结果输出。
不同的是,负正向预查匹配子模式前面的结果作为匹配结果,而负反向预查匹配子模式后面的结果作为匹配结果。
//内容 CNY:100.2 USD:222.1 USD:301.3 HKD:122.1 CNY:114.4 //正则表达式 /(?<!CNY:)\b\d+\.\d/g //匹配结果: 222.1 301.3 122.1
.匹配任意除换行符“\n”外的字符;
*表示匹配前一个字符0次或无限次;
?表示前边字符的0次或1次重复
+或*后跟?表示非贪婪匹配,即尽可能少的匹配,如*?重复任意次,但尽可能少重复;
.*? 表示匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。
如:a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab和ab。不加问号的话匹配的是aabab。
有两种情况:
1. num范围在1-99,表示匹配到第几个括号的重复字符。
具体用法只有:/(.)\1/ 用来匹配两个连续的字符。其他具体用法不明确
2. num是0或者是三位数,表示8进制所对应的ASCII码字符
str="1aa2" str.match(/(.)\141/g) 结果: ["1a"]
\c匹配control+控制字符,就是说匹配由x指明的控制字符。
比如:\cj匹配control+j,代表\n。
\ca \cb \cc ... 又分别匹配 control+a, control+b,control+c....,具体他们等价于什么,看运行的程序了
百度搜索控制字符可以查看所有a-z的匹配
[0-9]+匹配多个数字,[0-9]匹配单个数字,+匹配一个或多个。
{3,15}匹配长度介于3-15字符长度,{2}匹配确定的两次,{1,}至少匹配一次
例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。
例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。
()标记一个表达式的开始和结束位置。
.匹配换行符\n之外的任何单字符。
\将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义字符。
{}标记限定符表达式的开始和结束
|指明两项之间的一个选择,一般配合()使用。
[\u4e00-\u9fa5] //匹配常用中文汉字(繁体字可能匹配不到) [a-z] //匹配所有的小写字母 [A-Z] //匹配所有的大写字母 [a-zA-Z] //匹配所有的字母 [A-z] //匹配所有的字母 [0-9] //匹配所有的数字 [0-9\.\-] //匹配所有的数字,英文句号和减号 [ \f\r\t\n\v] //匹配所有的白字符(空格、换页符、回车符、水平制表符、垂直制表符) [^a-z] //除了小写字母以外的所有字符 [^\\\/\^] //除了(\)(/)(^)之外的所有字符 [^\"\'] //除了双引号(")和单引号(')之外的所有字符 ^[a-zA-Z0-9_]{1,}$ // 所有包含一个以上的字母、数字或下划线组成的字符串 ^[1-9][0-9]{0,}$ // 所有的正整数 ^\-{0,1}[0-9]{1,}$ // 所有的整数 /^[-]?(0{1}|[1-9][0-9]*)(\.[0-9]+)?$/ // 所有的浮点数 /^\w+@\w+\.\w+$/ //邮箱验证
由于章节编号在大的输入文档中会很可能超过九,所以您需要一种方式来处理两位或三位章节编号。下面的正则表达式匹配编号为任何位数的章节标题
/Chapter [1-9][0-9]*/
含义:匹配Chapter 加上一个大于零的数字,[1-9]匹配1到9任何一个数字,[0-9]匹配0-9任何一个数字,*表示前面的表达式[0-9]匹配次数大于等于0。之所以不用+,是因为编号不一定需要两位数或者更多位数,之所以不用?,是因为它会让编号限制在两位数。
下面的正则表达式匹配一个章节标题,该标题只包含两个尾随数字,并且出现在行首:
/^Chapter [1-9][0-9]{0,1}/
真正的章节标题不仅出现行的开始处,而且它还是该行中仅有的文本。它即出现在行首又出现在同一行的结尾。下面的表达式能确保指定的匹配只匹配章节而不匹配交叉引用。通过创建只匹配一行文本的开始和结尾的正则表达式,就可做到这一点。
/^Chapter [1-9][0-9]{0,1}$/
/a/匹配字母a
/7/匹配数字7
/a.c/匹配以a开头,c结尾,中间除了\n以外的换行符。(.可以匹配字符串中打印或非打印字符,除了\n)
/index\.html/匹配index.html(.前面加反斜杠代表就是.)
请注意,没有串联运算符。只须在一个字符后面键入另一个字符。(通俗讲,两个子表达式或者匹配符之间不需要连接符)
括在中括号表达式中的字符只匹配处于正则表达式中该位置的单个字符
/text [12345]/
可以匹配到text 1、text 2、text 3、text 4、text 5。
也可以写成
/text [1-5]/
但是开始值必须小于结束值
如果在中括号中使用连字符-,请用以下三种方法:
(1) [/-]反斜杠转义
(2) [-a-z]或[a-z-]将连字符放在中括号的开始或者结尾
(3) [!--]或[!-~]创建一个范围开始值小于连字符,结束值大于等于连字符
/text [^1-5]/中括号内以^开头表示匹配除了中括号内以外的任何数字和字符
替换使用 | 字符来允许在两个或多个替换选项之间进行选择。
/^Chapter|Section [1-9][0-9]{0,1}$/
上面的正则表达式要么匹配行首的单词 Chapter,要么匹配行尾的单词 Section 及跟在其后的任何数字。
/^(Chapter|Section) [1-9][0-9]{0,1}$/
在上面的正则表达式的适当位置添加括号,就可以使该正则表达式匹配 Chapter 1 或 Section 3
Js中的match()可以查找一个或多个正则表达式可以匹配的字符串,当正则为全局匹配时,返回所有匹配成功的值组成的数组。当正则为非全局匹配时,返回第一个匹配成功的相关内容。如果没有匹配到则返回null。
g可以控制是否查找多个
var str ="1;9uisjflk;jamsg56a654ag654ga"; var reg=/\d/g; console.log (str.match (reg)); 结果: ["1", "9", "5", "6", "6", "5", "4", "6", "5", "4"]
匹配连续的
var str ="1;9uisjflk; jamsg56a654ag654ga"; var reg=/\d+/g; console.log (str.match (reg)); 结果: ["1", "9", "56", "654", "654"]
Js中的test()可以验证字符串中有有没有符合要求的字符,如果字符串中有匹配的值返回 true ,否则返回 false。
var str="d2545ad5a4d54"; var patt=/\d/g; var result=patt.test(str); console.log(result); 结果: true exec() Js中的 exec()可以提取正则中符合要求的值,并且以数组的形式返回,与match()方法有相似之处(主要是全局匹配时不同,match()会一次性返回所有符合的字符,exec()是一个个返回 )。 不全局匹配的话(不加g):返回一个数组:匹配到的字符、匹配到的字符位置、匹配的整个字符串本身: var str="sad5a4d54add225sd22a"; var re=/\d+/; console.log(re.exec(str),'第一次'); console.log(re.exec(str),'第二次'); console.log(re.exec(str),'第三次'); console.log(re.exec(str),'第四次'); 结果: ["5", index: 3, input: "sad5a4d54add225sd22a", groups: undefined] "第一次" ["5", index: 3, input: "sad5a4d54add225sd22a", groups: undefined] "第二次" ["5", index: 3, input: "sad5a4d54add225sd22a", groups: undefined] "第三次" ["5", index: 3, input: "sad5a4d54add225sd22a", groups: undefined] "第四次"
如果全局匹配(加g):返回一个数组:匹配到的字符(每次调用指针会向后移一个,匹配到最后一个符合的字符,如果再次调用会返回null,如果还继续调用,就会从头开始匹配)、匹配到的字符位置、匹配的整个字符串本身
var str="sad5a4d54add225sd22a"; var re=/\d+/g; console.log(re.exec(str),'第一次') console.log(re.exec(str),'第二次') console.log(re.exec(str),'第三次') console.log(re.exec(str),'第四次') console.log(re.exec(str),'第五次') console.log(re.exec(str),'第六次') console.log(re.exec(str),'第七次') 结果: ["5", index: 3, input: "sad5a4d54add225sd22a", groups: undefined] "第一次" ["4", index: 5, input: "sad5a4d54add225sd22a", groups: undefined] "第二次" ["54", index: 7, input: "sad5a4d54add225sd22a", groups: undefined] "第三次" ["225", index: 12, input: "sad5a4d54add225sd22a", groups: undefined] "第四次" ["22", index: 17, input: "sad5a4d54add225sd22a", groups: undefined] "第五次" null "第六次" ["5", index: 3, input: "sad5a4d54add225sd22a", groups: undefined] "第七次"
search()与indexOf()方法类似,用来查找指定字符串在字符串中出现的位置。
返回的是第一个符合要求的结果在整个字符串中的位置
如果没有符合正则要求的结果,返回-1
var str="sad5a4d54add225sd22a"; var re=/\d+/g; console.log (str.search (re)); 结果: 3
replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
var str="sad5a4d54add225sd22a"; var re=/\d+/g; console.log (str.replace(re, "*")) 结果: sad*a*d*add*sd*a
函数执行一个正则表达式的搜索和替换
语法:mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
$pattern:要搜索的模式,可以是字符串或一个字符串数组。(正则表达式)
$replacement:用于替换的字符串或字符串数组。
$subject:要搜索替换的目标字符或字符串数组。
$limit:可选,对于每个模式用于每个subject字符串的最大可替换次数。默认是-1(无限制)。
$count:可选,替换执行的次数
返回值:
如果subject是一个数组,preg_replace()返回一个数组,其他情况返回一个字符串。
如果匹配被查找到,替换后的 subject 被返回,其他情况下 返回没有改变的 subject。如果发生错误,返回 NULL。
$str = 'runo o b'; $str = preg_replace('/\s+/', '', $str); // 结果将会改变为'runoob'
使用基于数组索引的搜索替换
$string = 'The quick brown fox jumped over the lazy dog.'; $patterns = array(); $patterns[0] = '/quick/'; $patterns[1] = '/brown/'; $patterns[2] = '/fox/'; $replacements = array(); $replacements[2] = 'bear'; $replacements[1] = 'black'; $replacements[0] = 'slow'; //虽然下标是倒叙,但数组的实际顺序还是210,所以quick对应bear,brown对应black,fox对 应slow echo preg_replace($patterns, $replacements, $string);
结果:
The bear black slow jumped over the lazy dog.
使用其他参数:
$count = 0; echo preg_replace(array('/\d/', '/\s/'), '*', 'xp 4 to', -1 , $count); echo $count; //3
结果:
xp***to 3
QueryList是一套用于内容采集的PHP工具,它使用更加现代化的开发思想,语法简洁、优雅,可扩展性强。相比传统的使用晦涩的正则表达式来做采集,QueryList使用了更加强大而优雅的CSS选择器来做采集,大大降低了PHP做采集的门槛,同时也让采集代码易读易维护。
QueryList是直接获取的页面上的元素内容,而不是直接获取api中的内容。
网址:https://querylist.cc/,新版环境要求php>=7.1
composer安装命令:
composer require jaeger/querylis
如果上面的composer命令太慢,请使用国内镜像:
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
说明:当时使用时是在tp5中测试使用的,所以下面的内容都是基于tp5进行举例的。
$ql=QueryList::get($src);
$data=$ql->find('div')->attr("id");//获取页面上一个div的id值
QueryList有个find()方法,用于采集单个元素,它通过jQuery选择器选择DOM元素,用法同jQuery的find()方法。
$ql=QueryList::get('https://www.iqshw.com/'); //获取第一张图片的链接地址,下面四种方法完全等价 $data[]=$ql->find('div')->attr('src'); $data[]=$ql->find('div')->src; $data[]=$ql->find('div:eq(0)')->src;//等推获取第n张:img:eq(n-1) $data[]=$ql->find('div')->eq(0)->src;//等推获取第n张:eq(n-1) //获取其他属性值 $data[]=$ql->find('div')->alt; //获取其它自定义属性值,只能获取一种 //获取所有属性值 $data[]=$ql->find('div')->attr("*"); //获取元素下的HTML内容 $data[]=$ql->find('#id')->html(); $data[]=$ql->find('#id .class')->html(); //获取其它自定义属性值,只能获取一种 //获取元素下的纯文本内容 $data[]=$ql->find('.class')->text();
获取多个元素的单个属性值(QueryList中凡是涉及到集合的地方返回的都是Collection集合对象,这个对象有个all()方法,用于把当前对象转成数组,所以你会发现下面很多写法都是$data->all() )
$ql=QueryList::get('https://www.iqshw.com/'); //获取所有图片的src属性值 $data[]=$ql->find('img')->map(function ($item){ return $item->src; })->all(); //等价与下面这句话 $data[]=$ql->find('img')->attrs('src')->all(); //获取元素中所有的html内容和text内容 $data[]=$ql->find('#id')->htmls()->all(); $data[]=$ql->find('.class')->texts()->all();
列表采集才是QueryList的核心功能,这里主要涉及到两个函数的用法:rules()和range()
$rules = [ '规则名1' => ['选择器1','元素属性'], '规则名2' => ['选择器2','元素属性'], // ... ]; //代码示例: $ql=QueryList::get('https://www.cnblogs.com/yulongcode/'); //定义采集规则 $rules=[ //采集标题 'title'=>['.postTitle2','text'], //采集内容 'text'=>['.c_b_p_desc','text'], //采集时间 'time'=>['.dayTitle','text'] ]; $data=$ql->rules($rules)->query()->getData()->all(); //等同于上面 $data=$ql->rules($rules)->queryData();
用range()函数来配合rules()进行循环采集列表内容,range()函数的作用是选择一个元素作为多个数据之间的“切片”。
$ql=QueryList::get('https://www.cnblogs.com/yulongcode/'); //定义采集规则 $rules=[ //采集标题 'title'=>['.postTitle2','text'], //采集内容 'text'=>['.c_b_p_desc','text'], //采集时间 'time'=>['.dayTitle','text'] ]; $range='.day';//切片选择器 $data=$ql->rules($rules)->range($range)->query()->getData()->all();
利用remove()来过滤不需要的内容:
$ql = QueryList::get('https://www.cnblogs.com/yulongcode/'); //移除选中html中的a标签 $data = $ql->find('.c_b_p_desc:eq(0)')->remove('a')->html(); //只保留a标签中的html内容 $data = $ql->find('.day:eq(0)')->find('a')->remove()->html();
列表采集时过滤不需要的元素,使用规则:
$rules = [ '规则名1' => ['选择器1','元素属性','内容过滤选择器'], '规则名2' => ['选择器2','元素属性','内容过滤选择器'], // ... ];
内容过滤选择器参数不光可以定义要移除的内容还可以定义要保留的内容,多个值之间用空格隔开, 有如下2条规则:
内容移除规则:选择器名前面添加减号(-),表示移除该标签以及标签内容。
内容保留规则:选择器名前面没有减号(-)(此时选择器只能为HTML标签名,不支持其他选择器), 当要采集的[元素属性] 值为text时表示需要保留的HTML标签以及内容,为html时表示要过滤掉的 HTML标签但保留内容。
$ql = QueryList::get('https://www.cnblogs.com/yulongcode/'); //定义采集规则 $rules = [ //采集标题 'title' => ['.postTitle2', 'text'], //采集内容 'text' => ['.c_b_p_desc', 'html','-a'], //采集时间 'time' => ['.dayTitle', 'text'] ]; $range = '.day';//切片选择器 $data = $ql->rules($rules)->range($range)->query()->getData()->all();
获取到数据后进行二次处理:
$ql = QueryList::get('https://www.cnblogs.com/yulongcode/'); //定义采集规则 $rules = [ //采集标题 'title' => ['.postTitle2', 'text'], //采集内容 'text' => ['.c_b_p_desc', 'html'], //采集时间 'time' => ['.dayTitle', 'text'] ]; $range = '.day';//切片选择器 $data = $ql->rules($rules)->range($range)->query()->getData(function($item){ $qls = QueryList::html($item['text']); $qls->find('a')->remove(); $item['content'] = $qls->find('')->html(); return $item; })->all();
$ql = QueryList::get('https://www.iqshw.com/'); //定义采集规则 $rules = [ //采集标题 'title' => ['a', 'text'], ]; $range = 'li';//切片选择器 //乱码处理,设置输出编码 $data = $ql->rules($rules)->range($range) ->encoding('UTF-8','GB2312') ->query()->getData()->all(); //如果设置输出参数无法解决乱码,那就使用removeHead()方法移除html头部 $data = $ql->rules($rules)->range($range) ->removeHead()->query()->getData()->all(); //或者 $data = $ql->rules($rules)->range($range) ->encoding('UTF-8','GB2312') ->removeHead()->query()->getData()->all(); //手动转码 $html=$text=iconv("GB2312","UTF-8",file_get_contents('https://www.iqshw.com/')); $data = QueryList::html($html)->rules([ 'title' => ['.news-comm-wrap a ', 'text'], ])->range($range)->query()->getData()->all();
$ql = QueryList::get('https://www.cnblogs.com/yulongcode/'); //定义采集规则 $rules = [ //采集标题 'title' => ['.postTitle2', 'text'], ]; $range = '.day';//切片选择器 //使用flatten()方法将多维集合转为一维的 $data = $ql->rules($rules)->range($range)->query()->getData()->flatten()->all();
$ql = QueryList::get('https://www.cnblogs.com/yulongcode/'); //定义采集规则 $rules = [ //采集标题 'title' => ['.postTitle2', 'text'], ]; $range = '.day';//切片选择器 //take()方法返回给定数量项目的新集合,对最初的采集结果data进行处理: $data = $ql->rules($rules)->range($range)->query()->getData()->take(2)->all();
$ql = QueryList::get('https://www.cnblogs.com/yulongcode/'); //定义采集规则 $rules = [ //采集标题 'title' => ['.postTitle2', 'text'], ]; $range = '.day';//切片选择器 //使用reverse()来倒转集合中的项目 $data = $ql->rules($rules)->range($range)->query()->getData()->reverse()->all();
$ql = QueryList::get('https://www.cnblogs.com/yulongcode/'); //定义采集规则 $rules = [ //采集标题 'title' => ['.postTitle2', 'text'], ]; $range = '.day';//切片选择器 //filter()方法用于按条件过滤数据,只保留满足条件的数据。 //只能过滤,不能修改添加原数据,如果需要修改添加则用getData $data = $ql->rules($rules)->range($range)->query()->getData() ->filter(function ($item){ return $item['title']!='PHP使用引用实现无限极分类'; })->all();
$ql = QueryList::get('https://www.cnblogs.com/yulongcode/'); //定义采集规则 $rules = [ //采集标题 'title' => ['.postTitle2', 'text'], ]; $range = '.day';//切片选择器 //map() 方法遍历集合并将每一个值传入给定的回调。 //该回调可以任意修改项目并返回,从而形成新的被修改过项目的集合。 $data = $ql->rules($rules)->range($range)->query()->getData() ->map(function ($item){ $item['time']=time(); return $item; })->all();
attr()方法除了获取DOM元素的值外,还有第二个参数,用于设置元素属性值
text()方法是获取元素的纯文本内容,加参数表示设置元素内容
html()方法是获取元素的HTML内容,加参数表示设置元素HTML内容
以上三个用法基本和jQuery相同,添加完后再获取,才会出现在获取的数据中。
append()追加元素
replaceWith()替换元素
removeAttr()移除元素属性
parent()用户获取当前元素的父级元素
next()和prev()用于获取当前元素临近的下一个元素和上一个元素
更多使用说明请查看使用文档。
直接在网站运行目录的.htaccess文件中输入以下内容:
Apache:
<IfModule mod_rewrite.c> RewriteEngine on RewriteBase / RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php?s=/$1 [QSA,PT,L] </IfModule>
Nginx:
location / { if (!-e $request_filename){ rewrite ^(.*)$ /index.php?s=$1 last; break; } }
/** * 判断两个时间之间相差的月份 * @param $time1 * @param $time2 * @return float|int */ function get_month_interval($time1, $time2) { if($time1>$time2){ $t=$time1; $time1=$time2; $time2=$t; } $y1=date('Y',$time1); $y2=date('Y',$time2); $m1=date('m',$time1); $m2=date('m',$time2); $months=($y2-$y1)*12+($m2-$m1); return $months; }
在vendor\topthink下执行composer命令,默认安装最新发送电子邮件的插件包:
composer require phpmailer/phpmailer
安装图例:
引入方式:
use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\Exception;
使用方法:
$mail = new PHPMailer(true);// Passing `true` enables exceptions try { //服务器配置 $mail->CharSet = "UTF-8";//设定邮件编码 $mail->SMTPDebug = 0;// 调试模式输出 $mail->isSMTP();// 使用SMTP $mail->Host = 'smtp.qq.com';// SMTP服务器 $mail->SMTPAuth = true;// 允许 SMTP 认证 $mail->Username = '*******@qq.com';// SMTP 用户名 即邮箱的用户名 $mail->Password = '**********';// SMTP 密码 部分邮箱是授权码(例如163邮箱) $mail->SMTPSecure = 'ssl';// 允许 TLS 或者ssl协议 $mail->Port = 465;// 服务器端口 25 或者465 具体要看邮箱服务器支持 $mail->setFrom('******@qq.com', 'shx');//发件人 $mail->addAddress($email, 'Joe');// 收件人 //$mail->addAddress('ellen@example.com');// 可添加多个收件人 //$mail->addReplyTo('xxxx@163.com', 'info');//回复的时候回复给哪个邮箱 建议和发件人一致 //$mail->addCC('cc@example.com');//抄送 //$mail->addBCC('bcc@example.com');//密送 //发送附件 // $mail->addAttachment('../xy.zip');// 添加附件 // $mail->addAttachment('../thumb-1.jpg', 'new.jpg');// 发送附件并且重命名 //Content $mail->isHTML(true);// 是否以HTML文档格式发送 发送后客户端可直接显示对应HTML内容 $mail->Subject = '我是邮件标题'; $mail->Body = '我是邮件内容'; $mail->AltBody = '如果邮件客户端不支持HTML则显示此内容'; $mail->send(); return '邮件发送成功'; } catch (Exception $e) { return '邮件发送失败: ' . $mail->ErrorInfo; }
本人用的是QQ邮箱的相关配置,所以这里申请方式就以QQ邮箱为例。
登入QQ邮箱。
进入设置,点击账户,向下翻,开启POP3/SMTP服务。
记录一下授权码,就是代码中的SMTP密码,无法复看,一旦忘记就只能重新申请!
SMTP Error: Could not authenticate.错误怎么解决?
网上大多数说的解决方案是:替换某个函数或者开启php.ini的某些功能。我个人曾多次出现此情况,前面的两个修改方案从未成功解决过错误,也可能我的问题和他们的报错原因不一样。本人多次入坑,强烈建议直接重新申请一次授权码就可以了,简单快捷!
这个错误我发现只要我配置好以后隔段时间如果不去使用它,可能就会产生这个错误,我的代码和配置从没动过,但是隔一段时间仍旧失效,所以我怀疑QQ邮箱这边可能有一定验证机制,长时间不使用就会出现类似于注销的情况。
GET在浏览器回退时是无害的,而POST会再次提交请求。
GET产生的URL地址可以被Bookmark(翻译:书签),而POST不可以。
GET请求会被浏览器主动cache,而POST不会,除非手动设置。
GET请求只能进行url编码,而POST支持多种编码方式。
GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
GET请求在URL中传送的参数是有长度限制的,而POST没有。
对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
GET参数通过URL传递,POST放在Request body中。