StringProperty, IntegerProperty, EnumProperty, …) cover most fields. When a payload carries structured data — a nested object or a list of objects — you compose it with three classes:
AbstractPropertyCollection— an iterable container of properties, mode-aware so you can vary fields betweenCREATE,UPDATE,VIEW,LIST,DELETE.AbstractObjectProperty— a property whose value is an object; it returns a collection.ArrayProperty— a property whose value is an array of objects; it wraps anAbstractObjectProperty.
Scenario
We’ll model aPOST /api/v1/orders request that looks like this:
customer object (with a nested shipping_address), and an items array of uniform objects.
1. Leaf collection: AddressPropertyCollection
Start with the innermost shape. A collection takes a mode constant in its constructor — you only need to branch when a field is present in some modes but not others.
2. Object wrapper: AddressObjectProperty
The property that lives inside another schema and plugs the collection into the framework’s object machinery:
3. Composite collection: CustomerPropertyCollection
Customer contains email plus the nested shipping_address object. Compose via addProperty(new AddressObjectProperty(...)):
4. Line items: ArrayProperty of objects
For "items": [...] you wrap an AbstractObjectProperty in an ArrayProperty.
5. Use them in a request
Reading nested data in the controller
TheParameterBag magic getter returns the raw typed value. For nested objects and arrays of objects, it returns an array:
RequestValidationMiddleware already validated every level before you got here — line1 is a non-empty string, country is one of the enum values, quantity is an integer. If anything was wrong the client got a 422 with a field path like items.0.quantity.
Mode-specific fields
Branch on the$mode you pass down from the top:
AbstractPropertyCollection::MODE_VIEW when composing the response, MODE_CREATE when composing the request — same collection class, correct schema in each place.
Tips
- Keep wrappers boring. An
AbstractObjectPropertysubclass usually only carries the$modeand delegates to a collection. Logic belongs in the collection (or the domain). - Reuse wrappers across requests and responses.
CustomerObjectPropertyused inCreateOrderRequestis the same class you’d use in aViewOrderResponse— swap the mode. - Validators stay per-property. Call
setMinLength,setMaxLength,setPattern,setIsRequired, etc. on the individualStringProperty/IntegerProperty/ etc. instances inside the collection.