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