apie/common-value-objects
Common value objects for the Apie ecosystem: ready-to-use PHP enums (e.g., Gender) and identifier base classes (e.g., UUID v4) for entities, fields, or composite value objects. Designed to be extended and used as examples in your own domain.
Installation:
composer require apie/common-value-objects
Ensure your composer.json includes "apie/common-value-objects": "^x.y.z" (replace with the latest version).
First Use Case:
Use a pre-built value object (e.g., UuidV4) for an entity ID:
use Apie\Core\Identifiers\UuidV4;
$id = UuidV4::createRandom(); // Generates a random UUIDv4
Key Files:
src/Apie/Core/Enums/ for enums (e.g., Gender).src/Apie/Core/Identifiers/ for identifiers (e.g., UuidV4, Slug).src/Apie/Core/ValueObjects/ for other VOs (e.g., FirstName, DateTimeRange).Extend UuidV4 (or other identifiers) for entity IDs:
use Apie\Core\Identifiers\UuidV4;
use Apie\Core\Identifiers\IdentifierInterface;
use ReflectionClass;
class PostId extends UuidV4 implements IdentifierInterface
{
public static function getReferenceFor(): ReflectionClass
{
return new ReflectionClass(Post::class);
}
}
Workflow:
PostId::createRandom() in Post constructor.getIdentifier() in entities to return the ID.Combine VOs (e.g., FirstName + LastName) for domain-specific types:
use Apie\Core\ValueObjects\FirstName;
use Apie\Core\ValueObjects\LastName;
class FullName
{
public function __construct(
private FirstName $firstName,
private LastName $lastName
) {}
}
Replace loose strings with strict VOs (e.g., SmallDatabaseText):
use Apie\Core\ValueObjects\SmallDatabaseText;
$title = new SmallDatabaseText("Laravel"); // Auto-trims and validates length
protected $id as PostId).
Override getKey() to return the VO’s string value:
public function getKey(): string
{
return $this->id->value();
}
StrongPasswordField):
use Apie\Core\ValueObjects\StrongPasswordField;
public function rules()
{
return [
'password' => ['required', function ($attribute, $value, $fail) {
try {
new StrongPasswordField($value);
} catch (\InvalidArgumentException $e) {
$fail($e->getMessage());
}
}]
];
}
Use enums (e.g., Gender) with Laravel’s HasFactory:
use Apie\Core\Enums\Gender;
class User extends Model
{
protected $casts = [
'gender' => Gender::class,
];
}
Identifier Reflection:
Forgetting to implement getReferenceFor() in custom identifiers will cause runtime errors when used with Apie entities.
VO Immutability:
VOs are immutable. Avoid direct property modification (e.g., $vo->value = 'new'). Use factory methods (e.g., UuidV4::fromString()).
Database Migrations:
uuid-ossp or uuid extension in PostgreSQL/MySQL.varchar with appropriate length (e.g., 255 for KebabCaseSlug).Password Validation:
StrongPasswordField enforces strict rules (e.g., min length, complexity). Customize by extending the class:
class CustomPassword extends StrongPasswordField
{
protected function validateStrength(string $value): void
{
if (strlen($value) < 10) {
throw new \InvalidArgumentException("Password must be at least 10 characters.");
}
}
}
InvalidArgumentException when constructing VOs:
try {
$name = new FirstName(""); // Throws if empty
} catch (\InvalidArgumentException $e) {
Log::error("Invalid name: " . $e->getMessage());
}
UuidV4::fromString() to validate UUIDs before insertion:
$id = UuidV4::fromString($request->input('id')); // Throws if invalid
Slug) to add domain logic:
class BlogSlug extends Slug
{
public function __construct(string $value)
{
if (str_contains($value, ' ')) {
throw new \InvalidArgumentException("Blog slugs cannot contain spaces.");
}
parent::__construct($value);
}
}
DateTimeRange to add custom constraints:
class BusinessHoursRange extends DateTimeRange
{
public function __construct(\DateTimeInterface $start, \DateTimeInterface $end)
{
if ($start->format('H') < 9 || $end->format('H') > 17) {
throw new \InvalidArgumentException("Hours must be between 9 AM and 5 PM.");
}
parent::__construct($start, $end);
}
}
$this->app->bind(FirstName::class, function ($app) {
return new FirstName($app['request']->input('first_name'));
});
UuidV4::createRandom() if generating IDs in bulk (e.g., for seeding).Slug for SEO URLs) to avoid redundant validation.$this->expectException(InvalidArgumentException::class);
new FirstName(""); // Empty name
How can I help you explore Laravel packages today?