tuki / soda
A lightweight library for property validation and object-to-array/JSON conversion using Symfony Validator and traits.
Requires
- php: ^8.3
- symfony/expression-language: ^7.4
- symfony/validator: ^7.0
Requires (Dev)
- phpunit/phpunit: ^12.5
This package is auto-updated.
Last update: 2026-04-01 00:30:38 UTC
README
Structured Object Data Arrayable
Soda is a lightweight, developer-friendly library designed to transform Plain Old PHP Objects (POPOs) into intelligent data structures. It provides seamless hydration from arrays/JSON, data extraction, and optional property validation using Symfony Constraints—all through a simple, Trait-based API.
Why Soda?
The name represents the four core architectural pillars of the library:
- Structured: Ensures your data objects follow a predictable and reliable schema.
- Object: Embraces pure PHP objects without requiring heavy inheritance or boilerplate.
- Data: Focused entirely on data integrity and safe information transfer (DTOs).
- Arrayable: Native, bidirectional conversion between raw arrays and structured objects.
Core Philosophy
Soda leverages Symfony Validator and PHP Reflection under the hood. It is designed to be opt-in: you decide which capabilities to grant your objects by using specific Traits and Contracts.
⚠️ IMPORTANT
For optimal performance and security:
- Use protected properties for your data. The
Validatestrait uses__setto trigger validation automatically when a protected property is modified from outside (via magic setter).- Public properties are only validated during factory creation (
fromArray,fromJson). If you modify a public property directly after the object is created, no automatic validation will occur.- Avoid overriding
__setunless you manually call$this->validate()within it, as it may break the automatic validation mechanism.
How to install
composer require tuki/soda
Quick Start
Basic Usage (Validation + Conversion)
<?php
use Tuki\Soda\Traits\Validates;
use Tuki\Soda\Traits\AsArray;
use Tuki\Soda\Contracts\Validatable;
use Symfony\Component\Validator\Constraints as Assert;
class User implements Validatable
{
use Validates, AsArray;
#[Assert\NotBlank]
#[Assert\Email]
protected string $email;
#[Assert\Length(min: 8)]
protected string $password;
// Public properties are only validated on creation
#[Assert\Range(min: 18)]
public int $age;
}
// Creation via Factory (validates immediately)
$user = User::fromArray([
'email' => 'user@example.com',
'password' => 'secret123',
'age' => 25
]);
// Updating a protected property (triggers automatic validation via __set)
$user->email = 'new-email@example.com';
// Converting back to array
$data = $user->toArray();
JSON Support
<?php
use Tuki\Soda\Traits\AsJson;
use Tuki\Soda\Contracts\Validatable;
use Symfony\Component\Validator\Constraints as Assert;
class Product implements Validatable
{
use AsJson, Validates;
#[Assert\NotBlank]
protected string $sku;
}
$product = Product::fromJson('{"sku": "ABC-123"}');
echo $product->toJson();
Standalone Trait Usage
You can use AsArray or AsJson without the Validates trait if you only need the conversion features without validation.
<?php
use Tuki\Soda\Traits\AsArray;
class SimpleUser
{
use AsArray;
public string $name;
public string $role;
}
// Works even without the Validates trait or Validatable contract
$user = SimpleUser::fromArray(['name' => 'Alice', 'role' => 'admin']);
$data = $user->toArray();
The Importance of the Validatable Contract
Automatic validation during factory creation (fromArray, fromJson) only occurs if your class implements the Validatable contract. If you use the traits without the contract, you must call validate() manually.
<?php
use Tuki\Soda\Traits\Validates;
use Tuki\Soda\Traits\AsArray;
use Symfony\Component\Validator\Constraints as Assert;
// Missing "implements Validatable"
class IncompleteUser
{
use AsArray, Validates;
#[Assert\Email]
public string $email;
}
// No validation occurs here even if the email is invalid!
$user = IncompleteUser::fromArray(['email' => 'not-an-email']);
// You must validate manually if the contract is missing
// $user->validate();
Manual Validation & Error Handling
You can also trigger validation manually and handle errors using the ObjectValidationException.
<?php
use Tuki\Soda\Traits\Validates;
use Tuki\Soda\Exceptions\ObjectValidationException;
use Symfony\Component\Validator\Constraints as Assert;
class Contact
{
use Validates;
#[Assert\NotBlank]
public string $name;
#[Assert\Email]
public string $email;
}
$contact = new Contact();
$contact->name = 'John';
$contact->email = 'invalid-email';
try {
$contact->validate();
} catch (ObjectValidationException $e) {
// Get all error messages grouped by property
$errors = $e->errorsBag();
/*
[
"email" => ["This value is not a valid email address."]
]
*/
}
Extended Constraints (Soda Power-Ups)
Soda extends Symfony's validation power with a set of custom constraints designed for common real-world scenarios, semantic clarity, and regional compliance.
You can find them under the Tuki\Soda\Validator\Constraints namespace.
Semantic & Convenience Helpers
These are "shortcuts" for common Symfony configurations to keep your DTOs clean.
| Constraint | Description | Equivalent to... |
|---|---|---|
#[SodaAssert\Required] | Field must not be empty or null. | #[Assert\NotBlank] |
#[SodaAssert\Prohibits] | Field must be empty or null. | #[Assert\Blank] |
#[SodaAssert\MinAge(x)] | Validates that a birth date corresponds to at least x years old. | #[Assert\LessThanOrEqual("-x years")] |
#[SodaAssert\MaxAge(x)] | Validates that a person is not older than x years. | #[Assert\GreaterThanOrEqual("-x years")] |
#[SodaAssert\Slug] | Validates a URL-friendly string (lowercase, numbers, hyphens). | #[Assert\Regex("/^[a-z0-9]+(?:-[a-z0-9]+)*$/")] |
#[SodaAssert\Phone] | Basic international phone format validation. | #[Assert\Regex("/^\+?[0-9\s\-]{7,15}$/")] |
Conditional Constraints
Powerful logic to make properties mandatory or prohibited based on other values.
| Constraint | Description |
|---|---|
#[SodaAssert\RequiredIf("expression")] | Required only if the condition is true. |
#[SodaAssert\RequiredWith("field")] | Required if the other field is present. |
#[SodaAssert\RequiredWithout("field")] | Required if the other field is missing. |
#[SodaAssert\ProhibitedIf("expression")] | Must be empty if the condition is true. |
#[SodaAssert\ProhibitedWith("field")] | Must be empty if the other field is present. |
#[SodaAssert\ProhibitedWithout("field")] | Must be empty if the other field is missing. |
Regional Compliance
Native support for identification numbers that Symfony doesn't include by default.
| Constraint | Description |
|---|---|
#[SodaAssert\IsNif] | Validates Spanish NIF (National ID). |
#[SodaAssert\IsNie] | Validates Spanish NIE (Foreigner ID). |
#[SodaAssert\IsCif] | Validates Spanish Business Tax ID (CIF) and Honduras RTN. |
Example: Advanced Validation
use Tuki\Soda\Validator\Constraints as SodaAssert;
class CheckoutRequest implements Validatable
{
use Validates, AsArray;
#[SodaAssert\Required]
public string $customerName;
#[SodaAssert\IsCif]
#[SodaAssert\RequiredIf("this.isCompany == true")]
protected ?string $taxId;
public bool $isCompany = false;
#[SodaAssert\MinAge(18)]
protected string $birthDate;
#[SodaAssert\RequiredWith("creditCardNumber")]
protected ?string $cvv;
}
License
Soda is open-source software licensed under the MIT License. Maintained by the Tuki Foundation.