首页
关于
Search
1
给你10个市场数据调研报告的免费下载网站!以后竞品数据就从这里找!
139 阅读
2
php接口优化 使用curl_multi_init批量请求
131 阅读
3
2024年备考系统架构设计师
102 阅读
4
《从菜鸟到大师之路 ElasticSearch 篇》
102 阅读
5
PHP 文件I/O
89 阅读
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
登录
Search
标签搜索
php函数
php语法
性能优化
安全
错误和异常处理
问题
vue
Composer
Session
缓存
框架
Swoole
api
并发
异步
正则表达式
php-fpm
mysql 索引
开发规范
协程
dafenqi
累计撰写
785
篇文章
累计收到
7
条评论
首页
栏目
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
副业
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
页面
关于
搜索到
785
篇与
的结果
2023-08-10
PHP的SimpleXML遍历所有子元素
PHP的SimpleXML遍历所有子元素<?phpheader('Content-Type:text/html;charset=utf-8'); //设置编码/ 创建XML格式的字符串 /$str = <<<XML<?xml version='1.0' encoding='gb2312'?> <computerbook>PHP从入门到精通</computerbook> <computerbook>PHP项目开发全程实录</computerbook>XML;/ /$xml = simplexml_load_string($str); //创建一个simplexml对象foreach($xml->children() as $layer_one){ //循环输出根节点 print_r($layer_one); //查看节点结构 echo ''; foreach($layer_one->children() as $layer_two){ //循环输出第二层根节点print_r($layer_two); //查看节点结构 echo '<br>';}}?>运行结果SimpleXMLElement Object ( [computerbook] => PHP从入门到精通 )SimpleXMLElement Object ( [0] => PHP从入门到精通 )SimpleXMLElement Object ( [computerbook] => PHP项目开发全程实录 )SimpleXMLElement Object ( [0] => PHP项目开发全程实录 )PHP的SimpleXML访问特定节点元素和属性<?phpheader('Content-Type:text/html;charset=utf-8'); //设置编码/ 创建XML格式的字符串 /$str = <<<XML<?xml version='1.0' encoding='gb2312'?> <computerbook>PHP从入门到精通</computerbook> <computerbook name='PHP项目开发全程实录'/>XML;/ /$xml = simplexml_load_string($str); //创建simpleXML对象echo $xml[name].''; //输出根元素的属性nameecho $xml->book[0]->computerbook.''; //输出子元素中computerbook的值echo $xml->book[1]->computerbook['name'].''; //输出computerbook的属性值?>运行结果商品PHP从入门到精通PHP项目开发全程实录
2023年08月10日
16 阅读
0 评论
0 点赞
2023-08-10
你必须知道的 17 个 Composer 最佳实践(已更新至 22 个)
你必须知道的 17 个 Composer 最佳实践(已更新至 22 个)尽管大多数 PHP 开发人员都知道如何使用 Composer,但并不是所有的人都在有效的或以最好的方式来使用它。 所以我决定总结一些在我日常工作流程很重要的东西。大多数技巧的哲学是 “稳,不冒险”,这意味着如果有更多的方法来处理某些事情,我会使用最有把握不容易出错的方法。Tip #1: 阅读文档我是真心这样认为的. 文档 是一个非常有用的东西并且在长远看来几个小时的阅读将会为您节省更多的时间。你会惊讶原来 Composer 可以做这么多事情.Tip #2: 注意 项目 和 库 之间的区别无论你是创建 项目 还是 库 , 了解这一点非常重要,它们每一个都需要单独的一套做法。一个库是一个可重用的包,你可以添加一个依赖比如: symfony / symfony,doctrine / orm 或者 elasticsearch/elasticsearch.一个项目通常是一个应用程序,依赖于几个库。它通常是不可重用的(没有其它项目会要求它作为依赖。典型的例子是电子商务网站,客户支持系统等)。我将在下面的提示中区分库和项目。Tip #3: 使用特定的依赖关系 #关于应用程序的版本如果你正在创建一个应用程序,您应该使用最具体的版本来定义依赖项,假如你需要解析 YAML 文件,你应该指定这样的依赖版本 “symfony / YAML”:“4.0.2”。即使你遵循了库的依赖版本控制,在次要版本和补丁版本中也有可能中断向后兼容性。例如,如果你使用的是 “symfony / Symfony”:“^ 3.1”, 会有 3.2 的版本有可能会破坏你的应用程序测试用列。或者可能新版本有 bug 没有修正,那么 php_codesniffer 检测你的代码格式的时候会导致新问题,这在很大程度上可能会破坏一个应用的构建。依赖关系的更新应该是深思熟虑的,而不是偶然的。其中的一个技巧更详细地讨论了它。这听起来像一个多余的,但它可以防止你的同事不小心在项目中添加一个新的库文件或更新所有依赖的时候出错,(不然的话,你们有可能会导致浪费大量的时间在审核代码上)。Tip #4: 对库依赖项使用版本范围创建库时,应尽可能定义最大的可用版本范围。比如创建了一个库,要使用 symfony/yaml 库进行 YAML 解析,就应这样写:"symfony/yaml": "^3.0 || ^4.0"这表示该库能从 Symfony 3.x 或 4.x 中任意版本中使用 symfony/yaml 。这相当重要,因为这个版本约束会传递给使用该库的应用程序。万一有两个库的请求存在冲突,比如一个要 ~3.1.0 ,另一个需要 ~3.2.0 ,则安装会失败。Tip #5: 开发应用程序要提交 composer.lock 文件到 git 版本库中创建了 一个项目,一定要把 composer.lock 文件提交到 git 中。 这会确保每一个人 —— 你、你的合作伙伴、你的 CI 服务器以及你的产品服务器 —— 所运行的应用程序拥有相同依赖的版本。乍一看有些画蛇添足,在 Tip #3 中已经提过要使用明确的版本号的约束了啊。这并不多余,要知道你使用的依赖项的依赖项并不受这些约束绑定(如 symfony/console 还依赖 symfony/polyfill-mbstring)。如果不提交 composer.lock 文件,就不会获取到相同版本的依赖集合。Tip #6: 开发库要把 composer.lock 文件添加到 .gitignore 文件中创建 一个库 (比如说叫 acme/my-library), 这就不应该把 composer.lock 文件提交到 git 库中了。该文件对使用该库的项目 It 不会有任何影响 。假设 acme/my-library 使用 monolog/monolog 作依赖项。你已经在版本库中提交了 composer.lock,开发 acme/my-library 的每个人都可能在使用 Monolog 的老旧版本。该库开发完成后,在实际项目中使用该库,就可能存在安装的 Monolog 是一个新版本 , 而此时就会和该库存在不兼容。可是你在之前根本就不会注意到兼容问题就因为这个 composer.lock!因此,最佳处理方式就是把 composer.lock 添加到 .gitignore 文件中,这样就避免了不小心提交它到版本库中引发的问题。如果还想确保该库与它的依赖项的不同版本保持兼容性,那继续阅读下一个 Tip !Tip #7: Travis CI 构建依赖项的不同版本当前 Tip 仅适合库(对于应用程序要指明具体的版本号)。如果你在构建开源的库,很有可能你会使用 Travis CI 来跑构建过程。默认情况下,在 composer.json 文件约束允许的条件下,composer 安装会安装依赖的最新可能版本。这就意味着对于 ^3.0 || ^4.0 这样的依赖约束,构建安装总是使用最新的 v4 版本发行包。 而 3.0 版本根本不会测试,所构建的库就可能与该版本不兼容,你的用户要哭了。幸好,composer 为安装低版本依赖项提供了一个开关 --prefer-lowest (应使用 --prefer-stable ,可阻止不稳定版本的安装)。已上传的 .travis.yml 配置类似下面的格式:language: phpphp:7.17.2env: matrix:- PREFER_LOWEST="--prefer-lowest --prefer-stable" - PREFER_LOWEST="" before_script:composer update $PREFER_LOWESTscript:composer ci代码详见 my mhujer/fio-api-php library 及 the build matrix on Travis CI虽然这解决了多数的不兼容问题,不过仍然要记得,依赖项的最低和最高版本间有太多的组合。他们仍旧可能存在不兼容的情况。Tip #8: 按名称对 require 和 require-dev 中的包排序按名称对 require 及 require-dev 中的包排序是非常好的实践。这在衍合一个分支时可以避免不必要的合并冲突。假如你把一个包添加到两个分支文件中的列表末尾,那每次合并都可能遇到冲突。手动进行包排序的话会很乏味,所以最好办法就是在 composer.json 中 配置一下 即可:{..."config": { "sort-packages": true },...}以后再要 require 一个新的包,它会自动添加到一个正确位置(不会跑到尾部)。Tip #9: 进行版本衍合或合并时不要合并 composer.lock 如果你在 composer.json (和 composer.lock)中添加了一个新依赖项,并且在该分支被合并前主分支中添加另一个依赖项,此时就需要对你的分支进行衍合处理。那么 composer.lock 文件就会得到一个合并冲突。千万别试图手动解决冲突,这是因为 composer.lock 文件包含了定义 composer.json 中依赖项的哈希值。所以即使你解决了冲突,这个最终合并结果的 lock 文件仍是错误的。最佳方案应该这样做,用下面一行代码在项目根目录创建一个 .gitattributes 文件,它会告诉 git 不要试图对 composer.lock 文件进行合并操作:/composer.lock -merge推荐 Trunk Based Development 方式(常用佳品,不会有错),使用临时的特性分支纠正这种问题。当你有个临时分支需要即时合并时,因此导致的 composer.lock 文件合并冲突的风险极小。你甚至可以仅仅为添加一个依赖项而创建分支,然后马上进行合并。假如在衍合过程中 composer.lock 遇到合并冲突又当如何呢? 使用主分支版本解决,这样仅仅修改 composer.json 文件即可(新增一个包)。然后运行 composer update --lock ,就会把 composer.json 文件的修改更新到 composer.lock 文件中。现在把已经更新的 composer.lock 文件提交到版本暂存区,然后继续衍合操作。Tip #10: 了解 require 和 require-dev 之间的区别能够意识到 require 和 require-dev 模块之间的区别是非常重要的。需要运行在应用中或者库中的包都应该被定义在 require (例如: Symfony, Doctrine, Twig, Guzzle, ...) 中。如果你正在创建一个库, 注意将什么内容定义为 require。因为这个部分的 每个依赖项同时也是使用了该库的应用的依赖。 开发应用程序 (或库) 所需的包应该定义在 require-dev (例如:PHPUnit, PHP_CodeSniffer, PHPStan) 中。Tip #11: 安全地升级依赖项我想大家对如下事实存有共识:应该定期对依赖项升级。 此处我想讨论的是依赖项的升级应该放在明处且慎之又慎,而不能是因其他活计的需要才顺手为之。如果在重构应用的同时又升级了库,那么就很难区分应用崩溃的原因是重构还是升级带来的。可用 composer outdated 命令查看哪些依赖项需要升级。追加一个 --direct (或 -D)参数开关是个聪明之举,这只会查看 composer.json 指定的依赖项。还有一个 -m 参数开关,只查看次版本号的升级列表。对每一个老版本的依赖项进行升级都要尊循如下步骤:创建新分支在 composer.json 文件中更新该依赖项版本到最新版本号运行 composer update phpunit/phpunit --with-dependencies (使用升级过的库替换 phpunit/phpunit)检查 Github 上库的版本库中 CHANGELOG 文件,检查是否存在重大变化。 如果存在就升级应用程序本地测试应用程序(使用 Symfony 的话还能在调试栏看到弃用警告)提交修改(包括 composer.json 、 composer.lock 及其他新版本正常运行所做的必要修改)等 CI 构建结束合并然后部署有时需要一次升级多个依赖项,比如升级 Doctrine 或 Symfony。这种情况下,就要在升级命令中把他们全部罗列出来:composer update symfony/symfony symfony/monolog-bundle --with-dependencies或者使用通配符升级所有指定命名空间的依赖:composer update symfony/* --with-dependencies这全都是很乏味的工作,但相对于不小心升级依赖项而言,这提供了额外保障。一个可接受的简捷方式就是一次升级所有 require-dev 中的依赖项(如果程序代码没有修改的话,否则还是建议创建独立分支以便代码审查)。Tip #12: 在 composer.json 中定义其他类型的依赖除了定义库作为依赖项外,也以在这儿定义其他东西。可以定义应用程序和库所支持的 PHP 版本:"require": {"php": "7.1.* || 7.2.*",},也能定义应用程序和库所需要的扩展。在尝试 docker 化自己的应用时,或是你的同伴头一次设置应用环境时,这招超级实用。"require": {"ext-mbstring": "*", "ext-pdo_mysql": "*",},(当 扩展版本不一致 时,版本号要用 * )。Tip #13: 在 CI 构建期间验证 composer.jsoncomposer.json 和 composer.lock 应当一直保持同步。因此,一直为他们保持自动核对是一个好主意。将此添加成为你的构建脚本的一部分将会确保 composer.lock 与 composer.json 保持同步:composer validate --no-check-all --strictTip #14: 在 PHPStorm 中使用 Composer 插件这里有一个 composer.json plugin for PHPStorm. 当手动修改 composer.json 时,插件会自动完成及执行一些验证.如果你在使用其他 IDE (或者只是一个编辑器), 你可以使用 its JSON schema 设置验证.Tip #15: 在 composer.json 中指明生产环境的 PHP 版本号如果你和我一样,有时还 在本地环境跑 PHP 最新预释版本, 那么就会处于升级依赖项的版本不能运行于生产环境的风险。现在我就在使用 PHP 7.2.0 ,也就意味着我安装的库可能在 7.1 版本中运行不了。如果生产环境跑的是 7.1 版本,安装就会失败。不过不用担心,有个非常简单的解决办法,在 composer.json 文件的 config 部分指明生产环境的 PHP 版本号即可:"config": {"platform": { "php": "7.1" }}别把它和 require 部分的设置搞混了,它的作用不同。你的应用就可以运行 7.1 或 7.2 版本下,而且同时指定了平台版本为 7.1 (这意味着依赖项的升级版本要和 平台版本 7.1 保持兼容):"require": {"php": "7.1.* || 7.2.*"},"config": {"platform": { "php": "7.1" }},Tip #16: 使用自有托管 Gitlab 上的私有包推荐使用 vcs 作为版本库类型,并且 Composer 决定获取包的合适的方法。比如,从 Github 上添加一个 fork,使用它的 API 下载整个版本库的 .zip 文件,而不用克隆。不过对一个私有的 Gitlab 安装来讲会更复杂。如果用 vcs 作版本库类型,Composer 会检测到它是个 Gitlab 类型的安装,会尝试使用 API 下载包(这要求有 API key。我不想设置,所以我只用 SSH 克隆安装了) :首先指明版本库类型是 git:"repositories": [{ "type": "git", "url": "git@gitlab.mycompany.cz:package-namespace/package-name.git" }]然后指明常用的包:"require": {"package-namespace/package-name": "1.0.0"}Tip #17: 临时使用 fork 下 bug 修复分支的方法如果在某个公共的库中找到一个 bug,并且在 Github 上自己的 fork 中修复了它, 这就需要从自己的版本库里安装这个库,而不是官方版本库(要到修复合并且修复的版本释出才行)。使用 内嵌别名 可轻松搞定:{"repositories": [ { "type": "vcs", "url": "https://github.com/you/monolog" } ], "require": { "symfony/monolog-bundle": "2.0", "monolog/monolog": "dev-bugfix as 1.0.x-dev" }}可以通过 设置 path 作为版本库类型 在本地测试这次修复,然后再 push 更新版本库。更新于 2018-01-08:文章发布后,我收到了一些建议,提供了更多的使用技巧。它们分别是:Tip #18:使用 prestissimo 加速你的包安装Composer 有个 hirak/prestissimo 插件,通过该插件能够以并行的方式进行下载,从而提高依赖包的安装速度。那么,这么好的东西,你现在该如何做?你仅仅需要马上全局安装这个插件,然后就可以自动地在所有项目中使用。composer global require hirak/prestissimoTip #19: 当你不确定时,测试你的版本约束即使在阅读 the documentation 之后,书写正确的版本约束在一些时候也是很棘手的.幸运的是,这里有 Packagist Semver Checker 可以用来检查哪个本部匹配特定的约束。他不是仅仅的分析版本约束,他从 Packagist 下载数据以来展示实际的发布版本.查看 the result for symfony/symfony:^3.1.Tip #20: 在生产环境中使用使用权威类映射文件应该在生产环境中 生成权威类映射文件 。这会让类映射文件中包含的所有类快速加载,而不必到磁盘文件系统进行任何检查。可以在生产环境构建时运行以下命令:composer dump-autoload --classmap-authoritativeTip #21: 为测试配置 autoload-dev你也不想在生产环境中加载测试文件(考虑到测试文件的大小和内存使用)。这可以通过配置 autoload-dev 解决(与 autoload 相似):"autoload": {"psr-4": { "Acme\\": "src/" }},"autoload-dev": {"psr-4": { "Acme\\": "tests/" }},Tip #22: 尝试 Composer 脚本Composer 脚本是一个创建构建脚本的轻量级工具。关于这个,我有另文述及。总结如果你不同意某些观点且阐述出你为什么不同意的意见(不要忘记标注 tip 的编号)我将很高兴。
2023年08月10日
13 阅读
0 评论
0 点赞
2023-08-10
如何防止PHP进程异常退出(进程被杀)?
如何防止PHP进程异常退出(进程被杀)?背景通常,在cli下运行的常驻后台PHP进程,可能异常退出,比如php执行过程中出现的致命错误,或被 kill 命令手动杀死等。如下面的php代码:<?phpwhile(1){$content = fgets(STDIN); if(empty($content)){ sleep(1); } //逻辑处理部分代码省略}?>查错我们使用register_shutdown_function可以跟踪到底是什么错误导致的进程退出。(想更多了解register_shutdown_function,请查看博文 妙用php中的register_shutdown_function和fastcgi_finish_request)加入了错误捕捉代码。如下:<?php$is_end = false;function catch_error(){ global $is_end; $time = date('Y-m-d H:i:s'); $error = error_get_last(); $msg = "$time [error]"; if($is_end){ $msg .= "is_end[yes]"; }else{ $msg .= "is_end[no]"; } if($error){ $msg .= var_export($error,1); } echo $msg."\r\n";}register_shutdown_function("catch_error");?>可是,php当进程再次退出时,在日志中并没有记录任何信息。说明register_shutdown_function方法根本没有执行。是什么导致register_shutdown_function方法没有运行呢?在php的官方文档中又这样一个注释:Shutdown functions will not be executed if the process is killed with a SIGTERM or SIGKILL signal. While you cannot intercept a SIGKILL, you can use pcntl_signal() to install a handler for a SIGTERM which uses exit() to end cleanly.注释的意思是当php进程获得SIGTERM和SIGKILL信号而退出时,是不执行register_shutdown_function方法的。可以使用pcntl_signal()方法来捕获信息,并调用相应的处理方法。好,如何检测是信号导致我们的php进程退出的呢?我们加入如下代码:<?phpdeclare(ticks = 1);function sig_handler($signo){ $time = date('Y-m-d H:i:s'); echo $time." exit signo[{$signo}]\r\n"; exit("");}pcntl_signal(SIGTERM, "sig_handler");pcntl_signal(SIGHUP, "sig_handler");pcntl_signal(SIGINT, "sig_handler");pcntl_signal(SIGQUIT, "sig_handler");pcntl_signal(SIGILL, "sig_handler");pcntl_signal(SIGPIPE, "sig_handler");pcntl_signal(SIGALRM, "sig_handler");?>过一段时间,发现php进程退出了,日志中出现了如下日志信息:2014-11-23 18:30:06 exit signo[14]2014-11-23 18:30:06 [error]is_end[no]看来是sigalarm信号导致php进程退出了。这个信号是可以捕获和处理的。kill 命令会发出sigalarm信号。最终的代码如下:<?phpdeclare(ticks = 1);$is_end = false;function catch_error(){ global $is_end; $time = date('Y-m-d H:i:s'); $error = error_get_last(); $msg = "$time [error]"; if($is_end){ $msg .= "is_end[yes]"; }else{ $msg .= "is_end[no]"; } if($error){ $msg .= var_export($error,1); } echo $msg."\r\n";}register_shutdown_function("catch_error");function sig_handler($signo){ $time = date('Y-m-d H:i:s'); if($signo == 14){ //忽略alarm信号 echo $time." ignore alarm signo[{$signo}]\r\n"; }else{ echo $time." exit signo[{$signo}]\r\n"; exit(""); }}pcntl_signal(SIGTERM, "sig_handler");pcntl_signal(SIGHUP, "sig_handler");pcntl_signal(SIGINT, "sig_handler");pcntl_signal(SIGQUIT, "sig_handler");pcntl_signal(SIGILL, "sig_handler");pcntl_signal(SIGPIPE, "sig_handler");pcntl_signal(SIGALRM, "sig_handler");while(1){$content = fgets(STDIN); if(empty($content)){ sleep(1); } //逻辑处理部分代码省略}$is_end = true;?>这时再kill 进程,在日志中就发现了alarm相关的日志,但是php进程依然在。PS:以上代码如果用kill -9命令杀死(SIGKILL信号),还是会退出的。也就是说SIGKILL信号是无法捕获的。参考http://blogread.cn/it/article/7256?f=wb_blogread
2023年08月10日
21 阅读
0 评论
0 点赞
2023-08-10
swoole 协程初体验
swoole 协程初体验date: 2018-5-30 14:31:38title: swoole| swoole 协程初体验description: 通过协程的执行初窥 swoole 中协程的调度; 理解协程为什么快; swoole 协程和 go 协程对比折腾 swoole 协程有一段时间了, 总结一篇入门贴, 希望对新手有帮助.内容概览:协程的执行顺序: 初窥 swoole 中协程的调度协程为什么快: 减少IO阻塞带来的性能损耗swoole 协程和 go 协程对比: 单进程 vs 多线程协程的执行顺序先来看看基础的例子:go(function () {echo "hello go1 \n";});echo "hello main \n";go(function () {echo "hello go2 \n";});go() 是 \Co::create() 的缩写, 用来创建一个协程, 接受 callback 作为参数, callback 中的代码, 会在这个新建的协程中执行.备注: \Swoole\Coroutine 可以简写为 \Co上面的代码执行结果:root@b98940b00a9b /v/w/c/p/swoole# php co.phphello go1hello mainhello go2执行结果和我们平时写代码的顺序, 好像没啥区别. 实际执行过程:运行此段代码, 系统启动一个新进程遇到 go(), 当前进程中生成一个协程, 协程中输出 heelo go1, 协程退出进程继续向下执行代码, 输出 hello main再生成一个协程, 协程中输出 heelo go2, 协程退出运行此段代码, 系统启动一个新进程. 如果不理解这句话, 你可以使用如下代码:// co.php<?phpsleep(100);执行并使用 ps aux 查看系统中的进程:root@b98940b00a9b /v/w/c/p/swoole# php co.php &⏎root@b98940b00a9b /v/w/c/p/swoole# ps auxPID USER TIME COMMAND1 root 0:00 php -a10 root 0:00 sh 19 root 0:01 fish 749 root 0:00 php co.php 760 root 0:00 ps aux⏎我们来稍微改一改, 体验协程的调度:use Co;go(function () {Co::sleep(1); // 只新增了一行代码 echo "hello go1 \n";});echo "hello main \n";go(function () {echo "hello go2 \n";});\Co::sleep() 函数功能和 sleep() 差不多, 但是它模拟的是 IO等待(IO后面会细讲). 执行的结果如下:root@b98940b00a9b /v/w/c/p/swoole# php co.phphello mainhello go2hello go1怎么不是顺序执行的呢? 实际执行过程:运行此段代码, 系统启动一个新进程遇到 go(), 当前进程中生成一个协程协程中遇到 IO阻塞 (这里是 Co::sleep() 模拟出的 IO等待), 协程让出控制, 进入协程调度队列进程继续向下执行, 输出 hello main执行下一个协程, 输出 hello go2之前的协程准备就绪, 继续执行, 输出 hello go1到这里, 已经可以看到 swoole 中 协程与进程的关系, 以及 协程的调度, 我们再改一改刚才的程序:go(function () {Co::sleep(1); echo "hello go1 \n";});echo "hello main \n";go(function () {Co::sleep(1); echo "hello go2 \n";});我想你已经知道输出是什么样子了:root@b98940b00a9b /v/w/c/p/swoole# php co.phphello mainhello go1hello go2⏎协程快在哪? 减少IO阻塞导致的性能损失大家可能听到使用协程的最多的理由, 可能就是 协程快. 那看起来和平时写得差不多的代码, 为什么就要快一些呢? 一个常见的理由是, 可以创建很多个协程来执行任务, 所以快. 这种说法是对的, 不过还停留在表面.首先, 一般的计算机任务分为 2 种:CPU密集型, 比如加减乘除等科学计算IO 密集型, 比如网络请求, 文件读写等其次, 高性能相关的 2 个概念:并行: 同一个时刻, 同一个 CPU 只能执行同一个任务, 要同时执行多个任务, 就需要有多个 CPU 才行并发: 由于 CPU 切换任务非常快, 快到人类可以感知的极限, 就会有很多任务 同时执行 的错觉了解了这些, 我们再来看协程, 协程适合的是 IO 密集型 应用, 因为协程在 IO阻塞 时会自动调度, 减少IO阻塞导致的时间损失.我们可以对比下面三段代码:普通版: 执行 4 个任务$n = 4;for ($i = 0; $i < $n; $i++) {sleep(1); echo microtime(true) . ": hello $i \n";};echo "hello main \n";root@b98940b00a9b /v/w/c/p/swoole# time php co.php1528965075.4608: hello 01528965076.461: hello 11528965077.4613: hello 21528965078.4616: hello 3hello mainreal 0m 4.02suser 0m 0.01ssys 0m 0.00s⏎单个协程版:$n = 4;go(function () use ($n) {for ($i = 0; $i < $n; $i++) { Co::sleep(1); echo microtime(true) . ": hello $i \n"; };});echo "hello main \n";root@b98940b00a9b /v/w/c/p/swoole# time php co.phphello main1528965150.4834: hello 01528965151.4846: hello 11528965152.4859: hello 21528965153.4872: hello 3real 0m 4.03suser 0m 0.00ssys 0m 0.02s⏎多协程版: 见证奇迹的时刻$n = 4;for ($i = 0; $i < $n; $i++) {go(function () use ($i) { Co::sleep(1); echo microtime(true) . ": hello $i \n"; });};echo "hello main \n";root@b98940b00a9b /v/w/c/p/swoole# time php co.phphello main1528965245.5491: hello 01528965245.5498: hello 31528965245.5502: hello 21528965245.5506: hello 1real 0m 1.02suser 0m 0.01ssys 0m 0.00s⏎为什么时间有这么大的差异呢:普通写法, 会遇到 IO阻塞 导致的性能损失单协程: 尽管 IO阻塞 引发了协程调度, 但当前只有一个协程, 调度之后还是执行当前协程多协程: 真正发挥出了协程的优势, 遇到 IO阻塞 时发生调度, IO就绪时恢复运行我们将多协程版稍微修改一下:多协程版2: CPU密集型$n = 4;for ($i = 0; $i < $n; $i++) {go(function () use ($i) { // Co::sleep(1); sleep(1); echo microtime(true) . ": hello $i \n"; });};echo "hello main \n";root@b98940b00a9b /v/w/c/p/swoole# time php co.php1528965743.4327: hello 01528965744.4331: hello 11528965745.4337: hello 21528965746.4342: hello 3hello mainreal 0m 4.02suser 0m 0.01ssys 0m 0.00s⏎只是将 Co::sleep() 改成了 sleep(), 时间又和普通版差不多了. 因为:sleep() 可以看做是 CPU密集型任务, 不会引起协程的调度Co::sleep() 模拟的是 IO密集型任务, 会引发协程的调度这也是为什么, 协程适合 IO密集型 的应用.再来一组对比的例子: 使用 redis// 同步版, redis使用时会有 IO 阻塞$cnt = 2000;for ($i = 0; $i < $cnt; $i++) {$redis = new \Redis(); $redis->connect('redis'); $redis->auth('123'); $key = $redis->get('key');}// 单协程版: 只有一个协程, 并没有使用到协程调度减少 IO 阻塞go(function () use ($cnt) {for ($i = 0; $i < $cnt; $i++) { $redis = new Co\Redis(); $redis->connect('redis', 6379); $redis->auth('123'); $redis->get('key'); }});// 多协程版, 真正使用到协程调度带来的 IO 阻塞时的调度for ($i = 0; $i < $cnt; $i++) {go(function () { $redis = new Co\Redis(); $redis->connect('redis', 6379); $redis->auth('123'); $redis->get('key'); });}性能对比:多协程版root@0124f915c976 /v/w/c/p/swoole# time php co.phpreal 0m 0.54suser 0m 0.04ssys 0m 0.23s⏎同步版root@0124f915c976 /v/w/c/p/swoole# time php co.phpreal 0m 1.48suser 0m 0.17ssys 0m 0.57s⏎swoole 协程和 go 协程对比: 单进程 vs 多线程接触过 go 协程的 coder, 初始接触 swoole 的协程会有点 懵, 比如对比下面的代码:package mainimport ("fmt" "time")func main() {go func() { fmt.Println("hello go") }() fmt.Println("hello main") time.Sleep(time.Second)}14:11 src $ go run test.gohello mainhello go刚写 go 协程的 coder, 在写这个代码的时候会被告知不要忘了 time.Sleep(time.Second), 否则看不到输出 hello go, 其次, hello go 与 hello main 的顺序也和 swoole 中的协程不一样.原因就在于 swoole 和 go 中, 实现协程调度的模型不同.上面 go 代码的执行过程:运行 go 代码, 系统启动一个新进程查找 package main, 然后执行其中的 func mian()遇到协程, 交给协程调度器执行继续向下执行, 输出 hello main如果不添加 time.Sleep(time.Second), main 函数执行完, 程序结束, 进程退出, 导致调度中的协程也终止go 中的协程, 使用的 MPG 模型:M 指的是 Machine, 一个M直接关联了一个内核线程P 指的是 processor, 代表了M所需的上下文环境, 也是处理用户级代码逻辑的处理器G 指的是 Goroutine, 其实本质上也是一种轻量级的线程MPG 模型而 swoole 中的协程调度使用 单进程模型, 所有协程都是在当前进程中进行调度, 单进程的好处也很明显 -- 简单 / 不用加锁 / 性能也高.无论是 go 的 MPG模型, 还是 swoole 的 单进程模型, 都是对 CSP理论 的实现.CSP通信方式, 在1985年时的论文就已经有了, 做理论研究的人, 如果没有能提前几年, 十几年甚至几十年的大胆假设, 可能很难提高了.写在最后今天从 go() 出发, 得以一瞥协程世界, 协程的世界里还有很多很有意思的东西, 需要我们去发现. 比如:我们普通版的代码是当前进程里执行的, 只是单个进程, 可我们现在可能有了很多协程, 会不会有什么奇遇呢?还有一个细节: swoole 中有 Co::sleep() 和 sleep() 2个方法的, 而 go 中只有 time.Sleep() 一个方法?这是 swoole 协程需要经历的一个阶段(毕竟 go 快 10 年了), 还不够 智能的判断 IO阻塞, 所以上面也使用了相应的协程版 redis co\Redis() -- 你得使用配套协程版, 才能达到协程调度的效果.如果对协程的发展阶段感兴趣, 可以阅读下面这篇文章:Why c++ coroutine?Why libgo?: 关于协程全景式的概述的, 推荐花时间读一读想解锁 swoole 协程的更多姿势:Swoole 2.1 正式版发布,协程+通道带来全新的 PHP 编程模式Swoole 4.0 正式版,面向生产环境的 PHP 协程引擎Swoole4-全新的PHP编程模式_韩天峰_PHPCON2018最后, 本期示例代码可以从我的开源项目中获取, 请享用
2023年08月10日
7 阅读
0 评论
0 点赞
2023-08-10
如何少写PHP “烂”代码
如何少写PHP “烂”代码写给初生牛犊不怕虎的童鞋们,大佬可随意摘看本章基于PHP Laravel前言经常会有人问目录如何设计比较好?代码如何分布好?怎么写一个可维护的项目?“烂”项目我也没少写,以下是参考互联网各大佬的文章总结及个人开发经验而来.Controllerclipboard.pngController顾名思义是控制器,在入门PHP的时候,就知道Controller代表MVC中的C层,MVC本身的概念就代码分离,教你如何如何将业务分开,但面临着业务的不断发展,代码的复杂度也随之提高,功能与功能之间的链接错综复杂,最后你的MVC就变成了下图,所以仅仅依托MVC的设计思想已经无法支撑不断发展的业务。现在我们将Controller的任务和能力重新定义,控制器仅仅控制Http Reqeust的请求,这样就符合了SOLID 单一功能原则.clipboard.png直接将业务代码写在Controller中,会使得代码及其臃肿,不易于维护和扩展<?phpnamespace App\Http\Controller; class UserController extends Controller{ public function register(Request $request){ $user = new User(); $user->username = $request->input('username'); $user->password = $request->input('password'); $result = $user->save(); return $result; } }这时就应该思考如何分离业务代码,我们引入Service的概念ServiceService本身译为服务将外部方法,公共方法注入到Service将Service注入到控制器clipboard.png像上图这样UserController<?phpnamespace App\Http\Controller; class UserController extends Controller{ public $request; protected $userService; public function __construct(Request $request, UserService $userService) { $this->request = $request; $this->userService = $userService; } public function register() { //... validation return $this->userService->register ($this->request->all()); } }UserService<?phpnamespace App\Service; class UserService{ public function register($data) { $username = $data['username']; $password = $data['password']; $password = encrypt ($password); $user = new User(); $user->username = $username; $user->password = $password; $result = $user->save(); return $result; } }到现在为止,我们至少将业务与请求彻底分开了。但还是不如人意,如果把所有的业务及CURD全部写在Service中,那只不过是将Controller的臃肿转移到了Service,那Service就没有什么存在意义了。所以我们需要继续分割Service,将对数据库的R操作独立出来,因为CUD的操作基本是一贯不变的,而R操作根据业务的复杂度则变的多姿多彩。所以独立R操作。这个时候我们引用Repository的概念。Repository我们使用Repository辅助Model,将相关的查询逻辑封装到不同的repository中,方便逻辑代码的维护符合SOLID的单一原则符合SOLID的依赖反转clipboard.pngUserController<?phpnamespace App\Http\Controller; class UserController extends Controller{ public $request; protected $userService; public function __construct(Request $request, UserService $userService) { $this->request = $request; $this->userService = $userService; } public function getUserInfo() { //... validation return $this->userService->getUserInfo ($this->request->all()); } }UserService<?phpnamespace App\Service; class UserService{ public $userRepository; public function __construct(UserRepository $userRepository){ $this->userRepository = $userRepository; } public function getUserInfo() { return $this->userRepository->getUserInfo($data); } }UserRepository<?phpnamespace App\Repository; class UserRepository{ public function getUserInfo($data) { $userId = $data['user_id']; $result = User::where('id',$userId)->first(); return $result; } }解决了R的问题,有人就问了,难道因为CUD比较统一简单就可以放在一起了吗?答案是NO,我们引用一个新的名词Action。Action这是看了@Charlie_Jade的文章才学到的独立每个操作文件,例如CreateUser,DeleteUser,UpdateUser符合SOLID的单一原则clipboard.pngUserController<?phpnamespace App\Http\Controller; class UserController extends Controller{ public $request; protected $userService; public function __construct(Request $request, UserService $userService) { $this->request = $request; $this->userService = $userService; } public function register(){ //... validation return $this->userService->register($this->request->all()); } public function getUserInfo() { return $this->userService->getUserInfo ($this->request->all()); } }UserService<?phpnamespace App\Service; class UserService{ public function getUserInfo(UserRepository $userRepository) { return $this->userRepository->getUserInfo($data); } public function register(){ $result = (new CreateUser())->execute($this->request->all()); return $result; } }UserRepository<?phpnamespace App\Repository; class UserRepository{ public function getUserInfo($data) { $userId = $data['user_id']; $result = User::where('id',$userId)->first(); return $result; } }CreateUser<?phpnamespace App\Action; use App\Model\Member; class CreateUser extends CreateUserWallet { public function execute(array $data) { $models = new Member(); $models->tel = $data['tel']; $models->password = $data['password']; $result = $models->save (); return $result; } }以上代码逻辑见下图clipboard.png除模版(V)等HTML,JS等,还需要一些其他的规则,或者说是方式去实现一些代码的解耦合,以下不再提供代码案例。Common译为公共的,常用的,再部分开发中,你可能需要一些公共的方法(并非公共的类,例如邮件发送等,用他并不合适),比如查询用户余额,查询用户是否注册或者是否在线,生成订单号等。使用Common更要简单。他更像一个公共函数库的样子clipboard.pngEvent不关心执行结果时可以选使用,不过Event的Listen也是提供了队列。Exception不要将你的所有错误提示都使用Return返回,很多时候你的返回未必是你的返回致谢感谢各位同学看完这篇文章,如果你有新的想法欢迎在评论区讨论.参考文章Laravel 的中大型專案架構:http://oomusou.io/laravel/arc...Laravel 程序架构设计思路使用动作类 : https://segmentfault.com/a/11...如何使用 Service 模式? : http://oomusou.io/laravel/ser...面向对象设计的SOLID原则 : https://www.cnblogs.com/shany...
2023年08月10日
17 阅读
0 评论
0 点赞
1
...
89
90
91
...
157