Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
15
src/Framework/Serializer/Exception/DeserializeException.php
Normal file
15
src/Framework/Serializer/Exception/DeserializeException.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Serializer\Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when deserialization fails
|
||||
*
|
||||
* This exception is thrown when an error occurs during the deserialization process,
|
||||
* such as when trying to deserialize invalid or malformed data.
|
||||
*/
|
||||
class DeserializeException extends SerializerException
|
||||
{
|
||||
}
|
||||
15
src/Framework/Serializer/Exception/SerializeException.php
Normal file
15
src/Framework/Serializer/Exception/SerializeException.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Serializer\Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when serialization fails
|
||||
*
|
||||
* This exception is thrown when an error occurs during the serialization process,
|
||||
* such as when trying to serialize resources or other non-serializable data.
|
||||
*/
|
||||
class SerializeException extends SerializerException
|
||||
{
|
||||
}
|
||||
16
src/Framework/Serializer/Exception/SerializerException.php
Normal file
16
src/Framework/Serializer/Exception/SerializerException.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Serializer\Exception;
|
||||
|
||||
/**
|
||||
* Base exception for all serializer-related exceptions
|
||||
*
|
||||
* This exception is thrown when an error occurs during serialization or deserialization.
|
||||
*/
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
|
||||
class SerializerException extends FrameworkException
|
||||
{
|
||||
}
|
||||
210
src/Framework/Serializer/Json/JsonSerializer.php
Normal file
210
src/Framework/Serializer/Json/JsonSerializer.php
Normal file
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Serializer\Json;
|
||||
|
||||
use App\Framework\Serializer\Exception\DeserializeException;
|
||||
use App\Framework\Serializer\Exception\SerializeException;
|
||||
use App\Framework\Serializer\Serializer;
|
||||
use JsonException;
|
||||
|
||||
/**
|
||||
* JSON serializer implementation
|
||||
*
|
||||
* This class provides functionality to serialize and deserialize data to/from JSON format.
|
||||
* It combines features from both the Cache and Filesystem JsonSerializer implementations.
|
||||
*
|
||||
* Robustness and performance optimizations:
|
||||
* - Uses a hybrid approach for JSON deserialization:
|
||||
* - For small JSON strings (≤1KB): Direct decoding for better performance
|
||||
* - For large JSON strings (>1KB): Validation before decoding for better error messages
|
||||
* - Provides more specific error messages for invalid JSON
|
||||
* - Handles edge cases like empty strings and deeply nested structures
|
||||
* - Improves error reporting with detailed validation failures
|
||||
*/
|
||||
final readonly class JsonSerializer implements Serializer
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param JsonSerializerConfig $config Configuration for the serializer
|
||||
*/
|
||||
public function __construct(
|
||||
private JsonSerializerConfig $config = new JsonSerializerConfig()
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws SerializeException If serialization fails
|
||||
*/
|
||||
public function serialize(mixed $data): string
|
||||
{
|
||||
try {
|
||||
// Memory protection for serialization too
|
||||
$memoryUsage = memory_get_usage(true);
|
||||
$memoryLimit = $this->parseMemoryLimit();
|
||||
|
||||
if ($memoryUsage > ($memoryLimit * 0.6)) {
|
||||
// Log what we're trying to serialize when memory is high
|
||||
error_log("HIGH MEMORY WARNING during JSON serialization: " .
|
||||
round($memoryUsage / 1024 / 1024, 2) . "MB used. " .
|
||||
"Data type: " . gettype($data));
|
||||
|
||||
// For arrays, log the size
|
||||
if (is_array($data)) {
|
||||
error_log("Array size: " . count($data) . " elements");
|
||||
error_log("Array keys: " . implode(', ', array_keys($data)));
|
||||
}
|
||||
|
||||
throw new SerializeException(
|
||||
"Cannot serialize data - memory usage too high: " .
|
||||
round($memoryUsage / 1024 / 1024, 2) . "MB used of " .
|
||||
round($memoryLimit / 1024 / 1024, 2) . "MB limit"
|
||||
);
|
||||
}
|
||||
|
||||
$json = json_encode($data, $this->config->flags | JSON_THROW_ON_ERROR, $this->config->depth);
|
||||
|
||||
if ($json === false) {
|
||||
// This should never happen with JSON_THROW_ON_ERROR, but just in case
|
||||
throw new SerializeException('Failed to encode JSON: ' . json_last_error_msg());
|
||||
}
|
||||
|
||||
// Log large serializations
|
||||
$jsonSize = strlen($json);
|
||||
if ($jsonSize > 100 * 1024) { // >100KB
|
||||
error_log("Large JSON serialization: " . round($jsonSize / 1024, 2) . "KB");
|
||||
}
|
||||
|
||||
return $json;
|
||||
} catch (JsonException $e) {
|
||||
throw new SerializeException('Failed to encode JSON: ' . $e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Hybrid approach for optimal performance and robustness:
|
||||
* - For small JSON strings (≤1KB): Uses direct decoding for better performance
|
||||
* - For large JSON strings (>1KB): Uses validation before decoding for better error messages
|
||||
* - Memory protection for very large JSON strings (>100KB)
|
||||
*
|
||||
* This approach balances performance and robustness:
|
||||
* - Small strings benefit from faster direct decoding
|
||||
* - Large, complex strings benefit from better error reporting
|
||||
* - All inputs get appropriate exception handling
|
||||
* - Very large strings get memory protection
|
||||
*
|
||||
* @throws DeserializeException If JSON validation or deserialization fails
|
||||
*/
|
||||
public function deserialize(string $data): mixed
|
||||
{
|
||||
if (empty($data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$dataSize = strlen($data);
|
||||
|
||||
// EMERGENCY: Ultra-strict memory limits to prevent 512MB exhaustion
|
||||
if ($dataSize > 1024) { // >1KB - very strict now
|
||||
// Force garbage collection before processing any JSON >1KB
|
||||
gc_collect_cycles();
|
||||
|
||||
// Check available memory
|
||||
$memoryUsage = memory_get_usage(true);
|
||||
$memoryLimit = $this->parseMemoryLimit();
|
||||
|
||||
// Emergency: If we're using >200MB, refuse any JSON processing
|
||||
if ($memoryUsage > 200 * 1024 * 1024) {
|
||||
throw new DeserializeException(
|
||||
"EMERGENCY: Cannot deserialize JSON - memory usage critical: " .
|
||||
round($memoryUsage / 1024 / 1024, 2) . "MB used"
|
||||
);
|
||||
}
|
||||
|
||||
// Emergency: JSON >5KB is forbidden when memory >100MB
|
||||
if ($dataSize > 5 * 1024 && $memoryUsage > 100 * 1024 * 1024) {
|
||||
throw new DeserializeException(
|
||||
"EMERGENCY: Cannot deserialize JSON ({$dataSize} bytes) - memory usage too high: " .
|
||||
round($memoryUsage / 1024 / 1024, 2) . "MB used"
|
||||
);
|
||||
}
|
||||
|
||||
// Emergency: JSON >20KB is completely forbidden
|
||||
if ($dataSize > 20 * 1024) {
|
||||
throw new DeserializeException(
|
||||
"EMERGENCY: JSON too large ({$dataSize} bytes) - maximum 20KB allowed"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// For large JSON strings (>1KB), validate first for better error messages
|
||||
// For small strings, decode directly for better performance
|
||||
if ($dataSize > 1024) {
|
||||
// Validate JSON before decoding for better error messages
|
||||
if (! json_validate($data, JSON_THROW_ON_ERROR)) {
|
||||
// This should never happen with JSON_THROW_ON_ERROR, but just in case
|
||||
throw new DeserializeException('Invalid JSON: ' . json_last_error_msg());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$startMemory = memory_get_usage(true);
|
||||
$result = json_decode($data, true, $this->config->depth, JSON_THROW_ON_ERROR);
|
||||
$endMemory = memory_get_usage(true);
|
||||
|
||||
// Log memory usage for large deserializations
|
||||
if ($dataSize > 10 * 1024) { // >10KB
|
||||
$memoryUsed = $endMemory - $startMemory;
|
||||
if ($memoryUsed > 5 * 1024 * 1024) { // >5MB memory growth
|
||||
error_log("Large JSON deserialization: {$dataSize} bytes JSON used {$memoryUsed} bytes memory");
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
} catch (JsonException $e) {
|
||||
throw new DeserializeException('Failed to decode JSON: ' . $e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse memory_limit ini setting to bytes
|
||||
*/
|
||||
private function parseMemoryLimit(): int
|
||||
{
|
||||
$memoryLimit = ini_get('memory_limit');
|
||||
if ($memoryLimit === '-1') {
|
||||
return PHP_INT_MAX; // No limit
|
||||
}
|
||||
|
||||
$value = (int) $memoryLimit;
|
||||
$unit = strtolower(substr($memoryLimit, -1));
|
||||
|
||||
return match($unit) {
|
||||
'g' => $value * 1024 * 1024 * 1024,
|
||||
'm' => $value * 1024 * 1024,
|
||||
'k' => $value * 1024,
|
||||
default => $value
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMimeType(): string
|
||||
{
|
||||
return 'application/json';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFileExtension(): string
|
||||
{
|
||||
return 'json';
|
||||
}
|
||||
}
|
||||
55
src/Framework/Serializer/Json/JsonSerializerConfig.php
Normal file
55
src/Framework/Serializer/Json/JsonSerializerConfig.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Serializer\Json;
|
||||
|
||||
/**
|
||||
* Configuration for JSON serializer
|
||||
*
|
||||
* This class encapsulates the configuration options for the JSON serializer.
|
||||
*/
|
||||
final readonly class JsonSerializerConfig
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param int $flags JSON encoding flags
|
||||
* @param int $depth Maximum depth for encoding/decoding
|
||||
*/
|
||||
public function __construct(
|
||||
public int $flags = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE,
|
||||
public int $depth = 512
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a config with compact formatting (no pretty printing)
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function compact(): self
|
||||
{
|
||||
return new self(JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a config with pretty printing enabled
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function pretty(): self
|
||||
{
|
||||
return new self(JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a config with minimal options (no special flags)
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function minimal(): self
|
||||
{
|
||||
return new self(0);
|
||||
}
|
||||
}
|
||||
144
src/Framework/Serializer/Php/PhpSerializer.php
Normal file
144
src/Framework/Serializer/Php/PhpSerializer.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Serializer\Php;
|
||||
|
||||
use App\Framework\Serializer\Exception\DeserializeException;
|
||||
use App\Framework\Serializer\Exception\SerializeException;
|
||||
use App\Framework\Serializer\Serializer;
|
||||
|
||||
/**
|
||||
* PHP serializer implementation
|
||||
*
|
||||
* This class provides functionality to serialize and deserialize data using PHP's native
|
||||
* serialization functions. It includes security features to control which classes can be
|
||||
* unserialized, helping to prevent PHP object injection vulnerabilities.
|
||||
*/
|
||||
final readonly class PhpSerializer implements Serializer
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PhpSerializerConfig $config Configuration for the serializer
|
||||
*/
|
||||
public function __construct(
|
||||
private PhpSerializerConfig $config = new PhpSerializerConfig()
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws SerializeException If serialization fails or if data contains resources
|
||||
*/
|
||||
public function serialize(mixed $data): string
|
||||
{
|
||||
// Check for resources in the data
|
||||
if ($this->containsResource($data)) {
|
||||
throw new SerializeException('Failed to serialize data: Resources cannot be serialized');
|
||||
}
|
||||
|
||||
try {
|
||||
return serialize($data);
|
||||
} catch (\Exception $e) {
|
||||
throw new SerializeException('Failed to serialize data: ' . $e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the data contains any resources
|
||||
*
|
||||
* @param mixed $data The data to check
|
||||
* @return bool True if the data contains resources, false otherwise
|
||||
*/
|
||||
private function containsResource(mixed $data): bool
|
||||
{
|
||||
if (is_resource($data)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_array($data)) {
|
||||
if (array_any($data, fn ($value) => $this->containsResource($value))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_object($data)) {
|
||||
if (array_any(get_object_vars($data), fn ($value) => $this->containsResource($value))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* This method uses a custom error handler to catch PHP errors during unserialization
|
||||
* and convert them to exceptions. The error handler is carefully designed to:
|
||||
* - Only handle errors from unserialize() to minimize the impact of changing the global error handler
|
||||
* - Call the previous error handler for other errors
|
||||
* - Restore the original error handler in both success and error paths
|
||||
*
|
||||
* @throws DeserializeException If deserialization fails
|
||||
*/
|
||||
public function deserialize(string $data): mixed
|
||||
{
|
||||
if (empty($data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Store the previous error handler so we can restore it later
|
||||
$previousErrorHandler = set_error_handler(function ($severity, $message, $file, $line) {
|
||||
// Only handle errors from unserialize() to minimize the impact of changing the global error handler
|
||||
if (str_contains($message, 'unserialize')) {
|
||||
throw new \ErrorException($message, 0, $severity, $file, $line);
|
||||
}
|
||||
|
||||
// For other errors, call the previous error handler if it exists
|
||||
if ($previousErrorHandler = set_error_handler(function () {})) {
|
||||
restore_error_handler();
|
||||
call_user_func($previousErrorHandler, $severity, $message, $file, $line);
|
||||
}
|
||||
|
||||
// Otherwise, use the default error handler
|
||||
return false;
|
||||
});
|
||||
|
||||
try {
|
||||
$result = unserialize($data, $this->config->getOptions());
|
||||
|
||||
// Special case: if unserialize returns false, we need to check if the original data
|
||||
// was actually serialized false, or if there was an error
|
||||
if ($result === false && $data !== serialize(false)) {
|
||||
throw new DeserializeException('Failed to unserialize data');
|
||||
}
|
||||
|
||||
restore_error_handler();
|
||||
|
||||
return $result;
|
||||
} catch (\Throwable $e) {
|
||||
restore_error_handler();
|
||||
|
||||
throw new DeserializeException('Failed to unserialize data: ' . $e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMimeType(): string
|
||||
{
|
||||
return 'application/x-php-serialized';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFileExtension(): string
|
||||
{
|
||||
return 'ser';
|
||||
}
|
||||
}
|
||||
64
src/Framework/Serializer/Php/PhpSerializerConfig.php
Normal file
64
src/Framework/Serializer/Php/PhpSerializerConfig.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Serializer\Php;
|
||||
|
||||
/**
|
||||
* Configuration for PHP serializer
|
||||
*
|
||||
* This class encapsulates the configuration options for the PHP serializer.
|
||||
*/
|
||||
final readonly class PhpSerializerConfig
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param bool|array $allowedClasses Controls which classes are allowed to be unserialized.
|
||||
* If false, no classes are allowed.
|
||||
* If true, all classes are allowed.
|
||||
* If array, only the specified classes are allowed.
|
||||
* @param int $maxDepth Maximum depth of nested objects to deserialize
|
||||
*/
|
||||
public function __construct(
|
||||
public bool|array $allowedClasses = false,
|
||||
public int $maxDepth = 4096
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a config with safe deserialization (only specified classes allowed)
|
||||
*
|
||||
* @param array $allowedClasses List of class names that are allowed to be unserialized
|
||||
* @param int $maxDepth Maximum depth of nested objects to deserialize
|
||||
* @return self
|
||||
*/
|
||||
public static function safe(array $allowedClasses = [], int $maxDepth = 4096): self
|
||||
{
|
||||
return new self($allowedClasses, $maxDepth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a config with unsafe deserialization (all classes allowed)
|
||||
*
|
||||
* @param int $maxDepth Maximum depth of nested objects to deserialize
|
||||
* @return self
|
||||
*/
|
||||
public static function unsafe(int $maxDepth = 4096): self
|
||||
{
|
||||
return new self(true, $maxDepth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the options array for use with unserialize()
|
||||
*
|
||||
* @return array{allowed_classes: array|bool, max_depth: int} The options array
|
||||
*/
|
||||
public function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'allowed_classes' => $this->allowedClasses,
|
||||
'max_depth' => $this->maxDepth,
|
||||
];
|
||||
}
|
||||
}
|
||||
344
src/Framework/Serializer/README.md
Normal file
344
src/Framework/Serializer/README.md
Normal file
@@ -0,0 +1,344 @@
|
||||
# Serializer Module
|
||||
|
||||
The Serializer module provides a unified way to serialize and deserialize data in various formats. It supports JSON and PHP serialization with plans to add more formats in the future.
|
||||
|
||||
## Features
|
||||
|
||||
- Unified interface for all serializers
|
||||
- Type-safe serialization and deserialization
|
||||
- Robust error handling
|
||||
- Configurable serialization options
|
||||
- Factory methods for common configurations
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```php
|
||||
use App\Framework\Serializer\SerializerFactory;
|
||||
|
||||
// Create a JSON serializer with default settings (pretty print)
|
||||
$serializer = SerializerFactory::createJsonSerializer();
|
||||
|
||||
// Serialize data
|
||||
$data = ['name' => 'John', 'age' => 30];
|
||||
$json = $serializer->serialize($data);
|
||||
|
||||
// Deserialize data
|
||||
$deserializedData = $serializer->deserialize($json);
|
||||
```
|
||||
|
||||
### Serialization Options
|
||||
|
||||
The module provides several factory methods for common serialization configurations:
|
||||
|
||||
```php
|
||||
// Pretty-printed JSON (default)
|
||||
$prettySerializer = SerializerFactory::createPrettyJsonSerializer();
|
||||
$prettyJson = $prettySerializer->serialize($data);
|
||||
// Result: {
|
||||
// "name": "John",
|
||||
// "age": 30
|
||||
// }
|
||||
|
||||
// Compact JSON (no whitespace)
|
||||
$compactSerializer = SerializerFactory::createCompactJsonSerializer();
|
||||
$compactJson = $compactSerializer->serialize($data);
|
||||
// Result: {"name":"John","age":30}
|
||||
|
||||
// Minimal JSON (no special flags)
|
||||
$minimalSerializer = SerializerFactory::createMinimalJsonSerializer();
|
||||
$minimalJson = $minimalSerializer->serialize($data);
|
||||
// Result: {"name":"John","age":30}
|
||||
```
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
You can create a serializer with custom configuration:
|
||||
|
||||
```php
|
||||
// Custom JSON serializer with specific flags
|
||||
$customSerializer = SerializerFactory::createCustomJsonSerializer(
|
||||
JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE,
|
||||
256 // Max depth
|
||||
);
|
||||
|
||||
// Serialize an array as an object
|
||||
$data = ['a', 'b', 'c'];
|
||||
$json = $customSerializer->serialize($data);
|
||||
// Result: {"0":"a","1":"b","2":"c"}
|
||||
```
|
||||
|
||||
### Using Config Objects Directly
|
||||
|
||||
You can also create and use config objects directly:
|
||||
|
||||
```php
|
||||
use App\Framework\Serializer\Json\JsonSerializer;
|
||||
use App\Framework\Serializer\Json\JsonSerializerConfig;
|
||||
|
||||
// Create a config object
|
||||
$config = new JsonSerializerConfig(
|
||||
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES,
|
||||
512
|
||||
);
|
||||
|
||||
// Create a serializer with the config
|
||||
$serializer = new JsonSerializer($config);
|
||||
|
||||
// Or use the factory methods on the config class
|
||||
$compactConfig = JsonSerializerConfig::compact();
|
||||
$prettyConfig = JsonSerializerConfig::pretty();
|
||||
$minimalConfig = JsonSerializerConfig::minimal();
|
||||
|
||||
// Create serializers with these configs
|
||||
$compactSerializer = new JsonSerializer($compactConfig);
|
||||
$prettySerializer = new JsonSerializer($prettyConfig);
|
||||
$minimalSerializer = new JsonSerializer($minimalConfig);
|
||||
```
|
||||
|
||||
### Exception Handling
|
||||
|
||||
The serializer module provides a dedicated exception hierarchy for better error handling:
|
||||
|
||||
- `SerializerException`: Base exception for all serializer-related exceptions
|
||||
- `SerializeException`: Thrown when serialization fails
|
||||
- `DeserializeException`: Thrown when deserialization fails
|
||||
|
||||
Example of handling deserialization errors:
|
||||
|
||||
```php
|
||||
use App\Framework\Serializer\SerializerFactory;
|
||||
use App\Framework\Serializer\Exception\SerializeException;
|
||||
use App\Framework\Serializer\Exception\DeserializeException;
|
||||
|
||||
$serializer = SerializerFactory::createJsonSerializer();
|
||||
|
||||
try {
|
||||
// Try to deserialize invalid JSON
|
||||
$data = $serializer->deserialize('{"name": "John", }'); // Invalid JSON
|
||||
} catch (DeserializeException $e) {
|
||||
// Handle deserialization errors
|
||||
echo "Deserialization error: " . $e->getMessage();
|
||||
} catch (SerializeException $e) {
|
||||
// Handle serialization errors
|
||||
echo "Serialization error: " . $e->getMessage();
|
||||
} catch (SerializerException $e) {
|
||||
// Handle other serializer-related errors
|
||||
echo "Serializer error: " . $e->getMessage();
|
||||
}
|
||||
```
|
||||
|
||||
### MIME Type and File Extension
|
||||
|
||||
The serializer provides methods to get the MIME type and file extension:
|
||||
|
||||
```php
|
||||
$serializer = SerializerFactory::createJsonSerializer();
|
||||
|
||||
// Get MIME type
|
||||
$mimeType = $serializer->getMimeType(); // "application/json"
|
||||
|
||||
// Get file extension
|
||||
$extension = $serializer->getFileExtension(); // "json"
|
||||
```
|
||||
|
||||
## Migration from Legacy Serializers
|
||||
|
||||
If you're using the legacy serializers, here's how to migrate:
|
||||
|
||||
### From Cache JsonSerializer
|
||||
|
||||
```php
|
||||
// Old code
|
||||
use App\Framework\Cache\Serializer\JsonSerializer;
|
||||
$serializer = new JsonSerializer();
|
||||
$json = $serializer->serialize($data);
|
||||
$data = $serializer->unserialize($json);
|
||||
|
||||
// New code
|
||||
use App\Framework\Serializer\SerializerFactory;
|
||||
$serializer = SerializerFactory::createJsonSerializer();
|
||||
$json = $serializer->serialize($data);
|
||||
$data = $serializer->deserialize($json);
|
||||
```
|
||||
|
||||
### From Filesystem JsonSerializer
|
||||
|
||||
```php
|
||||
// Old code
|
||||
use App\Framework\Filesystem\Serializers\JsonSerializer;
|
||||
$serializer = new JsonSerializer();
|
||||
$json = $serializer->serialize($data);
|
||||
$data = $serializer->deserialize($json);
|
||||
|
||||
// Factory methods
|
||||
$compactSerializer = JsonSerializer::compact();
|
||||
$prettySerializer = JsonSerializer::pretty();
|
||||
$minimalSerializer = JsonSerializer::minimal();
|
||||
|
||||
// New code
|
||||
use App\Framework\Serializer\SerializerFactory;
|
||||
$serializer = SerializerFactory::createJsonSerializer();
|
||||
$json = $serializer->serialize($data);
|
||||
$data = $serializer->deserialize($json);
|
||||
|
||||
// Factory methods
|
||||
$compactSerializer = SerializerFactory::createCompactJsonSerializer();
|
||||
$prettySerializer = SerializerFactory::createPrettyJsonSerializer();
|
||||
$minimalSerializer = SerializerFactory::createMinimalJsonSerializer();
|
||||
```
|
||||
|
||||
## Direct Usage of JsonSerializer
|
||||
|
||||
If you need direct access to the JsonSerializer class:
|
||||
|
||||
```php
|
||||
use App\Framework\Serializer\Json\JsonSerializer;
|
||||
|
||||
// Create with default settings
|
||||
$serializer = new JsonSerializer();
|
||||
|
||||
// Create with custom settings
|
||||
$serializer = new JsonSerializer(
|
||||
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES,
|
||||
512
|
||||
);
|
||||
|
||||
// Use factory methods
|
||||
$compactSerializer = JsonSerializer::compact();
|
||||
$prettySerializer = JsonSerializer::pretty();
|
||||
$minimalSerializer = JsonSerializer::minimal();
|
||||
```
|
||||
|
||||
## PHP Serialization
|
||||
|
||||
The module also provides PHP serialization capabilities with a focus on security.
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```php
|
||||
use App\Framework\Serializer\SerializerFactory;
|
||||
|
||||
// Create a PHP serializer with default settings (safe with no allowed classes)
|
||||
$serializer = SerializerFactory::createPhpSerializer();
|
||||
|
||||
// Serialize data
|
||||
$data = ['name' => 'John', 'age' => 30];
|
||||
$serialized = $serializer->serialize($data);
|
||||
|
||||
// Deserialize data
|
||||
$deserializedData = $serializer->deserialize($serialized);
|
||||
```
|
||||
|
||||
### Security Considerations
|
||||
|
||||
PHP serialization can be a security risk if used improperly, as it allows arbitrary code execution through object deserialization. The PHP serializer in this module is designed with security in mind:
|
||||
|
||||
- By default, no classes are allowed to be unserialized (only primitive types and arrays)
|
||||
- You can explicitly specify which classes are allowed to be unserialized
|
||||
- You can create an unsafe serializer if you need to deserialize any class (use with caution)
|
||||
|
||||
### Safe and Unsafe Deserialization
|
||||
|
||||
```php
|
||||
use App\Framework\Serializer\SerializerFactory;
|
||||
|
||||
// Safe serializer with specific allowed classes
|
||||
$safeSerializer = SerializerFactory::createSafePhpSerializer(['stdClass', 'DateTime']);
|
||||
|
||||
// Unsafe serializer (allows all classes - use with caution)
|
||||
$unsafeSerializer = SerializerFactory::createUnsafePhpSerializer();
|
||||
```
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
You can create a serializer with custom options:
|
||||
|
||||
```php
|
||||
use App\Framework\Serializer\SerializerFactory;
|
||||
|
||||
// Custom PHP serializer with specific allowed classes and max depth
|
||||
$customSerializer = SerializerFactory::createCustomPhpSerializer(
|
||||
['MyClass', 'MyOtherClass'], // allowed classes
|
||||
4096 // max depth (optional, defaults to 4096)
|
||||
);
|
||||
```
|
||||
|
||||
### Using PhpSerializer with Config Objects
|
||||
|
||||
You can create and use PHP serializer config objects directly:
|
||||
|
||||
```php
|
||||
use App\Framework\Serializer\Php\PhpSerializer;
|
||||
use App\Framework\Serializer\Php\PhpSerializerConfig;
|
||||
|
||||
// Create with default settings (safe with no allowed classes)
|
||||
$serializer = new PhpSerializer();
|
||||
|
||||
// Create a config object with custom options
|
||||
$config = new PhpSerializerConfig(
|
||||
['MyClass'], // allowed classes
|
||||
4096 // max depth (optional, defaults to 4096)
|
||||
);
|
||||
|
||||
// Create a serializer with the config
|
||||
$serializer = new PhpSerializer($config);
|
||||
|
||||
// Or use the factory methods on the config class
|
||||
$safeConfig = PhpSerializerConfig::safe(
|
||||
['stdClass'], // allowed classes
|
||||
4096 // max depth (optional, defaults to 4096)
|
||||
);
|
||||
$unsafeConfig = PhpSerializerConfig::unsafe(
|
||||
4096 // max depth (optional, defaults to 4096)
|
||||
);
|
||||
|
||||
// Create serializers with these configs
|
||||
$safeSerializer = new PhpSerializer($safeConfig);
|
||||
$unsafeSerializer = new PhpSerializer($unsafeConfig);
|
||||
```
|
||||
|
||||
### Migration from Legacy PHP Serializers
|
||||
|
||||
If you're using the legacy PHP serializers, here's how to migrate:
|
||||
|
||||
#### From Cache PhpSerializer
|
||||
|
||||
```php
|
||||
// Old code
|
||||
use App\Framework\Cache\Serializer\PhpSerializer;
|
||||
$serializer = new PhpSerializer();
|
||||
$serialized = $serializer->serialize($data);
|
||||
$data = $serializer->unserialize($serialized);
|
||||
|
||||
// New code
|
||||
use App\Framework\Serializer\SerializerFactory;
|
||||
$serializer = SerializerFactory::createUnsafePhpSerializer(); // For backward compatibility
|
||||
$serialized = $serializer->serialize($data);
|
||||
$data = $serializer->deserialize($serialized);
|
||||
```
|
||||
|
||||
#### From Filesystem PhpSerializer
|
||||
|
||||
```php
|
||||
// Old code
|
||||
use App\Framework\Filesystem\Serializers\PhpSerializer;
|
||||
$serializer = new PhpSerializer();
|
||||
$serialized = $serializer->serialize($data);
|
||||
$data = $serializer->deserialize($data);
|
||||
|
||||
// Factory methods
|
||||
$safeSerializer = PhpSerializer::safe(['stdClass']);
|
||||
$unsafeSerializer = PhpSerializer::unsafe();
|
||||
|
||||
// New code
|
||||
use App\Framework\Serializer\SerializerFactory;
|
||||
$serializer = SerializerFactory::createPhpSerializer();
|
||||
$serialized = $serializer->serialize($data);
|
||||
$data = $serializer->deserialize($serialized);
|
||||
|
||||
// Factory methods
|
||||
$safeSerializer = SerializerFactory::createSafePhpSerializer(['stdClass']);
|
||||
$unsafeSerializer = SerializerFactory::createUnsafePhpSerializer();
|
||||
```
|
||||
46
src/Framework/Serializer/Serializer.php
Normal file
46
src/Framework/Serializer/Serializer.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Serializer;
|
||||
|
||||
/**
|
||||
* Base interface for all serializers
|
||||
*
|
||||
* This interface defines the core functionality that all serializers must implement.
|
||||
* It combines the essential methods from both the Cache and Filesystem serializer interfaces.
|
||||
*/
|
||||
interface Serializer
|
||||
{
|
||||
/**
|
||||
* Serializes data to a string representation
|
||||
*
|
||||
* @param mixed $data The data to serialize
|
||||
* @return string The serialized data as a string
|
||||
* @throws \App\Framework\Serializer\Exception\SerializeException If serialization fails
|
||||
*/
|
||||
public function serialize(mixed $data): string;
|
||||
|
||||
/**
|
||||
* Deserializes a string representation back to data
|
||||
*
|
||||
* @param string $data The string to deserialize
|
||||
* @return mixed The deserialized data
|
||||
* @throws \App\Framework\Serializer\Exception\DeserializeException If deserialization fails
|
||||
*/
|
||||
public function deserialize(string $data): mixed;
|
||||
|
||||
/**
|
||||
* Returns the MIME type for the serialized data
|
||||
*
|
||||
* @return string The MIME type
|
||||
*/
|
||||
public function getMimeType(): string;
|
||||
|
||||
/**
|
||||
* Returns the standard file extension for the serialized data
|
||||
*
|
||||
* @return string The file extension without the leading dot
|
||||
*/
|
||||
public function getFileExtension(): string;
|
||||
}
|
||||
113
src/Framework/Serializer/SerializerFactory.php
Normal file
113
src/Framework/Serializer/SerializerFactory.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Serializer;
|
||||
|
||||
use App\Framework\Serializer\Json\JsonSerializer;
|
||||
use App\Framework\Serializer\Json\JsonSerializerConfig;
|
||||
use App\Framework\Serializer\Php\PhpSerializer;
|
||||
use App\Framework\Serializer\Php\PhpSerializerConfig;
|
||||
|
||||
/**
|
||||
* Factory for creating serializer instances
|
||||
*
|
||||
* This class provides methods for creating various serializer instances.
|
||||
*/
|
||||
final class SerializerFactory
|
||||
{
|
||||
/**
|
||||
* Creates a JSON serializer with default settings
|
||||
*
|
||||
* @return Serializer
|
||||
*/
|
||||
public static function createJsonSerializer(): Serializer
|
||||
{
|
||||
return new JsonSerializer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON serializer with pretty printing
|
||||
*
|
||||
* @return Serializer
|
||||
*/
|
||||
public static function createPrettyJsonSerializer(): Serializer
|
||||
{
|
||||
return new JsonSerializer(JsonSerializerConfig::pretty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON serializer with compact output
|
||||
*
|
||||
* @return Serializer
|
||||
*/
|
||||
public static function createCompactJsonSerializer(): Serializer
|
||||
{
|
||||
return new JsonSerializer(JsonSerializerConfig::compact());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON serializer with minimal settings
|
||||
*
|
||||
* @return Serializer
|
||||
*/
|
||||
public static function createMinimalJsonSerializer(): Serializer
|
||||
{
|
||||
return new JsonSerializer(JsonSerializerConfig::minimal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON serializer with custom settings
|
||||
*
|
||||
* @param int $flags JSON encoding flags
|
||||
* @param int $depth Maximum depth for encoding/decoding
|
||||
* @return Serializer
|
||||
*/
|
||||
public static function createCustomJsonSerializer(int $flags, int $depth = 512): Serializer
|
||||
{
|
||||
return new JsonSerializer(new JsonSerializerConfig($flags, $depth));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PHP serializer with default settings (safe with no allowed classes)
|
||||
*
|
||||
* @return Serializer
|
||||
*/
|
||||
public static function createPhpSerializer(): Serializer
|
||||
{
|
||||
return new PhpSerializer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PHP serializer with safe deserialization (only specified classes allowed)
|
||||
*
|
||||
* @param array $allowedClasses List of class names that are allowed to be unserialized
|
||||
* @return Serializer
|
||||
*/
|
||||
public static function createSafePhpSerializer(array $allowedClasses = []): Serializer
|
||||
{
|
||||
return new PhpSerializer(PhpSerializerConfig::safe($allowedClasses));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PHP serializer with unsafe deserialization (all classes allowed)
|
||||
*
|
||||
* @return Serializer
|
||||
*/
|
||||
public static function createUnsafePhpSerializer(): Serializer
|
||||
{
|
||||
return new PhpSerializer(PhpSerializerConfig::unsafe());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PHP serializer with custom options
|
||||
*
|
||||
* @param bool|array $allowedClasses Controls which classes are allowed to be unserialized
|
||||
* @param int $maxDepth Maximum depth of nested objects to deserialize
|
||||
* @return Serializer
|
||||
*/
|
||||
public static function createCustomPhpSerializer(bool|array $allowedClasses, int $maxDepth = 4096): Serializer
|
||||
{
|
||||
return new PhpSerializer(new PhpSerializerConfig($allowedClasses, $maxDepth));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user