#PHP: Support Getters by charjr · Pull Re...

1 messages · Page 1 of 1 (latest)

pliant bane
#

@floral lotus If I could pick you brain on this one, please 🙏
Since I'm guessing you implemented this in Java.
How does Dagger handle the implicit getters?

What I've got is:

  1. The SDK recognises what is/isn't a field.
  2. The SDK registers each field using withField on the relevant Dagger Object.
  3. ???
floral lotus
#

Let me share my vision of fields. And with that, maybe there's what you need to have it working in PHP 🙂
There's two aspects behind fields: serialization and access. You can't have access without serialization, but you can have serialization without access (the // +private in Go).
In java, private and public fields are all serialized by default. Non serialized fields are flagged as transient. I don't know how that works in PHP, but you need something like that. Either a capability of the language, or a special flag the JSON serialization/deserialization will understand.

Now, for fields that are serializable. In Java, a field is exposed by on its visibility. Public fields are accessible, private only if they have the @Function annotation.
All accessible fields are visible in the type defs with the withField annotation. This annotation will instruct the engine to expose getter on the field.
The way it works is all about JSON serialization. The JSON representation of the object as returned by the engine will contain the field value. When the user (or the module) set a value, it will be part of the serialized representation sent to the engine and be part of the serialized version back to the client.
What I mean here is you should have nothing to do as soon as the SDK is capable to serialize/deserialize the object. The SDK doesn't need to create any kind of getter, it's all about the JSON representation.

If I take this Java example:

private transient String notExportedField;

public Directory source;

@Function private String version;

private Container container;
  • notExportedField will not be serialized, not be deserialized. It's a purely internal field
  • source, version and container must be serialized / deserialized in JSON. That's enough for a function to set a value and retrieve it in an other function
  • source and version will be exposed using withField: it will be possible to get the value from the API, and to set it
  • container will be part of the serialization but without getter/setter

If you have the equivalent of this java module:

@Object
public class MyModule {
    private String foo;
    
    @Function
    public MyModule setFoo() {
        this.foo = "bar";
        return this;
    }
    
    @Function
    public String getFoo() {
        return this.foo;
    }

You should be able to run something like dagger -c '. | set-foo | get-foo' and that should return bar
In the generated entrypoint.java, we have:

.withFunction(
  dag().function("getFoo",
  dag().typeDef().withKind(TypeDefKind.STRING_KIND))))

To expose the function and

} else if (fnName.equals("getFoo")) {
  String res = obj.getFoo();
  return JsonConverter.toJSON(res);
}

Where we handle the function calls.

If you have the equivalent of this java module:

@Object
public class MyModule {
    public String foo;

    @Function
    public MyModule setFoo() {
        this.foo = "bar";
        return this;
    }
}

You should be able to run something like dagger -c '. | set-foo | foo' and that should return bar
In the generated entrypoint.java we have

.withField("foo", dag().typeDef().withKind(TypeDefKind.STRING_KIND)));

And that's it, no code to handle a function call as the field is retrieved directly by the engine.

floral lotus
pliant bane
#

**EDIT - TLDR: **It does help, I used your input to rubber duck debug below and think I've got some ideas what the problems are.
1. I need to figure this out in PHP-land. I suspect field names are being serialised in camelCase while dagger looks for kebab-case. So multi-word fields aren't found.
**2. If you have any ideas for this one, please share 🙏 ** Dagger does not recognise default values on fields as something that must happen before it accesses the field. At least, not in the same way it recognises constructors.

Original Ramblings ------------

If you have the equivalent of this java module:

#[DaggerObject]
class MyModule {
    #[DaggerFunction]
    public string $foo;

    #[DaggerFunction]
    public function setFoo(): MyModule
    {
        $this->foo = "bar";
        return $this;
    }
}

You should be able to run something like dagger -c '. | set-foo | foo' and that should return bar

✅ This works using my PR.

Current Problems

There are two problems I notice currently.

First Problem

The PHP equivalent of this example returns an empty string:

@Object
public class MyModule {
    public String fooBar;

    @Function
    public MyModule setFooBar() {
        this.fooBar = "baz";
        return this;
    }
}

