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

Anonymous class FromZval derive macro #78

Merged
merged 1 commit into from
Oct 3, 2021
Merged

Conversation

davidcole1340
Copy link
Owner

Progress towards #75

Added the #[derive(FromZval)] macro. Can be used on enums and structs.

  • Enums: Treats the object as a tagged union mixed parameter.
  • Struct: Treats the object as an anonymous class, filling the fields
    from an objects properties.

Both support generics and lifetimes so &'a str is valid. Next step in a
seperate PR is the corresponding IntoZval macro, which converts the
class into a Zval.

@davidcole1340
Copy link
Owner Author

Also started work on the inverse #[derive(IntoZval)] macro: from_zval_stdclass...into_zval_stdclass

@vodik
Copy link
Collaborator

vodik commented Sep 30, 2021

I really like this, but I think there are some interesting semantics to explore. I've been thinking about this for a few days and trying to get all my thoughts down, so apologies of this is somewhat unstructured.

My first through was that if I was to see the rust code that this feature would example, for example something like this:

#[derive(FromZVal)]
struct Args {
    name: String
}

#[php_function]
fn printer(args: Args) {
    println!("{}", args.name)
}

I might intuit that the following PHP code to work:

printer(['name' => 'Simon'])

Rather than this:

class Args {
    public function __construct(public string $name) {}
}

printer(new Args)

However both would be useful, and should work given PHP's own casting rules. But the catch is its part of PHP's casting rules. To write a PHP version than can take either I have to explicitly cast:

function printer(object|array $args) {
    $args = (object)$args
    echo "{$args->name}\n";
}

or

function printer(object|array $args) {
    $args = (array)$args
    echo "{$args['name']}\n";
}

I cannot rely on implicit conversion here.

FromZVal only unpacks into PHP types or enums representing union of types

Keep it as close to php type annotations as possible:

PHP Type Rust Type
array HashMap
callable Closure
bool bool
float f32
int u32/u64
string String
iterable ?
object ZendObject
mixed ZVal

Then nullable types turn into option types: ?string is represented as Option<String>

And union types are represented as enums: string|object would be represented as:

#[derive(FromZVal)]
enum CustomType {
    String(String),
    Object(ZendObject)
}

FromZVal should round trip by default

The nice part of keeping it simple like this is that all types can predictably round trip. Any type that can have FromZVal could also have a valid and expected IntoZVal (maybe we don't even need an explicit IntoZVal).

Represent casts with a different API, make them explicit

I like this functionality in this PR, but I'd suggest making is an explicit function call with a district trait/derive name. For example:

// Define this so function can take either array or object.
// Represents array|object
#[derive(FromZVal)]
enum Args {
    Object(ZendObject),
    Array(HashMap),
}

// Tentative name, bikeshedable
#[derive(CastFromZVal)]
struct Props {
    name: String
}

#[php_function]
fn printer(args: Args) {
    // Unpack either the object or the array into `Props`
    let props = php::cast(&args);
    println!("{}", props.name)
}

Current FromZVal attaches behaviour to the wrong layer

One other note I think is worth considering with the current design is that this FromZVal implementation currently first unpacks the ZVal to an object (ZendObject) and then to into the struct. This means if I just have an object, I can't unpack it without turning it into a ZVal first.

My other PR introduces FromZendObject trait and this is probably better organized under that trait. Within the current design, I'd advocate its more correct for from FromZVal to delegate to FromZendObject rather than having it all in one. Then you could get ZVal to String (via __toString) for free as well.

declare_strict

We might also want to consider strict vs not strict conversion rules in FromZVal. We might want to support both strict and non-strict versions of FromZVal:

let zval = 2.3f.into_zval().unwrap();

let a: Result<u64> = zval.extract_loosly();  // Holds Ok(2)
let b: Result<u64> = zval.extract();         // Holds Err(...)
let c: Result<String> = php::cast(&zval);    // Holds "2.3"

On that note, this is something I just learned, that __toString allows classes to be implicitly cast to strings. The following works unless its strict mode:

class Args {
    public function __construct(public string $name) {}
    
    public function __toString() { return $this->name; }
}

function printer(string $msg) {
    echo "$msg\n";
}

printer('Hello World');

// Not a type error
printer(new Args('Hello World'))

So to reiterate the above:

let obj: ZVal = ...;

let a: Result<String> = obj.extract_loosly();  // Calls `__toString`
let b: Result<String> = obj.extract();         // Err(...)
let c: Result<String> = php::cast(&obj);       // Calls `__toString`

@davidcole1340 davidcole1340 linked an issue Oct 1, 2021 that may be closed by this pull request
Copy link
Collaborator

@vodik vodik left a comment

Choose a reason for hiding this comment

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

Merge it in. The finer details of how casting and coersion work are better dealt with this in tree rather than out of tree.

I like it.

ext-php-rs-derive/src/function.rs Outdated Show resolved Hide resolved
ext-php-rs-derive/src/function.rs Outdated Show resolved Hide resolved
@davidcole1340 davidcole1340 merged commit c16c5d4 into master Oct 3, 2021
@davidcole1340 davidcole1340 deleted the from_zval_stdclass branch October 3, 2021 05:00
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.

Add FromZval derive macro
2 participants