├── .github └── workflows │ └── tests.yml ├── .gitignore ├── .php-cs-fixer.dist.php ├── LICENSE ├── README.md ├── composer.json ├── doc ├── imgs │ ├── base_relation.png │ ├── basic_relation.excalidraw │ ├── dto.excalidraw │ ├── dto.png │ └── rice_basic.png └── 国际化地区码.md ├── phpunit.xml ├── src ├── Components │ ├── Assembler │ │ └── BaseAssembler.php │ ├── DTO │ │ ├── BaseDTO.php │ │ └── PageDTO.php │ ├── Entity │ │ ├── AnnotationEntity.php │ │ ├── BaseEntity.php │ │ └── FrameEntity.php │ ├── Enum │ │ ├── BaseEnum.php │ │ ├── ExceptionEnum.php │ │ ├── HttpStatusCodeEnum.php │ │ ├── InvalidRequestEnum.php │ │ ├── KeyEnum.php │ │ ├── NameTypeEnum.php │ │ ├── ReturnCode │ │ │ ├── ClientErrorCode.php │ │ │ ├── ReturnCodeEnum.php │ │ │ ├── ServiceErrorCode.php │ │ │ └── SystemErrorCode.php │ │ ├── SupportEnum.php │ │ └── TypeEnum.php │ ├── Exception │ │ ├── BaseException.php │ │ ├── InternalServerErrorException.php │ │ └── InvalidRequestException.php │ └── VO │ │ ├── BaseVO.php │ │ ├── PageResponse.php │ │ └── Response.php ├── Contracts │ ├── CacheContract.php │ └── LogContract.php ├── PathManager.php └── Support │ ├── Abstracts │ └── Guzzle │ │ ├── GuzzleClient.php │ │ └── LaravelClient.php │ ├── Annotation │ ├── Base.php │ ├── ClassReflector.php │ ├── Override.php │ └── tags │ │ └── Doc.php │ ├── Converts │ ├── BaseMeter.php │ ├── LengthConvert.php │ ├── TypeConvert.php │ └── WeightConvert.php │ ├── File.php │ ├── FileParser.php │ ├── Lang.php │ ├── Loggers │ └── LaravelLog.php │ ├── Properties │ ├── DocComment.php │ ├── Method.php │ ├── Methods.php │ ├── Properties.php │ └── Property.php │ ├── Traits │ ├── Accessor.php │ ├── AutoFillProperties.php │ ├── AutoRegisterSingleton.php │ ├── Getter.php │ ├── Macroable.php │ ├── Scene.php │ ├── Setter.php │ └── Singleton.php │ └── Utils │ ├── ArrUtil.php │ ├── ExtractUtil.php │ ├── FrameTypeUtil.php │ ├── PerfUtil.php │ ├── SplUtil.php │ ├── StrUtil.php │ └── VerifyUtil.php └── tests ├── Cache.php ├── DTO ├── DTOTest.php ├── ObjDTO.php └── OrderListDTO.php ├── Lang └── LangTest.php ├── Performance └── AccessorTest.php ├── Support ├── AccessorTest.php ├── Annotation │ └── AnnotationTest.php ├── Converts │ ├── LengthConvertTest.php │ └── WeightConvertTest.php ├── DataExtractTest.php ├── Entity │ ├── Cat.php │ ├── Cat8.php │ ├── Eat.php │ ├── Eye.php │ ├── GetterCat.php │ ├── Perf.php │ ├── SetterCat.php │ └── Speak.php ├── EnumTest.php ├── ExceptionTest.php ├── FileNamespaceTest.php └── FillTest.php └── common.php /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | schedule: 9 | - cron: '0 0 * * *' 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | tests: 16 | runs-on: ubuntu-latest 17 | 18 | strategy: 19 | fail-fast: true 20 | matrix: 21 | php: [7.4, 8.1, 8.2, 8.3] 22 | 23 | name: PHP ${{ matrix.php }} 24 | 25 | steps: 26 | - name: Checkout code 27 | uses: actions/checkout@v4 28 | 29 | - name: Setup PHP 30 | uses: shivammathur/setup-php@v2 31 | with: 32 | php-version: ${{ matrix.php }} 33 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite 34 | coverage: none 35 | 36 | - name: Install Composer dependencies 37 | run: composer install --prefer-dist --no-interaction --no-progress 38 | 39 | - name: Execute tests 40 | run: vendor/bin/phpunit 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .buildpath 2 | .settings/ 3 | .project 4 | *.patch 5 | .idea/ 6 | .git/ 7 | runtime/ 8 | vendor/ 9 | .phpintel/ 10 | .env 11 | .DS_Store 12 | *.lock 13 | .phpunit* 14 | *.cache 15 | tests/generate 16 | tests/Storage 17 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | files() 9 | ->name('*.php') 10 | ->exclude('vendor') 11 | ->in(__DIR__) 12 | ->ignoreDotFiles(true) 13 | ->ignoreVCS(true); 14 | 15 | // 官方配置规则文档 https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/doc/rules/index.rst 16 | 17 | $rules = [ 18 | '@PSR2' => true, 19 | '@PSR12' => true, 20 | 'strict_param' => true, 21 | '@Symfony' => true, // 开启 symfony 配置,在官方文档中需要看 @symfony部分 22 | 23 | // Array Notation 24 | 'header_comment' => ['header' => ''], // 文件头注释 25 | 'array_syntax' => ['syntax' => 'short'], 26 | 'no_whitespace_before_comma_in_array' => ['after_heredoc' => true], // 数组中逗号前面没有空格 27 | 'trim_array_spaces' => true, // 去掉数组中多余的空格 28 | 29 | // Basic 30 | 'braces' => [ 31 | 'allow_single_line_anonymous_class_with_empty_body' => true, 32 | 'allow_single_line_closure' => true, 33 | ], 34 | 35 | // Comment 36 | 'no_empty_comment' => true, // 去掉空注释 37 | 38 | // Import 39 | 'ordered_imports' => ['sort_algorithm' => 'length'], // 按顺序use导入 40 | 'no_unused_imports' => true, // 删除没用到的use 41 | 42 | 'no_useless_else' => true, // 删除没有使用的else节点 43 | 'no_useless_return' => true, // 删除没有使用的return语句 44 | 'self_accessor' => true, // 在当前类中使用 self 代替类名 45 | 'php_unit_construct' => true, 46 | 'single_quote' => true, // 简单字符串应该使用单引号代替双引号 47 | 'no_whitespace_in_blank_line' => true, // 删除空行中的空格 48 | 'standardize_not_equals' => true, // 使用 <> 代替 != 49 | 'combine_consecutive_unsets' => true, // 当多个 unset 使用的时候,合并处理 50 | 'concat_space' => ['spacing' => 'one'], // .拼接必须有空格分割 51 | 'array_indentation' => true, // 数组的每个元素必须缩进一次 52 | 'blank_line_before_statement' => [ 53 | 'statements' => [ 54 | 'break', 55 | 'continue', 56 | 'declare', 57 | 'return', 58 | 'throw', 59 | 'try', 60 | ], 61 | ], // 空行换行必须在任何已配置的语句之前 62 | 'binary_operator_spaces' => [ 63 | 'default' => 'align_single_space', 64 | ], //等号对齐、数字箭头符号对齐 65 | 66 | // PHPDoc 67 | 'align_multiline_comment' => ['comment_type' => 'phpdocs_only'], 68 | 'no_blank_lines_after_phpdoc' => true, 69 | 'phpdoc_line_span' => true, 70 | 'no_empty_phpdoc' => true, 71 | 'phpdoc_separation' => false, // 不同注释部分按照单空行隔开 72 | 'phpdoc_indent' => true, 73 | 'no_superfluous_phpdoc_tags' => false, // 删除没有提供有效信息的@param和@return注解 74 | 'phpdoc_single_line_var_spacing' => true, 75 | 'phpdoc_summary' => true, 76 | 'phpdoc_align' => [ 77 | 'align' => 'vertical', 78 | 'tags' => [ 79 | 'param', 'throws', 'type', 'var', 'property', 80 | ], 81 | ], 82 | 'phpdoc_var_annotation_correct_order' => true, 83 | 84 | // Semicolon 85 | 'multiline_whitespace_before_semicolons' => true, 86 | 'no_empty_statement' => true, // 多余的分号 87 | 'no_singleline_whitespace_before_semicolons' => true, // 禁止只有单行空格和分号的写法 88 | 'space_after_semicolon' => ['remove_in_empty_for_expressions' => true], 89 | 90 | 'lowercase_cast' => true, // 类型强制小写 91 | 'constant_case' => true, // 常量为小写 92 | 'lowercase_static_reference' => true, // 静态调用为小写 93 | 'no_blank_lines_after_class_opening' => true, 94 | ]; 95 | 96 | return (new PhpCsFixer\Config()) 97 | ->setRiskyAllowed(true) 98 | ->setRules($rules) 99 | ->setFinder($finder); 100 | -------------------------------------------------------------------------------- /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 | 2 | ![image](./doc/imgs/rice_basic.png) 3 | 4 | [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) 5 | [![github star](https://img.shields.io/github/stars/dmf-code/basic.svg)]('https://github.com/dmf-code/basic/stargazers') 6 | [![github fork](https://img.shields.io/github/forks/dmf-code/basic.svg)]('https://github.com/dmf-code/basic/members') 7 | [![.github/workflows/ci.yml](https://github.com/rice-code/basic/actions/workflows/ci.yml/badge.svg)](https://github.com/rice-code/basic/actions/workflows/ci.yml) 8 | 9 | ## php工具包 (php basic tool) 10 | 11 | 12 | [中文文档](https://rice-code.github.io/zh/) 13 | 14 | ### 安装 15 | 16 | ```shell script 17 | composer require rice/basic 18 | ``` 19 | 20 | ### 功能点 21 | 1. 提供基础框架组件 [锚点](#框架组件) 22 | 2. 参数自动填充 [锚点](#请求参数自动数据填充) 23 | 3. 请求客户端封装 [锚点](#请求客户端封装) 24 | 4. 场景校验 [锚点](#场景校验) 25 | 26 | ### 使用场景 27 | 1. 数组替换为对象进行管理 28 | 2. 转换为对象后需要填充属性,可以使用参数自动填充功能 29 | 3. 封装字段 30 | 31 | ### 框架组件 32 | ```text 33 | BaseAssembler 34 | BaseDTO 35 | BaseEntity 36 | BaseEnum 37 | BaseException 38 | ``` 39 | 40 | ![继承对象关系图解](./doc/imgs/base_relation.png) 41 | 42 | #### Assembler 43 | 数据装配器,主要继承 `BaseAssembler` 类。该层主要是统一将 `DTO` 和 `Entity` 相互转换,如果缺少了 44 | 装配这一层,大部分代码可能就会落在 `Service` 层里面,而且参数这些会比较多,就会造成函数膨胀起来。代码 45 | 整洁的原理就是尽量细分,归类,所以提供装配器接口(面向接口编程而非实现)。 46 | 47 | > 可选,代码重构时可做优化,提高代码可读性 48 | 49 | ```php 50 | setName($request->name) 63 | ->setPassword($request->password); 64 | } 65 | } 66 | 67 | ``` 68 | 69 | #### DTO 70 | 数据传输层对象,主要继承 `BaseDTO` 类。该层主要是聚合业务层中的多个参数变量,保证编写的代码更加整洁, 71 | 并且参数变量更加直观。 72 | 73 | > 采用失血模型,基本上只做数据传输,不存在业务行为 74 | 75 | ![dto](./doc/imgs/dto.png) 76 | 77 | #### Entity 78 | 实体对象目录,主要继承 `BaseEntity` 类,业务逻辑中构建的具体实体模型。继承该抽象类的主体是业务中的 79 | 实体对象,主要考验个人对于建模的能力。这里和数据库的模型区别在于,模型是基于数据表进行建模的,实体是 80 | 基于业务进行建模的。 81 | 82 | > 采用充血模型,提高实体的内聚性 83 | 84 | #### Enum 85 | 枚举类目录,通常存放 `const` 变量, `ReturnCodeEnum` 类,按照阿里巴巴Java手册(泰山版)进行设计。 86 | 87 | ```php 88 | class ReturnCodeEnum extends BaseEnum 89 | implements ClientErrorCode, SystemErrorCode, ServiceErrorCode 90 | { 91 | /** 92 | * @default OK 93 | */ 94 | public const OK = '00000'; 95 | } 96 | ``` 97 | 使用该包,默认强制要求使用枚举类进行定义返回码和异常码。这样子做可以使代码更可读,并且国际化的信息也能够 98 | 与枚举类配合使用。例如: 99 | ```php 100 | /** 101 | * @level 一级宏观错误码 102 | * @zh-CN 用户端错误 103 | */ 104 | public const CLIENT_ERROR = 'A0001'; 105 | ``` 106 | `@zh-CN` 就是中文的描述,具体的标识可以参考国际化地区码。之前有使用过文件配置的方式进行配置结果发现, 107 | 使用起来不方便。需要新建不同地区码文件,而且 `Enum` 类对应相关国际化文件过于分散,导致不直观。现在 108 | 使用注解的形式进行捆绑在一起,变量与国际化信息更加聚合。 109 | 110 | 而且使用自动生成国际化文件可以直接使用 `json` 文件, 相对来说不需要可读性,比使用 `php` 更小。 111 | 112 | 113 | ##### 使用场景 114 | 对接第三方接口会存在请求 `uri` ,大多数时候我们可能会直接写在了 `service` 115 | 类中。这样子写其实就把该变量耦合到该类中了,会导致如果我要做一个并发请求的 116 | `service` 类的话,那么我要么定义多次 `uri` 路由。要么就直接用 `service::const` 117 | 直接从 `service2` 调用 `service1` 的代码。 118 | 为了更好的解耦代码,我们就需要使用到 `Enum` 类,因为枚举类只保存数据,而没有 119 | 业务行为,所以可以给多个 `service` 进行调用。 120 | 121 | > 为变量调用,提供解耦 122 | 123 | #### Exception 124 | 异常类目录, 与 `Enum` 类配合使用。按照功能模块等进行类的细化,做到单一责任。这样 125 | 可以更好的在异常抛出后做出不同的兜底措施。 126 | 127 | 推荐将所有异常相关的抛出都封装到该类进行抛出使用,方便统一管理异常。 128 | ```php 129 | `Accessor` 类默认 `setter`, `getter` 都启用,如果只需要 `setter` 183 | > 或者 `getter` 的话,可以再 `use Setter` 或 `use Getter` 184 | 185 | ##### bad 186 | ```php 187 | $cat->speak; 188 | ``` 189 | 190 | ##### better 191 | ```php 192 | $cat->getSpeak(); 193 | $cat->setSpeak($val); 194 | ``` 195 | 196 | > 面向对象三大特性之一封装,即隐藏对象内部数据的能力。如果都是公共属性的话, 197 | > 就会造成该对象没有任何限制的进行获取和修改属性数据,导致后续维护变得复杂。 198 | 199 | #### 注解使用 200 | 201 | ```php 202 | class Cat 203 | { 204 | use AutoFillProperties; 205 | 206 | /** 207 | * @var string 208 | */ 209 | public $eyes; 210 | 211 | /** 212 | * @var Eat 213 | */ 214 | public $eat; 215 | 216 | /** 217 | * @var Speak 218 | */ 219 | public $speak; 220 | 221 | /** 222 | * @var string[] 223 | */ 224 | public $hair; 225 | } 226 | ``` 227 | 228 | ##### php8 支持使用内置注解 229 | 230 | ```php 231 | class Cat 232 | { 233 | use AutoFillProperties; 234 | 235 | #[Doc(var: 'Eye[]', text: '眼睛')] 236 | public $eyes; 237 | 238 | #[Doc(var: 'Eat')] 239 | public $eat; 240 | 241 | #[Doc(var: 'Speak')] 242 | public $speak; 243 | 244 | #[Doc(var: 'string[]')] 245 | public $hair; 246 | } 247 | ``` 248 | 249 | 250 | 引入 `AutoFillProperties` 类,然后使用 `@var` 进行编写注解,第一个参数是变量类型,第二个就是注释。这里面 251 | 实现原理是使用类反射获取到相关注释的内容,正则进行匹配相关的值。最后判断这个类型是系统类型还是自定义类,是类的 252 | 话就需要读取文件的命名空间,获取到相关对象的命名空间,从而实例化对象。这里面提供了缓存,因为类的改动只会在编写 253 | 时经常变动。 254 | 255 | #### 请求参数自动数据填充 256 | `Laravel` 和 `Tp` 框架现在都支持自定义 `Request` 对象,所以这里我们可以定义所有的入参对象。然后使用 `basic` 257 | 包的 `AutoFillProperties` 类就能实现参数自动填充到 `Request` 对象的类属性中去了。 258 | 259 | `trait` `AutoFillProperties` 已使用类属性,使用该类必须避免重写问题。 260 | 261 | `src/Entity/FrameEntity.php`: 262 | 263 | ```php 264 | private static array $_filter = [ 265 | '_setter', 266 | '_getter', 267 | '_readOnly', 268 | '_params', 269 | '_properties', 270 | '_alias', 271 | '_cache', 272 | '_idx', 273 | ]; 274 | ``` 275 | 276 | `Laravel` 例子: 277 | 278 | ```php 279 | all()); 351 | $testRequest->check(); 352 | $testLogic = (new TestLogic()); 353 | 354 | $dto = TestAssembler::toDTO($request); 355 | $resp = $testLogic->doSomethink($dto); 356 | 357 | return Response::json($resp); 358 | } 359 | } 360 | ``` 361 | 362 | 这里面实例化 `TestRequest` 需要将全部参数作为参数,然后请求的参数命名默认采用需要采用蛇形,因为前端大部分是 363 | 蛇形命名规范。这里面默认会转为驼峰进行匹配 `TestRequest` 变量进行赋值。 364 | 365 | > Request 对象相当于是一个防腐层一样,一个业务中会存在展示,修改,删除等功能。每一部分参数都有些许不一致,但 366 | > 是不可能给增删改查单独写一个 Request 类,不然编码上面太多类了。 367 | 368 | #### 请求客户端封装 369 | `GuzzleClient` 包通用逻辑封装 370 | 371 | `Support/Abstracts/Guzzle/GuzzleClient.php` 372 | 373 | 在 `GuzzleClient` 之上再抽离一个匹配对应框架的客户端 `LaravelClient` 374 | `Support/Abstracts/Guzzle/LaravelClient.php` 375 | 376 | 因为 `GuzzleClient` 需要实例化的日志对象,所以需要适配不同的框架,可以类似 377 | `LaravelClient` 实现。 378 | 379 | ```php 380 | abstract class LaravelClient extends GuzzleClient 381 | { 382 | public static function build() 383 | { 384 | return new static(LaravelLog::build()); 385 | } 386 | } 387 | ``` 388 | 389 | > 具体的日志实现可参考 `Support/Loggers/LaravelLog.php` 390 | 391 | ##### 使用场景 392 | 该 `client` 只是对 `Guzzle` 包的业务封装,所以使用上与 `Guzzle` 无异。 393 | 比如我们可以使用 `$this->options` 提前设置通用属性。`setCallback`函数 394 | 必须要实现,这个是判断请求在业务上是否成功的标识。与 `isSuccess` 函数配套使用, 395 | 这样子就能把重复的逻辑抽象出来,只处理变化的部分。 396 | 397 | ```php 398 | class DouYinClient extends LaravelClient 399 | { 400 | // 通用初始化,可调用基类的 options 属性等 401 | public function init(): void 402 | { 403 | $this->options[RequestOptions::JSON] = [ 404 | 'app_id' => DouYinEnum::APP_ID, 405 | 'secret' => DouYinEnum::SECRET, 406 | 'auth_code' => DouYinEnum::AUTH_CODE, 407 | ]; 408 | } 409 | 410 | /** 411 | * @throws GuzzleException 412 | * @throws ClientException 413 | */ 414 | public function accessToken() 415 | { 416 | if ($accessToken = Redis::get(DouYinEnum::CACHE_KEY)) { 417 | return json_decode($accessToken, true); 418 | } 419 | 420 | $url = DouYinEnum::DOMAIN_URL.DouYinEnum::ACCESS_TOKEN_URL; 421 | 422 | $this->mergeOption( 423 | RequestOptions::JSON, 424 | [ 425 | 'grant_type' => DouYinEnum::getGrantType(DouYinEnum::ACCESS_TOKEN_URL) 426 | ] 427 | ); 428 | 429 | return $this->handle($url); 430 | } 431 | 432 | /** 433 | * @throws GuzzleException 434 | * @throws ClientException|JsonException 435 | */ 436 | public function refreshToken($refreshToken): array 437 | { 438 | $url = DouYinEnum::DOMAIN_URL.DouYinEnum::REFRESH_TOKEN_URL; 439 | 440 | $time = time(); 441 | 442 | $this->mergeOption( 443 | RequestOptions::JSON, 444 | [ 445 | 'grant_type' => DouYinEnum::getGrantType(DouYinEnum::REFRESH_TOKEN_URL), 446 | 'refresh_token' => $refreshToken, 447 | ] 448 | ); 449 | 450 | $data = $this->handle($url); 451 | 452 | $data['time'] = $time; 453 | Redis::set(DouYinEnum::CACHE_KEY, json_encode($data, JSON_UNESCAPED_UNICODE)); 454 | return $data; 455 | } 456 | 457 | /** 458 | * @param string $url 459 | * @return array 460 | * @throws ClientException 461 | * @throws GuzzleException 462 | */ 463 | private function handle(string $url): array 464 | { 465 | $this->setCallback(function (?ResponseInterface $response) { 466 | if (!$response) { 467 | return false; 468 | } 469 | 470 | $res = json_decode($response->getBody(), true); 471 | return $res['code'] === 0; 472 | }); 473 | 474 | $res = $this->client->post($url, $this->options); 475 | 476 | if (!$this->isSuccess()) { 477 | throw new ClientException('请求失败'); 478 | } 479 | return json_decode($res, true)['data']; 480 | } 481 | } 482 | ``` 483 | 484 | > tip: 请求的逻辑都要在该client类中实现,比如我有获取token和刷新token的请求, 485 | > 那么全部逻辑应该集中到该 `DouYinClient` 类中。这样子做业务上更加内聚,影响 486 | > 范围不会扩散。 487 | 488 | #### 场景校验 489 | 490 | 支持 `Laravel` 自定义 `Request` 使用场景校验规则,只要引入 `Scene` `traits` 类。 491 | 自动使用控制器的方法名称作为场景 `key` 进行注入。未定义相关场景 `key` 则按照 `rules` 492 | 定义执行全部规则校验。 493 | 494 | ```php 495 | 'required|max:1', 524 | 'auth_code' => 'required|max:2' 525 | ]; 526 | } 527 | 528 | public function scenes() 529 | { 530 | return [ 531 | 'callback' => [] 532 | ]; 533 | } 534 | } 535 | 536 | ``` 537 | 538 | ### 配套工具 539 | 540 | > 配合工具包使用更佳 541 | 542 | #### rice/ctl 543 | 544 | 1. setting, getting 注释生成命令 [锚点](https://github.com/rice-code/ctl#%E8%AE%BF%E9%97%AE%E5%99%A8%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90%E6%B3%A8%E9%87%8A) 545 | 2. json 转 class 对象命令 [锚点](https://github.com/rice-code/ctl#json-%E8%BD%AC-class-%E5%AF%B9%E8%B1%A1) 546 | 3. 多语言国际化(i18n) [锚点](https://github.com/rice-code/ctl#i18n-缓存生成) 547 | 548 | ```shell script 549 | composer require rice/ctl 550 | ``` 551 | 552 | 553 | ### 相关链接 554 | 555 | [创建属于自己的 composer 包](https://dmf-code.github.io/posts/54650cde2a44/) 556 | 557 | [国际化地区码](./doc/国际化地区码.md) 558 | 559 | [阿里巴巴Java手册(泰山版)](https://developer.aliyun.com/article/766288) 560 | 561 | ## Star History 562 | 563 | [![Star History Chart](https://api.star-history.com/svg?repos=dmf-code/basic&type=Date)](https://star-history.com/#dmf-code/basic&Date) 564 | 565 | 566 | ### 感谢 JetBrains 赞助 567 | 568 | ![](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg) 569 | 570 | [免费许可证计划](https://www.jetbrains.com.cn/community/opensource/#support) 571 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rice/basic", 3 | "description": "basic tool", 4 | "type": "library", 5 | "license": "Apache-2.0", 6 | "keywords": [ 7 | "php", 8 | "tools", 9 | "basic", 10 | "Clean code" 11 | ], 12 | "require": { 13 | "php": ">=7.4", 14 | "ext-json": "*", 15 | "ext-bcmath": "*", 16 | "guzzlehttp/guzzle": "^6.0 || ^7.0", 17 | "nesbot/carbon": "2.*" 18 | }, 19 | "require-dev": { 20 | "phpunit/phpunit": "^9" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "Rice\\Basic\\": "src/" 25 | }, 26 | "files": [ 27 | ] 28 | }, 29 | "autoload-dev": { 30 | "psr-4": { 31 | "Tests\\": "tests/" 32 | }, 33 | "files": [ 34 | "tests/common.php" 35 | ] 36 | }, 37 | "authors": [ 38 | { 39 | "name": "dengmf", 40 | "email": "1015814408@qq.com" 41 | } 42 | ], 43 | "minimum-stability": "dev", 44 | "scripts": { 45 | "test": [ 46 | "phpunit" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /doc/imgs/base_relation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rice-code/basic/d883e495bf252d21032077dae57acdaead25983d/doc/imgs/base_relation.png -------------------------------------------------------------------------------- /doc/imgs/basic_relation.excalidraw: -------------------------------------------------------------------------------- 1 | { 2 | "type": "excalidraw", 3 | "version": 2, 4 | "source": "https://excalidraw.com", 5 | "elements": [ 6 | { 7 | "id": "AIjcx6PXpwaL0HqFjUqsH", 8 | "type": "rectangle", 9 | "x": 480.9999694824219, 10 | "y": 313.9999542236328, 11 | "width": 300.79998779296875, 12 | "height": 95.00000000000001, 13 | "angle": 0, 14 | "strokeColor": "#000000", 15 | "backgroundColor": "transparent", 16 | "fillStyle": "hachure", 17 | "strokeWidth": 2, 18 | "strokeStyle": "solid", 19 | "roughness": 1, 20 | "opacity": 100, 21 | "groupIds": [], 22 | "roundness": { 23 | "type": 3 24 | }, 25 | "seed": 1596619318, 26 | "version": 370, 27 | "versionNonce": 819552118, 28 | "isDeleted": false, 29 | "boundElements": [ 30 | { 31 | "type": "text", 32 | "id": "4WYusmdoJomaGIZK7mH5T" 33 | }, 34 | { 35 | "id": "pqQWoxUEPIBfz7mIStAyY", 36 | "type": "arrow" 37 | } 38 | ], 39 | "updated": 1676454621499, 40 | "link": null, 41 | "locked": false 42 | }, 43 | { 44 | "id": "4WYusmdoJomaGIZK7mH5T", 45 | "type": "text", 46 | "x": 513.8999633789062, 47 | "y": 348.9999542236328, 48 | "width": 235, 49 | "height": 25, 50 | "angle": 0, 51 | "strokeColor": "#000000", 52 | "backgroundColor": "transparent", 53 | "fillStyle": "hachure", 54 | "strokeWidth": 1, 55 | "strokeStyle": "solid", 56 | "roughness": 1, 57 | "opacity": 100, 58 | "groupIds": [], 59 | "roundness": null, 60 | "seed": 1869469622, 61 | "version": 31, 62 | "versionNonce": 1804008170, 63 | "isDeleted": false, 64 | "boundElements": null, 65 | "updated": 1676454621499, 66 | "link": null, 67 | "locked": false, 68 | "text": "Abstract class (抽象类)", 69 | "fontSize": 20, 70 | "fontFamily": 1, 71 | "textAlign": "center", 72 | "verticalAlign": "middle", 73 | "baseline": 18, 74 | "containerId": "AIjcx6PXpwaL0HqFjUqsH", 75 | "originalText": "Abstract class (抽象类)" 76 | }, 77 | { 78 | "type": "arrow", 79 | "version": 1252, 80 | "versionNonce": 550475562, 81 | "isDeleted": false, 82 | "id": "pqQWoxUEPIBfz7mIStAyY", 83 | "fillStyle": "hachure", 84 | "strokeWidth": 1, 85 | "strokeStyle": "solid", 86 | "roughness": 1, 87 | "opacity": 100, 88 | "angle": 4.728849924822775, 89 | "x": 568.5950640879496, 90 | "y": 488.6144585798876, 91 | "strokeColor": "#000000", 92 | "backgroundColor": "transparent", 93 | "width": 119.25154941918166, 94 | "height": 1.8439834955960195, 95 | "seed": 1410079734, 96 | "groupIds": [ 97 | "Rz80k_x8menoI8xqoqKe1" 98 | ], 99 | "roundness": { 100 | "type": 2 101 | }, 102 | "boundElements": null, 103 | "updated": 1676454621499, 104 | "link": null, 105 | "locked": false, 106 | "startBinding": { 107 | "elementId": "0kwVXTPjo1ThiY9_xE8xy", 108 | "focus": -0.04817634009306097, 109 | "gap": 2.0658151061896888 110 | }, 111 | "endBinding": { 112 | "elementId": "AIjcx6PXpwaL0HqFjUqsH", 113 | "focus": 0.014396265689232253, 114 | "gap": 19.66836283719084 115 | }, 116 | "lastCommittedPoint": null, 117 | "startArrowhead": null, 118 | "endArrowhead": null, 119 | "points": [ 120 | [ 121 | 0, 122 | 0 123 | ], 124 | [ 125 | 119.25154941918166, 126 | -1.8439834955960195 127 | ] 128 | ] 129 | }, 130 | { 131 | "type": "line", 132 | "version": 771, 133 | "versionNonce": 1190676598, 134 | "isDeleted": false, 135 | "id": "IlL07hSz8JjrWaDjmQ1eb", 136 | "fillStyle": "hachure", 137 | "strokeWidth": 1, 138 | "strokeStyle": "solid", 139 | "roughness": 1, 140 | "opacity": 100, 141 | "angle": 4.728849924822775, 142 | "x": 629.523850766605, 143 | "y": 420.7614409328961, 144 | "strokeColor": "#000000", 145 | "backgroundColor": "transparent", 146 | "width": 0.5833457446028305, 147 | "height": 14.95349747853589, 148 | "seed": 1464616554, 149 | "groupIds": [ 150 | "Rz80k_x8menoI8xqoqKe1" 151 | ], 152 | "roundness": { 153 | "type": 2 154 | }, 155 | "boundElements": null, 156 | "updated": 1676454621499, 157 | "link": null, 158 | "locked": false, 159 | "startBinding": null, 160 | "endBinding": null, 161 | "lastCommittedPoint": null, 162 | "startArrowhead": null, 163 | "endArrowhead": null, 164 | "points": [ 165 | [ 166 | 0, 167 | 0 168 | ], 169 | [ 170 | -0.5833457446028305, 171 | 14.95349747853589 172 | ] 173 | ] 174 | }, 175 | { 176 | "type": "line", 177 | "version": 756, 178 | "versionNonce": 2045190634, 179 | "isDeleted": false, 180 | "id": "N3GWEkx8yOvumJngDWx8G", 181 | "fillStyle": "hachure", 182 | "strokeWidth": 1, 183 | "strokeStyle": "solid", 184 | "roughness": 1, 185 | "opacity": 100, 186 | "angle": 4.728849924822775, 187 | "x": 616.8479478045257, 188 | "y": 416.3994173305315, 189 | "strokeColor": "#000000", 190 | "backgroundColor": "transparent", 191 | "width": 15.19344919984339, 192 | "height": 8.057927911592065, 193 | "seed": 1196502326, 194 | "groupIds": [ 195 | "Rz80k_x8menoI8xqoqKe1" 196 | ], 197 | "roundness": { 198 | "type": 2 199 | }, 200 | "boundElements": null, 201 | "updated": 1676454621499, 202 | "link": null, 203 | "locked": false, 204 | "startBinding": null, 205 | "endBinding": null, 206 | "lastCommittedPoint": null, 207 | "startArrowhead": null, 208 | "endArrowhead": null, 209 | "points": [ 210 | [ 211 | 0, 212 | 0 213 | ], 214 | [ 215 | 15.19344919984339, 216 | 8.057927911592065 217 | ] 218 | ] 219 | }, 220 | { 221 | "type": "line", 222 | "version": 786, 223 | "versionNonce": 1426311606, 224 | "isDeleted": false, 225 | "id": "imUe8LXkGERj189C8Y53c", 226 | "fillStyle": "hachure", 227 | "strokeWidth": 1, 228 | "strokeStyle": "solid", 229 | "roughness": 1, 230 | "opacity": 100, 231 | "angle": 4.728849924822775, 232 | "x": 639.3507241420552, 233 | "y": 415.69239186670427, 234 | "strokeColor": "#000000", 235 | "backgroundColor": "transparent", 236 | "width": 14.218430211379896, 237 | "height": 7.519265799517552, 238 | "seed": 1938549034, 239 | "groupIds": [ 240 | "Rz80k_x8menoI8xqoqKe1" 241 | ], 242 | "roundness": { 243 | "type": 2 244 | }, 245 | "boundElements": null, 246 | "updated": 1676454621499, 247 | "link": null, 248 | "locked": false, 249 | "startBinding": null, 250 | "endBinding": null, 251 | "lastCommittedPoint": null, 252 | "startArrowhead": null, 253 | "endArrowhead": null, 254 | "points": [ 255 | [ 256 | 0, 257 | 0 258 | ], 259 | [ 260 | -14.218430211379896, 261 | 7.519265799517552 262 | ] 263 | ] 264 | }, 265 | { 266 | "id": "0kwVXTPjo1ThiY9_xE8xy", 267 | "type": "rectangle", 268 | "x": 447.1998596191406, 269 | "y": 549.9998779296875, 270 | "width": 382, 271 | "height": 100, 272 | "angle": 0, 273 | "strokeColor": "#000000", 274 | "backgroundColor": "transparent", 275 | "fillStyle": "hachure", 276 | "strokeWidth": 1, 277 | "strokeStyle": "solid", 278 | "roughness": 1, 279 | "opacity": 100, 280 | "groupIds": [], 281 | "roundness": { 282 | "type": 3 283 | }, 284 | "seed": 2027502262, 285 | "version": 329, 286 | "versionNonce": 1161778346, 287 | "isDeleted": false, 288 | "boundElements": [ 289 | { 290 | "type": "text", 291 | "id": "61MwYgCjr3D9PvpEPvY6_" 292 | }, 293 | { 294 | "id": "pqQWoxUEPIBfz7mIStAyY", 295 | "type": "arrow" 296 | } 297 | ], 298 | "updated": 1676454621499, 299 | "link": null, 300 | "locked": false 301 | }, 302 | { 303 | "id": "61MwYgCjr3D9PvpEPvY6_", 304 | "type": "text", 305 | "x": 563.1998596191406, 306 | "y": 587.4998779296875, 307 | "width": 150, 308 | "height": 25, 309 | "angle": 0, 310 | "strokeColor": "#000000", 311 | "backgroundColor": "transparent", 312 | "fillStyle": "hachure", 313 | "strokeWidth": 1, 314 | "strokeStyle": "solid", 315 | "roughness": 1, 316 | "opacity": 100, 317 | "groupIds": [], 318 | "roundness": null, 319 | "seed": 587176234, 320 | "version": 130, 321 | "versionNonce": 1457522550, 322 | "isDeleted": false, 323 | "boundElements": null, 324 | "updated": 1676454647688, 325 | "link": null, 326 | "locked": false, 327 | "text": "Subclass (子类)", 328 | "fontSize": 20, 329 | "fontFamily": 1, 330 | "textAlign": "center", 331 | "verticalAlign": "middle", 332 | "baseline": 18, 333 | "containerId": "0kwVXTPjo1ThiY9_xE8xy", 334 | "originalText": "Subclass (子类)" 335 | }, 336 | { 337 | "id": "L-K-QvPc_0g-SXR9vUTwX", 338 | "type": "text", 339 | "x": 875.7999877929688, 340 | "y": 282, 341 | "width": 144, 342 | "height": 25, 343 | "angle": 0, 344 | "strokeColor": "#000000", 345 | "backgroundColor": "transparent", 346 | "fillStyle": "hachure", 347 | "strokeWidth": 1, 348 | "strokeStyle": "solid", 349 | "roughness": 1, 350 | "opacity": 100, 351 | "groupIds": [], 352 | "roundness": null, 353 | "seed": 2005931690, 354 | "version": 73, 355 | "versionNonce": 471422006, 356 | "isDeleted": false, 357 | "boundElements": null, 358 | "updated": 1676454621499, 359 | "link": null, 360 | "locked": false, 361 | "text": "BaseAssembler", 362 | "fontSize": 20, 363 | "fontFamily": 1, 364 | "textAlign": "left", 365 | "verticalAlign": "top", 366 | "baseline": 18, 367 | "containerId": null, 368 | "originalText": "BaseAssembler" 369 | }, 370 | { 371 | "id": "cCKBt3J24nGS5yfZJHIgW", 372 | "type": "text", 373 | "x": 875.7999877929688, 374 | "y": 317, 375 | "width": 97, 376 | "height": 25, 377 | "angle": 0, 378 | "strokeColor": "#000000", 379 | "backgroundColor": "transparent", 380 | "fillStyle": "hachure", 381 | "strokeWidth": 1, 382 | "strokeStyle": "solid", 383 | "roughness": 1, 384 | "opacity": 100, 385 | "groupIds": [], 386 | "roundness": null, 387 | "seed": 438403318, 388 | "version": 73, 389 | "versionNonce": 1274917418, 390 | "isDeleted": false, 391 | "boundElements": null, 392 | "updated": 1676454621499, 393 | "link": null, 394 | "locked": false, 395 | "text": "BaseDTO", 396 | "fontSize": 20, 397 | "fontFamily": 1, 398 | "textAlign": "left", 399 | "verticalAlign": "top", 400 | "baseline": 18, 401 | "containerId": null, 402 | "originalText": "BaseDTO" 403 | }, 404 | { 405 | "id": "9jUoYupsTJND_-O86_d6C", 406 | "type": "text", 407 | "x": 875.7999877929688, 408 | "y": 352, 409 | "width": 110, 410 | "height": 25, 411 | "angle": 0, 412 | "strokeColor": "#000000", 413 | "backgroundColor": "transparent", 414 | "fillStyle": "hachure", 415 | "strokeWidth": 1, 416 | "strokeStyle": "solid", 417 | "roughness": 1, 418 | "opacity": 100, 419 | "groupIds": [], 420 | "roundness": null, 421 | "seed": 862695786, 422 | "version": 73, 423 | "versionNonce": 1099783542, 424 | "isDeleted": false, 425 | "boundElements": null, 426 | "updated": 1676454621499, 427 | "link": null, 428 | "locked": false, 429 | "text": "BaseEntity", 430 | "fontSize": 20, 431 | "fontFamily": 1, 432 | "textAlign": "left", 433 | "verticalAlign": "top", 434 | "baseline": 18, 435 | "containerId": null, 436 | "originalText": "BaseEntity" 437 | }, 438 | { 439 | "id": "aithtQr4TAdfavDlpJtET", 440 | "type": "text", 441 | "x": 875.7999877929688, 442 | "y": 387, 443 | "width": 98, 444 | "height": 25, 445 | "angle": 0, 446 | "strokeColor": "#000000", 447 | "backgroundColor": "transparent", 448 | "fillStyle": "hachure", 449 | "strokeWidth": 1, 450 | "strokeStyle": "solid", 451 | "roughness": 1, 452 | "opacity": 100, 453 | "groupIds": [], 454 | "roundness": null, 455 | "seed": 1332867638, 456 | "version": 73, 457 | "versionNonce": 1855577322, 458 | "isDeleted": false, 459 | "boundElements": null, 460 | "updated": 1676454621499, 461 | "link": null, 462 | "locked": false, 463 | "text": "BaseEnum", 464 | "fontSize": 20, 465 | "fontFamily": 1, 466 | "textAlign": "left", 467 | "verticalAlign": "top", 468 | "baseline": 18, 469 | "containerId": null, 470 | "originalText": "BaseEnum" 471 | }, 472 | { 473 | "id": "hEm47u7yTevPIph6F8O6s", 474 | "type": "text", 475 | "x": 875.7999877929688, 476 | "y": 422, 477 | "width": 142, 478 | "height": 25, 479 | "angle": 0, 480 | "strokeColor": "#000000", 481 | "backgroundColor": "transparent", 482 | "fillStyle": "hachure", 483 | "strokeWidth": 1, 484 | "strokeStyle": "solid", 485 | "roughness": 1, 486 | "opacity": 100, 487 | "groupIds": [], 488 | "roundness": null, 489 | "seed": 243659818, 490 | "version": 73, 491 | "versionNonce": 1177435830, 492 | "isDeleted": false, 493 | "boundElements": null, 494 | "updated": 1676454621499, 495 | "link": null, 496 | "locked": false, 497 | "text": "BaseException", 498 | "fontSize": 20, 499 | "fontFamily": 1, 500 | "textAlign": "left", 501 | "verticalAlign": "top", 502 | "baseline": 18, 503 | "containerId": null, 504 | "originalText": "BaseException" 505 | } 506 | ], 507 | "appState": { 508 | "gridSize": null, 509 | "viewBackgroundColor": "#ffffff" 510 | }, 511 | "files": {} 512 | } -------------------------------------------------------------------------------- /doc/imgs/dto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rice-code/basic/d883e495bf252d21032077dae57acdaead25983d/doc/imgs/dto.png -------------------------------------------------------------------------------- /doc/imgs/rice_basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rice-code/basic/d883e495bf252d21032077dae57acdaead25983d/doc/imgs/rice_basic.png -------------------------------------------------------------------------------- /doc/国际化地区码.md: -------------------------------------------------------------------------------- 1 | 2 | | 语言代码 | 国家/ 地区 | 3 | | --- | --- | 4 | | af | 公用荷兰语 | 5 | | af-ZA | 公用荷兰语 - 南非 | 6 | | sq | 阿尔巴尼亚 | 7 | | sq-AL | 阿尔巴尼亚 -阿尔巴尼亚 | 8 | | ar | 阿拉伯语 | 9 | | ar-DZ | 阿拉伯语 -阿尔及利亚 | 10 | | ar-BH | 阿拉伯语 -巴林 | 11 | | ar-EG | 阿拉伯语 -埃及 | 12 | | ar-IQ | 阿拉伯语 -伊拉克 | 13 | | ar-JO | 阿拉伯语 -约旦 | 14 | | ar-KW | 阿拉伯语 -科威特 | 15 | | ar-LB | 阿拉伯语 -黎巴嫩 | 16 | | ar-LY | 阿拉伯语 -利比亚 | 17 | | ar-MA | 阿拉伯语 -摩洛哥 | 18 | | ar-OM | 阿拉伯语 -阿曼 | 19 | | ar-QA | 阿拉伯语 -卡塔尔 | 20 | | ar-SA | 阿拉伯语 - 沙特阿拉伯 | 21 | | ar-SY | 阿拉伯语 -叙利亚共和国 | 22 | | ar-TN | 阿拉伯语 -北非的共和国 | 23 | | ar-AE | 阿拉伯语 - 阿拉伯联合酋长国 | 24 | | ar-YE | 阿拉伯语 -也门 | 25 | | hy | 亚美尼亚 | 26 | | hy-AM | 亚美尼亚的 -亚美尼亚 | 27 | | az | Azeri | 28 | | az-AZ-Cyrl | Azeri-(西里尔字母的) 阿塞拜疆 | 29 | | az-AZ-Latn | Azeri(拉丁文)- 阿塞拜疆 | 30 | | eu | 巴斯克 | 31 | | eu-ES | 巴斯克 -巴斯克 | 32 | | be | Belarusian | 33 | | be-BY | Belarusian-白俄罗斯 | 34 | | bg | 保加利亚 | 35 | | bg-BG | 保加利亚 -保加利亚 | 36 | | ca | 嘉泰罗尼亚 | 37 | | ca-ES | 嘉泰罗尼亚 -嘉泰罗尼亚 | 38 | | zh-HK | 华 - 香港的 SAR | 39 | | zh-MO | 华 - 澳门的 SAR | 40 | | zh-CN | 华 -中国 | 41 | | zh-CHS | 华 (单一化) | 42 | | zh-SG | 华 -新加坡 | 43 | | zh-TW | 华 -台湾 | 44 | | zh-CHT | 华 (传统的) | 45 | | hr | 克罗埃西亚 | 46 | | hr-HR | 克罗埃西亚 -克罗埃西亚 | 47 | | cs | 捷克 | 48 | | cs-CZ | 捷克 - 捷克 | 49 | | da | 丹麦文 | 50 | | da-DK | 丹麦文 -丹麦 | 51 | | div | Dhivehi | 52 | | div-MV | Dhivehi-马尔代夫 | 53 | | nl | 荷兰 | 54 | | nl-BE | 荷兰 -比利时 | 55 | | nl-NL | 荷兰 - 荷兰 | 56 | | en | 英国 | 57 | | en-AU | 英国 -澳洲 | 58 | | en-BZ | 英国 -伯利兹 | 59 | | en-CA | 英国 -加拿大 | 60 | | en-CB | 英国 -加勒比海 | 61 | | en-IE | 英国 -爱尔兰 | 62 | | en-JM | 英国 -牙买加 | 63 | | en-NZ | 英国 - 新西兰 | 64 | | en-PH | 英国 -菲律宾共和国 | 65 | | en-ZA | 英国 - 南非 | 66 | | en-TT | 英国 - 千里达托贝哥共和国 | 67 | | en-GB | 英国 - 英国 | 68 | | en-US | 英国 - 美国 | 69 | | en-ZW | 英国 -津巴布韦 | 70 | | et | 爱沙尼亚 | 71 | | et-EE | 爱沙尼亚的 -爱沙尼亚 | 72 | | fo | Faroese | 73 | | fo-FO | Faroese- 法罗群岛 | 74 | | fa | 波斯语 | 75 | | fa-IR | 波斯语 -伊朗王国 | 76 | | fi | 芬兰语 | 77 | | fi-FI | 芬兰语 -芬兰 | 78 | | fr | 法国 | 79 | | fr-BE | 法国 -比利时 | 80 | | fr-CA | 法国 -加拿大 | 81 | | fr-FR | 法国 -法国 | 82 | | fr-LU | 法国 -卢森堡 | 83 | | fr-MC | 法国 -摩纳哥 | 84 | | fr-CH | 法国 -瑞士 | 85 | | gl | 加利西亚 | 86 | | gl-ES | 加利西亚 -加利西亚 | 87 | | ka | 格鲁吉亚州 | 88 | | ka-GE | 格鲁吉亚州 -格鲁吉亚州 | 89 | | de | 德国 | 90 | | de-AT | 德国 -奥地利 | 91 | | de-DE | 德国 -德国 | 92 | | de-LI | 德国 -列支敦士登 | 93 | | de-LU | 德国 -卢森堡 | 94 | | de-CH | 德国 -瑞士 | 95 | | el | 希腊 | 96 | | el-GR | 希腊 -希腊 | 97 | | gu | Gujarati | 98 | | gu-IN | Gujarati-印度 | 99 | | he | 希伯来 | 100 | | he-IL | 希伯来 -以色列 | 101 | | hi | 北印度语 | 102 | | hi-IN | 北印度的 -印度 | 103 | | hu | 匈牙利 | 104 | | hu-HU | 匈牙利的 -匈牙利 | 105 | | is | 冰岛语 | 106 | | is-IS | 冰岛的 -冰岛 | 107 | | id | 印尼 | 108 | | id-ID | 印尼 -印尼 | 109 | | it | 意大利 | 110 | | it-IT | 意大利 -意大利 | 111 | | it-CH | 意大利 -瑞士 | 112 | | ja | 日本 | 113 | | ja-JP | 日本 -日本 | 114 | | kn | 卡纳达语 | 115 | | kn-IN | 卡纳达语 -印度 | 116 | | kk | Kazakh | 117 | | kk-KZ | Kazakh-哈萨克 | 118 | | kok | Konkani | 119 | | kok-IN | Konkani-印度 | 120 | | ko | 韩国 | 121 | | ko-KR | 韩国 -韩国 | 122 | | ky | Kyrgyz | 123 | | ky-KZ | Kyrgyz-哈萨克 | 124 | | lv | 拉脱维亚 | 125 | | lv-LV | 拉脱维亚的 -拉脱维亚 | 126 | | lt | 立陶宛 | 127 | | lt-LT | 立陶宛 -立陶宛 | 128 | | mk | 马其顿 | 129 | | mk-MK | 马其顿 -FYROM | 130 | | ms | 马来 | 131 | | ms-BN | 马来 -汶莱 | 132 | | ms-MY | 马来 -马来西亚 | 133 | | mr | 马拉地语 | 134 | | mr-IN | 马拉地语 -印度 | 135 | | mn | 蒙古 | 136 | | mn-MN | 蒙古 -蒙古 | 137 | | no | 挪威 | 138 | | nb-NO | 挪威 (Bokm?l) - 挪威 | 139 | | nn-NO | 挪威 (Nynorsk)- 挪威 | 140 | | pl | 波兰 | 141 | | pl-PL | 波兰 -波兰 | 142 | | pt | 葡萄牙 | 143 | | pt-BR | 葡萄牙 -巴西 | 144 | | pt-PT | 葡萄牙 -葡萄牙 | 145 | | pa | Punjab 语 | 146 | | pa-IN | Punjab 语 -印度 | 147 | | ro | 罗马尼亚语 | 148 | | ro-RO | 罗马尼亚语 -罗马尼亚 | 149 | | ru | 俄国 | 150 | | ru-RU | 俄国 -俄国 | 151 | | sa | 梵文 | 152 | | sa-IN | 梵文 -印度 | 153 | | sr-SP-Cyrl | 塞尔维亚 -(西里尔字母的) 塞尔 | 154 | | sr-SP-Latn | 塞尔维亚 (拉丁文)- 塞尔维亚共 | 155 | | sk | 斯洛伐克 | 156 | | sk-SK | 斯洛伐克 -斯洛伐克 | 157 | | sl | 斯洛文尼亚 | 158 | | sl-SI | 斯洛文尼亚 -斯洛文尼亚 | 159 | | es | 西班牙 | 160 | | es-AR | 西班牙 -阿根廷 | 161 | | es-BO | 西班牙 -玻利维亚 | 162 | | es-CL | 西班牙 -智利 | 163 | | es-CO | 西班牙 -哥伦比亚 | 164 | | es-CR | 西班牙 - 哥斯达黎加 | 165 | | es-DO | 西班牙 - 多米尼加共和国 | 166 | | es-EC | 西班牙 -厄瓜多尔 | 167 | | es-SV | 西班牙 - 萨尔瓦多 | 168 | | es-GT | 西班牙 -危地马拉 | 169 | | es-HN | 西班牙 -洪都拉斯 | 170 | | es-MX | 西班牙 -墨西哥 | 171 | | es-NI | 西班牙 -尼加拉瓜 | 172 | | es-PA | 西班牙 -巴拿马 | 173 | | es-PY | 西班牙 -巴拉圭 | 174 | | es-PE | 西班牙 -秘鲁 | 175 | | es-PR | 西班牙 - 波多黎各 | 176 | | es-ES | 西班牙 -西班牙 | 177 | | es-UY | 西班牙 -乌拉圭 | 178 | | es-VE | 西班牙 -委内瑞拉 | 179 | | sw | Swahili | 180 | | sw-KE | Swahili-肯尼亚 | 181 | | sv | 瑞典 | 182 | | sv-FI | 瑞典 -芬兰 | 183 | | sv-SE | 瑞典 -瑞典 | 184 | | syr | Syriac | 185 | | syr-SY | Syriac-叙利亚共和国 | 186 | | ta | 坦米尔 | 187 | | ta-IN | 坦米尔 -印度 | 188 | | tt | Tatar | 189 | | tt-RU | Tatar-俄国 | 190 | | te | Telugu | 191 | | te-IN | Telugu-印度 | 192 | | th | 泰国 | 193 | | th-TH | 泰国 -泰国 | 194 | | tr | 土耳其语 | 195 | | tr-TR | 土耳其语 -土耳其 | 196 | | uk | 乌克兰 | 197 | | uk-UA | 乌克兰 -乌克兰 | 198 | | ur | Urdu | 199 | | ur-PK | Urdu-巴基斯坦 | 200 | | uz | Uzbek | 201 | | uz-UZ-Cyrl | Uzbek-(西里尔字母的) 乌兹别克 | 202 | | uz-UZ-Latn | Uzbek(拉丁文)- 乌兹别克斯坦 | 203 | | vi | 越南 | 204 | | vi-VN | 越南 -越南 | 205 | 206 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | ./tests 11 | 12 | 13 | 14 | 15 | 16 | ./src 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Components/Assembler/BaseAssembler.php: -------------------------------------------------------------------------------- 1 | [], 14 | KeyEnum::FILE_USE_KEY => [], 15 | KeyEnum::FILE_ALIAS_KEY => [], 16 | ]; 17 | 18 | private static array $classProperties = []; 19 | private static array $classMethods = []; 20 | private static bool $checks = false; 21 | 22 | public static function build(?CacheContract $cache): self 23 | { 24 | $entity = self::getInstance(); 25 | if (empty(self::$caches[KeyEnum::FILE_USE_KEY])) { 26 | self::$caches = $cache ? $cache->get(KeyEnum::ANNOTATION_KEY, self::$caches) : self::$caches; 27 | } 28 | 29 | return $entity; 30 | } 31 | 32 | public function hasChangeFile(string $namespace): bool 33 | { 34 | // 当文件修改为空时,说明未进行缓存过任何文件 35 | if (empty(self::$classProperties[$namespace])) { 36 | return true; 37 | } 38 | 39 | // 已检查过,无需重复检查 40 | if (self::$caches) { 41 | return false; 42 | } 43 | 44 | foreach (self::$caches[KeyEnum::FILE_MTIME_KEY] ?? [] as $path => $time) { 45 | if (filemtime($path) !== (int) $time) { 46 | return true; 47 | } 48 | } 49 | 50 | return false; 51 | } 52 | 53 | public function getChangeFiles(): array 54 | { 55 | $changeFiles = []; 56 | foreach (self::$caches[KeyEnum::FILE_MTIME_KEY] as $path => $time) { 57 | if (filemtime($path) !== (int) $time) { 58 | $changeFiles[] = $path; 59 | } 60 | } 61 | 62 | // 标识为全部数据检查过 63 | self::$checks = true; 64 | 65 | return $changeFiles; 66 | } 67 | 68 | public static function setClassProperties(string $namespace, array $classProperties): void 69 | { 70 | self::$classProperties[$namespace] = $classProperties; 71 | } 72 | 73 | public static function getClassProperties($namespace = null, $key = null) 74 | { 75 | if ($namespace && $key) { 76 | return self::$classProperties[$namespace][$key] ?? null; 77 | } 78 | 79 | if ($namespace) { 80 | return self::$classProperties[$namespace] ?? null; 81 | } 82 | 83 | return self::$classProperties; 84 | } 85 | 86 | public static function setClassMethods(string $namespace, array $classMethods): void 87 | { 88 | self::$classMethods[$namespace] = $classMethods; 89 | } 90 | 91 | public static function getClassMethods($namespace = null, $key = null) 92 | { 93 | if ($namespace && $key) { 94 | return self::$classMethods[$namespace][$key] ?? null; 95 | } 96 | 97 | if ($namespace) { 98 | return self::$classMethods[$namespace] ?? null; 99 | } 100 | 101 | return self::$classMethods; 102 | } 103 | 104 | public static function getCaches(): array 105 | { 106 | return self::$caches; 107 | } 108 | 109 | public function setMtime($key, $value): self 110 | { 111 | self::$caches[KeyEnum::FILE_MTIME_KEY][$key] = $value; 112 | 113 | return $this; 114 | } 115 | 116 | public function delMtime($key): self 117 | { 118 | unset(self::$caches[KeyEnum::FILE_MTIME_KEY][$key]); 119 | 120 | return $this; 121 | } 122 | 123 | public function setUses($key, $value): self 124 | { 125 | self::$caches[KeyEnum::FILE_USE_KEY][$key] = $value; 126 | 127 | return $this; 128 | } 129 | 130 | public function getUses(string $className = null) 131 | { 132 | return $className ? self::$caches[KeyEnum::FILE_USE_KEY][$className] : self::$caches[KeyEnum::FILE_USE_KEY]; 133 | } 134 | 135 | public function delUses($key): self 136 | { 137 | unset(self::$caches[KeyEnum::FILE_USE_KEY][$key]); 138 | 139 | return $this; 140 | } 141 | 142 | public function setAlias($key, $value): self 143 | { 144 | self::$caches[KeyEnum::FILE_ALIAS_KEY][$key] = $value; 145 | 146 | return $this; 147 | } 148 | 149 | public function getAlias(string $className = null) 150 | { 151 | return $className ? self::$caches[KeyEnum::FILE_ALIAS_KEY][$className] : self::$caches[KeyEnum::FILE_ALIAS_KEY]; 152 | } 153 | 154 | public function delAlias($key): self 155 | { 156 | unset(self::$caches[KeyEnum::FILE_ALIAS_KEY][$key]); 157 | 158 | return $this; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/Components/Entity/BaseEntity.php: -------------------------------------------------------------------------------- 1 | '_setter', 9 | '_getter' => '_getter', 10 | '_readOnly' => '_readOnly', 11 | '_params' => '_params', 12 | '_properties' => '_properties', 13 | '_alias' => '_alias', 14 | '_cache' => '_cache', 15 | '_idx' => '_idx', 16 | ]; 17 | 18 | public static function getFilter(): array 19 | { 20 | return self::$_filter; 21 | } 22 | 23 | public static function inFilter($needle): bool 24 | { 25 | return isset(self::$_filter[$needle]); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Components/Enum/BaseEnum.php: -------------------------------------------------------------------------------- 1 | getConstants()); 61 | } 62 | 63 | public static function getParentConstants(): array 64 | { 65 | return self::$parentConsts[static::class] ?? (self::$parentConsts[static::class] = (new \ReflectionClass(self::class))->getConstants()); 66 | } 67 | 68 | public static function getChildConstants(): array 69 | { 70 | return self::$childConsts[static::class] ?? (self::$childConsts[static::class] = array_diff_key(self::getConstants(), self::getParentConstants())); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Components/Enum/ExceptionEnum.php: -------------------------------------------------------------------------------- 1 | execute($enumClass)->getClassProperties(); 44 | 45 | if (isset($properties[$enumClass])) { 46 | /** 47 | * @var Property $property 48 | */ 49 | foreach ($properties[$enumClass] as $property) { 50 | if ($message === $property->getValue()) { 51 | $this->fieldName = $message; 52 | $message = $property->getDocLabel(Lang::getInstance()->getLocale())[0]; 53 | } 54 | } 55 | } 56 | 57 | parent::__construct($message, $code, $previous); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Components/Exception/InternalServerErrorException.php: -------------------------------------------------------------------------------- 1 | page; 27 | } 28 | 29 | public function setPage(int $page): void 30 | { 31 | $this->page = $page; 32 | } 33 | 34 | public function getPerPage(): int 35 | { 36 | return $this->perPage; 37 | } 38 | 39 | public function setPerPage(int $perPage): void 40 | { 41 | $this->perPage = $perPage; 42 | } 43 | 44 | public function getTotalCount(): int 45 | { 46 | return $this->totalCount; 47 | } 48 | 49 | public function setTotalCount(int $totalCount): void 50 | { 51 | $this->totalCount = $totalCount; 52 | } 53 | 54 | public static function buildSuccess($data = [], $total = 0, $page = 1, $perPage = 20): self 55 | { 56 | $resp = new self(); 57 | $resp->setSuccess(true); 58 | $resp->setData($data); 59 | $resp->setTotalCount($total); 60 | $resp->setPage($page); 61 | $resp->setPerPage($perPage); 62 | 63 | return $resp; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Components/VO/Response.php: -------------------------------------------------------------------------------- 1 | success; 23 | } 24 | 25 | /** 26 | * @param bool $success 27 | */ 28 | public function setSuccess(bool $success): void 29 | { 30 | $this->success = $success; 31 | } 32 | 33 | /** 34 | * @param string $errCode 35 | */ 36 | public function setErrCode(string $errCode): void 37 | { 38 | $this->errCode = $errCode; 39 | } 40 | 41 | /** 42 | * @return string 43 | */ 44 | public function getErrCode(): string 45 | { 46 | return $this->errCode; 47 | } 48 | 49 | /** 50 | * @return string 51 | */ 52 | public function getErrMessage(): string 53 | { 54 | return $this->errMessage; 55 | } 56 | 57 | /** 58 | * @param string $errMessage 59 | */ 60 | public function setErrMessage(string $errMessage): void 61 | { 62 | $this->errMessage = $errMessage; 63 | } 64 | 65 | /** 66 | * @return array 67 | */ 68 | public function getData(): array 69 | { 70 | return $this->data; 71 | } 72 | 73 | /** 74 | * @param array $data 75 | */ 76 | public function setData(array $data): void 77 | { 78 | $this->data = $data; 79 | } 80 | 81 | public static function buildSuccess($data = []): self 82 | { 83 | $resp = new self(); 84 | $resp->setSuccess(true); 85 | $resp->setData($data); 86 | 87 | return $resp; 88 | } 89 | 90 | public static function buildFailure(string $errCode, string $errMessage, array $data = []): self 91 | { 92 | $resp = new self(); 93 | $resp->setSuccess(false); 94 | $resp->setErrCode($errCode); 95 | $resp->setErrMessage($errMessage); 96 | $resp->setData($data); 97 | 98 | return $resp; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Contracts/CacheContract.php: -------------------------------------------------------------------------------- 1 | project = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR; 34 | $this->cache = $this->project . 'cache' . DIRECTORY_SEPARATOR; 35 | $this->src = $this->project . 'src' . DIRECTORY_SEPARATOR; 36 | $this->test = $this->project . 'tests' . DIRECTORY_SEPARATOR; 37 | $this->components = $this->src . 'Components' . DIRECTORY_SEPARATOR; 38 | $this->assembler = $this->components . 'Assembler' . DIRECTORY_SEPARATOR; 39 | $this->dto = $this->components . 'DTO' . DIRECTORY_SEPARATOR; 40 | $this->entity = $this->components . 'Entity' . DIRECTORY_SEPARATOR; 41 | $this->enum = $this->components . 'Enum' . DIRECTORY_SEPARATOR; 42 | $this->exception = $this->components . 'Exception' . DIRECTORY_SEPARATOR; 43 | $this->console = $this->src . 'Console' . DIRECTORY_SEPARATOR; 44 | $this->lang = $this->src . 'Lang' . DIRECTORY_SEPARATOR; 45 | $this->support = $this->src . 'Support' . DIRECTORY_SEPARATOR; 46 | $this->template = $this->src . 'Template' . DIRECTORY_SEPARATOR; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Support/Abstracts/Guzzle/GuzzleClient.php: -------------------------------------------------------------------------------- 1 | client = new Client(); 55 | $this->log = $log; 56 | $this->init(); 57 | $this->logs(); 58 | $this->startAt = Carbon::now(); 59 | } 60 | 61 | public function setBizReportError(bool $val): self 62 | { 63 | $this->bizReportError = $val; 64 | 65 | return $this; 66 | } 67 | 68 | /** 69 | * 变量初始化. 70 | * 71 | * @return void 72 | */ 73 | abstract public function init(): void; 74 | 75 | /** 76 | * 是否成功判断. 77 | * 78 | * @return bool 79 | */ 80 | public function isSuccess(): bool 81 | { 82 | return $this->success; 83 | } 84 | 85 | /** 86 | * 写日志. 87 | * 88 | * @return void 89 | */ 90 | public function logs(): void 91 | { 92 | $this->options[RequestOptions::ON_STATS] = $this->getLogClosure(); 93 | } 94 | 95 | public function getLogClosure(): \Closure 96 | { 97 | return function (TransferStats $stats) { 98 | $this->request = $stats->getRequest(); 99 | $this->response = $stats->getResponse(); 100 | $errorData = $stats->getHandlerErrorData(); 101 | 102 | // https://curl.se/libcurl/c/libcurl-errors.html 103 | if (($errorData instanceof \Throwable) || (is_int($errorData) && $errorData > 0)) { 104 | $this->error = true; 105 | $this->bizReportError = false; 106 | } 107 | 108 | if (!$this->error) { 109 | $this->success = $this->getCallback(); 110 | } 111 | 112 | $logInfo = $this->getLogInfo($stats); 113 | 114 | $this->logHandle($logInfo); 115 | }; 116 | } 117 | 118 | /** 119 | * 日志信息处理. 120 | * 121 | * @param array $logInfo 122 | * @return void 123 | */ 124 | public function logHandle(array $logInfo): void 125 | { 126 | // 成功 127 | if ($this->success) { 128 | $this->log->info($this->message, $logInfo); 129 | 130 | return; 131 | } 132 | // curl 请求报错 133 | if ($this->error) { 134 | $this->log->warning($this->message, $logInfo); 135 | 136 | return; 137 | } 138 | 139 | // 业务 请求报错 140 | if ($this->bizReportError) { 141 | $this->log->error($this->message, $logInfo); 142 | 143 | return; 144 | } 145 | 146 | $this->log->debug($this->message, $logInfo); 147 | } 148 | 149 | public function getLogInfo(TransferStats $stats): array 150 | { 151 | $this->message .= Uri::composeComponents( 152 | $this->request->getUri()->getScheme(), 153 | $this->request->getUri()->getAuthority(), 154 | $this->request->getUri()->getPath(), 155 | '', 156 | '' 157 | ); 158 | 159 | return [ 160 | //是否请求成功 161 | 'succeed' => $this->success, 162 | //请求相关数据 163 | 'request' => [ 164 | 'uri' => (string) $this->request->getUri(), 165 | 'method' => $this->request->getMethod(), 166 | 'headers' => $this->request->getHeaders(), 167 | 'body' => (string) $this->request->getBody(), 168 | ], 169 | //响应相关数据 170 | 'response' => [ 171 | 'statusCode' => $this->response ? $this->response->getStatusCode() : -1, 172 | 'reasonPhrase' => $this->response ? $this->response->getReasonPhrase() : '', 173 | 'body' => $this->response ? (string) $this->response->getBody() : '', 174 | ], 175 | //请求耗时等详细数据 176 | 'handlerStats' => $stats->getHandlerStats(), 177 | //请求总耗时 178 | 'transferTime' => $stats->getTransferTime(), 179 | //请求开始时间 180 | 'startAt' => $this->startAt->format(CarbonInterface::MOCK_DATETIME_FORMAT), 181 | //请求结束时间 182 | 'endAt' => Carbon::now()->format(CarbonInterface::MOCK_DATETIME_FORMAT), 183 | //发送请求时相关的上下文变量 184 | 'extraContext' => $this->getContent(), 185 | ]; 186 | } 187 | 188 | public function mergeOption(string $key, array $value): void 189 | { 190 | if (!isset($this->options[$key])) { 191 | $this->options[$key] = $value; 192 | 193 | return; 194 | } 195 | $this->options[$key] = array_merge($this->options[$key], $value); 196 | } 197 | 198 | /** 199 | * @return bool 200 | */ 201 | public function getCallback(): bool 202 | { 203 | return ($this->callback)($this->response); 204 | } 205 | 206 | /** 207 | * 设置请求结果是否成功闭包函数. 208 | * 209 | * @param \Closure $callback 210 | */ 211 | public function setSuccessCondition(\Closure $callback): void 212 | { 213 | $this->callback = $callback; 214 | } 215 | 216 | /** 217 | * 需要添加上下文变量,可重写该函数. 218 | * 219 | * @return array 220 | */ 221 | public function getContent(): array 222 | { 223 | return []; 224 | } 225 | 226 | public function getOptions(): array 227 | { 228 | return $this->options; 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/Support/Abstracts/Guzzle/LaravelClient.php: -------------------------------------------------------------------------------- 1 | enableMethods; 62 | } 63 | 64 | public function setEnableMethods(bool $enableMethods): void 65 | { 66 | $this->enableMethods = $enableMethods; 67 | } 68 | 69 | public function __construct($cache = null) 70 | { 71 | $this->cache = $cache; 72 | $this->resolvedEntity = AnnotationEntity::build($cache); 73 | } 74 | 75 | /** 76 | * @throws ReflectionException 77 | */ 78 | public function execute(string $class): self 79 | { 80 | if (!$this->resolvedEntity->hasChangeFile($class)) { 81 | return $this; 82 | } 83 | 84 | $this->queue = $this->resolvedEntity->getChangeFiles(); 85 | // 构建命名空间 86 | $this->queue[] = $class; 87 | while (!empty($this->queue)) { 88 | $objClass = array_shift($this->queue); 89 | $this->buildClass($objClass); 90 | $this->analysisAttr(); 91 | if ($this->enableMethods) { 92 | $this->analysisMethod(); 93 | } 94 | } 95 | 96 | $this->writeCache(); 97 | 98 | return $this; 99 | } 100 | 101 | /** 102 | * 构建反射类. 103 | * @param $class 104 | * @return $this 105 | * @throws ReflectionException 106 | */ 107 | public function buildClass($class): self 108 | { 109 | $this->class = new ReflectionClass($class); 110 | $classNamespace = $this->class->getName(); 111 | $classFileName = $this->class->getFileName(); 112 | 113 | $this->resolvedEntity->setMtime($classFileName, filemtime($classFileName)); 114 | $this->parseFileForNamespace($classNamespace, $classFileName); 115 | 116 | return $this; 117 | } 118 | 119 | /** 120 | * @param string $classNamespace 121 | * @param $classFileName 122 | * @return void 123 | */ 124 | private function parseFileForNamespace(string $classNamespace, $classFileName): void 125 | { 126 | $parse = FileParser::getInstance()->execute($classNamespace, $classFileName); 127 | $this->resolvedEntity->setUses($classNamespace, $parse->getUses()[$classNamespace]); 128 | $this->resolvedEntity->setAlias($classNamespace, $parse->getAlias()[$classNamespace]); 129 | } 130 | 131 | public function writeCache(): void 132 | { 133 | if ($this->cache) { 134 | $this->cache->set( 135 | KeyEnum::ANNOTATION_KEY, 136 | json_encode($this->resolvedEntity::getCaches(), JSON_UNESCAPED_UNICODE) 137 | ); 138 | } 139 | } 140 | 141 | /** 142 | * @throws ReflectionException 143 | */ 144 | public function analysisAttr(): void 145 | { 146 | $className = $this->class->getName(); 147 | $properties = new Properties( 148 | $className, 149 | $this->resolvedEntity->getUses($className), 150 | $this->resolvedEntity->getAlias($className) 151 | ); 152 | $this->resolvedEntity::setClassProperties($className, $properties->getProperties($this->filter)); 153 | foreach ($properties->getAllPropertyNamespaceName() as $namespaceName) { 154 | if (!isset($this->resolvedClass[$namespaceName])) { 155 | $this->queue[] = $namespaceName; 156 | $this->resolvedClass[$namespaceName] = true; 157 | } 158 | } 159 | } 160 | 161 | /** 162 | * @throws ReflectionException 163 | */ 164 | public function analysisMethod(): void 165 | { 166 | $className = $this->class->getName(); 167 | $methods = new Methods($className); 168 | $this->resolvedEntity::setClassMethods($className, $methods->getMethods()); 169 | } 170 | 171 | public function getFileName(): string 172 | { 173 | return $this->class->getFileName(); 174 | } 175 | 176 | public function getUses(): array 177 | { 178 | return $this->resolvedEntity->getUses(); 179 | } 180 | 181 | public function getAlias(): array 182 | { 183 | return $this->resolvedEntity->getAlias(); 184 | } 185 | 186 | public function getClassProperties(): array 187 | { 188 | // 兼容类无 protected 变量问题 189 | return $this->resolvedEntity::getClassProperties(); 190 | } 191 | 192 | public function getProperty($key): ?Property 193 | { 194 | return $this->resolvedEntity::getClassProperties($this->class->getName(), $key); 195 | } 196 | 197 | public function getClassMethods($namespace = null, $key = null): array 198 | { 199 | // 兼容类无 protected 变量问题 200 | return $this->resolvedEntity::getClassMethods($namespace, $key); 201 | } 202 | 203 | /** 204 | * @return int 205 | */ 206 | public function getFilter(): int 207 | { 208 | return $this->filter; 209 | } 210 | 211 | /** 212 | * @param int $filter 213 | */ 214 | public function setFilter(int $filter): void 215 | { 216 | $this->filter = $filter; 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/Support/Annotation/Override.php: -------------------------------------------------------------------------------- 1 | var = $var; 14 | $this->text = $text; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Support/Converts/BaseMeter.php: -------------------------------------------------------------------------------- 1 | anchorPointUnit)) { 41 | throw new InternalServerErrorException(BaseEnum::CLASS_PROPERTY_IS_NOT_OVERRIDDEN); 42 | } 43 | 44 | $this->num = $num; 45 | $this->unit = $unit; 46 | $this->from($unit, $scale); 47 | } 48 | 49 | /** 50 | * 相加. 51 | * @param string $num 52 | * @param int $scale 53 | * @return $this 54 | */ 55 | public function add(string $num, int $scale = 0): self 56 | { 57 | $this->num = bcadd($this->num, $num, $scale); 58 | 59 | return $this; 60 | } 61 | 62 | /** 63 | * @param string $num 64 | * @param int|null $scale 65 | * @return $this 66 | */ 67 | public function sub(string $num, ?int $scale = 0): self 68 | { 69 | $this->num = bcsub($this->num, $num, $scale); 70 | 71 | return $this; 72 | } 73 | 74 | /** 75 | * 乘法. 76 | * @param string $num 77 | * @param int $scale 78 | * @return $this 79 | */ 80 | public function mul(string $num, int $scale = 0): self 81 | { 82 | $this->num = bcmul($this->num, $num, $scale); 83 | 84 | return $this; 85 | } 86 | 87 | /** 88 | * 除法. 89 | * @param string $num 90 | * @param int $scale 91 | * @return $this 92 | * @throws InternalServerErrorException 93 | */ 94 | public function div(string $num, int $scale = 0): self 95 | { 96 | if ('' === $num || '0' === $num) { 97 | throw new InternalServerErrorException(SupportEnum::CANNOT_DIVIDE_BY_ZERO); 98 | } 99 | 100 | $this->num = bcdiv($this->num, $num, $scale); 101 | 102 | return $this; 103 | } 104 | 105 | /** 106 | * 取余. 107 | * @param string $num 108 | * @param int $scale 109 | * @return $this 110 | */ 111 | public function mod(string $num, int $scale = 0): self 112 | { 113 | $this->num = bcmod($this->num, $num, $scale); 114 | 115 | return $this; 116 | } 117 | 118 | /** 119 | * 乘方. 120 | * @param string $exponent 121 | * @param int $scale 122 | * @return $this 123 | */ 124 | public function pow(string $exponent, int $scale = 0): self 125 | { 126 | $this->num = bcpow($this->num, $exponent, $scale); 127 | 128 | return $this; 129 | } 130 | 131 | /** 132 | * 乘方再取余. 133 | * @param string $exponent 134 | * @param string $modulus 135 | * @param int $scale 136 | * @return $this 137 | */ 138 | public function powmod(string $exponent, string $modulus, int $scale = 0): self 139 | { 140 | $this->num = bcpowmod($this->num, $exponent, $modulus, $scale); 141 | 142 | return $this; 143 | } 144 | 145 | /** 146 | * 二次方根. 147 | * @param int $scale 148 | * @return $this 149 | */ 150 | public function sqrt(int $scale = 0): self 151 | { 152 | $this->num = bcsqrt($this->num, $scale); 153 | 154 | return $this; 155 | } 156 | 157 | /** 158 | * 数字大小比较 159 | * 两个数相等时返回 0; $this->num 比 $num 大时返回 1; 其他则返回 -1。 160 | * @param string $num 161 | * @param int $scale 162 | * @return int 163 | */ 164 | public function comp(string $num, int $scale = 0): int 165 | { 166 | return bccomp($this->num, $num, $scale); 167 | } 168 | 169 | /** 170 | * 大于. 171 | * @param string $num 172 | * @param int $scale 173 | * @return bool 174 | */ 175 | public function gt(string $num, int $scale = 0): bool 176 | { 177 | return 1 === $this->comp($num, $scale); 178 | } 179 | 180 | /** 181 | * 小于. 182 | * @param string $num 183 | * @param int $scale 184 | * @return bool 185 | */ 186 | public function lt(string $num, int $scale = 0): bool 187 | { 188 | return -1 === $this->comp($num, $scale); 189 | } 190 | 191 | /** 192 | * 等于. 193 | * @param string $num 194 | * @param int $scale 195 | * @return bool 196 | */ 197 | public function eq(string $num, int $scale = 0): bool 198 | { 199 | return 0 === $this->comp($num, $scale); 200 | } 201 | 202 | /** 203 | * 全局设置小数位. 204 | * @param int $scale 205 | * @return $this 206 | */ 207 | public function setScale(int $scale = 0): self 208 | { 209 | bcscale($scale); 210 | 211 | return $this; 212 | } 213 | 214 | /** 215 | * 获取值 216 | * @param int $scale 217 | * @return string 218 | */ 219 | public function getNum(int $scale = 0): string 220 | { 221 | return number_format($this->num, $scale, '.', ''); 222 | } 223 | 224 | /** 225 | * 转为设置单位. 226 | * @param $unit 227 | * @param int $scale 228 | * @return string 229 | */ 230 | public function to($unit, int $scale = 4): string 231 | { 232 | $handle = $this->calculates[$unit]; 233 | if (is_callable($handle)) { 234 | return $handle($this->num, true); 235 | } 236 | 237 | return bcmul($this->num, $handle, $scale); 238 | } 239 | 240 | /** 241 | * 转为锚点单位. 242 | * @param $unit 243 | * @param int $scale 244 | * @return $this 245 | */ 246 | protected function from($unit, int $scale = 4): self 247 | { 248 | $handle = $this->calculates[$unit]; 249 | if (is_callable($handle)) { 250 | $this->num = $handle($this->num, false); 251 | } else { 252 | $this->mul($handle, $scale); 253 | } 254 | 255 | return $this; 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/Support/Converts/LengthConvert.php: -------------------------------------------------------------------------------- 1 | '1000', 29 | self::METRIC_M => '1', 30 | self::METRIC_DM => '0.1', 31 | self::METRIC_CM => '0.01', 32 | self::METRIC_MM => '0.001', 33 | // 英制 34 | self::BRITISH_IN => '0.0254', 35 | self::BRITISH_FT => '0.3048', 36 | self::BRITISH_YD => '0.9144', 37 | self::BRITISH_MI => '1609.344', 38 | ]; 39 | } 40 | -------------------------------------------------------------------------------- /src/Support/Converts/TypeConvert.php: -------------------------------------------------------------------------------- 1 | '1000', 29 | self::METRIC_KG => '1', 30 | self::METRIC_G => '0.001', 31 | self::METRIC_MG => '0.000001', 32 | // 常衡制 33 | self::BRITISH_ST => '6.35029', 34 | self::BRITISH_UKT => '1016.047', 35 | self::BRITISH_UST => '907.1847', 36 | self::BRITISH_LB => '0.453592', 37 | self::BRITISH_OZ => '0.0283495', 38 | ]; 39 | } 40 | -------------------------------------------------------------------------------- /src/Support/File.php: -------------------------------------------------------------------------------- 1 | handle = fopen($path, $mode); 12 | } 13 | 14 | public function __destruct() 15 | { 16 | if (!is_null($this->handle)) { 17 | fclose($this->handle); 18 | } 19 | } 20 | 21 | public function close(): void 22 | { 23 | if (!is_null($this->handle)) { 24 | fclose($this->handle); 25 | $this->handle = null; 26 | } 27 | } 28 | 29 | public function readLine(): \Generator 30 | { 31 | while (!feof($this->handle)) { 32 | yield fgets($this->handle); 33 | } 34 | fclose($this->handle); 35 | $this->handle = null; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Support/FileParser.php: -------------------------------------------------------------------------------- 1 | alias[$classNamespace])) { 33 | $this->alias[$classNamespace] = []; 34 | } 35 | 36 | if (!isset($this->uses[$classNamespace])) { 37 | $this->uses[$classNamespace] = []; 38 | } 39 | 40 | if (preg_match(self::CLASS_DEFINE_PATTERN, $rowData, $matches)) { 41 | $this->className = $matches[1] ?? ''; 42 | 43 | return true; 44 | } 45 | 46 | if (preg_match(self::NAMESPACE_PATTERN, $rowData, $matches)) { 47 | $this->uses[$classNamespace]['this'] = $matches[1] ?? ''; 48 | 49 | return false; 50 | } 51 | 52 | if (preg_match(self::USE_PATTERN, $rowData, $matches)) { 53 | $useNamespace = $matches[1] ?? ''; 54 | $as = $matches[2] ?? ''; 55 | if ($useNamespace) { 56 | $words = explode('\\', $useNamespace); 57 | $objName = array_pop($words); 58 | 59 | if (!empty($as)) { 60 | $this->alias[$classNamespace][$as] = $objName; 61 | } 62 | 63 | $this->uses[$classNamespace][$objName] = implode('\\', $words); 64 | } 65 | 66 | return false; 67 | } 68 | 69 | return false; 70 | } 71 | 72 | /** 73 | * 分析文件命名空间执行入口. 74 | * @param string $namespace 75 | * @param string $path 76 | * @return $this 77 | */ 78 | public function execute(string $namespace, string $path): self 79 | { 80 | $file = (new File($path)); 81 | $row = $file->readLine(); 82 | while ($row->valid()) { 83 | $isDone = $this->analysis($namespace, trim($row->current())); 84 | if ($isDone) { 85 | break; 86 | } 87 | $row->next(); 88 | } 89 | $file->close(); 90 | 91 | return $this; 92 | } 93 | 94 | public function getUses(): array 95 | { 96 | return $this->uses; 97 | } 98 | 99 | public function getAlias(): array 100 | { 101 | return $this->alias; 102 | } 103 | 104 | public function getNamespace($alias): string 105 | { 106 | return $this->uses[$alias]['this'] . DIRECTORY_SEPARATOR . $this->className; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Support/Lang.php: -------------------------------------------------------------------------------- 1 | locale = $locale; 22 | 23 | return $this; 24 | } 25 | 26 | /** 27 | * @return string 28 | */ 29 | public function getLocale(): string 30 | { 31 | return $this->locale; 32 | } 33 | 34 | /** 35 | * @param string $fileName 36 | * @return Lang 37 | */ 38 | public function setFileName(string $fileName): self 39 | { 40 | $this->fileName = $fileName; 41 | 42 | return $this; 43 | } 44 | 45 | /** 46 | * @return string 47 | */ 48 | public function getFileName(): string 49 | { 50 | return $this->fileName; 51 | } 52 | 53 | public function loadFile(): array 54 | { 55 | $langPath = PathManager::getInstance()->lang . $this->locale . DIRECTORY_SEPARATOR . $this->fileName . '.json'; 56 | 57 | $content = []; 58 | 59 | if (file_exists($langPath)) { 60 | $content = json_decode(file_get_contents($langPath), true, 512, JSON_THROW_ON_ERROR); 61 | } 62 | 63 | return $content; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Support/Loggers/LaravelLog.php: -------------------------------------------------------------------------------- 1 | instance->error($message, $content); 14 | } 15 | 16 | public function warning(string $message, array $content): void 17 | { 18 | $this->instance->warning($message, $content); 19 | } 20 | 21 | public function info(string $message, array $content): void 22 | { 23 | $this->instance->info($message, $content); 24 | } 25 | 26 | public function debug(string $message, array $content): void 27 | { 28 | $this->instance->debug($message, $content); 29 | } 30 | 31 | public static function build(): self 32 | { 33 | $log = (new self()); 34 | // app function 35 | $log->instance = app('log'); 36 | 37 | return $log; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Support/Properties/DocComment.php: -------------------------------------------------------------------------------- 1 | getName(); 15 | $value = $constant->getValue(); 16 | $docComment = $constant->getDocComment(); 17 | 18 | $labels = self::matchLabels($docComment); 19 | $comment = self::parseDocDesc($docComment); 20 | 21 | return [$name, $value, $comment, $labels]; 22 | } 23 | 24 | public static function getPropertyInfo(\ReflectionProperty $property): array 25 | { 26 | $type = $property->getType(); 27 | $name = $property->getName(); 28 | 29 | $type = $type instanceof \ReflectionType ? $type->getName() : null; 30 | 31 | $stronglyTyped = !is_null($type); 32 | 33 | $comment = ''; 34 | 35 | // 若是 php8 版本,默认查询是否定义 Doc 注解标签 36 | if (is_null($type) && FrameTypeUtil::isPHP(8) && $docs = $property->getAttributes(Doc::class)) { 37 | $type = $docs[0]->getArguments()['var']; 38 | $comment = $docs[0]->getArguments()['text']; 39 | } 40 | 41 | $docComment = $property->getDocComment() ?: ''; 42 | $labels = self::matchLabels($docComment); 43 | 44 | // 注释兜底 45 | if (is_null($type) && isset($labels['var'])) { 46 | $type = $labels['var'][0]; 47 | } 48 | // php8 版本注解未找到属性描述,尝试注释中找 49 | if (!$comment) { 50 | $comment = self::parseDocDesc($docComment); 51 | } 52 | 53 | return [$type, $name, $comment, $stronglyTyped, $labels]; 54 | } 55 | 56 | public static function getMethodInfo(\ReflectionMethod $method): array 57 | { 58 | $name = $method->getName(); 59 | 60 | $comment = ''; 61 | 62 | // 若是 php8 版本,默认查询是否定义 Doc 注解标签 63 | if (FrameTypeUtil::isPHP(8) && $docs = $method->getAttributes(Doc::class)) { 64 | $type = $docs[0]->getArguments()['var']; 65 | $comment = $docs[0]->getArguments()['text']; 66 | } 67 | 68 | $docComment = $method->getDocComment() ?: ''; 69 | $labels = self::matchLabels($docComment); 70 | 71 | // php8 版本注解未找到属性描述,尝试注释中找 72 | if (!$comment) { 73 | $comment = self::parseDocDesc($docComment); 74 | } 75 | 76 | return [$name, $comment, $labels]; 77 | } 78 | 79 | protected static function matchLabels(string $docComment): array 80 | { 81 | $docLabels = []; 82 | preg_match_all(self::LABEL_PATTERN, $docComment, $matches); 83 | $cnt = count($matches[0]); 84 | for ($i = 0; $i < $cnt; ++$i) { 85 | $docLabels[$matches[1][$i]][] = $matches[2][$i]; 86 | } 87 | 88 | return $docLabels; 89 | } 90 | 91 | protected static function parseDocDesc($docComment): string 92 | { 93 | $lines = explode(PHP_EOL, $docComment); 94 | 95 | if (1 === count($lines)) { 96 | // windows下兼容\n换行 97 | $lines = explode("\n", $docComment); 98 | } 99 | 100 | $descArr = []; 101 | 102 | foreach ($lines as $line) { 103 | $newLine = trim(ltrim(trim($line), '/*'), ' '); 104 | $docLine = self::parseDocLine($newLine); 105 | if ($docLine) { 106 | $descArr[] = $docLine; 107 | } 108 | } 109 | 110 | if (empty($descArr)) { 111 | return ''; 112 | } 113 | 114 | return count($descArr) > 1 ? implode(PHP_EOL, $descArr) : $descArr[0]; 115 | } 116 | 117 | protected static function parseDocLine(string $line): string 118 | { 119 | if (empty($line)) { 120 | return ''; 121 | } 122 | 123 | if (str_contains($line, '@')) { 124 | return ''; 125 | } 126 | 127 | return $line; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/Support/Properties/Method.php: -------------------------------------------------------------------------------- 1 | reflectionMethod = $reflectionMethod; 33 | $this->uses = $uses; 34 | $this->alias = $alias; 35 | } 36 | 37 | public function getMethod($filter = \ReflectionProperty::IS_PROTECTED): self 38 | { 39 | if (isset($this->name)) { 40 | return $this; 41 | } 42 | 43 | $this->handleMethod(); 44 | 45 | return $this; 46 | } 47 | 48 | /** 49 | * 根据类属性查询命名空间. 50 | * 51 | * @param Property $property 52 | * @return string 53 | */ 54 | protected function findNamespace(Property $property): ?string 55 | { 56 | $propertyType = $property->type; 57 | 58 | if (array_key_exists($propertyType, $this->alias)) { 59 | $propertyType = $this->alias[$propertyType]; 60 | } 61 | 62 | if (class_exists($namespace = $this->uses['this'] . '\\' . $propertyType)) { 63 | $property->isClass = true; 64 | 65 | return $namespace; 66 | } 67 | 68 | if (isset($this->uses[$propertyType]) && class_exists($namespace = $this->uses[$propertyType] . '\\' . $propertyType)) { 69 | $property->isClass = true; 70 | 71 | return $namespace; 72 | } 73 | 74 | return null; 75 | } 76 | 77 | /** 78 | * @return void 79 | */ 80 | public function handleMethod(): void 81 | { 82 | [$name, $comment, $labels] = DocComment::getMethodInfo($this->reflectionMethod); 83 | $this->name = $name; 84 | $this->docLabels = $labels; 85 | $this->docDesc = $comment; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Support/Properties/Methods.php: -------------------------------------------------------------------------------- 1 | refectionClass = new \ReflectionClass($namespace); 31 | $this->uses = $uses; 32 | $this->alias = $alias; 33 | } 34 | 35 | /** 36 | * @throws ReflectionException 37 | */ 38 | public function getMethods($filter = \ReflectionMethod::IS_PUBLIC): array 39 | { 40 | if (isset($this->methods)) { 41 | return $this->methods; 42 | } 43 | 44 | $methods = $this->refectionClass->getMethods(); 45 | foreach ($methods as $method) { 46 | $newMethod = new Method($method, $this->uses, $this->alias); 47 | $newMethod->getMethod($filter); 48 | if (array_key_exists('internal', $newMethod->docLabels)) { 49 | continue; 50 | } 51 | $this->methods[$this->refectionClass->getName().'@'.$method->getName()] = $newMethod; 52 | } 53 | 54 | return $this->methods ?? []; 55 | } 56 | 57 | 58 | /** 59 | * 获取类名 60 | * example: A\B\Foo. 61 | * 62 | * @return string 63 | */ 64 | public function getName(): string 65 | { 66 | return $this->refectionClass->getName(); 67 | } 68 | 69 | /** 70 | * 获取类短名 71 | * example: Foo. 72 | * 73 | * @return string 74 | */ 75 | public function getShortName(): string 76 | { 77 | return $this->refectionClass->getShortName(); 78 | } 79 | 80 | /** 81 | * 获取命名空间名称 82 | * example: A\B. 83 | * 84 | * @return string 85 | */ 86 | public function getNamespaceName(): string 87 | { 88 | return $this->refectionClass->getNamespaceName(); 89 | } 90 | 91 | /** 92 | * 获取所有属性的命名空间. 93 | * 94 | * @return array 95 | */ 96 | public function getAllPropertyNamespaceName(): array 97 | { 98 | $namespaces = []; 99 | 100 | foreach ($this->getProperties() as $property) { 101 | if (empty($property->namespace)) { 102 | continue; 103 | } 104 | $namespaces[] = $property->namespace; 105 | } 106 | 107 | return array_unique($namespaces); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Support/Properties/Properties.php: -------------------------------------------------------------------------------- 1 | refectionClass = new \ReflectionClass($namespace); 31 | $this->uses = $uses; 32 | $this->alias = $alias; 33 | } 34 | 35 | public function getProperties($filter = \ReflectionProperty::IS_PROTECTED): array 36 | { 37 | if (isset($this->properties)) { 38 | return $this->properties; 39 | } 40 | 41 | $constants = $this->refectionClass->getReflectionConstants(); 42 | $properties = $this->refectionClass->getProperties($filter); 43 | 44 | return array_merge( 45 | $this->handleConstants($constants), 46 | $this->handleProperties($properties) 47 | ); 48 | } 49 | 50 | /** 51 | * @param array $constants 52 | * @return array|Property[] 53 | */ 54 | public function handleConstants(array $constants): array 55 | { 56 | /** 57 | * @var \ReflectionClassConstant $constant 58 | */ 59 | foreach ($constants as $constant) { 60 | // 排除包内部使用变量 61 | if (FrameEntity::inFilter($constant->name)) { 62 | continue; 63 | } 64 | [$name, $value, $comment, $labels] = DocComment::getConstantInfo($constant); 65 | $newProperty = new Property( 66 | 'const', 67 | $name, 68 | $value, 69 | $comment, 70 | false, 71 | $labels 72 | ); 73 | 74 | $newProperty->namespace = null; 75 | $this->properties[$name] = $newProperty; 76 | } 77 | 78 | return $this->properties ?? []; 79 | } 80 | 81 | /** 82 | * 根据类属性查询命名空间. 83 | * 84 | * @param Property $property 85 | * @return string 86 | */ 87 | protected function findNamespace(Property $property): ?string 88 | { 89 | $propertyType = $property->type; 90 | 91 | if (array_key_exists($propertyType, $this->alias)) { 92 | $propertyType = $this->alias[$propertyType]; 93 | } 94 | 95 | if (class_exists($namespace = $this->uses['this'] . '\\' . $propertyType)) { 96 | $property->isClass = true; 97 | 98 | return $namespace; 99 | } 100 | 101 | if (isset($this->uses[$propertyType]) && class_exists($namespace = $this->uses[$propertyType] . '\\' . $propertyType)) { 102 | $property->isClass = true; 103 | 104 | return $namespace; 105 | } 106 | 107 | return null; 108 | } 109 | 110 | /** 111 | * @param array $properties 112 | * @return array|Property[] 113 | */ 114 | public function handleProperties(array $properties): array 115 | { 116 | foreach ($properties as $property) { 117 | /* 118 | * @var \ReflectionProperty $property 119 | */ 120 | $property->setAccessible(true); 121 | [$type, $name, $comment, $stronglyTyped, $labels] = DocComment::getPropertyInfo($property); 122 | 123 | // 存在内部注释标记的属性,不需要处理 124 | if (array_key_exists('internal', $labels)) { 125 | continue; 126 | } 127 | 128 | $newProperty = new Property( 129 | $type, 130 | $name, 131 | '', 132 | $comment, 133 | $stronglyTyped, 134 | $labels 135 | ); 136 | $newProperty->namespace = $this->findNamespace($newProperty); 137 | $this->properties[$name] = $newProperty; 138 | } 139 | 140 | return $this->properties ?? []; 141 | } 142 | 143 | /** 144 | * 获取类名 145 | * example: A\B\Foo. 146 | * 147 | * @return string 148 | */ 149 | public function getName(): string 150 | { 151 | return $this->refectionClass->getName(); 152 | } 153 | 154 | /** 155 | * 获取类短名 156 | * example: Foo. 157 | * 158 | * @return string 159 | */ 160 | public function getShortName(): string 161 | { 162 | return $this->refectionClass->getShortName(); 163 | } 164 | 165 | /** 166 | * 获取命名空间名称 167 | * example: A\B. 168 | * 169 | * @return string 170 | */ 171 | public function getNamespaceName(): string 172 | { 173 | return $this->refectionClass->getNamespaceName(); 174 | } 175 | 176 | /** 177 | * 获取所有属性的命名空间. 178 | * 179 | * @return array 180 | */ 181 | public function getAllPropertyNamespaceName(): array 182 | { 183 | $namespaces = []; 184 | 185 | foreach ($this->getProperties() as $property) { 186 | if (empty($property->namespace)) { 187 | continue; 188 | } 189 | $namespaces[] = $property->namespace; 190 | } 191 | 192 | return array_unique($namespaces); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/Support/Properties/Property.php: -------------------------------------------------------------------------------- 1 | type = $type; 54 | $this->name = $name; 55 | $this->value = $value; 56 | $this->docDesc = $comment; 57 | $this->stronglyTyped = $stronglyTyped; 58 | $this->docLabels = $docLabels; 59 | 60 | if (false !== strpos($this->type, '[]')) { 61 | $this->isArray = true; 62 | $this->type = str_replace('[]', '', $this->type); 63 | } 64 | } 65 | 66 | public function getDocDesc(): string 67 | { 68 | return $this->docDesc; 69 | } 70 | 71 | public function getDocLabels(): array 72 | { 73 | return $this->docLabels; 74 | } 75 | 76 | public function getDocLabel(string $key): array 77 | { 78 | return $this->docLabels[$key] ?? []; 79 | } 80 | 81 | public function getValue() 82 | { 83 | return $this->value; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Support/Traits/Accessor.php: -------------------------------------------------------------------------------- 1 | resetAccessor(); 46 | } 47 | 48 | $pattern = $this->getAuth(); 49 | 50 | $matches = []; 51 | preg_match($pattern, $name, $matches); 52 | 53 | $style = $matches[1] ?? null; 54 | $attrName = $matches[2] ?? null; 55 | 56 | if (is_null($style) && is_null($attrName)) { 57 | throw new InternalServerErrorException(BaseEnum::METHOD_NOT_DEFINE); 58 | } 59 | 60 | $attrName = lcfirst($attrName); 61 | 62 | if (!property_exists($this, $attrName)) { 63 | throw new InternalServerErrorException(BaseEnum::ATTR_NOT_DEFINE); 64 | } 65 | 66 | switch ($style) { 67 | case 'set': 68 | $this->setValue($attrName, $args); 69 | 70 | return $this; 71 | case 'get': 72 | return $this->getValue($attrName); 73 | } 74 | 75 | throw new InternalServerErrorException(BaseEnum::METHOD_NOT_DEFINE); 76 | } 77 | 78 | /** 79 | * @internal 80 | * @return string 81 | */ 82 | public function getAuth(): string 83 | { 84 | $pattern = '/^([sg]et)(.*)/'; 85 | 86 | if ($this->_getter && !$this->_setter) { 87 | $pattern = '/^(get)(.*)/'; 88 | } 89 | 90 | if (!$this->_getter && $this->_setter) { 91 | $pattern = '/^(set)(.*)/'; 92 | } 93 | 94 | return $pattern; 95 | } 96 | 97 | /** 98 | * @internal 99 | * @param $attrName 100 | * @param $args 101 | * @return void 102 | */ 103 | private function setValue($attrName, $args): void 104 | { 105 | $this->{$attrName} = $args[0]; 106 | } 107 | 108 | /** 109 | * @internal 110 | * @param $attrName 111 | * @return mixed 112 | */ 113 | private function getValue($attrName) 114 | { 115 | // 只读,因为对象 return 出去可以修改内部值,破坏封装性 116 | if ($this->_readOnly && is_object($this->{$attrName})) { 117 | return clone $this->{$attrName}; 118 | } 119 | 120 | return $this->{$attrName}; 121 | } 122 | 123 | /** 124 | * @internal 125 | * @param object $obj 126 | * @param array $fields 127 | * @param int $nameType 128 | * @return array 129 | */ 130 | private function assignElement(object $obj, array $fields, int $nameType): array 131 | { 132 | $oReflectionClass = new \ReflectionClass($obj); 133 | foreach ($oReflectionClass->getProperties() as $property) { 134 | $key = $property->getName(); 135 | 136 | // 过滤框架内部定义字段 137 | if (FrameEntity::inFilter($key)) { 138 | continue; 139 | } 140 | 141 | switch ($nameType) { 142 | case NameTypeEnum::CAMEL_CASE: 143 | $key = StrUtil::snakeCaseToCamelCase($key); 144 | 145 | break; 146 | case NameTypeEnum::SNAKE_CASE: 147 | $key = StrUtil::camelCaseToSnakeCase($key); 148 | 149 | break; 150 | } 151 | 152 | // 反射 private, protect 可见开启,保证能够获取属性值 (php8.1 默认开启) 153 | $property->setAccessible(true); 154 | $val = $property->getValue($obj); 155 | 156 | if (is_object($val)) { 157 | $val = $this->assignElement($val, $fields, $nameType); 158 | } 159 | 160 | if (is_array($val) && isset($val[0]) && is_object($val[0])) { 161 | $tempVal = []; 162 | foreach ($val as $item) { 163 | $tempVal[] = $this->assignElement($item, $fields, $nameType); 164 | } 165 | $val = $tempVal; 166 | } 167 | 168 | if (empty($fields)) { 169 | $result[$key] = $val; 170 | } elseif (in_array($key, $fields, true)) { 171 | $result[$key] = $val; 172 | } 173 | } 174 | 175 | return $result ?? []; 176 | } 177 | 178 | /** 179 | * @internal 180 | * @param $fields 181 | * @return array 182 | */ 183 | public function toArray($fields = []): array 184 | { 185 | return $this->assignElement($this, $fields, NameTypeEnum::UNLIMITED); 186 | } 187 | 188 | /** 189 | * @internal 190 | * @param $fields 191 | * @return array 192 | */ 193 | public function toSnakeCaseArray($fields = []): array 194 | { 195 | return $this->assignElement($this, $fields, NameTypeEnum::SNAKE_CASE); 196 | } 197 | 198 | /** 199 | * @internal 200 | * @param $fields 201 | * @return array 202 | */ 203 | public function toCamelCaseArray($fields = []): array 204 | { 205 | return $this->assignElement($this, $fields, NameTypeEnum::CAMEL_CASE); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/Support/Traits/AutoFillProperties.php: -------------------------------------------------------------------------------- 1 | registerSingleton(); 49 | } 50 | 51 | if (empty($params)) { 52 | return; 53 | } 54 | 55 | if (is_string($params)) { 56 | $params = json_decode($params, true); 57 | } 58 | 59 | if (!is_object($params) && !is_array($params)) { 60 | new InternalServerErrorException(TypeEnum::INVALID_TYPE); 61 | } 62 | 63 | if (is_object($params)) { 64 | $params = TypeConvert::objToArr($params); 65 | } 66 | 67 | $this->_params = $params; 68 | $annotation = new ClassReflector($cache); 69 | $this->_properties = $annotation->execute(get_class($this))->getClassProperties(); 70 | $this->_alias = $annotation->getAlias(); 71 | $this->_cache = $cache; 72 | 73 | $this->handle(); 74 | } 75 | 76 | /** 77 | * @throws InternalServerErrorException 78 | * @internal 79 | */ 80 | protected function handle(): void 81 | { 82 | $this->fill(); 83 | } 84 | 85 | /** 86 | * @throws InternalServerErrorException 87 | * @internal 88 | */ 89 | public function fill(): void 90 | { 91 | if (empty($this->_properties)) { 92 | return; 93 | } 94 | 95 | $propertyArr = ExtractUtil::getCamelCase($this->_properties, get_class($this)); 96 | 97 | /** 98 | * @var Property $property 99 | */ 100 | foreach ($propertyArr as $name => $property) { 101 | $loopIdx = StrUtil::snakeCaseToCamelCase($name); 102 | 103 | if (FrameEntity::inFilter($name)) { 104 | continue; 105 | } 106 | 107 | // 提取变量值 108 | $value = ExtractUtil::getValue($this->_params, $loopIdx); 109 | 110 | if (is_null($property)) { 111 | $this->{$name} = $value; 112 | 113 | continue; 114 | } 115 | if ($property->isClass) { 116 | $this->fillClass($property, $name, $value); 117 | 118 | continue; 119 | } 120 | 121 | if ($property->isArray) { 122 | $this->fillArray($name, $value ?? []); 123 | 124 | continue; 125 | } 126 | 127 | // 强类型未设置值时,设置为null会报错 128 | if ($property->stronglyTyped && is_null($value)) { 129 | continue; 130 | } 131 | 132 | $this->{$name} = $value; 133 | } 134 | } 135 | 136 | /** 137 | * 填充类属性值为类的值 138 | * 139 | * @internal 140 | * @param Property $property 141 | * @param $name 142 | * @param $values 143 | * @return void 144 | */ 145 | public function fillClass(Property $property, $name, $values): void 146 | { 147 | if (!isset($this->_properties[$property->namespace]) || is_null($values)) { 148 | $this->{$name} = null; 149 | 150 | return; 151 | } 152 | 153 | if ($property->isArray) { 154 | foreach ($values as $value) { 155 | $this->{$name}[] = new $property->namespace($value, $this->_cache); 156 | } 157 | 158 | return; 159 | } 160 | 161 | $this->{$name} = new $property->namespace($values, $this->_cache); 162 | } 163 | 164 | /** 165 | * 填充类属性为数组的值 166 | * 167 | * @internal 168 | * @param $name 169 | * @param array $values 170 | * @return void 171 | */ 172 | public function fillArray($name, array $values): void 173 | { 174 | $this->{$name} = $values; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/Support/Traits/AutoRegisterSingleton.php: -------------------------------------------------------------------------------- 1 | singleton(static::class, function () { 13 | return $this; 14 | }); 15 | } 16 | } 17 | 18 | public static function __callStatic($method, $params = []) 19 | { 20 | if (FrameTypeUtil::isLaravel()) { 21 | return app(static::class)->$method(...$params); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Support/Traits/Getter.php: -------------------------------------------------------------------------------- 1 | _setter = false; 10 | $this->_getter = true; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Support/Traits/Macroable.php: -------------------------------------------------------------------------------- 1 | bindTo($this, static::class), $parameters); 67 | } 68 | 69 | return call_user_func_array(static::$macros[$method], $parameters); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Support/Traits/Scene.php: -------------------------------------------------------------------------------- 1 | passesAuthorization()) { 10 | $this->failedAuthorization(); 11 | } 12 | 13 | $instance = $this->getValidatorInstance(); 14 | 15 | $instance->setRules($this->getSceneRules()); 16 | } 17 | 18 | /** 19 | * @return array 20 | */ 21 | public function getSceneRules(): array 22 | { 23 | $rules = $this->rules(); 24 | $actionMethod = $this->route()->getActionMethod(); 25 | 26 | $scenes = $this->scenes()[$actionMethod] ?? []; 27 | 28 | // 未定义默认未不进行规则校验 29 | if (empty($scenes)) { 30 | return []; 31 | } 32 | 33 | $newRules = []; 34 | foreach ($scenes as $k => $v) { 35 | if (is_numeric($k)) { 36 | $newRules[$v] = $rules[$v]; 37 | 38 | continue; 39 | } 40 | $newRules[$k] = $v; 41 | } 42 | 43 | return $newRules; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Support/Traits/Setter.php: -------------------------------------------------------------------------------- 1 | _setter = true; 10 | $this->_getter = false; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Support/Traits/Singleton.php: -------------------------------------------------------------------------------- 1 | 驼峰 -> 蛇形 依次获取值 86 | * @throws InternalServerErrorException 87 | */ 88 | public static function getValue($params, $key) 89 | { 90 | $value = self::get($params, $key); 91 | 92 | if (is_null($value)) { 93 | $value = self::getCamelCase($params, $key); 94 | } 95 | 96 | if (is_null($value)) { 97 | $value = self::getSnakeCase($params, $key); 98 | } 99 | 100 | return $value; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Support/Utils/FrameTypeUtil.php: -------------------------------------------------------------------------------- 1 | diffInSeconds($start); 24 | } 25 | 26 | /** 27 | * 毫秒. 28 | * 29 | * @param int $loop 30 | * @param $callback 31 | * @return int 32 | */ 33 | public static function milliseconds(int $loop, $callback): int 34 | { 35 | $start = Carbon::now(); 36 | for ($i = 0; $i < $loop; ++$i ) { 37 | $callback(); 38 | } 39 | 40 | return Carbon::now()->diffInMilliseconds($start); 41 | } 42 | 43 | /** 44 | * 微秒. 45 | * 46 | * @param int $loop 47 | * @param $callback 48 | * @return int 49 | */ 50 | public static function microseconds(int $loop, $callback): int 51 | { 52 | $start = Carbon::now(); 53 | for ($i = 0; $i < $loop; ++$i ) { 54 | $callback(); 55 | } 56 | 57 | return Carbon::now()->diffInMicroseconds($start); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Support/Utils/SplUtil.php: -------------------------------------------------------------------------------- 1 | $class] as $newClass) { 22 | $results += self::traitUsesRecursive($newClass); 23 | } 24 | 25 | return array_unique($results); 26 | } 27 | 28 | /** 29 | * 递归获取 trait 类中所有的 trait 类. 30 | * 31 | * @param $trait 32 | * @return array|false|string[] 33 | */ 34 | public static function traitUsesRecursive($trait) 35 | { 36 | $traits = class_uses($trait) ?: []; 37 | 38 | foreach ($traits as $newTrait) { 39 | $traits += self::traitUsesRecursive($newTrait); 40 | } 41 | 42 | return $traits; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Support/Utils/StrUtil.php: -------------------------------------------------------------------------------- 1 | test . 'Storage' . DIRECTORY_SEPARATOR . $key; 14 | file_put_contents($storage, $value); 15 | } 16 | 17 | /** 18 | * @throws JsonException 19 | */ 20 | public function get($key, $default = null) 21 | { 22 | $storage = PathManager::getInstance()->test . 'Storage' . DIRECTORY_SEPARATOR . $key; 23 | if (file_exists($storage)) { 24 | return json_decode(file_get_contents($storage), true, 512, JSON_THROW_ON_ERROR)[$key] ?? $default; 25 | } 26 | 27 | return $default; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/DTO/DTOTest.php: -------------------------------------------------------------------------------- 1 | setName('tests'); 17 | $this->assertEquals('tests', $dto->getName()); 18 | $dto->toArray(); 19 | } 20 | 21 | public function testPageDTO(): void 22 | { 23 | $dto = new OrderListDTO(['shop_id' => 1, 'page' => 3, 'per_page' => 10]); 24 | $this->assertEquals(1, $dto->getShopId()); 25 | $this->assertEquals(3, $dto->getPage()); 26 | $this->assertEquals(10, $dto->getPerPage()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/DTO/ObjDTO.php: -------------------------------------------------------------------------------- 1 | assertEquals('不能除以零', $throwable->getMessage()); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/Performance/AccessorTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 17 | } 18 | 19 | public function test1000(): void 20 | { 21 | Perf::runAccessor(1000); 22 | Perf::runAutoFill(1000); 23 | $this->assertTrue(true); 24 | } 25 | 26 | public function test10000(): void 27 | { 28 | Perf::runAccessor(10000); 29 | Perf::runAutoFill(10000); 30 | $this->assertTrue(true); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Support/AccessorTest.php: -------------------------------------------------------------------------------- 1 | [['size' => 'big'], ['size' => 'small']]]); 24 | 25 | $this->assertEquals('big', $cat->getEyes()[0]->getSize()); 26 | 27 | $setterCat = new SetterCat(['eyes' => [['size' => 'big'], ['size' => 'small']]]); 28 | 29 | Lang::getInstance()->setLocale('en'); 30 | $setterCat->setEyes('red'); 31 | 32 | try { 33 | $setterCat->getEyes(); 34 | } catch (\Exception $e) { 35 | $this->assertEquals('method not define', $e->getMessage()); 36 | } 37 | 38 | $getterCat = new GetterCat(['eyes' => [['size' => 'big'], ['size' => 'small']]]); 39 | $this->assertEquals('small', $getterCat->getEyes()[1]->getSize()); 40 | 41 | try { 42 | $getterCat->setEyes([['size' => 'big'], ['size' => 'small']]); 43 | } catch (\Exception $e) { 44 | $this->assertEquals('method not define', $e->getMessage()); 45 | } 46 | 47 | $success = Response::buildSuccess(); 48 | 49 | $this->assertArrayHasKey('success', $success->toArray()); 50 | $this->assertArrayHasKey('errCode', $success->toArray()); 51 | $this->assertArrayHasKey('errMessage', $success->toArray()); 52 | $this->assertArrayHasKey('data', $success->toArray()); 53 | 54 | $pageSuccess = PageResponse::buildSuccess(); 55 | $this->assertEquals(1, $pageSuccess->toArray()['page']); 56 | $this->assertEquals(20, $pageSuccess->toArray()['perPage']); 57 | $this->assertEquals(0, $pageSuccess->toArray()['totalCount']); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/Support/Annotation/AnnotationTest.php: -------------------------------------------------------------------------------- 1 | assertIsArray($annotation->execute(Cat::class)->getUses()); 26 | } 27 | 28 | /** 29 | * @throws ReflectionException 30 | */ 31 | public function testProperty(): void 32 | { 33 | $annotation = new ClassReflector(); 34 | 35 | $properties = $annotation->execute(Cat::class)->getClassProperties(); 36 | $this->assertArrayHasKey(Cat::class, $properties); 37 | /** 38 | * @var Property $eyes 39 | */ 40 | $eyes = $properties[Cat::class]['eyes']; 41 | $this->assertEquals('$this', $eyes->getDocLabels()['return'][0]); 42 | $this->assertEquals('眼睛.', $eyes->getDocDesc()); 43 | } 44 | 45 | public function testMethod(): void 46 | { 47 | $methods = new Methods(Cat::class); 48 | $this->assertEquals('isCat', $methods->getMethods()[Cat::class . '@' . 'isCat']->name); 49 | } 50 | 51 | /** 52 | * @throws ReflectionException 53 | */ 54 | public function testProperty8(): void 55 | { 56 | // 只对 php8 进行测试 57 | if (FrameTypeUtil::isPHP(7)) { 58 | $this->assertTrue(true); 59 | 60 | return; 61 | } 62 | $annotation = new ClassReflector(); 63 | 64 | $properties = $annotation->execute(Cat8::class)->getClassProperties(); 65 | $this->assertArrayHasKey(Cat8::class, $properties); 66 | /** 67 | * @var Property $eyes 68 | */ 69 | $eyes = $properties[Cat8::class]['eyes']; 70 | $this->assertEquals('眼睛', $eyes->getDocDesc()); 71 | } 72 | 73 | public function testLang() 74 | { 75 | $annotation = new ClassReflector(); 76 | $annotation->setFilter(\ReflectionProperty::IS_PUBLIC); 77 | $properties = $annotation->execute(SupportEnum::class)->getClassProperties(); 78 | $locale = Lang::getInstance()->setLocale('en')->getLocale(); 79 | $this->assertEquals( 80 | 'cannot divide by zero', 81 | $properties[SupportEnum::class]['CANNOT_DIVIDE_BY_ZERO']->getDocLabel($locale)[0] 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/Support/Converts/LengthConvertTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('0.01', $lengthConvert->getNum(2)); 14 | 15 | $lengthConvert = new LengthConvert('1', LengthConvert::METRIC_KM); 16 | $this->assertEquals('1000', $lengthConvert->getNum()); 17 | 18 | $this->assertEquals('1000', $lengthConvert->to(LengthConvert::METRIC_M, 0)); 19 | 20 | $this->assertEquals('25.4000', $lengthConvert->to(LengthConvert::BRITISH_IN)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Support/Converts/WeightConvertTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('0.001', $weightConvert->getNum(3)); 14 | 15 | $weightConvert = new WeightConvert('1', WeightConvert::METRIC_KG); 16 | $this->assertEquals('1', $weightConvert->getNum()); 17 | 18 | $this->assertEquals('6.3', $weightConvert->to(WeightConvert::BRITISH_ST, 1)); 19 | 20 | $this->assertEquals('0.0283', $weightConvert->to(WeightConvert::BRITISH_OZ)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Support/DataExtractTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(1, ExtractUtil::get($json, 'data.0.myNumber')); 18 | 19 | $array = ['data' => [['myNumber' => 1]]]; 20 | $this->assertEquals(1, ExtractUtil::get($array, 'data.0.myNumber')); 21 | 22 | $object = json_decode($json, false); 23 | $this->assertEquals(1, ExtractUtil::get($object, 'data.0.myNumber')); 24 | 25 | // 蛇形转驼峰 26 | $this->assertEquals(1, ExtractUtil::getCamelCase($array, 'data.0.my_number')); 27 | $this->assertEquals(1, ExtractUtil::getValue($array, 'data.0.my_number')); 28 | 29 | $array = ['data' => [['my_number' => 1]]]; 30 | // 驼峰转蛇形 31 | $this->assertEquals(1, ExtractUtil::getSnakeCase($array, 'data.0.myNumber')); 32 | $this->assertEquals(1, ExtractUtil::getValue($array, 'data.0.myNumber')); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Support/Entity/Cat.php: -------------------------------------------------------------------------------- 1 | setId(1); 17 | $dto->getId(); 18 | $dto->setShopId(1); 19 | $dto->getShopId(); 20 | $dto->setOrderNo('aaaaa'); 21 | $dto->getOrderNo(); 22 | $dto->setNum(1); 23 | $dto->getNum(); 24 | $dto->setPrice('11111'); 25 | $dto->getPrice(); 26 | $dto->setPage(1); 27 | $dto->getPage(); 28 | $dto->setPerPage(11); 29 | $dto->getPerPage(); 30 | }); 31 | $actual = $microseconds / (1000 * 1000 * 1.0); 32 | echo '| accessor | ' . $loop . ' | ' . $actual . 's |' . PHP_EOL; 33 | } 34 | 35 | public static function runAutoFill(int $loop): void 36 | { 37 | $microseconds = PerfUtil::microseconds($loop, static function () { 38 | new OrderListDTO([ 39 | 'id' => 1, 40 | 'order_no' => 'abc', 41 | 'shop_id' => 1, 42 | 'price' => '100.0', 43 | 'num' => 100, 44 | 'page' => 3, 45 | 'per_page' => 4, 46 | ]); 47 | }); 48 | $actual = $microseconds / (1000 * 1000 * 1.0); 49 | echo '| auto fill | ' . $loop . ' | ' . $actual . 's |' . PHP_EOL; 50 | } 51 | 52 | public static function runAutoFillCache(int $loop): void 53 | { 54 | $microseconds = PerfUtil::microseconds($loop, static function () { 55 | $cache = new Cache(); 56 | new OrderListDTO([ 57 | 'id' => 1, 58 | 'order_no' => 'abc', 59 | 'shop_id' => 1, 60 | 'price' => '100.0', 61 | 'num' => 100, 62 | 'page' => 3, 63 | 'per_page' => 4, 64 | ], $cache); 65 | }); 66 | $actual = $microseconds / (1000 * 1000 * 1.0); 67 | echo '| auto fill | ' . $loop . ' | ' . $actual . 's |' . PHP_EOL; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/Support/Entity/SetterCat.php: -------------------------------------------------------------------------------- 1 | assertIsArray(ExceptionEnum::getChildConstants()); 14 | $arr = [ExceptionEnum::ATTR_NOT_DEFINE, ExceptionEnum::METHOD_NOT_DEFINE]; 15 | 16 | $this->assertEquals($arr, [ 17 | ExceptionEnum::getParentConstants()['ATTR_NOT_DEFINE'], 18 | ExceptionEnum::getParentConstants()['METHOD_NOT_DEFINE'], 19 | ]); 20 | 21 | $this->assertEquals( 22 | SupportEnum::CANNOT_DIVIDE_BY_ZERO, 23 | SupportEnum::getConstants()['CANNOT_DIVIDE_BY_ZERO'] 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Support/ExceptionTest.php: -------------------------------------------------------------------------------- 1 | setLocale('en'); 19 | 20 | throw new InternalServerErrorException(SupportEnum::METHOD_NOT_DEFINE); 21 | } catch (InternalServerErrorException $e) { 22 | $this->assertEquals('method not define', $e->getMessage()); 23 | } 24 | 25 | try { 26 | Lang::getInstance()->setLocale('en'); 27 | 28 | throw new InvalidRequestException(InvalidRequestEnum::DEFAULT); 29 | } catch (InvalidRequestException $e) { 30 | $this->assertEquals('Business Error', $e->getMessage()); 31 | } 32 | 33 | try { 34 | Lang::getInstance()->setLocale('en'); 35 | 36 | InvalidRequestException::default(); 37 | } catch (InvalidRequestException $e) { 38 | $this->assertEquals(HttpStatusCodeEnum::INVALID_REQUEST, $e::httpStatusCode()); 39 | $this->assertEquals('Business Error', $e->getMessage()); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Support/FileNamespaceTest.php: -------------------------------------------------------------------------------- 1 | execute(Cat::class, __DIR__ . DIRECTORY_SEPARATOR . 'Entity' . DIRECTORY_SEPARATOR . 'Cat.php'); 14 | $uses = FileParser::getInstance()->getUses(); 15 | 16 | $this->assertArrayHasKey(Cat::class, $uses); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/Support/FillTest.php: -------------------------------------------------------------------------------- 1 | [['size' => 'big']], 19 | 'speak' => [ 20 | 'language' => 'english', 21 | ], 22 | 'hair' => [ 23 | 'short', 24 | 'long', 25 | ], 26 | ]; 27 | 28 | $cat = new Cat($params); 29 | $this->assertEquals('big', $cat->getEyes()[0]->getSize()); 30 | $this->assertNull($cat->getEat()); 31 | $this->assertEquals('english', $cat->getSpeak()->getLanguage()); 32 | $this->assertIsArray($cat->getHair()); 33 | $this->assertArrayHasKey('eyes', $cat->toArray()); 34 | $this->assertArrayHasKey('speak', $cat->toCamelCaseArray()); 35 | $this->assertArrayHasKey('hair', $cat->toSnakeCaseArray()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/common.php: -------------------------------------------------------------------------------- 1 |