Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add preliminary support for enums #201

Merged
merged 6 commits into from
Apr 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 11 additions & 7 deletions phper-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,32 +24,36 @@ pub fn register_all() {
pub fn register_configures() {
// versions
println!(
"cargo:rustc-cfg=phper_major_version=\"{}\"",
"cargo::rustc-cfg=phper_major_version=\"{}\"",
PHP_MAJOR_VERSION
);
println!(
"cargo:rustc-cfg=phper_minor_version=\"{}\"",
"cargo::rustc-cfg=phper_minor_version=\"{}\"",
PHP_MINOR_VERSION
);
println!(
"cargo:rustc-cfg=phper_release_version=\"{}\"",
"cargo::rustc-cfg=phper_release_version=\"{}\"",
PHP_RELEASE_VERSION
);

if PHP_DEBUG != 0 {
println!("cargo:rustc-cfg=phper_debug");
println!("cargo::rustc-cfg=phper_debug");
}

if USING_ZTS != 0 {
println!("cargo:rustc-cfg=phper_zts");
println!("cargo::rustc-cfg=phper_zts");
}

if PHP_VERSION_ID >= 80100 {
println!("cargo::rustc-cfg=phper_enum_supported");
}
}

/// Register link arguments for os-specified situation.
pub fn register_link_args() {
#[cfg(target_os = "macos")]
{
println!("cargo:rustc-link-arg=-undefined");
println!("cargo:rustc-link-arg=dynamic_lookup");
println!("cargo::rustc-link-arg=-undefined");
println!("cargo::rustc-link-arg=dynamic_lookup");
}
}
8 changes: 8 additions & 0 deletions phper-doc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,11 @@ phper = { workspace = true }
[dev-dependencies]
thiserror = "2.0.11"
reqwest = { version = "0.12.12", features = ["blocking", "cookies"] }

[build-dependencies]
phper-build = { workspace = true }

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(phper_enum_supported)',
] }
273 changes: 273 additions & 0 deletions phper-doc/doc/_06_module/_08_register_enum/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
# Register Enums

> PHP 8.1 and above introduced the Enum functionality. `PHPER` allowing you to create PHP enums using Rust code.

In `PHPER`, you can use the [`add_enum`](phper::modules::Module::add_enum) method to register enums.
According to PHP's enum specification, `PHPER` supports three types of enums:

1. Pure enums (without values)
2. Integer-backed enums
3. String-backed enums

## Creating Pure Enums

Pure enums are the simplest type of enum, having only member names without associated values. Use `EnumEntity<()>` to create a pure enum (or simply use `EnumEntity::new()` since `()` is the default type parameter).

```rust,no_run
use phper::{modules::Module, php_get_module, enums::EnumEntity, classes::Visibility};

#[php_get_module]
pub fn get_module() -> Module {
let mut module = Module::new(
env!("CARGO_CRATE_NAME"),
env!("CARGO_PKG_VERSION"),
env!("CARGO_PKG_AUTHORS"),
);

// Create a pure enum
let mut status = EnumEntity::new("Status");

// Add enum cases (without values)
status.add_case("PENDING", ());
status.add_case("ACTIVE", ());
status.add_case("INACTIVE", ());

// Register the enum to the module
module.add_enum(status);

module
}
```

This is equivalent to the following PHP code:

```php
enum Status {
case PENDING;
case ACTIVE;
case INACTIVE;
}
```

## Creating Integer-Backed Enums

Integer-backed enums associate each enum member with an integer value. Use `EnumEntity<i64>` to create an integer-backed enum.

```rust,no_run
use phper::{modules::Module, php_get_module, enums::EnumEntity};

#[php_get_module]
pub fn get_module() -> Module {
let mut module = Module::new(
env!("CARGO_CRATE_NAME"),
env!("CARGO_PKG_VERSION"),
env!("CARGO_PKG_AUTHORS"),
);

// Create an integer-backed enum
let mut level = EnumEntity::<i64>::new("Level");

// Add enum cases with their associated integer values
level.add_case("LOW", 1);
level.add_case("MEDIUM", 5);
level.add_case("HIGH", 10);

// Register the enum to the module
module.add_enum(level);

module
}
```

This is equivalent to the following PHP code:

```php
enum Level: int {
case LOW = 1;
case MEDIUM = 5;
case HIGH = 10;
}
```

## Creating String-Backed Enums

String-backed enums associate each enum member with a string value. Use `EnumEntity<String>` to create a string-backed enum.

```rust,no_run
use phper::{modules::Module, php_get_module, enums::EnumEntity};

#[php_get_module]
pub fn get_module() -> Module {
let mut module = Module::new(
env!("CARGO_CRATE_NAME"),
env!("CARGO_PKG_VERSION"),
env!("CARGO_PKG_AUTHORS"),
);

// Create a string-backed enum
let mut color = EnumEntity::<String>::new("Color");

// Add enum cases with their associated string values
color.add_case("RED", "FF0000".to_string());
color.add_case("GREEN", "00FF00".to_string());
color.add_case("BLUE", "0000FF".to_string());

// Register the enum to the module
module.add_enum(color);

module
}
```

