PHPackages                             slepic/value-object - 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. [Utility &amp; Helpers](/categories/utility)
4. /
5. slepic/value-object

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

slepic/value-object
===================

Simple value objects and enums

v0.2.0(5y ago)201492MITPHPPHP &gt;=7.4CI failing

Since Jun 8Pushed 3y ago1 watchersCompare

[ Source](https://github.com/slepic/php-value-object)[ Packagist](https://packagist.org/packages/slepic/value-object)[ RSS](/packages/slepic-value-object/feed)WikiDiscussions master Synced 5d ago

READMEChangelog (6)Dependencies (3)Versions (7)Used By (0)

php-value-object
================

[](#php-value-object)

PHP Value Objects

Requirements
============

[](#requirements)

- PHP &gt;=7.4

Installation
============

[](#installation)

```
composer require slepic/value-object

```

Introduction
============

[](#introduction)

This library aims to provide unification of commonalities of all value objects. Additionally, it provides some features that take advantage of the unified environment:

- unified language to describe violations can be integrated with validation systems
- common base value objects for enums, collections and scalar types with restrictions
- interfaces to unify conversions from and to primitive types, or even between objects
- automatic construction of composite value objects using their own class definition

What is a value object?
-----------------------

[](#what-is-a-value-object)

Value objects are guards of validity. Once a value object is constructed, it contains valid data. And as long as that object lives, we don't have to validate it again.

```
final class FullName
{
  public string $firstName;
  public string $surname;

  public function __construct(string $firstName, string $surname)
  {
    if ($firstName === '') {
      throw \InvalidArgumentException(''First name cannot be empty.');
    }

    if ($surname === '') {
      throw new \InvalidArgumentException('Surname cannot be empty.');
    }

    $this->firstName = $firstName;
    $this->surname = $surname;
  }
}

```

Note: For simplicity, I am exposing everything in the examples as public properties which allows modifications. They should really be private with public getters.

What commonalities do value objects have a how we unify them?
-------------------------------------------------------------

[](#what-commonalities-do-value-objects-have-a-how-we-unify-them)

- They are (or at least should be) immutable
    - we cannot fully enforce that the value objects you create are immutable
    - we can only support you in creating immutable objects
    - we provide base classes and traits that prevent implementation of some mutable magic methods and mutable methods of \\ArrayAccess
- They cannot be constructed into invalid state
    - we cannot fully enforce this either
    - we can support you by providing base value objects that obey the rule
- Attempt to construct them with invalid data leads to exception
    - we can unify what kind of exception is thrown
- They can be constructed from primitive data types
    - we can unify how value objects expose this ability
- They can be converted to primitive data types
    - we can unify how value objects expose this ability

How can we take advantage of the unifications?
----------------------------------------------

[](#how-can-we-take-advantage-of-the-unifications)

### Unified errors

[](#unified-errors)

Since value objects need valid state, they have to check it. And this inherently means that they are doing validations and they have to do it themselves. If we just let value objects throw \\InvalidArgumentException and instead we do validations (with client error reporting) beforehand, we are basically doing the validation twice. Once to feed the client with reasonable explanation where he screwed up and once in the value object to make sure it's not constructed from bullocks. And if we are lazy, we omit one or the other (or both in worst case).

Omitting validation outside value objects means that our value objects may throw and we end up with 500 Internal Server Error. Omitting validation inside value objects means that we will never be truly sure that they are valid. Omitting them both is just disaster. Having both is redundant and may lead to de-synchronization between the two.

If our value objects do the validations (which they should anyway) and offer a unified way to describe their expectations and eventual violations of said expectations, they effectively force you to validate your data:

- once
- in whichever point in code you find appropriate
- using the logic of your value objects

Unification of how value objects represent violations allows applications to incorporate value objects errors into their input validation process.

However, the way this incorporation is done for specific application is outside the scope of the library.

This library attempts to unify the error exception as `ViolationExceptionInterface` with a `getViolations(): array` method. A default implementation `ViolationException` is also provided by the library.

`ViolationInterface` is a marker interface for violations and each violation class name represents an error code. With possibility to carry additional information exposed as properties/getters. This also allows for error code inheritance and avoids error code conflicts between vendors.

Having an array of violations also gives is the option to report multiple violations at the same time.

```
final class FullName
{
  public string $firstName;
  public string $surname;

  public function __construct(string $firstName, string $surname)
  {
    $violations = []
    if ($firstName === '') {
      $violations[] = new Violation('First name cannot be empty.');
    }

    if ($surname === '') {
      $violations[] = new Violation('Surname cannot be empty.');
    }

    if (\count($violations) > 0) {
      throw new ViolationException($violations);
    }

    $this->firstName = $firstName;
    $this->surname = $surname;
  }
}

try {
  $slimShady = new FullName('Slim', 'Shady');
} catch (ViolationExceptionInterface $e) {
  return $this->processViolations($e->getViolations());
}

```

A set of implementations is provided by this library, including some violations that describe nested errors of collections.

### Unified conversions

[](#unified-conversions)

Often value objects provide named constructors that allow to construct them from a primitive. And it isn't also uncommon that value objects can convert themselves to a primitive type.

This library provides a set of interfaces that define how conversion from and to primitive types should look like.

In the example below, they are the `FromStringConstructableInterface` and `ToStringConvertibleInterface`, defining the `public static function fromString(string $value): self` and `public function __toString(): string` methods respectively.

This gives as unified environment. It is sure easier to work with if all value objects that can be constructed from a single string value have the same named constructor for this. And, well, in case of conversion to string, that is already covered by PHP's magic method `__toString()`, but we also offer interfaces for int, float, etc. that go about the same names like `ToIntConvertibleInterface` and so on...

```
final class FullName implements FromStringConstructableInterface, ToStringConvertibleInterface
{
  // ...

  public static function fromString(string $fullName): FullName
  {
    $parts = \explode(' ', $fullName, 2);
    if (\count($parts) !== 2) {
      throw \InvalidArgumentException('Full name must contain first name, a space and the surname.');
    }
    return new FullName($parts[0], $parts[1]);
  }

  public function __toString(): string
  {
    return $this->firstName . ' ' . $this->surname;
  }
}

$slimShady = FullName::fromString('Slim Shady');
echo (string) $slimShady;

```

And it allows to automatically construct composite value objects using their own class definition. See collections section.

### Common value objects

[](#common-value-objects)

Now, wait a minute! Both first name and surname check the same thing. Let's get rid of that duplication.

```
final class StringIsEmpty extends Violation
{
  public function __construct(string $message = '')
  {
    parent::__construct($message ?: 'Value cannot be empty');
  }
}

class NonEmptyString
{
  private string $value;

  public function __construct(string $value)
  {
    if ($value === '') {
      throw ViolationException::for(new StringIsEmpty());
    }
    $this->value = $value;
  }

  public function __toString(): string
  {
    return $this->value;
  }
}

final class FullName
{
  public NonEmptyString $firstName;
  public NonEmptyString $surname;

  public function __construct(NonEmptyString $firstName, NonEmptyString $surname)
  {
    $this->firstName = $firstName;
    $this->surname = $surname;
  }
}

```

We have moved the responsibility for checking the value emptiness to the `NonEmptyString` class. However we have lost the ability to be specific about which property it is that is empty.

That is however responsibility of the caller of the constructor, because he is now creating those NonEmptyString value objects.

Let's see such a caller in the form of a factory that creates the object from primitive strings. It now manages the error codes and messages and overrides the defaults, while using the same codes (violation classes).

```
function createFullName(string $firstName, string $surname): FullName
{
    $violations = [];

    try {
      $f = new NonEmptyString($firstName);
    } catch (ViolationExceptionInterface $e) {
      $violations[] = new StringIsEmpty('First name cannot be empty.');
    }

    try {
      $s = new NonEmptyString($surname);
    } catch (ViolationExceptionInterface $e) {
      $violations[] = new StringIsEmpty('Surname cannot be empty.');
    }

    if (\count($violations) > 0) {
      throw new ViolationException($violations);
    }

    return new FullName($f, $s);
}

```

But we have also added a new type that is now a guarantee of non empty string.

And you know, if you don't care that much for all the violations, you do just this:

```
function createFullName(string $firstName, string $surname): FullName
{
    return new FullName(
        new NonEmptyString($firstName),
        new NonEmptyString($surname)
    );
}

```

Anyway, we can now avoid some checks on other places.

```
function extractFirstChar(NonEmptyString $text): string
{
  // if we accepted the primitive `string $text` here, we should check its emptiness
  // and throw an exception otherwise. Now we don't have to :)
  return \substr((string) $text, 0, 1);
}

echo extractFirstChar($fullName->firstName) . '.' . extractFirstChar($fullName->surname) . '.';

```

This effectively forces the caller to validate the input at some point, while leaving the function to care only about its logic.

This library provides a set of base value objects that encapsulate some common restrictions we have on our primitive data types.

Note: ViolationInterface implementations should only describe the error, what it is that was violated. If consumers want to know everything that could have been violated, they have to reach for the value object type itself.

Immutable Traits
----------------

[](#immutable-traits)

This package offers two traits that support immutability of value objects.

`ImmutableObjectTrait` - disables implementation of magic `__set` and `__unset` methods.

`ImmutableArrayAccessTrait` - disables implementation of `ArrayAccess::offsetSet` and `ArrayAccess::offsetUnset`.

These traits are used by all the base value objects in this package. And you are encouraged to use them on your value objects as well.

Nevertheless you are still free to create public properties. There's actually one exception in this package that relies on public properties - DataTransferObject class. But other than that, we discourage you from using them, although it is often a bit less writing if you do.

You are also always free to modify your objects using reflection, etc. So to be truly immutable is basically impossible in PHP. But we try as much as we can :)

Base Value Objects
------------------

[](#base-value-objects)

Often we need to wrap primitive values and enforce some kind of limitation, like a limit on a string length, allow only subset of all characters in a string, or limit a maximum value of a number.

Simple implementations of scalar objects with these common restrictions can be found in the package.

### Strings

[](#strings)

- `Slepic\ValueObject\Strings\StringValue`
    - a string value object without restrictions
    - violation: `StringViolation`
- `Slepic\ValueObject\Strings\MaxRawLengthString`
    - a string value object with max length (using strlen)
    - children need to implement `protected static function maxLength(): int`
    - violation: `StringTooLong`
- `Slepic\ValueObject\Strings\MinRawLengthString`
    - a string value object with min length (using strlen)
    - children need to implement `protected static function minLength(): int`
    - violation: `StringTooShort`
- `Slepic\ValueObject\Strings\BoundedRawLengthString`
    - string value object with both min and max length (using strlen)
    - children need to implement both `protected static function minLength(): int` and `protected static function maxLength(): int`
    - violation: `StringLengthOutOfBounds`
- `Slepic\ValueObject\Strings\MaxMbLengthString`
    - a string value object with max length (using mb\_strlen)
    - children need to implement `protected static function maxLength(): int`
    - violation: `StringTooLong`
- `Slepic\ValueObject\Strings\MinMbLengthString`
    - a string value object with min length (using mb\_strlen)
    - children need to implement `protected static function minLength(): int`
    - violation: `StringTooShort`
- `Slepic\ValueObject\Strings\BoundedMbLengthString`
    - string value object with both min and max length (using mb\_strlen)
    - children need to implement both `protected static function minLength(): int` and `protected static function maxLength(): int`
    - violation: `StringLengthOutOfBounds`
- `Slepic\ValueObject\Strings\RegexTemplateString`
    - a string value object which checks the value to match a regex pattern
    - children need to implement `protected static function pattern(): string`
    - violation: `StringPatternViolation`

### Integers

[](#integers)

- see Slepic\\ValueObject\\Integers namespace

### Floats

[](#floats)

- see Slepic\\ValueObject\\Floats namespace

### Enums

[](#enums)

- see Slepic\\ValueObject\\Enums namespace

We consider several axis for enums

- strong vs. weak
    - strong enums
        - strong enums exist as singleton instances
        - strict comparision using `===` and `!==` is possible
    - weak enums
        - new instances can be created to represent the same value
    - must compare the underlying value to tell if the instances are the same
- value types
    - all allowed values of an enum must be of same type
    - we distinguish between string enums and int enums
    - float enums can be supported in future, but we dont have a use case for it now
- the way the set of allowed values is defined
    - class's constants values
    - class's constants keys
    - class's named constructors
    - custom way, driven by the enum class.

Each aspect has cons and pros. Currently only strong string enums are implemented.

### Collections

[](#collections)

The package supports 3 main types of collections

- see Slepic\\ValueObject\\Collections namespace

#### DataTransferObject

[](#datatransferobject)

- expects keys of the array to match its public property name and the values must match the corresponding property type
- the property types are denoted by their typehints.
- violations:
    - InvalidPropertyValue - if a known property has errors
    - MissingRequiredProperty - if a property without default value is not provided
    - UnknownProperty - if input contains property that does not exist on the DTO
        - this can be turned off in the children by overriding the class's protected constant IGNORE\_UNKNOWN\_PROPERTIES

```
class MyDto extends DataTransferObject
{
  public int $intProperty;
  public string $stringProperty;
}

new MyDto([
  'intProperty' => 10,
   'stringProperty' => 'text',
]); // ok
new MyDto([]); //ViolationsException with 2 MissingRequiredProperty violations

```

Note: If you sometimes need to construct the object not from array, but directly from separate variables, consider not extending DataTransferObject, implement your object, with own constructor and use `FromArrayConstructor` helper class to simplify the creation from array.

#### ArrayList

[](#arraylist)

- expects iterable with zero based index keys and all values matching the same type
- the value type is denoted by the return type of the `current()` method.
- violations:
    - InvalidListItem - when an item violates the expected type
    - TypeViolation - when indexes are not zero based

```
class MyList extends ArrayList
{
   public function current(): int
   {
      return parent::current();
   }
}

new MyList([1,2,3]); // ok
new MyList(['1', 2, 3]); // ViolationException with a InvalidListItem violation

```

#### ArrayMap

[](#arraymap)

- expects associative array with string keys and values matching the same type
- the value type is denoted by the return tpe of the `current()` method.
- violations:
    - InvalidPropertyValue - if a property value is invalid.

```
class MyMap extend ArrayMap
{
  public function current(): float
  {
     return parent::current();
  }
}

new MyMap(['a' => 1.0, 'b' => 2.0, 'c' => 3.5]); // ok
new MyMap(['a' => 1, 'b' => 2.0, 'c' => 3.5]); //ViolationException with InvalidPropertyValue

```

#### FromArrayConstructor

[](#fromarrayconstructor)

This is not a value object, it is a static helper class which simplifies construction of objects from associative array of named parameters for their class constructor.

```
class MyValueObject
{
  private string $x;
  private int $y;

  public function __construct(string $x, int $y)
  {
    $this->x = $x;
    $this->y = $y;
  }

  public static function fromArray(array $data): self
  {
    return FromArrayConstructor::constructFromArray(static::class, $data);
  }

  public function with(array $data): self
  {
    return FromArrayConstructor::combineWithArray($this, $data);
  }

  public functin toArray(): array
  {
    return FromArrayConstructor::extractConstructorArguments($this);
  }
}

$vo = MyValueObject::fromArray([
  'x' => 'test',
  'y' => 10,
]);

$vo2 = $vo->with(['y' => $vo->y + 1]);

```

This helper also supports upcasting and downcasting. See upcasting/downcasting sections.

Constructor parameters must have a default value to become optional in the input array. The parameters also must have a typehint.

By default the method reports any unexpected properties of the input through `UnknownProperty` violation. This can be turned off by passing true as the 3rd parameter `$ignoreExtraProperties`.

Basically, this method throws the same violations as DataTransferObject.

The `combineWithArray` method expects that non-instance properties exists with the same name as each constructor parameter, that is not passed as the `$data` argument to the `combineWithArray` method. These properties must have a compatible type with the respective constructor parameter. Calling the `combineWithArray` method on objects that don't obey this rule, will result in a LogicException and no violations will be reported.

The `extractConstructorArguments` method expects non-instance properties exist with the same name as each constructor parameter, These properties must have a compatible type with the respective constructor parameter. Calling the `extractConstructorArguments` method on objects that don't obey this rule, will result in a LogicException and no violations will be reported.

Note: writing these value objects will become even easier with PHP8's constructor promotion. If you only need your object constructed from array and the constructor itself seems like a burden, you should consider DataTransferObject instead.

#### DataStructure

[](#datastructure)

This is basically a wrapper for FromArrayConstructor capabilities, which also provides from and to array upcasting/downcasting capabilities. This is the most solid base for an immutable data structure, if you are willing to write the constructor with all its properties. If you are not willing, use DataTransferObject. However with PHP8's constructor promotion feature, this will become equally simple and the DataStructure class will become the choice #1.

```
class MyStructure extends DataStructure
{
  private NonEmptyString $name;
  private PositiveInteger $age;

  public function __construct(NonEmptyString $name, PositiveInteger $age)
  {
    $this->name = $name;
    $this->age = $age;
  }

  public function getName(): NonEmptyString
  {
    return $this->name;
  }

  public function getAge(): PositiveInteger
  {
    return $this->age;
  }
}

// automatically construct from array, potentialy using upcasting/downcasting
$vo = MyStructure::fromArray([
  'name' => 'Slim Shady',
  'age' => 18,
]);

// automatic withers with upcasting/downcasting ability
$vo2 = $vo->with(['age' => 19]);

// automatic to array conversion
echo \json_encode($vo2->toArray()); // {"name": "Slim Shady", "age": 19}

$vo->with(['name' => '']); // throws ViolationExceptionInterface

```

### Standards

[](#standards)

Additionaly we provide a set of standard value objects for common things, like email, etc. But this sections is currently not ready and in future this may probably be in a separate package.

- see Slepic\\ValueObject\\Standard namespace

### Upcasting

[](#upcasting)

Whenever a collection expects a value object type and it receives a primitive type, it will look for the appropriate upcasting interface on the target value object class. If it exists, it will automatically construct the value object using the interface.

```
final class MyDto extends DataTransferObject
{
  public StringValue $string;
}

new MyDto([
  'string' => 'value',
]);

```

Existing upcasting interfaces are:

- FromIntConstructableInterface
- FromFloatConstructableInterface
- FromStringConstructableInterface
- FromArrayConstructableInterface
- FromObjectConstructableInterface
- FromBoolConstructableInterface

### Downcasting

[](#downcasting)

Whenever a collection expects a primitive type and it receives an object, it will look for the appropriate downcasting interface on the value. If it exists it will be used to obtain the primitive value.

```
fina class MyDto extends DataTransferObject
{
  public string $string;
}

new MyDto([
  'string' => new StringValue('value'),
]);

```

Existing downcasting interface are:

- ToIntConvertibleInterface
- ToFloatConvertibleInterface
- ToStringConvertibleInterface
- ToArrayConvertibleInterface
- ToBoolConvertibleInterface

FAQ
===

[](#faq)

### Is this only usable for validation

[](#is-this-only-usable-for-validation)

No. That's only a side effect that it allows to enhance and integrate with your validation system.

### What other use cases are there.

[](#what-other-use-cases-are-there)

This is basically for any use case where you would use a value object. It just helps you write them in unified way and simplifies some of its concerns.

### How about contextual validation? Some field must be an email if another field has a specific value?

[](#how-about-contextual-validation-some-field-must-be-an-email-if-another-field-has-a-specific-value)

That is up to the constructor of the value object. Such a rule cannot be attributed to either of the fields alone. It's absolutely fine to define your own violation class for that and eventually reuse it in multiple value objects.

### How about nested validation rules? A client (user/api consumer etc) is sending data to your app and it gets back some errors? How do I communicate back "hey, addresses\[5\].state is not a valid state"?

[](#how-about-nested-validation-rules-a-client-userapi-consumer-etc-is-sending-data-to-your-app-and-it-gets-back-some-errors-how-do-i-communicate-back-hey-addresses5state-is-not-a-valid-state)

The communication of invalid state to the client is under control of your application. You can take advantage on unified violations environment, but you still have to be aware of what kinds of violations your value objects throw and represent each of them accordingly. Although violations are represented as classes, they really are just simple error codes with additional features leveraging the PHP class system. The violations from Collections namespace should make it easy enough to create nested violations for nested value objects. See the code of the collections to see how they are used. And you can also check their tests to see how we check for what happened in the nested violations tree.

### How about custom validation messages?

[](#how-about-custom-validation-messages)

Validation messages provided by ViolationInterface::getMessage() are not meant to be communicated to the client directly. They represent an immediately readable explanation of the violation for a developer. But if the violations are to be communicated to a general user, the messages should be generated within your application's validation system, based on the error codes and eventually other violation properties.

You can use the default messages probably only on API where devs are the only ones who is going to read them.

Chances are though, that in future the ViolationInterface::getMessage() method will be removed entirely.

Some component that simplify this task of integrating the violations into validation systems may be created in future. At this point this is out of the scope of the library, and it would probably become a separate package anyway.

### How about validation that requires dependencies (eg: a database connection)?

[](#how-about-validation-that-requires-dependencies-eg-a-database-connection)

It is of course possible to throw the unified ViolationExceptionInterface from any factory you want. But this cannot happen within the automatic upcasting, since it happens through static named constructors. But there is no problem passing an already created value object to an automatically constructed value object from this package. And that makes it irrelevant whether the passed-in value object needed database to be created or not.

Similar projects and inspiration
================================

[](#similar-projects-and-inspiration)

- [spatie/data-transfer-object](https://github.com/spatie/data-transfer-object)
    - This was a great inspiration to begin with
    - The class DataTransferObject got its name because of this package
- [immutablephp/immutable](https://github.com/immutablephp/immutable)
    - Rather simpler implementation of immutable value objects
    - This inspired the creation of the immutable traits in this package

Thank you, guys!

###  Health Score

27

—

LowBetter than 49% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity20

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity49

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 ~6 days

Total

6

Last Release

2139d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/5dd1cd12d4fe4974ad1e3609065ac4eb13c29023606c289c08153c432da827cf?d=identicon)[slepic](/maintainers/slepic)

---

Top Contributors

[![slepic](https://avatars.githubusercontent.com/u/8199404?v=4)](https://github.com/slepic "slepic (11 commits)")[![gravataLonga](https://avatars.githubusercontent.com/u/1126525?v=4)](https://github.com/gravataLonga "gravataLonga (1 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPsalm

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/slepic-value-object/health.svg)

```
[![Health](https://phpackages.com/badges/slepic-value-object/health.svg)](https://phpackages.com/packages/slepic-value-object)
```

PHPackages © 2026

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