偏向直接代码的 比如直接拿去用的api等
一些好用的函数 by me
/**
* 筛选二维数组,获取其中满足条件的数组
* 如:array:[[name:a,age:10],[name:b,age:20],[name:c,age:20]] 想要筛选其中age=20的数组
* 使用getSpecificArray($array,'age',20)即可
* 结果分为两个数组,self是你想要筛选的 other是不符合条件的 单独成一个数组
* 快捷方式 可直接函数后用 getSpecificArray($array,age,20)['self'];
*/
public function getSpecificArray($array,$key,$value){
$res_array = [];
//不提前申明 后面有的可能没有self或者other 会报错
$res_array['self'] = [];
$res_array['other'] = [];
foreach($array as $keyp=>$valuep){
if($valuep[$key]==$value){
$res_array['self'] []= $array[$keyp];
//unset($array[$keyp]);
}
elseif($valuep[$key]!=$value){
$res_array['other'] []= $array[$keyp];
}
}
return $res_array;
}
//上面的修改版本 筛选条件($valueArr)可以是多个值了 如age可以为 10 20(以数组的形式[]填入)
//但是时间复杂度可能需要优化,且或许可能存在为空的时候某些地方错误的问题 以后在考虑下
public function getSpecificArray2($array,$key,$valueArr=[]){
$res_array = [];
//不提前申明 后面有的可能没有self或者other 会报错
$res_array['self'] = [];
$res_array['other'] = [];
foreach($valueArr as $value){
foreach($array as $keyp=>$valuep){
if($valuep[$key]==$value){
$res_array['self'] []= $array[$keyp];
//unset($array[$keyp]);
}
/*
elseif($valuep[$key]!=$value){
$res_array['other'] []= $array[$keyp];
}
*/
}
}
foreach($array as $item){
if(!in_array($item, $res_array['self'])) $res_array['other'] []= $item;
}
return $res_array;
}
/**
* 将两个函数关联合并 类似数据库left join的模式
* eg:
* $array1=[['id'=>1,'address'=>'北京'],['id'=>2,'address'=>'北京'],['id'=>3,'address'=>'上海']];
* $array2= [['地址'=>'北京','code'=>100],['地址'=>'上海','code'=>200]];
* 将其left join为 [[id:1,address:北京,code:100],[id:2,address:北京,code:100],[id:3,address:上海,code:200]]
* 使用mergerArray($array1,array2,'address','地址')即可
* 若二者的join on 条件一直,则不需要填field2
* 若数组1有数组2没有的 会按照,leftjoin的原则 不加进去(直接不显示code的字段 而非code=null)
*/
function mergerArray($array1, $array2, $field1, $field2=null ) {
//使用array_merge函数合并关联数组时,若出现冲突 后面的会覆盖前面的 eg:array_merge(["a"=>"red","b"=>"green"],["c"=>"blue","b"=>"yellow"]) = [[a] => red [b] => yellow [c] => blue]
$ret = array();
$array_tmp = array();
//使用数组下标的办法 如果想要知道这里干了啥 打印 $array_tmp即可快速知道
foreach ($array2 as $item) {
if($field2 !==null){
$item[$field1] = $item[$field2];
unset($item[$field2]);
}
//如果array2 有多个相同的field 如['地址:北京,code:100'],['地址:北京,code:500']
//如北京有多个区 每个区区号不一样 那么可以在后面加['你要的名字'][]
//如$array_tmp[$item[$field1]]['区号集合'][] = $item;
$array_tmp[$item[$field1]] = $item;
}
foreach ($array1 as $item) {
//有时可能数组1存在数组二中没有的元素 如['id'=>4,'address'=>'天津']
//这里要用isset 而非==null 因为这个没定义过 会报错undefined
//if($array_tmp[$item[$field1]]==null)$array_tmp[$item[$field1]]=[];
if(!isset($array_tmp[$item[$field1]])) $array_tmp[$item[$field1]]=[];
$ret[] = array_merge($array_tmp[$item[$field1]], $item);
}
return $ret;
}
//php官方的
二维数组排序的:array_multisort(array_column($list, 'mtime'),SORT_DESC,$list);
//根据第二个参数对第一个数组进行排序 然后将顺序的变动同步到第三个数组list中
//获得当前的精确到毫秒的时间戳
public function mtime ()
{
//list($s1, $s2) = explode(' ', microtime());
//return (float)sprintf('%.0f',(floatval($s1)* 100000000 ));
$tmp = explode(' ', microtime());
//print_r($tmp[0]*100000000); die();
//return (int)bcmul($tmp[0],100000000);
return (float)$tmp[0]+(float)$tmp[1];
}
```
```php
二维转一维数组 第二种正常情况下更安全
$data=array_reduce($actions, function ($result, $value) {
return array_merge($result, array_values($value));
}, array());
$data = array_unique(array_filter(array_column($actions, 'id')));
```
```php
php二维数组按照多个key进行排序。
/**
* @param $array
* @param $cols
* @return array
*/
function array_msort($array, $cols) {
$colarr = array();
foreach ($cols as $col => $order) {
$colarr[$col] = array();
foreach ($array as $k => $row) {
$colarr[$col]['_' . $k] = strtolower($row[$col]);
}
}
$eval = 'array_multisort(';
foreach ($cols as $col => $order) {
$eval .= '$colarr[\'' . $col . '\'],' . $order . ',';
}
$eval = substr($eval, 0, -1) . ');';
eval($eval);
$ret = array();
foreach ($colarr as $col => $arr) {
foreach ($arr as $k => $v) {
$k = substr($k, 1);
if (!isset($ret[$k])) $ret[$k] = $array[$k];
$ret[$k][$col] = $array[$k][$col];
}
}
return $ret;
}
使用示例:
$arr1 = array(
array('id'=>1,'name'=>'aA','cat'=>'cc'),
array('id'=>2,'name'=>'aa','cat'=>'dd'),
array('id'=>3,'name'=>'bb','cat'=>'cc'),
array('id'=>4,'name'=>'bb','cat'=>'dd')
);
$arr2 = array_msort($arr1, array('name'=>SORT_DESC, 'cat'=>SORT_ASC));
/**
* @param string $beginTime1
* @param string $endTime1
* @param string $beginTime2
* @param string $endTime2
* @return bool
* 时间是否冲突 false代表不冲突 true代表冲突
*/
public function is_time_cross($beginTime1 = '', $endTime1 = '', $beginTime2 = '', $endTime2 = '')
{
if($endTime1<=$beginTime1) return true;
if($endTime2<=$beginTime2) return true;
$status = $beginTime2 - $beginTime1;
if ($status > 0) {
$status2 = $beginTime2 - $endTime1;
if ($status2 >= 0) {
return false;
} else {
return true;
}
} elseif ($status < 0) {
$status2 = $endTime2 - $beginTime1;
if ($status2 > 0) {
return true;
} else {
return false;
}
} else {
return true;
}
}
去重
$unique_before = array_unique($before);
$repeat_before = array_diff_assoc($before, $unique_before);
foreach ($repeat_before as $item) {
foreach ($before as $k => $v) {
if ($v == $item) unset($before[$k]);
}
}
preg_match("/^([0-9]+\,[0-9]+\;)+$/", $new, $matches_new);
//把重复的元素都删了
$unique = array_unique($outTime);
$repeat = array_diff_assoc($outTime, $unique);
foreach ($repeat as $item) {
foreach ($unique as $k => $v) {
if ($v == $item) unset($unique[$k]);
}
}
sort($unique);
```
```php
//判断两数组是否完全包含
$all = array
(
0 => 307,
1 => 157,
2 => 234,
3 => 200,
4 => 322,
5 => 324
);
$part = array
(
0 => 200,
1 => 234
);
$way1 = count(array_intersect($part, $all)) == count($part);
$way2= !array_diff($part, $all);
var_dump($way1);//true
var_dump($way2);//true
二维数组去重和排序
引自:
/**
* 二维数组按照指定键值去重
* @param $arr 需要去重的二维数组
* @param $key 需要去重所根据的索引
* @return mixed
*/
function arr_uniq($arr,$key)
{
$key_arr = [];
foreach ($arr as $k => $v) {
if (in_array($v[$key],$key_arr)) {
unset($arr[$k]);
} else {
$key_arr[] = $v[$key];
}
}
sort($arr);
return $arr;
}
/**
* 二维数组按指定的键值排序
* @param $array 需要排序的二维数组
* @param $keys 需要排序根据的索引
* @param string $type 正序/倒叙(默认倒序)
* @return array|string
*/
function array_sort( $array, $keys, $type='asc' )
{
if( !isset( $array ) || !is_array( $array ) || empty( $array ) ) return '';
if( !isset( $keys ) || trim( $keys ) == '' ) return '';
if( !isset( $type ) || $type == '' || !in_array( strtolower( $type ), array( 'asc', 'desc' ) ) ) return '';
$keysvalue = [];
foreach( $array as $key => $val ) {
$val[ $keys ] = str_replace( '-', '', $val[ $keys ] );
$val[ $keys ] = str_replace( ' ', '', $val[ $keys ] );
$val[ $keys ] = str_replace( ':', '', $val[ $keys ] );
$keysvalue[] = $val[ $keys ];
}
asort( $keysvalue ); //key值排序
reset( $keysvalue ); //指针重新指向数组第一个
foreach( $keysvalue as $key => $vals )
$keysort[] = $key;
$keysvalue = [];
$count = count( $keysort );
if( strtolower( $type ) != 'asc' ) {
for( $i = $count - 1; $i >= 0; $i-- )
$keysvalue[] = $array[ $keysort[ $i ] ];
}else{
for( $i = 0; $i < $count; $i++ )
$keysvalue[] = $array[ $keysort[ $i ] ];
}
return $keysvalue;
}
示例:
$arr = [
[
'total'=>123,
'serve'=>456,
'speed'=>789
],
[
'total'=>123,
'serve'=>45,
'speed'=>89
],
[
'total'=>1234,
'serve'=>456,
'speed'=>789
],
[
'total'=>"123",
'serve'=>456,
'speed'=>789
]
];// 去重
$re = arr_uniq($arr,'total');
var_dump($re);
输出:
array(2) {
[0] =>
array(3) {
'total' =>
int(123)
'serve' =>
int(456)
'speed' =>
int(789)
}
[1] =>
array(3) {
'total' =>
int(1234)
'serve' =>
int(456)
'speed' =>
int(789)
}
}
//排序
$re = array_sort($arr, 'speed', 'desc');
var_dump($re);
输出:
array(4) {
[0] =>
array(3) {
'total' =>
string(3) "123"
'serve' =>
int(456)
'speed' =>
int(789)
}
[1] =>
array(3) {
'total' =>
int(1234)
'serve' =>
int(456)
'speed' =>
int(789)
}
[2] =>
array(3) {
'total' =>
int(123)
'serve' =>
int(456)
'speed' =>
int(789)
}
[3] =>
array(3) {
'total' =>
int(123)
'serve' =>
int(45)
'speed' =>
int(89)
}
}
/**
* 工具函数-根据value对数组进行分组
* example:
* [['name'=>李四,'gender'=>'男'],['name'=>张三,'gender'=>'男'],['name'=>小丽,'gender'=>'女']]
* array_val_chunk($array,'gender') =》result:
* ['男'=>[['name'=>李四,'gender'=>'男'],['name'=>张三,'gender'=>'男']],'女'=>[['name'=>小丽,'gender'=>'女']]]
* @param $array
* @return array
*/
function array_val_chunk($array, $key_name = '')
{
$result = array();
foreach ($array as $value) {
if (empty($value[$key_name])) throw new Exception('数组格式错误', 1);
$result[$value[$key_name]][] = $value;
}
return $result;
}
php 同时包含中英文的数组按照首字母排序
$arr = explode(' ', '1 11 111 112 12 121 122 a aa aaa aab ab aba abb 阿里 百度 中 中国 中国国 中国中 中中 中中国 中中中');
shuffle($arr); //打乱数组
//collator_sort(collator_create('zh_CN'), $arr);
//usort($arr, function($a, $b) { return strnatcmp($a, $b); });
usort($arr, function($a, $b) { return strcmp($a, $b); });
echo implode(' ',$arr);
-
ls排序:
1 11 111 112 12 121 122 a aa aaa aab ab aba abb 阿里 百度 中 中国 中国国 中国中 中中 中中国 中中中 -
collator_sort(zh_CN)排序(类似Windows/Linux桌面文件管理器里的默认按名称上升排列):
1 11 12 111 112 121 122 a aa aaa aab ab aba abb 阿里 百度 中 中国 中国国 中国中 中中 中中国 中中中 -
strnatcmp排序:
1 11 12 111 112 121 122 a aa aaa aab ab aba abb 中 中中 中中中 中中国 中国 中国中 中国国 百度 阿里 -
strcmp排序:
1 11 111 112 12 121 122 a aa aaa aab ab aba abb 中 中中 中中中 中中国 中国 中国中 中国国 百度 阿里
区别
使用strcmp字符串比较排序其实就已经能够满足你的需求.
strnatcmp自然排序跟strcmp字符串比较排序主要区别在于数字,strnatcmp处理后的数字元素是由小到大排序,存在大小关系.
而ICU扩展intl里的简体中文排序器collator_sort(zh_CN)排序主要能够根据汉字的拼音进行排序,比如Ali,Baidu,ZhongGuo.感兴趣还可以看下我的这篇文章 PHP利用ICU扩展intl快速实现汉字转拼音以及按拼音首字母分组排序 ,汉字转拼音就一个函数的事:
echo transliterator_transliterate('Any-Latin; Latin-ASCII; Upper()', '中华有为');
//输出 ZHONG HUA YOU WEI
另一种方法
/*
* 中文和英文数组混杂的排序函数 会不会造成内存垃圾?
*/
private function utf8_array_asort($array) {
if(!isset($array) || !is_array($array)) {
return false;
}
$array_english_num = [];
$array_chinese = [];
foreach ($array as $v){
if(mb_detect_encoding($v, array("ASCII",'UTF-8',"GB2312","GBK",'BIG5'))=='ASCII') $array_english_num[]= $v;
if(mb_detect_encoding($v, array("ASCII",'UTF-8',"GB2312","GBK",'BIG5'))=='UTF-8') $array_chinese[]= $v;
}
/* 如果是二维数组 然后用array_multisort即可
foreach($array as $k=>$v) {
$array[$k] = iconv('UTF-8', 'GBK//IGNORE',$v);
}
*/
foreach($array_chinese as &$v) {
$v = iconv('UTF-8', 'GBK//IGNORE',$v);
}
asort($array_chinese);
asort($array_english_num);
foreach($array_chinese as &$v) {
$v = iconv('GBK', 'UTF-8//IGNORE', $v);
}
//由于array_merge不会合并空数组,所以不需要考虑xxx
return array_merge($array_english_num,$array_chinese);
}
内外网判断
工作需要判断ip是否是内网ip,本来想着使用正则自己写一个呢,后来发现php自带的有现成的函数filter_var() 。
除了ip验证外还有许多都可以验证,如url、email等等
验证ip是否是内网ip,如果是的话返回false,否则返回ip;
直接的话 浏览器看不到 要 var_dump
代码如下:
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)
内网ip
A类:10.0.0.0-10.255.255.255
B类:172.16.0.0-172.31.255.255
C类:192.168.0.0-192.168.255.255
本机地址:127.0.0.1
PHP FILTER_VALIDATE_IP 过滤器
定义和用法
FILTER_VALIDATE_IP 过滤器把值作为 IP 进行验证。
Name: “validate_ip”
ID-number: 275
可能的标志:
FILTER_FLAG_IPV4 - 要求值是合法的 IPv4 IP(比如 255.255.255.255)
FILTER_FLAG_IPV6 - 要求值是合法的 IPv6 IP(比如 2001:0db8:85a3:08d3:1319:8a2e:0370:7334)
FILTER_FLAG_NO_PRIV_RANGE - 要求值是 RFC 指定的私域 IP (比如 192.168.0.1)
FILTER_FLAG_NO_RES_RANGE - 要求值不在保留的 IP 范围内。该标志接受 IPV4 和 IPV6 值。
二维数组去重
$softs = array_unique($softs,SORT_REGULAR);
这个函数默认只能一维,会报错 arr to string的错误 所以
array_unique中加:SORT_REGULAR参数
排序 筛选 时间等各种方便的操作
看operation这个接口
ip2long
如何将四个字段以点分开的IP网络址协议地址转换成整数呢?PHP里有这么一个函数ip2long
php-接口性能测试方法–时间与内存
echo “数据format预处理”.date(‘Y-m-d H:i:s’,time());
echo ‘<hr>’;
<?php
function makeRangeYield($length) {
//$dataset = [];
for ($i = 0; $i < $length; $i++) {
yield $i;
// $dataset[] = $i;
}
//return $dataset;
}
//使用生成器
$startMemory = memory_get_usage();
$t1 = microtime(true);
$customRangeYield = makeRangeYield(1000000);
//结束
$t2 = microtime(true);
$endMemory = memory_get_usage();
foreach ($customRangeYield as $i) {
echo $i . PHP_EOL;
}
//输出
echo sprintf("内存使用: %f kb\n", ($endMemory - $startMemory) / 1024);//单位为kb
echo sprintf("耗时: %f秒\n", round($t2-$t1,3));
封装
/**
* 计算接口消耗时间与性能
* @return array
*/
public static function calPerformanceStart(){
return [microtime(true),memory_get_usage()];
}
/**
* 计算接口消耗时间与性能
* @return array eg:内存使用: 1453.484375 kb 耗时: 0.162000秒
*/
public static function calPerformanceEnd($start_array){
$t1= $start_array[0];
$startMemory = $start_array[1];
$t2 = microtime(true);
$endMemory = memory_get_usage();
echo '</br>';
echo '</br>';
echo PHP_EOL;
echo sprintf("内存使用: %f kb\n", ($endMemory - $startMemory) / 1024);//单位为kb
echo PHP_EOL;
echo '</br>';
echo sprintf("共耗时: %f秒\n", round($t2-$t1,3));
echo PHP_EOL;
echo '</br>';
}
PHP导出CSV格式
header("Content-type:text/csv");
//header("Content-Disposition:attachment;filename=" . $filename); //这个是?
header('Cache-Control:must-revalidate,post-check=0,pre-check=0');
header('Expires:0');
header('Pragma:public');
header("Content-Disposition:filename=".'produ'.'.csv');//设置文件名
$file = fopen("php://output", "w");
fwrite($file, chr(0xEF).chr(0xBB).chr(0xBF));//中文防乱码
fputcsv($fi,['field','command','name']); //设置字段名字
//写入 arr为你的数据数组
foreach ($arr as $item)
{
fputcsv($fi,$item);
}
//fclose
ini_set('memory_limit','1024M'); //设置程序运行的内存
ini_set('max_execution_time',0); //设置程序的执行时间,0为无上限
ini_set("pcre.backtrack_limit" , -1);
ini_set("pcre.recursion_limit" , -1);
header('Access-Control-Allow-Origin:*');//允许所有来源访问
//ob_end_clean(); //清除内存
//ob_start();
header("Content-Type: text/csv");
header("Content-Type: application/force-download");
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");
header("Content-Disposition:filename=".'导出记录'.'.csv');
$fp=fopen('php://output','w');
fwrite($fp, chr(0xEF).chr(0xBB).chr(0xBF));
另一个快速导出指定参数到csv的
public static function export_csv($filename, $col_fields, $list, $options=array(), $prodcombo='') {
header("Content-type:text/csv");
header("Content-Disposition:attachment;filename=".$filename);
header('Cache-Control:must-revalidate,post-check=0,pre-check=0');
header('Expires:0');
$fp = fopen('php://output', "w");
$tmp = array();
foreach ($col_fields as $field_name=>$_) {
$tmp []= @iconv('UTF-8', 'GB2312//IGNORE', $field_name);
}
fputcsv($fp, $tmp);
foreach ($list as $row) {
$tmp = array();
foreach ($col_fields as $field) {
if (is_callable($field)) {
$tmp []= @iconv('UTF-8', 'GB18030//IGNORE', Helper::csvFilter(call_user_func_array($field, array($row, $options))));
} else {
$row[$field] = self::fixSpecialField($field, $row[$field], $prodcombo);
$tmp []= @iconv('UTF-8', 'GB18030//IGNORE', Helper::csvFilter($row[$field]));
}
}
fputcsv($fp, $tmp);
}
fclose($fp);
exit(0);
}
技巧与使用篇
一些更零散的
文件可用超级全局变量file数组得到
microtime()
字符串的两部分的单位都是秒(第一部分并不是微秒为单位),第一部分是精确到微秒的部分
比如0.25139300 ,有效数字是6位,正好符合1秒=1,000毫秒=1,000,000微秒
使用echo microtime(true);直接获得浮点值。
curl和file_get_contents的区别
1.fopen /file_get_contents 每次请求都会重新做DNS查询,并不对 DNS信息进行缓存。但是CURL会自动对DNS信息进行缓存。对同一域名下的网页或者图片的请求只需要一次DNS查询。这大大减少了DNS查询的次数。所以CURL的性能比fopen /file_get_contents 好很多。
2.fopen /file_get_contents 在请求HTTP时,使用的是http_fopen_wrapper,不会keeplive。而curl却可以。这样在多次请求多个链接时,curl效率会好一些。
3.fopen / file_get_contents 函数会受到php.ini文件中allow_url_open选项配置的影响。如果该配置关闭了,则该函数也就失效了。而curl不受该配置的影响。
4.curl 可以模拟多种请求,例如:POST数据,表单提交等,用户可以按照自己的需求来定制请求。而fopen / file_get_contents只能使用get方式获取数据。
file_get_contents 获取远程文件时会把结果都存在一个字符串中 fiels函数则会储存成数组形式
由此可知curl在性能、速度、稳定性上都要优于file_get_contents,所以建议以后使用curl库进行网络请求。
<?php
$curl = curl_init();//初始化一个cURL对象
$url = "http://cart.jd.com/cart/cart.html?backurl=http://item.jd.com/176166.html&rid=0.9533184533 938766";
$header = array();
$header[] = 'User-Agent: 5.0 (iPhone; U; CPU iPhone OS 4_3 like Mac OS X; en-us)';
$header[] = 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8';
$header[]= 'Accept-Encoding: gzip,deflate';
$header[]= '//可以根据需要增加header内容';
curl_setopt($culr,CURLOPT_URL, $url);//设置你需要抓去的URL地址
curl_setopt($curl,CURLOPT_HEADER,$header );//设置header
curl_setopt($curl,CURLOPT_RETURNTRANSFER,1);//将结果返回输出到字符串
$str = curl_exec($curl);运行cURL,请求网页
curl_close($curl);//关闭url请求
return $str;//返回或者显示结果
php性能低的原因
作者:旗木五五开
链接:https://www.zhihu.com/question/422661037/answer/1602633070
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
php不管是哪个框架真的要到高性能的场景,都是撑不住的。原因?首先,没有连接池这个玩意,立马就拖垮了数据库的连接数,too many connection见过没?没见过的话等你见过了再来喷框架垃圾。其次,每次都要从入口文件加载一次全部代码,当然你可以用opcache减少编译。框架代码按照道理你应该只加载一次就够了,逻辑上应该从路由开始执行才对,这部分也是性能损失的原因。第三,php本身的io机制不是epoll的,理论上你php-fpm开了多少个进程,就能同时执行多少个请求,网上一堆教程教你要么保持和cpu核数一样,要么内存50m一个进程之类的方式。基于这个问题,php方面的性能优化只能靠叠服务器,不然你会经常碰到504 gateway timeout的问题。所以php的优化途径都是这样的:首先你随便用一个了框架。然后某天突然发现504 gateway timeout了,排除了你sql写的实在是烂的前提,你就会发现是php-fpm的进程不够用导致的,修改php-fpm.conf配置或者加机器。加完机器大概率会出现too many connection,原因是mysql的连接数不够用了,然后你就会想要么mysql主从分离,要么加缓存上redis,先查redis再查mysql。然后如果量继续加大,你就只能继续增加服务器,因为php-fpm进程数=1秒处理数导致的。接着你会发现服务器成本越来越贵,就差不多要换swoole或者go获取java了。当你换了go或者java或者swoole后,你就会发出服务器降了多少台,成本降了多少的感叹。所以选一个自己称手的框架上就行了,框架无非就是熟练它的各种类库罢了,那个熟练用哪个,非得有个高下立判就无趣了,真到性能瓶颈你都得换。说点Laravel的题外话,Laravel真的是除了性能不行,哪哪都行的框架。掌握好他的框架思想、设计模式、队列、调度等原理,真的大有裨益。
1、too many 的问题解决方案很多,如果使用的是云数据库,一般都有短链接优化功能,开启即可解决问题。如果是本地自建数据库,加一个数据库代理即可,但根据实践来看,往往引起数据库连接超量并不是因为没有连接池引起的,而是业务代码里出现了大量的循环创建mysql连接导致的,开启连接池只是避免了前端报错,避免服务crash,但问题本质还是存在
2、php-fpm并不是你们理解的那样,一个进程只能处理一个请求,fpm本质也是利用了linux的epool机制,可以同时轮询处理多个请求,但是要记住,业务代码里不能有阻塞代码,阻塞代码一定要放到后台进行,否则并发能力会大受影响,就真的变成一个进程一次只能处理一个请求了
3、用laravel这样的框架,正确的写代码,两台4G8核的服务器,可以支撑到日请求数300万,峰值每秒处理2000请求,一直到项目黄了也没有触摸到性能瓶颈,这一点是自己创业的经历,不多解释。
4、php十分适合低成本创业,php当前最大的问题是被语言创造者们抛弃了,得不到黑科技的注入了。
抑制错误@和TODO
print_r()
print_r()会将把数组的指针移到最后边。使用 reset() 可让指针回到开始处。
foreach
foreach还可可用于对象和普通数组
如果对普通数组使用,请
foreach (array_expression as $value)
const define static global globals[]区别
前二者用于定义常量 区别可参考https://www.cnblogs.com/yszr/p/10549511.html
后三者用于定义变量 区别以后补
php的奇淫异巧 $this->{$var}
$this->a = "hello";
$this->b = "hi";
$this->val = "howdy";
$val = "a";
echo $this->{$val}; // outputs "hello"
$val = "b";
echo $this->{$val}; // outputs "hi"
echo $this->val; // outputs "howdy"
echo $this->{"val"}; // also outputs "howdy"
相当于
$a = "hello";
$b = "hi";
$val = "a";
echo $$val; // outputs "hello"
$val = "b";
echo $$val; // outputs "hi"
还可动态调用函数
$this->{$fun_name}($plat_id, $v['id'], true)
json避免/转义
json_encode($params,JSON_UNESCAPED_SLASHES)
session
ini_set(‘session.gc_probability’,100); //否则不是百分之百 一般不改这个 特殊需求
ini_set(‘session.gc_divisor’,100);
设置后,会在返回值中多一个set-Cookie
微信小程序不能自动设置cookie,要先存到storge 然后在header中设置 Cookie
变量作用域
在所有函数外部定义的变量,拥有全局作用域。除了函数外,全局变量可以被脚本中的任何部分访问,但是要在一个函数中访问或修改一个全局变量,需要使用 global 关键字申明。
如 global $x,$y;
如果不声明,也可使用$GLOBALS['y']
得形式
在 PHP 函数内部声明的变量是局部变量,仅能在函数内部访问:
函数中也可使用static作用域
当一个函数完成时,它的所有变量通常都会被删除。然而,有时候您希望某个局部变量不要被删除。
要做到这一点,请在您第一次声明变量时使用 static 关键字:
<?php
function myTest()
{
static $x=0;
echo $x;
$x++;
echo PHP_EOL; // 换行符
}
myTest();
myTest();
myTest();
//然后,每次调用该函数时,该变量将会保留着函数前一次被调用时的值。
//注释:该变量仍然是函数的局部变量。
EOF
PHP 定界符 EOF 的作用就是按照原样,包括换行格式什么的,输出在其内部的东西;
$name="runoob";
$a= <<<EOF
"abc"$name
"123"
EOF;
// 结束需要独立一行且前后不能空格
echo $a;
三目运算符
1、传统的
(expr1) ? (expr2) : (expr3) 对 expr1 求值为 TRUE 时的值为 expr2,在 expr1 求值为 FALSE 时的值为 expr3。
2、PHP5.3新增
表达式 expr1 ?: expr3 在 expr1 求值为 TRUE 时返回 expr1,否则返回 expr3。
3、php7.3新增 null合并运算符
$site = $_GET[‘site’] ?? ‘菜鸟教程’;判断变量是否存在且值不为NULL,如果是,它就会返回自身的值,否则返回它的第二个操作数。
注:2和3得区别在于,当你想仅仅判断isset时候(不为布尔值、空数组等),二者是一致的。但是仅仅想判断isset的时候 用后面的不会出错。2的根据是true false 3的根据只有isset
命名空间的作用
1、用户编写的代码与PHP内部的类/函数/常量或第三方类/函数/常量之间的名字冲突。
2、为很长的标识符名称(通常是为了缓解第一类问题而定义的)创建一个别名(或简短)的名称,提高源代码的可读性。
//file1文件
namespace Foo\Bar\subnamespace;
function foo() {}
<?php
namespace Foo\Bar;
include 'file1.php';//需要include 命名空间仅仅是为了解决命名冲突的 不是真实的目录结构
function foo() {}
/* 非限定名称 */
foo(); // 解析为函数 Foo\Bar\foo
/* 限定名称 */
subnamespace\foo(); // 解析为函数 Foo\Bar\subnamespace\foo
/* 完全限定名称 */
\Foo\Bar\foo(); // 解析为函数 Foo\Bar\foo
?>
//use用于为很长的命名空间起别名
use My\Full\Classname as Another;
// 下面的例子与 use My\Full\NSname as NSname 相同
use My\Full\NSname;
面向对象-见另一个博客 比较的
const、define、static区别
使用const使得代码简单易读,const本身就是一个语言结构,而define是一个函数。另外const在编译时要比define快很多。
1、const用于类成员变量的定义,一经定义,不可修改。Define不可以用于类成员变量的定义,可用于全局常量。
2、Const可在类中使用,define不能
3、Const不能再条件语句中定义常量
表单直接发送
<form action="welcome.php" method="post">
//welcome.php文件
欢迎<?php echo $_POST["fname"]; ?>!<br>
定界符
为什么要使用定界符
比如你要打印代码 或者 一些gzip的数据 里面有单引号 然后就很麻烦 直接用引号包裹会报错
解决方案
heredoc和nowdoc
区别是后者不会解析$变量,使用差别是多了个引号
$gzip = <<<'EOF'
//EOF可为任意字符,然后下面的eof要顶格写 php7.3后对此做了优化 避免破坏格式
EOF
call
require和include以及once的区别
require() 语句的性能与 include() 相类似,都是包括并运行指定文件。不同之处在于:
1、对 include() 语句来说,在执行文件时每次都要进行读取和评估;而对于 require() 来说,文件只处理一次(实际上,文件内容替换 require() 语句,使它变成 PHP 脚本文件的一部分。)。这就意味着如果可能执行多次的代码,则使用 require() 效率比较高。另外一方面,如果每次执行代码时是读取不同的文件,或者有通过一组文件迭代的循环,就使用 include() 语句。
2、require一般放在最前面(类名前),而include可以在需要时,比如if循环时再引入,降低消耗。
3、PHP 系统在加载PHP程序时有一个伪编译过程,可使程序运行速度加快。但 incluce 的文档仍为解释执行。include 的文件中出错了,主程序继续往下执行,require 的文件出错了,主程序也停了,所以包含的文件出错对系统影响不大的话(如界面文件)就用 include,否则用 require。
4、include_once() 和 require_once() 语句也是在脚本执行期间包括运行指定文件。此行为和 include() 语句及 require() 类似,使用方法也一样。唯一区别是如果该文件中的代码已经被包括了,则不会再次包括。这两个语句应该用于在脚本执行期间,同一个文件有可能被包括超过一次的情况下,确保它只被包括一次,以避免函数重定义以及变量重新赋值等问题。
5、
总之:
1、当很多函数都使用,或者经常要被执行的时候,当被包含的文件出错不让他继续运行的时候,用require。
2、当出错不影响、而且不经常被使用到时,用include。
3、Require_once 虽然官方说php5.3之后的版本 性能和require差不多了 但是···实际测试好像还是差了很多 如php5.3.3 所以 能用require就不要require_once
empty()和isset()
单从字面意思理解,empty是判断一个变量是否为“空”,而isset 则是判断一个变量是否已经设置。
但是这里有一点绝对要注意起来:当一个变量值为0,empty 认为这个变量同等于空,返回的是true!
== 和 ===
1==2 判断是false (之所以写这个是为了防止二者自动转换为true 然后判定相等)
‘1’==1 是true
Php 10和5 如果是字符串类型 那么可能会错误的结果 判断出5>10???是么
$a = (bool)('1');
dump($a); //结果为true
浮点数(特别是涉及到钱的!)的运算必须用高精度运算函数
如 1/8 ==0.125可能判断错误 要用bcdiv
注解
比如throws 告诉使用的类要catch异常
@internal : 被此标签标记的内部类/方法,作用范围只能限于当前文件,外部文件不可调用.
@deprecated可以填写一个版本号,版本号的规则同@version
如果被标记的方法只是因为被其他新方法代替而被废弃,可以结合@see来表示被代替的方法
var_dump与print_r
print_r输出的时候 字符型的"0"可能也输出0,造成json_encode 类型不对 而且不好排查。所以用var_dump.还有引用 var_dump
pcntl
PHP的pcntl扩展提供了信号处理的功能,利用它可以让PHP来接管信号的处理,在开发服务器端守护进程方面,信号处理至关重要。
array_walk和foreach、for的性能比较
实验是我学习计算机科学的一个重要方法,计算机科学不是简单的智力游戏,它本质上来说不是一门科学,而是一个改造世界的工具。数学方法和实验方法是计算机研究的基本方法,也是我们学习的基本方法,数学锻炼我们的思维能力,实验锻炼我们的操作能力,解决实际问题的能力。我们每天的工作都应该看做是一次实验,要从每天的工作中,总结出对我们来说有用的东西。比如要写好php代码,一个很重要的东西就是效率,效率高不高,我们就要做实验。下面是我对php中的几个数组循环处理方法的评测,测试的代码很简单:
<?php
/**
* array_walk 和 foreach, for 的效率的比较。
* 我们要测试的是foreach, for, 和 array_walk的效率的问题。
*/
//产生一个10000的一个数组。
$max = 10000;
$test_arr = range(0, $max);
$temp;
//我们分别用三种方法测试求这些数加上1的值的时间。
// for 的方法
$t1 = microtime(true);
for ($i = 0; $i < $max; $i++) {
$temp = $temp + 1;
}
$t2 = microtime(true);
$t = $t2 - $t1;
echo "就使用for, 没有对数组操作 花费: {$t}\n";
$t1 = microtime(true);
for ($i = 0; $i < $max; $i++) {
$test_arr[$i] = $test_arr[$i] + 1;
}
$t2 = microtime(true);
$t = $t2 - $t1;
echo "使用for 并且直接对数组进行了操作 花费: {$t}\n";
$t1 = microtime(true);
for ($i = 0; $i < $max; $i++) {
addOne($test_arr[$i]);
}
$t2 = microtime(true);
$t = $t2 - $t1;
echo "使用for 调用函数对数组操作 花费 : {$t}\n";
$t1 = microtime(true);
foreach ($test_arr as $k => &$v) {
$temp = $temp + 1;
}
$t2 = microtime(true);
$t = $t2 - $t1;
echo "使用 foreach 没有对数组操作 花费 : {$t}\n";
$t1 = microtime(true);
foreach ($test_arr as $k => &$v) {
$v = $v + 1;
}
$t2 = microtime(true);
$t = $t2 - $t1;
echo "使用 foreach 直接对数组操作 : {$t}\n";
$t1 = microtime(true);
foreach ($test_arr as $k => &$v) {
addOne($v);
}
$t2 = microtime(true);
$t = $t2 - $t1;
echo "使用 foreach 调用函数对数组操作 : {$t}\n";
$t1 = microtime(true);
array_walk($test_arr, 'addOne');
$t2 = microtime(true);
$t = $t2 - $t1;
echo "使用 array_walk 花费 : {$t}\n";
function addOne(&$item) {
$item = $item + 1;
}
?>
执行的结果:
就使用for, 没有对数组操作 花费: 0.15388584136963
使用 foreach 没有对数组操作 花费 : 0.076934814453125
使用for 并且直接对数组进行了操作 花费: 0.14769005775452
使用 foreach 直接对数组操作 : 0.076115131378174
使用for 调用函数对数组操作 花费 : 0.32393312454224
使用 foreach 调用函数对数组操作 : 0.25716996192932
使用 array_walk 花费 : 0.17966890335083
在对10000个数的操作过程中,这个实验我们可以得出这样的结论:
-
foreach 的效率要比for 高很多,也许有很大的一个原因是for 要进行很多次条件判断。所以以后能用foreach的地方就用foreach,可以提高1倍的效率。
-
如果循环内要调用函数,用array_walk 最好,它的效率要比for 高出1倍,要比foreach高出43%的效率。
-
还有一个提示就是如果你这个程序对效率的要求是很高的,那不要在很深的循环中调用函数,要调用函数也要用array_walk,最好的直接把代码写在循环里面。
array_map 和array_walk区别
1、array_map的用法是array_map(函数名,数组),而array_walk的用法是array_walk(数组,函数名);
2、array_map里面的函数可以是自定义函数,也可以是php自带的函数,比如trim去除空格等。而array_walk里面的函数只能是自定义的函数
3、array_map不可以改变原函数的值,会获取到新的数组。array_walk是可以改变原函数的值的(加个引用)。
4、array_map必须要有返回值,因为要填充数组。而array_walk可以没有返回值,输出的话要在调用的函数中通过echo来输出。
static final const的区别
static: 定义类属性和类方法(静态属性和静态方法),之后还能修改
final: 定义不可被子类继承的类和方法
const:定义类常量(也不一定是类)不能修改了
php如何cli运行指定php文件的指定函数?
我有一个叫做address.php的文件,里面有几个函数。我想用命令行调用该文件中的特定功能,怎么样做到?该函数的名称称为exportAddress,该函数需要一个参数
通过使用-r参数,你可以在线运行脚本。
php -r “require ‘address.php’; exportAddress(12345);”
没有其他选择。PHP中的函数只能由PHP脚本调用。
向php数组添加元素的方法哪种更高效
$arr = array();
// 第一种
array_push($arr, ‘test’);
// 第二种
$arr[] = ‘test’;
复制代码
参考PHP官方文档:http://php.net/manual/zh/function.array-push.php
如果用 array_push() 来给数组增加一个单元,还不如用 $array[] = ,因为这样没有调用函数的额外负担。
如果第一个参数不是数组,array_push() 将发出一条警告。这和 $var[] 的行为不同,后者会新建一个数组。
php防sql注入
需要注意的
(7) 关闭PHP版本信息在http头中的泄漏
我们为了防止黑客获取服务器中php版本的信息,可以关闭该信息斜路在http头中:
expose_php = Off
比如黑客在 telnet www.12345.com 80 的时候,那么将无法看到PHP的信息。
(8) 关闭注册全局变量
在PHP中提交的变量,包括使用POST或者GET提交的变量,都将自动注册为全局变量,能够直接访问,
这是对服务器非常不安全的,所以我们不能让它注册为全局变量,就把注册全局变量选项关闭:
register_globals = Off
当然,如果这样设置了,那么获取对应变量的时候就要采用合理方式,比如获取GET提交的变量var,
那么就要用$_GET[‘var’]来进行获取,这个php程序员要注意。
(9) 打开magic_quotes_gpc来防止SQL注入
SQL注入是非常危险的问题,小则网站后台被入侵,重则整个服务器沦陷,
所以一定要小心。php.ini中有一个设置:
magic_quotes_gpc = Off
这个默认是关闭的,如果它打开后将自动把用户提交对sql的查询进行转换,
比如把 ’ 转为 '等,这对防止sql注射有重大作用。所以我们推荐设置为:
magic_quotes_gpc = On
(10) 错误信息控制
一般php在没有连接到数据库或者其他情况下会有提示错误,一般错误信息中会包含php脚本当
前的路径信息或者查询的SQL语句等信息,这类信息提供给黑客后,是不安全的,所以一般服务器建议禁止错误提示:
display_errors = Off
如果你却是是要显示错误信息,一定要设置显示错误的级别,比如只显示警告以上的信息:
error_reporting = E_WARNING & E_ERROR
当然,我还是建议关闭错误提示。
(11) 错误日志
建议在关闭display_errors后能够把错误信息记录下来,便于查找服务器运行的原因:
log_errors = On
同时也要设置错误日志存放的目录,建议根apache的日志存在一起:
error_log = D:/usr/local/apache2/logs/php_error.log
以下为转载
PHP中防止SQL注入的方法
【一、在服务器端配置】
安全,PHP代码编写是一方面,PHP的配置更是非常关键。
我们php手手工安装的,php的默认配置文件在 /usr/local/apache2/conf/php.ini,我们最主要就是要配置php.ini中的内容,让我们执行 php能够更安全。整个PHP中的安全设置主要是为了防止phpshell和SQL Injection的攻击,一下我们慢慢探讨。我们先使用任何编辑工具打开 /etc/local/apache2/conf/php.ini,如果你是采用其他方式安装,配置文件可能不在该目录。
(1) 打开php的安全模式
php的安全模式是个非常重要的内嵌的安全机制,能够控制一些php中的函数,比如system(),
同时把很多文件操作函数进行了权限控制,也不允许对某些关键文件的文件,比如/etc/passwd,
但是默认的php.ini是没有打开安全模式的,我们把它打开:
safe_mode = on
(2) 用户组安全
当safe_mode打开时,safe_mode_gid被关闭,那么php脚本能够对文件进行访问,而且相同
组的用户也能够对文件进行访问。
建议设置为:
safe_mode_gid = off
如果不进行设置,可能我们无法对我们服务器网站目录下的文件进行操作了,比如我们需要
对文件进行操作的时候。
(3) 安全模式下执行程序主目录
如果安全模式打开了,但是却是要执行某些程序的时候,可以指定要执行程序的主目录:
safe_mode_exec_dir = D:/usr/bin
一般情况下是不需要执行什么程序的,所以推荐不要执行系统程序目录,可以指向一个目录,
然后把需要执行的程序拷贝过去,比如:
safe_mode_exec_dir = D:/tmp/cmd
但是,我更推荐不要执行任何程序,那么就可以指向我们网页目录:
safe_mode_exec_dir = D:/usr/www
(4) 安全模式下包含文件
如果要在安全模式下包含某些公共文件,那么就修改一下选项:
safe_mode_include_dir = D:/usr/www/include/
其实一般php脚本中包含文件都是在程序自己已经写好了,这个可以根据具体需要设置。
(5) 控制php脚本能访问的目录
使用open_basedir选项能够控制PHP脚本只能访问指定的目录,这样能够避免PHP脚本访问
不应该访问的文件,一定程度上限制了phpshell的危害,我们一般可以设置为只能访问网站目录:
open_basedir = D:/usr/www
(6) 关闭危险函数
如果打开了安全模式,那么函数禁止是可以不需要的,但是我们为了安全还是考虑进去。比如,
我们觉得不希望执行包括system()等在那的能够执行命令的php函数,或者能够查看php信息的
phpinfo()等函数,那么我们就可以禁止它们:
disable_functions = system,passthru,exec,shell_exec,popen,phpinfo
如果你要禁止任何文件和目录的操作,那么可以关闭很多文件操作
disable_functions = chdir,chroot,dir,getcwd,opendir,readdir,scandir,fopen,unlink,delete,copy,mkdir, rmdir,rename,file,file_get_contents,fputs,fwrite,chgrp,chmod,chown
以上只是列了部分不叫常用的文件处理函数,你也可以把上面执行命令函数和这个函数结合,
就能够抵制大部分的phpshell了。
(7) 关闭PHP版本信息在http头中的泄漏
我们为了防止黑客获取服务器中php版本的信息,可以关闭该信息斜路在http头中:
expose_php = Off
比如黑客在 telnet www.12345.com 80 的时候,那么将无法看到PHP的信息。
(8) 关闭注册全局变量
在PHP中提交的变量,包括使用POST或者GET提交的变量,都将自动注册为全局变量,能够直接访问,
这是对服务器非常不安全的,所以我们不能让它注册为全局变量,就把注册全局变量选项关闭:
register_globals = Off
当然,如果这样设置了,那么获取对应变量的时候就要采用合理方式,比如获取GET提交的变量var,
那么就要用$_GET[‘var’]来进行获取,这个php程序员要注意。
(9) 打开magic_quotes_gpc来防止SQL注入
SQL注入是非常危险的问题,小则网站后台被入侵,重则整个服务器沦陷,
所以一定要小心。php.ini中有一个设置:
magic_quotes_gpc = Off
这个默认是关闭的,如果它打开后将自动把用户提交对sql的查询进行转换,
比如把 ’ 转为 '等,这对防止sql注射有重大作用。所以我们推荐设置为:
magic_quotes_gpc = On
(10) 错误信息控制
一般php在没有连接到数据库或者其他情况下会有提示错误,一般错误信息中会包含php脚本当
前的路径信息或者查询的SQL语句等信息,这类信息提供给黑客后,是不安全的,所以一般服务器建议禁止错误提示:
display_errors = Off
如果你却是是要显示错误信息,一定要设置显示错误的级别,比如只显示警告以上的信息:
error_reporting = E_WARNING & E_ERROR
当然,我还是建议关闭错误提示。
(11) 错误日志
建议在关闭display_errors后能够把错误信息记录下来,便于查找服务器运行的原因:
log_errors = On
同时也要设置错误日志存放的目录,建议根apache的日志存在一起:
error_log = D:/usr/local/apache2/logs/php_error.log
注意:给文件必须允许apache用户的和组具有写的权限。
MYSQL的降权运行
新建立一个用户比如mysqlstart
net user mysqlstart fuckmicrosoft /add
net localgroup users mysqlstart /del
不属于任何组
如果MYSQL装在d:\mysql ,那么,给 mysqlstart 完全控制 的权限
然后在系统服务中设置,MYSQL的服务属性,在登录属性当中,选择此用户 mysqlstart 然后输入密码,确定。
重新启动 MYSQL服务,然后MYSQL就运行在低权限下了。
如果是在windos平台下搭建的apache我们还需要注意一点,apache默认运行是system权限,
这很恐怖,这让人感觉很不爽.那我们就给apache降降权限吧。
net user apache fuckmicrosoft /add
net localgroup users apache /del
ok.我们建立了一个不属于任何组的用户apche。
我们打开计算机管理器,选服务,点apache服务的属性,我们选择log on,选择this account,我们填入上面所建立的账户和密码,
重启apache服务,ok,apache运行在低权限下了。
实际上我们还可以通过设置各个文件夹的权限,来让apache用户只能执行我们想让它能干的事情,给每一个目录建立一个单独能读写的用户。
这也是当前很多虚拟主机提供商的流行配置方法哦,不过这种方法用于防止这里就显的有点大材小用了。
【二、在PHP代码编写】
虽然国内很多PHP程序员仍在依靠addslashes防止SQL注入,还是建议大家加强中文防止SQL注入的检查。addslashes的问题在于黑客可以用0xbf27来代替单引号,而addslashes只是将0xbf27修改为0xbf5c27,成为一个有效的多字节字符,其中的0xbf5c仍会被看作是单引号,所以addslashes无法成功拦截。
当然addslashes也不是毫无用处,它是用于单字节字符串的处理,多字节字符还是用mysql_real_escape_string吧。
另外对于php手册中get_magic_quotes_gpc的举例:
if (!get_magic_quotes_gpc()) {
l
a
s
t
n
a
m
e
=
a
d
d
s
l
a
s
h
e
s
(
lastname = addslashes(
lastname=addslashes(_POST[‘lastname’]);
} else {
$lastname = $_POST[‘lastname’];
}
最好对magic_quotes_gpc已经开放的情况下,还是对$_POST[’lastname’]进行检查一下。
再说下mysql_real_escape_string和mysql_escape_string这2个函数的区别:
mysql_real_escape_string 必须在(PHP 4 >= 4.3.0, PHP 5)的情况下才能使用。否则只能用 mysql_escape_string ,两者的区别是:mysql_real_escape_string 考虑到连接的
当前字符集,而mysql_escape_string 不考虑。
总结一下:
- addslashes() 是强行加\;
- mysql_real_escape_string() 会判断字符集,但是对PHP版本有要求;
- mysql_escape_string不考虑连接的当前字符集。
在PHP编码的时候,如果考虑到一些比较基本的安全问题,首先一点:
- 初始化你的变量
为什么这么说呢?我们看下面的代码:
PHP代码
1
2
3
4
5
6
7
8
9
10
11
好,我们看上面的代码好像是能正常运行,没有问题,那么加入我提交一个非法的参数过去呢,那么效果会如何呢?比如我们的这个页是http://daybook.diandian.com/login.php,那么我们提交:http://daybook.diandian.com/login.php?admin=1,呵呵,你想一些,我们是不是直接就是管理员了,直接进行管理。
当然,可能我们不会犯这么简单错的错误,那么一些很隐秘的错误也可能导致这个问题,比如phpwind论坛有个漏洞,导致能够直接拿到管理员权限,就是因为有个$skin变量没有初始化,导致了后面一系列问题。那么我们如何避免上面的问题呢?首先,从php.ini入手,把php.ini里面的register_global =off,就是不是所有的注册变量为全局,那么就能避免了。但是,我们不是服务器管理员,只能从代码上改进了,那么我们如何改进上面的代码呢?我们改写如下:
PHP代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
那么这时候你再提交http://daybook.diandian.com/login.php?admin=1就不好使了,因为我们在一开始就把变量初始化为 $admin = 0 了,那么你就无法通过这个漏洞获取管理员权限。
- 防止SQL Injection (sql注射)
SQL 注射应该是目前程序危害最大的了,包括最早从asp到php,基本上都是国内这两年流行的技术,基本原理就是通过对提交变量的不过滤形成注入点然后使恶意用户能够提交一些sql查询语句,导致重要数据被窃取、数据丢失或者损坏,或者被入侵到后台管理。
那么我们既然了解了基本的注射入侵的方式,那么我们如何去防范呢?这个就应该我们从代码去入手了。
我们知道Web上提交数据有两种方式,一种是get、一种是post,那么很多常见的sql注射就是从get方式入手的,而且注射的语句里面一定是包含一些sql语句的,因为没有sql语句,那么如何进行,sql语句有四大句:select 、update、delete、insert,那么我们如果在我们提交的数据中进行过滤是不是能够避免这些问题呢?
于是我们使用正则就构建如下函数:
PHP代码
1
2
3
4
5
6
7
8
9
10
11
12
13
呵呵,那么我们就能够进行校验了,于是我们上面的程序代码就变成了下面的:
PHP代码
1
2
3
4
5
6
7
8
9
10
11
好,问题到这里似乎都解决了,但是我们有没有考虑过post提交的数据,大批量的数据呢?
比如一些字符可能会对数据库造成危害,比如 ’ _ ‘, ’ %’,这些字符都有特殊意义,那么我们如果进行控制呢?还有一点,就是当我们的php.ini里面的magic_quotes_gpc = off的时候,那么提交的不符合数据库规则的数据都是不会自动在前面加’ '的,那么我们要控制这些问题,于是构建如下函数:
PHP代码
1
2
3
4
5
6
7
8
9
10
11
12
13
我们又一次的避免了服务器被沦陷的危险。
最后,再考虑提交一些大批量数据的情况,比如发贴,或者写文章、新闻,我们需要一些函数来帮我们过滤和进行转换,再上面函数的基础上,我们构建如下函数:
PHP代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
呵呵,基本到这里,我们把一些情况都说了一遍,其实我觉得自己讲的东西还很少,至少我才只讲了两方面,再整个安全中是很少的内容了,考虑下一次讲更多,包括php安全配置,apache安全等等,让我们的安全正的是一个整体,作到最安全。
最后在告诉你上面表达的:1. 初始化你的变量 2. 一定记得要过滤你的变量
一个是没有对输入的数据进行过滤(过滤输入),还有一个是没有对发送到数据库的数据进行转义(转义输出)。这两个重要的步骤缺一不可,需要同时加以特别关注以减少程序错误。
对于攻击者来说,进行SQL注入攻击需要思考和试验,对数据库方案进行有根有据的推理非常有必要(当然假设攻击者看不到你的源程序和数据库方案),考虑以下简单的登录表单:
复制代码代码如下:
Username:
Password:
作为一个攻击者,他会从推测验证用户名和密码的查询语句开始。通过查看源文件,他就能开始猜测你的习惯。
比如命名习惯。通常会假设你表单中的字段名为与数据表中的字段名相同。当然,确保它们不同未必是一个可靠的安全措施。
第一次猜测,一般会使用下面例子中的查询:
复制代码代码如下:
<?php $password_hash = md5($_POST['password']); $sql = "SELECT count(*) FROM users WHERE username = '{$_POST['username']}' AND password = '$password_hash'"; ?>使用用户密码的MD5值原来是一个通行的做法,但现在并不是特别安全了。最近的研究表明MD5算法有缺陷,而且大量MD5数据库降低了MD5反向破解的难度。请访问http://md5.rednoize.com/ 查看演示(原文如此,山东大学教授王小云的研究表明可以很快的找到MD5的“碰撞”,就是可以产生相同的MD5值的不同两个文件和字串。MD5是信息摘要算法,而不是加密算法,反向破解也就无从谈起了。不过根据这个成果,在上面的特例中,直接使用md5是危险的。)。
最好的保护方法是在密码上附加一个你自己定义的字符串,例如:
复制代码代码如下:
<?php $salt = 'SHIFLETT'; $password_hash = md5($salt . md5($_POST['password'] . $salt)); ?>当然,攻击者未必在第一次就能猜中,他们常常还需要做一些试验。有一个比较好的试验方式是把单引号作为用户名录入,原因是这样可能会暴露一些重要信息。有很多开发人员在Mysql语句执行出错时会调用函数mysql_error()来报告错误。见下面的例子:
复制代码代码如下:
<?php mysql_query($sql) or exit(mysql_error()); ?>虽然该方法在开发中十分有用,但它能向攻击者暴露重要信息。如果攻击者把单引号做为用户名,mypass做为密码,查询语句就会变成:
复制代码代码如下:
<?php $sql = "SELECT * FROM users WHERE username = ''' AND password = 'a029d0df84eb5549c641e04a9ef389e5'"; ?>当该语句发送到MySQL后,系统就会显示如下错误信息:
复制代码代码如下:
You have an error in your SQL syntax. Check the manual that corresponds to your
MySQL server version for the right syntax to use near ‘WHERE username = ‘’’ AND
password = 'a029d0df84eb55
不费吹灰之力,攻击者已经知道了两个字段名(username和password)以及他们出现在查询中的顺序。除此以外,攻击者还知道了数据没有正确进行过滤(程序没有提示非法用户名)和转义(出现了数据库错误),同时整个WHERE条件的格式也暴露了,这样,攻击者就可以尝试操纵符合查询的记录了。
在这一点上,攻击者有很多选择。一是尝试填入一个特殊的用户名,以使查询无论用户名密码是否符合,都能得到匹配:
复制代码代码如下:
myuser’ or ‘foo’ = ‘foo’ –
假定将mypass作为密码,整个查询就会变成:
复制代码代码如下:
<?php $sql = "SELECT * FROM users WHERE username = 'myuser' or 'foo' = 'foo' -- AND password = 'a029d0df84eb5549c641e04a9ef389e5'"; ?>幸运的是,SQL注入是很容易避免的。正如前面所提及的,你必须坚持过滤输入和转义输出。
虽然两个步骤都不能省略,但只要实现其中的一个就能消除大多数的SQL注入风险。如果你只是过滤输入而没有转义输出,你很可能会遇到数据库错误(合法的数据也可能影响SQL查询的正确格式),但这也不可靠,合法的数据还可能改变SQL语句的行为。另一方面,如果你转义了输出,而没有过滤输入,就能保证数据不会影响SQL语句的格式,同时也防止了多种常见SQL注入攻击的方法。
当然,还是要坚持同时使用这两个步骤。过滤输入的方式完全取决于输入数据的类型(见第一章的示例),但转义用于向数据库发送的输出数据只要使用同一个函数即可。对于MySQL用户,可以使用函数mysql_real_escape_string( ):
复制代码代码如下:
<?php $clean = array(); $mysql = array(); $clean['last_name'] = "O'Reilly"; $mysql['last_name'] = mysql_real_escape_string($clean['last_name']); $sql = "INSERT INTO user (last_name) VALUES ('{$mysql['last_name']}')"; ?>尽量使用为你的数据库设计的转义函数。如果没有,使用函数addslashes()是最终的比较好的方法。
当所有用于建立一个SQL语句的数据被正确过滤和转义时,实际上也就避免了SQL注入的风险。如果你正在使用支持参数化查询语句和占位符的数据库操作类(如PEAR::DB, PDO等),你就会多得到一层保护。见下面的使用PEAR::DB的例子:
复制代码代码如下:
<?php $sql = 'INSERT INTO user (last_name) VALUES (?)'; $dbh->query($sql, array($clean['last_name'])); ?>由于在上例中数据不能直接影响查询语句的格式,SQL注入的风险就降低了。PEAR::DB会自动根据你的数据库的要求进行转义,所以你只需要过滤输出即可。
如果你正在使用参数化查询语句,输入的内容就只会作为数据来处理。这样就没有必要进行转义了,尽管你可能认为这是必要的一步(如果你希望坚持转义输出习惯的话)。实际上,这时是否转义基本上不会产生影响,因为这时没有特殊字符需要转换。在防止SQL注入这一点上,参数化查询语句为你的程序提供了强大的保护。
注:关于SQL注入,不得不说的是现在大多虚拟主机都会把magic_quotes_gpc选项打开,在这种情况下所有的客户端GET和POST的数据都会自动进行addslashes处理,所以此时对字符串值的SQL注入是不可行的,但要防止对数字值的SQL注入,如用intval()等函数进行处理。但如果你编写的是通用软件,则需要读取服务器的magic_quotes_gpc后进行相应处理。
错误与注意事项以及坑
更零散的
svc 用this->的写法可能找不到
array_search搜寻第一个的键名为0 会判断为false 一般还是用in_array把
如[{name: “搜狗浏览器”, soft_id: 10185},{ soft_id: 10185,name: “搜狗浏览器”}] array_unique(array,SORT_REGULAR)会失败
字符串和数字判断相等返回true的问题
更多详细的 看https://www.cnblogs.com/beenupper/p/12635779.html
if('a'==0){
echo "字符串a尽然等于0";//输出了
}
php虽然是弱类型的语言,但它是有数据类型的。大概分为三种类型:字符串、数字、布尔型。上面的问题出现是由于字符串转换为了数字类型。
正常情况下不同类型的值是不能比较的,php 为了比较进行了数据类型转换。把不同类型的值转换为相同类型后再比较。
规则如下:
宽松比较(==)类型转换规则
(1)数字和字符串比较,将字符串转为数字,然后进行比较
(2)数字和布尔型比较,将数字转为布尔型,然后进行比较
(3)字符串和布尔型比较,将字符串转为布尔型,然后进行比较。
宽松比较的落脚点只有两个,一个是布尔型,一个是数字。只有当数字和字符串比较的时候,会把字符串转为数字
三、字符串转数字
- 字符串的开始部分决定了它的数字值。
- 如果该字符串以合法的数字值开始,则使用该数值。否则其值为 0(零)。
- 合法数字值可以是正负号,后面跟着一个或多个数字(可能有小数点),再跟着可选的指数部分。指数部分由 ‘e’ 或 ‘E’ 后面跟着一个或多个数字构成。
四、剖析 ‘a’==0
‘a’ 这是一个字符串类型。0 是数字类型。使用 == 宽松比较,此时发生类型转换。字符串和数字比较,是将字符串转换为数字然后进行比较的。
运算步骤一:根据字符串转数字的规则,字符串的开始部分决定了它的数字值。该字符串的开头不是数字,则它的数字值为0。
所以’a’ 转换为数字类型时,它其实为0了。不仅’a’等于0,‘abc’,‘aabbcc’ 它们转为数字也是0哦。
运算步骤二:最后比较0是否等于0,结果为真。
字符串的布尔值和布尔值的相等问题
需要注意 即使强制类型转换也 比如(bool)‘false’==false 输出的是false
还是用0 1把
json变对象
在一次工作中跟前端对接API中出现的问题,前端说接口返回值跟接口文档不一致。API文档写的是返回数组,但是实际返回的是一个JSON对象。最后补充这个知识盲点:PHP的数组在转JSON的时候,如果索引连续,则转成数组。如果索引不连续,则会转成对象!
PHP数组转json后,元素变为object的坑🙈
今天项目遇到一个bug,组件排序是用数组做的排序,所以遇到了排序后的数组转为json字符串后,前端解析后变为了对象,导致了出现bug,自己研究了一下发现了一个坑。
首先测试key有序且下标从0开始的一个索引数组,代码如下:
<?php
$a[0] = '1a';
$a[1] = '2a';
$a[2] = '3a';
$b['a'] = $a;
$jStr = json_encode($b);
echo $jStr;
//结果是这样的
{“a”:[“1a”,“2a”,“3a”]}
可以看到这时,josn里的元素是数组,符合预期。
接下来试一下下标0开始,但是key无序的索引数组
<?php
$a[0] = '1a';
$a[2] = '2a';
$a[1] = '3a';
$b['a'] = $a;
$jStr = json_encode($b);
echo $jStr;
//结果是这样的
{“a”:{“0”:“1a”,“2”:“2a”,“1”:“3a”}}
这个时候已经可以看到,其中的a数组已经被json转为了对象
这个时候突发奇想,试一下key有序但是下标不是从0开始的索引数组:
<?php
$a[1] = '1a';
$a[2] = '2a';
$a[3] = '3a';
$b['a'] = $a;
$jStr = json_encode($b);
echo $jStr;
//结果如下:
{“a”:{“1”:“1a”,“2”:“2a”,“3”:“3a”}}
可以看到即使索引数组下标连续,但是下标key不是从0开始的索引数组,依然是会被转换为对象。
结论
如果也遇到了类似的需求,需要某种排序要用到这一步,一定要注意一下这里,要转换为从0开始,下标连续不间断的新数组,json_encode后才会是一个数组的形式。
我后来是转换为从0开始下标的新数组,并且使用ksort之后,json才成功转换为了想要的数组格式。
解决方案
加一个 :
a
r
r
a
y
=
a
r
r
a
y
v
a
l
u
e
s
(
array = array_values(
array=arrayvalues(array);
yield只能一维数组
static关键字
当某个函数一旦完成时,它的所有的变量通常都会删除,然而,如果您希望有些局部变量不被删除,请使用static关键字
注:当您没有在函数外部定义时,该变量仍然是函数的局部变量。
<?php
function myTest()
{
static $x=0;
echo $x;
$x++;
}
myTest();
echo "<br />";
myTest();
echo "<br />";
echo $x;
?>
运行结果
0
1
PHP Notice: Undefined variable: x in /root/soft/playground/index.php on line 16
版本差异——比如有的版本preg_match太大的问题
PHP7.3.14会遇到preg_match太大的问题 而7.4似乎就没有这个问题了
解决方法 php数组名后面可以加空格 数组键名可以有空格
if (count($ret) > 2000 && count($ret) < 4000) {
$notIn['push_action.id'] = array_slice($ret, 0, 2000);
$notIn['push_action.id '] = array_slice($ret, 2000);
} elseif (count($ret) >= 4000 && count($ret) < 6000) {
$notIn['push_action.id'] = array_slice($ret, 0, 2000);
$notIn['push_action.id '] = array_slice($ret, 2000, 2000);
$notIn['push_action.id '] = array_slice($ret, 4000);
}
else {
$notIn['push_action.id'] = $ret;
}
数组索引
当进行某些删除操作时候如
unset($php[1]);
数组的key可能变成Array ( [0] => 0 [3] => 120 ) 此时可sort重建索引 或者和一个空数组array_merge
foreach结束后 itmm会保存最后一次的值
foreach 去除元素遇到的问题
我有一个简单的数组,其中包含所有国家/地区的名称以及每个国家/地区在我的网站上注册的用户总数.它是这样的:
Array (
[1] => Array ( [name] => Afghanistan [total] => 3 )
[2] => Array ( [name] => Albania [total] => 0 )
)
而且,我正在尝试删除拥有0个用户的数组元素(国家/地区).
我已尝试使用此代码,但它无法正常工作:
foreach($country as KaTeX parse error: Expected '}', got 'EOF' at end of input: row) { if (row[‘total’] == 0) {
unset($row);
}
}
这段代码有什么问题?
如果取消设置($row),则只删除局部变量.
而是获取密钥并删除它:
foreach ($country as $i => KaTeX parse error: Expected '}', got 'EOF' at end of input: row) { if (row[‘total’] == 0) {
unset(
c
o
u
n
t
r
y
[
country[
country[i]);
}
}
php数组的根基是关联数组
在理解一门语言之前 先确定其根基
比如php数组都是基于关联数组设计的,因而普通数组的 unique等操作,其key也是原来的。
空值的坑
array_column可能回返回
Array
(
[0] =>
[1] =>
)
这种数据 而empty判断会失效。处理办法array_filter
foreach之前记得empty判断
empty($this->a);这种形式的语法会始终判断为真 不知道是不是php的bug 而且在controller层调用正常 svc层又不正常
一些用法
$this->redisObj->sAdd('proSafeSvcUpdateActivityPhonesFailed',...$this->phones);
public function broadcast($message, array $phones)
list
list() 函数用于在一次操作中给一组变量赋值。可实现类似golang,python 多个返回值的情况
$my_array = array("Dog","Cat","Horse");
list($a, , $c) = $my_array;
echo "我在这里只用了 $a 和 $c 变量。";
函数可添加类型
指定环境变量测试还是正式的方法
以下方法任选其一
一、但是好像不成功 算了 还是nginx吧
1.打开etc/profile文件:vim /etc/profile
新增一个环境变量export PHP_ENV=“DEV”,正式服务器设置为=“PRODUCTION”
这样通过系统环境变量来做一些不同的操作,或者存在不同的数据库账号密码
设置完后重新加载:source /etc/profile
2.修改PHP的php-fpm.conf文件,设置PHP环境变量
底部增加:env[PHP_ENV]=$PHP_ENV
重启PHP服务,service php-fpm restart
然后代码中用$_ENV[‘PHP_ENV’]获取即可
二、 nginx设置
location ~ .php($|/) {
try_files $uri =404;
fastcgi_pass unix:/tmp/php-cgi.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME
d
o
c
u
m
e
n
t
r
o
o
t
document_root
documentrootfastcgi_script_name;
fastcgi_param PHP_ENV ‘DEV’; # PRODUCT DEV RETURN
}
设置完要重启nginx。
然后如果要修改nginx配置 可以先还原配置完 然后最后再加这句就行
然后代码用$_SERVER[‘PHP_ENV’]获取即可
json_decode 对路径无法生效
四斜杠
$j = '{"class":"app\\\\api"}';
dump(json_decode($j, true));//["class" => "app\api"]
三斜杠
$j = '{"class":"app\\\api"}';
dump(json_decode($j, true));//["class" => "app\api"]
双斜杠
$j = '{"class":"app\\api"}';
dump(json_decode($j, true))//null;
单斜杠
$j = '{"class":"app\api"}';
dump(json_decode($j, true));//null
解决方法
1、
// 把json中的\替换为/
$str = str_replace("\\", "/", $json);
//注意:第一个指要两个\\,如果一个的话会被转义掉!!
2、将有反下划线的单独拿出来,用urlencode函数处理后再放进去
反斜杠类名
在new PHPExcel()时总报错,但在前面加个反斜杠就可以了,new \PHPExcel() ,这个斜杠表示,如果当前类中没有,就去全局搜索这个类
新版的php 构造函数不再推荐与类名相同了,如果继续使用 会抛出异常
当定义了命名空间后,要用\Exception
不能exception 因为定义了命名空间,就要用类的全称了 不然会报错
方法1 use \Exception;
方法2 throw new \Exception;
json_encode对数字的坑
json_encode($unique_soft, JSON_NUMERIC_CHECK);
因为查询mysql返回的数据,全部都是加了双引号的(都当成字符串)。我希望的是数字不要加双引号,而字符串就加上双引号。所以在进行json_encode() 时,加了参数 JSON_NUMERIC_CHECK(参考)
但是问题来了:加了这个参数后,它会把 decimal 类型的 价格 price 比如:20.00 ,后面的小数点去掉了,还有一个就是,比如说它也会把比如身份证号转换成了科学计数法了。
然后json_encode对浮点数,比如4.0会错误
//直接var_dump
array(9) {
["m2"]=>
array(2) {
["num"]=>
int(24)
["compare"]=>
float(20)
}
["base_board"]=>
array(2) {
["num"]=>
int(23)
["compare"]=>
float(0)
}
["cpu_name"]=>
array(2) {
["num"]=>
int(23)
["compare"]=>
float(-4.17)
}
["phys_memory"]=>
array(2) {
["num"]=>
int(46)
["compare"]=>
float(-8)
}
["disk_list"]=>
array(2) {
["num"]=>
int(26)
["compare"]=>
float(0)
}
["video_controller"]=>
array(2) {
["num"]=>
int(23)
["compare"]=>
float(0)
}
["physical_adapter"]=>
array(2) {
["num"]=>
int(26)
["compare"]=>
float(0)
}
["sound_device"]=>
array(2) {
["num"]=>
int(23)
["compare"]=>
float(0)
}
["desktop_monitor"]=>
array(2) {
["num"]=>
int(23)
["compare"]=>
float(0)
}
}
//json_encode后
{"m2":{"num":24,"compare":19.999999999999996},
"base_board":{"num":23,"compare":0},
"cpu_name":{"num":23,"compare":-4.1699999999999964},
"phys_memory":{"num":46,"compare":-7.9999999999999964},
"disk_list":{"num":26,"compare":0},
"video_controller":{"num":23,"compare":0},
"physical_adapter":{"num":26,"compare":0},
"sound_device":{"num":23,"compare":0},
"desktop_monitor":{"num":23,"compare":0}}
数组元素为null 但是isset判断为false
定义一个PHP 数组 $arr = array('jjbao'=>null,'cnima'=>null); isset($arr['jjbao'])
居然返回的false
把 null 改为 0 就没有问题了
找了半个小时 蛋痛啊
解决方法: isset() 对于数组中为 NULL 的值不会返回 TRUE,而 array_key_exists() 会。
unset内存反而大?
备注 unset也生效了 用unset之前 是500k 用了300k 但是注释那种 只要200k
小皮面板 phpstudy 的坑
composer
1先在phpstudy里安装composer 然后会显示版本过低 之后再linux系统根目录下 使用composer self-update 命令 更新composer