Skip to content

Binding objects

Greg Bowler edited this page Apr 12, 2026 · 15 revisions

Associative arrays are fine for quick examples, but in real applications we often already have proper objects representing our data.

DomTemplate can bind those objects directly, which means we can keep the type safety and behaviour of our domain model instead of flattening everything into arrays first, and objects also come with additional benefits when binding.

Public properties are bindable by default

readonly class Customer {
	public function __construct(
		public string $id,
		public string $name,
		public DateTime $createdAt,
	) {}
}

HTML:

<h1 data-bind:text="name">Customer name</h1>
<p>Customer ID: <span data-bind:text="id">000</span></p>
<time data-bind:text="createdAt">2000-01-01</time>

PHP:

$binder->bindData(new Customer(
	id: "abc123",
	name: "Cody",
	createdAt: new DateTime("2016-07-06 15:04:00"),
));

If a public property can be turned into a string, DomTemplate can use it as a bind value.

#[Bind] for explicit bindable methods

Sometimes the value that will be bound to the page needs calculating, rather than setting it as a public property.

use GT\DomTemplate\Bind;

readonly class Customer {
	public function __construct(
		public string $name,
		private DateTime $createdAt,
	) {}

	#[Bind("yearsActive")]
	public function calculateYearsActive():int {
		return $this->createdAt->diff(new DateTime())->y;
	}
}

HTML:

<p><span data-bind:text="name">Customer</span> has been with us for <span data-bind:text="yearsActive">0</span> years.</p>

Here we can expose exactly the value the template needs without teaching the template how to calculate it.

#[BindGetter] for inferred names

If the method is already shaped like a getter, we can skip the explicit key name.

use GT\DomTemplate\BindGetter;

readonly class Country {
	public function __construct(
		public string $code,
	) {}

	#[BindGetter]
	public function getName():string {
		return match($this->code) {
			"GB" => "United Kingdom",
			"JP" => "Japan",
			default => "Unknown",
		};
	}
}

getName() becomes the bind key name when using the BindGetter attribute.

For example:

  • getName() -> name
  • getTotalCost() -> totalCost
  • getFormattedDate() -> formattedDate

Nested object keys

Objects can reference other bindable objects, and DomTemplate can follow that chain with dot notation.

readonly class Address {
	public function __construct(
		public string $street,
		public Country $country,
	) {}
}

readonly class Customer {
	public function __construct(
		public string $name,
		public Address $address,
	) {}
}

HTML:

<h1 data-bind:text="name">Customer</h1>
<p data-bind:text="address.street">Street</p>
<p data-bind:text="address.country.name">Country</p>

If we bind a Customer, DomTemplate walks through the nested objects and resolves the final value.

Note

Remember, all of these practices apply when binding lists of objects too. Each data-list element can have bind properties referencing object properties within the current iteration.

Iterable objects

An object can also be made to represent a list.

If the object is iterable, bindData($object) can do two jobs in one go:

  • bind the object's own key/value data
  • bind list templates from the object as an iterable

This is useful for things like a CalendarDay object that has properties such as dateString, but also yields CalendarEvent objects.

asArray()

If an object defines an asArray() method, DomTemplate will use that as a key/value representation before looking any deeper.

That gives us a predictable override when we want to control exactly how an object is flattened for the template.

A practical rule of thumb

  • use public properties when the object is already a tidy view model
  • use #[Bind] for computed values with custom names
  • use #[BindGetter] for ordinary getter-style methods
  • use nested objects when the page naturally follows the domain structure

Objects and lists often overlap, but tables have their own rules. Read about it in the binding tables section.

Clone this wiki locally