找到
55
篇与
PHP语言
相关的结果
- 第 5 页
-
初探PHP-Parser和PHP代码混淆 初探PHP-Parser PHP-Parser 是 nikic 用PHP编写的PHP5.2到PHP7.4解析器,其目的是简化静态代码分析和操作。 解析 创建一个解析器实例: use PhpParser\ParserFactory; $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);ParserFactory接收以下几个参数: ParserFactory::PREFER_PHP7 :优先解析PHP7,如果PHP7解析失败则将脚本解析成PHP5 ParserFactory::PREFER_PHP5 :优先解析PHP5,如果PHP5解析失败则将脚本解析成PHP7 ParserFactory::ONLY_PHP7 :只解析成PHP7 ParserFactory::ONLY_PHP5 :只解析成PHP5 将PHP脚本解析成抽象语法树(AST) <?php use PhpParser\Error; use PhpParser\ParserFactory; require 'vendor/autoload.php'; $code = file_get_contents("./test.php"); $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); try { $ast = $parser->parse($code); } catch (Error $error) { echo "Parse error: {$error->getMessage()}"; } var_dump($ast);节点转储 如果是用上面的 var_dump 的话显示的 AST 可能会比较乱,那么我们可以使用 NodeDumper 生成一个更加直观的 AST <?php use PhpParser\NodeDumper; $nodeDumper = new NodeDumper; echo $nodeDumper->dump($stmts);或者我们使用 vendor/bin/php-parse 也是一样的效果 λ vendor/bin/php-parse test.php ====> File test.php: ==> Node dump: array( 0: Stmt_Expression( expr: Expr_Assign( var: Expr_Variable( name: a ) expr: Scalar_LNumber( value: 1 ) ) ) )节点树结构 PHP是一个成熟的脚本语言,它大约有140个不同的节点。但是为了方便使用,将他们分为三类: PhpParser\Node\Stmts 是语句节点,即不返回值且不能出现在表达式中的语言构造。例如,类定义是一个语句,它不返回值,你不能编写类似 func(class {}) 的语句。 PhpParser\Node\expr 是表达式节点,即返回值的语言构造,因此可以出现在其他表达式中。如:$var (PhpParser\Node\Expr\Variable)和func() (PhpParser\Node\Expr\FuncCall)。 PhpParser\Node\Scalars 是表示标量值的节点,如"string" (PhpParser\Node\scalar\string)、0 (PhpParser\Node\scalar\LNumber) 或魔术常量,如"FILE" (PhpParser\Node\scalar\MagicConst\FILE) 。所有 PhpParser\Node\scalar 都是延伸自 PhpParser\Node\Expr ,因为 scalar 也是表达式。 需要注意的是 PhpParser\Node\Name 和 PhpParser\Node\Arg 不在以上的节点之中 格式化打印 使用 PhpParser\PrettyPrinter 格式化代码 <?php use PhpParser\Error; use PhpParser\ParserFactory; use PhpParser\PrettyPrinter; require 'vendor/autoload.php'; $code = file_get_contents('./index.php'); $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); try { $ast = $parser->parse($code); } catch (Error $error) { echo "Parse error: {$error->getMessage()}"; return; } $prettyPrinter = new PrettyPrinter\Standard; $prettyCode = $prettyPrinter->prettyPrintFile($ast); echo $prettyCode;节点遍历 使用 PhpParser\NodeTraverser 我们可以遍历每一个节点,举几个简单的例子:解析php中的所有字符串,并输出 <?php use PhpParser\Error; use PhpParser\ParserFactory; use PhpParser\NodeTraverser; use PhpParser\NodeVisitorAbstract; use PhpParser\Node; require 'vendor/autoload.php'; class MyVisitor extends NodeVisitorAbstract { public function leaveNode(Node $node) { if ($node instanceof Node\Scalar\String_) { echo $node->value; } } } $code = file_get_contents("./test.php"); $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); $traverser = new NodeTraverser; $traverser->addVisitor(new MyVisitor); try { $ast = $parser->parse($code); $stmts = $traverser->traverse($ast); } catch (Error $error) { echo "Parse error: {$error->getMessage()}"; return; }遍历php中出现的函数以及类中的成员方法 <?php class MyVisitor extends NodeVisitorAbstract { public function leaveNode(Node $node) { if ( $node instanceof Node\Expr\FuncCall || $node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Stmt\Function_ || $node instanceof Node\Expr\MethodCall ) { echo $node->name; } } }替换php脚本中函数以及类的成员方法函数名为小写 <?php class MyVisitor extends NodeVisitorAbstract { public function leaveNode(Node $node) { if ($node instanceof Node\Expr\FuncCall) { $node->name->parts[0] = strtolower($node->name->parts[0]); } elseif ($node instanceof Node\Stmt\ClassMethod) { $node->name->name = strtolower($node->name->name); } elseif ($node instanceof Node\Stmt\Function_) { $node->name->name = strtolower($node->name->name); } elseif ($node instanceof Node\Expr\MethodCall) { $node->name->name = strtolower($node->name->name); } } }需要注意的是所有的 visitors 都必须实现 PhpParser\NodeVisitor 接口,该接口定义了如下4个方法: public function beforeTraverse(array $nodes); public function enterNode(\PhpParser\Node $node); public function leaveNode(\PhpParser\Node $node); public function afterTraverse(array $nodes); beforeTraverse 方法在遍历开始之前调用一次,并将其传递给调用遍历器的节点。此方法可用于在遍历之前重置值或准备遍历树。 afterTraverse 方法与 beforeTraverse 方法类似,唯一的区别是它只在遍历之后调用一次。 在每个节点上都调用 enterNode 和 leaveNode 方法,前者在它被输入时,即在它的子节点被遍历之前,后者在它被离开时。 这四个方法要么返回更改的节点,要么根本不返回(即null),在这种情况下,当前节点不更改。 PHP代码混淆 下面举两个php混淆的例子,比较简单(郑老板@zsx所说的20分钟内能解密出来的那种),主要是加深一下我们对 PhpParser 使用 phpjiami 大部分混淆都会把代码格式搞得很乱,用 PhpParser\PrettyPrinter 格式化一下代码 <?php use PhpParser\Error; use PhpParser\ParserFactory; use PhpParser\PrettyPrinter; require 'vendor/autoload.php'; $code = file_get_contents('./test.php'); $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); try { $ast = $parser->parse($code); } catch (Error $error) { echo "Parse error: {$error->getMessage()}\n"; return; } $prettyPrinter = new PrettyPrinter\Standard; $prettyCode = $prettyPrinter->prettyPrintFile($ast); file_put_contents('en_test.php', $prettyCode);格式基本能看了 图片 因为函数和变量的乱码让我们之后的调试比较难受,所以简单替换一下混淆的函数和变量 <?php use PhpParser\Error; use PhpParser\NodeFinder; use PhpParser\ParserFactory; require 'vendor/autoload.php'; $code = file_get_contents("./index_1.php"); $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); $nodeFinder = new NodeFinder; try { $ast = $parser->parse($code); } catch (Error $error) { echo "Parse error: {$error->getMessage()}\n"; return; } $Funcs = $nodeFinder->findInstanceOf($ast, PhpParser\Node\Stmt\Function_::class); $map = []; $v = 0; foreach ($Funcs as $func) { $funcname = $func->name->name; if (!isset($map[$funcname])) { if (!preg_match('/^[a-z0-9A-Z_]+$/', $funcname)) { $code = str_replace($funcname, "func" . $v, $code); $v++; $map[$funcname] = $v; } } } $v = 0; $map = []; $tokens = token_get_all($code); foreach ($tokens as $token) { if ($token[0] === T_VARIABLE) { if (!isset($map[$token[1]])) { if (!preg_match('/^\$[a-zA-Z0-9_]+$/', $token[1])) { $code = str_replace($token[1], '$v' . $v++, $code); $map[$token[1]] = $v; } } } } file_put_contents("index_2.php", $code);变量和函数基本能看了,还是有一些数据是乱码,这个是它自定义函数加密的字符串,大多数都是php内置的函数,我们调试一下就基本能看到了 图片 但是得注意一下,phpjiami有几个反调试的地,在35行的地方打个断点 图片 可以看到4个反调试的点: 第一个点: 当你是以cli运行php的时候就会直接 die() 掉,直接注释掉即可 php_sapi_name()=="cli" ? die() : '';第二个点: 和第一个差不多,也是验证运行环境的,cli模式下没有这些变量索引,直接注释即可 if (!isset($_SERVER["HTTP_HOST"]) && !isset($_SERVER["SERVER_ADDR"]) && !isset($_SERVER["REMOTE_ADDR"])) { die(); }第三个点: 如果你在 if 语句处停留超过100ms的话就会直接 die 掉,注释即可 $v46 = microtime(true) * 1000; eval(""); if (microtime(true) * 1000 - $v46 > 100) { die(); }第四个点: $51就是整个文件内容,这行是用于加密前的文件对比是否完整,如果不完整则执行$52(),因为$52不存在所以会直接报错退出,而如果对比是完整的话那么就是$53,虽然$53也不存在,但只是抛出一个Warning,所以我们这里也是直接把这行注释掉。 !strpos(func2(substr($v51, func2("???"), func2("???"))), md5(substr($51, func2("??"), func2("???")))) ? $52() : $53;注释完之后我们在return那里打一个断点,可以发现在return那里我们需要解密的文件内容呈现了出来。 图片 解密之后的内容: <?php @eval("//Encode by phpjiami.com,Free user."); // Clear the uploads directory every hour highlight_file(__FILE__); $sandbox = "uploads/" . md5("De1CTF2020" . $_SERVER['REMOTE_ADDR']); @mkdir($sandbox); @chdir($sandbox); if ($_POST["submit"]) { if (($_FILES["file"]["size"] < 2048) && Check()) { if ($_FILES["file"]["error"] > 0) { die($_FILES["file"]["error"]); } else { $filename = md5($_SERVER['REMOTE_ADDR']) . "_" . $_FILES["file"]["name"]; move_uploaded_file($_FILES["file"]["tmp_name"], $filename); echo "save in:" . $sandbox . "/" . $filename; } } else { echo "Not Allow!"; } } function Check() { $BlackExts = array("php"); $ext = explode(".", $_FILES["file"]["name"]); $exts = trim(end($ext)); $file_content = file_get_contents($_FILES["file"]["tmp_name"]); if ( !preg_match('/[a-z0-9;~^`&|]/is', $file_content) && !in_array($exts, $BlackExts) && !preg_match('/\.\./', $_FILES["file"]["name"]) ) { return true; } return false; } ?> <html> <head> <meta charset="utf-8"> <title>upload</title> </head> <body> <form action="index.php" method="post" enctype="multipart/form-data"> <input type="file" name="file" id="file"><br> <input type="submit" name="submit" value="submit"> </form> </body> </html>其实phpjiami加密之后的整个脚本就是解密我们文件的脚本,我们的文件内容被加密之后放在了 ?> 最后面 图片 整个解密过程也比较简单,其中$v51是我们加密之后内容,$v55是解密后的内容。 $v55 = str_rot13(@gzuncompress(func2(substr($v51,-974,$v55))));其中func2是解密函数 图片 最后是拿func2解密之后的代码放在这个 eval 中执行 图片 还有一种比较简单快捷的方法是通过 hook eval 去获取 eval 的参数,因为不涉及 PHP-Parser 所以就不过多展开了。 Enphp混淆 官网:http://enphp.djunny.com/ github: https://github.com/djunny/enphp 使用官方的加密例子: <?php include './func_v2.php'; $options = array( //混淆方法名 1=字母混淆 2=乱码混淆 'ob_function' => 2, //混淆函数产生变量最大长度 'ob_function_length' => 3, //混淆函数调用 1=混淆 0=不混淆 或者 array('eval', 'strpos') 为混淆指定方法 'ob_call' => 1, //随机插入乱码 'insert_mess' => 0, //混淆函数调用变量产生模式 1=字母混淆 2=乱码混淆 'encode_call' => 2, //混淆class 'ob_class' => 0, //混淆变量 方法参数 1=字母混淆 2=乱码混淆 'encode_var' => 2, //混淆变量最大长度 'encode_var_length' => 5, //混淆字符串常量 1=字母混淆 2=乱码混淆 'encode_str' => 2, //混淆字符串常量变量最大长度 'encode_str_length' => 3, // 混淆html 1=混淆 0=不混淆 'encode_html' => 2, // 混淆数字 1=混淆为0x00a 0=不混淆 'encode_number' => 1, // 混淆的字符串 以 gzencode 形式压缩 1=压缩 0=不压缩 'encode_gz' => 0, // 加换行(增加可阅读性) 'new_line' => 1, // 移除注释 1=移除 0=保留 'remove_comment' => 1, // debug 'debug' => 1, // 重复加密次数,加密次数越多反编译可能性越小,但性能会成倍降低 'deep' => 1, // PHP 版本 'php' => 7, ); $file = 'test.php'; $target_file = 'en_test.php'; enphp_file($file, $target_file, $options);加密之后大概长这样子 图片 可以看到,我们的大部分字符串、函数等等都被替换成了类似于 $GLOBALS{乱码}[num] 这种形式,我们将其输出看一下: 图片 可以看到我们原本的脚本中的字符串都在此数组里面,所以我们只要将$GLOBALS{乱码}[num]还原成原来对应的字符串即可。 那么我们如何获取 $GLOBALS{乱码} 数组的内容,很简单,在我们获取AST节点处打断点即可找到相关内容: 图片 $split = $ast[2]->expr->expr->args[0]->value->value; $all = $ast[2]->expr->expr->args[1]->value->value; $str = explode($split, $all); var_dump($str);可以看到,和上面输出的是一样的(如果加密等级不一样则还需要加一层 gzinflate ) 图片 然后就是通过AST一个节点一个节点将其替换即可,如果不知道节点类型的同学可以用 $GLOBALS[A][1] ,将其输出出来看一下即可,然后根据节点的类型和数据进行判断即可,如下: class PhpParser\Node\Expr\ArrayDimFetch#1104 (3) { public $var => class PhpParser\Node\Expr\ArrayDimFetch#1102 (3) { public $var => class PhpParser\Node\Expr\Variable#1099 (2) { public $name => string(7) "GLOBALS" protected $attributes => array(2) { ... } } public $dim => class PhpParser\Node\Expr\ConstFetch#1101 (2) { public $name => class PhpParser\Node\Name#1100 (2) { ... } protected $attributes => array(2) { ... } } protected $attributes => array(2) { 'startLine' => int(2) 'endLine' => int(2) } } public $dim => class PhpParser\Node\Scalar\LNumber#1103 (2) { public $value => int(1) protected $attributes => array(3) { 'startLine' => int(2) 'endLine' => int(2) 'kind' => int(10) } } protected $attributes => array(2) { 'startLine' => int(2) 'endLine' => int(2) } }根据上面的节点编写脚本 public function leaveNode(Node $node) { if ( $node instanceof PhpParser\Node\Expr\ArrayDimFetch && $node->var instanceof PhpParser\Node\Expr\ArrayDimFetch && $node->var->var instanceof PhpParser\Node\Expr\Variable && $node->var->var->name === "GLOBALS" && $node->var->dim instanceof PhpParser\Node\Expr\ConstFetch && $node->var->dim->name instanceof PhpParser\Node\Name && $node->var->dim->name->parts[0] === $this->str && $node->dim instanceof PhpParser\Node\Scalar\LNumber ) { return new PhpParser\Node\Scalar\String_($this->str_arr[$node->dim->value]); } return null; }解出来的内容如下,可以看到大部分已经成功解密出来了 图片 还有就是解密的一部分出现这样语句:('highlight_file')(__FILE__); ,很明显不符合我们平时的写法,将其节点重命名一下 if ( ($node instanceof Node\Expr\FuncCall || $node instanceof Node\Expr\StaticCall || $node instanceof Node\Expr\MethodCall) && $node->name instanceof Node\Scalar\String_ ) { $node->name = new Node\Name($node->name->value); }现在看起来就舒服多了 图片 我们分析剩下乱码的部分 图片 可以看到是函数里面的局部变量还是乱码,从第一句可以看出所有的局部变量都是以 & $GLOBALS[乱码] 为基础的,而 & $GLOBALS[乱码] 是我们上面已经找出来的,所以也是将其替换即可。 if ( $node instanceof \PhpParser\Node\Stmt\Expression && $node->expr instanceof \PhpParser\Node\Expr\AssignRef && $node->expr->var instanceof \PhpParser\Node\Expr\Variable && $node->expr->expr instanceof \PhpParser\Node\Expr\ArrayDimFetch && $node->expr->expr->var instanceof \PhpParser\Node\Expr\Variable && $node->expr->expr->var->name === "GLOBALS" && $node->expr->expr->dim instanceof \PhpParser\Node\Expr\ConstFetch && $node->expr->expr->dim->name instanceof \PhpParser\Node\Name && $node->expr->expr->dim->name->parts != [] ) { $this->Localvar = $node->expr->var->name; return NodeTraverser::REMOVE_NODE; } else if ( $node instanceof \PhpParser\Node\Expr\ArrayDimFetch && $node->var instanceof \PhpParser\Node\Expr\Variable && $node->var->name === $this->Localvar && $node->dim instanceof \PhpParser\Node\Scalar\LNumber ) { return new \PhpParser\Node\Scalar\String_($this->str_arr[$node->dim->value]); }替换之后,可以看到与 & $GLOBALS[乱码] 有关的已经全部被替换了,只有变量部分是乱码了 替换变量为 $v 这种形式 <?php function BeautifyVariables($code) { $v = 0; $map = []; $tokens = token_get_all($code); foreach ($tokens as $token) { if ($token[0] === T_VARIABLE) { if (!isset($map[$token[1]])) { if (!preg_match('/^\$[a-zA-Z0-9_]+$/', $token[1])) { $code = str_replace($token[1], '$v' . $v++, $code); $map[$token[1]] = $v; } } } } return $code; }至此所有代码全部被还原(除了变量名这种不可抗拒因素之外) 图片 还有一部分是没有用的全局变量和常量,手动或者根据AST去进行删除即可,下面贴一下完整解密脚本 <?php require "./vendor/autoload.php"; use \PhpParser\Error; use \PhpParser\ParserFactory; use \PhpParser\NodeTraverser; use \PhpParser\NodeVisitorAbstract; use \PhpParser\Node; use \PhpParser\PrettyPrinter\Standard; class MyVisitor extends NodeVisitorAbstract { public $str; public $str_arr; public $Localvar; public function leaveNode(Node $node) { if ( $node instanceof \PhpParser\Node\Expr\ArrayDimFetch && $node->var instanceof \PhpParser\Node\Expr\ArrayDimFetch && $node->var->var instanceof \PhpParser\Node\Expr\Variable && $node->var->var->name === "GLOBALS" && $node->var->dim instanceof \PhpParser\Node\Expr\ConstFetch && $node->var->dim->name instanceof \PhpParser\Node\Name && $node->var->dim->name->parts[0] === $this->str && $node->dim instanceof \PhpParser\Node\Scalar\LNumber ) { return new \PhpParser\Node\Scalar\String_($this->str_arr[$node->dim->value]); } if (($node instanceof Node\Expr\FuncCall || $node instanceof Node\Expr\StaticCall || $node instanceof Node\Expr\MethodCall) && $node->name instanceof Node\Scalar\String_ ) { $node->name = new Node\Name($node->name->value); } if ( $node instanceof \PhpParser\Node\Stmt\Expression && $node->expr instanceof \PhpParser\Node\Expr\AssignRef && $node->expr->var instanceof \PhpParser\Node\Expr\Variable && $node->expr->expr instanceof \PhpParser\Node\Expr\ArrayDimFetch && $node->expr->expr->var instanceof \PhpParser\Node\Expr\Variable && $node->expr->expr->var->name === "GLOBALS" && $node->expr->expr->dim instanceof \PhpParser\Node\Expr\ConstFetch && $node->expr->expr->dim->name instanceof \PhpParser\Node\Name && $node->expr->expr->dim->name->parts != [] ) { $this->Localvar = $node->expr->var->name; return NodeTraverser::REMOVE_NODE; } else if ( $node instanceof \PhpParser\Node\Expr\ArrayDimFetch && $node->var instanceof \PhpParser\Node\Expr\Variable && $node->var->name === $this->Localvar && $node->dim instanceof \PhpParser\Node\Scalar\LNumber ) { return new \PhpParser\Node\Scalar\String_($this->str_arr[$node->dim->value]); } return null; } } function BeautifyVariables($code) { $v = 0; $map = []; $tokens = token_get_all($code); foreach ($tokens as $token) { if ($token[0] === T_VARIABLE) { if (!isset($map[$token[1]])) { if (!preg_match('/^\$[a-zA-Z0-9_]+$/', $token[1])) { $code = str_replace($token[1], '$v' . $v++, $code); $map[$token[1]] = $v; } } } } return $code; } $code = file_get_contents("./en_test.php"); $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); try { $ast = $parser->parse($code); } catch (Error $error) { echo "Parse error: {$error->getMessage()}\n"; return; } var_dump($ast); $split = $ast[2]->expr->expr->args[0]->value->value; $all = $ast[2]->expr->expr->args[1]->value->value; $str1 = $ast[2]->expr->var->dim->name->parts[0]; $str_arr = explode($split, $all); $visitor = new MyVisitor; $visitor->str = $str1; $visitor->str_arr = $str_arr; $traverser = new NodeTraverser; $traverser->addVisitor($visitor); $stmts = $traverser->traverse($ast); $prettyPrinter = new Standard; $code = $prettyPrinter->prettyPrintFile($stmts); $code = BeautifyVariables($code); echo $code;注:需要注意的是 enphp 使用的全局变量不一定是 GLOBALS ,也可能是 _SERVER、_GET 等等,根据具体情况进行判断,还有就是加密等级不同对应的解密方式也是不同的,不过其中的思想都是大同小异 参考 https://www.52pojie.cn/thread-693641-1-1.html https://www.52pojie.cn/thread-883976-1-1.html https://xz.aliyun.com/t/7363 https://github.com/nikic/PHP-Parser/blob/master/doc/2_Usage_of_basic_components.markdown
-
关于 PHP 初级闭包(Closure) 匿名函数 提到闭包就不得不想起匿名函数,也叫闭包函数(closures),貌似 PHP 闭包实现主要就是靠它。声明一个匿名函数是这样: $func = function() { }; //带结束符可以看到,匿名函数因为没有名字,如果要使用它,需要将其返回给一个变量。匿名函数也像普通函数一样可以声明参数,调用方法也相同: $func = function($param) { echo $param; }; $func('易航博客'); //输出:易航博客顺便提一下,PHP 在引入闭包之前,也有一个可以创建匿名函数的函数: create function ,但是代码逻辑只能写成字符串,这样看起来很晦涩并且不好维护,所以很少有人用。 实现闭包 将匿名函数在普通函数中当做参数传入,也可以被返回。这就实现了一个简单的闭包。 下边有三个例子 例一:在函数里定义一个匿名函数,并且调用它 function printStr() { $func = function ($str) { echo $str; }; $func('易航博客'); } printStr();例二:在函数中把匿名函数返回,并且调用它 function getPrintStrFunc() { $func = function ($str) { echo $str; }; return $func; } $printStrFunc = getPrintStrFunc(); $printStrFunc('易航博客');例三:把匿名函数当做参数传递,并且调用它 function callFunc($func) { $func('易航博客'); } // 也可以直接将匿名函数进行传递。如果你了解js,这种写法可能会很熟悉 callFunc(function( $str ) { echo $str; });连接闭包和外界变量的关键字:USE 闭包可以保存所在代码块上下文的一些变量和值。PHP 在默认情况下,匿名函数不能调用所在代码块的上下文变量,而需要通过使用 use 关键字。 换一个例子看看: function getMoney() { $rmb = 1; $dollar = 6; $func = function () use ($rmb) { echo $rmb; echo $dollar; }; $func(); } getMoney(); //输出: //1 //报错,找不到dorllar变量可以看到,dollar 没有在 use 关键字中声明,在这个匿名函数里也就不能获取到它,所以开发中要注意这个问题。 有人可能会想到,是否可以在匿名函数中改变上下文的变量,但我发现是不可以的: function getMoney() { $rmb = 1; $func = function () use ($rmb) { echo $rmb; //把$rmb的值加1 $rmb++; }; $func(); echo $rmb; } getMoney(); // 输出: // 1 // 1所以 use 所引用的也只不过是变量的一个副本而已。但是我想要完全引用变量,而不是复制。 要达到这种效果,其实在变量前加上 & 符号就可以了: function getMoney() { $rmb = 1; $func = function () use (&$rmb) { echo $rmb; //把$rmb的值加1 $rmb++; }; $func(); echo $rmb; } getMoney(); // 输出: // 1 // 2这样匿名函数就可以引用上下文的变量了。如果将匿名函数返回给外界,匿名函数会保存 use 所引用的变量,而外界则不能得到这些变量,这样形成‘闭包’这个概念可能会更清晰一些。 根据描述改变一下上面的例子: function getMoneyFunc() { $rmb = 1; $func = function () use (&$rmb) { echo $rmb; //把$rmb的值加1 $rmb++; }; return $func; } $getMoney = getMoneyFunc(); $getMoney(); $getMoney(); $getMoney(); // 输出: // 1 // 2 // 3总结 PHP 闭包的特性并没有太大惊喜,其实用 class 就可以实现类似甚至强大得多的功能,更不能和 js 的闭包相提并论,只能期待 PHP 以后对闭包支持的改进。不过匿名函数还是挺有用的,比如在使用 preg_replace_callback 等之类的函数可以不用在外部声明回调函数了。 图片
-
被“嫌弃”的PHP:未来仍光明 即使面临各种新技术的挑战,PHP 的未来仍然光明。多年来,在 Web 开发社区内部形成了一种厌恶 PHP 的气氛。现如今,厌恶 PHP 和赞美新技术(如 Node)几乎成了一种奇想。特别是在年轻的社区,PHP 被认为是一只“恐龙”。 PHP 是一门伟大的编程语言。 它并不完美,有利有弊,但总的来说,如果你从事 Web 开发工作,你最好试着去理解它,而不是跟风去厌恶它…… 你甚至可以从中学到一些东西! 图片 我们来看看 PHP 和 Node 之间的区别,这些区别让很多人留在了 PHP 平台,也让其他人转向了不同的编程语言! 社区 PHP 社区比 Node 社区大。 NPM/Node 社区也很大,但缺乏能够真正维护好和做好 Node 包的人。 很多模块被弃用或不再更新。最糟糕的是,大多数模块依赖了其他大量的模块,这意味着如果你使用的模块依赖了一个包含漏洞的旧模块,你可能不知情,或者需要花很多时间自己去更新所有的东西。 这很重要,因为不管一门编程语言或一个框架有多好,如果没有人维护,或者如果没有关于它的讨论、PR或者开源项目,它最终就会消亡。 PHP 生态系统为你提供了大量的库:JWT 身份验证、生成 Excel 电子表格和 PDF、缓存管理、ORM 框架……这些库被广泛使用,具有很好的安全性,且提供了良好的文档。 Symfony 框架也提供了一些官方文档,比如 LexikJWTAuthentication! 事实上,大约 80% 的 Web 应用使用 PHP 开发。 框架 Symfony 和 Laravel 这两个主要的 PHP 框架现在是 Web 的一个巨大组成部分。Laravel 在美国很受欢迎,Symfony 在欧洲很受欢迎,如果我们把 WordPress 去掉,这两个框架占了 PHP 生态系统的 90% 以上。 这些框架比大多数 Node 的框架都要老,并且比现在的 Node 框架拥有更广泛的包和文档生态系统。 在使用 Symfony 过程中遇到了问题?版本 3?版本 4?这都不是事!大量的 StackOverflow 帖子、Medium 文章、官方文档可供你参考。 Symfony 和 Laravel 也提供了一些“基本的项目结构”,你当然可以不用它,你可以按照你想要的方式构建你的项目,但这些基本模式通常适用于多种类型的应用。 在大多数情况下,我们可以通过配置对它们进行调整,以满足我们的各种需求。由于这些框架已经推出了好几年,你可能想到的大多数有用的特性都已经有了,因为在你之前的那些开发人员也有与你同样的需求。 我们以 Express 为例,它是 Node 最著名的框架,主要用于编写 API,它并没有提供强制的结构。这意味着一个没有经验的开发人员更有可能构建出一些不符合标准的东西,而 PHP 框架在这方面的风险要小得多。 性能 Node 的速度很快,在某些情况下比 PHP 更快,但 PHP 也不是太糟糕。 PHP 8.1 借助 OPCache 和 JIT 编译获得更快的执行速度。 Node 在速度方面利用了它的异步特性,但它是单线程的。PHP 利用了在多个线程上运行的优势,而且是同步的。 事实上,现在服务器的价格一般都不会很高,伸缩一个 Web 应用从未像今天这样容易。对于小型 Web 应用来说,鉴于如今的计算能力,性能不再是一个值得花太多时间去争论的点。 然而,对于大规模的应用程序,价格可能是一个关注点。 这就是为什么把常用的 PHP-FPM/Nginx 栈换成 Swoole 会是一个不错的选择。 我曾经看到过一些 PHP 应用程序将 Swoole 作为底层的 HTTP 服务器,在性能上击败了 Node! 此外,使用消息队列是平衡应用程序工作负载的一个很好的方法,这可以很容易地使用 PHP 和 Node 来实现。 易用性 虽然 Node/Express 经常被用来编写 API,并与使用 React/Angular/Vue 等框架构建的前端通信,但大多数 PHP 框架都采用了 MVC 模式。 MVC 即模型视图控制器。一张图片胜过长篇大论:如果你不了解 MVC,这里有一张图可以帮你快速理解 MVC 模式。 图片 构建一个前后端分离的应用程序通常比使用支持前后端技术栈的框架要慢。事实上,许多后端开发人员知道如何编写出色的 HTML/CSS,但不熟悉 React 或其他框架的概念或语法。 结论 PHP 和 Node 各有优点和缺点。 如果你需要稳定性、可靠性和长期支持,我建议使用 PHP。 这些框架成熟且安全,在我看来是首选。 不过,对于需要高吞吐量和实时数据处理的 API 来说,Node 是一个不错的选择。另外,有一些项目不能用 PHP 来完成,比如 Discord 机器人(尽管可能可以用 PHP 来实现,但已经有一个官方的 JavaScript 库……) 有时候,用另一种编程语言来开发应用程序也是不错的,我们可以从中发现一些新的概念或做事的方式,然后将它们应用到其他编程语言中。
-
PHP 8.2 将在今年内发布,一起来看看都有什么新特征 PHP 8.2 预计将于今年 11 月发布,最新的稳定版本是 PHP 8.1.5。虽然现在还为时过早,但对更新的接受程度参差不齐。 但是,知道会发生什么可以帮助您为最新的 PHP 版本做好准备。通过了解新功能和不推荐使用的功能,您可以了解更新可能如何影响开发。这些知识还可以帮助您为最终发布做好准备。 在这篇文章中,我们将回顾最新的 PHP 版本。然后我们将介绍 PHP 8.2 中的新功能并讨论发布时间表。 图片 PHP 版本概述 PHP 7.4 引入了类型化属性、下划线数字分隔符和各种改进。从那时起,已经发布了更多的 PHP 迭代。 2020 年 11 月发布的 PHP 8.0 带来了一些基本功能。除了语法和性能增强之外,该版本还包括: 命名参数 匹配语法 Union 类型 Constructor Property Promotion JIT(影响 PHP 执行源代码的方式) 一年后出现了 PHP 8.1,这是最新的主要 PHP 版本。此更新包括重要功能,例如: Intersection 类型 只读属性 Enums Fibers 从不返回类型 掌握最新版本的 PHP 有助于提高网站的性能和安全性。但是,重要的是要知道在升级之前会发生哪些变化。如果您有兴趣测试 PHP 8.2 的当前状态,可以通过 GitHub 进行。 PHP 8.2 中的新功能 PHP 8.2 预计将于 2022 年底发布。这是当前的发布时间表,计划于 2022 年 11 月 24 日发布通用版本 (GA): 19be6b 19be6b 19be6b 19be6b 19be6b 19be6b 19be6b 19be6b 19be6b 19be6b 19be6b 19be6b 19be6b 19be6b 根据 PHP 网站上的官方文档,应该有一些新特性和不推荐使用的功能。 新的 memory_reset_peak_usage 函数 PHP 8.2 将包含一个名为 memory_reset_peak_usage 的新函数。它将重置 memory_get_peak_usage 函数返回的峰值内存使用量。 对于涉及多次调用操作并记录每次迭代的峰值内存使用情况的情况,此功能将很有帮助。开发人员将能够使用此新功能在请求的生命周期内的任何给定时间重置峰值内存使用量。 只读类 在 PHP 8.1 中引入,只读属性将在 PHP 8.2 中扩展以添加语法糖,以便所有类属性一次都是只读的: readonly class Post { public function __construct( public string $title, public Author $author, public string $body, public DateTime $publishedAt, ) { } }这将防止将动态属性添加到类中。 Null 和 False 独立类型 在 PHP 8.2 中,false 的返回类型将作为独立类型使用,而不是严格的联合类型,用于发生错误时,这已经是可能的: function alwaysFalse(): false { return false; }null 类型也是如此。例如,作为独立类型,与以前不同,NullPost::getAuthor() 将只能返回 null 。 弃用动态属性 动态属性将在 PHP 8.2 中被弃用,导致 PHP 9.0 出现 ErrorException。这些属性是在对象上设置的: class Post { public string $title; } // … $post->name = 'Name';动态属性允许在没有严格的类声明的情况下创建类(例如,值对象)时具有灵活性。对于依赖动态属性的开发人员来说,他们的弃用可能会成为一个问题,因为这会促使他们更多地进行静态分析。出于这个原因,一些开发人员对 PHP 8.2 的这种变化感到担忧。 但是,使用 __get 和 __set 的类仍将支持动态属性,stdClass 的对象也将如此。 或者,开发人员可以在这些属性的类上使用在全局命名空间中声明的新 #[AllowDynamicProperties]attribute: # [AllowDynamicProperties] class User{} $user = new User(); $user->foo = 'bar';虽然不建议这样做,但另一种选择是禁用弃用警告。 新的 /n 修饰符 PHP 8.2 将包含对 preg_* 函数系列的 /n (no capture) modifier 的支持。使用时,除了已命名的捕获组之外,任何具有()meta-characters 的组都不会捕获。 本质上,结果与将每个组标记为非捕获相同。 此更改背后的原因是修饰符简化了多个组的复杂正则表达式。开发人员可以将所有组标记为非捕获,而不是将每个组都营销为非捕获。 然后,他们可以选择并命名需要捕获的特定组。 在回溯中编辑参数 许多开发人员使用从代码库跟踪堆栈跟踪和生产错误的服务。这些服务可以在出现问题时通知用户。例如,在调试应用程序时跟踪调用堆栈很有帮助。 但是,有时堆栈跟踪可能包含敏感信息,例如用户名和密码。PHP 8.2 将包含一个 #[SensitiveParameter] 属性,当出现问题时,该属性将防止此信息包含在堆栈跟踪中: function test($foo, #[\SensitiveParameter] $bar, $baz) { throw new Exception('Error'); } test('foo', 'bar', 'baz');PHP 8.2 将使用敏感参数从堆栈跟踪中编辑私有信息,使其更加安全。这些参数确保数据不会出现在错误日志中。请注意,此属性仅可用于参数。 弃用 ${} 字符串插值 有多种方法可以使用 PHP 在字符串中嵌入变量。但是,PHP 8.2 将弃用两种方法。第一个是在字符串中使用 ${}: $str = "Hello ${world}";第二个是使用 ${}(变量) : $str = "Hello ${(world)}";这对开发人员来说可能不是一个重大问题,因为两种最流行的字符串插值方法仍然有效。 弃用部分支持的可调用对象 另一个不推荐使用的更改是部分支持的 callables。有多种方法可以在 PHP 中创建可调用对象。可以使用 $callable() 语法、user_call_func(array) 或使用带有回调的函数调用带或不带参数的函数。 已弃用的可调用模式包括以下内容: $callable = "self::method"; $callable = "parent::method"; $callable = "static::method"; $callable = ["self", "method"]; $callable = ["parent", "method"]; $callable = ["static", "method"]; $callable = ["MyClass", "MyParentClass::myMethod"]; $callable = [new MyClass(), "MyOtherClass::myMethod"]从 PHP 8.2 开始,调用上述任何一个都将导致以下弃用通知: class Test { public static function myMethod(): void { echo "Called"; } public static function call(): void { $callable = 'self::myMethod'; call_user_func($callable); } } $callable = Test::call(); // "Called"但是,将这些可调用对象传递给 is_callable 函数或将它们与可调用参数类型一起使用不会生成弃用消息。为了防止出现弃用通知,开发人员可以使用::class 魔术方法将可调用代码中的 parent、self 和 static 关键字转换为各自的类名。 更改背后的部分原因是允许将可调用对象用于类型化属性。它使它们不那么依赖于上下文。 MySQLi 不能再用 libmysql 编译 过去,PHP 支持两个库来连接 MySQL 数据库:mysqlnd 和 libmysql。自 PHP 5.4 起,前者已成为默认库。但是,可以通过扩展编译 MySQLi。 从 PHP 8.2 开始,将不支持使用 libmysql 编译 MySQLi 扩展。尝试这样做会导致配置错误: ./configure --with-mysqli=FOO不再支持将 mysqli 与外部库链接 这不太可能对开发人员造成任何重大错误。但是,通过 LDAP 和 SASL 自动重新连接和身份验证支持 libmysql 支持的两个 mysqlnd 不可用的最大功能。
-
PHP 过滤 XSS 攻击插件源码实例 Xss 攻击是最经常遇到的攻击了,其中原理大家应该都懂了,我就不再这里做更多的详解了。 今天给大家分享的实例源码,直接可用的那种。虽然现在很多框架都封装了这种,但是作为 PHP 开发者的你,XSS 攻击原理与防止还是要懂得的。 图片 文档说明: 1.将 waf.php 传到要包含的文件的目录 2.在页面中加入防护,有两种做法,根据情况二选一即可: a 在所需要防护的页面加入代码: require_once('waf.php');就可以做到页面防注入、跨站。如果想整站防注,就在网站的一个公用文件中,如数据库链接文件 config.inc.php 中添加 require_once('waf.php'); 来调用本代码 b 在每个文件最前加上代码 在 php.ini 中找到: Automatically add files before or after any PHP document. auto_prepend_file = waf.php路径;PHP 文件 waf.php 隐藏内容,请前往内页查看详情
-
PHP JsonDb 轻量级文件数据库系统 介绍 JsonDb 是一款由原生 PHP 实现的非关系型轻量级 JSON 文件数据库。如果你需要存储各种基础类的数据,或者一个站点内有多个小项目,那么 JsonDb 就是你最佳的选择。它包括查询、新增、更新、删除等对数据的基本操作,适合存储数据量不大的数据 使用帮助文档:https://yepydvmpxo.k.topthink.com/@json-db/ 图片 软件架构 由纯原生 PHP 实现的 JSON 文件数据库,将数据存储为 JSON 格式,不占用 MySql 资源纯以读写文件的形式查询数据库,用法类似于 ThinkPHP 的查询。 安装教程 composer require jsondb/jsondb:dev-master使用说明 use JsonDb\JsonDb\Db; // composer自动加载 require 'vendor/autoload.php'; // 默认关闭数据压缩、加密并开启调试模式,可使用自定义配置 // 自定义配置项 具体配置请参考文档:https://yepydvmpxo.k.topthink.com/@json-db/ $json_path = $_SERVER['DOCUMENT_ROOT'] . 'content' . DIRECTORY_SEPARATOR . 'JsonDb'; Db::setConfig([ 'path' => $json_path, // 数据存储路径(必须配置) 'file_suffix' => '.json', // 文件后缀名 'debug' => true, // 调试模式 'encode' => null, // 数据加密函数 'decode' => null, // 数据解密函数 ]); // 添加单条数据 Db::name('table_name')->insert([ 'a' => 5, 'b' => "测试5" ]); // 添加多条数据 Db::name('table_name')->insertAll([ [ 'a' => 5, 'b' => "测试5" ], [ 'c' => 1, 'b' => "测试" ] ]); // 删除一行中的部分数据 Db::name('table_name')->where('b', '测试3')->delete(['a', 'b']); // 删除一行数据 Db::name('table_name')->where('b', '测试3')->deleteAll(); // 更新数据 Db::name('table_name')->where('b', '测试4')->update(['c' => '测试测试']); // 根据ID查询数据 Db::name('table_name')->where('id', 0)->find(); // 查询单条数据 Db::name('table_name')->where('b', '测试')->find(); // 查询多条数据 Db::name('table_name')->where('b', '测试4')->select(); // 查询所有数据 Db::name('table_name')->selectAll(); // 自定义查询表达式 Db::name('table_name')->where('id', '>', 4)->select(); // 链式where Db::name('table_name')->where('id', 1)->where('a', 2)->select(); // 自定义判断条件 $select = Db::name('table_name')->where('`field_id` == 0 || `field_b` == `测试4`')->select(); // 字段LIKE查询 Db::name('table_name')->whereLike('b', '%测试')->select(); // 限制结果数量 Db::name('user')->where('status', 1)->limit(10)->select(); // 限制每次最大写入数量 Db::name('user')->limit(100)->insertAll($userList);
-
PHP获取网站标题、关键词与描述 PHP如何获取网站标题、关键词与描述呢? 在网页采集过程中,我们需要获取一个网站的meta信息,如title、keywords、description等,但是如果用普通的正则匹配很容易出错。那么到底该如何写这个PHP的代码,这篇文章为你带来解决方法。 使用get_meta_tags函数获取meta信息 比如我们要获取 http://blog.yihang.info 这个网页的meta信息,可以直接使用php内置函数get_meta_tags获取,代码如下: <?php $meta_tags = get_meta_tags("http://blog.yihang.info/"); print_r($meta_tags); ?>运行结果:你会发现获取了网页的关键词与描述,但是发现缺少了网页的标题,原因是标题并不是meta标签,而是组成的,所以我们的完整代码应该如下: /** 获取META信息 */ function get_sitemeta($url) { $data = file_get_contents($url); $meta = array(); if (!empty($data)) { #Title preg_match('/<TITLE>([\w\W]*?)<\/TITLE>/si', $data, $matches); if (!empty($matches[1])) { $meta['title'] = $matches[1]; } #Keywords preg_match('/<META\s+name="keywords"\s+content="([\w\W]*?)"/si', $data, $matches); if (empty($matches[1])) { preg_match("/<META\s+name='keywords'\s+content='([\w\W]*?)'/si", $data, $matches); } if (empty($matches[1])) { preg_match('/<META\s+content="([\w\W]*?)"\s+name="keywords"/si', $data, $matches); } if (empty($matches[1])) { preg_match('/<META\s+http-equiv="keywords"\s+content="([\w\W]*?)"/si', $data, $matches); } if (!empty($matches[1])) { $meta['keywords'] = $matches[1]; } #Description preg_match('/<META\s+name="description"\s+content="([\w\W]*?)"/si', $data, $matches); if (empty($matches[1])) { preg_match("/<META\s+name='description'\s+content='([\w\W]*?)'/si", $data, $matches); } if (empty($matches[1])) { preg_match('/<META\s+content="([\w\W]*?)"\s+name="description"/si', $data, $matches); } if (empty($matches[1])) { preg_match('/<META\s+http-equiv="description"\s+content="([\w\W]*?)"/si', $data, $matches); } if (!empty($matches[1])) { $meta['description'] = $matches[1]; } } return $meta; }
-
PHP图片上传代码 PHP上传图片完整实 HTML部分 <html> <head> <meta charset="utf-8"> <title>银狐笔记图片上传实例</title> </head> <body> <form action="up.php" method="post" enctype="multipart/form-data"> <label for="file">文件名:</label> <input type="file" name="file" id="file"><br> <input type="submit" name="submit" value="提交"> </form> </body> </html>form表单提交数据给PHP文件,enctype="multipart/form-data" ,这个代码不能少,获取文件类型。 PHP代码 <?php // 允许上传的图片后缀 $allowedExts = array("gif", "jpeg", "jpg", "png"); $temp = explode(".", $_FILES["file"]["name"]); echo $_FILES["file"]["size"]; $extension = end($temp); // 获取文件后缀名 if ( ( ($_FILES["file"]["type"] == "image/gif") || ($_FILES["file"]["type"] == "image/jpeg") || ($_FILES["file"]["type"] == "image/jpg") || ($_FILES["file"]["type"] == "image/pjpeg") || ($_FILES["file"]["type"] == "image/x-png") || ($_FILES["file"]["type"] == "image/png") ) && ($_FILES["file"]["size"] < 204800) // 小于 200 kb && in_array($extension, $allowedExts) ) { if ($_FILES["file"]["error"] > 0) { echo "错误:: " . $_FILES["file"]["error"] . "<br>"; } else { echo "上传文件名: " . $_FILES["file"]["name"] . "<br>"; echo "文件类型: " . $_FILES["file"]["type"] . "<br>"; echo "文件大小: " . ($_FILES["file"]["size"] / 1024) . " kB<br>"; echo "文件临时存储的位置: " . $_FILES["file"]["tmp_name"] . "<br>"; // 判断当前目录下的 upload 目录是否存在该文件 // 如果没有 upload 目录,你需要创建它,upload 目录权限为 777 if (file_exists("upload/" . $_FILES["file"]["name"])) { echo $_FILES["file"]["name"] . " 文件已经存在。 "; } else { // 如果 upload 目录不存在该文件则将文件上传到 upload 目录下 move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]); echo "文件存储在: " . "upload/" . $_FILES["file"]["name"]; } } } else { echo "非法的文件格式"; }
-
PHP万能Curl PHP完整实用Curl函数 PHP curl功能函数,请求网站那必须用curl,curl方法速度最快 这个curl函数功能非常全面了,可以请求99%的网站。 function curl($url, $paras = []) { $ch = curl_init(); if (isset($paras['Header'])) { $Header = $paras['Header']; } else { $Header[] = "Accept:*/*"; $Header[] = "Accept-Encoding:gzip,deflate,sdch"; $Header[] = "Accept-Language:zh-CN,zh;q=0.8"; $Header[] = "Connection:close"; } curl_setopt($ch, CURLOPT_HTTPHEADER, $Header); if (isset($paras['ctime'])) { // 连接超时 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $paras['ctime']); } else { curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); } if (isset($paras['rtime'])) { // 读取超时 curl_setopt($ch, CURLOPT_TIMEOUT, $paras['rtime']); } if (isset($paras['post'])) { curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $paras['post']); } if (is_array($paras['get'])) { $paras['get'] = http_build_query($paras['get']); $url = strstr($url, '?') ? trim($url, '&') . '&' . $paras['get'] : $url . '?' . $paras['get']; } if (isset($paras['header'])) { curl_setopt($ch, CURLOPT_HEADER, true); } if (isset($paras['cookie'])) { curl_setopt($ch, CURLOPT_COOKIE, $paras['cookie']); } if (isset($paras['refer'])) { if ($paras['refer'] == 1) { curl_setopt($ch, CURLOPT_REFERER, 'http://m.qzone.com/infocenter?g_f='); } else { curl_setopt($ch, CURLOPT_REFERER, $paras['refer']); } } if (isset($paras['ua'])) { curl_setopt($ch, CURLOPT_USERAGENT, $paras['ua']); } else { curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"); } if (isset($paras['nobody'])) { curl_setopt($ch, CURLOPT_NOBODY, 1); } curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_ENCODING, "gzip"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); if (isset($paras['GetCookie'])) { curl_setopt($ch, CURLOPT_HEADER, 1); $result = curl_exec($ch); preg_match_all("/Set-Cookie: (.*?);/m", $result, $matches); $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $header = substr($result, 0, $headerSize); //状态码 $body = substr($result, $headerSize); $ret = [ "Cookie" => $matches, "body" => $body, "header" => $header, 'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE), ]; curl_close($ch); return $ret; } $ret = curl_exec($ch); if (isset($paras['loadurl'])) { $Headers = curl_getinfo($ch); if (isset($Headers['redirect_url'])) { $ret = $Headers['redirect_url']; } else { $ret = false; } } curl_close($ch); return $ret; }使用方法 GET访问 curl('http://blog.yihang.info');GET携带参数访问 curl('http://blog.yihang.info', [ 'get' => [ 'url' => 'blog.yihang.info' ] ]);POST访问 curl('http://blog.yihang.info', [ 'post' => [ 'url' => 'blog.yihang.info' ] ]);或者 curl('http://blog.yihang.info', [ 'post' => 'url=blog.yihang.info' ]);带Cookie curl('http://blog.yihang.info', [ 'cookie' => 'cookie内容' ]);模拟refer curl('http://blog.yihang.info', [ 'refer' => 'https://xxx' ]);模拟UA curl('http://blog.yihang.info', [ 'ua' => 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36' ]);上传文件 curl('http://blog.yihang.info', [ 'post' => [ 'file' => new CURLFile(realpath("Curl.jpg")) ] ]);或者 curl('http://blog.yihang.info', [ 'post' => new CURLFile(realpath("Curl.jpg")) ]);获取301地址 curl('http://blog.yihang.info', [ 'loadurl' => 1 ]);返回Header信息 curl('http://blog.yihang.info', [ 'header' => 1 ]);设置请求头 curl('http://blog.yihang.info', [ 'Header' => [ 'accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3 accept-encoding: gzip, deflate, br accept-language: zh-CN,zh;q=0.9 cache-control: max-age=0' ] ]);获取请求的全部信息 curl('http://blog.yihang.info', [ 'post' => [ 'user' => 123456, 'pwd' => 123 ], 'GetCookie' => 1 ]);