Skip to content

Feat: create interface #411

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

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

Norbytus
Copy link
Contributor

@Norbytus Norbytus commented Mar 28, 2025

Impl interface registration.
I start impl interface registration, for now i just take ClassBuilder and use it in InterfaceBuilder to hide method to add property and extend other classes.

And i have question how we can make better this separation in rust form, or we can just use ClassBuilder every where, and if some one use ClassBuilder with ClassFlagInterface, and add property, extends. Its they problem)

use std::collections::HashMap;

use ::ext_php_rs::internal::property::PropertyInfo;
use ext_php_rs::{builders::FunctionBuilder, flags::ClassFlags, php_interface};

#[php_interface(name = "Test\\TestInterface")] // Its not implement yet
trait Test {
    fn test();
}

/// Should be generate from proc_macros
pub struct TestInterface;

impl ::ext_php_rs::class::RegisteredClass for TestInterface {
    const CLASS_NAME: &'static str = "Test\\TestInterface";

    const BUILDER_MODIFIER: Option<
        fn(ext_php_rs::builders::ClassBuilder) -> ext_php_rs::builders::ClassBuilder,
    > = None;

    const EXTENDS: Option<fn() -> &'static ext_php_rs::zend::ClassEntry> = None;

    const FLAGS: ClassFlags = ClassFlags::Interface;

    const IMPLEMENTS: &'static [fn() -> &'static ext_php_rs::zend::ClassEntry] = &[];

    fn get_metadata() -> &'static ext_php_rs::class::ClassMetadata<Self> {
        static METADATA: ::ext_php_rs::class::ClassMetadata<TestInterface> =
            ::ext_php_rs::class::ClassMetadata::new();

        &METADATA
    }

    // Method registration same as in default class
    // But without body and with and should be abstract
    fn method_builders() -> Vec<(
        ext_php_rs::builders::FunctionBuilder<'static>,
        ext_php_rs::flags::MethodFlags,
    )> {
        vec![(
            FunctionBuilder::new_abstract("test").returns(
                ext_php_rs::flags::DataType::Void,
                false,
                false,
            ),
            ext_php_rs::flags::MethodFlags::Public | ext_php_rs::flags::MethodFlags::Static,
        )]
    }
    
    fn constructor() -> Option<ext_php_rs::class::ConstructorMeta<Self>> {
        None
    }

    fn constants() -> &'static [(
        &'static str,
        &'static dyn ext_php_rs::convert::IntoZvalDyn,
        ext_php_rs::describe::DocComments,
    )] {
        &[]
    }

    fn get_properties<'a>() -> std::collections::HashMap<&'static str, PropertyInfo<'a, Self>> {
        HashMap::new()
    }
}

impl<'a> ::ext_php_rs::convert::FromZendObject<'a> for &'a TestInterface {
    #[inline]
    fn from_zend_object(
        obj: &'a ::ext_php_rs::types::ZendObject,
    ) -> ::ext_php_rs::error::Result<Self> {
        let obj = ::ext_php_rs::types::ZendClassObject::<TestInterface>::from_zend_obj(obj)
            .ok_or(::ext_php_rs::error::Error::InvalidScope)?;
        Ok(&**obj)
    }
}
impl<'a> ::ext_php_rs::convert::FromZendObjectMut<'a> for &'a mut TestInterface {
    #[inline]
    fn from_zend_object_mut(
        obj: &'a mut ::ext_php_rs::types::ZendObject,
    ) -> ::ext_php_rs::error::Result<Self> {
        let obj = ::ext_php_rs::types::ZendClassObject::<TestInterface>::from_zend_obj_mut(obj)
            .ok_or(::ext_php_rs::error::Error::InvalidScope)?;
        Ok(&mut **obj)
    }
}
impl<'a> ::ext_php_rs::convert::FromZval<'a> for &'a TestInterface {
    const TYPE: ::ext_php_rs::flags::DataType = ::ext_php_rs::flags::DataType::Object(Some(
        <TestInterface as ::ext_php_rs::class::RegisteredClass>::CLASS_NAME,
    ));
    #[inline]
    fn from_zval(zval: &'a ::ext_php_rs::types::Zval) -> ::std::option::Option<Self> {
        <Self as ::ext_php_rs::convert::FromZendObject>::from_zend_object(zval.object()?).ok()
    }
}
impl<'a> ::ext_php_rs::convert::FromZvalMut<'a> for &'a mut TestInterface {
    const TYPE: ::ext_php_rs::flags::DataType = ::ext_php_rs::flags::DataType::Object(Some(
        <TestInterface as ::ext_php_rs::class::RegisteredClass>::CLASS_NAME,
    ));
    #[inline]
    fn from_zval_mut(zval: &'a mut ::ext_php_rs::types::Zval) -> ::std::option::Option<Self> {
        <Self as ::ext_php_rs::convert::FromZendObjectMut>::from_zend_object_mut(zval.object_mut()?)
            .ok()
    }
}
impl ::ext_php_rs::convert::IntoZendObject for TestInterface {
    #[inline]
    fn into_zend_object(
        self,
    ) -> ::ext_php_rs::error::Result<::ext_php_rs::boxed::ZBox<::ext_php_rs::types::ZendObject>>
    {
        Ok(::ext_php_rs::types::ZendClassObject::new(self).into())
    }
}
impl ::ext_php_rs::convert::IntoZval for TestInterface {
    const TYPE: ::ext_php_rs::flags::DataType = ::ext_php_rs::flags::DataType::Object(Some(
        <TestInterface as ::ext_php_rs::class::RegisteredClass>::CLASS_NAME,
    ));
    const NULLABLE: bool = false;
    #[inline]
    fn set_zval(
        self,
        zv: &mut ::ext_php_rs::types::Zval,
        persistent: bool,
    ) -> ::ext_php_rs::error::Result<()> {
        use ::ext_php_rs::convert::IntoZendObject;
        self.into_zend_object()?.set_zval(zv, persistent)
    }
}

