We use cookies and similar technologies to enhance your browsing experience, analyze site traffic, and personalize content. You can customize your preferences at any time.
Manage your cookie consent preferences.

These cookies are essential for the proper functioning of our website. They enable core functionality such as page navigation, access to secure areas, and basic user interface features.

These cookies enable the website to provide enhanced functionality and personalization. They may be set by us or by third-party providers whose services we have added to our pages.

These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site. They help us to know which pages are the most and least popular and see how visitors move around the site.

These cookies are used to deliver advertisements that are more relevant to you and your interests. They are also used to limit the number of times you see an advertisement and help measure the effectiveness of advertising campaigns.

person with objects swirling around their head

Building an Enterprise Invoice Value Object in PHP 8.4+

Designing robust domain models requires strict encapsulation. Historically, implementing Domain-Driven Design (DDD) principles in PHP came at a heavy cost: endless boilerplate code, manual data validation loops, and exhaustive encapsulation setups that often drowned out the core business intent beneath layers of syntactic noise.

With the release of PHP 8.4, backend engineering has fundamentally shifted toward engine-level modernity. By leveraging advanced features such as Constructor Property Promotion, Asymmetric Visibility (public, private (set)), and Property Hooks, developers can now build elegant, highly performant, and resilient domain layers natively within the Zend Engine.

This guide provides a production-grade blueprint for building an enterprise-ready Invoice Value Object that cleanly enforces business invariants without sacrificing system performance.

The Architecture: Value Objects vs. Entities

In Domain-Driven Design, Value Objects are defined not by a unique identifier, but entirely by their attributes. They represent descriptive aspects of the domain (such as Money, Email, or an Invoice Summary) and are inherently immutable. When any value changes, it does not mutate the existing instance; instead, it yields a completely new instance.

Historically, achieving this structural safety required writing exhausting boilerplate code—private properties, multi-line constructor assignments, and verbose public getter methods just to make the data publicly readable but protected from external modification. PHP 8.4 eliminates this tradeoff between data integrity and verbose boilerplate, allowing us to build lightweight, self-validating, and engine-optimized structural blocks.

Production-Grade Implementation

Below is a detailed implementation of an enterprise-grade Invoice Value Object. It combines strict execution types, constructor promotion, asymmetric visibility constraints, and virtual property hooks to manage internal calculations natively.

php
1<?php
2 
3declare(strict_types=1);
4 
5namespace App\Domain\Billing;
6 
7use InvalidArgumentException;
8 
9/**
10 * Enterprise Invoice Value Object
11 * Optimized for PHP 8.4+ Engine-Level Encapsulation
12 */
13final class Invoice
14{
15 /**
16 * Virtual property computed dynamically on demand.
17 */
18 public float $total {
19 get => $this->subtotal + $this->tax;
20 }
21 
22 /**
23 * Leverage Constructor Property Promotion and Asymmetric Visibility
24 * to protect individual invariants and properties natively.
25 */
26 public function __construct(
27 public private(set) string $invoiceNumber,
28 public private(set) float $subtotal {
29 set {
30 if ($value <= 0) {
31 throw new InvalidArgumentException("Subtotal must be greater than zero.");
32 }
33 $this->subtotal = $value;
34 }
35 },
36 public private(set) float $tax {
37 set {
38 if ($value < 0) {
39 throw new InvalidArgumentException("Tax cannot be negative.");
40 }
41 $this->tax = $value;
42 }
43 },
44 public private(set) string $currency = 'USD'
45 ) {}
46 
47 /**
48 * Business Logic: Returns a completely new instance to preserve immutability.
49 */
50 public function withAdjustedTax(float $newTax): self
51 {
52 return new self(
53 $this->invoiceNumber,
54 $this->subtotal,
55 $newTax,
56 $this->currency
57 );
58 }
59}

Deep Dive: Engine-Level Innovations

1. Asymmetric Visibility (public private(set))

The combination of public readability and restricted write access allows properties to be safely exposed. External layers can query data directly ($invoice->subtotal) without routing execution through userland PHP getter methods, while mutations are prevented outside of the object's explicit boundaries. This natively establishes compile-time and runtime data immutability.

2. Property Hooks (get and set)

Property Hooks allow us to attach operations directly to the property declaration.

  • The set Hook: Operates on input validation and manipulation automatically when data is assigned during instantiation. The $value variable represents the incoming data payload.

  • The get Hook: Used here to build a virtual property ($total). The property $total does not exist as a standard state property in memory ; it is computed dynamically on demand when accessed.

3. Shorthand vs. Block Syntax

PHP 8.4 supports both shorthand arrow functions (=>) for immediate, straightforward transformations or retrievals, and full block syntax ({ ... }) for complex logic blocks requiring validation rules and explicit assignment execution paths.

Why This Wins the Backend Battle

Shifting data protection and access rules from userland PHP code directly into the engine core provides substantial engineering advantages for modern backend applications:

  1. Massive Memory & Performance Optimizations: By routing data access patterns directly through the engine core rather than manual userland PHP getter/setter methods, execution pathways are streamlined and instruction overhead is minimized.

  2. Elimination of Static Noise: Codebases become strictly expressive. Domain entities and value objects focus directly on representing business rules and core domain logic rather than working around language syntax limitations.

  3. Seamless Tooling Integration: Modern static analysis engines like PHPStan and Psalm parse asymmetric visibility definitions and property hooks natively. This guarantees ironclad code predictability and type safety within isolated development loops or GitHub Actions CI/CD pipelines before production deployments.

External Sources & Further Reading

  • Posted on: June 22nd, 2026
  • By: Darren Odden
  • On: Blog
  • property hooks
  • assymetric visibility

Share this on social media

About the author

Muppet Darren.

Darren Odden

Founder

Darren Odden is a seasoned software developer and web architect specializing in the modern PHP and Laravel ecosystems, where he designs elegant APIs and robust web applications. As a dedicated tech advocate, he focuses on community building and championing clean, modern development practices. When he isn’t diving into code or fine-tuning tech stacks, Darren balances his digital life by hitting the open road with his family in their travel trailer, camping, solving crossword puzzles, and immersing himself in the rich subcultures of classic hip-hop and vintage graffiti art.

Built for Developers, by Developers

Join the movement and discover why modern PHP is the sophisticated choice for elegant, high-scale applications in 2026.

Home

Policy

Reach Us

©2026 doPHP.dev