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

Closed
wants to merge 11 commits into 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

@kakserpom
Copy link
Contributor

@Xenira What do we need to make this happen?

@Xenira
Copy link
Collaborator

Xenira commented Jul 18, 2025

@kakserpom will make this happen. Just need the time... Doing my best to get stuff done, got work and personal life though. Did a lot in recent weeks so will prob need to cut back a little.

@Norbytus
Copy link
Contributor Author

Norbytus commented Jul 18, 2025

@Xenira What do we need to make this happen?

I try too, and will be a new pr

@Xenira
Copy link
Collaborator

Xenira commented Jul 18, 2025

@Norbytus thank you! Thought this was abandoned, nice to hear you are still working on it ❤️

@Norbytus
Copy link
Contributor Author

@Norbytus thank you! Thought this was abandoned, nice to hear you are still working on it ❤️

No, but I think i back, with some thoughts about interfaces

@Xenira
Copy link
Collaborator

Xenira commented Jul 18, 2025

@Norbytus Nice, mind sharing them in an issue? That way we can discuss them beforehand. As interfaces would be a big feature it would be nice to have a well thought through implementation.

@Norbytus Norbytus mentioned this pull request Jul 19, 2025
1 task
@Norbytus Norbytus closed this Jul 20, 2025
@Norbytus
Copy link
Contributor Author

Close this PR, open new one
#533

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
3 participants