@Norbytus Norbytus force-pushed the feat/create_interface branch from 0a0ec55 to 7e385b0 Compare March 28, 2025 18:43
@Norbytus Norbytus force-pushed the feat/create_interface branch 2 times, most recently from 86190da to 79e8a51 Compare March 28, 2025 18:53
@Norbytus Norbytus force-pushed the feat/create_interface branch from 79e8a51 to e38c821 Compare March 28, 2025 18:55
@Norbytus
Copy link
Contributor Author

Norbytus commented Apr 1, 2025

@Xenira can i ask you for some advice, how did more better with interface registration?

@Xenira
Copy link
Collaborator

Xenira commented Apr 2, 2025

@Norbytus first of all, thank you for the contribution!

I do not have the time to review it rn, but will try to get to this as soon as possible.

@Xenira can i ask you for some advice, how did more better with interface registration?

If you have specific questions I am always happy to help out. Not entirely sure what you mean with how did more better with interface registration. Could you rephrase that.

@Norbytus
Copy link
Contributor Author

Norbytus commented Apr 2, 2025

@Norbytus first of all, thank you for the contribution!

I do not have the time to review it rn, but will try to get to this as soon as possible.

@Xenira can i ask you for some advice, how did more better with interface registration?

If you have specific questions I am always happy to help out. Not entirely sure what you mean with how did more better with interface registration. Could you rephrase that.

First problem it a interface registration proc_macros for methods.
I see in impl_.rs in macro crate and it's specif for regular class method.
For example how i see this

/// It could be a regular php interface, but like in C with need dummy class for ZendEntry
trait SimpleInterface {
    pub fn test(&self);  
}

/// But this trait could not be php interface it's a abstract class
trait SimpleInterface {
    pub fn test(&self) {
    /// Some default bhv
    }
}

struct Test;

/// Cannot be abstract or interface, only regular class
impl Test {/* Methods */ }

Another problem, its registration interface in module. If we wanna make beauty and cool like

#[php_interface]
trait Test {}

In proc_macro we should generate a dummy struct like PhpTestInterface and when we shuold

#[php_module]
pub fn module(module: ModuleBuilder) -> ModuleBuilder {
    module.class::<PhpTestInterface>() // It isn't obvious
}```

@Xenira
Copy link
Collaborator

Xenira commented Apr 2, 2025

First problem it a interface registration proc_macros for methods. I see in impl_.rs in macro crate and it's specif for regular class method. For example how i see this

/// It could be a regular php interface, but like in C with need dummy class for ZendEntry
trait SimpleInterface {
    pub fn test(&self);  
}

/// But this trait could not be php interface it's a abstract class
trait SimpleInterface {
    pub fn test(&self) {
    /// Some default bhv
    }
}

struct Test;

/// Cannot be abstract or interface, only regular class
impl Test {/* Methods */ }

I would like to do this in two steps. In a first step we should only allow traits without default implementations.

Another problem, its registration interface in module. If we wanna make beauty and cool like

#[php_interface]
trait Test {}

In proc_macro we should generate a dummy struct like PhpTestInterface and when we shuold

#[php_module]
pub fn module(module: ModuleBuilder) -> ModuleBuilder {
    module.class::<PhpTestInterface>() // It isn't obvious
}```

While that would be the nicer version I would go with the same approach as functions and constants. Those have a wrap_function!(fn) and wrap_constant!(CONST) macro.

That way we do not rely on 'magic' renaming / creating of structs.

@Xenira Xenira linked an issue Apr 2, 2025 that may be closed by this pull request
@Norbytus Norbytus force-pushed the feat/create_interface branch from 59c2cc1 to 9eaf73b Compare April 6, 2025 13:27
Comment on lines +30 to +57

#[derive(Debug, Copy, Clone, FromMeta, Default)]
pub enum RenameRule {
/// Methods won't be renamed.
#[darling(rename = "none")]
None,
/// Methods will be converted to camelCase.
#[darling(rename = "camelCase")]
#[default]
Camel,
/// Methods will be converted to snake_case.
#[darling(rename = "snake_case")]
Snake,
}

pub trait Rename {
fn renmae(self, rule: &RenameRule) -> Self;
}

impl Rename for String {
fn renmae(self, rule: &RenameRule) -> Self {
match *rule {
RenameRule::None => self,
RenameRule::Camel => ident_case::RenameRule::CamelCase.apply_to_field(self),
RenameRule::Snake => ident_case::RenameRule::SnakeCase.apply_to_field(self),
}
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just want to let you know that this was just changed in #413. You might wanna rebase onto master

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I think do separate PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

declaring interfaces using traits
2 participants