A simple, secure, and flexible serializer that converts any PHP object or data structure into a simple scalar array, ready to be sent via REST API.
The serializer automatically handles backward and forward compatibility, security concerns, and supports a wide range of PHP types including DTOs, entities, enums, DateTime objects, and more.
- Zero dependencies - Works standalone without any required external packages
- Security-first - Automatically hides sensitive data (passwords, credit cards, PINs)
- Type-safe - Full support for PHP 8.0+ typed properties and modern features
- Recursion protection - Detects and prevents infinite loops in object graphs
- Depth limiting - Protects against overly deep nested structures (max 32 levels)
- Convention-based - Configurable behavior through the convention system
- Bridge interfaces - Extensible architecture for custom type handling
The package follows a simple yet powerful architecture with clear separation of concerns:
┌───────────────────────────────────────────────────────────┐
│ Serializer │
│ (Main entry point - singleton or instance-based usage) │
├───────────────────────────────────────────────────────────┤
│ SerializerConvention │
│ (Configuration: date format, null handling, hidden keys) │
├───────────────────────────────────────────────────────────┤
│ Bridge Interfaces │
│ ┌─────────────────────┐ ┌───────────────────────────┐ │
│ │ ItemsListInterface │ │ StatusCountInterface │ │
│ │ (List collections) │ │ (Status with count) │ │
│ └─────────────────────┘ └───────────────────────────┘ │
└───────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────┐
│ Supported Types │
│ - Scalars (string, int, float, bool, null) │
│ - Arrays (indexed and associative) │
│ - Objects (DTOs, entities, stdClass) │
│ - DateTimeInterface │
│ - UnitEnum (PHP 8.1+) │
│ - Nette\Utils\Paginator │
│ - Baraja\Localization\Translation │
│ - Baraja\EcommerceStandard\DTO\PriceInterface │
│ - Objects with __toString() method │
└───────────────────────────────────────────────────────────┘
The main class responsible for converting PHP data structures to arrays. It can be used as a singleton via Serializer::get() or instantiated with a custom convention.
Key features:
- Processes objects using reflection to access all properties (including private)
- Automatically skips internal properties (those starting with
_) - Tracks object instances to detect circular references
- Enforces depth limit to prevent stack overflow
Configuration class that controls serialization behavior:
| Property | Default | Description |
|---|---|---|
dateTimeFormat |
'Y-m-d H:i:s' |
Format for DateTime serialization |
rewriteTooStringMethod |
true |
Use __toString() for stringable objects |
rewriteNullToUndefined |
false |
Remove null values from output |
keysToHide |
['password', 'passwd', ...] |
Keys containing sensitive data |
ItemsListInterface - For objects representing a collection of items:
interface ItemsListInterface
{
/** @return array<int, array<string, mixed>> */
public function getData(): array;
}StatusCountInterface - For status objects with a count (useful for filters, tabs):
interface StatusCountInterface
{
public function getKey(): string;
public function getLabel(): string;
public function getCount(): int;
}It's best to use Composer for installation, and you can also find the package on Packagist and GitHub.
To install, simply use the command:
$ composer require baraja-core/serializerYou can use the package manually by creating an instance of the internal classes.
- PHP 8.0 or higher
- No required dependencies (optional integrations available)
use Baraja\Serializer\Serializer;
class UserDTO
{
public function __construct(
public string $name,
public string $email,
public int $age,
) {
}
}
$serializer = Serializer::get();
$user = new UserDTO(
name: 'Jan Barasek',
email: 'jan@example.com',
age: 30,
);
$result = $serializer->serialize($user);
// Result: ['name' => 'Jan Barasek', 'email' => 'jan@example.com', 'age' => 30]class AddressDTO
{
public function __construct(
public string $street,
public string $city,
) {
}
}
class PersonDTO
{
public function __construct(
public string $name,
public AddressDTO $address,
) {
}
}
$person = new PersonDTO(
name: 'Jan',
address: new AddressDTO(street: 'Main St', city: 'Prague'),
);
$result = $serializer->serialize($person);
// Result: [
// 'name' => 'Jan',
// 'address' => ['street' => 'Main St', 'city' => 'Prague']
// ]$data = [
'users' => [
new UserDTO('Alice', 'alice@example.com', 25),
new UserDTO('Bob', 'bob@example.com', 30),
],
'total' => 2,
];
$result = $serializer->serialize($data);class EventDTO
{
public function __construct(
public string $title,
public \DateTimeInterface $startDate,
) {
}
}
$event = new EventDTO(
title: 'Conference',
startDate: new \DateTime('2024-06-15 10:00:00'),
);
$result = $serializer->serialize($event);
// Result: ['title' => 'Conference', 'startDate' => '2024-06-15 10:00:00']enum Status: string
{
case Active = 'active';
case Inactive = 'inactive';
}
class ItemDTO
{
public function __construct(
public string $name,
public Status $status,
) {
}
}
$item = new ItemDTO(name: 'Product', status: Status::Active);
$result = $serializer->serialize($item);
// Result: ['name' => 'Product', 'status' => 'active']The serializer automatically detects and hides sensitive keys. When a sensitive key is found, its value is replaced with *****:
class LoginDTO
{
public function __construct(
public string $username,
public string $password,
) {
}
}
$login = new LoginDTO(username: 'admin', password: 'secret123');
$result = $serializer->serialize($login);
// Result: ['username' => 'admin', 'password' => '*****']Default hidden keys: password, passwd, pass, pwd, creditcard, credit card, cc, pin
Exception: BCrypt hashes (format $2[ayb]$...) are allowed through, as they are already securely hashed.
Properties starting with underscore (_) are automatically excluded from serialization:
class EntityDTO
{
public function __construct(
public string $name,
public string $_internalId, // Will be excluded
) {
}
}The serializer tracks object instances and throws an exception if circular references are detected:
class Node
{
public function __construct(
public string $name,
public ?Node $parent = null,
) {
}
}
$node = new Node('A');
$node->parent = $node; // Circular reference
$serializer->serialize($node);
// Throws: InvalidArgumentException with recursion warningCreate a custom convention by extending SerializerConvention:
use Baraja\Serializer\Serializer;
use Baraja\Serializer\SerializerConvention;
class CustomConvention extends SerializerConvention
{
private string $dateTimeFormat = 'c'; // ISO 8601
private bool $rewriteNullToUndefined = true;
}
$convention = new CustomConvention();
$serializer = new Serializer($convention);Implement this interface for paginated or list results:
use Baraja\Serializer\Bridge\ItemsListInterface;
class UserList implements ItemsListInterface
{
/** @param UserDTO[] $users */
public function __construct(
private array $users,
) {
}
public function getData(): array
{
return array_map(
fn(UserDTO $user) => [
'name' => $user->name,
'email' => $user->email,
],
$this->users,
);
}
}Convention: ItemsListInterface objects must be placed in a key named items.
Useful for filter tabs or status counts:
use Baraja\Serializer\Bridge\StatusCountInterface;
class OrderStatus implements StatusCountInterface
{
public function __construct(
private string $key,
private string $label,
private int $count,
) {
}
public function getKey(): string
{
return $this->key;
}
public function getLabel(): string
{
return $this->label;
}
public function getCount(): int
{
return $this->count;
}
}
$statuses = [
new OrderStatus('pending', 'Pending', 5),
new OrderStatus('completed', 'Completed', 42),
];
$result = $serializer->serialize($statuses);
// Result: [
// ['key' => 'pending', 'label' => 'Pending', 'count' => 5],
// ['key' => 'completed', 'label' => 'Completed', 'count' => 42],
// ]If nette/utils is installed, Paginator objects are automatically serialized:
use Nette\Utils\Paginator;
$paginator = new Paginator();
$paginator->setItemCount(100);
$paginator->setItemsPerPage(10);
$paginator->setPage(3);
$result = $serializer->serialize(['paginator' => $paginator]);
// Result: [
// 'paginator' => [
// 'page' => 3,
// 'pageCount' => 10,
// 'itemCount' => 100,
// 'itemsPerPage' => 10,
// 'firstPage' => 1,
// 'lastPage' => 10,
// 'isFirstPage' => false,
// 'isLastPage' => false,
// ]
// ]Convention: Paginator objects must be placed in a key named paginator.
If baraja-core/localization is installed, Translation objects are converted to strings:
use Baraja\Localization\Translation;
$translation = new Translation(['en' => 'Hello', 'cs' => 'Ahoj']);
$result = $serializer->serialize(['greeting' => $translation]);
// Result: ['greeting' => 'Hello'] (based on current locale)If baraja-core/ecommerce-standard is installed, PriceInterface objects are serialized:
// Assuming $price implements PriceInterface
$result = $serializer->serialize(['price' => $price]);
// Result: [
// 'price' => [
// 'value' => '199.00',
// 'currency' => 'USD',
// 'html' => '$199.00',
// 'isFree' => false,
// ]
// ]- ItemsListInterface must be in key
items - Paginator must be in key
paginator - Maximum depth is 32 levels
- Properties starting with
_are excluded
The serializer throws exceptions for:
- LogicException: Structure depth exceeds 32 levels
- InvalidArgumentException: Circular reference detected
- InvalidArgumentException: ItemsListInterface not in
itemskey - InvalidArgumentException: Paginator not in
paginatorkey - InvalidArgumentException: Unsupported value type
When a sensitive key with a non-BCrypt value is detected, the serializer logs a critical warning via Tracy Debugger (if available):
Security warning: User password may have been compromised!
The Baraja API prevented passwords being passed through the API in a readable form.
This helps identify potential security issues during development.
Jan Barasek - https://baraja.cz
baraja-core/serializer is licensed under the MIT license. See the LICENSE file for more details.