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