funeralzone/valueobjects
PHP 7.1+ value object toolkit for fundamental scalar-based VOs. Provides traits for strings/ints/etc, enums via constants, easy native serialization (fromNative/toNative), and encourages domain validation logic. Includes extension library for complex objects.
Installation Add the package via Composer:
composer require funeralzone/valueobjects
No additional configuration is required—it’s a drop-in library.
First Use Case: Creating a Value Object
Define a simple value object (e.g., Email) by extending \Funeralzone\ValueObjects\AbstractValueObject:
use Funeralzone\ValueObjects\AbstractValueObject;
class Email extends AbstractValueObject
{
protected $value;
public function __construct(string $value)
{
$this->value = $this->validate($value);
}
protected function validate(string $value): string
{
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException("Invalid email format.");
}
return $value;
}
public function getValue(): string
{
return $this->value;
}
}
Usage:
$email = new Email('user@example.com');
echo $email->getValue(); // "user@example.com"
Key Files to Explore
AbstractValueObject: Core class for inheritance.Exceptions: Built-in exceptions (e.g., InvalidValueException).ComparableValueObject: For objects needing comparison logic.class Age extends AbstractValueObject
{
public function __construct(int $value)
{
$this->value = $value < 0 ? 0 : $value; // Clamp negative values
}
}
ComparableValueObject for equals()/hashCode():
class UserId extends ComparableValueObject
{
public function equals($other): bool
{
return $other instanceof self && $this->value === $other->value;
}
}
class User extends Model
{
protected $casts = [
'email' => Email::class, // Automatically casts input to Email VO
];
}
public function rules(): array
{
return [
'email' => ['required', new Email], // Custom validator
];
}
$emails = collect([
new Email('a@example.com'),
new Email('b@example.com'),
])->map(fn ($email) => $email->getValue());
OrderTotal as a sum of Money VOs).return response()->json([
'user' => [
'email' => $user->email->getValue(), // Expose raw value
],
]);
email column) and hydrate VOs in accessors:
public function getEmailAttribute($value)
{
return new Email($value);
}
Over-Validation in Constructors
class Email extends AbstractValueObject
{
public static function fromString(string $value): self
{
return new self($value);
}
}
Circular Dependencies with Laravel Models
__toString() or custom JSON serialization:
protected $appends = ['email_value'];
public function getEmailValueAttribute()
{
return $this->email->getValue();
}
Performance with Large Collections
dd($this->value) in the constructor to inspect invalid inputs.InvalidValueException for graceful error handling:
try {
$email = new Email('invalid');
} catch (InvalidValueException $e) {
report($e); // Log to Laravel's error system
}
Custom Exceptions
Extend \Funeralzone\ValueObjects\Exceptions\InvalidValueException for domain-specific errors.
Dynamic Validation Use traits to share validation logic:
trait ValidatesEmail
{
protected function validate(string $value): string
{
return filter_var($value, FILTER_VALIDATE_EMAIL)
?: throw new InvalidValueException("Invalid email.");
}
}
Integration with Laravel Policies Use VOs in policy checks:
public function update(User $user, Email $email)
{
return $user->email->equals($email);
}
Email, UserId) for clarity.$this->expectException(InvalidValueException::class);
new Email('invalid');
$user = User::create(['email' => 'test@example.com']);
$this->assertInstanceOf(Email::class, $user->email);
How can I help you explore Laravel packages today?