This is equivalent to the following PHP code:

```php
enum Color: string {
case RED = "FF0000";
case GREEN = "00FF00";
case BLUE = "0000FF";
}
```

## Adding Constants

Enums can contain constants. Use the `add_constant` method to add constants to an enum.

```rust,no_run
use phper::{modules::Module, php_get_module, enums::EnumEntity};

let mut status = EnumEntity::new("Status");

// Add enum cases
status.add_case("PENDING", ());
status.add_case("ACTIVE", ());

// Add constants
status.add_constant("VERSION", "1.0.0");
status.add_constant("MAX_ATTEMPTS", 3);
```

This is equivalent to the following PHP code:

```php
enum Status {
case PENDING;
case ACTIVE;

public const VERSION = "1.0.0";
public const MAX_ATTEMPTS = 3;
}
```

## Adding Static Methods

You can add static methods to enums. Use the `add_static_method` method to add a static method to an enum.

```rust,no_run
use phper::{modules::Module, php_get_module, enums::EnumEntity, classes::Visibility};
use std::convert::Infallible;

let mut status = EnumEntity::new("Status");

// Add enum cases
status.add_case("PENDING", ());
status.add_case("ACTIVE", ());

// Add static method
status.add_static_method("getDescription", Visibility::Public, |_| {
Ok::<_, Infallible>("Status enumeration for tracking item states")
});
```

This is equivalent to the following PHP code:

```php
enum Status {
case PENDING;
case ACTIVE;

public static function getDescription(): string {
return "Status enumeration for tracking item states";
}
}
```

## Implementing Interfaces

You can make enums implement interfaces. Use the `implements` method to make an enum implement a specific interface.

```rust,no_run
use phper::{modules::Module, php_get_module, enums::EnumEntity, classes::Interface};

let mut color = EnumEntity::<String>::new("Color");

// Add enum cases
color.add_case("RED", "FF0000".to_string());
color.add_case("GREEN", "00FF00".to_string());

// Implement interface
color.implements(Interface::from_name("JsonSerializable"));

// Note: You need to add necessary methods to satisfy interface requirements
// For example, JsonSerializable interface requires the implementation of jsonSerialize method
```

## Using Built-in Enum Methods

PHP enums come with some built-in methods. Pure enums (`UnitEnum`) have the `cases()` method, while backed enums (`BackedEnum`) additionally have the `from()` and `tryFrom()` methods.

```php
// Examples of using built-in methods in PHP
$allCases = Color::cases(); // Returns an array of all enum cases

// Only available for backed enums
$colorFromValue = Color::from("FF0000"); // Returns RED
$colorOrNull = Color::tryFrom("INVALID"); // Returns null (when the value doesn't exist)
```

## Complete Example

Here's a comprehensive example using both pure and backed enums:

```rust,no_run
use phper::{
modules::Module,
php_get_module,
enums::EnumEntity,
classes::Visibility
};
use std::convert::Infallible;

#[php_get_module]
pub fn get_module() -> Module {
let mut module = Module::new(
env!("CARGO_CRATE_NAME"),
env!("CARGO_PKG_VERSION"),
env!("CARGO_PKG_AUTHORS"),
);

// Pure enum
let mut status = EnumEntity::new("Status");
status.add_case("PENDING", ());
status.add_case("ACTIVE", ());
status.add_case("INACTIVE", ());
status.add_constant("VERSION", "1.0.0");
status.add_static_method("getDescription", Visibility::Public, |_| {
Ok::<_, Infallible>("Status enumeration")
});

// Integer-backed enum
let mut level = EnumEntity::<i64>::new("Level");
level.add_case("LOW", 1);
level.add_case("MEDIUM", 5);
level.add_case("HIGH", 10);

// Register enums to the module
module.add_enum(status);
module.add_enum(level);

module
}
```

> **Note**: PHP enums require PHP 8.1 or higher. Make sure your extension sets the correct PHP version requirements.
4 changes: 4 additions & 0 deletions phper-doc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ pub mod _06_module {

#[doc = include_str!("../doc/_06_module/_07_register_interface/index.md")]
pub mod _07_register_interface {}

#[cfg(phper_enum_supported)]
#[doc = include_str!("../doc/_06_module/_08_register_enum/index.md")]
pub mod _08_register_enum {}
}

/// TODO
Expand Down
4 changes: 4 additions & 0 deletions phper-sys/php_wrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
#include <zend_observer.h>
#endif

#if PHP_VERSION_ID >= 80100
#include <zend_enum.h>
#endif

typedef ZEND_INI_MH(phper_zend_ini_mh);

typedef zend_class_entry *
Expand Down
Loading
Loading