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 impl_interface;
11mod interface;
12mod module;
13mod parsing;
14mod syn_ext;
15mod zval;
16
17use darling::FromMeta;
18use proc_macro::TokenStream;
19use proc_macro2::TokenStream as TokenStream2;
20use syn::{
21 DeriveInput, ItemConst, ItemEnum, ItemFn, ItemForeignMod, ItemImpl, ItemStruct, ItemTrait,
22};
23
24extern crate proc_macro;
25
26// BEGIN DOCS FROM classes.md
27/// # `#[php_class]` Attribute
28///
29/// Structs can be exported to PHP as classes with the `#[php_class]` attribute
30/// macro. This attribute derives the `RegisteredClass` trait on your struct, as
31/// well as registering the class to be registered with the `#[php_module]`
32/// macro.
33///
34/// ## Options
35///
36/// There are additional macros that modify the class. These macros **must** be
37/// placed underneath the `#[php_class]` attribute.
38///
39/// - `name` - Changes the name of the class when exported to PHP. The Rust
40/// struct name is kept the same. If no name is given, the name of the struct
41/// is used. Useful for namespacing classes.
42/// - `change_case` - Changes the case of the class name when exported to PHP.
43/// - `readonly` - Marks the class as readonly (PHP 8.2+). All properties in a
44/// readonly class are implicitly readonly.
45/// - `flags` - Sets class flags using `ClassFlags`, e.g. `#[php(flags =
46/// ClassFlags::Final)]` for a final class.
47/// - `#[php(extends(...))]` - Sets the parent class of the class. Can only be
48/// used once. Two forms are supported:
49/// - Simple type form: `#[php(extends(MyBaseClass))]` - For Rust-defined
50/// classes that implement `RegisteredClass`.
51/// - Explicit form: `#[php(extends(ce = ce_fn, stub = "ParentClass"))]` - For
52/// built-in PHP classes. `ce_fn` must be a function with the signature
53/// `fn() -> &'static ClassEntry`.
54/// - `#[php(implements(...))]` - Implements the given interface on the class.
55/// Can be used multiple times. Two forms are supported:
56/// - Simple type form: `#[php(implements(MyInterface))]` — For Rust-defined
57/// interfaces that implement `RegisteredClass`.
58/// - Explicit form: `#[php(implements(ce = ce_fn, stub = "InterfaceName"))]`
59/// — For built-in PHP interfaces. `ce_fn` must be a valid function with the
60/// signature `fn() -> &'static ClassEntry`.
61///
62/// You may also use the `#[php(prop)]` attribute on a struct field to use the
63/// field as a PHP property. By default, the property is public and its name is
64/// the field name converted to camelCase: the field `first_name` gives the
65/// property `firstName`. Property types must implement `IntoZval` and
66/// `FromZval`.
67///
68/// You can customize properties with these options:
69///
70/// - `name` - Allows you to rename the property, e.g. `#[php(prop, name =
71/// "new_name")]`
72/// - `change_case` - Allows you to rename the property using rename rules, e.g.
73/// `#[php(prop, change_case = "PascalCase")]`
74/// - `static` - Makes the property static (shared across all instances), e.g.
75/// `#[php(prop, static)]`
76/// - `flags` - Sets property visibility flags, e.g. `#[php(prop, flags =
77/// ext_php_rs::flags::PropertyFlags::Private)]`
78///
79/// ### Known limitation: `#[php(prop)]` on owned refcounted types accessed via
80/// `Exception::getMessage`-style C methods leaks one `zend_string` per call.
81///
82/// The generated read-property handler writes a fresh `zend_string` with
83/// refcount=1 into the `rv` slot via `set_zval`. PHP's `Exception::getMessage`
84/// (and siblings such as `getFile`) read this with `zval_get_string +
85/// RETURN_STR`, which addrefs to 2 and transfers the pointer to `return_value`
86/// without changing the refcount. The stack `rv` then goes out of scope without
87/// `zval_ptr_dtor`, orphaning one refcount per call.
88///
89/// This affects any `#[php_class]` extending `\Exception` with a `#[php(prop)]`
90/// field whose type allocates a `zend_string` (e.g. `String`, `Vec<u8>` when
91/// converted to a binary string, or any `IntoZval` impl producing
92/// `IS_STRING_EX`) — most commonly when the field shadows the parent
93/// `\Exception::$message`. Direct property access (`$obj->field`) via the
94/// `FETCH_OBJ_R` opcode is **not** affected because the bytecode handler
95/// properly consumes the rv ref.
96///
97/// **Workarounds, in order of preference:**
98///
99/// 1. **Do not shadow the inherited property name.** Rename the field (e.g.
100/// `payload` instead of `message`) and expose it through a `#[php_method]`
101/// getter. The method-return path is not affected by this leak. Note:
102/// `\Exception::getMessage` is `final` in PHP, so overriding it directly via
103/// `#[php_method] fn get_message(...)` is rejected at class registration.
104///
105/// 2. **Write the value into the parent's real property slot via
106/// `zend_update_property_stringl`** (raw FFI). PHP's `getMessage` then reads
107/// from real storage through `zend_std_read_property`, bypassing the leaky
108/// `rv` path entirely. This requires dropping `#[php(prop)]` from the shadow
109/// field and populating the parent slot at construction time. See
110/// `biscuit-php` (`src/errors.rs`) for a worked example.
111///
112/// Tracked by the
113/// `prop_string_field_does_not_leak_on_repeated_get_message` regression test.
114///
115/// ## Restrictions
116///
117/// ### No lifetime parameters
118///
119/// Rust lifetimes are used by the Rust compiler to reason about a program's
120/// memory safety. They are a compile-time only concept;
121/// there is no way to access Rust lifetimes at runtime from a dynamic language
122/// like PHP.
123///
124/// As soon as Rust data is exposed to PHP,
125/// there is no guarantee which the Rust compiler can make on how long the data
126/// will live. PHP is a reference-counted language and those references can be
127/// held for an arbitrarily long time, which is untraceable by the Rust
128/// compiler. The only possible way to express this correctly is to require that
129/// any `#[php_class]` does not borrow data for any lifetime shorter than the
130/// `'static` lifetime, i.e. the `#[php_class]` cannot have any lifetime
131/// parameters.
132///
133/// When you need to share ownership of data between PHP and Rust,
134/// instead of using borrowed references with lifetimes, consider using
135/// reference-counted smart pointers such as [Arc](https://doc.rust-lang.org/std/sync/struct.Arc.html).
136///
137/// ### No generic parameters
138///
139/// A Rust struct `Foo<T>` with a generic parameter `T` generates new compiled
140/// implementations each time it is used with a different concrete type for `T`.
141/// These new implementations are generated by the compiler at each usage site.
142/// This is incompatible with wrapping `Foo` in PHP,
143/// where there needs to be a single compiled implementation of `Foo` which is
144/// integrated with the PHP interpreter.
145///
146/// ## Example
147///
148/// This example creates a PHP class `Human`, adding a PHP property `address`.
149///
150/// ```rust,no_run,ignore
151/// # #![cfg_attr(windows, feature(abi_vectorcall))]
152/// # extern crate ext_php_rs;
153/// use ext_php_rs::prelude::*;
154///
155/// #[php_class]
156/// pub struct Human {
157/// name: String,
158/// age: i32,
159/// #[php(prop)]
160/// address: String,
161/// }
162///
163/// #[php_module]
164/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
165/// module.class::<Human>()
166/// }
167/// # fn main() {}
168/// ```
169///
170/// Create a custom exception `RedisException`, which extends `Exception`, and
171/// put it in the `Redis\Exception` namespace:
172///
173/// ```rust,no_run,ignore
174/// # #![cfg_attr(windows, feature(abi_vectorcall))]
175/// # extern crate ext_php_rs;
176/// use ext_php_rs::{
177/// prelude::*,
178/// exception::PhpException,
179/// zend::ce
180/// };
181///
182/// #[php_class]
183/// #[php(name = "Redis\\Exception\\RedisException")]
184/// #[php(extends(ce = ce::exception, stub = "\\Exception"))]
185/// #[derive(Default)]
186/// pub struct RedisException;
187///
188/// // Throw our newly created exception
189/// #[php_function]
190/// pub fn throw_exception() -> PhpResult<i32> {
191/// Err(PhpException::from_class::<RedisException>("Not good!".into()))
192/// }
193///
194/// #[php_module]
195/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
196/// module
197/// .class::<RedisException>()
198/// .function(wrap_function!(throw_exception))
199/// }
200/// # fn main() {}
201/// ```
202///
203/// ### Extending a Rust-defined Class
204///
205/// When extending another Rust-defined class, you can use the simpler type
206/// syntax:
207///
208/// ```rust,ignore
209/// # #![cfg_attr(windows, feature(abi_vectorcall))]
210/// # extern crate ext_php_rs;
211/// use ext_php_rs::prelude::*;
212///
213/// #[php_class]
214/// #[derive(Default)]
215/// pub struct Animal;
216///
217/// #[php_impl]
218/// impl Animal {
219/// pub fn speak(&self) -> &'static str {
220/// "..."
221/// }
222/// }
223///
224/// #[php_class]
225/// #[php(extends(Animal))]
226/// #[derive(Default)]
227/// pub struct Dog;
228///
229/// #[php_impl]
230/// impl Dog {
231/// pub fn speak(&self) -> &'static str {
232/// "Woof!"
233/// }
234/// }
235///
236/// #[php_module]
237/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
238/// module
239/// .class::<Animal>()
240/// .class::<Dog>()
241/// }
242/// # fn main() {}
243/// ```
244///
245/// #### Sharing Methods Between Parent and Child Classes
246///
247/// When both parent and child are Rust-defined classes, methods defined only in
248/// the parent won't automatically work when called on a child instance. This is
249/// because each Rust type has its own object handlers.
250///
251/// The recommended workaround is to use a Rust trait for shared behavior:
252///
253/// ```rust,ignore
254/// # #![cfg_attr(windows, feature(abi_vectorcall))]
255/// # extern crate ext_php_rs;
256/// use ext_php_rs::prelude::*;
257///
258/// /// Trait for shared behavior
259/// trait AnimalBehavior {
260/// fn speak(&self) -> &'static str {
261/// "..."
262/// }
263/// }
264///
265/// #[php_class]
266/// #[derive(Default)]
267/// pub struct Animal;
268///
269/// impl AnimalBehavior for Animal {}
270///
271/// #[php_impl]
272/// impl Animal {
273/// pub fn speak(&self) -> &'static str {
274/// AnimalBehavior::speak(self)
275/// }
276/// }
277///
278/// #[php_class]
279/// #[php(extends(Animal))]
280/// #[derive(Default)]
281/// pub struct Dog;
282///
283/// impl AnimalBehavior for Dog {
284/// fn speak(&self) -> &'static str {
285/// "Woof!"
286/// }
287/// }
288///
289/// #[php_impl]
290/// impl Dog {
291/// // Re-export the method so it works on Dog instances
292/// pub fn speak(&self) -> &'static str {
293/// AnimalBehavior::speak(self)
294/// }
295/// }
296///
297/// #[php_module]
298/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
299/// module
300/// .class::<Animal>()
301/// .class::<Dog>()
302/// }
303/// # fn main() {}
304/// ```
305///
306/// This pattern ensures that:
307/// - `$animal->speak()` returns `"..."`
308/// - `$dog->speak()` returns `"Woof!"`
309/// - `$dog instanceof Animal` is `true`
310///
311/// ## Implementing an Interface
312///
313/// To implement an interface, use `#[php(implements(...))]`. For built-in PHP
314/// interfaces, use the explicit form with `ce` and `stub`. For Rust-defined
315/// interfaces, you can use the simple type form. The following example implements [`ArrayAccess`](https://www.php.net/manual/en/class.arrayaccess.php):
316///
317/// ````rust,no_run,ignore
318/// # #![cfg_attr(windows, feature(abi_vectorcall))]
319/// # extern crate ext_php_rs;
320/// use ext_php_rs::{
321/// prelude::*,
322/// exception::PhpResult,
323/// types::Zval,
324/// zend::ce,
325/// };
326///
327/// #[php_class]
328/// #[php(implements(ce = ce::arrayaccess, stub = "\\ArrayAccess"))]
329/// #[derive(Default)]
330/// pub struct EvenNumbersArray;
331///
332/// /// Returns `true` if the array offset is an even number.
333/// /// Usage:
334/// /// ```php
335/// /// $arr = new EvenNumbersArray();
336/// /// var_dump($arr[0]); // true
337/// /// var_dump($arr[1]); // false
338/// /// var_dump($arr[2]); // true
339/// /// var_dump($arr[3]); // false
340/// /// var_dump($arr[4]); // true
341/// /// var_dump($arr[5] = true); // Fatal error: Uncaught Exception: Setting values is not supported
342/// /// ```
343/// #[php_impl]
344/// impl EvenNumbersArray {
345/// pub fn __construct() -> EvenNumbersArray {
346/// EvenNumbersArray {}
347/// }
348/// // We need to use `Zval` because ArrayAccess needs $offset to be a `mixed`
349/// pub fn offset_exists(&self, offset: &'_ Zval) -> bool {
350/// offset.is_long()
351/// }
352/// pub fn offset_get(&self, offset: &'_ Zval) -> PhpResult<bool> {
353/// let integer_offset = offset.long().ok_or("Expected integer offset")?;
354/// Ok(integer_offset % 2 == 0)
355/// }
356/// pub fn offset_set(&mut self, _offset: &'_ Zval, _value: &'_ Zval) -> PhpResult {
357/// Err("Setting values is not supported".into())
358/// }
359/// pub fn offset_unset(&mut self, _offset: &'_ Zval) -> PhpResult {
360/// Err("Setting values is not supported".into())
361/// }
362/// }
363///
364/// #[php_module]
365/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
366/// module.class::<EvenNumbersArray>()
367/// }
368/// # fn main() {}
369/// ````
370///
371/// ## Static Properties
372///
373/// Static properties are shared across all instances of a class. Use
374/// `#[php(prop, static)]` to declare a static property. Unlike instance
375/// properties, static properties are managed entirely by PHP and do not use
376/// Rust property handlers.
377///
378/// You can specify a default value using the `default` attribute:
379///
380/// ```rust,no_run,ignore
381/// # #![cfg_attr(windows, feature(abi_vectorcall))]
382/// # extern crate ext_php_rs;
383/// use ext_php_rs::prelude::*;
384/// use ext_php_rs::class::RegisteredClass;
385///
386/// #[php_class]
387/// pub struct Counter {
388/// #[php(prop)]
389/// pub instance_value: i32,
390/// #[php(prop, static, default = 0)]
391/// pub count: i32,
392/// #[php(prop, static, flags = ext_php_rs::flags::PropertyFlags::Private)]
393/// pub internal_state: String,
394/// }
395///
396/// #[php_impl]
397/// impl Counter {
398/// pub fn __construct(value: i32) -> Self {
399/// Self {
400/// instance_value: value,
401/// count: 0,
402/// internal_state: String::new(),
403/// }
404/// }
405///
406/// /// Increment the static counter from Rust
407/// pub fn increment() {
408/// let ce = Self::get_metadata().ce();
409/// let current: i64 = ce.get_static_property("count").unwrap_or(0);
410/// ce.set_static_property("count", current + 1).unwrap();
411/// }
412///
413/// /// Get the current count
414/// pub fn get_count() -> i64 {
415/// let ce = Self::get_metadata().ce();
416/// ce.get_static_property("count").unwrap_or(0)
417/// }
418/// }
419///
420/// #[php_module]
421/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
422/// module.class::<Counter>()
423/// }
424/// # fn main() {}
425/// ```
426///
427/// From PHP, you can access static properties directly on the class:
428///
429/// ```php
430/// // No need to initialize - count already has default value of 0
431/// Counter::increment();
432/// Counter::increment();
433/// echo Counter::$count; // 2
434/// echo Counter::getCount(); // 2
435/// ```
436///
437/// ## Abstract Classes
438///
439/// Abstract classes cannot be instantiated directly and may contain abstract
440/// methods that must be implemented by subclasses. Use `#[php(flags =
441/// ClassFlags::Abstract)]` to declare an abstract class:
442///
443/// ```rust,ignore
444/// use ext_php_rs::prelude::*;
445/// use ext_php_rs::flags::ClassFlags;
446///
447/// #[php_class]
448/// #[php(flags = ClassFlags::Abstract)]
449/// pub struct AbstractAnimal;
450///
451/// #[php_impl]
452/// impl AbstractAnimal {
453/// // Protected constructor for subclasses
454/// #[php(vis = "protected")]
455/// pub fn __construct() -> Self {
456/// Self
457/// }
458///
459/// // Abstract method - must be implemented by subclasses.
460/// // Body is never called; use unimplemented!() as a placeholder.
461/// #[php(abstract)]
462/// pub fn speak(&self) -> String {
463/// unimplemented!()
464/// }
465///
466/// // Concrete method - inherited by subclasses
467/// pub fn breathe(&self) {
468/// println!("Breathing...");
469/// }
470/// }
471/// ```
472///
473/// From PHP, you can extend this abstract class:
474///
475/// ```php
476/// class Dog extends AbstractAnimal {
477/// public function __construct() {
478/// parent::__construct();
479/// }
480///
481/// public function speak(): string {
482/// return "Woof!";
483/// }
484/// }
485///
486/// $dog = new Dog();
487/// echo $dog->speak(); // "Woof!"
488/// $dog->breathe(); // "Breathing..."
489///
490/// // This would cause an error:
491/// // $animal = new AbstractAnimal(); // Cannot instantiate abstract class
492/// ```
493///
494/// See the [impl documentation](./impl.md#abstract-methods) for more details on
495/// abstract methods.
496///
497/// ## Final Classes
498///
499/// Final classes cannot be extended. Use `#[php(flags = ClassFlags::Final)]` to
500/// declare a final class:
501///
502/// ```rust,ignore
503/// use ext_php_rs::prelude::*;
504/// use ext_php_rs::flags::ClassFlags;
505///
506/// #[php_class]
507/// #[php(flags = ClassFlags::Final)]
508/// pub struct FinalClass;
509/// ```
510///
511/// ## Readonly Classes (PHP 8.2+)
512///
513/// PHP 8.2 introduced [readonly classes](https://www.php.net/manual/en/language.oop5.basic.php#language.oop5.basic.class.readonly),
514/// where all properties are implicitly readonly. You can create a readonly
515/// class using the `#[php(readonly)]` attribute:
516///
517/// ```rust,ignore
518/// # #![cfg_attr(windows, feature(abi_vectorcall))]
519/// # extern crate ext_php_rs;
520/// use ext_php_rs::prelude::*;
521///
522/// #[php_class]
523/// #[php(readonly)]
524/// pub struct ImmutablePoint {
525/// x: f64,
526/// y: f64,
527/// }
528///
529/// #[php_impl]
530/// impl ImmutablePoint {
531/// pub fn __construct(x: f64, y: f64) -> Self {
532/// Self { x, y }
533/// }
534///
535/// pub fn get_x(&self) -> f64 {
536/// self.x
537/// }
538///
539/// pub fn get_y(&self) -> f64 {
540/// self.y
541/// }
542///
543/// pub fn distance_from_origin(&self) -> f64 {
544/// (self.x * self.x + self.y * self.y).sqrt()
545/// }
546/// }
547///
548/// #[php_module]
549/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
550/// module.class::<ImmutablePoint>()
551/// }
552/// # fn main() {}
553/// ```
554///
555/// From PHP:
556///
557/// ```php
558/// $point = new ImmutablePoint(3.0, 4.0);
559/// echo $point->getX(); // 3.0
560/// echo $point->getY(); // 4.0
561/// echo $point->distanceFromOrigin(); // 5.0
562///
563/// // On PHP 8.2+, you can verify the class is readonly:
564/// $reflection = new ReflectionClass(ImmutablePoint::class);
565/// var_dump($reflection->isReadOnly()); // true
566/// ```
567///
568/// The `readonly` attribute is compatible with other class attributes:
569///
570/// ```rust,ignore
571/// # #![cfg_attr(windows, feature(abi_vectorcall))]
572/// # extern crate ext_php_rs;
573/// use ext_php_rs::prelude::*;
574/// use ext_php_rs::flags::ClassFlags;
575///
576/// // Readonly + Final class
577/// #[php_class]
578/// #[php(readonly)]
579/// #[php(flags = ClassFlags::Final)]
580/// pub struct FinalImmutableData {
581/// value: String,
582/// }
583/// # fn main() {}
584/// ```
585///
586/// **Note:** The `readonly` attribute requires PHP 8.2 or later. Using it when
587/// compiling against an earlier PHP version will result in a compile error.
588///
589/// ### Conditional Compilation for Multi-Version Support
590///
591/// If your extension needs to support both PHP 8.1 and PHP 8.2+, you can use
592/// conditional compilation to only enable readonly on supported versions.
593///
594/// First, add `ext-php-rs-build` as a build dependency in your `Cargo.toml`:
595///
596/// ```toml
597/// [build-dependencies]
598/// ext-php-rs-build = "0.1"
599/// anyhow = "1"
600/// ```
601///
602/// Then create a `build.rs` that detects the PHP version and emits cfg flags:
603///
604/// ```rust,ignore
605/// use ext_php_rs_build::{find_php, PHPInfo, ApiVersion, emit_php_cfg_flags, emit_check_cfg};
606///
607/// fn main() -> anyhow::Result<()> {
608/// let php = find_php()?;
609/// let info = PHPInfo::get(&php)?;
610/// let version: ApiVersion = info.zend_version()?.try_into()?;
611///
612/// emit_check_cfg();
613/// emit_php_cfg_flags(version);
614/// Ok(())
615/// }
616/// ```
617///
618/// Now you can use `#[cfg(php82)]` to conditionally apply the readonly
619/// attribute:
620///
621/// ```rust,ignore
622/// #[php_class]
623/// #[cfg_attr(php82, php(readonly))]
624/// pub struct MaybeReadonlyClass {
625/// value: String,
626/// }
627/// ```
628///
629/// The `ext-php-rs-build` crate provides several useful utilities:
630///
631/// - `find_php()` - Locates the PHP executable (respects the `PHP` env var)
632/// - `PHPInfo::get()` - Runs `php -i` and parses the output
633/// - `ApiVersion` - Enum representing PHP versions (Php80, Php81, Php82, etc.)
634/// - `emit_php_cfg_flags()` - Emits `cargo:rustc-cfg=phpXX` for all supported
635/// versions
636/// - `emit_check_cfg()` - Emits check-cfg to avoid unknown cfg warnings
637///
638/// This is **optional** - if your extension only targets PHP 8.2+, you can use
639/// `#[php(readonly)]` directly without any build script setup.
640///
641/// ## Cloning
642///
643/// PHP's native `clone` operator is supported for `#[php_class]` structs that
644/// implement `Clone`. A derive or a hand-written `impl Clone` both work, and
645/// the spelling of the derive path does not matter. The cloned PHP object
646/// contains a copy of the Rust data:
647///
648/// ```rust,ignore
649/// use ext_php_rs::prelude::*;
650///
651/// #[php_class]
652/// #[derive(Clone)]
653/// pub struct Style {
654/// #[php(prop)]
655/// pub font_size: f64,
656/// #[php(prop)]
657/// pub color: String,
658/// }
659///
660/// #[php_impl]
661/// impl Style {
662/// pub fn __construct(font_size: f64, color: String) -> Self {
663/// Self { font_size, color }
664/// }
665/// }
666/// ```
667///
668/// ```php
669/// $style = new Style(12.0, 'red');
670/// $copy = clone $style;
671///
672/// $copy->fontSize = 16.0;
673/// echo $style->fontSize; // 12.0 — original is unchanged
674/// ```
675///
676/// Structs that do not implement `Clone` throw an error when cloned:
677///
678/// ```php
679/// // If MyClass does not implement Clone:
680/// $obj = new MyClass();
681/// $copy = clone $obj; // Error: Trying to clone an uncloneable object of class MyClass
682/// ```
683///
684/// ## Implementing Iterator
685///
686/// To make a Rust class usable with PHP's `foreach` loop, implement the
687/// [`Iterator`](https://www.php.net/manual/en/class.iterator.php) interface.
688/// This requires implementing five methods: `current()`, `key()`, `next()`,
689/// `rewind()`, and `valid()`.
690///
691/// The following example creates a `RangeIterator` that iterates over a range
692/// of integers:
693///
694/// ````rust,no_run,ignore
695/// # #![cfg_attr(windows, feature(abi_vectorcall))]
696/// # extern crate ext_php_rs;
697/// use ext_php_rs::{prelude::*, zend::ce};
698///
699/// #[php_class]
700/// #[php(implements(ce = ce::iterator, stub = "\\Iterator"))]
701/// pub struct RangeIterator {
702/// start: i64,
703/// end: i64,
704/// current: i64,
705/// index: i64,
706/// }
707///
708/// #[php_impl]
709/// impl RangeIterator {
710/// /// Create a new range iterator from start to end (inclusive).
711/// pub fn __construct(start: i64, end: i64) -> Self {
712/// Self {
713/// start,
714/// end,
715/// current: start,
716/// index: 0,
717/// }
718/// }
719///
720/// /// Return the current element.
721/// pub fn current(&self) -> i64 {
722/// self.current
723/// }
724///
725/// /// Return the key of the current element.
726/// pub fn key(&self) -> i64 {
727/// self.index
728/// }
729///
730/// /// Move forward to next element.
731/// pub fn next(&mut self) {
732/// self.current += 1;
733/// self.index += 1;
734/// }
735///
736/// /// Rewind the Iterator to the first element.
737/// pub fn rewind(&mut self) {
738/// self.current = self.start;
739/// self.index = 0;
740/// }
741///
742/// /// Checks if current position is valid.
743/// pub fn valid(&self) -> bool {
744/// self.current <= self.end
745/// }
746/// }
747///
748/// #[php_module]
749/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
750/// module.class::<RangeIterator>()
751/// }
752/// # fn main() {}
753/// ````
754///
755/// Using the iterator in PHP:
756///
757/// ```php
758/// <?php
759///
760/// $range = new RangeIterator(1, 5);
761///
762/// // Use with foreach
763/// foreach ($range as $key => $value) {
764/// echo "$key => $value\n";
765/// }
766/// // Output:
767/// // 0 => 1
768/// // 1 => 2
769/// // 2 => 3
770/// // 3 => 4
771/// // 4 => 5
772///
773/// // Works with iterator functions
774/// $arr = iterator_to_array(new RangeIterator(10, 12));
775/// // [0 => 10, 1 => 11, 2 => 12]
776///
777/// $count = iterator_count(new RangeIterator(1, 100));
778/// // 100
779/// ```
780///
781/// ### Iterator with Mixed Types
782///
783/// You can return different types for keys and values. The following example
784/// uses string keys:
785///
786/// ````rust,no_run,ignore
787/// # #![cfg_attr(windows, feature(abi_vectorcall))]
788/// # extern crate ext_php_rs;
789/// use ext_php_rs::{prelude::*, zend::ce};
790///
791/// #[php_class]
792/// #[php(implements(ce = ce::iterator, stub = "\\Iterator"))]
793/// pub struct MapIterator {
794/// keys: Vec<String>,
795/// values: Vec<String>,
796/// index: usize,
797/// }
798///
799/// #[php_impl]
800/// impl MapIterator {
801/// pub fn __construct() -> Self {
802/// Self {
803/// keys: vec!["first".into(), "second".into(), "third".into()],
804/// values: vec!["one".into(), "two".into(), "three".into()],
805/// index: 0,
806/// }
807/// }
808///
809/// pub fn current(&self) -> Option<String> {
810/// self.values.get(self.index).cloned()
811/// }
812///
813/// pub fn key(&self) -> Option<String> {
814/// self.keys.get(self.index).cloned()
815/// }
816///
817/// pub fn next(&mut self) {
818/// self.index += 1;
819/// }
820///
821/// pub fn rewind(&mut self) {
822/// self.index = 0;
823/// }
824///
825/// pub fn valid(&self) -> bool {
826/// self.index < self.keys.len()
827/// }
828/// }
829///
830/// #[php_module]
831/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
832/// module.class::<MapIterator>()
833/// }
834/// # fn main() {}
835/// ````
836///
837/// ```php
838/// <?php
839///
840/// $map = new MapIterator();
841/// foreach ($map as $key => $value) {
842/// echo "$key => $value\n";
843/// }
844/// // Output:
845/// // first => one
846/// // second => two
847/// // third => three
848/// ```
849// END DOCS FROM classes.md
850#[proc_macro_attribute]
851pub fn php_class(args: TokenStream, input: TokenStream) -> TokenStream {
852 php_class_internal(args.into(), input.into()).into()
853}
854
855#[allow(clippy::needless_pass_by_value)]
856fn php_class_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
857 let input = parse_macro_input2!(input as ItemStruct);
858 if let Err(e) = reject_args(&args, "php_class", PHP_ATTR_HINT) {
859 return e.to_compile_error();
860 }
861
862 class::parser(input).unwrap_or_else(|e| e.to_compile_error())
863}
864
865// BEGIN DOCS FROM enum.md
866/// # `#[php_enum]` Attribute
867///
868/// Enums can be exported to PHP as enums with the `#[php_enum]` attribute
869/// macro. This attribute derives the `RegisteredClass` and `PhpEnum` traits on
870/// your enum. To register the enum use the `enumeration::<EnumName>()` method
871/// on the `ModuleBuilder` in the `#[php_module]` macro.
872///
873/// ## Options
874///
875/// The `#[php_enum]` attribute can be configured with the following options:
876/// - `#[php(name = "EnumName")]` or `#[php(change_case = "snake_case")]`: Sets
877/// the name of the enum in PHP. The default is the `PascalCase` name of the
878/// enum.
879/// - `#[php(allow_native_discriminants)]`: Allows the use of native Rust
880/// discriminants (e.g., `Hearts = 1`).
881/// - `#[php(change_cases_case = "snake_case")]`: Sets the rename rule for every
882/// case that has no `name` or `change_case` of its own. The old name of this
883/// option, `rename_cases`, is a compile error.
884///
885/// PHP enums are always public. `#[php(vis = "...")]` on an enum is a compile
886/// error, and so is any argument passed to the macro itself, such as
887/// `#[php_enum(name = "Suit")]`.
888///
889/// The cases of the enum can be configured with the following options:
890/// - `#[php(name = "CaseName")]` or `#[php(change_case = "snake_case")]`: Sets
891/// the name of the enum case in PHP. The default is the `PascalCase` name of
892/// the case.
893/// - `#[php(value = "value")]` or `#[php(value = 123)]`: Sets the discriminant
894/// value for the enum case. This can be a string or an integer. If not set,
895/// the case will be exported as a simple enum case without a discriminant.
896///
897/// ### Example
898///
899/// This example creates a PHP enum `Suit`.
900///
901/// ```rust,no_run,ignore
902/// # #![cfg_attr(windows, feature(abi_vectorcall))]
903/// # extern crate ext_php_rs;
904/// use ext_php_rs::prelude::*;
905///
906/// #[php_enum]
907/// pub enum Suit {
908/// Hearts,
909/// Diamonds,
910/// Clubs,
911/// Spades,
912/// }
913///
914/// #[php_module]
915/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
916/// module.enumeration::<Suit>()
917/// }
918/// # fn main() {}
919/// ```
920///
921/// ## Backed Enums
922/// Enums can also be backed by either `i64` or `&'static str`. Those values can
923/// be set using the `#[php(value = "value")]` or `#[php(value = 123)]`
924/// attributes on the enum variants.
925///
926/// All variants must have a value of the same type, either all `i64` or all
927/// `&'static str`.
928///
929/// A backed enum converts to and from its value. The macro implements
930/// `From<Enum>` for the value type, and `TryFrom<i64>` or `TryFrom<&str>` for
931/// the enum. `TryFrom` returns `Error::InvalidEnumCase` when no case has the
932/// value.
933///
934/// ```rust,no_run,ignore
935/// # #![cfg_attr(windows, feature(abi_vectorcall))]
936/// # extern crate ext_php_rs;
937/// use ext_php_rs::prelude::*;
938///
939/// #[php_enum]
940/// pub enum Suit {
941/// #[php(value = "hearts")]
942/// Hearts,
943/// #[php(value = "diamonds")]
944/// Diamonds,
945/// #[php(value = "clubs")]
946/// Clubs,
947/// #[php(value = "spades")]
948/// Spades,
949/// }
950/// #[php_module]
951/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
952/// module.enumeration::<Suit>()
953/// }
954/// # fn main() {}
955/// ```
956///
957/// ### 'Native' Discriminators
958/// Native rust discriminants are currently not supported and will not be
959/// exported to PHP.
960///
961/// To avoid confusion a compiler error will be raised if you try to use a
962/// native discriminant. You can ignore this error by adding the
963/// `#[php(allow_native_discriminants)]` attribute to your enum.
964///
965/// ```rust,no_run,ignore
966/// # #![cfg_attr(windows, feature(abi_vectorcall))]
967/// # extern crate ext_php_rs;
968/// use ext_php_rs::prelude::*;
969///
970/// #[php_enum]
971/// #[php(allow_native_discriminants)]
972/// pub enum Suit {
973/// Hearts = 1,
974/// Diamonds = 2,
975/// Clubs = 3,
976/// Spades = 4,
977/// }
978///
979/// #[php_module]
980/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
981/// module.enumeration::<Suit>()
982/// }
983/// # fn main() {}
984/// ```
985///
986///
987/// TODO: Add backed enums example
988// END DOCS FROM enum.md
989#[proc_macro_attribute]
990pub fn php_enum(args: TokenStream, input: TokenStream) -> TokenStream {
991 php_enum_internal(args.into(), input.into()).into()
992}
993
994#[allow(clippy::needless_pass_by_value)]
995fn php_enum_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
996 let input = parse_macro_input2!(input as ItemEnum);
997 if let Err(e) = reject_args(&args, "php_enum", PHP_ATTR_HINT) {
998 return e.to_compile_error();
999 }
1000
1001 enum_::parser(input).unwrap_or_else(|e| e.to_compile_error())
1002}
1003
1004// BEGIN DOCS FROM interface.md
1005/// # `#[php_interface]` Attribute
1006///
1007/// You can export a `Trait` block to PHP. This exports all methods as well as
1008/// constants to PHP on the interface. Trait method SHOULD NOT contain default
1009/// implementations, as these are not supported in PHP interfaces.
1010///
1011/// ## Options
1012///
1013/// By default all constants are renamed to `UPPER_CASE` and all methods are
1014/// renamed to `camelCase`. This can be changed by passing the
1015/// `change_method_case` and `change_constant_case` as `#[php]` attributes on
1016/// the `impl` block. The options are:
1017///
1018/// - `#[php(change_method_case = "snake_case")]` - Renames the method to snake
1019/// case.
1020/// - `#[php(change_constant_case = "snake_case")]` - Renames the constant to
1021/// snake case.
1022///
1023/// See the [`name` and `change_case`](./php.md#name-and-change_case) section
1024/// for a list of all available cases.
1025///
1026/// The macro itself takes no arguments: `#[php_interface(name = "Foo")]` is a
1027/// compile error. Put the options in `#[php(...)]` on the trait.
1028///
1029/// ## Methods
1030///
1031/// A trait method accepts the `name`, `change_case`, `defaults`, `optional` and
1032/// `vis` options. Interfaces cannot declare property accessors or constructors,
1033/// so `getter`, `setter` and `constructor` are compile errors here. See
1034/// [`php_impl`](./impl.md#) for how each accepted option behaves.
1035///
1036/// ## Constants
1037///
1038/// A trait constant accepts the `name` and `change_case` options. Interface
1039/// constants are always public, so `vis` is a compile error here. See
1040/// [`php_impl`](./impl.md#constants) for the value types.
1041///
1042/// ## Example
1043///
1044/// Define an example trait with methods and constant:
1045///
1046/// ```rust,no_run,ignore
1047/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1048/// # extern crate ext_php_rs;
1049/// use ext_php_rs::{prelude::*, types::ZendClassObject};
1050///
1051///
1052/// #[php_interface]
1053/// #[php(name = "Rust\\TestInterface")]
1054/// trait Test {
1055/// const TEST: &'static str = "TEST";
1056///
1057/// fn co();
1058///
1059/// #[php(defaults(value = 0))]
1060/// fn set_value(&mut self, value: i32);
1061/// }
1062///
1063/// #[php_module]
1064/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
1065/// module
1066/// .interface::<PhpInterfaceTest>()
1067/// }
1068///
1069/// # fn main() {}
1070/// ```
1071///
1072/// Using our newly created interface in PHP:
1073///
1074/// ```php
1075/// <?php
1076///
1077/// assert(interface_exists("Rust\TestInterface"));
1078///
1079/// class B implements Rust\TestInterface {
1080///
1081/// public static function co() {}
1082///
1083/// public function setValue(?int $value = 0) {
1084///
1085/// }
1086/// }
1087/// ```
1088///
1089/// ## Interface Inheritance
1090///
1091/// PHP interfaces can extend other interfaces. You can achieve this in two
1092/// ways:
1093///
1094/// ### Using `#[php(extends(...))]`
1095///
1096/// Use the `extends` attribute to extend a built-in PHP interface or another
1097/// Rust-defined interface.
1098///
1099/// For built-in PHP interfaces, use the explicit form:
1100///
1101/// ```rust,no_run,ignore
1102/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1103/// # extern crate ext_php_rs;
1104/// use ext_php_rs::prelude::*;
1105/// use ext_php_rs::zend::ce;
1106///
1107/// #[php_interface]
1108/// #[php(extends(ce = ce::throwable, stub = "\\Throwable"))]
1109/// #[php(name = "MyException")]
1110/// trait MyExceptionInterface {
1111/// fn get_error_code(&self) -> i32;
1112/// }
1113///
1114/// # fn main() {}
1115/// ```
1116///
1117/// For Rust-defined interfaces, you can use the simpler type syntax:
1118///
1119/// ```rust,ignore
1120/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1121/// # extern crate ext_php_rs;
1122/// use ext_php_rs::prelude::*;
1123///
1124/// #[php_interface]
1125/// trait BaseInterface {
1126/// fn base_method(&self) -> i32;
1127/// }
1128///
1129/// #[php_interface]
1130/// #[php(extends(BaseInterface))]
1131/// trait ExtendedInterface {
1132/// fn extended_method(&self) -> String;
1133/// }
1134///
1135/// # fn main() {}
1136/// ```
1137///
1138/// ### Using Rust Trait Bounds
1139///
1140/// You can also use Rust's trait bound syntax. When a trait marked with
1141/// `#[php_interface]` has supertraits, the PHP interface will automatically
1142/// extend those parent interfaces:
1143///
1144/// ```rust,no_run,ignore
1145/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1146/// # extern crate ext_php_rs;
1147/// use ext_php_rs::prelude::*;
1148///
1149/// #[php_interface]
1150/// #[php(name = "Rust\\ParentInterface")]
1151/// trait ParentInterface {
1152/// fn parent_method(&self) -> String;
1153/// }
1154///
1155/// // ChildInterface extends ParentInterface in PHP
1156/// #[php_interface]
1157/// #[php(name = "Rust\\ChildInterface")]
1158/// trait ChildInterface: ParentInterface {
1159/// fn child_method(&self) -> String;
1160/// }
1161///
1162/// #[php_module]
1163/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
1164/// module
1165/// .interface::<PhpInterfaceParentInterface>()
1166/// .interface::<PhpInterfaceChildInterface>()
1167/// }
1168///
1169/// # fn main() {}
1170/// ```
1171///
1172/// In PHP:
1173///
1174/// ```php
1175/// <?php
1176///
1177/// // ChildInterface extends ParentInterface
1178/// assert(is_a('Rust\ChildInterface', 'Rust\ParentInterface', true));
1179/// ```
1180///
1181/// # `#[php_impl_interface]` Attribute
1182///
1183/// The `#[php_impl_interface]` attribute allows a Rust class to implement a
1184/// custom PHP interface defined with `#[php_interface]`. This creates a
1185/// relationship where PHP's `instanceof` and `is_a()` recognize the
1186/// implementation.
1187///
1188/// **Key feature**: The macro automatically registers the trait methods as PHP
1189/// methods on the class. You don't need to duplicate them in a separate
1190/// `#[php_impl]` block.
1191///
1192/// ## Example
1193///
1194/// ```rust,no_run,ignore
1195/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1196/// # extern crate ext_php_rs;
1197/// use ext_php_rs::prelude::*;
1198///
1199/// // Define a custom interface
1200/// #[php_interface]
1201/// #[php(name = "Rust\\Greetable")]
1202/// trait Greetable {
1203/// fn greet(&self) -> String;
1204/// }
1205///
1206/// // Define a class
1207/// #[php_class]
1208/// #[php(name = "Rust\\Greeter")]
1209/// pub struct Greeter {
1210/// name: String,
1211/// }
1212///
1213/// #[php_impl]
1214/// impl Greeter {
1215/// pub fn __construct(name: String) -> Self {
1216/// Self { name }
1217/// }
1218///
1219/// // Note: No need to add greet() here - it's automatically
1220/// // registered by #[php_impl_interface] below
1221/// }
1222///
1223/// // Implement the interface for the class
1224/// // This automatically registers greet() as a PHP method
1225/// #[php_impl_interface]
1226/// impl Greetable for Greeter {
1227/// fn greet(&self) -> String {
1228/// format!("Hello, {}!", self.name)
1229/// }
1230/// }
1231///
1232/// #[php_module]
1233/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
1234/// module
1235/// .interface::<PhpInterfaceGreetable>()
1236/// .class::<Greeter>()
1237/// }
1238///
1239/// # fn main() {}
1240/// ```
1241///
1242/// Using in PHP:
1243///
1244/// ```php
1245/// <?php
1246///
1247/// $greeter = new Rust\Greeter("World");
1248///
1249/// // instanceof works
1250/// assert($greeter instanceof Rust\Greetable);
1251///
1252/// // is_a() works
1253/// assert(is_a($greeter, 'Rust\Greetable'));
1254///
1255/// // The greet() method is available (registered by #[php_impl_interface])
1256/// echo $greeter->greet(); // Output: Hello, World!
1257///
1258/// // Can be used as type hint
1259/// function greet(Rust\Greetable $obj): void {
1260/// echo $obj->greet();
1261/// }
1262///
1263/// greet($greeter);
1264/// ```
1265///
1266/// ## When to Use
1267///
1268/// - Use `#[php_impl_interface]` for custom interfaces you define with
1269/// `#[php_interface]`
1270/// - Use `#[php(implements(ce = ...))]` on `#[php_class]` for built-in PHP
1271/// interfaces like `Iterator`, `ArrayAccess`, `Countable`, etc.
1272///
1273/// See the [Classes documentation](./classes.md#implementing-an-interface) for
1274/// examples of implementing built-in interfaces.
1275///
1276/// ## Cross-Crate Support
1277///
1278/// The `#[php_impl_interface]` macro supports cross-crate interface discovery
1279/// via the [`inventory`](https://crates.io/crates/inventory) crate. This means you can define
1280/// an interface in one crate and implement it in another crate, and the
1281/// implementation will be automatically discovered at link time.
1282///
1283/// ### Example: Defining an Interface in a Library Crate
1284///
1285/// First, create a library crate that defines the interface:
1286///
1287/// ```toml
1288/// # my-interfaces/Cargo.toml
1289/// [package]
1290/// name = "my-interfaces"
1291/// version = "0.1.0"
1292///
1293/// [dependencies]
1294/// ext-php-rs = "0.15"
1295/// ```
1296///
1297/// ```rust,no_run,ignore,ignore
1298/// // my-interfaces/src/lib.rs
1299/// use ext_php_rs::prelude::*;
1300///
1301/// /// A serializable interface that can convert objects to JSON.
1302/// #[php_interface]
1303/// #[php(name = "MyInterfaces\\Serializable")]
1304/// pub trait Serializable {
1305/// fn to_json(&self) -> String;
1306/// }
1307///
1308/// // Re-export the generated PHP interface struct for consumers
1309/// pub use PhpInterfaceSerializable;
1310/// ```
1311///
1312/// ### Example: Implementing the Interface in Another Crate
1313///
1314/// Now create your extension crate that implements the interface:
1315///
1316/// ```toml
1317/// # my-extension/Cargo.toml
1318/// [package]
1319/// name = "my-extension"
1320/// version = "0.1.0"
1321///
1322/// [lib]
1323/// crate-type = ["cdylib"]
1324///
1325/// [dependencies]
1326/// ext-php-rs = "0.15"
1327/// my-interfaces = { path = "../my-interfaces" }
1328/// ```
1329///
1330/// ```rust,no_run,ignore,ignore
1331/// // my-extension/src/lib.rs
1332/// use ext_php_rs::prelude::*;
1333/// use my_interfaces::Serializable;
1334///
1335/// #[php_class]
1336/// #[php(name = "MyExtension\\User")]
1337/// pub struct User {
1338/// name: String,
1339/// email: String,
1340/// }
1341///
1342/// #[php_impl]
1343/// impl User {
1344/// pub fn __construct(name: String, email: String) -> Self {
1345/// Self { name, email }
1346/// }
1347///
1348/// // Note: No need to add to_json() here - it's automatically
1349/// // registered by #[php_impl_interface] below
1350/// }
1351///
1352/// // Register the interface implementation
1353/// // This automatically registers to_json() as a PHP method
1354/// #[php_impl_interface]
1355/// impl Serializable for User {
1356/// fn to_json(&self) -> String {
1357/// format!(r#"{{"name":"{}","email":"{}"}}"#, self.name, self.email)
1358/// }
1359/// }
1360///
1361/// #[php_module]
1362/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
1363/// module
1364/// // Register the interface from the library crate
1365/// .interface::<my_interfaces::PhpInterfaceSerializable>()
1366/// .class::<User>()
1367/// }
1368/// ```
1369///
1370/// ### Using in PHP
1371///
1372/// ```php
1373/// <?php
1374///
1375/// use MyExtension\User;
1376/// use MyInterfaces\Serializable;
1377///
1378/// $user = new User("John", "john@example.com");
1379///
1380/// // instanceof works across crates
1381/// assert($user instanceof Serializable);
1382///
1383/// // Type hints work
1384/// function serialize_object(Serializable $obj): string {
1385/// return $obj->toJson();
1386/// }
1387///
1388/// echo serialize_object($user);
1389/// // Output: {"name":"John","email":"john@example.com"}
1390/// ```
1391///
1392/// ### Important Notes
1393///
1394/// 1. **Automatic method registration**: The `#[php_impl_interface]` macro
1395/// automatically registers all trait methods as PHP methods on the class.
1396/// You don't need to duplicate them in a `#[php_impl]` block.
1397///
1398/// 2. **Interface registration**: The interface must be registered in the
1399/// `#[php_module]` function using `.interface::<PhpInterfaceName>()`.
1400///
1401/// 3. **Link-time discovery**: The `inventory` crate uses link-time
1402/// registration for interface discovery, so all implementations are
1403/// automatically discovered when the final binary is linked.
1404///
1405/// 4. **Method names**: Each method gets the PHP name that `#[php_interface]`
1406/// declared for it. The trait's `change_method_case` and the `name` or
1407/// `change_case` of a trait method apply to the class too.
1408/// `#[php_impl_interface]` takes no arguments.
1409// END DOCS FROM interface.md
1410#[proc_macro_attribute]
1411pub fn php_interface(args: TokenStream, input: TokenStream) -> TokenStream {
1412 php_interface_internal(args.into(), input.into()).into()
1413}
1414
1415#[allow(clippy::needless_pass_by_value)]
1416fn php_interface_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
1417 let input = parse_macro_input2!(input as ItemTrait);
1418 if let Err(e) = reject_args(&args, "php_interface", PHP_ATTR_HINT) {
1419 return e.to_compile_error();
1420 }
1421
1422 interface::parser(input).unwrap_or_else(|e| e.to_compile_error())
1423}
1424
1425// BEGIN DOCS FROM function.md
1426/// # `#[php_function]` Attribute
1427///
1428/// Used to annotate functions which should be exported to PHP. Note that this
1429/// should not be used on class methods - see the `#[php_impl]` macro for that.
1430///
1431/// See the [list of types](../types/index.md) that are valid as parameter and
1432/// return types.
1433///
1434/// A function accepts the `name`, `change_case`, `defaults` and `optional`
1435/// options. PHP functions have no visibility, so `#[php(vis = "...")]` is a
1436/// compile error on a function.
1437///
1438/// ## Optional parameters
1439///
1440/// An `Option<T>` parameter accepts `null` and can be omitted. The macro reads
1441/// this from the type through `FromZvalMut::NULLABLE`, so a type alias such as
1442/// `type MaybeAge = Option<i64>` or the qualified path
1443/// `std::option::Option<i64>` behaves like `Option<i64>`. The trailing run of
1444/// parameters that are nullable or have a default value is optional.
1445///
1446/// ```rust,no_run,ignore
1447/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1448/// # extern crate ext_php_rs;
1449/// use ext_php_rs::prelude::*;
1450///
1451/// #[php_function]
1452/// pub fn greet(name: String, age: Option<i32>) -> String {
1453/// let mut greeting = format!("Hello, {}!", name);
1454///
1455/// if let Some(age) = age {
1456/// greeting += &format!(" You are {} years old.", age);
1457/// }
1458///
1459/// greeting
1460/// }
1461///
1462/// #[php_module]
1463/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1464/// module.function(wrap_function!(greet))
1465/// }
1466/// # fn main() {}
1467/// ```
1468///
1469/// Default parameter values can also be set for optional parameters. This is
1470/// done through the `#[php(defaults)]` attribute option. When an optional
1471/// parameter has a default, it does not need to be a variant of `Option`. The
1472/// default expression is converted into the parameter type with `Into`, and the
1473/// type must implement `StubLiteral`, which every scalar, string, `Option`,
1474/// `Vec` and `HashMap` does. The module renders the value once at load time
1475/// into the stub file and into `ReflectionParameter::getDefaultValue()`. Each
1476/// key in `defaults` must name a parameter of the function. If a key names no
1477/// parameter, the build fails:
1478///
1479/// ```rust,no_run,ignore
1480/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1481/// # extern crate ext_php_rs;
1482/// use ext_php_rs::prelude::*;
1483///
1484/// #[php_function]
1485/// #[php(defaults(offset = 0))]
1486/// pub fn rusty_strpos(haystack: &str, needle: &str, offset: i64) -> Option<usize> {
1487/// let haystack: String = haystack.chars().skip(offset as usize).collect();
1488/// haystack.find(needle)
1489/// }
1490///
1491/// #[php_module]
1492/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1493/// module.function(wrap_function!(rusty_strpos))
1494/// }
1495/// # fn main() {}
1496/// ```
1497///
1498/// Note that if there is a non-optional argument after an argument that is a
1499/// variant of `Option<T>`, the `Option<T>` argument will be deemed a nullable
1500/// argument rather than an optional argument.
1501///
1502/// ```rust,no_run,ignore
1503/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1504/// # extern crate ext_php_rs;
1505/// use ext_php_rs::prelude::*;
1506///
1507/// /// `age` will be deemed required and nullable rather than optional.
1508/// #[php_function]
1509/// pub fn greet(name: String, age: Option<i32>, description: String) -> String {
1510/// let mut greeting = format!("Hello, {}!", name);
1511///
1512/// if let Some(age) = age {
1513/// greeting += &format!(" You are {} years old.", age);
1514/// }
1515///
1516/// greeting += &format!(" {}.", description);
1517/// greeting
1518/// }
1519///
1520/// #[php_module]
1521/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1522/// module.function(wrap_function!(greet))
1523/// }
1524/// # fn main() {}
1525/// ```
1526///
1527/// You can also specify the optional arguments if you want to have nullable
1528/// arguments before optional arguments. This is done through the `optional`
1529/// attribute option, which names the first optional parameter. PHP callers can
1530/// omit that parameter and every parameter after it. Each of these parameters
1531/// must be an `Option<T>`, have a default, or be the variadic `&[T]` tail.
1532/// Otherwise the build fails with an error on the type of that parameter. If
1533/// `optional` names no parameter, the build also fails:
1534///
1535/// ```rust,no_run,ignore
1536/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1537/// # extern crate ext_php_rs;
1538/// use ext_php_rs::prelude::*;
1539///
1540/// /// `age` will be deemed required and nullable rather than optional,
1541/// /// while description will be optional.
1542/// #[php_function]
1543/// #[php(optional = "description")]
1544/// pub fn greet(name: String, age: Option<i32>, description: Option<String>) -> String {
1545/// let mut greeting = format!("Hello, {}!", name);
1546///
1547/// if let Some(age) = age {
1548/// greeting += &format!(" You are {} years old.", age);
1549/// }
1550///
1551/// if let Some(description) = description {
1552/// greeting += &format!(" {}.", description);
1553/// }
1554///
1555/// greeting
1556/// }
1557///
1558/// #[php_module]
1559/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1560/// module.function(wrap_function!(greet))
1561/// }
1562/// # fn main() {}
1563/// ```
1564///
1565/// ## Variadic Functions
1566///
1567/// A slice parameter `&[T]` in last position is variadic, the equivalent of the
1568/// PHP `...$args` syntax. `T` is any argument type: `&[&Zval]` receives the raw
1569/// values, `&[i64]` receives converted integers. A variadic parameter that is
1570/// not the last parameter is a compile error. The slice must be spelled in the
1571/// signature; a type alias that hides the slice is not detected.
1572///
1573/// ```rust,no_run,ignore
1574/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1575/// # extern crate ext_php_rs;
1576/// use ext_php_rs::{prelude::*, types::Zval};
1577///
1578/// /// This can be called from PHP as `add(1, 2, 3, 4, 5)`
1579/// #[php_function]
1580/// pub fn add(number: u32, numbers:&[&Zval]) -> u32 {
1581/// // numbers is a slice of 4 Zvals all of type long
1582/// number
1583/// }
1584///
1585/// #[php_module]
1586/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1587/// module.function(wrap_function!(add))
1588/// }
1589/// # fn main() {}
1590/// ```
1591///
1592/// ## Performance
1593///
1594/// The `#[php_function]` macro generates a zero-allocation fast path that reads
1595/// arguments directly from the PHP call frame, matching how native C extensions
1596/// parse parameters. This applies automatically — no configuration needed.
1597///
1598/// The same fast path is used for class methods defined with `#[php_impl]`,
1599/// including instance methods (`&self`, `&mut self`) and static methods.
1600///
1601/// ```rust,no_run,ignore
1602/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1603/// # extern crate ext_php_rs;
1604/// # use ext_php_rs::prelude::*;
1605/// // Fast path: standalone functions
1606/// #[php_function]
1607/// pub fn fast(name: String, age: Option<i32>) -> String { name }
1608///
1609/// // Fast path: parameters with defaults
1610/// #[php_function]
1611/// #[php(defaults(offset = 0))]
1612/// pub fn also_fast(haystack: &str, offset: i64) -> i64 { offset }
1613/// # fn main() {}
1614/// ```
1615///
1616/// ```rust,ignore
1617/// #[php_impl]
1618/// impl MyClass {
1619/// // Fast path: static methods
1620/// pub fn create(n: i32) -> Self { /* ... */ }
1621///
1622/// // Fast path: instance methods
1623/// pub fn get_value(&self, offset: i32) -> i32 { /* ... */ }
1624/// }
1625/// ```
1626///
1627/// The only case that falls back to the runtime argument parser is when using
1628/// variadic arguments (`&[T]`, the Rust equivalent of PHP's `...$args`):
1629///
1630/// ```rust,no_run,ignore
1631/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1632/// # extern crate ext_php_rs;
1633/// # use ext_php_rs::{prelude::*, types::Zval};
1634/// // Slower path: variadic arguments require runtime parsing
1635/// #[php_function]
1636/// pub fn add(first: u32, rest: &[&Zval]) -> u32 { first }
1637/// # fn main() {}
1638/// ```
1639///
1640/// ## Returning `Result<T, E>`
1641///
1642/// You can also return a `Result` from the function. The error variant will be
1643/// translated into an exception and thrown. See the section on
1644/// [exceptions](../exceptions.md) for more details.
1645// END DOCS FROM function.md
1646#[proc_macro_attribute]
1647pub fn php_function(args: TokenStream, input: TokenStream) -> TokenStream {
1648 php_function_internal(args.into(), input.into()).into()
1649}
1650
1651#[allow(clippy::needless_pass_by_value)]
1652fn php_function_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
1653 let input = parse_macro_input2!(input as ItemFn);
1654 if let Err(e) = reject_args(&args, "php_function", PHP_ATTR_HINT) {
1655 return e.to_compile_error();
1656 }
1657
1658 function::parser(input).unwrap_or_else(|e| e.to_compile_error())
1659}
1660
1661// BEGIN DOCS FROM constant.md
1662/// # `#[php_const]` Attribute
1663///
1664/// Exports a Rust constant as a global PHP constant. The constant can be any
1665/// type that implements `IntoConst`.
1666///
1667/// The `wrap_constant!()` macro can be used to simplify the registration of
1668/// constants. It sets the name and doc comments for the constant.
1669///
1670/// You can rename the const with options:
1671///
1672/// - `name` - Allows you to rename the property, e.g. `#[php(name =
1673/// "new_name")]`
1674/// - `change_case` - Allows you to rename the property using rename rules, e.g.
1675/// `#[php(change_case = "PascalCase")]`
1676///
1677/// A global constant has no visibility. `#[php(vis = "...")]` on a
1678/// `#[php_const]` is a compile error. Use `vis` on constants inside a
1679/// `#[php_impl]` block.
1680///
1681/// ## Examples
1682///
1683/// ```rust,no_run,ignore
1684/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1685/// # extern crate ext_php_rs;
1686/// use ext_php_rs::prelude::*;
1687///
1688/// #[php_const]
1689/// const TEST_CONSTANT: i32 = 100;
1690///
1691/// #[php_const]
1692/// #[php(name = "I_AM_RENAMED")]
1693/// const TEST_CONSTANT_THE_SECOND: i32 = 42;
1694///
1695/// #[php_const]
1696/// const ANOTHER_STRING_CONST: &'static str = "Hello world!";
1697///
1698/// #[php_module]
1699/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1700/// module
1701/// .constant(wrap_constant!(TEST_CONSTANT))
1702/// .constant(wrap_constant!(TEST_CONSTANT_THE_SECOND))
1703/// .constant(("MANUAL_CONSTANT", ANOTHER_STRING_CONST, &[]))
1704/// }
1705/// # fn main() {}
1706/// ```
1707///
1708/// ## PHP usage
1709///
1710/// ```php
1711/// <?php
1712///
1713/// var_dump(TEST_CONSTANT); // int(100)
1714/// var_dump(I_AM_RENAMED); // int(42)
1715/// var_dump(MANUAL_CONSTANT); // string(12) "Hello world!"
1716/// ```
1717// END DOCS FROM constant.md
1718#[proc_macro_attribute]
1719pub fn php_const(args: TokenStream, input: TokenStream) -> TokenStream {
1720 php_const_internal(args.into(), input.into()).into()
1721}
1722
1723#[allow(clippy::needless_pass_by_value)]
1724fn php_const_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
1725 let input = parse_macro_input2!(input as ItemConst);
1726 if let Err(e) = reject_args(&args, "php_const", PHP_ATTR_HINT) {
1727 return e.to_compile_error();
1728 }
1729
1730 constant::parser(input).unwrap_or_else(|e| e.to_compile_error())
1731}
1732
1733// BEGIN DOCS FROM module.md
1734/// # `#[php_module]` Attribute
1735///
1736/// The module macro is used to annotate the `get_module` function, which is
1737/// used by the PHP interpreter to retrieve information about your extension,
1738/// including the name, version, functions and extra initialization functions.
1739/// Regardless if you use this macro, your extension requires a `extern "C" fn
1740/// get_module()` so that PHP can get this information.
1741///
1742/// The function is renamed to `get_module` if you have used another name. The
1743/// macro also exports a crate-prefixed `<crate_name>_get_module` alias, used
1744/// when [statically linking the extension into
1745/// php-src](../advanced/static_linking.md). Building with
1746/// `EXT_PHP_RS_STATIC_EXT=1` removes the unmangled `get_module` export (the
1747/// function itself stays callable from Rust) so a statically linked
1748/// extension cannot collide with another extension exporting `get_module`.
1749/// Dynamic builds (`extension=` / `dl()`) must not set the variable.
1750///
1751/// The function is passed an instance of `ModuleBuilder` which allows you to
1752/// register the following (if required):
1753///
1754/// - Functions, classes, and constants
1755/// - Extension and request startup and shutdown functions.
1756/// - Read more about the PHP extension lifecycle [here](https://www.phpinternalsbook.com/php7/extensions_design/php_lifecycle.html).
1757/// - PHP extension information function
1758/// - Used by the `phpinfo()` function to get information about your
1759/// extension.
1760///
1761/// Classes and constants are not registered with PHP in the `get_module`
1762/// function. These are registered inside the extension startup function.
1763///
1764/// ## Startup failures
1765///
1766/// If a constant, an interface, a class or an enum cannot be registered, the
1767/// generated startup function logs the cause as an `E_CORE_WARNING` and returns
1768/// `FAILURE`. PHP then reports `Unable to start <extension> module`. At engine
1769/// startup PHP exits. From `dl()` the request fails. A panic during
1770/// registration gets the same treatment. The process does not abort in either
1771/// case.
1772///
1773/// `get_module` cannot report a failure, because PHP reads the returned entry
1774/// without a check. If the `ModuleBuilder` cannot become a module entry, for
1775/// example because a function name, an argument name or the module name
1776/// contains a NUL byte, the macro returns a placeholder entry with the name of
1777/// the crate. The startup function of that entry logs the build error and fails
1778/// as described above.
1779///
1780/// The `startup` function that you name in `#[php_module(startup = ...)]` is
1781/// your own `extern "C"` function. A panic inside it is not caught.
1782///
1783/// ## Usage
1784///
1785/// ```rust,no_run,ignore
1786/// # #![cfg_attr(windows, feature(abi_vectorcall))]
1787/// # extern crate ext_php_rs;
1788/// use ext_php_rs::{
1789/// prelude::*,
1790/// zend::ModuleEntry,
1791/// info_table_start,
1792/// info_table_row,
1793/// info_table_end
1794/// };
1795///
1796/// #[php_const]
1797/// pub const MY_CUSTOM_CONST: &'static str = "Hello, world!";
1798///
1799/// #[php_class]
1800/// pub struct Test {
1801/// a: i32,
1802/// b: i32
1803/// }
1804/// #[php_function]
1805/// pub fn hello_world() -> &'static str {
1806/// "Hello, world!"
1807/// }
1808///
1809/// /// Used by the `phpinfo()` function and when you run `php -i`.
1810/// /// This will probably be simplified with another macro eventually!
1811/// pub extern "C" fn php_module_info(_module: *mut ModuleEntry) {
1812/// info_table_start!();
1813/// info_table_row!("my extension", "enabled");
1814/// info_table_end!();
1815/// }
1816///
1817/// #[php_module]
1818/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
1819/// module
1820/// .constant(wrap_constant!(MY_CUSTOM_CONST))
1821/// .class::<Test>()
1822/// .function(wrap_function!(hello_world))
1823/// .info_function(php_module_info)
1824/// }
1825/// # fn main() {}
1826/// ```
1827// END DOCS FROM module.md
1828#[proc_macro_attribute]
1829pub fn php_module(args: TokenStream, input: TokenStream) -> TokenStream {
1830 php_module_internal(args.into(), input.into()).into()
1831}
1832
1833#[allow(clippy::needless_pass_by_value)]
1834fn php_module_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
1835 let input = parse_macro_input2!(input as ItemFn);
1836 if let Err(e) = reject_args(&args, "php_module", PHP_ATTR_HINT) {
1837 return e.to_compile_error();
1838 }
1839
1840 module::parser(input).unwrap_or_else(|e| e.to_compile_error())
1841}
1842
1843// BEGIN DOCS FROM impl.md
1844/// # `#[php_impl]` Attribute
1845///
1846/// You can export an entire `impl` block to PHP. This exports all methods as
1847/// well as constants to PHP on the class that it is implemented on. This
1848/// requires the `#[php_class]` macro to already be used on the underlying
1849/// struct. Trait implementations cannot be exported to PHP. Only one `impl`
1850/// block can be exported per class.
1851///
1852/// If you do not want a function exported to PHP, you should place it in a
1853/// separate `impl` block.
1854///
1855/// If you want to use async Rust, use `#[php_async_impl]`, instead: see [here
1856/// »](./async_impl.md) for more info.
1857///
1858/// ## Options
1859///
1860/// By default all constants are renamed to `UPPER_CASE` and all methods are
1861/// renamed to camelCase. This can be changed by passing the
1862/// `change_method_case` and `change_constant_case` as `#[php]` attributes on
1863/// the `impl` block. The options are:
1864///
1865/// - `#[php(change_method_case = "snake_case")]` - Renames the method to snake
1866/// case.
1867/// - `#[php(change_constant_case = "snake_case")]` - Renames the constant to
1868/// snake case.
1869///
1870/// See the [`name` and `change_case`](./php.md#name-and-change_case) section
1871/// for a list of all available cases.
1872///
1873/// ## Methods
1874///
1875/// Methods basically follow the same rules as functions, so read about the
1876/// [`php_function`] macro first. The primary difference between functions and
1877/// methods is they are bounded by their class object.
1878///
1879/// Both instance and static methods benefit from the same zero-allocation fast
1880/// path as standalone functions. See the
1881/// [Performance](./function.md#performance) section for details.
1882///
1883/// Class methods can take a `&self` or `&mut self` parameter. They cannot take
1884/// a consuming `self` parameter. Static methods can omit this `self` parameter.
1885///
1886/// To access the underlying Zend object, you can take a reference to a
1887/// `ZendClassObject<T>` in place of the self parameter, where the parameter
1888/// must be named `self_`. This can also be used to return a reference to
1889/// `$this`.
1890///
1891/// The rest of the options are passed as separate attributes:
1892///
1893/// - `#[php(defaults(i = 5, b = "\"hello\""))]` - Sets the default value for
1894/// parameter(s). Each key must name a parameter.
1895/// - `#[php(optional = i)]` - Sets the first optional parameter. Note that this
1896/// also sets the remaining parameters as optional, so each of them must be an
1897/// `Option<T>` or have a default. Otherwise the build fails.
1898/// - `#[php(vis = "public")]`, `#[php(vis = "protected")]` and `#[php(vis =
1899/// "private")]` - Sets the visibility of the method.
1900/// - `#[php(name = "method_name")]` - Renames the PHP method to a different
1901/// identifier, without renaming the Rust method name.
1902/// - `#[php(final)]` - Makes the method final (cannot be overridden in
1903/// subclasses).
1904/// - `#[php(abstract)]` - Makes the method abstract (must be implemented by
1905/// subclasses). Can only be used in abstract classes.
1906///
1907/// The `#[php(defaults)]` and `#[php(optional)]` attributes operate the same as
1908/// the equivalent function attribute parameters. A `self_` parameter is the PHP
1909/// object, not a PHP parameter, so `defaults` and `optional` cannot name it. A
1910/// `getter` or `setter` method accepts neither option.
1911///
1912/// ### Static Methods
1913///
1914/// Methods that do not take a `&self` or `&mut self` parameter are
1915/// automatically exported as static methods. These can be called on the class
1916/// itself without creating an instance.
1917///
1918/// ```rust,ignore
1919/// #[php_impl]
1920/// impl MyClass {
1921/// // Static method - no self parameter
1922/// pub fn create_default() -> Self {
1923/// Self { /* ... */ }
1924/// }
1925///
1926/// // Instance method - takes &self
1927/// pub fn get_value(&self) -> i32 {
1928/// self.value
1929/// }
1930/// }
1931/// ```
1932///
1933/// From PHP:
1934///
1935/// ```php
1936/// $obj = MyClass::createDefault(); // Static call
1937/// $val = $obj->getValue(); // Instance call
1938/// ```
1939///
1940/// ### Final Methods
1941///
1942/// Methods marked with `#[php(final)]` cannot be overridden in subclasses. This
1943/// is useful when you want to prevent modification of critical functionality.
1944///
1945/// ```rust,ignore
1946/// use ext_php_rs::prelude::*;
1947///
1948/// #[php_class]
1949/// pub struct SecureClass;
1950///
1951/// #[php_impl]
1952/// impl SecureClass {
1953/// #[php(final)]
1954/// pub fn secure_method(&self) -> &str {
1955/// "This cannot be overridden"
1956/// }
1957///
1958/// // Final static methods are also supported
1959/// #[php(final)]
1960/// pub fn secure_static() -> i32 {
1961/// 42
1962/// }
1963/// }
1964/// ```
1965///
1966/// ### Abstract Methods
1967///
1968/// Methods marked with `#[php(abstract)]` must be implemented by subclasses.
1969/// Abstract methods can only be defined in abstract classes (classes with
1970/// `ClassFlags::Abstract`).
1971///
1972/// ```rust,ignore
1973/// use ext_php_rs::prelude::*;
1974/// use ext_php_rs::flags::ClassFlags;
1975///
1976/// #[php_class]
1977/// #[php(flags = ClassFlags::Abstract)]
1978/// pub struct AbstractShape;
1979///
1980/// #[php_impl]
1981/// impl AbstractShape {
1982/// // Protected constructor for subclasses
1983/// #[php(vis = "protected")]
1984/// pub fn __construct() -> Self {
1985/// Self
1986/// }
1987///
1988/// // Abstract method - subclasses must implement this.
1989/// // The body is never called; use unimplemented!() as a placeholder.
1990/// #[php(abstract)]
1991/// pub fn area(&self) -> f64 {
1992/// unimplemented!()
1993/// }
1994///
1995/// // Concrete method in abstract class
1996/// pub fn describe(&self) -> String {
1997/// format!("A shape with area {}", self.area())
1998/// }
1999/// }
2000/// ```
2001///
2002/// **Note:** Abstract method bodies are never called - they exist only because
2003/// Rust syntax requires a body for methods in `impl` blocks. Use
2004/// `unimplemented!()` as a clear placeholder.
2005///
2006/// **Note:** If you try to use `#[php(abstract)]` on a method in a non-abstract
2007/// class, you will get a compile-time error.
2008///
2009/// **Note:** PHP does not support abstract static methods. If you need static
2010/// behavior that can be customized by subclasses, use a regular instance method
2011/// or the [late static binding](https://www.php.net/manual/en/language.oop5.late-static-bindings.php)
2012/// pattern in PHP.
2013///
2014/// ### Constructors
2015///
2016/// By default, if a class does not have a constructor, it is not constructable
2017/// from PHP. It can only be returned from a Rust function to PHP.
2018///
2019/// Constructors are Rust methods which can take any amount of parameters and
2020/// returns either `Self` or `Result<Self, E>`, where `E: Into<PhpException>`.
2021/// When the error variant of `Result` is encountered, it is thrown as an
2022/// exception and the class is not constructed.
2023///
2024/// Constructors are designated by either naming the method `__construct` or by
2025/// annotating a method with the `#[php(constructor)]` attribute. Note that when
2026/// using the attribute, the function is not exported to PHP like a regular
2027/// method.
2028///
2029/// Constructors cannot use the visibility or rename attributes listed above.
2030///
2031/// ## Constants
2032///
2033/// Constants are defined as regular Rust `impl` constants. Any type that
2034/// implements `IntoZval` can be used as a constant. A constant accepts the
2035/// `name`, `change_case` and `vis` options. The default visibility is `public`.
2036///
2037/// ```rust,no_run,ignore
2038/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2039/// # extern crate ext_php_rs;
2040/// # use ext_php_rs::prelude::*;
2041/// #[php_class]
2042/// pub struct Limits;
2043///
2044/// #[php_impl]
2045/// impl Limits {
2046/// const MAX_USERS: i64 = 100;
2047///
2048/// #[php(vis = "protected")]
2049/// const MAX_RETRIES: i64 = 3;
2050///
2051/// #[php(vis = "private", name = "SEED")]
2052/// const RANDOM_SEED: i64 = 42;
2053/// }
2054/// # fn main() {}
2055/// ```
2056///
2057/// PHP sees `public const MAX_USERS`, `protected const MAX_RETRIES` and
2058/// `private const SEED`. Reflection reports the same visibility, and the
2059/// generated stubs print it.
2060///
2061/// ## Property getters and setters
2062///
2063/// A class can expose a property whose value comes from Rust methods. Mark the
2064/// method that reads the value with `#[php(getter)]` and the method that writes
2065/// it with `#[php(setter)]`. The property name is the method name without its
2066/// `get_` or `set_` prefix, converted to camelCase. This is the same rule as
2067/// for `#[php(prop)]` fields: `get_first_name` gives the property `firstName`.
2068/// Properties are declared in the order in which their first
2069/// getter or setter appears in the `impl` block, after the `#[php(prop)]` field
2070/// properties of the struct.
2071///
2072/// If you want a different property name, pass a `name` or `change_case` option
2073/// to the `#[php]` attribute: `#[php(getter, name = "first_name")]` or
2074/// `#[php(getter, change_case = "snake_case")]`.
2075///
2076/// A property does not need both methods. A property with only a getter is
2077/// read-only. A property with only a setter is write-only.
2078///
2079/// One method cannot be both a getter and a setter. A property cannot have two
2080/// getters or two setters. The getter and the setter of one property must have
2081/// the same visibility. Each of these cases is a compile error.
2082///
2083/// A method property cannot have the same name as a `#[php(prop)]` field of the
2084/// struct. The two macros are expanded separately, so this is not a compile
2085/// error. The extension fails to load with this error:
2086///
2087/// ```text
2088/// property `Book::$title` is declared twice
2089/// ```
2090///
2091/// The PHP type of the property comes from the return type of the getter. If
2092/// the property has no getter, the type comes from the argument of the setter.
2093/// The documentation of the property comes from the getter, or from the setter
2094/// when the getter has none. Like field properties, the type must implement
2095/// `IntoZval` and `FromZval`.
2096///
2097/// ## Example
2098///
2099/// Continuing on from our `Human` example in the structs section, we will
2100/// define a constructor, as well as getters for the properties. We will also
2101/// define a constant for the maximum age of a `Human`.
2102///
2103/// ```rust,no_run,ignore
2104/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2105/// # extern crate ext_php_rs;
2106/// use ext_php_rs::{prelude::*, types::ZendClassObject};
2107///
2108/// #[php_class]
2109/// #[derive(Debug, Default)]
2110/// pub struct Human {
2111/// name: String,
2112/// age: i32,
2113/// #[php(prop)]
2114/// address: String,
2115/// }
2116///
2117/// #[php_impl]
2118/// impl Human {
2119/// const MAX_AGE: i32 = 100;
2120///
2121/// // No `#[constructor]` attribute required here - the name is `__construct`.
2122/// pub fn __construct(name: String, age: i32) -> Self {
2123/// Self {
2124/// name,
2125/// age,
2126/// address: String::new()
2127/// }
2128/// }
2129///
2130/// #[php(getter)]
2131/// pub fn get_name(&self) -> String {
2132/// self.name.to_string()
2133/// }
2134///
2135/// #[php(setter)]
2136/// pub fn set_name(&mut self, name: String) {
2137/// self.name = name;
2138/// }
2139///
2140/// #[php(getter)]
2141/// pub fn get_age(&self) -> i32 {
2142/// self.age
2143/// }
2144///
2145/// pub fn introduce(&self) {
2146/// println!("My name is {} and I am {} years old. I live at {}.", self.name, self.age, self.address);
2147/// }
2148///
2149/// pub fn get_raw_obj(self_: &mut ZendClassObject<Human>) -> &mut ZendClassObject<Human> {
2150/// dbg!(self_)
2151/// }
2152///
2153/// pub fn get_max_age() -> i32 {
2154/// Self::MAX_AGE
2155/// }
2156/// }
2157/// #[php_module]
2158/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
2159/// module.class::<Human>()
2160/// }
2161/// # fn main() {}
2162/// ```
2163///
2164/// Using our newly created class in PHP:
2165///
2166/// ```php
2167/// <?php
2168///
2169/// $me = new Human('David', 20);
2170///
2171/// $me->introduce(); // My name is David and I am 20 years old.
2172/// var_dump(Human::get_max_age()); // int(100)
2173/// var_dump(Human::MAX_AGE); // int(100)
2174/// ```
2175///
2176/// [`php_async_impl`]: ./async_impl.md
2177// END DOCS FROM impl.md
2178#[proc_macro_attribute]
2179pub fn php_impl(args: TokenStream, input: TokenStream) -> TokenStream {
2180 php_impl_internal(args.into(), input.into()).into()
2181}
2182
2183#[allow(clippy::needless_pass_by_value)]
2184fn php_impl_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
2185 let input = parse_macro_input2!(input as ItemImpl);
2186 if let Err(e) = reject_args(&args, "php_impl", PHP_ATTR_HINT) {
2187 return e.to_compile_error();
2188 }
2189
2190 impl_::parser(input).unwrap_or_else(|e| e.to_compile_error())
2191}
2192
2193/// # `#[php_impl_interface]` Attribute
2194///
2195/// Marks a trait implementation as implementing a PHP interface. This allows
2196/// Rust structs marked with `#[php_class]` to implement Rust traits marked
2197/// with `#[php_interface]`, and have PHP recognize the relationship.
2198///
2199/// **Key feature**: The macro automatically registers the trait methods as PHP
2200/// methods on the class. You don't need to duplicate them in a separate
2201/// `#[php_impl]` block.
2202///
2203/// ## Usage
2204///
2205/// ```rust,no_run,ignore
2206/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2207/// # extern crate ext_php_rs;
2208/// use ext_php_rs::prelude::*;
2209///
2210/// #[php_interface]
2211/// trait MyInterface {
2212/// fn my_method(&self) -> String;
2213/// }
2214///
2215/// #[php_class]
2216/// struct MyClass;
2217///
2218/// // The trait method my_method() is automatically registered as a PHP method
2219/// #[php_impl_interface]
2220/// impl MyInterface for MyClass {
2221/// fn my_method(&self) -> String {
2222/// "Hello from MyClass!".to_string()
2223/// }
2224/// }
2225///
2226/// #[php_module]
2227/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
2228/// module
2229/// .interface::<PhpInterfaceMyInterface>()
2230/// .class::<MyClass>()
2231/// }
2232/// # fn main() {}
2233/// ```
2234///
2235/// After registration, PHP's `is_a($obj, 'MyInterface')` will return `true`
2236/// for instances of `MyClass`, and `$obj->myMethod()` will be callable.
2237///
2238/// ## Method names
2239///
2240/// Each method is registered under the name that `#[php_interface]` gave it.
2241/// The trait's `change_method_case` and the `name` or `change_case` of a trait
2242/// method apply to the class too. The macro takes no arguments:
2243/// `#[php_impl_interface(change_method_case = "...")]` is a compile error.
2244///
2245/// ## Requirements
2246///
2247/// - The trait must be marked with `#[php_interface]`
2248/// - The struct must be marked with `#[php_class]`
2249/// - The interface must be registered before the class in the module builder
2250#[proc_macro_attribute]
2251pub fn php_impl_interface(args: TokenStream, input: TokenStream) -> TokenStream {
2252 php_impl_interface_internal(args.into(), input.into()).into()
2253}
2254
2255#[allow(clippy::needless_pass_by_value)]
2256fn php_impl_interface_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
2257 let input = parse_macro_input2!(input as ItemImpl);
2258 let attr_args = match darling::ast::NestedMeta::parse_meta_list(args) {
2259 Ok(v) => v,
2260 Err(e) => return e.to_compile_error(),
2261 };
2262 let args = match impl_interface::PhpImplInterfaceArgs::from_list(&attr_args) {
2263 Ok(v) => v,
2264 Err(e) => return e.write_errors(),
2265 };
2266
2267 impl_interface::parser(&args, &input).unwrap_or_else(|e| e.to_compile_error())
2268}
2269
2270// BEGIN DOCS FROM extern.md
2271/// # `#[php_extern]` Attribute
2272///
2273/// Attribute used to annotate `extern` blocks which are deemed as PHP
2274/// functions.
2275///
2276/// This allows you to 'import' PHP functions into Rust so that they can be
2277/// called like regular Rust functions. Parameters can be any type that
2278/// implements [`IntoZval`], and the return type can be anything that implements
2279/// [`From<Zval>`] (notice how [`Zval`] is consumed rather than borrowed in this
2280/// case).
2281///
2282/// Unlike most other attributes, this does not need to be placed inside a
2283/// `#[php_module]` block.
2284///
2285/// # Panics
2286///
2287/// The function can panic when called under a few circumstances:
2288///
2289/// * The function could not be found or was not callable.
2290/// * One of the parameters could not be converted into a [`Zval`].
2291/// * The actual function call failed internally.
2292/// * The output [`Zval`] could not be parsed into the output type.
2293///
2294/// Inside a function that PHP calls, these panics become a PHP `Error`. The
2295/// process does not abort. See [Exceptions](../exceptions.md#panics).
2296///
2297/// The last point can be important when interacting with functions that return
2298/// unions, such as [`strpos`] which can return an integer or a boolean. In this
2299/// case, a [`Zval`] should be returned as parsing a boolean to an integer is
2300/// invalid, and vice versa.
2301///
2302/// # Example
2303///
2304/// This `extern` block imports the [`strpos`] function from PHP. Notice that
2305/// the string parameters can take either [`String`] or [`&str`], the optional
2306/// parameter `offset` is an [`Option<i64>`], and the return value is a [`Zval`]
2307/// as the return type is an integer-boolean union.
2308///
2309/// ```rust,no_run,ignore
2310/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2311/// # extern crate ext_php_rs;
2312/// use ext_php_rs::{
2313/// prelude::*,
2314/// types::Zval,
2315/// };
2316///
2317/// #[php_extern]
2318/// extern "C" {
2319/// fn strpos(haystack: &str, needle: &str, offset: Option<i64>) -> Zval;
2320/// }
2321///
2322/// #[php_function]
2323/// pub fn my_strpos() {
2324/// assert_eq!(unsafe { strpos("Hello", "e", None) }.long(), Some(1));
2325/// }
2326///
2327/// #[php_module]
2328/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
2329/// module.function(wrap_function!(my_strpos))
2330/// }
2331/// # fn main() {}
2332/// ```
2333///
2334/// [`strpos`]: https://www.php.net/manual/en/function.strpos.php
2335/// [`IntoZval`]: crate::convert::IntoZval
2336/// [`Zval`]: crate::types::Zval
2337// END DOCS FROM extern.md
2338#[proc_macro_attribute]
2339pub fn php_extern(args: TokenStream, input: TokenStream) -> TokenStream {
2340 php_extern_internal(args.into(), input.into()).into()
2341}
2342
2343#[allow(clippy::needless_pass_by_value)]
2344fn php_extern_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
2345 let input = parse_macro_input2!(input as ItemForeignMod);
2346 if let Err(e) = reject_args(&args, "php_extern", NO_ARGS_HINT) {
2347 return e.to_compile_error();
2348 }
2349
2350 extern_::parser(input).unwrap_or_else(|e| e.to_compile_error())
2351}
2352
2353// BEGIN DOCS FROM zval_convert.md
2354/// # `ZvalConvert` Derive Macro
2355///
2356/// The `#[derive(ZvalConvert)]` macro derives the `FromZval` and `IntoZval`
2357/// traits on a struct or enum.
2358///
2359/// ## Structs
2360///
2361/// When used on a struct, the `FromZendObject` and `IntoZendObject` traits are
2362/// also implemented, mapping fields to properties in both directions. All
2363/// fields on the struct must implement `FromZval` as well. Generics are allowed
2364/// on structs that use the derive macro, however, the implementation will add a
2365/// `FromZval` bound to all generics types.
2366///
2367/// ### Examples
2368///
2369/// ```rust,no_run,ignore
2370/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2371/// # extern crate ext_php_rs;
2372/// use ext_php_rs::prelude::*;
2373///
2374/// #[derive(ZvalConvert)]
2375/// pub struct ExampleClass<'a> {
2376/// a: i32,
2377/// b: String,
2378/// c: &'a str
2379/// }
2380///
2381/// #[php_function]
2382/// pub fn take_object(obj: ExampleClass) {
2383/// dbg!(obj.a, obj.b, obj.c);
2384/// }
2385///
2386/// #[php_function]
2387/// pub fn give_object() -> ExampleClass<'static> {
2388/// ExampleClass {
2389/// a: 5,
2390/// b: "String".to_string(),
2391/// c: "Borrowed",
2392/// }
2393/// }
2394///
2395/// #[php_module]
2396/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
2397/// module
2398/// .function(wrap_function!(take_object))
2399/// .function(wrap_function!(give_object))
2400/// }
2401/// # fn main() {}
2402/// ```
2403///
2404/// Calling from PHP:
2405///
2406/// ```php
2407/// <?php
2408///
2409/// $obj = new stdClass;
2410/// $obj->a = 5;
2411/// $obj->b = 'Hello, world!';
2412/// $obj->c = 'another string';
2413///
2414/// take_object($obj);
2415/// var_dump(give_object());
2416/// ```
2417///
2418/// Another example involving generics:
2419///
2420/// ```rust,no_run,ignore
2421/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2422/// # extern crate ext_php_rs;
2423/// use ext_php_rs::prelude::*;
2424///
2425/// // T must implement both `PartialEq<i32>` and `FromZval`.
2426/// #[derive(Debug, ZvalConvert)]
2427/// pub struct CompareVals<T: PartialEq<i32>> {
2428/// a: T,
2429/// b: T
2430/// }
2431///
2432/// #[php_function]
2433/// pub fn take_object(obj: CompareVals<i32>) {
2434/// dbg!(obj);
2435/// }
2436///
2437/// #[php_module]
2438/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
2439/// module
2440/// .function(wrap_function!(take_object))
2441/// }
2442/// # fn main() {}
2443/// ```
2444///
2445/// ## Enums
2446///
2447/// When used on an enum, the `FromZval` implementation will treat the enum as a
2448/// tagged union with a mixed datatype. This allows you to accept multiple types
2449/// in a parameter, for example, a string and an integer.
2450///
2451/// The enum variants must not have named fields, and each variant must have
2452/// exactly one field (the type to extract from the zval). Optionally, the enum
2453/// may have one default variant with no data contained, which will be used when
2454/// the rest of the variants could not be extracted from the zval.
2455///
2456/// The ordering of the variants in the enum is important, as the `FromZval`
2457/// implementation will attempt to parse the zval data in order. For example, if
2458/// you put a `String` variant before an integer variant, the integer would be
2459/// converted to a string and passed as the string variant.
2460///
2461/// ### Examples
2462///
2463/// Basic example showing the importance of variant ordering and default field:
2464///
2465/// ```rust,no_run,ignore
2466/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2467/// # extern crate ext_php_rs;
2468/// use ext_php_rs::prelude::*;
2469///
2470/// #[derive(Debug, ZvalConvert)]
2471/// pub enum UnionExample<'a> {
2472/// Long(u64), // Long
2473/// ProperStr(&'a str), // Actual string - not a converted value
2474/// ParsedStr(String), // Potentially parsed string, i.e. a double
2475/// None // Zval did not contain anything that could be parsed above
2476/// }
2477///
2478/// #[php_function]
2479/// pub fn test_union(val: UnionExample) {
2480/// dbg!(val);
2481/// }
2482///
2483/// #[php_function]
2484/// pub fn give_union() -> UnionExample<'static> {
2485/// UnionExample::Long(5)
2486/// }
2487///
2488/// #[php_module]
2489/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
2490/// module
2491/// .function(wrap_function!(test_union))
2492/// .function(wrap_function!(give_union))
2493/// }
2494/// # fn main() {}
2495/// ```
2496///
2497/// Use in PHP:
2498///
2499/// ```php
2500/// test_union(5); // UnionExample::Long(5)
2501/// test_union("Hello, world!"); // UnionExample::ProperStr("Hello, world!")
2502/// test_union(5.66666); // UnionExample::ParsedStr("5.6666")
2503/// test_union(null); // UnionExample::None
2504/// var_dump(give_union()); // int(5)
2505/// ```
2506// END DOCS FROM zval_convert.md
2507#[proc_macro_derive(ZvalConvert, attributes(php))]
2508pub fn zval_convert_derive(input: TokenStream) -> TokenStream {
2509 zval_convert_derive_internal(input.into()).into()
2510}
2511
2512fn zval_convert_derive_internal(input: TokenStream2) -> TokenStream2 {
2513 let input = parse_macro_input2!(input as DeriveInput);
2514
2515 zval::parser(input).unwrap_or_else(|e| e.to_compile_error())
2516}
2517
2518/// Defines an `extern` function with the Zend fastcall convention based on
2519/// operating system.
2520///
2521/// On Windows, Zend fastcall functions use the vector calling convention, while
2522/// on all other operating systems no fastcall convention is used (just the
2523/// regular C calling convention).
2524///
2525/// This macro wraps a function and applies the correct calling convention.
2526///
2527/// ## Examples
2528///
2529/// ```rust,ignore
2530/// # #![cfg_attr(windows, feature(abi_vectorcall))]
2531/// use ext_php_rs::zend_fastcall;
2532///
2533/// zend_fastcall! {
2534/// pub extern fn test_hello_world(a: i32, b: i32) -> i32 {
2535/// a + b
2536/// }
2537/// }
2538/// ```
2539///
2540/// On Windows, this function will have the signature `pub extern "vectorcall"
2541/// fn(i32, i32) -> i32`, while on macOS/Linux the function will have the
2542/// signature `pub extern "C" fn(i32, i32) -> i32`.
2543///
2544/// ## Support
2545///
2546/// The `vectorcall` ABI is currently only supported on Windows with nightly
2547/// Rust and the `abi_vectorcall` feature enabled.
2548#[proc_macro]
2549pub fn zend_fastcall(input: TokenStream) -> TokenStream {
2550 zend_fastcall_internal(input.into()).into()
2551}
2552
2553fn zend_fastcall_internal(input: TokenStream2) -> TokenStream2 {
2554 let input = parse_macro_input2!(input as ItemFn);
2555
2556 fastcall::parser(input)
2557}
2558
2559/// Wraps a function to be used in the [`Module::function`] method.
2560#[proc_macro]
2561pub fn wrap_function(input: TokenStream) -> TokenStream {
2562 wrap_function_internal(input.into()).into()
2563}
2564
2565fn wrap_function_internal(input: TokenStream2) -> TokenStream2 {
2566 let input = parse_macro_input2!(input as syn::Path);
2567
2568 match function::wrap(&input) {
2569 Ok(parsed) => parsed,
2570 Err(e) => e.to_compile_error(),
2571 }
2572}
2573
2574/// Wraps a constant to be used in the [`ModuleBuilder::constant`] method.
2575#[proc_macro]
2576pub fn wrap_constant(input: TokenStream) -> TokenStream {
2577 wrap_constant_internal(input.into()).into()
2578}
2579
2580fn wrap_constant_internal(input: TokenStream2) -> TokenStream2 {
2581 let input = parse_macro_input2!(input as syn::Path);
2582
2583 match constant::wrap(&input) {
2584 Ok(parsed) => parsed,
2585 Err(e) => e.to_compile_error(),
2586 }
2587}
2588
2589const PHP_ATTR_HINT: &str = "Use `#[php(<args>)]` on the item instead.";
2590const NO_ARGS_HINT: &str = "It takes no arguments.";
2591
2592fn reject_args(args: &TokenStream2, macro_name: &str, hint: &str) -> Result<(), syn::Error> {
2593 if args.is_empty() {
2594 return Ok(());
2595 }
2596 Err(err!(args => "`#[{macro_name}(<args>)]` args are not supported. {hint}"))
2597}
2598
2599macro_rules! parse_macro_input2 {
2600 ($tokenstream:ident as $ty:ty) => {
2601 match syn::parse2::<$ty>($tokenstream) {
2602 Ok(data) => data,
2603 Err(err) => {
2604 return proc_macro2::TokenStream::from(err.to_compile_error());
2605 }
2606 }
2607 };
2608 ($tokenstream:ident) => {
2609 $crate::parse_macro_input!($tokenstream as _)
2610 };
2611}
2612
2613pub(crate) use parse_macro_input2;
2614
2615macro_rules! err {
2616 ($span:expr => $($msg:tt)*) => {
2617 ::syn::Error::new(::syn::spanned::Spanned::span(&$span), format!($($msg)*))
2618 };
2619 ($($msg:tt)*) => {
2620 ::syn::Error::new(::proc_macro2::Span::call_site(), format!($($msg)*))
2621 };
2622}
2623
2624/// Bails out of a function with a syn error.
2625macro_rules! bail {
2626 ($span:expr => $($msg:tt)*) => {
2627 return Err($crate::err!($span => $($msg)*))
2628 };
2629 ($($msg:tt)*) => {
2630 return Err($crate::err!($($msg)*))
2631 };
2632}
2633
2634pub(crate) use bail;
2635pub(crate) use err;
2636
2637pub(crate) mod prelude {
2638 pub(crate) trait OptionTokens {
2639 fn option_tokens(&self) -> proc_macro2::TokenStream;
2640 }
2641
2642 impl<T: quote::ToTokens> OptionTokens for Option<T> {
2643 fn option_tokens(&self) -> proc_macro2::TokenStream {
2644 if let Some(token) = self {
2645 quote::quote! { ::std::option::Option::Some(#token) }
2646 } else {
2647 quote::quote! { ::std::option::Option::None }
2648 }
2649 }
2650 }
2651
2652 pub(crate) use crate::{bail, err};
2653 pub(crate) type Result<T> = std::result::Result<T, syn::Error>;
2654}
2655
2656#[cfg(test)]
2657mod tests {
2658 use super::*;
2659 use std::path::PathBuf;
2660
2661 type AttributeFn =
2662 fn(proc_macro2::TokenStream, proc_macro2::TokenStream) -> proc_macro2::TokenStream;
2663 type FunctionLikeFn = fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream;
2664
2665 #[rustversion::attr(nightly, test)]
2666 #[allow(dead_code)]
2667 pub fn test_macrotest_expand() {
2668 macrotest::expand("tests/expand/*.rs");
2669 }
2670
2671 #[test]
2672 pub fn test_expand() {
2673 for entry in glob::glob("tests/expand/*.rs").expect("Failed to read expand tests glob") {
2674 let entry = entry.expect("Failed to read expand test file");
2675 runtime_expand_attr(&entry);
2676 runtime_expand_func(&entry);
2677 runtime_expand_derive(&entry);
2678 }
2679 }
2680
2681 fn runtime_expand_attr(path: &PathBuf) {
2682 let file = std::fs::File::open(path).expect("Failed to open expand test file");
2683 runtime_macros::emulate_attributelike_macro_expansion(
2684 file,
2685 &[
2686 ("php_class", php_class_internal as AttributeFn),
2687 ("php_const", php_const_internal as AttributeFn),
2688 ("php_enum", php_enum_internal as AttributeFn),
2689 ("php_interface", php_interface_internal as AttributeFn),
2690 ("php_extern", php_extern_internal as AttributeFn),
2691 ("php_function", php_function_internal as AttributeFn),
2692 ("php_impl", php_impl_internal as AttributeFn),
2693 (
2694 "php_impl_interface",
2695 php_impl_interface_internal as AttributeFn,
2696 ),
2697 ("php_module", php_module_internal as AttributeFn),
2698 ],
2699 )
2700 .expect("Failed to expand attribute macros in test file");
2701 }
2702
2703 fn runtime_expand_func(path: &PathBuf) {
2704 let file = std::fs::File::open(path).expect("Failed to open expand test file");
2705 runtime_macros::emulate_functionlike_macro_expansion(
2706 file,
2707 &[
2708 ("zend_fastcall", zend_fastcall_internal as FunctionLikeFn),
2709 ("wrap_function", wrap_function_internal as FunctionLikeFn),
2710 ("wrap_constant", wrap_constant_internal as FunctionLikeFn),
2711 ],
2712 )
2713 .expect("Failed to expand function-like macros in test file");
2714 }
2715
2716 fn runtime_expand_derive(path: &PathBuf) {
2717 let file = std::fs::File::open(path).expect("Failed to open expand test file");
2718 runtime_macros::emulate_derive_macro_expansion(
2719 file,
2720 &[("ZvalConvert", zval_convert_derive_internal)],
2721 )
2722 .expect("Failed to expand derive macros in test file");
2723 }
2724}