├── .github └── workflows │ └── php.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── docs ├── changelog.md └── todo.md ├── src ├── Concurrent │ ├── CallableWrapper.php │ ├── Future.php │ ├── Promise.php │ ├── Wrapper.php │ └── functions.php ├── Consts.php ├── Exceptions │ ├── BaseException.php │ └── UncatchableException.php ├── Helpers │ ├── ArrayHelper.php │ ├── ConvertHelper.php │ ├── DateHelper.php │ ├── DebugHelper.php │ ├── DirectoryHelper.php │ ├── EncryptHelper.php │ ├── FileHelper.php │ ├── NumberHelper.php │ ├── OsHelper.php │ ├── RegularHelper.php │ ├── StringHelper.php │ ├── UrlHelper.php │ └── ValidateHelper.php ├── Interfaces │ ├── Arrayable.php │ ├── Jsonable.php │ └── Throwable.php ├── Objects │ ├── ArrayObject.php │ ├── BaseObject.php │ └── StrictObject.php ├── Services │ └── BaseService.php ├── Util │ └── MacAddress.php └── Version.php └── tests ├── Future ├── .gitkeep ├── ExceptionFuture.php ├── FailFuture.php ├── MyGenerator.php ├── SuccessFuture.php └── UncatchableFuture.php ├── Objects ├── BaseCls.php ├── BaseServ.php ├── MathCls.php └── StrictCls.php ├── README.md ├── Unit ├── ArrayHelperTest.php ├── ConvertHelperTest.php ├── DateHelperTest.php ├── DebugHelperTest.php ├── DirectoryHelperTest.php ├── EncryptHelperTest.php ├── FileHelperTest.php ├── MacAddressTest.php ├── NumberHelperTest.php ├── ObjectsTest.php ├── OsHelperTest.php ├── PromiseTest.php ├── ServicesTest.php ├── StringHelperTest.php ├── UrlHelperTest.php └── ValidateHelperTest.php ├── bootstrap.php ├── data ├── banana.gif ├── bom.txt ├── green.jpg ├── php-logo.svg ├── php_elephant.png └── png.webp ├── phpunit.xml └── tmp └── .gitkeep /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: helper-test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build-test: 11 | runs-on: ${{ matrix.os }} 12 | env: 13 | PHP_EXTENSIONS: curl, dom, json, libxml, mbstring, xml, xmlwriter 14 | PHP_INI_VALUES: memory_limit=-1, error_reporting=-1, log_errors_max_len=0, display_errors=On 15 | 16 | strategy: 17 | # 策略组中,如果有一个失败了,则快速停止继续运行 18 | fail-fast: false 19 | matrix: 20 | os: 21 | - ubuntu-latest 22 | #- windows-latest 23 | 24 | php-version: 25 | - "7.2" 26 | - "7.3" 27 | - "7.4" 28 | - "8.0" 29 | - "8.1" 30 | - "8.2" 31 | 32 | steps: 33 | - name: Checkout 34 | uses: actions/checkout@v3 35 | 36 | - name: Install PHP 37 | uses: shivammathur/setup-php@v2 38 | with: 39 | php-version: ${{ matrix.php-version }} 40 | coverage: none 41 | extensions: ${{ env.PHP_EXTENSIONS }} 42 | ini-values: ${{ env.PHP_INI_VALUES }} 43 | tools: composer 44 | 45 | - name: Update dependencies with composer 46 | run: composer update --no-ansi --no-interaction --no-progress 47 | 48 | - name: Run tests with phpunit 49 | run: vendor/bin/phpunit --version && vendor/bin/phpunit --bootstrap=tests/bootstrap.php ./tests/ 50 | 51 | - name: Show info 52 | run: ls -a -h ./ && ls -a -h ./tests 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.log 3 | vendor 4 | composer.lock 5 | .vscode -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.2 5 | - 7.3 6 | - 7.4 7 | - 8.0 8 | 9 | os: 10 | - linux 11 | 12 | before_script: 13 | - travis_retry composer self-update 14 | - travis_retry composer install --no-interaction --prefer-source --dev 15 | - cd tests 16 | 17 | script: 18 | - ../vendor/bin/phpunit --coverage-clover=coverage.xml 19 | 20 | after_success: 21 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # php-helper 2 | 3 | k's php helper/library/utils 4 | php 常用函数库/工具集,包括数组、文件、验证、字符串、加解密等操作,具体见`src/Helpers`. 5 | 支持的php版本有: 6 | 7 | - 7.2 8 | - 7.3 9 | - 7.4 10 | - 8.0 11 | - 8.1 12 | - 8.2 13 | 14 | ### 相关 15 | 16 | [![Php Version](https://img.shields.io/badge/php-%3E=7.2-brightgreen.svg)](https://secure.php.net/) 17 | [![Build Status](https://github.com/kakuilan/php-helper/workflows/helper-test/badge.svg)](https://github.com/kakuilan/php-helper/actions) 18 | [![codecov](https://codecov.io/gh/kakuilan/php-helper/branch/master/graph/badge.svg)](https://codecov.io/gh/kakuilan/php-helper) 19 | [![Code Size](https://img.shields.io/github/languages/code-size/kakuilan/php-helper.svg?style=flat-square)](https://github.com/kakuilan/php-helper) 20 | [![Starts](https://img.shields.io/github/stars/kakuilan/php-helper.svg)](https://github.com/kakuilan/php-helper) 21 | [![Latest Version](https://img.shields.io/packagist/v/kakuilan/php-helper.svg)](https://packagist.org/packages/kakuilan/php-helper) 22 | 23 | ### 安装 24 | 25 | ```shell 26 | composer require kakuilan/php-helper 27 | ``` 28 | 29 | ### 测试 30 | 31 | ```sh 32 | phpunit --bootstrap=tests/bootstrap.php ./tests/ 33 | composer run-script test 34 | composer run-script cover 35 | # 或者 36 | cd tests 37 | phpunit 38 | ``` 39 | 40 | ### 更新日志 41 | 42 | 详见[[Changelog]](/docs/changelog.md) 43 | 44 | 45 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kakuilan/php-helper", 3 | "description": "k`s php helper/library/utils", 4 | "keywords": [ 5 | "php helper", 6 | "php library", 7 | "php utils" 8 | ], 9 | "homepage": "https://github.com/kakuilan/php-helper", 10 | "license": "Apache-2.0", 11 | "authors": [ 12 | { 13 | "name": "kakuilan", 14 | "email": "kakuilan@163.com", 15 | "homepage": "https://github.com/kakuilan" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=7.2", 20 | "ext-curl": "*", 21 | "ext-dom": "*", 22 | "ext-json": "*", 23 | "ext-mbstring": "*" 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "*" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Kph\\": "src", 31 | "Kph\\Tests\\": "tests" 32 | }, 33 | "files": [ 34 | "src/Concurrent/functions.php" 35 | ] 36 | }, 37 | "scripts": { 38 | "test": "phpunit --bootstrap=tests/bootstrap.php ./tests/", 39 | "cover": "phpunit --bootstrap=tests/bootstrap.php --coverage-clover=coverage.xml ./tests/" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [v0.5.3]- 2024-05-06 6 | 7 | #### Added 8 | 9 | - none 10 | 11 | #### Fixed 12 | 13 | - none 14 | 15 | #### Changed 16 | 17 | - 优化`EncryptHelper::authcode` 18 | 19 | #### Removed 20 | 21 | - none 22 | 23 | ## [v0.5.2]- 2023-06-25 24 | 25 | #### Added 26 | 27 | - none 28 | 29 | #### Fixed 30 | 31 | - 修复`UrlHelper::getUrl` 32 | 33 | #### Changed 34 | 35 | - none 36 | 37 | #### Removed 38 | 39 | - none 40 | 41 | ## [v0.5.1]- 2023-06-20 42 | 43 | #### Added 44 | 45 | - none 46 | 47 | #### Fixed 48 | 49 | - 修复`UrlHelper::getUrl` 50 | - 修复`ValidateHelper::isUrl` 51 | 52 | #### Changed 53 | 54 | - none 55 | 56 | #### Removed 57 | 58 | - none 59 | 60 | ## [v0.5.0]- 2023-06-16 61 | 62 | #### Added 63 | 64 | - none 65 | 66 | #### Fixed 67 | 68 | - none 69 | 70 | #### Changed 71 | 72 | - 修改`BaseService::getResult`,取消类型限制 73 | 74 | #### Removed 75 | 76 | - none 77 | 78 | ## [v0.4.9]- 2023-06-15 79 | 80 | #### Added 81 | 82 | - none 83 | 84 | #### Fixed 85 | 86 | - none 87 | 88 | #### Changed 89 | 90 | - 修改`BaseService::$result`类型为mixed 91 | 92 | #### Removed 93 | 94 | - none 95 | 96 | ## [v0.4.8]- 2023-06-06 97 | 98 | #### Added 99 | 100 | - none 101 | 102 | #### Fixed 103 | 104 | - 修复`StrictObject::isset` 105 | 106 | #### Changed 107 | 108 | - 优化`DateHelper::timestamp` 109 | - 优化`DateHelper::isBetween` 110 | - 优化`StringHelper::passwdSafeGrade` 111 | 112 | #### Removed 113 | 114 | - 移除`DateHelper::dateTime` 115 | - 移除`DateHelper::yearMonth` 116 | - 移除`DateHelper::monthDay` 117 | 118 | ## [v0.4.7]- 2023-05-04 119 | 120 | #### Added 121 | 122 | - 新增`ArrayHelper::xmlToArray` 123 | - 新增`ArrayHelper::arrayToXml` 124 | - 新增`DateHelper::timestamp` 125 | - 新增`DateHelper::year` 126 | - 新增`DateHelper::month` 127 | - 新增`DateHelper::day` 128 | - 新增`DateHelper::hour` 129 | - 新增`DateHelper::minute` 130 | - 新增`DateHelper::second` 131 | - 新增`DateHelper::yearMonth` 132 | - 新增`DateHelper::monthDay` 133 | - 新增`DateHelper::format` 134 | - 新增`DateHelper::dateTime` 135 | - 新增`DateHelper::isBetween` 136 | 137 | #### Fixed 138 | 139 | - none 140 | 141 | #### Changed 142 | 143 | - none 144 | 145 | #### Removed 146 | 147 | - none 148 | 149 | ## [v0.4.6]- 2023-02-28 150 | 151 | #### Added 152 | 153 | - 新增`BaseService::setResult` 154 | - 新增`BaseService::getResult` 155 | 156 | #### Fixed 157 | 158 | - none 159 | 160 | #### Changed 161 | 162 | - none 163 | 164 | #### Removed 165 | 166 | - none 167 | 168 | ## [v0.4.5]- 2022-04-01 169 | 170 | #### Added 171 | 172 | - none 173 | 174 | #### Fixed 175 | 176 | - 优化`EncryptHelper::authcode` 177 | 178 | #### Changed 179 | 180 | - none 181 | 182 | #### Removed 183 | 184 | - none 185 | 186 | ## [v0.4.4]- 2022-01-14 187 | 188 | #### Added 189 | 190 | - 新增`UrlHelper::getSiteUrl`,根据网址获取站点URL 191 | 192 | #### Fixed 193 | 194 | - none 195 | 196 | #### Changed 197 | 198 | - 将`OsHelper::getDomain`移动到`UrlHelper::getDomain` 199 | - 将`OsHelper::getUrl`移动到`UrlHelper::getUrl` 200 | - 将`OsHelper::getUri`移动到`UrlHelper::getUri` 201 | 202 | #### Removed 203 | 204 | - none 205 | 206 | ## [v0.4.3]- 2022-01-13 207 | 208 | #### Added 209 | 210 | - none 211 | 212 | #### Fixed 213 | 214 | - none 215 | 216 | #### Changed 217 | 218 | - 兼容php 8.0 219 | 220 | #### Removed 221 | 222 | - none 223 | 224 | ## [v0.4.2]- 2021-12-09 225 | 226 | #### Added 227 | 228 | - none 229 | 230 | #### Fixed 231 | 232 | - none 233 | 234 | #### Changed 235 | 236 | - 修改常量`DELIMITER` 237 | 238 | #### Removed 239 | 240 | - none 241 | 242 | ## [v0.4.1]- 2021-10-24 243 | 244 | #### Added 245 | 246 | - 增加`StringHelper::toBytes` 247 | - 增加`StringHelper::bytes2Str` 248 | 249 | #### Fixed 250 | 251 | - none 252 | 253 | #### Changed 254 | 255 | - none 256 | 257 | #### Removed 258 | 259 | - none 260 | 261 | ## [v0.4.0]- 2021-04-13 262 | 263 | #### Added 264 | 265 | - 增加`ValidateHelper::startsWiths` 266 | - 增加`ValidateHelper::endsWiths` 267 | 268 | #### Fixed 269 | 270 | - none 271 | 272 | #### Changed 273 | 274 | - none 275 | 276 | #### Removed 277 | 278 | - none 279 | 280 | ## [v0.3.9]- 2021-02-23 281 | 282 | #### Added 283 | 284 | - 增加`OsHelper::remoteFileExists` 285 | 286 | #### Fixed 287 | 288 | - none 289 | 290 | #### Changed 291 | 292 | - none 293 | 294 | #### Removed 295 | 296 | - none 297 | 298 | ## [v0.3.8]- 2021-01-11 299 | 300 | #### Added 301 | 302 | - 增加`OsHelper::isSsl` 303 | 304 | #### Fixed 305 | 306 | - none 307 | 308 | #### Changed 309 | 310 | - none 311 | 312 | #### Removed 313 | 314 | - none 315 | 316 | ## [v0.3.7]- 2021-01-05 317 | 318 | #### Added 319 | 320 | - 增加`OsHelper::isAjax` 321 | 322 | #### Fixed 323 | 324 | - none 325 | 326 | #### Changed 327 | 328 | - none 329 | 330 | #### Removed 331 | 332 | - none 333 | 334 | ## [v0.3.6]- 2020-12-19 335 | 336 | #### Added 337 | 338 | - none 339 | 340 | #### Fixed 341 | 342 | - none 343 | 344 | #### Changed 345 | 346 | - 修改`ArrayHelper::searchItem`支持可迭代的对象 347 | - 修改`ArrayHelper::searchMutil`支持可迭代的对象 348 | 349 | #### Removed 350 | 351 | - none 352 | 353 | ## [v0.3.5]- 2020-12-18 354 | 355 | #### Added 356 | 357 | - none 358 | 359 | #### Fixed 360 | 361 | - none 362 | 363 | #### Changed 364 | 365 | - 修改`ArrayHelper::searchItem`支持数组元素是对象 366 | - 修改`ArrayHelper::searchMutil`支持数组元素是对象 367 | 368 | #### Removed 369 | 370 | - none 371 | 372 | ## [v0.3.4]- 2020-12-10 373 | 374 | #### Added 375 | 376 | - 添加`OsHelper::runCommand` 377 | - 添加`ValidateHelper::isMacAddress` 378 | - 添加`Kph\Util\MacAddress` 379 | 380 | #### Fixed 381 | 382 | - none 383 | 384 | #### Changed 385 | 386 | - 修改`DebugHelper::errorLogHandler`临时目录 387 | - 修改`DirectoryHelper::formatDir`,兼容windows路径 388 | - 修改`OsHelper::getPhpPath`,兼容windows 389 | 390 | #### Removed 391 | 392 | - none 393 | 394 | ## [v0.3.3]- 2020-12-8 395 | 396 | #### Added 397 | 398 | - none 399 | 400 | #### Fixed 401 | 402 | - none 403 | 404 | #### Changed 405 | 406 | - 规范BaseService错误信息类型 407 | 408 | #### Removed 409 | 410 | - none 411 | 412 | ## [v0.3.2]- 2020-12-2 413 | 414 | #### Added 415 | 416 | - 方法`EncryptHelper::opensslEncrypt` 417 | - 方法`EncryptHelper::opensslDecrypt` 418 | 419 | #### Fixed 420 | 421 | - none 422 | 423 | #### Changed 424 | 425 | - none 426 | 427 | #### Removed 428 | 429 | - none 430 | 431 | ## [v0.3.1]- 2020-11-25 432 | 433 | #### Added 434 | 435 | - 方法`ConvertHelper::hex2Str` 436 | - 方法`ConvertHelper::str2hex` 437 | 438 | #### Fixed 439 | 440 | - none 441 | 442 | #### Changed 443 | 444 | - move `ArrayHelper::array2Object` to `ConvertHelper::array2Object` 445 | - move `ArrayHelper::object2Array` to `ConvertHelper::object2Array` 446 | 447 | #### Removed 448 | 449 | - none 450 | 451 | ## [v0.3.0]- 2020-11-21 452 | 453 | #### Added 454 | 455 | - 方法`NumberHelper::money2Yuan` 456 | - 方法`NumberHelper::nearLogarithm` 457 | - 方法`NumberHelper::splitNaturalNum` 458 | - 方法`OsHelper::getOS` 459 | - 方法`OsHelper::isMac` 460 | - 方法`StringHelper::grabBrackets` 461 | - 方法`StringHelper::stripBrackets` 462 | - 方法`StringHelper::toArray` 463 | 464 | #### Fixed 465 | 466 | - 修改`BaseService::getError`为null时类型错误 467 | 468 | #### Changed 469 | 470 | - 修改`FileHelper::img2Base64`,增加图片类型 471 | - 修改`StringHelper::dstrpos`,使用mb_strpos 472 | 473 | #### Removed 474 | 475 | - none 476 | 477 | ## [v0.2.9]- 2020-11-16 478 | 479 | #### Added 480 | 481 | - 方法`FileHelper::formatPath` 482 | - 方法`FileHelper::getAbsPath` 483 | - 方法`FileHelper::getRelativePath` 484 | - 方法`ValidateHelper::isNaturalRange` 485 | 486 | #### Fixed 487 | 488 | - none 489 | 490 | #### Changed 491 | 492 | - 修改`DirectoryHelper::formatDir`,允许特殊字符 493 | 494 | #### Removed 495 | 496 | - none 497 | 498 | ## [v0.2.8]- 2020-10-31 499 | 500 | #### Added 501 | 502 | - 方法`DateHelper::startOfHour` 503 | - 方法`DateHelper::endOfHour` 504 | 505 | #### Fixed 506 | 507 | - none 508 | 509 | #### Changed 510 | 511 | - none 512 | 513 | #### Removed 514 | 515 | - none 516 | 517 | ## [v0.2.7]- 2020-10-31 518 | 519 | #### Added 520 | 521 | - none 522 | 523 | #### Fixed 524 | 525 | - 修复`ArrayHelper::object2Array`当对象内嵌对象时不转换问题 526 | 527 | #### Changed 528 | 529 | - none 530 | 531 | #### Removed 532 | 533 | - none 534 | 535 | ## [v0.2.6]- 2020-10-19 536 | 537 | #### Added 538 | 539 | - 新增`NumberHelper::numberSub`数值截取方法 540 | 541 | #### Fixed 542 | 543 | - none 544 | 545 | #### Changed 546 | 547 | - 修改`NumberHelper::numberFormat`,去掉参数`$decPoint`和`$thousandssep` 548 | 549 | #### Removed 550 | 551 | - none 552 | 553 | ## [v0.2.5]- 2020-10-15 554 | 555 | #### Added 556 | 557 | - none 558 | 559 | #### Fixed 560 | 561 | - none 562 | 563 | #### Changed 564 | 565 | - 修改`DirectoryHelper::getFileTree`,弃用`glob`函数 566 | 567 | #### Removed 568 | 569 | - none 570 | 571 | ## [v0.2.4]- 2020-09-25 572 | 573 | #### Added 574 | 575 | - none 576 | 577 | #### Fixed 578 | 579 | - none 580 | 581 | #### Changed 582 | 583 | - 修改`ArrayHelper::regularSort`,增加参数`$recursive`是否递归 584 | 585 | #### Removed 586 | 587 | - none 588 | 589 | ## [v0.2.2]- 2020-09-22 590 | 591 | #### Added 592 | 593 | - none 594 | 595 | #### Fixed 596 | 597 | - none 598 | 599 | #### Changed 600 | 601 | - 修改`ArrayHelper::cutItems`,增加参数`$keepKey`是否保留键名 602 | 603 | #### Removed 604 | 605 | - none 606 | 607 | ## [v0.2.1]- 2020-09-17 608 | 609 | #### Added 610 | 611 | - none 612 | 613 | #### Fixed 614 | 615 | - 修复`Kph\Concurrent\makeClosureFun` 616 | 617 | #### Changed 618 | 619 | - none 620 | 621 | #### Removed 622 | 623 | - none 624 | 625 | ## [v0.2.0]- 2020-06-20 626 | 627 | #### Added 628 | 629 | - none 630 | 631 | #### Fixed 632 | 633 | - none 634 | 635 | #### Changed 636 | 637 | - 优化`EncryptHelper::authcode` 638 | 639 | #### Removed 640 | 641 | - none 642 | 643 | ## [v0.1.9]- 2020-05-20 644 | 645 | #### Added 646 | 647 | - none 648 | 649 | #### Fixed 650 | 651 | - 修复`StringHelper::toCamelCase`当输入首字母大写的驼峰字符串失败问题 652 | 653 | #### Changed 654 | 655 | - none 656 | 657 | #### Removed 658 | 659 | - none 660 | 661 | ## [v0.1.8]- 2020-05-19 662 | 663 | #### Added 664 | 665 | - none 666 | 667 | #### Fixed 668 | 669 | - none 670 | 671 | #### Changed 672 | 673 | - 方法`ValidateHelper::isIndexArray`参数不限制类型 674 | - 方法`ValidateHelper::isAssocArray`参数不限制类型 675 | - 方法`ValidateHelper::isOneDimensionalArray`参数不限制类型 676 | 677 | #### Removed 678 | 679 | - none 680 | 681 | ## [v0.1.7]- 2020-05-18 682 | 683 | #### Added 684 | 685 | - 方法`ValidateHelper::isOneDimensionalArray` 686 | 687 | #### Fixed 688 | 689 | - none 690 | 691 | #### Changed 692 | 693 | - none 694 | 695 | #### Removed 696 | 697 | - none 698 | 699 | ## [v0.1.6]- 2020-05-18 700 | 701 | #### Added 702 | 703 | - none 704 | 705 | #### Fixed 706 | 707 | - 修复`BaseObject::getClassMethods`当$filter为null时,在php7.2下失败的问题 708 | 709 | #### Changed 710 | 711 | - none 712 | 713 | #### Removed 714 | 715 | - none 716 | 717 | ## [v0.1.5]- 2020-05-18 718 | 719 | #### Added 720 | 721 | - 方法`ArrayHelper::regularSort` 722 | - 方法`BaseObject::formatNamespace` 723 | - 方法`BaseObject::getClass` 724 | - 方法`BaseObject::getClassMethods` 725 | - 方法`ValidateHelper::isEqualArray` 726 | 727 | #### Fixed 728 | 729 | - 修复`ValidateHelper::isIndexArray`存在负数索引时的问题 730 | 731 | #### Changed 732 | 733 | - none 734 | 735 | #### Removed 736 | 737 | - none 738 | 739 | ## [v0.1.4]- 2020-05-17 740 | 741 | #### Added 742 | 743 | - 方法`NumberHelper::randFloat` 744 | 745 | #### Fixed 746 | 747 | - none 748 | 749 | #### Changed 750 | 751 | - none 752 | 753 | #### Removed 754 | 755 | - none 756 | 757 | ## [v0.1.3]- 2020-05-16 758 | 759 | #### Added 760 | 761 | - 方法`ValidateHelper::isAssocArray` 762 | - 方法`ValidateHelper::isIndexArray` 763 | - 方法`ArrayHelper::compareSchema` 764 | 765 | #### Fixed 766 | 767 | - none 768 | 769 | #### Changed 770 | 771 | - none 772 | 773 | #### Removed 774 | 775 | - none 776 | 777 | ## [v0.1.2]- 2020-05-08 778 | 779 | #### Added 780 | 781 | - 方法`DateHelper::startOfDay` 782 | - 方法`DateHelper::endOfDay` 783 | - 方法`DateHelper::startOfMonth` 784 | - 方法`DateHelper::endOfMonth` 785 | - 方法`DateHelper::startOfYear` 786 | - 方法`DateHelper::endOfYear` 787 | - 方法`DateHelper::startOfWeek` 788 | - 方法`DateHelper::endOfWeek` 789 | 790 | #### Fixed 791 | 792 | - none 793 | 794 | #### Changed 795 | 796 | - none 797 | 798 | #### Removed 799 | 800 | - none 801 | 802 | ## [v0.1.1]- 2020-04-28 803 | 804 | #### Added 805 | 806 | - none 807 | 808 | #### Fixed 809 | 810 | - 修复`ValidateHelper::isNaturalNum`为0时错误 811 | 812 | #### Changed 813 | 814 | - none 815 | 816 | #### Removed 817 | 818 | - none 819 | 820 | ## [v0.1.0]- 2020-04-09 821 | 822 | #### Added 823 | 824 | - 方法`NumberHelper::numberForma` 825 | - 方法`OsHelper::isCliMode` 826 | - 方法`StringHelper::contains` 827 | - 方法`StringHelper::middle` 828 | - 方法`StringHelper::uuidV4` 829 | - 方法`ValidateHelper::isAlpha` 830 | - 方法`ValidateHelper::isAlphaChinese` 831 | - 方法`ValidateHelper::isAlphaNum` 832 | - 方法`ValidateHelper::isAlphaNumChinese` 833 | - 方法`ValidateHelper::isAlphaNumDash` 834 | - 方法`ValidateHelper::isAlphaNumDashChinese` 835 | 836 | #### Fixed 837 | 838 | - none 839 | 840 | #### Changed 841 | 842 | - none 843 | 844 | #### Removed 845 | 846 | - none 847 | 848 | ## [v0.0.8]- 2020-03-12 849 | 850 | #### Added 851 | 852 | - 方法`ArrayHelper::setDotKey` 853 | - 方法`ArrayHelper::getDotKey` 854 | - 方法`ArrayHelper::hasDotKey` 855 | 856 | #### Fixed 857 | 858 | - none 859 | 860 | #### Changed 861 | 862 | - none 863 | 864 | #### Removed 865 | 866 | - none 867 | 868 | ## [v0.0.7]- 2020-03-11 869 | 870 | #### Added 871 | 872 | - 方法`ValidateHelper::isQQ` 873 | - 方法`ValidateHelper::isNaturalNum` 874 | - 方法`StringHelper::passwdSafeGrade` 875 | 876 | #### Fixed 877 | 878 | - none 879 | 880 | #### Changed 881 | 882 | - none 883 | 884 | #### Removed 885 | 886 | - none 887 | 888 | ## [v0.0.6]- 2020-03-10 889 | 890 | #### Added 891 | 892 | - 方法`BaseObject::parseNamespacePath` 893 | - 方法`BaseObject::getShortName` 894 | - 方法`BaseObject::getNamespaceName` 895 | 896 | #### Fixed 897 | 898 | - none 899 | 900 | #### Changed 901 | 902 | - none 903 | 904 | #### Removed 905 | 906 | - 方法`BaseObject::getClassShortName` 907 | 908 | ## [v0.0.5]- 2020-03-10 909 | 910 | #### Added 911 | 912 | - 常量`Consts::DELIMITER` 913 | - 常量`Consts::PAAMAYIM_NEKUDOTAYIM` 914 | 915 | #### Fixed 916 | 917 | - none 918 | 919 | #### Changed 920 | 921 | - none 922 | 923 | #### Removed 924 | 925 | - none 926 | 927 | ## [v0.0.4]- 2020-03-09 928 | 929 | #### Added 930 | 931 | - 方法`ValidateHelper::isMultibyte` 932 | - 类`Kph\Exceptions\BaseException` 933 | - 类`Kph\Exceptions\UncatchableException` 934 | 935 | #### Fixed 936 | 937 | - 修复`StringHelper::fixHtml`BUG,使用DOMDocument替代正则 938 | - 修复`Concurrent\co`错误捕获 939 | 940 | #### Changed 941 | 942 | - rename `DirectoryHelper::emptyDir` to `DirectoryHelper::clearDir` 943 | - 修改`Future::resolve`,支持处理value是is_callable的情况 944 | 945 | #### Removed 946 | 947 | - `Kph\Concurrent\Exception\UncatchableException` 948 | 949 | ## [v0.0.3]- 2020-03-06 950 | 951 | #### Added 952 | 953 | - 方法`StringHelper::trim` 954 | - 方法`StringHelper::toCamelCase` 955 | - 方法`StringHelper::toSnakeCase` 956 | - 方法`StringHelper::toKebabCase` 957 | - 方法`StringHelper::removeBefore` 958 | - 方法`StringHelper::removeAfter` 959 | - 方法`ValidateHelper::isSpace` 960 | - 方法`ValidateHelper::isWhitespace` 961 | 962 | #### Fixed 963 | 964 | - none 965 | 966 | #### Changed 967 | 968 | - `ArrayHelper::dstrpos` 改为`StringHelper::dstrpos` 969 | - `StringHelper::removeSpace` 增加参数$all 970 | - `ValidateHelper::startsWith` 增加参数$ignoreCase 971 | - `ValidateHelper::endsWith` 增加参数$ignoreCase 972 | 973 | #### Removed 974 | 975 | - none 976 | 977 | ## [v0.0.2]- 2020-03-03 978 | 979 | #### Added 980 | 981 | - 方法`NumberHelper::geoDistance` 982 | - 方法`StringHelper::removeEmoji` 983 | 984 | #### Fixed 985 | 986 | - none 987 | 988 | #### Changed 989 | 990 | - none 991 | 992 | #### Removed 993 | 994 | - none 995 | 996 | -------------------------------------------------------------------------------- /docs/todo.md: -------------------------------------------------------------------------------- 1 | ### TODO 2 | - GeoDistance 3 | 4 | 5 | ### 常用函数库 6 | https://bitbucket.org/justusmeyer/php-helper 7 | https://gitee.com/wuwenbin/php-helper 8 | https://github.com/HustGuoHeng/library 9 | https://github.com/Intervention/helper 10 | https://github.com/Osub/myFunction 11 | https://github.com/a67793581/php_function 12 | https://github.com/anerg2046/helper 13 | https://github.com/bearns/support 14 | https://github.com/bocharsky-bw/Arrayzy 15 | https://github.com/browner12/helpers 16 | https://github.com/cjango/helper 17 | https://github.com/codepso/php-helper 18 | https://github.com/coolswater/commonFunctions 19 | https://github.com/elinxer/library 20 | https://github.com/gyselroth/php-helper 21 | https://github.com/iamhefang/php-helpers 22 | https://github.com/ice-php/functions 23 | https://github.com/jasny/php-functions 24 | https://github.com/jstewmc/php-helpers 25 | https://github.com/koboshi/tool 26 | https://github.com/kohana/ohanzee-helpers 27 | https://github.com/liujin0506/jliu-helper 28 | https://github.com/lodash-php/lodash-php 29 | https://github.com/markrogoyski/math-php 30 | https://github.com/moxuandi/yii2-helpers 31 | https://github.com/mylukin/LazyFunc 32 | https://github.com/nkkollaw/zubr 33 | https://github.com/penglitao/library 34 | https://github.com/ppabcd/Helpers 35 | https://github.com/sethink/function-lib 36 | https://github.com/swoole/library 37 | https://github.com/tkhatibi/php-helper 38 | https://github.com/top-think/think-helper 39 | https://github.com/whm19940308/phpTools 40 | https://github.com/wycto/helper 41 | https://github.com/yunkaiyueming/php_lib_code_center 42 | https://github.com/zhosoft/tools 43 | https://github.com/voku/portable-utf8 44 | https://github.com/mtibben/html2text 45 | https://gitee.com/jingyiGit/php-utils 46 | https://gitee.com/ping_yuan/php-utils 47 | https://gitee.com/vipkwd/phputils 48 | https://gitee.com/myadder/utils-php 49 | https://gitee.com/aguage/php-utils 50 | 51 | 52 | promise 53 | https://github.com/hprose/hprose-php 54 | https://github.com/streamcommon/promise 55 | https://github.com/php-promise/promise 56 | https://docs.aws.amazon.com/zh_cn/sdk-for-php/v3/developer-guide/guide_promises.html 57 | 58 | 59 | ### 静态检查 60 | ```sh 61 | phpstan analyze src 62 | ``` 63 | -------------------------------------------------------------------------------- /src/Concurrent/CallableWrapper.php: -------------------------------------------------------------------------------- 1 | obj; 28 | return all(func_get_args())->then(function($args) use ($obj) { 29 | return co(call_user_func_array($obj, $args)); 30 | }); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/Concurrent/Future.php: -------------------------------------------------------------------------------- 1 | resolve(call_user_func($computation)); 88 | } catch (UncatchableException $e) { 89 | $previou = $e->getPrevious(); 90 | throw (is_object($previou) ? $previou : $e); 91 | } catch (Throwable $e) { 92 | $this->reject($e); 93 | } 94 | } 95 | } 96 | 97 | 98 | /** 99 | * 私有调用 100 | * @param callable $callback 101 | * @param Future $next 102 | * @param mixed $params 103 | * @throws Throwable 104 | */ 105 | private function privateCall(callable $callback, Future $next, $params) { 106 | try { 107 | $r = call_user_func($callback, $params); 108 | $next->resolve($r); 109 | } catch (UncatchableException $e) { 110 | $previou = $e->getPrevious(); 111 | throw (is_object($previou) ? $previou : $e); 112 | } catch (Throwable $e) { 113 | $next->reject($e); 114 | } 115 | } 116 | 117 | 118 | /** 119 | * 私有解决 120 | * @param callable $onfulfill 成功事件 121 | * @param Future $next 122 | * @param mixed $params 123 | * @throws Throwable 124 | */ 125 | private function privateResolve($onfulfill, Future $next, $params) { 126 | if (is_callable($onfulfill)) { 127 | $this->privateCall($onfulfill, $next, $params); 128 | } else { 129 | $next->resolve($params); 130 | } 131 | } 132 | 133 | 134 | /** 135 | * 私有拒绝 136 | * @param callable $onreject 失败事件 137 | * @param Future $next 138 | * @param mixed $params 139 | * @throws Throwable 140 | */ 141 | private function privateReject($onreject, Future $next, $params) { 142 | if (is_callable($onreject)) { 143 | $this->privateCall($onreject, $next, $params); 144 | } else { 145 | $next->reject($params); 146 | } 147 | } 148 | 149 | 150 | /** 151 | * 解决 152 | * 该方法可以将状态为待定(pending)的 promise 对象变为成功(fulfilled)状态 153 | * @param mixed $value 154 | * @throws Throwable 155 | */ 156 | public function resolve($value) { 157 | if ($value === $this) { 158 | $this->reject(new TypeError('Self resolution')); 159 | return; 160 | } elseif (isFuture($value)) { 161 | $value->fill($this); 162 | return; 163 | } 164 | 165 | $then = null; 166 | if (is_callable($value)) { 167 | $then = $value; 168 | } elseif (is_object($value) && method_exists($value, 'then')) { 169 | $then = [$value, 'then']; 170 | }elseif(is_string($value) && class_exists($value) && method_exists($value, 'then')){ 171 | $obj = new $value(); 172 | $then = [$obj, 'then']; 173 | } 174 | 175 | if(!is_null($then)) { 176 | $notrun = true; 177 | $self = $this; 178 | try { 179 | call_user_func($then, function ($y) use (&$notrun, $self) { 180 | if ($notrun) { 181 | $notrun = false; 182 | $self->resolve($y); 183 | } 184 | }, function ($r) use (&$notrun, $self) { 185 | if ($notrun) { 186 | $notrun = false; 187 | $self->reject($r); 188 | } 189 | }); 190 | } catch (UncatchableException $e) { 191 | $previou = $e->getPrevious(); 192 | throw (is_object($previou) ? $previou : $e); 193 | } catch (Throwable $e) { 194 | if ($notrun) { 195 | $notrun = false; 196 | $this->reject($e); 197 | } 198 | } 199 | return; 200 | } 201 | 202 | if ($this->state === self::PENDING) { 203 | $this->state = self::FULFILLED; 204 | $this->value = $value; 205 | while (count($this->subscribers) > 0) { 206 | $subscriber = array_shift($this->subscribers); 207 | $this->privateResolve($subscriber['onfulfill'], $subscriber['next'], $value); 208 | } 209 | } 210 | } 211 | 212 | 213 | /** 214 | * 拒绝 215 | * 该方法可以将状态为待定(pending)的 promise 对象变为失败(rejected)状态 216 | * @param $reason 217 | * @throws Throwable 218 | */ 219 | public function reject($reason) { 220 | if ($this->state === self::PENDING) { 221 | $this->state = self::REJECTED; 222 | $this->reason = $reason; 223 | while (count($this->subscribers) > 0) { 224 | $subscriber = array_shift($this->subscribers); 225 | $this->privateReject($subscriber['onreject'], $subscriber['next'], $reason); 226 | } 227 | } 228 | } 229 | 230 | 231 | /** 232 | * 将要 233 | * 支持链式调用. 234 | * @param mixed $onfulfill 当成功时的执行体 235 | * @param mixed $onreject 当失败时的执行体 236 | * @return Future 237 | * @throws Throwable 238 | */ 239 | public function then($onfulfill, $onreject = null): Future { 240 | if (!is_callable($onfulfill)) { 241 | $onfulfill = null; 242 | } 243 | if (!is_callable($onreject)) { 244 | $onreject = null; 245 | } 246 | 247 | $next = new Future(); 248 | 249 | if ($this->state === self::FULFILLED) { 250 | $this->privateResolve($onfulfill, $next, $this->value); 251 | } elseif ($this->state === self::REJECTED) { 252 | $this->privateReject($onreject, $next, $this->reason); 253 | } else { 254 | array_push($this->subscribers, ['onfulfill' => $onfulfill, 'onreject' => $onreject, 'next' => $next]); 255 | } 256 | 257 | return $next; 258 | } 259 | 260 | 261 | /** 262 | * 完成 263 | * 类似then,但无返回值,不支持链式调用;用于单元测试. 264 | * @param $onfulfill 265 | * @param null $onreject 266 | * @throws Throwable 267 | */ 268 | public function done($onfulfill, $onreject = null): void { 269 | $this->then($onfulfill, $onreject)->then(null, function (Throwable $error) { 270 | throw new UncatchableException("", 0, $error); 271 | }); 272 | } 273 | 274 | 275 | /** 276 | * 失败 277 | * 该方法是 done(null, $onreject) 的简化.用于单元测试. 278 | * @param $onreject 279 | * @throws Throwable 280 | */ 281 | public function fail($onreject): void { 282 | $this->done(null, $onreject); 283 | } 284 | 285 | 286 | /** 287 | * 当完成时(无论成功或失败). 288 | * @param callable $fn 执行体 289 | * @return Future 290 | * @throws Throwable 291 | */ 292 | public function whenComplete(callable $fn): Future { 293 | return $this->then(function ($v) use ($fn) { 294 | makeClosureFun($fn, $v)(); 295 | return $v; 296 | }, function ($e) use ($fn) { 297 | makeClosureFun($fn, $e)(); 298 | throw $e; 299 | }); 300 | } 301 | 302 | 303 | /** 304 | * 完成 305 | * 无论成功或失败,支持链式调用. 306 | * 是then(oncomplete, oncomplete)的简化 307 | * @param callable $oncomplete 308 | * @return Future 309 | * @throws Throwable 310 | */ 311 | public function complete(callable $oncomplete = null): Future { 312 | $oncomplete = $oncomplete ?: function ($v) { 313 | return $v; 314 | }; 315 | return $this->then($oncomplete, $oncomplete); 316 | } 317 | 318 | 319 | /** 320 | * 总是 321 | * 无论成功或失败,不支持链式. 322 | * 是done(oncomplete, oncomplete) 的简化 323 | * @param callable $oncomplete 324 | * @throws Throwable 325 | */ 326 | public function always(callable $oncomplete): void { 327 | $this->done($oncomplete, $oncomplete); 328 | } 329 | 330 | 331 | /** 332 | * 将当前 promise 对象的值充填到参数所表示的 promise 对象中 333 | * @param $future 334 | * @throws Throwable 335 | */ 336 | public function fill($future): void { 337 | $this->then([$future, 'resolve'], [$future, 'reject']); 338 | } 339 | 340 | 341 | /** 342 | * then成功后简写,将结果(单一值)作为回调参数. 343 | * @param callable $onfulfilledCallback 344 | * @return Future 345 | * @throws Throwable 346 | */ 347 | public function tap(callable $onfulfilledCallback): Future { 348 | return $this->then(function ($result) use ($onfulfilledCallback) { 349 | call_user_func($onfulfilledCallback, $result); 350 | return $result; 351 | }); 352 | } 353 | 354 | 355 | /** 356 | * then成功后简写,将结果(数组)作为回调函数的参数. 357 | * @param callable $onfulfilledCallback 358 | * @return Future 359 | * @throws Throwable 360 | */ 361 | public function spread(callable $onfulfilledCallback): Future { 362 | return $this->then(function ($array) use ($onfulfilledCallback) { 363 | return call_user_func_array($onfulfilledCallback, $array); 364 | }); 365 | } 366 | 367 | 368 | /** 369 | * 返回当前 promise 对象的状态 370 | * 如果当前状态为待定(pending),返回值为:['state' => 'pending'] 371 | * 如果当前状态为成功(fulfilled),返回值为:['state' => 'fulfilled', 'value' => $promise->value] 372 | * 如果当前状态为失败(rejected),返回值为:['state' => 'rejected', 'reason' => $promise->reason] 373 | * @return array 374 | */ 375 | public function inspect(): array { 376 | $res = ['state' => $this->state,]; 377 | 378 | switch ($this->state) { 379 | case self::PENDING: 380 | break; 381 | case self::FULFILLED: 382 | $res['value'] = $this->value; 383 | break; 384 | case self::REJECTED: 385 | $res['reason'] = $this->reason; 386 | break; 387 | } 388 | 389 | return $res; 390 | } 391 | 392 | 393 | /** 394 | * 获取结果 395 | * @return mixed|null 396 | */ 397 | public function getResult() { 398 | $status = $this->inspect(); 399 | return $status['value'] ?? null; 400 | } 401 | 402 | 403 | /** 404 | * 获取原因 405 | * @return mixed|null 406 | */ 407 | public function getReason() { 408 | $status = $this->inspect(); 409 | return $status['reason'] ?? null; 410 | } 411 | 412 | 413 | /** 414 | * 是否正待定 415 | * @return bool 416 | */ 417 | public function isPending(): bool { 418 | return $this->state === self::PENDING; 419 | } 420 | 421 | 422 | /** 423 | * 是否已成功 424 | * @return bool 425 | */ 426 | public function isFulfilled(): bool { 427 | return $this->state === self::FULFILLED; 428 | } 429 | 430 | 431 | /** 432 | * 是否已失败 433 | * @return bool 434 | */ 435 | public function isRejected(): bool { 436 | return $this->state === self::REJECTED; 437 | } 438 | 439 | 440 | /** 441 | * 是否已完成 442 | * @return bool 443 | */ 444 | public function isCompleted(): bool { 445 | return in_array($this->state, [self::FULFILLED, self::REJECTED]); 446 | } 447 | 448 | 449 | /** 450 | * 捕获错误 451 | * 该方法是 then(null, $onreject) 的简化. 452 | * @param $onreject 453 | * @param callable $fn 454 | * @return Future 455 | * @throws Throwable 456 | */ 457 | public function catchError($onreject, callable $fn = null): Future { 458 | if (is_callable($fn)) { 459 | $self = $this; 460 | return $this->then(null, function ($e) use ($self, $onreject, $fn) { 461 | if (call_user_func($fn, $e)) { 462 | return $self->then(null, $onreject); 463 | } elseif($e instanceof Throwable) { 464 | throw $e; 465 | }else{ 466 | throw new BaseException(strval($e)); 467 | } 468 | }); 469 | } 470 | return $this->then(null, $onreject); 471 | } 472 | 473 | 474 | /** 475 | * 获取状态或结果key的值 476 | * @param string $key 477 | * @return string|Future 478 | * @throws Throwable 479 | */ 480 | public function __get(string $key) { 481 | if ($key == 'state') { 482 | return $this->state; 483 | } 484 | return $this->then(function ($result) use ($key) { 485 | $res = null; 486 | if (is_object($result)) { 487 | $res = $result->$key ?? null; 488 | }elseif (is_array($result)) { 489 | $res = $result[$key] ?? null; 490 | } 491 | 492 | return $res; 493 | }); 494 | } 495 | 496 | 497 | /** 498 | * 自动调用方法 499 | * @param string $method 方法名 500 | * @param array $args 参数 501 | * @return Future 502 | * @throws Throwable 503 | */ 504 | public function __call(string $method, array $args): Future { 505 | return $this->then(function ($result) use ($method, $args) { 506 | return all($args)->then(function ($args) use ($result, $method) { 507 | return call_user_func_array([$result, $method], $args); 508 | }); 509 | }); 510 | } 511 | 512 | 513 | } -------------------------------------------------------------------------------- /src/Concurrent/Promise.php: -------------------------------------------------------------------------------- 1 | resolve($value); 35 | }, function ($reason = null) use ($self) { 36 | $self->reject($reason); 37 | }); 38 | } 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /src/Concurrent/Wrapper.php: -------------------------------------------------------------------------------- 1 | obj = $obj; 28 | } 29 | 30 | 31 | /** 32 | * @param $name 33 | * @param array $arguments 34 | * @return Future 35 | * @throws Throwable 36 | */ 37 | public function __call($name, array $arguments):Future { 38 | $method = [$this->obj, $name]; 39 | return all($arguments)->then(function($args) use ($method, $name) { 40 | return co(call_user_func_array($method, $args)); 41 | }); 42 | } 43 | 44 | 45 | public function __get($name) { 46 | return $this->obj->$name ?? null; 47 | } 48 | 49 | 50 | public function __set($name, $value) { 51 | $this->obj->$name = $value; 52 | } 53 | 54 | 55 | public function __isset($name) { 56 | return isset($this->obj->$name); 57 | } 58 | 59 | 60 | public function __unset($name) { 61 | unset($this->obj->$name); 62 | } 63 | 64 | 65 | } -------------------------------------------------------------------------------- /src/Consts.php: -------------------------------------------------------------------------------- 1 | getMessage() . ' ##code:' . $this->getCode() . ' ##file:' . $this->getFile() . ' ##line:' . $this->getLine(); 29 | return $msg; 30 | } 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/Exceptions/UncatchableException.php: -------------------------------------------------------------------------------- 1 | $item) { 32 | if (is_array($item) && !empty($item)) { 33 | $arr[$k] = array_map(__METHOD__, $item); 34 | } elseif (is_object($item)) { 35 | $arr[$k] = self::object2Array($item); 36 | } 37 | } 38 | } else { 39 | $arr = (array)$arr; 40 | } 41 | 42 | return $arr; 43 | } 44 | 45 | 46 | /** 47 | * 数组转对象 48 | * @param array $arr 49 | * @return object 50 | */ 51 | public static function array2Object(array $arr): object { 52 | foreach ($arr as $k => $item) { 53 | if (is_array($item)) { 54 | $arr[$k] = empty($item) ? new \stdClass() : call_user_func(__METHOD__, $item); 55 | } 56 | } 57 | 58 | return (object)$arr; 59 | } 60 | 61 | 62 | /** 63 | * 字符串转十六进制 64 | * @param string $str 65 | * @return string 66 | */ 67 | public static function str2hex(string $str): string { 68 | $res = ''; 69 | for ($i = 0; $i < strlen($str); $i++) { 70 | $val = dechex(ord($str[$i])); 71 | if (strlen($val) < 2) { 72 | $val = "0" . $val; 73 | } 74 | $res .= $val; 75 | } 76 | 77 | return $res; 78 | } 79 | 80 | 81 | /** 82 | * 十六进制转字符串 83 | * @param string $str 84 | * @return string 85 | */ 86 | public static function hex2Str(string $str): string { 87 | $res = ''; 88 | $str = strtolower($str); 89 | for ($i = 0; $i < strlen($str); $i += 2) { 90 | $item = substr($str, $i, 2); 91 | $item = hexdec($item); 92 | $val = chr($item); 93 | $res .= $val; 94 | } 95 | 96 | return $res; 97 | } 98 | 99 | 100 | } -------------------------------------------------------------------------------- /src/Helpers/DebugHelper.php: -------------------------------------------------------------------------------- 1 | '致命运行时错误(E_ERROR)', 21 | '2' => '运行时警告(E_WARNING)', 22 | '4' => '编译时语法解析错误(E_PARSE)', 23 | '8' => '运行时提示(E_NOTICE)', 24 | '16' => 'PHP初始化致命错误(E_CORE_ERROR)', 25 | '32' => 'PHP初始化警告(E_CORE_WARNING)', 26 | '64' => 'Zend致命编译时错误(E_COMPILE_ERROR)', 27 | '128' => 'Zend编译时警告(E_COMPILE_WARNING)', 28 | '256' => '用户产生的错误(E_USER_ERROR)', 29 | '512' => '用户产生的警告(E_USER_WARNING)', 30 | '1024' => '用户产生的提示(E_USER_NOTICE)', 31 | '2048' => '代码提示(E_STRICT)', 32 | '4096' => '可捕获的致命错误(E_RECOVERABLE_ERROR)', 33 | '8192' => '运行时提示(E_DEPRECATED)', 34 | '16384' => '用户警告信息(E_USER_DEPRECATED)', 35 | '30719' => '所有错误警告(E_ALL)', 36 | ]; 37 | 38 | 39 | /** 40 | * 错误日志捕获(只适用于php7) 41 | * @param string $logFile 42 | */ 43 | public static function errorLogHandler(string $logFile = ''): void { 44 | if (empty($logFile)) { 45 | $tmpDir = sys_get_temp_dir(); 46 | $logFile = $tmpDir . '/phperr_' . date('Ymd') . '.log'; 47 | } elseif (!file_exists($logFile)) { 48 | @touch($logFile); 49 | } 50 | 51 | ini_set('log_errors', 1); //设置错误信息输出到文件 52 | ini_set('ignore_repeated_errors', 1);//不重复记录出现在同一个文件中的同一行代码上的错误信息 53 | 54 | $error = error_get_last();//获取最后发生的错误 55 | if (is_array($error)) { 56 | $errorType = self::ERROR_TYPES[$error['type']] ?? '未知类型'; 57 | 58 | $msg = sprintf('[%s] %s %s %s line:%s', 59 | date("Y-m-d H:i:s"), 60 | $errorType, 61 | $error['message'], 62 | $error['file'], 63 | $error['line']); 64 | 65 | //必须显式地记录错误 66 | error_log($msg . "\r\n", 3, $logFile); 67 | } 68 | } 69 | 70 | 71 | } -------------------------------------------------------------------------------- /src/Helpers/DirectoryHelper.php: -------------------------------------------------------------------------------- 1 | $file) { 62 | $fpath = $file->getRealPath(); 63 | if ($file->isDir()) { 64 | if ($type != 'file') { 65 | array_push($tree, $fpath); 66 | } 67 | } elseif ($type != 'dir') { 68 | array_push($tree, $fpath); 69 | } 70 | } 71 | } elseif (is_file($path) && $type != 'dir') { 72 | array_push($tree, $path); 73 | } 74 | 75 | return $tree; 76 | } 77 | 78 | 79 | /** 80 | * 获取目录大小,单位[字节] 81 | * @param string $path 82 | * @return int 83 | */ 84 | public static function getDirSize(string $path): int { 85 | $size = 0; 86 | if ($path == '' || !is_dir($path)) { 87 | return $size; 88 | } 89 | 90 | $dh = @opendir($path); //比dir($path)快 91 | while (false != ($file = @readdir($dh))) { 92 | if ($file != '.' and $file != '..') { 93 | $fielpath = $path . DIRECTORY_SEPARATOR . $file; 94 | if (is_dir($fielpath)) { 95 | $size += self::getDirSize($fielpath); 96 | } else { 97 | $size += filesize($fielpath); 98 | } 99 | } 100 | } 101 | @closedir($dh); 102 | return $size; 103 | } 104 | 105 | 106 | /** 107 | * 拷贝目录 108 | * @param string $from 源目录 109 | * @param string $dest 目标目录 110 | * @param bool $cover 是否覆盖已存在的文件 111 | * @return bool 112 | */ 113 | public static function copyDir(string $from, string $dest, bool $cover = false): bool { 114 | if (!file_exists($dest) && !@mkdir($dest, 0766, true)) { 115 | return false; 116 | } 117 | 118 | $dh = @opendir($from); 119 | while (false !== ($fileName = @readdir($dh))) { 120 | if (($fileName != ".") && ($fileName != "..")) { 121 | $newFile = "$dest/$fileName"; 122 | if (!is_dir("$from/$fileName")) { 123 | if (file_exists($newFile) && !$cover) { 124 | continue; 125 | } elseif (!copy("$from/$fileName", $newFile)) { 126 | return false; 127 | } 128 | } else { 129 | self::copyDir("$from/$fileName", $newFile, $cover); 130 | } 131 | } 132 | } 133 | @closedir($dh); 134 | 135 | return true; 136 | } 137 | 138 | 139 | /** 140 | * 批量改变目录模式(包括子目录和所属文件) 141 | * @param string $path 路径 142 | * @param int $filemode 文件模式 143 | * @param int $dirmode 目录模式 144 | */ 145 | public static function chmodBatch(string $path, int $filemode = 0766, int $dirmode = 0766): void { 146 | if ($path == '') { 147 | return; 148 | } 149 | 150 | if (is_dir($path)) { 151 | if (!@chmod($path, $dirmode)) { 152 | return; 153 | } 154 | $dh = @opendir($path); 155 | while (($file = @readdir($dh)) !== false) { 156 | if ($file != '.' && $file != '..') { 157 | $fullpath = $path . '/' . $file; 158 | self::chmodBatch($fullpath, $filemode, $dirmode); 159 | } 160 | } 161 | @closedir($dh); 162 | } elseif (!is_link($path)) { 163 | @chmod($path, $filemode); 164 | } 165 | } 166 | 167 | 168 | /** 169 | * 删除目录(目录下所有文件,包括本目录) 170 | * @param string $path 171 | * @return bool 172 | */ 173 | public static function delDir(string $path): bool { 174 | if (is_dir($path) && $dh = @opendir($path)) { 175 | while (false != ($file = @readdir($dh))) { 176 | if ($file != '.' && $file != '..') { 177 | $fielpath = $path . DIRECTORY_SEPARATOR . $file; 178 | if (is_dir($fielpath)) { 179 | self::delDir($fielpath); 180 | } else { 181 | @unlink($fielpath); 182 | } 183 | } 184 | } 185 | @closedir($dh); 186 | return @rmdir($path); 187 | } 188 | return false; 189 | } 190 | 191 | 192 | /** 193 | * 清空目录(删除目录下所有文件,仅保留当前目录) 194 | * @param string $path 195 | * @return bool 196 | */ 197 | public static function clearDir(string $path): bool { 198 | if ($path == '' || !is_dir($path)) { 199 | return false; 200 | } 201 | 202 | $dirs = []; 203 | $dir = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS); 204 | $iterator = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::CHILD_FIRST); 205 | 206 | foreach ($iterator as $single => $file) { 207 | $fpath = $file->getRealPath(); 208 | if ($file->isDir()) { 209 | array_push($dirs, $fpath); 210 | } else { 211 | //先删除文件 212 | @unlink($fpath); 213 | } 214 | } 215 | 216 | //再删除目录 217 | rsort($dirs); 218 | foreach ($dirs as $dir) { 219 | @rmdir($dir); 220 | } 221 | 222 | unset($dir, $iterator, $dirs); 223 | return true; 224 | } 225 | 226 | 227 | /** 228 | * 格式化路径字符串(路径后面加/) 229 | * @param string $dir 230 | * @return string 231 | */ 232 | public static function formatDir(string $dir): string { 233 | if ($dir == '') { 234 | return ''; 235 | } 236 | 237 | $old = [ 238 | '\\', 239 | '|', 240 | '<', 241 | '>', 242 | '?', 243 | ]; 244 | $replace = [ 245 | '/', 246 | '', 247 | '', 248 | '', 249 | '', 250 | ]; 251 | 252 | $dir = str_replace($old, $replace, $dir); 253 | $dir = preg_replace(RegularHelper::$patternDoubleSlash, '/', $dir); 254 | 255 | //是否有":" 256 | if (StringHelper::contains($dir, ':')) { 257 | $arr = explode('/', $dir); 258 | $first = array_shift($arr); 259 | 260 | //win下的路径,如C:\Users\Administrator 261 | if (!ValidateHelper::endsWith($first, ':')) { 262 | $first = str_replace(':', '', $first); 263 | } 264 | 265 | $last = implode('/', $arr); 266 | $last = str_replace(':', '', $last); 267 | $dir = "{$first}/{$last}"; 268 | } 269 | 270 | return rtrim($dir, ' / ') . '/'; 271 | } 272 | 273 | 274 | } -------------------------------------------------------------------------------- /src/Helpers/EncryptHelper.php: -------------------------------------------------------------------------------- 1 | 0) { 123 | return [substr($res, 26), $expTime]; 124 | } 125 | 126 | return ['', $expTime]; 127 | } 128 | } 129 | 130 | 131 | /** 132 | * 简单加密 133 | * @param string $data 数据 134 | * @param string $key 密钥 135 | * @return string 136 | */ 137 | public static function easyEncrypt(string $data, string $key): string { 138 | if ($data == '') { 139 | return ''; 140 | } 141 | 142 | $key = md5($key); 143 | $dataLen = strlen($data); 144 | $keyLen = strlen($key); 145 | $x = 0; 146 | $str = $char = ''; 147 | for ($i = 0; $i < $dataLen; $i++) { 148 | if ($x == $keyLen) { 149 | $x = 0; 150 | } 151 | 152 | $str .= chr(ord($data[$i]) + (ord($key[$x])) % 256); 153 | $x++; 154 | } 155 | 156 | return substr($key, 0, Consts::DYNAMIC_KEY_LEN) . self::base64UrlEncode($str); 157 | } 158 | 159 | 160 | /** 161 | * 简单解密 162 | * @param string $data 数据 163 | * @param string $key 密钥 164 | * @return string 165 | */ 166 | public static function easyDecrypt(string $data, string $key): string { 167 | if (strlen($data) < Consts::DYNAMIC_KEY_LEN) { 168 | return ''; 169 | } 170 | 171 | $key = md5($key); 172 | if (substr($key, 0, Consts::DYNAMIC_KEY_LEN) != substr($data, 0, Consts::DYNAMIC_KEY_LEN)) { 173 | return ''; 174 | } 175 | 176 | $data = self::base64UrlDecode(substr($data, Consts::DYNAMIC_KEY_LEN)); 177 | if (empty($data)) { 178 | return ''; 179 | } 180 | 181 | $dataLen = strlen($data); 182 | $keyLen = strlen($key); 183 | $x = 0; 184 | $str = $char = ''; 185 | for ($i = 0; $i < $dataLen; $i++) { 186 | if ($x == $keyLen) { 187 | $x = 0; 188 | } 189 | 190 | $c = ord($data[$i]); 191 | $k = ord($key[$x]); 192 | if ($c < $k) { 193 | $str .= chr(($c + 256) - $k); 194 | } else { 195 | $str .= chr($c - $k); 196 | } 197 | 198 | $x++; 199 | } 200 | 201 | return $str; 202 | } 203 | 204 | 205 | /** 206 | * MurmurHash3算法函数 207 | * @param string $data 要哈希的数据 208 | * @param int $seed 随机种子(仅素数) 209 | * @param bool $unsign 是否返回无符号值;为true时返回11位无符号整数,为false时返回10位有符号整数 210 | * @return float|int 211 | */ 212 | public static function murmurhash3Int(string $data, int $seed = 3, bool $unsign = true) { 213 | $key = array_values(unpack('C*', $data)); 214 | $klen = count($key); 215 | $h1 = abs($seed); 216 | for ($i = 0, $bytes = $klen - ($remainder = $klen & 3); $i < $bytes;) { 217 | $k1 = $key[$i] | ($key[++$i] << 8) | ($key[++$i] << 16) | ($key[++$i] << 24); 218 | ++$i; 219 | $k1 = (((($k1 & 0xffff) * 0xcc9e2d51) + ((((($k1 >= 0 ? $k1 >> 16 : (($k1 & 0x7fffffff) >> 16) | 0x8000)) * 0xcc9e2d51) & 0xffff) << 16))) & 0xffffffff; 220 | $k1 = $k1 << 15 | ($k1 >= 0 ? $k1 >> 17 : (($k1 & 0x7fffffff) >> 17) | 0x4000); 221 | $k1 = (((($k1 & 0xffff) * 0x1b873593) + ((((($k1 >= 0 ? $k1 >> 16 : (($k1 & 0x7fffffff) >> 16) | 0x8000)) * 0x1b873593) & 0xffff) << 16))) & 0xffffffff; 222 | $h1 ^= $k1; 223 | $h1 = $h1 << 13 | ($h1 >= 0 ? $h1 >> 19 : (($h1 & 0x7fffffff) >> 19) | 0x1000); 224 | $h1b = (((($h1 & 0xffff) * 5) + ((((($h1 >= 0 ? $h1 >> 16 : (($h1 & 0x7fffffff) >> 16) | 0x8000)) * 5) & 0xffff) << 16))) & 0xffffffff; 225 | $h1 = ((($h1b & 0xffff) + 0x6b64) + ((((($h1b >= 0 ? $h1b >> 16 : (($h1b & 0x7fffffff) >> 16) | 0x8000)) + 0xe654) & 0xffff) << 16)); 226 | } 227 | $k1 = 0; 228 | switch ($remainder) { 229 | case 3: 230 | $k1 ^= $key[$i + 2] << 16; 231 | case 2: 232 | $k1 ^= $key[$i + 1] << 8; 233 | case 1: 234 | $k1 ^= $key[$i]; 235 | $k1 = ((($k1 & 0xffff) * 0xcc9e2d51) + ((((($k1 >= 0 ? $k1 >> 16 : (($k1 & 0x7fffffff) >> 16) | 0x8000)) * 0xcc9e2d51) & 0xffff) << 16)) & 0xffffffff; 236 | $k1 = $k1 << 15 | ($k1 >= 0 ? $k1 >> 17 : (($k1 & 0x7fffffff) >> 17) | 0x4000); 237 | $k1 = ((($k1 & 0xffff) * 0x1b873593) + ((((($k1 >= 0 ? $k1 >> 16 : (($k1 & 0x7fffffff) >> 16) | 0x8000)) * 0x1b873593) & 0xffff) << 16)) & 0xffffffff; 238 | $h1 ^= $k1; 239 | } 240 | $h1 ^= $klen; 241 | $h1 ^= ($h1 >= 0 ? $h1 >> 16 : (($h1 & 0x7fffffff) >> 16) | 0x8000); 242 | $h1 = ((($h1 & 0xffff) * 0x85ebca6b) + ((((($h1 >= 0 ? $h1 >> 16 : (($h1 & 0x7fffffff) >> 16) | 0x8000)) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff; 243 | $h1 ^= ($h1 >= 0 ? $h1 >> 13 : (($h1 & 0x7fffffff) >> 13) | 0x40000); 244 | $h1 = (((($h1 & 0xffff) * 0xc2b2ae35) + ((((($h1 >= 0 ? $h1 >> 16 : (($h1 & 0x7fffffff) >> 16) | 0x8000)) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff; 245 | $h1 ^= ($h1 >= 0 ? $h1 >> 16 : (($h1 & 0x7fffffff) >> 16) | 0x8000); 246 | 247 | if ($unsign) { 248 | $h1 = ($h1 >= 0) ? bcadd('1' . str_repeat('0', 10), $h1) : abs($h1); 249 | } 250 | 251 | return $h1; 252 | } 253 | 254 | 255 | /** 256 | * openssl加密 257 | * @param string $str 明文 258 | * @param string $key 密钥 259 | * @param string $iv 初始化向量 260 | * @param string $method 密码学方式 261 | * @return string 262 | * @throws Exception 263 | */ 264 | public static function opensslEncrypt(string $str, string $key, string $iv = '', string $method = 'AES-128-CBC'): string { 265 | if (!extension_loaded('openssl')) { 266 | throw new Exception("You need to install OpenSSL module."); 267 | } 268 | 269 | if ($str == '') { 270 | return ''; 271 | } 272 | if ($key == '') { 273 | $key = md5($key); 274 | } 275 | 276 | $hx = hash('sha256', $iv); 277 | $ivLen = openssl_cipher_iv_length($method); 278 | $ivStr = substr($hx, 0, $ivLen); 279 | $res = openssl_encrypt($str, $method, $key, 0, $ivStr); 280 | 281 | return $res ? base64_encode($res) : ''; 282 | } 283 | 284 | 285 | /** 286 | * openssl解密 287 | * @param string $str 密文 288 | * @param string $key 密钥 289 | * @param string $iv 初始化向量 290 | * @param string $method 密码学方式 291 | * @return string 292 | * @throws Exception 293 | */ 294 | public static function opensslDecrypt(string $str, string $key, string $iv = '', string $method = 'AES-128-CBC'): string { 295 | if (!extension_loaded('openssl')) { 296 | throw new Exception("You need to install OpenSSL module."); 297 | } 298 | 299 | if ($str == '') { 300 | return ''; 301 | } 302 | if ($key == '') { 303 | $key = md5($key); 304 | } 305 | 306 | $str = base64_decode($str); 307 | $hx = hash('sha256', $iv); 308 | $ivLen = openssl_cipher_iv_length($method); 309 | $ivStr = substr($hx, 0, $ivLen); 310 | $res = openssl_decrypt($str, $method, $key, 0, $ivStr); 311 | 312 | return $res ? $res : ''; 313 | } 314 | 315 | 316 | } -------------------------------------------------------------------------------- /src/Helpers/NumberHelper.php: -------------------------------------------------------------------------------- 1 | = 1024 && $i < 5; $i++) { 32 | $size /= 1024; 33 | } 34 | 35 | return round($size, $dec) . $delimiter . ($units[$i] ?? Consts::UNKNOWN); 36 | } 37 | 38 | 39 | /** 40 | * 值是否在某范围内 41 | * @param int|float $val 值 42 | * @param int|float $min 小值 43 | * @param int|float $max 大值 44 | * @return bool 45 | */ 46 | public static function inRange($val, $min, $max): bool { 47 | $val = floatval($val); 48 | $min = floatval($min); 49 | $max = floatval($max); 50 | return $val >= $min && $val <= $max; 51 | } 52 | 53 | 54 | /** 55 | * 对数列求和,忽略非数值. 56 | * @param mixed ...$vals 57 | * @return float 58 | */ 59 | public static function sum(...$vals): float { 60 | $res = 0; 61 | foreach ($vals as $val) { 62 | if (is_numeric($val)) { 63 | $res += floatval($val); 64 | } 65 | } 66 | 67 | return $res; 68 | } 69 | 70 | 71 | /** 72 | * 对数列求平均值,忽略非数值. 73 | * @param mixed ...$vals 74 | * @return float 75 | */ 76 | public static function average(...$vals): float { 77 | $res = 0; 78 | $count = 0; 79 | $total = 0; 80 | foreach ($vals as $val) { 81 | if (is_numeric($val)) { 82 | $total += floatval($val); 83 | $count++; 84 | } 85 | } 86 | 87 | if ($count > 0) { 88 | $res = $total / $count; 89 | } 90 | 91 | return $res; 92 | } 93 | 94 | 95 | /** 96 | * 获取地理距离/米. 97 | * 参数分别为两点的经度和纬度.lat:-90~90,lng:-180~180. 98 | * @param float $lng1 起点经度 99 | * @param float $lat1 起点纬度 100 | * @param float $lng2 终点经度 101 | * @param float $lat2 终点纬度 102 | * @return float 103 | */ 104 | public static function geoDistance(float $lng1 = 0, float $lat1 = 0, float $lng2 = 0, float $lat2 = 0): float { 105 | $earthRadius = 6371000.0; 106 | $lat1 = ($lat1 * pi()) / 180; 107 | $lng1 = ($lng1 * pi()) / 180; 108 | $lat2 = ($lat2 * pi()) / 180; 109 | $lng2 = ($lng2 * pi()) / 180; 110 | 111 | $calcLongitude = $lng2 - $lng1; 112 | $calcLatitude = $lat2 - $lat1; 113 | $stepOne = pow(sin($calcLatitude / 2), 2) + cos($lat1) * cos($lat2) * pow(sin($calcLongitude / 2), 2); 114 | $stepTwo = 2 * asin(min(1, sqrt($stepOne))); 115 | $res = $earthRadius * $stepTwo; 116 | 117 | return $res; 118 | } 119 | 120 | 121 | /** 122 | * 数值格式化(会四舍五入) 123 | * @param float|int|string $number 要格式化的数字 124 | * @param int $decimals 小数位数 125 | * @return string 126 | */ 127 | public static function numberFormat($number, int $decimals = 2): string { 128 | return number_format(floatval($number), $decimals, '.', ''); 129 | } 130 | 131 | 132 | /** 133 | * 数值截取(不会四舍五入) 134 | * @param float|int|string $number 要格式化的数字 135 | * @param int $decimals 小数位数 136 | * @return float 137 | */ 138 | public static function numberSub($number, int $decimals = 2): float { 139 | if ($decimals == 0 && ValidateHelper::isInteger($number)) { 140 | return floatval($number); 141 | } 142 | 143 | return intval(floatval($number) * pow(10, $decimals)) / pow(10, $decimals); 144 | } 145 | 146 | 147 | /** 148 | * 生成随机浮点数 149 | * @param float $min 小值 150 | * @param float $max 大值 151 | * @return float 152 | */ 153 | public static function randFloat(float $min = 0, float $max = 1): float { 154 | return $min + mt_rand() / mt_getrandmax() * ($max - $min); 155 | } 156 | 157 | 158 | /** 159 | * 将金额转为大写人民币 160 | * @param float $num 金额,元(最大支持千亿) 161 | * @param int $decimals 精确小数位数(最大支持为3,即厘) 162 | * @return string 163 | * @throws BaseException 164 | */ 165 | public static function money2Yuan(float $num, int $decimals = 0): string { 166 | $int = intval($num); 167 | if (strlen($int) > 12) { 168 | throw new BaseException('The maximum value supports 12 bits!'); 169 | } 170 | 171 | $uppers = '零壹贰叁肆伍陆柒捌玖'; 172 | $units = '元拾佰仟万拾佰仟亿拾佰仟'; 173 | 174 | if ($decimals > 0) { 175 | $decimals = min($decimals, 3); 176 | $adds = ['角', '分', '厘']; 177 | $num = $num * pow(10, $decimals); 178 | 179 | for ($i = 0; $i < $decimals; $i++) { 180 | $units = $adds[$i] . $units; 181 | } 182 | } 183 | 184 | $res = ''; 185 | $i = 0; 186 | while (true) { 187 | if ($i == 0) { 188 | $n = substr($num, strlen($num) - 1, 1); 189 | } else { 190 | $n = $num % 10; 191 | } 192 | $p1 = substr($uppers, 3 * $n, 3); 193 | $p2 = substr($units, 3 * $i, 3); 194 | 195 | if ($n != '0' || ($n == '0' && ($p2 == '亿' || $p2 == '万' || $p2 == '元'))) { 196 | $res = $p1 . $p2 . $res; 197 | } else { 198 | $res = $p1 . $res; 199 | } 200 | 201 | $i = $i + 1; 202 | $num = $num / 10; 203 | $num = (int)$num; 204 | if ($num == 0) { 205 | break; 206 | } 207 | } 208 | 209 | $j = 0; 210 | $len = strlen($res); 211 | while ($j < $len) { 212 | $m = substr($res, $j, 6); 213 | if ($m == '零元' || $m == '零万' || $m == '零亿' || $m == '零零') { 214 | $left = substr($res, 0, $j); 215 | $right = substr($res, $j + 3); 216 | $res = $left . $right; 217 | $j = $j - 3; 218 | $len = $len - 3; 219 | } 220 | $j = $j + 3; 221 | } 222 | 223 | if (substr($res, strlen($res) - 3, 3) == '零') { 224 | $res = substr($res, 0, strlen($res) - 3); 225 | } 226 | 227 | if (empty($res)) { 228 | return "零元整"; 229 | } else { 230 | $res .= "整"; 231 | } 232 | 233 | return $res; 234 | } 235 | 236 | 237 | /** 238 | * 求以 $base 为底 $num 的对数临近值 239 | * @param mixed $num 非负数 240 | * @param int $base 底数 241 | * @param bool $left 是否向左取整 242 | * @return int 243 | * @throws BaseException 244 | */ 245 | public static function nearLogarithm($num, int $base = 2, bool $left = true): int { 246 | if (!is_numeric($num) || $num < 0) { 247 | throw new BaseException('The $num must be non-negative!'); 248 | } elseif ($base <= 0) { 249 | throw new BaseException('The $base must be a positive integer!'); 250 | } 251 | 252 | $log = log($num, $base); 253 | 254 | return $left ? intval($log) : intval(ceil($log)); 255 | } 256 | 257 | 258 | /** 259 | * 将自然数按底数进行拆解 260 | * @param int $num 自然数 261 | * @param int $base 底数 262 | * @return array 263 | * @throws BaseException 264 | */ 265 | public static function splitNaturalNum(int $num, int $base): array { 266 | if (!ValidateHelper::isNaturalNum($num)) { 267 | throw new BaseException('The $num must be a natural number!'); 268 | } elseif ($base <= 0) { 269 | throw new BaseException('The $base must be a positive integer!'); 270 | } 271 | 272 | $res = []; 273 | while ($num > $base) { 274 | $n = self::nearLogarithm($num, $base, true); 275 | $child = pow($base, $n); 276 | $num -= $child; 277 | array_push($res, $child); 278 | } 279 | 280 | if ($num > 0 || ($num == 0 && empty($res))) { 281 | array_push($res, $num); 282 | } 283 | 284 | return $res; 285 | } 286 | 287 | 288 | } -------------------------------------------------------------------------------- /src/Helpers/RegularHelper.php: -------------------------------------------------------------------------------- 1 | '/\(([^\(]*?)\)/is', 277 | '2' => '/\[([^\[]*?)\]/is', 278 | '4' => '/\{([^\{]*?)\}/is', 279 | '8' => '/\<([^\<]*?)\>/is', 280 | '16' => '/(([^(]*?))/u', 281 | '32' => '/【([^【]*?)】/u', 282 | '64' => '/《([^《]*?)》/u', 283 | ]; 284 | 285 | 286 | } -------------------------------------------------------------------------------- /src/Helpers/UrlHelper.php: -------------------------------------------------------------------------------- 1 | $val) { 31 | $url = str_replace($val, urlencode($val), $url); //将转译替换中文 32 | } 33 | if (strpos($url, ' ')) {//若存在空格 34 | $url = str_replace(' ', '%20', $url); 35 | } 36 | } 37 | return $url; 38 | } 39 | 40 | 41 | /** 42 | * 中文urldecode 43 | * @param string $url 44 | * @return string 45 | */ 46 | public static function cnUrldecode(string $url): string { 47 | $res = ""; 48 | $pos = 0; 49 | $len = strlen($url); 50 | while ($pos < $len) { 51 | $charAt = substr($url, $pos, 1); 52 | if ($charAt == '%') { 53 | $pos++; 54 | $charAt = substr($url, $pos, 1); 55 | if ($charAt == 'u') { 56 | // we got a unicode character 57 | $pos++; 58 | $unicodeHexVal = substr($url, $pos, 4); 59 | $unicode = hexdec($unicodeHexVal); 60 | $entity = "&#" . $unicode . ';'; 61 | $res .= utf8_encode($entity); 62 | $pos += 4; 63 | } else { 64 | // we have an escaped ascii character 65 | $hexVal = substr($url, $pos, 2); 66 | $res .= chr(hexdec($hexVal)); 67 | $pos += 2; 68 | } 69 | } else { 70 | $res .= $charAt; 71 | $pos++; 72 | } 73 | } 74 | return $res; 75 | } 76 | 77 | 78 | /** 79 | * 根据键值对数组,组建uri(带?的url参数串). 80 | * 若$replaceKeys非空,而$replaceVals为空时,则是删除$replaceKeys包含的键(参数名). 81 | * @param array $params 参数数组,最多二维 82 | * @param array $replaceKeys 要替换的键 83 | * @param array $replaceVals 要替换的值 84 | * @return string 85 | */ 86 | public static function buildUriParams(array $params, array $replaceKeys = [], array $replaceVals = []) { 87 | foreach ($replaceKeys as $key) { 88 | unset($params[$key]); 89 | } 90 | 91 | $res = ''; 92 | foreach ($params as $k => $v) { 93 | if (is_array($v)) { 94 | foreach ($v as $k2 => $v2) { 95 | if (is_int($k2)) { 96 | $k2 = ''; 97 | } 98 | $res .= "&{$k}[{$k2}]={$v2}"; 99 | } 100 | } else { 101 | $res .= "&{$k}={$v}"; 102 | } 103 | } 104 | 105 | if (!empty($replaceVals)) { 106 | foreach ($replaceVals as $k => $val) { 107 | $key = $replaceKeys[$k] ?? ''; 108 | if (!empty($key)) { 109 | $res .= "&{$key}={$val}"; 110 | } 111 | } 112 | } 113 | 114 | $res[0] = '?'; 115 | return $res; 116 | } 117 | 118 | 119 | /** 120 | * 格式化URL(替换重复的//) 121 | * @param string $url 122 | * @return string 123 | */ 124 | public static function formatUrl(string $url): string { 125 | if (!stripos($url, '://')) { 126 | $url = 'http://' . $url; 127 | } 128 | $url = str_replace("\\", "/", $url); 129 | return preg_replace('/([^:])[\/]{2,}/', '$1/', $url); 130 | } 131 | 132 | 133 | /** 134 | * 检查URL是否正常存在 135 | * @param string $url 136 | * @return bool 137 | */ 138 | public static function checkUrlExists(string $url): bool { 139 | if (empty($url)) { 140 | return false; 141 | } 142 | 143 | if (!stripos($url, '://')) { 144 | $url = 'http://' . $url; 145 | } 146 | if (!ValidateHelper::isUrl($url)) { 147 | return false; 148 | } 149 | 150 | $header = @get_headers($url, true); 151 | 152 | return isset($header[0]) && (strpos($header[0], '200') || strpos($header[0], '304')); 153 | } 154 | 155 | 156 | /** 157 | * 将URL转换为链接标签 158 | * @param string $url 含URL的字符串 159 | * @param array $protocols 要转换的协议, http/https, ftp/ftps, mail 160 | * @param string $target 是否新页面打开:_blank,_self,默认为空 161 | * @return string 162 | */ 163 | public static function url2Link(string $url, array $protocols = ['http', 'https'], string $target = ''): string { 164 | if (!empty($url)) { 165 | if (!empty(array_intersect($protocols, ['http', 'https']))) { 166 | $pattern = '@(http(s)?)?(://)?(([a-zA-Z])([-\w]+\.)+([^\s\.]+[^\s]*)+[^,.\s])@i'; 167 | $url = preg_replace($pattern, "$0", $url); 168 | } 169 | 170 | if (!empty(array_intersect($protocols, ['ftp', 'ftps']))) { 171 | $pattern = '/(ftp|ftps)\:\/\/[-a-zA-Z0-9@:%_+.~#?&\/=]+(\/\S*)?/i'; 172 | $url = preg_replace($pattern, "$0", $url); 173 | } 174 | 175 | if (in_array('mail', $protocols)) { 176 | $pattern = '/([^\s<]+?@[^\s<]+?\.[^\s<]+)(?$0", $url); 178 | } 179 | } 180 | 181 | return $url; 182 | } 183 | 184 | 185 | /** 186 | * 获取域名 187 | * @param string $url 188 | * @param bool $firstLevel 是否获取一级域名,如:abc.test.com取test.com 189 | * @param array $server server信息 190 | * @return string 191 | */ 192 | public static function getDomain(string $url, bool $firstLevel = false, array $server = []): string { 193 | if (empty($server)) { 194 | $server = $_SERVER; 195 | } 196 | if (empty($url)) { 197 | $url = $server['HTTP_HOST'] ?? ''; 198 | } 199 | 200 | if (!stripos($url, '://')) { 201 | $url = 'http://' . $url; 202 | } 203 | 204 | $parse = parse_url(strtolower($url)); 205 | $domain = ''; 206 | if (isset($parse['host'])) { 207 | $domain = $parse['host']; 208 | } 209 | 210 | if ($firstLevel) { 211 | $arr = explode('.', $domain); 212 | $size = count($arr); 213 | if ($size >= 2) { 214 | $domain = $arr[$size - 2] . '.' . end($arr); 215 | } 216 | } 217 | 218 | return $domain; 219 | } 220 | 221 | 222 | /** 223 | * 获取当前页面完整URL地址 224 | * @param array $server server信息 225 | * @return string 226 | */ 227 | public static function getUrl(array $server = []): string { 228 | if (empty($server)) { 229 | $server = $_SERVER; 230 | } 231 | 232 | $host = $server['HTTP_HOST'] ?? ''; 233 | $uri = $server['REQUEST_URI'] ?? ''; 234 | 235 | $scheme = $server['HTTP_X_FORWARDED_PROTO'] ?? ''; 236 | if (empty($scheme) && isset($server['HTTP_CF_VISITOR'])) { 237 | $obj = json_decode(strval($server['HTTP_CF_VISITOR']), true); 238 | if ($obj && is_object($obj)) { 239 | $scheme = $obj->scheme ?? ''; 240 | } 241 | } 242 | if (empty($scheme)) { 243 | $scheme = $server['REQUEST_SCHEME'] ?? 'http'; 244 | } 245 | 246 | if (empty($host)) { 247 | return ''; 248 | } 249 | 250 | $res = "{$scheme}://{$host}{$uri}"; 251 | 252 | return $res; 253 | } 254 | 255 | 256 | /** 257 | * 获取URI 258 | * @param array $server 259 | * @return string 260 | */ 261 | public static function getUri(array $server = []): string { 262 | if (empty($server)) { 263 | $server = $_SERVER; 264 | } 265 | 266 | if (isset($server['REQUEST_URI'])) { 267 | return $server['REQUEST_URI']; 268 | } 269 | 270 | $uri = ($server['PHP_SELF'] ?? '') . "?" . ($server['QUERY_STRING'] ?? ($server['argv'][0] ?? '')); 271 | return $uri; 272 | } 273 | 274 | 275 | /** 276 | * 获取站点URL 277 | * @param string $url 网址 278 | * @param array $server server信息 279 | * @return string 280 | */ 281 | public static function getSiteUrl(string $url, array $server = []): string { 282 | if (empty($url)) { 283 | $url = self::getUrl($server); 284 | } 285 | 286 | if (!stripos($url, '://')) { 287 | $url = 'http://' . $url; 288 | } 289 | 290 | if (!ValidateHelper::isUrl($url)) { 291 | return ''; 292 | } 293 | 294 | $arr = parse_url($url); 295 | $port = $arr['port'] ?? null; 296 | $scheme = $arr['scheme'] ?? null; 297 | if (!empty($port) && !in_array($port, [80, 443])) { 298 | $url = "{$scheme}://{$arr['host']}:{$port}/"; 299 | } else { 300 | $url = "{$scheme}://{$arr['host']}/"; 301 | } 302 | 303 | $url = strtolower($url); 304 | 305 | return self::formatUrl($url); 306 | } 307 | 308 | } -------------------------------------------------------------------------------- /src/Interfaces/Arrayable.php: -------------------------------------------------------------------------------- 1 | __datas = $datas; 51 | } 52 | } 53 | 54 | 55 | /** 56 | * 魔术get 57 | * @param $key 58 | * @return mixed|null 59 | */ 60 | public function __get($key) { 61 | return $this->__datas[$key] ?? null; 62 | } 63 | 64 | 65 | /** 66 | * 魔术set 67 | * @param $key 68 | * @param $value 69 | */ 70 | public function __set($key, $value) { 71 | $this->__datas[$key] = $value; 72 | } 73 | 74 | 75 | /** 76 | * 获取键值 77 | * @param $key 78 | * @return mixed|null 79 | */ 80 | public function get($key) { 81 | return $this->__datas[$key] ?? null; 82 | } 83 | 84 | 85 | /** 86 | * 设置键值 87 | * @param $key 88 | * @param $value 89 | */ 90 | public function set($key, $value): void { 91 | $this->__datas[$key] = $value; 92 | } 93 | 94 | 95 | /** 96 | * 键是否存在 97 | * @param mixed $offset 98 | * @return bool 99 | */ 100 | public function offsetExists($offset): bool { 101 | return isset($this->__datas[$offset]); 102 | } 103 | 104 | 105 | /** 106 | * 获取键值 107 | * @param mixed $offset 108 | * @return mixed|null 109 | */ 110 | public function offsetGet($offset) { 111 | return $this->__datas[$offset] ?? null; 112 | } 113 | 114 | 115 | /** 116 | * 设置键值 117 | * @param mixed $offset 118 | * @param mixed $value 119 | */ 120 | public function offsetSet($offset, $value): void { 121 | $this->__datas[$offset] = $value; 122 | } 123 | 124 | 125 | /** 126 | * 删除键 127 | * @param mixed $offset 128 | */ 129 | public function offsetUnset($offset): void { 130 | unset($this->__datas[$offset]); 131 | } 132 | 133 | 134 | /** 135 | * json序列化 136 | * @return array 137 | */ 138 | public function jsonSerialize(): array { 139 | return $this->__datas; 140 | } 141 | 142 | 143 | /** 144 | * 序列化 145 | * @return string 146 | */ 147 | public function serialize(): string { 148 | return serialize($this->__datas); 149 | } 150 | 151 | 152 | /** 153 | * 反序列化 154 | * @param string $serialized 155 | */ 156 | public function unserialize($serialized): void { 157 | $this->__datas = unserialize($serialized); 158 | } 159 | 160 | 161 | /** 162 | * 计算数量 163 | * @return int 164 | */ 165 | public function count(): int { 166 | return count($this->__datas); 167 | } 168 | 169 | 170 | /** 171 | * 当前元素 172 | * @return mixed 173 | */ 174 | public function current() { 175 | return current($this->__datas); 176 | } 177 | 178 | 179 | /** 180 | * 下一个元素 181 | * @return mixed|void 182 | */ 183 | public function next() { 184 | $this->__index++; 185 | return next($this->__datas); 186 | } 187 | 188 | 189 | /** 190 | * 获取当前元素的键 191 | * @return int|mixed|string|null 192 | */ 193 | public function key() { 194 | return key($this->__datas); 195 | } 196 | 197 | 198 | /** 199 | * 验证当前位置 200 | * @return bool 201 | */ 202 | public function valid(): bool { 203 | return count($this->__datas) >= $this->__index; 204 | } 205 | 206 | 207 | /** 208 | * 重置迭代器 209 | * @return mixed|void 210 | */ 211 | public function rewind() { 212 | $this->__index = 0; 213 | return reset($this->__datas); 214 | } 215 | 216 | 217 | /** 218 | * 返回数组 219 | * @return array 220 | */ 221 | public function toArray(): array { 222 | return $this->__datas; 223 | } 224 | 225 | 226 | /** 227 | * 返回json串 228 | * @param int $options 229 | * @param int $depth 230 | * @return mixed|string 231 | */ 232 | public function toJson(int $options = 0, int $depth = 512): string { 233 | return json_encode($this->__datas, $options, $depth); 234 | } 235 | 236 | 237 | /** 238 | * 查找元素 239 | * @param $needle 240 | * @param bool $strict 241 | * @return false|int|string 242 | */ 243 | public function search($needle, $strict = false) { 244 | return array_search($needle, $this->__datas, $strict); 245 | } 246 | 247 | 248 | /** 249 | * 元素位置 250 | * @param $needle 251 | * @return false|int|string 252 | */ 253 | public function indexOf($needle) { 254 | return $this->search($needle); 255 | } 256 | 257 | 258 | /** 259 | * 元素最后出现位置 260 | * @param $needle 261 | * @return bool|int|string 262 | */ 263 | public function lastIndexOf($needle) { 264 | $res = false; 265 | foreach ($this->__datas as $k => $it) { 266 | if ($needle == $it) { 267 | $res = $k; 268 | } 269 | } 270 | 271 | return $res; 272 | } 273 | 274 | 275 | /** 276 | * 删除元素(根据键) 277 | * @param $key 278 | * @return bool 279 | */ 280 | public function delete($key): bool { 281 | $res = false; 282 | if (isset($this->__datas[$key])) { 283 | unset($this->__datas[$key]); 284 | $res = true; 285 | } 286 | 287 | return $res; 288 | } 289 | 290 | 291 | /** 292 | * 移除元素(根据值) 293 | * @param $value 294 | * @return ArrayObject 295 | */ 296 | public function remove($value): ArrayObject { 297 | $key = $this->search($value); 298 | if ($key !== false) { 299 | unset($this->__datas[$key]); 300 | } 301 | 302 | return $this; 303 | } 304 | 305 | 306 | /** 307 | * 清空 308 | */ 309 | public function clear(): void { 310 | $this->__datas = []; 311 | } 312 | 313 | 314 | /** 315 | * 是否包含值 316 | * @param $val 317 | * @return bool 318 | */ 319 | public function contains($val): bool { 320 | return in_array($val, $this->__datas); 321 | } 322 | 323 | 324 | /** 325 | * 是否存在键 326 | * @param $key 327 | * @return bool 328 | */ 329 | public function exists($key): bool { 330 | return array_key_exists($key, $this->__datas); 331 | } 332 | 333 | 334 | /** 335 | * 连接 336 | * @param string $str 337 | * @return string 338 | */ 339 | public function join($str = ''): string { 340 | return implode($str, $this->__datas); 341 | } 342 | 343 | 344 | /** 345 | * 插入元素 346 | * @param int $offset 位置 347 | * @param mixed $val 元素值 348 | * @return bool 349 | */ 350 | public function insert($offset, $val): bool { 351 | if ($offset > $this->count()) { 352 | return false; 353 | } 354 | 355 | array_splice($this->__datas, $offset, 0, $val); 356 | 357 | return true; 358 | } 359 | 360 | 361 | /** 362 | * 是否为空 363 | * @return bool 364 | */ 365 | public function isEmpty(): bool { 366 | return empty($this->__datas); 367 | } 368 | 369 | 370 | /** 371 | * 求和 372 | * @return float|int 373 | */ 374 | public function sum() { 375 | return array_sum($this->__datas); 376 | } 377 | 378 | 379 | /** 380 | * 求乘积 381 | * @return float|int 382 | */ 383 | public function product() { 384 | return array_product($this->__datas); 385 | } 386 | 387 | 388 | /** 389 | * 用回调函数迭代地将数组简化为单一的值 390 | * @param callable $fn 391 | * @return mixed 392 | */ 393 | public function reduce(callable $fn, $initial = null) { 394 | return array_reduce($this->__datas, $fn, $initial); 395 | } 396 | 397 | 398 | /** 399 | * 向数组尾部追加元素 400 | * @param $val 401 | * @return int 402 | */ 403 | public function append($val): int { 404 | return array_push($this->__datas, $val); 405 | } 406 | 407 | 408 | /** 409 | * 向数组头部追加元素 410 | * @param $val 411 | * @return int 412 | */ 413 | public function prepend($val): int { 414 | return array_unshift($this->__datas, $val); 415 | } 416 | 417 | 418 | /** 419 | * 从数组尾部弹出元素 420 | * @return mixed 421 | */ 422 | public function pop() { 423 | return array_pop($this->__datas); 424 | } 425 | 426 | 427 | /** 428 | * 从数组头部弹出元素 429 | * @return mixed 430 | */ 431 | public function shift() { 432 | return array_shift($this->__datas); 433 | } 434 | 435 | 436 | /** 437 | * 数组切片 438 | * @param $offset 439 | * @param null $length 440 | * @return ArrayObject 441 | */ 442 | public function slice($offset, $length = null): ArrayObject { 443 | return new self(array_slice($this->__datas, $offset, $length)); 444 | } 445 | 446 | 447 | /** 448 | * 随机获取一个元素 449 | * @return mixed|null 450 | */ 451 | public function rand() { 452 | $key = array_rand($this->__datas, 1); 453 | return $this->__datas[$key] ?? null; 454 | } 455 | 456 | 457 | /** 458 | * 遍历数组 459 | * @param callable $fn 处理函数 460 | * @return ArrayObject 461 | */ 462 | public function each(callable $fn): ArrayObject { 463 | if ($this->count() > 0) { 464 | array_walk($this->__datas, $fn); 465 | } 466 | 467 | return $this; 468 | } 469 | 470 | 471 | /** 472 | * 遍历数组,并构建新数组 473 | * @param callable $fn 474 | * @return ArrayObject 475 | */ 476 | public function map(callable $fn): ArrayObject { 477 | return new self(array_map($fn, $this->__datas)); 478 | } 479 | 480 | 481 | /** 482 | * 所有值 483 | * @return ArrayObject 484 | */ 485 | public function values(): ArrayObject { 486 | return new self(array_values($this->__datas)); 487 | } 488 | 489 | 490 | /** 491 | * 所有键 492 | * @param null $search_value 493 | * @param bool $strict 494 | * @return ArrayObject 495 | */ 496 | public function keys($search_value = null, $strict = false): ArrayObject { 497 | if (is_null($search_value)) { 498 | $keys = array_keys($this->__datas); 499 | } else { 500 | $keys = array_keys($this->__datas, $search_value, $strict); 501 | } 502 | 503 | return new self($keys); 504 | } 505 | 506 | 507 | /** 508 | * 返回列 509 | * @param $column_key 510 | * @param null $index 511 | * @return ArrayObject 512 | */ 513 | public function column($column_key, $index = null): ArrayObject { 514 | return new self(array_column($this->__datas, $column_key, $index)); 515 | } 516 | 517 | 518 | /** 519 | * 去重 520 | * @param int $sort_flags 521 | * @return ArrayObject 522 | */ 523 | public function unique($sort_flags = SORT_STRING): ArrayObject { 524 | return new self(array_unique($this->__datas, $sort_flags)); 525 | } 526 | 527 | 528 | /** 529 | * 获取重复的元素 530 | * @param int $sort_flags 531 | * @return ArrayObject 532 | */ 533 | public function multiple($sort_flags = SORT_STRING): ArrayObject { 534 | $arr = array_unique($this->__datas, $sort_flags); 535 | return new self(array_merge(array_diff_assoc($this->__datas, $arr))); 536 | } 537 | 538 | 539 | /** 540 | * 排序 541 | * @param int $sort_flags 542 | * @return ArrayObject 543 | */ 544 | public function sort($sort_flags = SORT_REGULAR): ArrayObject { 545 | sort($this->__datas, $sort_flags); 546 | 547 | return $this; 548 | } 549 | 550 | 551 | /** 552 | * 反序 553 | * @param bool $preserve_keys 554 | * @return ArrayObject 555 | */ 556 | public function reverse($preserve_keys = false): ArrayObject { 557 | $this->__datas = array_reverse($this->__datas, $preserve_keys); 558 | 559 | return $this; 560 | } 561 | 562 | 563 | /** 564 | * 乱序 565 | * @return ArrayObject 566 | */ 567 | public function shuffle(): ArrayObject { 568 | if ($this->count() > 0) { 569 | shuffle($this->__datas); 570 | } 571 | 572 | return $this; 573 | } 574 | 575 | 576 | /** 577 | * 将一个数组分割成多个数组 578 | * @param $size 579 | * @param bool $preserve_keys 是否保留键名 580 | * @return ArrayObject 581 | */ 582 | public function chunk($size, $preserve_keys = false): ArrayObject { 583 | return new self(array_chunk($this->__datas, $size, $preserve_keys)); 584 | } 585 | 586 | 587 | /** 588 | * 交换数组中的键和值 589 | * @return ArrayObject 590 | */ 591 | public function flip(): ArrayObject { 592 | return new self(array_flip($this->__datas)); 593 | } 594 | 595 | 596 | /** 597 | * 过滤数组中的元素 598 | * @param callable $fn 599 | * @param int $flag 600 | * @return ArrayObject 601 | */ 602 | public function filter(callable $fn, $flag = 0): ArrayObject { 603 | return new self(array_filter($this->__datas, $fn, $flag)); 604 | } 605 | 606 | 607 | } -------------------------------------------------------------------------------- /src/Objects/BaseObject.php: -------------------------------------------------------------------------------- 1 | getMethods() : $class->getMethods($filter); 127 | if (!empty($methods)) { 128 | foreach ($methods as $methodObj) { 129 | array_push($res, $methodObj->name); 130 | } 131 | 132 | //不包括父类的方法 133 | if (!$includeParent && $parentClass = get_parent_class($name)) { 134 | $parentMethods = get_class_methods($parentClass); 135 | if (!empty($parentMethods)) { 136 | $res = array_diff($res, $parentMethods); 137 | } 138 | } 139 | } 140 | 141 | return $res; 142 | } 143 | 144 | 145 | /** 146 | * 实例化并返回[调用此方法的类,静态绑定] 147 | * 不可重写 148 | * @return object 149 | */ 150 | final public static function getSelfInstance(): object { 151 | if (is_null(static::$_self) || !is_object(static::$_self) || !(static::$_self instanceof static)) { 152 | //静态延迟绑定 153 | static::$_self = new static(); 154 | } 155 | 156 | return static::$_self; 157 | } 158 | 159 | 160 | /** 161 | * 是否存在当前[调用]类实例化 162 | * 不可重写 163 | * @return bool 164 | */ 165 | final public static function hasSelfInstance(): bool { 166 | return isset(static::$_self); 167 | } 168 | 169 | 170 | /** 171 | * 销毁当前[调用]类实例化 172 | * 不可重写 173 | */ 174 | final public static function destroySelfInstance(): void { 175 | static::$_self = null; 176 | } 177 | 178 | 179 | /** 180 | * 实例化并返回[最终父类] 181 | * 可重写,返回所重写的类 182 | * 当前返回BaseObject 183 | * @return BaseObject 184 | */ 185 | public static function getFinalInstance(): BaseObject { 186 | if (is_null(self::$_final) || !is_object(self::$_final) || !(self::$_final instanceof self)) { 187 | self::$_final = new self(); 188 | } 189 | 190 | return self::$_final; 191 | } 192 | 193 | 194 | /** 195 | * 是否存在最终类实例化 196 | * 可重写 197 | * @return bool 198 | */ 199 | public static function hasFinalInstance(): bool { 200 | return isset(self::$_final); 201 | } 202 | 203 | 204 | /** 205 | * 销毁最终类实例化 206 | * 可重写 207 | */ 208 | public static function destroyFinalInstance(): void { 209 | self::$_final = null; 210 | } 211 | 212 | 213 | } -------------------------------------------------------------------------------- /src/Objects/StrictObject.php: -------------------------------------------------------------------------------- 1 | $value) { 58 | $this->set($field, $value); 59 | } 60 | } 61 | 62 | 63 | /** 64 | * 获取该类的反射对象 65 | * @return ReflectionClass 66 | * @throws ReflectionException 67 | */ 68 | public function getReflectionObject() { 69 | if (is_null($this->__refCls)) { 70 | $this->__refCls = new ReflectionClass($this); 71 | } 72 | 73 | return $this->__refCls; 74 | } 75 | 76 | 77 | /** 78 | * 获取未定义时警告 79 | * @param string $name 名称 80 | * @throws Throwable 81 | */ 82 | protected function __undefinedGetWarn(string $name) { 83 | throw new Exception('Undefined readable property: ' . static::class . '::' . $name); 84 | } 85 | 86 | 87 | /** 88 | * 设置未定义时警告 89 | * @param string $name 90 | * @throws Throwable 91 | */ 92 | protected function __undefinedSetWarn(string $name) { 93 | throw new Exception('Undefined writable property: ' . static::class . '::' . $name); 94 | } 95 | 96 | 97 | /** 98 | * 检查是否空属性 99 | * @param string $name 100 | * @throws Throwable 101 | */ 102 | protected function __checkEmptyProperty(string $name) { 103 | if (is_null($name) || trim($name) === '') { 104 | throw new Exception('empty property: ' . static::class . '::'); 105 | } 106 | } 107 | 108 | 109 | /** 110 | * 获取属性值或调用获取方法,如 get(name) => getName() 111 | * @param string $name 112 | * @return mixed|void 113 | * @throws Throwable 114 | */ 115 | final public function get(string $name) { 116 | $this->__checkEmptyProperty($name); 117 | 118 | // 获取public、protected属性 119 | if (property_exists($this, $name)) { 120 | try { 121 | return $this->$name; 122 | } catch (Error $e) { 123 | } 124 | } 125 | 126 | // 对private属性,调用getXXX方法 127 | $methodName = 'get' . ucfirst($name); 128 | if (method_exists($this, $methodName)) { 129 | try { 130 | return $this->$methodName(); 131 | } catch (Error $e) { 132 | } 133 | } 134 | 135 | return $this->__undefinedGetWarn($name); 136 | } 137 | 138 | 139 | /** 140 | * 设置属性值或调用设置方法,如 set(name,val) => setName(val) 141 | * @param string $name 142 | * @param mixed $value 143 | * @return bool|void 144 | * @throws Throwable 145 | */ 146 | final public function set(string $name, $value = null) { 147 | $this->__checkEmptyProperty($name); 148 | 149 | // 设置public、protected属性 150 | if (property_exists($this, $name)) { 151 | try { 152 | $this->$name = $value; 153 | return true; 154 | } catch (Error $e) { 155 | } 156 | } 157 | 158 | // 对private属性,调用setXXX方法 159 | $methodName = 'set' . ucfirst($name); 160 | if (method_exists($this, $methodName)) { 161 | try { 162 | $this->$methodName($value); 163 | return true; 164 | } catch (Error $e) { 165 | } 166 | } 167 | 168 | //从销毁字段中移除 169 | $idx = array_search($name, $this->unsetFields); 170 | if ($idx != false) { 171 | unset($this->unsetFields[$name]); 172 | } 173 | 174 | return $this->__undefinedSetWarn($name); 175 | } 176 | 177 | 178 | /** 179 | * 属性是否存在(包括NULL值) 180 | * @param $name 181 | * @return bool 182 | */ 183 | public function isset($name): bool { 184 | if (is_null($name) || $name == '') { 185 | return false; 186 | } 187 | 188 | $res = isset($this->$name); //不能检查null值 189 | if (!$res && !in_array($name, $this->unsetFields)) { 190 | $fields = get_class_vars($this); 191 | $res = array_key_exists($name, $fields); 192 | } 193 | 194 | return $res; 195 | } 196 | 197 | 198 | /** 199 | * 销毁属性 200 | * @param $name 201 | */ 202 | public function unset($name): void { 203 | if (is_null($name) || $name == '') { 204 | return; 205 | } 206 | 207 | if (is_null($this->jsonFields)) { 208 | $this->getJsonFields(); 209 | } 210 | 211 | unset($this->$name); 212 | array_push($this->unsetFields, $name); 213 | } 214 | 215 | 216 | /** 217 | * 获取可json字段 218 | * @return array 219 | * @throws ReflectionException 220 | */ 221 | protected function getJsonFields(): array { 222 | if (is_null($this->jsonFields)) { 223 | $this->jsonFields = []; 224 | $ref = $this->getReflectionObject(); 225 | array_map(function (ReflectionProperty $fieldObj) { 226 | array_push($this->jsonFields, $fieldObj->getName()); 227 | }, array_filter($ref->getProperties(ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PUBLIC), function (ReflectionProperty $field) { 228 | return !$field->isStatic(); 229 | })); 230 | } 231 | 232 | return $this->jsonFields; 233 | } 234 | 235 | 236 | /** 237 | * json序列化(仅public、protected的属性) 238 | * @return array 239 | * @throws ReflectionException 240 | */ 241 | public function jsonSerialize(): array { 242 | $arr = []; 243 | $fields = $this->getJsonFields(); 244 | if (!empty($fields)) { 245 | array_map(function ($field) use (&$arr) { 246 | $arr[$field] = $this->{$field}; 247 | }, array_filter($fields, function (string $field) { 248 | //过滤已销毁的属性 249 | return $this->isset($field); 250 | })); 251 | } 252 | unset($fields); 253 | 254 | return $arr; 255 | } 256 | 257 | 258 | /** 259 | * 转为数组 260 | * @return array 261 | * @throws ReflectionException 262 | */ 263 | public function toArray(): array { 264 | return $this->jsonSerialize(); 265 | } 266 | 267 | 268 | /** 269 | * 转为json串 270 | * @param int $options 271 | * @param int $depth 272 | * @return string 273 | * @throws ReflectionException 274 | */ 275 | public function toJson(int $options = 0, int $depth = 512): string { 276 | return json_encode($this->jsonSerialize(), $options, $depth); 277 | } 278 | 279 | 280 | } -------------------------------------------------------------------------------- /src/Services/BaseService.php: -------------------------------------------------------------------------------- 1 | errno); 69 | } 70 | 71 | 72 | /** 73 | * 获取错误信息 74 | * @return string 75 | */ 76 | public function getError(): string { 77 | $res = strval($this->error); 78 | if ($this->errno && empty($res)) { 79 | $res = Consts::UNKNOWN; 80 | } 81 | 82 | return $res; 83 | } 84 | 85 | 86 | /** 87 | * 设置服务错误信息 88 | * @param string $error 错误信息 89 | * @param int|mixed $errno 错误代码 90 | */ 91 | public function setErrorInfo(string $error = '', $errno = null) { 92 | if ($error) { 93 | $this->error = $error; 94 | } 95 | 96 | if ($errno) { 97 | $this->errno = intval($errno); 98 | } 99 | } 100 | 101 | 102 | /** 103 | * 获取服务错误信息 104 | * @return array 105 | */ 106 | public function getErrorInfo(): array { 107 | return [ 108 | 'errno' => $this->getErrno(), 109 | 'error' => $this->getError(), 110 | ]; 111 | } 112 | 113 | 114 | /** 115 | * 设置结果 116 | * @param mixed $arr 117 | * @return void 118 | */ 119 | public function setResult($arr): void { 120 | $this->result = $arr; 121 | } 122 | 123 | 124 | /** 125 | * 获取结果 126 | * @return mixed 127 | */ 128 | public function getResult() { 129 | return $this->result; 130 | } 131 | 132 | } -------------------------------------------------------------------------------- /src/Util/MacAddress.php: -------------------------------------------------------------------------------- 1 | nick; 30 | } 31 | 32 | protected function setNick(string $nick) { 33 | $this->nick = $nick; 34 | } 35 | 36 | protected function setId($id) { 37 | $this->id = $id; 38 | } 39 | 40 | protected function getId() { 41 | return $this->id; 42 | } 43 | 44 | 45 | private function setNo($no) { 46 | $this->no = $no; 47 | } 48 | 49 | 50 | private function getNo() { 51 | return $this->no; 52 | } 53 | 54 | 55 | protected static function hello() { 56 | return 'hello'; 57 | } 58 | 59 | 60 | public static function world() { 61 | return 'world'; 62 | } 63 | 64 | 65 | } -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # test 2 | e.g. 3 | 4 | phpunit --bootstrap bootstrap.php --repeat 100 XxxTest.php 5 | 6 | phpunit --repeat 100 XxxTest.php 7 | -------------------------------------------------------------------------------- /tests/Unit/ConvertHelperTest.php: -------------------------------------------------------------------------------- 1 | id = $i; 26 | $chi->type = 'child'; 27 | $chi->name = 'boy-' . strval($i); 28 | $chi->childs = []; 29 | 30 | array_push($childs, $chi); 31 | } 32 | 33 | $par = new \stdClass(); 34 | $par->id = 0; 35 | $par->type = 'parent'; 36 | $par->name = 'hello'; 37 | $par->childs = $childs; 38 | $par->one = current($childs); 39 | 40 | $res1 = ConvertHelper::object2Array(new \stdClass()); 41 | $this->assertEmpty($res1); 42 | 43 | $res2 = ConvertHelper::object2Array($chi); 44 | $this->assertEquals(4, count($res2)); 45 | 46 | $res3 = ConvertHelper::object2Array($par); 47 | $this->assertEquals(4, count($res3['childs'])); 48 | 49 | $res4 = ConvertHelper::object2Array(1); 50 | $this->assertEquals(1, count($res4)); 51 | 52 | $obj = new \stdClass(); 53 | $obj->childs = array_fill(0, 5, $par); 54 | $res5 = ConvertHelper::object2Array($obj); 55 | $this->assertTrue(is_array($res5['childs'][0])); 56 | } 57 | 58 | 59 | public function testArrayToObject() { 60 | $arr = [ 61 | 'aa' => [ 62 | 'id' => 9, 63 | 'age' => 19, 64 | 'name' => 'hello', 65 | 'child' => [], 66 | ], 67 | 'bb' => [ 68 | 'id' => 2, 69 | 'age' => 31, 70 | 'name' => 'lizz', 71 | ], 72 | 'cc' => [ 73 | 'id' => 9, 74 | 'age' => 19, 75 | 'name' => 'hello', 76 | ], 77 | 'dd' => [ 78 | 'id' => 87, 79 | 'age' => 50, 80 | 'name' => 'zhang3', 81 | ], 82 | ]; 83 | 84 | $res1 = ConvertHelper::array2Object([]); 85 | $this->assertTrue(is_object($res1)); 86 | 87 | $res2 = ConvertHelper::array2Object($arr); 88 | $this->assertTrue(is_object($res2->aa->child)); 89 | } 90 | 91 | 92 | public function testStr2hex() { 93 | $str1 = 'hello'; 94 | $res1 = ConvertHelper::str2hex($str1); 95 | 96 | $str2 = "1+2=3\r"; 97 | $res2 = ConvertHelper::str2hex($str2); 98 | 99 | $this->assertEquals('68656c6c6f', $res1); 100 | $this->assertEquals('312b323d330d', $res2); 101 | } 102 | 103 | 104 | public function testHex2Str() { 105 | $str = '68656c6c6f'; 106 | $res = ConvertHelper::hex2Str($str); 107 | $this->assertEquals('hello', $res); 108 | } 109 | 110 | 111 | 112 | } -------------------------------------------------------------------------------- /tests/Unit/DebugHelperTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(file_exists($logFile)); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /tests/Unit/DirectoryHelperTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($res); 25 | 26 | $res = DirectoryHelper::mkdirDeep(''); 27 | $this->assertFalse($res); 28 | 29 | $res = DirectoryHelper::mkdirDeep($dir); 30 | $this->assertTrue($res); 31 | 32 | DirectoryHelper::mkdirDeep('/root/hello'); 33 | } 34 | 35 | 36 | public function testGetFileTree() { 37 | $all = DirectoryHelper::getFileTree(TESTDIR); 38 | $oth = DirectoryHelper::getFileTree(TESTDIR, 'unknow'); 39 | $dirs = DirectoryHelper::getFileTree(TESTDIR, 'dir'); 40 | $files = DirectoryHelper::getFileTree(TESTDIR, 'file'); 41 | $one = DirectoryHelper::getFileTree(__FILE__); 42 | 43 | $this->assertEquals(count($all), count($oth)); 44 | $this->assertEquals(count($all), count($dirs) + count($files)); 45 | $this->assertEquals(1, count($one)); 46 | 47 | DirectoryHelper::getFileTree(TESTDIR, 'file', true); 48 | } 49 | 50 | 51 | public function testGetDirSize() { 52 | $res = DirectoryHelper::getDirSize(TESTDIR); 53 | $this->assertGreaterThan(1, $res); 54 | 55 | $res = DirectoryHelper::getDirSize(''); 56 | $this->assertEquals(0, $res); 57 | } 58 | 59 | 60 | public function testCopyDirclearDirDelDir() { 61 | $baseDir = TESTDIR . 'tmp/backup/ab'; 62 | $backupDir1 = $baseDir . '/1'; 63 | $backupDir2 = $baseDir . '/2'; 64 | DirectoryHelper::chmodBatch($backupDir1, 766, 766); 65 | DirectoryHelper::chmodBatch($backupDir2, 766, 766); 66 | 67 | $fromDir = dirname(TESTDIR) . '/src'; 68 | $res1 = DirectoryHelper::copyDir($fromDir, $backupDir1); 69 | $res2 = DirectoryHelper::copyDir($fromDir, $backupDir1, true); 70 | $res3 = DirectoryHelper::copyDir($fromDir, $backupDir2); 71 | $res4 = DirectoryHelper::copyDir($fromDir, $backupDir2, true); 72 | $res5 = DirectoryHelper::copyDir($fromDir, $backupDir2, false); 73 | 74 | $this->assertTrue($res1); 75 | $this->assertTrue($res2); 76 | $this->assertTrue($res3); 77 | $this->assertTrue($res4); 78 | $this->assertTrue($res5); 79 | 80 | DirectoryHelper::chmodBatch('', 777, 777); 81 | DirectoryHelper::chmodBatch('/root', 777, 777); 82 | DirectoryHelper::chmodBatch($backupDir1, 777, 777); 83 | 84 | //clearDir 85 | $res5 = DirectoryHelper::clearDir(''); 86 | $res6 = DirectoryHelper::clearDir($backupDir1); 87 | $this->assertFalse($res5); 88 | $this->assertTrue($res6); 89 | $this->assertTrue(is_dir($backupDir1)); 90 | 91 | //delDir 92 | $res7 = DirectoryHelper::delDir(''); 93 | $res8 = DirectoryHelper::delDir($backupDir2); 94 | $this->assertFalse($res7); 95 | $this->assertTrue($res8); 96 | $this->assertFalse(is_dir($backupDir2)); 97 | 98 | DirectoryHelper::copyDir($fromDir, '/root/tmp'); 99 | // $files1 = DirectoryHelper::getFileTree($backupDir1); 100 | // $files2 = DirectoryHelper::getFileTree($backupDir2); 101 | // $this->assertEquals(0, count($files1)); 102 | // $this->assertEquals(0, count($files2)); 103 | } 104 | 105 | 106 | public function testFormatDir() { 107 | $res1 = DirectoryHelper::formatDir(''); 108 | $res2 = DirectoryHelper::formatDir('/usr|///tmp:\\\123/\abc<|\hello>\/%world?\\how$\\are'); 109 | 110 | $this->assertEmpty($res1); 111 | $this->assertEquals('/usr/tmp/123/abc/hello/%world/how$/are/', $res2); 112 | } 113 | 114 | 115 | } -------------------------------------------------------------------------------- /tests/Unit/EncryptHelperTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($res1, 'aHR0cHM6Ly90b29sLmdvb2dsZS5jb20ubmV0L2VuY3J5cHQ_dHlwZT00SGVsbG8gV29ybGQhIOS9oOWlve-8gQ'); 32 | 33 | $res2 = EncryptHelper::base64UrlDecode($res1); 34 | $this->assertEquals($res2, $str); 35 | } 36 | 37 | 38 | public function testAuthcode() { 39 | $origin = 'hello world!'; 40 | $key = '123456'; 41 | 42 | $enres = EncryptHelper::authcode($origin, $key, true, Consts::TTL_ONE_YEAR); 43 | $deres = EncryptHelper::authcode($enres[0], $key, false); 44 | $this->assertEquals($origin, $deres[0]); 45 | $this->assertEquals($enres[1], $deres[1]); 46 | 47 | $res1 = EncryptHelper::authcode('', '', true); 48 | $res2 = EncryptHelper::authcode('', '', false); 49 | $this->assertEquals('', $res1[0]); 50 | $this->assertEquals('', $res2[0]); 51 | 52 | $res3 = EncryptHelper::authcode('hello', $key, false); 53 | $this->assertEquals('', $res3[0]); 54 | 55 | $res4 = EncryptHelper::authcode('681ff2aaPIUK-k3oHs4StYD', $key, false); 56 | $this->assertEquals('', $res4[0]); 57 | 58 | $enres = EncryptHelper::authcode(self::$strHello, self::$emptyMd5, true, 0); 59 | //res:8c9eb7905a6SdXZfm-GoJpYKu6CzMgF0I-7neF-x3UKIUpYuIZSnK_2ZqaYSZlZw0Ofzwa2Bn0QZ6b4SLzSz 60 | $deres = EncryptHelper::authcode($enres[0], self::$emptyMd5, false); 61 | $this->assertEquals(self::$strHello, $deres[0]); 62 | $this->assertEquals($enres[1], $deres[1]); 63 | 64 | $enres = EncryptHelper::authcode(self::$strHelloEmoji, self::$emptyMd5, true, 0); 65 | //res:b42374af3DqX22zi207OJXsz6xP2vEXto39TPK_UzcJOdDZV0kQHPUFm5JOw-aWISFi0snglsrYtp5tpYGRuhgw50TPY8UnFSf912uZI38vGON0KHqAgCatmtdoBZ4VJI6IkHio-JLxbt8hkuCz1HCOElUkZxBMnGUle 66 | $deres = EncryptHelper::authcode($enres[0], self::$emptyMd5, false); 67 | $this->assertEquals(self::$strHelloEmoji, $deres[0]); 68 | $this->assertEquals($enres[1], $deres[1]); 69 | 70 | $key = substr(self::$emptyMd5, 0, 16); 71 | $enres = EncryptHelper::authcode(self::$strJson, $key, true, 0); 72 | //res:52a0945eK4NyxvnjEBnPlToROzO4KLKE9VvrqtxAiLPVPDK-HkvzahyMbxydmSifc3TQIo4mbsi9gzq7vbJ64YzpB_DP 73 | $deres = EncryptHelper::authcode($enres[0], $key, false); 74 | $this->assertEquals(self::$strJson, $deres[0]); 75 | $this->assertEquals($enres[1], $deres[1]); 76 | } 77 | 78 | 79 | public function testEasyEncryptDecrypt() { 80 | $origin = 'hello world!你好,世界!'; 81 | $key = '123456'; 82 | 83 | $enres = EncryptHelper::easyEncrypt($origin, $key); 84 | $deres = EncryptHelper::easyDecrypt($enres, $key); 85 | $this->assertEquals($origin, $deres); 86 | 87 | $res1 = EncryptHelper::easyEncrypt('', $key); 88 | $this->assertEquals('', $res1); 89 | 90 | $res2 = EncryptHelper::easyDecrypt('', $key); 91 | $this->assertEquals('', $res2); 92 | 93 | $res3 = EncryptHelper::easyDecrypt('0adc39zZaczdODqqimpcaCGfYBRwciJPLxFO3NTce8VfS5', $key); 94 | $this->assertEquals('', $res3); 95 | 96 | $res4 = EncryptHelper::easyDecrypt('e10adc39 ', $key); 97 | $this->assertEquals('', $res4); 98 | 99 | $str = implode('', range(0, 99)); 100 | $res5 = EncryptHelper::easyEncrypt($str, $key); 101 | $res6 = EncryptHelper::easyDecrypt($res5, $key); 102 | $this->assertEquals($str, $res6); 103 | } 104 | 105 | 106 | public function testMurmurhash3Int() { 107 | $origin = 'hello'; 108 | $res1 = EncryptHelper::murmurhash3Int($origin); 109 | $res2 = EncryptHelper::murmurhash3Int($origin, 3, false); 110 | $this->assertEquals(11, strlen($res1)); 111 | $this->assertEquals(10, strlen($res2)); 112 | 113 | $origin .= '2'; 114 | $res3 = EncryptHelper::murmurhash3Int($origin); 115 | $origin .= '3'; 116 | $res4 = EncryptHelper::murmurhash3Int($origin); 117 | } 118 | 119 | 120 | public function testOpensslEncryptDecrypt() { 121 | $str = 'hello world.'; 122 | $key = 'Ti*1@^LSxg1E#^Gc'; 123 | $iv = '37nCVPl5HtTKYBqW'; 124 | 125 | $res0 = EncryptHelper::opensslEncrypt('', ''); 126 | $res1 = EncryptHelper::opensslDecrypt('', ''); 127 | $this->assertEmpty($res0); 128 | $this->assertEmpty($res1); 129 | 130 | $cipherText1 = EncryptHelper::opensslEncrypt($str, $key); 131 | $cipherText2 = EncryptHelper::opensslEncrypt($str, $key, $iv); 132 | $cipherText3 = EncryptHelper::opensslEncrypt($str, $key, $iv, 'aes-256-cbc'); 133 | $cipherText4 = EncryptHelper::opensslEncrypt($str, $key, $iv, 'des-ede3-cbc'); 134 | 135 | $clearText1 = EncryptHelper::opensslDecrypt($cipherText1, $key); 136 | $clearText2 = EncryptHelper::opensslDecrypt($cipherText2, $key, $iv); 137 | $clearText3 = EncryptHelper::opensslDecrypt($cipherText3, $key, $iv, 'aes-256-cbc'); 138 | $clearText4 = EncryptHelper::opensslDecrypt($cipherText4, $key, $iv, 'des-ede3-cbc'); 139 | 140 | $this->assertEquals($str, $clearText1); 141 | $this->assertEquals($str, $clearText2); 142 | $this->assertEquals($str, $clearText3); 143 | $this->assertEquals($str, $clearText4); 144 | 145 | } 146 | 147 | 148 | } -------------------------------------------------------------------------------- /tests/Unit/FileHelperTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('php', $ext1); 25 | $this->assertEquals('jpg', $ext2); 26 | } 27 | 28 | 29 | public function testWriteFile() { 30 | $file = TESTDIR . 'tmp/abc/test.log'; 31 | $res = FileHelper::writeFile($file, 'hello world'); 32 | $this->assertTrue($res); 33 | $this->assertTrue(file_exists($file)); 34 | } 35 | 36 | 37 | public function testRemoveBom() { 38 | $file = TESTDIR . 'data/bom.txt'; 39 | $str = file_get_contents($file); 40 | $len1 = strlen($str); 41 | 42 | $res = FileHelper::removeBom($str); 43 | $len2 = strlen($res); 44 | 45 | $this->assertEquals(3, ($len1 - $len2)); 46 | } 47 | 48 | 49 | public function testCreateZip() { 50 | $files = [ 51 | TESTDIR . 'tmp/abc/test.log', 52 | TESTDIR . '../src', 53 | TESTDIR . '../vendor', 54 | ]; 55 | $dest = TESTDIR . 'tmp/test.zip'; 56 | $dest2 = TESTDIR . 'tmp/hello/test.zip'; 57 | 58 | $res1 = FileHelper::createZip($files, $dest, true); 59 | $res2 = FileHelper::createZip($files, $dest, false); 60 | $res3 = FileHelper::createZip([], $dest2, false); 61 | $res4 = FileHelper::createZip($files, $dest2, true); 62 | $this->assertTrue($res1); 63 | $this->assertFalse($res2); 64 | $this->assertFalse($res3); 65 | $this->assertFalse($res4); 66 | FileHelper::createZip($files, '/root/tmp/test.zip', true); 67 | } 68 | 69 | 70 | public function testImg2Base64() { 71 | $img = TESTDIR . 'data/php_elephant.png'; 72 | $str = FileHelper::img2Base64($img); 73 | $this->assertNotEmpty($str); 74 | $this->assertGreaterThan(1, strpos($str, 'png')); 75 | 76 | $img = TESTDIR . 'data/png.webp'; 77 | $str = FileHelper::img2Base64($img); 78 | $this->assertGreaterThan(1, strpos($str, 'webp')); 79 | 80 | $img = TESTDIR . 'data/banana.gif'; 81 | $str = FileHelper::img2Base64($img); 82 | $this->assertGreaterThan(1, strpos($str, 'gif')); 83 | 84 | $img = TESTDIR . 'data/green.jpg'; 85 | $str = FileHelper::img2Base64($img); 86 | $this->assertGreaterThan(1, strpos($str, 'jpeg')); 87 | 88 | $str = FileHelper::img2Base64(''); 89 | $this->assertEmpty($str); 90 | } 91 | 92 | 93 | public function testGetAllMimes() { 94 | $res = FileHelper::getAllMimes(); 95 | $this->assertGreaterThan(1, count($res)); 96 | } 97 | 98 | 99 | public function testGetFileMime() { 100 | $img1 = TESTDIR . 'data/png.webp'; 101 | $img2 = TESTDIR . 'data/php-logo.svg'; 102 | $mim1 = FileHelper::getFileMime($img1); 103 | $mim2 = FileHelper::getFileMime($img2); 104 | 105 | $this->assertNotEmpty($mim1); 106 | $this->assertNotEmpty($mim2); 107 | } 108 | 109 | 110 | public function testReadInArray() { 111 | $file = TESTDIR . 'data/bom.txt'; 112 | $arr = FileHelper::readInArray($file); 113 | $this->assertGreaterThan(1, count($arr)); 114 | 115 | $arr = FileHelper::readInArray('/tmp/hello/1234'); 116 | $this->assertEmpty($arr); 117 | } 118 | 119 | 120 | public function testFormatPath() { 121 | $res1 = FileHelper::formatPath(''); 122 | $res2 = FileHelper::formatPath('/usr|///tmp:\\\123/\abc<|\hello>\/%world?\\how$\\are\@#test.png'); 123 | 124 | $this->assertEmpty($res1); 125 | $this->assertEquals('/usr/tmp/123/abc/hello/%world/how$/are/@#test.png', $res2); 126 | } 127 | 128 | 129 | public function testGetAbsPath() { 130 | $path1 = 'ArrayHelperTest.php'; 131 | $path2 = './Unit/DateHelperTest.php'; 132 | $path3 = '../../docs/changelog.md'; 133 | $path4 = '../../../.gitignore'; 134 | $path5 = '/var/www/site'; 135 | 136 | $res0 = FileHelper::getAbsPath(''); 137 | $res1 = FileHelper::getAbsPath($path1, __DIR__); 138 | $res2 = FileHelper::getAbsPath($path2, TESTDIR); 139 | $res3 = FileHelper::getAbsPath($path3, __DIR__); 140 | $res4 = FileHelper::getAbsPath($path4); 141 | $res5 = FileHelper::getAbsPath($path5); 142 | 143 | $this->assertEmpty($res0); 144 | $this->assertTrue(file_exists($res1)); 145 | $this->assertTrue(file_exists($res2)); 146 | $this->assertTrue(file_exists($res3)); 147 | $this->assertFalse(file_exists($res4)); 148 | $this->assertEquals($path5, $res5); 149 | } 150 | 151 | 152 | public function testGetRelativePath() { 153 | $f1 = '/var/www/php/ci/a.php'; 154 | $f2 = '/usr/local/php/log/test.log'; 155 | $f3 = '/var/www/php/zf/b.php'; 156 | $f4 = '/var/www/img/a.php'; 157 | $f5 = '/var/www/api/img/b.php'; 158 | 159 | $res1 = FileHelper::getRelativePath($f1, ''); 160 | $res2 = FileHelper::getRelativePath($f1, $f2); 161 | $res3 = FileHelper::getRelativePath($f1, $f3); 162 | $res4 = FileHelper::getRelativePath($f4, $f5); 163 | $res5 = FileHelper::getRelativePath($f5, $f4); 164 | 165 | $this->assertEquals($res1, $f1); 166 | $this->assertEquals($res2, $f1); 167 | $this->assertEquals($res3, '../../ci/a.php'); 168 | $this->assertEquals($res4, '../../../img/a.php'); 169 | $this->assertEquals($res5, '../../api/img/b.php'); 170 | } 171 | 172 | 173 | } -------------------------------------------------------------------------------- /tests/Unit/MacAddressTest.php: -------------------------------------------------------------------------------- 1 | assertNotEmpty($res0); 26 | $this->assertEmpty($res2); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /tests/Unit/NumberHelperTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('0B', $res1); 30 | $this->assertEquals('1000KB', $res2); 31 | $this->assertEquals('976.563MB', $res3); 32 | $this->assertEquals('953.6743GB', $res4); 33 | } 34 | 35 | 36 | public function testInRange() { 37 | $res1 = NumberHelper::inRange(3, 9, 12); 38 | $res2 = NumberHelper::inRange(68, 12, 132); 39 | $res3 = NumberHelper::inRange(3.14159, 1.01, 8.003); 40 | 41 | $this->assertFalse($res1); 42 | $this->assertTrue($res2); 43 | $this->assertTrue($res3); 44 | } 45 | 46 | 47 | public function testSum() { 48 | $res1 = NumberHelper::sum(1, 3, 4, 6); 49 | $res2 = NumberHelper::sum(-1, 0.5, true, [], 4, 'hello', 231); 50 | 51 | $this->assertEquals(14, $res1); 52 | $this->assertEquals(234.5, $res2); 53 | } 54 | 55 | 56 | public function testAverage() { 57 | $res1 = NumberHelper::average(1, 3, 4, 6); 58 | $res2 = NumberHelper::average(-1, 0.5, true, [], 4, 'hello', 231); 59 | 60 | $this->assertEquals(3.5, $res1); 61 | $this->assertEquals(58.625, $res2); 62 | } 63 | 64 | 65 | public function testGeoDistance() { 66 | $lat1 = 30.0; 67 | $lng1 = 45.0; 68 | $lat2 = 40.0; 69 | $lng2 = 90.0; 70 | 71 | $res1 = NumberHelper::geoDistance($lng1, $lat1, $lng2, $lat2); 72 | 73 | $lat1 = 390.0; 74 | $lng1 = 405.0; 75 | $lat2 = -320.0; 76 | $lng2 = 90.0; 77 | 78 | $res2 = NumberHelper::geoDistance($lng1, $lat1, $lng2, $lat2); 79 | 80 | $res3 = number_format($res1, 7, '.', ''); 81 | $res4 = number_format($res2, 7, '.', ''); 82 | 83 | $this->assertEquals('4199598.4916152', $res3); 84 | $this->assertEquals($res3, $res4); 85 | } 86 | 87 | 88 | public function testNumberFormat() { 89 | $num1 = 123000; 90 | $num2 = 1234.56789; 91 | 92 | $res1 = NumberHelper::numberFormat($num1); 93 | $res2 = NumberHelper::numberFormat($num2, 3); 94 | 95 | $this->assertEquals('123000.00', $res1); 96 | $this->assertEquals('1234.568', $res2); 97 | } 98 | 99 | 100 | public function testNumberSub() { 101 | $num1 = '123000'; 102 | $num2 = 1234.56789; 103 | 104 | $res1 = NumberHelper::numberSub($num1, 0); 105 | $res2 = NumberHelper::numberSub($num2, 3); 106 | 107 | $this->assertEquals('123000', $res1); 108 | $this->assertEquals('1234.567', $res2); 109 | } 110 | 111 | 112 | public function testRandFloat() { 113 | $tests = [ 114 | [0, 1], 115 | [1, 9], 116 | [-5, 5], 117 | [-1204, 6534], 118 | ]; 119 | foreach ($tests as $test) { 120 | $expected = NumberHelper::randFloat($test[0], $test[1]); 121 | $chk = NumberHelper::inRange($expected, $test[0], $test[1]); 122 | $this->assertTrue($chk); 123 | } 124 | } 125 | 126 | 127 | public function testMoney2Yuan() { 128 | $num0 = 123456789087654; 129 | $num1 = 123456789876; 130 | $num2 = 12345678908.7; 131 | $num3 = 12345678908.76; 132 | $num4 = 12345678908.765; 133 | $num5 = 12345678908.7654; 134 | 135 | try { 136 | NumberHelper::money2Yuan($num0); 137 | } catch (Throwable $e) { 138 | $this->assertTrue($e instanceof BaseException); 139 | } 140 | 141 | $res1 = NumberHelper::money2Yuan($num1); 142 | $res2 = NumberHelper::money2Yuan($num2, 1); 143 | $res3 = NumberHelper::money2Yuan($num3, 2); 144 | $res4 = NumberHelper::money2Yuan($num4, 3); 145 | $res5 = NumberHelper::money2Yuan($num5, 5); 146 | $res6 = NumberHelper::money2Yuan(0.0, 5); 147 | 148 | $this->assertEquals($res1, '壹仟贰佰叁拾肆亿伍仟陆佰柒拾捌万玖仟捌佰柒拾陆元整'); 149 | $this->assertEquals($res2, '壹佰贰拾叁亿肆仟伍佰陆拾柒万捌仟玖佰零捌元柒角整'); 150 | $this->assertEquals($res3, '壹佰贰拾叁亿肆仟伍佰陆拾柒万捌仟玖佰零捌元柒角陆分整'); 151 | $this->assertEquals($res4, '壹佰贰拾叁亿肆仟伍佰陆拾柒万捌仟玖佰零捌元柒角陆分伍厘整'); 152 | $this->assertEquals($res4, $res5); 153 | $this->assertEquals($res6, '零元整'); 154 | } 155 | 156 | 157 | public function testNearLogarithm() { 158 | $res1 = NumberHelper::nearLogarithm(1000, 10); 159 | $res2 = NumberHelper::nearLogarithm(1005, 10, true); 160 | $res3 = NumberHelper::nearLogarithm(1005, 10, false); 161 | 162 | $res4 = NumberHelper::nearLogarithm(8, 2, true); 163 | $res5 = NumberHelper::nearLogarithm(8, 2, false); 164 | 165 | $res6 = NumberHelper::nearLogarithm(14, 2, true); 166 | $res7 = NumberHelper::nearLogarithm(14, 2, false); 167 | 168 | $this->assertEquals($res1, 3); 169 | $this->assertEquals($res2, 3); 170 | $this->assertEquals($res3, 4); 171 | $this->assertEquals($res4, $res5); 172 | $this->assertEquals($res6, 3); 173 | $this->assertEquals($res7, 4); 174 | 175 | try { 176 | NumberHelper::nearLogarithm(-14, 2, false); 177 | } catch (Throwable $e) { 178 | $this->assertTrue($e instanceof BaseException); 179 | } 180 | try { 181 | NumberHelper::nearLogarithm(19, -2, false); 182 | } catch (Throwable $e) { 183 | $this->assertTrue($e instanceof BaseException); 184 | } 185 | } 186 | 187 | 188 | public function testSplitNaturalNum() { 189 | $res1 = NumberHelper::splitNaturalNum(15, 2); 190 | $res2 = NumberHelper::splitNaturalNum(36, 2); 191 | $res3 = NumberHelper::splitNaturalNum(37, 2); 192 | $res4 = NumberHelper::splitNaturalNum(4, 2); 193 | 194 | $this->assertEquals(4, count($res1)); 195 | $this->assertEquals(2, count($res2)); 196 | $this->assertEquals(3, count($res3)); 197 | $this->assertEquals(4, current($res4)); 198 | 199 | try { 200 | NumberHelper::splitNaturalNum(-14, 2, false); 201 | } catch (Throwable $e) { 202 | $this->assertTrue($e instanceof BaseException); 203 | } 204 | try { 205 | NumberHelper::splitNaturalNum(19, -2, false); 206 | } catch (Throwable $e) { 207 | $this->assertTrue($e instanceof BaseException); 208 | } 209 | } 210 | 211 | 212 | } -------------------------------------------------------------------------------- /tests/Unit/ObjectsTest.php: -------------------------------------------------------------------------------- 1 | name = 'hello'; 37 | $baseObj->gender = 0; 38 | 39 | $this->assertEquals(strval($baseObj), get_class($baseObj)); 40 | $this->assertEquals($baseObj::getShortName(), 'BaseCls'); 41 | 42 | $arr1 = BaseCls::parseNamespacePath(); 43 | $arr2 = BaseObject::parseNamespacePath($baseObj); 44 | $arr3 = BaseObject::parseNamespacePath("\Kph\Objects\BaseObject"); 45 | 46 | $cls1 = StrictCls::getShortName(); 47 | $cls2 = BaseObject::getShortName($baseObj); 48 | $cls3 = BaseObject::getShortName("\PHPUnit\Framework\TestCase"); 49 | 50 | $nsp1 = StrictCls::getNamespaceName(); 51 | $nsp2 = BaseObject::getNamespaceName($baseObj); 52 | $nsp3 = BaseObject::getNamespaceName("\PHPUnit\Framework\TestCase"); 53 | 54 | $this->assertEquals(4, count($arr1)); 55 | $this->assertEquals(4, count($arr2)); 56 | $this->assertEquals(3, count($arr3)); 57 | 58 | $this->assertEquals('StrictCls', $cls1); 59 | $this->assertEquals('BaseCls', $cls2); 60 | $this->assertEquals('TestCase', $cls3); 61 | 62 | $this->assertEquals("Kph\Tests\Objects", $nsp1); 63 | $this->assertEquals("Kph\Tests\Objects", $nsp2); 64 | $this->assertEquals("PHPUnit\Framework", $nsp3); 65 | } 66 | 67 | 68 | /** 69 | * 严格对象测试 70 | * @throws ReflectionException 71 | * @throws Throwable 72 | */ 73 | public function testStrict() { 74 | $striObj = new StrictCls(['name' => 'zhang3']); 75 | $ref = $striObj->getReflectionObject(); 76 | $this->assertTrue($ref instanceof ReflectionClass); 77 | 78 | // 空属性 79 | try { 80 | $striObj->get(''); 81 | } catch (Exception $e) { 82 | $this->assertTrue(stripos($e->getMessage(), 'empty property') !== false); 83 | } 84 | 85 | // 访问protected属性 86 | $gender = $striObj->get('gender'); 87 | $this->assertEquals($gender, 'man'); 88 | 89 | $nick = $striObj->get('nick'); 90 | $this->assertEquals($nick, 'boot'); 91 | 92 | // 访问private属性 93 | $id = $striObj->get('id'); 94 | $this->assertEquals($id, 1); 95 | 96 | // 访问不存在的属性 97 | try { 98 | $none = $striObj->get('none'); 99 | } catch (Exception $e) { 100 | $this->assertTrue(stripos($e->getMessage(), 'Undefined readable property') !== false); 101 | } 102 | 103 | // 访问属性存在,但getXXX方法私有 104 | try { 105 | $no = $striObj->get('no'); 106 | } catch (Exception $e) { 107 | $this->assertTrue(stripos($e->getMessage(), 'Undefined readable property') !== false); 108 | } 109 | 110 | // 设置protected属性 111 | $gender = 'woman'; 112 | $striObj->set('gender', $gender); 113 | $this->assertEquals($gender, $striObj->get('gender')); 114 | 115 | $nick = 'hello'; 116 | $striObj->set('nick', $nick); 117 | $this->assertEquals($nick, $striObj->get('nick')); 118 | 119 | // 设置private属性 120 | $id = 5; 121 | $striObj->set('id', $id); 122 | $this->assertEquals($id, $striObj->get('id')); 123 | 124 | // 设置不存在的属性 125 | try { 126 | $striObj->set('none', true); 127 | } catch (Exception $e) { 128 | $this->assertTrue(stripos($e->getMessage(), 'Undefined writable property') !== false); 129 | } 130 | 131 | // 设置属性存在,但getXXX方法私有 132 | try { 133 | $striObj->set('no', 2); 134 | } catch (Exception $e) { 135 | $this->assertTrue(stripos($e->getMessage(), 'Undefined writable property') !== false); 136 | } 137 | 138 | // 属性值为null 139 | $key = 'name'; 140 | $this->assertEquals(true, $striObj->isset($key)); 141 | $striObj->set($key, null); 142 | $this->assertEquals(true, $striObj->isset($key)); 143 | 144 | // 销毁属性 145 | $striObj->unset($key); 146 | $this->assertEquals(false, $striObj->isset($key)); 147 | $this->assertEquals(false, $striObj->isset(null)); 148 | 149 | // json化 150 | $json1 = json_encode($striObj); 151 | $arr1 = json_decode($json1, true); 152 | $json2 = $striObj->toJson(); 153 | $arr2 = $striObj->toArray(); 154 | 155 | $this->assertEquals($json1, $json2); 156 | $this->assertEqualsCanonicalizing($arr1, $arr2); 157 | 158 | } 159 | 160 | 161 | /** 162 | * 数组对象测试 163 | */ 164 | public function testArray() { 165 | $arrObj = new ArrayObject(['a' => 1, 'b' => 2, 'c' => 3]); 166 | 167 | $this->assertEquals($arrObj->a, 1); 168 | 169 | $arrObj->d = 4; 170 | $this->assertTrue($arrObj->offsetExists('d')); 171 | 172 | $arrObj->offsetSet('d', 5); 173 | $this->assertEquals($arrObj->offsetGet('d'), 5); 174 | 175 | $arrObj->set('e', 5); 176 | $this->assertEquals($arrObj->get('e'), 5); 177 | 178 | //json 179 | $json1 = json_encode($arrObj); 180 | $json2 = $arrObj->toJson(); 181 | $this->assertEquals($json1, $json2); 182 | 183 | $count1 = $arrObj->count(); 184 | $seri = $arrObj->serialize(); 185 | $arrObj->unserialize($seri); 186 | $count2 = $arrObj->count(); 187 | $this->assertEquals($count1, $count2); 188 | 189 | $this->assertEquals($arrObj->current(), 1); 190 | $this->assertEquals($arrObj->next(), 2); 191 | 192 | $this->assertEquals($arrObj->key(), 'b'); 193 | $this->assertTrue($arrObj->valid()); 194 | 195 | $arrObj->rewind(); 196 | $this->assertEquals($arrObj->key(), 'a'); 197 | 198 | $arr = $arrObj->toArray(); 199 | $this->assertEquals($arrObj->count(), count($arr)); 200 | 201 | $idx = $arrObj->search(3); 202 | $idx2 = $arrObj->indexOf(3); 203 | $this->assertEquals($idx, $idx2); 204 | 205 | $idx3 = $arrObj->lastIndexOf(5); 206 | $this->assertEquals($idx3, 'e'); 207 | 208 | $keys = $arrObj->keys(5); 209 | $this->assertEquals($keys->count(), 2); 210 | 211 | $this->assertTrue($arrObj->delete('e')); 212 | 213 | $arrObj->remove(5); 214 | $arrObj->offsetUnset('c'); 215 | $this->assertFalse($arrObj->exists('e')); 216 | $this->assertFalse($arrObj->exists('c')); 217 | $this->assertFalse($arrObj->contains(5)); 218 | 219 | $str = $arrObj->join(','); 220 | $this->assertFalse(empty($str)); 221 | 222 | $arrObj->clear(); 223 | $this->assertTrue($arrObj->isEmpty()); 224 | 225 | $arrObj->insert(0, 2); 226 | $arrObj->insert(2, 6); 227 | $arrObj->append(3); 228 | $arrObj->prepend(1); 229 | 230 | $sum = $arrObj->sum(); 231 | $pro = $arrObj->product(); 232 | $sum2 = $arrObj->reduce(function ($carry, $item) { 233 | $carry += $item; 234 | return $carry; 235 | }); 236 | $this->assertEquals($sum, $pro); 237 | $this->assertEquals($sum, $sum2); 238 | 239 | $obj2 = $arrObj->slice(0, 2); 240 | $this->assertEquals($obj2->count(), 2); 241 | 242 | $obj2->pop(); 243 | $obj2->shift(); 244 | $this->assertEquals($obj2->count(), 0); 245 | 246 | $item = $arrObj->rand(); 247 | $this->assertTrue($arrObj->contains($item)); 248 | 249 | $arrObj->each(function (&$val, $key) { 250 | $val = pow($val, $key); 251 | }); 252 | $this->assertEquals($arrObj->sum(), 12); 253 | 254 | $obj3 = $arrObj->map(function ($val) { 255 | return $val * 2; 256 | }); 257 | $this->assertEquals($obj3->sum(), 24); 258 | 259 | $values = $arrObj->values(); 260 | $keys = $arrObj->keys(); 261 | $this->assertEquals($values->count(), $keys->count()); 262 | 263 | $arrObj->prepend(2); 264 | $arrObj->prepend(3); 265 | $arrObj->prepend(9); 266 | 267 | $obj4 = $arrObj->unique(); 268 | $this->assertTrue($obj4->count() < $arrObj->count()); 269 | 270 | $obj5 = $arrObj->multiple(); 271 | $this->assertEquals($obj5->count(), 2); 272 | 273 | $arrObj->sort(); 274 | $this->assertEquals($arrObj->current(), 1); 275 | 276 | $arrObj->reverse(); 277 | $this->assertEquals($arrObj->current(), 9); 278 | 279 | $arrObj->shuffle(); 280 | $obj6 = $arrObj->chunk(3); 281 | $this->assertEquals($obj6->count(), 2); 282 | 283 | $obj7 = $obj4->flip(); 284 | $this->assertEquals($obj7->current(), 0); 285 | 286 | $obj8 = $arrObj->filter(function ($val) { 287 | return $val > 4; 288 | }); 289 | $this->assertEquals($obj8->count(), 2); 290 | 291 | $arrObj->clear(); 292 | $arrObj->append(['name' => 'zhang3', 'age' => '20',]); 293 | $arrObj->append(['name' => 'li4', 'age' => '22',]); 294 | $arrObj->append(['name' => 'zhao5', 'age' => '33',]); 295 | $arrObj->append(['name' => 'wang6', 'age' => '45',]); 296 | $names = $arrObj->column('name'); 297 | $this->assertEquals($names->count(), 4); 298 | 299 | } 300 | 301 | 302 | public function testGetClassMethods() { 303 | $res1 = BaseObject::getClassMethods(BaseCls::class); 304 | $res2 = BaseObject::getClassMethods(BaseCls::class, ReflectionMethod::IS_STATIC); 305 | $res3 = BaseObject::getClassMethods(BaseCls::class, ReflectionMethod::IS_PUBLIC, false); 306 | $dif1 = array_diff($res1, $res2); 307 | $chk1 = ValidateHelper::isEqualArray($res3, ['time', '__call']); 308 | $chk2 = ValidateHelper::isEqualArray($dif1, ['time', '__call', '__toString']); 309 | $this->assertTrue($chk1); 310 | $this->assertTrue($chk2); 311 | 312 | $res4 = BaseObject::getClassMethods(StrictCls::class); 313 | $res5 = BaseObject::getClassMethods(StrictCls::class, ReflectionMethod::IS_PROTECTED); 314 | $res6 = BaseObject::getClassMethods(StrictCls::class, ReflectionMethod::IS_PUBLIC, false); 315 | $dif2 = array_diff($res4, $res5); 316 | $chk3 = ValidateHelper::isEqualArray($res6, ['world']); 317 | $this->assertTrue($chk3); 318 | $this->assertNotEmpty($dif2); 319 | 320 | } 321 | 322 | 323 | } -------------------------------------------------------------------------------- /tests/Unit/OsHelperTest.php: -------------------------------------------------------------------------------- 1 | '/var/www', 25 | 'REMOTE_ADDR' => '172.17.0.1', 26 | 'REMOTE_PORT' => '51186', 27 | 'SERVER_SOFTWARE' => 'PHP 7.4.3 Development Server', 28 | 'SERVER_PROTOCOL' => 'HTTP/1.1', 29 | 'SERVER_NAME' => '0.0.0.0', 30 | 'SERVER_PORT' => '8000', 31 | 'REQUEST_URI' => '/index.php?name=hello&age=20&from=world', 32 | 'REQUEST_METHOD' => 'GET', 33 | 'SCRIPT_NAME' => '/index.php', 34 | 'SCRIPT_FILENAME' => '/var/www/index.php', 35 | 'PHP_SELF' => '/index.php', 36 | 'QUERY_STRING' => 'name=hello&age=20&from=world', 37 | 'HTTP_X_REAL_IP' => '192.168.56.1', 38 | 'HTTP_X_FORWARDED_FOR' => '192.168.56.1', 39 | 'HTTP_X_REAL_PORT' => '80', 40 | 'HTTP_X_FORWARDED_PROTO' => 'http', 41 | 'HTTP_HOST' => 'www.test.loc', 42 | 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0', 43 | 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 44 | 'HTTP_ACCEPT_LANGUAGE' => 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', 45 | 'HTTP_ACCEPT_ENCODING' => 'gzip, deflate', 46 | 'HTTP_UPGRADE_INSECURE_REQUESTS' => '1', 47 | 'REQUEST_TIME_FLOAT' => 1582623381.699998, 48 | 'REQUEST_TIME' => 1582623381, 49 | ]; 50 | 51 | 52 | public function testGetOS() { 53 | $res = OsHelper::getOS(); 54 | $this->assertNotEmpty($res); 55 | } 56 | 57 | 58 | public function testIsWindowsLinuxMac() { 59 | $res1 = OsHelper::isWindows(); 60 | $res2 = OsHelper::isLinux(); 61 | $res3 = OsHelper::isMac(); 62 | 63 | $this->assertTrue($res1 || $res2); 64 | $this->assertFalse($res3); 65 | } 66 | 67 | 68 | public function testGetPhpPath() { 69 | $res = OsHelper::getPhpPath(); 70 | $this->assertNotEmpty($res); 71 | } 72 | 73 | 74 | public function testIsPortOpen() { 75 | $res1 = OsHelper::isPortOpen('localhost', 8899); 76 | $res2 = OsHelper::isPortOpen('baidu.com', 80); 77 | 78 | $this->assertFalse($res1); 79 | $this->assertTrue($res2); 80 | } 81 | 82 | 83 | public function testIsWritable() { 84 | $res1 = OsHelper::isWritable(TESTDIR . 'tmp'); 85 | $res2 = OsHelper::isWritable('/root/tmp/hehe'); 86 | 87 | $this->assertTrue($res1); 88 | $this->assertFalse($res2); 89 | } 90 | 91 | 92 | public function testGetBrowser() { 93 | $agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'; 94 | $res = OsHelper::getBrowser($agent); 95 | 96 | $this->assertGreaterThan(1, stripos($res['name'], 'Chrome')); 97 | $this->assertNotEmpty($res['platform']); 98 | 99 | $agents = ValidateHelperTest::$userAgents; 100 | foreach ($agents as $agent) { 101 | $res = OsHelper::getBrowser($agent); 102 | $this->assertNotEmpty($res['name']); 103 | } 104 | 105 | $res = OsHelper::getBrowser(); 106 | $this->assertEquals(Consts::UNKNOWN, $res['name']); 107 | } 108 | 109 | 110 | public function testGetClientOS() { 111 | $agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'; 112 | $res1 = OsHelper::getClientOS($agent); 113 | $res2 = OsHelper::getClientOS(''); 114 | $this->assertEquals('Windows', $res1); 115 | $this->assertEquals(Consts::UNKNOWN, $res2); 116 | 117 | $agents = ValidateHelperTest::$userAgents; 118 | foreach ($agents as $agent) { 119 | $res = OsHelper::getClientOS($agent); 120 | $this->assertNotEmpty($res); 121 | } 122 | } 123 | 124 | 125 | public function testGetClientIp() { 126 | $server = self::$server; 127 | $res = OsHelper::getClientIp($server); 128 | $this->assertEquals('192.168.56.1', $res); 129 | 130 | $server['HTTP_X_FORWARDED_FOR'] = '220.181.38.148'; 131 | $res = OsHelper::getClientIp($server); 132 | $this->assertEquals('220.181.38.148', $res); 133 | 134 | unset($server['HTTP_X_FORWARDED_FOR']); 135 | $res = OsHelper::getClientIp($server); 136 | $this->assertEquals('172.17.0.1', $res); 137 | 138 | $res = OsHelper::getClientIp(); 139 | $this->assertEquals('0.0.0.0', $res); 140 | } 141 | 142 | 143 | public function testGetServerIP() { 144 | $server = self::$server; 145 | $res = OsHelper::getServerIP($server); 146 | $this->assertNotEmpty($res); 147 | $this->assertNotEquals('0.0.0.0', $res); 148 | 149 | $res = OsHelper::getServerIP(); 150 | $this->assertNotEmpty($res); 151 | $this->assertNotEquals('0.0.0.0', $res); 152 | 153 | putenv("SERVER_ADDR='192.168.1.1'"); 154 | $res = OsHelper::getServerIP([]); 155 | $this->assertNotEquals('192.168.1.1', $res); 156 | } 157 | 158 | 159 | public function testIp2UnsignedInt() { 160 | $ip1 = '172.17.0.1'; 161 | $ip2 = '192.168.56.1'; 162 | $ip3 = 'hello'; 163 | $ip4 = '200.117.248.17'; 164 | 165 | $res1 = OsHelper::ip2UnsignedInt($ip1); 166 | $res2 = OsHelper::ip2UnsignedInt($ip2); 167 | $res3 = OsHelper::ip2UnsignedInt($ip3); 168 | $res4 = OsHelper::ip2UnsignedInt($ip4); 169 | $res5 = OsHelper::ip2UnsignedInt(''); 170 | 171 | $this->assertGreaterThan(1, $res1); 172 | $this->assertGreaterThan(1, $res2); 173 | $this->assertEquals(0, $res3); 174 | $this->assertGreaterThan(1, $res4); 175 | $this->assertEquals(0, $res5); 176 | } 177 | 178 | 179 | public function testGetRemoteImageSize() { 180 | $url = 'https://www.baidu.com/img/bd_logo1.png'; 181 | 182 | $res1 = OsHelper::getRemoteImageSize($url . '?a=1', 'hello', false, 5, 256); 183 | $res2 = OsHelper::getRemoteImageSize($url . '?a=2', 'curl', true, 5, 256); 184 | $res3 = OsHelper::getRemoteImageSize('http://test.loc/img/hello.jpg'); 185 | 186 | $this->assertNotEmpty($res1); 187 | $this->assertEquals($res1['width'], $res2['width']); 188 | $this->assertEquals($res1['height'], $res2['height']); 189 | $this->assertEquals(0, $res1['size']); 190 | $this->assertGreaterThan(1, $res2['size']); 191 | $this->assertEmpty($res3); 192 | 193 | OsHelper::getRemoteImageSize('https://raw.githubusercontent.com/kakuilan/kgo/master/testdata/gopher10th-large.jpg', 'curl', true, 1, 24); 194 | } 195 | 196 | 197 | public function testCurlDownload() { 198 | $des1 = $backupDir1 = TESTDIR . 'tmp/download.txt'; 199 | $des2 = $backupDir1 = TESTDIR . 'tmp/hello/download.txt'; 200 | 201 | $res1 = OsHelper::curlDownload('http://test.loc/hello', '', [], false); 202 | $res2 = OsHelper::curlDownload('https://www.baidu.com/', '', ['connect_timeout' => 5, 'timeout' => 5], true); 203 | $res3 = OsHelper::curlDownload('hello world'); 204 | 205 | $res4 = OsHelper::curlDownload('https://www.baidu.com/', $des1, ['connect_timeout' => 5, 'timeout' => 5], false); 206 | $res5 = OsHelper::curlDownload('https://www.baidu.com/', $des1, ['connect_timeout' => 5, 'timeout' => 5], true); 207 | $res6 = OsHelper::curlDownload('https://www.baidu.com/', $des2, ['connect_timeout' => 5, 'timeout' => 5], false); 208 | 209 | $this->assertFalse($res1); 210 | $this->assertNotEmpty($res2); 211 | $this->assertFalse($res3); 212 | $this->assertTrue($res4); 213 | $this->assertNotEmpty($res5); 214 | $this->assertFalse($res6); 215 | } 216 | 217 | 218 | public function testIsCliMode() { 219 | $chk = OsHelper::isCliMode(); 220 | $this->assertTrue($chk); 221 | } 222 | 223 | 224 | public function testRunCommand() { 225 | $dir = TESTDIR; 226 | if (OsHelper::isWindows()) { 227 | $dir = str_replace('/', '\\', $dir); 228 | $command = "dir {$dir}"; 229 | } else { 230 | $command = "ls -l {$dir}"; 231 | } 232 | 233 | $res0 = OsHelper::runCommand(""); 234 | $res1 = OsHelper::runCommand($command); 235 | 236 | $this->assertEmpty($res0); 237 | $this->assertNotEmpty($res1); 238 | } 239 | 240 | 241 | public function testIsAjax() { 242 | $header1 = [ 243 | 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36', 244 | 'X-Requested-With' => 'XMLHttpRequest', 245 | ]; 246 | $header2 = [ 247 | 'Accept-Language' => 'zh-CN,zh;q=0.9,en;q=0.8', 248 | 'Connection' => 'keep-alive', 249 | ]; 250 | 251 | $res1 = OsHelper::isAjax(); 252 | $res2 = OsHelper::isAjax($header1); 253 | $res3 = OsHelper::isAjax($header2); 254 | 255 | $this->assertFalse($res1); 256 | $this->assertTrue($res2); 257 | $this->assertFalse($res3); 258 | } 259 | 260 | 261 | public function testIsSsl() { 262 | $server1 = [ 263 | 'HTTP_HOST' => 'test.loc', 264 | 'SERVER_NAME' => 'test.loc', 265 | 'SERVER_PORT' => '80', 266 | 'SERVER_ADDR' => '127.0.0.1', 267 | 'REQUEST_SCHEME' => 'http', 268 | 'SERVER_PROTOCOL' => 'HTTP/1.1', 269 | ]; 270 | $server2 = [ 271 | 'HTTP_HOST' => 'test.com', 272 | 'SERVER_NAME' => 'test.com', 273 | 'SERVER_PORT' => '443', 274 | 'SERVER_ADDR' => '127.0.0.1', 275 | 'HTTPS' => 'on', 276 | 'REQUEST_SCHEME' => 'https', 277 | 'SERVER_PROTOCOL' => 'HTTP/2.0', 278 | ]; 279 | 280 | $res1 = OsHelper::isSsl($server1); 281 | $res2 = OsHelper::isSsl($server2); 282 | 283 | $this->assertFalse($res1); 284 | $this->assertTrue($res2); 285 | } 286 | 287 | 288 | public function testRemoteFileExists() { 289 | $url1 = 'https://www.baidu.com'; 290 | $url2 = 'https://www.baidu.com/img/no.gif'; 291 | 292 | $res1 = OsHelper::remoteFileExists($url1); 293 | $res2 = OsHelper::remoteFileExists($url2, ['timeout' => 10, 'connect_timeout' => 2]); 294 | 295 | $this->assertTrue($res1); 296 | $this->assertFalse($res2); 297 | } 298 | 299 | 300 | } -------------------------------------------------------------------------------- /tests/Unit/ServicesTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($servName2, 'BaseObject'); 31 | $this->assertTrue(BaseServ::hasFinalInstance()); 32 | BaseServ::destroyFinalInstance(); 33 | $this->assertFalse(BaseServ::hasFinalInstance()); 34 | 35 | // 获取当前类的实例化 36 | $serv3 = BaseServ::getSelfInstance(); 37 | $servName3 = $serv3::getShortName(); 38 | $this->assertEquals($servName3, 'BaseServ'); 39 | $this->assertTrue(BaseServ::hasSelfInstance()); 40 | BaseServ::destroySelfInstance(); 41 | $this->assertFalse(BaseServ::hasSelfInstance()); 42 | 43 | $serv->setErrorInfo(123, '找不到资源'); 44 | $errArr = $serv->getErrorInfo(); 45 | $errno = $serv->getErrno(); 46 | $error = $serv->getError(); 47 | $this->assertEquals($errArr['errno'], $errno); 48 | $this->assertEquals($errArr['error'], $error); 49 | 50 | } 51 | 52 | 53 | } -------------------------------------------------------------------------------- /tests/Unit/UrlHelperTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($url, $res2); 32 | $this->assertEquals('©℗', $res3); 33 | } 34 | 35 | 36 | public function testBuildUriParams() { 37 | $arr = [ 38 | 'name' => 'li4', 39 | 'age' => 28, 40 | 'has' => [ 41 | 0 => 'apple', 42 | 1 => 'watermelon', 43 | 2 => 'banana', 44 | 'favorite' => 'litchi', 45 | ], 46 | 'from' => 'xiguan', 47 | 'to' => 'baoan', 48 | ]; 49 | 50 | $res1 = UrlHelper::buildUriParams($arr); 51 | $res2 = UrlHelper::buildUriParams($arr, ['from', 'to']); 52 | $res3 = UrlHelper::buildUriParams($arr, ['from', 'to'], ['futian', 'dogu']); 53 | 54 | $this->assertEquals('?', substr($res1, 0, 1)); 55 | $this->assertFalse(stripos($res2, 'from')); 56 | $this->assertTrue(stripos($res3, 'from') !== false); 57 | } 58 | 59 | 60 | public function testFormatUrl() { 61 | $url = 'www.test.loc//abc\\hello/\kit\/name=zang&age=11'; 62 | $res = UrlHelper::formatUrl($url); 63 | 64 | $this->assertEquals('http://www.test.loc/abc/hello/kit/name=zang&age=11', $res); 65 | } 66 | 67 | 68 | public function testCheckUrlExists() { 69 | $res1 = UrlHelper::checkUrlExists(''); 70 | $res2 = UrlHelper::checkUrlExists('hello world'); 71 | $res3 = UrlHelper::checkUrlExists('https://www.baidu.com/'); 72 | 73 | $this->assertFalse($res1); 74 | $this->assertFalse($res2); 75 | $this->assertTrue($res3); 76 | } 77 | 78 | 79 | public function testUrl2Link() { 80 | $res1 = UrlHelper::url2Link(''); 81 | $res2 = UrlHelper::url2Link('http://google.com'); 82 | $res3 = UrlHelper::url2Link('ftp://192.168.1.2/abc.pdf', ['ftp']); 83 | $res4 = UrlHelper::url2Link('test@qq.com', ['mail']); 84 | 85 | $this->assertEmpty($res1); 86 | $this->assertTrue(stripos($res2, 'href') !== false); 87 | $this->assertTrue(stripos($res3, 'href') !== false); 88 | $this->assertTrue(stripos($res4, 'href') !== false); 89 | } 90 | 91 | 92 | public function testGetDomainUrlUri() { 93 | $server = OsHelperTest::$server; 94 | $url = 'http://www.test.loc/index.php?name=hello&age=20&from=world'; 95 | $res1 = UrlHelper::getDomain($url, false, $server); 96 | $res2 = UrlHelper::getDomain($url, true, $server); 97 | 98 | $this->assertEquals('www.test.loc', $res1); 99 | $this->assertEquals('test.loc', $res2); 100 | 101 | $res3 = UrlHelper::getUrl($server); 102 | $this->assertEquals($url, $res3); 103 | 104 | $res4 = UrlHelper::getUri($server); 105 | unset($server['REQUEST_URI']); 106 | $res5 = UrlHelper::getUri($server); 107 | $this->assertEquals($res4, $res5); 108 | 109 | $res6 = UrlHelper::getDomain('', true, $server); 110 | $this->assertEquals('test.loc', $res6); 111 | 112 | $res7 = UrlHelper::getUrl(); 113 | $chk = ValidateHelper::isUrl($res7); 114 | $this->assertFalse($chk); 115 | 116 | $res8 = UrlHelper::getUri(); 117 | $this->assertNotEmpty($res8); 118 | 119 | $res9 = UrlHelper::getDomain(''); 120 | $this->assertEmpty($res9); 121 | } 122 | 123 | 124 | public function testGetSiteUrl() { 125 | $server = OsHelperTest::$server; 126 | $str = 'hello world!'; 127 | $url1 = 'http://www.test.loc/index.php?name=hello&age=20&from=world'; 128 | $url2 = 'rpc.test.com:8899/hello'; 129 | 130 | $res1 = UrlHelper::getSiteUrl($str); 131 | $this->assertEmpty($res1); 132 | 133 | $res2 = UrlHelper::getSiteUrl('', $server); 134 | $this->assertNotEmpty($res2); 135 | 136 | $res3 = UrlHelper::getSiteUrl($url1); 137 | $this->assertNotEmpty($res3); 138 | 139 | $res4 = UrlHelper::getSiteUrl($url2); 140 | $this->assertNotEmpty($res4); 141 | } 142 | 143 | 144 | } -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /tests/data/php_elephant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kakuilan/php-helper/f34ebbb5082b20e87201ca2d0c5ea808c43616c6/tests/data/php_elephant.png -------------------------------------------------------------------------------- /tests/data/png.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kakuilan/php-helper/f34ebbb5082b20e87201ca2d0c5ea808c43616c6/tests/data/png.webp -------------------------------------------------------------------------------- /tests/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 15 | 16 | ./Unit 17 | 18 | 19 | ./Feature 20 | 21 | 22 | 23 | 24 | 25 | ../src/ 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/tmp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kakuilan/php-helper/f34ebbb5082b20e87201ca2d0c5ea808c43616c6/tests/tmp/.gitkeep --------------------------------------------------------------------------------