Skip to content

[Release] KaririCode Transformer v1.0.0 🚀 #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Oct 26, 2024
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
Loading