peachpiecompiler / peachpie

PeachPie - the PHP compiler and runtime for .NET and .NET Core
https://www.peachpie.io
Apache License 2.0
2.31k stars 201 forks source link

Help with solving the problem with the magic __get and __set #1106

Closed FibonacciFox closed 1 year ago

FibonacciFox commented 1 year ago

There is a wrapper class, it is written to interact with Avalonia Ui components.

using System;
using System.Reflection;
using Pchp.Core;

namespace Peachpie.Avalonia.Experimental;

public class BaseWrapper<T> where T : new()
{
    public T GetWrappedObject()
    {
        return _wrappedObject;
    }

    public PhpValue __get(PhpValue propertyName)
    {
        return _getProperty(propertyName);
    }

    public void __set(PhpValue propertyName, PhpValue value)
    {
        _setProperty(propertyName, value);
    }

    private PropertyInfo GetWrappedProperty(PhpValue propertyName)
    {
        return _wrappedObject.GetType().GetProperty(propertyName.ToString());
    }

    private PhpValue _getProperty(PhpValue propertyName)
    {
        var property = GetWrappedProperty(propertyName);
        if (property != null) return PhpValue.FromClr(property.GetValue(_wrappedObject));

        throw new ArgumentException(
            $"Property '{propertyName}' not found on object of type '{_wrappedObject.GetType().Name}'");
    }

    private void _setProperty(PhpValue propertyName, PhpValue value)
    {
        var property = GetWrappedProperty(propertyName);
        if (property != null) 
            property.SetValue(_wrappedObject, value.ToClr());
        else
            throw new ArgumentException(
                $"Property '{propertyName}' not found on object of type '{_wrappedObject.GetType().Name}'");
    }

    private readonly T _wrappedObject = new();
}
using System;
using Avalonia;
using Pchp.Core;
using Peachpie.Avalonia.Experimental;

namespace Peachpie.Avalonia;

public class AvaloniaWrapper<T> : BaseWrapper<T> where T : AvaloniaObject, new()
{
    public void On(PhpValue eventName, Closure callback)
    {
        var eventInfo = GetWrappedObject().GetType().GetEvent(eventName.ToString());
        if (eventInfo != null)
        {
            eventInfo.AddEventHandler(GetWrappedObject(),
                CreateDelegate(eventInfo.EventHandlerType,
                    (_, e) => { callback.call(null, PhpValue.FromClass(GetWrappedObject()), PhpValue.FromClass(e)); }));
        }
        else
        {
            throw new ArgumentException(
                $"Event '{eventName}' not found on object of type '{GetWrappedObject().GetType().Name}'");
        }
    }

    static private Delegate CreateDelegate(Type type, EventHandler handler)
    {
        return (Delegate) type.GetConstructor(new[] {typeof(object), typeof(IntPtr)})
            ?.Invoke(new[] {handler.Target, handler.Method.MethodHandle.GetFunctionPointer()});
    }
}

Access to properties and fields using the wrapper is implemented via get and set!

image

But in order for the magic methods to work, I have to create an heir class.

image

image

image

image

Without a stub class, I can't get access to fields and properties.

Please tell me how to implement the above functionality without using a stub class. :(

jakubmisek commented 1 year ago

Currently, this PHP-specific feature can only be defined in the PHP language.

As a workaround, in C#, you can define

class MyDynamicClass {
  internal PhpArray __peach__runtimeFields;
}

so the class can have dynamic properties int PHP:

$x = new MyDynamicClass();
echo $x->prop; // looks into "__peach__runtimeFields" array
FibonacciFox commented 1 year ago

@jakubmisek ,Thanks, everything works fine. Closing the topic.