PHPackages                             zolta/forge - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Validation &amp; Sanitization](/categories/validation)
4. /
5. zolta/forge

ActiveLibrary[Validation &amp; Sanitization](/categories/validation)

zolta/forge
===========

Domain layer foundation for PHP 8.2+ providing Value Objects with attribute-based validation, Entities, Aggregates, Rules, Specifications, Policies, Invariants, Transformers, and a framework-agnostic adapter system.

v1.0.1(1mo ago)112↓100%1MITPHPPHP ^8.2

Since Apr 24Pushed 1mo agoCompare

[ Source](https://github.com/zoltasoft/forge)[ Packagist](https://packagist.org/packages/zolta/forge)[ RSS](/packages/zolta-forge/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (13)Versions (3)Used By (1)

Zolta Forge
===========

[](#zolta-forge)

**The missing domain layer for PHP.**

Stop scattering validation across controllers, form requests, and service classes. Forge gives you true Domain-Driven Design primitives — Value Objects, Entities, Aggregates, Rules, Specifications, Policies, Invariants, and Transformers — all driven by PHP 8 attributes, all enforced at construction time, all framework-agnostic.

```
$email = Email::resolve(['address' => '  John@Example.com  ']);
// Trimmed → lowercased → format-validated → domain-ready. One line.
```

---

Why Forge?
----------

[](#why-forge)

### The problem

[](#the-problem)

PHP's ecosystem has excellent tools for persistence (Eloquent, Doctrine) and HTTP (Laravel, Symfony), but the **domain layer** — the part that enforces your business rules — is left as a DIY exercise. The result: validation logic scattered across controllers, form requests, middleware, and service classes. Rules are duplicated. Invariants are violated. Bugs hide in the gaps.

### What Forge does differently

[](#what-forge-does-differently)

ApproachHow it worksTrade-offManual VOsHand-written constructors with inline validationNo reuse, no composition, no discoverySpatie Laravel DataData transfer objects with castingPersistence-layer focused, no domain rules/specs/invariantsDoctrine EmbeddablesORM-coupled value typesTied to Doctrine lifecycle, no standalone resolution**Zolta Forge****Attribute-driven pipeline: Transform → Validate → Specify → Construct → Enforce****Zero inheritance tax, composable, framework-agnostic**Forge treats Value Object construction as a **pipeline**, not a constructor:

1. **`#[Transform]`** — Normalize raw input (trim, lowercase, parse dates)
2. **`#[UseRule]`** — Guard property constraints (non-empty, max length, regex, password strength)
3. **`#[UseSpecification]`** — Evaluate business logic (email format, allowed domain, minimum age)
4. **Nested VO resolution** — Recursively construct child Value Objects from raw arrays
5. **`#[UseInvariant]`** — Enforce class-level structural guarantees post-construction
6. **`#[UsePolicy]`** — Apply post-construction behavior (token generation, hash verification)

Every stage is **declarative** (PHP 8 attributes), **composable** (`.and()`, `.or()`, `.not()`), and **cacheable** (sub-millisecond on warm calls).

### Who is this for?

[](#who-is-this-for)

- Teams building **domain-rich applications** who want rules enforced at the model, not the controller
- Projects that need **shared domain logic** across multiple entry points (API, CLI, queue workers, events)
- Developers who want DDD without the ceremonial overhead of Java/C#-style frameworks

Installation
------------

[](#installation)

```
composer require zolta/forge
```

Laravel adapter discovery is automatic through Composer metadata.

---

What's in the box
-----------------

[](#whats-in-the-box)

### Value Objects with auto-resolution

[](#value-objects-with-auto-resolution)

The centerpiece. Declare properties with attributes — Forge handles the rest:

```
use Zolta\Domain\Attributes\Transform;
use Zolta\Domain\Attributes\UseRule;
use Zolta\Domain\Attributes\UseSpecification;
use Zolta\Domain\Rules\NonEmptyRule;
use Zolta\Domain\Transformers\EmailNormalizer;
use Zolta\Domain\Specifications\EmailFormatSpecification;
use Zolta\Domain\ValueObjects\ValueObject;

final class Email extends ValueObject
{
    public function __construct(
        #[Transform(EmailNormalizer::class, ['lowercase' => true])]
        #[UseRule(NonEmptyRule::class)]
        #[UseSpecification(EmailFormatSpecification::class)]
        public readonly string $address,
    ) {}
}

$email = Email::resolve(['address' => '  John@Example.com  ']);
// Pipeline: trim → lowercase → assert non-empty → validate format
// Result: $email->address === "john@example.com"
```

Nested VOs resolve recursively — a `UserCredential` containing an `Email` and `Password` resolves the entire tree from a flat array:

```
$credential = UserCredential::resolve([
    'email' => ['address' => 'john@example.com'],
    'password' => ['value' => 'S3cure!Pass'],
]);
```

### 8 built-in rules

[](#8-built-in-rules)

`NonEmptyRule` · `MaxLengthRule` · `RegexRule` · `UuidRule` · `PasswordRule` · `PasswordPolicyRule` · `PermissionNameRule` · `PositiveNumberRule`

All composable: `$rule->and(new MaxLengthRule(255))->or(new FallbackRule())`.

### 5 built-in specifications

[](#5-built-in-specifications)

`EmailFormatSpecification` · `AllowedDomainSpecification` · `UuidSpecification` · `MinAgeSpecification` + abstract base for your own.

Compose with logical operators: `$spec->and($other)`, `$spec->or($fallback)`, `$spec->not()`.

### 3 built-in transformers

[](#3-built-in-transformers)

`EmailNormalizer` · `DateTimeNormalizer` · `IdentifierNormalizer` — pipe them: `$t->and(new OtherTransformer())`.

### Invariants &amp; Policies

[](#invariants--policies)

**Invariants** enforce class-level structural guarantees after construction:

```
#[UseInvariant(CreditInvariant::class)]
final class Credit extends ValueObject { /* ... */ }
```

**Policies** apply post-construction behavior — token generation, hash verification, expiry checks:

```
#[UsePolicy(AccessTokenPolicy::class)]
final class AccessToken extends ValueObject { /* ... */ }
```

Built-in: `EmailVOInvariant` · `RoleNameInvariant` · `VerifiedAtInvariant` · `CreditInvariant` · `PasswordPolicy` · `EmailPolicy` · `AccessTokenPolicy`.

### Entities &amp; Aggregates

[](#entities--aggregates)

Event-recording domain objects with immutability protection:

```
class User extends AggregateRoot
{
    // Magic read access, immutable writes, domain event recording
    public static function create(...): self
    {
        $user = new self(...);
        $user->recordThat(new UserCreatedEvent($user->id));
        return $user;
    }
}
```

### Framework-agnostic adapter system

[](#framework-agnostic-adapter-system)

Forge auto-discovers framework adapters from Composer metadata — no manual registration, no framework coupling:

```
FrameworkBootstrap::boot(); // discovers Laravel, Symfony, or custom adapters
ContainerRegistry::set(app()); // PSR-11 compatible
$logger = ContainerRegistry::resolve(LoggerInterface::class);
```

Consumer packages (`zolta/cqrs`, `zolta/http`) use this same mechanism to stay decoupled.

---

Performance
-----------

[](#performance)

Benchmarked on a real application (Laravel 12, PHP 8.3, SQLite):

OperationCold (first call)Warm (cached)Single VO hydration2–5ms**&lt; 0.6ms**Complex command (3 nested VOs)9ms**&lt; 1ms**Reflection metadata resolution4ms**cached — 0ms**Forge caches ReflectionClass instances, property metadata, attribute data, and type resolution maps per class. After the first construction, subsequent VO creation is effectively free.

---

25+ built-in Value Objects
--------------------------

[](#25-built-in-value-objects)

Production-ready domain types for common use cases:

**Identity:** `UserId` · `RoleId` · `PermissionId` · `OAuthAccountId` · `AbstractUuid`
**Auth:** `Email` · `Password` · `AccessToken` · `RefreshToken` · `VerificationCode` · `UserCredential`
**OAuth:** `OAuthProvider` (Google, Microsoft, GitHub) · `OAuthProviderId`
**Profile:** `Username` · `RoleName` · `PermissionName` · `AvatarUrl`
**Domain:** `Credit` · `Description` · `Pagination` · `Terms`

Tests
-----

[](#tests)

```
composer run qa          # Full suite: lint + analyse + phpmd + rector + test
composer run test        # PHPUnit only
```

Monorepo runner:

```
./scripts/run-package-tests.sh packages/forge qa
```

**50 tests, 71 assertions** covering VO resolution, rule composition, specification logic, invariant enforcement, entity events, and adapter discovery.

---

Collaboration
-------------

[](#collaboration)

1. Keep Forge focused on domain concerns only.
2. Put integration/runtime concerns in adapters or consumer packages.
3. Run `composer run qa` before opening a PR.

---

Part of the Zolta Ecosystem
---------------------------

[](#part-of-the-zolta-ecosystem)

Forge is the **foundation layer** — consumed by the application and transport layers:

```
┌─────────────────────────────────────────────┐
│  zolta/http (Transport)                     │
│  Attribute-driven routing & response        │
├─────────────────────────────────────────────┤
│  zolta/cqrs (Application)                   │
│  Commands, queries, events, transactions    │
├─────────────────────────────────────────────┤
│  zolta/forge (Domain) ← you are here        │
│  Value Objects, rules, specs, entities      │
└─────────────────────────────────────────────┘

```

- **CQRS** hydrates commands and queries using Forge's VO resolution pipeline
- **HTTP** maps validated request data into domain objects through Forge
- Domain constraints are enforced **once**, at the model — not duplicated across controllers, handlers, or jobs

PackageLayerLink**zolta/forge****Domain**You are herezolta/cqrsApplication[`packages/cqrs`](../cqrs)zolta/httpTransport[`packages/http`](../http)---

Documentation
-------------

[](#documentation)

Full documentation is available in the [`docs/`](./docs/) directory.

License
-------

[](#license)

[MIT](LICENSE) © 2026 Redouane Taleb

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance91

Actively maintained with recent releases

Popularity9

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 91.7% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~3 days

Total

2

Last Release

42d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/751b94d8f42b73b4851757bbdb5eed691d145fb41e94df15ce3a69557ea9d8e7?d=identicon)[redouaneT](/maintainers/redouaneT)

---

Top Contributors

[![redouaneT](https://avatars.githubusercontent.com/u/96720720?v=4)](https://github.com/redouaneT "redouaneT (11 commits)")[![Copilot](https://avatars.githubusercontent.com/in/1143301?v=4)](https://github.com/Copilot "Copilot (1 commits)")

---

Tags

phplaravelspecificationValue Objectentityrulesdddtransformerdomainhexagonal-architectureforgepoliciesaggregate-rootzoltainvariants

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan, Rector

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/zolta-forge/health.svg)

```
[![Health](https://phpackages.com/badges/zolta-forge/health.svg)](https://phpackages.com/packages/zolta-forge)
```

###  Alternatives

[api-platform/core

Build a fully-featured hypermedia or GraphQL API in minutes!

2.6k50.1M306](/packages/api-platform-core)[craftcms/cms

Craft CMS

3.6k3.6M2.9k](/packages/craftcms-cms)[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.4M195](/packages/sulu-sulu)[api-platform/symfony

Symfony API Platform integration

354.0M107](/packages/api-platform-symfony)[api-platform/serializer

API Platform core Serializer

244.3M70](/packages/api-platform-serializer)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

585.4M506](/packages/shopware-core)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
