RFC: FusionValueObjects that return (generated) type safe PHP objects

i hope the following parts are self explanatory - maybe i will write more about this idea:

//
// Integration
//
root = Vendor:Integration.Person
prototype(Vendor:Integration.Person) < prototype(Neos.Neos:ContentComponent) {
    person = Vendor:ValueObject.Person {
        name = Vendor:ValueObject.Name {
            first = "Marc Henry"
            last = "Schultz"
        }
        species = "Neosianer"
        height = 1.80
    }

    renderer = afx`
        <Vendor:Presentation.Person person={props.person} />
    `
}


//
// Presentation, with proptypes that expects php object "FusionValueObjects\\Vendor\\ValueObject\\Name"
//
prototype(Vendor:Presentation.Person) < prototype(Neos.Fusion:Component) {
    // Future syntax:
    // person Vendor:ValueObject.Person
    // Now
    @propTypes {
        person = ${PropTypes.instanceOf("FusionValueObjects\\Vendor\\ValueObject\\Name")}
    }

    renderer = afx`
        <h1>{person.name.first}</h1>
    `
}

//
// ValueObject, which is the base for a php object with the same structure.
//
prototype(Vendor:ValueObject.Person) < prototype(MhsDesign.FusionValueObjects:ValueObject) {
    @types {
        # will be transformed to class-string
        # name = "FusionValueObjects\\Vendor\\ValueObject\\Name"
        name = Vendor:ValueObject.Name
        height = 'int'
        species = 'string'
        age = '?int'
    }
    // default values, without context!
    age = null
}
// Dynamicly created once for each object,
// by aop the runtime creation, get the merged array tree,
// look into the global prototypes of type `MhsDesign.FusionValueObjects:ValueObject`
// generatet those classes, and run via eval before the runtime renders.
// ! can even be influenced by 3rd pary packages...

namespace FusionValueObjects\Vendor\ValueObject {
    class Person {
        public function __construct(
            public int $height,
            public string $species,
            public \FusionValueObjects\Vendor\ValueObject\Name $name,
            public ?int $age = null,
        ) {
        }
    }
}
// pass arguments via php8 named arguments.
// all the stricness is covered by php already - we cant pass too many args or the wrong type ^^
class ValueObjectImplementation extends DataStructureImplementation
{
    public function evaluate()
    {
        $valueClassName = 'FusionValueObjects\\' . str_replace(['.', ':'], '\\', $this->fusionObjectName);
        return new $valueClassName(...parent::evaluate());
    }
}

or we have in a dedicated Fusion/ValueObject folder a Person Folder with a Person.fusion and a Person.php

those files could be mapped via name automatically or within Person.fusion the is a @implementation

maybe we can convince composer to auto load that dir …

Hey Marc,

From the snippets I can see where you are going, but not where you come from. i.e. I’m missing a perspective on the problem you’re trying to solve.

In my personal opinion type safety is a great feature, but more or less exclusively at write time (e.g. to allow the IDE to auto-complete and highlight bugs up front).

Is that also what you have in mind or what do you expect to gain from a runtime type check?

This tries to address the problem that passing around fusion dataStructures does not scale that well. Especially in larger projects this quickly gets out of hand.

It may be a very useful tool to be able to define an object on one side of the system that can be passed around and consumed by another component while still beeing sure that all requirements are met.

I personally would prefer to generate the PHP implementations in a PHP capable cache instead of aop but like the approach to define typed fusion datastructures in fusion.

I wouldn’t deny that point at all but IMO we should collect more concrete issues that arise from the current situation in order to improve our judgement on the different solutions

i just implemented this idea and tried this out, it works wonderfull and we can also have typed collections (Foo:Value[]).

this specific idea rose exactly from what @mficzel said and what was documented in the november sprint:

prototype(My.ValueObject) < prototype(Neos.Fusion:DataStructure) {
   foo:string = ''
   bar:bool = false
}

prototype(My.Component) < prototype(Neos.Fusion:Component) {
   vos:[My.ValueObject] = ${[]}
   baz:int = 0
}

Neos and Flow Future Notes / Discussion - CodiMD (sandstorm.de)


ofcourse i would always prefer good ide support for properties, than actual enforcement of those on language level - but since this is wayyyy harder to archive i was thinking about better runtime types.

and when a Neos.Fusion:DataStructure just returns an array its hard to know what you have got (runtime wise …)

im interested, what do you mean by that exactly?


i already got rid of the aop in my locally experiment: the challenge was for each unique $this->fusionObjectName i need to generate a php class based on the properties of the global first level prototype. Currently im recursive triggering this render once to build the php class once via $this->runtime->evaluate("<$this-fusionObjectName>") as i found no other fast way to get in the current $fusionObject only the properties / config of the global object (its merged at this point)

I think it would be great to release this an external prototype package to gain experience.