├── .clang-format ├── .github └── workflows │ ├── ci.yml │ ├── license.yml │ └── release-plz.yml ├── .gitignore ├── .licenserc.yaml ├── .rustfmt.toml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── complex │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── build.rs │ ├── src │ │ └── lib.rs │ └── tests │ │ ├── integration.rs │ │ └── php │ │ └── test.php ├── hello │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── config.m4 │ ├── package.xml │ └── src │ │ └── lib.rs ├── http-client │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── build.rs │ ├── src │ │ ├── client.rs │ │ ├── errors.rs │ │ ├── lib.rs │ │ ├── request.rs │ │ └── response.rs │ └── tests │ │ ├── integration.rs │ │ └── php │ │ └── test.php ├── http-server │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── build.rs │ ├── src │ │ ├── errors.rs │ │ ├── lib.rs │ │ ├── request.rs │ │ ├── response.rs │ │ └── server.rs │ └── tests │ │ ├── integration.rs │ │ └── php │ │ └── test.php └── logging │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── build.rs │ ├── src │ └── lib.rs │ └── tests │ ├── integration.rs │ └── php │ ├── test_php_deprecated.php │ ├── test_php_error.php │ ├── test_php_notice.php │ ├── test_php_say.php │ └── test_php_warning.php ├── phper-alloc ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs └── src │ └── lib.rs ├── phper-build ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md └── src │ └── lib.rs ├── phper-doc ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── doc │ ├── _01_introduction │ │ └── index.md │ ├── _02_quick_start │ │ ├── _01_write_your_first_extension │ │ │ └── index.md │ │ ├── _02_write_a_simple_http_client │ │ │ └── index.md │ │ └── index.md │ ├── _03_integrate_with_pecl │ │ └── index.md │ ├── _04_zval │ │ └── index.md │ ├── _05_internal_types │ │ ├── _01_z_str │ │ │ └── index.md │ │ ├── _02_z_arr │ │ │ └── index.md │ │ ├── _03_z_obj │ │ │ └── index.md │ │ └── index.md │ └── _06_module │ │ ├── _01_hooks │ │ └── index.md │ │ ├── _02_register_functions │ │ └── index.md │ │ ├── _03_register_constants │ │ └── index.md │ │ ├── _04_register_ini_settings │ │ └── index.md │ │ ├── _05_extension_information │ │ └── index.md │ │ ├── _06_register_class │ │ └── index.md │ │ ├── _07_register_interface │ │ └── index.md │ │ ├── _08_register_enum │ │ └── index.md │ │ └── index.md └── src │ └── lib.rs ├── phper-macros ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── src │ ├── alloc.rs │ ├── derives.rs │ ├── globals.rs │ ├── inner.rs │ ├── lib.rs │ ├── log.rs │ └── utils.rs └── tests │ └── integration.rs ├── phper-sys ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── php_wrapper.c └── src │ └── lib.rs ├── phper-test ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── etc │ └── php-fpm.conf └── src │ ├── cli.rs │ ├── context.rs │ ├── fpm.rs │ ├── lib.rs │ └── utils.rs ├── phper ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs └── src │ ├── alloc.rs │ ├── arrays.rs │ ├── classes.rs │ ├── constants.rs │ ├── enums.rs │ ├── errors.rs │ ├── functions.rs │ ├── ini.rs │ ├── lib.rs │ ├── macros.rs │ ├── modules.rs │ ├── objects.rs │ ├── output.rs │ ├── references.rs │ ├── resources.rs │ ├── strings.rs │ ├── types.rs │ ├── utils.rs │ └── values.rs ├── rust-toolchain.toml └── tests └── integration ├── Cargo.toml ├── LICENSE ├── build.rs ├── src ├── arguments.rs ├── arrays.rs ├── classes.rs ├── constants.rs ├── enums.rs ├── errors.rs ├── functions.rs ├── ini.rs ├── lib.rs ├── objects.rs ├── references.rs ├── strings.rs ├── typehints.rs └── values.rs └── tests ├── integration.rs └── php ├── _common.php ├── arguments.php ├── arrays.php ├── classes.php ├── constants.php ├── enums.php ├── errors.php ├── functions.php ├── ini.php ├── objects.php ├── phpinfo.php ├── references.php ├── reflection.php ├── strings.php ├── typehints.php └── values.php /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveMacros: false 7 | AlignConsecutiveAssignments: false 8 | AlignConsecutiveDeclarations: false 9 | AlignEscapedNewlines: Right 10 | AlignOperands: true 11 | AlignTrailingComments: true 12 | AllowAllArgumentsOnNextLine: true 13 | AllowAllConstructorInitializersOnNextLine: true 14 | AllowAllParametersOfDeclarationOnNextLine: true 15 | AllowShortBlocksOnASingleLine: Never 16 | AllowShortCaseLabelsOnASingleLine: false 17 | AllowShortFunctionsOnASingleLine: Empty 18 | AllowShortLambdasOnASingleLine: All 19 | AllowShortIfStatementsOnASingleLine: Never 20 | AllowShortLoopsOnASingleLine: false 21 | AlwaysBreakAfterDefinitionReturnType: None 22 | AlwaysBreakAfterReturnType: None 23 | AlwaysBreakBeforeMultilineStrings: false 24 | AlwaysBreakTemplateDeclarations: MultiLine 25 | BinPackArguments: true 26 | BinPackParameters: true 27 | BraceWrapping: 28 | AfterCaseLabel: false 29 | AfterClass: false 30 | AfterControlStatement: false 31 | AfterEnum: false 32 | AfterFunction: false 33 | AfterNamespace: false 34 | AfterObjCDeclaration: false 35 | AfterStruct: false 36 | AfterUnion: false 37 | AfterExternBlock: false 38 | BeforeCatch: false 39 | BeforeElse: false 40 | IndentBraces: false 41 | SplitEmptyFunction: true 42 | SplitEmptyRecord: true 43 | SplitEmptyNamespace: true 44 | BreakBeforeBinaryOperators: None 45 | BreakBeforeBraces: Attach 46 | BreakBeforeInheritanceComma: false 47 | BreakInheritanceList: BeforeColon 48 | BreakBeforeTernaryOperators: true 49 | BreakConstructorInitializersBeforeComma: false 50 | BreakConstructorInitializers: BeforeColon 51 | BreakAfterJavaFieldAnnotations: false 52 | BreakStringLiterals: true 53 | ColumnLimit: 80 54 | CommentPragmas: '^ IWYU pragma:' 55 | CompactNamespaces: false 56 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 57 | ConstructorInitializerIndentWidth: 4 58 | ContinuationIndentWidth: 4 59 | Cpp11BracedListStyle: true 60 | DeriveLineEnding: true 61 | DerivePointerAlignment: false 62 | DisableFormat: false 63 | ExperimentalAutoDetectBinPacking: false 64 | FixNamespaceComments: true 65 | ForEachMacros: 66 | - foreach 67 | - Q_FOREACH 68 | - BOOST_FOREACH 69 | IncludeBlocks: Preserve 70 | IncludeCategories: 71 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 72 | Priority: 2 73 | SortPriority: 0 74 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 75 | Priority: 3 76 | SortPriority: 0 77 | - Regex: '.*' 78 | Priority: 1 79 | SortPriority: 0 80 | IncludeIsMainRegex: '(Test)?$' 81 | IncludeIsMainSourceRegex: '' 82 | IndentCaseLabels: false 83 | IndentGotoLabels: true 84 | IndentPPDirectives: None 85 | IndentWidth: 4 86 | IndentWrappedFunctionNames: false 87 | JavaScriptQuotes: Leave 88 | JavaScriptWrapImports: true 89 | KeepEmptyLinesAtTheStartOfBlocks: true 90 | MacroBlockBegin: '' 91 | MacroBlockEnd: '' 92 | MaxEmptyLinesToKeep: 1 93 | NamespaceIndentation: None 94 | ObjCBinPackProtocolList: Auto 95 | ObjCBlockIndentWidth: 2 96 | ObjCSpaceAfterProperty: false 97 | ObjCSpaceBeforeProtocolList: true 98 | PenaltyBreakAssignment: 2 99 | PenaltyBreakBeforeFirstCallParameter: 19 100 | PenaltyBreakComment: 300 101 | PenaltyBreakFirstLessLess: 120 102 | PenaltyBreakString: 1000 103 | PenaltyBreakTemplateDeclaration: 10 104 | PenaltyExcessCharacter: 1000000 105 | PenaltyReturnTypeOnItsOwnLine: 60 106 | PointerAlignment: Right 107 | ReflowComments: true 108 | SortIncludes: true 109 | SortUsingDeclarations: true 110 | SpaceAfterCStyleCast: false 111 | SpaceAfterLogicalNot: false 112 | SpaceAfterTemplateKeyword: true 113 | SpaceBeforeAssignmentOperators: true 114 | SpaceBeforeCpp11BracedList: false 115 | SpaceBeforeCtorInitializerColon: true 116 | SpaceBeforeInheritanceColon: true 117 | SpaceBeforeParens: ControlStatements 118 | SpaceBeforeRangeBasedForLoopColon: true 119 | SpaceInEmptyBlock: false 120 | SpaceInEmptyParentheses: false 121 | SpacesBeforeTrailingComments: 1 122 | SpacesInAngles: false 123 | SpacesInConditionalStatement: false 124 | SpacesInContainerLiterals: true 125 | SpacesInCStyleCastParentheses: false 126 | SpacesInParentheses: false 127 | SpacesInSquareBrackets: false 128 | SpaceBeforeSquareBrackets: false 129 | Standard: Latest 130 | StatementMacros: 131 | - Q_UNUSED 132 | - QT_REQUIRE_VERSION 133 | TabWidth: 8 134 | UseCRLF: false 135 | UseTab: Never 136 | ... 137 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 PHPER Framework Team 2 | # PHPER is licensed under Mulan PSL v2. 3 | # You can use this software according to the terms and conditions of the Mulan 4 | # PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | # http://license.coscl.org.cn/MulanPSL2 6 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | # KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | # NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | # See the Mulan PSL v2 for more details. 10 | 11 | name: CI 12 | 13 | on: 14 | push: 15 | branches: 16 | - master 17 | pull_request: 18 | branches: [ "**" ] 19 | 20 | env: 21 | RUST_LOG: debug 22 | CARGO_TERM_COLOR: always 23 | RUST_BACKTRACE: "1" 24 | RUSTFLAGS: "-D warnings" 25 | LLVM_CONFIG_PATH: llvm-config-18 26 | 27 | jobs: 28 | required: 29 | name: Required 30 | needs: 31 | - ci 32 | runs-on: ubuntu-24.04 33 | steps: 34 | - name: Check results 35 | run: | 36 | [[ ${{ needs.ci.result }} == 'success' ]] || exit 1; 37 | 38 | ci: 39 | name: CI 40 | strategy: 41 | fail-fast: false 42 | matrix: 43 | os: 44 | - ubuntu-24.04 45 | - macos-14 46 | php-version: 47 | - "7.0" 48 | - "7.1" 49 | - "7.2" 50 | - "7.3" 51 | - "7.4" 52 | - "8.0" 53 | - "8.1" 54 | - "8.2" 55 | - "8.3" 56 | - "8.4" 57 | 58 | runs-on: ${{ matrix.os }} 59 | steps: 60 | - name: Checkout 61 | uses: actions/checkout@v2 62 | 63 | - name: Install libclang for Linux 64 | if: matrix.os == 'ubuntu-24.04' 65 | run: sudo apt-get update && sudo apt-get install -y llvm-18-dev libclang-18-dev 66 | 67 | - name: Setup PHP 68 | uses: shivammathur/setup-php@v2 69 | with: 70 | php-version: ${{ matrix.php-version }} 71 | tools: php-config 72 | 73 | - name: Setup php-fpm for Linux 74 | if: matrix.os == 'ubuntu-24.04' 75 | run: | 76 | sudo apt-get update 77 | sudo apt-get install -y php${{ matrix.php-version }}-fpm 78 | sudo rm -f /usr/sbin/php-fpm 79 | sudo ln -s /usr/sbin/php-fpm${{ matrix.php-version }} /usr/sbin/php-fpm 80 | 81 | - name: Setup php-fpm for Macos 82 | if: matrix.os == 'macos-14' 83 | run: | 84 | brew install php@${{ matrix.php-version }} 85 | echo "/opt/homebrew/opt/php@${{ matrix.php-version }}/bin" >> "$GITHUB_PATH" 86 | echo "/opt/homebrew/opt/php@${{ matrix.php-version }}/sbin" >> "$GITHUB_PATH" 87 | 88 | - name: PHP version 89 | run: | 90 | php --version 91 | php-fpm --version 92 | php-config || true 93 | 94 | [[ `php --version` == PHP\ ${{ matrix.php-version }}.* ]] || exit 1; 95 | [[ `php-fpm --version` == PHP\ ${{ matrix.php-version }}.* ]] || exit 1; 96 | [[ `php-config --version` == ${{ matrix.php-version }}.* ]] || exit 1; 97 | 98 | - name: Install Rust Nightly 99 | uses: actions-rs/toolchain@v1 100 | with: 101 | toolchain: nightly 102 | components: rustfmt 103 | 104 | - name: Setup cargo cache 105 | uses: actions/cache@v3 106 | with: 107 | path: | 108 | ~/.cargo/bin/ 109 | ~/.cargo/registry/index/ 110 | ~/.cargo/registry/cache/ 111 | ~/.cargo/git/db/ 112 | target/ 113 | key: ${{ matrix.os }}-ci-${{ matrix.php-version }}-${{ hashFiles('**/Cargo.lock') }} 114 | 115 | - name: Cargo fmt 116 | uses: actions-rs/cargo@v1 117 | with: 118 | toolchain: nightly 119 | command: fmt 120 | args: --all -- --check 121 | 122 | - name: Cargo clippy 123 | uses: actions-rs/cargo@v1 124 | with: 125 | command: clippy 126 | args: --release 127 | 128 | - name: Cargo build 129 | uses: actions-rs/cargo@v1 130 | with: 131 | command: build 132 | args: --release 133 | 134 | - name: Cargo test 135 | uses: actions-rs/cargo@v1 136 | with: 137 | command: test 138 | args: --release -- --nocapture 139 | 140 | - name: Cargo doc 141 | uses: actions-rs/cargo@v1 142 | env: 143 | RUSTDOCFLAGS: "-D warnings --cfg docsrs" 144 | with: 145 | toolchain: nightly 146 | command: doc 147 | args: --workspace --no-deps --all-features 148 | -------------------------------------------------------------------------------- /.github/workflows/license.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 PHPER Framework Team 2 | # PHPER is licensed under Mulan PSL v2. 3 | # You can use this software according to the terms and conditions of the Mulan 4 | # PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | # http://license.coscl.org.cn/MulanPSL2 6 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | # KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | # NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | # See the Mulan PSL v2 for more details. 10 | 11 | name: License 12 | 13 | on: 14 | push: 15 | branches: 16 | - master 17 | pull_request: 18 | branches: [ "**" ] 19 | 20 | jobs: 21 | license-check: 22 | name: License check 23 | runs-on: ubuntu-24.04 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v2 27 | - name: Check License Header 28 | uses: apache/skywalking-eyes/header/@d5466651aaded6fbd588d2278eccc469afc89d92 29 | with: 30 | config: .licenserc.yaml 31 | -------------------------------------------------------------------------------- /.github/workflows/release-plz.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 PHPER Framework Team 2 | # PHPER is licensed under Mulan PSL v2. 3 | # You can use this software according to the terms and conditions of the Mulan 4 | # PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | # http://license.coscl.org.cn/MulanPSL2 6 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | # KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | # NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | # See the Mulan PSL v2 for more details. 10 | 11 | name: Release-plz 12 | 13 | permissions: 14 | pull-requests: write 15 | contents: write 16 | 17 | on: workflow_dispatch 18 | 19 | env: 20 | RUST_LOG: debug 21 | CARGO_TERM_COLOR: always 22 | RUST_BACKTRACE: "1" 23 | RUSTFLAGS: "-D warnings" 24 | LLVM_CONFIG_PATH: llvm-config-18 25 | 26 | jobs: 27 | # Release unpublished packages. 28 | release-plz-release: 29 | name: Release-plz release 30 | runs-on: ubuntu-latest 31 | if: ${{ github.repository_owner == 'phper-framework' }} 32 | permissions: 33 | contents: write 34 | steps: 35 | - name: Checkout repository 36 | uses: actions/checkout@v4 37 | with: 38 | fetch-depth: 0 39 | - name: Install Rust toolchain 40 | uses: dtolnay/rust-toolchain@stable 41 | 42 | - name: Install libclang 43 | run: sudo apt-get install -y llvm-18-dev libclang-18-dev 44 | 45 | - name: Setup PHP 46 | uses: shivammathur/setup-php@v2 47 | with: 48 | php-version: 7.4 49 | tools: php-config 50 | 51 | - name: Run release-plz 52 | uses: release-plz/action@v0.5 53 | with: 54 | command: release 55 | env: 56 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 57 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 58 | 59 | # Create a PR with the new versions and changelog, preparing the next release. 60 | release-plz-pr: 61 | name: Release-plz PR 62 | runs-on: ubuntu-latest 63 | if: ${{ github.repository_owner == 'phper-framework' }} 64 | permissions: 65 | contents: write 66 | pull-requests: write 67 | concurrency: 68 | group: release-plz-${{ github.ref }} 69 | cancel-in-progress: false 70 | steps: 71 | - name: Checkout repository 72 | uses: actions/checkout@v4 73 | with: 74 | fetch-depth: 0 75 | - name: Install Rust toolchain 76 | uses: dtolnay/rust-toolchain@stable 77 | - name: Run release-plz 78 | uses: release-plz/action@v0.5 79 | with: 80 | command: release-pr 81 | env: 82 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 83 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | /.cargo 4 | /vendor 5 | core 6 | compile_commands.json 7 | -------------------------------------------------------------------------------- /.licenserc.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 PHPER Framework Team 2 | # PHPER is licensed under Mulan PSL v2. 3 | # You can use this software according to the terms and conditions of the Mulan 4 | # PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | # http://license.coscl.org.cn/MulanPSL2 6 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | # KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | # NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | # See the Mulan PSL v2 for more details. 10 | 11 | header: 12 | license: 13 | spdx-id: MulanPSL-2.0 14 | copyright-owner: PHPER Framework Team 15 | software-name: PHPER 16 | 17 | paths-ignore: 18 | - '**/*.lock' 19 | - '**/*.md' 20 | - '**/.gitignore' 21 | - '**/.gitmodules' 22 | - '.cargo' 23 | - '.clang-format' 24 | - '.idea' 25 | - '.vscode' 26 | - 'LICENSE' 27 | - 'NOTICE' 28 | - 'config.m4' 29 | - 'vendor' 30 | 31 | comment: on-failure 32 | 33 | dependency: 34 | files: 35 | - Cargo.toml 36 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 PHPER Framework Team 2 | # PHPER is licensed under Mulan PSL v2. 3 | # You can use this software according to the terms and conditions of the Mulan 4 | # PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | # http://license.coscl.org.cn/MulanPSL2 6 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | # KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | # NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | # See the Mulan PSL v2 for more details. 10 | 11 | fn_params_layout = "Compressed" 12 | format_code_in_doc_comments = true 13 | format_macro_bodies = true 14 | format_macro_matchers = true 15 | format_strings = true 16 | imports_granularity = "Crate" 17 | merge_derives = true 18 | newline_style = "Unix" 19 | normalize_comments = true 20 | reorder_impl_items = true 21 | use_field_init_shorthand = true 22 | wrap_comments = true 23 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 PHPER Framework Team 2 | # PHPER is licensed under Mulan PSL v2. 3 | # You can use this software according to the terms and conditions of the Mulan 4 | # PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | # http://license.coscl.org.cn/MulanPSL2 6 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | # KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | # NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | # See the Mulan PSL v2 for more details. 10 | 11 | [workspace] 12 | resolver = "2" 13 | members = [ 14 | "phper", 15 | "phper-alloc", 16 | "phper-build", 17 | "phper-macros", 18 | "phper-sys", 19 | "phper-test", 20 | "phper-doc", 21 | 22 | # internal 23 | "examples/*", 24 | "tests/integration", 25 | ] 26 | 27 | [workspace.package] 28 | authors = ["PHPER Framework Team", "jmjoy "] 29 | edition = "2024" 30 | license = "MulanPSL-2.0" 31 | repository = "https://github.com/phper-framework/phper" 32 | rust-version = "1.85" 33 | 34 | [workspace.dependencies] 35 | phper = { version = "0.16.1", path = "./phper" } 36 | phper-alloc = { version = "0.15.2", path = "./phper-alloc" } 37 | phper-build = { version = "0.15.2", path = "./phper-build" } 38 | phper-macros = { version = "0.15.1", path = "./phper-macros" } 39 | phper-sys = { version = "0.15.2", path = "./phper-sys" } 40 | phper-test = { version = "0.15.1", path = "./phper-test" } 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHPER (PHP Enjoy Rust) 2 | 3 | [![CI](https://github.com/phper-framework/phper/actions/workflows/ci.yml/badge.svg)](https://github.com/phper-framework/phper/actions/workflows/ci.yml) 4 | [![Crates](https://img.shields.io/crates/v/phper)](https://crates.io/crates/phper) 5 | [![Docs](https://img.shields.io/docsrs/phper)](https://docs.rs/phper) 6 | [![License](https://img.shields.io/crates/l/phper)](https://github.com/phper-framework/phper/blob/master/LICENSE) 7 | 8 | ## Rust ❤️ PHP 9 | 10 | The framework that allows us to write PHP extensions using pure and safe Rust whenever possible. 11 | 12 | ## Documentation & Tutorial 13 | 14 | - Documentation: 15 | - Tutorial: 16 | 17 | ## Requirement 18 | 19 | ### Necessary 20 | 21 | - **rust** 1.85 or later 22 | - **libclang** 9.0 or later 23 | - **php** 7.0 or later 24 | 25 | ### Tested Support 26 | 27 | | **Category** | **Item** | **Status** | 28 | | --------------- | --------- | ---------- | 29 | | **OS** | Linux | ✅ | 30 | | | macOS | ✅ | 31 | | | Windows | ❌ | 32 | | **PHP Version** | 7.0 ~ 7.4 | ✅ | 33 | | | 8.0 ~ 8.4 | ✅ | 34 | | **PHP Mode** | NTS | ✅ | 35 | | | ZTS | ❌ | 36 | | **SAPI** | CLI | ✅ | 37 | | | FPM | ✅ | 38 | | **Debug** | Disable | ✅ | 39 | | | Enable | ❌ | 40 | 41 | ## Examples 42 | 43 | See [examples](https://github.com/phper-framework/phper/tree/master/examples). 44 | 45 | ## The projects using PHPER 46 | 47 | - [apache/skywalking-php](https://github.com/apache/skywalking-php) - The PHP Agent for Apache SkyWalking, which provides the native tracing abilities for PHP project. 48 | 49 | - [phper-framework/jieba-php](https://github.com/phper-framework/jieba-php) - The Jieba Chinese Word Segmentation Implemented in Rust Bound for PHP. 50 | 51 | ## License 52 | 53 | [MulanPSL-2.0](https://github.com/phper-framework/phper/blob/master/LICENSE). 54 | -------------------------------------------------------------------------------- /examples/complex/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 PHPER Framework Team 2 | # PHPER is licensed under Mulan PSL v2. 3 | # You can use this software according to the terms and conditions of the Mulan 4 | # PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | # http://license.coscl.org.cn/MulanPSL2 6 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | # KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | # NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | # See the Mulan PSL v2 for more details. 10 | 11 | [package] 12 | name = "phper-example-complex" 13 | version = "0.0.0" 14 | authors = { workspace = true } 15 | edition = { workspace = true } 16 | rust-version = { workspace = true } 17 | publish = false 18 | license = { workspace = true } 19 | 20 | [lib] 21 | name = "complex" 22 | crate-type = ["lib", "cdylib"] 23 | 24 | [dependencies] 25 | phper = { workspace = true } 26 | 27 | [dev-dependencies] 28 | phper-test = { workspace = true } 29 | 30 | [build-dependencies] 31 | phper-build = { workspace = true } 32 | -------------------------------------------------------------------------------- /examples/complex/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /examples/complex/README.md: -------------------------------------------------------------------------------- 1 | # complex 2 | 3 | Complex example. 4 | 5 | ## Environment 6 | 7 | ```bash 8 | # Optional, specify if php isn't installed globally. 9 | export PHP_CONFIG= 10 | ``` 11 | 12 | ## Build 13 | 14 | ```bash 15 | cargo build --release 16 | ``` 17 | 18 | ## Test 19 | 20 | ```bash 21 | cargo test --release 22 | ``` 23 | 24 | ## Install 25 | 26 | ```bash 27 | cp target/release/libcomplex.so `${PHP_CONFIG:=php-config} --extension-dir` 28 | ``` 29 | 30 | ## License 31 | 32 | [MulanPSL-2.0](https://github.com/phper-framework/phper/blob/master/LICENSE). 33 | -------------------------------------------------------------------------------- /examples/complex/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | fn main() { 12 | phper_build::register_configures(); 13 | 14 | #[cfg(target_os = "macos")] 15 | { 16 | println!("cargo:rustc-link-arg=-undefined"); 17 | println!("cargo:rustc-link-arg=dynamic_lookup"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/complex/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use phper::{ 12 | arrays::ZArray, 13 | classes::{ClassEntity, Visibility}, 14 | functions::Argument, 15 | ini::{Policy, ini_get}, 16 | modules::Module, 17 | objects::StateObj, 18 | php_get_module, 19 | values::ZVal, 20 | }; 21 | use std::{convert::Infallible, ffi::CStr}; 22 | 23 | fn say_hello(arguments: &mut [ZVal]) -> phper::Result { 24 | let name = &mut arguments[0]; 25 | name.convert_to_string(); 26 | let name = name.as_z_str().unwrap().to_str()?; 27 | Ok(format!("Hello, {}!\n", name)) 28 | } 29 | 30 | fn throw_exception(_: &mut [ZVal]) -> phper::Result<()> { 31 | Err(phper::Error::Boxed("I am sorry".into())) 32 | } 33 | 34 | #[php_get_module] 35 | pub fn get_module() -> Module { 36 | let mut module = Module::new( 37 | env!("CARGO_CRATE_NAME"), 38 | env!("CARGO_PKG_VERSION"), 39 | env!("CARGO_PKG_AUTHORS"), 40 | ); 41 | 42 | // register module ini 43 | module.add_ini("complex.enable", false, Policy::All); 44 | module.add_ini("complex.num", 100, Policy::All); 45 | module.add_ini("complex.ratio", 1.5, Policy::All); 46 | module.add_ini( 47 | "complex.description", 48 | "hello world.".to_owned(), 49 | Policy::All, 50 | ); 51 | 52 | // register hook functions 53 | module.on_module_init(|| {}); 54 | module.on_module_shutdown(|| {}); 55 | module.on_request_init(|| {}); 56 | module.on_request_shutdown(|| {}); 57 | 58 | // register functions 59 | module 60 | .add_function("complex_say_hello", say_hello) 61 | .argument(Argument::new("name")); 62 | module.add_function("complex_throw_exception", throw_exception); 63 | module.add_function("complex_get_all_ini", |_: &mut [ZVal]| { 64 | let mut arr = ZArray::new(); 65 | 66 | let complex_enable = ZVal::from(ini_get::("complex.enable")); 67 | arr.insert("complex.enable", complex_enable); 68 | 69 | let complex_description = ZVal::from(ini_get::>("complex.description")); 70 | arr.insert("complex.description", complex_description); 71 | Ok::<_, Infallible>(arr) 72 | }); 73 | 74 | // register classes 75 | let mut foo_class = ClassEntity::new("FooClass"); 76 | foo_class.add_property("foo", Visibility::Private, 100); 77 | foo_class.add_method( 78 | "getFoo", 79 | Visibility::Public, 80 | |this: &mut StateObj<()>, _: &mut [ZVal]| { 81 | let prop = this.get_property("foo"); 82 | Ok::<_, phper::Error>(prop.clone()) 83 | }, 84 | ); 85 | foo_class 86 | .add_method( 87 | "setFoo", 88 | Visibility::Public, 89 | |this: &mut StateObj<()>, arguments: &mut [ZVal]| -> phper::Result<()> { 90 | this.set_property("foo", arguments[0].clone()); 91 | Ok(()) 92 | }, 93 | ) 94 | .argument(Argument::new("foo")); 95 | module.add_class(foo_class); 96 | 97 | // register extra info 98 | module.add_info("extra info key", "extra info value"); 99 | 100 | module 101 | } 102 | -------------------------------------------------------------------------------- /examples/complex/tests/integration.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use phper_test::{cli::test_php_scripts, utils::get_lib_path}; 12 | use std::{ 13 | env, 14 | path::{Path, PathBuf}, 15 | }; 16 | 17 | #[test] 18 | fn test_php() { 19 | test_php_scripts( 20 | get_lib_path( 21 | PathBuf::from(env!("CARGO_MANIFEST_DIR")) 22 | .join("..") 23 | .join("..") 24 | .join("target"), 25 | "complex", 26 | ), 27 | &[&Path::new(env!("CARGO_MANIFEST_DIR")) 28 | .join("tests") 29 | .join("php") 30 | .join("test.php")], 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /examples/complex/tests/php/test.php: -------------------------------------------------------------------------------- 1 | getMessage(), "I am sorry"); 24 | } 25 | 26 | assert_eq(complex_get_all_ini(), [ 27 | "complex.enable" => false, 28 | "complex.description" => "hello world.", 29 | ]); 30 | 31 | $foo = new FooClass(); 32 | assert_eq($foo->getFoo(), 100); 33 | 34 | $foo->setFoo("Hello"); 35 | assert_eq($foo->getFoo(), "Hello"); 36 | 37 | function assert_eq($left, $right) { 38 | if ($left !== $right) { 39 | throw new AssertionError(sprintf("left != right,\n left: %s,\n right: %s", var_export($left, true), var_export($right, true))); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/hello/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo, will have compiled files and executables. 2 | /target/ 3 | 4 | # These are backup files generated by rustfmt. 5 | **/*.rs.bk 6 | 7 | hello-*.tgz 8 | *.lo 9 | *.la 10 | .libs 11 | acinclude.m4 12 | aclocal.m4 13 | autom4te.cache 14 | build 15 | config.guess 16 | config.h 17 | config.h.in 18 | config.h.in~ 19 | config.log 20 | config.nice 21 | config.status 22 | config.sub 23 | configure 24 | configure.ac 25 | configure.in 26 | include 27 | install-sh 28 | libtool 29 | ltmain.sh 30 | Makefile 31 | Makefile.fragments 32 | Makefile.global 33 | Makefile.objects 34 | missing 35 | mkinstalldirs 36 | modules 37 | php_test_results_*.txt 38 | phpt.* 39 | run-test-info.php 40 | run-tests.php 41 | tests/**/*.diff 42 | tests/**/*.out 43 | tests/**/*.exp 44 | tests/**/*.log 45 | tests/**/*.db 46 | tests/**/*.mem 47 | tmp-php.ini 48 | -------------------------------------------------------------------------------- /examples/hello/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 PHPER Framework Team 2 | # PHPER is licensed under Mulan PSL v2. 3 | # You can use this software according to the terms and conditions of the Mulan 4 | # PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | # http://license.coscl.org.cn/MulanPSL2 6 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | # KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | # NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | # See the Mulan PSL v2 for more details. 10 | 11 | [package] 12 | name = "phper-example-hello" 13 | version = "0.0.0" 14 | publish = false 15 | authors = { workspace = true } 16 | edition = { workspace = true } 17 | rust-version = { workspace = true } 18 | license = { workspace = true } 19 | 20 | [lib] 21 | name = "hello" 22 | crate-type = ["lib", "cdylib"] 23 | 24 | [dependencies] 25 | phper = { workspace = true } 26 | -------------------------------------------------------------------------------- /examples/hello/README.md: -------------------------------------------------------------------------------- 1 | # hello 2 | 3 | Hello world example. 4 | 5 | ## Environment 6 | 7 | ```bash 8 | # Optional, specify if php isn't installed globally. 9 | export PHP_CONFIG= 10 | ``` 11 | 12 | ## Build 13 | 14 | ```bash 15 | cargo build --release 16 | ``` 17 | 18 | ## Run 19 | 20 | ```bash 21 | php -d "extension=target/release/libhello.so" -r "say_hello('Bob');" 22 | ``` 23 | 24 | ## License 25 | 26 | [MulanPSL-2.0](https://github.com/phper-framework/phper/blob/master/LICENSE). 27 | -------------------------------------------------------------------------------- /examples/hello/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | fn main() { 12 | #[cfg(target_os = "macos")] 13 | { 14 | println!("cargo:rustc-link-arg=-undefined"); 15 | println!("cargo:rustc-link-arg=dynamic_lookup"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/hello/config.m4: -------------------------------------------------------------------------------- 1 | dnl Copyright (c) 2022 PHPER Framework Team 2 | dnl PHPER is licensed under Mulan PSL v2. 3 | dnl You can use this software according to the terms and conditions of the Mulan 4 | dnl PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | dnl http://license.coscl.org.cn/MulanPSL2 6 | dnl THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | dnl KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | dnl NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | dnl See the Mulan PSL v2 for more details. 10 | 11 | PHP_ARG_ENABLE([hello], 12 | [whether to enable hello support], 13 | [AS_HELP_STRING([--enable-hello], 14 | [Enable hello support])], 15 | [no]) 16 | 17 | dnl If not enable, `cargo build` run with argument `--release`. 18 | PHP_ARG_ENABLE([cargo_debug], [whether to enable cargo debug mode], 19 | [ --enable-cargo-debug Enable cargo debug], no, no) 20 | 21 | if test "$PHP_hello" != "no"; then 22 | dnl Check cargo command exists or not. 23 | AC_PATH_PROG(CARGO, cargo, no) 24 | if ! test -x "$CARGO"; then 25 | AC_MSG_ERROR([cargo command missing, please reinstall the cargo distribution]) 26 | fi 27 | 28 | AC_DEFINE(HAVE_hello, 1, [ Have hello support ]) 29 | 30 | PHP_NEW_EXTENSION(hello, [ ], $ext_shared) 31 | 32 | CARGO_MODE_FLAGS="--release" 33 | CARGO_MODE_DIR="release" 34 | 35 | if test "$PHP_CARGO_DEBUG" != "no"; then 36 | CARGO_MODE_FLAGS="" 37 | CARGO_MODE_DIR="debug" 38 | fi 39 | 40 | cat >>Makefile.objects<< EOF 41 | all: cargo_build 42 | 43 | clean: cargo_clean 44 | 45 | cargo_build: 46 | # Build the extension file 47 | PHP_CONFIG=$PHP_PHP_CONFIG cargo build $CARGO_MODE_FLAGS 48 | 49 | # Copy the extension file from target dir to modules 50 | if [[ -f ./target/$CARGO_MODE_DIR/libhello.dylib ]] ; then \\ 51 | cp ./target/$CARGO_MODE_DIR/libhello.dylib ./modules/hello.so ; fi 52 | if [[ -f ./target/$CARGO_MODE_DIR/libhello.so ]] ; then \\ 53 | cp ./target/$CARGO_MODE_DIR/libhello.so ./modules/hello.so ; fi 54 | 55 | cargo_clean: 56 | cargo clean 57 | 58 | .PHONY: cargo_build cargo_clean 59 | EOF 60 | 61 | dnl Symbolic link the files for `cargo build` 62 | AC_CONFIG_LINKS([ \ 63 | Cargo.lock:Cargo.lock \ 64 | Cargo.toml:Cargo.toml \ 65 | build.rs:build.rs \ 66 | src:src \ 67 | ]) 68 | fi 69 | -------------------------------------------------------------------------------- /examples/hello/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 17 | hello 18 | pecl.php.net 19 | Hello world example. 20 | The Hello world example of phper. 21 | 22 | jmjoy 23 | jmjoy 24 | jmjoy@apache.org 25 | yes 26 | 27 | 1970-01-01 28 | 29 | 0.0.0 30 | 0.0.0 31 | 32 | 33 | stable 34 | stable 35 | 36 | MulanPSL-2.0 37 | Release notes. 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 7.2.0 52 | 53 | 54 | 1.4.0 55 | 56 | 57 | 58 | hello 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /examples/hello/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use phper::{echo, functions::Argument, modules::Module, php_get_module, values::ZVal}; 12 | 13 | /// The php function, receive arguments with type `ZVal`. 14 | fn say_hello(arguments: &mut [ZVal]) -> phper::Result<()> { 15 | // Get the first argument, expect the type `ZStr`, and convert to Rust utf-8 16 | // str. 17 | let name = arguments[0].expect_z_str()?.to_str()?; 18 | 19 | // Macro which do php internal `echo`. 20 | echo!("Hello, {}!\n", name); 21 | 22 | Ok(()) 23 | } 24 | 25 | /// This is the entry of php extension, the attribute macro `php_get_module` 26 | /// will generate the `extern "C" fn`. 27 | #[php_get_module] 28 | pub fn get_module() -> Module { 29 | // New `Module` with extension info. 30 | let mut module = Module::new( 31 | env!("CARGO_CRATE_NAME"), 32 | env!("CARGO_PKG_VERSION"), 33 | env!("CARGO_PKG_AUTHORS"), 34 | ); 35 | 36 | // Register function `say_hello`, with one argument `name`. 37 | module 38 | .add_function("say_hello", say_hello) 39 | .argument(Argument::new("name")); 40 | 41 | module 42 | } 43 | -------------------------------------------------------------------------------- /examples/http-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 PHPER Framework Team 2 | # PHPER is licensed under Mulan PSL v2. 3 | # You can use this software according to the terms and conditions of the Mulan 4 | # PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | # http://license.coscl.org.cn/MulanPSL2 6 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | # KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | # NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | # See the Mulan PSL v2 for more details. 10 | 11 | [package] 12 | name = "phper-example-http-client" 13 | version = "0.0.0" 14 | authors = { workspace = true } 15 | edition = { workspace = true } 16 | rust-version = { workspace = true } 17 | publish = false 18 | license = { workspace = true } 19 | 20 | [lib] 21 | name = "http_client" 22 | crate-type = ["lib", "cdylib"] 23 | 24 | [dependencies] 25 | phper = { workspace = true } 26 | reqwest = { version = "0.12.12", features = ["blocking", "cookies"] } 27 | thiserror = "2.0.11" 28 | 29 | [dev-dependencies] 30 | phper-test = { workspace = true } 31 | -------------------------------------------------------------------------------- /examples/http-client/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /examples/http-client/README.md: -------------------------------------------------------------------------------- 1 | # http-client 2 | 3 | Http client example. 4 | 5 | Power by [reqwest::blocking](https://docs.rs/reqwest/0.11.4/reqwest/blocking/index.html) api. 6 | 7 | ## Environment 8 | 9 | ```bash 10 | # Optional, specify if php isn't installed globally. 11 | export PHP_CONFIG= 12 | ``` 13 | 14 | ## Build 15 | 16 | ```bash 17 | cargo build --release 18 | ``` 19 | 20 | ## Test 21 | 22 | ```bash 23 | cargo test --release 24 | ``` 25 | 26 | ## Install 27 | 28 | ```bash 29 | cp target/release/libhttp_client.so `${PHP_CONFIG:=php-config} --extension-dir` 30 | ``` 31 | 32 | ## License 33 | 34 | [MulanPSL-2.0](https://github.com/phper-framework/phper/blob/master/LICENSE). 35 | -------------------------------------------------------------------------------- /examples/http-client/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | fn main() { 12 | #[cfg(target_os = "macos")] 13 | { 14 | println!("cargo:rustc-link-arg=-undefined"); 15 | println!("cargo:rustc-link-arg=dynamic_lookup"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/http-client/src/client.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use crate::{errors::HttpClientError, request::RequestBuilderClass}; 12 | use phper::{ 13 | alloc::ToRefOwned, 14 | classes::{ClassEntity, StateClass, Visibility}, 15 | functions::Argument, 16 | }; 17 | use reqwest::blocking::{Client, ClientBuilder}; 18 | use std::{convert::Infallible, mem::take, time::Duration}; 19 | 20 | pub type ClientBuilderClass = StateClass; 21 | 22 | pub type ClientClass = StateClass>; 23 | 24 | const HTTP_CLIENT_BUILDER_CLASS_NAME: &str = "HttpClient\\HttpClientBuilder"; 25 | 26 | const HTTP_CLIENT_CLASS_NAME: &str = "HttpClient\\HttpClient"; 27 | 28 | pub fn make_client_builder_class(client_class: ClientClass) -> ClassEntity { 29 | // `new_with_default_state_constructor` means initialize the state of 30 | // `ClientBuilder` as `Default::default`. 31 | let mut class = ClassEntity::new_with_default_state_constructor(HTTP_CLIENT_BUILDER_CLASS_NAME); 32 | 33 | // Inner call the `ClientBuilder::timeout`. 34 | class 35 | .add_method("timeout", Visibility::Public, |this, arguments| { 36 | let ms = arguments[0].expect_long()?; 37 | let state = this.as_mut_state(); 38 | let builder: ClientBuilder = take(state); 39 | *state = builder.timeout(Duration::from_millis(ms as u64)); 40 | Ok::<_, phper::Error>(this.to_ref_owned()) 41 | }) 42 | .argument(Argument::new("ms")); 43 | 44 | // Inner call the `ClientBuilder::cookie_store`. 45 | class 46 | .add_method("cookie_store", Visibility::Public, |this, arguments| { 47 | let enable = arguments[0].expect_bool()?; 48 | let state = this.as_mut_state(); 49 | let builder: ClientBuilder = take(state); 50 | *state = builder.cookie_store(enable); 51 | Ok::<_, phper::Error>(this.to_ref_owned()) 52 | }) 53 | .argument(Argument::new("enable")); 54 | 55 | // Inner call the `ClientBuilder::build`, and wrap the result `Client` in 56 | // Object. 57 | class.add_method("build", Visibility::Public, move |this, _arguments| { 58 | let state = take(this.as_mut_state()); 59 | let client = ClientBuilder::build(state).map_err(HttpClientError::Reqwest)?; 60 | let mut object = client_class.init_object()?; 61 | *object.as_mut_state() = Some(client); 62 | Ok::<_, phper::Error>(object) 63 | }); 64 | 65 | class 66 | } 67 | 68 | pub fn make_client_class( 69 | request_builder_class: RequestBuilderClass, 70 | ) -> ClassEntity> { 71 | let mut class = 72 | ClassEntity::>::new_with_default_state_constructor(HTTP_CLIENT_CLASS_NAME); 73 | 74 | class.add_method("__construct", Visibility::Private, |_, _| { 75 | Ok::<_, Infallible>(()) 76 | }); 77 | 78 | let request_build_class_ = request_builder_class.clone(); 79 | class 80 | .add_method("get", Visibility::Public, move |this, arguments| { 81 | let url = arguments[0].expect_z_str()?.to_str().unwrap(); 82 | let client = this.as_state().as_ref().unwrap(); 83 | let request_builder = client.get(url); 84 | let mut object = request_build_class_.init_object()?; 85 | *object.as_mut_state() = Some(request_builder); 86 | Ok::<_, phper::Error>(object) 87 | }) 88 | .argument(Argument::new("url")); 89 | 90 | class 91 | .add_method("post", Visibility::Public, move |this, arguments| { 92 | let url = arguments[0].expect_z_str()?.to_str().unwrap(); 93 | let client = this.as_state().as_ref().unwrap(); 94 | let request_builder = client.post(url); 95 | let mut object = request_builder_class.init_object()?; 96 | *object.as_mut_state() = Some(request_builder); 97 | Ok::<_, phper::Error>(object) 98 | }) 99 | .argument(Argument::new("url")); 100 | 101 | class 102 | } 103 | -------------------------------------------------------------------------------- /examples/http-client/src/errors.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use phper::{ 12 | classes::{ClassEntity, ClassEntry, StateClass}, 13 | errors::{Throwable, exception_class}, 14 | }; 15 | 16 | /// The exception class name of extension. 17 | const EXCEPTION_CLASS_NAME: &str = "HttpClient\\HttpClientException"; 18 | 19 | pub fn make_exception_class() -> ClassEntity<()> { 20 | let mut class = ClassEntity::new(EXCEPTION_CLASS_NAME); 21 | // The `extends` is same as the PHP class `extends`. 22 | class.extends(StateClass::from_name("Exception")); 23 | class 24 | } 25 | 26 | #[derive(Debug, thiserror::Error)] 27 | pub enum HttpClientError { 28 | #[error(transparent)] 29 | Reqwest(reqwest::Error), 30 | 31 | #[error("should call '{method_name}()' before call 'body()'")] 32 | ResponseAfterRead { method_name: String }, 33 | 34 | #[error("should not call 'body()' multi time")] 35 | ResponseHadRead, 36 | } 37 | 38 | impl Throwable for HttpClientError { 39 | fn get_class(&self) -> &ClassEntry { 40 | ClassEntry::from_globals(EXCEPTION_CLASS_NAME).unwrap_or_else(|_| exception_class()) 41 | } 42 | } 43 | 44 | impl From for phper::Error { 45 | fn from(e: HttpClientError) -> Self { 46 | phper::Error::throw(e) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/http-client/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use crate::{ 12 | client::{make_client_builder_class, make_client_class}, 13 | errors::make_exception_class, 14 | request::make_request_builder_class, 15 | response::make_response_class, 16 | }; 17 | use phper::{modules::Module, php_get_module}; 18 | 19 | pub mod client; 20 | pub mod errors; 21 | pub mod request; 22 | pub mod response; 23 | 24 | #[php_get_module] 25 | pub fn get_module() -> Module { 26 | let mut module = Module::new( 27 | env!("CARGO_CRATE_NAME"), 28 | env!("CARGO_PKG_VERSION"), 29 | env!("CARGO_PKG_AUTHORS"), 30 | ); 31 | 32 | module.add_class(make_exception_class()); 33 | let response_class = module.add_class(make_response_class()); 34 | let request_builder_class = module.add_class(make_request_builder_class(response_class)); 35 | let client_class = module.add_class(make_client_class(request_builder_class)); 36 | module.add_class(make_client_builder_class(client_class)); 37 | 38 | module 39 | } 40 | -------------------------------------------------------------------------------- /examples/http-client/src/request.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use crate::{errors::HttpClientError, response::ResponseClass}; 12 | use phper::classes::{ClassEntity, StateClass, Visibility}; 13 | use reqwest::blocking::RequestBuilder; 14 | use std::{convert::Infallible, mem::take}; 15 | 16 | pub type RequestBuilderClass = StateClass>; 17 | 18 | pub const REQUEST_BUILDER_CLASS_NAME: &str = "HttpClient\\RequestBuilder"; 19 | 20 | pub fn make_request_builder_class( 21 | response_class: ResponseClass, 22 | ) -> ClassEntity> { 23 | let mut class = ClassEntity::>::new_with_default_state_constructor( 24 | REQUEST_BUILDER_CLASS_NAME, 25 | ); 26 | 27 | class.add_method("__construct", Visibility::Private, |_, _| { 28 | Ok::<_, Infallible>(()) 29 | }); 30 | 31 | class.add_method("send", Visibility::Public, move |this, _arguments| { 32 | let state = take(this.as_mut_state()); 33 | let response = state.unwrap().send().map_err(HttpClientError::Reqwest)?; 34 | let mut object = response_class.new_object([])?; 35 | *object.as_mut_state() = Some(response); 36 | Ok::<_, phper::Error>(object) 37 | }); 38 | 39 | class 40 | } 41 | -------------------------------------------------------------------------------- /examples/http-client/src/response.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use crate::errors::HttpClientError; 12 | use phper::{ 13 | arrays::{InsertKey, ZArray}, 14 | classes::{ClassEntity, StateClass, Visibility}, 15 | values::ZVal, 16 | }; 17 | use reqwest::blocking::Response; 18 | use std::mem::take; 19 | 20 | pub type ResponseClass = StateClass>; 21 | 22 | pub const RESPONSE_CLASS_NAME: &str = "HttpClient\\Response"; 23 | 24 | pub fn make_response_class() -> ClassEntity> { 25 | let mut class = 26 | ClassEntity::>::new_with_default_state_constructor(RESPONSE_CLASS_NAME); 27 | 28 | class.add_method("body", Visibility::Public, |this, _arguments| { 29 | let response = take(this.as_mut_state()); 30 | let response = response.ok_or(HttpClientError::ResponseHadRead)?; 31 | let body = response.bytes().map_err(HttpClientError::Reqwest)?; 32 | Ok::<_, phper::Error>(body.to_vec()) 33 | }); 34 | 35 | class.add_method("status", Visibility::Public, |this, _arguments| { 36 | let response = 37 | this.as_state() 38 | .as_ref() 39 | .ok_or_else(|| HttpClientError::ResponseAfterRead { 40 | method_name: "status".to_owned(), 41 | })?; 42 | 43 | Ok::<_, HttpClientError>(response.status().as_u16() as i64) 44 | }); 45 | 46 | class.add_method("headers", Visibility::Public, |this, _arguments| { 47 | let response = 48 | this.as_state() 49 | .as_ref() 50 | .ok_or_else(|| HttpClientError::ResponseAfterRead { 51 | method_name: "headers".to_owned(), 52 | })?; 53 | let headers_map = response 54 | .headers() 55 | .iter() 56 | .fold(ZArray::new(), |mut acc, (key, value)| { 57 | let arr = acc.entry(key.as_str()).or_insert(ZVal::from(ZArray::new())); 58 | arr.as_mut_z_arr() 59 | .unwrap() 60 | .insert(InsertKey::NextIndex, ZVal::from(value.as_bytes())); 61 | acc 62 | }); 63 | Ok::<_, HttpClientError>(headers_map) 64 | }); 65 | 66 | class 67 | } 68 | -------------------------------------------------------------------------------- /examples/http-client/tests/integration.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use phper_test::{cli::test_php_scripts, utils::get_lib_path}; 12 | use std::{ 13 | env, 14 | path::{Path, PathBuf}, 15 | }; 16 | 17 | #[test] 18 | fn test_php() { 19 | test_php_scripts( 20 | get_lib_path( 21 | PathBuf::from(env!("CARGO_MANIFEST_DIR")) 22 | .join("..") 23 | .join("..") 24 | .join("target"), 25 | "http_client", 26 | ), 27 | &[&Path::new(env!("CARGO_MANIFEST_DIR")) 28 | .join("tests") 29 | .join("php") 30 | .join("test.php")], 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /examples/http-client/tests/php/test.php: -------------------------------------------------------------------------------- 1 | timeout(15000) 24 | ->cookie_store(true) 25 | ->build(); 26 | 27 | $response = $client->get("https://example.com/")->send(); 28 | var_dump([ 29 | "status" => $response->status(), 30 | "headers" => $response->headers(), 31 | "body" => $response->body(), 32 | ]); 33 | 34 | try { 35 | $client->get("file:///")->send(); 36 | throw new AssertionError("no throw exception"); 37 | } catch (HttpClientException $e) { 38 | } 39 | -------------------------------------------------------------------------------- /examples/http-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 PHPER Framework Team 2 | # PHPER is licensed under Mulan PSL v2. 3 | # You can use this software according to the terms and conditions of the Mulan 4 | # PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | # http://license.coscl.org.cn/MulanPSL2 6 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | # KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | # NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | # See the Mulan PSL v2 for more details. 10 | 11 | [package] 12 | name = "phper-example-http-server" 13 | version = "0.0.0" 14 | authors = { workspace = true } 15 | edition = { workspace = true } 16 | rust-version = { workspace = true } 17 | publish = false 18 | license = { workspace = true } 19 | 20 | [lib] 21 | name = "http_server" 22 | crate-type = ["lib", "cdylib"] 23 | 24 | [dependencies] 25 | hyper = { version = "1.6.0", features = ["http1", "server"] } 26 | axum = "0.8.1" 27 | phper = { workspace = true } 28 | thiserror = "2.0.11" 29 | tokio = { version = "1.43.0", features = ["full"] } 30 | reqwest = { version = "0.12.12", features = ["blocking"] } 31 | 32 | [dev-dependencies] 33 | phper-test = { workspace = true } 34 | reqwest = "0.12.12" 35 | -------------------------------------------------------------------------------- /examples/http-server/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /examples/http-server/README.md: -------------------------------------------------------------------------------- 1 | # http-server 2 | 3 | Http server example. 4 | 5 | Power by [tokio](https://crates.io/crates/tokio) and [axum](https://crates.io/crates/axum). 6 | 7 | Because PHP is a single threaded model (NTS), so tokio runtime uses current thread scheduler. 8 | 9 | This is just a demo program, and if it want to be as powerful as `swoole`, 10 | it need to work hard on multiprocessing and asynchronous components. 11 | 12 | ## Environment 13 | 14 | ```bash 15 | # Optional, specify if php isn't installed globally. 16 | export PHP_CONFIG= 17 | ``` 18 | 19 | ## Build 20 | 21 | ```bash 22 | cargo build --release 23 | ``` 24 | 25 | ## Test 26 | 27 | ```bash 28 | cargo test --release 29 | ``` 30 | 31 | ## Run 32 | 33 | ```bash 34 | # Start web server: 35 | php -d "extension=target/release/libhttp_server.so" examples/http-server/tests/php/test.php 36 | 37 | # Request: 38 | curl -i http://127.0.0.1:9000/ 39 | ``` 40 | 41 | ## License 42 | 43 | [MulanPSL-2.0](https://github.com/phper-framework/phper/blob/master/LICENSE). 44 | -------------------------------------------------------------------------------- /examples/http-server/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | fn main() { 12 | #[cfg(target_os = "macos")] 13 | { 14 | println!("cargo:rustc-link-arg=-undefined"); 15 | println!("cargo:rustc-link-arg=dynamic_lookup"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/http-server/src/errors.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use phper::{ 12 | classes::{ClassEntity, ClassEntry, StateClass}, 13 | errors::{Throwable, exception_class}, 14 | }; 15 | use std::error::Error; 16 | 17 | const EXCEPTION_CLASS_NAME: &str = "HttpServer\\HttpServerException"; 18 | 19 | /// Wraps any Error, implements `Throwable` and `Into`. 20 | #[derive(Debug, thiserror::Error)] 21 | #[error(transparent)] 22 | pub struct HttpServerError(pub Box); 23 | 24 | impl HttpServerError { 25 | pub fn new(e: impl Into>) -> Self { 26 | Self(e.into()) 27 | } 28 | } 29 | 30 | impl Throwable for HttpServerError { 31 | fn get_class(&self) -> &ClassEntry { 32 | ClassEntry::from_globals(EXCEPTION_CLASS_NAME).unwrap_or_else(|_| exception_class()) 33 | } 34 | } 35 | 36 | impl From for phper::Error { 37 | fn from(e: HttpServerError) -> Self { 38 | phper::Error::throw(e) 39 | } 40 | } 41 | 42 | /// Register the class `HttpServer\HttpServerException` by `ClassEntity`. 43 | pub fn make_exception_class() -> ClassEntity<()> { 44 | let mut class = ClassEntity::new(EXCEPTION_CLASS_NAME); 45 | // As an Exception class, inheriting from the base Exception class is important. 46 | class.extends(StateClass::from_name("Exception")); 47 | class 48 | } 49 | -------------------------------------------------------------------------------- /examples/http-server/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use crate::{ 12 | errors::make_exception_class, request::make_request_class, response::make_response_class, 13 | server::make_server_class, 14 | }; 15 | use phper::{modules::Module, php_get_module}; 16 | 17 | pub mod errors; 18 | pub mod request; 19 | pub mod response; 20 | pub mod server; 21 | 22 | #[php_get_module] 23 | pub fn get_module() -> Module { 24 | // Add module info. 25 | let mut module = Module::new( 26 | env!("CARGO_CRATE_NAME"), 27 | env!("CARGO_PKG_VERSION"), 28 | env!("CARGO_PKG_AUTHORS"), 29 | ); 30 | 31 | // Register classes. 32 | module.add_class(make_exception_class()); 33 | let request_class = module.add_class(make_request_class()); 34 | let response_class = module.add_class(make_response_class()); 35 | module.add_class(make_server_class(request_class, response_class)); 36 | 37 | module 38 | } 39 | -------------------------------------------------------------------------------- /examples/http-server/src/request.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use phper::{ 12 | arrays::ZArray, 13 | classes::{ClassEntity, StateClass, Visibility}, 14 | }; 15 | use std::convert::Infallible; 16 | 17 | pub type RequestClass = StateClass<()>; 18 | 19 | pub const HTTP_REQUEST_CLASS_NAME: &str = "HttpServer\\HttpRequest"; 20 | 21 | /// Register the class `HttpServer\HttpRequest` by `ClassEntity`. 22 | pub fn make_request_class() -> ClassEntity<()> { 23 | let mut class = ClassEntity::new(HTTP_REQUEST_CLASS_NAME); 24 | 25 | // Register the http headers field with public visibility. 26 | class.add_property("headers", Visibility::Public, ()); 27 | 28 | // Register the http body field with public visibility. 29 | class.add_property("data", Visibility::Public, ()); 30 | 31 | // Register the constructor method with public visibility, initialize the 32 | // headers with empty array. 33 | class.add_method("__construct", Visibility::Public, |this, _arguments| { 34 | this.set_property("headers", ZArray::new()); 35 | Ok::<_, Infallible>(()) 36 | }); 37 | 38 | class 39 | } 40 | -------------------------------------------------------------------------------- /examples/http-server/src/response.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use crate::errors::HttpServerError; 12 | use axum::{ 13 | body::Body, 14 | http::{HeaderName, HeaderValue, Response}, 15 | }; 16 | use phper::{ 17 | classes::{ClassEntity, StateClass, Visibility}, 18 | functions::Argument, 19 | }; 20 | 21 | pub type ResponseClass = StateClass>; 22 | 23 | pub const HTTP_RESPONSE_CLASS_NAME: &str = "HttpServer\\HttpResponse"; 24 | 25 | /// Register the class `HttpServer\HttpResponse` by `ClassEntity`, with the 26 | /// inner state `Response`. 27 | pub fn make_response_class() -> ClassEntity> { 28 | let mut class = ClassEntity::new_with_default_state_constructor(HTTP_RESPONSE_CLASS_NAME); 29 | 30 | // Register the header method with public visibility, accept `name` and `value` 31 | // parameters. 32 | class 33 | .add_method("header", Visibility::Public, |this, arguments| { 34 | let name = arguments[0].expect_z_str()?.to_bytes(); 35 | let value = arguments[1].expect_z_str()?.to_bytes(); 36 | 37 | // Inject the header into inner response state. 38 | let response: &mut Response = this.as_mut_state(); 39 | response.headers_mut().insert( 40 | HeaderName::from_bytes(name).map_err(HttpServerError::new)?, 41 | HeaderValue::from_bytes(value).map_err(HttpServerError::new)?, 42 | ); 43 | 44 | Ok::<_, phper::Error>(()) 45 | }) 46 | .argument(Argument::new("name")) 47 | .argument(Argument::new("value")); 48 | 49 | // Register the end method with public visibility, accept `data` parameters. 50 | class 51 | .add_method("end", Visibility::Public, |this, arguments| { 52 | // Inject the body content into inner response state. 53 | let response: &mut Response = this.as_mut_state(); 54 | *response.body_mut() = arguments[0].expect_z_str()?.to_bytes().to_vec().into(); 55 | Ok::<_, phper::Error>(()) 56 | }) 57 | .argument(Argument::new("data")); 58 | 59 | class 60 | } 61 | -------------------------------------------------------------------------------- /examples/http-server/tests/integration.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use axum::http::header::CONTENT_TYPE; 12 | use hyper::StatusCode; 13 | use phper_test::{cli::test_long_term_php_script_with_condition, utils::get_lib_path}; 14 | use reqwest::blocking::Client; 15 | use std::{ 16 | env, 17 | path::{Path, PathBuf}, 18 | thread::sleep, 19 | time::Duration, 20 | }; 21 | 22 | #[test] 23 | fn test_php() { 24 | test_long_term_php_script_with_condition( 25 | get_lib_path( 26 | PathBuf::from(env!("CARGO_MANIFEST_DIR")) 27 | .join("..") 28 | .join("..") 29 | .join("target"), 30 | "http_server", 31 | ), 32 | Path::new(env!("CARGO_MANIFEST_DIR")) 33 | .join("tests") 34 | .join("php") 35 | .join("test.php"), 36 | |_| { 37 | // wait for server startup. 38 | sleep(Duration::from_secs(3)); 39 | 40 | let client = Client::new(); 41 | for _ in 0..5 { 42 | let response = client.get("http://127.0.0.1:9000/").send().unwrap(); 43 | assert_eq!(response.status(), StatusCode::OK); 44 | let content_type = response.headers().get(CONTENT_TYPE).unwrap(); 45 | assert_eq!(content_type, "text/plain"); 46 | let body = response.text().unwrap(); 47 | assert_eq!(body, "Hello World\n"); 48 | } 49 | }, 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /examples/http-server/tests/php/test.php: -------------------------------------------------------------------------------- 1 | onRequest(function ($request, $response) { 22 | echo "HEADERS:\n"; 23 | foreach ($request->headers as $key => $value) { 24 | echo "$key => $value\n"; 25 | } 26 | echo "\nBODY:\n{$request->data}\n\n"; 27 | 28 | $response->header('Content-Type', 'text/plain'); 29 | $response->header('X-Foo', 'Bar'); 30 | $response->end("Hello World\n"); 31 | }); 32 | 33 | echo "Listening http://127.0.0.1:9000\n\n"; 34 | 35 | $server->start(); 36 | -------------------------------------------------------------------------------- /examples/logging/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 PHPER Framework Team 2 | # PHPER is licensed under Mulan PSL v2. 3 | # You can use this software according to the terms and conditions of the Mulan 4 | # PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | # http://license.coscl.org.cn/MulanPSL2 6 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | # KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | # NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | # See the Mulan PSL v2 for more details. 10 | 11 | [package] 12 | name = "phper-example-logging" 13 | version = "0.0.0" 14 | authors = { workspace = true } 15 | edition = { workspace = true } 16 | rust-version = { workspace = true } 17 | publish = false 18 | license = { workspace = true } 19 | 20 | [lib] 21 | name = "logging" 22 | crate-type = ["lib", "cdylib"] 23 | 24 | [dependencies] 25 | phper = { workspace = true } 26 | 27 | [dev-dependencies] 28 | phper-test = { workspace = true } 29 | 30 | [build-dependencies] 31 | phper-build = { workspace = true } 32 | -------------------------------------------------------------------------------- /examples/logging/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /examples/logging/README.md: -------------------------------------------------------------------------------- 1 | # logging 2 | 3 | Log example. 4 | 5 | ## Environment 6 | 7 | ```bash 8 | # Optional, specify if php isn't installed globally. 9 | export PHP_CONFIG= 10 | ``` 11 | 12 | ## Build 13 | 14 | ```bash 15 | cargo build --release 16 | ``` 17 | 18 | ## Test 19 | 20 | ```bash 21 | cargo test --release 22 | ``` 23 | 24 | ## Install 25 | 26 | ```bash 27 | cp target/release/liblogging.so `${PHP_CONFIG:=php-config} --extension-dir` 28 | ``` 29 | 30 | ## License 31 | 32 | [MulanPSL-2.0](https://github.com/phper-framework/phper/blob/master/LICENSE). 33 | -------------------------------------------------------------------------------- /examples/logging/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | fn main() { 12 | #[cfg(target_os = "macos")] 13 | { 14 | println!("cargo:rustc-link-arg=-undefined"); 15 | println!("cargo:rustc-link-arg=dynamic_lookup"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/logging/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use phper::{ 12 | deprecated, echo, error, functions::Argument, modules::Module, notice, php_get_module, 13 | values::ZVal, warning, 14 | }; 15 | 16 | #[php_get_module] 17 | pub fn get_module() -> Module { 18 | let mut module = Module::new( 19 | env!("CARGO_CRATE_NAME"), 20 | env!("CARGO_PKG_VERSION"), 21 | env!("CARGO_PKG_AUTHORS"), 22 | ); 23 | 24 | module 25 | .add_function("log_say", |params: &mut [ZVal]| -> phper::Result<()> { 26 | params[0].convert_to_string(); 27 | let message = params[0] 28 | .as_z_str() 29 | .unwrap() 30 | .to_str() 31 | .map_err(phper::Error::boxed)?; 32 | echo!("Hello, {}!", message); 33 | Ok(()) 34 | }) 35 | .argument(Argument::new("message")); 36 | 37 | module 38 | .add_function("log_notice", |params: &mut [ZVal]| -> phper::Result<()> { 39 | params[0].convert_to_string(); 40 | let message = params[0] 41 | .as_z_str() 42 | .unwrap() 43 | .to_str() 44 | .map_err(phper::Error::boxed)?; 45 | notice!("Something happened: {}", message); 46 | Ok(()) 47 | }) 48 | .argument(Argument::new("message")); 49 | 50 | module 51 | .add_function("log_warning", |params: &mut [ZVal]| -> phper::Result<()> { 52 | params[0].convert_to_string(); 53 | let message = params[0] 54 | .as_z_str() 55 | .unwrap() 56 | .to_str() 57 | .map_err(phper::Error::boxed)?; 58 | warning!("Something warning: {}", message); 59 | Ok(()) 60 | }) 61 | .argument(Argument::new("message")); 62 | 63 | module 64 | .add_function("log_error", |params: &mut [ZVal]| -> phper::Result<()> { 65 | params[0].convert_to_string(); 66 | let message = params[0] 67 | .expect_z_str()? 68 | .to_str() 69 | .map_err(phper::Error::boxed)?; 70 | error!("Something gone failed: {}", message); 71 | Ok(()) 72 | }) 73 | .argument(Argument::new("message")); 74 | 75 | module 76 | .add_function( 77 | "log_deprecated", 78 | |params: &mut [ZVal]| -> phper::Result<()> { 79 | params[0].convert_to_string(); 80 | let message = params[0] 81 | .expect_z_str()? 82 | .to_str() 83 | .map_err(phper::Error::boxed)?; 84 | deprecated!("Something deprecated: {}", message); 85 | Ok(()) 86 | }, 87 | ) 88 | .argument(Argument::new("message")); 89 | 90 | module 91 | } 92 | -------------------------------------------------------------------------------- /examples/logging/tests/integration.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use phper_test::{cli::test_php_scripts_with_condition, utils::get_lib_path}; 12 | use std::{ 13 | env, 14 | path::{Path, PathBuf}, 15 | str, 16 | }; 17 | 18 | #[test] 19 | fn test_php() { 20 | let base_dir = Path::new(env!("CARGO_MANIFEST_DIR")) 21 | .join("tests") 22 | .join("php"); 23 | 24 | test_php_scripts_with_condition( 25 | get_lib_path( 26 | PathBuf::from(env!("CARGO_MANIFEST_DIR")) 27 | .join("..") 28 | .join("..") 29 | .join("target"), 30 | "logging", 31 | ), 32 | &[ 33 | (&base_dir.join("test_php_say.php"), &|output| { 34 | let stdout = str::from_utf8(&output.stdout).unwrap(); 35 | stdout == "Hello, world!" && output.status.success() 36 | }), 37 | (&base_dir.join("test_php_notice.php"), &|output| { 38 | let stdout = str::from_utf8(&output.stdout).unwrap(); 39 | stdout.contains("Notice:") 40 | && stdout.contains("Something happened: just for test") 41 | && output.status.success() 42 | }), 43 | (&base_dir.join("test_php_warning.php"), &|output| { 44 | let stdout = str::from_utf8(&output.stdout).unwrap(); 45 | stdout.contains("Warning:") 46 | && stdout.contains("Something warning: just for test") 47 | && output.status.success() 48 | }), 49 | (&base_dir.join("test_php_error.php"), &|output| { 50 | let stdout = str::from_utf8(&output.stdout).unwrap(); 51 | stdout.contains("Fatal error:") 52 | && stdout.contains("Something gone failed: just for test") 53 | }), 54 | (&base_dir.join("test_php_deprecated.php"), &|output| { 55 | let stdout = str::from_utf8(&output.stdout).unwrap(); 56 | stdout.contains("Deprecated:") 57 | && stdout.contains("Something deprecated: just for test") 58 | && output.status.success() 59 | }), 60 | ], 61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /examples/logging/tests/php/test_php_deprecated.php: -------------------------------------------------------------------------------- 1 | ; 22 | 23 | /// Creates owned data from borrowed data, by increasing refcount. 24 | fn to_ref_owned(&mut self) -> Self::Owned; 25 | } 26 | 27 | /// Duplicate an object without deep copy, but to only add the refcount, for php 28 | /// refcount struct. 29 | pub trait RefClone { 30 | /// Returns a refcount value with same reference of the value. 31 | fn ref_clone(&mut self) -> Self; 32 | } 33 | -------------------------------------------------------------------------------- /phper-build/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.15.2](https://github.com/phper-framework/phper/compare/phper-build-v0.15.1...phper-build-v0.15.2) - 2025-05-01 11 | 12 | ### Added 13 | 14 | - Add preliminary support for enums ([#201](https://github.com/phper-framework/phper/pull/201)) 15 | 16 | ## [0.15.1](https://github.com/phper-framework/phper/compare/phper-build-v0.15.0...phper-build-v0.15.1) - 2025-04-04 17 | 18 | ### Other 19 | 20 | - Remove workspace.package.version ([#199](https://github.com/phper-framework/phper/pull/199)) 21 | -------------------------------------------------------------------------------- /phper-build/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 PHPER Framework Team 2 | # PHPER is licensed under Mulan PSL v2. 3 | # You can use this software according to the terms and conditions of the Mulan 4 | # PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | # http://license.coscl.org.cn/MulanPSL2 6 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | # KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | # NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | # See the Mulan PSL v2 for more details. 10 | 11 | [package] 12 | name = "phper-build" 13 | description = "Generates stubs for project using phper." 14 | keywords = ["php", "binding"] 15 | version = "0.15.2" 16 | authors = { workspace = true } 17 | edition = { workspace = true } 18 | rust-version = { workspace = true } 19 | repository = { workspace = true } 20 | license = { workspace = true } 21 | 22 | [dependencies] 23 | phper-sys = { workspace = true } 24 | -------------------------------------------------------------------------------- /phper-build/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /phper-build/README.md: -------------------------------------------------------------------------------- 1 | # phper-build 2 | 3 | Generate stubs for project using [phper](https://crates.io/crates/phper). 4 | 5 | Add this crate in your `[build-dependencies]` and using in `build.rs`. 6 | 7 | ## License 8 | 9 | [MulanPSL-2.0](https://github.com/phper-framework/phper/blob/master/LICENSE). 10 | -------------------------------------------------------------------------------- /phper-build/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | #![warn(rust_2018_idioms, missing_docs)] 12 | #![warn(clippy::dbg_macro)] 13 | #![doc = include_str!("../README.md")] 14 | 15 | use phper_sys::*; 16 | 17 | /// Register all php build relative configure parameters, used in `build.rs`. 18 | pub fn register_all() { 19 | register_link_args(); 20 | register_configures(); 21 | } 22 | 23 | /// Register useful rust cfg for project using phper. 24 | pub fn register_configures() { 25 | // versions 26 | println!( 27 | "cargo::rustc-cfg=phper_major_version=\"{}\"", 28 | PHP_MAJOR_VERSION 29 | ); 30 | println!( 31 | "cargo::rustc-cfg=phper_minor_version=\"{}\"", 32 | PHP_MINOR_VERSION 33 | ); 34 | println!( 35 | "cargo::rustc-cfg=phper_release_version=\"{}\"", 36 | PHP_RELEASE_VERSION 37 | ); 38 | 39 | if PHP_DEBUG != 0 { 40 | println!("cargo::rustc-cfg=phper_debug"); 41 | } 42 | 43 | if USING_ZTS != 0 { 44 | println!("cargo::rustc-cfg=phper_zts"); 45 | } 46 | 47 | if PHP_VERSION_ID >= 80100 { 48 | println!("cargo::rustc-cfg=phper_enum_supported"); 49 | } 50 | } 51 | 52 | /// Register link arguments for os-specified situation. 53 | pub fn register_link_args() { 54 | #[cfg(target_os = "macos")] 55 | { 56 | println!("cargo::rustc-link-arg=-undefined"); 57 | println!("cargo::rustc-link-arg=dynamic_lookup"); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /phper-doc/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.15.2](https://github.com/phper-framework/phper/compare/phper-doc-v0.15.1...phper-doc-v0.15.2) - 2025-05-01 11 | 12 | ### Added 13 | 14 | - Enhance enum functionality with access methods ([#203](https://github.com/phper-framework/phper/pull/203)) 15 | - Add preliminary support for enums ([#201](https://github.com/phper-framework/phper/pull/201)) 16 | 17 | ### Other 18 | 19 | - Adjust the documentation of the enum ([#202](https://github.com/phper-framework/phper/pull/202)) 20 | 21 | ## [0.15.1](https://github.com/phper-framework/phper/compare/phper-doc-v0.15.0...phper-doc-v0.15.1) - 2025-04-04 22 | 23 | ### Other 24 | 25 | - Remove workspace.package.version ([#199](https://github.com/phper-framework/phper/pull/199)) 26 | - allow static interface methods ([#198](https://github.com/phper-framework/phper/pull/198)) 27 | - test and document co-dependent classes ([#194](https://github.com/phper-framework/phper/pull/194)) 28 | - improve Interface::extends and ClassEntry::extends ([#190](https://github.com/phper-framework/phper/pull/190)) 29 | - refactor ClassEntity.implements ([#189](https://github.com/phper-framework/phper/pull/189)) 30 | - [breaking] add argument and return value type-hints ([#187](https://github.com/phper-framework/phper/pull/187)) 31 | -------------------------------------------------------------------------------- /phper-doc/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 PHPER Framework Team 2 | # PHPER is licensed under Mulan PSL v2. 3 | # You can use this software according to the terms and conditions of the Mulan 4 | # PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | # http://license.coscl.org.cn/MulanPSL2 6 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | # KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | # NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | # See the Mulan PSL v2 for more details. 10 | 11 | [package] 12 | name = "phper-doc" 13 | description = "The documentation of phper." 14 | keywords = ["php", "documentation"] 15 | version = "0.15.2" 16 | authors = { workspace = true } 17 | edition = { workspace = true } 18 | rust-version = { workspace = true } 19 | repository = { workspace = true } 20 | license = { workspace = true } 21 | 22 | [dependencies] 23 | phper = { workspace = true } 24 | 25 | [dev-dependencies] 26 | thiserror = "2.0.11" 27 | reqwest = { version = "0.12.12", features = ["blocking", "cookies"] } 28 | 29 | [build-dependencies] 30 | phper-build = { workspace = true } 31 | 32 | [lints.rust] 33 | unexpected_cfgs = { level = "warn", check-cfg = [ 34 | 'cfg(phper_enum_supported)', 35 | ] } 36 | 37 | [package.metadata.docs.rs] 38 | rustdoc-args = ["--cfg", "docsrs"] 39 | all-features = true 40 | -------------------------------------------------------------------------------- /phper-doc/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /phper-doc/README.md: -------------------------------------------------------------------------------- 1 | # PHPER documentation 2 | 3 | This is the documentation of [phper](https://crates.io/crates/phper). 4 | 5 | **There is nothing here other than documentation, so you don't have to import this crate as a dependency.** 6 | 7 | ## License 8 | 9 | [MulanPSL-2.0](https://github.com/phper-framework/phper/blob/master/LICENSE). 10 | -------------------------------------------------------------------------------- /phper-doc/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | fn main() { 12 | phper_build::register_all(); 13 | } 14 | -------------------------------------------------------------------------------- /phper-doc/doc/_01_introduction/index.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | `PHPER` is the framework that allows us to write PHP extensions using pure and safe Rust whenever possible. 4 | 5 | `PHPER` means `PHP Enjoy Rust`. 6 | 7 | ## Rust ❤️ PHP 8 | 9 | The crates are not only the PHP binding for Rust, but also the framework for writing PHP extension. 10 | 11 | ## Purpose 12 | 13 | I used to use the C language to write PHP extensions. At that time, C and C++ were the only ways to write PHP extensions. 14 | 15 | But I found the problem was that using the C language could easily cause memory problems, which were very troublesome when debugging PHP extensions. 16 | 17 | Moreover, third-party libraries in the C language are not easy to use, and version compatibility problems are often encountered in dynamic linking, which is inconvenient to use. 18 | 19 | Later, Rust appeared, and I started to use C to call Rust's FFI to develop PHP extensions. The experience was better than using only the C language. 20 | 21 | However, it was not convenient for Rust to generate C ABI and then call C, so I got the idea of using pure Rust to write PHP extensions. 22 | 23 | So I started building the framework for phper. 24 | 25 | Another goal is to enable PHP to benefit from the Rust ecosystem. 26 | -------------------------------------------------------------------------------- /phper-doc/doc/_02_quick_start/_01_write_your_first_extension/index.md: -------------------------------------------------------------------------------- 1 | # Write your first extension 2 | 3 | Here we will write the `hello world` extension, which has a function, receive the person name and echo hello to the person. 4 | 5 | Full example is . 6 | 7 | ## Steps 8 | 9 | 1. Make sure `libclang` is installed (required by [bindgen](https://rust-lang.github.io/rust-bindgen/requirements.html)). 10 | 11 | `phper` require libclang *9.0+*. 12 | 13 | ```shell 14 | # If you are using debian like linux system: 15 | sudo apt install llvm-10-dev libclang-10-dev 16 | ``` 17 | 18 | 1. Create the cargo project, with the extension name. 19 | 20 | ```shell 21 | cargo new --lib hello 22 | 23 | cd hello 24 | ``` 25 | 26 | 1. Add the metadata to the `Cargo.toml` to build the `.so` file. 27 | 28 | ```toml 29 | # Cargo.toml 30 | 31 | [lib] 32 | crate-type = ["cdylib"] 33 | ``` 34 | 35 | Run the command to add `phper` dependency. 36 | 37 | ```shell 38 | cargo add phper 39 | ``` 40 | 41 | 1. Create the `build.rs` (adapting MacOS). 42 | 43 | ```rust,no_run 44 | fn main() { 45 | #[cfg(target_os = "macos")] 46 | { 47 | println!("cargo:rustc-link-arg=-undefined"); 48 | println!("cargo:rustc-link-arg=dynamic_lookup"); 49 | } 50 | } 51 | ``` 52 | 53 | 1. Add this code to `src/lib.rs`. 54 | 55 | ```rust 56 | use phper::{echo, functions::Argument, modules::Module, php_get_module, values::ZVal}; 57 | 58 | /// The php function, receive arguments with type `ZVal`. 59 | fn say_hello(arguments: &mut [ZVal]) -> phper::Result<()> { 60 | // Get the first argument, expect the type `ZStr`, and convert to Rust utf-8 61 | // str. 62 | let name = arguments[0].expect_z_str()?.to_str()?; 63 | 64 | // Macro which do php internal `echo`. 65 | echo!("Hello, {}!\n", name); 66 | 67 | Ok(()) 68 | } 69 | 70 | /// This is the entry of php extension, the attribute macro `php_get_module` 71 | /// will generate the `extern "C" fn`. 72 | #[php_get_module] 73 | pub fn get_module() -> Module { 74 | // New `Module` with extension info. 75 | let mut module = Module::new( 76 | env!("CARGO_PKG_NAME"), 77 | env!("CARGO_PKG_VERSION"), 78 | env!("CARGO_PKG_AUTHORS"), 79 | ); 80 | 81 | // Register function `say_hello`, with one argument `name`. 82 | module.add_function("say_hello", say_hello).argument(Argument::new("name")); 83 | 84 | module 85 | } 86 | ``` 87 | 88 | 1. Build, if your PHP isn't installed globally, you should specify the path of `php-config`. 89 | 90 | ```bash 91 | # Optional, specify if php isn't installed globally, 92 | # this environment is used by `phper-sys`. 93 | # 94 | # export PHP_CONFIG= 95 | 96 | # Build libhello.so. 97 | cargo build 98 | ``` 99 | 100 | 1. Run the php command with the extension. 101 | 102 | ```shell 103 | php -d "extension=target/debug/libhello.so" -r "say_hello('Bob');" 104 | ``` 105 | 106 | Then you can get the output: 107 | 108 | ```text 109 | Hello, Bob! 110 | ``` 111 | -------------------------------------------------------------------------------- /phper-doc/doc/_02_quick_start/index.md: -------------------------------------------------------------------------------- 1 | # Quick start 2 | 3 | At the beginning, you need to: 4 | 5 | 1. Be familiar with the Rust language. 6 | 2. Know the general process of PHP extension development. 7 | 8 | Here the tutorial will not explain the installation methods of Rust and PHP. 9 | -------------------------------------------------------------------------------- /phper-doc/doc/_04_zval/index.md: -------------------------------------------------------------------------------- 1 | # Zval 2 | 3 | > Refer to: 4 | > 5 | > A zval (short for “Zend value”) represents an arbitrary PHP value. 6 | > As such it is likely the most important structure in all of PHP and 7 | > you’ll be working with it a lot. 8 | 9 | The [`phper::values::ZVal`] is the wrapper of php zval. 10 | 11 | ## Actual type of ZVal 12 | 13 | PHP is a dynamically typed language, zval can represent multiple types, 14 | but there is only one type at a time, you can use 15 | [`phper::values::ZVal::get_type_info`] to get the actual type. 16 | 17 | ## Convert Rust type to ZVal 18 | 19 | The [`phper::values::ZVal`] implements a lot of [`std::convert::From`] for the 20 | conversion. 21 | 22 | Here is the mapping of relationships between Rust types and base PHP types. 23 | 24 | | Trait | Rust type | PHP type | 25 | | --------------- | --------------------------- | -------- | 26 | | `From<()>` | `()` | null | 27 | | `From` | `bool` | bool | 28 | | `From` | `i64` | long | 29 | | `From` | `f64` | double | 30 | | `From<&str>` | `&str` | string | 31 | | `From<&CStr>` | `&CStr` | string | 32 | | `From<&[u8]>` | `&[u8]` | string | 33 | | `From>` | `Vec` | string | 34 | | `From` | [`phper::strings::ZString`] | string | 35 | | `From` | [`phper::arrays::ZArray`] | array | 36 | | `From` | [`phper::objects::ZObject`] | object | 37 | 38 | There are also composite types that implement `From`. 39 | 40 | - `From>`: if Some(T), T will be converted to PHP type like `From`, 41 | or `None` will be converted to `null`. 42 | 43 | ```rust,no_run 44 | use phper::values::ZVal; 45 | 46 | assert!(ZVal::from(()).get_type_info().is_null()); 47 | assert!(ZVal::from(100i64).get_type_info().is_long()); 48 | ``` 49 | 50 | ## Convert ZVal to Rust type 51 | 52 | Now you can use `as_*` or `expect_*` methods to convert ZVal to Rust types. 53 | 54 | - The `as_*` returns `Option`. 55 | 56 | - The `expect_*` returns `phper::Result`, if convert failed, 57 | [phper::errors::ExpectTypeError] will be returned, with the message: 58 | `type error: must be of type {expect_type}, {actual_type} given")`. 59 | 60 | ```rust,no_run 61 | use phper::echo; 62 | use phper::values::ZVal; 63 | 64 | fn say_hello(arguments: &mut [ZVal]) -> phper::Result<()> { 65 | // Get the first argument, expect the type `ZStr`, and convert to Rust utf-8 66 | // str. 67 | let name = arguments[0].expect_z_str()?.to_str()?; 68 | 69 | // Macro which runs PHP internal function `echo`. 70 | echo!("Hello, {}!\n", name); 71 | 72 | Ok(()) 73 | } 74 | ``` 75 | 76 | ## Value copy & reference counting copy 77 | 78 | The [`phper::values::ZVal`] both implements [`std::clone::Clone`] and 79 | [`phper::alloc::RefClone`]. 80 | 81 | - [`std::clone::Clone`]: represent value copy (Type ZObject is excluded because it is always passed by reference). 82 | 83 | - [`phper::alloc::RefClone`]: represent reference counting copy (Type (), bool, 84 | i64, f64 is excluded because they are not reference counting types). 85 | 86 | ## PHP internal cast 87 | 88 | > Refer to: 89 | 90 | PHP is a weakly typed language, yet it supports type casting internally. 91 | 92 | The zend engine provides `convert_to_*` functions to do the type cast, and 93 | `ZVal` wraps them directly. 94 | 95 | ## Callable 96 | 97 | The [`phper::values::ZVal`] can be called via [`phper::values::ZVal::call`]. Make 98 | sure that the actual type is callable (string, array or closure). 99 | 100 | ```rust,no_run 101 | use phper::values::ZVal; 102 | use phper::arrays::ZArray; 103 | 104 | let mut arr = ZArray::new(); 105 | arr.insert("a", ZVal::from(1)); 106 | arr.insert("b", ZVal::from(2)); 107 | let ret = ZVal::from("json_encode").call(&mut [ZVal::from(arr)]).unwrap(); 108 | assert_eq!(ret.expect_z_str().unwrap().to_str(), Ok(r#"{"a":1,"b":2}"#)); 109 | ``` 110 | -------------------------------------------------------------------------------- /phper-doc/doc/_05_internal_types/_01_z_str/index.md: -------------------------------------------------------------------------------- 1 | # Z Str 2 | 3 | > A string is series of characters, where a character is the same as a byte. 4 | > 5 | > Refer: 6 | 7 | The [`&ZStr`](phper::strings::ZStr) and [`ZString`](phper::strings::ZString) are 8 | wrappers for [`zend_string`](phper::sys::zend_string). 9 | 10 | `ZStr` can be converted to `&[u8]`, `&CStr` and `&str`. 11 | 12 | `ZString` can be constructed from `impl AsRef<[u8]>` and has pair of `from_raw()` 13 | and `into_raw()`, like in [`Box`]. 14 | 15 | ```rust,no_run 16 | use phper::strings::ZString; 17 | 18 | let s = ZString::new("Hello world!"); 19 | 20 | // Will leak memory. 21 | let ptr = ZString::into_raw(s); 22 | 23 | // retake pointer. 24 | let ss = unsafe { ZString::from_raw(ptr) }; 25 | 26 | // `ZString` implements `PartialEq`. 27 | assert_eq!(ss, "Hello world!"); 28 | ``` 29 | 30 | `ZString` can be dereferenced to `ZStr`. 31 | 32 | ```rust,no_run 33 | use phper::strings::ZString; 34 | 35 | let s = ZString::new("Hello world!"); 36 | 37 | // `to_str` is the method of `ZStr`. 38 | assert_eq!(s.to_str(), Ok("Hello world!")); 39 | ``` 40 | 41 | `ZStr` implements `ToOwned`. It can upgrade to `ZString` by value copying. 42 | 43 | Because `zend_string` is reference counting type, so `ZStr` also implements 44 | [`ToRefOwned`](phper::alloc::ToRefOwned) (just like 45 | [`RefClone`](phper::alloc::RefClone) for [`ZVal`](phper::values::ZVal)), can 46 | upgrade to `ZString` by refcount increment. 47 | 48 | ```rust,no_run 49 | use phper::sys; 50 | use phper::strings::ZStr; 51 | use phper::alloc::ToRefOwned; 52 | 53 | unsafe extern "C" { 54 | fn something() -> *mut sys::zend_string; 55 | } 56 | 57 | let s = unsafe { ZStr::from_mut_ptr(something()) }; 58 | 59 | // By value copying. 60 | let _s = s.to_owned(); 61 | 62 | // By refcount increment. 63 | let _s = s.to_ref_owned(); 64 | ``` 65 | 66 | Note that neither `ZStr` nor `ZString` implements `Send` and `Sync`, because PHP 67 | is single-threaded. 68 | -------------------------------------------------------------------------------- /phper-doc/doc/_05_internal_types/_02_z_arr/index.md: -------------------------------------------------------------------------------- 1 | # Z Arr 2 | 3 | > An array in PHP is actually an ordered map. A map is a type that associates 4 | > values to keys. This type is optimized for several different uses; it can be 5 | > treated as an array, list (vector), hash table (an implementation of a map), 6 | > dictionary, collection, stack, queue, and probably more. As array values can 7 | > be other arrays, trees and multidimensional arrays are also possible. 8 | > 9 | > Refer: 10 | 11 | *In fact, I don't agree with PHP's practice of mixing list and map. I prefer* 12 | *python to separate list and dictionary as two types. For example, when* 13 | *serializing into json, the serialization function has to judge whether the key* 14 | *of the array starts from 0 and increment by 1 to confirm whether the array is* 15 | *a list. I think it is a waste of performance.* 16 | 17 | The [`&ZArr`](phper::arrays::ZArr) and [`ZArray`](phper::arrays::ZArray) are 18 | the wrappers for [`zend_array`](phper::sys::zend_array) (same as `Hashtable`). 19 | 20 | [`&ZArr`](phper::arrays::ZArr) acts like [`HashMap`](std::collections::HashMap), 21 | also has api `insert()`, `get()`, `remove()`, but it's key type is 22 | [`Key`](phper::arrays::Key) and value type is [`ZVal`](phper::values::ZVal). 23 | 24 | Notice that phper prefers to use [`Symtables`](https://www.phpinternalsbook.com/php5/hashtables/array_api.html#symtables) api `zend_symtable_*`, 25 | so `get(42)` and `get("42")` should be considered the same. 26 | 27 | `ZArray` can be dereferenced to `ZArr`. 28 | 29 | ```rust,no_run 30 | use phper::arrays::{ZArray, InsertKey}; 31 | use phper::values::ZVal; 32 | 33 | let mut arr = ZArray::new(); 34 | 35 | arr.insert(InsertKey::NextIndex, ZVal::default()); 36 | arr.insert(10, ZVal::from(100)); 37 | arr.insert("foo", ZVal::from("bar")); 38 | 39 | let _i = arr.get("10"); 40 | 41 | arr.remove("foo"); 42 | ``` 43 | 44 | `ZArr` can be iterated by `iter()`. 45 | 46 | ```rust,no_run 47 | use phper::arrays::ZArray; 48 | use phper::values::ZVal; 49 | 50 | let arr = ZArray::new(); 51 | 52 | for (k, v) in arr.iter() { 53 | } 54 | ``` 55 | 56 | `ZArr` implements `ToOwned` and it can upgrade to `ZArray` by value copying via 57 | `zend_array_dup`. 58 | 59 | Because `zend_array` is reference counting type, `ZArr` also implements 60 | [`ToRefOwned`](phper::alloc::ToRefOwned) (similar to 61 | [`RefClone`](phper::alloc::RefClone) for [`ZVal`](phper::values::ZVal)), allowing an 62 | upgrade to `ZArray` by incrementing the refcount. 63 | 64 | ```rust,no_run 65 | use phper::sys; 66 | use phper::arrays::ZArr; 67 | use phper::alloc::ToRefOwned; 68 | 69 | unsafe extern "C" { 70 | fn something() -> *mut sys::zend_array; 71 | } 72 | 73 | let arr = unsafe { ZArr::from_mut_ptr(something()) }; 74 | 75 | // By value copy. 76 | let _arr = arr.to_owned(); 77 | 78 | // By refcount increment. 79 | let _arr = arr.to_ref_owned(); 80 | ``` 81 | 82 | Note that neither `ZArr` nor `ZArray` implements `Send` and `Sync`, because PHP 83 | is single-threaded. 84 | -------------------------------------------------------------------------------- /phper-doc/doc/_05_internal_types/_03_z_obj/index.md: -------------------------------------------------------------------------------- 1 | # Z Obj 2 | 3 | The [`&ZObj`](phper::objects::ZObj) and [`ZObject`](phper::objects::ZObject) are 4 | the wrappers for [`zend_object`](phper::sys::zend_object). 5 | 6 | You can do OOP operation using `ZObj` or `ZObject`, like getting and setting properties, 7 | calling methods, etc. 8 | 9 | ```rust,no_run 10 | use phper::classes::ClassEntry; 11 | use phper::objects::ZObject; 12 | use phper::errors::exception_class; 13 | 14 | let mut e: ZObject = exception_class().new_object([]).unwrap(); 15 | e.set_property("code", 403); 16 | e.set_property("message", "oh no"); 17 | let _message = e.call("getMessage", []).unwrap(); 18 | ``` 19 | 20 | `ZObj` implements `ToRefOwned` to upgrade to `ZObject`, duplicate the object via increment refcount. 21 | 22 | `ZObject` implements `RefClone`, same as `ZObj::to_owned`. 23 | 24 | ```rust,no_run 25 | use phper::sys; 26 | use phper::objects::ZObj; 27 | use phper::alloc::ToRefOwned; 28 | 29 | unsafe extern "C" { 30 | fn something() -> *mut sys::zend_object; 31 | } 32 | 33 | let o = unsafe { ZObj::from_mut_ptr(something()) }; 34 | 35 | // By refcount increment. 36 | let _o = o.to_ref_owned(); 37 | ``` 38 | 39 | Note that neither `ZObj` nor `ZObject` implements `Send` and `Sync`, because PHP 40 | is single-threaded. 41 | -------------------------------------------------------------------------------- /phper-doc/doc/_05_internal_types/index.md: -------------------------------------------------------------------------------- 1 | # Internal types 2 | 3 | ## DST & Owned Type 4 | 5 | In Rust, there are many types that appear in pairs, like [str] / [String], 6 | [OsStr](std::ffi::OsStr) / [OsString](std::ffi::OsString), 7 | [CStr](std::ffi::CStr) / [CString](std::ffi::CString). 8 | 9 | For example: 10 | 11 | - [str]: Dynamically sized type, implements `!Sized`, usually used with reference 12 | notation, as `&str`. 13 | - [String]: Ownership type, encapsulates a pointer to a heap memory allocation. 14 | 15 | PHPER follows this design, there are the following types: 16 | 17 | - [ZStr](phper::strings::ZStr) / [ZString](phper::strings::ZString) 18 | - [ZArr](phper::arrays::ZArr) / [ZArray](phper::arrays::ZArray) 19 | - [ZObj](phper::objects::ZObj) / [ZObject](phper::objects::ZObject) 20 | 21 | > It seems that there is no need to separate into two types, but `ZStr`, 22 | > `ZArr`, and `ZObj` are all transparent types. Therefore, the fields of the struct 23 | > cannot be changed. Separating an ownership struct `ZString`, `ZArray`, and `ZObject`, 24 | > and then we can change fields of the struct in the future. 25 | 26 | ## Mapping relationship 27 | 28 | Here is the mapping of relationships between Rust types and base PHP types. 29 | 30 | | Rust type | PHP type | 31 | | ---------------- | -------- | 32 | | `()` | null | 33 | | `bool` | bool | 34 | | `i64` | long | 35 | | `f64` | double | 36 | | `ZStr / ZString` | string | 37 | | `ZArr / ZArray` | array | 38 | | `ZObj / ZObject` | object | 39 | | `ZRes` | resource | 40 | 41 | *Why is there no ZResource? Because Resource is a relatively old type, it* 42 | *is generally replaced by Class now, and the role of ZRes is only compatible* 43 | *with old extension resources.* 44 | -------------------------------------------------------------------------------- /phper-doc/doc/_06_module/_01_hooks/index.md: -------------------------------------------------------------------------------- 1 | # Hooks 2 | 3 | > PHP is a complex piece of machinery, and its lifecycle should be understood 4 | > by anyone who wants to grasp how PHP operates. 5 | > 6 | > Refer: 7 | 8 | PHP provides many hooks in lifecycle for extension to override. 9 | 10 | There are `MINIT`, `MSHUTDOWN`, `RINIT`, `RSHUTDOWN`, `GINIT`, `RSHUTDOWN`. 11 | 12 | Correspondingly, `PHPER` sets these hooks to complete some internal operations, 13 | such as registering extension information, functions, classes, constants, etc. 14 | However, it also exposes these hooks to users for overwriting. 15 | 16 | The following presents the corresponding relationships between PHP hooks and `Module` 17 | methods: 18 | 19 | | PHP hooks | `Module` method | 20 | | --------- | -------------------------------------------------------- | 21 | | MINIT | [on_module_init](phper::modules::Module::on_module_init) | 22 | | MSHUTDOWN | [on_module_shutdown](phper::modules::Module::on_module_shutdown) | 23 | | RINIT | [on_request_init](phper::modules::Module::on_request_init) | 24 | | RSHUTDOWN | [on_request_shutdown](phper::modules::Module::on_request_shutdown) | 25 | 26 | 27 | ```rust,no_run 28 | use phper::{modules::Module, php_get_module}; 29 | 30 | #[php_get_module] 31 | pub fn get_module() -> Module { 32 | let mut module = Module::new( 33 | env!("CARGO_CRATE_NAME"), 34 | env!("CARGO_PKG_VERSION"), 35 | env!("CARGO_PKG_AUTHORS"), 36 | ); 37 | 38 | module.on_module_init(|| { 39 | // Do somethings in `MINIT` stage. 40 | }); 41 | 42 | module 43 | } 44 | ``` 45 | -------------------------------------------------------------------------------- /phper-doc/doc/_06_module/_02_register_functions/index.md: -------------------------------------------------------------------------------- 1 | # Register functions 2 | 3 | In `PHPER`, you can use [`add_function`](phper::modules::Module::add_function) to 4 | register functions. 5 | 6 | ```rust,no_run 7 | use phper::{modules::Module, php_get_module, functions::Argument, echo}; 8 | 9 | #[php_get_module] 10 | pub fn get_module() -> Module { 11 | let mut module = Module::new( 12 | env!("CARGO_CRATE_NAME"), 13 | env!("CARGO_PKG_VERSION"), 14 | env!("CARGO_PKG_AUTHORS"), 15 | ); 16 | 17 | module.add_function("say_hello", |arguments| -> phper::Result<()> { 18 | let name = arguments[0].expect_z_str()?.to_str()?; 19 | echo!("Hello, {}!\n", name); 20 | Ok(()) 21 | }).argument(Argument::new("name")); 22 | 23 | module 24 | } 25 | ``` 26 | 27 | This example registers a function called `say_hello` and accepts a parameter 28 | `name` passed by value, similarly in PHP: 29 | 30 | ```php 31 | `: 39 | 40 | ```txt 41 | Extension [ extension #56 hello version ] { 42 | 43 | - Functions { 44 | Function [ function say_hello ] { 45 | 46 | - Parameters [1] { 47 | Parameter #0 [ $name ] 48 | } 49 | } 50 | } 51 | } 52 | ``` 53 | 54 | It is useful to register the parameters of the function, which can limit the 55 | number of parameters of the function. 56 | 57 | Especially when the parameters need to be passed by reference. 58 | 59 | ```rust,no_run 60 | use phper::{modules::Module, php_get_module, functions::Argument}; 61 | 62 | #[php_get_module] 63 | pub fn get_module() -> Module { 64 | let mut module = Module::new( 65 | env!("CARGO_CRATE_NAME"), 66 | env!("CARGO_PKG_VERSION"), 67 | env!("CARGO_PKG_AUTHORS"), 68 | ); 69 | 70 | module.add_function("add_count", |arguments| -> phper::Result<()> { 71 | let count = arguments[0].expect_mut_z_ref()?; 72 | *count.val_mut().expect_mut_long()? += 100; 73 | Ok(()) 74 | }).argument(Argument::new("count").by_ref()); 75 | 76 | module 77 | } 78 | ``` 79 | 80 | Here, the argument is registered as 81 | [`Argument::by_ref`](phper::functions::Argument::by_ref). Therefore, the type of 82 | the `count` parameter is no longer long, but a reference. 83 | 84 | ## Argument and return type modifiers 85 | 86 | Arguments can have type-hints, nullability and default values applied. Here we define a function that accepts 87 | a nullable class (in this case, an interface), and a string with a default value: 88 | 89 | ```rust,no_run 90 | use phper::{modules::Module, php_get_module, functions::Argument, echo}; 91 | use phper::types::ArgumentTypeHint; 92 | 93 | #[php_get_module] 94 | pub fn get_module() -> Module { 95 | let mut module = Module::new( 96 | env!("CARGO_CRATE_NAME"), 97 | env!("CARGO_PKG_VERSION"), 98 | env!("CARGO_PKG_AUTHORS"), 99 | ); 100 | 101 | module.add_function("my_function", |_| -> phper::Result<()> { 102 | Ok(()) 103 | }) 104 | .argument(Argument::new("a_class").with_type_hint(ArgumentTypeHint::ClassEntry(String::from(r"\MyNamespace\MyInterface"))).allow_null()) 105 | .argument(Argument::new("name").with_type_hint(ArgumentTypeHint::String).with_default_value("'my_default'")) 106 | .argument(Argument::new("optional_bool").with_type_hint(ArgumentTypeHint::Bool).optional()); 107 | 108 | module 109 | } 110 | ``` 111 | 112 | The output of `php --re` for this function would look like: 113 | 114 | ```txt 115 | Function [ function my_function ] { 116 | 117 | - Parameters [3] { 118 | Parameter #0 [ ?class_name $a_class ] 119 | Parameter #1 [ string $name = 'my_default' ] 120 | Parameter #2 [ bool $optional_bool = ] 121 | } 122 | } 123 | ``` 124 | -------------------------------------------------------------------------------- /phper-doc/doc/_06_module/_03_register_constants/index.md: -------------------------------------------------------------------------------- 1 | # Register constants 2 | 3 | In `PHPER`, you can use [`add_constant`](phper::modules::Module::add_constant) to 4 | register constants. 5 | 6 | ```rust,no_run 7 | use phper::{modules::Module, php_get_module}; 8 | 9 | #[php_get_module] 10 | pub fn get_module() -> Module { 11 | let mut module = Module::new( 12 | env!("CARGO_CRATE_NAME"), 13 | env!("CARGO_PKG_VERSION"), 14 | env!("CARGO_PKG_AUTHORS"), 15 | ); 16 | 17 | module.add_constant("FOO", 100i64); 18 | 19 | module 20 | } 21 | ``` 22 | 23 | Because in PHP, you can also use copyable values as constants, such as long, 24 | double and string, so the value have to implement [`Scalar`](phper::types::Scalar). 25 | -------------------------------------------------------------------------------- /phper-doc/doc/_06_module/_04_register_ini_settings/index.md: -------------------------------------------------------------------------------- 1 | # Register ini settings 2 | 3 | In `PHPER`, you can use [`add_ini`](phper::modules::Module::add_ini) to 4 | register ini settings. 5 | 6 | ```rust,no_run 7 | use phper::{modules::Module, php_get_module, ini::Policy}; 8 | 9 | #[php_get_module] 10 | pub fn get_module() -> Module { 11 | let mut module = Module::new( 12 | env!("CARGO_CRATE_NAME"), 13 | env!("CARGO_PKG_VERSION"), 14 | env!("CARGO_PKG_AUTHORS"), 15 | ); 16 | 17 | module.add_ini("demo.enable", false, Policy::All); 18 | module.add_ini("demo.foo", 100, Policy::All); 19 | 20 | module 21 | } 22 | ``` 23 | 24 | About the policy of setting, you can refer to 25 | . 26 | 27 | ## Configure ini settings 28 | 29 | The user can configure the ini settings in the `php.ini`. If not configured, the 30 | configuration item will use the default value. 31 | 32 | ```ini 33 | demo.enable = On 34 | ``` 35 | 36 | You can show the ini settings by `php --ri `. 37 | 38 | ```txt 39 | demo 40 | 41 | version => 0.0.0 42 | authors => PHPER Framework Team:jmjoy 43 | 44 | Directive => Local Value => Master Value 45 | demo.enable => 1 => 1 46 | demo.num => 100 => 100 47 | ``` 48 | 49 | ## Get ini settings 50 | 51 | After the ini settings registered, you can get it by 52 | [`ini_get`](phper::ini::ini_get). 53 | 54 | ```rust,no_run 55 | use phper::ini::ini_get; 56 | 57 | let _foo = ini_get::("demo.enable"); 58 | let _bar = ini_get::("demo.foo"); 59 | ``` 60 | -------------------------------------------------------------------------------- /phper-doc/doc/_06_module/_05_extension_information/index.md: -------------------------------------------------------------------------------- 1 | # Extension information 2 | 3 | By default, `PHPER` will auto register the `MINFO` handle, show the info about 4 | extension name, version, authors, and display configuration items. 5 | 6 | As you execute the command `php --ri `: 7 | 8 | ```txt 9 | demo 10 | 11 | version => 0.0.0 12 | authors => PHPER Framework Team:jmjoy 13 | 14 | Directive => Local Value => Master Value 15 | complex.enable => 0 => 0 16 | complex.foo => 100 => 100 17 | ``` 18 | 19 | If you want to add extra info items, you can use 20 | [`Module::add_info`](phper::modules::Module::add_info) method. 21 | 22 | ```rust,no_run 23 | use phper::{modules::Module, php_get_module}; 24 | 25 | #[php_get_module] 26 | pub fn get_module() -> Module { 27 | let mut module = Module::new( 28 | env!("CARGO_CRATE_NAME"), 29 | env!("CARGO_PKG_VERSION"), 30 | env!("CARGO_PKG_AUTHORS"), 31 | ); 32 | 33 | module.add_info("extra info key", "extra info value"); 34 | 35 | module 36 | } 37 | ``` 38 | 39 | Then build the extension and call `php --ri `: 40 | 41 | ```txt 42 | demo 43 | 44 | version => 0.0.0 45 | authors => PHPER Framework Team:jmjoy 46 | extra info key => extra info value 47 | 48 | Directive => Local Value => Master Value 49 | complex.enable => 0 => 0 50 | complex.foo => 100 => 100 51 | ``` 52 | 53 | The `extra info key` item is appeared. 54 | -------------------------------------------------------------------------------- /phper-doc/doc/_06_module/_07_register_interface/index.md: -------------------------------------------------------------------------------- 1 | # Register interface 2 | 3 | Registering interfaces is similar to registering classes. 4 | 5 | First, you have to instantiate the class builder. 6 | [`InterfaceEntity`](phper::classes::InterfaceEntity), 7 | then extends interfaces (if there are), 8 | add public abstract methods, finally add it into the `Module`. 9 | 10 | Here is the simplest example: 11 | 12 | ```rust,no_run 13 | use phper::{classes::InterfaceEntity, modules::Module, php_get_module}; 14 | 15 | #[php_get_module] 16 | pub fn get_module() -> Module { 17 | let mut module = Module::new( 18 | env!("CARGO_CRATE_NAME"), 19 | env!("CARGO_PKG_VERSION"), 20 | env!("CARGO_PKG_AUTHORS"), 21 | ); 22 | 23 | let foo = InterfaceEntity::new("Foo"); 24 | 25 | module.add_interface(foo); 26 | 27 | module 28 | } 29 | ``` 30 | 31 | Similarly in PHP: 32 | 33 | ```php 34 | Just to clarify, in PHP's source code, PHP extensions are named as "PHP modules." 4 | > 5 | > PHP extensions (aka PHP “modules”) are loaded in INI files with an “extension=pib.so” line. 6 | > 7 | > Refer: 8 | 9 | [Module](phper::modules::Module) is the entry of registering hooks, functions, 10 | classes, ini properties, constants, etc. 11 | -------------------------------------------------------------------------------- /phper-doc/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | #![warn(rust_2018_idioms, missing_docs)] 12 | #![warn(clippy::dbg_macro, clippy::print_stdout)] 13 | #![doc = include_str!("../README.md")] 14 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 15 | 16 | pub use phper; 17 | 18 | #[doc = include_str!("../doc/_01_introduction/index.md")] 19 | pub mod _01_introduction {} 20 | 21 | #[doc = include_str!("../doc/_02_quick_start/index.md")] 22 | pub mod _02_quick_start { 23 | 24 | #[doc = include_str!("../doc/_02_quick_start/_01_write_your_first_extension/index.md")] 25 | pub mod _01_write_your_first_extension {} 26 | 27 | #[doc = include_str!("../doc/_02_quick_start/_02_write_a_simple_http_client/index.md")] 28 | pub mod _02_write_a_simple_http_client {} 29 | } 30 | 31 | #[doc = include_str!("../doc/_03_integrate_with_pecl/index.md")] 32 | pub mod _03_integrate_with_pecl {} 33 | 34 | #[doc = include_str!("../doc/_04_zval/index.md")] 35 | pub mod _04_zval {} 36 | 37 | #[doc = include_str!("../doc/_05_internal_types/index.md")] 38 | pub mod _05_internal_types { 39 | 40 | #[doc = include_str!("../doc/_05_internal_types/_01_z_str/index.md")] 41 | pub mod _01_z_str {} 42 | 43 | #[doc = include_str!("../doc/_05_internal_types/_02_z_arr/index.md")] 44 | pub mod _02_z_arr {} 45 | 46 | #[doc = include_str!("../doc/_05_internal_types/_03_z_obj/index.md")] 47 | pub mod _03_z_obj {} 48 | } 49 | 50 | #[doc = include_str!("../doc/_06_module/index.md")] 51 | pub mod _06_module { 52 | 53 | #[doc = include_str!("../doc/_06_module/_01_hooks/index.md")] 54 | pub mod _01_hooks {} 55 | 56 | #[doc = include_str!("../doc/_06_module/_02_register_functions/index.md")] 57 | pub mod _02_register_functions {} 58 | 59 | #[doc = include_str!("../doc/_06_module/_03_register_constants/index.md")] 60 | pub mod _03_register_constants {} 61 | 62 | #[doc = include_str!("../doc/_06_module/_04_register_ini_settings/index.md")] 63 | pub mod _04_register_ini_settings {} 64 | 65 | #[doc = include_str!("../doc/_06_module/_05_extension_information/index.md")] 66 | pub mod _05_extension_information {} 67 | 68 | #[doc = include_str!("../doc/_06_module/_06_register_class/index.md")] 69 | pub mod _06_register_class {} 70 | 71 | #[doc = include_str!("../doc/_06_module/_07_register_interface/index.md")] 72 | pub mod _07_register_interface {} 73 | 74 | #[cfg(phper_enum_supported)] 75 | #[doc = include_str!("../doc/_06_module/_08_register_enum/index.md")] 76 | pub mod _08_register_enum {} 77 | } 78 | 79 | /// TODO 80 | pub mod _07_allocation {} 81 | 82 | /// TODO 83 | pub mod _08_handle_exception {} 84 | 85 | /// TODO 86 | pub mod _09_build_script {} 87 | 88 | /// TODO 89 | pub mod _10_integration_tests {} 90 | 91 | /// TODO 92 | pub mod _11_macros {} 93 | -------------------------------------------------------------------------------- /phper-macros/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.15.1](https://github.com/phper-framework/phper/compare/phper-macros-v0.15.0...phper-macros-v0.15.1) - 2025-04-04 11 | 12 | ### Other 13 | 14 | - Remove workspace.package.version ([#199](https://github.com/phper-framework/phper/pull/199)) 15 | -------------------------------------------------------------------------------- /phper-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 PHPER Framework Team 2 | # PHPER is licensed under Mulan PSL v2. 3 | # You can use this software according to the terms and conditions of the Mulan 4 | # PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | # http://license.coscl.org.cn/MulanPSL2 6 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | # KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | # NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | # See the Mulan PSL v2 for more details. 10 | 11 | [package] 12 | name = "phper-macros" 13 | description = "The proc-macros for phper crate." 14 | keywords = ["php", "proc-macro"] 15 | version = "0.15.1" 16 | authors = { workspace = true } 17 | edition = { workspace = true } 18 | rust-version = { workspace = true } 19 | repository = { workspace = true } 20 | license = { workspace = true } 21 | 22 | [lib] 23 | proc-macro = true 24 | 25 | [dependencies] 26 | quote = "1.0.38" 27 | syn = { version = "2.0.98", features = ["full"] } 28 | proc-macro2 = "1.0.93" 29 | 30 | [dev-dependencies] 31 | syn = { version = "2.0.98", features = ["full", "extra-traits"] } 32 | -------------------------------------------------------------------------------- /phper-macros/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /phper-macros/README.md: -------------------------------------------------------------------------------- 1 | # phper-macros 2 | 3 | The proc-macros for [phper](https://crates.io/crates/phper). 4 | 5 | ## License 6 | 7 | [MulanPSL-2.0](https://github.com/phper-framework/phper/blob/master/LICENSE). 8 | -------------------------------------------------------------------------------- /phper-macros/src/alloc.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | -------------------------------------------------------------------------------- /phper-macros/src/derives.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | -------------------------------------------------------------------------------- /phper-macros/src/globals.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | -------------------------------------------------------------------------------- /phper-macros/src/inner.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use proc_macro::TokenStream; 12 | use quote::quote; 13 | use syn::{ItemFn, Visibility, parse_macro_input}; 14 | 15 | pub(crate) fn php_get_module(_attr: TokenStream, input: TokenStream) -> TokenStream { 16 | let input = parse_macro_input!(input as ItemFn); 17 | 18 | let vis = &input.vis; 19 | let ret = &input.sig.output; 20 | let inputs = &input.sig.inputs; 21 | let name = &input.sig.ident; 22 | let body = &input.block; 23 | let attrs = &input.attrs; 24 | 25 | if name != "get_module" { 26 | return quote! { compile_error!("function name with attribute `php_get_module` must be `get_module`") }.into(); 27 | } 28 | 29 | if !matches!(vis, Visibility::Public(..)) { 30 | return quote! { compile_error!("function `get_module` must be public"); }.into(); 31 | } 32 | 33 | let result = quote! { 34 | #[unsafe(no_mangle)] 35 | #[doc(hidden)] 36 | #(#attrs)* 37 | #vis extern "C" fn #name() -> *const ::phper::sys::zend_module_entry { 38 | fn internal(#inputs) #ret { 39 | #body 40 | } 41 | let internal: fn() -> ::phper::modules::Module = internal; 42 | unsafe { internal().module_entry() } 43 | } 44 | }; 45 | 46 | result.into() 47 | } 48 | -------------------------------------------------------------------------------- /phper-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | #![warn(rust_2018_idioms, missing_docs)] 12 | #![warn(clippy::dbg_macro, clippy::print_stdout)] 13 | #![doc = include_str!("../README.md")] 14 | 15 | // TODO Write a bridge macro for easy usage about register functions and 16 | // classes, like `cxx`. 17 | 18 | mod alloc; 19 | mod derives; 20 | mod globals; 21 | mod inner; 22 | mod log; 23 | mod utils; 24 | 25 | use proc_macro::TokenStream; 26 | 27 | /// PHP module entry, wrap the `phper::modules::Module` write operation. 28 | /// 29 | /// # Examples 30 | /// 31 | /// ```no_test 32 | /// use phper::{php_get_module, modules::Module}; 33 | /// 34 | /// #[php_get_module] 35 | /// pub fn get_module() -> Module { 36 | /// let mut module = Module::new( 37 | /// env!("CARGO_CRATE_NAME"), 38 | /// env!("CARGO_PKG_VERSION"), 39 | /// env!("CARGO_PKG_AUTHORS"), 40 | /// ); 41 | /// 42 | /// // ... 43 | /// 44 | /// module 45 | /// } 46 | /// ``` 47 | #[proc_macro_attribute] 48 | pub fn php_get_module(attr: TokenStream, input: TokenStream) -> TokenStream { 49 | inner::php_get_module(attr, input) 50 | } 51 | -------------------------------------------------------------------------------- /phper-macros/src/log.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | -------------------------------------------------------------------------------- /phper-macros/src/utils.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | -------------------------------------------------------------------------------- /phper-macros/tests/integration.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | -------------------------------------------------------------------------------- /phper-sys/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.15.2](https://github.com/phper-framework/phper/compare/phper-sys-v0.15.1...phper-sys-v0.15.2) - 2025-05-01 11 | 12 | ### Added 13 | 14 | - Add preliminary support for enums ([#201](https://github.com/phper-framework/phper/pull/201)) 15 | 16 | ### Fixed 17 | 18 | - fix ClassEntry typehints ([#206](https://github.com/phper-framework/phper/pull/206)) 19 | 20 | ## [0.15.1](https://github.com/phper-framework/phper/compare/phper-sys-v0.15.0...phper-sys-v0.15.1) - 2025-04-04 21 | 22 | ### Other 23 | 24 | - Remove workspace.package.version ([#199](https://github.com/phper-framework/phper/pull/199)) 25 | - [breaking] add argument and return value type-hints ([#187](https://github.com/phper-framework/phper/pull/187)) 26 | -------------------------------------------------------------------------------- /phper-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 PHPER Framework Team 2 | # PHPER is licensed under Mulan PSL v2. 3 | # You can use this software according to the terms and conditions of the Mulan 4 | # PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | # http://license.coscl.org.cn/MulanPSL2 6 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | # KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | # NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | # See the Mulan PSL v2 for more details. 10 | 11 | [package] 12 | name = "phper-sys" 13 | description = "Low level PHP binding for Rust." 14 | keywords = ["php", "binding"] 15 | version = "0.15.2" 16 | authors = { workspace = true } 17 | edition = { workspace = true } 18 | rust-version = { workspace = true } 19 | repository = { workspace = true } 20 | license = { workspace = true } 21 | 22 | [build-dependencies] 23 | bindgen = "0.71.1" 24 | cc = "1.2.14" 25 | regex = "1.11.1" 26 | -------------------------------------------------------------------------------- /phper-sys/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /phper-sys/README.md: -------------------------------------------------------------------------------- 1 | # phper-sys 2 | 3 | Low level PHP binding for Rust. 4 | 5 | The php-config is needed. You can set environment `PHP_CONFIG` to specify the path. 6 | 7 | ## License 8 | 9 | [MulanPSL-2.0](https://github.com/phper-framework/phper/blob/master/LICENSE). 10 | -------------------------------------------------------------------------------- /phper-sys/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use bindgen::Builder; 12 | use std::{env, ffi::OsStr, fmt::Debug, path::PathBuf, process::Command}; 13 | 14 | fn main() { 15 | println!("cargo:rerun-if-changed=php_wrapper.c"); 16 | println!("cargo:rerun-if-env-changed=PHP_CONFIG"); 17 | 18 | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); 19 | let php_config = env::var("PHP_CONFIG").unwrap_or_else(|_| "php-config".to_string()); 20 | 21 | let includes = execute_command(&[php_config.as_str(), "--includes"]); 22 | let includes = includes.split(' ').collect::>(); 23 | 24 | // Generate libphpwrapper.a. 25 | 26 | let mut builder = cc::Build::new(); 27 | for include in &includes { 28 | builder.flag(include); 29 | } 30 | builder.file("php_wrapper.c").compile("phpwrapper"); 31 | 32 | // Generate bindgen file. 33 | let include_dirs = includes 34 | .iter() 35 | .map(|include| &include[2..]) 36 | .collect::>(); 37 | 38 | for dir in include_dirs.iter() { 39 | println!("cargo:include={}", dir); 40 | } 41 | 42 | let mut builder = Builder::default() 43 | .header("php_wrapper.c") 44 | .allowlist_file("php_wrapper\\.c") 45 | // Block the `zend_ini_parse_quantity` because it's document causes the doc test to fail. 46 | .blocklist_function("zend_ini_parse_quantity") 47 | // Block the `zend_startup` because it fails checks. 48 | .blocklist_function("zend_startup") 49 | // Block the `zend_random_bytes_insecure` because it fails checks. 50 | .blocklist_item("zend_random_bytes_insecure") 51 | .clang_args(&includes) 52 | .derive_default(true); 53 | 54 | // iterate over the php include directories, and update the builder 55 | // to only create bindings from the header files in those directories 56 | for dir in include_dirs.iter() { 57 | let p = PathBuf::from(dir).join(".*\\.h"); 58 | builder = builder.allowlist_file(p.display().to_string()); 59 | } 60 | 61 | let generated_path = out_path.join("php_bindings.rs"); 62 | 63 | builder 64 | .generate() 65 | .expect("Unable to generate bindings") 66 | .write_to_file(generated_path) 67 | .expect("Unable to write output file"); 68 | } 69 | 70 | fn execute_command + Debug>(argv: &[S]) -> String { 71 | let mut command = Command::new(&argv[0]); 72 | command.args(&argv[1..]); 73 | let output = command 74 | .output() 75 | .unwrap_or_else(|_| panic!("Execute command {:?} failed", &argv)) 76 | .stdout; 77 | String::from_utf8(output).unwrap().trim().to_owned() 78 | } 79 | -------------------------------------------------------------------------------- /phper-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | #![warn(rust_2018_idioms)] 12 | #![warn(clippy::dbg_macro, clippy::print_stdout)] 13 | #![allow(non_upper_case_globals)] 14 | #![allow(non_camel_case_types)] 15 | #![allow(non_snake_case)] 16 | // TODO Because `bindgen` generates codes contains deref nullptr, temporary suppression. 17 | #![allow(deref_nullptr)] 18 | #![allow(clippy::all)] 19 | // TODO unsafe_op_in_unsafe_fn warning in edition 2024, temporary suppression. 20 | #![allow(unsafe_op_in_unsafe_fn)] 21 | #![doc = include_str!("../README.md")] 22 | 23 | include!(concat!(env!("OUT_DIR"), "/php_bindings.rs")); 24 | -------------------------------------------------------------------------------- /phper-test/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.15.1](https://github.com/phper-framework/phper/compare/phper-test-v0.15.0...phper-test-v0.15.1) - 2025-04-04 11 | 12 | ### Other 13 | 14 | - Remove workspace.package.version ([#199](https://github.com/phper-framework/phper/pull/199)) 15 | - update readme and remove dep once_cell ([#196](https://github.com/phper-framework/phper/pull/196)) 16 | -------------------------------------------------------------------------------- /phper-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 PHPER Framework Team 2 | # PHPER is licensed under Mulan PSL v2. 3 | # You can use this software according to the terms and conditions of the Mulan 4 | # PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | # http://license.coscl.org.cn/MulanPSL2 6 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | # KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | # NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | # See the Mulan PSL v2 for more details. 10 | 11 | [package] 12 | name = "phper-test" 13 | description = "PHPer testing utilities." 14 | keywords = ["php", "binding"] 15 | version = "0.15.1" 16 | authors = { workspace = true } 17 | edition = { workspace = true } 18 | rust-version = { workspace = true } 19 | repository = { workspace = true } 20 | license = { workspace = true } 21 | 22 | [dependencies] 23 | fastcgi-client = "0.9.0" 24 | libc = "0.2.169" 25 | phper-macros = { workspace = true } 26 | tempfile = "3.17.1" 27 | tokio = { version = "1.43.0", features = ["full"] } 28 | 29 | [package.metadata.docs.rs] 30 | rustdoc-args = ["--cfg", "docsrs"] 31 | all-features = true 32 | -------------------------------------------------------------------------------- /phper-test/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /phper-test/README.md: -------------------------------------------------------------------------------- 1 | # phper-test 2 | 3 | Integration test tool for [phper](https://crates.io/crates/phper). 4 | 5 | The `php-config` is needed. You can set environment `PHP_CONFIG` to specify the path. 6 | 7 | ## Notice 8 | 9 | 1. Because the `phper-test` depends on the `cdylib` to do integration test, but now `cargo test` don't build `cdylib` in `[lib]`, so you must call `cargo build` before `cargo test`. 10 | 11 | Maybe, when the feature [artifact-dependencies](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies) becomes stable, or the [issue](https://github.com/rust-lang/cargo/issues/8628) be solved, you don't have to call `cargo build` in advance, but I think it will be a long long stage. 12 | 13 | 1. Or, define an `[[example]]` section, re-export all the symbols of your crate, and set `lto = true`. It's strange, but this is the only method to just run `cargo test` without running `cargo build` in advance. Please refer to [tests/integration/Cargo.toml](https://github.com/phper-framework/phper/blob/master/tests/integration/Cargo.toml). 14 | 15 | ## License 16 | 17 | [MulanPSL-2.0](https://github.com/phper-framework/phper/blob/master/LICENSE). 18 | -------------------------------------------------------------------------------- /phper-test/etc/php-fpm.conf: -------------------------------------------------------------------------------- 1 | ; Copyright (c) 2022 PHPER Framework Team 2 | ; PHPER is licensed under Mulan PSL v2. 3 | ; You can use this software according to the terms and conditions of the Mulan 4 | ; PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | ; http://license.coscl.org.cn/MulanPSL2 6 | ; THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | ; KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | ; NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | ; See the Mulan PSL v2 for more details. 10 | 11 | [global] 12 | error_log = /tmp/.php-fpm.log 13 | 14 | [www] 15 | user = $USER 16 | group = $USER 17 | listen = 127.0.0.1:9000 18 | pm = static 19 | pm.max_children = 6 20 | pm.max_requests = 500 21 | -------------------------------------------------------------------------------- /phper-test/src/cli.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | //! Test tools for php cli program. 12 | 13 | use crate::context::Context; 14 | use std::{ 15 | panic::{UnwindSafe, catch_unwind, resume_unwind}, 16 | path::Path, 17 | process::{Child, Output}, 18 | }; 19 | 20 | /// Check your extension by executing the php script, if the all executing 21 | /// return success, than the test is pass. 22 | /// 23 | /// - `lib_path` is the path of extension lib. 24 | /// 25 | /// - `scripts` is the path of your php test scripts. 26 | /// 27 | /// See [example hello integration test](https://github.com/phper-framework/phper/blob/master/examples/hello/tests/integration.rs). 28 | pub fn test_php_scripts(lib_path: impl AsRef, scripts: &[&dyn AsRef]) { 29 | let condition = |output: Output| output.status.success(); 30 | let scripts = scripts 31 | .iter() 32 | .map(|s| (*s, &condition as _)) 33 | .collect::>(); 34 | test_php_scripts_with_condition(lib_path, &scripts); 35 | } 36 | 37 | /// Script and condition pair. 38 | pub type ScriptCondition<'a> = (&'a dyn AsRef, &'a dyn Fn(Output) -> bool); 39 | 40 | /// Check your extension by executing the php script, if the all your specified 41 | /// checkers are pass, than the test is pass. 42 | /// 43 | /// - `exec_path` is the path of the make executable, which will be used to 44 | /// detect the path of extension lib. 45 | /// 46 | /// - `scripts` is the slice of the tuple, format is `(path of your php test 47 | /// script, checker function or closure)`. 48 | /// 49 | /// See [example logging integration test](https://github.com/phper-framework/phper/blob/master/examples/logging/tests/integration.rs). 50 | pub fn test_php_scripts_with_condition( 51 | lib_path: impl AsRef, scripts: &[ScriptCondition<'_>], 52 | ) { 53 | let context = Context::get_global(); 54 | 55 | for (script, condition) in scripts { 56 | let mut cmd = context.create_command_with_lib(&lib_path, script); 57 | 58 | let output = cmd.output().unwrap(); 59 | let path = script.as_ref().to_str().unwrap(); 60 | 61 | let mut stdout = String::from_utf8(output.stdout.clone()).unwrap(); 62 | if stdout.is_empty() { 63 | stdout.push_str(""); 64 | } 65 | 66 | let mut stderr = String::from_utf8(output.stderr.clone()).unwrap(); 67 | if stderr.is_empty() { 68 | stderr.push_str(""); 69 | } 70 | 71 | eprintln!( 72 | "===== command =====\n{} {}\n===== stdout ======\n{}\n===== stderr ======\n{}", 73 | &context.php_bin, 74 | cmd.get_args().join(" "), 75 | stdout, 76 | stderr, 77 | ); 78 | #[cfg(target_os = "linux")] 79 | if output.status.code().is_none() { 80 | use std::os::unix::process::ExitStatusExt; 81 | eprintln!( 82 | "===== signal ======\nExitStatusExt is None, the signal is: {:?}", 83 | output.status.signal() 84 | ); 85 | } 86 | 87 | if !condition(output) { 88 | panic!("test php file `{}` failed", path); 89 | } 90 | } 91 | } 92 | 93 | /// Check your extension by executing the long term php script such as http 94 | /// server, if the all your specified checkers are pass, than the test is pass. 95 | #[allow(clippy::zombie_processes)] 96 | pub fn test_long_term_php_script_with_condition( 97 | lib_path: impl AsRef, script: impl AsRef, 98 | condition: impl FnOnce(&Child) + UnwindSafe, 99 | ) { 100 | let context = Context::get_global(); 101 | let mut command = context.create_command_with_lib(lib_path, script); 102 | let mut child = command.spawn().unwrap(); 103 | let r = catch_unwind(|| condition(&child)); 104 | child.kill().unwrap(); 105 | if let Err(e) = r { 106 | resume_unwind(e); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /phper-test/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | #![warn(rust_2018_idioms, missing_docs)] 12 | #![warn(clippy::dbg_macro, clippy::print_stdout)] 13 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 14 | #![doc = include_str!("../README.md")] 15 | 16 | pub mod cli; 17 | mod context; 18 | pub mod fpm; 19 | pub mod utils; 20 | -------------------------------------------------------------------------------- /phper-test/src/utils.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | //! Utils for PHP test situation. 12 | 13 | use std::{ 14 | ffi::{OsStr, OsString}, 15 | fmt::Debug, 16 | path::{Path, PathBuf}, 17 | process::Command, 18 | }; 19 | 20 | pub(crate) fn execute_command + Debug>(argv: &[S]) -> String { 21 | let mut command = Command::new(&argv[0]); 22 | command.args(&argv[1..]); 23 | let output = command 24 | .output() 25 | .unwrap_or_else(|_| panic!("Execute command {:?} failed", &argv)) 26 | .stdout; 27 | String::from_utf8(output).unwrap().trim().to_owned() 28 | } 29 | 30 | pub(crate) fn spawn_command + Debug>( 31 | argv: &[S], wait_time: Option, 32 | ) -> std::process::Child { 33 | use std::{process::Stdio, thread}; 34 | 35 | let mut command = Command::new(&argv[0]); 36 | let child = command 37 | .args(&argv[1..]) 38 | .stdin(Stdio::null()) 39 | .stdout(Stdio::null()) 40 | .stderr(Stdio::null()) 41 | .spawn() 42 | .unwrap_or_else(|_| panic!("Execute command {:?} failed", argv)); 43 | 44 | // Sleep to wait program running. 45 | if let Some(wait_time) = wait_time { 46 | thread::sleep(wait_time); 47 | } 48 | 49 | // Check process is running. 50 | let id = child.id(); 51 | unsafe { 52 | assert_eq!( 53 | libc::kill(id.try_into().unwrap(), 0), 54 | 0, 55 | "start process failed: {:?}", 56 | argv 57 | ); 58 | } 59 | 60 | child 61 | } 62 | 63 | /// Get the dynamic link library path generated by `cargo build`. 64 | pub fn get_lib_path(target_path: impl AsRef, package_name: &str) -> PathBuf { 65 | let target_path = target_path.as_ref(); 66 | let mut path = target_path.to_path_buf(); 67 | path.push(if cfg!(debug_assertions) { 68 | "debug" 69 | } else { 70 | "release" 71 | }); 72 | let mut ext_name = OsString::new(); 73 | ext_name.push("lib"); 74 | ext_name.push(package_name); 75 | ext_name.push(if cfg!(target_os = "linux") { 76 | ".so" 77 | } else if cfg!(target_os = "macos") { 78 | ".dylib" 79 | } else if cfg!(target_os = "windows") { 80 | ".dll" 81 | } else { 82 | "" 83 | }); 84 | path.join(ext_name) 85 | } 86 | -------------------------------------------------------------------------------- /phper/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.16.1](https://github.com/phper-framework/phper/compare/phper-v0.16.0...phper-v0.16.1) - 2025-05-01 11 | 12 | ### Added 13 | 14 | - Introduce new_persistent method for ZString ([#204](https://github.com/phper-framework/phper/pull/204)) 15 | - Enhance enum functionality with access methods ([#203](https://github.com/phper-framework/phper/pull/203)) 16 | - Add preliminary support for enums ([#201](https://github.com/phper-framework/phper/pull/201)) 17 | 18 | ## [0.16.0](https://github.com/phper-framework/phper/compare/phper-v0.15.1...phper-v0.16.0) - 2025-04-04 19 | 20 | ### Added 21 | 22 | - [**breaking**] rename `bind_*` to `bound_*` ([#192](https://github.com/phper-framework/phper/pull/192)) 23 | 24 | ### Other 25 | 26 | - allow static interface methods ([#198](https://github.com/phper-framework/phper/pull/198)) 27 | - update readme and remove dep once_cell ([#196](https://github.com/phper-framework/phper/pull/196)) 28 | - optimizing `extends` and `implements` ([#193](https://github.com/phper-framework/phper/pull/193)) 29 | - improve Interface::extends and ClassEntry::extends ([#190](https://github.com/phper-framework/phper/pull/190)) 30 | - refactor ClassEntity.implements ([#189](https://github.com/phper-framework/phper/pull/189)) 31 | - [breaking] add argument and return value type-hints ([#187](https://github.com/phper-framework/phper/pull/187)) 32 | -------------------------------------------------------------------------------- /phper/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 PHPER Framework Team 2 | # PHPER is licensed under Mulan PSL v2. 3 | # You can use this software according to the terms and conditions of the Mulan 4 | # PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | # http://license.coscl.org.cn/MulanPSL2 6 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | # KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | # NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | # See the Mulan PSL v2 for more details. 10 | 11 | [package] 12 | name = "phper" 13 | description = "The framework that allows us to write PHP extensions using pure and safe Rust whenever possible." 14 | documentation = "https://docs.rs/phper" 15 | readme = "README.md" 16 | keywords = ["php", "binding", "extension", "module"] 17 | version = "0.16.1" 18 | authors = { workspace = true } 19 | edition = { workspace = true } 20 | rust-version = { workspace = true } 21 | repository = { workspace = true } 22 | license = { workspace = true } 23 | 24 | [dependencies] 25 | cfg-if = "1.0.0" 26 | derive_more = { version = "2.0.1", features = ["from", "constructor"] } 27 | indexmap = "2.7.1" 28 | phper-alloc = { workspace = true } 29 | phper-macros = { workspace = true } 30 | phper-sys = { workspace = true } 31 | sealed = "0.6.0" 32 | thiserror = "2.0.11" 33 | 34 | [build-dependencies] 35 | phper-build = { workspace = true } 36 | phper-sys = { workspace = true } 37 | 38 | [lints.rust] 39 | unexpected_cfgs = { level = "warn", check-cfg = [ 40 | 'cfg(phper_major_version, values("7"))', 41 | 'cfg(phper_major_version, values("8"))', 42 | 'cfg(phper_minor_version, values("0"))', 43 | 'cfg(phper_minor_version, values("1"))', 44 | 'cfg(phper_minor_version, values("2"))', 45 | 'cfg(phper_minor_version, values("3"))', 46 | 'cfg(phper_minor_version, values("4"))', 47 | 'cfg(phper_zts)', 48 | 'cfg(phper_enum_supported)', 49 | ] } 50 | 51 | [package.metadata.docs.rs] 52 | rustdoc-args = ["--cfg", "docsrs"] 53 | all-features = true 54 | -------------------------------------------------------------------------------- /phper/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /phper/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /phper/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use phper_sys::*; 12 | 13 | fn main() { 14 | phper_build::register_all(); 15 | 16 | assert_eq!( 17 | USING_ZTS, 0, 18 | "PHPER not support ZTS mode now (php built with `--enable-maintainer-zts` or \ 19 | `--enable-zts`)." 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /phper/src/alloc.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | //! Memory allocation utilities and boxed types for PHP values. 12 | 13 | pub use phper_alloc::{RefClone, ToRefOwned}; 14 | use std::{ 15 | borrow::{Borrow, BorrowMut}, 16 | fmt::{self}, 17 | mem::ManuallyDrop, 18 | ops::{Deref, DerefMut}, 19 | }; 20 | 21 | /// A smart pointer for PHP values allocated in the Zend Engine memory. 22 | /// 23 | /// `EBox` provides owned access to values allocated in PHP's memory 24 | /// management system. It automatically handles deallocation when dropped, 25 | /// ensuring proper cleanup of PHP resources. 26 | pub struct EBox { 27 | ptr: *mut T, 28 | } 29 | 30 | impl EBox { 31 | /// Constructs from a raw pointer. 32 | /// 33 | /// # Safety 34 | /// 35 | /// Make sure the pointer is from `into_raw`, or created from `emalloc`. 36 | pub unsafe fn from_raw(raw: *mut T) -> Self { 37 | Self { ptr: raw } 38 | } 39 | 40 | /// Consumes and returning a wrapped raw pointer. 41 | /// 42 | /// Will leak memory. 43 | pub fn into_raw(b: EBox) -> *mut T { 44 | ManuallyDrop::new(b).ptr 45 | } 46 | } 47 | 48 | impl fmt::Debug for EBox { 49 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 50 | fmt::Debug::fmt(&**self, f) 51 | } 52 | } 53 | 54 | impl Deref for EBox { 55 | type Target = T; 56 | 57 | fn deref(&self) -> &Self::Target { 58 | unsafe { self.ptr.as_ref().unwrap() } 59 | } 60 | } 61 | 62 | impl DerefMut for EBox { 63 | fn deref_mut(&mut self) -> &mut Self::Target { 64 | unsafe { self.ptr.as_mut().unwrap() } 65 | } 66 | } 67 | 68 | impl Drop for EBox { 69 | fn drop(&mut self) { 70 | unsafe { 71 | self.ptr.drop_in_place(); 72 | } 73 | } 74 | } 75 | 76 | impl Borrow for EBox { 77 | fn borrow(&self) -> &T { 78 | unsafe { self.ptr.as_ref().unwrap() } 79 | } 80 | } 81 | 82 | impl BorrowMut for EBox { 83 | fn borrow_mut(&mut self) -> &mut T { 84 | unsafe { self.ptr.as_mut().unwrap() } 85 | } 86 | } 87 | 88 | impl AsRef for EBox { 89 | fn as_ref(&self) -> &T { 90 | unsafe { self.ptr.as_ref().unwrap() } 91 | } 92 | } 93 | 94 | impl AsMut for EBox { 95 | fn as_mut(&mut self) -> &mut T { 96 | unsafe { self.ptr.as_mut().unwrap() } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /phper/src/constants.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | //! Apis relate to [zend_constant](crate::sys::zend_constant). 12 | 13 | use crate::{sys::*, types::Scalar}; 14 | use std::ffi::{c_char, c_int}; 15 | 16 | pub(crate) struct Constant { 17 | name: String, 18 | value: Scalar, 19 | } 20 | 21 | impl Constant { 22 | pub fn new(name: impl Into, value: impl Into) -> Self { 23 | Self { 24 | name: name.into(), 25 | value: value.into(), 26 | } 27 | } 28 | 29 | pub(crate) fn register(&self, module_number: c_int) { 30 | let name_ptr = self.name.as_ptr() as *const c_char; 31 | let name_len = self.name.len(); 32 | let flags = (CONST_PERSISTENT | CONST_CS) as c_int; 33 | 34 | unsafe { 35 | match &self.value { 36 | Scalar::Null => { 37 | zend_register_null_constant(name_ptr, name_len, flags, module_number) 38 | } 39 | Scalar::Bool(b) => zend_register_bool_constant( 40 | name_ptr, 41 | name_len, 42 | *b as zend_bool, 43 | flags, 44 | module_number, 45 | ), 46 | Scalar::I64(i) => zend_register_long_constant( 47 | name_ptr, 48 | name_len, 49 | *i as zend_long, 50 | flags, 51 | module_number, 52 | ), 53 | Scalar::F64(f) => { 54 | zend_register_double_constant(name_ptr, name_len, *f, flags, module_number) 55 | } 56 | Scalar::String(s) => { 57 | let s_ptr = s.as_ptr() as *mut u8; 58 | zend_register_stringl_constant( 59 | name_ptr, 60 | name_len, 61 | s_ptr.cast(), 62 | s.len(), 63 | flags, 64 | module_number, 65 | ) 66 | } 67 | Scalar::Bytes(s) => { 68 | let s_ptr = s.as_ptr() as *mut u8; 69 | zend_register_stringl_constant( 70 | name_ptr, 71 | name_len, 72 | s_ptr.cast(), 73 | s.len(), 74 | flags, 75 | module_number, 76 | ) 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /phper/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | #![warn(rust_2018_idioms, missing_docs)] 12 | #![warn(clippy::dbg_macro, clippy::print_stdout)] 13 | #![doc = include_str!("../README.md")] 14 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 15 | 16 | #[macro_use] 17 | mod macros; 18 | 19 | pub mod alloc; 20 | pub mod arrays; 21 | pub mod classes; 22 | pub(crate) mod constants; 23 | pub mod enums; 24 | pub mod errors; 25 | pub mod functions; 26 | pub mod ini; 27 | pub mod modules; 28 | pub mod objects; 29 | pub mod output; 30 | pub mod references; 31 | pub mod resources; 32 | pub mod strings; 33 | pub mod types; 34 | mod utils; 35 | pub mod values; 36 | 37 | pub use crate::errors::{Error, Result, ok}; 38 | pub use phper_macros::*; 39 | pub use phper_sys as sys; 40 | -------------------------------------------------------------------------------- /phper/src/macros.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | /// PHP echo. 12 | /// 13 | /// # Examples 14 | /// 15 | /// ```no_test 16 | /// phper::echo!("Hello, {}!", message) 17 | /// ``` 18 | #[macro_export] 19 | macro_rules! echo { 20 | ($($arg:tt)*) => ({ 21 | $crate::output::echo(std::format!($($arg)*)) 22 | }) 23 | } 24 | 25 | /// PHP error logging, will exit the request. 26 | /// 27 | /// # Examples 28 | /// 29 | /// ```no_test 30 | /// phper::errro!("Hello, {}!", message) 31 | /// ``` 32 | #[macro_export] 33 | macro_rules! error { 34 | ($($arg:tt)*) => ({ 35 | $crate::output::log($crate::output::LogLevel::Error, std::format!($($arg)*)) 36 | }) 37 | } 38 | 39 | /// PHP warning logging. 40 | /// 41 | /// # Examples 42 | /// 43 | /// ```no_test 44 | /// phper::warning!("Hello, {}!", message) 45 | /// ``` 46 | #[macro_export] 47 | macro_rules! warning { 48 | ($($arg:tt)*) => ({ 49 | $crate::output::log($crate::output::LogLevel::Warning, std::format!($($arg)*)) 50 | }) 51 | } 52 | 53 | /// PHP notice logging. 54 | /// 55 | /// # Examples 56 | /// 57 | /// ```no_test 58 | /// phper::notice!("Hello, {}!", message) 59 | /// ``` 60 | #[macro_export] 61 | macro_rules! notice { 62 | ($($arg:tt)*) => ({ 63 | $crate::output::log($crate::output::LogLevel::Notice, std::format!($($arg)*)) 64 | }) 65 | } 66 | 67 | /// PHP deprecated logging. 68 | /// 69 | /// # Examples 70 | /// 71 | /// ```no_test 72 | /// phper::deprecated!("Hello, {}!", message) 73 | /// ``` 74 | #[macro_export] 75 | macro_rules! deprecated { 76 | ($($arg:tt)*) => ({ 77 | $crate::output::log($crate::output::LogLevel::Deprecated, std::format!($($arg)*)) 78 | }) 79 | } 80 | 81 | /// Equivalent to the php `CG`. 82 | #[macro_export] 83 | macro_rules! cg { 84 | ($x:ident) => { 85 | $crate::sys::compiler_globals.$x 86 | }; 87 | } 88 | 89 | /// Equivalent to the php `EG`. 90 | #[macro_export] 91 | macro_rules! eg { 92 | ($x:ident) => { 93 | $crate::sys::executor_globals.$x 94 | }; 95 | } 96 | 97 | /// Equivalent to the php `PG`. 98 | #[macro_export] 99 | macro_rules! pg { 100 | ($x:ident) => { 101 | $crate::sys::core_globals.$x 102 | }; 103 | } 104 | 105 | /// Equivalent to the php `SG`. 106 | #[macro_export] 107 | macro_rules! sg { 108 | ($x:ident) => { 109 | $crate::sys::sapi_globals.$x 110 | }; 111 | } 112 | -------------------------------------------------------------------------------- /phper/src/output.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | //! Logs and echo facilities. 12 | 13 | use crate::{sys::*, utils::ensure_end_with_zero}; 14 | use std::ptr::null; 15 | 16 | /// Log level. 17 | #[repr(u32)] 18 | #[derive(Copy, Clone, PartialEq, Eq)] 19 | pub enum LogLevel { 20 | /// Error level. 21 | Error = E_ERROR, 22 | /// Warning level. 23 | Warning = E_WARNING, 24 | /// Notice level. 25 | Notice = E_NOTICE, 26 | /// Deprecated level. 27 | Deprecated = E_DEPRECATED, 28 | } 29 | 30 | /// log message with level. 31 | pub fn log(level: LogLevel, message: impl Into) { 32 | let message = ensure_end_with_zero(message); 33 | unsafe { 34 | php_error_docref1( 35 | null(), 36 | c"".as_ptr().cast(), 37 | level as i32, 38 | message.as_ptr().cast(), 39 | ); 40 | } 41 | } 42 | 43 | /// Just like PHP `echo`. 44 | #[allow(clippy::useless_conversion)] 45 | pub fn echo(message: impl Into) { 46 | let message = ensure_end_with_zero(message); 47 | unsafe { 48 | zend_write.expect("function zend_write can't be null")( 49 | message.as_ptr().cast(), 50 | message.as_bytes().len().try_into().unwrap(), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /phper/src/references.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | //! Apis relate to [zend_resource]. 12 | 13 | use crate::{sys::*, values::ZVal}; 14 | use std::fmt::{self, Debug}; 15 | 16 | /// Wrapper of [zend_resource]. 17 | #[repr(transparent)] 18 | pub struct ZRef { 19 | inner: zend_reference, 20 | } 21 | 22 | impl ZRef { 23 | /// Wraps a raw pointer. 24 | /// 25 | /// # Safety 26 | /// 27 | /// Create from raw pointer. 28 | /// 29 | /// # Panics 30 | /// 31 | /// Panics if pointer is null. 32 | pub unsafe fn from_ptr<'a>(ptr: *const zend_reference) -> &'a Self { 33 | unsafe { 34 | (ptr as *const Self) 35 | .as_ref() 36 | .expect("ptr should not be null") 37 | } 38 | } 39 | 40 | /// Wraps a raw pointer, return None if pointer is null. 41 | /// 42 | /// # Safety 43 | /// 44 | /// Create from raw pointer. 45 | pub unsafe fn try_from_ptr<'a>(ptr: *const zend_reference) -> Option<&'a Self> { 46 | unsafe { (ptr as *const Self).as_ref() } 47 | } 48 | 49 | /// Wraps a raw pointer. 50 | /// 51 | /// # Safety 52 | /// 53 | /// Create from raw pointer. 54 | /// 55 | /// # Panics 56 | /// 57 | /// Panics if pointer is null. 58 | pub unsafe fn from_mut_ptr<'a>(ptr: *mut zend_reference) -> &'a mut Self { 59 | unsafe { (ptr as *mut Self).as_mut().expect("ptr should not be null") } 60 | } 61 | 62 | /// Wraps a raw pointer, return None if pointer is null. 63 | /// 64 | /// # Safety 65 | /// 66 | /// Create from raw pointer. 67 | pub unsafe fn try_from_mut_ptr<'a>(ptr: *mut zend_reference) -> Option<&'a mut Self> { 68 | unsafe { (ptr as *mut Self).as_mut() } 69 | } 70 | 71 | /// Returns a raw pointer wrapped. 72 | pub const fn as_ptr(&self) -> *const zend_reference { 73 | &self.inner 74 | } 75 | 76 | /// Returns a raw pointer wrapped. 77 | #[inline] 78 | pub fn as_mut_ptr(&mut self) -> *mut zend_reference { 79 | &mut self.inner 80 | } 81 | 82 | /// Gets actual value reference. 83 | pub fn val(&self) -> &ZVal { 84 | unsafe { ZVal::from_ptr(&self.inner.val) } 85 | } 86 | 87 | /// Gets actual value mutable reference. 88 | pub fn val_mut(&mut self) -> &mut ZVal { 89 | unsafe { ZVal::from_mut_ptr(&mut self.inner.val) } 90 | } 91 | } 92 | 93 | impl Debug for ZRef { 94 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 95 | f.debug_struct("ZRef").field("val", &self.val()).finish() 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /phper/src/resources.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | //! Apis relate to [zend_resource]. 12 | 13 | use crate::sys::*; 14 | use std::fmt::{self, Debug}; 15 | 16 | /// Wrapper of [zend_resource]. 17 | #[repr(transparent)] 18 | pub struct ZRes { 19 | inner: zend_resource, 20 | } 21 | 22 | impl ZRes { 23 | /// Wraps a raw pointer. 24 | /// 25 | /// # Safety 26 | /// 27 | /// Create from raw pointer. 28 | /// 29 | /// # Panics 30 | /// 31 | /// Panics if pointer is null. 32 | pub unsafe fn from_ptr<'a>(ptr: *const zend_resource) -> &'a Self { 33 | unsafe { 34 | (ptr as *const Self) 35 | .as_ref() 36 | .expect("ptr should not be null") 37 | } 38 | } 39 | 40 | /// Wraps a raw pointer, return None if pointer is null. 41 | /// 42 | /// # Safety 43 | /// 44 | /// Create from raw pointer. 45 | pub unsafe fn try_from_ptr<'a>(ptr: *const zend_resource) -> Option<&'a Self> { 46 | unsafe { (ptr as *const Self).as_ref() } 47 | } 48 | 49 | /// Wraps a raw pointer. 50 | /// 51 | /// # Safety 52 | /// 53 | /// Create from raw pointer. 54 | /// 55 | /// # Panics 56 | /// 57 | /// Panics if pointer is null. 58 | pub unsafe fn from_mut_ptr<'a>(ptr: *mut zend_resource) -> &'a mut Self { 59 | unsafe { (ptr as *mut Self).as_mut().expect("ptr should not be null") } 60 | } 61 | 62 | /// Wraps a raw pointer, return None if pointer is null. 63 | /// 64 | /// # Safety 65 | /// 66 | /// Create from raw pointer. 67 | pub unsafe fn try_from_mut_ptr<'a>(ptr: *mut zend_resource) -> Option<&'a mut Self> { 68 | unsafe { (ptr as *mut Self).as_mut() } 69 | } 70 | 71 | /// Returns a raw pointer wrapped. 72 | pub const fn as_ptr(&self) -> *const zend_resource { 73 | &self.inner 74 | } 75 | 76 | /// Returns a raw pointer wrapped. 77 | #[inline] 78 | pub fn as_mut_ptr(&mut self) -> *mut zend_resource { 79 | &mut self.inner 80 | } 81 | 82 | /// Gets the resource handle. 83 | #[allow(clippy::useless_conversion)] 84 | pub fn handle(&self) -> i64 { 85 | self.inner.handle.into() 86 | } 87 | } 88 | 89 | impl Debug for ZRes { 90 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 91 | f.debug_struct("ZRes") 92 | .field("handle", &self.handle()) 93 | .finish() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /phper/src/utils.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | //! Internal useful utils. 12 | 13 | use std::ffi::CString; 14 | 15 | pub(crate) fn ensure_end_with_zero(s: impl Into) -> CString { 16 | CString::new(s.into()).expect("CString::new failed") 17 | } 18 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 PHPER Framework Team 2 | # PHPER is licensed under Mulan PSL v2. 3 | # You can use this software according to the terms and conditions of the Mulan 4 | # PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | # http://license.coscl.org.cn/MulanPSL2 6 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | # KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | # NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | # See the Mulan PSL v2 for more details. 10 | 11 | [toolchain] 12 | channel = "1.85" 13 | components = ["clippy", "rustfmt"] 14 | -------------------------------------------------------------------------------- /tests/integration/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 PHPER Framework Team 2 | # PHPER is licensed under Mulan PSL v2. 3 | # You can use this software according to the terms and conditions of the Mulan 4 | # PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | # http://license.coscl.org.cn/MulanPSL2 6 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | # KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | # NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | # See the Mulan PSL v2 for more details. 10 | 11 | [package] 12 | name = "integration" 13 | version = "0.0.0" 14 | authors = { workspace = true } 15 | edition = { workspace = true } 16 | rust-version = { workspace = true } 17 | publish = false 18 | license = { workspace = true } 19 | 20 | [lib] 21 | crate-type = ["lib", "cdylib"] 22 | 23 | [dependencies] 24 | indexmap = "2.7.1" 25 | phper = { workspace = true } 26 | 27 | [dev-dependencies] 28 | phper-test = { workspace = true } 29 | 30 | [build-dependencies] 31 | phper-build = { workspace = true } 32 | 33 | [lints.rust] 34 | unexpected_cfgs = { level = "warn", check-cfg = [ 35 | 'cfg(phper_major_version, values("8"))', 36 | 'cfg(phper_enum_supported)', 37 | ] } 38 | -------------------------------------------------------------------------------- /tests/integration/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /tests/integration/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | fn main() { 12 | phper_build::register_all(); 13 | } 14 | -------------------------------------------------------------------------------- /tests/integration/src/arguments.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use phper::{ 12 | alloc::ToRefOwned, arrays::ZArray, functions::Argument, modules::Module, objects::ZObject, 13 | values::ZVal, 14 | }; 15 | 16 | pub fn integrate(module: &mut Module) { 17 | integrate_arguments(module); 18 | } 19 | 20 | fn integrate_arguments(module: &mut Module) { 21 | module 22 | .add_function("integrate_arguments_null", |arguments: &mut [ZVal]| { 23 | arguments[0].expect_null() 24 | }) 25 | .argument(Argument::new("a")); 26 | 27 | module 28 | .add_function( 29 | "integrate_arguments_long", 30 | |arguments: &mut [ZVal]| -> phper::Result { 31 | let a = arguments[0].expect_long()?; 32 | arguments[1].convert_to_long(); 33 | let b = arguments[1].as_long().unwrap(); 34 | Ok(a + b) 35 | }, 36 | ) 37 | .arguments([Argument::new("a"), Argument::new("b")]); 38 | 39 | module 40 | .add_function("integrate_arguments_double", |arguments: &mut [ZVal]| { 41 | arguments[0].expect_double() 42 | }) 43 | .argument(Argument::new("a")); 44 | 45 | module 46 | .add_function( 47 | "integrate_arguments_string", 48 | |arguments: &mut [ZVal]| -> phper::Result { 49 | let a = arguments[0].expect_z_str()?.to_str()?.to_owned(); 50 | arguments[1].convert_to_string(); 51 | let b = arguments[1].as_z_str().unwrap().to_str()?; 52 | Ok(format!("{}, {}", a, b)) 53 | }, 54 | ) 55 | .argument(Argument::new("a")) 56 | .argument(Argument::new("b")); 57 | 58 | module 59 | .add_function( 60 | "integrate_arguments_array", 61 | |arguments: &mut [ZVal]| -> phper::Result { 62 | let a = arguments[0].expect_z_arr()?; 63 | let mut b = a.to_owned(); 64 | b.insert("a", ZVal::from(1)); 65 | b.insert("foo", ZVal::from("bar")); 66 | Ok(b) 67 | }, 68 | ) 69 | .argument(Argument::new("a")); 70 | 71 | module 72 | .add_function( 73 | "integrate_arguments_object", 74 | |arguments: &mut [ZVal]| -> phper::Result { 75 | let a = arguments[0].expect_mut_z_obj()?; 76 | let mut a = a.to_ref_owned(); 77 | a.set_property("foo", ZVal::from("bar")); 78 | Ok(a) 79 | }, 80 | ) 81 | .argument(Argument::new("a")); 82 | 83 | module 84 | .add_function( 85 | "integrate_arguments_optional", 86 | |arguments: &mut [ZVal]| -> phper::Result { 87 | let a = arguments[0].expect_z_str()?.to_str()?; 88 | let b = arguments 89 | .get(1) 90 | .map(|b| b.expect_bool()) 91 | .transpose()? 92 | .unwrap_or_default(); 93 | Ok(format!("{}: {}", a, b)) 94 | }, 95 | ) 96 | .argument(Argument::new("a")) 97 | .argument(Argument::new("b").optional()); 98 | } 99 | -------------------------------------------------------------------------------- /tests/integration/src/constants.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use phper::modules::Module; 12 | 13 | pub fn integrate(module: &mut Module) { 14 | module.add_constant("INTEGRATE_CONST_NULL", ()); 15 | module.add_constant("INTEGRATE_CONST_TRUE", true); 16 | module.add_constant("INTEGRATE_CONST_FALSE", false); 17 | module.add_constant("INTEGRATE_CONST_LONG", 100i64); 18 | module.add_constant("INTEGRATE_CONST_DOUBLE", 200.); 19 | module.add_constant("INTEGRATE_CONST_STRING", "something"); 20 | module.add_constant("INTEGRATE_CONST_BYTES", "something".as_bytes().to_owned()); 21 | } 22 | -------------------------------------------------------------------------------- /tests/integration/src/errors.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use phper::{ 12 | errors::{ThrowObject, exception_class}, 13 | modules::Module, 14 | }; 15 | use std::io; 16 | 17 | pub fn integrate(module: &mut Module) { 18 | { 19 | let e = phper::Error::boxed("something wrong"); 20 | assert!(matches!(e, phper::Error::Boxed(..))); 21 | assert_eq!(e.to_string(), "something wrong"); 22 | } 23 | 24 | { 25 | let e = phper::Error::boxed(io::Error::new(io::ErrorKind::Other, "oh no!")); 26 | assert!(matches!(e, phper::Error::Boxed(..))); 27 | assert_eq!(e.to_string(), "oh no!"); 28 | } 29 | 30 | module.add_function("integrate_throw_boxed", |_arguments| { 31 | Err::<(), _>(phper::Error::boxed("What's wrong with you?")) 32 | }); 33 | 34 | module.add_function("integrate_throw_object", |_arguments| { 35 | let obj = exception_class().new_object(["Forbidden".into(), 403.into()])?; 36 | let obj = ThrowObject::new(obj)?; 37 | Err::<(), _>(phper::Error::Throw(obj)) 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /tests/integration/src/functions.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use phper::{ 12 | arrays::ZArray, 13 | errors::throw, 14 | functions::{Argument, call}, 15 | modules::Module, 16 | values::ZVal, 17 | }; 18 | use std::{convert::Infallible, io}; 19 | 20 | pub fn integrate(module: &mut Module) { 21 | module.add_function( 22 | "integrate_functions_call", 23 | |_: &mut [ZVal]| -> phper::Result<()> { 24 | let mut arr = ZArray::new(); 25 | arr.insert("a", ZVal::from(1)); 26 | arr.insert("b", ZVal::from(2)); 27 | let ret = call("array_sum", &mut [ZVal::from(arr)])?; 28 | assert_eq!(ret.expect_long()?, 3); 29 | Ok(()) 30 | }, 31 | ); 32 | 33 | module 34 | .add_function( 35 | "integrate_functions_call_callable", 36 | |arguments: &mut [ZVal]| { 37 | if let [head, tail @ ..] = arguments { 38 | Ok::<_, phper::Error>(head.call(tail)?) 39 | } else { 40 | unreachable!() 41 | } 42 | }, 43 | ) 44 | .argument(Argument::new("fn")); 45 | 46 | module.add_function( 47 | "integrate_functions_throw_error_exception", 48 | |_| -> phper::Result<()> { Err(phper::Error::boxed("throw error exception")) }, 49 | ); 50 | 51 | module.add_function("integrate_functions_exception_guard", |_| { 52 | unsafe { 53 | throw(phper::Error::Io(io::Error::new( 54 | io::ErrorKind::Other, 55 | "other io error", 56 | ))); 57 | } 58 | let e = call("integrate_functions_throw_error_exception", []).unwrap_err(); 59 | assert_eq!(e.to_string(), "throw error exception"); 60 | Ok::<_, Infallible>(()) 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /tests/integration/src/ini.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use phper::{ 12 | ini::{Policy, ini_get}, 13 | modules::Module, 14 | }; 15 | use std::{convert::Infallible, ffi::CStr}; 16 | 17 | pub fn integrate(module: &mut Module) { 18 | module.add_ini("INTEGRATE_INI_TRUE", true, Policy::System); 19 | module.add_ini("INTEGRATE_INI_FALSE", false, Policy::System); 20 | module.add_ini("INTEGRATE_INI_LONG", 100i64, Policy::System); 21 | module.add_ini("INTEGRATE_INI_DOUBLE", 200., Policy::System); 22 | module.add_ini( 23 | "INTEGRATE_INI_STRING", 24 | "something".to_owned(), 25 | Policy::System, 26 | ); 27 | 28 | module.add_function("integrate_ini_assert", |_| { 29 | assert!(ini_get::("INTEGRATE_INI_TRUE")); 30 | assert!(!ini_get::("INTEGRATE_INI_FALSE")); 31 | assert_eq!(ini_get::("INTEGRATE_INI_LONG"), 100); 32 | assert_eq!(ini_get::("INTEGRATE_INI_DOUBLE"), 200.); 33 | assert_eq!( 34 | ini_get::>("INTEGRATE_INI_STRING"), 35 | Some(c"something") 36 | ); 37 | Ok::<_, Infallible>(()) 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /tests/integration/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | #![warn(rust_2018_idioms, clippy::dbg_macro, clippy::print_stdout)] 12 | 13 | mod arguments; 14 | mod arrays; 15 | mod classes; 16 | mod constants; 17 | mod enums; 18 | mod errors; 19 | mod functions; 20 | mod ini; 21 | mod objects; 22 | mod references; 23 | mod strings; 24 | mod typehints; 25 | mod values; 26 | 27 | use phper::{modules::Module, php_get_module}; 28 | 29 | #[php_get_module] 30 | pub fn get_module() -> Module { 31 | let mut module = Module::new( 32 | env!("CARGO_CRATE_NAME"), 33 | env!("CARGO_PKG_VERSION"), 34 | env!("CARGO_PKG_AUTHORS"), 35 | ); 36 | 37 | arguments::integrate(&mut module); 38 | arrays::integrate(&mut module); 39 | classes::integrate(&mut module); 40 | functions::integrate(&mut module); 41 | objects::integrate(&mut module); 42 | strings::integrate(&mut module); 43 | values::integrate(&mut module); 44 | constants::integrate(&mut module); 45 | ini::integrate(&mut module); 46 | errors::integrate(&mut module); 47 | references::integrate(&mut module); 48 | typehints::integrate(&mut module); 49 | #[cfg(phper_enum_supported)] 50 | enums::integrate(&mut module); 51 | 52 | module 53 | } 54 | -------------------------------------------------------------------------------- /tests/integration/src/references.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use phper::{functions::Argument, modules::Module}; 12 | 13 | #[allow(clippy::disallowed_names)] 14 | pub fn integrate(module: &mut Module) { 15 | module 16 | .add_function("integrate_test_reference", |arguments| { 17 | let foo = arguments[0].expect_mut_z_ref()?; 18 | *foo.val_mut().expect_mut_long()? += 100; 19 | 20 | let bar = arguments[1].expect_z_ref()?; 21 | bar.val().expect_null()?; 22 | 23 | *arguments[1].as_mut_z_ref().unwrap().val_mut() = "hello".into(); 24 | 25 | Ok::<_, phper::Error>(()) 26 | }) 27 | .arguments([Argument::new("foo").by_ref(), Argument::new("bar").by_ref()]); 28 | } 29 | -------------------------------------------------------------------------------- /tests/integration/src/strings.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use phper::{modules::Module, strings::ZString, values::ZVal}; 12 | 13 | pub fn integrate(module: &mut Module) { 14 | module.add_function( 15 | "integrate_strings_zend_string_new", 16 | |_: &mut [ZVal]| -> phper::Result<()> { 17 | let zs = ZString::new("hello"); 18 | assert_eq!(zs.to_str()?, "hello"); 19 | 20 | let zs = ZString::new([1, 2, 3]); 21 | assert_eq!(AsRef::<[u8]>::as_ref(&zs), &[1, 2, 3]); 22 | 23 | assert!(ZString::new("hello") == ZString::new(b"hello")); 24 | 25 | Ok(()) 26 | }, 27 | ); 28 | 29 | module.add_function( 30 | "integrate_strings_zend_string_new_persistent", 31 | |_: &mut [ZVal]| -> phper::Result<()> { 32 | let zs = ZString::new_persistent("persistent_hello"); 33 | assert_eq!(zs.to_str()?, "persistent_hello"); 34 | 35 | let zs = ZString::new_persistent([4, 5, 6]); 36 | assert_eq!(AsRef::<[u8]>::as_ref(&zs), &[4, 5, 6]); 37 | 38 | assert!( 39 | ZString::new_persistent("persistent") == ZString::new_persistent(b"persistent") 40 | ); 41 | 42 | Ok(()) 43 | }, 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /tests/integration/tests/integration.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 PHPER Framework Team 2 | // PHPER is licensed under Mulan PSL v2. 3 | // You can use this software according to the terms and conditions of the Mulan 4 | // PSL v2. You may obtain a copy of Mulan PSL v2 at: 5 | // http://license.coscl.org.cn/MulanPSL2 6 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 7 | // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 8 | // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | // See the Mulan PSL v2 for more details. 10 | 11 | use phper_test::{cli::test_php_scripts, fpm, fpm::test_fpm_request, utils::get_lib_path}; 12 | use std::{ 13 | env, 14 | path::{Path, PathBuf}, 15 | }; 16 | 17 | #[test] 18 | fn test_cli() { 19 | let tests_php_dir = Path::new(env!("CARGO_MANIFEST_DIR")) 20 | .join("tests") 21 | .join("php"); 22 | 23 | test_php_scripts( 24 | get_lib_path( 25 | PathBuf::from(env!("CARGO_MANIFEST_DIR")) 26 | .join("..") 27 | .join("..") 28 | .join("target"), 29 | "integration", 30 | ), 31 | &[ 32 | &tests_php_dir.join("phpinfo.php"), 33 | &tests_php_dir.join("arguments.php"), 34 | &tests_php_dir.join("arrays.php"), 35 | &tests_php_dir.join("classes.php"), 36 | &tests_php_dir.join("functions.php"), 37 | &tests_php_dir.join("objects.php"), 38 | &tests_php_dir.join("strings.php"), 39 | &tests_php_dir.join("values.php"), 40 | &tests_php_dir.join("constants.php"), 41 | &tests_php_dir.join("ini.php"), 42 | &tests_php_dir.join("references.php"), 43 | &tests_php_dir.join("errors.php"), 44 | &tests_php_dir.join("reflection.php"), 45 | &tests_php_dir.join("typehints.php"), 46 | &tests_php_dir.join("enums.php"), 47 | ], 48 | ); 49 | } 50 | 51 | #[test] 52 | fn test_fpm() { 53 | let tests_php_dir = Path::new(env!("CARGO_MANIFEST_DIR")) 54 | .join("tests") 55 | .join("php"); 56 | 57 | fpm::setup(get_lib_path( 58 | PathBuf::from(env!("CARGO_MANIFEST_DIR")) 59 | .join("..") 60 | .join("..") 61 | .join("target"), 62 | "integration", 63 | )); 64 | 65 | test_fpm_request("GET", &tests_php_dir, "/arguments.php", None, None); 66 | test_fpm_request("GET", &tests_php_dir, "/arrays.php", None, None); 67 | test_fpm_request("GET", &tests_php_dir, "/classes.php", None, None); 68 | test_fpm_request("GET", &tests_php_dir, "/functions.php", None, None); 69 | test_fpm_request("GET", &tests_php_dir, "/objects.php", None, None); 70 | test_fpm_request("GET", &tests_php_dir, "/strings.php", None, None); 71 | test_fpm_request("GET", &tests_php_dir, "/values.php", None, None); 72 | test_fpm_request("GET", &tests_php_dir, "/constants.php", None, None); 73 | test_fpm_request("GET", &tests_php_dir, "/ini.php", None, None); 74 | test_fpm_request("GET", &tests_php_dir, "/enums.php", None, None); 75 | } 76 | -------------------------------------------------------------------------------- /tests/integration/tests/php/_common.php: -------------------------------------------------------------------------------- 1 | $value) { 42 | if ($object->$key !== $value) { 43 | throw new AssertionError(sprintf("expect field `%s` value %s, found `%s`", $key, $value, $object->$key)); 44 | } 45 | } 46 | } 47 | 48 | function assert_throw($callable, $expect_exception_class, $expect_exception_code, $expect_exception_message) { 49 | try { 50 | $callable(); 51 | throw new AssertionError("`{$expect_exception_message}` not throws"); 52 | } catch (Throwable $e) { 53 | if (get_class($e) != $expect_exception_class) { 54 | throw new AssertionError(sprintf("expect exception class `%s`, found `%s`", $expect_exception_class, get_class($e))); 55 | } 56 | if ($e->getCode() != $expect_exception_code) { 57 | throw new AssertionError(sprintf("expect exception code `%s`, found `%s`", $expect_exception_code, $e->getCode())); 58 | } 59 | if ($e->getMessage() != $expect_exception_message) { 60 | throw new AssertionError(sprintf("expect exception message `%s`, found `%s`", $expect_exception_message, $e->getMessage())); 61 | } 62 | } 63 | } 64 | 65 | function array_ksort($array) { 66 | ksort($array); 67 | return $array; 68 | } 69 | 70 | /* @var string $version Basic php version ('7.0', '8.4') */ 71 | function php_at_least(string $version) { 72 | return version_compare(PHP_VERSION, $version, '>='); 73 | } 74 | -------------------------------------------------------------------------------- /tests/integration/tests/php/arguments.php: -------------------------------------------------------------------------------- 1 | = 70100) { 17 | $argumentCountErrorName = "ArgumentCountError"; 18 | } else { 19 | $argumentCountErrorName = "TypeError"; 20 | } 21 | 22 | assert_eq(integrate_arguments_null(null), null); 23 | 24 | assert_throw(function () { integrate_arguments_null(); }, $argumentCountErrorName, 0, "integrate_arguments_null(): expects at least 1 parameter(s), 0 given"); 25 | assert_throw(function () { integrate_arguments_null(1); }, "TypeError", 0, "type error: must be of type null, int given"); 26 | 27 | assert_eq(integrate_arguments_long(1, 2), 3); 28 | assert_eq(integrate_arguments_long(1, "2"), 3); 29 | assert_throw(function () { integrate_arguments_long("1", "2"); }, "TypeError", 0, "type error: must be of type int, string given"); 30 | 31 | assert_eq(integrate_arguments_double(1.0), 1.0); 32 | assert_throw(function () { integrate_arguments_double(1); }, "TypeError", 0, "type error: must be of type float, int given"); 33 | 34 | assert_eq(integrate_arguments_string("hello", "world"), "hello, world"); 35 | assert_eq(integrate_arguments_string("hello", 123), "hello, 123"); 36 | assert_throw(function () { integrate_arguments_string(1, 2); }, "TypeError", 0, "type error: must be of type string, int given"); 37 | 38 | assert_eq(integrate_arguments_array(["a" => 1]), ["a" => 1, "foo" => "bar"]); 39 | assert_throw(function () { integrate_arguments_array(null); }, "TypeError", 0, "type error: must be of type array, null given"); 40 | 41 | $obj = new stdClass(); 42 | $obj->a = 1; 43 | assert_object(integrate_arguments_object($obj), "stdClass", ["a" => 1, "foo" => "bar"]); 44 | assert_throw(function () { integrate_arguments_object(1); }, "TypeError", 0, "type error: must be of type object, int given"); 45 | 46 | assert_throw(function () { integrate_arguments_optional(); }, $argumentCountErrorName, 0, "integrate_arguments_optional(): expects at least 1 parameter(s), 0 given"); 47 | assert_eq(integrate_arguments_optional("foo"), "foo: false"); 48 | assert_eq(integrate_arguments_optional("foo", true), "foo: true"); 49 | assert_eq(integrate_arguments_optional("foo", true, "bar"), "foo: true"); 50 | -------------------------------------------------------------------------------- /tests/integration/tests/php/arrays.php: -------------------------------------------------------------------------------- 1 | incr(); 30 | assert_eq($b->get(), 123456); 31 | assert_eq($b2->get(), 123457); 32 | 33 | class B2 extends IntegrationTest\Objects\B {} 34 | $b2 = new B2(); 35 | $b22 = clone $b2; 36 | assert_eq($b22->get(), 123456); 37 | -------------------------------------------------------------------------------- /tests/integration/tests/php/phpinfo.php: -------------------------------------------------------------------------------- 1 | getName(), "integration"); 18 | assert_eq($ext->getVersion(), "0.0.0"); 19 | -------------------------------------------------------------------------------- /tests/integration/tests/php/strings.php: -------------------------------------------------------------------------------- 1 | 1, "b" => "foo"]); 25 | assert_object(integration_values_return_object(), "stdClass", ["foo" => "bar"]); 26 | assert_eq(integration_values_return_option_i64_some(), 64); 27 | assert_eq(integration_values_return_option_i64_none(), null); 28 | assert_eq(integration_values_return_result_string_ok(), "foo"); 29 | assert_throw("integration_values_return_result_string_err", "ErrorException", 0, "a zhe"); 30 | assert_eq(integration_values_return_val(), "foo"); 31 | --------------------------------------------------------------------------------