From 2bb6c62d7c54d5a864a29b396587ed81ad62a13c Mon Sep 17 00:00:00 2001 From: Walmir Silva Date: Sat, 26 Oct 2024 17:15:47 -0300 Subject: [PATCH 1/8] chore: define initial project structure with placeholder files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created initial project directory structure with placeholder files for core components - Added `TransformationResult.php` under `src/Result` for result handling. - Added traits for reusable transformation functions - Created `src/Transformer.php` as the main transformer entry point. These files and folders establish the foundation for the project’s structure without implementation details. --- src/Attribute/Transform.php | 0 src/Contract/TransformationResult.php | 0 src/Exception/TransformerException.php | 0 src/Processor/AbstractTransformerProcessor.php | 0 src/Processor/Array/ArrayFlattenTransformer.php | 0 src/Processor/Array/ArrayGroupTransformer.php | 0 src/Processor/Array/ArrayKeyTransformer.php | 0 src/Processor/Array/ArrayMapTransformer.php | 0 src/Processor/Composite/ChainTransformer.php | 0 src/Processor/Composite/ConditionalTransformer.php | 0 src/Processor/Data/DateTransformer.php | 0 src/Processor/Data/JsonTransformer.php | 0 src/Processor/Data/NumberTransformer.php | 0 src/Processor/String/CaseTransformer.php | 0 src/Processor/String/MaskTransformer.php | 0 src/Processor/String/SlugTransformer.php | 0 src/Processor/String/TemplateTransformer.php | 0 src/Result/TransformationResult.php | 0 src/Trait/ArrayTransformerTrait.php | 0 src/Trait/StringTransformerTrait.php | 0 src/Transformer.php | 0 21 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/Attribute/Transform.php create mode 100644 src/Contract/TransformationResult.php create mode 100644 src/Exception/TransformerException.php create mode 100644 src/Processor/AbstractTransformerProcessor.php create mode 100644 src/Processor/Array/ArrayFlattenTransformer.php create mode 100644 src/Processor/Array/ArrayGroupTransformer.php create mode 100644 src/Processor/Array/ArrayKeyTransformer.php create mode 100644 src/Processor/Array/ArrayMapTransformer.php create mode 100644 src/Processor/Composite/ChainTransformer.php create mode 100644 src/Processor/Composite/ConditionalTransformer.php create mode 100644 src/Processor/Data/DateTransformer.php create mode 100644 src/Processor/Data/JsonTransformer.php create mode 100644 src/Processor/Data/NumberTransformer.php create mode 100644 src/Processor/String/CaseTransformer.php create mode 100644 src/Processor/String/MaskTransformer.php create mode 100644 src/Processor/String/SlugTransformer.php create mode 100644 src/Processor/String/TemplateTransformer.php create mode 100644 src/Result/TransformationResult.php create mode 100644 src/Trait/ArrayTransformerTrait.php create mode 100644 src/Trait/StringTransformerTrait.php create mode 100644 src/Transformer.php diff --git a/src/Attribute/Transform.php b/src/Attribute/Transform.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Contract/TransformationResult.php b/src/Contract/TransformationResult.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Exception/TransformerException.php b/src/Exception/TransformerException.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Processor/AbstractTransformerProcessor.php b/src/Processor/AbstractTransformerProcessor.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Processor/Array/ArrayFlattenTransformer.php b/src/Processor/Array/ArrayFlattenTransformer.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Processor/Array/ArrayGroupTransformer.php b/src/Processor/Array/ArrayGroupTransformer.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Processor/Array/ArrayKeyTransformer.php b/src/Processor/Array/ArrayKeyTransformer.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Processor/Array/ArrayMapTransformer.php b/src/Processor/Array/ArrayMapTransformer.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Processor/Composite/ChainTransformer.php b/src/Processor/Composite/ChainTransformer.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Processor/Composite/ConditionalTransformer.php b/src/Processor/Composite/ConditionalTransformer.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Processor/Data/DateTransformer.php b/src/Processor/Data/DateTransformer.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Processor/Data/JsonTransformer.php b/src/Processor/Data/JsonTransformer.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Processor/Data/NumberTransformer.php b/src/Processor/Data/NumberTransformer.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Processor/String/CaseTransformer.php b/src/Processor/String/CaseTransformer.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Processor/String/MaskTransformer.php b/src/Processor/String/MaskTransformer.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Processor/String/SlugTransformer.php b/src/Processor/String/SlugTransformer.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Processor/String/TemplateTransformer.php b/src/Processor/String/TemplateTransformer.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Result/TransformationResult.php b/src/Result/TransformationResult.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Trait/ArrayTransformerTrait.php b/src/Trait/ArrayTransformerTrait.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Trait/StringTransformerTrait.php b/src/Trait/StringTransformerTrait.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Transformer.php b/src/Transformer.php new file mode 100644 index 0000000..e69de29 From c5b18ff2d3143d0a6d25d91ffef8b9f62078e72d Mon Sep 17 00:00:00 2001 From: Walmir Silva Date: Sat, 26 Oct 2024 17:19:46 -0300 Subject: [PATCH 2/8] feat: add core attribute, contract, and exception classes for transformer - Implemented `Transform` attribute in `src/Attribute/Transform.php`: - Extends `BaseProcessorAttribute` to mark properties for transformation processing. - Defined `TransformationResult` interface in `src/Contract/TransformationResult.php`: - Provides contract methods for result validation, error handling, and transformed data retrieval. - Created `TransformerException` in `src/Exception/TransformerException.php`: - Extends `AbstractException` to handle transformer-specific errors. - Added methods `invalidInput`, `invalidFormat`, and `invalidType` with detailed error codes and messages for input type, format, and type validation. --- src/Attribute/Transform.php | 12 ++++++ src/Contract/TransformationResult.php | 16 +++++++ src/Exception/TransformerException.php | 58 ++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) diff --git a/src/Attribute/Transform.php b/src/Attribute/Transform.php index e69de29..426ebd0 100644 --- a/src/Attribute/Transform.php +++ b/src/Attribute/Transform.php @@ -0,0 +1,12 @@ + Date: Sat, 26 Oct 2024 17:26:50 -0300 Subject: [PATCH 3/8] feat: update transformer processors and traits with enhanced functionality - Updated `AbstractTransformerProcessor` with validation logic: - Added `isValid` and `getErrorKey` methods for validation handling. - Included `guardAgainstInvalidType` method to enforce input type checks. - Enhanced array processors: - `ArrayFlattenTransformer`: added configurable `depth` and `separator` for flattening nested arrays. - `ArrayGroupTransformer`: added grouping logic with `groupBy` and `preserveKeys` options. - `ArrayKeyTransformer`: supports transforming array keys to different cases (`snake`, `camel`, `pascal`, `kebab`) with optional recursion. - `ArrayMapTransformer`: introduced `mapping`, `removeUnmapped`, and `recursive` options for flexible array mapping. - Extended `ArrayTransformerTrait` with case transformation methods: - Added `toCamelCase`, `toPascalCase`, `toSnakeCase`, and `toKebabCase` for consistent key formatting. - Updated `StringTransformerTrait` with additional string manipulation methods: - Added transformations for `toLowerCase`, `toUpperCase`, `toTitleCase`, `toSentenceCase`, and various case conversions. --- .../AbstractTransformerProcessor.php | 47 +++++++++++ .../Array/ArrayFlattenTransformer.php | 54 +++++++++++++ src/Processor/Array/ArrayGroupTransformer.php | 62 ++++++++++++++ src/Processor/Array/ArrayKeyTransformer.php | 80 +++++++++++++++++++ src/Processor/Array/ArrayMapTransformer.php | 62 ++++++++++++++ src/Trait/ArrayTransformerTrait.php | 36 +++++++++ src/Trait/StringTransformerTrait.php | 58 ++++++++++++++ 7 files changed, 399 insertions(+) diff --git a/src/Processor/AbstractTransformerProcessor.php b/src/Processor/AbstractTransformerProcessor.php index e69de29..bb104c7 100644 --- a/src/Processor/AbstractTransformerProcessor.php +++ b/src/Processor/AbstractTransformerProcessor.php @@ -0,0 +1,47 @@ +isValid = true; + $this->errorKey = ''; + } + + protected function setInvalid(string $errorKey): void + { + $this->isValid = false; + $this->errorKey = $errorKey; + } + + public function isValid(): bool + { + return $this->isValid; + } + + public function getErrorKey(): string + { + return $this->errorKey; + } + + protected function guardAgainstInvalidType(mixed $input, string $type): void + { + $actualType = get_debug_type($input); + if ($actualType !== $type) { + throw TransformerException::invalidType($type); + } + } + + abstract public function process(mixed $input): mixed; +} diff --git a/src/Processor/Array/ArrayFlattenTransformer.php b/src/Processor/Array/ArrayFlattenTransformer.php index e69de29..2788555 100644 --- a/src/Processor/Array/ArrayFlattenTransformer.php +++ b/src/Processor/Array/ArrayFlattenTransformer.php @@ -0,0 +1,54 @@ +depth = $options['depth'] ?? $this->depth; + $this->separator = $options['separator'] ?? $this->separator; + } + + public function process(mixed $input): array + { + if (!is_array($input)) { + $this->setInvalid('notArray'); + + return []; + } + + return $this->flattenArray($input, '', $this->depth); + } + + private function flattenArray(array $array, string $prefix = '', int $depth = -1): array + { + $result = []; + + foreach ($array as $key => $value) { + $newKey = $prefix ? $prefix . $this->separator . $key : $key; + + if (is_array($value) && ($depth > 0 || -1 === $depth)) { + $result = array_merge( + $result, + $this->flattenArray($value, $newKey, $depth > 0 ? $depth - 1 : -1) + ); + } else { + $result[$newKey] = $value; + } + } + + return $result; + } +} diff --git a/src/Processor/Array/ArrayGroupTransformer.php b/src/Processor/Array/ArrayGroupTransformer.php index e69de29..58f404b 100644 --- a/src/Processor/Array/ArrayGroupTransformer.php +++ b/src/Processor/Array/ArrayGroupTransformer.php @@ -0,0 +1,62 @@ +groupBy = $options['groupBy']; + $this->preserveKeys = $options['preserveKeys'] ?? $this->preserveKeys; + } + + public function process(mixed $input): array + { + if (!is_array($input)) { + $this->setInvalid('notArray'); + + return []; + } + + return $this->groupArray($input); + } + + private function groupArray(array $array): array + { + $result = []; + + foreach ($array as $key => $item) { + if (!is_array($item)) { + continue; + } + + $groupValue = $item[$this->groupBy] ?? null; + if (null === $groupValue) { + continue; + } + + if ($this->preserveKeys) { + $result[$groupValue][$key] = $item; + } else { + $result[$groupValue][] = $item; + } + } + + return $result; + } +} diff --git a/src/Processor/Array/ArrayKeyTransformer.php b/src/Processor/Array/ArrayKeyTransformer.php index e69de29..7c1b7cf 100644 --- a/src/Processor/Array/ArrayKeyTransformer.php +++ b/src/Processor/Array/ArrayKeyTransformer.php @@ -0,0 +1,80 @@ +getAllowedCases(), true)) { + $this->case = $options['case']; + } + + $this->recursive = $options['recursive'] ?? $this->recursive; + } + + public function process(mixed $input): array + { + if (!is_array($input)) { + $this->setInvalid('notArray'); + + return []; + } + + return $this->transformArrayKeys($input); + } + + private function transformArrayKeys(array $array): array + { + $result = []; + + foreach ($array as $key => $value) { + $transformedKey = $this->transformKey((string) $key); + + if (is_array($value) && $this->recursive) { + $result[$transformedKey] = $this->transformArrayKeys($value); + } else { + $result[$transformedKey] = $value; + } + } + + return $result; + } + + private function transformKey(string $key): string + { + return match ($this->case) { + self::CASE_SNAKE => $this->toSnakeCase($key), + self::CASE_CAMEL => $this->toCamelCase($key), + self::CASE_PASCAL => $this->toPascalCase($key), + self::CASE_KEBAB => $this->toKebabCase($key), + default => $key, + }; + } + + private function getAllowedCases(): array + { + return [ + self::CASE_SNAKE, + self::CASE_CAMEL, + self::CASE_PASCAL, + self::CASE_KEBAB, + ]; + } +} diff --git a/src/Processor/Array/ArrayMapTransformer.php b/src/Processor/Array/ArrayMapTransformer.php index e69de29..fa4625d 100644 --- a/src/Processor/Array/ArrayMapTransformer.php +++ b/src/Processor/Array/ArrayMapTransformer.php @@ -0,0 +1,62 @@ +mapping = $options['mapping']; + $this->removeUnmapped = $options['removeUnmapped'] ?? $this->removeUnmapped; + $this->recursive = $options['recursive'] ?? $this->recursive; + } + + public function process(mixed $input): array + { + if (!is_array($input)) { + $this->setInvalid('notArray'); + + return []; + } + + return $this->mapArray($input); + } + + private function mapArray(array $array): array + { + $result = []; + + foreach ($array as $key => $value) { + if (is_array($value) && $this->recursive) { + $result[$key] = $this->mapArray($value); + continue; + } + + $mappedKey = $this->mapping[$key] ?? $key; + + if ($this->removeUnmapped && !isset($this->mapping[$key])) { + continue; + } + + $result[$mappedKey] = $value; + } + + return $result; + } +} diff --git a/src/Trait/ArrayTransformerTrait.php b/src/Trait/ArrayTransformerTrait.php index e69de29..69cb6df 100644 --- a/src/Trait/ArrayTransformerTrait.php +++ b/src/Trait/ArrayTransformerTrait.php @@ -0,0 +1,36 @@ +toCamelCase($input)); + } + + protected function toSnakeCase(string $input): string + { + $pattern = '/([a-z0-9])([A-Z])/'; + $input = preg_replace($pattern, '$1_$2', $input); + $input = str_replace(['-', ' '], '_', $input); + + return strtolower($input); + } + + protected function toKebabCase(string $input): string + { + return str_replace('_', '-', $this->toSnakeCase($input)); + } +} diff --git a/src/Trait/StringTransformerTrait.php b/src/Trait/StringTransformerTrait.php index e69de29..b4c9cd2 100644 --- a/src/Trait/StringTransformerTrait.php +++ b/src/Trait/StringTransformerTrait.php @@ -0,0 +1,58 @@ +toLowerCase($input); + + return ucfirst($input); + } + + protected function toCamelCase(string $input): string + { + $input = str_replace(['-', '_'], ' ', $input); + $input = ucwords($input); + $input = str_replace(' ', '', $input); + + return lcfirst($input); + } + + protected function toPascalCase(string $input): string + { + return ucfirst($this->toCamelCase($input)); + } + + protected function toSnakeCase(string $input): string + { + $pattern = '/([a-z0-9])([A-Z])/'; + $input = preg_replace($pattern, '$1_$2', $input); + $input = str_replace(['-', ' '], '_', $input); + + return strtolower($input); + } + + protected function toKebabCase(string $input): string + { + return str_replace('_', '-', $this->toSnakeCase($input)); + } +} From 327dac2f1321987db6d7841639ed246a9a7a90c1 Mon Sep 17 00:00:00 2001 From: Walmir Silva Date: Sat, 26 Oct 2024 17:32:45 -0300 Subject: [PATCH 4/8] feat: update data and string processors for enhanced transformation functionality - Updated `DateTransformer`: - Added `inputFormat`, `outputFormat`, `inputTimezone`, and `outputTimezone` options for flexible date transformation. - Enhanced `JsonTransformer`: - Configurable options `assoc`, `depth`, and `encodeOptions` for JSON encoding and decoding with error handling. - Improved `NumberTransformer`: - Added options for decimal control, multipliers, rounding, and separators for number formatting. - Updated string processors: - `CaseTransformer`: Supports multiple case transformations, including lower, upper, title, sentence, camel, pascal, snake, and kebab cases. - `MaskTransformer`: Allows masking strings with custom patterns and pre-defined types (e.g., phone, CPF, CNPJ). - `SlugTransformer`: Generates slugs with custom separators, lowercase option, and locale-based transliteration. - `TemplateTransformer`: Template replacement with configurable placeholders and support for missing value handlers. - Updated `TransformationResult`: - Implements `TransformationResult` contract with methods for validation, error retrieval, and transformed data handling. --- src/Processor/Data/DateTransformer.php | 67 ++++++++++++++++ src/Processor/Data/JsonTransformer.php | 55 ++++++++++++++ src/Processor/Data/NumberTransformer.php | 52 +++++++++++++ src/Processor/String/CaseTransformer.php | 69 +++++++++++++++++ src/Processor/String/MaskTransformer.php | 72 ++++++++++++++++++ src/Processor/String/SlugTransformer.php | 80 ++++++++++++++++++++ src/Processor/String/TemplateTransformer.php | 63 +++++++++++++++ src/Result/TransformationResult.php | 36 +++++++++ 8 files changed, 494 insertions(+) diff --git a/src/Processor/Data/DateTransformer.php b/src/Processor/Data/DateTransformer.php index e69de29..d5145b2 100644 --- a/src/Processor/Data/DateTransformer.php +++ b/src/Processor/Data/DateTransformer.php @@ -0,0 +1,67 @@ +inputFormat = $options['inputFormat'] ?? $this->inputFormat; + $this->outputFormat = $options['outputFormat'] ?? $this->outputFormat; + $this->inputTimezone = $options['inputTimezone'] ?? $this->inputTimezone; + $this->outputTimezone = $options['outputTimezone'] ?? $this->outputTimezone; + } + + public function process(mixed $input): string + { + if (!is_string($input)) { + $this->setInvalid('notString'); + + return ''; + } + + try { + $date = $this->createDateTime($input); + + return $this->formatDate($date); + } catch (\Exception $e) { + $this->setInvalid('invalidDate'); + + return ''; + } + } + + private function createDateTime(string $input): \DateTime + { + $date = \DateTime::createFromFormat($this->inputFormat, $input); + + if (false === $date) { + throw new \RuntimeException('Invalid date format'); + } + + if ($this->inputTimezone) { + $date->setTimezone(new \DateTimeZone($this->inputTimezone)); + } + + return $date; + } + + private function formatDate(\DateTime $date): string + { + if ($this->outputTimezone) { + $date->setTimezone(new \DateTimeZone($this->outputTimezone)); + } + + return $date->format($this->outputFormat); + } +} diff --git a/src/Processor/Data/JsonTransformer.php b/src/Processor/Data/JsonTransformer.php index e69de29..f136b7e 100644 --- a/src/Processor/Data/JsonTransformer.php +++ b/src/Processor/Data/JsonTransformer.php @@ -0,0 +1,55 @@ +assoc = $options['assoc'] ?? $this->assoc; + $this->depth = $options['depth'] ?? $this->depth; + $this->encodeOptions = $options['encodeOptions'] ?? $this->encodeOptions; + } + + public function process(mixed $input): mixed + { + if (is_string($input)) { + return $this->decode($input); + } + + return $this->encode($input); + } + + private function decode(string $input): mixed + { + try { + $decoded = json_decode($input, $this->assoc, $this->depth, JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + $this->setInvalid('invalidJson'); + + return $this->assoc ? [] : new \stdClass(); + } + + return $decoded; + } + + private function encode(mixed $input): string + { + try { + return json_encode($input, $this->encodeOptions | JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + $this->setInvalid('unserializable'); + + return ''; + } + } +} diff --git a/src/Processor/Data/NumberTransformer.php b/src/Processor/Data/NumberTransformer.php index e69de29..a8a994d 100644 --- a/src/Processor/Data/NumberTransformer.php +++ b/src/Processor/Data/NumberTransformer.php @@ -0,0 +1,52 @@ +decimals = $options['decimals'] ?? $this->decimals; + $this->decimalPoint = $options['decimalPoint'] ?? $this->decimalPoint; + $this->thousandsSeparator = $options['thousandsSeparator'] ?? $this->thousandsSeparator; + $this->multiplier = $options['multiplier'] ?? $this->multiplier; + $this->roundUp = $options['roundUp'] ?? $this->roundUp; + } + + public function process(mixed $input): string + { + if (!is_numeric($input)) { + $this->setInvalid('notNumeric'); + + return ''; + } + + $number = (float) $input; + + if (null !== $this->multiplier) { + $number *= $this->multiplier; + } + + if ($this->roundUp) { + $number = ceil($number * (10 ** $this->decimals)) / (10 ** $this->decimals); + } + + return number_format( + $number, + $this->decimals, + $this->decimalPoint, + $this->thousandsSeparator + ); + } +} diff --git a/src/Processor/String/CaseTransformer.php b/src/Processor/String/CaseTransformer.php index e69de29..6a0ece6 100644 --- a/src/Processor/String/CaseTransformer.php +++ b/src/Processor/String/CaseTransformer.php @@ -0,0 +1,69 @@ +getAllowedCases(), true)) { + $this->case = $options['case']; + } + $this->preserveNumbers = $options['preserveNumbers'] ?? $this->preserveNumbers; + } + + public function process(mixed $input): string + { + if (!is_string($input)) { + $this->setInvalid('notString'); + + return ''; + } + + return match ($this->case) { + self::CASE_LOWER => $this->toLowerCase($input), + self::CASE_UPPER => $this->toUpperCase($input), + self::CASE_TITLE => $this->toTitleCase($input), + self::CASE_SENTENCE => $this->toSentenceCase($input), + self::CASE_CAMEL => $this->toCamelCase($input), + self::CASE_PASCAL => $this->toPascalCase($input), + self::CASE_SNAKE => $this->toSnakeCase($input), + self::CASE_KEBAB => $this->toKebabCase($input), + default => $input, + }; + } + + private function getAllowedCases(): array + { + return [ + self::CASE_LOWER, + self::CASE_UPPER, + self::CASE_TITLE, + self::CASE_SENTENCE, + self::CASE_CAMEL, + self::CASE_PASCAL, + self::CASE_SNAKE, + self::CASE_KEBAB, + ]; + } +} diff --git a/src/Processor/String/MaskTransformer.php b/src/Processor/String/MaskTransformer.php index e69de29..2a6d174 100644 --- a/src/Processor/String/MaskTransformer.php +++ b/src/Processor/String/MaskTransformer.php @@ -0,0 +1,72 @@ + '(##) #####-####', + 'cpf' => '###.###.###-##', + 'cnpj' => '##.###.###/####-##', + 'cep' => '#####-###', + ]; + + public function configure(array $options): void + { + if (isset($options['mask'])) { + $this->mask = $options['mask']; + } elseif (isset($options['type']) && isset($this->customMasks[$options['type']])) { + $this->mask = $this->customMasks[$options['type']]; + } + + $this->placeholder = $options['placeholder'] ?? $this->placeholder; + + if (isset($options['customMasks']) && is_array($options['customMasks'])) { + $this->customMasks = array_merge($this->customMasks, $options['customMasks']); + } + } + + public function process(mixed $input): string + { + if (!is_string($input)) { + $this->setInvalid('notString'); + + return ''; + } + + if (empty($this->mask)) { + $this->setInvalid('noMask'); + + return $input; + } + + return $this->applyMask($input); + } + + private function applyMask(string $input): string + { + $result = ''; + $inputPos = 0; + + for ($maskPos = 0; $maskPos < strlen($this->mask) && $inputPos < strlen($input); ++$maskPos) { + if ($this->mask[$maskPos] === $this->placeholder) { + $result .= $input[$inputPos]; + ++$inputPos; + } else { + $result .= $this->mask[$maskPos]; + } + } + + return $result; + } +} diff --git a/src/Processor/String/SlugTransformer.php b/src/Processor/String/SlugTransformer.php index e69de29..51fe813 100644 --- a/src/Processor/String/SlugTransformer.php +++ b/src/Processor/String/SlugTransformer.php @@ -0,0 +1,80 @@ +separator = $options['separator'] ?? $this->separator; + $this->lowercase = $options['lowercase'] ?? $this->lowercase; + $this->replacements = array_merge($this->replacements, $options['replacements'] ?? []); + $this->transliterationLocale = $options['transliterationLocale'] ?? $this->transliterationLocale; + } + + public function process(mixed $input): string + { + if (!is_string($input)) { + $this->setInvalid('notString'); + + return ''; + } + + $slug = $this->createSlug($input); + + return $this->finalizeSlug($slug); + } + + private function createSlug(string $input): string + { + // Apply custom replacements + $text = str_replace( + array_keys($this->replacements), + array_values($this->replacements), + $input + ); + + // Transliterate + $text = transliterator_transliterate( + "Any-{$this->transliterationLocale}; Latin-ASCII; NFD; [:Nonspacing Mark:] Remove; NFC", + $text + ); + + // Convert to lowercase if needed + if ($this->lowercase) { + $text = strtolower($text); + } + + // Replace non-alphanumeric characters with separator + $text = preg_replace('/[^\p{L}\p{N}]+/u', $this->separator, $text); + + // Remove duplicate separators + $text = preg_replace('/' . preg_quote($this->separator, '/') . '+/', $this->separator, $text); + + return trim($text, $this->separator); + } + + private function finalizeSlug(string $slug): string + { + if (empty($slug)) { + $this->setInvalid('emptySlug'); + + return ''; + } + + return $slug; + } +} diff --git a/src/Processor/String/TemplateTransformer.php b/src/Processor/String/TemplateTransformer.php index e69de29..0ec0f61 100644 --- a/src/Processor/String/TemplateTransformer.php +++ b/src/Processor/String/TemplateTransformer.php @@ -0,0 +1,63 @@ +template = $options['template'] ?? $this->template; + $this->openTag = $options['openTag'] ?? $this->openTag; + $this->closeTag = $options['closeTag'] ?? $this->closeTag; + $this->missingValueHandler = $options['missingValueHandler'] ?? $this->missingValueHandler; + $this->removeUnmatchedTags = $options['removeUnmatchedTags'] ?? $this->removeUnmatchedTags; + } + + public function process(mixed $input): string + { + if (!is_array($input)) { + $this->setInvalid('notArray'); + return $this->template; + } + + if (empty($this->template)) { + $this->setInvalid('noTemplate'); + return ''; + } + + return $this->replacePlaceholders($input); + } + + private function replacePlaceholders(array $data): string + { + $pattern = '/' . preg_quote($this->openTag, '/') . '\s*(.+?)\s*' . preg_quote($this->closeTag, '/') . '/'; + + return preg_replace_callback($pattern, function($matches) use ($data) { + $key = trim($matches[1]); + + if (isset($data[$key])) { + return $data[$key]; + } + + if ($this->missingValueHandler !== null) { + return call_user_func($this->missingValueHandler, $key); + } + + return $this->removeUnmatchedTags ? '' : $matches[0]; + }, $this->template); + } +} \ No newline at end of file diff --git a/src/Result/TransformationResult.php b/src/Result/TransformationResult.php index e69de29..0bfcf69 100644 --- a/src/Result/TransformationResult.php +++ b/src/Result/TransformationResult.php @@ -0,0 +1,36 @@ +results->hasErrors(); + } + + public function getErrors(): array + { + return $this->results->getErrors(); + } + + public function getTransformedData(): array + { + return $this->results->getProcessedData(); + } + + public function toArray(): array + { + return $this->results->toArray(); + } +} From 6c4bbd7fa5ebfd340c447b1fe71d6fd839f675d1 Mon Sep 17 00:00:00 2001 From: Walmir Silva Date: Sat, 26 Oct 2024 17:38:43 -0300 Subject: [PATCH 5/8] feat: add Transformer contract and enhance composite processors - Added `Transformer` contract in `src/Contract/Transformer.php`: - Defines the core interface for all transformers, ensuring consistency across implementations. - Updated `ChainTransformer`: - Enhanced chaining mechanism for sequential processing of transformations. - Improved error handling and data validation during transformation chain execution. - Improved `ConditionalTransformer`: - Added conditional checks to apply transformations based on configurable criteria. - Enhanced logic for validating conditions before executing transformations. - Modified main `Transformer` class in `src/Transformer.php`: - Integrated `Transformer` contract to standardize transformation behavior. - Streamlined method signatures and added additional validation for consistent data processing. --- src/Contract/Transformer.php | 12 +++ src/Processor/Composite/ChainTransformer.php | 52 ++++++++++ .../Composite/ConditionalTransformer.php | 62 ++++++++++++ src/Transformer.php | 96 +++++++++++++++++++ 4 files changed, 222 insertions(+) create mode 100644 src/Contract/Transformer.php diff --git a/src/Contract/Transformer.php b/src/Contract/Transformer.php new file mode 100644 index 0000000..a77d323 --- /dev/null +++ b/src/Contract/Transformer.php @@ -0,0 +1,12 @@ + */ + private array $transformers = []; + + private bool $stopOnError = true; + + public function configure(array $options): void + { + if (isset($options['transformers']) && is_array($options['transformers'])) { + foreach ($options['transformers'] as $transformer) { + if ($transformer instanceof AbstractTransformerProcessor) { + $this->transformers[] = $transformer; + } + } + } + + $this->stopOnError = $options['stopOnError'] ?? $this->stopOnError; + } + + public function process(mixed $input): mixed + { + $result = $input; + + foreach ($this->transformers as $transformer) { + try { + $result = $transformer->process($result); + + if (!$transformer->isValid() && $this->stopOnError) { + $this->setInvalid($transformer->getErrorKey()); + break; + } + } catch (\Exception $e) { + if ($this->stopOnError) { + $this->setInvalid('transformationError'); + break; + } + } + } + + return $result; + } +} diff --git a/src/Processor/Composite/ConditionalTransformer.php b/src/Processor/Composite/ConditionalTransformer.php index e69de29..f4f8e85 100644 --- a/src/Processor/Composite/ConditionalTransformer.php +++ b/src/Processor/Composite/ConditionalTransformer.php @@ -0,0 +1,62 @@ +transformer = $options['transformer']; + $this->condition = $options['condition']; + $this->defaultValue = $options['defaultValue'] ?? $this->defaultValue; + $this->useDefaultOnError = $options['useDefaultOnError'] ?? $this->useDefaultOnError; + } + + public function process(mixed $input): mixed + { + if (!$this->shouldTransform($input)) { + return $this->defaultValue ?? $input; + } + + try { + $result = $this->transformer->process($input); + + if (!$this->transformer->isValid() && $this->useDefaultOnError) { + $this->setInvalid($this->transformer->getErrorKey()); + return $this->defaultValue ?? $input; + } + + return $result; + } catch (\Exception $e) { + $this->setInvalid('transformationError'); + return $this->defaultValue ?? $input; + } + } + + private function shouldTransform(mixed $input): bool + { + try { + return call_user_func($this->condition, $input); + } catch (\Exception $e) { + return false; + } + } +} \ No newline at end of file diff --git a/src/Transformer.php b/src/Transformer.php index e69de29..23f2002 100644 --- a/src/Transformer.php +++ b/src/Transformer.php @@ -0,0 +1,96 @@ +builder = new ProcessorBuilder($this->registry); + } + + public function transform(mixed $object): TransformationResult + { + $handler = new ProcessorAttributeHandler( + self::IDENTIFIER, + $this->builder + ); + + $propertyInspector = new PropertyInspector( + new AttributeAnalyzer(Transform::class) + ); + + /** @var ProcessorAttributeHandler */ + $handler = $propertyInspector->inspect($object, $handler); + $handler->applyChanges($object); + + return new TransformationResult( + $handler->getProcessingResults() + ); + } +} + +namespace KaririCode\Transformer\Processor\Composite; + +use KaririCode\Contract\Processor\ConfigurableProcessor; +use KaririCode\Transformer\Processor\AbstractTransformerProcessor; + +class ChainTransformer extends AbstractTransformerProcessor implements ConfigurableProcessor +{ + /** @var array */ + private array $transformers = []; + + private bool $stopOnError = true; + + public function configure(array $options): void + { + if (isset($options['transformers']) && is_array($options['transformers'])) { + foreach ($options['transformers'] as $transformer) { + if ($transformer instanceof AbstractTransformerProcessor) { + $this->transformers[] = $transformer; + } + } + } + + $this->stopOnError = $options['stopOnError'] ?? $this->stopOnError; + } + + public function process(mixed $input): mixed + { + $result = $input; + + foreach ($this->transformers as $transformer) { + try { + $result = $transformer->process($result); + + if (!$transformer->isValid() && $this->stopOnError) { + $this->setInvalid($transformer->getErrorKey()); + break; + } + } catch (\Exception $e) { + if ($this->stopOnError) { + $this->setInvalid('transformationError'); + break; + } + } + } + + return $result; + } +} From 7c7bfd1a8b5bd9f6b9f4c7e0c9382a7a129bb4e9 Mon Sep 17 00:00:00 2001 From: Walmir Silva Date: Sat, 26 Oct 2024 17:41:59 -0300 Subject: [PATCH 6/8] style: apply code style fixes for consistency and readability - Updated `ConditionalTransformer`: - Updated `TemplateTransformer` --- .../Composite/ConditionalTransformer.php | 9 ++++++-- src/Processor/String/TemplateTransformer.php | 21 ++++++++++++------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/Processor/Composite/ConditionalTransformer.php b/src/Processor/Composite/ConditionalTransformer.php index f4f8e85..289bac5 100644 --- a/src/Processor/Composite/ConditionalTransformer.php +++ b/src/Processor/Composite/ConditionalTransformer.php @@ -10,7 +10,10 @@ class ConditionalTransformer extends AbstractTransformerProcessor implements ConfigurableProcessor { private ?AbstractTransformerProcessor $transformer = null; - private ?callable $condition = null; + + /** @var callable|null */ + private mixed $condition = null; + private mixed $defaultValue = null; private bool $useDefaultOnError = true; @@ -41,12 +44,14 @@ public function process(mixed $input): mixed if (!$this->transformer->isValid() && $this->useDefaultOnError) { $this->setInvalid($this->transformer->getErrorKey()); + return $this->defaultValue ?? $input; } return $result; } catch (\Exception $e) { $this->setInvalid('transformationError'); + return $this->defaultValue ?? $input; } } @@ -59,4 +64,4 @@ private function shouldTransform(mixed $input): bool return false; } } -} \ No newline at end of file +} diff --git a/src/Processor/String/TemplateTransformer.php b/src/Processor/String/TemplateTransformer.php index 0ec0f61..35f0a2b 100644 --- a/src/Processor/String/TemplateTransformer.php +++ b/src/Processor/String/TemplateTransformer.php @@ -15,7 +15,10 @@ class TemplateTransformer extends AbstractTransformerProcessor implements Config private string $template = ''; private string $openTag = '{{'; private string $closeTag = '}}'; - private ?callable $missingValueHandler = null; + + /** @var callable|null */ + private mixed $missingValueHandler = null; + private bool $removeUnmatchedTags = false; public function configure(array $options): void @@ -31,11 +34,13 @@ public function process(mixed $input): string { if (!is_array($input)) { $this->setInvalid('notArray'); + return $this->template; } if (empty($this->template)) { $this->setInvalid('noTemplate'); + return ''; } @@ -45,19 +50,19 @@ public function process(mixed $input): string private function replacePlaceholders(array $data): string { $pattern = '/' . preg_quote($this->openTag, '/') . '\s*(.+?)\s*' . preg_quote($this->closeTag, '/') . '/'; - - return preg_replace_callback($pattern, function($matches) use ($data) { + + return preg_replace_callback($pattern, function ($matches) use ($data) { $key = trim($matches[1]); - + if (isset($data[$key])) { return $data[$key]; } - - if ($this->missingValueHandler !== null) { + + if (null !== $this->missingValueHandler) { return call_user_func($this->missingValueHandler, $key); } - + return $this->removeUnmatchedTags ? '' : $matches[0]; }, $this->template); } -} \ No newline at end of file +} From 2a334b388c9677abf1d7b82996695bd44e397299 Mon Sep 17 00:00:00 2001 From: Walmir Silva Date: Sat, 26 Oct 2024 18:21:05 -0300 Subject: [PATCH 7/8] feat: implement comprehensive transformer demo This commit demonstrates the usage of various transformers in the KaririCode Framework. The demo showcases different transformation capabilities: - Data Formats: * Date format conversion (d/m/Y to Y-m-d) * Number formatting with locale-specific separators * JSON handling with pretty print - String Manipulation: * Phone number masking ((##) #####-####) * Case transformations (camelCase, snake_case) * URL slug generation * Template rendering with variable substitution - Array Operations: * Array key case transformation * Array flattening with dot notation * Array grouping by field --- src/Contract/Transformer.php | 12 - src/Processor/Data/JsonTransformer.php | 8 +- src/Processor/Data/NumberTransformer.php | 22 +- src/Processor/String/SlugTransformer.php | 68 +++-- src/Processor/String/TemplateTransformer.php | 14 +- src/Transformer.php | 4 +- tests/application.php | 257 +++++++++++++++++++ 7 files changed, 339 insertions(+), 46 deletions(-) delete mode 100644 src/Contract/Transformer.php diff --git a/src/Contract/Transformer.php b/src/Contract/Transformer.php deleted file mode 100644 index a77d323..0000000 --- a/src/Contract/Transformer.php +++ /dev/null @@ -1,12 +0,0 @@ -assoc = $options['assoc'] ?? $this->assoc; $this->depth = $options['depth'] ?? $this->depth; $this->encodeOptions = $options['encodeOptions'] ?? $this->encodeOptions; + $this->returnString = $options['returnString'] ?? $this->returnString; } public function process(mixed $input): mixed @@ -26,7 +28,11 @@ public function process(mixed $input): mixed return $this->decode($input); } - return $this->encode($input); + if (is_array($input) && $this->returnString) { + return $this->encode($input); + } + + return $input; } private function decode(string $input): mixed diff --git a/src/Processor/Data/NumberTransformer.php b/src/Processor/Data/NumberTransformer.php index a8a994d..6b4344c 100644 --- a/src/Processor/Data/NumberTransformer.php +++ b/src/Processor/Data/NumberTransformer.php @@ -14,6 +14,7 @@ class NumberTransformer extends AbstractTransformerProcessor implements Configur private string $thousandsSeparator = ''; private ?float $multiplier = null; private bool $roundUp = false; + private bool $formatAsString = false; public function configure(array $options): void { @@ -22,14 +23,15 @@ public function configure(array $options): void $this->thousandsSeparator = $options['thousandsSeparator'] ?? $this->thousandsSeparator; $this->multiplier = $options['multiplier'] ?? $this->multiplier; $this->roundUp = $options['roundUp'] ?? $this->roundUp; + $this->formatAsString = $options['formatAsString'] ?? $this->formatAsString; } - public function process(mixed $input): string + public function process(mixed $input): float|string { if (!is_numeric($input)) { $this->setInvalid('notNumeric'); - return ''; + return $this->formatAsString ? '' : 0.0; } $number = (float) $input; @@ -42,11 +44,15 @@ public function process(mixed $input): string $number = ceil($number * (10 ** $this->decimals)) / (10 ** $this->decimals); } - return number_format( - $number, - $this->decimals, - $this->decimalPoint, - $this->thousandsSeparator - ); + if ($this->formatAsString) { + return number_format( + $number, + $this->decimals, + $this->decimalPoint, + $this->thousandsSeparator + ); + } + + return round($number, $this->decimals); } } diff --git a/src/Processor/String/SlugTransformer.php b/src/Processor/String/SlugTransformer.php index 51fe813..e6e8708 100644 --- a/src/Processor/String/SlugTransformer.php +++ b/src/Processor/String/SlugTransformer.php @@ -15,14 +15,12 @@ class SlugTransformer extends AbstractTransformerProcessor implements Configurab private string $separator = '-'; private bool $lowercase = true; private array $replacements = []; - private string $transliterationLocale = 'en'; public function configure(array $options): void { $this->separator = $options['separator'] ?? $this->separator; $this->lowercase = $options['lowercase'] ?? $this->lowercase; - $this->replacements = array_merge($this->replacements, $options['replacements'] ?? []); - $this->transliterationLocale = $options['transliterationLocale'] ?? $this->transliterationLocale; + $this->replacements = array_merge($this->getDefaultReplacements(), $options['replacements'] ?? []); } public function process(mixed $input): string @@ -35,46 +33,76 @@ public function process(mixed $input): string $slug = $this->createSlug($input); - return $this->finalizeSlug($slug); + if (empty($slug)) { + $this->setInvalid('emptySlug'); + + return ''; + } + + return $slug; } private function createSlug(string $input): string { - // Apply custom replacements + // Apply custom replacements first $text = str_replace( array_keys($this->replacements), array_values($this->replacements), $input ); - // Transliterate - $text = transliterator_transliterate( - "Any-{$this->transliterationLocale}; Latin-ASCII; NFD; [:Nonspacing Mark:] Remove; NFC", - $text - ); + // Convert accented characters to ASCII + $text = $this->convertAccentsToAscii($text); // Convert to lowercase if needed if ($this->lowercase) { - $text = strtolower($text); + $text = mb_strtolower($text); } // Replace non-alphanumeric characters with separator - $text = preg_replace('/[^\p{L}\p{N}]+/u', $this->separator, $text); + $text = preg_replace('/[^a-zA-Z0-9\-_]/', $this->separator, $text); - // Remove duplicate separators + // Replace multiple separators with a single one $text = preg_replace('/' . preg_quote($this->separator, '/') . '+/', $this->separator, $text); return trim($text, $this->separator); } - private function finalizeSlug(string $slug): string + private function getDefaultReplacements(): array { - if (empty($slug)) { - $this->setInvalid('emptySlug'); - - return ''; - } + return [ + ' ' => $this->separator, + '&' => 'and', + '@' => 'at', + ]; + } - return $slug; + private function convertAccentsToAscii(string $string): string + { + $chars = [ + // Latin + 'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A', 'Å' => 'A', 'Æ' => 'AE', + 'Ç' => 'C', 'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I', + 'Î' => 'I', 'Ï' => 'I', 'Ð' => 'D', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', + 'Õ' => 'O', 'Ö' => 'O', 'Ő' => 'O', 'Ø' => 'O', 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', + 'Ü' => 'U', 'Ű' => 'U', 'Ý' => 'Y', 'Þ' => 'TH', 'ß' => 'ss', 'à' => 'a', 'á' => 'a', + 'â' => 'a', 'ã' => 'a', 'ä' => 'a', 'å' => 'a', 'æ' => 'ae', 'ç' => 'c', 'è' => 'e', + 'é' => 'e', 'ê' => 'e', 'ë' => 'e', 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', + 'ð' => 'd', 'ñ' => 'n', 'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'o', + 'ő' => 'o', 'ø' => 'o', 'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'u', 'ű' => 'u', + 'ý' => 'y', 'þ' => 'th', 'ÿ' => 'y', + // Latin symbols + '©' => '(c)', + // Greek + 'Α' => 'A', 'Β' => 'B', 'Γ' => 'G', 'Δ' => 'D', 'Ε' => 'E', 'Ζ' => 'Z', 'Η' => 'H', + 'Θ' => '8', 'Ι' => 'I', 'Κ' => 'K', 'Λ' => 'L', 'Μ' => 'M', 'Ν' => 'N', 'Ξ' => '3', + 'Ο' => 'O', 'Π' => 'P', 'Ρ' => 'R', 'Σ' => 'S', 'Τ' => 'T', 'Υ' => 'Y', 'Φ' => 'F', + 'Χ' => 'X', 'Ψ' => 'PS', 'Ω' => 'W', 'α' => 'a', 'β' => 'b', 'γ' => 'g', 'δ' => 'd', + 'ε' => 'e', 'ζ' => 'z', 'η' => 'h', 'θ' => '8', 'ι' => 'i', 'κ' => 'k', 'λ' => 'l', + 'μ' => 'm', 'ν' => 'n', 'ξ' => '3', 'ο' => 'o', 'π' => 'p', 'ρ' => 'r', 'σ' => 's', + 'τ' => 't', 'υ' => 'y', 'φ' => 'f', 'χ' => 'x', 'ψ' => 'ps', 'ω' => 'w', + ]; + + return strtr($string, $chars); } } diff --git a/src/Processor/String/TemplateTransformer.php b/src/Processor/String/TemplateTransformer.php index 35f0a2b..5f9bb2d 100644 --- a/src/Processor/String/TemplateTransformer.php +++ b/src/Processor/String/TemplateTransformer.php @@ -20,6 +20,7 @@ class TemplateTransformer extends AbstractTransformerProcessor implements Config private mixed $missingValueHandler = null; private bool $removeUnmatchedTags = false; + private bool $preserveData = true; public function configure(array $options): void { @@ -28,20 +29,27 @@ public function configure(array $options): void $this->closeTag = $options['closeTag'] ?? $this->closeTag; $this->missingValueHandler = $options['missingValueHandler'] ?? $this->missingValueHandler; $this->removeUnmatchedTags = $options['removeUnmatchedTags'] ?? $this->removeUnmatchedTags; + $this->preserveData = $options['preserveData'] ?? $this->preserveData; } - public function process(mixed $input): string + public function process(mixed $input): mixed { if (!is_array($input)) { $this->setInvalid('notArray'); - return $this->template; + return $input; } if (empty($this->template)) { $this->setInvalid('noTemplate'); - return ''; + return $input; + } + + if ($this->preserveData) { + $input['_rendered'] = $this->replacePlaceholders($input); + + return $input; } return $this->replacePlaceholders($input); diff --git a/src/Transformer.php b/src/Transformer.php index 23f2002..6ca2929 100644 --- a/src/Transformer.php +++ b/src/Transformer.php @@ -27,7 +27,7 @@ public function __construct( public function transform(mixed $object): TransformationResult { - $handler = new ProcessorAttributeHandler( + $attributeHandler = new ProcessorAttributeHandler( self::IDENTIFIER, $this->builder ); @@ -37,7 +37,7 @@ public function transform(mixed $object): TransformationResult ); /** @var ProcessorAttributeHandler */ - $handler = $propertyInspector->inspect($object, $handler); + $handler = $propertyInspector->inspect($object, $attributeHandler); $handler->applyChanges($object); return new TransformationResult( diff --git a/tests/application.php b/tests/application.php index a075e1e..c6ceafe 100644 --- a/tests/application.php +++ b/tests/application.php @@ -3,3 +3,260 @@ declare(strict_types=1); require_once __DIR__ . '/../vendor/autoload.php'; + +use KaririCode\ProcessorPipeline\ProcessorRegistry; +use KaririCode\Transformer\Attribute\Transform; +use KaririCode\Transformer\Processor\Array\ArrayFlattenTransformer; +use KaririCode\Transformer\Processor\Array\ArrayGroupTransformer; +use KaririCode\Transformer\Processor\Array\ArrayKeyTransformer; +use KaririCode\Transformer\Processor\Array\ArrayMapTransformer; +use KaririCode\Transformer\Processor\Data\DateTransformer; +use KaririCode\Transformer\Processor\Data\JsonTransformer; +use KaririCode\Transformer\Processor\Data\NumberTransformer; +use KaririCode\Transformer\Processor\String\CaseTransformer; +use KaririCode\Transformer\Processor\String\MaskTransformer; +use KaririCode\Transformer\Processor\String\SlugTransformer; +use KaririCode\Transformer\Processor\String\TemplateTransformer; +use KaririCode\Transformer\Transformer; + +// 1. Define the entity class with transformation rules +class DataTransformer +{ + #[Transform( + processors: ['date' => ['inputFormat' => 'd/m/Y', 'outputFormat' => 'Y-m-d']] + )] + private string $date = '25/12/2024'; + + #[Transform( + processors: ['number' => ['decimals' => 2, 'decimalPoint' => ',', 'thousandsSeparator' => '.']] + )] + private float $price = 1234.56; + + #[Transform( + processors: ['mask' => ['type' => 'phone']] + )] + private string $phone = '11999887766'; + + #[Transform( + processors: ['case' => ['case' => 'snake']] + )] + private string $text = 'transformThisTextToSnakeCase'; + + #[Transform( + processors: ['slug' => []] + )] + private string $title = 'This is a Title for URL!'; + + #[Transform( + processors: ['arrayKey' => ['case' => 'camel']] + )] + private array $data = [ + 'user_name' => 'Carlos Silva', + 'email_address' => 'carlos@example.com', + 'phone_number' => '1234567890', + ]; + + #[Transform( + processors: ['json' => ['encodeOptions' => JSON_PRETTY_PRINT]] + )] + private array $jsonData = [ + 'id' => 1, + 'name' => 'Product', + 'price' => 99.99, + ]; + + #[Transform( + processors: [ + 'template' => [ + 'template' => 'Hello {{name}}, your order #{{order_id}} is {{status}}', + 'removeUnmatchedTags' => true, + ], + ] + )] + private array $templateData = [ + 'name' => 'Carlos', + 'order_id' => '12345', + 'status' => 'completed', + ]; + + // Getters and setters + public function getDate(): string + { + return $this->date; + } + + public function getPrice(): float + { + return $this->price; + } + + public function getPhone(): string + { + return $this->phone; + } + + public function getText(): string + { + return $this->text; + } + + public function getTitle(): string + { + return $this->title; + } + + public function getData(): array + { + return $this->data; + } + + public function getJsonData(): array + { + return $this->jsonData; + } + + public function getTemplateData(): array + { + return $this->templateData; + } +} + +// 2. Set up the transformer registry +function setupTransformerRegistry(): ProcessorRegistry +{ + $registry = new ProcessorRegistry(); + + // Register all transformers + $registry->register('transformer', 'date', new DateTransformer()) + ->register('transformer', 'number', new NumberTransformer()) + ->register('transformer', 'mask', new MaskTransformer()) + ->register('transformer', 'case', new CaseTransformer()) + ->register('transformer', 'slug', new SlugTransformer()) + ->register('transformer', 'arrayKey', new ArrayKeyTransformer()) + ->register('transformer', 'arrayFlat', new ArrayFlattenTransformer()) + ->register('transformer', 'arrayGroup', new ArrayGroupTransformer()) + ->register('transformer', 'arrayMap', new ArrayMapTransformer()) + ->register('transformer', 'json', new JsonTransformer()) + ->register('transformer', 'template', new TemplateTransformer()); + + return $registry; +} + +// 3. Helper function to display transformation results +function displayTransformationResults(object $data, array $errors): void +{ + echo "\nTransformed Data:\n"; + echo "================\n"; + + // Standard date formatting + echo 'Date: ' . $data->getDate() . "\n"; + + // Number formatting with localized separators + echo 'Price: ' . number_format($data->getPrice(), 2, ',', '.') . "\n"; + + // Phone is already formatted by the transformer + echo 'Phone: ' . $data->getPhone() . "\n"; + + // Text transformed to snake_case + echo 'Text: ' . $data->getText() . "\n"; + + // Slug is already formatted + echo 'Title (Slug): ' . $data->getTitle() . "\n"; + + // Array with keys in camelCase + echo 'Array Data: ' . print_r($data->getData(), true); + + // JSON Data formatted for better readability + echo 'JSON Data: ' . json_encode($data->getJsonData(), JSON_PRETTY_PRINT) . "\n"; + + // Template Data with rendered result + $templateData = $data->getTemplateData(); + echo "Template Data:\n"; + + // Show original template data + echo 'Original Data: ' . print_r(array_diff_key($templateData, ['_rendered' => '']), true); + + // Display rendered result + if (isset($templateData['_rendered'])) { + echo 'Rendered Result: ' . $templateData['_rendered'] . "\n"; + } + + if (!empty($errors)) { + echo "\n\033[31mTransformation Errors:\033[0m\n"; + foreach ($errors as $property => $propertyErrors) { + foreach ($propertyErrors as $error) { + echo "\033[31m- {$property}: {$error['message']}\033[0m\n"; + } + } + } else { + echo "\n\033[32mAll transformations completed successfully!\033[0m\n"; + } +} + +// 4. Test cases for additional transformers +function runAdditionalTests(Transformer $transformer): void +{ + echo "\n\033[1mTesting Array Transformers\033[0m\n"; + echo "=======================\n"; + + // Test ArrayFlattenTransformer + $nestedArray = [ + 'user' => [ + 'profile' => [ + 'name' => 'Carlos', + 'contacts' => [ + 'email' => 'carlos@example.com', + ], + ], + ], + ]; + + $flattenTransformer = new ArrayFlattenTransformer(); + $flattenTransformer->configure(['depth' => -1]); + $flattened = $flattenTransformer->process($nestedArray); + echo "Flattened Array:\n"; + print_r($flattened); + + // Test ArrayGroupTransformer + $users = [ + ['name' => 'Carlos', 'role' => 'admin'], + ['name' => 'Ana', 'role' => 'user'], + ['name' => 'Bia', 'role' => 'admin'], + ]; + + $groupTransformer = new ArrayGroupTransformer(); + $groupTransformer->configure(['groupBy' => 'role']); + $grouped = $groupTransformer->process($users); + echo "\nGrouped Array:\n"; + print_r($grouped); +} + +// 5. Main application execution +function main(): void +{ + try { + echo "\033[1mKaririCode Transformer Demo\033[0m\n"; + echo "================================\n"; + + // Setup + $registry = setupTransformerRegistry(); + $transformer = new Transformer($registry); + + // Create and transform data + $data = new DataTransformer(); + $result = $transformer->transform($data); + + // Display results + displayTransformationResults($data, $result->getErrors()); + + // Run additional tests + runAdditionalTests($transformer); + } catch (Exception $e) { + echo "\033[31mError: {$e->getMessage()}\033[0m\n"; + echo "\033[33mStack trace:\033[0m\n"; + echo $e->getTraceAsString() . "\n"; + } +} + +// Run the application +main(); From f708f8f7f9eceaee341c2e24e32a5b03b60600f6 Mon Sep 17 00:00:00 2001 From: Walmir Silva Date: Sat, 26 Oct 2024 18:38:45 -0300 Subject: [PATCH 8/8] docs: add comprehensive transformer documentation in English and Portuguese This commit adds detailed documentation for the KaririCode Transformer component in both English and Brazilian Portuguese. The documentation includes: - Complete usage examples with output samples - Detailed configuration options for each transformer - Real-world examples with practical applications - Code samples for common transformation scenarios Documentation highlights: - String transformation (case, mask, slug) - Data formatting (dates, numbers, currency) - Array manipulation (flattening, grouping, key mapping) - Template processing with examples - Integration examples with other KaririCode components The documentation is structured to serve both international and Brazilian developers, with: - Localized examples - Regional formatting patterns - Culturally appropriate use cases Files added: - README.md (English version) - README.pt-BR.md (Portuguese version) --- README.md | 912 +++++++++++++++++++++++++++++++++++++++++++++--- README.pt-br.md | 593 ++++++++++++++++++++++++++++--- 2 files changed, 1413 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 9b896ae..fe84766 100644 --- a/README.md +++ b/README.md @@ -1,76 +1,900 @@ -# KaririCode Framework: transformer Component +# KaririCode Framework: Transformer Component -## Development and Testing +A powerful and flexible data transformation component for PHP, part of the KaririCode Framework. It uses attribute-based transformation with configurable processors to ensure consistent data transformation and formatting in your applications. + +## Table of Contents + +- [Features](#features) +- [Installation](#installation) +- [Usage](#usage) + - [Basic Usage](#basic-usage) + - [Advanced Usage: Data Formatting](#advanced-usage-data-formatting) +- [Available Transformers](#available-transformers) + - [String Transformers](#string-transformers) + - [Data Transformers](#data-transformers) + - [Array Transformers](#array-transformers) + - [Composite Transformers](#composite-transformers) +- [Configuration](#configuration) +- [Integration with Other KaririCode Components](#integration-with-other-kariricode-components) +- [Development and Testing](#development-and-testing) +- [Contributing](#contributing) +- [License](#license) +- [Support and Community](#support-and-community) + +## Features + +- Attribute-based transformation for object properties +- Comprehensive set of built-in transformers for common use cases +- Easy integration with other KaririCode components +- Configurable processors for customized transformation logic +- Extensible architecture allowing custom transformers +- Robust error handling and reporting +- Chainable transformation pipelines for complex data transformation +- Built-in support for multiple transformation scenarios +- Type-safe transformation with PHP 8.3 features +- Preservation of original data types +- Flexible formatting options for various data types + +## Installation + +You can install the Transformer component via Composer: + +```bash +composer require kariricode/transformer +``` + +### Requirements + +- PHP 8.3 or higher +- Composer +- Extensions: `ext-mbstring`, `ext-json` + +## Usage + +### Basic Usage + +1. Define your data class with transformation attributes: + +```php +use KaririCode\Transformer\Attribute\Transform; + +class DataFormatter +{ + #[Transform( + processors: ['date' => ['inputFormat' => 'd/m/Y', 'outputFormat' => 'Y-m-d']] + )] + private string $date = '25/12/2024'; + + #[Transform( + processors: ['number' => ['decimals' => 2, 'decimalPoint' => ',', 'thousandsSeparator' => '.']] + )] + private float $price = 1234.56; + + #[Transform( + processors: ['mask' => ['type' => 'phone']] + )] + private string $phone = '11999887766'; + + // Getters and setters... +} +``` + +2. Set up the transformer and use it: + +```php +use KaririCode\ProcessorPipeline\ProcessorRegistry; +use KaririCode\Transformer\Transformer; +use KaririCode\Transformer\Processor\Data\{DateTransformer, NumberTransformer}; +use KaririCode\Transformer\Processor\String\MaskTransformer; + +$registry = new ProcessorRegistry(); +$registry->register('transformer', 'date', new DateTransformer()); +$registry->register('transformer', 'number', new NumberTransformer()); +$registry->register('transformer', 'mask', new MaskTransformer()); + +$transformer = new Transformer($registry); + +$formatter = new DataFormatter(); +$result = $transformer->transform($formatter); + +if ($result->isValid()) { + echo "Date: " . $formatter->getDate() . "\n"; // Output: 2024-12-25 + echo "Price: " . $formatter->getPrice() . "\n"; // Output: 1.234,56 + echo "Phone: " . $formatter->getPhone() . "\n"; // Output: (11) 99988-7766 +} +``` + +### Advanced Usage: Data Formatting + +Here's an example of how to use the KaririCode Transformer in a real-world scenario, demonstrating various transformation capabilities: + +```php +use KaririCode\Transformer\Attribute\Transform; + +class ComplexDataTransformer +{ + #[Transform( + processors: ['case' => ['case' => 'snake']] + )] + private string $text = 'transformThisTextToSnakeCase'; + + #[Transform( + processors: ['slug' => []] + )] + private string $title = 'This is a Title for URL!'; + + #[Transform( + processors: ['arrayKey' => ['case' => 'camel']] + )] + private array $data = [ + 'user_name' => 'John Doe', + 'email_address' => 'john@example.com', + 'phone_number' => '1234567890' + ]; + + #[Transform( + processors: [ + 'template' => [ + 'template' => 'Hello {{name}}, your order #{{order_id}} is {{status}}', + 'removeUnmatchedTags' => true, + 'preserveData' => true + ] + ] + )] + private array $templateData = [ + 'name' => 'John', + 'order_id' => '12345', + 'status' => 'completed' + ]; + + // Getters and setters... +} +``` + +## Practical Examples + +### 1. String Transformation Examples + +```php +class StringTransformerExample +{ + #[Transform( + processors: ['case' => ['case' => 'snake']] + )] + private string $methodName = 'getUserProfileData'; + + #[Transform( + processors: ['case' => ['case' => 'camel']] + )] + private string $variableName = 'user_profile_data'; + + #[Transform( + processors: ['slug' => ['separator' => '-']] + )] + private string $articleTitle = 'How to Use PHP 8.3 Features!'; + + #[Transform( + processors: ['mask' => ['type' => 'phone']] + )] + private string $phoneNumber = '11999887766'; +} + +// Output: +// methodName: get_user_profile_data +// variableName: userProfileData +// articleTitle: how-to-use-php-8-3-features +// phoneNumber: (11) 99988-7766 +``` + +### 2. Number and Currency Formatting + +```php +class CurrencyTransformerExample +{ + #[Transform( + processors: ['number' => [ + 'decimals' => 2, + 'decimalPoint' => ',', + 'thousandsSeparator' => '.' + ]] + )] + private float $price = 1234567.89; + + #[Transform( + processors: ['number' => [ + 'decimals' => 0, + 'thousandsSeparator' => ',' + ]] + )] + private int $quantity = 1000000; +} + +// Output: +// price: 1.234.567,89 +// quantity: 1,000,000 +``` + +### 3. Date Transformation for Different Locales + +```php +class DateTransformerExample +{ + #[Transform( + processors: ['date' => [ + 'inputFormat' => 'd/m/Y', + 'outputFormat' => 'Y-m-d' + ]] + )] + private string $sqlDate = '25/12/2024'; + + #[Transform( + processors: ['date' => [ + 'inputFormat' => 'Y-m-d', + 'outputFormat' => 'F j, Y' + ]] + )] + private string $displayDate = '2024-12-25'; + + #[Transform( + processors: ['date' => [ + 'inputFormat' => 'Y-m-d H:i:s', + 'outputFormat' => 'd/m/Y H:i', + 'inputTimezone' => 'UTC', + 'outputTimezone' => 'America/Sao_Paulo' + ]] + )] + private string $timestamp = '2024-12-25 15:30:00'; +} + +// Output: +// sqlDate: 2024-12-25 +// displayDate: December 25, 2024 +// timestamp: 25/12/2024 12:30 +``` + +### 4. Array Transformation for API Response + +```php +class ApiResponseTransformerExample +{ + #[Transform( + processors: ['arrayKey' => ['case' => 'camel']] + )] + private array $userData = [ + 'user_id' => 123, + 'first_name' => 'John', + 'last_name' => 'Doe', + 'email_address' => 'john@example.com', + 'phone_numbers' => [ + 'home_phone' => '1234567890', + 'work_phone' => '0987654321' + ] + ]; + + #[Transform( + processors: ['arrayFlat' => ['separator' => '.']] + )] + private array $nestedConfig = [ + 'database' => [ + 'mysql' => [ + 'host' => 'localhost', + 'port' => 3306 + ] + ], + 'cache' => [ + 'redis' => [ + 'host' => '127.0.0.1', + 'port' => 6379 + ] + ] + ]; +} + +// Output: +// userData: +// { +// "userId": 123, +// "firstName": "John", +// "lastName": "Doe", +// "emailAddress": "john@example.com", +// "phoneNumbers": { +// "homePhone": "1234567890", +// "workPhone": "0987654321" +// } +// } +// +// nestedConfig: +// { +// "database.mysql.host": "localhost", +// "database.mysql.port": 3306, +// "cache.redis.host": "127.0.0.1", +// "cache.redis.port": 6379 +// } +``` + +### 5. Template Transformation for Notifications + +```php +class NotificationTransformerExample +{ + #[Transform( + processors: [ + 'template' => [ + 'template' => <<