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