Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Valueobjects Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation Add the package via Composer:

    composer require funeralzone/valueobjects
    

    No additional configuration is required—it’s a drop-in library.

  2. 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"
    
  3. Key Files to Explore

    • AbstractValueObject: Core class for inheritance.
    • Exceptions: Built-in exceptions (e.g., InvalidValueException).
    • ComparableValueObject: For objects needing comparison logic.

Implementation Patterns

1. Validation and Immutability

  • Pattern: Enforce strict validation in the constructor.
    class Age extends AbstractValueObject
    {
        public function __construct(int $value)
        {
            $this->value = $value < 0 ? 0 : $value; // Clamp negative values
        }
    }
    
  • Why? Ensures objects are always in a valid state post-creation.

2. Comparison Logic

  • Extend ComparableValueObject for equals()/hashCode():
    class UserId extends ComparableValueObject
    {
        public function equals($other): bool
        {
            return $other instanceof self && $this->value === $other->value;
        }
    }
    
  • Use Case: Ideal for IDs, UUIDs, or any unique identifiers in Laravel models.

3. Integration with Laravel Models

  • Store Value Objects in Attributes:
    class User extends Model
    {
        protected $casts = [
            'email' => Email::class, // Automatically casts input to Email VO
        ];
    }
    
  • Form Request Validation:
    public function rules(): array
    {
        return [
            'email' => ['required', new Email], // Custom validator
        ];
    }
    

4. Collections and Aggregates

  • Group Value Objects in Collections:
    $emails = collect([
        new Email('a@example.com'),
        new Email('b@example.com'),
    ])->map(fn ($email) => $email->getValue());
    
  • Domain Aggregates: Use VOs to model complex domain logic (e.g., OrderTotal as a sum of Money VOs).

5. Serialization

  • JSON/API Responses:
    return response()->json([
        'user' => [
            'email' => $user->email->getValue(), // Expose raw value
        ],
    ]);
    
  • Database Storage: Store the raw value (e.g., email column) and hydrate VOs in accessors:
    public function getEmailAttribute($value)
    {
        return new Email($value);
    }
    

Gotchas and Tips

Pitfalls

  1. Over-Validation in Constructors

    • Issue: Throws exceptions on invalid input, which can break fluent APIs.
    • Fix: Use a factory method for validation:
      class Email extends AbstractValueObject
      {
          public static function fromString(string $value): self
          {
              return new self($value);
          }
      }
      
  2. Circular Dependencies with Laravel Models

    • Issue: Circular references between models and VOs can cause serialization errors.
    • Fix: Use __toString() or custom JSON serialization:
      protected $appends = ['email_value'];
      public function getEmailValueAttribute()
      {
          return $this->email->getValue();
      }
      
  3. Performance with Large Collections

    • Issue: Comparing VOs in collections can be slow.
    • Tip: Cache hash values or use lightweight VOs for comparisons.

Debugging Tips

  • Check Validation Logic: Use dd($this->value) in the constructor to inspect invalid inputs.
  • Leverage Exceptions: Catch InvalidValueException for graceful error handling:
    try {
        $email = new Email('invalid');
    } catch (InvalidValueException $e) {
        report($e); // Log to Laravel's error system
    }
    

Extension Points

  1. Custom Exceptions Extend \Funeralzone\ValueObjects\Exceptions\InvalidValueException for domain-specific errors.

  2. 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.");
        }
    }
    
  3. Integration with Laravel Policies Use VOs in policy checks:

    public function update(User $user, Email $email)
    {
        return $user->email->equals($email);
    }
    

Config Quirks

  • No Configuration Needed: The library is stateless; all behavior is defined in your VOs.
  • Naming Conventions: Prefix VOs with nouns (e.g., Email, UserId) for clarity.

Testing Strategies

  • Unit Tests: Mock invalid inputs to verify validation:
    $this->expectException(InvalidValueException::class);
    new Email('invalid');
    
  • Integration Tests: Test VO hydration in Laravel models:
    $user = User::create(['email' => 'test@example.com']);
    $this->assertInstanceOf(Email::class, $user->email);
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
terminal42/code-quality-tools
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky