Advanced Usage
This guide covers advanced usage patterns, performance optimization, security considerations, and integration strategies for the PHP Serializer library.
Table of Contents
- Performance Optimization
- Security Considerations
- Integration Patterns
- Complex Data Structures
- Error Handling
- Best Practices
Performance Optimization
Internal Caching
The Serialize class implements automatic caching to improve performance when serializing multiple objects of the same type. The cache stores property metadata, ReflectionClass instances, and method existence checks. This provides significant performance benefits (3-5x speedup) when processing multiple objects of the same type.
For detailed information about caching behavior, performance impact, and cache lifetime, see the Serialize class documentation - Performance and Caching section.
Reusing Property Handlers
Property handlers should be reused when processing multiple objects:
// Efficient - reuse handler instance
$handler = new SnakeToCamelCase();
foreach ($dbRows as $row) {
$user = new User();
ObjectCopy::copy($row, $user, $handler);
$users[] = $user;
}
// Less efficient - creates new handler each iteration
foreach ($dbRows as $row) {
$user = new User();
ObjectCopy::copy($row, $user, new SnakeToCamelCase());
$users[] = $user;
}
Reusing Formatter Instances
Similarly, reuse formatter instances:
// Efficient
$formatter = (new XmlFormatter())
->withRootElement("users")
->withListElement("user");
foreach ($batches as $batch) {
$xml = $formatter->process($batch);
// Process $xml
}
// Less efficient
foreach ($batches as $batch) {
$xml = (new XmlFormatter())
->withRootElement("users")
->withListElement("user")
->process($batch);
}
Stop at First Level
Use withStopAtFirstLevel() to avoid deep recursion when you only need top-level properties:
class User {
public $id;
public $name;
public $address; // Complex object with many nested properties
public $orders; // Array of Order objects
}
// Only serialize first level - much faster
$result = Serialize::from($user)
->withStopAtFirstLevel()
->toArray();
// Result: id, name, address (object), orders (array)
// Nested properties of address and orders are not expanded
Ignore Unnecessary Properties
Reduce processing time by ignoring properties you don't need:
$result = Serialize::from($user)
->withIgnoreProperties([
'internalCache',
'tempData',
'debugInfo',
'largeBlob'
])
->toArray();
Choosing the Right Format
Different formats have different performance characteristics:
// Fastest to slowest for large datasets:
// 1. JSON - native PHP function, very fast
$json = Serialize::from($data)->toJson();
// 2. Array - no formatting overhead
$array = Serialize::from($data)->toArray();
// 3. CSV - efficient for tabular data
$csv = Serialize::from($data)->toCsv();
// 4. YAML - slower due to complex formatting
$yaml = Serialize::from($data)->toYaml();
// 5. XML - slowest due to DOM manipulation
$xml = Serialize::from($data)->toXml();
Security Considerations
Hiding Sensitive Data
Always hide sensitive properties before serialization:
class User {
public $id;
public $username;
private $password;
private $apiKey;
private $secretToken;
public function getPassword() { return $this->password; }
public function getApiKey() { return $this->apiKey; }
public function getSecretToken() { return $this->secretToken; }
}
// Bad - exposes sensitive data
$userData = Serialize::from($user)->toJson();
// Good - explicitly ignore sensitive fields
$userData = Serialize::from($user)
->withIgnoreProperties(['password', 'apiKey', 'secretToken'])
->toJson();
API Response Sanitization
Create dedicated DTO (Data Transfer Object) classes for API responses:
class User {
public $id;
public $username;
public $email;
private $password;
private $internalNotes;
}
class UserResponseDTO {
public $id;
public $username;
public $email;
// password and internalNotes not included
}
// Convert User to DTO
$user = $userRepository->find($id);
$dto = new UserResponseDTO();
ObjectCopy::copy($user, $dto);
// Serialize DTO (only safe fields included)
$response = Serialize::from($dto)->toJson();
Unserializing User Input
Never unserialize untrusted data with unserialize():
// DANGEROUS - DO NOT DO THIS
$userInput = $_POST['data'];
$object = unserialize($userInput); // CAN EXECUTE ARBITRARY CODE!
// Safe alternative - use JSON
$userInput = $_POST['data'];
$data = json_decode($userInput, true);
if (json_last_error() === JSON_ERROR_NONE) {
$object = new MyClass();
ObjectCopy::copy($data, $object);
}
Preventing Information Disclosure
Use value handlers to sanitize data:
$sanitizeHandler = function ($prop, $target, $value, $instance) {
// Remove internal IDs
if (str_starts_with($target, 'internal')) {
return null;
}
// Truncate long strings
if (is_string($value) && strlen($value) > 1000) {
return substr($value, 0, 1000) . '...';
}
// Sanitize HTML
if (in_array($target, ['description', 'bio', 'comment'])) {
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}
return $value;
};
$result = Serialize::from($data)
->parseAttributes($sanitizeHandler, null);
SQL Injection Prevention
When using serializer with database operations:
// Bad - concatenating serialized data into SQL
$data = Serialize::from($object)->toJson();
$sql = "INSERT INTO data (json) VALUES ('$data')"; // VULNERABLE!
// Good - using prepared statements
$data = Serialize::from($object)->toJson();
$stmt = $pdo->prepare("INSERT INTO data (json) VALUES (?)");
$stmt->execute([$data]);
Cross-Site Scripting (XSS) Prevention
Escape output when displaying serialized data:
$json = Serialize::from($user)->toJson();
// Bad - directly echoing in HTML
echo "<div>$json</div>"; // VULNERABLE if $json contains </script>
// Good - proper escaping
echo '<div>' . htmlspecialchars($json, ENT_QUOTES, 'UTF-8') . '</div>';
// Even better - use Content-Type header for API responses
header('Content-Type: application/json');
echo $json;
Integration Patterns
The PHP Serializer library integrates seamlessly with popular frameworks, ORMs, and libraries. For complete integration examples including:
- ORM Integration: Doctrine, Eloquent
- Framework Integration: Symfony, Laravel
- API Integration: REST clients and servers
- Message Queue Integration: RabbitMQ, Redis
- Cache Integration: PSR-6, PSR-16
- GraphQL Integration: Schema and resolvers
See the Integration Examples Guide for detailed, working examples.
Complex Data Structures
Circular References
The library does not automatically handle circular references. You must break the cycle:
class User {
public $id;
public $name;
public $company; // References Company
}
class Company {
public $id;
public $name;
public $users; // References User[]
}
// Problem: circular reference
$user->company = $company;
$company->users[] = $user;
// Solution 1: Use withStopAtFirstLevel()
$result = Serialize::from($user)
->withStopAtFirstLevel()
->toArray();
// Solution 2: Ignore the circular property
$result = Serialize::from($user)
->withIgnoreProperties(['company'])
->toArray();
// Solution 3: Create DTOs without circular references
class UserDTO {
public $id;
public $name;
public $companyId; // Just the ID, not the full object
}
Deeply Nested Objects
For deeply nested objects, consider performance implications:
// Slow - serializes everything recursively
$result = Serialize::from($deeplyNestedObject)->toArray();
// Fast - only first level
$result = Serialize::from($deeplyNestedObject)
->withStopAtFirstLevel()
->toArray();
// Alternative - selective expansion
class OrderDTO {
public $id;
public $customer; // Expand customer
public $items; // Expand items
public $itemDetails; // Don't expand item details
public static function fromOrder(Order $order): self {
$dto = new self();
$dto->id = $order->getId();
// Manually control expansion
$dto->customer = Serialize::from($order->getCustomer())
->withStopAtFirstLevel()
->toArray();
$dto->items = array_map(
fn($item) => ['id' => $item->getId(), 'name' => $item->getName()],
$order->getItems()
);
return $dto;
}
}
Collections and Iterators
use Doctrine\Common\Collections\Collection;
// Handle Doctrine Collections
$result = Serialize::from($user)
->toArray();
// Collections are automatically converted to arrays
// But you may want to control the output:
$users = $company->getUsers(); // Collection
$userData = array_map(
fn($user) => Serialize::from($user)
->withIgnoreProperties(['password'])
->toArray(),
$users->toArray()
);
Handling Special Types
// DateTime objects
class Event {
public DateTime $startDate;
public DateTime $endDate;
}
$event = new Event();
$event->startDate = new DateTime('2024-01-01');
$event->endDate = new DateTime('2024-01-31');
// DateTime objects are serialized as arrays by default
$result = Serialize::from($event)->toArray();
// Result includes DateTime internal structure (not ideal)
// Better: use parseAttributes to format dates
$result = Serialize::from($event)->parseAttributes(
function ($attr, $value, $keyName, $propName, $getterName) {
if ($value instanceof DateTime) {
return $value->format('Y-m-d H:i:s');
}
return $value;
},
null
);
Error Handling
JSON Encoding Errors
try {
$json = Serialize::from($data)->toJson();
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException('JSON encoding failed: ' . json_last_error_msg());
}
return $json;
} catch (Exception $e) {
// Log error
error_log("Serialization error: " . $e->getMessage());
// Return error response
return json_encode(['error' => 'Serialization failed']);
}
Handling Missing Properties
// Source has properties target doesn't have
$source = ['id' => 1, 'name' => 'John', 'unknown' => 'value'];
class Target {
public $id;
public $name;
// No 'unknown' property
}
$target = new Target();
ObjectCopy::copy($source, $target); // 'unknown' is silently ignored
// This is expected behavior - extra properties are ignored
Type Mismatch Handling
// Use a value handler to handle type mismatches gracefully
$handler = new DirectTransform(
function ($prop, $target, $value, $instance) {
try {
// Attempt type coercion
if ($target === 'age' && is_numeric($value)) {
return (int)$value;
}
if ($target === 'createdAt' && is_string($value)) {
return new DateTime($value);
}
return $value;
} catch (Exception $e) {
// Log error and return null or default
error_log("Value transformation error for {$target}: " . $e->getMessage());
return null;
}
}
);
Best Practices
1. Use DTOs for API Responses
// Good - dedicated DTO
class UserResponseDTO {
public $id;
public $username;
public $email;
// Only public-safe fields
}
// Create from entity
$dto = new UserResponseDTO();
ObjectCopy::copy($user, $dto);
$json = Serialize::from($dto)->toJson();
2. Create Reusable Transformers
class UserTransformer {
public function toArray(User $user): array {
return Serialize::from($user)
->withIgnoreProperties(['password', 'apiKey'])
->toArray();
}
public function toPublicArray(User $user): array {
return Serialize::from($user)
->withIgnoreProperties(['password', 'apiKey', 'email', 'phone'])
->toArray();
}
}
3. Document Security Implications
class UserService {
/**
* Export users to JSON
*
* SECURITY: This method excludes sensitive fields (password, apiKey)
* but includes email addresses. Only use for authorized admin users.
*/
public function exportUsers(): string {
$users = $this->repository->findAll();
return Serialize::from($users)
->withIgnoreProperties(['password', 'apiKey'])
->toJson();
}
}
4. Use Type Hints
// Good - clear types
public function serializeUser(User $user): array {
return Serialize::from($user)->toArray();
}
// Less clear
public function serializeUser($user) {
return Serialize::from($user)->toArray();
}
5. Test Edge Cases
class UserSerializerTest extends TestCase {
public function testSerializeWithNullValues() {
$user = new User();
$user->name = null;
$result = Serialize::from($user)->toArray();
$this->assertArrayHasKey('name', $result);
$result = Serialize::from($user)
->withDoNotParseNullValues()
->toArray();
$this->assertArrayNotHasKey('name', $result);
}
public function testSerializeHidesSensitiveData() {
$user = new User();
$user->password = 'secret';
$result = Serialize::from($user)
->withIgnoreProperties(['password'])
->toArray();
$this->assertArrayNotHasKey('password', $result);
}
}
6. Monitor Performance
class PerformanceMonitor {
public function monitorSerialization(User $user): array {
$start = microtime(true);
$result = Serialize::from($user)->toArray();
$duration = microtime(true) - $start;
if ($duration > 0.1) { // 100ms threshold
error_log("Slow serialization: {$duration}s for User ID {$user->getId()}");
}
return $result;
}
}
Related Components
- Serialize - Main serialization class
- Formatters - Output format customization
- Property Handlers - Property transformation
- Troubleshooting - Common issues and solutions