As you said, it's all about serialization. I suspect it serializes parameters verbatim i.e. there's a serialized fooBar while dagger is looking for foo-bar.

pliant bane
#

Second Problem

@Object
public class MyModule {
    public String foo;

    public MyModule() {
        foo = "bar";
    }
}
#[DaggerObject]
class MyModule
{
    #[DaggerFunction]
    public string $foo;

    #[DaggerFunction]
    public function __construct() {
        $this->foo = "bar";
    }
}

✅ If I call dagger -c '. | foo with the PHP equivalent of this, it works, I get bar.

But, PHP allows default values to be set on properties. I don't know what the Java equivalent is so I'll have to stick to PHP here.

#[DaggerObject]
class MyModule
{
    #[DaggerFunction]
    public string $foo = "bar";
}

As far as PHP is concerned, this example behaves identically to setting foo inside the constructor. As far as Dagger is concerned, foo is never set.
Calling dagger -c '. | foo returns an empty string.

In fact I can even do this:

#[DaggerObject]
class MyModule
{
    #[DaggerFunction]
    public string $foo = "bar";

    #[DaggerFunction]
    public function __construct() {}
}

The constructor does nothing. But now, calling dagger -c '. | foo returns bar.

#

My first thought on the second problem is a bit of a hack. But it should work:

if object has field:
    if object does not use constructor:
        register constructor anyway.

PHP's constructor always exists, but if you don't override it, it's a no-op.

If you got any better ideas, go for it. I was reaching for my thinking cap but I must've got the wrong hat 🤠

floral lotus
#

Yes, I think the idea to always add a default constructor if none has been defined but you have fields is a good approach

#

Regarding the first problem about field names, let me check how it's done in Java
But I think in the exposed types in the graphql API it's all about camelCase

pliant bane
floral lotus
#

That's what Claude says about the JSON serialization for java 🫣

Based on the exploration, here's how fields are serialized in the Java SDK:

JSON Serialization in sdk/java

Library Used
The SDK uses Jakarta JSON-B (Yasson 3.0.4) - not Jackson or Gson.

Field Naming Convention: Identity (no transformation)
The JSON field names match the Java field names exactly as declared. There's no automatic case transformation configured.
For example, a Java field named myField would be serialized as "myField" in JSON.

Special Cases with @JsonbProperty
When the JSON field name needs to differ from the Java field name, the @JsonbProperty annotation is used. From sdk/java/dagger-codegen-maven-plugin/src/main/java/io/dagger/codegen/introspection/Field.java:

@JsonbProperty("type")
private TypeRef typeRef;

@JsonbProperty("isDeprecated")
private boolean deprecated;

This maps:

  • JSON "type" → Java typeRef
  • JSON "isDeprecated" → Java deprecated

Configuration
The serialization uses a custom FieldsStrategy that makes all non-static, non-transient fields visible. No PropertyNamingStrategy is set, so it defaults to identity mapping.

In summary: The Java SDK uses camelCase (matching Java conventions), since fields are serialized with their exact Java names and Java uses camelCase by convention.

pliant bane
#

Hmm... I guess the problem lies elsewhere.

Thank you for the help, you've saved me diving down the wrong rabbit hole.

#

I'll have to dig into it more.

floral lotus
#

if with a field foo this is working but with a field fooBar it's not, I'm quite confident this is a serialisation issue.
Maybe what you can try is to test with graphql queries to see what are the values sent by the engine. Or if you can plug enough logs, for instance just before the SDK deserialize the object in the entrypoint. That would probably gives you the answer

#

I remember when I wrote some parts of the Java SDK it took me some time to really understand what it was supposed to do

pliant bane
#

Yeah, there's still a few parts I don't understand enough of yet. But I'll have to work them out before I can add some of the bigger features/fixes

floral lotus
#

One thing that might help is to run DAGGER_SESSION_TOKEN=test dagger listen --allow-cors on your module (with foorBar)
Then with this http header:

{
  "Authorization": "Basic dGVzdDo="
}

You can run queries to http://127.0.0.1:8081/query and they will be scoped to the module. So you can see exactly using graphql what's going on, what are the values returned by the server, etc. Maybe that can help.