Skip to content

Commit

Permalink
Merge pull request #1 from KaririCode-Framework/develop
Browse files Browse the repository at this point in the history
[Release] KaririCode Transformer v1.0.0 🚀
  • Loading branch information
walmir-silva authored Oct 26, 2024
2 parents a3e2bdb + f708f8f commit 2747ecb
Show file tree
Hide file tree
Showing 24 changed files with 2,917 additions and 92 deletions.
912 changes: 868 additions & 44 deletions README.md

Large diffs are not rendered by default.

593 changes: 545 additions & 48 deletions README.pt-br.md

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions src/Attribute/Transform.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace KaririCode\Transformer\Attribute;

use KaririCode\Contract\Processor\Attribute\BaseProcessorAttribute;

#[\Attribute(\Attribute::TARGET_PROPERTY)]
final class Transform extends BaseProcessorAttribute
{
}
16 changes: 16 additions & 0 deletions src/Contract/TransformationResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace KaririCode\Transformer\Contract;

interface TransformationResult
{
public function isValid(): bool;

public function getErrors(): array;

public function getTransformedData(): array;

public function toArray(): array;
}
58 changes: 58 additions & 0 deletions src/Exception/TransformerException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

namespace KaririCode\Transformer\Exception;

use KaririCode\Exception\AbstractException;

final class TransformerException extends AbstractException
{
private const CODE_INVALID_INPUT = 5001;
private const CODE_INVALID_FORMAT = 5002;
private const CODE_INVALID_TYPE = 5003;

public static function invalidInput(string $expectedType, string $actualType): self
{
$message = sprintf(
'Invalid input type. Expected %s, got %s.',
$expectedType,
$actualType
);

return self::createException(
self::CODE_INVALID_INPUT,
'INVALID_INPUT_TYPE',
$message
);
}

public static function invalidFormat(string $format, string $value): self
{
$message = sprintf(
'Invalid format. Expected format %s, got %s.',
$format,
$value
);

return self::createException(
self::CODE_INVALID_FORMAT,
'INVALID_FORMAT',
$message
);
}

public static function invalidType(string $expectedType): self
{
$message = sprintf(
'Invalid type. Expected %s.',
$expectedType
);

return self::createException(
self::CODE_INVALID_TYPE,
'INVALID_TYPE',
$message
);
}
}
47 changes: 47 additions & 0 deletions src/Processor/AbstractTransformerProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace KaririCode\Transformer\Processor;

use KaririCode\Contract\Processor\Processor;
use KaririCode\Contract\Processor\ValidatableProcessor;
use KaririCode\Transformer\Exception\TransformerException;

abstract class AbstractTransformerProcessor implements Processor, ValidatableProcessor
{
protected bool $isValid = true;
protected string $errorKey = '';

public function reset(): void
{
$this->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;
}
54 changes: 54 additions & 0 deletions src/Processor/Array/ArrayFlattenTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace KaririCode\Transformer\Processor\Array;

use KaririCode\Contract\Processor\ConfigurableProcessor;
use KaririCode\Transformer\Processor\AbstractTransformerProcessor;
use KaririCode\Transformer\Trait\ArrayTransformerTrait;

class ArrayFlattenTransformer extends AbstractTransformerProcessor implements ConfigurableProcessor
{
use ArrayTransformerTrait;

private int $depth = -1;
private string $separator = '.';

public function configure(array $options): void
{
$this->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;
}
}
62 changes: 62 additions & 0 deletions src/Processor/Array/ArrayGroupTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace KaririCode\Transformer\Processor\Array;

use KaririCode\Contract\Processor\ConfigurableProcessor;
use KaririCode\Transformer\Processor\AbstractTransformerProcessor;
use KaririCode\Transformer\Trait\ArrayTransformerTrait;

class ArrayGroupTransformer extends AbstractTransformerProcessor implements ConfigurableProcessor
{
use ArrayTransformerTrait;

private string $groupBy = '';
private bool $preserveKeys = false;

public function configure(array $options): void
{
if (!isset($options['groupBy'])) {
throw new \InvalidArgumentException('The groupBy option is required');
}

$this->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;
}
}
80 changes: 80 additions & 0 deletions src/Processor/Array/ArrayKeyTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

declare(strict_types=1);

namespace KaririCode\Transformer\Processor\Array;

use KaririCode\Contract\Processor\ConfigurableProcessor;
use KaririCode\Transformer\Processor\AbstractTransformerProcessor;
use KaririCode\Transformer\Trait\ArrayTransformerTrait;

class ArrayKeyTransformer extends AbstractTransformerProcessor implements ConfigurableProcessor
{
use ArrayTransformerTrait;

private const CASE_SNAKE = 'snake';
private const CASE_CAMEL = 'camel';
private const CASE_PASCAL = 'pascal';
private const CASE_KEBAB = 'kebab';

private string $case = self::CASE_SNAKE;
private bool $recursive = true;

public function configure(array $options): void
{
if (isset($options['case']) && in_array($options['case'], $this->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,
];
}
}
62 changes: 62 additions & 0 deletions src/Processor/Array/ArrayMapTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace KaririCode\Transformer\Processor\Array;

use KaririCode\Contract\Processor\ConfigurableProcessor;
use KaririCode\Transformer\Processor\AbstractTransformerProcessor;
use KaririCode\Transformer\Trait\ArrayTransformerTrait;

class ArrayMapTransformer extends AbstractTransformerProcessor implements ConfigurableProcessor
{
use ArrayTransformerTrait;

private array $mapping = [];
private bool $removeUnmapped = false;
private bool $recursive = true;

public function configure(array $options): void
{
if (!isset($options['mapping']) || !is_array($options['mapping'])) {
throw new \InvalidArgumentException('The mapping option is required and must be an array');
}

$this->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;
}
}
Loading

0 comments on commit 2747ecb

Please sign in to comment.