ext_php_rs_derive/lib.rs
1//! Macros for the `php-ext` crate.
2mod class;
3mod constant;
4mod enum_;
5mod extern_;
6mod fastcall;
7mod function;
8mod helpers;
9mod impl_;
10mod module;
11mod parsing;
12mod syn_ext;
13mod zval;
14
15use proc_macro::TokenStream;
16use proc_macro2::TokenStream as TokenStream2;
17use syn::{DeriveInput, ItemConst, ItemEnum, ItemFn, ItemForeignMod, ItemImpl, ItemStruct};
18
19extern crate proc_macro;
20
21// BEGIN DOCS FROM classes.md
22/// # `#[php_class]` Attribute
23///
24/// Structs can be exported to PHP as classes with the `#[php_class]` attribute
25/// macro. This attribute derives the `RegisteredClass` trait on your struct, as
26/// well as registering the class to be registered with the `#[php_module]` macro.
27///
28/// ## Options
29///
30/// There are additional macros that modify the class. These macros **must** be
31/// placed underneath the `#[php_class]` attribute.
32///
33/// - `name` - Changes the name of the class when exported to PHP. The Rust struct
34/// name is kept the same. If no name is given, the name of the struct is used.
35/// Useful for namespacing classes.
36/// - `change_case` - Changes the case of the class name when exported to PHP.
37/// - `#[php(extends(ce = ce_fn, stub = "ParentClass"))]` - Sets the parent class of the class. Can only be used once.
38/// `ce_fn` must be a function with the signature `fn() -> &'static ClassEntry`.
39/// - `#[php(implements(ce = ce_fn, stub = "InterfaceName"))]` - Implements the given interface on the class. Can be used
40/// multiple times. `ce_fn` must be a valid function with the signature
41/// `fn() -> &'static ClassEntry`.
42///
43/// You may also use the `#[php(prop)]` attribute on a struct field to use the field as a
44/// PHP property. By default, the field will be accessible from PHP publicly with
45/// the same name as the field. Property types must implement `IntoZval` and
46/// `FromZval`.
47///
48/// You can rename the property with options:
49///
50/// - `name` - Allows you to rename the property, e.g.
51/// `#[php(name = "new_name")]`
52/// - `change_case` - Allows you to rename the property using rename rules, e.g.
53/// `#[php(change_case = PascalCase)]`
54///
55/// ## Restrictions
56///
57/// ### No lifetime parameters
58///
59/// Rust lifetimes are used by the Rust compiler to reason about a program's memory safety.
60/// They are a compile-time only concept;
61/// there is no way to access Rust lifetimes at runtime from a dynamic language like PHP.
62///
63/// As soon as Rust data is exposed to PHP,
64/// there is no guarantee which the Rust compiler can make on how long the data will live.
65/// PHP is a reference-counted language and those references can be held
66/// for an arbitrarily long time, which is untraceable by the Rust compiler.
67/// The only possible way to express this correctly is to require that any `#[php_class]`
68/// does not borrow data for any lifetime shorter than the `'static` lifetime,
69/// i.e. the `#[php_class]` cannot have any lifetime parameters.
70///
71/// When you need to share ownership of data between PHP and Rust,
72/// instead of using borrowed references with lifetimes, consider using
73/// reference-counted smart pointers such as [Arc](https://doc.rust-lang.org/std/sync/struct.Arc.html).
74///
75/// ### No generic parameters
76///
77/// A Rust struct `Foo<T>` with a generic parameter `T` generates new compiled implementations
78/// each time it is used with a different concrete type for `T`.
79/// These new implementations are generated by the compiler at each usage site.
80/// This is incompatible with wrapping `Foo` in PHP,
81/// where there needs to be a single compiled implementation of `Foo` which is integrated with the PHP interpreter.
82///
83/// ## Example
84///
85/// This example creates a PHP class `Human`, adding a PHP property `address`.
86///
87/// ```rust,no_run,ignore
88/// # #![cfg_attr(windows, feature(abi_vectorcall))]
89/// # extern crate ext_php_rs;
90/// use ext_php_rs::prelude::*;
91///
92/// #[php_class]
93/// pub struct Human {
94/// name: String,
95/// age: i32,
96/// #[php(prop)]
97/// address: String,
98/// }
99///
100/// #[php_module]
101/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
102/// module.class::<Human>()
103/// }
104/// # fn main() {}
105/// ```
106///
107/// Create a custom exception `RedisException`, which extends `Exception`, and put
108/// it in the `Redis\Exception` namespace:
109///
110/// ```rust,no_run,ignore
111/// # #![cfg_attr(windows, feature(abi_vectorcall))]
112/// # extern crate ext_php_rs;
113/// use ext_php_rs::{
114/// prelude::*,
115/// exception::PhpException,
116/// zend::ce
117/// };
118///
119/// #[php_class]
120/// #[php(name = "Redis\\Exception\\RedisException")]
121/// #[php(extends(ce = ce::exception, stub = "\\Exception"))]
122/// #[derive(Default)]
123/// pub struct RedisException;
124///
125/// // Throw our newly created exception
126/// #[php_function]
127/// pub fn throw_exception() -> PhpResult<i32> {
128/// Err(PhpException::from_class::<RedisException>("Not good!".into()))
129/// }
130///
131/// #[php_module]
132/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
133/// module
134/// .class::<RedisException>()
135/// .function(wrap_function!(throw_exception))
136/// }
137/// # fn main() {}
138/// ```
139///
140/// ## Implementing an Interface
141///
142/// To implement an interface, use `#[php(implements(ce = ce_fn, stub = "InterfaceName")]` where `ce_fn` is an function returning a `ClassEntry`.
143/// The following example implements [`ArrayAccess`](https://www.php.net/manual/en/class.arrayaccess.php):
144///
145/// ````rust,no_run,ignore
146/// # #![cfg_attr(windows, feature(abi_vectorcall))]
147/// # extern crate ext_php_rs;
148/// use ext_php_rs::{
149/// prelude::*,
150/// exception::PhpResult,
151/// types::Zval,
152/// zend::ce,
153/// };
154///
155/// #[php_class]
156/// #[php(implements(ce = ce::arrayaccess, stub = "\\ArrayAccess"))]
157/// #[derive(Default)]
158/// pub struct EvenNumbersArray;
159///
160/// /// Returns `true` if the array offset is an even number.
161/// /// Usage:
162/// /// ```php
163/// /// $arr = new EvenNumbersArray();
164/// /// var_dump($arr[0]); // true
165/// /// var_dump($arr[1]); // false
166/// /// var_dump($arr[2]); // true
167/// /// var_dump($arr[3]); // false
168/// /// var_dump($arr[4]); // true
169/// /// var_dump($arr[5] = true); // Fatal error: Uncaught Exception: Setting values is not supported
170/// /// ```
171/// #[php_impl]
172/// impl EvenNumbersArray {
173/// pub fn __construct() -> EvenNumbersArray {
174/// EvenNumbersArray {}
175/// }
176/// // We need to use `Zval` because ArrayAccess needs $offset to be a `mixed`
177/// pub fn offset_exists(&self, offset: &'_ Zval) -> bool {
178/// offset.is_long()
179/// }
180/// pub fn offset_get(&self, offset: &'_ Zval) -> PhpResult<bool> {
181/// let integer_offset = offset.long().ok_or("Expected integer offset")?;
182/// Ok(integer_offset % 2 == 0)
183/// }
184/// pub fn offset_set(&mut self, _offset: &'_ Zval, _value: &'_ Zval) -> PhpResult {
185/// Err("Setting values is not supported".into())
186/// }
187/// pub fn offset_unset(&mut self, _offset: &'_ Zval) -> PhpResult {
188/// Err("Setting values is not supported".into())
189/// }
190/// }
191///
192/// #[php_module]
193/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
194/// module.class::<EvenNumbersArray>()
195/// }
196/// # fn main() {}
197/// ````
198// END DOCS FROM classes.md
199#[proc_macro_attribute]
200pub fn php_class(args: TokenStream, input: TokenStream) -> TokenStream {
201 php_class_internal(args.into(), input.into()).into()
202}
203
204#[allow(clippy::needless_pass_by_value)]
205fn php_class_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
206 let input = parse_macro_input2!(input as ItemStruct);
207 if !args.is_empty() {
208 return err!(input => "`#[php_class(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
209 }
210
211 class::parser(input).unwrap_or_else(|e| e.to_compile_error())
212}
213
214// BEGIN DOCS FROM enum.md
215/// # `#[php_enum]` Attribute
216///
217/// Enums can be exported to PHP as enums with the `#[php_enum]` attribute macro.
218/// This attribute derives the `RegisteredClass` and `PhpEnum` traits on your enum.
219/// To register the enum use the `enumeration::<EnumName>()` method on the `ModuleBuilder`
220/// in the `#[php_module]` macro.
221///
222/// ## Options
223///
224/// The `#[php_enum]` attribute can be configured with the following options:
225/// - `#[php(name = "EnumName")]` or `#[php(change_case = snake_case)]`: Sets the name of the enum in PHP.
226/// The default is the `PascalCase` name of the enum.
227/// - `#[php(allow_native_discriminants)]`: Allows the use of native Rust discriminants (e.g., `Hearts = 1`).
228///
229/// The cases of the enum can be configured with the following options:
230/// - `#[php(name = "CaseName")]` or `#[php(change_case = snake_case)]`: Sets the name of the enum case in PHP.
231/// The default is the `PascalCase` name of the case.
232/// - `#[php(value = "value")]` or `#[php(value = 123)]`: Sets the discriminant value for the enum case.
233/// This can be a string or an integer. If not set, the case will be exported as a simple enum case without a discriminant.
234///
235/// ### Example
236///
237/// This example creates a PHP enum `Suit`.
238///
239/// ```rust,no_run,ignore
240/// # #![cfg_attr(windows, feature(abi_vectorcall))]
241/// # extern crate ext_php_rs;
242/// use ext_php_rs::prelude::*;
243///
244/// #[php_enum]
245/// pub enum Suit {
246/// Hearts,
247/// Diamonds,
248/// Clubs,
249/// Spades,
250/// }
251///
252/// #[php_module]
253/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
254/// module.enumeration::<Suit>()
255/// }
256/// # fn main() {}
257/// ```
258///
259/// ## Backed Enums
260/// Enums can also be backed by either `i64` or `&'static str`. Those values can be set using the
261/// `#[php(value = "value")]` or `#[php(value = 123)]` attributes on the enum variants.
262///
263/// All variants must have a value of the same type, either all `i64` or all `&'static str`.
264///
265/// ```rust,no_run,ignore
266/// # #![cfg_attr(windows, feature(abi_vectorcall))]
267/// # extern crate ext_php_rs;
268/// use ext_php_rs::prelude::*;
269///
270/// #[php_enum]
271/// pub enum Suit {
272/// #[php(value = "hearts")]
273/// Hearts,
274/// #[php(value = "diamonds")]
275/// Diamonds,
276/// #[php(value = "clubs")]
277/// Clubs,
278/// #[php(value = "spades")]
279/// Spades,
280/// }
281/// #[php_module]
282/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
283/// module.enumeration::<Suit>()
284/// }
285/// # fn main() {}
286/// ```
287///
288/// ### 'Native' Discriminators
289/// Native rust discriminants are currently not supported and will not be exported to PHP.
290///
291/// To avoid confusion a compiler error will be raised if you try to use a native discriminant.
292/// You can ignore this error by adding the `#[php(allow_native_discriminants)]` attribute to your enum.
293///
294/// ```rust,no_run,ignore
295/// # #![cfg_attr(windows, feature(abi_vectorcall))]
296/// # extern crate ext_php_rs;
297/// use ext_php_rs::prelude::*;
298///
299/// #[php_enum]
300/// #[php(allow_native_discriminants)]
301/// pub enum Suit {
302/// Hearts = 1,
303/// Diamonds = 2,
304/// Clubs = 3,
305/// Spades = 4,
306/// }
307///
308/// #[php_module]
309/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
310/// module.enumeration::<Suit>()
311/// }
312/// # fn main() {}
313/// ```
314///
315///
316/// TODO: Add backed enums example
317// END DOCS FROM enum.md
318#[proc_macro_attribute]
319pub fn php_enum(args: TokenStream, input: TokenStream) -> TokenStream {
320 php_enum_internal(args.into(), input.into()).into()
321}
322
323fn php_enum_internal(_args: TokenStream2, input: TokenStream2) -> TokenStream2 {
324 let input = parse_macro_input2!(input as ItemEnum);
325
326 enum_::parser(input).unwrap_or_else(|e| e.to_compile_error())
327}
328
329// BEGIN DOCS FROM function.md
330/// # `#[php_function]` Attribute
331///
332/// Used to annotate functions which should be exported to PHP. Note that this
333/// should not be used on class methods - see the `#[php_impl]` macro for that.
334///
335/// See the [list of types](../types/index.md) that are valid as parameter and
336/// return types.
337///
338/// ## Optional parameters
339///
340/// Optional parameters can be used by setting the Rust parameter type to a variant
341/// of `Option<T>`. The macro will then figure out which parameters are optional by
342/// using the last consecutive arguments that are a variant of `Option<T>` or have a
343/// default value.
344///
345/// ```rust,no_run,ignore
346/// # #![cfg_attr(windows, feature(abi_vectorcall))]
347/// # extern crate ext_php_rs;
348/// use ext_php_rs::prelude::*;
349///
350/// #[php_function]
351/// pub fn greet(name: String, age: Option<i32>) -> String {
352/// let mut greeting = format!("Hello, {}!", name);
353///
354/// if let Some(age) = age {
355/// greeting += &format!(" You are {} years old.", age);
356/// }
357///
358/// greeting
359/// }
360///
361/// #[php_module]
362/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
363/// module.function(wrap_function!(greet))
364/// }
365/// # fn main() {}
366/// ```
367///
368/// Default parameter values can also be set for optional parameters. This is done
369/// through the `#[php(defaults)]` attribute option. When an optional parameter has a
370/// default, it does not need to be a variant of `Option`:
371///
372/// ```rust,no_run,ignore
373/// # #![cfg_attr(windows, feature(abi_vectorcall))]
374/// # extern crate ext_php_rs;
375/// use ext_php_rs::prelude::*;
376///
377/// #[php_function]
378/// #[php(defaults(offset = 0))]
379/// pub fn rusty_strpos(haystack: &str, needle: &str, offset: i64) -> Option<usize> {
380/// let haystack: String = haystack.chars().skip(offset as usize).collect();
381/// haystack.find(needle)
382/// }
383///
384/// #[php_module]
385/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
386/// module.function(wrap_function!(rusty_strpos))
387/// }
388/// # fn main() {}
389/// ```
390///
391/// Note that if there is a non-optional argument after an argument that is a
392/// variant of `Option<T>`, the `Option<T>` argument will be deemed a nullable
393/// argument rather than an optional argument.
394///
395/// ```rust,no_run,ignore
396/// # #![cfg_attr(windows, feature(abi_vectorcall))]
397/// # extern crate ext_php_rs;
398/// use ext_php_rs::prelude::*;
399///
400/// /// `age` will be deemed required and nullable rather than optional.
401/// #[php_function]
402/// pub fn greet(name: String, age: Option<i32>, description: String) -> String {
403/// let mut greeting = format!("Hello, {}!", name);
404///
405/// if let Some(age) = age {
406/// greeting += &format!(" You are {} years old.", age);
407/// }
408///
409/// greeting += &format!(" {}.", description);
410/// greeting
411/// }
412///
413/// #[php_module]
414/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
415/// module.function(wrap_function!(greet))
416/// }
417/// # fn main() {}
418/// ```
419///
420/// You can also specify the optional arguments if you want to have nullable
421/// arguments before optional arguments. This is done through an attribute
422/// parameter:
423///
424/// ```rust,no_run,ignore
425/// # #![cfg_attr(windows, feature(abi_vectorcall))]
426/// # extern crate ext_php_rs;
427/// use ext_php_rs::prelude::*;
428///
429/// /// `age` will be deemed required and nullable rather than optional,
430/// /// while description will be optional.
431/// #[php_function]
432/// #[php(optional = "description")]
433/// pub fn greet(name: String, age: Option<i32>, description: Option<String>) -> String {
434/// let mut greeting = format!("Hello, {}!", name);
435///
436/// if let Some(age) = age {
437/// greeting += &format!(" You are {} years old.", age);
438/// }
439///
440/// if let Some(description) = description {
441/// greeting += &format!(" {}.", description);
442/// }
443///
444/// greeting
445/// }
446///
447/// #[php_module]
448/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
449/// module.function(wrap_function!(greet))
450/// }
451/// # fn main() {}
452/// ```
453///
454/// ## Variadic Functions
455///
456/// Variadic functions can be implemented by specifying the last argument in the Rust
457/// function to the type `&[&Zval]`. This is the equivalent of a PHP function using
458/// the `...$args` syntax.
459///
460/// ```rust,no_run,ignore
461/// # #![cfg_attr(windows, feature(abi_vectorcall))]
462/// # extern crate ext_php_rs;
463/// use ext_php_rs::{prelude::*, types::Zval};
464///
465/// /// This can be called from PHP as `add(1, 2, 3, 4, 5)`
466/// #[php_function]
467/// pub fn add(number: u32, numbers:&[&Zval]) -> u32 {
468/// // numbers is a slice of 4 Zvals all of type long
469/// number
470/// }
471///
472/// #[php_module]
473/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
474/// module.function(wrap_function!(add))
475/// }
476/// # fn main() {}
477/// ```
478///
479/// ## Returning `Result<T, E>`
480///
481/// You can also return a `Result` from the function. The error variant will be
482/// translated into an exception and thrown. See the section on
483/// [exceptions](../exceptions.md) for more details.
484// END DOCS FROM function.md
485#[proc_macro_attribute]
486pub fn php_function(args: TokenStream, input: TokenStream) -> TokenStream {
487 php_function_internal(args.into(), input.into()).into()
488}
489
490#[allow(clippy::needless_pass_by_value)]
491fn php_function_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
492 let input = parse_macro_input2!(input as ItemFn);
493 if !args.is_empty() {
494 return err!(input => "`#[php_function(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
495 }
496
497 function::parser(input).unwrap_or_else(|e| e.to_compile_error())
498}
499
500// BEGIN DOCS FROM constant.md
501/// # `#[php_const]` Attribute
502///
503/// Exports a Rust constant as a global PHP constant. The constant can be any type
504/// that implements `IntoConst`.
505///
506/// The `wrap_constant!()` macro can be used to simplify the registration of constants.
507/// It sets the name and doc comments for the constant.
508///
509/// You can rename the const with options:
510///
511/// - `name` - Allows you to rename the property, e.g.
512/// `#[php(name = "new_name")]`
513/// - `change_case` - Allows you to rename the property using rename rules, e.g.
514/// `#[php(change_case = PascalCase)]`
515///
516/// ## Examples
517///
518/// ```rust,no_run,ignore
519/// # #![cfg_attr(windows, feature(abi_vectorcall))]
520/// # extern crate ext_php_rs;
521/// use ext_php_rs::prelude::*;
522///
523/// #[php_const]
524/// const TEST_CONSTANT: i32 = 100;
525///
526/// #[php_const]
527/// #[php(name = "I_AM_RENAMED")]
528/// const TEST_CONSTANT_THE_SECOND: i32 = 42;
529///
530/// #[php_const]
531/// const ANOTHER_STRING_CONST: &'static str = "Hello world!";
532///
533/// #[php_module]
534/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
535/// module
536/// .constant(wrap_constant!(TEST_CONSTANT))
537/// .constant(wrap_constant!(TEST_CONSTANT_THE_SECOND))
538/// .constant(("MANUAL_CONSTANT", ANOTHER_STRING_CONST, &[]))
539/// }
540/// # fn main() {}
541/// ```
542///
543/// ## PHP usage
544///
545/// ```php
546/// <?php
547///
548/// var_dump(TEST_CONSTANT); // int(100)
549/// var_dump(I_AM_RENAMED); // int(42)
550/// var_dump(MANUAL_CONSTANT); // string(12) "Hello world!"
551/// ```
552// END DOCS FROM constant.md
553#[proc_macro_attribute]
554pub fn php_const(args: TokenStream, input: TokenStream) -> TokenStream {
555 php_const_internal(args.into(), input.into()).into()
556}
557
558#[allow(clippy::needless_pass_by_value)]
559fn php_const_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
560 let input = parse_macro_input2!(input as ItemConst);
561 if !args.is_empty() {
562 return err!(input => "`#[php_const(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
563 }
564
565 constant::parser(input).unwrap_or_else(|e| e.to_compile_error())
566}
567
568// BEGIN DOCS FROM module.md
569/// # `#[php_module]` Attribute
570///
571/// The module macro is used to annotate the `get_module` function, which is used by
572/// the PHP interpreter to retrieve information about your extension, including the
573/// name, version, functions and extra initialization functions. Regardless if you
574/// use this macro, your extension requires a `extern "C" fn get_module()` so that
575/// PHP can get this information.
576///
577/// The function is renamed to `get_module` if you have used another name. The
578/// function is passed an instance of `ModuleBuilder` which allows you to register
579/// the following (if required):
580///
581/// - Functions, classes, and constants
582/// - Extension and request startup and shutdown functions.
583/// - Read more about the PHP extension lifecycle
584/// [here](https://www.phpinternalsbook.com/php7/extensions_design/php_lifecycle.html).
585/// - PHP extension information function
586/// - Used by the `phpinfo()` function to get information about your extension.
587///
588/// Classes and constants are not registered with PHP in the `get_module` function. These are
589/// registered inside the extension startup function.
590///
591/// ## Usage
592///
593/// ```rust,no_run,ignore
594/// # #![cfg_attr(windows, feature(abi_vectorcall))]
595/// # extern crate ext_php_rs;
596/// use ext_php_rs::{
597/// prelude::*,
598/// zend::ModuleEntry,
599/// info_table_start,
600/// info_table_row,
601/// info_table_end
602/// };
603///
604/// #[php_const]
605/// pub const MY_CUSTOM_CONST: &'static str = "Hello, world!";
606///
607/// #[php_class]
608/// pub struct Test {
609/// a: i32,
610/// b: i32
611/// }
612/// #[php_function]
613/// pub fn hello_world() -> &'static str {
614/// "Hello, world!"
615/// }
616///
617/// /// Used by the `phpinfo()` function and when you run `php -i`.
618/// /// This will probably be simplified with another macro eventually!
619/// pub extern "C" fn php_module_info(_module: *mut ModuleEntry) {
620/// info_table_start!();
621/// info_table_row!("my extension", "enabled");
622/// info_table_end!();
623/// }
624///
625/// #[php_module]
626/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
627/// module
628/// .constant(wrap_constant!(MY_CUSTOM_CONST))
629/// .class::<Test>()
630/// .function(wrap_function!(hello_world))
631/// .info_function(php_module_info)
632/// }
633/// # fn main() {}
634/// ```
635// END DOCS FROM module.md
636#[proc_macro_attribute]
637pub fn php_module(args: TokenStream, input: TokenStream) -> TokenStream {
638 php_module_internal(args.into(), input.into()).into()
639}
640
641#[allow(clippy::needless_pass_by_value)]
642fn php_module_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
643 let input = parse_macro_input2!(input as ItemFn);
644 if !args.is_empty() {
645 return err!(input => "`#[php_module(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
646 }
647
648 module::parser(input).unwrap_or_else(|e| e.to_compile_error())
649}
650
651// BEGIN DOCS FROM impl.md
652/// # `#[php_impl]` Attribute
653///
654/// You can export an entire `impl` block to PHP. This exports all methods as well
655/// as constants to PHP on the class that it is implemented on. This requires the
656/// `#[php_class]` macro to already be used on the underlying struct. Trait
657/// implementations cannot be exported to PHP. Only one `impl` block can be exported
658/// per class.
659///
660/// If you do not want a function exported to PHP, you should place it in a separate
661/// `impl` block.
662///
663/// If you want to use async Rust, use `#[php_async_impl]`, instead: see [here »](./async_impl.md) for more info.
664///
665/// ## Options
666///
667/// By default all constants are renamed to `UPPER_CASE` and all methods are renamed to
668/// camelCase. This can be changed by passing the `change_method_case` and
669/// `change_constant_case` as `#[php]` attributes on the `impl` block. The options are:
670///
671/// - `#[php(change_method_case = "snake_case")]` - Renames the method to snake case.
672/// - `#[php(change_constant_case = "snake_case")]` - Renames the constant to snake case.
673///
674/// See the [`name` and `change_case`](./php.md#name-and-change_case) section for a list of all
675/// available cases.
676///
677/// ## Methods
678///
679/// Methods basically follow the same rules as functions, so read about the
680/// [`php_function`] macro first. The primary difference between functions and
681/// methods is they are bounded by their class object.
682///
683/// Class methods can take a `&self` or `&mut self` parameter. They cannot take a
684/// consuming `self` parameter. Static methods can omit this `self` parameter.
685///
686/// To access the underlying Zend object, you can take a reference to a
687/// `ZendClassObject<T>` in place of the self parameter, where the parameter must
688/// be named `self_`. This can also be used to return a reference to `$this`.
689///
690/// The rest of the options are passed as separate attributes:
691///
692/// - `#[php(defaults(i = 5, b = "hello"))]` - Sets the default value for parameter(s).
693/// - `#[php(optional = i)]` - Sets the first optional parameter. Note that this also sets
694/// the remaining parameters as optional, so all optional parameters must be a
695/// variant of `Option<T>`.
696/// - `#[php(vis = "public")]`, `#[php(vis = "protected")]` and `#[php(vis = "private")]` - Sets the visibility of the
697/// method.
698/// - `#[php(name = "method_name")]` - Renames the PHP method to a different identifier,
699/// without renaming the Rust method name.
700///
701/// The `#[php(defaults)]` and `#[php(optional)]` attributes operate the same as the
702/// equivalent function attribute parameters.
703///
704/// ### Constructors
705///
706/// By default, if a class does not have a constructor, it is not constructable from
707/// PHP. It can only be returned from a Rust function to PHP.
708///
709/// Constructors are Rust methods which can take any amount of parameters and
710/// returns either `Self` or `Result<Self, E>`, where `E: Into<PhpException>`. When
711/// the error variant of `Result` is encountered, it is thrown as an exception and
712/// the class is not constructed.
713///
714/// Constructors are designated by either naming the method `__construct` or by
715/// annotating a method with the `#[php(constructor)]` attribute. Note that when using
716/// the attribute, the function is not exported to PHP like a regular method.
717///
718/// Constructors cannot use the visibility or rename attributes listed above.
719///
720/// ## Constants
721///
722/// Constants are defined as regular Rust `impl` constants. Any type that implements
723/// `IntoZval` can be used as a constant. Constant visibility is not supported at
724/// the moment, and therefore no attributes are valid on constants.
725///
726/// ## Property getters and setters
727///
728/// You can add properties to classes which use Rust functions as getters and/or
729/// setters. This is done with the `#[php(getter)]` and `#[php(setter)]` attributes. By
730/// default, the `get_` or `set_` prefix is trimmed from the start of the function
731/// name, and the remainder is used as the property name.
732///
733/// If you want to use a different name for the property, you can pass a `name` or
734/// `change_case` option to the `#[php]` attribute which will change the property name.
735///
736/// Properties do not necessarily have to have both a getter and a setter, if the
737/// property is immutable the setter can be omitted, and vice versa for getters.
738///
739/// The `#[php(getter)]` and `#[php(setter)]` attributes are mutually exclusive on methods.
740/// Properties cannot have multiple getters or setters, and the property name cannot
741/// conflict with field properties defined on the struct.
742///
743/// As the same as field properties, method property types must implement both
744/// `IntoZval` and `FromZval`.
745///
746/// ## Example
747///
748/// Continuing on from our `Human` example in the structs section, we will define a
749/// constructor, as well as getters for the properties. We will also define a
750/// constant for the maximum age of a `Human`.
751///
752/// ```rust,no_run,ignore
753/// # #![cfg_attr(windows, feature(abi_vectorcall))]
754/// # extern crate ext_php_rs;
755/// use ext_php_rs::{prelude::*, types::ZendClassObject};
756///
757/// #[php_class]
758/// #[derive(Debug, Default)]
759/// pub struct Human {
760/// name: String,
761/// age: i32,
762/// #[php(prop)]
763/// address: String,
764/// }
765///
766/// #[php_impl]
767/// impl Human {
768/// const MAX_AGE: i32 = 100;
769///
770/// // No `#[constructor]` attribute required here - the name is `__construct`.
771/// pub fn __construct(name: String, age: i32) -> Self {
772/// Self {
773/// name,
774/// age,
775/// address: String::new()
776/// }
777/// }
778///
779/// #[php(getter)]
780/// pub fn get_name(&self) -> String {
781/// self.name.to_string()
782/// }
783///
784/// #[php(setter)]
785/// pub fn set_name(&mut self, name: String) {
786/// self.name = name;
787/// }
788///
789/// #[php(getter)]
790/// pub fn get_age(&self) -> i32 {
791/// self.age
792/// }
793///
794/// pub fn introduce(&self) {
795/// println!("My name is {} and I am {} years old. I live at {}.", self.name, self.age, self.address);
796/// }
797///
798/// pub fn get_raw_obj(self_: &mut ZendClassObject<Human>) -> &mut ZendClassObject<Human> {
799/// dbg!(self_)
800/// }
801///
802/// pub fn get_max_age() -> i32 {
803/// Self::MAX_AGE
804/// }
805/// }
806/// #[php_module]
807/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
808/// module.class::<Human>()
809/// }
810/// # fn main() {}
811/// ```
812///
813/// Using our newly created class in PHP:
814///
815/// ```php
816/// <?php
817///
818/// $me = new Human('David', 20);
819///
820/// $me->introduce(); // My name is David and I am 20 years old.
821/// var_dump(Human::get_max_age()); // int(100)
822/// var_dump(Human::MAX_AGE); // int(100)
823/// ```
824///
825/// [`php_async_impl`]: ./async_impl.md
826// END DOCS FROM impl.md
827#[proc_macro_attribute]
828pub fn php_impl(args: TokenStream, input: TokenStream) -> TokenStream {
829 php_impl_internal(args.into(), input.into()).into()
830}
831
832#[allow(clippy::needless_pass_by_value)]
833fn php_impl_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
834 let input = parse_macro_input2!(input as ItemImpl);
835 if !args.is_empty() {
836 return err!(input => "`#[php_impl(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
837 }
838
839 impl_::parser(input).unwrap_or_else(|e| e.to_compile_error())
840}
841
842// BEGIN DOCS FROM extern.md
843/// # `#[php_extern]` Attribute
844///
845/// Attribute used to annotate `extern` blocks which are deemed as PHP
846/// functions.
847///
848/// This allows you to 'import' PHP functions into Rust so that they can be
849/// called like regular Rust functions. Parameters can be any type that
850/// implements [`IntoZval`], and the return type can be anything that implements
851/// [`From<Zval>`] (notice how [`Zval`] is consumed rather than borrowed in this
852/// case).
853///
854/// Unlike most other attributes, this does not need to be placed inside a
855/// `#[php_module]` block.
856///
857/// # Panics
858///
859/// The function can panic when called under a few circumstances:
860///
861/// * The function could not be found or was not callable.
862/// * One of the parameters could not be converted into a [`Zval`].
863/// * The actual function call failed internally.
864/// * The output [`Zval`] could not be parsed into the output type.
865///
866/// The last point can be important when interacting with functions that return
867/// unions, such as [`strpos`] which can return an integer or a boolean. In this
868/// case, a [`Zval`] should be returned as parsing a boolean to an integer is
869/// invalid, and vice versa.
870///
871/// # Example
872///
873/// This `extern` block imports the [`strpos`] function from PHP. Notice that
874/// the string parameters can take either [`String`] or [`&str`], the optional
875/// parameter `offset` is an [`Option<i64>`], and the return value is a [`Zval`]
876/// as the return type is an integer-boolean union.
877///
878/// ```rust,no_run,ignore
879/// # #![cfg_attr(windows, feature(abi_vectorcall))]
880/// # extern crate ext_php_rs;
881/// use ext_php_rs::{
882/// prelude::*,
883/// types::Zval,
884/// };
885///
886/// #[php_extern]
887/// extern "C" {
888/// fn strpos(haystack: &str, needle: &str, offset: Option<i64>) -> Zval;
889/// }
890///
891/// #[php_function]
892/// pub fn my_strpos() {
893/// assert_eq!(unsafe { strpos("Hello", "e", None) }.long(), Some(1));
894/// }
895///
896/// #[php_module]
897/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
898/// module.function(wrap_function!(my_strpos))
899/// }
900/// # fn main() {}
901/// ```
902///
903/// [`strpos`]: https://www.php.net/manual/en/function.strpos.php
904/// [`IntoZval`]: crate::convert::IntoZval
905/// [`Zval`]: crate::types::Zval
906// END DOCS FROM extern.md
907#[proc_macro_attribute]
908pub fn php_extern(args: TokenStream, input: TokenStream) -> TokenStream {
909 php_extern_internal(args.into(), input.into()).into()
910}
911
912#[allow(clippy::needless_pass_by_value)]
913fn php_extern_internal(_: TokenStream2, input: TokenStream2) -> TokenStream2 {
914 let input = parse_macro_input2!(input as ItemForeignMod);
915
916 extern_::parser(input).unwrap_or_else(|e| e.to_compile_error())
917}
918
919// BEGIN DOCS FROM zval_convert.md
920/// # `ZvalConvert` Derive Macro
921///
922/// The `#[derive(ZvalConvert)]` macro derives the `FromZval` and `IntoZval` traits
923/// on a struct or enum.
924///
925/// ## Structs
926///
927/// When used on a struct, the `FromZendObject` and `IntoZendObject` traits are also
928/// implemented, mapping fields to properties in both directions. All fields on the
929/// struct must implement `FromZval` as well. Generics are allowed on structs that
930/// use the derive macro, however, the implementation will add a `FromZval` bound to
931/// all generics types.
932///
933/// ### Examples
934///
935/// ```rust,no_run,ignore
936/// # #![cfg_attr(windows, feature(abi_vectorcall))]
937/// # extern crate ext_php_rs;
938/// use ext_php_rs::prelude::*;
939///
940/// #[derive(ZvalConvert)]
941/// pub struct ExampleClass<'a> {
942/// a: i32,
943/// b: String,
944/// c: &'a str
945/// }
946///
947/// #[php_function]
948/// pub fn take_object(obj: ExampleClass) {
949/// dbg!(obj.a, obj.b, obj.c);
950/// }
951///
952/// #[php_function]
953/// pub fn give_object() -> ExampleClass<'static> {
954/// ExampleClass {
955/// a: 5,
956/// b: "String".to_string(),
957/// c: "Borrowed",
958/// }
959/// }
960///
961/// #[php_module]
962/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
963/// module
964/// .function(wrap_function!(take_object))
965/// .function(wrap_function!(give_object))
966/// }
967/// # fn main() {}
968/// ```
969///
970/// Calling from PHP:
971///
972/// ```php
973/// <?php
974///
975/// $obj = new stdClass;
976/// $obj->a = 5;
977/// $obj->b = 'Hello, world!';
978/// $obj->c = 'another string';
979///
980/// take_object($obj);
981/// var_dump(give_object());
982/// ```
983///
984/// Another example involving generics:
985///
986/// ```rust,no_run,ignore
987/// # #![cfg_attr(windows, feature(abi_vectorcall))]
988/// # extern crate ext_php_rs;
989/// use ext_php_rs::prelude::*;
990///
991/// // T must implement both `PartialEq<i32>` and `FromZval`.
992/// #[derive(Debug, ZvalConvert)]
993/// pub struct CompareVals<T: PartialEq<i32>> {
994/// a: T,
995/// b: T
996/// }
997///
998/// #[php_function]
999/// pub fn take_object(obj: CompareVals<i32>) {
1000/// dbg!(obj);
1001/// }
1002///
1003/// #[php_module]
1004/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1005/// module
1006/// .function(wrap_function!(take_object))
1007/// }
1008/// # fn main() {}
1009/// ```
1010///
1011/// ## Enums
1012///
1013/// When used on an enum, the `FromZval` implementation will treat the enum as a
1014/// tagged union with a mixed datatype. This allows you to accept multiple types in
1015/// a parameter, for example, a string and an integer.
1016///
1017/// The enum variants must not have named fields, and each variant must have exactly
1018/// one field (the type to extract from the zval). Optionally, the enum may have one
1019/// default variant with no data contained, which will be used when the rest of the
1020/// variants could not be extracted from the zval.
1021///
1022/// The ordering of the variants in the enum is important, as the `FromZval`
1023/// implementation will attempt to parse the zval data in order. For example, if you
1024/// put a `String` variant before an integer variant, the integer would be converted
1025/// to a string and passed as the string variant.
1026///
1027/// ### Examples
1028///
1029/// Basic example showing the importance of variant ordering and default field:
1030///
1031/// ```rust,no_run,ignore
1032/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1033/// # extern crate ext_php_rs;
1034/// use ext_php_rs::prelude::*;
1035///
1036/// #[derive(Debug, ZvalConvert)]
1037/// pub enum UnionExample<'a> {
1038/// Long(u64), // Long
1039/// ProperStr(&'a str), // Actual string - not a converted value
1040/// ParsedStr(String), // Potentially parsed string, i.e. a double
1041/// None // Zval did not contain anything that could be parsed above
1042/// }
1043///
1044/// #[php_function]
1045/// pub fn test_union(val: UnionExample) {
1046/// dbg!(val);
1047/// }
1048///
1049/// #[php_function]
1050/// pub fn give_union() -> UnionExample<'static> {
1051/// UnionExample::Long(5)
1052/// }
1053///
1054/// #[php_module]
1055/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1056/// module
1057/// .function(wrap_function!(test_union))
1058/// .function(wrap_function!(give_union))
1059/// }
1060/// # fn main() {}
1061/// ```
1062///
1063/// Use in PHP:
1064///
1065/// ```php
1066/// test_union(5); // UnionExample::Long(5)
1067/// test_union("Hello, world!"); // UnionExample::ProperStr("Hello, world!")
1068/// test_union(5.66666); // UnionExample::ParsedStr("5.6666")
1069/// test_union(null); // UnionExample::None
1070/// var_dump(give_union()); // int(5)
1071/// ```
1072// END DOCS FROM zval_convert.md
1073#[proc_macro_derive(ZvalConvert)]
1074pub fn zval_convert_derive(input: TokenStream) -> TokenStream {
1075 zval_convert_derive_internal(input.into()).into()
1076}
1077
1078fn zval_convert_derive_internal(input: TokenStream2) -> TokenStream2 {
1079 let input = parse_macro_input2!(input as DeriveInput);
1080
1081 zval::parser(input).unwrap_or_else(|e| e.to_compile_error())
1082}
1083
1084/// Defines an `extern` function with the Zend fastcall convention based on
1085/// operating system.
1086///
1087/// On Windows, Zend fastcall functions use the vector calling convention, while
1088/// on all other operating systems no fastcall convention is used (just the
1089/// regular C calling convention).
1090///
1091/// This macro wraps a function and applies the correct calling convention.
1092///
1093/// ## Examples
1094///
1095/// ```rust,ignore
1096/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1097/// use ext_php_rs::zend_fastcall;
1098///
1099/// zend_fastcall! {
1100/// pub extern fn test_hello_world(a: i32, b: i32) -> i32 {
1101/// a + b
1102/// }
1103/// }
1104/// ```
1105///
1106/// On Windows, this function will have the signature `pub extern "vectorcall"
1107/// fn(i32, i32) -> i32`, while on macOS/Linux the function will have the
1108/// signature `pub extern "C" fn(i32, i32) -> i32`.
1109///
1110/// ## Support
1111///
1112/// The `vectorcall` ABI is currently only supported on Windows with nightly
1113/// Rust and the `abi_vectorcall` feature enabled.
1114#[proc_macro]
1115pub fn zend_fastcall(input: TokenStream) -> TokenStream {
1116 zend_fastcall_internal(input.into()).into()
1117}
1118
1119fn zend_fastcall_internal(input: TokenStream2) -> TokenStream2 {
1120 let input = parse_macro_input2!(input as ItemFn);
1121
1122 fastcall::parser(input)
1123}
1124
1125/// Wraps a function to be used in the [`Module::function`] method.
1126#[proc_macro]
1127pub fn wrap_function(input: TokenStream) -> TokenStream {
1128 wrap_function_internal(input.into()).into()
1129}
1130
1131fn wrap_function_internal(input: TokenStream2) -> TokenStream2 {
1132 let input = parse_macro_input2!(input as syn::Path);
1133
1134 match function::wrap(&input) {
1135 Ok(parsed) => parsed,
1136 Err(e) => e.to_compile_error(),
1137 }
1138}
1139
1140/// Wraps a constant to be used in the [`ModuleBuilder::constant`] method.
1141#[proc_macro]
1142pub fn wrap_constant(input: TokenStream) -> TokenStream {
1143 wrap_constant_internal(input.into()).into()
1144}
1145
1146fn wrap_constant_internal(input: TokenStream2) -> TokenStream2 {
1147 let input = parse_macro_input2!(input as syn::Path);
1148
1149 match constant::wrap(&input) {
1150 Ok(parsed) => parsed,
1151 Err(e) => e.to_compile_error(),
1152 }
1153}
1154
1155macro_rules! parse_macro_input2 {
1156 ($tokenstream:ident as $ty:ty) => {
1157 match syn::parse2::<$ty>($tokenstream) {
1158 Ok(data) => data,
1159 Err(err) => {
1160 return proc_macro2::TokenStream::from(err.to_compile_error());
1161 }
1162 }
1163 };
1164 ($tokenstream:ident) => {
1165 $crate::parse_macro_input!($tokenstream as _)
1166 };
1167}
1168
1169pub(crate) use parse_macro_input2;
1170
1171macro_rules! err {
1172 ($span:expr => $($msg:tt)*) => {
1173 ::syn::Error::new(::syn::spanned::Spanned::span(&$span), format!($($msg)*))
1174 };
1175 ($($msg:tt)*) => {
1176 ::syn::Error::new(::proc_macro2::Span::call_site(), format!($($msg)*))
1177 };
1178}
1179
1180/// Bails out of a function with a syn error.
1181macro_rules! bail {
1182 ($span:expr => $($msg:tt)*) => {
1183 return Err($crate::err!($span => $($msg)*))
1184 };
1185 ($($msg:tt)*) => {
1186 return Err($crate::err!($($msg)*))
1187 };
1188}
1189
1190pub(crate) use bail;
1191pub(crate) use err;
1192
1193pub(crate) mod prelude {
1194 pub(crate) trait OptionTokens {
1195 fn option_tokens(&self) -> proc_macro2::TokenStream;
1196 }
1197
1198 impl<T: quote::ToTokens> OptionTokens for Option<T> {
1199 fn option_tokens(&self) -> proc_macro2::TokenStream {
1200 if let Some(token) = self {
1201 quote::quote! { ::std::option::Option::Some(#token) }
1202 } else {
1203 quote::quote! { ::std::option::Option::None }
1204 }
1205 }
1206 }
1207
1208 pub(crate) use crate::{bail, err};
1209 pub(crate) type Result<T> = std::result::Result<T, syn::Error>;
1210}
1211
1212#[cfg(test)]
1213mod tests {
1214 use super::*;
1215 use std::path::PathBuf;
1216
1217 type AttributeFn =
1218 fn(proc_macro2::TokenStream, proc_macro2::TokenStream) -> proc_macro2::TokenStream;
1219 type FunctionLikeFn = fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream;
1220
1221 #[test]
1222 pub fn test_expand() {
1223 macrotest::expand("tests/expand/*.rs");
1224 for entry in glob::glob("tests/expand/*.rs").expect("Failed to read expand tests glob") {
1225 let entry = entry.expect("Failed to read expand test file");
1226 runtime_expand_attr(&entry);
1227 runtime_expand_func(&entry);
1228 runtime_expand_derive(&entry);
1229 }
1230 }
1231
1232 fn runtime_expand_attr(path: &PathBuf) {
1233 let file = std::fs::File::open(path).expect("Failed to open expand test file");
1234 runtime_macros::emulate_attributelike_macro_expansion(
1235 file,
1236 &[
1237 ("php_class", php_class_internal as AttributeFn),
1238 ("php_const", php_const_internal as AttributeFn),
1239 ("php_enum", php_enum_internal as AttributeFn),
1240 ("php_extern", php_extern_internal as AttributeFn),
1241 ("php_function", php_function_internal as AttributeFn),
1242 ("php_impl", php_impl_internal as AttributeFn),
1243 ("php_module", php_module_internal as AttributeFn),
1244 ],
1245 )
1246 .expect("Failed to expand attribute macros in test file");
1247 }
1248
1249 fn runtime_expand_func(path: &PathBuf) {
1250 let file = std::fs::File::open(path).expect("Failed to open expand test file");
1251 runtime_macros::emulate_functionlike_macro_expansion(
1252 file,
1253 &[
1254 ("zend_fastcall", zend_fastcall_internal as FunctionLikeFn),
1255 ("wrap_function", wrap_function_internal as FunctionLikeFn),
1256 ("wrap_constant", wrap_constant_internal as FunctionLikeFn),
1257 ],
1258 )
1259 .expect("Failed to expand function-like macros in test file");
1260 }
1261
1262 fn runtime_expand_derive(path: &PathBuf) {
1263 let file = std::fs::File::open(path).expect("Failed to open expand test file");
1264 runtime_macros::emulate_derive_macro_expansion(
1265 file,
1266 &[("ZvalConvert", zval_convert_derive_internal)],
1267 )
1268 .expect("Failed to expand derive macros in test file");
1269 }
1270}