找到
54
篇与
PHP语言
相关的结果
- 第 4 页
-
探索NativePHP:PHP桌面应用开发的新星 在编程的世界里,PHP一直以其强大的Web开发能力而闻名。然而,随着技术的发展,PHP的应用领域也在不断扩展。今天,我要介绍的是一个令人兴奋的新库——NativePHP,它允许开发者使用PHP语言来编写桌面应用程序。 NativePHP简介 NativePHP是一个新兴的PHP库,它利用PHP的强大功能和易用性,为开发者提供了一种全新的桌面应用开发方式。这个库通过与本地操作系统的交互,使得PHP代码能够直接调用系统级的API,从而实现桌面应用的功能。 图片 入门指导 要开始使用NativePHP,你需要确保你的开发环境已经安装了PHP和Composer。 1. 安装NativePHP:你可以通过Composer来安装NativePHP库。在你的项目目录下运行以下命令: composer require nativephp/php2. 创建你的第一个应用:安装完成后,你可以开始编写你的第一个NativePHP应用。以下是一个简单的Hello World示例: require 'vendor/autoload.php'; use NativePHP\Application; $app = new Application(); $app->run();图片 代码案例 让我们来看一个更复杂的例子,展示如何使用NativePHP创建一个简单的窗口应用: require 'vendor/autoload.php'; use NativePHP\Application; use NativePHP\Window; $app = new Application(); $window = new Window(); $window->setTitle('Hello NativePHP'); $window->setSize(800, 600); $window->show(); $app->run();NativePHP优劣势 优势: 易用性:NativePHP继承了PHP的易用性,使得开发者可以快速上手。 跨平台:NativePHP支持Windows、macOS和Linux,使得你的应用可以跨平台运行。 社区支持:虽然是一个新兴库,但NativePHP已经吸引了一些开发者的关注,社区正在逐渐壮大。 劣势: 性能:与C++或Java等语言编写的桌面应用相比,PHP的性能可能会有所不足。 成熟度:作为一个新兴库,NativePHP可能还缺乏一些成熟的功能和工具。 使用方法和场景 NativePHP适用于那些希望使用PHP来开发桌面应用的开发者。它特别适合以下场景: 快速原型开发:如果你需要快速验证一个想法,NativePHP可以帮助你快速构建一个桌面应用原型。 小型到中型项目:对于不需要极高性能的小型到中型桌面应用,NativePHP是一个不错的选择。 注意事项 在使用NativePHP时,开发者需要注意以下几点: 性能优化:由于PHP的性能限制,开发者需要对应用进行性能优化。 安全性:与Web应用相比,桌面应用可能面临不同的安全挑战,开发者需要特别注意。 结尾 亲爱的开发工程师们,NativePHP为我们打开了一个全新的世界,让我们能够用熟悉的PHP语言来探索桌面应用的开发。不要害怕挑战,勇敢地尝试新事物,你会发现编程的乐趣远不止于此。让我们一起,用代码创造未来! 在这篇文章中,我们深入探讨了NativePHP库,并提供了入门指导、代码案例和使用场景。希望这些信息能帮助你在开发旅程中做出明智的选择,并在技术的海洋中乘风破浪。
-
Web 开发 2025:PHP 依然有一席之地 在 2025 年即将到来之际,许多新晋 Web 开发人员都在思考一个问题:学习 PHP 还有必要吗? PHP 作为 Web 开发领域的元老,几十年来一直占据着主导地位,其影响力不容小觑。超过 75% 的网站都运行在 WordPress、Drupal 和 Joomla 等平台之上,而这些平台正是由 PHP 驱动的。然而,随着新兴编程语言和框架的涌现,PHP 的未来似乎蒙上了一层阴影,学习它的价值也因此受到质疑。 为了帮助您做出明智的决定,我们将深入探讨在 2025 年学习 PHP 的利弊,并分析其未来发展趋势。 1、PHP 的持续相关性 尽管 Python、JavaScript 和 Ruby 等新兴语言来势汹汹,但 PHP 在 Web 开发领域的地位依然不可撼动。尤其是 WordPress 等巨头平台,凭借 PHP 构建,牢牢占据着内容管理系统(CMS)市场的半壁江山。因此,对于专注于网站、博客或电子商务解决方案开发的开发者来说,掌握 PHP 依然是至关重要的技能。 2、框架和工具 现代 PHP 开发早已摆脱了过去的繁琐,这主要归功于 Laravel 等框架的兴起。Laravel 以其优雅的语法和强大的功能著称,为开发者提供了前所未有的便捷体验。Artisan 命令行工具、Eloquent ORM 和 Blade 模板引擎等更是如虎添翼,极大提升了开发效率。类似的,其他流行框架如 Django 和 Ruby on Rails 也采用了类似的设计理念, 致力于为开发者提供高效愉悦的开发体验。 3、PHP 8.x 和性能提升 PHP 并未故步自封,而是不断发展进化。PHP 8.0 及其后续版本的发布,带来了显著的性能提升和安全增强。其中,全新的 JIT 编译器极大提升了代码执行速度, 使 PHP 在与其他语言的竞争中更具优势。此外,PHP 还积极拥抱其他语言,支持与 Node.js 和 Python 等语言的集成,进一步拓展了其应用领域。 4、繁荣的社区和就业机会 PHP 并未故步自封,而是不断发展进化。PHP 8.0 及其后续版本的发布,带来了显著的性能提升和安全增强。其中,全新的 JIT 编译器极大提升了代码执行速度, 使 PHP 在与其他语言的竞争中更具优势。此外,PHP 还积极拥抱其他语言,支持与 Node.js 和 Python 等语言的集成,进一步拓展了其应用领域。 5、PHP 与新语言 虽然 PHP 宝刀未老,但我们也必须承认,一些新兴语言和框架在某些方面已经走在了前面,尤其是在现代 Web 应用开发领域。 JavaScript (Node.js): Node.js 已然成为全栈开发的热门选择,其最大的优势在于允许开发者使用 JavaScript 同时进行前端和后端开发。随着 React、Vue 等 JavaScript 框架的流行,越来越多的开发者投入 Node.js 的怀抱,享受其带来的流畅开发体验。 Python (Django/Flask): Python 以其简洁易读的语法著称,并且在数据科学和人工智能领域得到了广泛应用。这使得 Python 也成为了 Web 开发领域的热门选择。与 PHP 相比,Django 和 Flask 等 Python 框架提供了更强大的功能和更简洁的语法,对新一代开发者更具吸引力。 Go: Go 语言以其超快的速度和简洁的语法而闻名,在高性能应用和微服务领域越来越受欢迎。 尽管 PHP 依然占据一席之地,但学习 JavaScript 或 Python 等语言可以为你打开更多大门,让你在数据科学、机器学习或移动应用开发等领域大展拳脚。 6、您应该在 2025 年学习 PHP 吗? 如果你专注于开发基于 CMS 的项目,或者需要维护现有的 PHP 系统,那么学习 PHP 依然是明智之举。 而如果你立志成为全栈开发者,JavaScript 或 Python 或许是更佳选择。 当然,鱼和熊掌也可兼得。精通 PHP 的同时掌握前端开发技能,无疑会让你在求职市场上脱颖而出。更重要的是,借助 Laravel 等现代框架,PHP 开发体验也丝毫不逊色于其他语言。 PHP 将成为 2025 年最受好评的编程语言 图片 图表说明:PHP 将成为 2025 年最受好评的编程语言 这张图表预测了到 2025 年,PHP 与其他主流编程语言(JavaScript、Python、Java 和 Go)在受欢迎程度、就业机会和 Web 开发领域的主导地位。 1、受欢迎程度(%):该指标反映了开发者对每种语言的使用率和满意度 JavaScript 以 95% 的高比例遥遥领先,Python 紧随其后,占比 92%。 PHP 依然保持着 80% 的强劲势头,表明其与 Web 开发的密切联系。 Java 和 Go 分别以 75% 和 60% 的比例位居其后。 2、就业机会(%):该指标预测了市场对掌握每种语言的开发者的需求程度 PHP 的就业市场份额高达 82%,凸显了其强劲的就业前景,尤其是在 Web 开发和 CMS 平台领域。 JavaScript 和 Python 分别以 90% 和 88% 的比例领先,而 Java 和 Go 也保持稳定,分别占比 80% 和 65%。 3、Web 开发主导地位(%):该指标衡量每种语言在 Web 开发领域的影响力 JavaScript 在前端开发领域占据绝对优势,占比高达 95%。 PHP 作为服务器端开发的传统优势依然存在,占比 85%,这意味着它将继续为大量 Web 应用提供支持,尤其是 WordPress 和 Laravel 等平台。 Python (70%) 和 Java (50%) 在 Web 应用开发中也占有一席之地,而 Go (40%) 正在某些细分领域逐渐崭露头角。 总而言之,这份图表强调了 PHP 在 Web 开发领域的重要地位,它依然是需求旺盛的主流语言之一,对于从事 CMS 平台和遗留系统开发的开发者来说更是不可或缺的技能。 结论 展望 2025 年,PHP 在 Web 开发领域依然占据着不可忽视的地位,尤其是在 CMS 开发和遗留系统维护方面。 当然,为了拓宽职业发展道路,或者进军人工智能、数据科学等前沿领域,学习 JavaScript 或 Python 等语言也是非常有益的。 总而言之,PHP 本身在不断发展进步,加之 Laravel 等现代框架的助力,对于有志于成为 Web 开发者的新人来说,PHP 依然是一项值得学习的宝贵技能,尤其是在 Web 开发领域。
-
轻HTTP - 基于PHP的一款超灵活专业级HTTP轻量网络请求库 简介 轻HTTP请求库是一款专业级轻量的PHP HTTP客户端,用于发送各种HTTP请求。它支持GET、POST、HEAD、DELETE、PUT、PATCH等方法,可以轻松发送JSON、XML等格式的数据。 $client = new Client(); $response = $client->get('http://www.example.com'); echo $response; // 输出响应体该库的主要特性: 支持主流的HTTP方法:GET、POST、HEAD、DELETE、PUT、PATCH 等。可以发送各种请求,获取不同的响应。 支持URL参数、请求体、请求头、Cookie等设置。可以定制灵活的请求参数。 发送请求体支持JSON、XML、文本等格式。支持的请求数据类型丰富。 简单易用,代码量小巧轻量,使用方法灵活。接口简单明了,学习成本低。 基于PHP原生curl扩展,性能高效稳定。利用curl实现,性能优秀。 项目地址:https://gitee.com/yh-it/php-http-request 觉得本项目不错的话可以帮忙点一下星星Star哦 安装 通过 Composer 安装 1. 安装 Composer curl -sS https://getcomposer.org/installer | php2. 运行安装命令 composer require network/http:dev-master3. 启用 Composer 自动加载 require 'vendor/autoload.php';启用后,可直接使用 $client = new Client(); 基本使用 use network\http\Client; $client = new Client(); $client->param('name', '易航'); // 设置请求参数 $client->header('User-Agent', 'Mozilla/5.0'); // 设置请求头 $response = $client->get('http://www.bri6.cn'); // 发送GET请求 echo $response; // 输出响应体请求参数用于构造请求URL的参数,请求头用于定制客户端信息,发起GET请求后获取响应,并输出响应体。 实例化 Client $client = new network\http\Client();param 和 header 方法 可以调用 param() 方法添加URL参数, header() 方法添加请求头。它们既支持单个设置,也支持批量设置: // 单个设置 $client->param('name', '易航'); $client->header('User-Agent', 'Mozilla/5.0'); // 批量设置 $client->param(['name' => '易航', 'age' => 25]); $client->header(['User-Agent' => 'Mozilla/5.0', 'Content-Type' => 'application/json']);发送GET请求 $response = $client->get('http://bri6.cn'); $response = $client->send('http://www.bri6.cn','GET'); $response = $client->modth('GET')->send('http://www.bri6.cn'); $response = $client->url('http://www.bri6.cn')->send();可以传入请求头和查询参数: $response = $client->get('http://bri6.cn', ['name' => '易航'], ['Accept' => 'application/json']);发送POST请求 $response = $client->post('http://bri6.cn', ['name' => '易航']); 可以传入请求体、请求头: $response = $client->post('http://bri6.cn', ['name' => '易航'], ['Content-Type' => 'application/x-www-form-urlencoded']);其他请求方法 delete(): 发送DELETE请求 put(): 发送PUT请求 patch(): 发送PATCH请求 $response = $client->delete('http://www.bri6.cn'); $response = $client->put('http://www.bri6.cn', ['title' => 'Easy']); $response = $client->patch('http://www.bri6.cn', ['views' => 999]);send 方法 send 方法是该库中最为核心和灵活的一个方法,它可以接收三个参数,并可以对这三个参数的顺序进行混合传参。 传统方式 $client->send('http://www.bri6.cn', 'GET', ['name' => '易航']);方法和URL顺序交换 $client->send('GET', 'http://www.bri6.cn', ['name' => '易航']);URL和参数顺序交换 $client->send(['name' => '易航'], 'http://www.bri6.cn', 'GET');链式设置URL和方法,最后传入参数 $client->url('http://www.bri6.cn')->method('GET')->send(['name' => '易航']);链式设置参数,最后传入URL和方法 $client->param(['name' => '易航'])->send('http://www.bri6.cn', 'GET');链式设置方法,最后传入URL和参数 $client->method('GET')->send('http://www.bri6.cn', ['name' => '易航']);send 方法会自动识别第一个参数是 URL、方法还是参数。同时并不强制要求三个参数全部传入,可以只传入一个或两个参数,并可以通过链式调用的方式进行参数设置。 所以根据不同的参数顺序和参数类型,您可以选择一种简洁清晰和习惯的方式进行请求发送。send 方法力求代码的灵活性,同时也不会造成使用上的困扰。 send 方法核心代码: class Client { public function send($param1 = null, $param2 = null, $param3 = null) { $info = $this->_initSend($param1, $param2, $param3); if (!empty($info->method)) $this->method($info->method); if (!empty($info->url)) $this->url($info->url); if ((!empty($info->params)) && is_array($info->params)) $this->param($info->params); $this->_initialize(); $response_body = curl_exec($this->ch); $http_code = curl_getinfo($this->ch, CURLINFO_HTTP_CODE); $header_size = curl_getinfo($this->ch, CURLINFO_HEADER_SIZE); curl_close($this->ch); $response = $this->response = new Response($http_code, $header_size, $response_body); return $response; } private function _initSend($param1, $param2, $param3) { $url = $this->_isUrl($param1) ?: $this->_isUrl($param2) ?: $this->_isUrl($param3); $method = $this->_isMethod($param1) ?: $this->_isMethod($param2) ?: $this->_isMethod($param3); $params = is_array($param1) ?: is_array($param2) ?: is_array($param3); return (object) [ 'url' => $url, 'method' => $method, 'params' => $params ]; } }所以根据不同的参数顺序和参数类型,您可以选择一种简洁清晰和习惯的方式进行请求发送。send 方法力求代码的灵活性,同时也不会造成使用上的困扰。 助手函数 轻HTTP请求库提供以下助手函数: post() 发送POST请求。用法: network\http\post($url, $params, $headers, $options) $url: 请求URL $params: 携带的参数,可以是数组或false $headers: 自定义请求头,数组格式 $options: 配置信息,数组格式 返回response对象。 示例: $response = network\http\post('http://www.bri6.cn', ['name' => '易航']);get() 发送GET请求。用法: network\http\get($url, $params, $headers, $options)参数与post()方法相同。 示例: $response = network\http\get('http://www.bri6.cn', ['category' => 'technology']);delete() 发送DELETE请求。用法: network\http\delete($url, $params, $headers, $options)参数与post()方法相同。 示例: $response = network\http\delete('http://www.bri6.cn'); put() 发送PUT请求。用法: network\http\put($url, $params, $headers, $options)参数与post()方法相同。 示例: $response = network\http\put('http://www.bri6.cn', ['title' => 'Easy']);patch() 发送PATCH请求。用法: network\http\patch($url, $params, $headers, $options)参数与post()方法相同。 示例: $response = network\http\patch('http://www.bri6.cn', ['views' => 999]); 获取响应信息 使用响应对象获取响应信息: $response = $client->get('http://bri6.cn');可以从响应对象中获取以下信息: $response->code() :获取响应状态码 示例: $code = $response->code(); // 200 $response->header('name') : 获取指定响应头 示例: $content_type = $response->header('Content-Type'); // application/json $response->headers(): 以数组形式获取所有响应头 示例: $headers = $response->headers(); /* [ 'Content-Type' => 'application/json', 'X-Powered-By' => 'PHP/7.2.10' ] */ $response->body(): 获取原始响应体字符串 示例: $body = $response->body(); // '{"name": "易航"}' $response->toObject(): 如果响应是JSON,转换为对象 示例: $data = $response->toObject(); // stdClass Object // ( // [name] => 易航 // ) $response->toArray(): 如果响应是JSON,转换为数组 示例: $data = $response->toArray(); // ['name' => '易航']显示响应体 可以直接输出响应体: echo $response;或者: echo $response->body();这将直接输出响应的原始字符串内容。 示例: $response = $client->get('http://www.bri6.cn'); echo $response; // {"id": 25, "title": "Easy PHP", "content": "Some content here", "views": 999} echo $response->body(); // {"id": 25, "title": "Easy PHP", "content": "Some content here", "views": 999}当您直接输出 $response 对象时,PHP会自动调用 __toString() 魔术方法,并返回响应体字符串。 如果响应的内容类型是JSON,调用 toObject() 或 toArray() 方法转换后再输出也是很好的选择: $data = $response->toArray(); print_r($data); /* [ "id" => 25, "title" => "Easy PHP", "content" => "Some content here", "views" => 999 ] */ 错误与异常 请求过程中发生的curl错误或解析响应时发生的错误将抛出异常,使用try/catch进行捕获。 示例: try { $response = $client->get('http://www.bri6.cn'); } catch (Exception $e) { echo $e->getMessage(); // CURLOPT_URL: Failure (2) 'The requested URL returned error: 404 NOT FOUND' }轻HTTP请求库使用了GuzzleHttp作为底层HTTP客户端,所以会抛出GuzzleHttp相关的异常,常见的有: ConnectionException: 连接异常 RequestException: 请求异常 ClientException: 客户端错误(4xx响应) BadResponseException: 服务器错误(5xx响应) TooManyRedirectsException: 重定向过多异常 所有的异常都继承自Exception,可以使用try/catch捕获。 当解析响应JSON数据时发生错误,也会抛出异常: try { $data = $response->toObject(); } catch (InvalidJsonException $e) { echo $e->getMessage(); // Syntax error, malformed JSON }InvalidJsonException异常是由轻HTTP请求库自己抛出的,用于表明响应JSON数据格式错误。 错误和异常处理是开发一个健全的应用很重要的一环,希望这个错误与异常页面能帮助您更好地处理轻HTTP请求库可能产生的各种异常情况。如果您有任何疑问,请告知我。 其他 另外,如果需要对请求库进行定制开发,可以继承Client类并重写send()方法: namespace network\http; class CustomClient extends Client { public function send($url, $params, $headers) { // 定制发送请求的逻辑 // 调用parent::send($url, $params, $headers)发送请求 } }然后通过 new CustomClient() 使用定制的客户端。 希望这个HTTP客户端库和使用文档能为您提供帮助!如果有任何问题请提Issue或Pull Request。 我会持续更新文档,完整记录轻HTTP请求库的所有功能和用法。如果文档的任何部分不够详尽,请提Issue告知我。 希望这个简洁实用的轻HTTP请求库和配套文档能为广大PHP开发者提供更多便捷!
-
Web 开发 2025:PHP 依然有一席之地 PHP图片 在 2025 年即将到来之际,许多新晋 Web 开发人员都在思考一个问题:学习 PHP 还有必要吗? PHP 作为 Web 开发领域的元老,几十年来一直占据着主导地位,其影响力不容小觑。超过 75% 的网站都运行在 WordPress、Drupal 和 Joomla 等平台之上,而这些平台正是由 PHP 驱动的。然而,随着新兴编程语言和框架的涌现,PHP 的未来似乎蒙上了一层阴影,学习它的价值也因此受到质疑。 为了帮助您做出明智的决定,我们将深入探讨在 2025 年学习 PHP 的利弊,并分析其未来发展趋势。 1、PHP 的持续相关性 尽管 Python、JavaScript 和 Ruby 等新兴语言来势汹汹,但 PHP 在 Web 开发领域的地位依然不可撼动。尤其是 WordPress 等巨头平台,凭借 PHP 构建,牢牢占据着内容管理系统(CMS)市场的半壁江山。因此,对于专注于网站、博客或电子商务解决方案开发的开发者来说,掌握 PHP 依然是至关重要的技能。 2、框架和工具 现代 PHP 开发早已摆脱了过去的繁琐,这主要归功于 Laravel 等框架的兴起。Laravel 以其优雅的语法和强大的功能著称,为开发者提供了前所未有的便捷体验。Artisan 命令行工具、Eloquent ORM 和 Blade 模板引擎等更是如虎添翼,极大提升了开发效率。类似的,其他流行框架如 Django 和 Ruby on Rails 也采用了类似的设计理念, 致力于为开发者提供高效愉悦的开发体验。 3、PHP 8.x 和性能提升 PHP 并未故步自封,而是不断发展进化。PHP 8.0 及其后续版本的发布,带来了显著的性能提升和安全增强。其中,全新的 JIT 编译器极大提升了代码执行速度, 使 PHP 在与其他语言的竞争中更具优势。此外,PHP 还积极拥抱其他语言,支持与 Node.js 和 Python 等语言的集成,进一步拓展了其应用领域。 4、繁荣的社区和就业机会 PHP 并未故步自封,而是不断发展进化。PHP 8.0 及其后续版本的发布,带来了显著的性能提升和安全增强。其中,全新的 JIT 编译器极大提升了代码执行速度, 使 PHP 在与其他语言的竞争中更具优势。此外,PHP 还积极拥抱其他语言,支持与 Node.js 和 Python 等语言的集成,进一步拓展了其应用领域。 5、PHP 与新语言 虽然 PHP 宝刀未老,但我们也必须承认,一些新兴语言和框架在某些方面已经走在了前面,尤其是在现代 Web 应用开发领域。 JavaScript (Node.js): Node.js 已然成为全栈开发的热门选择,其最大的优势在于允许开发者使用 JavaScript 同时进行前端和后端开发。随着 React、Vue 等 JavaScript 框架的流行,越来越多的开发者投入 Node.js 的怀抱,享受其带来的流畅开发体验。 Python (Django/Flask): Python 以其简洁易读的语法著称,并且在数据科学和人工智能领域得到了广泛应用。这使得 Python 也成为了 Web 开发领域的热门选择。与 PHP 相比,Django 和 Flask 等 Python 框架提供了更强大的功能和更简洁的语法,对新一代开发者更具吸引力。 Go: Go 语言以其超快的速度和简洁的语法而闻名,在高性能应用和微服务领域越来越受欢迎。 尽管 PHP 依然占据一席之地,但学习 JavaScript 或 Python 等语言可以为你打开更多大门,让你在数据科学、机器学习或移动应用开发等领域大展拳脚。 6、您应该在 2025 年学习 PHP 吗? 如果你专注于开发基于 CMS 的项目,或者需要维护现有的 PHP 系统,那么学习 PHP 依然是明智之举。 而如果你立志成为全栈开发者,JavaScript 或 Python 或许是更佳选择。 当然,鱼和熊掌也可兼得。精通 PHP 的同时掌握前端开发技能,无疑会让你在求职市场上脱颖而出。更重要的是,借助 Laravel 等现代框架,PHP 开发体验也丝毫不逊色于其他语言。 PHP 将成为 2025 年最受好评的编程语言 PHP 将成为 2025 年最受好评的编程语言图片 这张图表预测了到 2025 年,PHP 与其他主流编程语言(JavaScript、Python、Java 和 Go)在受欢迎程度、就业机会和 Web 开发领域的主导地位。 1、受欢迎程度(%):该指标反映了开发者对每种语言的使用率和满意度。 JavaScript 以 95% 的高比例遥遥领先,Python 紧随其后,占比 92%。 PHP 依然保持着 80% 的强劲势头,表明其与 Web 开发的密切联系。 Java 和 Go 分别以 75% 和 60% 的比例位居其后。 2、就业机会(%):该指标预测了市场对掌握每种语言的开发者的需求程度。 PHP 的就业市场份额高达 82%,凸显了其强劲的就业前景,尤其是在 Web 开发和 CMS 平台领域。 JavaScript 和 Python 分别以 90% 和 88% 的比例领先,而 Java 和 Go 也保持稳定,分别占比 80% 和 65%。 3、Web 开发主导地位(%):该指标衡量每种语言在 Web 开发领域的影响力。 JavaScript 在前端开发领域占据绝对优势,占比高达 95%。 PHP 作为服务器端开发的传统优势依然存在,占比 85%,这意味着它将继续为大量 Web 应用提供支持,尤其是 WordPress 和 Laravel 等平台。 Python (70%) 和 Java (50%) 在 Web 应用开发中也占有一席之地,而 Go (40%) 正在某些细分领域逐渐崭露头角。 总而言之,这份图表强调了 PHP 在 Web 开发领域的重要地位,它依然是需求旺盛的主流语言之一,对于从事 CMS 平台和遗留系统开发的开发者来说更是不可或缺的技能。 结论 展望 2025 年,PHP 在 Web 开发领域依然占据着不可忽视的地位,尤其是在 CMS 开发和遗留系统维护方面。 当然,为了拓宽职业发展道路,或者进军人工智能、数据科学等前沿领域,学习 JavaScript 或 Python 等语言也是非常有益的。 总而言之,PHP 本身在不断发展进步,加之 Laravel 等现代框架的助力,对于有志于成为 Web 开发者的新人来说,PHP 依然是一项值得学习的宝贵技能,尤其是在 Web 开发领域。
-
PHP异步协程开发:优化邮件发送的速度与稳定性 在现代的互联网应用中,邮件发送是一个非常重要的功能,无论是用户注册验证、订单确认还是密码重置等等,都离不开邮件的发送。然而,传统的同步邮件发送方式在处理大量邮件发送时往往效率低下且不稳定。为了解决这个问题,我们可以使用PHP的异步协程开发,通过并发发送邮件,提高发送速度和稳定性。 本文将详细介绍使用PHP异步协程来优化邮件发送的方法,并通过具体的代码示例来说明。 一、PHP异步协程简介 PHP异步协程是指通过利用事件循环机制,将多个任务并发执行,以提高应用程序的执行效率。在传统的PHP开发中,我们通过多线程或多进程来实现并发处理,但这种方式会增加系统资源的开销。而PHP异步协程则通过单线程来同时处理多个任务,不会引起资源开销过大的问题。 二、优化邮件发送的原理 传统的邮件发送方式是同步的,即每发送一封邮件,都要等待邮件发送完成后再发送下一封。这样一来,当需要发送大量邮件时,会花费很长的时间,而且容易造成服务器的负载过大。 而通过PHP异步协程开发,我们可以将邮件发送任务封装成一个异步协程,然后一次性并发发送多个任务,提高发送效率。同时,由于使用了异步协程,可以避免等待发送完成的时间,从而提高整体的稳定性。 三、使用PHP异步协程发送邮件的代码示例 下面是一个使用PHP异步协程发送邮件的代码示例: use SwooleCoroutine; use SwooleCoroutineChannel; use PHPMailerPHPMailerPHPMailer; function sendMail($to, $subject, $body) { go(function () use ($to, $subject, $body) { $mail = new PHPMailer; $mail->isSMTP(); $mail->Host = 'smtp.example.com'; $mail->SMTPAuth = true; $mail->Username = 'username'; $mail->Password = 'password'; $mail->SMTPSecure = 'tls'; $mail->Port = 587; $mail->setFrom('from@example.com'); $mail->addAddress($to); $mail->Subject = $subject; $mail->Body = $body; if ($mail->send()) { echo "发送成功 "; } else { echo "发送失败:" . $mail->ErrorInfo . " "; } }); } $channel = new Channel(); go(function () use ($channel) { for ($i = 1; $i <= 100; $i++) { $channel->push(["to@example.com", "测试邮件{$i}", "这是一封测试邮件"]); } $channel->close(); }); go(function () use ($channel) { while ($data = $channel->pop()) { sendMail($data[0], $data[1], $data[2]); } }); Coroutine::create(function () { Coroutine::sleep(1); // 等待所有邮件发送完成 swoole_event_exit(); // 退出事件循环 });上述代码首先定义了一个 sendMail 函数,用于发送邮件。在 sendMail 函数内部,我们使用了 PHPMailer 库来实现邮件的发送。在异步协程中发送邮件时,需要等待邮件发送完成的时间,因此我们使用了协程的方式来进行处理,保证发送的效率。 然后,我们创建了一个 Channel 通道,将待发送的邮件信息推入该通道,并在另外一个协程中进行发送。 最后,我们使用协程的方式来等待所有邮件发送完成,并退出事件循环。 四、总结 通过使用PHP异步协程开发,我们可以优化邮件发送的速度和稳定性,提高应用程序的性能和响应能力。同时,异步协程的使用也能减少服务器资源的开销,更好地满足用户的需求。 当我们在开发邮件发送功能时,可以参考上述的代码示例,并根据实际需求进行相应的调整和优化。希望本文对广大开发者在优化邮件发送方面有所帮助。
-
PHPY 打破语言界限,使PHP引入Python生态,开创PHP语言AI编程时代! PHPY是什么? phpy 是识沃团队最新推出的开源项目,目标是为 PHP 引入 Python 生态,来弥补 PHP 生态的空缺和不足。phpy 使得 PHP 可以调用所有 Python 的包。包括当下非常流行的 AI 库,如:PyTorch、transformers、TensorFlow 等包括当下非常流行的 AI 库,如 PyTorch、transformers、TensorFlow 等,以及科学计算库,如 Numpy、Pandas、Scikit 等,还可以使用图形界面库,如 PyQt、wxPython 等。 不建议在 php-fpm/apache 短生命周期运行环境下使用,频繁地导入/销毁模块的开销会消耗大量资源环境 Linux环境:Ubuntu 22.04.3 LTS PHP版本:PHP 8.1 或以上版本 Python 3.10 或以上版本 安装PHP8.3 下载 wget wget https://www.php.net/distributions/php-8.3.0.tar.gz tar -zxvf php-8.3.0.tar.gz下载地址:https://www.php.net/downloads安装依赖包 sudo apt-get install libfcgi-dev libfcgi0ldbl libjpeg-turbo8-dev libmcrypt-dev libssl-dev libc-client2007e libc-client2007e-dev libxml2-dev libbz2-dev libcurl4-openssl-dev libjpeg-dev libpng-dev libfreetype6-dev libkrb5-dev libpq-dev libxml2-dev libxslt1-dev libzip-dev libsqlite3-dev libonig-dev pkg-config libxml2-dev libkrb5-dev libssl-dev libsqlite3 libbz2-dev libpng-dev libjpg-dev libfreetype6-dev libc-client2007e-dev libonig-dev libreadline-dev libxslt-dev libzip-dev如果安装的依赖包不存在,请通过命令:apt-cache search freetype 查找相应的安装包安装即可编译 ./configure \ --prefix=/usr/local/php-8.3 \ --with-config-file-path=/usr/local/php-8.3/etc \ --with-zlib-dir \ --with-freetype \ --enable-mbstring \ --enable-soap \ --enable-calendar \ --with-curl \ --with-zlib \ --enable-gd \ --disable-rpath \ --enable-inline-optimization \ --with-bz2 \ --with-zlib \ --enable-sockets \ --enable-sysvsem \ --enable-sysvshm \ --enable-pcntl \ --enable-mbregex \ --enable-exif \ --enable-bcmath \ --with-mhash \ --with-zip \ --with-pdo-mysql \ --with-mysqli \ --with-mysql-sock=/var/run/mysqld/mysqld.sock \ --with-jpeg \ --with-openssl \ --with-fpm-user=www \ --with-fpm-group=www \ --with-libdir=/lib/x86_64-linux-gnu \ --enable-ftp \ --with-kerberos \ --with-gettext \ --with-xmlrpc \ --with-xsl \ --enable-opcache \ --enable-intl \ --with-pear \ --enable-fpm编译安装 make make install配置文件 核心 php.ini 配置文件cp php.ini-production /usr/local/php-8.3/etc/php.ini查看PHP版本/usr/local/php-8.3/bin/php -v PHP 8.3.0 (cli) (built: Dec 5 2023 20:03:56) (NTS) Copyright (c) The PHP Group Zend Engine v4.3.0, Copyright (c) Zend Technologies安装Python 安装Anaconda Anaconda 是一个开源的Anaconda是专注于数据分析的Python发行版本,包含了conda、Python等190多个科学包及其依赖项。 Anaconda就是可以便捷获取包且对包能够进行管理,包括了python和很多常见的软件库和一个包管理器conda。常见的科学计算类的库都包含在里面了,使得安装比常规python安装要容易,同时对环境可以统一管理的发行版本。 下载地址:https://www.anaconda.com/download#downloads wget https://repo.anaconda.com/archive/Anaconda3-2023.09-0-Linux-x86_64.sh命令行中切换到anaconda文件所在目录 sh Anaconda3-2022.05-Linux-x86_64.sh accept the license terms--yes默认安装路径,/home/用户名/anaconda3=查看Anaconda版本 终端输入 conda --version 或者 conda -V /home/www/anaconda3/condabin/conda --version conda 23.7.4创建虚拟环境 $ conda create -n tinywan-python310 python=3.10 Collecting package metadata (current_repodata.json): done Solving environment: done ==> WARNING: A newer version of conda exists. <== current version: 23.7.4 latest version: 23.11.0 Please update conda by running $ conda update -n base -c defaults conda Or to minimize the number of packages updated during conda update use conda install conda=23.11.0 ## Package Plan ## environment location: /home/www/anaconda3/envs/tinywan-python310 added / updated specs: - python=3.10 The following packages will be downloaded: package | build ---------------------------|----------------- pip-23.3.1 | py310h06a4308_0 2.7 MB python-3.10.13 | h955ad1f_0 26.8 MB setuptools-68.0.0 | py310h06a4308_0 936 KB wheel-0.41.2 | py310h06a4308_0 109 KB ------------------------------------------------------------ Total: 30.5 MB The following NEW packages will be INSTALLED: _libgcc_mutex pkgs/main/linux-64::_libgcc_mutex-0.1-main _openmp_mutex pkgs/main/linux-64::_openmp_mutex-5.1-1_gnu bzip2 pkgs/main/linux-64::bzip2-1.0.8-h7b6447c_0 ca-certificates pkgs/main/linux-64::ca-certificates-2023.08.22-h06a4308_0 ld_impl_linux-64 pkgs/main/linux-64::ld_impl_linux-64-2.38-h1181459_1 libffi pkgs/main/linux-64::libffi-3.4.4-h6a678d5_0 libgcc-ng pkgs/main/linux-64::libgcc-ng-11.2.0-h1234567_1 libgomp pkgs/main/linux-64::libgomp-11.2.0-h1234567_1 libstdcxx-ng pkgs/main/linux-64::libstdcxx-ng-11.2.0-h1234567_1 libuuid pkgs/main/linux-64::libuuid-1.41.5-h5eee18b_0 ncurses pkgs/main/linux-64::ncurses-6.4-h6a678d5_0 openssl pkgs/main/linux-64::openssl-3.0.12-h7f8727e_0 pip pkgs/main/linux-64::pip-23.3.1-py310h06a4308_0 python pkgs/main/linux-64::python-3.10.13-h955ad1f_0 readline pkgs/main/linux-64::readline-8.2-h5eee18b_0 setuptools pkgs/main/linux-64::setuptools-68.0.0-py310h06a4308_0 sqlite pkgs/main/linux-64::sqlite-3.41.2-h5eee18b_0 tk pkgs/main/linux-64::tk-8.6.12-h1ccaba5_0 tzdata pkgs/main/noarch::tzdata-2023c-h04d1e81_0 wheel pkgs/main/linux-64::wheel-0.41.2-py310h06a4308_0 xz pkgs/main/linux-64::xz-5.4.5-h5eee18b_0 zlib pkgs/main/linux-64::zlib-1.2.13-h5eee18b_0 Proceed ([y]/n)? y Downloading and Extracting Packages Preparing transaction: done Verifying transaction: done Executing transaction: done # # To activate this environment, use # # $ conda activate tinywan-python310 # # To deactivate an active environment, use # # $ conda deactivate激活虚拟环境 conda activate tinywan-test查看已有的虚拟环境 $ conda env list # conda environments: # base /home/www/anaconda3 tinywan-test * /home/www/anaconda3/envs/tinywan-test进入虚拟环境查看Python版本 (tinywan-python310) www@xxxxxxx:~/anaconda3$ python --version Python 3.10.13安装phpy 下载 git clone https://github.com/swoole/phpy.git生成 ./configure 配置文件 /usr/local/php-8.3/bin/phpize --with-php-config=/usr/local/php-8.3/bin/php-config指定配置文件 ./configure --with-php-config=/usr/local/php-8.3/bin/php-config --with-python-dir=/home/www/anaconda3/envs/tinywan-python310 --with-python-version=3.10参数说明 --with-php-config PHP配置文件 --with-python-dir Python安装目录 --with-python-version Python版本号(只能使用次版本好,如:3.10.15,则填写3.10) 编译安装 make -j4 sudo make installphp.ini 扩展添加 安装成功后,修改 php.ini ,加入 extension=phpy.so vim /usr/local/php-8.3/etc/php.ini // 添加 extension=phpy.so执行 php -m 检查是否成功加载扩展。 $ php -m |grep phpy phpy使用 案例 os.php <?php function main() { $m = PyCore::import("os"); var_dump($m instanceof PyObject); $rs = $m->uname(); echo $rs; echo $rs->version; } main()查看当前操作系统版本执行结果 /phpy/examples$ /usr/local/php-8.3/bin/php os.php bool(true) posix.uname_result(sysname='Linux', nodename='iZbp1cqx6cq0t2gpl995gqZ', release='4.15.0-137-generic', version='#141-Ubuntu SMP Fri Feb 19 13:46:27 UTC 2021', machine='x86_64')#141-Ubuntu SMP Fri Feb 19 13:46:27 UTC 2021(tinywan-python310)其他 gcc 版本升级 ubuntu18.04的Gcc7.5.0升级到9.4.01、添加Ubuntu的测试工具链 (Toolchain) PPA。这个PPA包含了最新版本的GCC,包括GCC 9: sudo add-apt-repository ppa:ubuntu-toolchain-r/test2、更新你的包列表: sudo apt-get update sudo apt-get upgrade3、查看gcc所有版本 sudo apt-cache search gcc4、安装GCC-9: sudo apt install gcc-95、为了让你的系统默认使用GCC-9,你需要更新你的update-alternatives。首先,安装GCC-9为一个可选项 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 90这个命令告诉 update-alternatives 系统 GCC-9 是一个可选项,并给它一个优先级 90 。优先级最高的版本将成为默认版本。如果安装成功会是这样的: gcc -v gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~18.04)
-
PHP 8.0:新时代的编程巨浪 随着技术的不断进步,PHP也在不断演化,迎来了它的8.0版本。这一版本不仅仅是数字的增加,更是PHP语言的一次重大飞跃。今天,让我们一起探索PHP 8.0的新特性,以及它如何为开发者带来前所未有的便利。 图片 PHP 8.0简介 PHP 8.0是PHP语言的最新主要版本,它引入了许多新特性和改进,包括JIT编译器、命名参数、联合类型、属性、改进的类型系统等。这些变化旨在提高性能、增强代码的可读性和可维护性。 PHP 7与PHP 8.0的差异 PHP 8.0在多个方面与PHP 7有所不同,以下是一些主要的差异: JIT编译器:PHP 8.0引入了Just-In-Time (JIT) 编译器,这使得PHP代码的执行速度有了显著提升。 命名参数:PHP 8.0允许开发者使用命名参数,这使得函数调用更加灵活和可读。 联合类型:PHP 8.0支持联合类型,允许一个变量或函数参数接受多种类型的值。 属性:PHP 8.0引入了属性(Attributes),这是一种新的元数据语法,可以用于注解类、方法和属性。 改进的类型系统:PHP 8.0对类型系统进行了改进,包括更严格的类型检查和新的类型声明。 PHP 8.0新增功能和方法 PHP 8.0带来了许多新功能和方法,以下是一些值得关注的亮点: 图片 JIT编译器:通过在配置文件中启用JIT,可以显著提高代码执行速度。 命名参数:允许在函数调用时指定参数名称,例如: htmlspecialchars($string, double_encode: false);联合类型:允许函数参数或返回值为多种类型之一,例如: function foo(int|float $number): int|float { return $number * 2; }属性:使用属性来注解代码,例如: #[Route("/api/items/{id}", methods: ["GET"])] function getItem($id) { // 你的代码 }Match表达式:提供了一种更简洁的switch语法,例如: $status = match ($statusCode) { 200 => 'OK', 404 => 'Not Found', default => 'Unknown', };入门指导 要开始使用PHP 8.0,你需要确保你的开发环境已经支持这个版本。你可以通过以下步骤来安装或升级到PHP 8.0: 检查当前版本:首先,运行以下命令来检查你当前的PHP版本: php -v升级到PHP 8.0:如果你的系统支持包管理器,你可以通过包管理器来安装或升级到PHP 8.0。例如,在Ubuntu上,你可以使用以下命令: sudo apt update sudo apt install php8.0代码案例 让我们来看一个简单的例子,展示PHP 8.0中的一些新特性: // 命名参数 htmlspecialchars($string, double_encode: false); // 联合类型 function foo(int|float $number): int|float { return $number * 2; } // 属性 #[Route("/api/items/{id}", methods: ["GET"])] function getItem($id) { // 你的代码 } // Match表达式 $status = match ($statusCode) { 200 => 'OK', 404 => 'Not Found', default => 'Unknown', };PHP 8.0优劣势 优势: 性能提升:PHP 8.0引入了JIT编译器,显著提高了执行速度。 代码简洁:命名参数和联合类型等新特性使得代码更加简洁和易读。 开发效率:属性和其他改进减少了样板代码,提高了开发效率。 劣势: 兼容性问题:升级到PHP 8.0可能会遇到一些兼容性问题,需要对现有代码进行适配。 学习曲线:新特性可能需要开发者花费时间去学习和适应。 使用方法和场景 PHP 8.0适用于所有希望提高性能和代码质量的PHP开发者。它特别适合以下场景: 高性能应用:对于需要快速响应和高吞吐量的应用,PHP 8.0的JIT编译器是一个巨大的优势。 现代化开发:PHP 8.0的新特性使得代码更加现代化,适合追求最新技术趋势的开发者。 注意事项 在使用PHP 8.0时,开发者需要注意以下几点: 兼容性测试:在升级之前,确保对现有代码进行充分的兼容性测试。 文档学习:深入学习PHP 8.0的新特性,充分利用其带来的优势。 结尾 亲爱的开发工程师们,PHP 8.0为我们带来了新的机遇和挑战。让我们拥抱变化,勇敢地迈向新时代。记住,技术的海洋无边无际,每一次升级都是我们航行的新起点。明天,我们将继续探索PHP 8.1的奥秘,敬请期待! 在这篇文章中,我们深入探讨了PHP 8.0的新特性,并提供了入门指导、代码案例和使用场景。希望这些信息能帮助你在开发旅程中做出明智的选择,并在技术的海洋中乘风破浪。
-
ThinkPHP中日期时间区间查询以及whereTime用法 一、使用where方法进行时间的比较查询 where('create_time', '> time', '2021-8-8'); // 大于某个时间 where('create_time', '<= time', '2020-8-8'); // 小于某个时间 where('create_time', 'between time', ['2020-1-1', '2020-10-1']); // 时间区间查询二、使用whereTime方法 whereTime('birthday', '>=', '1970-10-1')->select(); // 大于某个时间 whereTime('birthday', '<', '2000-10-1')->select(); // 小于某个时间 whereTime('birthday', 'between', ['1970-10-1', '2000-10-1'])->select(); // 时间区间查询 whereTime('birthday', 'not between', ['1970-10-1', '2000-10-1'])->select(); // 不在某个时间区间三、时间表达式 // 获取今天的文章 Db::table('think_news')->whereTime('create_time', 'today')->select(); // 获取昨天的文章 Db::table('think_news')->whereTime('create_time', 'yesterday')->select(); // 获取本周的文章 Db::table('think_news')->whereTime('create_time', 'week')->select(); // 获取上周的文章 Db::table('think_news')->whereTime('create_time', 'last week')->select(); // 获取本月的文章 Db::table('think_news')->whereTime('create_time', 'month')->select(); // 获取上月的文章 Db::table('think_news')->whereTime('create_time', 'last month')->select(); // 获取今年的文章 Db::table('think_news')->whereTime('create_time', 'year')->select(); // 获取去年的文章 Db::table('think_news')->whereTime('create_time', 'last year')->select();四、如果查询当天、本周、本月和今年的时间,还可以简化为: // 获取今天的文章 Db::table('think_news')->whereTime('create_time', 'd')->select(); // 获取本周的文章 Db::table('think_news')->whereTime('create_time', 'w')->select(); // 获取本月的文章 Db::table('think_news')->whereTime('create_time', 'm')->select(); // 获取今年的文章 Db::table('think_news')->whereTime('create_time', 'y')->select();五、时间范围查询 // 查询两个小时内的文章 Db::table('think_news')->whereTime('create_time', '-2 hours')->select(); // 查询两天内的文章 Db::table('think_news')->whereTime('create_time', '-2 days')->select();图片
-
初探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