diff --git a/README.md b/README.md index c4ae23a881..0e7ad64846 100644 --- a/README.md +++ b/README.md @@ -19,22 +19,19 @@ Export a simple function `function hello_world(string $name): string` to PHP: ```rust #![cfg_attr(windows, feature(abi_vectorcall))] -use ext_php_rs::prelude::*; - -/// Gives you a nice greeting! -/// -/// @param string $name Your name. -/// -/// @return string Nice greeting! -#[php_function] -pub fn hello_world(name: String) -> String { - format!("Hello, {}!", name) -} +use ext_php_rs::php_module; -// Required to register the extension with PHP. #[php_module] -pub fn module(module: ModuleBuilder) -> ModuleBuilder { - module +mod module { + /// Gives you a nice greeting! + /// + /// @param string $name Your name. + /// + /// @return string Nice greeting! + #[php_function] + pub fn hello_world(name: String) -> String { + format!("Hello, {}!", name) + } } ``` @@ -148,7 +145,7 @@ best resource at the moment. This can be viewed at [docs.rs]. functionality to be cross-platform is on the roadmap. - To build the application in `DEBUG` mode on Windows, you must have a `PHP SDK` built with the `DEBUG` option enabled - and specify the `PHP_LIB` to the folder containing the lib files. + and specify the `PHP_LIB` to the folder containing the lib files. For example: set `PHP_LIB=C:\php-sdk\php-dev\vc16\x64\php-8.3.13-src\x64\Debug_TS`. [vectorcall]: https://docs.microsoft.com/en-us/cpp/cpp/vectorcall?view=msvc-170 diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml index 67d05e25fb..96c3e176c6 100644 --- a/crates/macros/Cargo.toml +++ b/crates/macros/Cargo.toml @@ -19,3 +19,6 @@ quote = "1.0.9" proc-macro2 = "1.0.26" lazy_static = "1.4.0" anyhow = "1.0" + +[lints.rust] +missing_docs = "warn" diff --git a/crates/macros/src/class.rs b/crates/macros/src/class.rs index c10a015429..e10c368337 100644 --- a/crates/macros/src/class.rs +++ b/crates/macros/src/class.rs @@ -1,6 +1,5 @@ use std::collections::HashMap; -use crate::STATE; use anyhow::{anyhow, bail, Context, Result}; use darling::{FromMeta, ToTokens}; use proc_macro2::{Ident, Span, TokenStream}; @@ -41,7 +40,7 @@ pub struct AttrArgs { flags: Option, } -pub fn parser(args: AttributeArgs, mut input: ItemStruct) -> Result { +pub fn parser(args: AttributeArgs, mut input: ItemStruct) -> Result<(TokenStream, String, Class)> { let args = AttrArgs::from_list(&args) .map_err(|e| anyhow!("Unable to parse attribute arguments: {:?}", e))?; @@ -52,7 +51,11 @@ pub fn parser(args: AttributeArgs, mut input: ItemStruct) -> Result input.attrs = { let mut unused = vec![]; - for attr in input.attrs.into_iter() { + for attr in input + .attrs + .into_iter() + .filter(|a| !a.path.is_ident("php_class")) + { match parse_attribute(&attr)? { Some(parsed) => match parsed { ParsedAttribute::Extends(class) => { @@ -132,23 +135,15 @@ pub fn parser(args: AttributeArgs, mut input: ItemStruct) -> Result ..Default::default() }; - let mut state = STATE.lock(); + Ok(( + quote! { + #input - if state.built_module { - bail!("The `#[php_module]` macro must be called last to ensure functions and classes are registered."); - } - - if state.startup_function.is_some() { - bail!("The `#[php_startup]` macro must be called after all the classes have been defined."); - } - - state.classes.insert(ident.to_string(), class); - - Ok(quote! { - #input - - ::ext_php_rs::class_derives!(#ident); - }) + ::ext_php_rs::class_derives!(#ident); + }, + ident.to_string(), + class, + )) } #[derive(Debug)] diff --git a/crates/macros/src/constant.rs b/crates/macros/src/constant.rs index 3a9516c26b..5f552ef398 100644 --- a/crates/macros/src/constant.rs +++ b/crates/macros/src/constant.rs @@ -1,12 +1,10 @@ use crate::helpers::get_docs; -use anyhow::{bail, Result}; +use anyhow::Result; use darling::ToTokens; use proc_macro2::TokenStream; use quote::quote; use syn::{Expr, ItemConst}; -use crate::STATE; - #[derive(Debug)] pub struct Constant { pub name: String, @@ -15,23 +13,22 @@ pub struct Constant { pub value: String, } -pub fn parser(input: ItemConst) -> Result { - let mut state = STATE.lock(); - - if state.startup_function.is_some() { - bail!("Constants must be declared before you declare your startup function and module function."); - } - - state.constants.push(Constant { +pub fn parser(input: &mut ItemConst) -> Result<(TokenStream, Constant)> { + let constant = Constant { name: input.ident.to_string(), docs: get_docs(&input.attrs), value: input.expr.to_token_stream().to_string(), - }); + }; + + input.attrs.remove(0); - Ok(quote! { - #[allow(dead_code)] - #input - }) + Ok(( + quote! { + #[allow(dead_code)] + #input + }, + constant, + )) } impl Constant { diff --git a/crates/macros/src/function.rs b/crates/macros/src/function.rs index 670d32fedc..e2f4e07f25 100644 --- a/crates/macros/src/function.rs +++ b/crates/macros/src/function.rs @@ -1,14 +1,14 @@ use std::collections::HashMap; use crate::helpers::get_docs; -use crate::{syn_ext::DropLifetimes, STATE}; +use crate::syn_ext::DropLifetimes; use anyhow::{anyhow, bail, Result}; use darling::{FromMeta, ToTokens}; use proc_macro2::{Ident, Literal, Span, TokenStream}; use quote::quote; use syn::{ - punctuated::Punctuated, AttributeArgs, FnArg, GenericArgument, ItemFn, Lit, PathArguments, - ReturnType, Signature, Token, Type, TypePath, + punctuated::Punctuated, FnArg, GenericArgument, ItemFn, Lit, PathArguments, ReturnType, + Signature, Token, Type, TypePath, }; #[derive(Default, Debug, FromMeta)] @@ -40,13 +40,8 @@ pub struct Function { pub output: Option<(String, bool)>, } -pub fn parser(args: AttributeArgs, input: ItemFn) -> Result<(TokenStream, Function)> { - let attr_args = match AttrArgs::from_list(&args) { - Ok(args) => args, - Err(e) => bail!("Unable to parse attribute arguments: {:?}", e), - }; - - let ItemFn { sig, .. } = &input; +pub fn parser(attr_args: AttrArgs, input: &ItemFn) -> Result<(TokenStream, Function)> { + let ItemFn { sig, block, .. } = &input; let Signature { ident, output, @@ -69,7 +64,8 @@ pub fn parser(args: AttributeArgs, input: ItemFn) -> Result<(TokenStream, Functi let return_type = get_return_type(output)?; let func = quote! { - #input + #sig + #block ::ext_php_rs::zend_fastcall! { #[doc(hidden)] @@ -89,12 +85,6 @@ pub fn parser(args: AttributeArgs, input: ItemFn) -> Result<(TokenStream, Functi } }; - let mut state = STATE.lock(); - - if state.built_module && !attr_args.ignore_module { - bail!("The `#[php_module]` macro must be called last to ensure functions are registered. To ignore this error, pass the `ignore_module` option into this attribute invocation: `#[php_function(ignore_module)]`"); - } - let function = Function { name: attr_args.name.unwrap_or_else(|| ident.to_string()), docs: get_docs(&input.attrs), @@ -104,8 +94,6 @@ pub fn parser(args: AttributeArgs, input: ItemFn) -> Result<(TokenStream, Functi output: return_type, }; - state.functions.push(function.clone()); - Ok((func, function)) } diff --git a/crates/macros/src/impl_.rs b/crates/macros/src/impl_.rs index 821ead014a..2ddaf14848 100644 --- a/crates/macros/src/impl_.rs +++ b/crates/macros/src/impl_.rs @@ -5,6 +5,7 @@ use quote::quote; use std::collections::HashMap; use syn::{Attribute, AttributeArgs, ItemImpl, Lit, Meta, NestedMeta}; +use crate::class::Class; use crate::helpers::get_docs; use crate::{ class::{Property, PropertyAttr}, @@ -94,7 +95,11 @@ pub enum PropAttrTy { Setter, } -pub fn parser(args: AttributeArgs, input: ItemImpl) -> Result { +pub fn parser( + args: AttributeArgs, + input: ItemImpl, + classes: &mut HashMap, +) -> Result { let args = AttrArgs::from_list(&args) .map_err(|e| anyhow!("Unable to parse attribute arguments: {:?}", e))?; @@ -105,15 +110,7 @@ pub fn parser(args: AttributeArgs, input: ItemImpl) -> Result { bail!("This macro cannot be used on trait implementations."); } - let mut state = crate::STATE.lock(); - - if state.startup_function.is_some() { - bail!( - "Impls must be declared before you declare your startup function and module function." - ); - } - - let class = state.classes.get_mut(&class_name).ok_or_else(|| { + let class = classes.get_mut(&class_name).ok_or_else(|| { anyhow!( "You must use `#[php_class]` on the struct before using this attribute on the impl." ) diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 2e6deaf8ab..0dd947de9b 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -1,3 +1,4 @@ +//! # Macros for PHP bindings mod class; mod constant; mod extern_; @@ -7,77 +8,43 @@ mod helpers; mod impl_; mod method; mod module; +mod module_builder; mod startup_function; mod syn_ext; mod zval; -use std::{ - collections::HashMap, - sync::{Mutex, MutexGuard}, -}; - -use constant::Constant; use proc_macro::TokenStream; use proc_macro2::Span; -use syn::{ - parse_macro_input, AttributeArgs, DeriveInput, ItemConst, ItemFn, ItemForeignMod, ItemImpl, - ItemStruct, -}; +use syn::{parse_macro_input, DeriveInput, ItemFn, ItemForeignMod, ItemMod}; extern crate proc_macro; -#[derive(Default, Debug)] -struct State { - functions: Vec, - classes: HashMap, - constants: Vec, - startup_function: Option, - built_module: bool, -} - -lazy_static::lazy_static! { - pub(crate) static ref STATE: StateMutex = StateMutex::new(); -} - -struct StateMutex(Mutex); - -impl StateMutex { - pub fn new() -> Self { - Self(Mutex::new(Default::default())) - } - - pub fn lock(&self) -> MutexGuard { - self.0.lock().unwrap_or_else(|e| e.into_inner()) - } -} - +/// Structs can be exported to PHP as classes with the #[php_class] attribute macro. This attribute derives the RegisteredClass trait on your struct, as well as registering the class to be registered with the #[php_module] macro. #[proc_macro_attribute] -pub fn php_class(args: TokenStream, input: TokenStream) -> TokenStream { - let args = parse_macro_input!(args as AttributeArgs); - let input = parse_macro_input!(input as ItemStruct); - - match class::parser(args, input) { - Ok(parsed) => parsed, - Err(e) => syn::Error::new(Span::call_site(), e).to_compile_error(), - } +pub fn php_class(_args: TokenStream, _input: TokenStream) -> TokenStream { + syn::Error::new( + Span::call_site(), + "php_class can only be used inside a #[php_module] module", + ) + .to_compile_error() .into() } +/// Used to annotate functions which should be exported to PHP. Note that this should not be used on class methods - see the #[php_impl] macro for that. #[proc_macro_attribute] -pub fn php_function(args: TokenStream, input: TokenStream) -> TokenStream { - let args = parse_macro_input!(args as AttributeArgs); - let input = parse_macro_input!(input as ItemFn); - - match function::parser(args, input) { - Ok((parsed, _)) => parsed, - Err(e) => syn::Error::new(Span::call_site(), e).to_compile_error(), - } +pub fn php_function(_args: TokenStream, _input: TokenStream) -> TokenStream { + syn::Error::new( + Span::call_site(), + "php_function can only be used inside a #[php_module] module", + ) + .to_compile_error() .into() } +/// The module macro is used to annotate the get_module function, which is used by the PHP interpreter to retrieve information about your extension, including the name, version, functions and extra initialization functions. Regardless if you use this macro, your extension requires a extern "C" fn get_module() so that PHP can get this information. #[proc_macro_attribute] -pub fn php_module(_: TokenStream, input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as ItemFn); +pub fn php_module(_args: TokenStream, input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as ItemMod); match module::parser(input) { Ok(parsed) => parsed, @@ -86,41 +53,47 @@ pub fn php_module(_: TokenStream, input: TokenStream) -> TokenStream { .into() } +/// Used to define the PHP extension startup function. This function is used to register extension classes and constants with the PHP interpreter. #[proc_macro_attribute] -pub fn php_startup(args: TokenStream, input: TokenStream) -> TokenStream { - let args = parse_macro_input!(args as AttributeArgs); - let input = parse_macro_input!(input as ItemFn); - - match startup_function::parser(Some(args), input) { - Ok(parsed) => parsed, - Err(e) => syn::Error::new(Span::call_site(), e).to_compile_error(), - } +pub fn php_startup(_args: TokenStream, _input: TokenStream) -> TokenStream { + syn::Error::new( + Span::call_site(), + "php_startup can only be used inside a #[php_module] module", + ) + .to_compile_error() .into() } +/// You can export an entire impl block to PHP. This exports all methods as well as constants to PHP on the class that it is implemented on. This requires the #[php_class] macro to already be used on the underlying struct. Trait implementations cannot be exported to PHP. #[proc_macro_attribute] -pub fn php_impl(args: TokenStream, input: TokenStream) -> TokenStream { - let args = parse_macro_input!(args as AttributeArgs); - let input = parse_macro_input!(input as ItemImpl); - - match impl_::parser(args, input) { - Ok(parsed) => parsed, - Err(e) => syn::Error::new(Span::call_site(), e).to_compile_error(), - } +pub fn php_impl(_args: TokenStream, _input: TokenStream) -> TokenStream { + syn::Error::new( + Span::call_site(), + "php_impl can only be used inside a #[php_module] module", + ) + .to_compile_error() .into() } +/// Exports a Rust constant as a global PHP constant. The constant can be any type that implements [`IntoConst`]. #[proc_macro_attribute] -pub fn php_const(_: TokenStream, input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as ItemConst); - - match constant::parser(input) { - Ok(parsed) => parsed, - Err(e) => syn::Error::new(Span::call_site(), e).to_compile_error(), - } +pub fn php_const(_args: TokenStream, _input: TokenStream) -> TokenStream { + syn::Error::new( + Span::call_site(), + "php_const can only be used inside a #[php_module] module", + ) + .to_compile_error() .into() } +/// Attribute used to annotate `extern` blocks which are deemed as PHP +/// functions. +/// +/// This allows you to 'import' PHP functions into Rust so that they can be +/// called like regular Rust functions. Parameters can be any type that +/// implements [`IntoZval`], and the return type can be anything that implements +/// [`From`] (notice how [`Zval`] is consumed rather than borrowed in this +/// case). #[proc_macro_attribute] pub fn php_extern(_: TokenStream, input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as ItemForeignMod); @@ -132,6 +105,9 @@ pub fn php_extern(_: TokenStream, input: TokenStream) -> TokenStream { .into() } +/// Derives the traits required to convert a struct or enum to and from a +/// [`Zval`]. Both [`FromZval`] and [`IntoZval`] are implemented on types which +/// use this macro. #[proc_macro_derive(ZvalConvert)] pub fn zval_convert_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); @@ -143,6 +119,14 @@ pub fn zval_convert_derive(input: TokenStream) -> TokenStream { .into() } +/// Defines an `extern` function with the Zend fastcall convention based on +/// operating system. +/// +/// On Windows, Zend fastcall functions use the vector calling convention, while +/// on all other operating systems no fastcall convention is used (just the +/// regular C calling convention). +/// +/// This macro wraps a function and applies the correct calling convention. #[proc_macro] pub fn zend_fastcall(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as ItemFn); diff --git a/crates/macros/src/module.rs b/crates/macros/src/module.rs index f350fd472b..4decafa27a 100644 --- a/crates/macros/src/module.rs +++ b/crates/macros/src/module.rs @@ -1,97 +1,73 @@ -use std::sync::MutexGuard; +use std::collections::HashMap; -use anyhow::{anyhow, bail, Result}; +use anyhow::Result; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use syn::{ItemFn, Signature, Type}; +use syn::{Item, ItemMod, Type}; use crate::{ class::{Class, Property}, + constant::Constant, function::{Arg, Function}, - startup_function, State, STATE, + module_builder::ModuleBuilder, }; -pub fn parser(input: ItemFn) -> Result { - let ItemFn { sig, block, .. } = input; - let Signature { output, inputs, .. } = sig; - let stmts = &block.stmts; - - let mut state = STATE.lock(); - - if state.built_module { - bail!("You may only define a module with the `#[php_module]` attribute once."); +pub fn parser(input: ItemMod) -> Result { + if input.ident != "module" { + return Ok( + quote! { compile_error!("The `php_module` attribute must be used on a module named `module`."); }, + ); + } + if input.content.is_none() { + return Ok(quote! {}); } - state.built_module = true; - - // Generate startup function if one hasn't already been tagged with the macro. - let startup_fn = if (!state.classes.is_empty() || !state.constants.is_empty()) - && state.startup_function.is_none() - { - drop(state); - - let parsed = syn::parse2(quote! { - fn php_module_startup() {} - }) - .map_err(|_| anyhow!("Unable to generate PHP module startup function."))?; - let startup = startup_function::parser(None, parsed)?; - - state = STATE.lock(); - Some(startup) - } else { - None - }; - - let functions = state - .functions - .iter() - .map(|func| func.get_builder()) - .collect::>(); - let startup = state.startup_function.as_ref().map(|ident| { - let ident = Ident::new(ident, Span::call_site()); - quote! { - .startup_function(#ident) - } - }); - let registered_classes_impls = state - .classes - .values() - .map(generate_registered_class_impl) - .collect::>>()?; - let describe_fn = generate_stubs(&state); - - let result = quote! { - #(#registered_classes_impls)* - - #startup_fn - - #[doc(hidden)] - #[no_mangle] - pub extern "C" fn get_module() -> *mut ::ext_php_rs::zend::ModuleEntry { - fn internal(#inputs) #output { - #(#stmts)* + let mut builder = ModuleBuilder::new(); + let (_, content) = &input.content.expect("module content is missing"); + for item in content { + match item { + Item::Fn(f) => { + if f.attrs.iter().any(|a| a.path.is_ident("php_startup")) { + builder.set_startup_function(f.clone()); + continue; + } else if f.attrs.iter().any(|a| a.path.is_ident("php_function")) { + builder.add_function(f.clone()); + continue; + } } - - let mut builder = ::ext_php_rs::builders::ModuleBuilder::new( - env!("CARGO_PKG_NAME"), - env!("CARGO_PKG_VERSION") - ) - #startup - #(.function(#functions.unwrap()))* - ; - - // TODO allow result return types - let builder = internal(builder); - - match builder.build() { - Ok(module) => module.into_raw(), - Err(e) => panic!("Failed to build PHP module: {:?}", e), + Item::Const(c) => { + builder.add_constant(c.clone()); + continue; + } + Item::Struct(s) => { + if s.attrs.iter().any(|a| a.path.is_ident("php_class")) { + builder.add_class(s.clone()); + continue; + } } + Item::Impl(i) => { + if i.attrs.iter().any(|a| a.path.is_ident("php_impl")) { + builder.add_implementation(i.clone()); + continue; + } + } + _ => {} } + builder.add_unmapped(item.clone()); + } - #describe_fn - }; - Ok(result) + // let ItemFn { sig, block, .. } = input; + // let Signature { output, inputs, .. } = sig; + // let stmts = &block.stmts; + + // let registered_classes_impls = state + // .classes + // .values() + // .map(generate_registered_class_impl) + // .collect::>>()?; + // let describe_fn = generate_stubs(&state); + + Ok(builder.build()) } /// Generates an implementation for `RegisteredClass` on the given class. @@ -151,8 +127,12 @@ pub trait Describe { fn describe(&self) -> TokenStream; } -fn generate_stubs(state: &MutexGuard) -> TokenStream { - let module = state.describe(); +pub(crate) fn generate_stubs( + functions: &[Function], + classes: &HashMap, + constants: &[Constant], +) -> TokenStream { + let module = (functions, classes, constants).describe(); quote! { #[cfg(debug_assertions)] @@ -363,11 +343,11 @@ impl Describe for crate::constant::Constant { } } -impl Describe for State { +impl Describe for (&[Function], &HashMap, &[Constant]) { fn describe(&self) -> TokenStream { - let functs = self.functions.iter().map(Describe::describe); - let classes = self.classes.values().map(|class| class.describe()); - let constants = self.constants.iter().map(Describe::describe); + let functs = self.0.iter().map(Describe::describe); + let classes = self.1.values().map(|class| class.describe()); + let constants = self.2.iter().map(Describe::describe); quote! { Module { diff --git a/crates/macros/src/module_builder.rs b/crates/macros/src/module_builder.rs new file mode 100644 index 0000000000..954076ad6e --- /dev/null +++ b/crates/macros/src/module_builder.rs @@ -0,0 +1,245 @@ +use std::collections::HashMap; + +use anyhow::{anyhow, Result}; +use darling::FromMeta; +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{ + spanned::Spanned as _, Attribute, AttributeArgs, Ident, Item, ItemConst, ItemFn, ItemImpl, + ItemStruct, NestedMeta, +}; + +use crate::{ + class::{self, Class}, + constant::{self, Constant}, + function, impl_, + module::{self, generate_registered_class_impl}, + startup_function, +}; + +#[derive(Default)] +pub(crate) struct ModuleBuilder { + pub functions: Vec, + pub startup_function: Option, + pub constants: Vec, + pub classes: Vec, + pub implementations: Vec, + pub unmapped: Vec, +} + +impl ModuleBuilder { + pub fn new() -> Self { + Self::default() + } + + pub fn add_function(&mut self, function: ItemFn) { + self.functions.push(function); + } + + pub fn set_startup_function(&mut self, function: ItemFn) { + self.startup_function = Some(function); + } + + pub fn add_constant(&mut self, constant: ItemConst) { + self.constants.push(constant); + } + + pub fn add_class(&mut self, class: ItemStruct) { + self.classes.push(class); + } + + pub fn add_implementation(&mut self, implementation: ItemImpl) { + self.implementations.push(implementation); + } + + pub fn add_unmapped(&mut self, item: Item) { + self.unmapped.push(item); + } + + pub fn build(&self) -> TokenStream { + let (class_stream, mut classes) = self.build_classes(); + let impl_stream = &self + .implementations + .iter() + .map(|implementation| { + let args = implementation + .attrs + .iter() + .find(|attr| attr.path.is_ident("php_impl")); + let args = parse_metadata(args.unwrap()); + impl_::parser(args, implementation.clone(), &mut classes) + }) + .collect::, _>>() + .unwrap(); + + let (function_stream, functions) = self.build_functions(); + let (constant_stream, constants) = self.build_constants(); + let (startup_function, startup_ident) = + self.build_startup_function(&classes, &constants).unwrap(); + + let describe_fn = module::generate_stubs(&functions, &classes, &constants); + + let functions = functions + .iter() + .map(|func| func.get_builder()) + .collect::>(); + let registered_classes_impls = classes + .values() + .map(generate_registered_class_impl) + .collect::, _>>() + .unwrap(); + let unmapped = &self.unmapped; + + quote! { + mod module { + use ::ext_php_rs::prelude::*; + #class_stream + #(#impl_stream)* + #(#registered_classes_impls)* + #function_stream + #constant_stream + #startup_function + #(#unmapped)* + + #[doc(hidden)] + #[no_mangle] + pub extern "C" fn get_module() -> *mut ::ext_php_rs::zend::ModuleEntry { + // fn internal(#inputs) #output { + // #(#stmts)* + // } + + let mut builder = ::ext_php_rs::builders::ModuleBuilder::new( + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_VERSION") + ) + .startup_function(#startup_ident) + #(.function(#functions.unwrap()))* + ; + + // TODO allow result return types + // let builder = internal(builder); + + match builder.build() { + Ok(module) => module.into_raw(), + Err(e) => panic!("Failed to build PHP module: {:?}", e), + } + } + + #describe_fn + } + } + } + + fn build_functions(&self) -> (TokenStream, Vec) { + let functions = self + .functions + .iter() + .map(|f| { + let attr = f.attrs.iter().find(|a| a.path.is_ident("php_function")); + let args = parse_attr(attr.unwrap()).unwrap(); + function::parser(args, f) + }) + .collect::, _>>() + .unwrap(); + + let tokens = functions.iter().map(|(tokens, _)| tokens); + + ( + quote! { #(#tokens)* }, + functions.into_iter().map(|(_, f)| f).collect(), + ) + } + + fn build_constants(&self) -> (TokenStream, Vec) { + let constants = self + .constants + .iter() + .map(|c| constant::parser(&mut c.clone())) + .collect::, _>>() + .unwrap(); + + let tokens = constants.iter().map(|(tokens, _)| tokens); + + ( + quote! { #(#tokens)* }, + constants.into_iter().map(|(_, c)| c).collect(), + ) + } + + fn build_classes(&self) -> (TokenStream, HashMap) { + let structs = self + .classes + .iter() + .map(|class| { + let args = class + .attrs + .iter() + .find(|attr| attr.path.is_ident("php_class")); + let args = parse_metadata(args.unwrap()); + class::parser(args, class.clone()) + }) + .collect::, _>>() + .unwrap(); + + let tokens = structs.iter().map(|(tokens, _, _)| tokens); + + ( + quote! { #(#tokens)* }, + structs + .into_iter() + .map(|(_, name, class)| (name, class)) + .collect(), + ) + } + + fn build_startup_function( + &self, + classes: &HashMap, + constants: &[Constant], + ) -> Result<(TokenStream, Ident)> { + self.startup_function + .as_ref() + .map(|f| { + let attr = f.attrs.iter().find(|a| a.path.is_ident("php_startup")); + let args = parse_attr(attr.unwrap()).unwrap(); + startup_function::parser(Some(args), f, classes, constants) + }) + .unwrap_or_else(|| { + let parsed = syn::parse2(quote! { + fn php_module_startup() {} + }) + .map_err(|_| anyhow!("Unable to generate PHP module startup function."))?; + startup_function::parser(None, &parsed, classes, constants) + }) + } +} + +fn parse_attr(attr: &Attribute) -> Result +where + T: FromMeta, +{ + let meta = parse_metadata(attr); + + parse_from_meta(&meta, Some(attr.span())) +} + +fn parse_metadata(attr: &Attribute) -> Vec { + if let Ok(args) = attr.parse_args::().map(|args| args.into()) { + syn::parse_macro_input::parse::(args).unwrap_or_default() + } else { + vec![] + } +} + +fn parse_from_meta(meta: &[NestedMeta], call_site: Option) -> Result +where + T: FromMeta, +{ + T::from_list(meta).map_err(|e| { + syn::Error::new( + call_site.unwrap_or_else(Span::call_site), + format!("Unable to parse attribute arguments: {:?}", e), + ) + .to_compile_error() + }) +} diff --git a/crates/macros/src/startup_function.rs b/crates/macros/src/startup_function.rs index 695753d6d9..a70fe4bd01 100644 --- a/crates/macros/src/startup_function.rs +++ b/crates/macros/src/startup_function.rs @@ -4,33 +4,30 @@ use anyhow::{anyhow, Result}; use darling::FromMeta; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use syn::{AttributeArgs, Expr, ItemFn, Signature}; +use syn::{Expr, ItemFn, Signature}; -use crate::{class::Class, constant::Constant, STATE}; +use crate::{class::Class, constant::Constant}; #[derive(Default, Debug, FromMeta)] #[darling(default)] -struct StartupArgs { +pub(crate) struct StartupArgs { before: bool, } -pub fn parser(args: Option, input: ItemFn) -> Result { - let args = if let Some(args) = args { - StartupArgs::from_list(&args) - .map_err(|e| anyhow!("Unable to parse attribute arguments: {:?}", e))? - } else { - StartupArgs::default() - }; +pub fn parser( + args: Option, + input: &ItemFn, + classes: &HashMap, + constants: &[Constant], +) -> Result<(TokenStream, Ident)> { + let args = args.unwrap_or_default(); let ItemFn { sig, block, .. } = input; let Signature { ident, .. } = sig; let stmts = &block.stmts; - let mut state = STATE.lock(); - state.startup_function = Some(ident.to_string()); - - let classes = build_classes(&state.classes)?; - let constants = build_constants(&state.constants); + let classes = build_classes(classes)?; + let constants = build_constants(constants); let (before, after) = if args.before { (Some(quote! { internal(ty, module_number); }), None) } else { @@ -58,7 +55,7 @@ pub fn parser(args: Option, input: ItemFn) -> Result } }; - Ok(func) + Ok((func, ident.clone())) } /// Returns a vector of `ClassBuilder`s for each class. @@ -68,7 +65,7 @@ fn build_classes(classes: &HashMap) -> Result> { .map(|(name, class)| { let Class { class_name, .. } = &class; let ident = Ident::new(name, Span::call_site()); - let meta = Ident::new(&format!("_{name}_META"), Span::call_site()); + let meta = Ident::new(&format!("_{}_META", ident), Span::call_site()); let methods = class.methods.iter().map(|method| { let builder = method.get_builder(&ident); let flags = method.get_flags(); diff --git a/guide/src/exceptions.md b/guide/src/exceptions.md index 4e5abc74c8..9a7b165937 100644 --- a/guide/src/exceptions.md +++ b/guide/src/exceptions.md @@ -30,19 +30,18 @@ the `#[php_function]` attribute. # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; use ext_php_rs::prelude::*; -use std::convert::TryInto; - -// Trivial example - PHP represents all integers as `u64` on 64-bit systems -// so the `u32` would be converted back to `u64`, but that's okay for an example. -#[php_function] -pub fn something_fallible(n: u64) -> PhpResult { - let n: u32 = n.try_into().map_err(|_| "Could not convert into u32")?; - Ok(n) -} #[php_module] -pub fn module(module: ModuleBuilder) -> ModuleBuilder { - module +mod module { + use std::convert::TryInto; + + // Trivial example - PHP represents all integers as `u64` on 64-bit systems + // so the `u32` would be converted back to `u64`, but that's okay for an example. + #[php_function] + pub fn something_fallible(n: u64) -> PhpResult { + let n: u32 = n.try_into().map_err(|_| "Could not convert into u32")?; + Ok(n) + } } # fn main() {} ``` diff --git a/guide/src/ini-settings.md b/guide/src/ini-settings.md index 22820c5e2e..8c67ce260f 100644 --- a/guide/src/ini-settings.md +++ b/guide/src/ini-settings.md @@ -11,19 +11,23 @@ All PHP INI definitions must be registered with PHP to get / set their values vi # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; -# use ext_php_rs::zend::IniEntryDef; -# use ext_php_rs::flags::IniEntryPermission; - -#[php_startup] -pub fn startup_function(ty: i32, module_number: i32) { - let ini_entries: Vec = vec![ - IniEntryDef::new( - "my_extension.display_emoji".to_owned(), - "yes".to_owned(), - IniEntryPermission::All, - ), - ]; - IniEntryDef::register(ini_entries, module_number); + +#[php_module] +mod module { + # use ext_php_rs::zend::IniEntryDef; + # use ext_php_rs::flags::IniEntryPermission; + + #[php_startup] + pub fn startup_function(ty: i32, module_number: i32) { + let ini_entries: Vec = vec![ + IniEntryDef::new( + "my_extension.display_emoji".to_owned(), + "yes".to_owned(), + IniEntryPermission::All, + ), + ]; + IniEntryDef::register(ini_entries, module_number); + } } # fn main() {} ``` @@ -36,13 +40,16 @@ The INI values are stored as part of the `GlobalExecutor`, and can be accessed v # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; -# use ext_php_rs::zend::ExecutorGlobals; -#[php_startup] -pub fn startup_function(ty: i32, module_number: i32) { - // Get all INI values - let ini_values = ExecutorGlobals::get().ini_values(); // HashMap> - let my_ini_value = ini_values.get("my_extension.display_emoji"); // Option> +#[php_module] +mod module { + # use ext_php_rs::zend::ExecutorGlobals; + #[php_startup] + pub fn startup_function(ty: i32, module_number: i32) { + // Get all INI values + let ini_values = ExecutorGlobals::get().ini_values(); // HashMap> + let my_ini_value = ini_values.get("my_extension.display_emoji"); // Option> + } } # fn main() {} ``` diff --git a/guide/src/macros/classes.md b/guide/src/macros/classes.md index 0935a02dc8..42575eacc9 100644 --- a/guide/src/macros/classes.md +++ b/guide/src/macros/classes.md @@ -2,7 +2,8 @@ Structs can be exported to PHP as classes with the `#[php_class]` attribute macro. This attribute derives the `RegisteredClass` trait on your struct, as -well as registering the class to be registered with the `#[php_module]` macro. +well as registering the class to be registered with the `#[php_module]` containing +the class. ## Options @@ -12,8 +13,7 @@ The attribute takes some options to modify the output of the class: name is kept the same. If no name is given, the name of the struct is used. Useful for namespacing classes. -There are also additional macros that modify the class. These macros **must** be -placed underneath the `#[php_class]` attribute. +There are also additional macros that modify the class. - `#[extends(ce)]` - Sets the parent class of the class. Can only be used once. `ce` must be a valid Rust expression when it is called inside the @@ -23,7 +23,7 @@ placed underneath the `#[php_class]` attribute. the `#[php_module]` function. You may also use the `#[prop]` attribute on a struct field to use the field as a -PHP property. By default, the field will be accessible from PHP publicly with +PHP property. By default, the field will then be accessible from PHP publicly with the same name as the field. Property types must implement `IntoZval` and `FromZval`. @@ -68,17 +68,16 @@ This example creates a PHP class `Human`, adding a PHP property `address`. # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; -#[php_class] -pub struct Human { - name: String, - age: i32, - #[prop] - address: String, +#[php_module] +mod module { + #[php_class] + pub struct Human { + name: String, + age: i32, + #[prop] + address: String, + } } -# #[php_module] -# pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { -# module -# } # fn main() {} ``` @@ -89,22 +88,22 @@ it in the `Redis\Exception` namespace: # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; use ext_php_rs::prelude::*; -use ext_php_rs::{exception::PhpException, zend::ce}; -#[php_class(name = "Redis\\Exception\\RedisException")] -#[extends(ce::exception())] -#[derive(Default)] -pub struct RedisException; +#[php_module] +pub mod module { + use ext_php_rs::{exception::PhpException, zend::ce}; + + #[php_class(name = "Redis\\Exception\\RedisException")] + #[extends(ce::exception())] + #[derive(Default)] + pub struct RedisException; -// Throw our newly created exception -#[php_function] -pub fn throw_exception() -> PhpResult { - Err(PhpException::from_class::("Not good!".into())) + // Throw our newly created exception + #[php_function] + pub fn throw_exception() -> PhpResult { + Err(PhpException::from_class::("Not good!".into())) + } } -# #[php_module] -# pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { -# module -# } # fn main() {} ``` @@ -116,47 +115,47 @@ The following example implements [`ArrayAccess`](https://www.php.net/manual/en/c # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; use ext_php_rs::prelude::*; -use ext_php_rs::{exception::PhpResult, types::Zval, zend::ce}; - -#[php_class] -#[implements(ce::arrayaccess())] -#[derive(Default)] -pub struct EvenNumbersArray; - -/// Returns `true` if the array offset is an even number. -/// Usage: -/// ```php -/// $arr = new EvenNumbersArray(); -/// var_dump($arr[0]); // true -/// var_dump($arr[1]); // false -/// var_dump($arr[2]); // true -/// var_dump($arr[3]); // false -/// var_dump($arr[4]); // true -/// var_dump($arr[5] = true); // Fatal error: Uncaught Exception: Setting values is not supported -/// ``` -#[php_impl] -impl EvenNumbersArray { - pub fn __construct() -> EvenNumbersArray { - EvenNumbersArray {} - } - // We need to use `Zval` because ArrayAccess needs $offset to be a `mixed` - pub fn offset_exists(&self, offset: &'_ Zval) -> bool { - offset.is_long() - } - pub fn offset_get(&self, offset: &'_ Zval) -> PhpResult { - let integer_offset = offset.long().ok_or("Expected integer offset")?; - Ok(integer_offset % 2 == 0) - } - pub fn offset_set(&mut self, _offset: &'_ Zval, _value: &'_ Zval) -> PhpResult { - Err("Setting values is not supported".into()) - } - pub fn offset_unset(&mut self, _offset: &'_ Zval) -> PhpResult { - Err("Setting values is not supported".into()) + +#[php_module] +mod module { + use ext_php_rs::{exception::PhpResult, types::Zval, zend::ce}; + + #[php_class] + #[implements(ce::arrayaccess())] + #[derive(Default)] + pub struct EvenNumbersArray; + + /// Returns `true` if the array offset is an even number. + /// Usage: + /// ```php + /// $arr = new EvenNumbersArray(); + /// var_dump($arr[0]); // true + /// var_dump($arr[1]); // false + /// var_dump($arr[2]); // true + /// var_dump($arr[3]); // false + /// var_dump($arr[4]); // true + /// var_dump($arr[5] = true); // Fatal error: Uncaught Exception: Setting values is not supported + /// ``` + #[php_impl] + impl EvenNumbersArray { + pub fn __construct() -> EvenNumbersArray { + EvenNumbersArray {} + } + // We need to use `Zval` because ArrayAccess needs $offset to be a `mixed` + pub fn offset_exists(&self, offset: &'_ Zval) -> bool { + offset.is_long() + } + pub fn offset_get(&self, offset: &'_ Zval) -> PhpResult { + let integer_offset = offset.long().ok_or("Expected integer offset")?; + Ok(integer_offset % 2 == 0) + } + pub fn offset_set(&mut self, _offset: &'_ Zval, _value: &'_ Zval) -> PhpResult { + Err("Setting values is not supported".into()) + } + pub fn offset_unset(&mut self, _offset: &'_ Zval) -> PhpResult { + Err("Setting values is not supported".into()) + } } } -# #[php_module] -# pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { -# module -# } # fn main() {} ``` diff --git a/guide/src/macros/constant.md b/guide/src/macros/constant.md index 72f83446ad..51b9e0e024 100644 --- a/guide/src/macros/constant.md +++ b/guide/src/macros/constant.md @@ -9,13 +9,14 @@ that implements `IntoConst`. # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; -#[php_const] -const TEST_CONSTANT: i32 = 100; +#[php_module] +mod module { + #[php_const] + const TEST_CONSTANT: i32 = 100; -#[php_const] -const ANOTHER_STRING_CONST: &'static str = "Hello world!"; -# #[php_module] -# pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { module } + #[php_const] + const ANOTHER_STRING_CONST: &'static str = "Hello world!"; +} # fn main() {} ``` diff --git a/guide/src/macros/extern.md b/guide/src/macros/extern.md new file mode 100644 index 0000000000..16d76fb816 --- /dev/null +++ b/guide/src/macros/extern.md @@ -0,0 +1,61 @@ +# `#[php_extern]` + +Attribute used to annotate `extern` blocks which are deemed as PHP +functions. + +This allows you to 'import' PHP functions into Rust so that they can be +called like regular Rust functions. Parameters can be any type that +implements [`IntoZval`], and the return type can be anything that implements +[`From`] (notice how [`Zval`] is consumed rather than borrowed in this +case). + +Unlike most other attributes, this does not need to be placed inside a +`#[php_module]` block. + +# Panics + +The function can panic when called under a few circumstances: + +* The function could not be found or was not callable. +* One of the parameters could not be converted into a [`Zval`]. +* The actual function call failed internally. +* The output [`Zval`] could not be parsed into the output type. + +The last point can be important when interacting with functions that return +unions, such as [`strpos`] which can return an integer or a boolean. In this +case, a [`Zval`] should be returned as parsing a boolean to an integer is +invalid, and vice versa. + +# Example + +This `extern` block imports the [`strpos`] function from PHP. Notice that +the string parameters can take either [`String`] or [`&str`], the optional +parameter `offset` is an [`Option`], and the return value is a [`Zval`] +as the return type is an integer-boolean union. + +```rust,no_run +# #![cfg_attr(windows, feature(abi_vectorcall))] +# extern crate ext_php_rs; +# use ext_php_rs::prelude::*; +# use ext_php_rs::types::Zval; +#[php_extern] +extern "C" { + fn strpos(haystack: &str, needle: &str, offset: Option) -> Zval; +} + +#[php_module] +mod module { + # use ext_php_rs::types::Zval; + use super::strpos; + + #[php_function] + pub fn my_strpos() { + assert_eq!(unsafe { strpos("Hello", "e", None) }.long(), Some(1)); + } +} +# fn main() {} +``` + +[`strpos`]: https://www.php.net/manual/en/function.strpos.php +[`IntoZval`]: crate::convert::IntoZval +[`Zval`]: crate::types::Zval diff --git a/guide/src/macros/function.md b/guide/src/macros/function.md index d961f5f27b..4687164807 100644 --- a/guide/src/macros/function.md +++ b/guide/src/macros/function.md @@ -17,15 +17,18 @@ default value. # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; -#[php_function] -pub fn greet(name: String, age: Option) -> String { - let mut greeting = format!("Hello, {}!", name); +#[php_module] +mod module { + #[php_function] + pub fn greet(name: String, age: Option) -> String { + let mut greeting = format!("Hello, {}!", name); - if let Some(age) = age { - greeting += &format!(" You are {} years old.", age); - } + if let Some(age) = age { + greeting += &format!(" You are {} years old.", age); + } - greeting + greeting + } } # fn main() {} ``` @@ -38,10 +41,13 @@ default, it does not need to be a variant of `Option`: # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; -#[php_function(defaults(offset = 0))] -pub fn rusty_strpos(haystack: &str, needle: &str, offset: i64) -> Option { - let haystack: String = haystack.chars().skip(offset as usize).collect(); - haystack.find(needle) +#[php_module] +mod module { + #[php_function(defaults(offset = 0))] + pub fn rusty_strpos(haystack: &str, needle: &str, offset: i64) -> Option { + let haystack: String = haystack.chars().skip(offset as usize).collect(); + haystack.find(needle) + } } # fn main() {} ``` @@ -54,17 +60,20 @@ argument rather than an optional argument. # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; -/// `age` will be deemed required and nullable rather than optional. -#[php_function] -pub fn greet(name: String, age: Option, description: String) -> String { - let mut greeting = format!("Hello, {}!", name); +#[php_module] +mod module { + /// `age` will be deemed required and nullable rather than optional. + #[php_function] + pub fn greet(name: String, age: Option, description: String) -> String { + let mut greeting = format!("Hello, {}!", name); - if let Some(age) = age { - greeting += &format!(" You are {} years old.", age); - } + if let Some(age) = age { + greeting += &format!(" You are {} years old.", age); + } - greeting += &format!(" {}.", description); - greeting + greeting += &format!(" {}.", description); + greeting + } } # fn main() {} ``` @@ -77,21 +86,24 @@ parameter: # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; -/// `age` will be deemed required and nullable rather than optional, -/// while description will be optional. -#[php_function(optional = "description")] -pub fn greet(name: String, age: Option, description: Option) -> String { - let mut greeting = format!("Hello, {}!", name); - - if let Some(age) = age { - greeting += &format!(" You are {} years old.", age); +#[php_module] +mod module { + /// `age` will be deemed required and nullable rather than optional, + /// while description will be optional. + #[php_function(optional = "description")] + pub fn greet(name: String, age: Option, description: Option) -> String { + let mut greeting = format!("Hello, {}!", name); + + if let Some(age) = age { + greeting += &format!(" You are {} years old.", age); + } + + if let Some(description) = description { + greeting += &format!(" {}.", description); + } + + greeting } - - if let Some(description) = description { - greeting += &format!(" {}.", description); - } - - greeting } # fn main() {} ``` @@ -106,12 +118,15 @@ the `...$args` syntax. # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; -# use ext_php_rs::types::Zval; -/// This can be called from PHP as `add(1, 2, 3, 4, 5)` -#[php_function] -pub fn add(number: u32, numbers:&[&Zval]) -> u32 { - // numbers is a slice of 4 Zvals all of type long - number +#[php_module] +mod module { + # use ext_php_rs::types::Zval; + /// This can be called from PHP as `add(1, 2, 3, 4, 5)` + #[php_function] + pub fn add(number: u32, numbers:&[&Zval]) -> u32 { + // numbers is a slice of 4 Zvals all of type long + number + } } # fn main() {} ``` diff --git a/guide/src/macros/impl.md b/guide/src/macros/impl.md index db12ecaf3d..deac737d80 100644 --- a/guide/src/macros/impl.md +++ b/guide/src/macros/impl.md @@ -100,55 +100,57 @@ constant for the maximum age of a `Human`. ```rust,no_run # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; -# use ext_php_rs::{prelude::*, types::ZendClassObject}; -# #[php_class] -# #[derive(Debug, Default)] -# pub struct Human { -# name: String, -# age: i32, -# #[prop] -# address: String, -# } -#[php_impl] -impl Human { - const MAX_AGE: i32 = 100; - - // No `#[constructor]` attribute required here - the name is `__construct`. - pub fn __construct(name: String, age: i32) -> Self { - Self { name, age, address: String::new() } +# use ext_php_rs::php_module; +#[php_module] +mod module { + use ext_php_rs::types::ZendClassObject; + + #[php_class] + #[derive(Debug, Default)] + pub struct Human { + name: String, + age: i32, + #[prop] + address: String, } - #[getter] - pub fn get_name(&self) -> String { - self.name.to_string() - } - - #[setter] - pub fn set_name(&mut self, name: String) { - self.name = name; - } - - #[getter] - pub fn get_age(&self) -> i32 { - self.age - } - - pub fn introduce(&self) { - println!("My name is {} and I am {} years old. I live at {}.", self.name, self.age, self.address); - } - - pub fn get_raw_obj(#[this] this: &mut ZendClassObject) { - dbg!(this); - } - - pub fn get_max_age() -> i32 { - Self::MAX_AGE + #[php_impl] + impl Human { + const MAX_AGE: i32 = 100; + + // No `#[constructor]` attribute required here - the name is `__construct`. + pub fn __construct(name: String, age: i32) -> Self { + Self { name, age, address: String::new() } + } + + #[getter] + pub fn get_name(&self) -> String { + self.name.to_string() + } + + #[setter] + pub fn set_name(&mut self, name: String) { + self.name = name; + } + + #[getter] + pub fn get_age(&self) -> i32 { + self.age + } + + pub fn introduce(&self) { + println!("My name is {} and I am {} years old. I live at {}.", self.name, self.age, self.address); + } + + pub fn get_raw_obj(#[this] this: &mut ZendClassObject) { + dbg!(this); + } + + pub fn get_max_age() -> i32 { + Self::MAX_AGE + } } } -# #[php_module] -# pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { -# module -# } # fn main() {} ``` diff --git a/guide/src/macros/index.md b/guide/src/macros/index.md index b97285ba4d..be77ed80db 100644 --- a/guide/src/macros/index.md +++ b/guide/src/macros/index.md @@ -12,21 +12,11 @@ used from PHP without fiddling around with zvals. - [`php_impl`] - Used to export a Rust `impl` block to PHP, including all methods and constants. - [`php_const`] - Used to export a Rust constant to PHP as a global constant. +- [`php_extern`] - Attribute used to annotate `extern` blocks which are deemed as + PHP functions. -These macros do abuse the fact that (at the moment) proc macro expansion _seems_ -to happen orderly, on one single thread. It has been stated many times that this -order is undefined behaviour ([see here]), so these macros _could_ break at any -time with a `rustc` update (let's just keep our fingers crossed). - -The macros abuse this fact by storing a global state, which stores information -about all the constants, functions, methods and classes you have registered -throughout your crate. It is then read out of the state in the function tagged -with the `#[php_module]` attribute. This is why this function **must** be the -last function in your crate. - -In the case the ordering does change (or we find out that it already was not in -order), the most likely solution will be having to register your PHP exports -manually inside the `#[php_module]` function. +All macros, except for `php_extern`, must be placed inside a module annotaded with +the `#[php_module]` macro. Currently only one `#[php_module]` module is allowed. [`php_module`]: ./module.md [`php_startup`]: ./module_startup.md @@ -34,4 +24,5 @@ manually inside the `#[php_module]` function. [`php_class`]: ./classes.md [`php_impl`]: ./impl.md [`php_const`]: ./constant.md +[`php_extern`]: ./extern.md [see here]: https://github.com/rust-lang/reference/issues/578 diff --git a/guide/src/macros/module.md b/guide/src/macros/module.md index 273fd43872..f0c4e66b78 100644 --- a/guide/src/macros/module.md +++ b/guide/src/macros/module.md @@ -1,8 +1,10 @@ # `#[php_module]` -The module macro is used to annotate the `get_module` function, which is used by -the PHP interpreter to retrieve information about your extension, including the -name, version, functions and extra initialization functions. Regardless if you +The module macro is used to annotate a module containing your extensions content. +Most macros provided by `ext-php-rs` need to be placed inside this module. +The PHP interpreter retrieves information about your extension, including the +name, version, functions and extra initialization functions using the `get_module` +function generated by the `#[php_module]` macro. Regardless if you use this macro, your extension requires a `extern "C" fn get_module()` so that PHP can get this information. @@ -11,25 +13,11 @@ automatically registered with the extension in this function. If you have defined any constants or classes with their corresponding macros, a 'module startup' function will also be generated if it has not already been defined. -Automatically registering these functions requires you to define the module -function **after** all other functions have been registered, as macros are -expanded in-order, therefore this macro will not know that other functions have -been used after. - -The function is renamed to `get_module` if you have used another name. The -function is passed an instance of `ModuleBuilder` which allows you to register -the following (if required): - -- Extension and request startup and shutdown functions. - - Read more about the PHP extension lifecycle - [here](https://www.phpinternalsbook.com/php7/extensions_design/php_lifecycle.html). -- PHP extension information function - - Used by the `phpinfo()` function to get information about your extension. -- Functions not automatically registered - Classes and constants are not registered in the `get_module` function. These are registered inside the extension startup function. +# TODO: implement info_function + ## Usage ```rust,ignore diff --git a/guide/src/macros/module_startup.md b/guide/src/macros/module_startup.md index cd950171d9..fa4e22d3a1 100644 --- a/guide/src/macros/module_startup.md +++ b/guide/src/macros/module_startup.md @@ -5,7 +5,7 @@ register extension classes and constants with the PHP interpreter. This function is automatically generated if you have registered classes or constants and have not already used this macro. If you do use this macro, it -will be automatically registered in the `get_module` function when you use the +will be automatically registered in the `get_module` function generated by the `#[php_module]` attribute. Most of the time you won't need to use this macro as the startup function will @@ -20,9 +20,12 @@ Read more about what the module startup function is used for # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; -#[php_startup] -pub fn startup_function() { +#[php_module] +mod module { + #[php_startup] + pub fn startup_function() { + } } # fn main() {} ``` diff --git a/guide/src/macros/zval_convert.md b/guide/src/macros/zval_convert.md index 876a4b1757..757c8d4a6f 100644 --- a/guide/src/macros/zval_convert.md +++ b/guide/src/macros/zval_convert.md @@ -25,20 +25,24 @@ pub struct ExampleClass<'a> { c: &'a str } -#[php_function] -pub fn take_object(obj: ExampleClass) { - dbg!(obj.a, obj.b, obj.c); -} +#[php_module] +mod module { + use super::ExampleClass; + + #[php_function] + pub fn take_object(obj: ExampleClass) { + dbg!(obj.a, obj.b, obj.c); + } -#[php_function] -pub fn give_object() -> ExampleClass<'static> { - ExampleClass { - a: 5, - b: "String".to_string(), - c: "Borrowed", + #[php_function] + pub fn give_object() -> ExampleClass<'static> { + ExampleClass { + a: 5, + b: "String".to_string(), + c: "Borrowed", + } } } -# #[php_module] pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { module } # fn main() {} ``` @@ -70,11 +74,15 @@ pub struct CompareVals> { b: T } -#[php_function] -pub fn take_object(obj: CompareVals) { - dbg!(obj); +#[php_module] +mod module { + use super::CompareVals; + + #[php_function] + pub fn take_object(obj: CompareVals) { + dbg!(obj); + } } -# #[php_module] pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { module } # fn main() {} ``` @@ -111,16 +119,20 @@ pub enum UnionExample<'a> { None // Zval did not contain anything that could be parsed above } -#[php_function] -pub fn test_union(val: UnionExample) { - dbg!(val); -} +#[php_module] +mod module { + use super::UnionExample; + + #[php_function] + pub fn test_union(val: UnionExample) { + dbg!(val); + } -#[php_function] -pub fn give_union() -> UnionExample<'static> { - UnionExample::Long(5) + #[php_function] + pub fn give_union() -> UnionExample<'static> { + UnionExample::Long(5) + } } -# #[php_module] pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { module } # fn main() {} ``` diff --git a/guide/src/types/binary.md b/guide/src/types/binary.md index 74f9f9de3b..d5db081c0d 100644 --- a/guide/src/types/binary.md +++ b/guide/src/types/binary.md @@ -25,17 +25,21 @@ f32, f64). # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; use ext_php_rs::prelude::*; -use ext_php_rs::binary::Binary; -#[php_function] -pub fn test_binary(input: Binary) -> Binary { - for i in input.iter() { - println!("{}", i); - } +#[php_module] +mod module { + use ext_php_rs::binary::Binary; + + #[php_function] + pub fn test_binary(input: Binary) -> Binary { + for i in input.iter() { + println!("{}", i); + } - vec![5, 4, 3, 2, 1] - .into_iter() - .collect::>() + vec![5, 4, 3, 2, 1] + .into_iter() + .collect::>() + } } # fn main() {} ``` diff --git a/guide/src/types/binary_slice.md b/guide/src/types/binary_slice.md index 1ea193f7da..01b7806454 100644 --- a/guide/src/types/binary_slice.md +++ b/guide/src/types/binary_slice.md @@ -25,16 +25,20 @@ isize, usize, f32, f64). # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; use ext_php_rs::prelude::*; -use ext_php_rs::binary_slice::BinarySlice; -#[php_function] -pub fn test_binary_slice(input: BinarySlice) -> u8 { - let mut sum = 0; - for i in input.iter() { - sum += i; - } +#[php_module] +mod module { + use ext_php_rs::binary_slice::BinarySlice; + + #[php_function] + pub fn test_binary_slice(input: BinarySlice) -> u8 { + let mut sum = 0; + for i in input.iter() { + sum += i; + } - sum + sum + } } # fn main() {} ``` diff --git a/guide/src/types/bool.md b/guide/src/types/bool.md index 498b40ec39..756311191b 100644 --- a/guide/src/types/bool.md +++ b/guide/src/types/bool.md @@ -26,12 +26,15 @@ enum Zval { # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; -#[php_function] -pub fn test_bool(input: bool) -> String { - if input { - "Yes!".into() - } else { - "No!".into() +#[php_module] +mod module { + #[php_function] + pub fn test_bool(input: bool) -> String { + if input { + "Yes!".into() + } else { + "No!".into() + } } } # fn main() {} @@ -52,10 +55,14 @@ var_dump(test_bool(false)); // string(3) "No!" # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; -# use ext_php_rs::types; -#[php_function] -pub fn test_bool(input: &mut types::Zval) { - input.reference_mut().unwrap().set_bool(false); +#[php_module] +mod module { + # use ext_php_rs::types; + + #[php_function] + pub fn test_bool(input: &mut types::Zval) { + input.reference_mut().unwrap().set_bool(false); + } } # fn main() {} ``` diff --git a/guide/src/types/class_object.md b/guide/src/types/class_object.md index ed6125cf4a..43aff982d7 100644 --- a/guide/src/types/class_object.md +++ b/guide/src/types/class_object.md @@ -15,27 +15,28 @@ object as a superset of an object, as a class object contains a Zend object. ```rust,no_run # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; -use ext_php_rs::{prelude::*, types::ZendClassObject}; +use ext_php_rs::php_module; -#[php_class] -pub struct Example { - foo: i32, - bar: i32 -} +#[php_module] +mod module { + use ext_php_rs::types::ZendClassObject; + + #[php_class] + pub struct Example { + foo: i32, + bar: i32 + } -#[php_impl] -impl Example { - // Even though this function doesn't have a `self` type, it is still treated as an associated method - // and not a static method. - pub fn builder_pattern(#[this] this: &mut ZendClassObject) -> &mut ZendClassObject { - // do something with `this` - this + #[php_impl] + impl Example { + // Even though this function doesn't have a `self` type, it is still treated as an associated method + // and not a static method. + pub fn builder_pattern(#[this] this: &mut ZendClassObject) -> &mut ZendClassObject { + // do something with `this` + this + } } } -# #[php_module] -# pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { -# module -# } # fn main() {} ``` @@ -46,21 +47,20 @@ impl Example { # extern crate ext_php_rs; use ext_php_rs::prelude::*; -#[php_class] -pub struct Example { - foo: i32, - bar: i32 -} +#[php_module] +mod module { + #[php_class] + pub struct Example { + foo: i32, + bar: i32 + } -#[php_impl] -impl Example { - pub fn make_new(foo: i32, bar: i32) -> Example { - Example { foo, bar } + #[php_impl] + impl Example { + pub fn make_new(foo: i32, bar: i32) -> Example { + Example { foo, bar } + } } } -# #[php_module] -# pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { -# module -# } # fn main() {} ``` diff --git a/guide/src/types/closure.md b/guide/src/types/closure.md index 19ddfd435c..1768c36324 100644 --- a/guide/src/types/closure.md +++ b/guide/src/types/closure.md @@ -47,24 +47,27 @@ fact that it can modify variables in its scope. # extern crate ext_php_rs; use ext_php_rs::prelude::*; -#[php_function] -pub fn closure_get_string() -> Closure { - // Return a closure which takes two integers and returns a string - Closure::wrap(Box::new(|a, b| { - format!("A: {} B: {}", a, b) - }) as Box String>) -} - -#[php_function] -pub fn closure_count() -> Closure { - let mut count = 0i32; - - // Return a closure which takes an integer, adds it to a persistent integer, - // and returns the updated value. - Closure::wrap(Box::new(move |a: i32| { - count += a; - count - }) as Box i32>) +#[php_module] +mod module { + #[php_function] + pub fn closure_get_string() -> Closure { + // Return a closure which takes two integers and returns a string + Closure::wrap(Box::new(|a, b| { + format!("A: {} B: {}", a, b) + }) as Box String>) + } + + #[php_function] + pub fn closure_count() -> Closure { + let mut count = 0i32; + + // Return a closure which takes an integer, adds it to a persistent integer, + // and returns the updated value. + Closure::wrap(Box::new(move |a: i32| { + count += a; + count + }) as Box i32>) + } } # fn main() {} ``` @@ -88,14 +91,17 @@ will be thrown. # extern crate ext_php_rs; use ext_php_rs::prelude::*; -#[php_function] -pub fn closure_return_string() -> Closure { - let example: String = "Hello, world!".into(); - - // This closure consumes `example` and therefore cannot be called more than once. - Closure::wrap_once(Box::new(move || { - example - }) as Box String>) +#[php_module] +mod module { + #[php_function] + pub fn closure_return_string() -> Closure { + let example: String = "Hello, world!".into(); + + // This closure consumes `example` and therefore cannot be called more than once. + Closure::wrap_once(Box::new(move || { + example + }) as Box String>) + } } # fn main() {} ``` @@ -116,10 +122,13 @@ function by its name, or as a parameter. They can be called through the # extern crate ext_php_rs; use ext_php_rs::prelude::*; -#[php_function] -pub fn callable_parameter(call: ZendCallable) { - let val = call.try_call(vec![&0, &1, &"Hello"]).expect("Failed to call function"); - dbg!(val); +#[php_module] +mod module { + #[php_function] + pub fn callable_parameter(call: ZendCallable) { + let val = call.try_call(vec![&0, &1, &"Hello"]).expect("Failed to call function"); + dbg!(val); + } } # fn main() {} ``` diff --git a/guide/src/types/functions.md b/guide/src/types/functions.md index 475a27b794..e270072749 100644 --- a/guide/src/types/functions.md +++ b/guide/src/types/functions.md @@ -1,28 +1,30 @@ # Functions & methods -PHP functions and methods are represented by the `Function` struct. +PHP functions and methods are represented by the `Function` struct. -You can use the `try_from_function` and `try_from_method` methods to obtain a Function struct corresponding to the passed function or static method name. -It's heavily recommended you reuse returned `Function` objects, to avoid the overhead of looking up the function/method name. +You can use the `try_from_function` and `try_from_method` methods to obtain a Function struct corresponding to the passed function or static method name. +It's heavily recommended you reuse returned `Function` objects, to avoid the overhead of looking up the function/method name. ```rust,no_run # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; use ext_php_rs::prelude::*; -use ext_php_rs::zend::Function; +#[php_module] +mod module { + use ext_php_rs::zend::Function; -#[php_function] -pub fn test_function() -> () { - let var_dump = Function::try_from_function("var_dump").unwrap(); - let _ = var_dump.try_call(vec![&"abc"]); -} + #[php_function] + pub fn test_function() -> () { + let var_dump = Function::try_from_function("var_dump").unwrap(); + let _ = var_dump.try_call(vec![&"abc"]); + } -#[php_function] -pub fn test_method() -> () { - let f = Function::try_from_method("ClassName", "staticMethod").unwrap(); - let _ = f.try_call(vec![&"abc"]); + #[php_function] + pub fn test_method() -> () { + let f = Function::try_from_method("ClassName", "staticMethod").unwrap(); + let _ = f.try_call(vec![&"abc"]); + } } - # fn main() {} ``` diff --git a/guide/src/types/hashmap.md b/guide/src/types/hashmap.md index d0003e2b2f..3cb6a4375e 100644 --- a/guide/src/types/hashmap.md +++ b/guide/src/types/hashmap.md @@ -20,16 +20,19 @@ Converting from a `HashMap` to a zval is valid when the key implements # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; -# use std::collections::HashMap; -#[php_function] -pub fn test_hashmap(hm: HashMap) -> Vec { - for (k, v) in hm.iter() { - println!("k: {} v: {}", k, v); +#[php_module] +mod module { + # use std::collections::HashMap; + #[php_function] + pub fn test_hashmap(hm: HashMap) -> Vec { + for (k, v) in hm.iter() { + println!("k: {} v: {}", k, v); + } + + hm.into_iter() + .map(|(_, v)| v) + .collect::>() } - - hm.into_iter() - .map(|(_, v)| v) - .collect::>() } # fn main() {} ``` diff --git a/guide/src/types/iterable.md b/guide/src/types/iterable.md index fec8072e3f..0aeae66897 100644 --- a/guide/src/types/iterable.md +++ b/guide/src/types/iterable.md @@ -6,7 +6,7 @@ |---------------|----------------|-----------------| ---------------- |----------------------------------| | Yes | No | No | No | `ZendHashTable` or `ZendIterator` | -Converting from a zval to a `Iterable` is valid when the value is either an array or an object +Converting from a zval to a `Iterable` is valid when the value is either an array or an object that implements the `Traversable` interface. This means that any value that can be used in a `foreach` loop can be converted into a `Iterable`. @@ -16,11 +16,14 @@ that implements the `Traversable` interface. This means that any value that can # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; -# use ext_php_rs::types::Iterable; -#[php_function] -pub fn test_iterable(mut iterable: Iterable) { - for (k, v) in iterable.iter().expect("cannot rewind iterator") { - println!("k: {} v: {}", k.string().unwrap(), v.string().unwrap()); +#[php_module] +mod module { + # use ext_php_rs::types::Iterable; + #[php_function] + pub fn test_iterable(mut iterable: Iterable) { + for (k, v) in iterable.iter().expect("cannot rewind iterator") { + println!("k: {} v: {}", k.string().unwrap(), v.string().unwrap()); + } } } # fn main() {} diff --git a/guide/src/types/iterator.md b/guide/src/types/iterator.md index 4c53e6727d..08b098230c 100644 --- a/guide/src/types/iterator.md +++ b/guide/src/types/iterator.md @@ -6,12 +6,12 @@ |---------------| -------------- |-----------------| ---------------- | ------------------ | | No | Yes | No | No | `ZendIterator` | -Converting from a zval to a `ZendIterator` is valid when there is an associated iterator to -the variable. This means that any value, at the exception of an `array`, that can be used in +Converting from a zval to a `ZendIterator` is valid when there is an associated iterator to +the variable. This means that any value, at the exception of an `array`, that can be used in a `foreach` loop can be converted into a `ZendIterator`. As an example, a `Generator` can be used but also a the result of a `query` call with `PDO`. -If you want a more universal `iterable` type that also supports arrays, see [Iterable](./iterable.md). +If you want a more universal `iterable` type that also supports arrays, see [Iterable](./iterable.md). ## Rust example @@ -19,13 +19,16 @@ If you want a more universal `iterable` type that also supports arrays, see [Ite # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; -# use ext_php_rs::types::ZendIterator; -#[php_function] -pub fn test_iterator(iterator: &mut ZendIterator) { - for (k, v) in iterator.iter().expect("cannot rewind iterator") { - // Note that the key can be anything, even an object - // when iterating over Traversables! - println!("k: {} v: {}", k.string().unwrap(), v.string().unwrap()); +#[php_module] +mod module { + # use ext_php_rs::types::ZendIterator; + #[php_function] + pub fn test_iterator(iterator: &mut ZendIterator) { + for (k, v) in iterator.iter().expect("cannot rewind iterator") { + // Note that the key can be anything, even an object + // when iterating over Traversables! + println!("k: {} v: {}", k.string().unwrap(), v.string().unwrap()); + } } } # fn main() {} diff --git a/guide/src/types/numbers.md b/guide/src/types/numbers.md index 68755a1dbf..73260c2530 100644 --- a/guide/src/types/numbers.md +++ b/guide/src/types/numbers.md @@ -25,10 +25,13 @@ fallible. # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; -#[php_function] -pub fn test_numbers(a: i32, b: u32, c: f32) -> u8 { - println!("a {} b {} c {}", a, b, c); - 0 +#[php_module] +mod module { + #[php_function] + pub fn test_numbers(a: i32, b: u32, c: f32) -> u8 { + println!("a {} b {} c {}", a, b, c); + 0 + } } # fn main() {} ``` diff --git a/guide/src/types/object.md b/guide/src/types/object.md index 57f9371d28..aab8e85607 100644 --- a/guide/src/types/object.md +++ b/guide/src/types/object.md @@ -20,17 +20,18 @@ object. ```rust,no_run # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; -use ext_php_rs::{prelude::*, types::ZendObject}; +use ext_php_rs::php_module; -// Take an object reference and also return it. -#[php_function] -pub fn take_obj(obj: &mut ZendObject) -> () { - let _ = obj.try_call_method("hello", vec![&"arg1", &"arg2"]); +#[php_module] +mod module { + use ext_php_rs::types::ZendObject; + + // Take an object reference and also return it. + #[php_function] + pub fn take_obj(obj: &mut ZendObject) -> () { + let _ = obj.try_call_method("hello", vec![&"arg1", &"arg2"]); + } } -# #[php_module] -# pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { -# module -# } # fn main() {} ``` @@ -39,18 +40,19 @@ pub fn take_obj(obj: &mut ZendObject) -> () { ```rust,no_run # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; -use ext_php_rs::{prelude::*, types::ZendObject}; +use ext_php_rs::php_module; + +#[php_module] +mod module { + use ext_php_rs::types::ZendObject; -// Take an object reference and also return it. -#[php_function] -pub fn take_obj(obj: &mut ZendObject) -> &mut ZendObject { - let _ = obj.set_property("hello", 5); - dbg!(obj) + // Take an object reference and also return it. + #[php_function] + pub fn take_obj(obj: &mut ZendObject) -> &mut ZendObject { + let _ = obj.set_property("hello", 5); + dbg!(obj) + } } -# #[php_module] -# pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { -# module -# } # fn main() {} ``` @@ -59,19 +61,20 @@ pub fn take_obj(obj: &mut ZendObject) -> &mut ZendObject { ```rust,no_run # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; -use ext_php_rs::{prelude::*, types::ZendObject, boxed::ZBox}; - -// Create a new `stdClass` and return it. -#[php_function] -pub fn make_object() -> ZBox { - let mut obj = ZendObject::new_stdclass(); - let _ = obj.set_property("hello", 5); - obj +use ext_php_rs::php_module; + +#[php_module] +mod module { + use ext_php_rs::{types::ZendObject, boxed::ZBox}; + + // Create a new `stdClass` and return it. + #[php_function] + pub fn make_object() -> ZBox { + let mut obj = ZendObject::new_stdclass(); + let _ = obj.set_property("hello", 5); + obj + } } -# #[php_module] -# pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { -# module -# } # fn main() {} ``` diff --git a/guide/src/types/option.md b/guide/src/types/option.md index 8acb48765f..4311a0a6b6 100644 --- a/guide/src/types/option.md +++ b/guide/src/types/option.md @@ -22,9 +22,12 @@ null to PHP. # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; -#[php_function] -pub fn test_option_null(input: Option) -> Option { - input.map(|input| format!("Hello {}", input).into()) +#[php_module] +mod module { + #[php_function] + pub fn test_option_null(input: Option) -> Option { + input.map(|input| format!("Hello {}", input).into()) + } } # fn main() {} ``` diff --git a/guide/src/types/str.md b/guide/src/types/str.md index 26688553c5..c41f37de86 100644 --- a/guide/src/types/str.md +++ b/guide/src/types/str.md @@ -21,14 +21,17 @@ PHP strings. # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; -#[php_function] -pub fn str_example(input: &str) -> String { - format!("Hello {}", input) -} - -#[php_function] -pub fn str_return_example() -> &'static str { - "Hello from Rust" +#[php_module] +mod module { + #[php_function] + pub fn str_example(input: &str) -> String { + format!("Hello {}", input) + } + + #[php_function] + pub fn str_return_example() -> &'static str { + "Hello from Rust" + } } # fn main() {} ``` diff --git a/guide/src/types/string.md b/guide/src/types/string.md index 317bcf994f..5c908bf51d 100644 --- a/guide/src/types/string.md +++ b/guide/src/types/string.md @@ -20,9 +20,12 @@ be thrown if one is encountered while converting a `String` to a zval. # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; -#[php_function] -pub fn str_example(input: String) -> String { - format!("Hello {}", input) +#[php_module] +mod module { + #[php_function] + pub fn str_example(input: String) -> String { + format!("Hello {}", input) + } } # fn main() {} ``` diff --git a/guide/src/types/vec.md b/guide/src/types/vec.md index 62dcf3e9e9..bb87b88f00 100644 --- a/guide/src/types/vec.md +++ b/guide/src/types/vec.md @@ -22,9 +22,12 @@ fail. # #![cfg_attr(windows, feature(abi_vectorcall))] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; -#[php_function] -pub fn test_vec(vec: Vec) -> String { - vec.join(" ") +#[php_module] +mod module { + #[php_function] + pub fn test_vec(vec: Vec) -> String { + vec.join(" ") + } } # fn main() {} ``` diff --git a/src/exception.rs b/src/exception.rs index aee0d9099f..9e22453e69 100644 --- a/src/exception.rs +++ b/src/exception.rs @@ -183,27 +183,30 @@ pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) -> Result<()> /// /// ```no_run /// use ext_php_rs::prelude::*; -/// use ext_php_rs::exception::throw_object; /// use crate::ext_php_rs::convert::IntoZval; /// -/// #[php_class] -/// #[extends(ext_php_rs::zend::ce::exception())] -/// pub struct JsException { -/// #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)] -/// message: String, -/// #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)] -/// code: i32, -/// #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)] -/// file: String, -/// } -/// /// #[php_module] -/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { -/// module +/// mod module { +/// use crate::ext_php_rs::convert::IntoZval; +/// use ext_php_rs::exception::throw_object; +/// +/// #[php_class] +/// #[extends(ext_php_rs::zend::ce::exception())] +/// pub struct JsException { +/// #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)] +/// message: String, +/// #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)] +/// code: i32, +/// #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)] +/// file: String, +/// } +/// +/// fn trow() { +/// let error = JsException { message: "A JS error occurred.".to_string(), code: 100, file: "index.js".to_string() }; +/// throw_object( error.into_zval(true).unwrap() ); +/// } /// } /// -/// let error = JsException { message: "A JS error occurred.".to_string(), code: 100, file: "index.js".to_string() }; -/// throw_object( error.into_zval(true).unwrap() ); /// ``` pub fn throw_object(zval: Zval) -> Result<()> { let mut zv = core::mem::ManuallyDrop::new(zval); diff --git a/src/lib.rs b/src/lib.rs index 1f42a1d878..2c4cee35ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,650 +65,12 @@ pub const PHP_DEBUG: bool = cfg!(php_debug); /// Whether the extension is compiled for PHP thread-safe mode. pub const PHP_ZTS: bool = cfg!(php_zts); -/// Attribute used to annotate constants to be exported to PHP. -/// -/// The declared constant is left intact (apart from the addition of the -/// `#[allow(dead_code)]` attribute in the case that you do not use the Rust -/// constant). -/// -/// These declarations must happen before you declare your [`macro@php_startup`] -/// function (or [`macro@php_module`] function if you do not have a startup -/// function). -/// -/// # Example -/// -/// ``` -/// # #![cfg_attr(windows, feature(abi_vectorcall))] -/// # use ext_php_rs::prelude::*; -/// #[php_const] -/// const TEST_CONSTANT: i32 = 100; -/// -/// #[php_const] -/// const ANOTHER_CONST: &str = "Hello, world!"; -/// # #[php_module] -/// # pub fn module(module: ModuleBuilder) -> ModuleBuilder { -/// # module -/// # } -/// ``` +pub use ext_php_rs_derive::php_class; pub use ext_php_rs_derive::php_const; - -/// Attribute used to annotate `extern` blocks which are deemed as PHP -/// functions. -/// -/// This allows you to 'import' PHP functions into Rust so that they can be -/// called like regular Rust functions. Parameters can be any type that -/// implements [`IntoZval`], and the return type can be anything that implements -/// [`From`] (notice how [`Zval`] is consumed rather than borrowed in this -/// case). -/// -/// # Panics -/// -/// The function can panic when called under a few circumstances: -/// -/// * The function could not be found or was not callable. -/// * One of the parameters could not be converted into a [`Zval`]. -/// * The actual function call failed internally. -/// * The output [`Zval`] could not be parsed into the output type. -/// -/// The last point can be important when interacting with functions that return -/// unions, such as [`strpos`] which can return an integer or a boolean. In this -/// case, a [`Zval`] should be returned as parsing a boolean to an integer is -/// invalid, and vice versa. -/// -/// # Example -/// -/// This `extern` block imports the [`strpos`] function from PHP. Notice that -/// the string parameters can take either [`String`] or [`&str`], the optional -/// parameter `offset` is an [`Option`], and the return value is a [`Zval`] -/// as the return type is an integer-boolean union. -/// -/// ``` -/// # #![cfg_attr(windows, feature(abi_vectorcall))] -/// # use ext_php_rs::prelude::*; -/// # use ext_php_rs::types::Zval; -/// #[php_extern] -/// extern "C" { -/// fn strpos(haystack: &str, needle: &str, offset: Option) -> Zval; -/// } -/// -/// #[php_function] -/// pub fn my_strpos() { -/// assert_eq!(unsafe { strpos("Hello", "e", None) }.long(), Some(1)); -/// } -/// # #[php_module] -/// # pub fn module(module: ModuleBuilder) -> ModuleBuilder { -/// # module -/// # } -/// ``` -/// -/// [`strpos`]: https://www.php.net/manual/en/function.strpos.php -/// [`IntoZval`]: crate::convert::IntoZval -/// [`Zval`]: crate::types::Zval pub use ext_php_rs_derive::php_extern; - -/// Attribute used to annotate a function as a PHP function. -/// -/// Only types that implement [`FromZval`] can be used as parameter and return -/// types. These include but are not limited to the following: -/// -/// - Most primitive integers ([`i8`], [`i16`], [`i32`], [`i64`], [`u8`], -/// [`u16`], [`u32`], [`u64`], -/// [`usize`], [`isize`]) -/// - Double-precision floating point numbers ([`f64`]) -/// - [`bool`] -/// - [`String`] -/// - [`Vec`] and [`HashMap`](std::collections::HashMap) where `T: -/// FromZval`. -/// - [`Binary`] for passing binary data as a string, where `T: Pack`. -/// - [`ZendCallable`] for receiving PHP callables, not applicable for return -/// values. -/// - [`Option`] where `T: FromZval`. When used as a parameter, the parameter -/// will be -/// deemed nullable, and will contain [`None`] when `null` is passed. When used -/// as a return type, if [`None`] is returned the [`Zval`] will be set to null. -/// Optional parameters *must* be of the type [`Option`]. -/// -/// Additionally, you are able to return a variant of [`Result`]. `T` must -/// implement [`IntoZval`] and `E` must implement `Into`. If an -/// error variant is returned, a PHP exception is thrown using the -/// [`PhpException`] struct contents. -/// -/// You are able to implement [`FromZval`] on your own custom types to have -/// arguments passed in seamlessly. Similarly, you can implement [`IntoZval`] on -/// values that you want to be able to be returned from PHP functions. -/// -/// Parameters may be deemed optional by passing the parameter name into the -/// attribute options. Note that all parameters that are optional (which -/// includes the given optional parameter as well as all parameters after) -/// *must* be of the type [`Option`], where `T` is a valid type. -/// -/// Generics are *not* supported. -/// -/// Behind the scenes, an `extern "C"` wrapper function is generated, which is -/// actually called by PHP. The first example function would be converted into a -/// function which looks like so: -/// -/// ```no_run -/// # #![cfg_attr(windows, feature(abi_vectorcall))] -/// # use ext_php_rs::{prelude::*, exception::PhpException, zend::ExecuteData, convert::{FromZvalMut, IntoZval}, types::Zval, args::{Arg, ArgParser}}; -/// pub fn hello(name: String) -> String { -/// format!("Hello, {}!", name) -/// } -/// -/// pub extern "C" fn _internal_php_hello(ex: &mut ExecuteData, retval: &mut Zval) { -/// let mut name = Arg::new("name", ::TYPE); -/// let parser = ex.parser() -/// .arg(&mut name) -/// .parse(); -/// -/// if parser.is_err() { -/// return; -/// } -/// -/// let result = hello(match name.val() { -/// Some(val) => val, -/// None => { -/// PhpException::default("Invalid value given for argument `name`.".into()) -/// .throw() -/// .expect("Failed to throw exception: Invalid value given for argument `name`."); -/// return; -/// } -/// }); -/// -/// match result.set_zval(retval, false) { -/// Ok(_) => {}, -/// Err(e) => { -/// let e: PhpException = e.into(); -/// e.throw().expect("Failed to throw exception: Failed to set return value."); -/// } -/// }; -/// } -/// ``` -/// -/// This allows the original function to continue being used while also being -/// exported as a PHP function. -/// -/// # Examples -/// -/// Creating a simple function which will return a string. The function still -/// must be declared in the PHP module to be able to call. -/// -/// ``` -/// # #![cfg_attr(windows, feature(abi_vectorcall))] -/// # use ext_php_rs::prelude::*; -/// #[php_function] -/// pub fn hello(name: String) -> String { -/// format!("Hello, {}!", name) -/// } -/// # #[php_module] -/// # pub fn module(module: ModuleBuilder) -> ModuleBuilder { -/// # module -/// # } -/// ``` -/// -/// Parameters can also be deemed optional by passing the parameter name in the -/// attribute options. This function takes one required parameter (`name`) and -/// two optional parameters (`description` and `age`). -/// -/// ``` -/// # #![cfg_attr(windows, feature(abi_vectorcall))] -/// # use ext_php_rs::prelude::*; -/// #[php_function(optional = "description")] -/// pub fn hello(name: String, description: Option, age: Option) -> String { -/// let mut response = format!("Hello, {}!", name); -/// -/// if let Some(description) = description { -/// response.push_str(format!(" {}.", description).as_ref()); -/// } -/// -/// if let Some(age) = age { -/// response.push_str(format!(" I am {} year(s) old.", age).as_ref()); -/// } -/// -/// response -/// } -/// # #[php_module] -/// # pub fn module(module: ModuleBuilder) -> ModuleBuilder { -/// # module -/// # } -/// ``` -/// -/// Defaults can also be given in a similar fashion. For example, the above -/// function could have default values for `description` and `age` by changing -/// the attribute to the following: -/// -/// ``` -/// # #![cfg_attr(windows, feature(abi_vectorcall))] -/// # use ext_php_rs::prelude::*; -/// #[php_function(optional = "description", defaults(description = "David", age = 10))] -/// pub fn hello(name: String, description: String, age: i32) -> String { -/// format!("Hello, {}! {}. I am {} year(s) old.", name, description, age) -/// } -/// # #[php_module] -/// # pub fn module(module: ModuleBuilder) -> ModuleBuilder { -/// # module -/// # } -/// ``` -/// -/// [`Result`]: std::result::Result -/// [`FunctionBuilder`]: crate::php::function::FunctionBuilder -/// [`FromZval`]: crate::convert::FromZval -/// [`IntoZval`]: crate::convert::IntoZval -/// [`Zval`]: crate::types::Zval. -/// [`Binary`]: crate::binary::Binary -/// [`ZendCallable`]: crate::types::ZendCallable -/// [`PhpException`]: crate::exception::PhpException pub use ext_php_rs_derive::php_function; - -/// Annotates a structs `impl` block, declaring that all methods and constants -/// declared inside the `impl` block will be declared as PHP methods and -/// constants. -/// -/// If you do not want to export a method to PHP, declare it in another `impl` -/// block that is not tagged with this macro. -/// -/// The declared methods and functions are kept intact so they can continue to -/// be called from Rust. Methods do generate an additional function, with an -/// identifier in the format `_internal_php_#ident`. -/// -/// Methods and constants are declared mostly the same as their global -/// counterparts, so read the documentation on the [`macro@php_function`] and -/// [`macro@php_const`] macros for more details. -/// -/// The main difference is that the contents of the `impl` block *do not* need -/// to be tagged with additional attributes - this macro assumes that all -/// contents of the `impl` block are to be exported to PHP. -/// -/// The only contrary to this is setting the visibility, optional argument and -/// default arguments for methods. These are done through separate macros: -/// -/// - `#[defaults(key = value, ...)]` for setting defaults of method variables, -/// similar to the -/// function macro. Arguments with defaults need to be optional. -/// - `#[optional(key)]` for setting `key` as an optional argument (and -/// therefore the rest of the -/// arguments). -/// - `#[public]`, `#[protected]` and `#[private]` for setting the visibility of -/// the method, -/// defaulting to public. The Rust visibility has no effect on the PHP -/// visibility. -/// -/// Methods can take a immutable or a mutable reference to `self`, but cannot -/// consume `self`. They can also take no reference to `self` which indicates a -/// static method. -/// -/// ## Constructors -/// -/// You may add *one* constructor to the impl block. This method must be called -/// `__construct` or be tagged with the `#[constructor]` attribute, and it will -/// not be exported to PHP like a regular method. -/// -/// The constructor method must not take a reference to `self` and must return -/// `Self` or [`Result`][`Result`], where `E: Into`. -/// -/// # Example -/// -/// ```no_run -/// # #![cfg_attr(windows, feature(abi_vectorcall))] -/// # use ext_php_rs::prelude::*; -/// #[php_class] -/// #[derive(Debug)] -/// pub struct Human { -/// name: String, -/// age: i32, -/// } -/// -/// #[php_impl] -/// impl Human { -/// // Class constant - `Human::AGE_LIMIT` -/// const AGE_LIMIT: i32 = 100; -/// -/// #[optional(age)] -/// #[defaults(age = 0)] -/// pub fn __construct(name: String, age: i32) -> Self { -/// Self { name, age } -/// } -/// -/// pub fn get_name(&self) -> String { -/// self.name.clone() -/// } -/// -/// pub fn get_age(&self) -> i32 { -/// self.age -/// } -/// -/// // Static method - `Human::get_age_limit()` -/// pub fn get_age_limit() -> i32 { -/// Self::AGE_LIMIT -/// } -/// } -/// -/// #[php_module] -/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { -/// module -/// } -/// ``` pub use ext_php_rs_derive::php_impl; - -/// Annotates a function that will be used by PHP to retrieve information about -/// the module. -/// -/// In the process, the function is wrapped by an `extern "C"` function which is -/// called from PHP, which then calls the given function. -/// -/// As well as wrapping the function, the `ModuleBuilder` is initialized and -/// functions which have already been declared with the [`macro@php_function`] -/// attribute will be registered with the module, so ideally you won't have to -/// do anything inside the function. -/// -/// The attribute must be called on a function *last*, i.e. the last proc-macro -/// to be compiled, as the attribute relies on all other PHP attributes being -/// compiled before the module. If another PHP attribute is compiled after the -/// module attribute, an error will be thrown. -/// -/// Note that if the function is not called `get_module`, it will be renamed. -/// -/// If you have defined classes using the [`macro@php_class`] macro and you have -/// not defined a startup function, it will be automatically declared and -/// registered. -/// -/// # Example -/// -/// The `get_module` function is required in every PHP extension. This is a bare -/// minimum example, since the function is declared above the module it will -/// automatically be registered when the module attribute is called. -/// -/// ``` -/// # #![cfg_attr(windows, feature(abi_vectorcall))] -/// # use ext_php_rs::prelude::*; -/// #[php_function] -/// pub fn hello(name: String) -> String { -/// format!("Hello, {}!", name) -/// } -/// -/// #[php_module] -/// pub fn module(module: ModuleBuilder) -> ModuleBuilder { -/// module -/// } -/// ``` pub use ext_php_rs_derive::php_module; - -/// Annotates a struct that will be exported to PHP as a class. -/// -/// By default, the class cannot be constructed from PHP. You must add a -/// constructor method in the [`macro@php_impl`] impl block to be able to -/// construct the object from PHP. -/// -/// This attribute takes a set of optional arguments: -/// -/// * `name` - The name of the exported class, if it is different from the Rust -/// struct name. This can be useful for namespaced classes, as you cannot -/// place backslashes in Rust struct names. -/// -/// Any struct that uses this attribute can also provide an optional set of -/// extra attributes, used to modify the class. These attributes must be used -/// **underneath** this attribute, as they are not valid Rust attributes, and -/// instead are parsed by this attribute: -/// -/// * `#[extends(ce)]` - Sets the parent class of this new class. Can only be -/// used once, and `ce` may be any valid expression. -/// * `#[implements(ce)]` - Implements an interface on the new class. Can be -/// used multiple times, and `ce` may be any valid expression. -/// -/// This attribute (and its associated structs) must be defined *above* the -/// startup function (which is annotated by the [`macro@php_startup`] macro, or -/// automatically generated just above the [`macro@php_module`] function). -/// -/// Fields defined on the struct *are not* the same as PHP properties, and are -/// only accessible from Rust. -/// -/// # Example -/// -/// Export a simple class called `Example`, with 3 Rust fields. -/// -/// ``` -/// # #![cfg_attr(windows, feature(abi_vectorcall))] -/// # use ext_php_rs::prelude::*; -/// #[php_class] -/// pub struct Example { -/// x: i32, -/// y: String, -/// z: bool -/// } -/// -/// #[php_module] -/// pub fn module(module: ModuleBuilder) -> ModuleBuilder { -/// module -/// } -/// ``` -/// -/// Create a custom exception `RedisException` inside the namespace -/// `Redis\Exception`: -/// -/// ``` -/// # #![cfg_attr(windows, feature(abi_vectorcall))] -/// # use ext_php_rs::prelude::*; -/// use ext_php_rs::exception::PhpException; -/// use ext_php_rs::zend::ce; -/// -/// #[php_class(name = "Redis\\Exception\\RedisException")] -/// #[extends(ce::exception())] -/// pub struct Example; -/// -/// #[php_function] -/// pub fn throw_exception() -> Result { -/// Err(PhpException::from_class::("Bad things happen".into())) -/// } -/// -/// #[php_module] -/// pub fn module(module: ModuleBuilder) -> ModuleBuilder { -/// module -/// } -/// ``` -pub use ext_php_rs_derive::php_class; - -/// Annotates a function that will be called by PHP when the module starts up. -/// Generally used to register classes and constants. -/// -/// As well as annotating the function, any classes and constants that had been -/// declared using the [`macro@php_class`], [`macro@php_const`] and -/// [`macro@php_impl`] attributes will be registered inside this function. -/// -/// This function *must* be declared before the [`macro@php_module`] function, -/// as this function needs to be declared when building the module. -/// -/// This function will automatically be generated if not already declared with -/// this macro if you have registered any classes or constants when using the -/// [`macro@php_module`] macro. -/// -/// The attribute accepts one optional flag -- `#[php_startup(before)]` -- -/// which forces the annotated function to be called _before_ the other classes -/// and constants are registered. By default the annotated function is called -/// after these classes and constants are registered. -/// -/// # Example -/// -/// ``` -/// # #![cfg_attr(windows, feature(abi_vectorcall))] -/// # use ext_php_rs::prelude::*; -/// #[php_startup] -/// pub fn startup_function() { -/// // do whatever you need to do... -/// } -/// # #[php_module] -/// # pub fn module(module: ModuleBuilder) -> ModuleBuilder { -/// # module -/// # } -/// ``` pub use ext_php_rs_derive::php_startup; - -/// Derives the traits required to convert a struct or enum to and from a -/// [`Zval`]. Both [`FromZval`] and [`IntoZval`] are implemented on types which -/// use this macro. -/// -/// # Structs -/// -/// When the macro is used on a struct, the [`FromZendObject`] and -/// [`IntoZendObject`] traits are also implemented, and will attempt to retrieve -/// values for the struct fields from the objects properties. This can be useful -/// when you expect some arbitrary object (of which the type does not matter), -/// but you care about the value of the properties. -/// -/// All properties must implement [`FromZval`] and [`IntoZval`] themselves. -/// Generics are supported, however, a [`FromZval`] and [`IntoZval`] bound will -/// be added. If one property cannot be retrieved from the object, the whole -/// conversion will fail. -/// -/// ## Examples -/// -/// Basic example with some primitive PHP type. -/// -/// ``` -/// # #![cfg_attr(windows, feature(abi_vectorcall))] -/// # use ext_php_rs::prelude::*; -/// #[derive(Debug, ZvalConvert)] -/// pub struct ExampleStruct<'a> { -/// a: i32, -/// b: String, -/// c: &'a str -/// } -/// -/// #[php_function] -/// pub fn take_object(obj: ExampleStruct) { -/// dbg!(obj); -/// } -/// -/// #[php_function] -/// pub fn give_object() -> ExampleStruct<'static> { -/// ExampleStruct { -/// a: 5, -/// b: "Hello, world!".into(), -/// c: "Static string", -/// } -/// } -/// ``` -/// -/// Can be used in PHP: -/// -/// ```php -/// $obj = (object) [ -/// 'a' => 5, -/// 'b' => 'Hello, world!', -/// 'c' => 'asdf', -/// ]; -/// take_object($obj); -/// var_dump(give_object()); -/// ``` -/// -/// Another example involving generics: -/// -/// ``` -/// # #![cfg_attr(windows, feature(abi_vectorcall))] -/// # use ext_php_rs::prelude::*; -/// #[derive(Debug, ZvalConvert)] -/// pub struct CompareVals> { -/// a: T, -/// b: T -/// } -/// -/// #[php_function] -/// pub fn take_object(obj: CompareVals) { -/// dbg!(obj); -/// } -/// ``` -/// -/// # Enums -/// -/// When the macro is used on an enum, the [`FromZval`] and [`IntoZval`] -/// implementations will treat the enum as a tagged union with a mixed datatype. -/// This allows you to accept two different types in a parameter, for example, a -/// string and an integer. -/// -/// The enum variants must not have named fields (i.e. not in the form of a -/// struct), and must have exactly one field, the type to extract from the -/// [`Zval`]. Optionally, the enum may have a single default, empty variant, -/// which is used when the [`Zval`] did not contain any data to fill -/// the other variants. This empty variant is equivalent to `null` in PHP. -/// -/// The ordering of the enum variants is important, as the [`Zval`] contents is -/// matched in order of the variants. For example, [`Zval::string`] will attempt -/// to read a string from the [`Zval`], and if the [`Zval`] contains a long, the -/// long will be converted to a string. If a string variant was placed above an -/// integer variant in the enum, the integer would be converted into a -/// string and passed as the string variant. -/// -/// ## Examples -/// -/// Basic example showing the importance of variant ordering and default field: -/// -/// ``` -/// # #![cfg_attr(windows, feature(abi_vectorcall))] -/// # use ext_php_rs::prelude::*; -/// #[derive(Debug, ZvalConvert)] -/// pub enum UnionExample<'a> { -/// Long(u64), // Long -/// ProperStr(&'a str), // Actual string - not a converted value -/// ParsedStr(String), // Potentially parsed string, i.e. a double -/// None // Zval did not contain anything that could be parsed above -/// } -/// -/// #[php_function] -/// pub fn test_union(val: UnionExample) { -/// dbg!(val); -/// } -/// -/// #[php_function] -/// pub fn give_union() -> UnionExample<'static> { -/// UnionExample::Long(5) -/// } -/// ``` -/// -/// Use in PHP: -/// -/// ```php -/// test_union(5); // UnionExample::Long(5) -/// test_union("Hello, world!"); // UnionExample::ProperStr("Hello, world!") -/// test_union(5.66666); // UnionExample::ParsedStr("5.6666") -/// test_union(null); // UnionExample::None -/// var_dump(give_union()); // int(5) -/// ``` -/// -/// [`FromZval`]: crate::convert::FromZval -/// [`IntoZval`]: crate::convert::IntoZval -/// [`FromZendObject`]: crate::convert::FromZendObject -/// [`IntoZendObject`]: crate::convert::IntoZendObject -/// [`Zval`]: crate::types::Zval. -/// [`Zval::string`]: crate::types::Zval.::string -pub use ext_php_rs_derive::ZvalConvert; - -/// Defines an `extern` function with the Zend fastcall convention based on -/// operating system. -/// -/// On Windows, Zend fastcall functions use the vector calling convention, while -/// on all other operating systems no fastcall convention is used (just the -/// regular C calling convention). -/// -/// This macro wraps a function and applies the correct calling convention. -/// -/// ## Examples -/// -/// ``` -/// # #![cfg_attr(windows, feature(abi_vectorcall))] -/// use ext_php_rs::zend_fastcall; -/// -/// zend_fastcall! { -/// pub extern fn test_hello_world(a: i32, b: i32) -> i32 { -/// a + b -/// } -/// } -/// ``` -/// -/// On Windows, this function will have the signature `pub extern "vectorcall" -/// fn(i32, i32) -> i32`, while on macOS/Linux the function will have the -/// signature `pub extern "C" fn(i32, i32) -> i32`. -/// -/// ## Support -/// -/// The `vectorcall` ABI is currently only supported on Windows with nightly -/// Rust and the `abi_vectorcall` feature enabled. pub use ext_php_rs_derive::zend_fastcall; +pub use ext_php_rs_derive::ZvalConvert; diff --git a/src/zend/ex.rs b/src/zend/ex.rs index cb389cb2e7..59eb1732fa 100644 --- a/src/zend/ex.rs +++ b/src/zend/ex.rs @@ -107,31 +107,31 @@ impl ExecuteData { /// # Example /// /// ```no_run - /// use ext_php_rs::{types::Zval, zend::ExecuteData, args::Arg, flags::DataType, prelude::*}; + /// use ext_php_rs::php_module; /// - /// #[php_class] - /// #[derive(Debug)] - /// struct Example; + /// #[php_module] + /// mod module { + /// use ext_php_rs::{types::Zval, zend::ExecuteData, args::Arg, flags::DataType}; /// - /// #[no_mangle] - /// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) { - /// let mut a = Arg::new("a", DataType::Long); + /// #[php_class] + /// #[derive(Debug)] + /// struct Example; /// - /// let (parser, this) = ex.parser_method::(); - /// let parser = parser - /// .arg(&mut a) - /// .parse(); + /// #[no_mangle] + /// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) { + /// let mut a = Arg::new("a", DataType::Long); /// - /// if parser.is_err() { - /// return; - /// } + /// let (parser, this) = ex.parser_method::(); + /// let parser = parser + /// .arg(&mut a) + /// .parse(); /// - /// dbg!(a, this); - /// } + /// if parser.is_err() { + /// return; + /// } /// - /// #[php_module] - /// pub fn module(module: ModuleBuilder) -> ModuleBuilder { - /// module + /// dbg!(a, this); + /// } /// } /// ``` /// @@ -155,21 +155,21 @@ impl ExecuteData { /// # Example /// /// ```no_run - /// use ext_php_rs::{types::Zval, zend::ExecuteData, prelude::*}; + /// use ext_php_rs::php_module; /// - /// #[php_class] - /// #[derive(Debug)] - /// struct Example; + /// #[php_module] + /// mod module { + /// use ext_php_rs::{types::Zval, zend::ExecuteData}; /// - /// #[no_mangle] - /// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) { - /// let this = ex.get_object::(); - /// dbg!(this); - /// } + /// #[php_class] + /// #[derive(Debug)] + /// struct Example; /// - /// #[php_module] - /// pub fn module(module: ModuleBuilder) -> ModuleBuilder { - /// module + /// #[no_mangle] + /// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) { + /// let this = ex.get_object::(); + /// dbg!(this); + /// } /// } /// ``` pub fn get_object(&mut self) -> Option<&mut ZendClassObject> { diff --git a/tests/module.rs b/tests/module.rs index 6407f998e6..6cd90d951e 100644 --- a/tests/module.rs +++ b/tests/module.rs @@ -10,7 +10,7 @@ use ext_php_rs::prelude::*; fn test_module() { Embed::run(|| { // Allow to load the module - unsafe { zend_register_module_ex(get_module()) }; + unsafe { zend_register_module_ex(module::get_module()) }; let result = Embed::eval("$foo = hello_world('foo');"); @@ -26,17 +26,15 @@ fn test_module() { }); } -/// Gives you a nice greeting! -/// -/// @param string $name Your name. -/// -/// @return string Nice greeting! -#[php_function] -pub fn hello_world(name: String) -> String { - format!("Hello, {}!", name) -} - #[php_module] -pub fn module(module: ModuleBuilder) -> ModuleBuilder { - module +mod module { + /// Gives you a nice greeting! + /// + /// @param string $name Your name. + /// + /// @return string Nice greeting! + #[php_function] + pub fn hello_world(name: String) -> String { + format!("Hello, {}!", name) + } } diff --git a/tests/sapi.rs b/tests/sapi.rs index 98a53e9530..ee66fb7666 100644 --- a/tests/sapi.rs +++ b/tests/sapi.rs @@ -8,8 +8,8 @@ use ext_php_rs::ffi::{ php_module_shutdown, php_module_startup, php_request_shutdown, php_request_startup, sapi_shutdown, sapi_startup, ZEND_RESULT_CODE_SUCCESS, }; -use ext_php_rs::prelude::*; use ext_php_rs::zend::try_catch_first; +use ext_php_rs::{php_module, prelude::*}; use std::ffi::c_char; static mut LAST_OUTPUT: String = String::new(); @@ -33,7 +33,7 @@ fn test_sapi() { builder = builder.ub_write_function(output_tester); let sapi = builder.build().unwrap().into_raw(); - let module = get_module(); + let module = module::get_module(); unsafe { ext_php_rs_sapi_startup(); @@ -82,17 +82,15 @@ fn test_sapi() { } } -/// Gives you a nice greeting! -/// -/// @param string $name Your name. -/// -/// @return string Nice greeting! -#[php_function] -pub fn hello_world(name: String) -> String { - format!("Hello, {}!", name) -} - #[php_module] -pub fn module(module: ModuleBuilder) -> ModuleBuilder { - module +mod module { + /// Gives you a nice greeting! + /// + /// @param string $name Your name. + /// + /// @return string Nice greeting! + #[php_function] + pub fn hello_world(name: String) -> String { + format!("Hello, {}!", name) + } } diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 78482d7232..d29186c812 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -1,213 +1,210 @@ -#![cfg_attr(windows, feature(abi_vectorcall))] -use ext_php_rs::{ - binary::Binary, - boxed::ZBox, - prelude::*, - types::{ArrayKey, ZendHashTable, ZendObject, Zval}, - zend::ProcessGlobals, -}; -use std::collections::HashMap; - -#[php_function] -pub fn test_str(a: &str) -> &str { - a -} - -#[php_function] -pub fn test_string(a: String) -> String { - a -} +use ext_php_rs::prelude::php_module; -#[php_function] -pub fn test_bool(a: bool) -> bool { - a -} +#[php_module] +mod module { + use ext_php_rs::{ + binary::Binary, + boxed::ZBox, + types::{ArrayKey, ZendHashTable, ZendObject, Zval}, + zend::ProcessGlobals, + }; + use std::collections::HashMap; + + #[php_function()] + pub fn test_str(a: &str) -> &str { + a + } -#[php_function] -pub fn test_number_signed(a: i32) -> i32 { - a -} + #[php_function()] + pub fn test_string(a: String) -> String { + a + } -#[php_function] -pub fn test_number_unsigned(a: u32) -> u32 { - a -} + #[php_function()] + pub fn test_bool(a: bool) -> bool { + a + } -#[php_function] -pub fn test_number_float(a: f32) -> f32 { - a -} + #[php_function()] + pub fn test_number_signed(a: i32) -> i32 { + a + } -#[php_function] -pub fn test_array(a: Vec) -> Vec { - a -} + #[php_function()] + pub fn test_number_unsigned(a: u32) -> u32 { + a + } -#[php_function] -pub fn test_array_assoc(a: HashMap) -> HashMap { - a -} + #[php_function()] + pub fn test_number_float(a: f32) -> f32 { + a + } -#[php_function] -pub fn test_binary(a: Binary) -> Binary { - a -} + #[php_function()] + pub fn test_array(a: Vec) -> Vec { + a + } -#[php_function] -pub fn test_nullable(a: Option) -> Option { - a -} + #[php_function()] + pub fn test_array_assoc(a: HashMap) -> HashMap { + a + } -#[php_function] -pub fn test_object(a: &mut ZendObject) -> &mut ZendObject { - a -} + #[php_function()] + pub fn test_binary(a: Binary) -> Binary { + a + } -// GLOBALS -#[php_function] -pub fn test_globals_http_get() -> ZBox { - ProcessGlobals::get().http_get_vars().to_owned() -} + #[php_function()] + pub fn test_nullable(a: Option) -> Option { + a + } -#[php_function] -pub fn test_globals_http_post() -> ZBox { - ProcessGlobals::get().http_post_vars().to_owned() -} + #[php_function()] + pub fn test_object(a: &mut ZendObject) -> &mut ZendObject { + a + } -#[php_function] -pub fn test_globals_http_cookie() -> ZBox { - ProcessGlobals::get().http_cookie_vars().to_owned() -} + // GLOBALS + #[php_function] + pub fn test_globals_http_get() -> ZBox { + ProcessGlobals::get().http_get_vars().to_owned() + } -#[php_function] -pub fn test_globals_http_server() -> ZBox { - ProcessGlobals::get().http_server_vars().unwrap().to_owned() -} + #[php_function] + pub fn test_globals_http_post() -> ZBox { + ProcessGlobals::get().http_post_vars().to_owned() + } -#[php_function] -pub fn test_globals_http_request() -> ZBox { - ProcessGlobals::get() - .http_request_vars() - .unwrap() - .to_owned() -} + #[php_function] + pub fn test_globals_http_cookie() -> ZBox { + ProcessGlobals::get().http_cookie_vars().to_owned() + } -#[php_function] -pub fn test_globals_http_files() -> ZBox { - ProcessGlobals::get().http_files_vars().to_owned() -} + #[php_function] + pub fn test_globals_http_server() -> ZBox { + ProcessGlobals::get().http_server_vars().unwrap().to_owned() + } -#[php_function] -pub fn test_closure() -> Closure { - Closure::wrap(Box::new(|a| a) as Box String>) -} + #[php_function] + pub fn test_globals_http_request() -> ZBox { + ProcessGlobals::get() + .http_request_vars() + .unwrap() + .to_owned() + } -#[php_function] -pub fn test_closure_once(a: String) -> Closure { - Closure::wrap_once(Box::new(move || a) as Box String>) -} + #[php_function] + pub fn test_globals_http_files() -> ZBox { + ProcessGlobals::get().http_files_vars().to_owned() + } -#[php_function] -pub fn test_callable(call: ZendCallable, a: String) -> Zval { - call.try_call(vec![&a]).expect("Failed to call function") -} + #[php_function()] + pub fn test_closure() -> Closure { + Closure::wrap(Box::new(|a| a) as Box String>) + } -#[php_function] -pub fn iter_next(ht: &ZendHashTable) -> Vec { - ht.iter() - .flat_map(|(k, v)| [key_to_zval(k), v.shallow_clone()]) - .collect() -} + #[php_function()] + pub fn test_closure_once(a: String) -> Closure { + Closure::wrap_once(Box::new(move || a) as Box String>) + } -#[php_function] -pub fn iter_back(ht: &ZendHashTable) -> Vec { - ht.iter() - .rev() - .flat_map(|(k, v)| [key_to_zval(k), v.shallow_clone()]) - .collect() -} + #[php_function()] + pub fn test_callable(call: ZendCallable, a: String) -> Zval { + call.try_call(vec![&a]).expect("Failed to call function") + } -#[php_function] -pub fn iter_next_back(ht: &ZendHashTable, modulus: usize) -> Vec> { - let mut result = Vec::with_capacity(ht.len()); - let mut iter = ht.iter(); + #[php_function] + pub fn iter_next(ht: &ZendHashTable) -> Vec { + ht.iter() + .flat_map(|(k, v)| [key_to_zval(k), v.shallow_clone()]) + .collect() + } - for i in 0..ht.len() + modulus { - let entry = if i % modulus == 0 { - iter.next_back() - } else { - iter.next() - }; + #[php_function] + pub fn iter_back(ht: &ZendHashTable) -> Vec { + ht.iter() + .rev() + .flat_map(|(k, v)| [key_to_zval(k), v.shallow_clone()]) + .collect() + } - if let Some((k, v)) = entry { - result.push(Some(key_to_zval(k))); - result.push(Some(v.shallow_clone())); - } else { - result.push(None); + #[php_function] + pub fn iter_next_back(ht: &ZendHashTable, modulus: usize) -> Vec> { + let mut result = Vec::with_capacity(ht.len()); + let mut iter = ht.iter(); + + for i in 0..ht.len() + modulus { + let entry = if i % modulus == 0 { + iter.next_back() + } else { + iter.next() + }; + + if let Some((k, v)) = entry { + result.push(Some(key_to_zval(k))); + result.push(Some(v.shallow_clone())); + } else { + result.push(None); + } } - } - result -} + result + } -fn key_to_zval(key: ArrayKey) -> Zval { - match key { - ArrayKey::String(s) => { - let mut zval = Zval::new(); - let _ = zval.set_string(s.as_str(), false); - zval - } - ArrayKey::Long(l) => { - let mut zval = Zval::new(); - zval.set_long(l); - zval + fn key_to_zval(key: ArrayKey) -> Zval { + match key { + ArrayKey::String(s) => { + let mut zval = Zval::new(); + let _ = zval.set_string(s.as_str(), false); + zval + } + ArrayKey::Long(l) => { + let mut zval = Zval::new(); + zval.set_long(l); + zval + } } } -} - -#[php_class] -pub struct TestClass { - string: String, - number: i32, - #[prop] - boolean: bool, -} - -#[php_impl] -impl TestClass { - #[getter] - pub fn get_string(&self) -> String { - self.string.to_string() + #[php_class] + pub struct TestClass { + string: String, + number: i32, + #[prop] + boolean: bool, } - #[setter] - pub fn set_string(&mut self, string: String) { - self.string = string; - } + #[php_impl] + impl TestClass { + #[getter] + pub fn get_string(&self) -> String { + self.string.to_string() + } - #[getter] - pub fn get_number(&self) -> i32 { - self.number - } + #[setter] + pub fn set_string(&mut self, string: String) { + self.string = string; + } - #[setter] - pub fn set_number(&mut self, number: i32) { - self.number = number; - } -} + #[getter] + pub fn get_number(&self) -> i32 { + self.number + } -#[php_function] -pub fn test_class(string: String, number: i32) -> TestClass { - TestClass { - string, - number, - boolean: true, + #[setter] + pub fn set_number(&mut self, number: i32) { + self.number = number; + } } -} -#[php_module] -pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { - module + #[php_function()] + pub fn test_class(string: String, number: i32) -> TestClass { + TestClass { + string, + number, + boolean: true, + } + } } #[cfg(test)]